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/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new aeab55bc846 IGNITE-28755 SQL Calcite: Fix typeId collisions for 
autogenerated types - Fixes #13219.
aeab55bc846 is described below

commit aeab55bc8463060eb6e0f8dd4278d32bcac41615
Author: Aleksey Plekhanov <[email protected]>
AuthorDate: Fri Jun 19 11:09:29 2026 +0500

    IGNITE-28755 SQL Calcite: Fix typeId collisions for autogenerated types - 
Fixes #13219.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 .../query/calcite/exec/ddl/DdlCommandHandler.java  |  57 +++++++++++-
 .../integration/TableDdlIntegrationTest.java       | 100 +++++++++++++++++++++
 .../ignite/internal/MarshallerContextImpl.java     |  21 ++++-
 .../internal/processors/marshaller/MappedName.java |   4 +-
 4 files changed, 174 insertions(+), 8 deletions(-)

diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/DdlCommandHandler.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/DdlCommandHandler.java
index 48f6dbf845b..9cba5c4c3a3 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/DdlCommandHandler.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ddl/DdlCommandHandler.java
@@ -28,12 +28,13 @@ import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Supplier;
-import javax.annotation.Nullable;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.Table;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.MarshallerContextImpl;
+import org.apache.ignite.internal.binary.BinaryContext;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
 import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
@@ -64,7 +65,9 @@ import 
org.apache.ignite.internal.processors.security.IgniteSecurity;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.plugin.security.SecurityPermission;
+import org.jetbrains.annotations.Nullable;
 
+import static org.apache.ignite.internal.MarshallerPlatformIds.JAVA_ID;
 import static 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal.SAVEPOINTS_EXPLICIT_TX_ONLY;
 import static org.apache.ignite.internal.processors.query.QueryUtils.convert;
 import static 
org.apache.ignite.internal.processors.query.QueryUtils.isDdlOnSchemaSupported;
@@ -180,6 +183,12 @@ public class DdlCommandHandler {
 
         CacheConfiguration<?, ?> ccfg = new 
CacheConfiguration<>(cmd.tableName());
 
+        if (!F.isEmpty(cmd.valueTypeName()))
+            validateTypeName(cmd.valueTypeName());
+
+        if (!F.isEmpty(cmd.keyTypeName()))
+            validateTypeName(cmd.keyTypeName());
+
         QueryEntity e = toQueryEntity(cmd);
 
         ccfg.setQueryEntities(Collections.singleton(e));
@@ -436,14 +445,23 @@ public class DdlCommandHandler {
         if (!F.isEmpty(scale))
             res.setFieldsScale(scale);
 
-        String valTypeName = 
QueryUtils.createTableValueTypeName(cmd.schemaName(), cmd.tableName());
+        String generatedValTypeName;
+        String generatedKeyTypeName;
+
+        do {
+            generatedValTypeName = 
QueryUtils.createTableValueTypeName(cmd.schemaName(), cmd.tableName());
+            generatedKeyTypeName = 
QueryUtils.createTableKeyTypeName(generatedValTypeName);
+        }
+        while (hasTypeIdCollisions(generatedValTypeName) || 
hasTypeIdCollisions(generatedKeyTypeName));
+
+        String valTypeName = generatedValTypeName;
 
         String keyTypeName;
         if ((!F.isEmpty(cmd.primaryKeyColumns()) && 
cmd.primaryKeyColumns().size() > 1) || !F.isEmpty(cmd.keyTypeName())) {
             keyTypeName = cmd.keyTypeName();
 
             if (F.isEmpty(keyTypeName))
-                keyTypeName = QueryUtils.createTableKeyTypeName(valTypeName);
+                keyTypeName = generatedKeyTypeName;
 
             if (!F.isEmpty(cmd.primaryKeyColumns())) {
                 res.setKeyFields(new LinkedHashSet<>(cmd.primaryKeyColumns()));
@@ -457,7 +475,7 @@ public class DdlCommandHandler {
             if (cmd.wrapKey()) {
                 res.setKeyFields(Set.copyOf(cmd.primaryKeyColumns()));
 
-                keyTypeName = QueryUtils.createTableKeyTypeName(valTypeName);
+                keyTypeName = generatedKeyTypeName;
 
                 res.setPreserveKeysOrder(true);
             }
@@ -498,4 +516,35 @@ public class DdlCommandHandler {
 
         return res;
     }
+
+    /** */
+    private void validateTypeName(String typeName) {
+        String duplicatedTypeName = duplicatedTypeName(typeName);
+        if (duplicatedTypeName != null) {
+            throw new IgniteSQLException(
+                "Duplicate ID [typeId=" + 
qryProc.objectContext().binaryContext().typeId(typeName) +
+                ", oldCls=" + duplicatedTypeName +
+                ", newCls=" + typeName + "], please retry with another type 
name.");
+        }
+    }
+
+    /** */
+    private boolean hasTypeIdCollisions(String typeName) {
+        return duplicatedTypeName(typeName) != null;
+    }
+
+    /** */
+    private @Nullable String duplicatedTypeName(String typeName) {
+        BinaryContext binCtx = qryProc.objectContext().binaryContext();
+
+        int typeId = binCtx.typeId(typeName);
+
+        String anotherTypeName = 
((MarshallerContextImpl)binCtx.marshaller().getContext()).resolveClassName(JAVA_ID,
 typeId);
+
+        if (anotherTypeName == null || anotherTypeName.equals(typeName)
+            || 
binCtx.userTypeName(anotherTypeName).equals(binCtx.userTypeName(typeName)))
+            return null;
+
+        return anotherTypeName;
+    }
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDdlIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDdlIntegrationTest.java
index 4941c251cd0..7164c0d8eb1 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDdlIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/TableDdlIntegrationTest.java
@@ -29,14 +29,21 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import org.apache.ignite.IgniteBinary;
 import org.apache.ignite.IgniteCache;
+import org.apache.ignite.binary.BinaryBasicNameMapper;
 import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.cache.CacheWriteSynchronizationMode;
 import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.configuration.BinaryConfiguration;
 import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.MarshallerContextImpl;
+import org.apache.ignite.internal.binary.BinaryContext;
 import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
+import org.apache.ignite.internal.processors.marshaller.MappedName;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import 
org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
@@ -45,10 +52,12 @@ import 
org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.transactions.TransactionConcurrency;
 import org.hamcrest.CustomMatcher;
 import org.hamcrest.Matcher;
 import org.junit.Test;
 
+import static org.apache.ignite.internal.MarshallerPlatformIds.JAVA_ID;
 import static 
org.apache.ignite.internal.processors.query.calcite.TestUtils.hasSize;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.hasItem;
@@ -57,6 +66,12 @@ import static org.junit.Assert.assertThat;
 
 /** */
 public class TableDdlIntegrationTest extends AbstractDdlIntegrationTest {
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        return 
super.getConfiguration(igniteInstanceName).setBinaryConfiguration(
+            new BinaryConfiguration().setNameMapper(new 
BinaryBasicNameMapper(true)));
+    }
+
     /**
      * Creates table with two columns, where the first column is PK,
      * and verifies created cache.
@@ -1240,6 +1255,91 @@ public class TableDdlIntegrationTest extends 
AbstractDdlIntegrationTest {
         }
     }
 
+    /**
+     * Tests that type id collisions for autogenerated types are automatically 
resolved.
+     */
+    @Test
+    public void testAutogeneratedTypeIdCollision() {
+        BinaryContext binCtx = client.context().cacheObjects().binaryContext();
+
+        binCtx.registerUserClassName(binCtx.typeId("TestTypeName"), 
"TestTypeName", false, true, JAVA_ID);
+
+        MarshallerContextImpl marshCtx = 
(MarshallerContextImpl)binCtx.marshaller().getContext();
+
+        Map<Integer, MappedName> mappings = 
marshCtx.getCachedMappings().get(JAVA_ID);
+
+        MappedName dummyName = new MappedName("Dummy", true);
+
+        // Maximize probability of collision.
+        for (int i = 0; i < 20_000_000; i++)
+            mappings.putIfAbsent(i, dummyName);
+        try {
+            for (int i = 0; i < 100; i++) {
+                sql("CREATE TABLE test (id1 int, id2 int, val int, primary key 
(id1, id2))");
+                sql("INSERT INTO test VALUES (0, 0, 0)");
+                sql("DROP TABLE test");
+            }
+        }
+        finally {
+            mappings.values().removeIf(v -> v == dummyName);
+        }
+    }
+
+    /**
+     * Tests that table with explicitly provided types with type id collisions 
cannot be created.
+     */
+    @Test
+    public void testExplicitTypeIdCollision() {
+        IgniteBinary bin = client.binary();
+        String type0 = "TestType";
+
+        // Register user binary type.
+        bin.builder(type0).build();
+
+        // Check that explicit type can be used.
+        sql("CREATE TABLE test (id1 int, id2 int, val int, primary key (id1, 
id2)) WITH KEY_TYPE=\"" + type0 + "\"");
+        sql("INSERT INTO test VALUES (0, 0, 0)");
+        sql("DROP TABLE test");
+
+        sql("CREATE TABLE test (id1 int, id2 int, val int, primary key (id1, 
id2)) WITH VALUE_TYPE=\"" + type0 + "\"");
+        sql("INSERT INTO test VALUES (0, 0, 0)");
+        sql("DROP TABLE test");
+
+        // Check that explicit different types mapped with name mapper to the 
same type can be used.
+        String pkg = "org.apache.ignite.";
+        String type1 = pkg + type0;
+        String type2 = pkg.toUpperCase() + type0;
+
+        // Use default type ID mapper, which use lowercase chars for type ID 
generation, but name mapper with "simple name" flag.
+        // So both "org.apache.ignite.TestType" and 
"ORG.APACHE.IGNITE.TestType" types are converted to the "TestType"
+        // by name mapper, and have the same type ID, but have different type 
ID with "TestType".
+        assertNotSame(bin.typeId(type0), bin.typeId(type1));
+        assertEquals(bin.typeId(type1), bin.typeId(type2));
+
+        bin.builder(type1).build();
+
+        sql("CREATE TABLE test (id1 int, id2 int, val int, primary key (id1, 
id2)) WITH KEY_TYPE=\"" + type2 + "\"");
+        sql("INSERT INTO test VALUES (0, 0, 0)");
+        sql("DROP TABLE test");
+
+        sql("CREATE TABLE test (id1 int, id2 int, val int, primary key (id1, 
id2)) WITH VALUE_TYPE=\"" + type2 + "\"");
+        sql("INSERT INTO test VALUES (0, 0, 0)");
+        sql("DROP TABLE test");
+
+        // Check that duplicated type can't be used.
+        String duplicatedType = 
"SQL_PUBLIC_T1_85fe3916_800f_40de_9e8f_8dbbae60c04d_KEY";
+
+        
assertEquals(client.binary().typeId(TransactionConcurrency.class.getName()), 
client.binary().typeId(duplicatedType));
+
+        assertThrows("CREATE TABLE test (id1 int, id2 int, val int, primary 
key (id1, id2)) " +
+                "WITH KEY_TYPE=\"" + duplicatedType + '\"',
+            IgniteSQLException.class, "Duplicate ID");
+
+        assertThrows("CREATE TABLE test (id1 int, id2 int, val int, primary 
key (id1, id2)) " +
+                "WITH VALUE_TYPE=\"" + duplicatedType + '\"',
+            IgniteSQLException.class, "Duplicate ID");
+    }
+
     /**
      * Creates table with primary key and check inline size.
      */
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java
index 8fa94b71df7..fda923e3596 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java
@@ -541,14 +541,15 @@ public class MarshallerContextImpl implements 
MarshallerContext {
     /**
      * @param platformId Platform id.
      * @param typeId Type id.
+     * @param ensureAccepted Ensure that mapping is accepted.
      */
-    public String resolveMissedMapping(byte platformId, int typeId) {
+    private @Nullable String resolveClassName(byte platformId, int typeId, 
boolean ensureAccepted) {
         ConcurrentMap<Integer, MappedName> cache = getCacheFor(platformId);
 
         MappedName mappedName = cache.get(typeId);
 
         if (mappedName != null) {
-            assert mappedName.accepted() : mappedName;
+            assert !ensureAccepted || mappedName.accepted() : mappedName;
 
             return mappedName.className();
         }
@@ -556,6 +557,22 @@ public class MarshallerContextImpl implements 
MarshallerContext {
         return null;
     }
 
+    /**
+     * @param platformId Platform id.
+     * @param typeId Type id.
+     */
+    public @Nullable String resolveClassName(byte platformId, int typeId) {
+        return resolveClassName(platformId, typeId, false);
+    }
+
+    /**
+     * @param platformId Platform id.
+     * @param typeId Type id.
+     */
+    public @Nullable String resolveMissedMapping(byte platformId, int typeId) {
+        return resolveClassName(platformId, typeId, true);
+    }
+
     /** {@inheritDoc} */
     @Override public boolean isSystemType(String typeName) {
         return sysTypesSet.contains(typeName);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/marshaller/MappedName.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/marshaller/MappedName.java
index 4a000fadc23..58b301b8c0f 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/marshaller/MappedName.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/marshaller/MappedName.java
@@ -34,7 +34,7 @@ public final class MappedName implements Serializable, 
Message {
     @Order(0)
     String clsName;
 
-    /** */
+    /** Flag, showing whether this mapping was accepted by other nodes or not. 
*/
     @Order(1)
     boolean accepted;
 
@@ -58,7 +58,7 @@ public final class MappedName implements Serializable, 
Message {
     }
 
     /**
-     *
+     * @return {@code True} if this mapping was accepted by other nodes.
      */
     public boolean accepted() {
         return accepted;

Reply via email to