You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ni...@apache.org on 2020/10/11 09:59:40 UTC

[ignite] branch master updated: IGNITE-13553 Check type of the indexed fields added (#8330)

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

nizhikov 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 2987996  IGNITE-13553 Check type of the indexed fields added (#8330)
2987996 is described below

commit 29879964cd74ce022bad5c59b5c6db170f19e822
Author: Nikolay <ni...@apache.org>
AuthorDate: Sun Oct 11 12:59:12 2020 +0300

    IGNITE-13553 Check type of the indexed fields added (#8330)
---
 .../processors/query/QueryTypeDescriptorImpl.java  |  66 +++++
 .../apache/ignite/tests/p2p/cache/Container.java   |  29 +++
 .../processors/cache/index/BasicIndexTest.java     |   4 +-
 .../query/CreateIndexOnInvalidDataTypeTest.java    |  18 +-
 .../query/WrongQueryEntityFieldTypeTest.java       | 289 +++++++++++++++++++++
 .../IgniteBinaryCacheQueryTestSuite2.java          |   2 +
 6 files changed, 395 insertions(+), 13 deletions(-)

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 f53b38c..4fe0fcc 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
@@ -581,6 +581,16 @@ 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);
+    }
+
+    /** Validate properties. */
+    private void validateProps(Object key, Object val) throws IgniteCheckedException {
         if (F.isEmpty(validateProps))
             return;
 
@@ -660,6 +670,62 @@ public class QueryTypeDescriptorImpl implements GridQueryTypeDescriptor {
         }
     }
 
+    /** Validate indexed values. */
+    private void validateIndexes(Object key, Object val) throws IgniteCheckedException {
+        if (F.isEmpty(idxs))
+            return;
+
+        for (QueryIndexDescriptorImpl idx : idxs.values()) {
+            for (String idxField : idx.fields()) {
+                GridQueryProperty prop = props.get(idxField);
+
+                Object propVal;
+                Class<?> propType;
+
+                if (F.eq(idxField, keyFieldAlias()) || F.eq(idxField, KEY_FIELD_NAME)) {
+                    propVal = key instanceof KeyCacheObject ? ((CacheObject) key).value(coCtx, true) : key;
+
+                    propType = propVal == null ? null : propVal.getClass();
+                }
+                else if (F.eq(idxField, valueFieldAlias()) || F.eq(idxField, VAL_FIELD_NAME)) {
+                    propVal = val instanceof CacheObject ? ((CacheObject)val).value(coCtx, true) : val;
+
+                    propType = propVal == null ? null : propVal.getClass();
+                }
+                else {
+                    propVal = prop.value(key, val);
+
+                    propType = prop.type();
+                }
+
+                if (propVal == null)
+                    continue;
+
+                if (!(propVal instanceof BinaryObject)) {
+                    if (!U.box(propType).isAssignableFrom(U.box(propVal.getClass()))) {
+                        // Some reference type arrays end up being converted to Object[]
+                        if (!(propType.isArray() && Object[].class == propVal.getClass() &&
+                            Arrays.stream((Object[]) propVal).
+                                noneMatch(x -> x != null && !U.box(propType.getComponentType()).isAssignableFrom(U.box(x.getClass())))))
+                        {
+                            throw new IgniteSQLException("Type for a column '" + idxField +
+                                "' is not compatible with index definition. Expected '" +
+                                propType.getSimpleName() + "', actual type '" +
+                                propVal.getClass().getSimpleName() + "'");
+                        }
+                    }
+                }
+                else if (coCtx.kernalContext().cacheObjects().typeId(propType.getName()) !=
+                    ((BinaryObject)propVal).type().typeId()) {
+                    throw new IgniteSQLException("Type for a column '" + idxField +
+                        "' is not compatible with index definition. Expected '" +
+                        propType.getSimpleName() + "', actual type '" +
+                        ((BinaryObject)propVal).type().typeName() + "'");
+                }
+            }
+        }
+    }
+
     /** {@inheritDoc} */
     @SuppressWarnings("ForLoopReplaceableByForEach")
     @Override public void setDefaults(Object key, Object val) throws IgniteCheckedException {
diff --git a/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Container.java b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Container.java
new file mode 100644
index 0000000..fff3612
--- /dev/null
+++ b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/cache/Container.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.tests.p2p.cache;
+
+/** */
+public class Container {
+    /** */
+    private Object field;
+
+    /** */
+    public Container(Object field) {
+        this.field = field;
+    }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BasicIndexTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BasicIndexTest.java
index 8725e3a..24a3759 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BasicIndexTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/BasicIndexTest.java
@@ -1351,11 +1351,11 @@ public class BasicIndexTest extends AbstractIndexingCommonTest {
 
         startGrid();
 
-        sql("CREATE TABLE TEST (ID INT PRIMARY KEY, val_int INT, VAL_OBJ OTHER)");
+        sql("CREATE TABLE TEST (ID INT PRIMARY KEY, val_int INT, VAL_OBJ LONG)");
         sql("CREATE INDEX TEST_VAL_INT ON TEST(VAL_INT)");
         sql("CREATE INDEX TEST_VAL_OBJ ON TEST(VAL_OBJ)");
 
-        sql("INSERT INTO TEST VALUES (0, 0, ?)", new Pojo(0));
+        sql("INSERT INTO TEST VALUES (0, 0, ?)", 0L);
 
         GridTestUtils.assertThrows(log, () -> {
             sql("SELECT * FROM TEST WHERE VAL_OBJ < CURRENT_TIMESTAMP()").getAll();
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/CreateIndexOnInvalidDataTypeTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/CreateIndexOnInvalidDataTypeTest.java
index bead5e4..269693e 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/CreateIndexOnInvalidDataTypeTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/CreateIndexOnInvalidDataTypeTest.java
@@ -33,12 +33,13 @@ import org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.configuration.DataStorageConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.failure.StopNodeFailureHandler;
-import org.apache.ignite.internal.processors.cache.CachePartialUpdateCheckedException;
 import org.apache.ignite.internal.processors.cache.index.AbstractIndexingCommonTest;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.junit.Test;
 
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+
 /**
  * Checks add field with invalid data type to index.
  */
@@ -157,12 +158,9 @@ public class CreateIndexOnInvalidDataTypeTest extends AbstractIndexingCommonTest
         bob.setField("VAL_INT", 10);
         bob.setField("VAL_DATE", new java.util.Date());
 
-        GridTestUtils.assertThrowsAnyCause(log, () -> {
-                grid().cache("test").put(0, bob.build());
+        assertThrowsWithCause(() -> grid().cache("test").put(0, bob.build()), IgniteSQLException.class);
 
-                return null;
-            },
-            CachePartialUpdateCheckedException.class, "Failed to update keys");
+        assertNull(grid().cache("test").get(0));
 
         sql("DROP INDEX TEST_0_VAL_DATE_IDX");
 
@@ -171,7 +169,7 @@ public class CreateIndexOnInvalidDataTypeTest extends AbstractIndexingCommonTest
 
         List<List<?>> res = sql("SELECT VAL_INT FROM TEST WHERE VAL_INT > 0").getAll();
 
-        assertEquals(2, res.size());
+        assertEquals(1, res.size());
     }
 
     /**
@@ -185,16 +183,14 @@ public class CreateIndexOnInvalidDataTypeTest extends AbstractIndexingCommonTest
             .setArgs(args), false);
     }
 
-    /**
-     *
-     */
+    /** */
     private static class Value {
         /** */
         @QuerySqlField
         int val_int;
 
         /** */
-        java.util.Date val_date;
+        Date val_date;
 
         /**
          * @param val Test value.
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/WrongQueryEntityFieldTypeTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/WrongQueryEntityFieldTypeTest.java
new file mode 100644
index 0000000..0f17b11
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/WrongQueryEntityFieldTypeTest.java
@@ -0,0 +1,289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.query;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+import javax.cache.CacheException;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.client.ClientCache;
+import org.apache.ignite.client.ClientCacheConfiguration;
+import org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.ClientTransaction;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.failure.FailureContext;
+import org.apache.ignite.failure.FailureHandler;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.transactions.Transaction;
+import org.apache.ignite.transactions.TransactionConcurrency;
+import org.apache.ignite.transactions.TransactionIsolation;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.apache.ignite.cache.CacheAtomicityMode.ATOMIC;
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL_SNAPSHOT;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+import static org.apache.ignite.transactions.TransactionConcurrency.OPTIMISTIC;
+
+/** */
+@RunWith(Parameterized.class)
+public class WrongQueryEntityFieldTypeTest extends GridCommonAbstractTest {
+    /** */
+    @Parameterized.Parameter()
+    public CacheAtomicityMode mode;
+
+    /** */
+    @Parameterized.Parameter(1)
+    public int backups;
+
+    /** */
+    @Parameterized.Parameter(2)
+    public Supplier<?> val;
+
+    /** */
+    @Parameterized.Parameter(3)
+    public String idxFld;
+
+    /** */
+    @Parameterized.Parameter(4)
+    public Class<?> idxFldType;
+
+    /** */
+    @Parameterized.Parameter(5)
+    public int gridCnt;
+
+    /** */
+    @Parameterized.Parameters(name = "cacheMode={0},backups={1},idxFld={3},idxFldType={4},gridCnt={5}")
+    public static Collection<Object[]> parameters() {
+        Supplier<?> person = WrongQueryEntityFieldTypeTest::personInside;
+        Supplier<?> floatInsteadLong = WrongQueryEntityFieldTypeTest::floatInside;
+        Supplier<?> organization = WrongQueryEntityFieldTypeTest::personInsideOrganization;
+
+        Collection<Object[]> params = new ArrayList<>();
+
+        for (CacheAtomicityMode cacheMode : CacheAtomicityMode.values()) {
+            for (int backups = 0; backups < 4; backups++) {
+                for (int gridCnt = 1; gridCnt < 4; gridCnt++) {
+                    params.add(new Object[] {cacheMode, backups, person, "field", String.class, gridCnt});
+                    params.add(new Object[] {cacheMode, backups, floatInsteadLong, "field", Long.class, gridCnt});
+                    params.add(new Object[] {cacheMode, backups, organization, "head", String.class, gridCnt});
+                }
+            }
+        }
+
+        return params;
+    }
+
+    /** */
+    private volatile boolean sysThreadFail;
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected FailureHandler getFailureHandler(String igniteInstanceName) {
+        return new FailureHandler() {
+            @Override public boolean onFailure(Ignite ignite, FailureContext failureCtx) {
+                sysThreadFail = true;
+
+                return false;
+            }
+        };
+    }
+
+    /** */
+    @Test
+    public void testPutFromThinClient() throws Exception {
+        withThinClient((cli, cache) -> {
+            assertThrowsWithCause(() -> cache.put(1, val.get()), ClientException.class);
+
+            assertNull(cache.withKeepBinary().get(1));
+        });
+    }
+
+    /** */
+    @Test
+    public void testPut() throws Exception {
+        withNode((ign, cache) -> {
+            assertThrowsWithCause(() -> cache.put(1, val.get()), CacheException.class);
+
+            assertNull(cache.withKeepBinary().get(1));
+        });
+    }
+
+    /** */
+    @Test
+    public void testPutFromThinClientTx() throws Exception {
+        if (mode == ATOMIC)
+            return;
+
+        withThinClient((cli, cache) -> {
+            for (TransactionConcurrency conc : TransactionConcurrency.values()) {
+                for (TransactionIsolation iso: TransactionIsolation.values()) {
+                    if (conc == OPTIMISTIC && mode == TRANSACTIONAL_SNAPSHOT)
+                        continue;
+
+                    assertThrowsWithCause(() -> {
+                        try (ClientTransaction tx = cli.transactions().txStart(conc, iso)) {
+                            cache.put(1, val.get());
+
+                            tx.commit();
+                        }
+                    }, ClientException.class);
+
+                    assertNull(cache.withKeepBinary().get(1));
+                }
+            }
+        });
+    }
+
+    /** */
+    @Test
+    public void testPutTx() throws Exception {
+        if (mode == ATOMIC)
+            return;
+
+        withNode((ign, cache) -> {
+            for (TransactionConcurrency conc : TransactionConcurrency.values()) {
+                for (TransactionIsolation iso : TransactionIsolation.values()) {
+                    if (conc == OPTIMISTIC && mode == TRANSACTIONAL_SNAPSHOT)
+                        continue;
+
+                    assertThrowsWithCause(() -> {
+                        try (Transaction tx = ign.transactions().txStart(conc, iso)) {
+                            cache.put(1, val.get());
+
+                            tx.commit();
+                        }
+                    }, mode == TRANSACTIONAL_SNAPSHOT ? CacheException.class : IgniteSQLException.class);
+
+                    assertNull(cache.withKeepBinary().get(1));
+                }
+            }
+        });
+    }
+
+    /** Perform action with Thin client. */
+    private void withThinClient(BiConsumer<IgniteClient, ClientCache<Integer, Object>> consumer) throws Exception {
+        startGrids(gridCnt);
+
+        try (IgniteClient cli = Ignition.startClient(new ClientConfiguration().setAddresses("127.0.0.1:10800"))) {
+            ClientCache<Integer, Object> cache = cli.createCache(new ClientCacheConfiguration()
+                .setName("TEST")
+                .setAtomicityMode(mode)
+                .setBackups(backups)
+                .setQueryEntities(queryEntity()));
+
+            consumer.accept(cli, cache);
+
+            assertFalse(sysThreadFail);
+        }
+    }
+
+    /** Perform action with Ignite node. */
+    private void withNode(BiConsumer<Ignite, IgniteCache<Integer, Object>> consumer) throws Exception {
+        IgniteEx ign = startGrids(gridCnt);
+
+        IgniteCache<Integer, Object> cache = ign.createCache(new CacheConfiguration<Integer, Object>()
+            .setName("TEST")
+            .setAtomicityMode(mode)
+            .setBackups(backups)
+            .setQueryEntities(Collections.singleton(queryEntity())));
+
+        consumer.accept(ign, cache);
+
+        assertFalse(sysThreadFail);
+    }
+
+    /** @return Organization with the Person inside. */
+    public static Object personInsideOrganization() {
+        try {
+            ClassLoader ldr = getExternalClassLoader();
+
+            Class<?> organization = ldr.loadClass("org.apache.ignite.tests.p2p.cache.Organization");
+            Class<?> person = ldr.loadClass("org.apache.ignite.tests.p2p.cache.Person");
+            Class<?> address = ldr.loadClass("org.apache.ignite.tests.p2p.cache.Address");
+
+            Object p = person.getConstructor(String.class).newInstance("test");
+
+            return organization.getConstructor(String.class, person, address).newInstance("org", p, null);
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** @return Container with the Person inside. */
+    public static Object personInside() {
+        try {
+            ClassLoader ldr = getExternalClassLoader();
+
+            Class<?> container = ldr.loadClass("org.apache.ignite.tests.p2p.cache.Container");
+            Class<?> personCls = ldr.loadClass("org.apache.ignite.tests.p2p.cache.Person");
+
+            Object person = personCls.getConstructor().newInstance();
+
+            return container.getConstructor(Object.class).newInstance(person);
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** @return Container with the float inside. */
+    public static Object floatInside() {
+        try {
+            ClassLoader ldr = getExternalClassLoader();
+
+            Class<?> container = ldr.loadClass("org.apache.ignite.tests.p2p.cache.Container");
+
+            return container.getConstructor(Object.class).newInstance(1f);
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** @return Query entity. */
+    private QueryEntity queryEntity() {
+        LinkedHashMap<String, String> fields = new LinkedHashMap<>();
+
+        fields.put(idxFld, idxFldType.getName()); //Actual type of the field is different.
+
+        return new QueryEntity()
+            .setKeyType(Integer.class.getName())
+            .setValueType(val.get().getClass().getName())
+            .setFields(fields)
+            .setIndexes(Collections.singleton(new QueryIndex(idxFld)));
+    }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite2.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite2.java
index 5f96c9f..2022fce 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite2.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite2.java
@@ -71,6 +71,7 @@ import org.apache.ignite.internal.processors.query.SqlLocalQueryConnectionAndSta
 import org.apache.ignite.internal.processors.query.SqlPartOfComplexPkLookupTest;
 import org.apache.ignite.internal.processors.query.SqlQueriesTopologyMappingTest;
 import org.apache.ignite.internal.processors.query.SqlTwoCachesInGroupWithSameEntryTest;
+import org.apache.ignite.internal.processors.query.WrongQueryEntityFieldTypeTest;
 import org.apache.ignite.internal.processors.query.h2.CacheQueryEntityWithDateTimeApiFieldsTest;
 import org.apache.ignite.internal.processors.query.h2.DmlStatementsProcessorTest;
 import org.apache.ignite.internal.processors.query.h2.twostep.CacheQueryMemoryLeakTest;
@@ -94,6 +95,7 @@ import org.junit.runners.Suite;
     DefaultQueryTimeoutTestSuite.class,
 
     CreateIndexOnInvalidDataTypeTest.class,
+    WrongQueryEntityFieldTypeTest.class,
 
     DisabledSqlFunctionsTest.class,