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);
}