CAY-2863 DbEntity qualifiers are no longer applied to JOIN conditions
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 17a1dda..78ccd1c 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@
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 615c9cc..d6369e1 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.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 @@
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 87ab9bc..fe573f6 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 @@
* @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 @@
}
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 @@
}
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 313582c..ca26d6c 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 @@
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 @@
@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 @@
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 ff0eb0f..c1a9e4c 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 @@
}
}
+ // 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 78d4c33..562db04 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,11 +54,15 @@
/**
* 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
*/
CayennePath EMPTY_PATH = new EmptyCayennePath(NO_MARKER);
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 5c11dc8..4bd1d92 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 @@
// 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 @@
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);
+ }
+ }
}