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;
}
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]);
}
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.");
}
}
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);
}
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);
}
}
}
}
}
Aggregations