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/22 14:50:56 UTC

svn commit: r902087 - in /cayenne/main/branches/STABLE-3.0: docs/doc/src/main/resources/ framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/ framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/ fr...

Author: aadamchik
Date: Fri Jan 22 13:50:54 2010
New Revision: 902087

URL: http://svn.apache.org/viewvc?rev=902087&view=rev
Log:
CAY-1069 EJBQL: support paths across flattened relationships

porting from 3.1. Original SVN revs (repacked): 898317, 898673, 899270, 899291, 899283

Added:
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/FlattenedTest4.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/Relationships.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest4.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_Relationships.java
Modified:
    cayenne/main/branches/STABLE-3.0/docs/doc/src/main/resources/RELEASE-NOTES.txt
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLConditionTranslator.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLJoinAppender.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLPathTranslator.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFlattenedRelationshipsTest.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextFlattenedAttributesTest.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest1.java
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFlattenedRelationshipsTest.xml
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/relationships.map.xml
    cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/testmap.map.xml

Modified: cayenne/main/branches/STABLE-3.0/docs/doc/src/main/resources/RELEASE-NOTES.txt
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/docs/doc/src/main/resources/RELEASE-NOTES.txt?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/docs/doc/src/main/resources/RELEASE-NOTES.txt (original)
+++ cayenne/main/branches/STABLE-3.0/docs/doc/src/main/resources/RELEASE-NOTES.txt Fri Jan 22 13:50:54 2010
@@ -14,6 +14,7 @@
 
 Changes/New Features Since 3.0RC1:
 
+CAY-1069 EJBQL: support paths across flattened relationships
 CAY-1216 Improve the Remote-Object-Persistence-Tutorial for 3.0
 CAY-1326 Document Cayenne Embeddables Feature
 CAY-1352 in "code generation" dialog add generation embeddables

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLConditionTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLConditionTranslator.java?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLConditionTranslator.java (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLConditionTranslator.java Fri Jan 22 13:50:54 2010
@@ -46,6 +46,7 @@
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.reflect.AttributeProperty;
 import org.apache.cayenne.reflect.ClassDescriptor;
 import org.apache.cayenne.reflect.Property;
@@ -164,19 +165,37 @@
 
         String subqueryId = context.createIdAlias(id);
         ClassDescriptor targetDescriptor = context.getEntityDescriptor(subqueryId);
+       
+        if (expression.isNegated()) {
+            context.append(" NOT");
+        }
+
+        context.append(" EXISTS (SELECT 1 FROM ");
+
         String subqueryTableName = targetDescriptor
                 .getEntity()
                 .getDbEntity()
                 .getFullyQualifiedName();
         String subqueryRootAlias = context.getTableAlias(subqueryId, subqueryTableName);
 
-        context.append(" (SELECT COUNT(1) FROM ");
-        // not using "AS" to separate table name and alias name - OpenBase doesn't
-        // support "AS", and the rest of the databases do not care
-        context.append(subqueryTableName).append(' ').append(subqueryRootAlias);
+        ObjRelationship relationship = (ObjRelationship) correlatedEntityDescriptor
+                .getEntity()
+                .getRelationship(path.getRelativePath());
+        
+        if (relationship.getDbRelationshipPath().contains(".")) {
+            // if the DbRelationshipPath contains '.', the relationship is flattened
+            subqueryRootAlias = processFlattenedRelationShip(
+                    subqueryRootAlias,
+                    relationship);
+        }
+        else {
+            // not using "AS" to separate table name and alias name - OpenBase doesn't
+            // support "AS", and the rest of the databases do not care
+            context.append(subqueryTableName).append(' ').append(subqueryRootAlias);
+
+        }
         context.append(" WHERE");
 
-        // TODO: andrus, 8/11/2007 flattened?
         DbRelationship correlatedJoinRelationship = context.getIncomingRelationships(
                 new EJBQLTableId(id)).get(0);
         Iterator<DbJoin> it = correlatedJoinRelationship.getJoins().iterator();
@@ -238,23 +257,38 @@
 
         String subqueryId = context.createIdAlias(id);
         ClassDescriptor targetDescriptor = context.getEntityDescriptor(subqueryId);
+
+        if (expression.isNegated()) {
+            context.append(" NOT");
+        }
+
+        context.append(" EXISTS (SELECT 1 FROM ");
+
         String subqueryTableName = targetDescriptor
                 .getEntity()
                 .getDbEntity()
                 .getFullyQualifiedName();
         String subqueryRootAlias = context.getTableAlias(subqueryId, subqueryTableName);
 
-        if (expression.isNegated()) {
-            context.append(" NOT");
+        ObjRelationship relationship = (ObjRelationship) correlatedEntityDescriptor
+                .getEntity()
+                .getRelationship(path.getRelativePath());
+        
+        if (relationship.getDbRelationshipPath().contains(".")) {
+            // if the DbRelationshipPath contains '.', the relationship is flattened
+            subqueryRootAlias = processFlattenedRelationShip(
+                    subqueryRootAlias,
+                    relationship);
+        }
+        else {
+            // not using "AS" to separate table name and alias name - OpenBase doesn't
+            // support "AS", and the rest of the databases do not care
+            context.append(subqueryTableName).append(' ').append(subqueryRootAlias);
+
         }
 
-        context.append(" EXISTS (SELECT 1 FROM ");
-        // not using "AS" to separate table name and alias name - OpenBase doesn't
-        // support "AS", and the rest of the databases do not care
-        context.append(subqueryTableName).append(' ').append(subqueryRootAlias);
         context.append(" WHERE");
 
-        // TODO: andrus, 8/11/2007 flattened?
         DbRelationship correlatedJoinRelationship = context.getIncomingRelationships(
                 new EJBQLTableId(id)).get(0);
 
@@ -278,6 +312,60 @@
         return false;
     }
 
+    private String processFlattenedRelationShip(
+            String subqueryRootAlias,
+            ObjRelationship relationship) {
+        List<DbRelationship> dbRelationships = relationship
+                .getDbRelationships();
+        // reverse order to get the nearest to the correlated of the direct relation
+        for (int i = dbRelationships.size() - 1; i > 0; i--) {
+            DbRelationship dbRelationship = dbRelationships.get(i);
+            String subqueryTargetTableName = dbRelationship.getTargetEntityName();
+            String subqueryTargetAlias;
+            if (i == dbRelationships.size() - 1) {
+                subqueryTargetAlias = subqueryRootAlias;
+                context.append(subqueryTargetTableName).append(' ').append(
+                        subqueryTargetAlias);
+            }
+            else {
+                subqueryTargetAlias = context.getTableAlias(
+                        subqueryTargetTableName,
+                        subqueryTargetTableName);
+            }
+           
+
+            context.append(" JOIN ");
+
+            String subquerySourceTableName = dbRelationship
+                    .getSourceEntity()
+                    .getName();
+            String subquerySourceAlias = context.getTableAlias(
+                    subquerySourceTableName,
+                    subquerySourceTableName);
+
+            context.append(subquerySourceTableName).append(' ').append(subquerySourceAlias);
+
+            context.append(" ON (");
+
+            List<DbJoin> joins = dbRelationship.getJoins();
+            Iterator<DbJoin> it = joins.iterator();
+            while (it.hasNext()) {
+                DbJoin join = it.next();
+                context.append(' ').append(subqueryTargetAlias).append('.').append(
+                        join.getTargetName()).append(" = ");
+                context.append(subquerySourceAlias).append('.').append(
+                        join.getSourceName());
+                if (it.hasNext()) {
+                    context.append(" AND");
+                }
+            }
+            context.append(" )");
+            subqueryRootAlias = subquerySourceAlias;
+
+        }
+        return subqueryRootAlias;
+    }
+
     @Override
     public boolean visitAll(EJBQLExpression expression) {
         context.append(" ALL");

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLFromTranslator.java Fri Jan 22 13:50:54 2010
@@ -53,7 +53,7 @@
     public boolean visitFromItem(EJBQLFromItem expression, int finishedChildIndex) {
 
         String id = expression.getId();
-
+        
         if (lastId != null) {
             context.append(',');
             context.markCurrentPosition(EJBQLJoinAppender.makeJoinTailMarker(lastId));

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLJoinAppender.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLJoinAppender.java?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLJoinAppender.java (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLJoinAppender.java Fri Jan 22 13:50:54 2010
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.access.jdbc;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -32,6 +33,9 @@
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.query.EntityResultSegment;
+import org.apache.cayenne.util.CayenneMapEntry;
 
 /**
  * Handles appending joins to the content buffer at a marked position.
@@ -98,7 +102,6 @@
             throw new EJBQLException("No join configured for id " + rhsId);
         }
 
-        // TODO: andrus, 4/8/2007 - support for flattened relationships
         DbRelationship incomingDB = joinRelationships.get(0);
 
         // TODO: andrus, 1/6/2008 - move reusable join check here...
@@ -107,9 +110,10 @@
         String tableName;
 
         if (sourceEntity instanceof DbEntity) {
-           tableName = ((DbEntity) sourceEntity).getFullyQualifiedName();
-        } else {
-           tableName = sourceEntity.getName();
+            tableName = ((DbEntity) sourceEntity).getFullyQualifiedName();
+        }
+        else {
+            tableName = sourceEntity.getName();
         }
 
         String sourceAlias = context.getTableAlias(lhsId.getEntityId(), tableName);
@@ -121,58 +125,125 @@
         try {
 
             context.append(" ").append(semantics);
-            String targetAlias = appendTable(rhsId);
-            context.append(" ON (");
+            String targetAlias = "";
+            if (joinRelationships.size() > 1) {
+                // if size of relationship list greater than 1,
+                // it's a flattened relationship
+                context.append(" ");
+
+                for (int i = 1; i < joinRelationships.size(); i++) {
+                    DbRelationship dbRelationship = joinRelationships.get(i);
+
+                    String subquerySourceTableName = dbRelationship
+                            .getSourceEntity()
+                            .getName();
+                    String subquerySourceAlias = context.getTableAlias(
+                            subquerySourceTableName,
+                            subquerySourceTableName);
+
+                    String subqueryTargetTableName = dbRelationship.getTargetEntityName();
+                    
+                    String subqueryTargetAlias = "";
+                    if(i==joinRelationships.size()-1){
+                        // it's the last table alias
+                        subqueryTargetAlias = context.getTableAlias(rhsId.getEntityId(), subqueryTargetTableName);
+                    } else {
+                        subqueryTargetAlias = context.getTableAlias(
+                                subqueryTargetTableName,
+                                subqueryTargetTableName);
+                    }
+                    if (i == 1) {
+                        // first apply the joins defined in query
+                        context.append(subquerySourceTableName).append(' ').append(
+                                subquerySourceAlias);
+
+                        generateJoiningExpression(
+                                incomingDB,
+                                sourceAlias,
+                                subquerySourceAlias);
+
+                    }
+
+                    context.append(" LEFT OUTER JOIN ");
+                    context.append(subqueryTargetTableName).append(' ').append(
+                            subqueryTargetAlias);
+                    generateJoiningExpression(
+                            dbRelationship,
+                            subquerySourceAlias,
+                            subqueryTargetAlias);
+                }
 
-            Iterator<DbJoin> it = incomingDB.getJoins().iterator();
-            if (it.hasNext()) {
-                DbJoin dbJoin = it.next();
-                context
-                        .append(sourceAlias)
-                        .append('.')
-                        .append(dbJoin.getSourceName())
-                        .append(" = ")
-                        .append(targetAlias)
-                        .append('.')
-                        .append(dbJoin.getTargetName());
-            }
-
-            while (it.hasNext()) {
-                context.append(", ");
-                DbJoin dbJoin = it.next();
-                context
-                        .append(sourceAlias)
-                        .append('.')
-                        .append(dbJoin.getSourceName())
-                        .append(" = ")
-                        .append(targetAlias)
-                        .append('.')
-                        .append(dbJoin.getTargetName());
+            }
+            else {
+                // non-flattened relationship
+                targetAlias = appendTable(rhsId);
+                // apply the joins defined in query
+                generateJoiningExpression(incomingDB, sourceAlias, targetAlias);
             }
 
-            context.append(")");
         }
         finally {
             if (marker != null) {
                 context.popMarker();
             }
         }
+
+    }
+
+    private void generateJoiningExpression(
+            DbRelationship incomingDB,
+            String sourceAlias,
+            String targetAlias) {
+        context.append(" ON (");
+
+        Iterator<DbJoin> it = incomingDB.getJoins().iterator();
+        if (it.hasNext()) {
+            DbJoin dbJoin = it.next();
+            context
+                    .append(sourceAlias)
+                    .append('.')
+                    .append(dbJoin.getSourceName())
+                    .append(" = ")
+                    .append(targetAlias)
+                    .append('.')
+                    .append(dbJoin.getTargetName());
+        }
+
+        while (it.hasNext()) {
+            context.append(", ");
+            DbJoin dbJoin = it.next();
+            context
+                    .append(sourceAlias)
+                    .append('.')
+                    .append(dbJoin.getSourceName())
+                    .append(" = ")
+                    .append(targetAlias)
+                    .append('.')
+                    .append(dbJoin.getTargetName());
+        }
+
+        context.append(")");
     }
 
     public String appendTable(EJBQLTableId id) {
 
-        String tableName = id.getDbEntity(context).getFullyQualifiedName();
+        DbEntity dbEntity = id.getDbEntity(context);
+        
+        String tableName = dbEntity.getFullyQualifiedName();
         String alias;
 
         if (context.isUsingAliases()) {
             // TODO: andrus 1/5/2007 - if the same table is joined more than once, this
             // will create an incorrect alias.
             alias = context.getTableAlias(id.getEntityId(), tableName);
-
+            
             // not using "AS" to separate table name and alias name - OpenBase doesn't
             // support
             // "AS", and the rest of the databases do not care
             context.append(' ').append(tableName).append(' ').append(alias);
+            
+            generateJoinsForFlattenedAttributes(id, alias);
+           
         }
         else {
             context.append(' ').append(tableName);
@@ -209,6 +280,71 @@
         return alias;
     }
 
+    /**
+     * Generates Joins statements for those flattened attributes that appear after the
+     * FROM clause, e.g. in WHERE, ORDER BY, etc clauses. Flattened attributes of the
+     * entity from the SELECT clause are processed earlier and therefore are omitted.
+     * 
+     * @param id table to JOIN id
+     * @param alias table alias
+     */
+    private void generateJoinsForFlattenedAttributes(EJBQLTableId id, String alias) {
+        String entityName = context
+                .getEntityDescriptor(id.getEntityId())
+                .getEntity()
+                .getName();
+        boolean isProcessingOmitted = false;
+        // if the dbPath is not null, all attributes of the entity are processed earlier
+        isProcessingOmitted = id.getDbPath() != null;
+        String sourceExpression = context.getCompiledExpression().getSource();
+
+        List<Object> resultSetMapping = context.getMetadata().getResultSetMapping();
+        for (Object mapping : resultSetMapping) {
+            if (mapping instanceof EntityResultSegment) {
+                if (entityName.equals(((EntityResultSegment) mapping)
+                        .getClassDescriptor()
+                        .getEntity()
+                        .getName())) {
+                    // if entity is included into SELECT clause, all its attributes are processed earlier
+                    isProcessingOmitted = true;
+                    break;
+                }
+
+            }
+        }
+
+        if (!isProcessingOmitted) {
+            Collection<ObjAttribute> attributes = context.getEntityDescriptor(
+                    id.getEntityId()).getEntity().getAttributes();
+            for (ObjAttribute objAttribute : attributes) {
+                if (objAttribute.isFlattened()
+                        && sourceExpression.contains(id.getEntityId()
+                                + "."
+                                + objAttribute.getName())) {
+                    // joins for attribute are generated if it is flattened and appears in original statement
+                    Iterator<CayenneMapEntry> dbPathIterator = objAttribute
+                            .getDbPathIterator();
+                    while (dbPathIterator.hasNext()) {
+                        CayenneMapEntry next = dbPathIterator.next();
+                        if (next instanceof DbRelationship) {
+                            DbRelationship rel = (DbRelationship) next;
+                            context.append(" LEFT OUTER JOIN ");
+                            String targetEntityName = rel.getTargetEntityName();
+                            String subqueryTargetAlias = context.getTableAlias(id
+                                    .getEntityId(), targetEntityName);
+                            context.append(targetEntityName).append(' ').append(
+                                    subqueryTargetAlias);
+                            generateJoiningExpression(rel, context.getTableAlias(id
+                                    .getEntityId(), rel.getSourceEntity().getName()), subqueryTargetAlias);
+                        }
+
+                    }
+                }
+
+            }
+        }
+    }
+
     private EJBQLExpression ejbqlQualifierForEntityAndSubclasses(
             Expression qualifier,
             String entityId) {

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLPathTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLPathTranslator.java?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLPathTranslator.java (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLPathTranslator.java Fri Jan 22 13:50:54 2010
@@ -120,8 +120,21 @@
 
         if (oldPath != null) {
             this.idPath = oldPath;
-            this.lastAlias = context.getTableAlias(oldPath, currentEntity
-                    .getDbEntity().getFullyQualifiedName());
+            Relationship lastRelationship = currentEntity
+                    .getRelationship(lastPathComponent);
+            if (lastRelationship != null) {
+                ObjEntity targetEntity = (ObjEntity) lastRelationship.getTargetEntity();
+
+                this.lastAlias = context.getTableAlias(fullPath, targetEntity
+                        .getDbEntity()
+                        .getFullyQualifiedName());
+            }
+            else {
+                this.lastAlias = context.getTableAlias(oldPath, currentEntity
+                        .getDbEntity()
+                        .getFullyQualifiedName());
+
+            }
         }
         else {
 
@@ -216,14 +229,14 @@
             // use an outer join for to-many matches
             resolveJoin(false);
 
-            // TODO: andrus, 6/21/2007 - flattened support
-            DbRelationship dbRelationship = relationship.getDbRelationships().get(0);
+            DbRelationship dbRelationship = chooseDbRelationship(relationship);
             DbEntity table = (DbEntity) dbRelationship.getTargetEntity();
 
             String alias = this.lastAlias != null ? lastAlias : context.getTableAlias(
                     idPath,
                     table.getFullyQualifiedName());
-
+           
+            
             Collection<DbAttribute> pks = table.getPrimaryKeys();
 
             if (pks.size() == 1) {
@@ -238,12 +251,12 @@
                 throw new EJBQLException(
                         "Multi-column PK to-many matches are not yet supported.");
             }
+            
         }
         else {
             // match FK against the target object
 
-            // TODO: andrus, 6/21/2007 - flattened support
-            DbRelationship dbRelationship = relationship.getDbRelationships().get(0);
+            DbRelationship dbRelationship = chooseDbRelationship(relationship);
             DbEntity table = (DbEntity) dbRelationship.getSourceEntity();
 
             String alias = this.lastAlias != null ? lastAlias : context.getTableAlias(
@@ -278,6 +291,34 @@
             }
         }
     }
+    
+    /**
+     * Checks if the object relationship is flattened and then chooses
+     * the corresponding db relationship. The last in idPath if isFlattened and
+     * the first in list otherwise.
+     * 
+     * @param relationship the object relationship
+     * 
+     * @return {@link DbRelationship}
+     */
+    protected DbRelationship chooseDbRelationship(ObjRelationship relationship) {
+
+        List<DbRelationship> dbRelationships = relationship.getDbRelationships();
+        String dbRelationshipPath = relationship.getDbRelationshipPath();
+
+        if (dbRelationshipPath.contains(".")) {
+            String dbRelName = dbRelationshipPath.substring(dbRelationshipPath
+                    .lastIndexOf(".") + 1);
+            for (DbRelationship dbR : dbRelationships) {
+                if (dbR.getName().equals(dbRelName)) {
+                    return dbR;
+                }
+
+            }
+        }
+        return relationship.getDbRelationships().get(0);
+
+    }
 
     public boolean isUsingAliases() {
         return usingAliases;

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/jdbc/EJBQLSelectColumnsTranslator.java Fri Jan 22 13:50:54 2010
@@ -22,6 +22,7 @@
 import java.util.Iterator;
 import java.util.Map;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.ejbql.EJBQLBaseVisitor;
 import org.apache.cayenne.ejbql.EJBQLException;
@@ -134,9 +135,45 @@
                 DbEntity table = currentEntity.getDbEntity();
                 String alias = this.lastAlias != null ? lastAlias : context
                         .getTableAlias(idPath, table.getFullyQualifiedName());
+                if (attribute.isFlattened()) {
+                    Iterator<?> dbPathIterator = attribute.getDbPathIterator();
+                    EJBQLTableId lhsId = new EJBQLTableId(idPath);
+
+                    while (dbPathIterator.hasNext()) {
+                        Object pathPart = dbPathIterator.next();
+                        // DbRelationships not processed, because they will be processed
+                        // later when appending table
+                        if (pathPart == null) {
+                            throw new CayenneRuntimeException(
+                                    "ObjAttribute has no component: "
+                                            + attribute.getName());
+                        }
+                        else if (pathPart instanceof DbAttribute) {
+                            DbAttribute dbAttribute = (DbAttribute) pathPart;
+                            appendColumn(
+                                    attribute,
+                                    context.getTableAlias(
+                                            lhsId.getEntityId(),
+                                            dbAttribute.getEntity().getName()),
+                                    dbAttribute);
 
-                DbAttribute dbAttribute = attribute.getDbAttribute();
+                        }
 
+                    }
+
+                }
+                else {
+
+                    DbAttribute dbAttribute = attribute.getDbAttribute();
+
+                    appendColumn(attribute, alias, dbAttribute);
+                }
+            }
+
+            private void appendColumn(
+                    ObjAttribute attribute,
+                    String alias,
+                    DbAttribute dbAttribute) {
                 if (context.isAppendingResultColumns()) {
                     context.append(" #result('");
                 }
@@ -163,6 +200,7 @@
                             .append(")");
                 }
             }
+
         };
         expression.visit(pathTranslator);
         return false;

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFlattenedRelationshipsTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFlattenedRelationshipsTest.java?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFlattenedRelationshipsTest.java (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextEJBQLFlattenedRelationshipsTest.java Fri Jan 22 13:50:54 2010
@@ -18,8 +18,14 @@
  ****************************************************************/
 package org.apache.cayenne.access;
 
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
 import org.apache.cayenne.DataObjectUtils;
 import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
 import org.apache.cayenne.query.EJBQLQuery;
 import org.apache.cayenne.testdo.relationship.FlattenedTest1;
 import org.apache.cayenne.unit.RelationshipCase;
@@ -33,23 +39,222 @@
                 + "WHERE f MEMBER OF ft.ft3Array AND ft = :ft";
 
         ObjectContext context = createDataContext();
+        FlattenedTest1 ft = DataObjectUtils.objectForPK(context, FlattenedTest1.class, 2);
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+        query.setParameter("ft", ft);
+
+        List<?> objects = context.performQuery(query);
+        assertEquals(2, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+
+        assertTrue(ids.contains(new Integer(2)));
+        assertTrue(ids.contains(new Integer(3)));
+    }
+    
+    public void testCollectionMemberOfThetaJoinLongRelationshipSequence() throws Exception {
+        String ejbql = "SELECT f FROM FlattenedTest4 f, FlattenedTest1 ft "
+                + "WHERE f MEMBER OF ft.ft4ArrayFor1 AND ft = :ft";
+
+        ObjectContext context = createDataContext();
         FlattenedTest1 ft = DataObjectUtils.objectForPK(context, FlattenedTest1.class, 1);
         EJBQLQuery query = new EJBQLQuery(ejbql);
         query.setParameter("ft", ft);
 
-        // TODO: andrus 2008/06/09 - this fails until we fix CAY-1069 (for correlated join
-        // case see EJBQLConditionTranslator.visitMemberOf(..)
-        // List<?> objects = context.performQuery(query);
-        // assertEquals(2, objects.size());
-        //
-        // Set<Object> ids = new HashSet<Object>();
-        // Iterator<?> it = objects.iterator();
-        // while (it.hasNext()) {
-        // Object id = DataObjectUtils.pkForObject((Persistent) it.next());
-        // ids.add(id);
-        // }
-        //
-        // assertTrue(ids.contains(new Integer(2)));
-        // assertTrue(ids.contains(new Integer(3)));
+        List<?> objects = context.performQuery(query);
+        assertEquals(1, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+
+        assertTrue(ids.contains(new Integer(1)));
+        
+        
+        ft = DataObjectUtils.objectForPK(context, FlattenedTest1.class, 2);
+        query = new EJBQLQuery(ejbql);
+        query.setParameter("ft", ft);
+
+        objects = context.performQuery(query);
+        assertEquals(0, objects.size());
+    }
+
+    public void testCollectionInnerJoin() throws Exception {
+        String ejbql = "SELECT ft FROM FlattenedTest1 ft INNER JOIN ft.ft3Array f WHERE ft = :ft";
+
+        ObjectContext context = createDataContext();
+        FlattenedTest1 ft = DataObjectUtils.objectForPK(context, FlattenedTest1.class, 1);
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+        query.setParameter("ft", ft);
+
+        List<?> objects = context.performQuery(query);
+        assertNotNull(objects);
+        assertFalse(objects.isEmpty());
+        assertEquals(1, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+
+        assertTrue(ids.contains(new Integer(1)));
+        
+    }
+
+    
+    public void testCollectionAsInnerJoin() throws Exception {
+        // this query is equivalent to the previous INNER JOIN example
+        String ejbql = "SELECT OBJECT(ft) FROM FlattenedTest1 ft, IN(ft.ft3Array) f WHERE ft = :ft";
+
+        ObjectContext context = createDataContext();
+        FlattenedTest1 ft = DataObjectUtils.objectForPK(context, FlattenedTest1.class, 1);
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+        query.setParameter("ft", ft);
+        
+        List<?> objects = context.performQuery(query);
+        assertNotNull(objects);
+        assertFalse(objects.isEmpty());
+        assertEquals(1, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+
+        assertTrue(ids.contains(new Integer(1)));
+        
+    }
+
+    public void testCollectionThetaJoin() throws Exception {
+        String ejbql = "SELECT DISTINCT ft FROM FlattenedTest1 ft , FlattenedTest3 f3 WHERE f3.toFT1 = ft";
+
+        ObjectContext context = createDataContext();
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+        
+        List<?> objects = context.performQuery(query);
+        assertNotNull(objects);
+        assertFalse(objects.isEmpty());
+        assertEquals(2, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+        assertTrue(ids.contains(new Integer(1)));
+        assertTrue(ids.contains(new Integer(2)));
+
+    }
+
+    public void testCollectionIdentificationVariable() throws Exception {
+        String ejbql = "SELECT ft.ft3Array FROM FlattenedTest1 ft WHERE ft = :ft";
+
+        ObjectContext context = createDataContext();
+        FlattenedTest1 ft = DataObjectUtils.objectForPK(context, FlattenedTest1.class, 2);
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+        query.setParameter("ft", ft);
+
+        List<?> objects = context.performQuery(query);
+        assertNotNull(objects);
+        assertFalse(objects.isEmpty());
+        assertEquals(2, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+
+        assertTrue(ids.contains(new Integer(2)));
+        assertTrue(ids.contains(new Integer(3)));
+
+    }
+
+    public void testAssociationFieldSelect() throws Exception {
+        
+        String ejbql = "SELECT ft3.toFT1 FROM FlattenedTest3 ft3 WHERE ft3.toFT1 = :ft";
+        
+        ObjectContext context = createDataContext();
+        FlattenedTest1 ft = DataObjectUtils.objectForPK(context, FlattenedTest1.class, 1);
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+        query.setParameter("ft", ft);
+
+        List<?> objects = context.performQuery(query);
+        assertEquals(1, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+
+        assertTrue(ids.contains(new Integer(1)));
+
+    }
+    
+    public void testCollectionSubquery() throws Exception {
+        String ejbql = "SELECT ft FROM FlattenedTest1 ft "
+        + "WHERE (SELECT COUNT(f) FROM ft.ft3Array f) = 1";
+
+        ObjectContext context = createDataContext();
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+        
+        // TODO fails but not because of flattened relationship,
+        // the reason is that property "ft3Array" inside the subquery 
+        // parses as unmapped
+        /*List<?> objects = context.performQuery(query);
+        assertNotNull(objects);
+        assertFalse(objects.isEmpty());
+        assertEquals(1, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+
+        assertTrue(ids.contains(new Integer(2)));*/
+        
+
+    }
+    
+    public void testCollectionSubquery1() throws Exception {
+        String ejbql = "SELECT ft FROM FlattenedTest1 ft "
+        + "WHERE (SELECT COUNT(f3) FROM FlattenedTest3 f3 WHERE f3 MEMBER OF ft.ft3Array) > 1";
+
+        ObjectContext context = createDataContext();
+        EJBQLQuery query = new EJBQLQuery(ejbql);
+        
+        List<?> objects = context.performQuery(query);
+        
+        assertNotNull(objects);
+        assertFalse(objects.isEmpty());
+        assertEquals(1, objects.size());
+
+        Set<Object> ids = new HashSet<Object>();
+        Iterator<?> it = objects.iterator();
+        while (it.hasNext()) {
+            Object id = DataObjectUtils.pkForObject((Persistent) it.next());
+            ids.add(id);
+        }
+
+        assertTrue(ids.contains(new Integer(2)));
     }
+   
 }

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextFlattenedAttributesTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextFlattenedAttributesTest.java?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextFlattenedAttributesTest.java (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/DataContextFlattenedAttributesTest.java Fri Jan 22 13:50:54 2010
@@ -28,6 +28,7 @@
 
 import org.apache.art.Artist;
 import org.apache.art.CompoundPainting;
+import org.apache.art.Gallery;
 import org.apache.cayenne.DataObjectUtils;
 import org.apache.cayenne.PersistenceState;
 import org.apache.cayenne.exp.ExpressionFactory;
@@ -200,7 +201,97 @@
             assertEquals(PersistenceState.COMMITTED, painting.getPersistenceState());
         }
     }
+    
+    public void testSelectEJQBQLCollectionTheta() throws Exception {
+        populateTables();
+        EJBQLQuery query = new EJBQLQuery("SELECT DISTINCT a FROM CompoundPainting cp, Artist a "
+                + "WHERE a.artistName=cp.artistName ORDER BY a.artistName");
+               
+        List<?> objects = context.performQuery(query);
+
+        assertNotNull(objects);
+        assertEquals(4, objects.size());
+        Iterator<?> i = objects.iterator();
+        int index=1;
+        while (i.hasNext()) {
+            Artist artist = (Artist) i.next();
+            assertEquals("artist" + index, artist.getArtistName());
+            index++;
+        }
+    }
+    
+    public void testSelectEJQBQLLike() throws Exception {
+        populateTables();
+        EJBQLQuery query = new EJBQLQuery(
+                "SELECT a FROM CompoundPainting a WHERE a.artistName LIKE 'artist%' " +
+                "ORDER BY a.paintingTitle");
+               
+        List<?> objects = context.performQuery(query);
+
+        assertNotNull(objects);
+        assertEquals(8, objects.size());
+        Iterator<?> i = objects.iterator();
+        int index=1;
+        while (i.hasNext()) {
+            CompoundPainting painting = (CompoundPainting) i.next();
+            assertEquals("painting" + index, painting.getPaintingTitle());
+            index++;
+        }
+    }
+    
+    public void testSelectEJQBQLBetween() throws Exception {
+        populateTables();
+        EJBQLQuery query = new EJBQLQuery(
+                "SELECT a FROM CompoundPainting a " +
+                "WHERE a.artistName BETWEEN 'artist1' AND 'artist4' " +
+                "ORDER BY a.paintingTitle");
+               
+        List<?> objects = context.performQuery(query);
+
+        assertNotNull(objects);
+        assertEquals(8, objects.size());
+        Iterator<?> i = objects.iterator();
+        int index=1;
+        while (i.hasNext()) {
+            CompoundPainting painting = (CompoundPainting) i.next();
+            assertEquals("painting" + index, painting.getPaintingTitle());
+            index++;
+        }
+    }
+    
+    public void testSelectEJQBQLSubquery() throws Exception {
+        populateTables();
+        EJBQLQuery query = new EJBQLQuery(
+                "SELECT g FROM Gallery g WHERE " +
+                "(SELECT COUNT(cp) FROM CompoundPainting cp WHERE g.galleryName=cp.galleryName) = 4");
+                
+        List<?> objects = context.performQuery(query);
+
+        assertNotNull(objects);
+        assertEquals(1, objects.size());
+        Gallery gallery = (Gallery) objects.get(0);
+        assertEquals("gallery2", gallery.getGalleryName());
+        
+    }
+    
+    public void testSelectEJQBQLHaving() throws Exception {
+        populateTables();
+        EJBQLQuery query = new EJBQLQuery(
+                "SELECT cp.galleryName, COUNT(a) from  Artist a, CompoundPainting cp "+
+                "WHERE cp.artistName = a.artistName "+
+                "GROUP BY cp.galleryName " +
+                "HAVING cp.galleryName LIKE 'gallery1'");
+                
+               
+        List<Object[]> objects = context.performQuery(query);
 
+        assertNotNull(objects);
+        assertEquals(1, objects.size());
+        Object[] galleryItem = objects.get(0);
+        assertEquals("gallery1", galleryItem[0]);
+        assertEquals(3L, galleryItem[1]);
+    }
+    
     public void testInsert() {
         CompoundPainting o1 = context.newObject(CompoundPainting.class);
         o1.setArtistName("A1");

Added: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/FlattenedTest4.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/FlattenedTest4.java?rev=902087&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/FlattenedTest4.java (added)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/FlattenedTest4.java Fri Jan 22 13:50:54 2010
@@ -0,0 +1,7 @@
+package org.apache.cayenne.testdo.relationship;
+
+import org.apache.cayenne.testdo.relationship.auto._FlattenedTest4;
+
+public class FlattenedTest4 extends _FlattenedTest4 {
+
+}

Added: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/Relationships.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/Relationships.java?rev=902087&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/Relationships.java (added)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/Relationships.java Fri Jan 22 13:50:54 2010
@@ -0,0 +1,18 @@
+package org.apache.cayenne.testdo.relationship;
+
+import org.apache.cayenne.testdo.relationship.auto._Relationships;
+
+public class Relationships extends _Relationships {
+
+    private static Relationships instance;
+
+    private Relationships() {}
+
+    public static Relationships getInstance() {
+        if(instance == null) {
+            instance = new Relationships();
+        }
+
+        return instance;
+    }
+}

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest1.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest1.java?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest1.java (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest1.java Fri Jan 22 13:50:54 2010
@@ -2,19 +2,25 @@
 
 import java.util.List;
 
-/** Class _FlattenedTest1 was generated by Cayenne.
-  * It is probably a good idea to avoid changing this class manually, 
-  * since it may be overwritten next time code is regenerated. 
-  * If you need to make any customizations, please use subclass. 
-  */
-public class _FlattenedTest1 extends org.apache.cayenne.CayenneDataObject {
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.testdo.relationship.FlattenedTest2;
+import org.apache.cayenne.testdo.relationship.FlattenedTest3;
+import org.apache.cayenne.testdo.relationship.FlattenedTest4;
+
+/**
+ * Class _FlattenedTest1 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _FlattenedTest1 extends CayenneDataObject {
 
     public static final String NAME_PROPERTY = "name";
     public static final String FT2ARRAY_PROPERTY = "ft2Array";
     public static final String FT3ARRAY_PROPERTY = "ft3Array";
     public static final String FT3OVER_COMPLEX_PROPERTY = "ft3OverComplex";
+    public static final String FT4ARRAY_FOR1_PROPERTY = "ft4ArrayFor1";
 
-    public static final String FT1_ID_PK_COLUMN = "FT1_ID";
 
     public void setName(String name) {
         writeProperty("name", name);
@@ -22,39 +28,41 @@
     public String getName() {
         return (String)readProperty("name");
     }
-    
-    
-    public void addToFt2Array(org.apache.cayenne.testdo.relationship.FlattenedTest2 obj) {
+
+    public void addToFt2Array(FlattenedTest2 obj) {
         addToManyTarget("ft2Array", obj, true);
     }
-    public void removeFromFt2Array(org.apache.cayenne.testdo.relationship.FlattenedTest2 obj) {
+    public void removeFromFt2Array(FlattenedTest2 obj) {
         removeToManyTarget("ft2Array", obj, true);
     }
-    public List getFt2Array() {
-        return (List)readProperty("ft2Array");
+    @SuppressWarnings("unchecked")
+    public List<FlattenedTest2> getFt2Array() {
+        return (List<FlattenedTest2>)readProperty("ft2Array");
+    }
+
+
+    @SuppressWarnings("unchecked")
+    public List<FlattenedTest3> getFt3Array() {
+        return (List<FlattenedTest3>)readProperty("ft3Array");
     }
-    
-    
-    public void addToFt3Array(org.apache.cayenne.testdo.relationship.FlattenedTest3 obj) {
-        addToManyTarget("ft3Array", obj, true);
-    }
-    public void removeFromFt3Array(org.apache.cayenne.testdo.relationship.FlattenedTest3 obj) {
-        removeToManyTarget("ft3Array", obj, true);
-    }
-    public List getFt3Array() {
-        return (List)readProperty("ft3Array");
-    }
-    
-    
-    public void addToFt3OverComplex(org.apache.cayenne.testdo.relationship.FlattenedTest3 obj) {
+
+
+    public void addToFt3OverComplex(FlattenedTest3 obj) {
         addToManyTarget("ft3OverComplex", obj, true);
     }
-    public void removeFromFt3OverComplex(org.apache.cayenne.testdo.relationship.FlattenedTest3 obj) {
+    public void removeFromFt3OverComplex(FlattenedTest3 obj) {
         removeToManyTarget("ft3OverComplex", obj, true);
     }
-    public List getFt3OverComplex() {
-        return (List)readProperty("ft3OverComplex");
+    @SuppressWarnings("unchecked")
+    public List<FlattenedTest3> getFt3OverComplex() {
+        return (List<FlattenedTest3>)readProperty("ft3OverComplex");
+    }
+
+
+    @SuppressWarnings("unchecked")
+    public List<FlattenedTest4> getFt4ArrayFor1() {
+        return (List<FlattenedTest4>)readProperty("ft4ArrayFor1");
     }
-    
-    
+
+
 }

Added: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest4.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest4.java?rev=902087&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest4.java (added)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_FlattenedTest4.java Fri Jan 22 13:50:54 2010
@@ -0,0 +1,34 @@
+package org.apache.cayenne.testdo.relationship.auto;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.testdo.relationship.FlattenedTest3;
+
+/**
+ * Class _FlattenedTest4 was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _FlattenedTest4 extends CayenneDataObject {
+
+    public static final String NAME_PROPERTY = "name";
+    public static final String TO_FT3_PROPERTY = "toFT3";
+
+
+    public void setName(String name) {
+        writeProperty("name", name);
+    }
+    public String getName() {
+        return (String)readProperty("name");
+    }
+
+    public void setToFT3(FlattenedTest3 toFT3) {
+        setToOneTarget("toFT3", toFT3, true);
+    }
+
+    public FlattenedTest3 getToFT3() {
+        return (FlattenedTest3)readProperty("toFT3");
+    }
+
+
+}

Added: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_Relationships.java
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_Relationships.java?rev=902087&view=auto
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_Relationships.java (added)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/relationship/auto/_Relationships.java Fri Jan 22 13:50:54 2010
@@ -0,0 +1,12 @@
+package org.apache.cayenne.testdo.relationship.auto;
+
+
+
+/**
+ * This class was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public class _Relationships {
+}
\ No newline at end of file

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFlattenedRelationshipsTest.xml
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFlattenedRelationshipsTest.xml?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFlattenedRelationshipsTest.xml (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/dml/access.DataContextEJBQLFlattenedRelationshipsTest.xml Fri Jan 22 13:50:54 2010
@@ -45,6 +45,12 @@
 		insert into FLATTENED_TEST_3 (FT3_ID, FT2_ID, NAME) values (3, 2, 'ft3-b')
 		</value></constructor-arg>
 	</bean>
+	<bean id="FT41" class="org.apache.cayenne.unit.util.UpdatingSQLTemplate">
+		<constructor-arg type="java.lang.Class"><value>org.apache.cayenne.testdo.relationship.FlattenedTest4</value></constructor-arg>
+		<constructor-arg><value>
+		insert into FLATTENED_TEST_4 (FT4_ID, FT3_ID, NAME) values (1, 1, 'ft4')
+		</value></constructor-arg>
+	</bean>
 
 	<bean id="testCollectionMemberOfThetaJoin" class="java.util.ArrayList">
 		<constructor-arg>
@@ -56,6 +62,7 @@
 				<ref bean="FT31"/>
 				<ref bean="FT32"/>
 				<ref bean="FT33"/>
+				<ref bean="FT41"/>
 			</list>
 		</constructor-arg>
 	</bean>

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/relationships.map.xml
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/relationships.map.xml?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/relationships.map.xml (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/relationships.map.xml Fri Jan 22 13:50:54 2010
@@ -73,6 +73,11 @@
 		<db-attribute name="FT3_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 		<db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="100"/>
 	</db-entity>
+	<db-entity name="FLATTENED_TEST_4">
+		<db-attribute name="FT3_ID" type="INTEGER"/>
+		<db-attribute name="FT4_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
+		<db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="100"/>
+	</db-entity>
 	<db-entity name="ID_MAP_TO_MANY">
 		<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
 	</db-entity>
@@ -164,6 +169,9 @@
 	<obj-entity name="FlattenedTest3" className="org.apache.cayenne.testdo.relationship.FlattenedTest3" dbEntityName="FLATTENED_TEST_3">
 		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
 	</obj-entity>
+	<obj-entity name="FlattenedTest4" className="org.apache.cayenne.testdo.relationship.FlattenedTest4" dbEntityName="FLATTENED_TEST_4">
+		<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
+	</obj-entity>
 	<obj-entity name="IdMapToMany" className="org.apache.cayenne.testdo.relationship.IdMapToMany" dbEntityName="ID_MAP_TO_MANY">
 	</obj-entity>
 	<obj-entity name="IdMapToManyTarget" className="org.apache.cayenne.testdo.relationship.IdMapToManyTarget" dbEntityName="ID_MAP_TO_MANY_TARGET">
@@ -276,9 +284,15 @@
 	<db-relationship name="complexJoins" source="FLATTENED_TEST_3" target="COMPLEX_JOIN" toMany="true">
 		<db-attribute-pair source="FT3_ID" target="FT3_FK"/>
 	</db-relationship>
+	<db-relationship name="ft4Array" source="FLATTENED_TEST_3" target="FLATTENED_TEST_4" toMany="true">
+		<db-attribute-pair source="FT3_ID" target="FT3_ID"/>
+	</db-relationship>
 	<db-relationship name="toFT2" source="FLATTENED_TEST_3" target="FLATTENED_TEST_2" toMany="false">
 		<db-attribute-pair source="FT2_ID" target="FT2_ID"/>
 	</db-relationship>
+	<db-relationship name="toFT3" source="FLATTENED_TEST_4" target="FLATTENED_TEST_3" toMany="false">
+		<db-attribute-pair source="FT3_ID" target="FT3_ID"/>
+	</db-relationship>
 	<db-relationship name="targets" source="ID_MAP_TO_MANY" target="ID_MAP_TO_MANY_TARGET" toMany="true">
 		<db-attribute-pair source="ID" target="MAP_TO_MANY_ID"/>
 	</db-relationship>
@@ -352,10 +366,13 @@
 	<obj-relationship name="ft2Array" source="FlattenedTest1" target="FlattenedTest2" db-relationship-path="ft2Array"/>
 	<obj-relationship name="ft3Array" source="FlattenedTest1" target="FlattenedTest3" db-relationship-path="ft2Array.ft3Array"/>
 	<obj-relationship name="ft3OverComplex" source="FlattenedTest1" target="FlattenedTest3" db-relationship-path="complexJoins.toFT3"/>
+	<obj-relationship name="ft4ArrayFor1" source="FlattenedTest1" target="FlattenedTest4" db-relationship-path="ft2Array.ft3Array.ft4Array"/>
 	<obj-relationship name="ft3Array" source="FlattenedTest2" target="FlattenedTest3" db-relationship-path="ft3Array"/>
 	<obj-relationship name="toFT1" source="FlattenedTest2" target="FlattenedTest1" db-relationship-path="toFT1"/>
+	<obj-relationship name="ft4Array" source="FlattenedTest3" target="FlattenedTest4" db-relationship-path="ft4Array"/>
 	<obj-relationship name="toFT1" source="FlattenedTest3" target="FlattenedTest1" db-relationship-path="toFT2.toFT1"/>
 	<obj-relationship name="toFT2" source="FlattenedTest3" target="FlattenedTest2" db-relationship-path="toFT2"/>
+	<obj-relationship name="toFT3" source="FlattenedTest4" target="FlattenedTest3" db-relationship-path="toFT3"/>
 	<obj-relationship name="targets" source="IdMapToMany" target="IdMapToManyTarget" collection-type="java.util.Map" db-relationship-path="targets"/>
 	<obj-relationship name="mapToMany" source="IdMapToManyTarget" target="IdMapToMany" db-relationship-path="mapToMany"/>
 	<obj-relationship name="targets" source="MapToMany" target="MapToManyTarget" collection-type="java.util.Map" map-key="name" db-relationship-path="targets"/>

Modified: cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/testmap.map.xml
URL: http://svn.apache.org/viewvc/cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/testmap.map.xml?rev=902087&r1=902086&r2=902087&view=diff
==============================================================================
--- cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/testmap.map.xml (original)
+++ cayenne/main/branches/STABLE-3.0/framework/cayenne-jdk1.5-unpublished/src/test/resources/testmap.map.xml Fri Jan 22 13:50:54 2010
@@ -633,6 +633,10 @@
 	<query name="QueryWithSharedCache" factory="org.apache.cayenne.map.SelectQueryBuilder" root="obj-entity" root-name="Artist">
 		<property name="cayenne.GenericSelectQuery.cacheStrategy" value="SHARED_CACHE"/>
 	</query>
+	<query name="SelectDateTest" factory="org.apache.cayenne.map.SQLTemplateBuilder" root="data-map" root-name="testmap">
+		<property name="cayenne.GenericSelectQuery.fetchingDataRows" value="true"/>
+		<sql><![CDATA[SELECT * FROM DATE_TEST]]></sql>
+	</query>
 	<query name="SelectTestLower" factory="org.apache.cayenne.map.SQLTemplateBuilder" root="data-map" root-name="testmap">
 		<property name="cayenne.GenericSelectQuery.fetchingDataRows" value="true"/>
 		<property name="cayenne.SQLTemplate.columnNameCapitalization" value="LOWER"/>