This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits 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 fe0618bbe CAY-3006 Can't match a "self" property of an entity with a
compound PK
fe0618bbe is described below
commit fe0618bbe27dd5f431eb6f201fa39b033bb717b5
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun Aug 30 16:43:11 2026 -0400
CAY-3006 Can't match a "self" property of an entity with a compound PK
---
RELEASE-NOTES.txt | 1 +
.../translator/select/QualifierTranslator.java | 46 +++++++++++++--
.../translator/select/QualifierTranslatorIT.java | 67 ++++++++++++++++++++++
3 files changed, 108 insertions(+), 6 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 391fe3fbc..d383ab82c 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@ CAY-3001 Modeler: Auto-increment attribute is not picked up
CAY-3002 Modeler: NPE after clicking path chooser
CAY-3003 Modeler: NPE editing unmapped ObjAttribute
CAY-3005 ResultIterator.allRows() doesn't convert to DataObjects
+CAY-3006 Can't match a "self" property of an entity with a compound PK
----------------------------------
Release: 5.0-M3
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 78cf33f8d..17b5ab913 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
@@ -283,11 +283,11 @@ class QualifierTranslator implements TraversalHandler {
ASTFullObject fullObject = (ASTFullObject) node;
if (fullObject.getOperandCount() == 0) {
Collection<DbAttribute> dbAttributes =
context.getMetadata().getDbEntity().getPrimaryKeys();
+ String alias =
context.getTableTree().aliasForPath(CayennePath.EMPTY_PATH);
if (dbAttributes.size() > 1) {
- throw new CayenneRuntimeException("Unable to translate
reference on entity with more than one PK.");
+ return createMultiPkMatch(node, parentNode,
dbAttributes, alias);
}
DbAttribute attribute = dbAttributes.iterator().next();
- String alias =
context.getTableTree().aliasForPath(CayennePath.EMPTY_PATH);
return table(alias).column(attribute).build();
} else {
return null;
@@ -404,6 +404,36 @@ class QualifierTranslator implements TraversalHandler {
return null;
}
+ /**
+ * Matches the root entity referenced as a whole (i.e. a bare {@link
ASTFullObject}) against an
+ * {@link ObjectId} or a {@link Persistent}, expanding the comparison over
all the PK columns. This is the
+ * root-entity counterpart of {@link
#createMultiAttributeMatch(Expression, Expression, PathTranslationResult)}.
+ */
+ private Node createMultiPkMatch(Expression node, Expression parentNode,
+ Collection<DbAttribute> pkAttributes,
String alias) {
+ if (parentNode == null) {
+ throw new CayenneRuntimeException("Unable to translate reference
on entity with more than one PK.");
+ }
+
+ Map<String, Object> valueSnapshot =
getMultiAttributeValueSnapshot(node, parentNode);
+ Node multiValueComparison = buildMultiValueComparison(pkAttributes,
alias, valueSnapshot);
+
+ // replace current node with multi value comparison
+ Node currentNodeParent = currentNode.getParent();
+ currentNodeParent.replaceChild(currentNodeParent.getChildrenCount() -
1, multiValueComparison);
+ multiValueComparison.setParent(currentNodeParent);
+ currentNode = currentNodeParent;
+
+ // we should skip all related nodes as we build this part of the tree
manually
+ expressionsToSkip.add(node);
+ expressionsToSkip.add(parentNode);
+ for (int i = 0; i < parentNode.getOperandCount(); i++) {
+ expressionsToSkip.add(parentNode.getOperand(i));
+ }
+
+ return null;
+ }
+
private Map<String, Object> getMultiAttributeValueSnapshot(Expression
node, Expression parentNode) {
int siblings = parentNode.getOperandCount();
for (int i = 0; i < siblings; i++) {
@@ -427,13 +457,17 @@ class QualifierTranslator implements TraversalHandler {
}
private Node buildMultiValueComparison(PathTranslationResult result,
Map<String, Object> valueSnapshot) {
- ExpressionNodeBuilder expressionNodeBuilder = null;
- ExpressionNodeBuilder eq;
-
CayennePath path = result.getLastAttributePath();
String alias = context.getTableTree().aliasForPath(path);
+ return buildMultiValueComparison(result.getDbAttributes(), alias,
valueSnapshot);
+ }
+
+ private Node buildMultiValueComparison(Collection<DbAttribute> attributes,
String alias,
+ Map<String, Object> valueSnapshot) {
+ ExpressionNodeBuilder expressionNodeBuilder = null;
+ ExpressionNodeBuilder eq;
- for (DbAttribute attribute : result.getDbAttributes()) {
+ for (DbAttribute attribute : attributes) {
Object nextValue = valueSnapshot.get(attribute.getName());
eq = table(alias).column(attribute).eq(value(nextValue));
if (expressionNodeBuilder == null) {
diff --git
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
index 9191ab9bb..668fbd6ae 100644
---
a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
+++
b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/QualifierTranslatorIT.java
@@ -21,8 +21,11 @@ package org.apache.cayenne.access.translator.select;
import org.apache.cayenne.access.sqlbuilder.SQLGenerationVisitor;
import org.apache.cayenne.access.sqlbuilder.DefaultSQLAppendable;
import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.ObjectId;
import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.query.SelectById;
import org.apache.cayenne.runtime.CayenneRuntime;
import org.apache.cayenne.test.jdbc.TableHelper;
import org.apache.cayenne.testdo.compound.CompoundFkTestEntity;
@@ -34,9 +37,13 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class QualifierTranslatorIT {
@@ -120,4 +127,64 @@ public class QualifierTranslatorIT {
}
+ /**
+ * A bare "self" reference on an entity with a compound PK must expand
over all PK columns,
+ * the same way a to-one relationship path does.
+ */
+ @Test
+ public void compoundPKSelfWithObjectId() {
+ ObjectId id = ObjectId.of("CompoundPkTestEntity", Map.of("KEY1",
"PK1", "KEY2", "PK2"));
+
+ ObjectSelect<CompoundPkTestEntity> query =
ObjectSelect.query(CompoundPkTestEntity.class)
+ .where(CompoundPkTestEntity.SELF.eqId(id));
+
+ assertEquals(" cpt.KEY1 = 'PK1' AND cpt.KEY2 = 'PK2'",
translate(query));
+ }
+
+ @Test
+ public void compoundPKSelfWithPersistent() {
+ CompoundPkTestEntity testEntity =
ObjectSelect.query(CompoundPkTestEntity.class)
+
.where(CompoundPkTestEntity.NAME.eq("BBB")).selectOne(env.context());
+ assertNotNull(testEntity);
+
+ ObjectSelect<CompoundPkTestEntity> query =
ObjectSelect.query(CompoundPkTestEntity.class)
+ .where(CompoundPkTestEntity.SELF.eqId(testEntity));
+
+ assertEquals(" cpt.KEY1 = 'PK1' AND cpt.KEY2 = 'PK2'",
translate(query));
+ }
+
+ @Test
+ public void compoundPKSelfSelectsTheRightRow() {
+ ObjectId id = ObjectId.of("CompoundPkTestEntity", Map.of("KEY1",
"PK3", "KEY2", "PK4"));
+
+ CompoundPkTestEntity viaSelf =
ObjectSelect.query(CompoundPkTestEntity.class)
+
.where(CompoundPkTestEntity.SELF.eqId(id)).selectOne(env.context());
+ assertNotNull(viaSelf);
+ assertEquals("CCC", viaSelf.getName());
+
+ // must agree with the dedicated by-id query
+ assertSame(SelectById.queryObjectId(CompoundPkTestEntity.class,
id).selectOne(env.context()), viaSelf);
+ }
+
+ /**
+ * A compound-PK "self" reference still cannot be matched against a scalar
- there is no single
+ * PK column to compare it to.
+ */
+ @Test
+ public void compoundPKSelfWithScalarFails() {
+ ObjectSelect<CompoundPkTestEntity> query =
ObjectSelect.query(CompoundPkTestEntity.class)
+ .where(CompoundPkTestEntity.SELF.eqId("PK1"));
+
+ CayenneRuntimeException e =
assertThrows(CayenneRuntimeException.class, () -> translate(query));
+ assertTrue(e.getMessage().contains("Multi attribute ObjPath isn't
matched with valid value"), e.getMessage());
+ }
+
+ private String translate(ObjectSelect<?> query) {
+ SelectTranslatorContext context = new SelectTranslatorContext(
+ query, runtime.getDataDomain().getDefaultNode().getAdapter(),
env.context().getEntityResolver(), null);
+ Node node =
context.getQualifierTranslator().translate(query.getWhere());
+ SQLGenerationVisitor visitor = new SQLGenerationVisitor(new
DefaultSQLAppendable(null), null);
+ node.visit(visitor);
+ return visitor.getSQLString();
+ }
}