Search in sources :

Example 1 with PrefetchTreeNode

use of org.apache.cayenne.query.PrefetchTreeNode in project cayenne by apache.

the class DefaultSelectTranslator method appendQueryColumns.

/**
 * Appends columns needed for object SelectQuery to the provided columns
 * list.
 */
<T> List<ColumnDescriptor> appendQueryColumns(final List<ColumnDescriptor> columns, SelectQuery<T> query, ClassDescriptor descriptor, final String tableAlias) {
    final Set<ColumnTracker> attributes = new HashSet<>();
    // fetched attributes include attributes that are either:
    // 
    // * class properties
    // * PK
    // * FK used in relationship
    // * joined prefetch PK
    ObjEntity oe = descriptor.getEntity();
    PropertyVisitor visitor = new PropertyVisitor() {

        public boolean visitAttribute(AttributeProperty property) {
            ObjAttribute oa = property.getAttribute();
            resetJoinStack();
            Iterator<CayenneMapEntry> dbPathIterator = oa.getDbPathIterator();
            while (dbPathIterator.hasNext()) {
                Object pathPart = dbPathIterator.next();
                if (pathPart == null) {
                    throw new CayenneRuntimeException("ObjAttribute has no component: %s", oa.getName());
                } else if (pathPart instanceof DbRelationship) {
                    DbRelationship rel = (DbRelationship) pathPart;
                    dbRelationshipAdded(rel, JoinType.LEFT_OUTER, null);
                } else if (pathPart instanceof DbAttribute) {
                    DbAttribute dbAttr = (DbAttribute) pathPart;
                    appendColumn(columns, oa, dbAttr, attributes, null, tableAlias);
                }
            }
            return true;
        }

        public boolean visitToMany(ToManyProperty property) {
            visitRelationship(property);
            return true;
        }

        public boolean visitToOne(ToOneProperty property) {
            visitRelationship(property);
            return true;
        }

        private void visitRelationship(ArcProperty property) {
            resetJoinStack();
            ObjRelationship rel = property.getRelationship();
            DbRelationship dbRel = rel.getDbRelationships().get(0);
            List<DbJoin> joins = dbRel.getJoins();
            for (DbJoin join : joins) {
                DbAttribute src = join.getSource();
                appendColumn(columns, null, src, attributes, null, tableAlias);
            }
        }
    };
    descriptor.visitAllProperties(visitor);
    // stack should be reset, because all root table attributes go with "t0"
    // table alias
    resetJoinStack();
    // add remaining needed attrs from DbEntity
    DbEntity table = oe.getDbEntity();
    for (DbAttribute dba : table.getPrimaryKeys()) {
        appendColumn(columns, null, dba, attributes, null, tableAlias);
    }
    if (query instanceof PrefetchSelectQuery) {
        // for each relationship path add PK of the target entity...
        for (String path : ((PrefetchSelectQuery) query).getResultPaths()) {
            ASTDbPath pathExp = (ASTDbPath) oe.translateToDbPath(ExpressionFactory.exp(path));
            // add joins and find terminating element
            resetJoinStack();
            PathComponent<DbAttribute, DbRelationship> lastComponent = null;
            for (PathComponent<DbAttribute, DbRelationship> component : table.resolvePath(pathExp, getPathAliases())) {
                if (component.getRelationship() != null) {
                    // do not invoke dbRelationshipAdded(), invoke
                    // pushJoin() instead. This is to prevent
                    // 'forcingDistinct' flipping to true, that will result
                    // in unneeded extra processing and sometimes in invalid
                    // results (see CAY-1979). Distinctness of each row is
                    // guaranteed by the prefetch query semantics - we
                    // include target ID in the result columns
                    getJoinStack().pushJoin(component.getRelationship(), component.getJoinType(), null);
                }
                lastComponent = component;
            }
            // process terminating element
            if (lastComponent != null) {
                DbRelationship relationship = lastComponent.getRelationship();
                if (relationship != null) {
                    String labelPrefix = pathExp.getPath();
                    DbEntity targetEntity = relationship.getTargetEntity();
                    for (DbAttribute pk : targetEntity.getPrimaryKeys()) {
                        // note that we my select a source attribute, but
                        // label it as
                        // target for simplified snapshot processing
                        appendColumn(columns, null, pk, attributes, labelPrefix + '.' + pk.getName());
                    }
                }
            }
        }
    }
    // handle joint prefetches directly attached to this query...
    if (query.getPrefetchTree() != null) {
        // perform some sort of union or sub-queries.
        for (PrefetchTreeNode prefetch : query.getPrefetchTree().getChildren()) {
            prefetch.setEntityName(oe.getName());
        }
        for (PrefetchTreeNode prefetch : query.getPrefetchTree().adjacentJointNodes()) {
            // for each prefetch add all joins plus columns from the target
            // entity
            Expression prefetchExp = ExpressionFactory.exp(prefetch.getPath());
            ASTDbPath dbPrefetch = (ASTDbPath) oe.translateToDbPath(prefetchExp);
            resetJoinStack();
            DbRelationship r = null;
            for (PathComponent<DbAttribute, DbRelationship> component : table.resolvePath(dbPrefetch, getPathAliases())) {
                r = component.getRelationship();
                dbRelationshipAdded(r, JoinType.LEFT_OUTER, null);
            }
            if (r == null) {
                throw new CayenneRuntimeException("Invalid joint prefetch '%s' for entity: %s", prefetch, oe.getName());
            }
            // add columns from the target entity, including those that are matched
            // against the FK of the source entity.
            // This is needed to determine whether optional relationships are null
            // go via target OE to make sure that Java types are mapped correctly...
            ObjRelationship targetRel = (ObjRelationship) prefetchExp.evaluate(oe);
            ObjEntity targetEntity = targetRel.getTargetEntity();
            String labelPrefix = dbPrefetch.getPath();
            PropertyVisitor prefetchVisitor = new PropertyVisitor() {

                public boolean visitAttribute(AttributeProperty property) {
                    ObjAttribute oa = property.getAttribute();
                    Iterator<CayenneMapEntry> dbPathIterator = oa.getDbPathIterator();
                    while (dbPathIterator.hasNext()) {
                        Object pathPart = dbPathIterator.next();
                        if (pathPart == null) {
                            throw new CayenneRuntimeException("ObjAttribute has no component: %s", oa.getName());
                        } else if (pathPart instanceof DbRelationship) {
                            DbRelationship rel = (DbRelationship) pathPart;
                            dbRelationshipAdded(rel, JoinType.INNER, null);
                        } else if (pathPart instanceof DbAttribute) {
                            DbAttribute dbAttr = (DbAttribute) pathPart;
                            appendColumn(columns, oa, dbAttr, attributes, labelPrefix + '.' + dbAttr.getName());
                        }
                    }
                    return true;
                }

                public boolean visitToMany(ToManyProperty property) {
                    return true;
                }

                public boolean visitToOne(ToOneProperty property) {
                    return true;
                }
            };
            ClassDescriptor prefetchClassDescriptor = entityResolver.getClassDescriptor(targetEntity.getName());
            prefetchClassDescriptor.visitAllProperties(prefetchVisitor);
            // append remaining target attributes such as keys
            DbEntity targetDbEntity = r.getTargetEntity();
            for (DbAttribute attribute : targetDbEntity.getAttributes()) {
                appendColumn(columns, null, attribute, attributes, labelPrefix + '.' + attribute.getName());
            }
        }
    }
    return columns;
}
Also used : ArcProperty(org.apache.cayenne.reflect.ArcProperty) ObjAttribute(org.apache.cayenne.map.ObjAttribute) ASTDbPath(org.apache.cayenne.exp.parser.ASTDbPath) ClassDescriptor(org.apache.cayenne.reflect.ClassDescriptor) CayenneRuntimeException(org.apache.cayenne.CayenneRuntimeException) DbAttribute(org.apache.cayenne.map.DbAttribute) ToOneProperty(org.apache.cayenne.reflect.ToOneProperty) CayenneMapEntry(org.apache.cayenne.util.CayenneMapEntry) DbEntity(org.apache.cayenne.map.DbEntity) PrefetchTreeNode(org.apache.cayenne.query.PrefetchTreeNode) HashSet(java.util.HashSet) ObjRelationship(org.apache.cayenne.map.ObjRelationship) AttributeProperty(org.apache.cayenne.reflect.AttributeProperty) PrefetchSelectQuery(org.apache.cayenne.query.PrefetchSelectQuery) ObjEntity(org.apache.cayenne.map.ObjEntity) ToManyProperty(org.apache.cayenne.reflect.ToManyProperty) Expression(org.apache.cayenne.exp.Expression) DbRelationship(org.apache.cayenne.map.DbRelationship) DbJoin(org.apache.cayenne.map.DbJoin) PropertyVisitor(org.apache.cayenne.reflect.PropertyVisitor)

Example 2 with PrefetchTreeNode

use of org.apache.cayenne.query.PrefetchTreeNode in project cayenne by apache.

the class SelectAction method forSuppressedDistinct.

private <T> ResultIterator<T> forSuppressedDistinct(ResultIterator<T> iterator, SelectTranslator translator) {
    if (!translator.isSuppressingDistinct() || queryMetadata.isSuppressingDistinct()) {
        return iterator;
    }
    // wrap result iterator if distinct has to be suppressed
    // a joint prefetch warrants full row compare
    final boolean[] compareFullRows = new boolean[1];
    compareFullRows[0] = translator.hasJoins();
    final PrefetchTreeNode rootPrefetch = queryMetadata.getPrefetchTree();
    if (!compareFullRows[0] && rootPrefetch != null) {
        rootPrefetch.traverse(new PrefetchProcessor() {

            @Override
            public void finishPrefetch(PrefetchTreeNode node) {
            }

            @Override
            public boolean startDisjointPrefetch(PrefetchTreeNode node) {
                // continue to children only if we are at root
                return rootPrefetch == node;
            }

            @Override
            public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) {
                // continue to children only if we are at root
                return rootPrefetch == node;
            }

            @Override
            public boolean startUnknownPrefetch(PrefetchTreeNode node) {
                // continue to children only if we are at root
                return rootPrefetch == node;
            }

            @Override
            public boolean startJointPrefetch(PrefetchTreeNode node) {
                if (rootPrefetch != node) {
                    compareFullRows[0] = true;
                    return false;
                }
                return true;
            }

            @Override
            public boolean startPhantomPrefetch(PrefetchTreeNode node) {
                return true;
            }
        });
    }
    return new DistinctResultIterator<>(iterator, queryMetadata.getDbEntity(), compareFullRows[0]);
}
Also used : PrefetchTreeNode(org.apache.cayenne.query.PrefetchTreeNode) PrefetchProcessor(org.apache.cayenne.query.PrefetchProcessor)

Example 3 with PrefetchTreeNode

use of org.apache.cayenne.query.PrefetchTreeNode in project cayenne by apache.

the class PrefetchNodeStage method processJoint.

private void processJoint(TranslatorContext context) {
    QueryMetadata queryMetadata = context.getMetadata();
    PrefetchTreeNode prefetch = queryMetadata.getPrefetchTree();
    if (prefetch == null) {
        return;
    }
    ObjEntity objEntity = queryMetadata.getObjEntity();
    boolean warnPrefetchWithLimit = false;
    for (PrefetchTreeNode node : prefetch.adjacentJointNodes()) {
        Expression prefetchExp = ExpressionFactory.exp(node.getPath());
        ASTDbPath dbPrefetch = (ASTDbPath) objEntity.translateToDbPath(prefetchExp);
        final String dbPath = dbPrefetch.getPath();
        DbEntity dbEntity = objEntity.getDbEntity();
        PathComponents components = new PathComponents(dbPath);
        StringBuilder fullPath = new StringBuilder();
        for (String c : components.getAll()) {
            DbRelationship rel = dbEntity.getRelationship(c);
            if (rel == null) {
                throw new CayenneRuntimeException("Unable to resolve path %s for entity %s", dbPath, objEntity.getName());
            }
            if (fullPath.length() > 0) {
                fullPath.append('.');
            }
            context.getTableTree().addJoinTable("p:" + fullPath.append(c).toString(), rel, JoinType.LEFT_OUTER);
            dbEntity = rel.getTargetEntity();
        }
        ObjRelationship targetRel = (ObjRelationship) prefetchExp.evaluate(objEntity);
        ClassDescriptor prefetchClassDescriptor = context.getResolver().getClassDescriptor(targetRel.getTargetEntityName());
        DescriptorColumnExtractor columnExtractor = new DescriptorColumnExtractor(context, prefetchClassDescriptor);
        columnExtractor.extract("p:" + dbPath);
        if (!warnPrefetchWithLimit && targetRel.isToMany() && (queryMetadata.getFetchLimit() > 0 || queryMetadata.getFetchOffset() > 0)) {
            warnPrefetchWithLimit = true;
        }
    }
    // warn about a potentially faulty joint prefetch + limit combination
    if (warnPrefetchWithLimit) {
        LOGGER.warn("The query uses both limit/offset and a joint prefetch, this most probably will lead to an incorrect result. " + "Either use disjointById prefetch or get a full result set.");
    }
}
Also used : ObjRelationship(org.apache.cayenne.map.ObjRelationship) QueryMetadata(org.apache.cayenne.query.QueryMetadata) ASTDbPath(org.apache.cayenne.exp.parser.ASTDbPath) ClassDescriptor(org.apache.cayenne.reflect.ClassDescriptor) CayenneRuntimeException(org.apache.cayenne.CayenneRuntimeException) ObjEntity(org.apache.cayenne.map.ObjEntity) DbEntity(org.apache.cayenne.map.DbEntity) Expression(org.apache.cayenne.exp.Expression) PrefetchTreeNode(org.apache.cayenne.query.PrefetchTreeNode) DbRelationship(org.apache.cayenne.map.DbRelationship)

Example 4 with PrefetchTreeNode

use of org.apache.cayenne.query.PrefetchTreeNode in project cayenne by apache.

the class PrefetchProcessorJointNode method buildRowMapping.

/**
 * Configures row columns mapping for this node entity.
 */
private void buildRowMapping() {
    final Map<String, ColumnDescriptor> targetSource = new TreeMap<>();
    // build a DB path .. find parent node that terminates the joint group...
    PrefetchTreeNode jointRoot = this;
    while (jointRoot.getParent() != null && !jointRoot.isDisjointPrefetch() && !jointRoot.isDisjointByIdPrefetch()) {
        jointRoot = jointRoot.getParent();
    }
    final String prefix;
    if (jointRoot != this) {
        Expression objectPath = ExpressionFactory.exp(getPath(jointRoot));
        ASTPath translated = (ASTPath) ((PrefetchProcessorNode) jointRoot).getResolver().getEntity().translateToDbPath(objectPath);
        // make sure we do not include "db:" prefix
        prefix = translated.getOperand(0) + ".";
    } else {
        prefix = "";
    }
    if (getParent() != null && !getParent().isPhantom() && getIncoming() != null && !getIncoming().getRelationship().isFlattened()) {
        DbRelationship r = getIncoming().getRelationship().getDbRelationships().get(0);
        for (final DbJoin join : r.getJoins()) {
            appendColumn(targetSource, join.getTargetName(), prefix + join.getTargetName());
        }
    }
    ClassDescriptor descriptor = resolver.getDescriptor();
    descriptor.visitAllProperties(new PropertyVisitor() {

        public boolean visitAttribute(AttributeProperty property) {
            String target = property.getAttribute().getDbAttributePath();
            if (!property.getAttribute().isLazy()) {
                appendColumn(targetSource, target, prefix + target);
            }
            return true;
        }

        public boolean visitToMany(ToManyProperty property) {
            return visitRelationship(property);
        }

        public boolean visitToOne(ToOneProperty property) {
            return visitRelationship(property);
        }

        private boolean visitRelationship(ArcProperty arc) {
            DbRelationship dbRel = arc.getRelationship().getDbRelationships().get(0);
            for (DbAttribute attribute : dbRel.getSourceAttributes()) {
                String target = attribute.getName();
                appendColumn(targetSource, target, prefix + target);
            }
            return true;
        }
    });
    // append id columns ... (some may have been appended already via relationships)
    for (String pkName : descriptor.getEntity().getPrimaryKeyNames()) {
        appendColumn(targetSource, pkName, prefix + pkName);
    }
    // append inheritance discriminator columns...
    for (ObjAttribute column : descriptor.getDiscriminatorColumns()) {
        String target = column.getDbAttributePath();
        appendColumn(targetSource, target, prefix + target);
    }
    int size = targetSource.size();
    this.rowCapacity = (int) Math.ceil(size / 0.75);
    this.columns = new ColumnDescriptor[size];
    targetSource.values().toArray(columns);
}
Also used : ArcProperty(org.apache.cayenne.reflect.ArcProperty) ClassDescriptor(org.apache.cayenne.reflect.ClassDescriptor) ObjAttribute(org.apache.cayenne.map.ObjAttribute) ColumnDescriptor(org.apache.cayenne.access.jdbc.ColumnDescriptor) DbAttribute(org.apache.cayenne.map.DbAttribute) TreeMap(java.util.TreeMap) AttributeProperty(org.apache.cayenne.reflect.AttributeProperty) ToOneProperty(org.apache.cayenne.reflect.ToOneProperty) ToManyProperty(org.apache.cayenne.reflect.ToManyProperty) Expression(org.apache.cayenne.exp.Expression) PrefetchTreeNode(org.apache.cayenne.query.PrefetchTreeNode) DbRelationship(org.apache.cayenne.map.DbRelationship) DbJoin(org.apache.cayenne.map.DbJoin) ASTPath(org.apache.cayenne.exp.parser.ASTPath) PropertyVisitor(org.apache.cayenne.reflect.PropertyVisitor)

Example 5 with PrefetchTreeNode

use of org.apache.cayenne.query.PrefetchTreeNode in project cayenne by apache.

the class EJBQLIdentifierColumnsTranslator method addPrefetchedColumnsIfAny.

private void addPrefetchedColumnsIfAny(final String visitedIdentifier) {
    PrefetchTreeNode prefetchTree = context.getCompiledExpression().getPrefetchTree();
    if (prefetchTree != null) {
        for (PrefetchTreeNode prefetch : prefetchTree.adjacentJointNodes()) {
            ClassDescriptor descriptor = context.getEntityDescriptor(prefetch.getEjbqlPathEntityId());
            if (visitedIdentifier.equals(prefetch.getEjbqlPathEntityId())) {
                DbEntity table = descriptor.getRootDbEntities().iterator().next();
                ObjEntity objectEntity = descriptor.getEntity();
                prefetch.setEntityName(objectEntity.getName());
                Expression prefetchExp = ExpressionFactory.exp(prefetch.getPath());
                Expression dbPrefetch = objectEntity.translateToDbPath(prefetchExp);
                DbRelationship r = null;
                for (PathComponent<DbAttribute, DbRelationship> component : table.resolvePath(dbPrefetch, context.getMetadata().getPathSplitAliases())) {
                    r = component.getRelationship();
                }
                if (r == null) {
                    throw new CayenneRuntimeException("Invalid joint prefetch '%s' for entity: %s", prefetch, objectEntity.getName());
                }
                for (DbAttribute attribute : r.getTargetEntity().getAttributes()) {
                    appendColumn(prefetch.getEjbqlPathEntityId() + "." + prefetch.getPath(), attribute, "", prefetch.getPath() + "." + attribute.getName(), null);
                }
            }
        }
    }
}
Also used : ObjEntity(org.apache.cayenne.map.ObjEntity) ClassDescriptor(org.apache.cayenne.reflect.ClassDescriptor) DbEntity(org.apache.cayenne.map.DbEntity) EJBQLExpression(org.apache.cayenne.ejbql.EJBQLExpression) Expression(org.apache.cayenne.exp.Expression) PrefetchTreeNode(org.apache.cayenne.query.PrefetchTreeNode) DbRelationship(org.apache.cayenne.map.DbRelationship) DbAttribute(org.apache.cayenne.map.DbAttribute) CayenneRuntimeException(org.apache.cayenne.CayenneRuntimeException)

Aggregations

PrefetchTreeNode (org.apache.cayenne.query.PrefetchTreeNode)12 ClassDescriptor (org.apache.cayenne.reflect.ClassDescriptor)7 ArrayList (java.util.ArrayList)4 HashMap (java.util.HashMap)4 CayenneRuntimeException (org.apache.cayenne.CayenneRuntimeException)4 Expression (org.apache.cayenne.exp.Expression)4 DbRelationship (org.apache.cayenne.map.DbRelationship)4 ObjEntity (org.apache.cayenne.map.ObjEntity)4 DbAttribute (org.apache.cayenne.map.DbAttribute)3 DbEntity (org.apache.cayenne.map.DbEntity)3 ObjRelationship (org.apache.cayenne.map.ObjRelationship)3 QueryMetadata (org.apache.cayenne.query.QueryMetadata)3 ArcProperty (org.apache.cayenne.reflect.ArcProperty)3 Map (java.util.Map)2 DataRow (org.apache.cayenne.DataRow)2 EJBQLExpression (org.apache.cayenne.ejbql.EJBQLExpression)2 ASTDbPath (org.apache.cayenne.exp.parser.ASTDbPath)2 DbJoin (org.apache.cayenne.map.DbJoin)2 ObjAttribute (org.apache.cayenne.map.ObjAttribute)2 MockQueryMetadata (org.apache.cayenne.query.MockQueryMetadata)2