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 2010/01/23 09:04:08 UTC

svn commit: r902364 - in /cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src: main/java/org/apache/cayenne/access/ main/java/org/apache/cayenne/access/jdbc/ main/java/org/apache/cayenne/ejbql/ main/java/org/apache/cayenne/ejbql/parser/ main/ja...

Author: aadamchik
Date: Sat Jan 23 08:04:01 2010
New Revision: 902364

URL: http://svn.apache.org/viewvc?rev=902364&view=rev
Log:
CAY-1366 EJBQL: Support for fetch joins

patch by Ksenia Khailenko

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFetchJoinTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFetchJoinTest.xml
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/HierarchicalObjectResolver.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/jdbc/EJBQLFromTranslator.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLIdentifierColumnsTranslator.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectTranslator.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EntityRowReader.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/EJBQLCompiledExpression.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/CompiledExpression.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/Compiler.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLInnerFetchJoin.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLOuterFetchJoin.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.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=902364&r1=902363&r2=902364&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 Sat Jan 23 08:04:01 2010
@@ -20,11 +20,15 @@
 package org.apache.cayenne.access;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.cayenne.CayenneException;
 import org.apache.cayenne.CayenneRuntimeException;
@@ -636,15 +640,31 @@
                 rowsColumn.add((DataRow) rows.get(i)[position]);
             }
             List<Persistent> objects;
-
-            // take a shortcut when no prefetches exist...
+            if (prefetchTree != null) {
+                PrefetchTreeNode prefetchTreeNode = null;
+                for (PrefetchTreeNode prefetch : prefetchTree.getChildren()) {
+                    if (descriptor.getEntity().getName().equals(prefetch.getEntityName())) {
+                        if (prefetchTreeNode == null) {
+                            prefetchTreeNode = new PrefetchTreeNode();
+                        }
+                        PrefetchTreeNode addPath = prefetchTreeNode.addPath(prefetch
+                                .getPath());
+                        addPath.setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+                        addPath.setPhantom(false);
+                    }
+                }
+                prefetchTree = prefetchTreeNode;
+            }
             if (prefetchTree == null) {
                 objects = new ObjectResolver(context, descriptor, metadata
                         .isRefreshingObjects())
                         .synchronizedObjectsFromDataRows(rowsColumn);
             }
             else {
-                HierarchicalObjectResolver resolver = new HierarchicalObjectResolver(context, metadata);
+                HierarchicalObjectResolver resolver = new HierarchicalObjectResolver(
+                        context,
+                        metadata,
+                        descriptor, true);
                 objects = resolver.synchronizedObjectsFromDataRows(
                         prefetchTree,
                         rowsColumn,
@@ -660,7 +680,6 @@
 
             List<Object> rsMapping = metadata.getResultSetMapping();
             int width = rsMapping.size();
-
             // no conversions needed for scalar positions; reuse Object[]'s to fill them
             // with resolved objects
             List<List<?>> resultLists = new ArrayList<List<?>>(width);
@@ -670,16 +689,27 @@
                     EntityResultSegment entitySegment = (EntityResultSegment) rsMapping
                             .get(i);
                     List<Persistent> nextResult = toObjects(entitySegment
-                            .getClassDescriptor(), null, mainRows, i);
+                            .getClassDescriptor(), metadata.getPrefetchTree() , mainRows, i);
+                    
                     resultLists.add(nextResult);
-
+                    
                     for (int j = 0; j < rowsLen; j++) {
                         Object[] row = mainRows.get(j);
                         row[i] = nextResult.get(j);
                     }
                 }
             }
-
+            Set<List<?>> seen = new HashSet(mainRows.size());
+            Iterator<Object[]> it = mainRows.iterator();
+            while (it.hasNext()) {
+                
+                if (!seen.add(Arrays.asList(it.next()))) {
+                    it.remove();
+                    
+                }
+                
+            }
+            
             // invoke callbacks now that all objects are resolved...
             LifecycleCallbackRegistry callbackRegistry = context
                     .getEntityResolver()

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java Sat Jan 23 08:04:01 2010
@@ -32,6 +32,7 @@
 import org.apache.cayenne.query.PrefetchProcessor;
 import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.reflect.ClassDescriptor;
 
 /**
  * Processes a number of DataRow sets corresponding to a given prefetch tree, resolving
@@ -43,6 +44,8 @@
     DataContext context;
     QueryMetadata queryMetadata;
     DataRowStore cache;
+    ClassDescriptor descriptor;
+    boolean needToSaveDuplicates;
 
     HierarchicalObjectResolver(DataContext context, QueryMetadata queryMetadata) {
         this.queryMetadata = queryMetadata;
@@ -50,6 +53,13 @@
         this.cache = context.getObjectStore().getDataRowCache();
     }
 
+    HierarchicalObjectResolver(DataContext context, QueryMetadata metadata,
+            ClassDescriptor descriptor, boolean needToSaveDuplicates) {
+        this(context, metadata);
+        this.descriptor = descriptor;
+        this.needToSaveDuplicates = needToSaveDuplicates;
+    }
+
     /**
      * Properly synchronized version of 'resolveObjectTree'.
      */
@@ -175,7 +185,7 @@
 
             // TODO: see TODO in ObjectResolver.relatedObjectsFromDataRows
 
-            if (node.isDisjointPrefetch()) {
+            if (node.isDisjointPrefetch() && !needToSaveDuplicates) {
                 PrefetchProcessorNode processorNode = (PrefetchProcessorNode) node;
                 if (processorNode.isJointChildren()) {
                     List<Persistent> objects = processorNode.getObjects();

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=902364&r1=902363&r2=902364&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 Sat Jan 23 08:04:01 2010
@@ -36,6 +36,7 @@
     private PrefetchProcessorNode root;
     private LinkedList<PrefetchProcessorNode> nodeStack;
 
+    private ClassDescriptor descriptor;
     private List mainResultRows;
     private Map extraResultsByPath;
 
@@ -45,6 +46,7 @@
         this.queryMetadata = objectTreeResolver.queryMetadata;
         this.mainResultRows = mainResultRows;
         this.extraResultsByPath = extraResultsByPath;
+        this.descriptor=objectTreeResolver.descriptor;
     }
 
     PrefetchProcessorNode buildTree(PrefetchTreeNode tree) {
@@ -147,7 +149,11 @@
         }
         else {
             arc = null;
-            descriptor = queryMetadata.getClassDescriptor();
+            if(this.descriptor!=null){
+                descriptor=this.descriptor;
+            }else{
+                descriptor = queryMetadata.getClassDescriptor();
+            }
             rows = mainResultRows;
         }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java Sat Jan 23 08:04:01 2010
@@ -21,7 +21,9 @@
 import org.apache.cayenne.ejbql.EJBQLBaseVisitor;
 import org.apache.cayenne.ejbql.EJBQLExpression;
 import org.apache.cayenne.ejbql.parser.EJBQLFromItem;
+import org.apache.cayenne.ejbql.parser.EJBQLInnerFetchJoin;
 import org.apache.cayenne.ejbql.parser.EJBQLJoin;
+import org.apache.cayenne.ejbql.parser.EJBQLOuterFetchJoin;
 
 /**
  * @since 3.0
@@ -66,8 +68,14 @@
 
     @Override
     public boolean visitInnerFetchJoin(EJBQLJoin join) {
-        // TODO: andrus, 4/9/2007 - support for prefetching
-        throw new UnsupportedOperationException("Fetch joins are not yet supported");
+        joinAppender.appendInnerJoin(
+                null,
+                new EJBQLTableId(join.getLeftHandSideId()),
+                new EJBQLTableId(((EJBQLInnerFetchJoin) join).getRightHandSideId()));
+
+        context.markCurrentPosition(EJBQLJoinAppender
+                .makeJoinTailMarker(((EJBQLInnerFetchJoin) join).getRightHandSideId()));
+        return false;
     }
 
     @Override
@@ -84,8 +92,14 @@
 
     @Override
     public boolean visitOuterFetchJoin(EJBQLJoin join) {
-        // TODO: andrus, 4/9/2007 - support for prefetching
-        throw new UnsupportedOperationException("Fetch joins are not yet supported");
+        joinAppender.appendOuterJoin(
+                null,
+                new EJBQLTableId(join.getLeftHandSideId()),
+                new EJBQLTableId(((EJBQLOuterFetchJoin) join).getRightHandSideId()));
+
+        context.markCurrentPosition(EJBQLJoinAppender
+                .makeJoinTailMarker(((EJBQLOuterFetchJoin) join).getRightHandSideId()));
+        return false;
     }
 
     @Override

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLIdentifierColumnsTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLIdentifierColumnsTranslator.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLIdentifierColumnsTranslator.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLIdentifierColumnsTranslator.java Sat Jan 23 08:04:01 2010
@@ -27,12 +27,16 @@
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.ejbql.EJBQLBaseVisitor;
 import org.apache.cayenne.ejbql.EJBQLExpression;
+import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.PathComponent;
+import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.reflect.ArcProperty;
 import org.apache.cayenne.reflect.AttributeProperty;
 import org.apache.cayenne.reflect.ClassDescriptor;
@@ -151,9 +155,58 @@
             appendColumn(idVar, attribute, attribute.getDbAttribute(), fields);
         }
 
+        addPrefetchedColumnsIfAny(idVar);
+        
         return false;
     }
 
+    private void addPrefetchedColumnsIfAny(final String visitedIdentifier) {
+        PrefetchTreeNode prefetchTree = context.getCompiledExpression().getPrefetchTree();
+        if (prefetchTree != null) {
+            for (PrefetchTreeNode prefetch : prefetchTree.adjacentJointNodes()) {
+                ClassDescriptor descriptor = context.getEntityDescriptor(prefetch
+                        .getEjbqlPathEntityId());
+                if (visitedIdentifier.equals(prefetch.getEjbqlPathEntityId())) {
+                    DbEntity table = descriptor.getRootDbEntities().iterator().next();
+                    ObjEntity objectEntity = descriptor.getEntity();
+                    prefetch.setEntityName(objectEntity.getName());
+                    Expression prefetchExp = Expression.fromString(prefetch.getPath());
+                    Expression dbPrefetch = objectEntity.translateToDbPath(prefetchExp);
+
+                    DbRelationship r = null;
+                    for (PathComponent<DbAttribute, DbRelationship> component : table
+                            .resolvePath(dbPrefetch, context
+                                    .getMetadata()
+                                    .getPathSplitAliases())) {
+                        r = component.getRelationship();
+
+                    }
+
+                    if (r == null) {
+                        throw new CayenneRuntimeException("Invalid joint prefetch '"
+                                + prefetch
+                                + "' for entity: "
+                                + objectEntity.getName());
+                    }
+
+                    Iterator<DbAttribute> targetAttributes = (Iterator<DbAttribute>) r
+                            .getTargetEntity()
+                            .getAttributes()
+                            .iterator();
+                    while (targetAttributes.hasNext()) {
+                        DbAttribute attribute = targetAttributes.next();
+                        appendColumn(prefetch.getEjbqlPathEntityId()
+                                + "."
+                                + prefetch.getPath(), attribute, "", prefetch.getPath()
+                                + "."
+                                + attribute.getName(), null);
+
+                    }
+                }
+            }
+        }
+    }
+
     public void appendColumn(
             String identifier,
             ObjAttribute property,
@@ -168,6 +221,20 @@
             DbAttribute column,
             Map<String, String> fields,
             String javaType) {
+        String columnLabel = "";
+        if (context.isAppendingResultColumns()) {
+               columnLabel = fields.get(property != null ? property
+                        .getDbAttributePath() : column.getName());
+        }
+        appendColumn(identifier, column, columnLabel, columnLabel, javaType);
+    }
+    
+    public void appendColumn(
+            String identifier,
+            DbAttribute column,
+            String columnAlias,
+            String dataRowKey,
+            String javaType) {
 
         DbEntity table = (DbEntity) column.getEntity();
         String alias = context.getTableAlias(identifier, table.getFullyQualifiedName());
@@ -190,18 +257,15 @@
                     javaType = TypesMapping.getJavaBySqlType(column.getType());
                 }
 
-                String columnLabel = fields.get(property != null ? property
-                        .getDbAttributePath() : column.getName());
-
                 // TODO: andrus 6/27/2007 - the last parameter is an unofficial "jdbcType"
                 // pending CAY-813 implementation, switch to #column directive
                 context
                         .append("' '")
                         .append(javaType)
                         .append("' '")
-                        .append(columnLabel)
+                        .append(columnAlias)
                         .append("' '")
-                        .append(columnLabel)
+                        .append(dataRowKey)
                         .append("' " + column.getType())
                         .append(")");
             }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java Sat Jan 23 08:04:01 2010
@@ -104,26 +104,11 @@
                     context.append(!first ? ", " : " ");
 
                     DbAttribute dbAttribute = it.next();
-
-                    if (context.isAppendingResultColumns()) {
-                        context.append(" #result('");
-                    }
-                    else {
-                        context.append(' ');
-                    }
-
-                    context.append(alias).append('.').append(dbAttribute.getName());
-
-                    if (context.isAppendingResultColumns()) {
-
-                        String javaType = TypesMapping.getJavaBySqlType(dbAttribute
-                                .getType());
-                        String columnLabel = fields.get(dbAttribute.getName());
-
-                        context.append("' '").append(javaType).append("' '").append(
-                                columnLabel).append("' '").append(columnLabel).append(
-                                "' " + dbAttribute.getType()).append(")");
-                    }
+                    appendColumn(
+                            TypesMapping.getJavaBySqlType(dbAttribute.getType()),
+                            alias,
+                            dbAttribute,
+                            fields!=null?fields.get(dbAttribute.getName()):"");
 
                     first = false;
                 }
@@ -151,11 +136,13 @@
                         else if (pathPart instanceof DbAttribute) {
                             DbAttribute dbAttribute = (DbAttribute) pathPart;
                             appendColumn(
-                                    attribute,
+                                    attribute.getType(),
                                     context.getTableAlias(
                                             lhsId.getEntityId(),
                                             dbAttribute.getEntity().getName()),
-                                    dbAttribute);
+                                    dbAttribute,
+                                    context.isAppendingResultColumns() ? context
+                                            .nextColumnAlias() : "");
 
                         }
 
@@ -166,38 +153,8 @@
 
                     DbAttribute dbAttribute = attribute.getDbAttribute();
 
-                    appendColumn(attribute, alias, dbAttribute);
-                }
-            }
-
-            private void appendColumn(
-                    ObjAttribute attribute,
-                    String alias,
-                    DbAttribute dbAttribute) {
-                if (context.isAppendingResultColumns()) {
-                    context.append(" #result('");
-                }
-                else {
-                    context.append(' ');
-                }
-
-                context.append(alias).append('.').append(dbAttribute.getName());
-
-                if (context.isAppendingResultColumns()) {
-                    String columnAlias = context.nextColumnAlias();
-
-                    // TODO: andrus 6/27/2007 - the last parameter is an unofficial
-                    // "jdbcType"
-                    // pending CAY-813 implementation, switch to #column directive
-                    context
-                            .append("' '")
-                            .append(attribute.getType())
-                            .append("' '")
-                            .append(columnAlias)
-                            .append("' '")
-                            .append(columnAlias)
-                            .append("' " + dbAttribute.getType())
-                            .append(")");
+                    appendColumn(attribute.getType(), alias, dbAttribute, context
+                            .isAppendingResultColumns() ? context.nextColumnAlias() : "");
                 }
             }
 
@@ -213,4 +170,37 @@
         return false;
     }
 
+    public void appendColumn(
+            String javaType,
+            String alias,
+            DbAttribute dbAttribute,
+            String columnAlias) {
+        if (context.isAppendingResultColumns()) {
+            context.append(" #result('");
+        }
+        else {
+            context.append(' ');
+        }
+
+        context.append(alias).append('.').append(dbAttribute.getName());
+
+        if (context.isAppendingResultColumns()) {
+            // String columnAlias = context.nextColumnAlias();
+
+            // TODO: andrus 6/27/2007 - the last parameter is an unofficial
+            // "jdbcType"
+            // pending CAY-813 implementation, switch to #column directive
+            context
+                    .append("' '")
+                    .append(javaType)
+                    .append("' '")
+                    .append(columnAlias)
+                    .append("' '")
+                    .append(columnAlias)
+                    .append("' " + dbAttribute.getType())
+                    .append(")");
+        }
+
+    }
+
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectTranslator.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectTranslator.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectTranslator.java Sat Jan 23 08:04:01 2010
@@ -116,4 +116,5 @@
         expression.visit(context.getTranslatorFactory().getConditionTranslator(context));
         return false;
     }
+
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EntityRowReader.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EntityRowReader.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EntityRowReader.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EntityRowReader.java Sat Jan 23 08:04:01 2010
@@ -65,7 +65,12 @@
             // query translator may change the order of fields compare to the entity
             // result, so figure out DataRow labels by doing reverse lookup of
             // RowDescriptor labels...
-            labels[i] = segmentMetadata.getColumnPath(columns[startIndex + i].getDataRowKey());
+            if(columns[startIndex + i].getDataRowKey().contains(".")){
+                // if the dataRowKey contains ".", it is prefetched column and we can use it instead of search the name by alias
+                labels[i]=columns[startIndex + i].getDataRowKey();
+            }else{
+                labels[i] = segmentMetadata.getColumnPath(columns[startIndex + i].getDataRowKey());
+            }
         }
     }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/EJBQLCompiledExpression.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/EJBQLCompiledExpression.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/EJBQLCompiledExpression.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/EJBQLCompiledExpression.java Sat Jan 23 08:04:01 2010
@@ -22,6 +22,7 @@
 
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.SQLResult;
+import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.reflect.ClassDescriptor;
 
 /**
@@ -63,4 +64,9 @@
      * expression.
      */
     SQLResult getResult();
+    
+    /**
+     * Returns prefetched columns tree for fetch joins.
+     */
+    PrefetchTreeNode getPrefetchTree();
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/CompiledExpression.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/CompiledExpression.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/CompiledExpression.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/CompiledExpression.java Sat Jan 23 08:04:01 2010
@@ -27,6 +27,7 @@
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.map.SQLResult;
+import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.reflect.ClassDescriptor;
 
 /**
@@ -42,7 +43,9 @@
     private Map<String, ObjRelationship> incomingById;
     private EJBQLExpression expression;
     private SQLResult result;
-
+    private PrefetchTreeNode prefetchTree;
+    
+    
     public ClassDescriptor getEntityDescriptor(String idVariable) {
         if (idVariable == null) {
             return null;
@@ -99,4 +102,12 @@
     void setResult(SQLResult resultSetMapping) {
         this.result = resultSetMapping;
     }
+    
+    public PrefetchTreeNode getPrefetchTree() {
+        return prefetchTree;
+    }
+
+    public void setPrefetchTree(PrefetchTreeNode prefetchTree) {
+        this.prefetchTree = prefetchTree;
+    }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/Compiler.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/Compiler.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/Compiler.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/Compiler.java Sat Jan 23 08:04:01 2010
@@ -40,6 +40,7 @@
 import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.map.SQLResult;
+import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.reflect.ArcProperty;
 import org.apache.cayenne.reflect.AttributeProperty;
 import org.apache.cayenne.reflect.ClassDescriptor;
@@ -69,7 +70,8 @@
     private EJBQLExpressionVisitor pathVisitor;
     private EJBQLExpressionVisitor rootDescriptorVisitor;
     private List<Object> resultComponents;
-
+    private PrefetchTreeNode prefetchTree = null;
+    
     Compiler(EntityResolver resolver) {
         this.resolver = resolver;
         this.descriptorsById = new HashMap<String, ClassDescriptor>();
@@ -85,7 +87,7 @@
         parsed.visit(new CompilationVisitor());
 
         Map<EJBQLPath, Integer> pathsInSelect = new HashMap<EJBQLPath, Integer>();
-
+       
         if (parsed != null) {
             for (int i = 0; i < parsed.getChildrenCount(); i++) {
                 if (parsed.getChild(i) instanceof EJBQLSelectClause) {
@@ -129,10 +131,8 @@
                 }
             }
         }
-
         // postprocess paths, now that all id vars are resolved
         if (paths != null) {
-            int elenent = 0;
             for (EJBQLPath path : paths) {
                 String id = normalizeIdPath(path.getId());
                 ClassDescriptor descriptor = descriptorsById.get(id);
@@ -156,7 +156,7 @@
                         incoming = ((ArcProperty) property).getRelationship();
                         descriptor = ((ArcProperty) property).getTargetDescriptor();
                         pathRelationshipString = buffer.substring(0, buffer.length());
-
+                        
                         descriptorsById.put(pathRelationshipString, descriptor);
                         incomingById.put(pathRelationshipString, incoming);
                         
@@ -178,11 +178,12 @@
                         resultComponents.add(pathsInSelect.get(path).intValue(), ident);
                         rootId = pathRelationshipString;
                     }
-                };
-                elenent++;
+                }
             }
         }
-
+       
+        
+        
         CompiledExpression compiled = new CompiledExpression();
         compiled.setExpression(parsed);
         compiled.setSource(source);
@@ -190,7 +191,8 @@
         compiled.setRootId(rootId);
         compiled.setDescriptorsById(descriptorsById);
         compiled.setIncomingById(incomingById);
-
+        compiled.setPrefetchTree(prefetchTree);
+        
         if (resultComponents != null) {
             SQLResult mapping = new SQLResult();
 
@@ -200,9 +202,27 @@
                     mapping.addColumnResult((String) nextMapping);
                 }
                 else if (nextMapping instanceof EJBQLExpression) {
-                    mapping.addEntityResult(compileEntityResult(
+                    EntityResult compileEntityResult = compileEntityResult(
                             (EJBQLExpression) nextMapping,
-                            i));
+                            i);
+                    if (prefetchTree != null) {
+                        for (PrefetchTreeNode prefetch : prefetchTree.getChildren()) {
+                            if (((EJBQLExpression) nextMapping).getText().equals(
+                                    prefetch.getEjbqlPathEntityId())) {
+                                EJBQLIdentifier ident = new EJBQLIdentifier(0);
+                                ident.text = prefetch.getEjbqlPathEntityId()
+                                        + "."
+                                        + prefetch.getPath();
+
+                                compileEntityResult = compileEntityResultWithPrefetch(
+                                        compileEntityResult,
+                                        ident);
+
+                            }
+                        }
+                    }
+                    mapping.addEntityResult(compileEntityResult);
+                    
                 }
             }
 
@@ -213,6 +233,72 @@
         return compiled;
     }
 
+    private EntityResult compileEntityResultWithPrefetch(EntityResult compiledResult, EJBQLExpression prefetchExpression){
+        final EntityResult result = compiledResult;
+        String id = prefetchExpression.getText().toLowerCase();
+        ClassDescriptor descriptor = descriptorsById.get(id);
+        if (descriptor == null) {
+            descriptor = descriptorsById.get(prefetchExpression.getText());
+        }
+        final String prefix = prefetchExpression.getText().substring(prefetchExpression.getText().indexOf(".")+1);
+      
+        final Set<String> visited = new HashSet<String>();
+
+        PropertyVisitor visitor = new PropertyVisitor() {
+
+            public boolean visitAttribute(AttributeProperty property) {
+                ObjAttribute oa = property.getAttribute();
+                if (visited.add(oa.getDbAttributePath())) {
+                    result.addObjectField(
+                            oa.getEntity().getName(),
+                            "fetch."+prefix+"."+oa.getName(),
+                            prefix +"."+ oa.getDbAttributeName());
+                }
+                return true;
+            }
+
+            public boolean visitToMany(ToManyProperty property) {
+                return true;
+            }
+
+            public boolean visitToOne(ToOneProperty property) {
+                ObjRelationship rel = property.getRelationship();
+                DbRelationship dbRel = rel.getDbRelationships().get(0);
+
+                for (DbJoin join : dbRel.getJoins()) {
+                    DbAttribute src = join.getSource();
+                    if (src.isForeignKey() && visited.add(src.getName())) {
+                        result.addDbField("fetch."+prefix+"."+src.getName(), prefix +"."+ src.getName());
+                    }
+                }
+
+                return true;
+            }
+        };
+
+        descriptor.visitAllProperties(visitor);
+
+        // append id columns ... (some may have been appended already via relationships)
+        for (String pkName : descriptor.getEntity().getPrimaryKeyNames()) {
+            if (visited.add(pkName)) {
+                result.addDbField("fetch."+prefix+"."+pkName, prefix +"."+ pkName);
+            }
+        }
+
+        // append inheritance discriminator columns...
+        Iterator<ObjAttribute> discriminatorColumns = descriptor
+                .getDiscriminatorColumns();
+        while (discriminatorColumns.hasNext()) {
+            ObjAttribute column = discriminatorColumns.next();
+
+            if (visited.add(column.getName())) {
+                result.addDbField("fetch."+prefix+"."+column.getDbAttributePath(), prefix +"."+ column.getDbAttributePath());
+            }
+        }
+
+        return result;
+    }
+    
     private EntityResult compileEntityResult(EJBQLExpression expression, int position) {
         String id = expression.getText().toLowerCase();
         ClassDescriptor descriptor = descriptorsById.get(id);
@@ -329,6 +415,7 @@
 
         @Override
         public boolean visitInnerFetchJoin(EJBQLJoin join) {
+            prepareFetchJoin(join);
             join.visit(joinVisitor);
             return false;
         }
@@ -341,6 +428,7 @@
 
         @Override
         public boolean visitOuterFetchJoin(EJBQLJoin join) {
+            prepareFetchJoin(join);
             join.visit(joinVisitor);
             return false;
         }
@@ -370,6 +458,20 @@
         public boolean visitSubselect(EJBQLExpression expression) {
             return super.visitSubselect(expression);
         }
+        
+        private void prepareFetchJoin(EJBQLJoin join) {
+            if (prefetchTree == null) {
+                prefetchTree = new PrefetchTreeNode();
+            }
+            EJBQLPath fetchJoin = (EJBQLPath) join.getChild(0);
+            addPath(fetchJoin);
+
+            PrefetchTreeNode node = prefetchTree.addPath(fetchJoin.getRelativePath());
+            node.setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
+            node.setPhantom(false);
+            node.setEjbqlPathEntityId(fetchJoin.getChild(0).getText());
+        }
+
     }
 
     class FromItemVisitor extends EJBQLBaseVisitor {

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLInnerFetchJoin.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLInnerFetchJoin.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLInnerFetchJoin.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLInnerFetchJoin.java Sat Jan 23 08:04:01 2010
@@ -33,4 +33,15 @@
     protected boolean visitNode(EJBQLExpressionVisitor visitor) {
         return visitor.visitInnerFetchJoin(this);
     }
+    
+    @Override
+    public String getRightHandSideId() {
+        int len = getChildrenCount();
+        if (len < 1) {
+            return null;
+        }
+
+        EJBQLPath path = (EJBQLPath) getChild(0);
+        return path.getAbsolutePath();
+    }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLOuterFetchJoin.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLOuterFetchJoin.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLOuterFetchJoin.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/ejbql/parser/EJBQLOuterFetchJoin.java Sat Jan 23 08:04:01 2010
@@ -33,4 +33,15 @@
     protected boolean visitNode(EJBQLExpressionVisitor visitor) {
         return visitor.visitOuterFetchJoin(this);
     }
+    
+    @Override
+    public String getRightHandSideId() {
+        int len = getChildrenCount();
+        if (len < 1) {
+            return null;
+        }
+
+        EJBQLPath path = (EJBQLPath) getChild(0);
+        return path.getAbsolutePath();
+    }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/DefaultEntityResultSegment.java Sat Jan 23 08:04:01 2010
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.map;
 
+import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.cayenne.query.EntityResultSegment;

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityResult.java Sat Jan 23 08:04:01 2010
@@ -58,12 +58,11 @@
         }
 
         Map<String, String> dbFields = new HashMap<String, String>();
-        for (FieldResult field : fields) {
 
-            if (field.isDbAttribute()) {
+        for (FieldResult field : fields) {
+            if(field.isDbAttribute()||field.getAttributeName().startsWith("fetch.")){
                 dbFields.put(field.getAttributeName(), field.getColumn());
-            }
-            else {
+            } else {
                 ObjEntity entity = field.getEntityName() != null ? resolver
                         .getObjEntity(field.getEntityName()) : getRootEntity(resolver);
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQuery.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQuery.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQuery.java Sat Jan 23 08:04:01 2010
@@ -125,7 +125,7 @@
             throws EJBQLException {
         if (expression == null) {
             this.expression = EJBQLParserFactory.getParser().compile(
-                    ejbqlStatement,
+                    ejbqlStatement, 
                     resolver);
         }
 
@@ -270,4 +270,6 @@
     public int getStatementFetchSize() {
         return metadata.getStatementFetchSize();
     }
+    
+    
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/EJBQLQueryMetadata.java Sat Jan 23 08:04:01 2010
@@ -36,7 +36,7 @@
 
     boolean resolve(EntityResolver resolver, EJBQLQuery query) {
         EJBQLCompiledExpression expression = query.getExpression(resolver);
-
+        setPrefetchTree(expression.getPrefetchTree());
         resultSetMapping = expression.getResult() != null ? expression
                 .getResult()
                 .getResolvedComponents(resolver) : null;

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java?rev=902364&r1=902363&r2=902364&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java Sat Jan 23 08:04:01 2010
@@ -45,6 +45,8 @@
     protected String name;
     protected boolean phantom;
     protected int semantics;
+    protected String ejbqlPathEntityId;
+    protected String entityName;
 
     // transient parent allows cloning parts of the tree via serialization
     protected transient PrefetchTreeNode parent;
@@ -343,6 +345,23 @@
     public boolean isDisjointPrefetch() {
         return semantics == DISJOINT_PREFETCH_SEMANTICS;
     }
+    
+    public String getEjbqlPathEntityId() {
+        return ejbqlPathEntityId;
+    }
+
+    public void setEjbqlPathEntityId(String ejbqlPathEntityId) {
+        this.ejbqlPathEntityId = ejbqlPathEntityId;
+    }
+
+    public String getEntityName() {
+        return entityName;
+    }
+    
+    public void setEntityName(String entityName) {
+        this.entityName = entityName;
+    }
+    
 
     // **** custom serialization that supports serializing subtrees...
 

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFetchJoinTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFetchJoinTest.java?rev=902364&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFetchJoinTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFetchJoinTest.java Sat Jan 23 08:04:01 2010
@@ -0,0 +1,362 @@
+/*****************************************************************
+ *   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.Iterator;
+import java.util.List;
+
+import org.apache.art.Artist;
+import org.apache.art.ArtistExhibit;
+import org.apache.art.Gallery;
+import org.apache.art.Painting;
+import org.apache.cayenne.PersistenceState;
+import org.apache.cayenne.ValueHolder;
+import org.apache.cayenne.query.EJBQLQuery;
+import org.apache.cayenne.unit.CayenneCase;
+
+public class DataContextEJBQLFetchJoinTest extends CayenneCase {
+
+    @Override
+    protected void setUp() throws Exception {
+        deleteTestData();
+    }
+
+    public void testFetchJoinForOneEntity() throws Exception {
+        createTestData("testOneEntityFetchJoin");
+        String ejbql = "SELECT a FROM Artist a JOIN FETCH a.paintingArray ";
+
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+
+        DataContext context = createDataContext();
+
+        List objects = context.performQuery(query);
+
+        blockQueries();
+        try {
+            assertEquals(2, objects.size());
+
+            Iterator it = objects.iterator();
+            while (it.hasNext()) {
+                Artist a = (Artist) it.next();
+                List list = a.getPaintingArray();
+
+                assertNotNull(list);
+                assertFalse(((ValueHolder) list).isFault());
+
+                Iterator children = list.iterator();
+                while (children.hasNext()) {
+                    Painting p = (Painting) children.next();
+                    assertEquals(PersistenceState.COMMITTED, p.getPersistenceState());
+                    // make sure properties are not null..
+                    assertNotNull(p.getPaintingTitle());
+                }
+            }
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testSeveralFetchJoins() throws Exception {
+        createTestData("testSeveralFetchJoins");
+        String ejbql = "SELECT a "
+                + "FROM Artist a JOIN FETCH a.paintingArray JOIN FETCH a.artistExhibitArray "
+                + "WHERE a.artistName = 'A1'";
+
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+
+        DataContext context = createDataContext();
+
+        List objects = context.performQuery(query);
+
+        blockQueries();
+        try {
+            assertEquals(1, objects.size());
+
+            Artist a = (Artist) objects.get(0);
+            assertEquals("A1", a.getArtistName());
+
+            List<Painting> paintings = a.getPaintingArray();
+
+            assertNotNull(paintings);
+            assertFalse(((ValueHolder) paintings).isFault());
+            assertEquals(2, paintings.size());
+
+            List<String> expectedPaintingsNames = new ArrayList<String>();
+            expectedPaintingsNames.add("P11");
+            expectedPaintingsNames.add("P12");
+
+            Iterator<Painting> paintingsIterator = paintings.iterator();
+            while (paintingsIterator.hasNext()) {
+                Painting p = paintingsIterator.next();
+                assertEquals(PersistenceState.COMMITTED, p.getPersistenceState());
+                assertNotNull(p.getPaintingTitle());
+                assertTrue(expectedPaintingsNames.contains(p.getPaintingTitle()));
+            }
+
+            List<ArtistExhibit> exibits = a.getArtistExhibitArray();
+
+            assertNotNull(exibits);
+            assertFalse(((ValueHolder) exibits).isFault());
+            assertEquals(2, exibits.size());
+
+            Iterator<ArtistExhibit> exibitsIterator = exibits.iterator();
+            while (exibitsIterator.hasNext()) {
+                ArtistExhibit ae = exibitsIterator.next();
+                assertEquals(PersistenceState.COMMITTED, ae.getPersistenceState());
+                assertNotNull(ae.getObjectId());
+
+            }
+
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testSeveralEntitiesFetchJoins() throws Exception {
+        createTestData("testSeveralFetchJoins");
+        String ejbql = "SELECT DISTINCT a , g "
+                + "FROM Artist a JOIN FETCH a.paintingArray , Gallery g JOIN FETCH g.exhibitArray "
+                + "WHERE a.artistName='A1' AND g.galleryName='gallery1'";
+
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+
+        DataContext context = createDataContext();
+
+        List objects = context.performQuery(query);
+
+        blockQueries();
+        try {
+            assertNotNull(objects);
+            assertFalse(objects.isEmpty());
+            assertEquals(1, objects.size());
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testSeveralEntitiesAndScalarFetchInnerJoins() throws Exception {
+        createTestData("testSeveralFetchJoins");
+        String ejbql = "SELECT DISTINCT a, a.artistName , g "
+                + "FROM Artist a JOIN FETCH a.paintingArray, Gallery g JOIN FETCH g.exhibitArray "
+                + "ORDER BY a.artistName";
+
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+
+        DataContext context = createDataContext();
+
+        List objects = context.performQuery(query);
+
+        blockQueries();
+        try {
+            assertEquals(2, objects.size());
+
+            Object[] firstRow = (Object[]) objects.get(0);
+            Artist a = (Artist) firstRow[0];
+            assertEquals("A1", a.getArtistName());
+
+            List<Painting> paintings = a.getPaintingArray();
+
+            assertNotNull(paintings);
+            assertFalse(((ValueHolder) paintings).isFault());
+            assertEquals(2, paintings.size());
+
+            List<String> expectedPaintingsNames = new ArrayList<String>();
+            expectedPaintingsNames.add("P11");
+            expectedPaintingsNames.add("P12");
+
+            Iterator<Painting> paintingsIterator = paintings.iterator();
+            while (paintingsIterator.hasNext()) {
+                Painting p = paintingsIterator.next();
+                assertEquals(PersistenceState.COMMITTED, p.getPersistenceState());
+                assertNotNull(p.getPaintingTitle());
+                assertTrue(expectedPaintingsNames.contains(p.getPaintingTitle()));
+            }
+            String artistName = (String) firstRow[1];
+            assertEquals("A1", artistName);
+
+            Gallery g1 = (Gallery) firstRow[2];
+            assertEquals("gallery1", g1.getGalleryName());
+
+            List exibits = g1.getExhibitArray();
+
+            assertNotNull(exibits);
+            assertFalse(((ValueHolder) exibits).isFault());
+            assertEquals(2, exibits.size());
+
+            Object[] secondRow = (Object[]) objects.get(1);
+            a = (Artist) secondRow[0];
+            assertEquals("A2", a.getArtistName());
+
+            paintings = a.getPaintingArray();
+
+            assertNotNull(paintings);
+            assertFalse(((ValueHolder) paintings).isFault());
+            assertEquals(1, paintings.size());
+
+            expectedPaintingsNames = new ArrayList<String>();
+            expectedPaintingsNames.add("P2");
+
+            paintingsIterator = paintings.iterator();
+            while (paintingsIterator.hasNext()) {
+                Painting p = paintingsIterator.next();
+                assertEquals(PersistenceState.COMMITTED, p.getPersistenceState());
+                assertNotNull(p.getPaintingTitle());
+                assertTrue(expectedPaintingsNames.contains(p.getPaintingTitle()));
+            }
+            artistName = (String) secondRow[1];
+            assertEquals("A2", artistName);
+
+            Gallery g2 = (Gallery) secondRow[2];
+            assertEquals(g1, g2);
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testSeveralEntitiesAndScalarFetchOuterJoins() throws Exception {
+        createTestData("testSeveralFetchJoins");
+        String ejbql = "SELECT DISTINCT a, a.artistName , g "
+                + "FROM Artist a LEFT JOIN FETCH a.paintingArray, Gallery g LEFT JOIN FETCH g.exhibitArray "
+                + "ORDER BY a.artistName";
+
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+
+        DataContext context = createDataContext();
+
+        List objects = context.performQuery(query);
+
+        blockQueries();
+        try {
+            assertEquals(6, objects.size());
+
+            Object[] row = (Object[]) objects.get(0);
+            Artist a1 = (Artist) row[0];
+            assertEquals("A1", a1.getArtistName());
+
+            List<Painting> paintings = a1.getPaintingArray();
+
+            assertNotNull(paintings);
+            assertFalse(((ValueHolder) paintings).isFault());
+            assertEquals(2, paintings.size());
+
+            List<String> expectedPaintingsNames = new ArrayList<String>();
+            expectedPaintingsNames.add("P11");
+            expectedPaintingsNames.add("P12");
+
+            Iterator<Painting> paintingsIterator = paintings.iterator();
+            while (paintingsIterator.hasNext()) {
+                Painting p = paintingsIterator.next();
+                assertEquals(PersistenceState.COMMITTED, p.getPersistenceState());
+                assertNotNull(p.getPaintingTitle());
+                assertTrue(expectedPaintingsNames.contains(p.getPaintingTitle()));
+            }
+            String artistName1 = (String) row[1];
+            assertEquals("A1", artistName1);
+
+            Gallery g1 = (Gallery) row[2];
+            assertEquals("gallery1", g1.getGalleryName());
+
+            List exibits = g1.getExhibitArray();
+
+            assertNotNull(exibits);
+            assertFalse(((ValueHolder) exibits).isFault());
+            assertEquals(2, exibits.size());
+
+            row = (Object[]) objects.get(1);
+
+            assertEquals(a1, (Artist) row[0]);
+            assertEquals(artistName1, row[1]);
+
+            Gallery g2 = (Gallery) row[2];
+            assertEquals("gallery2", g2.getGalleryName());
+
+            exibits = g2.getExhibitArray();
+
+            assertTrue(exibits.isEmpty());
+
+            row = (Object[]) objects.get(2);
+
+            Artist a2 = (Artist) row[0];
+            assertEquals("A2", a2.getArtistName());
+
+            paintings = a2.getPaintingArray();
+
+            assertNotNull(paintings);
+            assertEquals(1, paintings.size());
+
+            Painting p = paintings.get(0);
+            assertEquals(PersistenceState.COMMITTED, p.getPersistenceState());
+            assertNotNull(p.getPaintingTitle());
+            assertEquals("P2", p.getPaintingTitle());
+
+            String artistName2 = (String) row[1];
+            assertEquals("A2", artistName2);
+            assertEquals(g2, row[2]);
+
+            row = (Object[]) objects.get(3);
+
+            assertEquals(a2, (Artist) row[0]);
+            assertEquals(artistName2, row[1]);
+            assertEquals(g1, row[2]);
+
+            row = (Object[]) objects.get(4);
+
+            Artist a3 = (Artist) row[0];
+            assertEquals("A3", a3.getArtistName());
+
+            paintings = a3.getPaintingArray();
+
+            assertTrue(paintings.isEmpty());
+
+            String artistName3 = (String) row[1];
+            assertEquals("A3", artistName3);
+            assertEquals(g1, row[2]);
+
+            row = (Object[]) objects.get(5);
+
+            assertEquals(a3, (Artist) row[0]);
+            assertEquals(artistName3, row[1]);
+            assertEquals(g2, row[2]);
+        }
+        finally {
+            unblockQueries();
+        }
+    }
+
+    public void testFlattenedFetchJoins() throws Exception {
+        createTestData("testSeveralFetchJoins");
+        String ejbql = "SELECT a "
+                + "FROM Artist a JOIN FETCH a.paintingArray.toPaintingInfo";
+
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+
+        DataContext context = createDataContext();
+
+        // TODO fails because of error translating of chain join relationship
+        /*
+         * List objects = context.performQuery(query); blockQueries(); try { } finally {
+         * unblockQueries(); }
+         */
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFetchJoinTest.xml
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFetchJoinTest.xml?rev=902364&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFetchJoinTest.xml (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFetchJoinTest.xml Sat Jan 23 08:04:01 2010
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
+
+<beans default-lazy-init="true">
+	<bean id="A1" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Artist</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>insert into ARTIST (ARTIST_ID, ARTIST_NAME) values (1, 'A1')</value>
+		</constructor-arg>
+	</bean>
+
+	<bean id="A2" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Artist</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>insert into ARTIST (ARTIST_ID, ARTIST_NAME) values (2, 'A2')</value>
+		</constructor-arg>
+	</bean>
+
+	<bean id="A3" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Artist</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>insert into ARTIST (ARTIST_ID, ARTIST_NAME) values (3, 'A3')</value>
+		</constructor-arg>
+	</bean>
+
+	<bean id="P11" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Painting</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>
+				INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE)
+				VALUES (1, 'P11', 1, 3000)
+			</value>
+		</constructor-arg>
+	</bean>
+
+	<bean id="P21" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Painting</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>
+				INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE)
+				VALUES (2, 'P2', 2, 5000)
+			</value>
+		</constructor-arg>
+	</bean>
+
+	<bean id="P12" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Painting</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>
+				INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE)
+				VALUES (3, 'P12', 1, 3000)
+			</value>
+		</constructor-arg>
+	</bean>
+
+	<bean id="P22" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Painting</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>
+				INSERT INTO PAINTING (PAINTING_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE)
+				VALUES (4, 'P22', 2, 3000)
+			</value>
+		</constructor-arg>
+	</bean>
+	
+	<bean id="P13" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Painting</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>
+				INSERT INTO PAINTING (PAINTING_ID, GALLERY_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE)
+				VALUES (5, 1, 'C1', 1, 5000)
+			</value>
+		</constructor-arg>
+	</bean>
+	
+	<bean id="P23" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class">
+			<value>org.apache.art.Painting</value>
+		</constructor-arg>
+		<constructor-arg>
+			<value>
+				INSERT INTO PAINTING (PAINTING_ID, GALLERY_ID, PAINTING_TITLE, ARTIST_ID, ESTIMATED_PRICE)
+				VALUES (6, 2, 'C2', 2, 5000)
+			</value>
+		</constructor-arg>
+	</bean>
+	
+	<bean id="G1" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class"><value>org.apache.art.Gallery</value></constructor-arg>
+		<constructor-arg><value>
+		INSERT INTO GALLERY (GALLERY_ID, GALLERY_NAME) VALUES (1, 'gallery1')
+		</value></constructor-arg>
+	</bean>
+	
+	<bean id="G2" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class"><value>org.apache.art.Gallery</value></constructor-arg>
+		<constructor-arg><value>
+		INSERT INTO GALLERY (GALLERY_ID, GALLERY_NAME) VALUES (2, 'gallery2')
+		</value></constructor-arg>
+	</bean>
+	
+	<bean id="E11" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class"><value>org.apache.art.Exhibit</value></constructor-arg>
+		<constructor-arg><value>
+		INSERT INTO EXHIBIT (EXHIBIT_ID, GALLERY_ID, CLOSING_DATE, OPENING_DATE) VALUES (1, 1, '2009-10-01', '2009-10-02')
+		</value></constructor-arg>
+	</bean>
+	<bean id="E12" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class"><value>org.apache.art.Exhibit</value></constructor-arg>
+		<constructor-arg><value>
+		INSERT INTO EXHIBIT (EXHIBIT_ID, GALLERY_ID, CLOSING_DATE, OPENING_DATE) VALUES (2, 1, '2009-10-01', '2009-10-02')
+		</value></constructor-arg>
+	</bean>
+	<bean id="AE11" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class"><value>org.apache.art.ArtistExhibit</value></constructor-arg>
+		<constructor-arg><value>
+		INSERT INTO ARTIST_EXHIBIT (ARTIST_ID, EXHIBIT_ID) VALUES (1, 1)
+		</value></constructor-arg>
+	</bean>
+	
+	<bean id="AE12" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class"><value>org.apache.art.ArtistExhibit</value></constructor-arg>
+		<constructor-arg><value>
+		INSERT INTO ARTIST_EXHIBIT (ARTIST_ID, EXHIBIT_ID) VALUES (1, 2)
+		</value></constructor-arg>
+	</bean>
+	<!-- ======================================= -->
+	<!-- Data Sets -->
+	<!-- ======================================= -->
+
+	<bean id="testOneEntityFetchJoin" class="java.util.ArrayList">
+		<constructor-arg>
+			<list>
+				<ref bean="A1" />
+				<ref bean="A2" />
+				<ref bean="A3" />
+				<ref bean="P11" />
+				<ref bean="P12" />
+				<ref bean="P21" />
+			</list>
+		</constructor-arg>
+	</bean>
+	<bean id="testSeveralFetchJoins" class="java.util.ArrayList">
+		<constructor-arg>
+			<list>
+				<ref bean="A1" />
+				<ref bean="A2" />
+				<ref bean="A3" />
+				<ref bean="P11" />
+				<ref bean="P12" />
+				<ref bean="P21" />
+				<ref bean="G1" />
+				<ref bean="G2" />
+				<ref bean="E11" />
+				<ref bean="E12" />
+				<ref bean="AE11" />
+				<ref bean="AE12" />
+			</list>
+		</constructor-arg>
+	</bean>
+	
+</beans>
\ No newline at end of file