cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aadamc...@apache.org
Subject [11/17] cayenne git commit: CAY-2026 Java 7
Date Sat, 12 Sep 2015 10:02:39 GMT
http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java b/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
index c1eeee2..7c66056 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/IncrementalFaultList.java
@@ -58,781 +58,778 @@ import org.apache.cayenne.util.Util;
  */
 public class IncrementalFaultList<E> implements List<E>, Serializable {
 
-    protected int pageSize;
-    protected List elements;
-    protected DataContext dataContext;
-    protected ObjEntity rootEntity;
-    protected SelectQuery<?> internalQuery;
-    protected int unfetchedObjects;
-
-    /**
-     * Stores a hint allowing to distinguish data rows from unfetched ids when
-     * the query fetches data rows.
-     */
-    protected int idWidth;
-
-    private IncrementalListHelper helper;
-
-    /**
-     * Defines the upper limit on the size of fetches. This is needed to avoid
-     * where clause size limitations.
-     */
-    protected int maxFetchSize;
-
-    // Don't confuse this with the JDBC ResultSet fetch size setting - this
-    // controls
-    // the where clause generation that is necessary to fetch specific records a
-    // page
-    // at a time. Some JDBC Drivers/Databases may have limits on statement
-    // length
-    // or complexity of the where clause - e.g., PostgreSQL having a default
-    // limit of
-    // 10,000 nested expressions.
-
-    /**
-     * Creates a new IncrementalFaultList using a given DataContext and query.
-     * 
-     * @param dataContext
-     *            DataContext used by IncrementalFaultList to fill itself with
-     *            objects.
-     * @param query
-     *            Main query used to retrieve data. Must have "pageSize"
-     *            property set to a value greater than zero.
-     * @param maxFetchSize
-     *            maximum number of fetches in one query
-     */
-    public IncrementalFaultList(DataContext dataContext, Query query, int maxFetchSize) {
-        QueryMetadata metadata = query.getMetaData(dataContext.getEntityResolver());
-        if (metadata.getPageSize() <= 0) {
-            throw new CayenneRuntimeException("Not a paginated query; page size: " + metadata.getPageSize());
-        }
-
-        this.dataContext = dataContext;
-        this.pageSize = metadata.getPageSize();
-        this.rootEntity = metadata.getObjEntity();
-
-        if (rootEntity == null) {
-            throw new CayenneRuntimeException("Pagination is not supported for queries not rooted in an ObjEntity");
-        }
-
-        // create an internal query, it is a partial replica of
-        // the original query and will serve as a value holder for
-        // various parameters
-        this.internalQuery = new SelectQuery<Object>(rootEntity);
-        this.internalQuery.setFetchingDataRows(metadata.isFetchingDataRows());
-        this.internalQuery.setPrefetchTree(metadata.getPrefetchTree());
-
-        this.helper = createHelper(metadata);
-        this.idWidth = metadata.getDbEntity().getPrimaryKeys().size();
-
-        List<Object> elementsUnsynced = new ArrayList<Object>();
-        fillIn(query, elementsUnsynced);
-        this.elements = Collections.synchronizedList(elementsUnsynced);
-
-        this.maxFetchSize = maxFetchSize;
-    }
-
-    /**
-     * @since 3.0
-     */
-    IncrementalListHelper createHelper(QueryMetadata metadata) {
-        if (metadata.isFetchingDataRows()) {
-            return new DataRowListHelper();
-        } else {
-            return new PersistentListHelper();
-        }
-    }
-
-    /**
-     * @since 1.2
-     */
-    SelectQuery getInternalQuery() {
-        return internalQuery;
-    }
-
-    /**
-     * Performs initialization of the list of objects. Only the first page is
-     * fully resolved. For the rest of the list, only ObjectIds are read.
-     * 
-     * @since 3.0
-     */
-    protected void fillIn(final Query query, List elementsList) {
-
-        elementsList.clear();
-
-        ResultIterator it = dataContext.performIteratedQuery(query);
-        try {
-
-            while (it.hasNextRow()) {
-                elementsList.add(it.nextRow());
-            }
-        } finally {
-            it.close();
-        }
-
-        unfetchedObjects = elementsList.size();
-    }
-
-    /**
-     * Will resolve all unread objects.
-     */
-    public void resolveAll() {
-        resolveInterval(0, size());
-    }
-
-    /**
-     * Checks that an object is of the same type as the rest of objects
-     * (DataObject or DataRows depending on the query type).
-     */
-    private void validateListObject(Object object) throws IllegalArgumentException {
-
-        // I am not sure if such a check makes sense???
-
-        if (internalQuery.isFetchingDataRows()) {
-            if (!(object instanceof Map)) {
-                throw new IllegalArgumentException("Only Map objects can be stored in this list.");
-            }
-        } else {
-            if (!(object instanceof Persistent)) {
-                throw new IllegalArgumentException("Only DataObjects can be stored in this list.");
-            }
-        }
-    }
-
-    /**
-     * Resolves a sublist of objects starting at <code>fromIndex</code> up to
-     * but not including <code>toIndex</code>. Internally performs bound
-     * checking and trims indexes accordingly.
-     */
-    protected void resolveInterval(int fromIndex, int toIndex) {
-        if (fromIndex >= toIndex) {
-            return;
-        }
-
-        synchronized (elements) {
-            if (elements.size() == 0) {
-                return;
-            }
-
-            // perform bound checking
-            if (fromIndex < 0) {
-                fromIndex = 0;
-            }
-
-            if (toIndex > elements.size()) {
-                toIndex = elements.size();
-            }
-
-            List<Expression> quals = new ArrayList<Expression>(pageSize);
-            List<Object> ids = new ArrayList<Object>(pageSize);
-            for (int i = fromIndex; i < toIndex; i++) {
-                Object object = elements.get(i);
-                if (helper.unresolvedSuspect(object)) {
-                    quals.add(buildIdQualifier(object));
-                    ids.add(object);
-                }
-            }
-
-            int qualsSize = quals.size();
-            if (qualsSize == 0) {
-                return;
-            }
-
-            // fetch the range of objects in fetchSize chunks
-            boolean fetchesDataRows = internalQuery.isFetchingDataRows();
-            List<Object> objects = new ArrayList<Object>(qualsSize);
-
-            int fetchSize = maxFetchSize > 0 ? maxFetchSize : Integer.MAX_VALUE;
-
-            int fetchEnd = Math.min(qualsSize, fetchSize);
-            int fetchBegin = 0;
-            while (fetchBegin < qualsSize) {
-                SelectQuery<Object> query = new SelectQuery<Object>(rootEntity, ExpressionFactory.joinExp(
-                        Expression.OR, quals.subList(fetchBegin, fetchEnd)));
-
-                query.setFetchingDataRows(fetchesDataRows);
-
-                if (!query.isFetchingDataRows()) {
-                    query.setPrefetchTree(internalQuery.getPrefetchTree());
-                }
-
-                objects.addAll(dataContext.performQuery(query));
-                fetchBegin = fetchEnd;
-                fetchEnd += Math.min(fetchSize, qualsSize - fetchEnd);
-            }
-
-            // sanity check - database data may have changed
-            checkPageResultConsistency(objects, ids);
-
-            // replace ids in the list with objects
-            Iterator it = objects.iterator();
-            while (it.hasNext()) {
-                helper.updateWithResolvedObjectInRange(it.next(), fromIndex, toIndex);
-            }
-
-            unfetchedObjects -= objects.size();
-        }
-    }
-
-    /**
-     * Returns a qualifier expression for an unresolved id object.
-     * 
-     * @since 3.0
-     */
-    Expression buildIdQualifier(Object id) {
-
-        Map<String, ?> map = (Map<String, ?>) id;
-        if (map.isEmpty()) {
-            throw new CayenneRuntimeException("Empty id map");
-        }
-
-        return ExpressionFactory.matchAllDbExp(map, Expression.EQUAL_TO);
-    }
-
-    /**
-     * @since 3.0
-     */
-    void checkPageResultConsistency(List<?> objects, List<?> ids) {
-
-        if (objects.size() < ids.size()) {
-            // find missing ids
-            StringBuilder buffer = new StringBuilder();
-            buffer.append("Some ObjectIds are missing from the database. ");
-            buffer.append("Expected ").append(ids.size()).append(", fetched ").append(objects.size());
-
-            boolean first = true;
-            for (Object id : ids) {
-                boolean found = false;
-
-                for (Object object : objects) {
-
-                    if (helper.replacesObject(object, id)) {
-                        found = true;
-                        break;
-                    }
-                }
-
-                if (!found) {
-                    if (first) {
-                        first = false;
-                    } else {
-                        buffer.append(", ");
-                    }
-
-                    buffer.append(id.toString());
-                }
-            }
-
-            throw new CayenneRuntimeException(buffer.toString());
-        } else if (objects.size() > ids.size()) {
-            throw new CayenneRuntimeException("Expected " + ids.size() + " objects, retrieved " + objects.size());
-        }
-    }
-
-    /**
-     * Returns zero-based index of the virtual "page" for a given array element
-     * index.
-     */
-    public int pageIndex(int elementIndex) {
-        if (elementIndex < 0 || elementIndex > size()) {
-            throw new IndexOutOfBoundsException("Index: " + elementIndex);
-        }
-
-        if (pageSize <= 0 || elementIndex < 0) {
-            return -1;
-        }
-
-        return elementIndex / pageSize;
-    }
-
-    /**
-     * Get the upper bound on the number of records to resolve in one round trip
-     * to the database. This setting governs the size/complexity of the where
-     * clause generated to retrieve the next page of records. If the fetch size
-     * is less than the page size, then multiple fetches will be made to resolve
-     * a page.
-     */
-    public int getMaxFetchSize() {
-        return maxFetchSize;
-    }
-
-    public void setMaxFetchSize(int fetchSize) {
-        this.maxFetchSize = fetchSize;
-    }
-
-    /**
-     * Returns the dataContext.
-     * 
-     * @return DataContext
-     */
-    public DataContext getDataContext() {
-        return dataContext;
-    }
-
-    /**
-     * Returns the pageSize.
-     * 
-     * @return int
-     */
-    public int getPageSize() {
-        return pageSize;
-    }
-
-    /**
-     * Returns a list iterator for this list. DataObjects are resolved a page
-     * (according to getPageSize()) at a time as necessary - when retrieved with
-     * next() or previous().
-     */
-    public ListIterator<E> listIterator() {
-        return new IncrementalListIterator(0);
-    }
-
-    /**
-     * Returns a list iterator of the elements in this list (in proper
-     * sequence), starting at the specified position in this list. The specified
-     * index indicates the first element that would be returned by an initial
-     * call to the next method. An initial call to the previous method would
-     * return the element with the specified index minus one. DataObjects are
-     * resolved a page at a time (according to getPageSize()) as necessary -
-     * when retrieved with next() or previous().
-     */
-    public ListIterator<E> listIterator(int index) {
-        if (index < 0 || index > size()) {
-            throw new IndexOutOfBoundsException("Index: " + index);
-        }
-
-        return new IncrementalListIterator(index);
-    }
-
-    /**
-     * Return an iterator for this list. DataObjects are resolved a page
-     * (according to getPageSize()) at a time as necessary - when retrieved with
-     * next().
-     */
-    public Iterator<E> iterator() {
-        // by virtue of get(index)'s implementation, resolution of ids into
-        // objects will occur on pageSize boundaries as necessary.
-        return new Iterator<E>() {
-
-            int listIndex = 0;
-
-            public boolean hasNext() {
-                return (listIndex < elements.size());
-            }
-
-            public E next() {
-                if (listIndex >= elements.size())
-                    throw new NoSuchElementException("no more elements");
-
-                return get(listIndex++);
-            }
-
-            public void remove() {
-                throw new UnsupportedOperationException("remove not supported.");
-            }
-        };
-    }
-
-    /**
-     * @see java.util.List#add(int, Object)
-     */
-    public void add(int index, Object element) {
-        validateListObject(element);
-
-        synchronized (elements) {
-            elements.add(index, element);
-        }
-    }
-
-    /**
-     * @see java.util.Collection#add(Object)
-     */
-    public boolean add(Object o) {
-        validateListObject(o);
-
-        synchronized (elements) {
-            return elements.add(o);
-        }
-    }
-
-    /**
-     * @see java.util.Collection#addAll(Collection)
-     */
-    public boolean addAll(Collection<? extends E> c) {
-        synchronized (elements) {
-            return elements.addAll(c);
-        }
-    }
-
-    /**
-     * @see java.util.List#addAll(int, Collection)
-     */
-    public boolean addAll(int index, Collection<? extends E> c) {
-        synchronized (elements) {
-            return elements.addAll(index, c);
-        }
-    }
-
-    /**
-     * @see java.util.Collection#clear()
-     */
-    public void clear() {
-        synchronized (elements) {
-            elements.clear();
-        }
-    }
-
-    /**
-     * @see java.util.Collection#contains(Object)
-     */
-    public boolean contains(Object o) {
-        synchronized (elements) {
-            return elements.contains(o);
-        }
-    }
-
-    /**
-     * @see java.util.Collection#containsAll(Collection)
-     */
-    public boolean containsAll(Collection<?> c) {
-        synchronized (elements) {
-            return elements.containsAll(c);
-        }
-    }
-
-    public E get(int index) {
-        synchronized (elements) {
-            Object o = elements.get(index);
-
-            if (helper.unresolvedSuspect(o)) {
-                // read this page
-                int pageStart = pageIndex(index) * pageSize;
-                resolveInterval(pageStart, pageStart + pageSize);
-
-                return (E) elements.get(index);
-            } else {
-                return (E) o;
-            }
-        }
-    }
-
-    /**
-     * @see java.util.List#indexOf(Object)
-     */
-    public int indexOf(Object o) {
-        return helper.indexOfObject(o);
-    }
-
-    /**
-     * @see java.util.Collection#isEmpty()
-     */
-    public boolean isEmpty() {
-        synchronized (elements) {
-            return elements.isEmpty();
-        }
-    }
-
-    public int lastIndexOf(Object o) {
-        return helper.lastIndexOfObject(o);
-    }
-
-    public E remove(int index) {
-        synchronized (elements) {
-            // have to resolve the page to return correct object
-            E object = get(index);
-            elements.remove(index);
-            return object;
-        }
-    }
-
-    public boolean remove(Object o) {
-        synchronized (elements) {
-            return elements.remove(o);
-        }
-    }
-
-    public boolean removeAll(Collection<?> c) {
-        synchronized (elements) {
-            return elements.removeAll(c);
-        }
-    }
-
-    public boolean retainAll(Collection<?> c) {
-        synchronized (elements) {
-            return elements.retainAll(c);
-        }
-    }
-
-    /**
-     * @see java.util.List#set(int, Object)
-     */
-    public E set(int index, Object element) {
-        validateListObject(element);
-
-        synchronized (elements) {
-            return (E) elements.set(index, element);
-        }
-    }
-
-    /**
-     * @see java.util.Collection#size()
-     */
-    public int size() {
-        synchronized (elements) {
-            return elements.size();
-        }
-    }
-
-    public List<E> subList(int fromIndex, int toIndex) {
-        synchronized (elements) {
-            resolveInterval(fromIndex, toIndex);
-            return elements.subList(fromIndex, toIndex);
-        }
-    }
-
-    public Object[] toArray() {
-        resolveAll();
-
-        return elements.toArray();
-    }
-
-    public <T> T[] toArray(T[] a) {
-        resolveAll();
-
-        return (T[]) elements.toArray(a);
-    }
-
-    /**
-     * Returns a total number of objects that are not resolved yet.
-     */
-    public int getUnfetchedObjects() {
-        return unfetchedObjects;
-    }
-
-    abstract class IncrementalListHelper implements Serializable {
-
-        int indexOfObject(Object object) {
-            if (unresolvedSuspect(object)) {
-                return -1;
-            }
-
-            synchronized (elements) {
-                for (int i = 0; i < elements.size(); i++) {
-                    if (objectsAreEqual(object, elements.get(i))) {
-                        return i;
-                    }
-                }
-            }
-            return -1;
-        }
-
-        int lastIndexOfObject(Object object) {
-            if (unresolvedSuspect(object)) {
-                return -1;
-            }
-
-            synchronized (elements) {
-                for (int i = elements.size() - 1; i >= 0; i--) {
-                    if (objectsAreEqual(object, elements.get(i))) {
-                        return i;
-                    }
-                }
-            }
-
-            return -1;
-        }
-
-        void updateWithResolvedObjectInRange(Object object, int from, int to) {
-            boolean found = false;
-
-            synchronized (elements) {
-
-                for (int i = from; i < to; i++) {
-                    if (replacesObject(object, elements.get(i))) {
-                        elements.set(i, object);
-                        found = true;
-                        break;
-                    }
-                }
-            }
-
-            if (!found) {
-                throw new CayenneRuntimeException("Can't find id for " + object);
-            }
-        }
-
-        /**
-         * Returns true if an object is not the type of object expected in the
-         * list. This method is not expected to perform thorough checking of the
-         * object type. What's important is the guarantee that an unresolved
-         * object representation will always return true for this method, and
-         * resolved will return false. Other types of objects that users may
-         * choose to add to the list will not be analyzed in detail.
-         */
-        abstract boolean unresolvedSuspect(Object object);
-
-        abstract boolean objectsAreEqual(Object object, Object objectInTheList);
-
-        abstract boolean replacesObject(Object object, Object objectInTheList);
-    }
-
-    class PersistentListHelper extends IncrementalListHelper {
-
-        @Override
-        boolean unresolvedSuspect(Object object) {
-            if (!(object instanceof Persistent)) {
-                return true;
-            }
-
-            // don't do a full check for object type matching the type of
-            // objects in the
-            // list... what's important is a quick "false" return if the object
-            // is of type
-            // representing unresolved objects.. furthermore, if inheritance is
-            // involved,
-            // we'll need an even more extensive check (see CAY-1142 on
-            // inheritance
-            // issues).
-
-            return false;
-        }
-
-        @Override
-        boolean objectsAreEqual(Object object, Object objectInTheList) {
-
-            if (objectInTheList instanceof Persistent) {
-                // due to object uniquing this should be sufficient
-                return object == objectInTheList;
-            } else {
-                return ((Persistent) object).getObjectId().getIdSnapshot().equals(objectInTheList);
-            }
-        }
-
-        @Override
-        boolean replacesObject(Object object, Object objectInTheList) {
-            if (objectInTheList instanceof Persistent) {
-                return false;
-            }
-
-            Persistent dataObject = (Persistent) object;
-            return dataObject.getObjectId().getIdSnapshot().equals(objectInTheList);
-        }
-    }
-
-    class DataRowListHelper extends IncrementalListHelper {
-
-        @Override
-        boolean unresolvedSuspect(Object object) {
-            if (!(object instanceof Map)) {
-                return true;
-            }
-
-            return false;
-        }
-
-        @Override
-        boolean objectsAreEqual(Object object, Object objectInTheList) {
-            if (object == null && objectInTheList == null) {
-                return true;
-            }
-
-            if (object != null && objectInTheList != null) {
-
-                Map<?, ?> id = (Map<?, ?>) objectInTheList;
-                Map<?, ?> map = (Map<?, ?>) object;
-
-                if (id.size() != map.size()) {
-                    return false;
-                }
-
-                // id must be a subset of this map
-                for (Map.Entry<?, ?> entry : id.entrySet()) {
-                    Object key = entry.getKey();
-                    Object value = entry.getValue();
-                    if (!Util.nullSafeEquals(value, map.get(key))) {
-                        return false;
-                    }
-                }
-
-                return true;
-            }
-
-            return false;
-        }
-
-        @Override
-        boolean replacesObject(Object object, Object objectInTheList) {
-
-            Map<?, ?> id = (Map<?, ?>) objectInTheList;
-            if (id.size() > idWidth) {
-                return false;
-            }
-
-            // id must be a subset of this map
-            Map<?, ?> map = (Map<?, ?>) object;
-            for (Map.Entry<?, ?> entry : id.entrySet()) {
-                Object key = entry.getKey();
-                Object value = entry.getValue();
-                if (!Util.nullSafeEquals(value, map.get(key))) {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-    }
-
-    class IncrementalListIterator implements ListIterator<E> {
-
-        // by virtue of get(index)'s implementation, resolution of ids into
-        // objects will occur on pageSize boundaries as necessary.
-
-        int listIndex;
-
-        public IncrementalListIterator(int startIndex) {
-            this.listIndex = startIndex;
-        }
-
-        public void add(Object o) {
-            throw new UnsupportedOperationException("add operation not supported");
-        }
-
-        public boolean hasNext() {
-            return (listIndex < elements.size());
-        }
-
-        public boolean hasPrevious() {
-            return (listIndex > 0);
-        }
-
-        public E next() {
-            if (listIndex >= elements.size())
-                throw new NoSuchElementException("at the end of the list");
-
-            return get(listIndex++);
-        }
-
-        public int nextIndex() {
-            return listIndex;
-        }
-
-        public E previous() {
-            if (listIndex < 1)
-                throw new NoSuchElementException("at the beginning of the list");
-
-            return get(--listIndex);
-        }
-
-        public int previousIndex() {
-            return (listIndex - 1);
-        }
+	protected int pageSize;
+	protected List elements;
+	protected DataContext dataContext;
+	protected ObjEntity rootEntity;
+	protected SelectQuery<?> internalQuery;
+	protected int unfetchedObjects;
+
+	/**
+	 * Stores a hint allowing to distinguish data rows from unfetched ids when
+	 * the query fetches data rows.
+	 */
+	protected int idWidth;
+
+	private IncrementalListHelper helper;
+
+	/**
+	 * Defines the upper limit on the size of fetches. This is needed to avoid
+	 * where clause size limitations.
+	 */
+	protected int maxFetchSize;
+
+	// Don't confuse this with the JDBC ResultSet fetch size setting - this
+	// controls
+	// the where clause generation that is necessary to fetch specific records a
+	// page
+	// at a time. Some JDBC Drivers/Databases may have limits on statement
+	// length
+	// or complexity of the where clause - e.g., PostgreSQL having a default
+	// limit of
+	// 10,000 nested expressions.
+
+	/**
+	 * Creates a new IncrementalFaultList using a given DataContext and query.
+	 * 
+	 * @param dataContext
+	 *            DataContext used by IncrementalFaultList to fill itself with
+	 *            objects.
+	 * @param query
+	 *            Main query used to retrieve data. Must have "pageSize"
+	 *            property set to a value greater than zero.
+	 * @param maxFetchSize
+	 *            maximum number of fetches in one query
+	 */
+	public IncrementalFaultList(DataContext dataContext, Query query, int maxFetchSize) {
+		QueryMetadata metadata = query.getMetaData(dataContext.getEntityResolver());
+		if (metadata.getPageSize() <= 0) {
+			throw new CayenneRuntimeException("Not a paginated query; page size: " + metadata.getPageSize());
+		}
+
+		this.dataContext = dataContext;
+		this.pageSize = metadata.getPageSize();
+		this.rootEntity = metadata.getObjEntity();
+
+		if (rootEntity == null) {
+			throw new CayenneRuntimeException("Pagination is not supported for queries not rooted in an ObjEntity");
+		}
+
+		// create an internal query, it is a partial replica of
+		// the original query and will serve as a value holder for
+		// various parameters
+		this.internalQuery = new SelectQuery<Object>(rootEntity);
+		this.internalQuery.setFetchingDataRows(metadata.isFetchingDataRows());
+		this.internalQuery.setPrefetchTree(metadata.getPrefetchTree());
+
+		this.helper = createHelper(metadata);
+		this.idWidth = metadata.getDbEntity().getPrimaryKeys().size();
+
+		List<Object> elementsUnsynced = new ArrayList<Object>();
+		fillIn(query, elementsUnsynced);
+		this.elements = Collections.synchronizedList(elementsUnsynced);
+
+		this.maxFetchSize = maxFetchSize;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	IncrementalListHelper createHelper(QueryMetadata metadata) {
+		if (metadata.isFetchingDataRows()) {
+			return new DataRowListHelper();
+		} else {
+			return new PersistentListHelper();
+		}
+	}
+
+	/**
+	 * @since 1.2
+	 */
+	SelectQuery getInternalQuery() {
+		return internalQuery;
+	}
+
+	/**
+	 * Performs initialization of the list of objects. Only the first page is
+	 * fully resolved. For the rest of the list, only ObjectIds are read.
+	 * 
+	 * @since 3.0
+	 */
+	protected void fillIn(final Query query, List elementsList) {
+
+		elementsList.clear();
+
+		try (ResultIterator it = dataContext.performIteratedQuery(query);) {
+
+			while (it.hasNextRow()) {
+				elementsList.add(it.nextRow());
+			}
+		}
+
+		unfetchedObjects = elementsList.size();
+	}
+
+	/**
+	 * Will resolve all unread objects.
+	 */
+	public void resolveAll() {
+		resolveInterval(0, size());
+	}
+
+	/**
+	 * Checks that an object is of the same type as the rest of objects
+	 * (DataObject or DataRows depending on the query type).
+	 */
+	private void validateListObject(Object object) throws IllegalArgumentException {
+
+		// I am not sure if such a check makes sense???
+
+		if (internalQuery.isFetchingDataRows()) {
+			if (!(object instanceof Map)) {
+				throw new IllegalArgumentException("Only Map objects can be stored in this list.");
+			}
+		} else {
+			if (!(object instanceof Persistent)) {
+				throw new IllegalArgumentException("Only DataObjects can be stored in this list.");
+			}
+		}
+	}
+
+	/**
+	 * Resolves a sublist of objects starting at <code>fromIndex</code> up to
+	 * but not including <code>toIndex</code>. Internally performs bound
+	 * checking and trims indexes accordingly.
+	 */
+	protected void resolveInterval(int fromIndex, int toIndex) {
+		if (fromIndex >= toIndex) {
+			return;
+		}
+
+		synchronized (elements) {
+			if (elements.size() == 0) {
+				return;
+			}
+
+			// perform bound checking
+			if (fromIndex < 0) {
+				fromIndex = 0;
+			}
+
+			if (toIndex > elements.size()) {
+				toIndex = elements.size();
+			}
+
+			List<Expression> quals = new ArrayList<Expression>(pageSize);
+			List<Object> ids = new ArrayList<Object>(pageSize);
+			for (int i = fromIndex; i < toIndex; i++) {
+				Object object = elements.get(i);
+				if (helper.unresolvedSuspect(object)) {
+					quals.add(buildIdQualifier(object));
+					ids.add(object);
+				}
+			}
+
+			int qualsSize = quals.size();
+			if (qualsSize == 0) {
+				return;
+			}
+
+			// fetch the range of objects in fetchSize chunks
+			boolean fetchesDataRows = internalQuery.isFetchingDataRows();
+			List<Object> objects = new ArrayList<Object>(qualsSize);
+
+			int fetchSize = maxFetchSize > 0 ? maxFetchSize : Integer.MAX_VALUE;
+
+			int fetchEnd = Math.min(qualsSize, fetchSize);
+			int fetchBegin = 0;
+			while (fetchBegin < qualsSize) {
+				SelectQuery<Object> query = new SelectQuery<Object>(rootEntity, ExpressionFactory.joinExp(
+						Expression.OR, quals.subList(fetchBegin, fetchEnd)));
+
+				query.setFetchingDataRows(fetchesDataRows);
+
+				if (!query.isFetchingDataRows()) {
+					query.setPrefetchTree(internalQuery.getPrefetchTree());
+				}
+
+				objects.addAll(dataContext.performQuery(query));
+				fetchBegin = fetchEnd;
+				fetchEnd += Math.min(fetchSize, qualsSize - fetchEnd);
+			}
+
+			// sanity check - database data may have changed
+			checkPageResultConsistency(objects, ids);
+
+			// replace ids in the list with objects
+			Iterator it = objects.iterator();
+			while (it.hasNext()) {
+				helper.updateWithResolvedObjectInRange(it.next(), fromIndex, toIndex);
+			}
+
+			unfetchedObjects -= objects.size();
+		}
+	}
+
+	/**
+	 * Returns a qualifier expression for an unresolved id object.
+	 * 
+	 * @since 3.0
+	 */
+	Expression buildIdQualifier(Object id) {
+
+		Map<String, ?> map = (Map<String, ?>) id;
+		if (map.isEmpty()) {
+			throw new CayenneRuntimeException("Empty id map");
+		}
+
+		return ExpressionFactory.matchAllDbExp(map, Expression.EQUAL_TO);
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	void checkPageResultConsistency(List<?> objects, List<?> ids) {
+
+		if (objects.size() < ids.size()) {
+			// find missing ids
+			StringBuilder buffer = new StringBuilder();
+			buffer.append("Some ObjectIds are missing from the database. ");
+			buffer.append("Expected ").append(ids.size()).append(", fetched ").append(objects.size());
+
+			boolean first = true;
+			for (Object id : ids) {
+				boolean found = false;
+
+				for (Object object : objects) {
+
+					if (helper.replacesObject(object, id)) {
+						found = true;
+						break;
+					}
+				}
+
+				if (!found) {
+					if (first) {
+						first = false;
+					} else {
+						buffer.append(", ");
+					}
+
+					buffer.append(id.toString());
+				}
+			}
+
+			throw new CayenneRuntimeException(buffer.toString());
+		} else if (objects.size() > ids.size()) {
+			throw new CayenneRuntimeException("Expected " + ids.size() + " objects, retrieved " + objects.size());
+		}
+	}
+
+	/**
+	 * Returns zero-based index of the virtual "page" for a given array element
+	 * index.
+	 */
+	public int pageIndex(int elementIndex) {
+		if (elementIndex < 0 || elementIndex > size()) {
+			throw new IndexOutOfBoundsException("Index: " + elementIndex);
+		}
+
+		if (pageSize <= 0 || elementIndex < 0) {
+			return -1;
+		}
+
+		return elementIndex / pageSize;
+	}
+
+	/**
+	 * Get the upper bound on the number of records to resolve in one round trip
+	 * to the database. This setting governs the size/complexity of the where
+	 * clause generated to retrieve the next page of records. If the fetch size
+	 * is less than the page size, then multiple fetches will be made to resolve
+	 * a page.
+	 */
+	public int getMaxFetchSize() {
+		return maxFetchSize;
+	}
+
+	public void setMaxFetchSize(int fetchSize) {
+		this.maxFetchSize = fetchSize;
+	}
+
+	/**
+	 * Returns the dataContext.
+	 * 
+	 * @return DataContext
+	 */
+	public DataContext getDataContext() {
+		return dataContext;
+	}
+
+	/**
+	 * Returns the pageSize.
+	 * 
+	 * @return int
+	 */
+	public int getPageSize() {
+		return pageSize;
+	}
+
+	/**
+	 * Returns a list iterator for this list. DataObjects are resolved a page
+	 * (according to getPageSize()) at a time as necessary - when retrieved with
+	 * next() or previous().
+	 */
+	public ListIterator<E> listIterator() {
+		return new IncrementalListIterator(0);
+	}
+
+	/**
+	 * Returns a list iterator of the elements in this list (in proper
+	 * sequence), starting at the specified position in this list. The specified
+	 * index indicates the first element that would be returned by an initial
+	 * call to the next method. An initial call to the previous method would
+	 * return the element with the specified index minus one. DataObjects are
+	 * resolved a page at a time (according to getPageSize()) as necessary -
+	 * when retrieved with next() or previous().
+	 */
+	public ListIterator<E> listIterator(int index) {
+		if (index < 0 || index > size()) {
+			throw new IndexOutOfBoundsException("Index: " + index);
+		}
+
+		return new IncrementalListIterator(index);
+	}
+
+	/**
+	 * Return an iterator for this list. DataObjects are resolved a page
+	 * (according to getPageSize()) at a time as necessary - when retrieved with
+	 * next().
+	 */
+	public Iterator<E> iterator() {
+		// by virtue of get(index)'s implementation, resolution of ids into
+		// objects will occur on pageSize boundaries as necessary.
+		return new Iterator<E>() {
+
+			int listIndex = 0;
+
+			public boolean hasNext() {
+				return (listIndex < elements.size());
+			}
+
+			public E next() {
+				if (listIndex >= elements.size())
+					throw new NoSuchElementException("no more elements");
+
+				return get(listIndex++);
+			}
+
+			public void remove() {
+				throw new UnsupportedOperationException("remove not supported.");
+			}
+		};
+	}
+
+	/**
+	 * @see java.util.List#add(int, Object)
+	 */
+	public void add(int index, Object element) {
+		validateListObject(element);
+
+		synchronized (elements) {
+			elements.add(index, element);
+		}
+	}
+
+	/**
+	 * @see java.util.Collection#add(Object)
+	 */
+	public boolean add(Object o) {
+		validateListObject(o);
+
+		synchronized (elements) {
+			return elements.add(o);
+		}
+	}
+
+	/**
+	 * @see java.util.Collection#addAll(Collection)
+	 */
+	public boolean addAll(Collection<? extends E> c) {
+		synchronized (elements) {
+			return elements.addAll(c);
+		}
+	}
+
+	/**
+	 * @see java.util.List#addAll(int, Collection)
+	 */
+	public boolean addAll(int index, Collection<? extends E> c) {
+		synchronized (elements) {
+			return elements.addAll(index, c);
+		}
+	}
+
+	/**
+	 * @see java.util.Collection#clear()
+	 */
+	public void clear() {
+		synchronized (elements) {
+			elements.clear();
+		}
+	}
+
+	/**
+	 * @see java.util.Collection#contains(Object)
+	 */
+	public boolean contains(Object o) {
+		synchronized (elements) {
+			return elements.contains(o);
+		}
+	}
+
+	/**
+	 * @see java.util.Collection#containsAll(Collection)
+	 */
+	public boolean containsAll(Collection<?> c) {
+		synchronized (elements) {
+			return elements.containsAll(c);
+		}
+	}
+
+	public E get(int index) {
+		synchronized (elements) {
+			Object o = elements.get(index);
+
+			if (helper.unresolvedSuspect(o)) {
+				// read this page
+				int pageStart = pageIndex(index) * pageSize;
+				resolveInterval(pageStart, pageStart + pageSize);
+
+				return (E) elements.get(index);
+			} else {
+				return (E) o;
+			}
+		}
+	}
+
+	/**
+	 * @see java.util.List#indexOf(Object)
+	 */
+	public int indexOf(Object o) {
+		return helper.indexOfObject(o);
+	}
+
+	/**
+	 * @see java.util.Collection#isEmpty()
+	 */
+	public boolean isEmpty() {
+		synchronized (elements) {
+			return elements.isEmpty();
+		}
+	}
+
+	public int lastIndexOf(Object o) {
+		return helper.lastIndexOfObject(o);
+	}
+
+	public E remove(int index) {
+		synchronized (elements) {
+			// have to resolve the page to return correct object
+			E object = get(index);
+			elements.remove(index);
+			return object;
+		}
+	}
+
+	public boolean remove(Object o) {
+		synchronized (elements) {
+			return elements.remove(o);
+		}
+	}
+
+	public boolean removeAll(Collection<?> c) {
+		synchronized (elements) {
+			return elements.removeAll(c);
+		}
+	}
+
+	public boolean retainAll(Collection<?> c) {
+		synchronized (elements) {
+			return elements.retainAll(c);
+		}
+	}
+
+	/**
+	 * @see java.util.List#set(int, Object)
+	 */
+	public E set(int index, Object element) {
+		validateListObject(element);
+
+		synchronized (elements) {
+			return (E) elements.set(index, element);
+		}
+	}
+
+	/**
+	 * @see java.util.Collection#size()
+	 */
+	public int size() {
+		synchronized (elements) {
+			return elements.size();
+		}
+	}
+
+	public List<E> subList(int fromIndex, int toIndex) {
+		synchronized (elements) {
+			resolveInterval(fromIndex, toIndex);
+			return elements.subList(fromIndex, toIndex);
+		}
+	}
+
+	public Object[] toArray() {
+		resolveAll();
+
+		return elements.toArray();
+	}
+
+	public <T> T[] toArray(T[] a) {
+		resolveAll();
+
+		return (T[]) elements.toArray(a);
+	}
+
+	/**
+	 * Returns a total number of objects that are not resolved yet.
+	 */
+	public int getUnfetchedObjects() {
+		return unfetchedObjects;
+	}
+
+	abstract class IncrementalListHelper implements Serializable {
+
+		int indexOfObject(Object object) {
+			if (unresolvedSuspect(object)) {
+				return -1;
+			}
+
+			synchronized (elements) {
+				for (int i = 0; i < elements.size(); i++) {
+					if (objectsAreEqual(object, elements.get(i))) {
+						return i;
+					}
+				}
+			}
+			return -1;
+		}
+
+		int lastIndexOfObject(Object object) {
+			if (unresolvedSuspect(object)) {
+				return -1;
+			}
+
+			synchronized (elements) {
+				for (int i = elements.size() - 1; i >= 0; i--) {
+					if (objectsAreEqual(object, elements.get(i))) {
+						return i;
+					}
+				}
+			}
+
+			return -1;
+		}
+
+		void updateWithResolvedObjectInRange(Object object, int from, int to) {
+			boolean found = false;
+
+			synchronized (elements) {
+
+				for (int i = from; i < to; i++) {
+					if (replacesObject(object, elements.get(i))) {
+						elements.set(i, object);
+						found = true;
+						break;
+					}
+				}
+			}
+
+			if (!found) {
+				throw new CayenneRuntimeException("Can't find id for " + object);
+			}
+		}
+
+		/**
+		 * Returns true if an object is not the type of object expected in the
+		 * list. This method is not expected to perform thorough checking of the
+		 * object type. What's important is the guarantee that an unresolved
+		 * object representation will always return true for this method, and
+		 * resolved will return false. Other types of objects that users may
+		 * choose to add to the list will not be analyzed in detail.
+		 */
+		abstract boolean unresolvedSuspect(Object object);
+
+		abstract boolean objectsAreEqual(Object object, Object objectInTheList);
+
+		abstract boolean replacesObject(Object object, Object objectInTheList);
+	}
+
+	class PersistentListHelper extends IncrementalListHelper {
+
+		@Override
+		boolean unresolvedSuspect(Object object) {
+			if (!(object instanceof Persistent)) {
+				return true;
+			}
+
+			// don't do a full check for object type matching the type of
+			// objects in the
+			// list... what's important is a quick "false" return if the object
+			// is of type
+			// representing unresolved objects.. furthermore, if inheritance is
+			// involved,
+			// we'll need an even more extensive check (see CAY-1142 on
+			// inheritance
+			// issues).
+
+			return false;
+		}
+
+		@Override
+		boolean objectsAreEqual(Object object, Object objectInTheList) {
+
+			if (objectInTheList instanceof Persistent) {
+				// due to object uniquing this should be sufficient
+				return object == objectInTheList;
+			} else {
+				return ((Persistent) object).getObjectId().getIdSnapshot().equals(objectInTheList);
+			}
+		}
+
+		@Override
+		boolean replacesObject(Object object, Object objectInTheList) {
+			if (objectInTheList instanceof Persistent) {
+				return false;
+			}
+
+			Persistent dataObject = (Persistent) object;
+			return dataObject.getObjectId().getIdSnapshot().equals(objectInTheList);
+		}
+	}
+
+	class DataRowListHelper extends IncrementalListHelper {
+
+		@Override
+		boolean unresolvedSuspect(Object object) {
+			if (!(object instanceof Map)) {
+				return true;
+			}
+
+			return false;
+		}
+
+		@Override
+		boolean objectsAreEqual(Object object, Object objectInTheList) {
+			if (object == null && objectInTheList == null) {
+				return true;
+			}
+
+			if (object != null && objectInTheList != null) {
+
+				Map<?, ?> id = (Map<?, ?>) objectInTheList;
+				Map<?, ?> map = (Map<?, ?>) object;
+
+				if (id.size() != map.size()) {
+					return false;
+				}
+
+				// id must be a subset of this map
+				for (Map.Entry<?, ?> entry : id.entrySet()) {
+					Object key = entry.getKey();
+					Object value = entry.getValue();
+					if (!Util.nullSafeEquals(value, map.get(key))) {
+						return false;
+					}
+				}
+
+				return true;
+			}
+
+			return false;
+		}
+
+		@Override
+		boolean replacesObject(Object object, Object objectInTheList) {
+
+			Map<?, ?> id = (Map<?, ?>) objectInTheList;
+			if (id.size() > idWidth) {
+				return false;
+			}
+
+			// id must be a subset of this map
+			Map<?, ?> map = (Map<?, ?>) object;
+			for (Map.Entry<?, ?> entry : id.entrySet()) {
+				Object key = entry.getKey();
+				Object value = entry.getValue();
+				if (!Util.nullSafeEquals(value, map.get(key))) {
+					return false;
+				}
+			}
+
+			return true;
+		}
+	}
+
+	class IncrementalListIterator implements ListIterator<E> {
+
+		// by virtue of get(index)'s implementation, resolution of ids into
+		// objects will occur on pageSize boundaries as necessary.
+
+		int listIndex;
+
+		public IncrementalListIterator(int startIndex) {
+			this.listIndex = startIndex;
+		}
+
+		public void add(Object o) {
+			throw new UnsupportedOperationException("add operation not supported");
+		}
+
+		public boolean hasNext() {
+			return (listIndex < elements.size());
+		}
+
+		public boolean hasPrevious() {
+			return (listIndex > 0);
+		}
+
+		public E next() {
+			if (listIndex >= elements.size())
+				throw new NoSuchElementException("at the end of the list");
+
+			return get(listIndex++);
+		}
+
+		public int nextIndex() {
+			return listIndex;
+		}
+
+		public E previous() {
+			if (listIndex < 1)
+				throw new NoSuchElementException("at the beginning of the list");
+
+			return get(--listIndex);
+		}
+
+		public int previousIndex() {
+			return (listIndex - 1);
+		}
 
-        public void remove() {
-            throw new UnsupportedOperationException("remove operation not supported");
-        }
-
-        public void set(Object o) {
-            IncrementalFaultList.this.set(listIndex - 1, o);
-        }
-    }
+		public void remove() {
+			throw new UnsupportedOperationException("remove operation not supported");
+		}
+
+		public void set(Object o) {
+			IncrementalFaultList.this.set(listIndex - 1, o);
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java
index 15f389f..3a7675f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/CreateIfNoSchemaStrategy.java
@@ -39,84 +39,68 @@ import org.apache.commons.logging.LogFactory;
  */
 public class CreateIfNoSchemaStrategy extends BaseSchemaUpdateStrategy {
 
-    final static Log logger = LogFactory.getLog(CreateIfNoSchemaStrategy.class);
+	final static Log logger = LogFactory.getLog(CreateIfNoSchemaStrategy.class);
 
-    @Override
-    protected void processSchemaUpdate(DataNode dataNode) throws SQLException {
+	@Override
+	protected void processSchemaUpdate(DataNode dataNode) throws SQLException {
 
-        Map<String, Boolean> nameTables = getNameTablesInDB(dataNode);
-        Collection<DbEntity> entities = dataNode.getEntityResolver().getDbEntities();
-        boolean generate = true;
-        Iterator<DbEntity> it = entities.iterator();
-        while (it.hasNext()) {
-            if (nameTables.get(it.next().getName()) != null) {
-                generate = false;
-                break;
-            }
-        }
+		Map<String, Boolean> nameTables = getNameTablesInDB(dataNode);
+		Collection<DbEntity> entities = dataNode.getEntityResolver().getDbEntities();
+		boolean generate = true;
+		Iterator<DbEntity> it = entities.iterator();
+		while (it.hasNext()) {
+			if (nameTables.get(it.next().getName()) != null) {
+				generate = false;
+				break;
+			}
+		}
 
-        if (generate) {
-            logger.info("No schema detected, will create mapped tables");
-            generate(dataNode);
-        }
-        else {
-            logger.info("Full or partial schema detected, skipping tables creation");
-        }
-    }
+		if (generate) {
+			logger.info("No schema detected, will create mapped tables");
+			generate(dataNode);
+		} else {
+			logger.info("Full or partial schema detected, skipping tables creation");
+		}
+	}
 
-    private void generate(DataNode dataNode) {
-        Collection<DataMap> map = dataNode.getDataMaps();
-        Iterator<DataMap> iterator = map.iterator();
-        while (iterator.hasNext()) {
-            DbGenerator gen = new DbGenerator(dataNode.getAdapter(), iterator.next(), 
-                    dataNode.getJdbcEventLogger());
-            gen.setShouldCreateTables(true);
-            gen.setShouldDropTables(false);
-            gen.setShouldCreateFKConstraints(true);
-            gen.setShouldCreatePKSupport(true);
-            gen.setShouldDropPKSupport(false);
-            try {
-                gen.runGenerator(dataNode.getDataSource());
-            }
-            catch (Exception e) {
-                throw new CayenneRuntimeException(e);
-            }
-        }
-    }
+	private void generate(DataNode dataNode) {
+		Collection<DataMap> map = dataNode.getDataMaps();
+		Iterator<DataMap> iterator = map.iterator();
+		while (iterator.hasNext()) {
+			DbGenerator gen = new DbGenerator(dataNode.getAdapter(), iterator.next(), dataNode.getJdbcEventLogger());
+			gen.setShouldCreateTables(true);
+			gen.setShouldDropTables(false);
+			gen.setShouldCreateFKConstraints(true);
+			gen.setShouldCreatePKSupport(true);
+			gen.setShouldDropPKSupport(false);
+			try {
+				gen.runGenerator(dataNode.getDataSource());
+			} catch (Exception e) {
+				throw new CayenneRuntimeException(e);
+			}
+		}
+	}
 
-    /**
-     * Returns all the table names in database.
-     * 
-     * @throws SQLException
-     */
-    protected Map<String, Boolean> getNameTablesInDB(DataNode dataNode)
-            throws SQLException {
-        String tableLabel = dataNode.getAdapter().tableTypeForTable();
-        Connection con = null;
-        Map<String, Boolean> nameTables = new HashMap<String, Boolean>();
-        con = dataNode.getDataSource().getConnection();
+	/**
+	 * Returns all the table names in database.
+	 * 
+	 * @throws SQLException
+	 */
+	protected Map<String, Boolean> getNameTablesInDB(DataNode dataNode) throws SQLException {
+		String tableLabel = dataNode.getAdapter().tableTypeForTable();
+		Map<String, Boolean> nameTables = new HashMap<String, Boolean>();
 
-        try {
-            ResultSet rs = con.getMetaData().getTables(null, null, "%", new String[] {
-                tableLabel
-            });
+		try (Connection con = dataNode.getDataSource().getConnection();) {
 
-            try {
+			try (ResultSet rs = con.getMetaData().getTables(null, null, "%", new String[] { tableLabel });) {
 
-                while (rs.next()) {
-                    String name = rs.getString("TABLE_NAME");
-                    nameTables.put(name, false);
-                }
-            }
-            finally {
-                rs.close();
-            }
+				while (rs.next()) {
+					String name = rs.getString("TABLE_NAME");
+					nameTables.put(name, false);
+				}
+			}
+		}
 
-        }
-        finally {
-
-            con.close();
-        }
-        return nameTables;
-    }
+		return nameTables;
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaAnalyzer.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaAnalyzer.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaAnalyzer.java
index 86e9110..86f29c8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaAnalyzer.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/SchemaAnalyzer.java
@@ -36,189 +36,171 @@ import org.apache.cayenne.map.DbEntity;
  */
 class SchemaAnalyzer {
 
-    private Map<String, String> mapTableInDB;
-    private List<String> tableNoInDB;
-    private Map<String, Collection<String>> nameSchemaMap;
-    private Map<String, Collection<String>> schemaNameMap;
-    private Map<Map<String, String>, Collection<DbAttribute>> entityTables;
-    private String errorMessage;
-
-    SchemaAnalyzer() {
-        errorMessage = null;
-        mapTableInDB = new HashMap<String, String>();
-        tableNoInDB = new ArrayList<String>();
-        nameSchemaMap = new HashMap<String, Collection<String>>();
-        schemaNameMap = new HashMap<String, Collection<String>>();
-        entityTables = new HashMap<Map<String, String>, Collection<DbAttribute>>();
-    }
-
-    public List<String> getTableNoInDB() {
-        return tableNoInDB;
-    }
-
-    public void compareColumns(DatabaseMetaData md) throws SQLException {
-
-        for (Map.Entry<String, String> map : mapTableInDB.entrySet()) {
-
-            String schema = map.getValue();
-            String name = map.getKey();
-
-            ResultSet rs = md.getColumns(null, schema, name, null);
-            try {
-                Map<String, String> schemaName = new HashMap<String, String>();
-                schemaName.put(name, schema);
-                Collection<DbAttribute> atribute = entityTables.get(schemaName);
-                if (atribute == null) {
-                    schemaName.remove(name);
-                    schemaName.put(name, null);
-                    atribute = entityTables.get(schemaName);
-                }
-                if (atribute != null && rs.getFetchSize() != 0) {
-                    int countColumn = 0;
-                    int isInEntity = 0;
-                    while (rs.next()) {
-                        countColumn++;
-                        String columnName = rs.getString("COLUMN_NAME");
-                        for (DbAttribute attr : atribute) {
-
-                            if (attr.getName().equalsIgnoreCase(columnName)) {
-                                isInEntity++;
-                                continue;
-                            }
-                        }
-                    }
-
-                    if (countColumn != atribute.size()) {
-                        errorMessage = "different number of columns in table " + name;
-                        continue;
-                    }
-                    if (countColumn != isInEntity && errorMessage == null) {
-                        errorMessage = "no columns in table "
-                                + name
-                                + " or does not match the type of column";
-                        continue;
-                    }
-
-                }
-            }
-            finally {
-                rs.close();
-            }
-        }
-
-    }
-
-    public boolean compareTables(DatabaseMetaData md, Collection<DbEntity> entities) {
-
-        boolean isIncluded = true;
-        for (DbEntity ent : entities) {
-
-            String name = ent.getName();
-            String schema = ent.getSchema();
-            Collection<DbAttribute> atributes = ent.getAttributes();
-
-            if (schema != null) {
-                if (schemaNameMap.get(schema) != null) {
-                    Collection<String> names = schemaNameMap.get(schema);
-                    if (names.contains(name)) {
-                         mapTableInDB.put(name, schema);
-                    } else {
-                        tableNoInDB.add(name);
-                    }
-                } else {
-                    isIncluded = false;
-                    errorMessage = "no schema " + schema + " in db";
-                    break;
-                }
-            }
-            else {
-                if (nameSchemaMap.get(name) != null
-                        || !ent.getDataMap().isQuotingSQLIdentifiers()
-                        && (nameSchemaMap.get(name.toLowerCase()) != null || nameSchemaMap
-                                .get(name.toUpperCase()) != null)) {
-                    Collection<String> sc = nameSchemaMap.get(name);
-                    if (sc == null) {
-                        if (nameSchemaMap.get(name.toLowerCase()) != null) {
-                            sc = nameSchemaMap.get(name.toLowerCase());
-                        }
-                        else {
-                            sc = nameSchemaMap.get(name.toUpperCase());
-                        }
-                    }
-
-                    if (sc.size() == 1) {
-                        mapTableInDB.put(name, sc.iterator().next());
-                    }
-                    else {
-                        errorMessage = " enter the schema. Table found in the schemas: ";
-                        Iterator<String> it = sc.iterator();
-                        String names = "";
-                        while (it.hasNext()) {
-                            names += it.next() + ", ";
-                        }
-                        errorMessage = errorMessage + names;
-                    }
-                }
-                else {
-                    tableNoInDB.add(name);
-                }
-            }
-            Map<String, String> schemaName = new HashMap<String, String>();
-            schemaName.put(name, schema);
-            entityTables.put(schemaName, atributes);
-        }
-        return isIncluded;
-    }
-
-    public void analyzeSchemas(List<String> schemas, DatabaseMetaData md)
-            throws SQLException {
-
-        if (schemas.size() == 0) {
-            schemas.add("%");
-        }
-        for (String schema : schemas) {
-            ResultSet tables = md.getTables(null, schema, null, null);
-
-            Collection<String> tableInSchema = new ArrayList<String>();
-            try {
-                while (tables.next()) {
-                    String name = tables.getString("TABLE_NAME");
-                    if (name == null || name.startsWith("BIN$")) {
-                        continue;
-                    }
-
-                    tableInSchema.add(name);
-                    if (nameSchemaMap.get(name) != null) {
-                        Collection<String> sc = nameSchemaMap.get(name);
-                        Iterator<String> iSc = sc.iterator();
-                        boolean inSchema = false;
-                        while (iSc.hasNext()) {
-                            if (iSc.next().equals(schema)) {
-                                inSchema = true;
-                            }
-                        }
-                        if (!inSchema) {
-                            sc.add(schema);
-                            nameSchemaMap.remove(name);
-                            nameSchemaMap.put(name, sc);
-                        }
-
-                    }
-                    else {
-                        Collection<String> sc = new ArrayList<String>();
-                        sc.add(schema);
-                        nameSchemaMap.put(name, sc);
-                    }
-                }
-                schemaNameMap.put(schema, tableInSchema);
-            }
-            finally {
-                tables.close();
-            }
-        }
-    }
-
-    public String getErrorMessage() {
-        return errorMessage;
-    }
+	private Map<String, String> mapTableInDB;
+	private List<String> tableNoInDB;
+	private Map<String, Collection<String>> nameSchemaMap;
+	private Map<String, Collection<String>> schemaNameMap;
+	private Map<Map<String, String>, Collection<DbAttribute>> entityTables;
+	private String errorMessage;
+
+	SchemaAnalyzer() {
+		errorMessage = null;
+		mapTableInDB = new HashMap<String, String>();
+		tableNoInDB = new ArrayList<String>();
+		nameSchemaMap = new HashMap<String, Collection<String>>();
+		schemaNameMap = new HashMap<String, Collection<String>>();
+		entityTables = new HashMap<Map<String, String>, Collection<DbAttribute>>();
+	}
+
+	public List<String> getTableNoInDB() {
+		return tableNoInDB;
+	}
+
+	public void compareColumns(DatabaseMetaData md) throws SQLException {
+
+		for (Map.Entry<String, String> map : mapTableInDB.entrySet()) {
+
+			String schema = map.getValue();
+			String name = map.getKey();
+
+			try (ResultSet rs = md.getColumns(null, schema, name, null);) {
+				Map<String, String> schemaName = new HashMap<String, String>();
+				schemaName.put(name, schema);
+				Collection<DbAttribute> atribute = entityTables.get(schemaName);
+				if (atribute == null) {
+					schemaName.remove(name);
+					schemaName.put(name, null);
+					atribute = entityTables.get(schemaName);
+				}
+				if (atribute != null && rs.getFetchSize() != 0) {
+					int countColumn = 0;
+					int isInEntity = 0;
+					while (rs.next()) {
+						countColumn++;
+						String columnName = rs.getString("COLUMN_NAME");
+						for (DbAttribute attr : atribute) {
+
+							if (attr.getName().equalsIgnoreCase(columnName)) {
+								isInEntity++;
+								continue;
+							}
+						}
+					}
+
+					if (countColumn != atribute.size()) {
+						errorMessage = "different number of columns in table " + name;
+						continue;
+					}
+					if (countColumn != isInEntity && errorMessage == null) {
+						errorMessage = "no columns in table " + name + " or does not match the type of column";
+						continue;
+					}
+				}
+			}
+		}
+
+	}
+
+	public boolean compareTables(DatabaseMetaData md, Collection<DbEntity> entities) {
+
+		boolean isIncluded = true;
+		for (DbEntity ent : entities) {
+
+			String name = ent.getName();
+			String schema = ent.getSchema();
+			Collection<DbAttribute> atributes = ent.getAttributes();
+
+			if (schema != null) {
+				if (schemaNameMap.get(schema) != null) {
+					Collection<String> names = schemaNameMap.get(schema);
+					if (names.contains(name)) {
+						mapTableInDB.put(name, schema);
+					} else {
+						tableNoInDB.add(name);
+					}
+				} else {
+					isIncluded = false;
+					errorMessage = "no schema " + schema + " in db";
+					break;
+				}
+			} else {
+				if (nameSchemaMap.get(name) != null
+						|| !ent.getDataMap().isQuotingSQLIdentifiers()
+						&& (nameSchemaMap.get(name.toLowerCase()) != null || nameSchemaMap.get(name.toUpperCase()) != null)) {
+					Collection<String> sc = nameSchemaMap.get(name);
+					if (sc == null) {
+						if (nameSchemaMap.get(name.toLowerCase()) != null) {
+							sc = nameSchemaMap.get(name.toLowerCase());
+						} else {
+							sc = nameSchemaMap.get(name.toUpperCase());
+						}
+					}
+
+					if (sc.size() == 1) {
+						mapTableInDB.put(name, sc.iterator().next());
+					} else {
+						errorMessage = " enter the schema. Table found in the schemas: ";
+						Iterator<String> it = sc.iterator();
+						String names = "";
+						while (it.hasNext()) {
+							names += it.next() + ", ";
+						}
+						errorMessage = errorMessage + names;
+					}
+				} else {
+					tableNoInDB.add(name);
+				}
+			}
+			Map<String, String> schemaName = new HashMap<String, String>();
+			schemaName.put(name, schema);
+			entityTables.put(schemaName, atributes);
+		}
+		return isIncluded;
+	}
+
+	public void analyzeSchemas(List<String> schemas, DatabaseMetaData md) throws SQLException {
+
+		if (schemas.size() == 0) {
+			schemas.add("%");
+		}
+		for (String schema : schemas) {
+
+			Collection<String> tableInSchema = new ArrayList<String>();
+			try (ResultSet tables = md.getTables(null, schema, null, null);) {
+				while (tables.next()) {
+					String name = tables.getString("TABLE_NAME");
+					if (name == null || name.startsWith("BIN$")) {
+						continue;
+					}
+
+					tableInSchema.add(name);
+					if (nameSchemaMap.get(name) != null) {
+						Collection<String> sc = nameSchemaMap.get(name);
+						Iterator<String> iSc = sc.iterator();
+						boolean inSchema = false;
+						while (iSc.hasNext()) {
+							if (iSc.next().equals(schema)) {
+								inSchema = true;
+							}
+						}
+						if (!inSchema) {
+							sc.add(schema);
+							nameSchemaMap.remove(name);
+							nameSchemaMap.put(name, sc);
+						}
+
+					} else {
+						Collection<String> sc = new ArrayList<String>();
+						sc.add(schema);
+						nameSchemaMap.put(name, sc);
+					}
+				}
+				schemaNameMap.put(schema, tableInSchema);
+			}
+		}
+	}
+
+	public String getErrorMessage() {
+		return errorMessage;
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java
index b7cf1ea..051feab 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/dbsync/ThrowOnPartialSchemaStrategy.java
@@ -37,91 +37,71 @@ import org.apache.commons.logging.LogFactory;
  */
 public class ThrowOnPartialSchemaStrategy extends BaseSchemaUpdateStrategy {
 
-    final static Log logger = LogFactory.getLog(ThrowOnPartialSchemaStrategy.class);
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    protected void processSchemaUpdate(DataNode dataNode) throws SQLException {
-
-        SchemaAnalyzer analyzer = new SchemaAnalyzer();
-
-        List<String> schemas = new ArrayList<String>();
-        DatabaseMetaData md = null;
-        Connection connection = null;
-        try {
-            connection = dataNode.getDataSource().getConnection();
-            
-            try {
-                md = connection.getMetaData();
-                ResultSet rs = md.getSchemas();
-    
-                try {
-                    while (rs.next()) {
-                        String schemaName = rs.getString(1);
-                        schemas.add(schemaName);
-                    }
-                }
-                finally {
-                    rs.close();
-                }
-            }
-            finally {
-                connection.close();
-            }
-            analyzer.analyzeSchemas(schemas, md);
-        }
-        catch (Exception e) {
-            logger.debug("Exception analyzing schema, ignoring", e);
-        }
-
-        Collection<DbEntity> entities = dataNode.getEntityResolver().getDbEntities();
-
-        boolean isIncluded = analyzer.compareTables(md, entities);
-
-        if (isIncluded && analyzer.getErrorMessage() == null) {
-            try {
-                analyzer.compareColumns(md);
-            }
-            catch (SQLException e) {
-                logger.debug("Exception analyzing schema, ignoring", e);
-            }
-        }
-
-        processSchemaUpdate(dataNode, analyzer.getTableNoInDB(), analyzer
-                .getErrorMessage(), entities.size());
-    }
-
-    protected void processSchemaUpdate(
-            DataNode dataNode,
-            List<String> mergerOnlyTable,
-            String errorMessage,
-            int entitiesSize) throws SQLException {
-
-        if (mergerOnlyTable.size() == 0 && errorMessage == null) {
-            logger.info("Full schema is present");
-        }
-        else {
-            logger.info("Error - missing or partial schema detected");
-            StringBuilder buffer = new StringBuilder("Schema mismatch detected");
-
-            if (errorMessage != null) {
-                buffer.append(": ").append(errorMessage);
-            }
-            else if (mergerOnlyTable.size() == entitiesSize) {
-                buffer.append(": no schema found");
-            }
-            else {
-                if (mergerOnlyTable.size() > 0) {
-                    buffer
-                            .append(": missing table '")
-                            .append(mergerOnlyTable.get(0))
-                            .append('\'');
-                }
-            }
-
-            throw new CayenneRuntimeException(buffer.toString());
-        }
-    }
+	final static Log logger = LogFactory.getLog(ThrowOnPartialSchemaStrategy.class);
+
+	/**
+	 * @since 3.0
+	 */
+	@Override
+	protected void processSchemaUpdate(DataNode dataNode) throws SQLException {
+
+		SchemaAnalyzer analyzer = new SchemaAnalyzer();
+
+		List<String> schemas = new ArrayList<String>();
+		DatabaseMetaData md = null;
+		try {
+
+			try (Connection connection = dataNode.getDataSource().getConnection();) {
+				md = connection.getMetaData();
+
+				try (ResultSet rs = md.getSchemas();) {
+					while (rs.next()) {
+						String schemaName = rs.getString(1);
+						schemas.add(schemaName);
+					}
+				}
+			}
+
+			analyzer.analyzeSchemas(schemas, md);
+		} catch (Exception e) {
+			logger.debug("Exception analyzing schema, ignoring", e);
+		}
+
+		Collection<DbEntity> entities = dataNode.getEntityResolver().getDbEntities();
+
+		boolean isIncluded = analyzer.compareTables(md, entities);
+
+		if (isIncluded && analyzer.getErrorMessage() == null) {
+			try {
+				analyzer.compareColumns(md);
+			} catch (SQLException e) {
+				logger.debug("Exception analyzing schema, ignoring", e);
+			}
+		}
+
+		processSchemaUpdate(dataNode, analyzer.getTableNoInDB(), analyzer.getErrorMessage(), entities.size());
+	}
+
+	protected void processSchemaUpdate(DataNode dataNode, List<String> mergerOnlyTable, String errorMessage,
+			int entitiesSize) throws SQLException {
+
+		if (mergerOnlyTable.size() == 0 && errorMessage == null) {
+			logger.info("Full schema is present");
+		} else {
+			logger.info("Error - missing or partial schema detected");
+			StringBuilder buffer = new StringBuilder("Schema mismatch detected");
+
+			if (errorMessage != null) {
+				buffer.append(": ").append(errorMessage);
+			} else if (mergerOnlyTable.size() == entitiesSize) {
+				buffer.append(": no schema found");
+			} else {
+				if (mergerOnlyTable.size() > 0) {
+					buffer.append(": missing table '").append(mergerOnlyTable.get(0)).append('\'');
+				}
+			}
+
+			throw new CayenneRuntimeException(buffer.toString());
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/26d8434d/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
index ca3d1ef..faff51c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
@@ -49,226 +49,216 @@ import org.apache.cayenne.query.InsertBatchQuery;
  */
 public class BatchAction extends BaseSQLAction {
 
-    protected boolean runningAsBatch;
-    protected BatchQuery query;
-    protected RowDescriptor keyRowDescriptor;
-
-    private static void bind(DbAdapter adapter, PreparedStatement statement, ParameterBinding[] bindings)
-            throws SQLException, Exception {
-
-        for (ParameterBinding b : bindings) {
-            if (!b.isExcluded()) {
-                adapter.bindParameter(statement, b.getValue(), b.getStatementPosition(), b.getAttribute().getType(), b
-                        .getAttribute().getScale());
-            }
-        }
-    }
-
-    /**
-     * @since 4.0
-     */
-    public BatchAction(BatchQuery query, DataNode dataNode, boolean runningAsBatch) {
-        super(dataNode);
-        this.query = query;
-        this.runningAsBatch = runningAsBatch;
-    }
-
-    /**
-     * @return Query which originated this action
-     */
-    public BatchQuery getQuery() {
-        return query;
-    }
-
-    @Override
-    public void performAction(Connection connection, OperationObserver observer) throws SQLException, Exception {
-
-        BatchTranslator translator = createTranslator();
-        boolean generatesKeys = hasGeneratedKeys();
-
-        if (runningAsBatch && !generatesKeys) {
-            runAsBatch(connection, translator, observer);
-        } else {
-            runAsIndividualQueries(connection, translator, observer, generatesKeys);
-        }
-    }
-
-    protected BatchTranslator createTranslator() throws CayenneException {
-        return dataNode.batchTranslator(query, null);
-    }
-
-    protected void runAsBatch(Connection con, BatchTranslator translator, OperationObserver delegate)
-            throws SQLException, Exception {
-
-        String sql = translator.getSql();
-        JdbcEventLogger logger = dataNode.getJdbcEventLogger();
-        boolean isLoggable = logger.isLoggable();
-
-        // log batch SQL execution
-        logger.logQuery(sql, Collections.EMPTY_LIST);
-
-        // run batch
-
-        DbAdapter adapter = dataNode.getAdapter();
-        PreparedStatement statement = con.prepareStatement(sql);
-        try {
-            for (BatchQueryRow row : query.getRows()) {
-
-                ParameterBinding[] bindings = translator.updateBindings(row);
-                logger.logQueryParameters("batch bind", bindings);
-                bind(adapter, statement, bindings);
-
-                statement.addBatch();
-            }
-
-            // execute the whole batch
-            int[] results = statement.executeBatch();
-            delegate.nextBatchCount(query, results);
-
-            if (isLoggable) {
-                int totalUpdateCount = 0;
-                for (int result : results) {
-
-                    // this means Statement.SUCCESS_NO_INFO or
-                    // Statement.EXECUTE_FAILED
-                    if (result < 0) {
-                        totalUpdateCount = Statement.SUCCESS_NO_INFO;
-                        break;
-                    }
-
-                    totalUpdateCount += result;
-                }
-
-                logger.logUpdateCount(totalUpdateCount);
-            }
-        } finally {
-            try {
-                statement.close();
-            } catch (Exception e) {
-            }
-        }
-    }
-
-    /**
-     * Executes batch as individual queries over the same prepared statement.
-     */
-    protected void runAsIndividualQueries(Connection connection, BatchTranslator translator,
-            OperationObserver delegate, boolean generatesKeys) throws SQLException, Exception {
-
-        JdbcEventLogger logger = dataNode.getJdbcEventLogger();
-        boolean useOptimisticLock = query.isUsingOptimisticLocking();
-
-        String queryStr = translator.getSql();
-
-        // log batch SQL execution
-        logger.logQuery(queryStr, Collections.EMPTY_LIST);
-
-        // run batch queries one by one
-
-        DbAdapter adapter = dataNode.getAdapter();
-        PreparedStatement statement = (generatesKeys) ? connection.prepareStatement(queryStr,
-                Statement.RETURN_GENERATED_KEYS) : connection.prepareStatement(queryStr);
-        try {
-            for (BatchQueryRow row : query.getRows()) {
-
-                ParameterBinding[] bindings = translator.updateBindings(row);
-                logger.logQueryParameters("bind", bindings);
-
-                bind(adapter, statement, bindings);
-
-                int updated = statement.executeUpdate();
-                if (useOptimisticLock && updated != 1) {
-                    throw new OptimisticLockException(row.getObjectId(), query.getDbEntity(), queryStr,
-                            row.getQualifier());
-                }
-
-                delegate.nextCount(query, updated);
-
-                if (generatesKeys) {
-                    processGeneratedKeys(statement, delegate, row);
-                }
-
-                logger.logUpdateCount(updated);
-            }
-        } finally {
-            try {
-                statement.close();
-            } catch (Exception e) {
-            }
-        }
-    }
-
-    /**
-     * Returns whether BatchQuery generates any keys.
-     */
-    protected boolean hasGeneratedKeys() {
-        // see if we are configured to support generated keys
-        if (!dataNode.getAdapter().supportsGeneratedKeys()) {
-            return false;
-        }
-
-        // see if the query needs them
-        if (query instanceof InsertBatchQuery) {
-
-            // see if any of the generated attributes is PK
-            for (final DbAttribute attr : query.getDbEntity().getGeneratedAttributes()) {
-                if (attr.isPrimaryKey()) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Implements generated keys extraction supported in JDBC 3.0 specification.
-     * 
-     * @since 4.0
-     */
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    protected void processGeneratedKeys(Statement statement, OperationObserver observer, BatchQueryRow row)
-            throws SQLException, CayenneException {
-
-        ResultSet keysRS = statement.getGeneratedKeys();
-
-        // TODO: andrus, 7/4/2007 - (1) get the type of meaningful PK's from
-        // their
-        // ObjAttributes; (2) use a different form of Statement.execute -
-        // "execute(String,String[])" to be able to map generated column names
-        // (this way
-        // we can support multiple columns.. although need to check how well
-        // this works
-        // with most common drivers)
-
-        RowDescriptorBuilder builder = new RowDescriptorBuilder();
-
-        if (this.keyRowDescriptor == null) {
-            // attempt to figure out the right descriptor from the mapping...
-            Collection<DbAttribute> generated = query.getDbEntity().getGeneratedAttributes();
-            if (generated.size() == 1) {
-                DbAttribute key = generated.iterator().next();
-
-                ColumnDescriptor[] columns = new ColumnDescriptor[1];
-
-                // use column name from result set, but type and Java class from
-                // DB
-                // attribute
-                columns[0] = new ColumnDescriptor(keysRS.getMetaData(), 1);
-                columns[0].setJdbcType(key.getType());
-                columns[0].setJavaClass(TypesMapping.getJavaBySqlType(key.getType()));
-                builder.setColumns(columns);
-            } else {
-                builder.setResultSet(keysRS);
-            }
-
-            this.keyRowDescriptor = builder.getDescriptor(dataNode.getAdapter().getExtendedTypes());
-        }
-
-        RowReader<?> rowReader = dataNode.rowReader(keyRowDescriptor, query.getMetaData(dataNode.getEntityResolver()),
-                Collections.<ObjAttribute, ColumnDescriptor> emptyMap());
-        ResultIterator iterator = new JDBCResultIterator(null, keysRS, rowReader);
-
-        observer.nextGeneratedRows(query, iterator, row.getObjectId());
-    }
+	protected boolean runningAsBatch;
+	protected BatchQuery query;
+	protected RowDescriptor keyRowDescriptor;
+
+	private static void bind(DbAdapter adapter, PreparedStatement statement, ParameterBinding[] bindings)
+			throws SQLException, Exception {
+
+		for (ParameterBinding b : bindings) {
+			if (!b.isExcluded()) {
+				adapter.bindParameter(statement, b.getValue(), b.getStatementPosition(), b.getAttribute().getType(), b
+						.getAttribute().getScale());
+			}
+		}
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public BatchAction(BatchQuery query, DataNode dataNode, boolean runningAsBatch) {
+		super(dataNode);
+		this.query = query;
+		this.runningAsBatch = runningAsBatch;
+	}
+
+	/**
+	 * @return Query which originated this action
+	 */
+	public BatchQuery getQuery() {
+		return query;
+	}
+
+	@Override
+	public void performAction(Connection connection, OperationObserver observer) throws SQLException, Exception {
+
+		BatchTranslator translator = createTranslator();
+		boolean generatesKeys = hasGeneratedKeys();
+
+		if (runningAsBatch && !generatesKeys) {
+			runAsBatch(connection, translator, observer);
+		} else {
+			runAsIndividualQueries(connection, translator, observer, generatesKeys);
+		}
+	}
+
+	protected BatchTranslator createTranslator() throws CayenneException {
+		return dataNode.batchTranslator(query, null);
+	}
+
+	protected void runAsBatch(Connection con, BatchTranslator translator, OperationObserver delegate)
+			throws SQLException, Exception {
+
+		String sql = translator.getSql();
+		JdbcEventLogger logger = dataNode.getJdbcEventLogger();
+		boolean isLoggable = logger.isLoggable();
+
+		// log batch SQL execution
+		logger.logQuery(sql, Collections.EMPTY_LIST);
+
+		// run batch
+
+		DbAdapter adapter = dataNode.getAdapter();
+
+		try (PreparedStatement statement = con.prepareStatement(sql);) {
+			for (BatchQueryRow row : query.getRows()) {
+
+				ParameterBinding[] bindings = translator.updateBindings(row);
+				logger.logQueryParameters("batch bind", bindings);
+				bind(adapter, statement, bindings);
+
+				statement.addBatch();
+			}
+
+			// execute the whole batch
+			int[] results = statement.executeBatch();
+			delegate.nextBatchCount(query, results);
+
+			if (isLoggable) {
+				int totalUpdateCount = 0;
+				for (int result : results) {
+
+					// this means Statement.SUCCESS_NO_INFO or
+					// Statement.EXECUTE_FAILED
+					if (result < 0) {
+						totalUpdateCount = Statement.SUCCESS_NO_INFO;
+						break;
+					}
+
+					totalUpdateCount += result;
+				}
+
+				logger.logUpdateCount(totalUpdateCount);
+			}
+		}
+	}
+
+	/**
+	 * Executes batch as individual queries over the same prepared statement.
+	 */
+	protected void runAsIndividualQueries(Connection connection, BatchTranslator translator,
+			OperationObserver delegate, boolean generatesKeys) throws SQLException, Exception {
+
+		JdbcEventLogger logger = dataNode.getJdbcEventLogger();
+		boolean useOptimisticLock = query.isUsingOptimisticLocking();
+
+		String queryStr = translator.getSql();
+
+		// log batch SQL execution
+		logger.logQuery(queryStr, Collections.EMPTY_LIST);
+
+		// run batch queries one by one
+
+		DbAdapter adapter = dataNode.getAdapter();
+
+		try (PreparedStatement statement = (generatesKeys) ? connection.prepareStatement(queryStr,
+				Statement.RETURN_GENERATED_KEYS) : connection.prepareStatement(queryStr);) {
+			for (BatchQueryRow row : query.getRows()) {
+
+				ParameterBinding[] bindings = translator.updateBindings(row);
+				logger.logQueryParameters("bind", bindings);
+
+				bind(adapter, statement, bindings);
+
+				int updated = statement.executeUpdate();
+				if (useOptimisticLock && updated != 1) {
+					throw new OptimisticLockException(row.getObjectId(), query.getDbEntity(), queryStr,
+							row.getQualifier());
+				}
+
+				delegate.nextCount(query, updated);
+
+				if (generatesKeys) {
+					processGeneratedKeys(statement, delegate, row);
+				}
+
+				logger.logUpdateCount(updated);
+			}
+		}
+	}
+
+	/**
+	 * Returns whether BatchQuery generates any keys.
+	 */
+	protected boolean hasGeneratedKeys() {
+		// see if we are configured to support generated keys
+		if (!dataNode.getAdapter().supportsGeneratedKeys()) {
+			return false;
+		}
+
+		// see if the query needs them
+		if (query instanceof InsertBatchQuery) {
+
+			// see if any of the generated attributes is PK
+			for (final DbAttribute attr : query.getDbEntity().getGeneratedAttributes()) {
+				if (attr.isPrimaryKey()) {
+					return true;
+				}
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Implements generated keys extraction supported in JDBC 3.0 specification.
+	 * 
+	 * @since 4.0
+	 */
+	@SuppressWarnings({ "rawtypes", "unchecked" })
+	protected void processGeneratedKeys(Statement statement, OperationObserver observer, BatchQueryRow row)
+			throws SQLException, CayenneException {
+
+		ResultSet keysRS = statement.getGeneratedKeys();
+
+		// TODO: andrus, 7/4/2007 - (1) get the type of meaningful PK's from
+		// their
+		// ObjAttributes; (2) use a different form of Statement.execute -
+		// "execute(String,String[])" to be able to map generated column names
+		// (this way
+		// we can support multiple columns.. although need to check how well
+		// this works
+		// with most common drivers)
+
+		RowDescriptorBuilder builder = new RowDescriptorBuilder();
+
+		if (this.keyRowDescriptor == null) {
+			// attempt to figure out the right descriptor from the mapping...
+			Collection<DbAttribute> generated = query.getDbEntity().getGeneratedAttributes();
+			if (generated.size() == 1) {
+				DbAttribute key = generated.iterator().next();
+
+				ColumnDescriptor[] columns = new ColumnDescriptor[1];
+
+				// use column name from result set, but type and Java class from
+				// DB
+				// attribute
+				columns[0] = new ColumnDescriptor(keysRS.getMetaData(), 1);
+				columns[0].setJdbcType(key.getType());
+				columns[0].setJavaClass(TypesMapping.getJavaBySqlType(key.getType()));
+				builder.setColumns(columns);
+			} else {
+				builder.setResultSet(keysRS);
+			}
+
+			this.keyRowDescriptor = builder.getDescriptor(dataNode.getAdapter().getExtendedTypes());
+		}
+
+		RowReader<?> rowReader = dataNode.rowReader(keyRowDescriptor, query.getMetaData(dataNode.getEntityResolver()),
+				Collections.<ObjAttribute, ColumnDescriptor> emptyMap());
+		ResultIterator iterator = new JDBCResultIterator(null, keysRS, rowReader);
+
+		observer.nextGeneratedRows(query, iterator, row.getObjectId());
+	}
 }


Mime
View raw message