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.
      */

Reply via email to