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

alexpl 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 e225c19d20d IGNITE-26467 SQL: Fix indexed field validation - Fixes 
#12370.
e225c19d20d is described below

commit e225c19d20d57d714fc79cca769a8089f37ac122
Author: Aleksey Plekhanov <[email protected]>
AuthorDate: Fri Oct 3 10:18:32 2025 +0300

    IGNITE-26467 SQL: Fix indexed field validation - Fixes #12370.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 .../integration/KeyClassChangeIntegrationTest.java | 103 +++++++++++++++++-
 .../processors/query/QueryTypeDescriptorImpl.java  | 115 +++++++++++++++------
 .../internal/processors/query/QueryUtils.java      |   2 +
 3 files changed, 186 insertions(+), 34 deletions(-)

diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/KeyClassChangeIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/KeyClassChangeIntegrationTest.java
index 45078d52a44..6cefef53b03 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/KeyClassChangeIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/KeyClassChangeIntegrationTest.java
@@ -16,12 +16,17 @@
  */
 package org.apache.ignite.internal.processors.query.calcite.integration;
 
+import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.IntFunction;
 import javax.cache.CacheException;
+import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.cache.QueryEntity;
 import org.apache.ignite.cache.affinity.AffinityKeyMapped;
 import org.apache.ignite.cache.query.annotations.QuerySqlField;
 import org.apache.ignite.cluster.ClusterState;
@@ -30,8 +35,8 @@ import 
org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.configuration.DataStorageConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.failure.TestFailureHandler;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.testframework.GridTestUtils;
-import org.junit.Ignore;
 import org.junit.Test;
 
 /** */
@@ -48,6 +53,9 @@ public class KeyClassChangeIntegrationTest extends 
AbstractMultiEngineIntegratio
     /** */
     private Class<?> indexedKeyCls;
 
+    /** */
+    private boolean pds;
+
     /** {@inheritDoc} */
     @Override protected void beforeTest() throws Exception {
         super.beforeTest();
@@ -55,6 +63,7 @@ public class KeyClassChangeIntegrationTest extends 
AbstractMultiEngineIntegratio
         failureHnd = new TestFailureHandler(true);
         validateTypes = false;
         indexedKeyCls = BaseKey.class;
+        pds = false;
 
         cleanPersistenceDir();
     }
@@ -72,7 +81,7 @@ public class KeyClassChangeIntegrationTest extends 
AbstractMultiEngineIntegratio
 
         cfg.setDataStorageConfiguration(new DataStorageConfiguration()
             .setDefaultDataRegionConfiguration(new DataRegionConfiguration()
-                .setPersistenceEnabled(true)));
+                .setPersistenceEnabled(pds)));
 
         cfg.setFailureHandler(failureHnd);
 
@@ -122,12 +131,98 @@ public class KeyClassChangeIntegrationTest extends 
AbstractMultiEngineIntegratio
     }
 
     /** */
-    @Ignore("https://issues.apache.org/jira/browse/IGNITE-26467";)
     @Test
     public void testIncompatibleAffinityField() throws Exception {
         testDifferentKeyTypes(false, IncompatibleAffinityFieldKey::new, true, 
false);
     }
 
+    /** */
+    @Test
+    public void testConcurrentIndexRecreate() throws Exception {
+        startGrid(0);
+
+        AtomicBoolean stop = new AtomicBoolean();
+
+        sql(grid(0), "CREATE INDEX IDX_NAME ON \"" + DEFAULT_CACHE_NAME + 
"\".TESTVALUE(NAME)");
+
+        GridTestUtils.runAsync(() -> {
+            while (!stop.get()) {
+                sql(grid(0), "CREATE INDEX IDX ON \"" + DEFAULT_CACHE_NAME + 
"\".TESTVALUE(UID)");
+                sql(grid(0), "DROP INDEX \"" + DEFAULT_CACHE_NAME + "\".IDX");
+            }
+        });
+
+        IgniteCache<Object, Object> cache = grid(0).cache(DEFAULT_CACHE_NAME);
+
+        try {
+            for (int i = 0; i < 100_000; i++) {
+                Object key = new BaseKey(i);
+                cache.put(key, new TestValue(UUID.randomUUID(), 
Integer.toString(i)));
+                cache.remove(key);
+            }
+        }
+        finally {
+            stop.set(true);
+        }
+
+        assertEquals(0, cache.size());
+    }
+
+    /** */
+    @Test
+    public void testPrimitiveKey() throws Exception {
+        indexedKeyCls = Integer.class;
+
+        Ignite ignite = startGrid(0);
+
+        IgniteCache<Object, Object> cache = ignite.cache(DEFAULT_CACHE_NAME);
+
+        cache.put(0, new TestValue(UUID.randomUUID(), "0"));
+
+        GridTestUtils.assertThrowsWithCause(() -> cache.put(new BaseKey(0), 
new TestValue(UUID.randomUUID(), "0")),
+            CacheException.class);
+
+        GridTestUtils.assertThrowsWithCause(() -> cache.put("0", new 
TestValue(UUID.randomUUID(), "0")),
+            CacheException.class);
+
+        assertEquals(1, cache.size());
+        assertEquals(1,
+            sql(grid(0), "select _KEY, name from \"" + DEFAULT_CACHE_NAME + 
"\".TESTVALUE").size());
+    }
+
+    /** */
+    @Test
+    public void testPrimitiveKeyWithAlias() throws Exception {
+        Ignite ignite = startGrid(0);
+
+        LinkedHashMap<String, String> fields = new LinkedHashMap<>();
+        fields.put("KEY", Integer.class.getName());
+        fields.put("UID", UUID.class.getName());
+        fields.put("NAME", String.class.getName());
+
+        IgniteCache<Object, Object> cache = ignite.getOrCreateCache(new 
CacheConfiguration<>("cache")
+            .setQueryEntities(Collections.singletonList(
+                new QueryEntity()
+                    .setKeyType(Integer.class.getName())
+                    .setValueType(TestValue.class.getName())
+                    .setFields(fields)
+                    .setKeyFieldName("KEY")
+                    .setAliases(F.asMap("KEY", "ID"))
+        )));
+
+        cache.put(0, new TestValue(UUID.randomUUID(), "0"));
+
+        GridTestUtils.assertThrowsWithCause(() -> cache.put(new BaseKey(0), 
new TestValue(UUID.randomUUID(), "0")),
+            CacheException.class);
+
+        GridTestUtils.assertThrowsWithCause(() -> cache.put("0", new 
TestValue(UUID.randomUUID(), "0")),
+            CacheException.class);
+
+        assertEquals(1, cache.size());
+        assertEquals(1,
+            sql(grid(0), "select id, name from \"cache\".TESTVALUE").size());
+    }
+
     /** */
     private void testDifferentKeyTypes(
         boolean restart,
@@ -135,6 +230,8 @@ public class KeyClassChangeIntegrationTest extends 
AbstractMultiEngineIntegratio
         boolean expFailureOnPut,
         boolean expFailureOnSelect
     ) throws Exception {
+        pds = restart;
+
         startGrids(SRV_CNT).cluster().state(ClusterState.ACTIVE);
 
         IgniteCache<Object, Object> cache0 = grid(0).cache(DEFAULT_CACHE_NAME);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
index 3be6819a1e9..2ad7a194cc0 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -131,6 +132,9 @@ public class QueryTypeDescriptorImpl implements 
GridQueryTypeDescriptor {
     /** */
     private List<GridQueryProperty> validateProps;
 
+    /** */
+    private volatile List<GridQueryProperty> validateIdxProps;
+
     /** */
     private List<GridQueryProperty> propsWithDefaultValue;
 
@@ -267,7 +271,7 @@ public class QueryTypeDescriptorImpl implements 
GridQueryTypeDescriptor {
     /** {@inheritDoc} */
     @Override public Map<String, GridQueryIndexDescriptor> indexes() {
         synchronized (idxMux) {
-            return Collections.<String, 
GridQueryIndexDescriptor>unmodifiableMap(idxs);
+            return Map.copyOf(idxs);
         }
     }
 
@@ -331,6 +335,9 @@ public class QueryTypeDescriptorImpl implements 
GridQueryTypeDescriptor {
         synchronized (idxMux) {
             if (idxs.put(idx.name(), idx) != null)
                 throw new IgniteCheckedException("Index with name '" + 
idx.name() + "' already exists.");
+
+            if (validateIdxProps != null) // Do not rebuild for each index on 
initialization.
+                updateIdxProps();
         }
     }
 
@@ -342,7 +349,56 @@ public class QueryTypeDescriptorImpl implements 
GridQueryTypeDescriptor {
     public void dropIndex(String idxName) {
         synchronized (idxMux) {
             idxs.remove(idxName);
+
+            updateIdxProps();
+        }
+    }
+
+    /** */
+    public void onInitialized() {
+        synchronized (idxMux) {
+            updateIdxProps();
+        }
+    }
+
+    /** */
+    private void updateIdxProps() {
+        List<GridQueryProperty> idxProps = new ArrayList<>();
+        Set<String> usedProps = new HashSet<>();
+
+        // idxs iterator must be guarded by idxMux.
+        for (GridQueryIndexDescriptor idx : idxs.values()) {
+            for (String fld : idx.fields()) {
+                if (usedProps.add(fld)) {
+                    GridQueryProperty prop = props.get(fld);
+
+                    if (prop != null)
+                        idxProps.add(prop);
+                }
+            }
+        }
+
+        if (affKey != null && usedProps.add(affKey)) {
+            GridQueryProperty prop = props.get(affKey);
+
+            if (prop != null)
+                idxProps.add(prop);
+        }
+
+        if (!F.isEmpty(primaryKeyFields())) {
+            for (String fld : primaryKeyFields()) {
+                if (usedProps.add(fld)) {
+                    GridQueryProperty prop = props.get(fld);
+
+                    if (prop != null)
+                        idxProps.add(prop);
+                }
+            }
         }
+        else if (F.isEmpty(keyFieldAlias()) || 
!usedProps.contains(keyFieldAlias()))
+            idxProps.add(new QueryUtils.KeyOrValProperty(true, KEY_FIELD_NAME, 
keyClass()));
+
+        validateIdxProps = idxProps;
     }
 
     /**
@@ -609,9 +665,6 @@ public class QueryTypeDescriptorImpl implements 
GridQueryTypeDescriptor {
     /** {@inheritDoc} */
     @SuppressWarnings("ForLoopReplaceableByForEach")
     @Override public void validateKeyAndValue(Object key, Object val) throws 
IgniteCheckedException {
-        if (F.isEmpty(validateProps) && F.isEmpty(idxs))
-            return;
-
         validateProps(key, val);
 
         validateIndexes(key, val);
@@ -683,43 +736,43 @@ public class QueryTypeDescriptorImpl implements 
GridQueryTypeDescriptor {
 
     /** Validate indexed values. */
     private void validateIndexes(Object key, Object val) throws 
IgniteCheckedException {
-        if (F.isEmpty(idxs))
+        if (F.isEmpty(validateIdxProps))
             return;
 
-        for (QueryIndexDescriptorImpl idx : idxs.values()) {
-            for (String idxField : idx.fields()) {
-                GridQueryProperty prop = props.get(idxField);
-
-                Object propVal;
-                Class<?> propType;
-
-                if (Objects.equals(idxField, keyFieldAlias()) || 
Objects.equals(idxField, KEY_FIELD_NAME)) {
-                    propVal = key instanceof KeyCacheObject ? 
((CacheObject)key).value(coCtx, true) : key;
-
-                    propType = propVal == null ? null : propVal.getClass();
-                }
-                else if (Objects.equals(idxField, valueFieldAlias()) || 
Objects.equals(idxField, VAL_FIELD_NAME)) {
-                    propVal = val instanceof CacheObject ? 
((CacheObject)val).value(coCtx, true) : val;
+        for (GridQueryProperty prop : validateIdxProps) {
+            Object propVal;
+            Class<?> propType = prop.type();
 
-                    propType = propVal == null ? null : propVal.getClass();
-                }
-                else {
-                    propVal = prop.value(key, val);
+            if (propType == Object.class)
+                continue;
 
-                    propType = prop.type();
-                }
+            if (Objects.equals(prop.name(), keyFieldAlias()) || 
Objects.equals(prop.name(), KEY_FIELD_NAME))
+                propVal = unwrap(key);
+            else if (Objects.equals(prop.name(), valueFieldAlias()) || 
Objects.equals(prop.name(), VAL_FIELD_NAME))
+                propVal = unwrap(val);
+            else
+                propVal = prop.value(key, val);
 
-                if (propVal == null)
-                    continue;
+            if (propVal == null)
+                continue;
 
-                if (!isCompatibleWithPropertyType(propVal, propType)) {
-                    throw new IgniteSQLException("Type for a column '" + 
idxField + "' is not compatible with index definition." +
-                        " Expected '" + prop.type().getSimpleName() + "', 
actual type '" + typeName(propVal) + "'");
-                }
+            if (!isCompatibleWithPropertyType(propVal, propType)) {
+                throw new IgniteSQLException("Type for a column '" + 
prop.name() + "' is not compatible with index definition." +
+                    " Expected '" + propType.getSimpleName() + "', actual type 
'" + typeName(propVal) + "'");
             }
         }
     }
 
+    /** */
+    private Object unwrap(Object val) {
+        if (val instanceof BinaryObject)
+            return val;
+        else if (val instanceof CacheObject)
+            return ((CacheObject)val).value(coCtx, false);
+        else
+            return val;
+    }
+
     /**
      * Checks if the specified object is compatible with the type of the 
column through which this object will be accessed.
      *
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
index 90ca30e35bc..172e94713bd 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
@@ -624,6 +624,8 @@ public class QueryUtils {
             desc.affinityFieldInlineSize(-1);
         }
 
+        desc.onInitialized();
+
         return new QueryTypeCandidate(typeId, altTypeId, desc);
     }
 

Reply via email to