Repository: ignite Updated Branches: refs/heads/master 0a6b44cee -> bd5065b1b
IGNITE-5188: CREATE TABLE: added affinity key support. This closes #2097. Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/bd5065b1 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/bd5065b1 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/bd5065b1 Branch: refs/heads/master Commit: bd5065b1b1a4babf7cd882b9753700e7a58a6094 Parents: 0a6b44c Author: Alexander Paschenko <alexander.a.pasche...@gmail.com> Authored: Thu Jun 8 12:07:38 2017 +0300 Committer: devozerov <voze...@gridgain.com> Committed: Thu Jun 8 12:07:38 2017 +0300 ---------------------------------------------------------------------- .../ignite/internal/binary/BinaryContext.java | 1 - .../ignite/internal/binary/BinaryFieldEx.java | 5 + .../ignite/internal/binary/BinaryFieldImpl.java | 5 + .../query/DynamicTableAffinityKeyMapper.java | 92 ++++++++++ .../processors/query/GridQueryProcessor.java | 52 +++--- .../internal/processors/query/QueryUtils.java | 19 ++- .../query/h2/ddl/DdlStatementsProcessor.java | 2 +- .../processors/query/h2/opt/GridH2Table.java | 5 +- .../query/h2/sql/GridSqlCreateTable.java | 17 ++ .../query/h2/sql/GridSqlQueryParser.java | 44 +++++ .../cache/index/H2DynamicTableSelfTest.java | 170 ++++++++++++++++++- 11 files changed, 375 insertions(+), 37 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java index 70bc2f9..be02ba1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java @@ -116,7 +116,6 @@ import org.apache.ignite.lang.IgniteBiTuple; import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.marshaller.MarshallerContext; import org.apache.ignite.marshaller.MarshallerUtils; -import org.apache.ignite.internal.marshaller.optimized.OptimizedMarshaller; import org.jetbrains.annotations.Nullable; import org.jsr166.ConcurrentHashMap8; http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldEx.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldEx.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldEx.java index 42aa282..c998022 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldEx.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldEx.java @@ -26,6 +26,11 @@ import org.apache.ignite.binary.BinaryObject; */ public interface BinaryFieldEx extends BinaryField { /** + * @return Type ID this field relates to. + */ + public int typeId(); + + /** * Writes field value to the given byte buffer. * * @param obj Object from which the field should be extracted. http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java index c318397..59bd03d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryFieldImpl.java @@ -109,6 +109,11 @@ public class BinaryFieldImpl implements BinaryFieldEx { } /** {@inheritDoc} */ + @Override public int typeId() { + return typeId; + } + + /** {@inheritDoc} */ @Override public boolean writeField(BinaryObject obj, ByteBuffer buf) { BinaryObjectExImpl obj0 = (BinaryObjectExImpl)obj; http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/core/src/main/java/org/apache/ignite/internal/processors/query/DynamicTableAffinityKeyMapper.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/DynamicTableAffinityKeyMapper.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/DynamicTableAffinityKeyMapper.java new file mode 100644 index 0000000..e49341a --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/DynamicTableAffinityKeyMapper.java @@ -0,0 +1,92 @@ +/* + * 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 org.apache.ignite.binary.BinaryObject; +import org.apache.ignite.binary.BinaryType; +import org.apache.ignite.cache.affinity.AffinityKeyMapper; +import org.apache.ignite.internal.binary.BinaryFieldEx; +import org.apache.ignite.internal.binary.BinaryObjectEx; +import org.apache.ignite.internal.util.typedef.F; + +/** + * Trivial mapper to take extract field value from binary object of specific type as affinity key. + */ +@SuppressWarnings("deprecation") +public class DynamicTableAffinityKeyMapper implements AffinityKeyMapper { + /** */ + private static final long serialVersionUID = 0L; + + /** Type name. */ + private final String typeName; + + /** Field name. */ + private final String fieldName; + + /** Type id for faster type checks. */ + private transient volatile BinaryFieldEx field; + + /** + * Constructor. + * + * @param typeName Type name. + * @param fieldName Field name. + */ + DynamicTableAffinityKeyMapper(String typeName, String fieldName) { + this.typeName = typeName; + this.fieldName = fieldName; + } + + /** + * @return Field name. + */ + public String fieldName() { + return fieldName; + } + + /** {@inheritDoc} */ + @Override public Object affinityKey(Object key) { + if (!(key instanceof BinaryObject)) + return key; + + assert key instanceof BinaryObjectEx; + + BinaryObjectEx key0 = (BinaryObjectEx)key; + + if (field == null) { + BinaryType type = key0.type(); + + if (!F.eq(type.typeName(), typeName)) + return key; + + field = (BinaryFieldEx)type.field(fieldName); + } + + if (!F.eq(key0.typeId(), field.typeId())) + return key; + + Object affKey = field.value(key0); + + return affKey != null ? affKey : key; + } + + /** {@inheritDoc} */ + @Override public void reset() { + // No-op. + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java index 48c7229..4311b12 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java @@ -17,6 +17,25 @@ package org.apache.ignite.internal.processors.query; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.cache.Cache; +import javax.cache.CacheException; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; @@ -34,8 +53,8 @@ import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.events.CacheQueryExecutedEvent; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteInternalFuture; -import org.apache.ignite.internal.managers.communication.GridMessageListener; import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.managers.communication.GridMessageListener; import org.apache.ignite.internal.processors.GridProcessorAdapter; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.CacheObject; @@ -72,9 +91,9 @@ import org.apache.ignite.internal.util.lang.GridCloseableIterator; import org.apache.ignite.internal.util.lang.GridClosureException; import org.apache.ignite.internal.util.lang.IgniteOutClosureX; import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.internal.CU; import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.util.typedef.T3; +import org.apache.ignite.internal.util.typedef.internal.CU; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.util.worker.GridWorker; import org.apache.ignite.internal.util.worker.GridWorkerFuture; @@ -88,26 +107,6 @@ import org.apache.ignite.spi.indexing.IndexingQueryFilter; import org.apache.ignite.thread.IgniteThread; import org.jetbrains.annotations.Nullable; -import javax.cache.Cache; -import javax.cache.CacheException; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicBoolean; - import static org.apache.ignite.events.EventType.EVT_CACHE_QUERY_EXECUTED; import static org.apache.ignite.internal.GridTopic.TOPIC_SCHEMA; import static org.apache.ignite.internal.IgniteComponentType.INDEXING; @@ -1279,13 +1278,14 @@ public class GridQueryProcessor extends GridProcessorAdapter { * @param schemaName Schema name to create table in. * @param entity Entity to create table from. * @param templateName Template name. + * @param affinityKey Affinity key column name. * @param atomicityMode Atomicity mode. * @param backups Backups. * @param ifNotExists Quietly ignore this command if table already exists. * @throws IgniteCheckedException If failed. */ @SuppressWarnings("unchecked") - public void dynamicTableCreate(String schemaName, QueryEntity entity, String templateName, + public void dynamicTableCreate(String schemaName, QueryEntity entity, String templateName, String affinityKey, @Nullable CacheAtomicityMode atomicityMode, int backups, boolean ifNotExists) throws IgniteCheckedException { assert !F.isEmpty(templateName); assert backups >= 0; @@ -1316,6 +1316,12 @@ public class GridQueryProcessor extends GridProcessorAdapter { ccfg.setSqlEscapeAll(true); ccfg.setQueryEntities(Collections.singleton(entity)); + if (affinityKey != null) { + assert entity.getFields().containsKey(affinityKey) && entity.getKeyFields().contains(affinityKey); + + ccfg.setAffinityMapper(new DynamicTableAffinityKeyMapper(entity.getKeyType(), affinityKey)); + } + boolean res; try { http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java ---------------------------------------------------------------------- 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 fbd9afa..e0815fd 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 @@ -427,16 +427,19 @@ public class QueryUtils { if (valCls != null) altTypeId = new QueryTypeIdKey(cacheName, valCls); - if (!cctx.customAffinityMapper() && qryEntity.findKeyType() != null) { - // Need to setup affinity key for distributed joins. - String affField = ctx.cacheObjects().affinityField(qryEntity.findKeyType()); + String affField = null; - if (affField != null) { - if (!escape) - affField = normalizeObjectName(affField, false); + // Need to setup affinity key for distributed joins. + if (!cctx.customAffinityMapper() && qryEntity.findKeyType() != null) + affField = ctx.cacheObjects().affinityField(qryEntity.findKeyType()); + else if (cctx.config().getAffinityMapper() instanceof DynamicTableAffinityKeyMapper) + affField = ((DynamicTableAffinityKeyMapper)cctx.config().getAffinityMapper()).fieldName(); - desc.affinityKey(affField); - } + if (affField != null) { + if (!escape) + affField = normalizeObjectName(affField, false); + + desc.affinityKey(affField); } } else { http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java index 5efc5c9..d6bcabd 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java @@ -178,7 +178,7 @@ public class DdlStatementsProcessor { throw err; ctx.query().dynamicTableCreate(cmd.schemaName(), e, cmd.templateName(), - cmd.atomicityMode(), cmd.backups(), cmd.ifNotExists()); + cmd.affinityKey(), cmd.atomicityMode(), cmd.backups(), cmd.ifNotExists()); } } else if (stmt0 instanceof GridSqlDropTable) { http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java index d656cc3..4f3ef01 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java @@ -34,6 +34,7 @@ import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.KeyCacheObject; import org.apache.ignite.internal.processors.cache.query.QueryTable; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; +import org.apache.ignite.internal.processors.query.DynamicTableAffinityKeyMapper; import org.apache.ignite.internal.processors.query.h2.database.H2RowFactory; import org.apache.ignite.internal.processors.query.h2.database.H2TreeIndex; import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory; @@ -130,7 +131,9 @@ public class GridH2Table extends TableBase { this.desc = desc; this.cctx = cctx; - if (desc != null && desc.context() != null && !desc.context().customAffinityMapper()) { + if (desc != null && desc.context() != null && + (!desc.context().customAffinityMapper() || + desc.context().config().getAffinityMapper() instanceof DynamicTableAffinityKeyMapper)) { boolean affinityColExists = true; String affKey = desc.type().affinityKey(); http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateTable.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateTable.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateTable.java index 50348fe..edced6b 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateTable.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlCreateTable.java @@ -53,6 +53,9 @@ public class GridSqlCreateTable extends GridSqlStatement { /** Primary key columns. */ private LinkedHashSet<String> pkCols; + /** Name of the column that represents affinity key. */ + private String affinityKey; + /** Extra WITH-params. */ private List<String> params; @@ -127,6 +130,20 @@ public class GridSqlCreateTable extends GridSqlStatement { } /** + * @return Name of the column that represents affinity key. + */ + public String affinityKey() { + return affinityKey; + } + + /** + * @param affinityKey Name of the column that represents affinity key. + */ + public void affinityKey(String affinityKey) { + this.affinityKey = affinityKey; + } + + /** * @return Schema name upon which this statement has been issued. */ public String schemaName() { http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java index fdb908e..d8baed3 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQueryParser.java @@ -405,6 +405,9 @@ public class GridSqlQueryParser { private static final String PARAM_ATOMICITY = "ATOMICITY"; /** */ + private static final String PARAM_AFFINITY_KEY = "AFFINITYKEY"; + + /** */ private final IdentityHashMap<Object, Object> h2ObjToGridObj = new IdentityHashMap<>(); /** */ @@ -1048,6 +1051,47 @@ public class GridSqlQueryParser { break; + case PARAM_AFFINITY_KEY: + ensureNotEmpty(name, val); + + String affColName = null; + + // Either strip column name off its quotes, or uppercase it. + if (val.startsWith("'")) { + if (val.length() == 1 || !val.endsWith("'")) + throw new IgniteSQLException("Affinity key column name does not have trailing quote: " + val, + IgniteQueryErrorCode.PARSING); + + val = val.substring(1, val.length() - 1); + + ensureNotEmpty(name, val); + + affColName = val; + } + else { + for (String colName : res.columns().keySet()) { + if (val.equalsIgnoreCase(colName)) { + if (affColName != null) + throw new IgniteSQLException("Ambiguous affinity column name, use single quotes " + + "for case sensitivity: " + val, IgniteQueryErrorCode.PARSING); + + affColName = colName; + } + } + } + + if (affColName == null || !res.columns().containsKey(affColName)) + throw new IgniteSQLException("Affinity key column with given name not found: " + val, + IgniteQueryErrorCode.PARSING); + + if (!res.primaryKeyColumns().contains(affColName)) + throw new IgniteSQLException("Affinity key column must be one of key columns: " + affColName, + IgniteQueryErrorCode.PARSING); + + res.affinityKey(affColName); + + break; + default: throw new IgniteSQLException("Unsupported parameter: " + name, IgniteQueryErrorCode.PARSING); } http://git-wip-us.apache.org/repos/asf/ignite/blob/bd5065b1/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicTableSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicTableSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicTableSelfTest.java index 699fdad..e4babba 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicTableSelfTest.java +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/H2DynamicTableSelfTest.java @@ -19,13 +19,15 @@ package org.apache.ignite.internal.processors.cache.index; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Random; import java.util.concurrent.Callable; - import javax.cache.CacheException; - import org.apache.ignite.Ignite; import org.apache.ignite.IgniteException; import org.apache.ignite.Ignition; @@ -41,6 +43,7 @@ import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.binary.BinaryMarshaller; import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor; import org.apache.ignite.internal.processors.query.GridQueryProperty; +import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor; import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.QueryTypeDescriptorImpl; import org.apache.ignite.internal.processors.query.QueryUtils; @@ -401,7 +404,7 @@ public class H2DynamicTableSelfTest extends AbstractSchemaSelfTest { e.setKeyType("CityKey"); e.setValueType("City"); - queryProcessor(client()).dynamicTableCreate("PUBLIC", e, CacheMode.PARTITIONED.name(), + queryProcessor(client()).dynamicTableCreate("PUBLIC", e, CacheMode.PARTITIONED.name(), null, CacheAtomicityMode.ATOMIC, 10, false); return null; @@ -427,6 +430,167 @@ public class H2DynamicTableSelfTest extends AbstractSchemaSelfTest { } /** + * @throws Exception if failed. + */ + public void testAffinityKey() throws Exception { + executeDdl("CREATE TABLE \"City\" (\"name\" varchar primary key, \"code\" int) WITH \"affinityKey='name'\""); + + assertAffinityCacheConfiguration("City", "name"); + + executeDdl("INSERT INTO \"City\" (\"name\", \"code\") values ('A', 1), ('B', 2), ('C', 3)"); + + List<String> cityNames = Arrays.asList("A", "B", "C"); + + List<Integer> cityCodes = Arrays.asList(1, 2, 3); + + // We need unique name for this table to avoid conflicts with existing binary metadata. + executeDdl("CREATE TABLE \"Person2\" (\"id\" int, \"city\" varchar," + + " \"name\" varchar, \"surname\" varchar, \"age\" int, PRIMARY KEY (\"id\", \"city\")) WITH " + + "\"template=cache,affinityKey='city'\""); + + assertAffinityCacheConfiguration("Person2", "city"); + + Random r = new Random(); + + Map<Integer, Integer> personId2cityCode = new HashMap<>(); + + for (int i = 0; i < 100; i++) { + int cityIdx = r.nextInt(3); + + String cityName = cityNames.get(cityIdx); + + int cityCode = cityCodes.get(cityIdx); + + personId2cityCode.put(i, cityCode); + + queryProcessor(client()).querySqlFieldsNoCache(new SqlFieldsQuery("insert into \"Person2\"(\"id\", " + + "\"city\") values (?, ?)").setArgs(i, cityName), true).getAll(); + } + + List<List<?>> res = queryProcessor(client()).querySqlFieldsNoCache(new SqlFieldsQuery("select \"id\", " + + "c.\"code\" from \"Person2\" p left join \"City\" c on p.\"city\" = c.\"name\" where c.\"name\" " + + "is not null"), true).getAll(); + + assertEquals(100, res.size()); + + for (int i = 0; i < 100; i++) { + assertNotNull(res.get(i).get(0)); + + assertNotNull(res.get(i).get(1)); + + int id = (Integer)res.get(i).get(0); + + int code = (Integer)res.get(i).get(1); + + assertEquals((int)personId2cityCode.get(id), code); + } + } + + /** + * Test various cases of affinity key column specification. + */ + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + public void testAffinityKeyCaseSensitivity() { + executeDdl("CREATE TABLE \"A\" (\"name\" varchar primary key, \"code\" int) WITH \"affinityKey='name'\""); + + assertAffinityCacheConfiguration("A", "name"); + + executeDdl("CREATE TABLE \"B\" (name varchar primary key, \"code\" int) WITH \"affinityKey=name\""); + + assertAffinityCacheConfiguration("B", "NAME"); + + executeDdl("CREATE TABLE \"C\" (name varchar primary key, \"code\" int) WITH \"affinityKey=NamE\""); + + assertAffinityCacheConfiguration("C", "NAME"); + + executeDdl("CREATE TABLE \"D\" (\"name\" varchar primary key, \"code\" int) WITH \"affinityKey=NAME\""); + + assertAffinityCacheConfiguration("D", "name"); + + // Error arises because user has specified case sensitive affinity column name + GridTestUtils.assertThrows(null, new Callable<Object>() { + @Override public Object call() throws Exception { + executeDdl("CREATE TABLE \"E\" (name varchar primary key, \"code\" int) WITH \"affinityKey='Name'\""); + + return null; + } + }, IgniteSQLException.class, "Affinity key column with given name not found: Name"); + + // Error arises because user declares case insensitive affinity column name while having two 'name' + // columns whose names are equal in ignore case. + GridTestUtils.assertThrows(null, new Callable<Object>() { + @Override public Object call() throws Exception { + executeDdl("CREATE TABLE \"E\" (\"name\" varchar, \"Name\" int, val int, primary key(\"name\", " + + "\"Name\")) WITH \"affinityKey=name\""); + + return null; + } + }, IgniteSQLException.class, "Ambiguous affinity column name, use single quotes for case sensitivity: name"); + + executeDdl("CREATE TABLE \"E\" (\"name\" varchar, \"Name\" int, val int, primary key(\"name\", " + + "\"Name\")) WITH \"affinityKey='Name'\""); + + assertAffinityCacheConfiguration("E", "Name"); + } + + /** + * Tests that attempting to specify an affinity key that actually is a value column yields an error. + */ + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + public void testAffinityKeyNotKeyColumn() { + // Error arises because user has specified case sensitive affinity column name + GridTestUtils.assertThrows(null, new Callable<Object>() { + @Override public Object call() throws Exception { + executeDdl("CREATE TABLE \"E\" (name varchar primary key, \"code\" int) WITH \"affinityKey=code\""); + + return null; + } + }, IgniteSQLException.class, "Affinity key column must be one of key columns: code"); + } + + /** + * Tests that attempting to specify an affinity key that actually is a value column yields an error. + */ + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + public void testAffinityKeyNotFound() { + // Error arises because user has specified case sensitive affinity column name + GridTestUtils.assertThrows(null, new Callable<Object>() { + @Override public Object call() throws Exception { + executeDdl("CREATE TABLE \"E\" (name varchar primary key, \"code\" int) WITH \"affinityKey=missing\""); + + return null; + } + }, IgniteSQLException.class, "Affinity key column with given name not found: missing"); + } + + /** + * Check that dynamic cache created with {@code CREATE TABLE} is correctly configured affinity wise. + * @param cacheName Cache name to check. + * @param affKeyFieldName Expected affinity key field name. + */ + private void assertAffinityCacheConfiguration(String cacheName, String affKeyFieldName) { + String actualCacheName = cacheName(cacheName); + + Collection<GridQueryTypeDescriptor> types = client().context().query().types(actualCacheName); + + assertEquals(1, types.size()); + + GridQueryTypeDescriptor type = types.iterator().next(); + + assertTrue(type.name().startsWith(actualCacheName)); + assertEquals(cacheName, type.tableName()); + assertEquals(affKeyFieldName, type.affinityKey()); + + GridH2Table tbl = ((IgniteH2Indexing)queryProcessor(client()).getIndexing()).dataTable("PUBLIC", cacheName); + + assertNotNull(tbl); + + assertNotNull(tbl.getAffinityKeyColumn()); + + assertEquals(affKeyFieldName, tbl.getAffinityKeyColumn().columnName); + } + + /** * Execute {@code CREATE TABLE} w/given params. * @param params Engine parameters. */