This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/master by this push: new 55acf42bc CAY-2844 Joint prefetch doesn't use ObjEntity qualifier 55acf42bc is described below commit 55acf42bcd29f3d663f5e343d20e8f681c856c43 Author: Nikita Timofeev <stari...@gmail.com> AuthorDate: Tue Mar 5 13:26:51 2024 +0400 CAY-2844 Joint prefetch doesn't use ObjEntity qualifier --- RELEASE-NOTES.txt | 3 +- .../access/translator/select/PathProcessor.java | 4 +- .../translator/select/PrefetchNodeStage.java | 18 +++++- .../access/translator/select/TableTree.java | 11 +++- .../access/translator/select/TableTreeNode.java | 11 +++- .../translator/select/TableTreeQualifierStage.java | 41 ++++++------ .../translator/select/TranslatorContext.java | 11 ++++ .../org/apache/cayenne/CDOQualifiedEntitiesIT.java | 72 ++++++++++++++++++++++ 8 files changed, 139 insertions(+), 32 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 0fdc8e180..1b60260c1 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -98,4 +98,5 @@ CAY-2815 Incorrect translation of aliased expression CAY-2827 Saved data-source XML data doesn't correspond to the XSD schema CAY-2838 Vertical Inheritance: Problem setting db attribute to null via flattened path CAY-2840 Vertical Inheritance: Missing subclass attributes with joint prefetch -CAY-2841 Multi column ColumnSelect with SHARED_CACHE fails after 1st select \ No newline at end of file +CAY-2841 Multi column ColumnSelect with SHARED_CACHE fails after 1st select +CAY-2844 Joint prefetch doesn't use ObjEntity qualifier \ No newline at end of file diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java index 2f4712cbe..f4968031b 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java @@ -36,7 +36,6 @@ import java.util.Optional; */ abstract class PathProcessor<T extends Entity<?,?,?>> implements PathTranslationResult { - public static final char OUTER_JOIN_INDICATOR = '+'; public static final char SPLIT_PATH_INDICATOR = '#'; public static final String DB_PATH_ALIAS_INDICATOR = "db:"; @@ -62,6 +61,9 @@ abstract class PathProcessor<T extends Entity<?,?,?>> implements PathTranslation } public PathTranslationResult process(CayennePath path) { + if(path.marker() != CayennePath.NO_MARKER) { + currentDbPath = currentDbPath.withMarker(path.marker()); + } List<CayennePathSegment> segments = path.segments(); int size = segments.size(); for (int i = 0; i < size; i++) { diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java index e4d010c71..accabd374 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java @@ -81,9 +81,11 @@ class PrefetchNodeStage implements TranslationStage { for(PrefetchTreeNode node : prefetch.adjacentJointNodes()) { Expression prefetchExp = ExpressionFactory.pathExp(node.getPath()); + ObjRelationship targetRel = (ObjRelationship) prefetchExp.evaluate(objEntity); ASTDbPath dbPrefetch = (ASTDbPath) objEntity.translateToDbPath(prefetchExp); CayennePath dbPath = dbPrefetch.getPath(); DbEntity dbEntity = objEntity.getDbEntity(); + Expression targetQualifier = context.getResolver().getClassDescriptor(targetRel.getTargetEntityName()).getEntityInheritanceTree().qualifierForEntityAndSubclasses(); CayennePath fullPath = CayennePath.EMPTY_PATH.withMarker(CayennePath.PREFETCH_MARKER); @@ -94,13 +96,16 @@ class PrefetchNodeStage implements TranslationStage { } fullPath = fullPath.dot(c); - context.getTableTree().addJoinTable(fullPath, rel, JoinType.LEFT_OUTER); + if(targetQualifier != null && c == dbPath.last()) { + targetQualifier = translateToPrefetchQualifier(targetRel.getTargetEntity(), targetQualifier); + context.getTableTree().addJoinTable(fullPath.withMarker(CayennePath.PREFETCH_MARKER), rel, JoinType.LEFT_OUTER, targetQualifier); + } else { + context.getTableTree().addJoinTable(fullPath.withMarker(CayennePath.PREFETCH_MARKER), 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(dbPath.withMarker(CayennePath.PREFETCH_MARKER)); @@ -117,6 +122,13 @@ class PrefetchNodeStage implements TranslationStage { } } + Expression translateToPrefetchQualifier(ObjEntity entity, Expression targetQualifier) { + Expression expression = entity.translateToDbPath(targetQualifier); + return expression.transform(o -> o instanceof ASTDbPath + ? ExpressionFactory.dbPathExp(((ASTDbPath) o).getPath().withMarker(CayennePath.PREFETCH_MARKER)) + : o); + } + private void processPrefetchQuery(TranslatorContext context) { Select<?> select = context.getQuery().unwrap(); if(!(select instanceof PrefetchSelectQuery)) { diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java index 43fbbda33..87ab9bcd0 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java @@ -25,11 +25,11 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import org.apache.cayenne.CayenneRuntimeException; +import org.apache.cayenne.exp.Expression; import org.apache.cayenne.exp.path.CayennePath; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.JoinType; -import org.apache.cayenne.util.Util; /** * @since 4.2 @@ -57,11 +57,16 @@ class TableTree { } void addJoinTable(CayennePath path, DbRelationship relationship, JoinType joinType) { - if (tableNodes.get(path) != null) { + addJoinTable(path, relationship, joinType, null); + } + + void addJoinTable(CayennePath path, DbRelationship relationship, JoinType joinType, Expression additionalQualifier) { + TableTreeNode treeNode = tableNodes.get(path); + if (treeNode != null) { return; } - TableTreeNode node = new TableTreeNode(path, relationship, nextTableAlias(), joinType); + TableTreeNode node = new TableTreeNode(path, relationship, nextTableAlias(), joinType, additionalQualifier); tableNodes.put(path, node); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java index b4a09117a..c2b68563b 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java @@ -19,6 +19,7 @@ package org.apache.cayenne.access.translator.select; +import org.apache.cayenne.exp.Expression; import org.apache.cayenne.exp.path.CayennePath; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.DbRelationship; @@ -38,20 +39,24 @@ class TableTreeNode { // relationship that connects this node with parent (or null if this is root) private final DbRelationship relationship; + private final Expression additionalQualifier; + TableTreeNode(DbEntity entity, String tableAlias) { this.attributePath = CayennePath.EMPTY_PATH; this.entity = entity; this.tableAlias = tableAlias; this.relationship = null; this.joinType = null; + this.additionalQualifier = null; } - TableTreeNode(CayennePath path, DbRelationship relationship, String tableAlias, JoinType joinType) { + TableTreeNode(CayennePath path, DbRelationship relationship, String tableAlias, JoinType joinType, Expression additionalQualifier) { this.attributePath = path; this.entity = relationship.getTargetEntity(); this.tableAlias = tableAlias; this.relationship = relationship; this.joinType = joinType; + this.additionalQualifier = additionalQualifier; } public CayennePath getAttributePath() { @@ -73,4 +78,8 @@ class TableTreeNode { public DbRelationship getRelationship() { return relationship; } + + public Expression getAdditionalQualifier() { + return additionalQualifier; + } } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java index ba7eaec7b..313582ca8 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java @@ -19,16 +19,12 @@ package org.apache.cayenne.access.translator.select; -import org.apache.cayenne.access.sqlbuilder.NodeBuilder; import org.apache.cayenne.access.sqlbuilder.sqltree.Node; import org.apache.cayenne.exp.Expression; import org.apache.cayenne.exp.parser.ASTDbPath; import org.apache.cayenne.exp.parser.ASTPath; import org.apache.cayenne.exp.path.CayennePath; -import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.exp; -import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.node; - /** * @since 4.2 */ @@ -37,29 +33,28 @@ class TableTreeQualifierStage implements TranslationStage { @Override public void perform(TranslatorContext context) { context.getTableTree().visit(node -> { - Expression dbQualifier = node.getEntity().getQualifier(); - if (dbQualifier != null) { - CayennePath pathToRoot = node.getAttributePath(); - dbQualifier = dbQualifier.transform(input -> { - if (input instanceof ASTPath) { - CayennePath path = pathToRoot.dot(((ASTPath) input).getPath()); - return new ASTDbPath(path); - } - return input; - }); - Node rootQualifier = context.getQualifierNode(); - Node translatedQualifier = context.getQualifierTranslator().translate(dbQualifier); - if (rootQualifier != null) { - NodeBuilder expressionNodeBuilder = exp(node(rootQualifier)).and(node(translatedQualifier)); - context.setQualifierNode(expressionNodeBuilder.build()); - } else { - context.setQualifierNode(translatedQualifier); - } - } + appendQualifier(context, node, node.getEntity().getQualifier()); + appendQualifier(context, node, node.getAdditionalQualifier()); }); if(context.getQualifierNode() != null) { context.getSelectBuilder().where(context.getQualifierNode()); } } + + private static void appendQualifier(TranslatorContext context, TableTreeNode node, Expression dbQualifier) { + if (dbQualifier == null) { + return; + } + + CayennePath pathToRoot = node.getAttributePath(); + dbQualifier = dbQualifier.transform(input -> + // here we are not only marking path as prefetch, but changing ObjPath to DB (without ) + input instanceof ASTPath + ? new ASTDbPath(pathToRoot.dot(((ASTPath) input).getPath()).withMarker(CayennePath.PREFETCH_MARKER)) + : input + ); + Node translatedQualifier = context.getQualifierTranslator().translate(dbQualifier); + context.appendQualifierNode(translatedQualifier); + } } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java index 0c9894549..237cfac07 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java @@ -42,6 +42,9 @@ import org.apache.cayenne.map.SQLResult; import org.apache.cayenne.query.Ordering; import org.apache.cayenne.query.QueryMetadata; +import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.exp; +import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.node; + /** * Context that holds all data necessary for query translation as well as a result of that translation. * @@ -291,6 +294,14 @@ public class TranslatorContext implements SQLGenerationContext { this.qualifierNode = qualifierNode; } + void appendQualifierNode(Node qualifierNode) { + if(this.qualifierNode == null) { + this.qualifierNode = qualifierNode; + } else { + this.qualifierNode = exp(node(this.qualifierNode)).and(node(qualifierNode)).build(); + } + } + Node getQualifierNode() { return qualifierNode; } diff --git a/cayenne/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java b/cayenne/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java index e5bffea2d..68b722774 100644 --- a/cayenne/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java @@ -128,6 +128,78 @@ public class CDOQualifiedEntitiesIT extends RuntimeCase { } } + @Test + public void testJointPrefetchToMany() throws Exception { + if (accessStackAdapter.supportsNullBoolean()) { + + createReadToManyDataSet(); + + List<Qualified1> roots = ObjectSelect.query(Qualified1.class) + .prefetch(Qualified1.QUALIFIED2S.joint()) + .select(context); + + assertEquals(1, roots.size()); + + Qualified1 root = roots.get(0); + + assertEquals("OX1", root.getName()); + + List<Qualified2> related = root.getQualified2s(); + assertEquals(1, related.size()); + + Qualified2 r = related.get(0); + assertEquals("OY1", r.getName()); + } + } + + @Test + public void testDisjointPrefetchToMany() throws Exception { + if (accessStackAdapter.supportsNullBoolean()) { + + createReadToManyDataSet(); + + List<Qualified1> roots = ObjectSelect.query(Qualified1.class) + .prefetch(Qualified1.QUALIFIED2S.disjoint()) + .select(context); + + assertEquals(1, roots.size()); + + Qualified1 root = roots.get(0); + + assertEquals("OX1", root.getName()); + + List<Qualified2> related = root.getQualified2s(); + assertEquals(1, related.size()); + + Qualified2 r = related.get(0); + assertEquals("OY1", r.getName()); + } + } + + @Test + public void testDisjointByIdPrefetchToMany() throws Exception { + if (accessStackAdapter.supportsNullBoolean()) { + + createReadToManyDataSet(); + + List<Qualified1> roots = ObjectSelect.query(Qualified1.class) + .prefetch(Qualified1.QUALIFIED2S.disjointById()) + .select(context); + + assertEquals(1, roots.size()); + + Qualified1 root = roots.get(0); + + assertEquals("OX1", root.getName()); + + List<Qualified2> related = root.getQualified2s(); + assertEquals(1, related.size()); + + Qualified2 r = related.get(0); + assertEquals("OY1", r.getName()); + } + } + @Test public void testReadToOne() throws Exception { if (accessStackAdapter.supportsNullBoolean()) {