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 2008/12/28 00:18:21 UTC

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

Author: aadamchik
Date: Sat Dec 27 15:18:20 2008
New Revision: 729693

URL: http://svn.apache.org/viewvc?rev=729693&view=rev
Log:
CAY-1162 SelectDescriptor concept

basic support for inheritance

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/DiscriminatorBuilder.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilder.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/MappedColumnBuilder.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/select/
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilderTest.java
Modified:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityRowReader.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegment.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegmentBuilder.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySelectColumn.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeRowReader.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/SelectDescriptorBuilder.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityInheritanceTree.java

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/DiscriminatorBuilder.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/DiscriminatorBuilder.java?rev=729693&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/DiscriminatorBuilder.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/DiscriminatorBuilder.java Sat Dec 27 15:18:20 2008
@@ -0,0 +1,82 @@
+/*****************************************************************
+ *   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.select;
+
+import java.util.List;
+
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.TraversalHelper;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityInheritanceTree;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * @since 3.0
+ */
+class DiscriminatorBuilder extends MappedColumnBuilder {
+
+    private EntityInheritanceTree node;
+
+    DiscriminatorBuilder(ExtendedTypeMap extendedTypes, EntityInheritanceTree node) {
+        super(extendedTypes);
+        this.node = node;
+    }
+
+    List<EntitySelectColumn> buildColumns() {
+        appendColumns(node);
+        return columns;
+    }
+
+    private void appendColumns(EntityInheritanceTree node) {
+
+        ObjEntity entity = node.getEntity();
+        if (!entity.isAbstract()) {
+
+            Expression qualifier = entity.getDeclaredQualifier();
+            if (qualifier != null) {
+                appendColumns(qualifier);
+            }
+        }
+
+        for (EntityInheritanceTree childNode : node.getChildren()) {
+            appendColumns(childNode);
+        }
+    }
+
+    private void appendColumns(Expression expression) {
+
+        final ObjEntity entity = node.getEntity();
+        final DbEntity dbEntity = entity.getDbEntity();
+
+        // find and register discriminator columns
+        expression.traverse(new TraversalHelper() {
+
+            @Override
+            public void startNode(Expression node, Expression parentNode) {
+                if (node.getType() == Expression.DB_PATH) {
+                    append(dbEntity, node);
+                }
+                else if (node.getType() == Expression.OBJ_PATH) {
+                    append(entity, node);
+                }
+            }
+        });
+    }
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityRowReader.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityRowReader.java?rev=729693&r1=729692&r2=729693&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityRowReader.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityRowReader.java Sat Dec 27 15:18:20 2008
@@ -33,6 +33,7 @@
  */
 class EntityRowReader implements RowReader<Object> {
 
+    private EntityRowReader superReader;
     private ExtendedType[] converters;
     private String[] dataRowKeys;
     private int[] jdbcTypes;
@@ -41,25 +42,53 @@
     private int mapCapacity;
 
     EntityRowReader(String entityName, List<EntitySelectColumn> columns) {
+
         int len = columns.size();
+        int[] columnIndexes = new int[len];
+
+        for (int i = 0; i < len; i++) {
+            columnIndexes[i] = i + 1;
+        }
+
+        init(entityName, columns, columnIndexes);
+    }
 
+    EntityRowReader(String entityName, List<EntitySelectColumn> columns,
+            int[] columnIndexes) {
+        init(entityName, columns, columnIndexes);
+    }
+
+    private void init(
+            String entityName,
+            List<EntitySelectColumn> columns,
+            int[] columnIndexes) {
+        this.entityName = entityName;
+
+        int len = columns.size();
         this.mapCapacity = (int) Math.ceil(len / 0.75);
         this.converters = new ExtendedType[len];
         this.dataRowKeys = new String[len];
         this.jdbcTypes = new int[len];
-        this.columnIndexes = new int[len];
+        this.columnIndexes = columnIndexes;
 
         for (int i = 0; i < len; i++) {
             converters[i] = columns.get(i).getConverter();
             dataRowKeys[i] = columns.get(i).getDataRowKey();
             jdbcTypes[i] = columns.get(i).getJdbcType();
-            columnIndexes[i] = i + 1;
+        }
+    }
+
+    void setSuperReader(EntityRowReader superReader) {
+        this.superReader = superReader;
+
+        if (superReader != null) {
+            mapCapacity += superReader.mapCapacity;
         }
     }
 
     public void setColumnOffset(int offset) {
         for (int i = 0; i < columnIndexes.length; i++) {
-            columnIndexes[i] = i + offset + 1;
+            columnIndexes[i] = columnIndexes[i] + offset;
         }
     }
 
@@ -67,6 +96,16 @@
         DataRow row = new DataRow(mapCapacity);
         row.setEntityName(entityName);
 
+        if (superReader != null) {
+            superReader.fillRow(resultSet, row);
+        }
+
+        fillRow(resultSet, row);
+
+        return row;
+    }
+
+    private final void fillRow(ResultSet resultSet, DataRow row) throws CayenneException {
         int len = converters.length;
 
         try {
@@ -85,7 +124,6 @@
             throw new CayenneException("Exception materializing row", Util
                     .unwindException(ex));
         }
-
-        return row;
     }
+
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegment.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegment.java?rev=729693&r1=729692&r2=729693&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegment.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegment.java Sat Dec 27 15:18:20 2008
@@ -28,19 +28,23 @@
  */
 class EntitySegment implements SelectDescriptor<Object> {
 
-    private List<? extends SelectColumn> columns;
+    private List<EntitySelectColumn> columns;
     private RowReader<Object> rowReader;
 
-    EntitySegment(RowReader<Object> rowReader, List<? extends SelectColumn> columns) {
+    EntitySegment(RowReader<Object> rowReader, List<EntitySelectColumn> columns) {
         this.columns = columns;
         this.rowReader = rowReader;
     }
+    
+    RowReader<Object> getRowReader() {
+        return rowReader;
+    }
 
-    public List<? extends SelectColumn> getColumns() {
+    public List<EntitySelectColumn> getColumns() {
         return columns;
     }
 
     public RowReader<Object> getRowReader(ResultSet resultSet) throws CayenneException {
-        return rowReader;
+        return getRowReader();
     }
 }

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegmentBuilder.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegmentBuilder.java?rev=729693&r1=729692&r2=729693&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegmentBuilder.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySegmentBuilder.java Sat Dec 27 15:18:20 2008
@@ -18,46 +18,34 @@
  ****************************************************************/
 package org.apache.cayenne.access.select;
 
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.cayenne.access.types.ExtendedTypeMap;
-import org.apache.cayenne.dba.TypesMapping;
 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.query.QueryMetadata;
-import org.apache.cayenne.reflect.ClassDescriptor;
 
 /**
  * @since 3.0
  */
-class EntitySegmentBuilder {
+class EntitySegmentBuilder extends MappedColumnBuilder {
 
-    private QueryMetadata metadata;
-    private ExtendedTypeMap extendedTypes;
-    private List<EntitySelectColumn> columns;
-    private Map<DbAttribute, Integer> columnMap;
-    private DbEntity dbEntity;
-    private ClassDescriptor classDescriptor;
+    protected QueryMetadata metadata;
+    protected ObjEntity entity;
 
     EntitySegmentBuilder(QueryMetadata metadata, ExtendedTypeMap extendedTypes,
-            ClassDescriptor classDescriptor, DbEntity dbEntity) {
+            ObjEntity entity) {
 
-        this.columns = new ArrayList<EntitySelectColumn>();
-        this.columnMap = new HashMap<DbAttribute, Integer>();
-        this.dbEntity = dbEntity;
-        this.classDescriptor = classDescriptor;
+        super(extendedTypes);
+        this.entity = entity;
         this.metadata = metadata;
-        this.extendedTypes = extendedTypes;
     }
 
-    SelectDescriptor<Object> getDescriptor() {
+    List<EntitySelectColumn> buildColumns() {
         if (metadata.getPageSize() > 0) {
             appendId();
         }
@@ -65,6 +53,13 @@
             appendAll();
         }
 
+        return columns;
+    }
+
+    EntitySegment buildSegment() {
+
+        buildColumns();
+
         RowReader<Object> rowReader;
         // read single column ID as scalar
         if (metadata.getPageSize() > 0 && columns.size() == 1) {
@@ -72,15 +67,13 @@
             rowReader = new ScalarRowReader(column.getConverter(), column.getJdbcType());
         }
         else {
-            rowReader = new EntityRowReader(
-                    classDescriptor.getEntity().getName(),
-                    columns);
+            rowReader = new EntityRowReader(entity.getName(), columns);
         }
 
         return new EntitySegment(rowReader, columns);
     }
 
-    private void appendId() {
+    protected void appendId() {
         // append meaningful attributes prior to any special DbAttributes; this way if
         // there is an overlap between meaningful and Db attributes, the right Java
         // type will be used.
@@ -88,7 +81,7 @@
         appendIdDbAttributes();
     }
 
-    private void appendAll() {
+    protected void appendAll() {
         // append meaningful attributes prior to any special DbAttributes; this way if
         // there is an overlap between meaningful and Db attributes, the right Java
         // type will be used.
@@ -99,86 +92,48 @@
         appendJointPrefetches();
     }
 
-    private void appendIdObjAttributes() {
-        for (ObjAttribute attribute : classDescriptor.getEntity().getDeclaredAttributes()) {
+    protected void appendIdObjAttributes() {
+        for (ObjAttribute attribute : entity.getDeclaredAttributes()) {
 
             if (attribute.isPrimaryKey()) {
-                appendObjAttribute(attribute);
+                append(attribute);
             }
         }
     }
 
-    private void appendObjAttributes() {
-        for (ObjAttribute attribute : classDescriptor.getEntity().getDeclaredAttributes()) {
-            appendObjAttribute(attribute);
+    protected void appendObjAttributes() {
+        for (ObjAttribute attribute : entity.getDeclaredAttributes()) {
+            append(attribute);
         }
     }
 
-    private void appendIdDbAttributes() {
-        for (DbAttribute attribute : dbEntity.getPrimaryKeys()) {
-            appendDbAttrribute(attribute);
+    protected void appendIdDbAttributes() {
+
+        // if this ObjENtity inherits DbEntity from super, we will rely in super
+        // descriptor to map ID columns
+        if (entity.getDbEntityName() != null) {
+            for (DbAttribute attribute : entity.getDbEntity().getPrimaryKeys()) {
+                append(attribute);
+            }
         }
     }
 
-    private void appendFK() {
-        for (ObjRelationship relationship : classDescriptor
-                .getEntity()
-                .getDeclaredRelationships()) {
+    protected void appendFK() {
+        for (ObjRelationship relationship : entity.getDeclaredRelationships()) {
 
             DbRelationship dbRel = relationship.getDbRelationships().get(0);
 
             List<DbJoin> joins = dbRel.getJoins();
             int len = joins.size();
             for (int i = 0; i < len; i++) {
-                appendDbAttrribute(joins.get(i).getSource());
+                append(joins.get(i).getSource());
             }
         }
     }
 
-    private void appendJointPrefetches() {
+    protected void appendJointPrefetches() {
         if (metadata.getPrefetchTree() != null) {
             throw new UnsupportedOperationException("TODO: joint prefetches");
         }
     }
-
-    private void appendObjAttribute(ObjAttribute attribute) {
-        EntitySelectColumn column = new EntitySelectColumn();
-
-        DbAttribute dbAttribute = attribute.getDbAttribute();
-
-        // void column
-        if (dbAttribute == null) {
-            int jdbcType = TypesMapping.getSqlTypeByJava(attribute.getType());
-            column.setColumnName(TypesMapping.isNumeric(jdbcType) ? "1" : "'1'");
-            column.setJdbcType(jdbcType);
-        }
-        else {
-            column.setColumnName(dbAttribute.getName());
-            column.setJdbcType(dbAttribute.getType());
-        }
-
-        column.setDataRowKey(attribute.getDbAttributePath());
-        column.setPath(attribute);
-        column.setConverter(extendedTypes.getRegisteredType(attribute.getType()));
-
-        columnMap.put(dbAttribute, columns.size());
-        columns.add(column);
-    }
-
-    private void appendDbAttrribute(DbAttribute attribute) {
-        // skip if already appended via ObjAttributes
-        if (!columnMap.containsKey(attribute)) {
-
-            EntitySelectColumn column = new EntitySelectColumn();
-            column.setColumnName(attribute.getName());
-            column.setJdbcType(attribute.getType());
-            column.setDataRowKey(attribute.getName());
-
-            String javaType = TypesMapping.getJavaBySqlType(attribute.getType());
-            column.setConverter(extendedTypes.getRegisteredType(javaType));
-
-            columnMap.put(attribute, columns.size());
-            columns.add(column);
-        }
-    }
-}
\ No newline at end of file
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySelectColumn.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySelectColumn.java?rev=729693&r1=729692&r2=729693&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySelectColumn.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntitySelectColumn.java Sat Dec 27 15:18:20 2008
@@ -18,14 +18,11 @@
  ****************************************************************/
 package org.apache.cayenne.access.select;
 
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjAttribute;
 
 /**
  * @since 3.0
@@ -71,19 +68,7 @@
         this.jdbcType = jdbcType;
     }
 
-    void setPath(ObjAttribute attribute) {
-
-        List<DbRelationship> path = new ArrayList<DbRelationship>(2);
-        Iterator<?> it = attribute.getDbPathIterator();
-        while (it.hasNext()) {
-            Object pathComponent = it.next();
-            if (!(pathComponent instanceof DbRelationship)) {
-                break;
-            }
-
-            path.add((DbRelationship) pathComponent);
-        }
-
+    void setPath(List<DbRelationship> path) {
         this.path = path;
     }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeRowReader.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeRowReader.java?rev=729693&r1=729692&r2=729693&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeRowReader.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeRowReader.java Sat Dec 27 15:18:20 2008
@@ -32,15 +32,15 @@
 class EntityTreeRowReader implements RowReader<Object> {
 
     private RowReader<?> discriminatorReader;
-    private Expression[] discriminatorExpressions;
-    private RowReader<Object>[] rowReaders;
+    private Expression[] entityQualifiers;
+    private RowReader<Object>[] entityReaders;
 
-    EntityTreeRowReader(RowReader<?> discriminatorReader,
-            Expression[] discriminatorExpressions, RowReader<Object>[] rowReaders) {
+    EntityTreeRowReader(RowReader<?> discriminatorReader, Expression[] entityQualifiers,
+            RowReader<Object>[] entityReaders) {
 
         this.discriminatorReader = discriminatorReader;
-        this.discriminatorExpressions = discriminatorExpressions;
-        this.rowReaders = rowReaders;
+        this.entityQualifiers = entityQualifiers;
+        this.entityReaders = entityReaders;
     }
 
     public void setColumnOffset(int offset) {
@@ -52,14 +52,17 @@
         Map<String, Object> discriminator = (Map<String, Object>) discriminatorReader
                 .readRow(resultSet);
 
-        int len = discriminatorExpressions.length;
-        for (int i = 0; i < len; i++) {
+        // read qualifiers list in reverse order to ensure that for each superclass,
+        // subclass qualifiers are run first... This way we can even support empty
+        // qualifier superclasses.
+        int len = entityQualifiers.length;
+        for (int i = len - 1; i >= 0; i--) {
 
             // TODO: andrus, 12/25/2008 - Expression in-memory evaluation for each row in
             // a ResultSet will be a very slow operation. This procedure should be
             // optimized somehow...
-            if (discriminatorExpressions[i].match(discriminator)) {
-                return rowReaders[i].readRow(resultSet);
+            if (entityQualifiers[i].match(discriminator)) {
+                return entityReaders[i].readRow(resultSet);
             }
         }
 

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilder.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilder.java?rev=729693&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilder.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilder.java Sat Dec 27 15:18:20 2008
@@ -0,0 +1,157 @@
+/*****************************************************************
+ *   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.select;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTTrue;
+import org.apache.cayenne.map.EntityInheritanceTree;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.reflect.ClassDescriptor;
+
+/**
+ * @since 3.0
+ */
+class EntityTreeSegmentBuilder {
+
+    private QueryMetadata metadata;
+    protected ExtendedTypeMap extendedTypes;
+
+    private List<Expression> entityQualifiers;
+    private List<RowReader<?>> entityReaders;
+
+    protected ClassDescriptor classDescriptor;
+    protected List<EntitySelectColumn> columns;
+    protected Map<String, Integer> columnMap;
+
+    EntityTreeSegmentBuilder(QueryMetadata metadata, ExtendedTypeMap extendedTypes,
+            ClassDescriptor classDescriptor) {
+
+        this.metadata = metadata;
+        this.extendedTypes = extendedTypes;
+        this.classDescriptor = classDescriptor;
+
+        this.entityQualifiers = new ArrayList<Expression>();
+        this.entityReaders = new ArrayList<RowReader<?>>();
+        this.columns = new ArrayList<EntitySelectColumn>();
+        this.columnMap = new HashMap<String, Integer>();
+    }
+
+    SelectDescriptor<Object> getDescriptor() {
+
+        EntityInheritanceTree inheritanceTree = classDescriptor
+                .getEntityInheritanceTree();
+
+        if (inheritanceTree == null) {
+            throw new IllegalStateException(
+                    "EntityNonUnionTreeSegmentBuilder should only be used when inheritance is involved.");
+        }
+
+        appendColumns(inheritanceTree, null);
+
+        RowReader<Object> discriminatorReader = mergeColumns(
+                null,
+                new DiscriminatorBuilder(extendedTypes, inheritanceTree).buildColumns());
+
+        int size = this.entityQualifiers.size();
+        Expression[] entityQualifiers = this.entityQualifiers
+                .toArray(new Expression[size]);
+        RowReader<Object>[] entityReaders = this.entityReaders
+                .toArray(new RowReader[size]);
+        RowReader<Object> rowReader = new EntityTreeRowReader(
+                discriminatorReader,
+                entityQualifiers,
+                entityReaders);
+        return new EntitySegment(rowReader, columns);
+    }
+
+    /**
+     * Merges subsegment columns into the main columns list, generating a RowReader for
+     * subsegment.
+     */
+    private EntityRowReader mergeColumns(
+            String entityName,
+            List<EntitySelectColumn> columnsToMerge) {
+
+        int[] indexes = new int[columnsToMerge.size()];
+
+        for (int i = 0; i < indexes.length; i++) {
+
+            EntitySelectColumn column = columnsToMerge.get(i);
+
+            Integer columnIndex = columnMap.get(column.getDataRowKey());
+            if (columnIndex == null) {
+                columnIndex = columns.size();
+                columnMap.put(column.getDataRowKey(), columnIndex);
+                columns.add(column);
+            }
+
+            indexes[i] = columnIndex.intValue() + 1;
+        }
+
+        return new EntityRowReader(entityName, columnsToMerge, indexes);
+    }
+
+    private void appendColumns(EntityInheritanceTree node, EntityRowReader superReader) {
+
+        EntityRowReader reader = processNode(node);
+        reader.setSuperReader(superReader);
+        
+        for (EntityInheritanceTree childNode : node.getChildren()) {
+            appendColumns(childNode, reader);
+        }
+    }
+
+    private EntityRowReader processNode(EntityInheritanceTree node) {
+
+        List<EntitySelectColumn> entityColumns = new EntitySegmentBuilder(
+                metadata,
+                extendedTypes,
+                node.getEntity()).buildColumns();
+
+        // merge columns
+        EntityRowReader rowReader = mergeColumns(
+                node.getEntity().getName(),
+                entityColumns);
+
+        // record entity qualifier and row reader...
+        if (!node.getEntity().isAbstract()) {
+
+            // register DB qualifier
+            Expression qualifier = node.getDbQualifier();
+
+            if (qualifier == null) {
+                qualifier = new ASTTrue();
+            }
+
+            entityQualifiers.add(qualifier);
+
+            // TODO: this row reader has incorrect offset and doesn't take into account
+            // superclass columns
+            entityReaders.add(rowReader);
+        }
+        
+        return rowReader;
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/MappedColumnBuilder.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/MappedColumnBuilder.java?rev=729693&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/MappedColumnBuilder.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/MappedColumnBuilder.java Sat Dec 27 15:18:20 2008
@@ -0,0 +1,192 @@
+/*****************************************************************
+ *   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.select;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+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;
+
+/**
+ * @since 3.0
+ */
+abstract class MappedColumnBuilder {
+
+    protected List<EntitySelectColumn> columns;
+    protected Map<String, Integer> columnMap;
+    protected ExtendedTypeMap extendedTypes;
+
+    MappedColumnBuilder(ExtendedTypeMap extendedTypes) {
+        this.columns = new ArrayList<EntitySelectColumn>();
+        this.columnMap = new HashMap<String, Integer>();
+        this.extendedTypes = extendedTypes;
+    }
+
+    /**
+     * Appends an ObjAttribute belonging to a root ObjEntity.
+     */
+    protected void append(ObjAttribute attribute) {
+
+        if (!columnMap.containsKey(attribute.getDbAttributePath())) {
+
+            List<DbRelationship> path = new ArrayList<DbRelationship>(2);
+            Iterator<?> it = attribute.getDbPathIterator();
+            while (it.hasNext()) {
+                Object pathComponent = it.next();
+                if (!(pathComponent instanceof DbRelationship)) {
+                    break;
+                }
+
+                path.add((DbRelationship) pathComponent);
+            }
+
+            makeColumn(attribute.getDbAttributePath(), attribute, path);
+        }
+    }
+
+    /**
+     * Appends a DbAttribute belonging to a root DbEntity.
+     */
+    protected void append(DbAttribute attribute) {
+        // skip if already appended via ObjAttributes
+        if (!columnMap.containsKey(attribute.getName())) {
+            makeColumn(attribute.getName(), attribute, null);
+        }
+    }
+
+    /**
+     * Appends a column matching a path Expression rooted in DbEntity.
+     */
+    protected void append(DbEntity root, Expression dbPath) {
+
+        String pathString = dbPath.getOperand(0).toString();
+        if (!columnMap.containsKey(pathString)) {
+
+            List<DbRelationship> relationships = null;
+
+            for (PathComponent<DbAttribute, DbRelationship> c : root.resolvePath(
+                    dbPath,
+                    Collections.emptyMap())) {
+
+                if (c.isLast()) {
+                    makeColumn(pathString, c.getAttribute(), relationships);
+                }
+                else {
+
+                    if (relationships == null) {
+                        relationships = new ArrayList<DbRelationship>(2);
+                    }
+
+                    relationships.add(c.getRelationship());
+                }
+            }
+        }
+    }
+
+    /**
+     * Appends a column matching a path Expression rooted in ObjEntity.
+     */
+    protected void append(ObjEntity root, Expression objPath) {
+
+        Expression dbPath = root.translateToDbPath(objPath);
+        String pathString = dbPath.getOperand(0).toString();
+        if (!columnMap.containsKey(pathString)) {
+
+            List<DbRelationship> relationships = null;
+
+            for (PathComponent<ObjAttribute, ObjRelationship> c : root.resolvePath(
+                    objPath,
+                    Collections.emptyMap())) {
+
+                if (c.isLast()) {
+                    makeColumn(pathString, c.getAttribute(), relationships);
+                }
+                else {
+
+                    if (relationships == null) {
+                        relationships = new ArrayList<DbRelationship>(2);
+                    }
+
+                    relationships.addAll(c.getRelationship().getDbRelationships());
+                }
+            }
+        }
+    }
+
+    private EntitySelectColumn makeColumn(
+            String dataRowKey,
+            ObjAttribute attribute,
+            List<DbRelationship> relationships) {
+
+        EntitySelectColumn column = new EntitySelectColumn();
+        DbAttribute dbAttribute = attribute.getDbAttribute();
+
+        // void column
+        if (dbAttribute == null) {
+            int jdbcType = TypesMapping.getSqlTypeByJava(attribute.getType());
+            column.setColumnName(TypesMapping.isNumeric(jdbcType) ? "1" : "'1'");
+            column.setJdbcType(jdbcType);
+        }
+        else {
+            column.setColumnName(dbAttribute.getName());
+            column.setJdbcType(dbAttribute.getType());
+        }
+
+        column.setDataRowKey(dataRowKey);
+        column.setConverter(extendedTypes.getRegisteredType(attribute.getType()));
+        column.setPath(relationships);
+
+        columnMap.put(dataRowKey, columns.size());
+        columns.add(column);
+
+        return column;
+    }
+
+    private EntitySelectColumn makeColumn(
+            String dataRowKey,
+            DbAttribute attribute,
+            List<DbRelationship> relationships) {
+        EntitySelectColumn column = new EntitySelectColumn();
+        column.setColumnName(attribute.getName());
+        column.setJdbcType(attribute.getType());
+        column.setDataRowKey(dataRowKey);
+
+        String javaType = TypesMapping.getJavaBySqlType(attribute.getType());
+        column.setConverter(extendedTypes.getRegisteredType(javaType));
+        column.setPath(relationships);
+
+        columnMap.put(dataRowKey, columns.size());
+        columns.add(column);
+
+        return column;
+    }
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/SelectDescriptorBuilder.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/SelectDescriptorBuilder.java?rev=729693&r1=729692&r2=729693&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/SelectDescriptorBuilder.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/select/SelectDescriptorBuilder.java Sat Dec 27 15:18:20 2008
@@ -124,14 +124,12 @@
         }
 
         if (classDescriptor.getEntityInheritanceTree() != null) {
-            throw new UnsupportedOperationException("TODO: Inheritance aware queries");
+            return new EntityTreeSegmentBuilder(metadata, extendedTypes, classDescriptor)
+                    .getDescriptor();
         }
         else {
-            return new EntitySegmentBuilder(
-                    metadata,
-                    extendedTypes,
-                    classDescriptor,
-                    classDescriptor.getEntity().getDbEntity()).getDescriptor();
+            return new EntitySegmentBuilder(metadata, extendedTypes, classDescriptor
+                    .getEntity()).buildSegment();
         }
     }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityInheritanceTree.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityInheritanceTree.java?rev=729693&r1=729692&r2=729693&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityInheritanceTree.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/map/EntityInheritanceTree.java Sat Dec 27 15:18:20 2008
@@ -87,19 +87,33 @@
             }
         }
 
-        Expression qualifier = entity.getDeclaredQualifier();
+        Expression qualifier = getDbQualifier();
         if (qualifier != null) {
-            if (normalizedQualifier == null) {
-                normalizedQualifier = entity.translateToDbPath(qualifier);
-            }
-
-            return normalizedQualifier.match(row) ? entity : null;
+            return qualifier.match(row) ? entity : null;
         }
 
         // no qualifier ... matches all rows
         return entity;
     }
 
+    /**
+     * Returns entity qualifier expressed as DB path qualifier or null if entity has no
+     * qualifier.
+     * 
+     * @since 3.0
+     */
+    public Expression getDbQualifier() {
+        if (entity.getDeclaredQualifier() == null) {
+            return null;
+        }
+
+        if (normalizedQualifier == null) {
+            normalizedQualifier = entity.translateToDbPath(entity.getDeclaredQualifier());
+        }
+
+        return normalizedQualifier;
+    }
+
     public void addChildNode(EntityInheritanceTree node) {
         if (subentities == null) {
             subentities = new ArrayList<EntityInheritanceTree>(2);

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilderTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilderTest.java?rev=729693&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilderTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/select/EntityTreeSegmentBuilderTest.java Sat Dec 27 15:18:20 2008
@@ -0,0 +1,147 @@
+/*****************************************************************
+ *   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.select;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.DataRow;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.testdo.inherit.AbstractPerson;
+import org.apache.cayenne.unit.PeopleCase;
+
+import com.mockrunner.mock.jdbc.MockResultSet;
+
+public class EntityTreeSegmentBuilderTest extends PeopleCase {
+
+    public void testGetDescriptorColumns() {
+
+        SelectQuery query = new SelectQuery(AbstractPerson.class);
+
+        EntityResolver resolver = getDomain().getEntityResolver();
+        QueryMetadata md = query.getMetaData(resolver);
+        ClassDescriptor descriptor = md.getClassDescriptor();
+        ExtendedTypeMap converters = getDomain()
+                .getDataNodes()
+                .iterator()
+                .next()
+                .getAdapter()
+                .getExtendedTypes();
+
+        SelectDescriptor<Object> select = new EntityTreeSegmentBuilder(
+                md,
+                converters,
+                descriptor).getDescriptor();
+
+        List<? extends SelectColumn> columns = select.getColumns();
+
+        Collection<String> columnNames = new ArrayList<String>(Arrays.asList(
+                "CLIENT_COMPANY_ID",
+                "CLIENT_CONTACT_TYPE",
+                "DEPARTMENT_ID",
+                "NAME",
+                "PERSON_ID",
+                "PERSON_TYPE",
+                "SALARY"));
+
+        for (SelectColumn column : columns) {
+            columnNames.remove(column.getDataRowKey());
+        }
+
+        assertTrue("Missing columns: " + columnNames, columnNames.isEmpty());
+        assertEquals("Unexpected columns present", 7, columns.size());
+    }
+
+    public void testGetDescriptorRowReader() throws Exception {
+        SelectQuery query = new SelectQuery(AbstractPerson.class);
+
+        EntityResolver resolver = getDomain().getEntityResolver();
+        QueryMetadata md = query.getMetaData(resolver);
+        ClassDescriptor descriptor = md.getClassDescriptor();
+        ExtendedTypeMap converters = getDomain()
+                .getDataNodes()
+                .iterator()
+                .next()
+                .getAdapter()
+                .getExtendedTypes();
+
+        SelectDescriptor<Object> select = new EntityTreeSegmentBuilder(
+                md,
+                converters,
+                descriptor).getDescriptor();
+
+        List<? extends SelectColumn> columns = select.getColumns();
+
+        Map<String, Object> employeeRowMap = new HashMap<String, Object>();
+        employeeRowMap.put("PERSON_ID", 1);
+        employeeRowMap.put("PERSON_TYPE", "EE");
+        employeeRowMap.put("NAME", "E1");
+        employeeRowMap.put("SALARY", new Float(1.0));
+        employeeRowMap.put("DEPARTMENT_ID", 1);
+
+        Map<String, Object> managerRowMap = new HashMap<String, Object>();
+        managerRowMap.put("PERSON_ID", 2);
+        managerRowMap.put("PERSON_TYPE", "EM");
+        managerRowMap.put("NAME", "E2");
+        managerRowMap.put("SALARY", new Float(2.0));
+        managerRowMap.put("DEPARTMENT_ID", 2);
+
+        Map<String, Object> crRowMap = new HashMap<String, Object>();
+        crRowMap.put("PERSON_ID", 3);
+        crRowMap.put("PERSON_TYPE", "C");
+        crRowMap.put("NAME", "E2");
+        crRowMap.put("CLIENT_CONTACT_TYPE", "XX");
+        crRowMap.put("CLIENT_COMPANY_ID", 3);
+
+        List<Object> employeeRow = new ArrayList<Object>();
+        List<Object> managerRow = new ArrayList<Object>();
+        List<Object> crRow = new ArrayList<Object>();
+
+        MockResultSet rs = new MockResultSet("test");
+        for (SelectColumn column : columns) {
+            rs.addColumn(column.getColumnName(0, null));
+
+            employeeRow.add(employeeRowMap.get(column.getDataRowKey()));
+            managerRow.add(managerRowMap.get(column.getDataRowKey()));
+            crRow.add(crRowMap.get(column.getDataRowKey()));
+        }
+
+        rs.addRow(employeeRow);
+        rs.addRow(managerRow);
+        rs.addRow(crRow);
+
+        RowReader<Object> reader = select.getRowReader(rs);
+
+        rs.next();
+        DataRow employeeRowRead = (DataRow) reader.readRow(rs);
+        assertEquals("Employee", employeeRowRead.getEntityName());
+        assertEquals(
+                "Invalid row read: " + employeeRowRead,
+                employeeRowMap,
+                employeeRowRead);
+    }
+}