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 a69c2d93f CAY-2863 DbEntity qualifiers are no longer applied to JOIN 
conditions
a69c2d93f is described below

commit a69c2d93f9ccc3fba2f88d38b1422bc1cc47d26f
Author: Nikita Timofeev <[email protected]>
AuthorDate: Mon Nov 10 17:39:54 2025 +0400

    CAY-2863 DbEntity qualifiers are no longer applied to JOIN conditions
---
 RELEASE-NOTES.txt                                  |  1 +
 .../translator/select/QualifierTranslator.java     |  4 ++
 .../access/translator/select/TableTree.java        | 11 +++++
 .../translator/select/TableTreeQualifierStage.java | 19 +++------
 .../access/translator/select/TableTreeStage.java   | 36 +++++++++++++++++
 .../org/apache/cayenne/exp/path/CayennePath.java   |  6 ++-
 .../select/DefaultSelectTranslatorIT.java          | 47 ++++++++++++++++++----
 7 files changed, 102 insertions(+), 22 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 17a1ddac8..78ccd1c11 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@ Bug Fixes:
 
 CAY-2701 MySQL DST-related LocalDateTime issues
 CAY-2836 ObjectSelect.selectCount() throws if a query contains ordering
+CAY-2863 DbEntity qualifiers are no longer applied to JOIN conditions
 CAY-2871 QualifierTranslator breaks on a relationship with a compound FK
 CAY-2872 CayenneModeler "Documentation" link is broken
 CAY-2876 Memory leak in the ObjectStore
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
index 615c9cc2a..d6369e118 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
@@ -36,6 +36,7 @@ import org.apache.cayenne.exp.parser.ASTFullObject;
 import org.apache.cayenne.exp.parser.ASTFunctionCall;
 import org.apache.cayenne.exp.parser.ASTNotExists;
 import org.apache.cayenne.exp.parser.ASTObjPath;
+import org.apache.cayenne.exp.parser.ASTPath;
 import org.apache.cayenne.exp.parser.ASTScalar;
 import org.apache.cayenne.exp.parser.ASTSubquery;
 import org.apache.cayenne.exp.parser.PatternMatchNode;
@@ -295,6 +296,9 @@ class QualifierTranslator implements TraversalHandler {
             return new EmptyNode();
         } else {
             String alias = 
context.getTableTree().aliasForPath(result.getLastAttributePath());
+            if(TableTree.CURRENT_ALIAS.equals(alias)) {
+                alias = node.getPathAliases().get(TableTree.CURRENT_ALIAS);
+            }
             return table(alias).column(result.getLastAttribute()).build();
         }
     }
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 87ab9bcd0..fe573f6fe 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
@@ -35,6 +35,9 @@ import org.apache.cayenne.map.JoinType;
  * @since 4.2
  */
 class TableTree {
+
+    public static final String CURRENT_ALIAS = "__current_table_alias__";
+
     /**
      * Tables mapped by db path it's spawned by.
      * Can be following:
@@ -61,6 +64,10 @@ class TableTree {
     }
 
     void addJoinTable(CayennePath path, DbRelationship relationship, JoinType 
joinType, Expression additionalQualifier) {
+        // skip adding new node if we are resolving table tree itself
+        if(path.marker() == CayennePath.TABLE_TREE_MARKER) {
+            return;
+        }
         TableTreeNode treeNode = tableNodes.get(path);
         if (treeNode != null) {
             return;
@@ -71,6 +78,10 @@ class TableTree {
     }
 
     String aliasForPath(CayennePath attributePath) {
+        // should be resolved dynamically by the caller
+        if(attributePath.marker() == CayennePath.TABLE_TREE_MARKER) {
+            return CURRENT_ALIAS;
+        }
         if(attributePath.isEmpty()) {
             return rootNode.getTableAlias();
         }
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 313582ca8..ca26d6cb3 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
@@ -21,9 +21,6 @@ package org.apache.cayenne.access.translator.select;
 
 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;
 
 /**
  * @since 4.2
@@ -33,8 +30,11 @@ class TableTreeQualifierStage implements TranslationStage {
     @Override
     public void perform(TranslatorContext context) {
         context.getTableTree().visit(node -> {
-            appendQualifier(context, node, node.getEntity().getQualifier());
-            appendQualifier(context, node, node.getAdditionalQualifier());
+            if(node.getRelationship() == null) {
+                // translate only root qualifier here, joined tables are 
processed in the `TableTreeStage`
+                appendQualifier(context, node, 
node.getEntity().getQualifier());
+                appendQualifier(context, node, node.getAdditionalQualifier());
+            }
         });
 
         if(context.getQualifierNode() != null) {
@@ -46,14 +46,7 @@ class TableTreeQualifierStage implements TranslationStage {
         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
-        );
+        dbQualifier = TableTreeStage.translateToDbPath(node, dbQualifier);
         Node translatedQualifier = 
context.getQualifierTranslator().translate(dbQualifier);
         context.appendQualifierNode(translatedQualifier);
     }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java
index ff0eb0f65..c1a9e4ca7 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java
@@ -20,10 +20,16 @@
 package org.apache.cayenne.access.translator.select;
 
 import java.util.List;
+import java.util.Map;
 
 import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
 import org.apache.cayenne.access.sqlbuilder.JoinNodeBuilder;
 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 org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbJoin;
 
@@ -74,6 +80,36 @@ class TableTreeStage implements TranslationStage {
             }
         }
 
+        // append entity qualifiers
+        expressionNodeBuilder = appendQualifier(expressionNodeBuilder, 
context, node, node.getEntity().getQualifier());
+        expressionNodeBuilder = appendQualifier(expressionNodeBuilder, 
context, node, node.getAdditionalQualifier());
         return expressionNodeBuilder;
     }
+
+    private static ExpressionNodeBuilder appendQualifier(ExpressionNodeBuilder 
joinBuilder,
+                                        TranslatorContext context,
+                                        TableTreeNode node,
+                                        Expression dbQualifier) {
+        if (dbQualifier == null) {
+            return joinBuilder;
+        }
+
+        dbQualifier = translateToDbPath(node, dbQualifier);
+        Node translatedQualifier = 
context.getQualifierTranslator().translate(dbQualifier);
+        return joinBuilder.and(() -> translatedQualifier);
+    }
+
+    static Expression translateToDbPath(TableTreeNode node, Expression 
dbQualifier) {
+        CayennePath pathToRoot = node.getAttributePath();
+        dbQualifier = dbQualifier.transform(input -> {
+            // here we are not only marking path, but changing ObjPath to DB
+            if (input instanceof ASTPath) {
+                ASTDbPath dbPath = new ASTDbPath(pathToRoot.dot(((ASTPath) 
input).getPath()).withMarker(CayennePath.TABLE_TREE_MARKER));
+                dbPath.setPathAliases(Map.of(TableTree.CURRENT_ALIAS, 
node.getTableAlias()));
+                return dbPath;
+            }
+            return input;
+        });
+        return dbQualifier;
+    }
 }
diff --git a/cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java 
b/cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java
index 78d4c3368..562db0419 100644
--- a/cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java
+++ b/cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java
@@ -54,10 +54,14 @@ public interface CayennePath extends 
Iterable<CayennePathSegment>, Serializable
 
     /**
      * Prefetch path marker
-     * TODO: this marker used only for prefetch processing
      */
     int PREFETCH_MARKER = 1;
 
+    /**
+     * Marker denotes paths inside tree resolution logic
+     */
+    int TABLE_TREE_MARKER = 2;
+
     /**
      * Constant value for an empty path
      */
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java
index 5c11dc80d..4bd1d92e7 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java
@@ -149,14 +149,18 @@ public class DefaultSelectTranslatorIT extends 
RuntimeCase {
                        // do some simple assertions to make sure all parts are 
in
                        assertNotNull(generatedSql);
                        assertTrue(generatedSql.startsWith("SELECT "));
-                       assertTrue(generatedSql.indexOf(" FROM ") > 0);
-                       if (generatedSql.contains("RTRIM")) {
-                               assertTrue(generatedSql.indexOf("ARTIST_NAME) 
=") > generatedSql.indexOf("RTRIM("));
-                       } else if (generatedSql.contains("TRIM")) {
-                               assertTrue(generatedSql.indexOf("ARTIST_NAME) 
=") > generatedSql.indexOf("TRIM("));
-                       } else {
-                               assertTrue(generatedSql.indexOf("ARTIST_NAME 
=") > 0);
-                       }
+
+                       int iFrom = generatedSql.indexOf(" FROM ");
+                       int iPaintingTable = generatedSql.indexOf(" PAINTING ");
+                       int iArtistTable = generatedSql.indexOf(" ARTIST ");
+                       int iName = generatedSql.indexOf("ARTIST_NAME =");
+                       int iOrder = generatedSql.indexOf(" ORDER");
+
+                       assertTrue(iFrom > 0);
+                       assertTrue(iPaintingTable > iFrom);
+                       assertTrue(iArtistTable > iPaintingTable);
+                       assertTrue(iName > iArtistTable);
+                       assertTrue(iOrder > iName);
 
                } finally {
                        entity.setQualifier(null);
@@ -854,4 +858,31 @@ public class DefaultSelectTranslatorIT extends RuntimeCase 
{
                int totalJoins = translator.getContext().getTableCount() - 1;
                assertEquals(4, totalJoins);
        }
+
+       @Test
+       public void testDbEntityQualifier_JoinQuery() throws Exception {
+
+               final DbEntity entity = 
context.getEntityResolver().getDbEntity("ARTIST");
+               entity.setQualifier(ExpressionFactory.exp("ARTIST_NAME = 
'Should be on JOIN condition and not WHERE'"));
+
+               ObjectSelect<Painting> q = ObjectSelect.query(Painting.class)
+                               .where
+                                               (
+                                                               
Painting.TO_ARTIST.dot(Artist.DATE_OF_BIRTH).eq(new java.sql.Date(1, 0, 1))
+                                                                               
.orExp(Painting.TO_GALLERY.dot(Gallery.GALLERY_NAME).like("G%"))
+                                               );
+
+               // If the DbEntity qualifier is set on the WHERE condition then 
the OR expression will fail to find matches
+
+               SelectTranslator transl = new DefaultSelectTranslator(q, 
dataNode.getAdapter(), dataNode.getEntityResolver());
+               try {
+                       String generatedSql = transl.getSql();
+                       int whereNdx = generatedSql.indexOf(" WHERE ");
+                       int joinNdx = generatedSql.indexOf(" JOIN ARTIST ");
+                       assertTrue(generatedSql.substring(joinNdx, 
whereNdx).indexOf("ARTIST_NAME") > 0); // Should be in JOIN condition
+                       assertTrue(generatedSql.indexOf("ARTIST_NAME", 
whereNdx) < 0); // Should not be part of WHERE
+               } finally {
+                       entity.setQualifier(null);
+               }
+       }
 }

Reply via email to