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;