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()) {

Reply via email to