Skip to content

Commit a69c2d9

Browse files
committed
CAY-2863 DbEntity qualifiers are no longer applied to JOIN conditions
1 parent e052b07 commit a69c2d9

File tree

7 files changed

+102
-22
lines changed

7 files changed

+102
-22
lines changed

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Bug Fixes:
2424

2525
CAY-2701 MySQL DST-related LocalDateTime issues
2626
CAY-2836 ObjectSelect.selectCount() throws if a query contains ordering
27+
CAY-2863 DbEntity qualifiers are no longer applied to JOIN conditions
2728
CAY-2871 QualifierTranslator breaks on a relationship with a compound FK
2829
CAY-2872 CayenneModeler "Documentation" link is broken
2930
CAY-2876 Memory leak in the ObjectStore

cayenne/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.cayenne.exp.parser.ASTFunctionCall;
3737
import org.apache.cayenne.exp.parser.ASTNotExists;
3838
import org.apache.cayenne.exp.parser.ASTObjPath;
39+
import org.apache.cayenne.exp.parser.ASTPath;
3940
import org.apache.cayenne.exp.parser.ASTScalar;
4041
import org.apache.cayenne.exp.parser.ASTSubquery;
4142
import org.apache.cayenne.exp.parser.PatternMatchNode;
@@ -295,6 +296,9 @@ private Node processPathTranslationResult(Expression node, Expression parentNode
295296
return new EmptyNode();
296297
} else {
297298
String alias = context.getTableTree().aliasForPath(result.getLastAttributePath());
299+
if(TableTree.CURRENT_ALIAS.equals(alias)) {
300+
alias = node.getPathAliases().get(TableTree.CURRENT_ALIAS);
301+
}
298302
return table(alias).column(result.getLastAttribute()).build();
299303
}
300304
}

cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTree.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
* @since 4.2
3636
*/
3737
class TableTree {
38+
39+
public static final String CURRENT_ALIAS = "__current_table_alias__";
40+
3841
/**
3942
* Tables mapped by db path it's spawned by.
4043
* Can be following:
@@ -61,6 +64,10 @@ void addJoinTable(CayennePath path, DbRelationship relationship, JoinType joinTy
6164
}
6265

6366
void addJoinTable(CayennePath path, DbRelationship relationship, JoinType joinType, Expression additionalQualifier) {
67+
// skip adding new node if we are resolving table tree itself
68+
if(path.marker() == CayennePath.TABLE_TREE_MARKER) {
69+
return;
70+
}
6471
TableTreeNode treeNode = tableNodes.get(path);
6572
if (treeNode != null) {
6673
return;
@@ -71,6 +78,10 @@ void addJoinTable(CayennePath path, DbRelationship relationship, JoinType joinTy
7178
}
7279

7380
String aliasForPath(CayennePath attributePath) {
81+
// should be resolved dynamically by the caller
82+
if(attributePath.marker() == CayennePath.TABLE_TREE_MARKER) {
83+
return CURRENT_ALIAS;
84+
}
7485
if(attributePath.isEmpty()) {
7586
return rootNode.getTableAlias();
7687
}

cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeQualifierStage.java

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121

2222
import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
2323
import org.apache.cayenne.exp.Expression;
24-
import org.apache.cayenne.exp.parser.ASTDbPath;
25-
import org.apache.cayenne.exp.parser.ASTPath;
26-
import org.apache.cayenne.exp.path.CayennePath;
2724

2825
/**
2926
* @since 4.2
@@ -33,8 +30,11 @@ class TableTreeQualifierStage implements TranslationStage {
3330
@Override
3431
public void perform(TranslatorContext context) {
3532
context.getTableTree().visit(node -> {
36-
appendQualifier(context, node, node.getEntity().getQualifier());
37-
appendQualifier(context, node, node.getAdditionalQualifier());
33+
if(node.getRelationship() == null) {
34+
// translate only root qualifier here, joined tables are processed in the `TableTreeStage`
35+
appendQualifier(context, node, node.getEntity().getQualifier());
36+
appendQualifier(context, node, node.getAdditionalQualifier());
37+
}
3838
});
3939

4040
if(context.getQualifierNode() != null) {
@@ -46,14 +46,7 @@ private static void appendQualifier(TranslatorContext context, TableTreeNode nod
4646
if (dbQualifier == null) {
4747
return;
4848
}
49-
50-
CayennePath pathToRoot = node.getAttributePath();
51-
dbQualifier = dbQualifier.transform(input ->
52-
// here we are not only marking path as prefetch, but changing ObjPath to DB (without )
53-
input instanceof ASTPath
54-
? new ASTDbPath(pathToRoot.dot(((ASTPath) input).getPath()).withMarker(CayennePath.PREFETCH_MARKER))
55-
: input
56-
);
49+
dbQualifier = TableTreeStage.translateToDbPath(node, dbQualifier);
5750
Node translatedQualifier = context.getQualifierTranslator().translate(dbQualifier);
5851
context.appendQualifierNode(translatedQualifier);
5952
}

cayenne/src/main/java/org/apache/cayenne/access/translator/select/TableTreeStage.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,16 @@
2020
package org.apache.cayenne.access.translator.select;
2121

2222
import java.util.List;
23+
import java.util.Map;
2324

2425
import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
2526
import org.apache.cayenne.access.sqlbuilder.JoinNodeBuilder;
2627
import org.apache.cayenne.access.sqlbuilder.NodeBuilder;
28+
import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
29+
import org.apache.cayenne.exp.Expression;
30+
import org.apache.cayenne.exp.parser.ASTDbPath;
31+
import org.apache.cayenne.exp.parser.ASTPath;
32+
import org.apache.cayenne.exp.path.CayennePath;
2733
import org.apache.cayenne.map.DbAttribute;
2834
import org.apache.cayenne.map.DbJoin;
2935

@@ -74,6 +80,36 @@ private NodeBuilder getJoinExpression(TranslatorContext context, TableTreeNode n
7480
}
7581
}
7682

83+
// append entity qualifiers
84+
expressionNodeBuilder = appendQualifier(expressionNodeBuilder, context, node, node.getEntity().getQualifier());
85+
expressionNodeBuilder = appendQualifier(expressionNodeBuilder, context, node, node.getAdditionalQualifier());
7786
return expressionNodeBuilder;
7887
}
88+
89+
private static ExpressionNodeBuilder appendQualifier(ExpressionNodeBuilder joinBuilder,
90+
TranslatorContext context,
91+
TableTreeNode node,
92+
Expression dbQualifier) {
93+
if (dbQualifier == null) {
94+
return joinBuilder;
95+
}
96+
97+
dbQualifier = translateToDbPath(node, dbQualifier);
98+
Node translatedQualifier = context.getQualifierTranslator().translate(dbQualifier);
99+
return joinBuilder.and(() -> translatedQualifier);
100+
}
101+
102+
static Expression translateToDbPath(TableTreeNode node, Expression dbQualifier) {
103+
CayennePath pathToRoot = node.getAttributePath();
104+
dbQualifier = dbQualifier.transform(input -> {
105+
// here we are not only marking path, but changing ObjPath to DB
106+
if (input instanceof ASTPath) {
107+
ASTDbPath dbPath = new ASTDbPath(pathToRoot.dot(((ASTPath) input).getPath()).withMarker(CayennePath.TABLE_TREE_MARKER));
108+
dbPath.setPathAliases(Map.of(TableTree.CURRENT_ALIAS, node.getTableAlias()));
109+
return dbPath;
110+
}
111+
return input;
112+
});
113+
return dbQualifier;
114+
}
79115
}

cayenne/src/main/java/org/apache/cayenne/exp/path/CayennePath.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,14 @@ public interface CayennePath extends Iterable<CayennePathSegment>, Serializable
5454

5555
/**
5656
* Prefetch path marker
57-
* TODO: this marker used only for prefetch processing
5857
*/
5958
int PREFETCH_MARKER = 1;
6059

60+
/**
61+
* Marker denotes paths inside tree resolution logic
62+
*/
63+
int TABLE_TREE_MARKER = 2;
64+
6165
/**
6266
* Constant value for an empty path
6367
*/

cayenne/src/test/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorIT.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,18 @@ public void testDbEntityQualifier_OuterJoin() throws Exception {
149149
// do some simple assertions to make sure all parts are in
150150
assertNotNull(generatedSql);
151151
assertTrue(generatedSql.startsWith("SELECT "));
152-
assertTrue(generatedSql.indexOf(" FROM ") > 0);
153-
if (generatedSql.contains("RTRIM")) {
154-
assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("RTRIM("));
155-
} else if (generatedSql.contains("TRIM")) {
156-
assertTrue(generatedSql.indexOf("ARTIST_NAME) =") > generatedSql.indexOf("TRIM("));
157-
} else {
158-
assertTrue(generatedSql.indexOf("ARTIST_NAME =") > 0);
159-
}
152+
153+
int iFrom = generatedSql.indexOf(" FROM ");
154+
int iPaintingTable = generatedSql.indexOf(" PAINTING ");
155+
int iArtistTable = generatedSql.indexOf(" ARTIST ");
156+
int iName = generatedSql.indexOf("ARTIST_NAME =");
157+
int iOrder = generatedSql.indexOf(" ORDER");
158+
159+
assertTrue(iFrom > 0);
160+
assertTrue(iPaintingTable > iFrom);
161+
assertTrue(iArtistTable > iPaintingTable);
162+
assertTrue(iName > iArtistTable);
163+
assertTrue(iOrder > iName);
160164

161165
} finally {
162166
entity.setQualifier(null);
@@ -854,4 +858,31 @@ public void testAliasedJoins_FlattenedRelationship() {
854858
int totalJoins = translator.getContext().getTableCount() - 1;
855859
assertEquals(4, totalJoins);
856860
}
861+
862+
@Test
863+
public void testDbEntityQualifier_JoinQuery() throws Exception {
864+
865+
final DbEntity entity = context.getEntityResolver().getDbEntity("ARTIST");
866+
entity.setQualifier(ExpressionFactory.exp("ARTIST_NAME = 'Should be on JOIN condition and not WHERE'"));
867+
868+
ObjectSelect<Painting> q = ObjectSelect.query(Painting.class)
869+
.where
870+
(
871+
Painting.TO_ARTIST.dot(Artist.DATE_OF_BIRTH).eq(new java.sql.Date(1, 0, 1))
872+
.orExp(Painting.TO_GALLERY.dot(Gallery.GALLERY_NAME).like("G%"))
873+
);
874+
875+
// If the DbEntity qualifier is set on the WHERE condition then the OR expression will fail to find matches
876+
877+
SelectTranslator transl = new DefaultSelectTranslator(q, dataNode.getAdapter(), dataNode.getEntityResolver());
878+
try {
879+
String generatedSql = transl.getSql();
880+
int whereNdx = generatedSql.indexOf(" WHERE ");
881+
int joinNdx = generatedSql.indexOf(" JOIN ARTIST ");
882+
assertTrue(generatedSql.substring(joinNdx, whereNdx).indexOf("ARTIST_NAME") > 0); // Should be in JOIN condition
883+
assertTrue(generatedSql.indexOf("ARTIST_NAME", whereNdx) < 0); // Should not be part of WHERE
884+
} finally {
885+
entity.setQualifier(null);
886+
}
887+
}
857888
}

0 commit comments

Comments
 (0)