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 <[email protected]>
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()) {