This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch STABLE-4.2 in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/STABLE-4.2 by this push: new 1124cb9de CAY-2844 Joint prefetch doesn't use ObjEntity qualifier 1124cb9de is described below commit 1124cb9deca380f68449a1cdc50f86e71eb96f54 Author: Nikita Timofeev <stari...@gmail.com> AuthorDate: Tue Mar 5 13:26:51 2024 +0400 CAY-2844 Joint prefetch doesn't use ObjEntity qualifier (cherry picked from commit 55acf42bcd29f3d663f5e343d20e8f681c856c43) --- RELEASE-NOTES.txt | 1 + .../access/translator/select/DbPathProcessor.java | 5 +- .../access/translator/select/PathProcessor.java | 11 ++-- .../translator/select/PrefetchNodeStage.java | 19 +++++- .../access/translator/select/TableTree.java | 12 +++- .../access/translator/select/TableTreeNode.java | 11 +++- .../translator/select/TableTreeQualifierStage.java | 50 ++++++++------- .../translator/select/TranslatorContext.java | 11 ++++ .../org/apache/cayenne/CDOQualifiedEntitiesIT.java | 72 ++++++++++++++++++++++ 9 files changed, 151 insertions(+), 41 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index dbad3137f..cd90a551c 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -23,6 +23,7 @@ CAY-2815 Incorrect translation of aliased expression 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 +CAY-2844 Joint prefetch doesn't use ObjEntity qualifier ---------------------------------- Release: 4.2 diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java index 104299df0..c01bd0582 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java @@ -30,7 +30,7 @@ import org.apache.cayenne.map.JoinType; */ class DbPathProcessor extends PathProcessor<DbEntity> { - private boolean flattenedPath; + private final boolean flattenedPath; DbPathProcessor(TranslatorContext context, DbEntity entity, String parentPath, boolean flattenedPath) { super(context, entity); @@ -47,6 +47,9 @@ class DbPathProcessor extends PathProcessor<DbEntity> { @Override protected void processNormalAttribute(String next) { + if(next.startsWith("p:")) { + next = next.substring(2); + } DbAttribute dbAttribute = entity.getAttribute(next); if (dbAttribute != null) { processAttribute(dbAttribute); diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java index 8c68ebf57..3786eac13 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java @@ -21,7 +21,6 @@ package org.apache.cayenne.access.translator.select; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbRelationship; -import org.apache.cayenne.map.Embeddable; import org.apache.cayenne.map.Entity; import java.util.ArrayList; @@ -61,6 +60,9 @@ abstract class PathProcessor<T extends Entity> implements PathTranslationResult } public PathTranslationResult process(String path) { + if(path.startsWith("p:")) { + currentDbPath.insert(0, "p:"); + } PathComponents components = new PathComponents(path); String[] rawComponents = components.getAll(); for (int i = 0; i < rawComponents.length; i++) { @@ -111,18 +113,13 @@ abstract class PathProcessor<T extends Entity> implements PathTranslationResult return Optional.of(relationship); } - @Override - public Optional<Embeddable> getEmbeddable() { - return Optional.empty(); - } - @Override public String getFinalPath() { return currentDbPath.toString(); } protected void appendCurrentPath(String nextSegment) { - if (currentDbPath.length() > 0) { + if (currentDbPath.length() > 0 && currentDbPath.charAt(currentDbPath.length() - 1) != ':') { currentDbPath.append('.'); } currentDbPath.append(nextSegment); diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java index 924a6e916..f85c0a465 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java @@ -78,9 +78,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); final String dbPath = dbPrefetch.getPath(); DbEntity dbEntity = objEntity.getDbEntity(); + Expression targetQualifier = context.getResolver().getClassDescriptor(targetRel.getTargetEntityName()).getEntityInheritanceTree().qualifierForEntityAndSubclasses(); PathComponents components = new PathComponents(dbPath); StringBuilder fullPath = new StringBuilder(); @@ -92,13 +94,17 @@ class PrefetchNodeStage implements TranslationStage { if(fullPath.length() > 0) { fullPath.append('.'); } - context.getTableTree().addJoinTable("p:" + fullPath.append(c).toString(), rel, JoinType.LEFT_OUTER); + fullPath.append(c); + if(targetQualifier != null && c.equals(components.getLast())) { + targetQualifier = translateToPrefetchQualifier(targetRel.getTargetEntity(), targetQualifier); + context.getTableTree().addJoinTable("p:" + fullPath, rel, JoinType.LEFT_OUTER, targetQualifier); + } else { + context.getTableTree().addJoinTable("p:" + fullPath, 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); @@ -115,6 +121,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("p:" + ((ASTDbPath) o).getPath()) + : o); + } + private void processPrefetchQuery(TranslatorContext context) { Select<?> select = context.getQuery().unwrap(); if(!(select instanceof PrefetchSelectQuery)) { diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java index 0816ea8b3..7b04a4464 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.apache.cayenne.CayenneRuntimeException; +import org.apache.cayenne.exp.Expression; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.JoinType; @@ -44,7 +45,7 @@ class TableTree { private final Map<String, TableTreeNode> tableNodes; private final TableTree parentTree; - private TableTreeNode rootNode; + private final TableTreeNode rootNode; private int tableAliasSequence; @@ -56,11 +57,16 @@ class TableTree { } void addJoinTable(String path, DbRelationship relationship, JoinType joinType) { - if (tableNodes.get(path) != null) { + addJoinTable(path, relationship, joinType, null); + } + + void addJoinTable(String 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-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java index ac8ae9dcc..4ab196fbb 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeNode.java +++ b/cayenne-server/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.map.DbEntity; import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.JoinType; @@ -37,20 +38,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 = new PathComponents(""); this.entity = entity; this.tableAlias = tableAlias; this.relationship = null; this.joinType = null; + this.additionalQualifier = null; } - TableTreeNode(String path, DbRelationship relationship, String tableAlias, JoinType joinType) { + TableTreeNode(String path, DbRelationship relationship, String tableAlias, JoinType joinType, Expression additionalQualifier) { this.attributePath = new PathComponents(path); this.entity = relationship.getTargetEntity(); this.tableAlias = tableAlias; this.relationship = relationship; this.joinType = joinType; + this.additionalQualifier = additionalQualifier; } public PathComponents getAttributePath() { @@ -72,4 +77,8 @@ class TableTreeNode { public DbRelationship getRelationship() { return relationship; } + + public Expression getAdditionalQualifier() { + return additionalQualifier; + } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java index d805eb727..53b84ae07 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java @@ -19,15 +19,11 @@ 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 static org.apache.cayenne.access.sqlbuilder.SQLBuilder.exp; -import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.node; - /** * @since 4.2 */ @@ -36,32 +32,34 @@ class TableTreeQualifierStage implements TranslationStage { @Override public void perform(TranslatorContext context) { context.getTableTree().visit(node -> { - Expression dbQualifier = node.getEntity().getQualifier(); - if (dbQualifier != null) { - String pathToRoot = node.getAttributePath().getPath(); - dbQualifier = dbQualifier.transform(input -> { - if (input instanceof ASTPath) { - String path = ((ASTPath) input).getPath(); - if(!pathToRoot.isEmpty()) { - path = pathToRoot + '.' + path; - } - 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; + } + + String pathToRoot = node.getAttributePath().getPath(); + dbQualifier = dbQualifier.transform(input -> { + if (input instanceof ASTPath) { + String path = ((ASTPath) input).getPath(); + // here we are not only marking path as prefetch, but changing ObjPath to DB + // (without conversion, as it's a convenience option to allow path without "db:" in the Modeler) + if(!pathToRoot.isEmpty()) { + path = pathToRoot + '.' + path; + } + return new ASTDbPath(path); + } + return input; + }); + Node translatedQualifier = context.getQualifierTranslator().translate(dbQualifier); + context.appendQualifierNode(translatedQualifier); + } } diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java index 702bc3a51..2032cc58f 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java @@ -39,6 +39,9 @@ import org.apache.cayenne.map.EntityResult; import org.apache.cayenne.map.SQLResult; 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. * @@ -262,6 +265,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-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java b/cayenne-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java index bfe2b6836..18a0890de 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/CDOQualifiedEntitiesIT.java @@ -128,6 +128,78 @@ public class CDOQualifiedEntitiesIT extends ServerCase { } } + @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()) {