You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2009/09/07 22:48:51 UTC

svn commit: r812272 - in /cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src: main/java/org/apache/cayenne/access/ main/java/org/apache/cayenne/access/trans/ test/java/org/apache/cayenne/access/

Author: aadamchik
Date: Mon Sep  7 20:48:50 2009
New Revision: 812272

URL: http://svn.apache.org/viewvc?rev=812272&view=rev
Log:
CAY-1250 Prefetching doesn't work with prefetched subentity

perfromance research / improvements / refactoring

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java
      - copied, changed from r811963, cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectTreeResolver.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolverNode.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachementStrategy.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/NoopParentAttachmentStrategy.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ParentAttachmentStrategy.java
Removed:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectTreeResolver.java
Modified:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectResolver.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorJointNode.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorNode.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilder.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/SelectTranslator.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/InheritanceTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilderTest.java

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java?rev=812272&r1=812271&r2=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java Mon Sep  7 20:48:50 2009
@@ -570,7 +570,7 @@
                         .synchronizedObjectsFromDataRows(normalizedRows);
             }
             else {
-                ObjectTreeResolver resolver = new ObjectTreeResolver(context, metadata);
+                HierarchicalObjectResolver resolver = new HierarchicalObjectResolver(context, metadata);
                 objects = resolver.synchronizedObjectsFromDataRows(
                         prefetchTree,
                         normalizedRows,
@@ -644,7 +644,7 @@
                         .synchronizedObjectsFromDataRows(rowsColumn);
             }
             else {
-                ObjectTreeResolver resolver = new ObjectTreeResolver(context, metadata);
+                HierarchicalObjectResolver resolver = new HierarchicalObjectResolver(context, metadata);
                 objects = resolver.synchronizedObjectsFromDataRows(
                         prefetchTree,
                         rowsColumn,

Copied: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java (from r811963, cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectTreeResolver.java)
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java?p2=cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java&p1=cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectTreeResolver.java&r1=811963&r2=812272&rev=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectTreeResolver.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java Mon Sep  7 20:48:50 2009
@@ -37,16 +37,14 @@
  * Processes a number of DataRow sets corresponding to a given prefetch tree, resolving
  * DataRows to an object tree. Can process any combination of joint and disjoint sets, per
  * prefetch tree.
- * 
- * @since 1.2
  */
-class ObjectTreeResolver {
+class HierarchicalObjectResolver {
 
     DataContext context;
     QueryMetadata queryMetadata;
     DataRowStore cache;
 
-    ObjectTreeResolver(DataContext context, QueryMetadata queryMetadata) {
+    HierarchicalObjectResolver(DataContext context, QueryMetadata queryMetadata) {
         this.queryMetadata = queryMetadata;
         this.context = context;
         this.cache = context.getObjectStore().getDataRowCache();
@@ -67,7 +65,7 @@
         }
     }
 
-    List resolveObjectTree(
+    private List resolveObjectTree(
             PrefetchTreeNode tree,
             List mainResultRows,
             Map extraResultsByPath) {
@@ -108,18 +106,9 @@
                 return true;
             }
 
-            if (processorNode.isPartitionedByParent()) {
-
-                List objects = processorNode.getResolver().relatedObjectsFromDataRows(
-                        processorNode.getDataRows(),
-                        processorNode);
-                processorNode.setObjects(objects);
-            }
-            else {
-                List objects = processorNode.getResolver().objectsFromDataRows(
-                        processorNode.getDataRows());
-                processorNode.setObjects(objects);
-            }
+            List objects = processorNode.getResolver().objectsFromDataRows(
+                    processorNode.getDataRows());
+            processorNode.setObjects(objects);
 
             return true;
         }
@@ -133,20 +122,19 @@
 
                 PrefetchProcessorJointNode processorNode = (PrefetchProcessorJointNode) node;
 
-                JointProcessor subprocessor = new JointProcessor(
-                        (PrefetchProcessorJointNode) node);
+                JointProcessor subprocessor = new JointProcessor(processorNode);
 
                 PrefetchProcessorNode parent = (PrefetchProcessorNode) processorNode
                         .getParent();
-                
-                while(parent != null && parent.isPhantom()) {
+
+                while (parent != null && parent.isPhantom()) {
                     parent = (PrefetchProcessorNode) parent.getParent();
                 }
-                
-                if(parent == null) {
+
+                if (parent == null) {
                     return false;
                 }
-                
+
                 List parentRows = parent.getDataRows();
 
                 // phantom node?

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolverNode.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolverNode.java?rev=812272&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolverNode.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolverNode.java Mon Sep  7 20:48:50 2009
@@ -0,0 +1,105 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.reflect.ClassDescriptor;
+
+class HierarchicalObjectResolverNode extends ObjectResolver {
+
+    private ParentAttachmentStrategy parentAttachmentStrategy;
+
+    HierarchicalObjectResolverNode(PrefetchProcessorNode node, DataContext context,
+            ClassDescriptor descriptor, boolean refresh) {
+        super(context, descriptor, refresh);
+
+        if (node.getParent() == null || node.getParent().isPhantom()) {
+            this.parentAttachmentStrategy = new NoopParentAttachmentStrategy();
+        }
+        else {
+            this.parentAttachmentStrategy = new JoinedIdParentAttachementStrategy(context
+                    .getGraphManager(), node);
+        }
+    }
+
+    @Override
+    List<Persistent> objectsFromDataRows(List<? extends DataRow> rows) {
+
+        if (rows == null || rows.size() == 0) {
+            return new ArrayList<Persistent>(1);
+        }
+
+        List<Persistent> results = new ArrayList<Persistent>(rows.size());
+
+        // here we can get the same object repeated multiple times in case of
+        // many-to-many between prefetched and main entity... this is needed to
+        // connect prefetched objects to the main objects. To avoid needlessly refreshing
+        // the same object multiple times, track which objectids area alrady loaded in
+        // this pass
+        Map<ObjectId, Persistent> seen = new HashMap<ObjectId, Persistent>();
+
+        for (DataRow row : rows) {
+
+            // determine entity to use
+            ClassDescriptor classDescriptor = descriptorResolutionStrategy
+                    .descriptorForRow(row);
+
+            // not using DataRow.createObjectId for performance reasons - ObjectResolver
+            // has all needed metadata already cached.
+            ObjectId anId = createObjectId(row, classDescriptor.getEntity(), null);
+
+            Persistent object = seen.get(anId);
+
+            if (object == null) {
+                object = objectFromDataRow(row, anId, classDescriptor);
+
+                if (object == null) {
+                    throw new CayenneRuntimeException("Can't build Object from row: "
+                            + row);
+                }
+                seen.put(anId, object);
+            }
+
+            // keep the dupe objects (and data rows) around, as there maybe an attached
+            // joint prefetch...
+            results.add(object);
+
+            parentAttachmentStrategy.linkToParent(row, object);
+        }
+
+        // now deal with snapshots
+
+        // TODO: refactoring: dupes will clutter the lists and cause extra processing...
+        // removal of dupes happens only downstream, as we need the objects matching
+        // fetched rows for joint prefetch resolving... maybe pushback unique and
+        // non-unique lists to the "node", instead of returning a single list from this
+        // method
+        cache.snapshotsUpdatedForObjects(results, rows, refreshObjects);
+
+        return results;
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachementStrategy.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachementStrategy.java?rev=812272&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachementStrategy.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/JoinedIdParentAttachementStrategy.java Mon Sep  7 20:48:50 2009
@@ -0,0 +1,99 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.graph.GraphManager;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.reflect.ClassDescriptor;
+
+/**
+ * A ParentAttachmentStrategy that extracts parent ObjectId from the joined columns in the
+ * child snapshot.
+ */
+class JoinedIdParentAttachementStrategy implements ParentAttachmentStrategy {
+
+    private String relatedIdPrefix;
+    private Collection<ObjEntity> sourceEntities;
+    private PrefetchProcessorNode node;
+    private GraphManager graphManager;
+
+    JoinedIdParentAttachementStrategy(GraphManager graphManager,
+            PrefetchProcessorNode node) {
+
+        ClassDescriptor parentDescriptor = ((PrefetchProcessorNode) node.getParent())
+                .getResolver()
+                .getDescriptor();
+
+        relatedIdPrefix = node
+                .getIncoming()
+                .getRelationship()
+                .getReverseDbRelationshipPath()
+                + ".";
+
+        if (parentDescriptor.getEntityInheritanceTree() == null) {
+            sourceEntities = Collections.singletonList(parentDescriptor.getEntity());
+        }
+        else {
+            sourceEntities = parentDescriptor.getEntityInheritanceTree().allSubEntities();
+        }
+
+        this.node = node;
+        this.graphManager = graphManager;
+    }
+
+    public void linkToParent(DataRow row, Persistent object) {
+        Persistent parentObject = null;
+
+        for (ObjEntity entity : sourceEntities) {
+            if (entity.isAbstract()) {
+                continue;
+            }
+
+            ObjectId id = node.getResolver().createObjectId(row, entity, relatedIdPrefix);
+            if (id == null) {
+                throw new CayenneRuntimeException("Can't build ObjectId from row: "
+                        + row
+                        + ", entity: "
+                        + entity.getName()
+                        + ", prefix: "
+                        + relatedIdPrefix);
+            }
+
+            parentObject = (Persistent) graphManager.getNode(id);
+
+            if (parentObject != null) {
+                break;
+            }
+        }
+
+        // don't attach to hollow objects
+        if (parentObject != null
+                && parentObject.getPersistenceState() != PersistenceState.HOLLOW) {
+            node.linkToParent(object, parentObject);
+        }
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/NoopParentAttachmentStrategy.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/NoopParentAttachmentStrategy.java?rev=812272&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/NoopParentAttachmentStrategy.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/NoopParentAttachmentStrategy.java Mon Sep  7 20:48:50 2009
@@ -0,0 +1,29 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access;
+
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.Persistent;
+
+final class NoopParentAttachmentStrategy implements ParentAttachmentStrategy {
+
+    public void linkToParent(DataRow row, Persistent object) {
+        // do nothing...
+    }
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectResolver.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectResolver.java?rev=812272&r1=812271&r2=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectResolver.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ObjectResolver.java Mon Sep  7 20:48:50 2009
@@ -21,15 +21,14 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.DataObject;
 import org.apache.cayenne.DataRow;
+import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.PersistenceState;
 import org.apache.cayenne.Persistent;
@@ -56,10 +55,7 @@
     DescriptorResolutionStrategy descriptorResolutionStrategy;
 
     ObjectResolver(DataContext context, ClassDescriptor descriptor, boolean refresh) {
-        init(context, descriptor, refresh);
-    }
 
-    void init(DataContext context, ClassDescriptor descriptor, boolean refresh) {
         // sanity check
         DbEntity dbEntity = descriptor.getEntity().getDbEntity();
         if (dbEntity == null) {
@@ -130,135 +126,6 @@
         return results;
     }
 
-    /**
-     * Processes a list of rows for a result set that has objects related to a set of
-     * parent objects via some relationship defined in PrefetchProcessorNode parameter.
-     * Relationships are linked in this method, assuming that parent PK columns are
-     * included in each row and are prefixed with DB relationship name.
-     * <p>
-     * Synchronization note. This method requires EXTERNAL synchronization on ObjectStore
-     * and DataRowStore.
-     * </p>
-     */
-    List<Persistent> relatedObjectsFromDataRows(List rows, PrefetchProcessorNode node) {
-        if (rows == null || rows.size() == 0) {
-            return new ArrayList<Persistent>(1);
-        }
-
-        boolean linkToParent = node.getParent() != null && !node.getParent().isPhantom();
-
-        String relatedIdPrefix = null;
-
-        // since we do not add descriptor columns for the source entity in the result,
-        // will need to try all subentities to find parent object...
-        Collection<ObjEntity> sourceEntities = null;
-
-        if (linkToParent) {
-            ClassDescriptor parentDescriptor = ((PrefetchProcessorNode) node.getParent())
-                    .getResolver()
-                    .getDescriptor();
-            relatedIdPrefix = node
-                    .getIncoming()
-                    .getRelationship()
-                    .getReverseDbRelationshipPath()
-                    + ".";
-
-            if (parentDescriptor.getEntityInheritanceTree() == null) {
-                sourceEntities = Collections.singletonList(parentDescriptor.getEntity());
-            }
-            else {
-                sourceEntities = parentDescriptor
-                        .getEntityInheritanceTree()
-                        .allSubEntities();
-            }
-        }
-
-        List<Persistent> results = new ArrayList<Persistent>(rows.size());
-
-        // here we can get the same object repeated multiple times in case of
-        // many-to-many between prefetched and main entity... this is needed to
-        // connect prefetched objects to the main objects. To avoid needlessly refreshing
-        // the same object multiple times, track which objectids area alrady loaded in
-        // this pass
-        Map<ObjectId, Persistent> seen = new HashMap<ObjectId, Persistent>();
-
-        Iterator it = rows.iterator();
-
-        while (it.hasNext()) {
-
-            DataRow row = (DataRow) it.next();
-
-            // determine entity to use
-            ClassDescriptor classDescriptor = descriptorResolutionStrategy
-                    .descriptorForRow(row);
-
-            // not using DataRow.createObjectId for performance reasons - ObjectResolver
-            // has all needed metadata already cached.
-            ObjectId anId = createObjectId(row, classDescriptor.getEntity(), null);
-
-            Persistent object = seen.get(anId);
-
-            if (object == null) {
-                object = objectFromDataRow(row, anId, classDescriptor);
-
-                if (object == null) {
-                    throw new CayenneRuntimeException("Can't build Object from row: "
-                            + row);
-                }
-                seen.put(anId, object);
-            }
-
-            // keep the dupe objects (and data rows) around, as there maybe an attached
-            // joint prefetch...
-            results.add(object);
-
-            if (linkToParent) {
-
-                Persistent parentObject = null;
-
-                for (ObjEntity entity : sourceEntities) {
-                    if (entity.isAbstract()) {
-                        continue;
-                    }
-
-                    ObjectId id = createObjectId(row, entity, relatedIdPrefix);
-                    if (id == null) {
-                        throw new CayenneRuntimeException(
-                                "Can't build ObjectId from row: "
-                                        + row
-                                        + ", entity: "
-                                        + entity.getName()
-                                        + ", prefix: "
-                                        + relatedIdPrefix);
-                    }
-
-                    parentObject = (Persistent) context.getObjectStore().getNode(id);
-
-                    if (parentObject != null) {
-                        break;
-                    }
-                }
-
-                // don't attach to hollow objects
-                if (parentObject != null
-                        && parentObject.getPersistenceState() != PersistenceState.HOLLOW) {
-                    node.linkToParent(object, parentObject);
-                }
-            }
-        }
-
-        // now deal with snapshots
-
-        // TODO: refactoring: dupes will clutter the lists and cause extra processing...
-        // removal of dupes happens only downstream, as we need the objects matching
-        // fetched rows for joint prefetch resolving... maybe pushback unique and
-        // non-unique lists to the "node", instead of returning a single list from this
-        // method
-        cache.snapshotsUpdatedForObjects(results, rows, refreshObjects);
-
-        return results;
-    }
-
     Persistent objectFromDataRow(DataRow row) {
         // determine entity to use
         ClassDescriptor classDescriptor = descriptorResolutionStrategy
@@ -337,6 +204,10 @@
         return context.getEntityResolver();
     }
 
+    ObjectContext getContext() {
+        return context;
+    }
+
     ObjectId createObjectId(DataRow dataRow, ObjEntity objEntity, String namePrefix) {
 
         Collection<DbAttribute> pk = objEntity == this.descriptor.getEntity()

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ParentAttachmentStrategy.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ParentAttachmentStrategy.java?rev=812272&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ParentAttachmentStrategy.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ParentAttachmentStrategy.java Mon Sep  7 20:48:50 2009
@@ -0,0 +1,27 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access;
+
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.Persistent;
+
+interface ParentAttachmentStrategy {
+
+    void linkToParent(DataRow row, Persistent object);
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorJointNode.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorJointNode.java?rev=812272&r1=812271&r2=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorJointNode.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorJointNode.java Mon Sep  7 20:48:50 2009
@@ -61,7 +61,7 @@
     Map<Map, Persistent> resolved;
     List<DataRow> resolvedRows;
 
-    PrefetchProcessorJointNode(PrefetchTreeNode parent, String segmentPath) {
+    PrefetchProcessorJointNode(PrefetchProcessorNode parent, String segmentPath) {
         super(parent, segmentPath);
     }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorNode.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorNode.java?rev=812272&r1=812271&r2=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorNode.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorNode.java Mon Sep  7 20:48:50 2009
@@ -52,7 +52,7 @@
 
     Persistent lastResolved;
 
-    PrefetchProcessorNode(PrefetchTreeNode parent, String segmentPath) {
+    PrefetchProcessorNode(PrefetchProcessorNode parent, String segmentPath) {
         super(parent, segmentPath);
     }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilder.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilder.java?rev=812272&r1=812271&r2=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilder.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilder.java Mon Sep  7 20:48:50 2009
@@ -39,7 +39,7 @@
     private List mainResultRows;
     private Map extraResultsByPath;
 
-    PrefetchProcessorTreeBuilder(ObjectTreeResolver objectTreeResolver,
+    PrefetchProcessorTreeBuilder(HierarchicalObjectResolver objectTreeResolver,
             List mainResultRows, Map extraResultsByPath) {
         this.context = objectTreeResolver.context;
         this.queryMetadata = objectTreeResolver.queryMetadata;
@@ -152,9 +152,19 @@
         }
 
         node.setDataRows(rows);
-        node.setResolver(new ObjectResolver(context, descriptor, queryMetadata
-                .isRefreshingObjects()));
+
         node.setIncoming(arc);
+        if (node.getParent() != null) {
+            node.setResolver(new HierarchicalObjectResolverNode(
+                    node,
+                    context,
+                    descriptor,
+                    queryMetadata.isRefreshingObjects()));
+        }
+        else {
+            node.setResolver(new ObjectResolver(context, descriptor, queryMetadata
+                    .isRefreshingObjects()));
+        }
 
         if (currentNode != null) {
             currentNode.addChild(node);

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/SelectTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/SelectTranslator.java?rev=812272&r1=812271&r2=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/SelectTranslator.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/trans/SelectTranslator.java Mon Sep  7 20:48:50 2009
@@ -389,8 +389,7 @@
 
         if (query instanceof PrefetchSelectQuery) {
 
-            // for each relationship path add closest FK or PK, for each attribute path,
-            // add specified column
+            // for each relationship path add PK of the target entity...
             for (String path : ((PrefetchSelectQuery) query).getResultPaths()) {
 
                 Expression pathExp = oe.translateToDbPath(Expression.fromString(path));

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/InheritanceTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/InheritanceTest.java?rev=812272&r1=812271&r2=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/InheritanceTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/InheritanceTest.java Mon Sep  7 20:48:50 2009
@@ -24,6 +24,7 @@
 import java.util.List;
 
 import org.apache.cayenne.DataObjectUtils;
+import org.apache.cayenne.query.Ordering;
 import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.query.SQLTemplate;
 import org.apache.cayenne.query.SelectQuery;
@@ -159,12 +160,17 @@
                 PersonNotes.class,
                 "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (2, 'AA', 2)"));
 
+        context.performGenericQuery(new SQLTemplate(
+                PersonNotes.class,
+                "INSERT INTO PERSON_NOTES (ID, NOTES, PERSON_ID) VALUES (3, 'BB', 2)"));
+
         SelectQuery query = new SelectQuery(PersonNotes.class);
         query.addPrefetch(PersonNotes.PERSON_PROPERTY);
+        query.addOrdering(PersonNotes.NOTES_PROPERTY, Ordering.ASC);
 
-        PersonNotes note = (PersonNotes) DataObjectUtils.objectForQuery(
-                createDataContext(),
-                query);
+        List<PersonNotes> notes = createDataContext().performQuery(query);
+        assertEquals(2, notes.size());
+        PersonNotes note = notes.get(0);
 
         blockQueries();
         try {

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilderTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilderTest.java?rev=812272&r1=812271&r2=812272&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilderTest.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/PrefetchProcessorTreeBuilderTest.java Mon Sep  7 20:48:50 2009
@@ -82,7 +82,7 @@
         };
 
         PrefetchTreeNode tree = new PrefetchTreeNode();
-        ObjectTreeResolver resolver = new ObjectTreeResolver(
+        HierarchicalObjectResolver resolver = new HierarchicalObjectResolver(
                 createDataContext(),
                 metadata);
         PrefetchProcessorTreeBuilder builder = new PrefetchProcessorTreeBuilder(
@@ -157,7 +157,7 @@
             }
         };
 
-        ObjectTreeResolver resolver = new ObjectTreeResolver(
+        HierarchicalObjectResolver resolver = new HierarchicalObjectResolver(
                 createDataContext(),
                 metadata);
         PrefetchProcessorTreeBuilder builder = new PrefetchProcessorTreeBuilder(