This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch past-M2
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 074cd9eaf1732191d05f75a2b525eb74947fcb66
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun Jun 14 13:47:28 2026 -0400

    CAY-2964 ClassCastException for non-generated meaningful PKs
---
 RELEASE-NOTES.txt                                  |  1 +
 .../access/flush/PermanentObjectIdVisitor.java     | 37 +++++++++++-----------
 .../org/apache/cayenne/dba/JdbcPkGenerator.java    | 26 ++++++++-------
 .../java/org/apache/cayenne/dba/PkGenerator.java   | 18 ++++++++---
 .../dba/sqlserver/SQLServerPkGenerator.java        |  4 +--
 .../apache/cayenne/access/DataContextExtrasIT.java |  2 +-
 6 files changed, 52 insertions(+), 36 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index cc06801ef..e381602f4 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@ CAY-2958 Empty join is saved
 CAY-2959 Modeler: DbRelationship dialog "Cancel" doesn't cancel
 CAY-2960 Undoing renamed relationship change throws
 CAY-2961 PostgreSQL "text" column is reverse-engineered as CLOB
+CAY-2964 ClassCastException for non-generated meaningful PKs
 
 ----------------------------------
 Release: 5.0-M2
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/flush/PermanentObjectIdVisitor.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/flush/PermanentObjectIdVisitor.java
index d1412a6d9..a412db1c2 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/flush/PermanentObjectIdVisitor.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/flush/PermanentObjectIdVisitor.java
@@ -19,8 +19,6 @@
 
 package org.apache.cayenne.access.flush;
 
-import java.util.Map;
-
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.access.DataDomain;
@@ -37,6 +35,8 @@ import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.reflect.ClassDescriptor;
 
+import java.util.Map;
+
 /**
  * Visitor that fills replacement map of {@link ObjectId}s of inserted objects.
  *
@@ -65,9 +65,9 @@ class PermanentObjectIdVisitor implements 
DbRowOpVisitor<Void> {
             return null;
         }
 
-        if((lastObjEntity == null && lastDbEntity == null) || 
!id.getEntityName().equals(lastEntityName)) {
+        if ((lastObjEntity == null && lastDbEntity == null) || 
!id.getEntityName().equals(lastEntityName)) {
             lastEntityName = id.getEntityName();
-            if(lastEntityName.startsWith(ASTDbPath.DB_PREFIX)) {
+            if (lastEntityName.startsWith(ASTDbPath.DB_PREFIX)) {
                 lastDbEntity = 
resolver.getDbEntity(lastEntityName.substring(ASTDbPath.DB_PREFIX.length()));
                 lastObjEntity = null;
                 lastDescriptor = null;
@@ -87,7 +87,7 @@ class PermanentObjectIdVisitor implements 
DbRowOpVisitor<Void> {
     @Override
     public Void visitDelete(DeleteDbRowOp dbRow) {
         // flattened ids will be temporary for delete operation, replace it
-        if(dbRow.getChangeId().isTemporary() && 
dbRow.getChangeId().isReplacementIdAttached()) {
+        if (dbRow.getChangeId().isTemporary() && 
dbRow.getChangeId().isReplacementIdAttached()) {
             dbRow.setChangeId(dbRow.getChangeId().createReplacementId());
         }
         return null;
@@ -110,18 +110,17 @@ class PermanentObjectIdVisitor implements 
DbRowOpVisitor<Void> {
                 continue;
             }
 
+            ObjAttribute objAttr = lastObjEntity != null ? 
lastObjEntity.getAttributeForDbAttribute(dbAttr) : null;
+
             // handle meaningful PK
-            if(lastObjEntity != null) {
-                ObjAttribute objAttr = 
lastObjEntity.getAttributeForDbAttribute(dbAttr);
-                if (objAttr != null) {
-                    Object value = 
lastDescriptor.getProperty(objAttr.getName()).readPropertyDirectly(dbRow.getObject());
-                    if (value != null) {
-                        // primitive 0 has to be treated as NULL, or otherwise 
we can't generate PK for POJO's
-                        Class<?> javaClass = objAttr.getJavaClass();
-                        if (!javaClass.isPrimitive() || !(value instanceof 
Number number) || number.intValue() != 0) {
-                            idMap.put(dbAttrName, value);
-                            continue;
-                        }
+            if (objAttr != null) {
+                Object value = 
lastDescriptor.getProperty(objAttr.getName()).readPropertyDirectly(dbRow.getObject());
+                if (value != null) {
+                    // primitive 0 has to be treated as NULL, or otherwise we 
can't generate PK for POJO's
+                    Class<?> javaClass = objAttr.getJavaClass();
+                    if (!javaClass.isPrimitive() || !(value instanceof Number 
number) || number.intValue() != 0) {
+                        idMap.put(dbAttrName, value);
+                        continue;
                     }
                 }
             }
@@ -140,11 +139,13 @@ class PermanentObjectIdVisitor implements 
DbRowOpVisitor<Void> {
 
             // finally, use database generation mechanism
             try {
-                Object pkValue = pkGenerator.generatePk(lastNode, dbAttr);
+                Class<?> javaType = objAttr != null ? objAttr.getJavaClass() : 
null;
+
+                Object pkValue = pkGenerator.generatePk(lastNode, dbAttr, 
javaType);
                 idMap.put(dbAttrName, pkValue);
                 autoPkDone = true;
             } catch (Exception ex) {
-                throw new CayenneRuntimeException("Error generating PK: %s", 
ex,  ex.getMessage());
+                throw new CayenneRuntimeException("Error generating PK: %s", 
ex, ex.getMessage());
             }
         }
     }
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java
index 6423370ef..9e15df372 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcPkGenerator.java
@@ -25,6 +25,8 @@ import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.ResultIterator;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.OperationObserver;
+import org.apache.cayenne.access.types.ValueObjectType;
+import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbKeyGenerator;
@@ -185,17 +187,8 @@ public class JdbcPkGenerator implements PkGenerator {
         }
     }
 
-    /**
-     * Generates a unique and non-repeating primary key for specified dbEntity.
-     * <p>
-     * This implementation is naive since it does not lock the database rows
-     * when executing select and subsequent update. Adapter-specific
-     * implementations are more robust.
-     * </p>
-     *
-     * @since 3.0
-     */
-    public Object generatePk(DataNode node, DbAttribute pk) throws Exception {
+    @Override
+    public Object generatePk(DataNode node, DbAttribute pk, Class<?> javaType) 
throws Exception {
 
         DbEntity entity = pk.getEntity();
 
@@ -239,6 +232,17 @@ public class JdbcPkGenerator implements PkGenerator {
             }
         }
 
+        if (javaType != null) {
+            ValueObjectTypeRegistry registry = 
node.getEntityResolver().getValueObjectTypeRegistry();
+            if (registry != null) {
+                ValueObjectType<Object, Long> converter = 
(ValueObjectType<Object, Long>) registry.getValueType(javaType);
+                if (converter != null) {
+                    return converter.toJavaObject(value);
+                }
+            }
+        }
+
+        // legacy behavior for plain numeric PKs
         if (pk.getType() == Types.BIGINT) {
             return value;
         } else {
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/PkGenerator.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/PkGenerator.java
index 440ce5ec4..a7a79222f 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/PkGenerator.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/PkGenerator.java
@@ -19,12 +19,12 @@
 
 package org.apache.cayenne.dba;
 
-import java.util.List;
-
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 
+import java.util.List;
+
 /**
  * Defines methods to support automatic primary key generation.
  */
@@ -66,9 +66,19 @@ public interface PkGenerator {
     /**
      * Generates a unique and non-repeating primary key for specified PK 
attribute.
      *
-     * @since 3.0
+     * @deprecated unused by the framework that relies on {@link 
#generatePk(DataNode, DbAttribute, Class)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
+    default Object generatePk(DataNode dataNode, DbAttribute pk) throws 
Exception {
+        return generatePk(dataNode, pk, null);
+    }
+
+    /**
+     * Generates a unique and non-repeating primary key for the specified PK 
attribute
+     *
+     * @since 5.0
      */
-    Object generatePk(DataNode dataNode, DbAttribute pk) throws Exception;
+    Object generatePk(DataNode dataNode, DbAttribute pk, Class<?> javaType) 
throws Exception;
 
     /**
      * Install the adapter associated with current PkGenerator
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerPkGenerator.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerPkGenerator.java
index 36800e375..5dd23a2e4 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerPkGenerator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerPkGenerator.java
@@ -105,14 +105,14 @@ public class SQLServerPkGenerator extends 
OraclePkGenerator {
     }
 
     @Override
-    public Object generatePk(DataNode node, DbAttribute pk) throws Exception {
+    public Object generatePk(DataNode node, DbAttribute pk, Class<?> javaType) 
throws Exception {
         DbEntity entity = pk.getEntity();
 
         //check key on UNIQUEIDENTIFIER; UNIQUEIDENTIFIER is a character with 
a length of 36
         if (TypesMapping.isCharacter(pk.getType()) && pk.getMaxLength() == 
MAX_LENGTH_GUID) {
             return guidPkFromDatabase(node, entity);
         } else {
-            return super.generatePk(node, pk);
+            return super.generatePk(node, pk, javaType);
         }
     }
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java 
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java
index 1e4ed51b0..eee773d11 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java
@@ -253,7 +253,7 @@ public class DataContextExtrasIT {
         PkGenerator newGenerator = new JdbcPkGenerator(jdbcAdapter) {
 
             @Override
-            public Object generatePk(DataNode node, DbAttribute pk) {
+            public Object generatePk(DataNode node, DbAttribute pk, Class<?> 
javaType) {
                 throw new CayenneRuntimeException("Intentional");
             }
         };

Reply via email to