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 09d56b747d833..c9266500397b6 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 @@ -548,10 +548,6 @@ public static void processBinaryMeta(GridKernalContext ctx, QueryEntity qryEntit Set notNulls = qryEntity.getNotNullFields(); Map dlftVals = qryEntity.getDefaultFieldValues(); - // We have to distinguish between empty and null keyFields when the key is not of SQL type - - // when a key is not of SQL type, absence of a field in nonnull keyFields tell us that this field - // is a value field, and null keyFields tells us that current configuration - // does not tell us anything about this field's ownership. boolean hasKeyFields = (keyFields != null); boolean isKeyClsSqlType = isSqlType(d.keyClass()); @@ -565,20 +561,28 @@ public static void processBinaryMeta(GridKernalContext ctx, QueryEntity qryEntit } } + // We are creating binary properties for all the fields, even if field is of sql type (keyFieldName or + // valueFieldName). In that case we rely on the fact, that binary property's methods value() and + // setValue() will never get called, because there is no value to extract, key/val object itself is a + // value. for (Map.Entry entry : qryEntity.getFields().entrySet()) { - Boolean isKeyField; + String fieldName = entry.getKey(); + String fieldType = entry.getValue(); - if (isKeyClsSqlType) // We don't care about keyFields in this case - it might be null, or empty, or anything + boolean isKeyField; + + if (isKeyClsSqlType) + // Entire key is not field of itself, even if it is set in "keyFields". isKeyField = false; else - isKeyField = (hasKeyFields ? keyFields.contains(entry.getKey()) : null); + isKeyField = hasKeyFields && keyFields.contains(fieldName); - boolean notNull = notNulls != null && notNulls.contains(entry.getKey()); + boolean notNull = notNulls != null && notNulls.contains(fieldName); - Object dfltVal = dlftVals != null ? dlftVals.get(entry.getKey()) : null; + Object dfltVal = dlftVals != null ? dlftVals.get(fieldName) : null; QueryBinaryProperty prop = buildBinaryProperty(ctx, entry.getKey(), - U.classForName(entry.getValue(), Object.class, true), + U.classForName(fieldType, Object.class, true), d.aliases(), isKeyField, notNull, dfltVal); d.addProperty(prop, false); @@ -727,7 +731,7 @@ else if (idxTyp != null) * @throws IgniteCheckedException On error. */ public static QueryBinaryProperty buildBinaryProperty(GridKernalContext ctx, String pathStr, Class resType, - Map aliases, @Nullable Boolean isKeyField, boolean notNull, Object dlftVal) throws IgniteCheckedException { + Map aliases, boolean isKeyField, boolean notNull, Object dlftVal) throws IgniteCheckedException { String[] path = pathStr.split("\\."); QueryBinaryProperty res = null; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/property/QueryBinaryProperty.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/property/QueryBinaryProperty.java index f440d124c55e6..5623e635e7dae 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/property/QueryBinaryProperty.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/property/QueryBinaryProperty.java @@ -53,8 +53,8 @@ public class QueryBinaryProperty implements GridQueryProperty { /** Result class. */ private Class type; - /** */ - private volatile int isKeyProp; + /** Defines where value should be extracted from : cache entry's key or value. */ + private final boolean isKeyProp; /** Binary field to speed-up deserialization. */ private volatile BinaryField field; @@ -62,9 +62,6 @@ public class QueryBinaryProperty implements GridQueryProperty { /** Flag indicating that we already tried to take a field. */ private volatile boolean fieldTaken; - /** Whether user was warned about missing property. */ - private volatile boolean warned; - /** */ private final boolean notNull; @@ -78,13 +75,13 @@ public class QueryBinaryProperty implements GridQueryProperty { * @param propName Property name. * @param parent Parent property. * @param type Result type. - * @param key {@code true} if key property, {@code false} otherwise, {@code null} if unknown. + * @param key {@code true} if key property, {@code false} otherwise. * @param alias Field alias. * @param notNull {@code true} if null value is not allowed. * @param defaultValue Default value. */ public QueryBinaryProperty(GridKernalContext ctx, String propName, QueryBinaryProperty parent, - Class type, @Nullable Boolean key, String alias, boolean notNull, Object defaultValue) { + Class type, boolean key, String alias, boolean notNull, Object defaultValue) { this.ctx = ctx; log = ctx.log(QueryBinaryProperty.class); @@ -94,10 +91,7 @@ public QueryBinaryProperty(GridKernalContext ctx, String propName, QueryBinaryPr this.parent = parent; this.type = type; this.notNull = notNull; - - if (key != null) - this.isKeyProp = key ? 1 : -1; - + this.isKeyProp = key; this.defaultValue = defaultValue; } @@ -115,33 +109,12 @@ public QueryBinaryProperty(GridKernalContext ctx, String propName, QueryBinaryPr throw new IgniteCheckedException("Non-binary object received as a result of property extraction " + "[parent=" + parent + ", propName=" + propName + ", obj=" + obj + ']'); } - else { - int isKeyProp0 = isKeyProp; - - if (isKeyProp0 == 0) { - // Key is allowed to be a non-binary object here. - // We check key before value consistently with ClassProperty. - if (key instanceof BinaryObject && ((BinaryObject)key).hasField(propName)) - isKeyProp = isKeyProp0 = 1; - else if (val instanceof BinaryObject && ((BinaryObject)val).hasField(propName)) - isKeyProp = isKeyProp0 = -1; - else { - if (!warned) { - U.warn(log, "Neither key nor value have property \"" + propName + "\" " + - "(is cache indexing configured correctly?)"); - - warned = true; - } - - return null; - } - } - - obj = isKeyProp0 == 1 ? key : val; - } + else + obj = isKeyProp ? key : val; if (obj instanceof BinaryObject) { BinaryObject obj0 = (BinaryObject) obj; + return fieldValue(obj0); } else if (obj instanceof BinaryObjectBuilder) { @@ -263,13 +236,7 @@ private Object fieldValue(BinaryObject obj) { /** {@inheritDoc} */ @Override public boolean key() { - int isKeyProp0 = isKeyProp; - - if (isKeyProp0 == 0) - throw new IllegalStateException("Ownership flag not set for binary property. Have you set 'keyFields'" + - " property of QueryEntity in programmatic or XML configuration?"); - - return isKeyProp0 == 1; + return isKeyProp; } /** {@inheritDoc} */ diff --git a/modules/core/src/test/java/org/apache/ignite/client/FunctionalTest.java b/modules/core/src/test/java/org/apache/ignite/client/FunctionalTest.java index b49f7e3a186f8..0a60bdb9dfa8e 100644 --- a/modules/core/src/test/java/org/apache/ignite/client/FunctionalTest.java +++ b/modules/core/src/test/java/org/apache/ignite/client/FunctionalTest.java @@ -162,7 +162,10 @@ public void testCacheConfiguration() throws Exception { SimpleEntry::getKey, SimpleEntry::getValue, (a, b) -> a, LinkedHashMap::new )) ) - .setKeyFields(Collections.singleton("id")) + // During query normalization null keyFields become empty set. + // Set empty collection for comparator. + .setKeyFields(Collections.emptySet()) + .setKeyFieldName("id") .setNotNullFields(Collections.singleton("id")) .setDefaultFieldValues(Collections.singletonMap("id", 0)) .setIndexes(Collections.singletonList(new QueryIndex("id", true, "IDX_EMPLOYEE_ID"))) diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/CacheBinaryKeyConcurrentQueryTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/CacheBinaryKeyConcurrentQueryTest.java index 2aad0d6202571..f7c7a350b4ffb 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/CacheBinaryKeyConcurrentQueryTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/CacheBinaryKeyConcurrentQueryTest.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.cache; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; @@ -240,6 +241,8 @@ private CacheConfiguration cacheConfiguration(String name, CacheAtomicityMode at qryEntity.addQueryField("id", Integer.class.getName(), null); qryEntity.addQueryField("val", Integer.class.getName(), null); + qryEntity.setKeyFields(Collections.singleton("id")); + qryEntity.setIndexes(F.asList(new QueryIndex("id"), new QueryIndex("val"))); ccfg.setQueryEntities(F.asList(qryEntity)); diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheDistributedJoinCollocatedAndNotTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheDistributedJoinCollocatedAndNotTest.java index 18c551cfd0545..3c6ee3db56a88 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheDistributedJoinCollocatedAndNotTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheDistributedJoinCollocatedAndNotTest.java @@ -19,6 +19,8 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.ignite.Ignite; @@ -87,6 +89,8 @@ public class IgniteCacheDistributedJoinCollocatedAndNotTest extends AbstractInde entity.addQueryField("affKey", Integer.class.getName(), null); entity.addQueryField("name", String.class.getName(), null); + entity.setKeyFields(new HashSet<>(Arrays.asList("id", "affKey"))); + ccfg.setQueryEntities(F.asList(entity)); ccfgs.add(ccfg); diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheJoinQueryWithAffinityKeyTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheJoinQueryWithAffinityKeyTest.java index 4c7dbd0de5706..8d08447bdadb8 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheJoinQueryWithAffinityKeyTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheJoinQueryWithAffinityKeyTest.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.cache; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -365,6 +366,7 @@ private CacheConfiguration cacheConfiguration(CacheMode cacheMode, QueryEntity person = new QueryEntity(); person.setKeyType(personKeyType); person.setValueType(Person.class.getName()); + person.setKeyFields(Collections.singleton("id")); person.addQueryField("orgId", Integer.class.getName(), null); person.addQueryField("id", Integer.class.getName(), null); person.addQueryField("name", String.class.getName(), null); diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheSqlInsertValidationSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheSqlInsertValidationSelfTest.java new file mode 100644 index 0000000000000..1809d08388434 --- /dev/null +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/IgniteCacheSqlInsertValidationSelfTest.java @@ -0,0 +1,286 @@ +/* + * 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.cache; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.apache.ignite.IgniteCache; +import org.apache.ignite.cache.QueryEntity; +import org.apache.ignite.cache.query.QueryCursor; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.cache.query.annotations.QuerySqlField; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.internal.processors.query.IgniteSQLException; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; + +/** + * Tests for validation of inserts sql queries. + */ +@RunWith(JUnit4.class) +public class IgniteCacheSqlInsertValidationSelfTest extends GridCommonAbstractTest { + /** Entry point for sql api. Contains table configurations too. */ + private static IgniteCache cache; + + /** Default value for fk2 field of WITH_KEY_FLDS table. */ + private static final Long DEFAULT_FK2_VAL = 42L; + + /** Default value for fk2 field of WITH_KEY_FLDS table. */ + private static final Long DEFAULT_FK1_VAL = null; + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + startGrid(0); + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + Map defsFK2 = new HashMap<>(); + defsFK2.put("fk2", DEFAULT_FK2_VAL); + + cache = jcache(grid(0), defaultCacheConfiguration() + .setName("testCache") + .setQueryEntities(Arrays.asList( + new QueryEntity(Key.class.getName(), Val.class.getName()) + .addQueryField("fk1", "java.lang.Long", null) + .addQueryField("fk2", "java.lang.Long", null) + .addQueryField("fv1", "java.lang.Long", null) + .addQueryField("fv2", "java.lang.Long", null) + .setTableName("FORGOTTEN_KEY_FLDS"), + new QueryEntity(Key.class.getName(), Integer.class.getName()) + .addQueryField("fk1", "java.lang.Long", null) + .addQueryField("fk2", "java.lang.Long", null) + .setDefaultFieldValues(defsFK2) + .setKeyFields(new HashSet<>(Arrays.asList("fk1", "fk2"))) + .setTableName("WITH_KEY_FLDS"), + new QueryEntity(Integer.class.getName(), Val2.class.getName()) + .addQueryField("fv1", "java.lang.Long", null) + .addQueryField("fv2", "java.lang.Long", null) + .setTableName("INT_KEY_TAB"), + new QueryEntity(SuperKey.class, String.class) + .setTableName("SUPER_TAB") + )) + , "testCache"); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + if (cache != null) + cache.destroy(); + } + + /** + * Check that if we cannot insert row using sql due to we don't have keyFields in the configuration, we are still + * able to put using cache api. + */ + @Test + public void testCacheApiIsStillAllowed() { + cache.put(new Key(1, 2), new Val(3, 4)); + + assertNotNull("Expected cache to contain object ", cache.get(new Key(1, 2))); + } + + /** + * Check that we are able to perform sql insert using special "_key" field. Even in case of non sql key. + */ + @Test + public void testInsertDefaultKeyName() { + Object cnt = execute("INSERT INTO INT_KEY_TAB (_key, fv1, fv2) VALUES (1 , 2 , 3)").get(0).get(0); + + assertEquals("Expected one row successfully inserted ", 1L, cnt); + } + + /** + * Check forgotten key fields. + * If we've forgotten to specify key fields and we don't specify _key, then default key is inserted. + */ + @Test + public void testIncorrectComplex() { + execute("INSERT INTO FORGOTTEN_KEY_FLDS(FK1, FK2, FV1, FV2) VALUES (2,3,4,5)"); + + GridTestUtils.assertThrows(log(), + () -> execute("INSERT INTO FORGOTTEN_KEY_FLDS(FK1, FK2, FV1, FV2) VALUES (8,9,10,11)"), + IgniteSQLException.class, + "Duplicate key during INSERT"); + } + + /** + * Check that we can specify only one pk column (out of two). Second one should be of default value for type; + */ + @Test + public void testNotAllKeyColsComplex() { + execute("INSERT INTO WITH_KEY_FLDS(FK1, _val) VALUES (7, 1)"); // Missing FK2 -> (7, 42, 1) + execute("INSERT INTO WITH_KEY_FLDS(FK2, _val) VALUES (15, 2)"); // Missing FK1 -> (null, 15, 2) + + Long fk2 = (Long)execute("SELECT FK2 FROM WITH_KEY_FLDS WHERE _val = 1").get(0).get(0); + Long fk1 = (Long)execute("SELECT FK1 FROM WITH_KEY_FLDS WHERE _val = 2").get(0).get(0); + + assertEquals(DEFAULT_FK2_VAL, fk2); + assertEquals(DEFAULT_FK1_VAL, fk1); + } + + /** + * Check that we can't perform insert without at least one key field specified. + */ + @Test + public void testMixedPlaceholderWithOtherKeyFields() { + GridTestUtils.assertThrows(log(), + () -> execute("INSERT INTO WITH_KEY_FLDS(_key, FK1, _val) VALUES (?, ?, ?)", + new Key(1, 2), 42, 43), + IgniteSQLException.class, + "Column _KEY refers to entire key cache object."); + } + + /** + * Check that key can contain nested field with its own fields. Check that we can insert mixing sql and non sql + * values. + */ + @Test + public void testSuperKey() { + execute("INSERT INTO SUPER_TAB (SUPERKEYID, NESTEDKEY, _val) VALUES (?, ?, ?)", + 123, new NestedKey("the name "), "the _val value"); + } + + /** + * Check that key can contain nested field with its own fields. Check that we can insert using _key placeholder. + */ + @Test + public void testSuperKeyNative() { + execute("INSERT INTO SUPER_TAB (_key, _val) VALUES (?, ?)", + new SuperKey(1, new NestedKey("the name")), + "_val value"); + } + + /** + * Check we can amend fields list part. + */ + @Test + public void testInsertImplicitAllFields() { + execute("CREATE TABLE PUBLIC.IMPLICIT_INS (id1 BIGINT, id2 BIGINT, val BIGINT, PRIMARY KEY(id1, id2))"); + + execute("INSERT INTO PUBLIC.IMPLICIT_INS VALUES (1,2,3)"); + } + + /** + * Execute native sql. + * + * @param sql query. + * @param args arguments of SqlFieldsQuery. + * @return {@link QueryCursor#getAll()} - result of the query. + */ + private List> execute(String sql, Object... args) { + return cache.query(new SqlFieldsQuery(sql).setArgs(args)).getAll(); + } + + /** + * @param qryEntity Query entity. + * @return Cache configuration. + */ + protected CacheConfiguration cacheConfiguration(QueryEntity qryEntity) { + CacheConfiguration cache = defaultCacheConfiguration(); + + cache.setBackups(1); + cache.setWriteSynchronizationMode(FULL_SYNC); + cache.setQueryEntities(Collections.singletonList(qryEntity)); + + return cache; + } + + private static class Key { + private long fk1; + + private long fk2; + + public Key(long fk1, long fk2) { + this.fk1 = fk1; + this.fk2 = fk2; + } + + @Override public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Key key = (Key)o; + return fk1 == key.fk1 && + fk2 == key.fk2; + } + + @Override public int hashCode() { + return Objects.hash(fk1, fk2); + } + } + + private static class SuperKey { + @QuerySqlField + private long superKeyId; + + @QuerySqlField + private NestedKey nestedKey; + + public SuperKey(long superKeyId, NestedKey nestedKey) { + this.superKeyId = superKeyId; + this.nestedKey = nestedKey; + } + } + + private static class NestedKey { + @QuerySqlField + private String name; + + public NestedKey(String name) { + this.name = name; + } + } + + private static class Val { + private long fv1; + + private long fv2; + + public Val(long fv1, long fv2) { + this.fv1 = fv1; + this.fv2 = fv2; + } + } + + private static class Val2 { + private long fv1; + + private long fv2; + + public Val2(long fv1, long fv2) { + this.fv1 = fv1; + this.fv2 = fv2; + } + } +} \ No newline at end of file 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 2d2d141606a59..04897c6afcf79 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 @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; @@ -95,6 +96,7 @@ public class BasicIndexTest extends AbstractIndexingCommonTest { .setKeyType(Key.class.getName()) .setValueType(Val.class.getName()) .setFields(fields) + .setKeyFields(new HashSet<>(Arrays.asList("keyStr", "keyLong", "keyPojo"))) .setIndexes(indexes) )) .setSqlIndexMaxInlineSize(inlineSize); diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/sql/IgniteCachePartitionedAtomicColumnConstraintsTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/sql/IgniteCachePartitionedAtomicColumnConstraintsTest.java new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java index 2fe55a26e149d..df4c5c521be7e 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java +++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite.java @@ -24,6 +24,7 @@ import org.apache.ignite.internal.processors.cache.IgniteCacheBinaryObjectsScanSelfTest; import org.apache.ignite.internal.processors.cache.IgniteCacheBinaryObjectsScanWithEventsSelfTest; import org.apache.ignite.internal.processors.cache.BigEntryQueryTest; +import org.apache.ignite.internal.processors.cache.IgniteCacheSqlInsertValidationSelfTest; import org.apache.ignite.internal.processors.query.RunningQueriesTest; import org.apache.ignite.internal.processors.query.h2.GridIndexFullRebuildTest; @@ -49,6 +50,8 @@ public static TestSuite suite() throws Exception { suite.addTestSuite(BinaryMetadataConcurrentUpdateWithIndexesTest.class); + suite.addTestSuite(IgniteCacheSqlInsertValidationSelfTest.class); + //Should be adjusted. Not ready to be used with BinaryMarshaller. //suite.addTestSuite(GridCacheBinarySwapScanQuerySelfTest.class); diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs index 5c48284010c31..ae57880c15496 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs @@ -283,8 +283,11 @@ public void TestInvalidCompositeKey() ValueTypeName = "Foo", Fields = new[] { + /// Next two fieleds belong to the object, so should have been marked with + // But if we forgot to do this - all fields are treated as value fields. Key fields have default values and second insert fails. new QueryField("Lo", typeof(int)), new QueryField("Hi", typeof(int)), + new QueryField("Id", typeof(int)), new QueryField("Name", typeof(string)) } @@ -296,8 +299,7 @@ public void TestInvalidCompositeKey() () => cache.Query(new SqlFieldsQuery("insert into foo(lo, hi, id, name) " + "values (1, 2, 3, 'John'), (4, 5, 6, 'Mary')"))); - Assert.AreEqual("Ownership flag not set for binary property. Have you set 'keyFields' " + - "property of QueryEntity in programmatic or XML configuration?", ex.Message); + StringAssert.StartsWith("Failed to INSERT some keys because they are already in cache", ex.Message); } /// diff --git a/modules/platforms/python/tests/test_binary.py b/modules/platforms/python/tests/test_binary.py new file mode 100644 index 0000000000000..e69de29bb2d1d