You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2018/11/13 13:25:12 UTC

ignite git commit: IGNITE-10231: SQL: moved partition extraction logic into separate class to simplify further development. This closes #5374.

Repository: ignite
Updated Branches:
  refs/heads/master 41bc714df -> 4ea3d4c3e


IGNITE-10231: SQL: moved partition extraction logic into separate class to simplify further development. This closes #5374.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/4ea3d4c3
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/4ea3d4c3
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/4ea3d4c3

Branch: refs/heads/master
Commit: 4ea3d4c3e3464de9cac7e05a639eee0aa57c22ed
Parents: 41bc714
Author: devozerov <vo...@gridgain.com>
Authored: Tue Nov 13 16:25:04 2018 +0300
Committer: devozerov <vo...@gridgain.com>
Committed: Tue Nov 13 16:25:04 2018 +0300

----------------------------------------------------------------------
 .../cache/query/CacheQueryPartitionInfo.java    | 143 -------
 .../cache/query/GridCacheTwoStepQuery.java      |   8 +-
 .../processors/query/h2/IgniteH2Indexing.java   |   8 +-
 .../query/h2/affinity/PartitionExtractor.java   | 384 +++++++++++++++++++
 .../query/h2/affinity/PartitionInfo.java        | 143 +++++++
 .../query/h2/sql/GridSqlQuerySplitter.java      | 338 +---------------
 6 files changed, 539 insertions(+), 485 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/4ea3d4c3/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryPartitionInfo.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryPartitionInfo.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryPartitionInfo.java
deleted file mode 100644
index 12e9a39..0000000
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryPartitionInfo.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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.query;
-
-import org.apache.ignite.internal.util.typedef.internal.S;
-
-/**
- * Holds the partition calculation info extracted from a query.
- * The query may have several such items associated with it.
- *
- * The query may contain expressions containing key or affinity key.
- * Such expressions can be used as hints to derive small isolated set
- * of partitions the query needs to run on.
- *
- * In case expression contains constant (e.g. _key = 100), the partition
- * can be calculated right away and saved into cache along with the query.
- *
- * In case expression has a parameter (e.g. _key = ?), the effective
- * partition varies with each run of the query. Hence, instead of partition,
- * one must store the info required to calculate partition.
- *
- * The given class holds the required info, so that effective partition
- * can be calculated during query parameter binding.
- */
-public class CacheQueryPartitionInfo {
-    /** */
-    private final int partId;
-
-    /** */
-    private final String cacheName;
-
-    /** */
-    private final String tableName;
-
-    /** */
-    private final int dataType;
-
-    /** */
-    private final int paramIdx;
-
-    /**
-     * @param partId Partition id, or -1 if parameter binding required.
-     * @param cacheName Cache name required for partition calculation.
-     * @param tableName Table name required for proper type conversion.
-     * @param dataType Required data type id for the query parameter.
-     * @param paramIdx Query parameter index required for partition calculation.
-     */
-    public CacheQueryPartitionInfo(int partId, String cacheName, String tableName, int dataType, int paramIdx) {
-        // In case partition is not known, both cacheName and tableName must be provided.
-        assert (partId >= 0) ^ ((cacheName != null) && (tableName != null));
-
-        this.partId = partId;
-        this.cacheName = cacheName;
-        this.tableName = tableName;
-        this.dataType = dataType;
-        this.paramIdx = paramIdx;
-    }
-
-    /**
-     * @return Partition id, or -1 if parameter binding is required to calculate partition.
-     */
-    public int partition() {
-        return partId;
-    }
-
-    /**
-     * @return Cache name required for partition calculation.
-     */
-    public String cacheName() {
-        return cacheName;
-    }
-
-    /**
-     * @return Table name.
-     */
-    public String tableName() {
-        return tableName;
-    }
-
-    /**
-     * @return Required data type for the query parameter.
-     */
-    public int dataType() {
-        return dataType;
-    }
-
-    /**
-     * @return Query parameter index required for partition calculation.
-     */
-    public int paramIdx() {
-        return paramIdx;
-    }
-
-    /** {@inheritDoc} */
-    @Override public int hashCode() {
-        return partId ^ dataType ^ paramIdx ^
-            (cacheName == null ? 0 : cacheName.hashCode()) ^
-            (tableName == null ? 0 : tableName.hashCode());
-    }
-
-    /** {@inheritDoc} */
-    @SuppressWarnings("SimplifiableIfStatement")
-    @Override public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-
-        if (!(obj instanceof CacheQueryPartitionInfo))
-            return false;
-
-        CacheQueryPartitionInfo other = (CacheQueryPartitionInfo)obj;
-
-        if (partId >= 0)
-            return partId == other.partId;
-
-        if (other.cacheName == null || other.tableName == null)
-            return false;
-
-        return other.cacheName.equals(cacheName) &&
-            other.tableName.equals(tableName) &&
-            other.dataType == dataType &&
-            other.paramIdx == paramIdx;
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return S.toString(CacheQueryPartitionInfo.class, this);
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4ea3d4c3/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java
index a5f0ca2..526b37b 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheTwoStepQuery.java
@@ -20,6 +20,8 @@ package org.apache.ignite.internal.processors.cache.query;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+
+import org.apache.ignite.internal.processors.query.h2.affinity.PartitionInfo;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.internal.S;
 
@@ -63,7 +65,7 @@ public class GridCacheTwoStepQuery {
     private boolean local;
 
     /** */
-    private CacheQueryPartitionInfo[] derivedPartitions;
+    private PartitionInfo[] derivedPartitions;
 
     /** */
     private boolean mvccEnabled;
@@ -221,14 +223,14 @@ public class GridCacheTwoStepQuery {
     /**
      * @return Query derived partitions info.
      */
-    public CacheQueryPartitionInfo[] derivedPartitions() {
+    public PartitionInfo[] derivedPartitions() {
         return this.derivedPartitions;
     }
 
     /**
      * @param derivedPartitions Query derived partitions info.
      */
-    public void derivedPartitions(CacheQueryPartitionInfo[] derivedPartitions) {
+    public void derivedPartitions(PartitionInfo[] derivedPartitions) {
         this.derivedPartitions = derivedPartitions;
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/4ea3d4c3/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
index 283e503..0cb3a21 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
@@ -78,7 +78,7 @@ import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
-import org.apache.ignite.internal.processors.cache.query.CacheQueryPartitionInfo;
+import org.apache.ignite.internal.processors.query.h2.affinity.PartitionInfo;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryManager;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryMarshallable;
 import org.apache.ignite.internal.processors.cache.query.GridCacheTwoStepQuery;
@@ -3691,12 +3691,12 @@ public class IgniteH2Indexing implements GridQueryIndexing {
      * @return Partitions.
      * @throws IgniteCheckedException, If fails.
      */
-    private int[] calculateQueryPartitions(CacheQueryPartitionInfo[] partInfoList, Object[] params)
+    private int[] calculateQueryPartitions(PartitionInfo[] partInfoList, Object[] params)
         throws IgniteCheckedException {
 
         ArrayList<Integer> list = new ArrayList<>(partInfoList.length);
 
-        for (CacheQueryPartitionInfo partInfo: partInfoList) {
+        for (PartitionInfo partInfo: partInfoList) {
             int partId = (partInfo.partition() >= 0) ? partInfo.partition() :
                 bindPartitionInfoParameter(partInfo, params);
 
@@ -3729,7 +3729,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
      * @return Partition.
      * @throws IgniteCheckedException, If fails.
      */
-    private int bindPartitionInfoParameter(CacheQueryPartitionInfo partInfo, Object[] params)
+    private int bindPartitionInfoParameter(PartitionInfo partInfo, Object[] params)
         throws IgniteCheckedException {
         assert partInfo != null;
         assert partInfo.partition() < 0;

http://git-wip-us.apache.org/repos/asf/ignite/blob/4ea3d4c3/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/affinity/PartitionExtractor.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/affinity/PartitionExtractor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/affinity/PartitionExtractor.java
new file mode 100644
index 0000000..e76b211
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/affinity/PartitionExtractor.java
@@ -0,0 +1,384 @@
+/*
+ * 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.h2.affinity;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.cache.query.GridCacheSqlQuery;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
+import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlAst;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlColumn;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlConst;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlElement;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlOperation;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlOperationType;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlParameter;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQuery;
+import org.apache.ignite.internal.processors.query.h2.sql.GridSqlSelect;
+import org.h2.table.Column;
+import org.h2.table.IndexColumn;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.apache.ignite.internal.processors.query.h2.opt.GridH2KeyValueRowOnheap.DEFAULT_COLUMNS_COUNT;
+
+/**
+ * Utility class to extract partitions from the query.
+ */
+public class PartitionExtractor {
+    /**
+     * Ensures all given queries have non-empty derived partitions and merges them.
+     *
+     * @param queries Collection of queries.
+     * @return Derived partitions for all queries, or {@code null}.
+     */
+    public static PartitionInfo[] mergePartitionsFromMultipleQueries(List<GridCacheSqlQuery> queries) {
+        PartitionInfo[] result = null;
+
+        for (GridCacheSqlQuery qry : queries) {
+            PartitionInfo[] partInfo = (PartitionInfo[])qry.derivedPartitions();
+
+            if (partInfo == null) {
+                result = null;
+
+                break;
+            }
+
+            if (result == null)
+                result = partInfo;
+            else
+                result = mergePartitionInfo(result, partInfo);
+        }
+
+        return result;
+    }
+
+    /**
+     * Checks if given query contains expressions over key or affinity key
+     * that make it possible to run it only on a small isolated
+     * set of partitions.
+     *
+     * @param qry Query.
+     * @param ctx Kernal context.
+     * @return Array of partitions, or {@code null} if none identified
+     */
+    public static PartitionInfo[] derivePartitionsFromQuery(GridSqlQuery qry, GridKernalContext ctx)
+        throws IgniteCheckedException {
+
+        // No unions support yet.
+        if (!(qry instanceof GridSqlSelect))
+            return null;
+
+        GridSqlSelect select = (GridSqlSelect)qry;
+
+        // no joins support yet.
+        if (select.from() == null || select.from().size() != 1)
+            return null;
+
+        return extractPartition(select.where(), ctx);
+    }
+
+    /**
+     * @param el AST element to start with.
+     * @param ctx Kernal context.
+     * @return Array of partition info objects, or {@code null} if none identified
+     */
+    private static PartitionInfo[] extractPartition(GridSqlAst el, GridKernalContext ctx)
+        throws IgniteCheckedException {
+
+        if (!(el instanceof GridSqlOperation))
+            return null;
+
+        GridSqlOperation op = (GridSqlOperation)el;
+
+        switch (op.operationType()) {
+            case EQUAL: {
+                PartitionInfo partInfo = extractPartitionFromEquality(op, ctx);
+
+                if (partInfo != null)
+                    return new PartitionInfo[] { partInfo };
+
+                return null;
+            }
+
+            case AND: {
+                assert op.size() == 2;
+
+                PartitionInfo[] partsLeft = extractPartition(op.child(0), ctx);
+                PartitionInfo[] partsRight = extractPartition(op.child(1), ctx);
+
+                if (partsLeft != null && partsRight != null)
+                    return null; //kind of conflict (_key = 1) and (_key = 2)
+
+                if (partsLeft != null)
+                    return partsLeft;
+
+                if (partsRight != null)
+                    return partsRight;
+
+                return null;
+            }
+
+            case OR: {
+                assert op.size() == 2;
+
+                PartitionInfo[] partsLeft = extractPartition(op.child(0), ctx);
+                PartitionInfo[] partsRight = extractPartition(op.child(1), ctx);
+
+                if (partsLeft != null && partsRight != null)
+                    return mergePartitionInfo(partsLeft, partsRight);
+
+                return null;
+            }
+
+            case IN: {
+                // Operation should contain at least two children: left (column) and right (const or column).
+                if (op.size() < 2)
+                    return null;
+
+                // Left operand should be column.
+                GridSqlAst left = op.child();
+
+                GridSqlColumn leftCol;
+
+                if (left instanceof GridSqlColumn)
+                    leftCol = (GridSqlColumn)left;
+                else
+                    return null;
+
+                // Can work only with Ignite's tables.
+                if (!(leftCol.column().getTable() instanceof GridH2Table))
+                    return null;
+
+                PartitionInfo[] res = new PartitionInfo[op.size() - 1];
+
+                for (int i = 1; i < op.size(); i++) {
+                    GridSqlAst right = op.child(i);
+
+                    GridSqlConst rightConst;
+                    GridSqlParameter rightParam;
+
+                    if (right instanceof GridSqlConst) {
+                        rightConst = (GridSqlConst)right;
+                        rightParam = null;
+                    }
+                    else if (right instanceof GridSqlParameter) {
+                        rightConst = null;
+                        rightParam = (GridSqlParameter)right;
+                    }
+                    else
+                        // One of members of "IN" list is neither const, nor param, so we do no know it's partition.
+                        // As this is disjunction, not knowing partition of a single element leads to unknown partition
+                        // set globally. Hence, returning null.
+                        return null;
+
+                    PartitionInfo cur = getCacheQueryPartitionInfo(
+                        leftCol.column(),
+                        rightConst,
+                        rightParam,
+                        ctx
+                    );
+
+                    // Same thing as above: single unknown partition in disjunction defeats optimization.
+                    if (cur == null)
+                        return null;
+
+                    res[i - 1] = cur;
+                }
+
+                return res;
+            }
+
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Analyses the equality operation and extracts the partition if possible
+     *
+     * @param op AST equality operation.
+     * @param ctx Kernal Context.
+     * @return partition info, or {@code null} if none identified
+     */
+    private static PartitionInfo extractPartitionFromEquality(GridSqlOperation op, GridKernalContext ctx)
+        throws IgniteCheckedException {
+
+        assert op.operationType() == GridSqlOperationType.EQUAL;
+
+        GridSqlElement left = op.child(0);
+        GridSqlElement right = op.child(1);
+
+        GridSqlColumn leftCol;
+
+        if (left instanceof GridSqlColumn)
+            leftCol = (GridSqlColumn)left;
+        else
+            return null;
+
+        if (!(leftCol.column().getTable() instanceof GridH2Table))
+            return null;
+
+        GridSqlConst rightConst;
+        GridSqlParameter rightParam;
+
+        if (right instanceof GridSqlConst) {
+            rightConst = (GridSqlConst)right;
+            rightParam = null;
+        }
+        else if (right instanceof GridSqlParameter) {
+            rightConst = null;
+            rightParam = (GridSqlParameter)right;
+        }
+        else
+            return null;
+
+        return getCacheQueryPartitionInfo(leftCol.column(), rightConst, rightParam, ctx);
+    }
+
+    /**
+     * Merges two partition info arrays, removing duplicates
+     *
+     * @param a Partition info array.
+     * @param b Partition info array.
+     * @return Result.
+     */
+    private static PartitionInfo[] mergePartitionInfo(PartitionInfo[] a, PartitionInfo[] b) {
+        assert a != null;
+        assert b != null;
+
+        if (a.length == 1 && b.length == 1) {
+            if (a[0].equals(b[0]))
+                return new PartitionInfo[] { a[0] };
+
+            return new PartitionInfo[] { a[0], b[0] };
+        }
+
+        ArrayList<PartitionInfo> list = new ArrayList<>(a.length + b.length);
+
+        Collections.addAll(list, a);
+
+        for (PartitionInfo part: b) {
+            int i = 0;
+
+            while (i < list.size() && !list.get(i).equals(part))
+                i++;
+
+            if (i == list.size())
+                list.add(part);
+        }
+
+        PartitionInfo[] result = new PartitionInfo[list.size()];
+
+        for (int i = 0; i < list.size(); i++)
+            result[i] = list.get(i);
+
+        return result;
+    }
+
+    /**
+     * Extracts the partition if possible
+     * @param leftCol Column on the lsft side.
+     * @param rightConst Constant on the right side.
+     * @param rightParam Parameter on the right side.
+     * @param ctx Kernal Context.
+     * @return partition info, or {@code null} if none identified
+     * @throws IgniteCheckedException If failed.
+     */
+    @Nullable
+    private static PartitionInfo getCacheQueryPartitionInfo(
+        Column leftCol,
+        GridSqlConst rightConst,
+        GridSqlParameter rightParam,
+        GridKernalContext ctx
+    ) throws IgniteCheckedException {
+        assert leftCol != null;
+        assert leftCol.getTable() != null;
+        assert leftCol.getTable() instanceof GridH2Table;
+
+        GridH2Table tbl = (GridH2Table)leftCol.getTable();
+
+        if (!isAffinityKey(leftCol.getColumnId(), tbl))
+            return null;
+
+        GridH2RowDescriptor desc = tbl.rowDescriptor();
+
+        IndexColumn affKeyCol = tbl.getAffinityKeyColumn();
+
+        int colId = leftCol.getColumnId();
+
+        if ((affKeyCol == null || colId != affKeyCol.column.getColumnId()) && !desc.isKeyColumn(colId))
+            return null;
+
+        if (rightConst != null) {
+            int part = ctx.affinity().partition(tbl.cacheName(), rightConst.value().getObject());
+
+            return new PartitionInfo(
+                part,
+                null,
+                null,
+                -1,
+                -1
+            );
+        }
+        else if (rightParam != null) {
+            return new PartitionInfo(
+                -1,
+                tbl.cacheName(),
+                tbl.getName(),
+                leftCol.getType(),
+                rightParam.index()
+            );
+        }
+        else
+            return null;
+    }
+
+    /**
+     *
+     * @param colId Column ID to check
+     * @param tbl H2 Table
+     * @return is affinity key or not
+     */
+    private static boolean isAffinityKey(int colId, GridH2Table tbl) {
+        GridH2RowDescriptor desc = tbl.rowDescriptor();
+
+        if (desc.isKeyColumn(colId))
+            return true;
+
+        IndexColumn affKeyCol = tbl.getAffinityKeyColumn();
+
+        try {
+            return affKeyCol != null && colId >= DEFAULT_COLUMNS_COUNT && desc.isColumnKeyProperty(colId - DEFAULT_COLUMNS_COUNT) && colId == affKeyCol.column.getColumnId();
+        }
+        catch(IllegalStateException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Private constructor.
+     */
+    private PartitionExtractor() {
+        // No-op.
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4ea3d4c3/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/affinity/PartitionInfo.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/affinity/PartitionInfo.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/affinity/PartitionInfo.java
new file mode 100644
index 0000000..a6c8dba
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/affinity/PartitionInfo.java
@@ -0,0 +1,143 @@
+/*
+ * 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.h2.affinity;
+
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Holds the partition calculation info extracted from a query.
+ * The query may have several such items associated with it.
+ *
+ * The query may contain expressions containing key or affinity key.
+ * Such expressions can be used as hints to derive small isolated set
+ * of partitions the query needs to run on.
+ *
+ * In case expression contains constant (e.g. _key = 100), the partition
+ * can be calculated right away and saved into cache along with the query.
+ *
+ * In case expression has a parameter (e.g. _key = ?), the effective
+ * partition varies with each run of the query. Hence, instead of partition,
+ * one must store the info required to calculate partition.
+ *
+ * The given class holds the required info, so that effective partition
+ * can be calculated during query parameter binding.
+ */
+public class PartitionInfo {
+    /** */
+    private final int partId;
+
+    /** */
+    private final String cacheName;
+
+    /** */
+    private final String tableName;
+
+    /** */
+    private final int dataType;
+
+    /** */
+    private final int paramIdx;
+
+    /**
+     * @param partId Partition id, or -1 if parameter binding required.
+     * @param cacheName Cache name required for partition calculation.
+     * @param tableName Table name required for proper type conversion.
+     * @param dataType Required data type id for the query parameter.
+     * @param paramIdx Query parameter index required for partition calculation.
+     */
+    public PartitionInfo(int partId, String cacheName, String tableName, int dataType, int paramIdx) {
+        // In case partition is not known, both cacheName and tableName must be provided.
+        assert (partId >= 0) ^ ((cacheName != null) && (tableName != null));
+
+        this.partId = partId;
+        this.cacheName = cacheName;
+        this.tableName = tableName;
+        this.dataType = dataType;
+        this.paramIdx = paramIdx;
+    }
+
+    /**
+     * @return Partition id, or -1 if parameter binding is required to calculate partition.
+     */
+    public int partition() {
+        return partId;
+    }
+
+    /**
+     * @return Cache name required for partition calculation.
+     */
+    public String cacheName() {
+        return cacheName;
+    }
+
+    /**
+     * @return Table name.
+     */
+    public String tableName() {
+        return tableName;
+    }
+
+    /**
+     * @return Required data type for the query parameter.
+     */
+    public int dataType() {
+        return dataType;
+    }
+
+    /**
+     * @return Query parameter index required for partition calculation.
+     */
+    public int paramIdx() {
+        return paramIdx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return partId ^ dataType ^ paramIdx ^
+            (cacheName == null ? 0 : cacheName.hashCode()) ^
+            (tableName == null ? 0 : tableName.hashCode());
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("SimplifiableIfStatement")
+    @Override public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+
+        if (!(obj instanceof PartitionInfo))
+            return false;
+
+        PartitionInfo other = (PartitionInfo)obj;
+
+        if (partId >= 0)
+            return partId == other.partId;
+
+        if (other.cacheName == null || other.tableName == null)
+            return false;
+
+        return other.cacheName.equals(cacheName) &&
+            other.tableName.equals(tableName) &&
+            other.dataType == dataType &&
+            other.paramIdx == paramIdx;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(PartitionInfo.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4ea3d4c3/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
index 9987a0d..bbf10ff 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
@@ -35,7 +35,6 @@ import javax.cache.CacheException;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.internal.GridKernalContext;
-import org.apache.ignite.internal.processors.cache.query.CacheQueryPartitionInfo;
 import org.apache.ignite.internal.processors.cache.query.GridCacheSqlQuery;
 import org.apache.ignite.internal.processors.cache.query.GridCacheTwoStepQuery;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
@@ -43,8 +42,7 @@ import org.apache.ignite.internal.processors.cache.query.QueryTable;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.h2.H2Utils;
 import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
-import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
-import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
+import org.apache.ignite.internal.processors.query.h2.affinity.PartitionExtractor;
 import org.apache.ignite.internal.util.lang.GridTreePrinter;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.F;
@@ -53,13 +51,10 @@ import org.apache.ignite.internal.util.typedef.internal.S;
 import org.h2.command.Prepared;
 import org.h2.command.dml.Query;
 import org.h2.command.dml.SelectUnion;
-import org.h2.table.Column;
-import org.h2.table.IndexColumn;
 import org.h2.value.Value;
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.query.h2.opt.GridH2CollocationModel.isCollocated;
-import static org.apache.ignite.internal.processors.query.h2.opt.GridH2KeyValueRowOnheap.DEFAULT_COLUMNS_COUNT;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlConst.TRUE;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.AVG;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.CAST;
@@ -266,7 +261,7 @@ public class GridSqlQuerySplitter {
         twoStepQry.distributedJoins(distributedJoins);
 
         // all map queries must have non-empty derivedPartitions to use this feature.
-        twoStepQry.derivedPartitions(mergePartitionsFromMultipleQueries(twoStepQry.mapQueries()));
+        twoStepQry.derivedPartitions(PartitionExtractor.mergePartitionsFromMultipleQueries(twoStepQry.mapQueries()));
 
         twoStepQry.forUpdate(forUpdate);
 
@@ -1554,7 +1549,7 @@ public class GridSqlQuerySplitter {
         map.hasSubQueries(hasSubQueries);
 
         if (map.isPartitioned())
-            map.derivedPartitions(derivePartitionsFromQuery(mapQry, ctx));
+            map.derivedPartitions(PartitionExtractor.derivePartitionsFromQuery(mapQry, ctx));
 
         mapSqlQrys.add(map);
     }
@@ -2267,306 +2262,6 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * Checks if given query contains expressions over key or affinity key
-     * that make it possible to run it only on a small isolated
-     * set of partitions.
-     *
-     * @param qry Query.
-     * @param ctx Kernal context.
-     * @return Array of partitions, or {@code null} if none identified
-     */
-    private static CacheQueryPartitionInfo[] derivePartitionsFromQuery(GridSqlQuery qry, GridKernalContext ctx)
-        throws IgniteCheckedException {
-
-        if (!(qry instanceof GridSqlSelect))
-            return null;
-
-        GridSqlSelect select = (GridSqlSelect)qry;
-
-        // no joins support yet
-        if (select.from() == null || select.from().size() != 1)
-            return null;
-
-        return extractPartition(select.where(), ctx);
-    }
-
-    /**
-     * @param el AST element to start with.
-     * @param ctx Kernal context.
-     * @return Array of partition info objects, or {@code null} if none identified
-     */
-    private static CacheQueryPartitionInfo[] extractPartition(GridSqlAst el, GridKernalContext ctx)
-        throws IgniteCheckedException {
-
-        if (!(el instanceof GridSqlOperation))
-            return null;
-
-        GridSqlOperation op = (GridSqlOperation)el;
-
-        switch (op.operationType()) {
-            case EQUAL: {
-                CacheQueryPartitionInfo partInfo = extractPartitionFromEquality(op, ctx);
-
-                if (partInfo != null)
-                    return new CacheQueryPartitionInfo[] { partInfo };
-
-                return null;
-            }
-
-            case AND: {
-                assert op.size() == 2;
-
-                CacheQueryPartitionInfo[] partsLeft = extractPartition(op.child(0), ctx);
-                CacheQueryPartitionInfo[] partsRight = extractPartition(op.child(1), ctx);
-
-                if (partsLeft != null && partsRight != null)
-                    return null; //kind of conflict (_key = 1) and (_key = 2)
-
-                if (partsLeft != null)
-                    return partsLeft;
-
-                if (partsRight != null)
-                    return partsRight;
-
-                return null;
-            }
-
-            case OR: {
-                assert op.size() == 2;
-
-                CacheQueryPartitionInfo[] partsLeft = extractPartition(op.child(0), ctx);
-                CacheQueryPartitionInfo[] partsRight = extractPartition(op.child(1), ctx);
-
-                if (partsLeft != null && partsRight != null)
-                    return mergePartitionInfo(partsLeft, partsRight);
-
-                return null;
-            }
-
-            case IN: {
-                // Operation should contain at least two children: left (column) and right (const or column).
-                if (op.size() < 2)
-                    return null;
-
-                // Left operand should be column.
-                GridSqlAst left = op.child();
-
-                GridSqlColumn leftCol;
-
-                if (left instanceof GridSqlColumn)
-                    leftCol = (GridSqlColumn)left;
-                else
-                    return null;
-
-                // Can work only with Ignite's tables.
-                if (!(leftCol.column().getTable() instanceof GridH2Table))
-                    return null;
-
-                CacheQueryPartitionInfo[] res = new CacheQueryPartitionInfo[op.size() - 1];
-
-                for (int i = 1; i < op.size(); i++) {
-                    GridSqlAst right = op.child(i);
-
-                    GridSqlConst rightConst;
-                    GridSqlParameter rightParam;
-
-                    if (right instanceof GridSqlConst) {
-                        rightConst = (GridSqlConst)right;
-                        rightParam = null;
-                    }
-                    else if (right instanceof GridSqlParameter) {
-                        rightConst = null;
-                        rightParam = (GridSqlParameter)right;
-                    }
-                    else
-                        // One of members of "IN" list is neither const, nor param, so we do no know it's partition.
-                        // As this is disjunction, not knowing partition of a single element leads to unknown partition
-                        // set globally. Hence, returning null.
-                        return null;
-
-                    CacheQueryPartitionInfo cur = getCacheQueryPartitionInfo(
-                        leftCol.column(),
-                        rightConst,
-                        rightParam,
-                        ctx
-                    );
-
-                    // Same thing as above: single unknown partition in disjunction defeats optimization.
-                    if (cur == null)
-                        return null;
-
-                    res[i - 1] = cur;
-                }
-
-                return res;
-            }
-
-            default:
-                return null;
-        }
-    }
-
-    /**
-     * Analyses the equality operation and extracts the partition if possible
-     *
-     * @param op AST equality operation.
-     * @param ctx Kernal Context.
-     * @return partition info, or {@code null} if none identified
-     */
-    private static CacheQueryPartitionInfo extractPartitionFromEquality(GridSqlOperation op, GridKernalContext ctx)
-        throws IgniteCheckedException {
-
-        assert op.operationType() == GridSqlOperationType.EQUAL;
-
-        GridSqlElement left = op.child(0);
-        GridSqlElement right = op.child(1);
-
-        GridSqlColumn leftCol;
-
-        if (left instanceof GridSqlColumn)
-            leftCol = (GridSqlColumn)left;
-        else
-            return null;
-
-        if (!(leftCol.column().getTable() instanceof GridH2Table))
-            return null;
-
-        GridSqlConst rightConst;
-        GridSqlParameter rightParam;
-
-        if (right instanceof GridSqlConst) {
-            rightConst = (GridSqlConst)right;
-            rightParam = null;
-        }
-        else if (right instanceof GridSqlParameter) {
-            rightConst = null;
-            rightParam = (GridSqlParameter)right;
-        }
-        else
-            return null;
-
-        return getCacheQueryPartitionInfo(leftCol.column(), rightConst, rightParam, ctx);
-    }
-
-    /**
-     * Extracts the partition if possible
-     * @param leftCol Column on the lsft side.
-     * @param rightConst Constant on the right side.
-     * @param rightParam Parameter on the right side.
-     * @param ctx Kernal Context.
-     * @return partition info, or {@code null} if none identified
-     * @throws IgniteCheckedException If failed.
-     */
-    @Nullable private static CacheQueryPartitionInfo getCacheQueryPartitionInfo(
-        Column leftCol,
-        GridSqlConst rightConst,
-        GridSqlParameter rightParam,
-        GridKernalContext ctx
-    ) throws IgniteCheckedException {
-        assert leftCol != null;
-        assert leftCol.getTable() != null;
-        assert leftCol.getTable() instanceof GridH2Table;
-
-        GridH2Table tbl = (GridH2Table)leftCol.getTable();
-
-        if (!isAffinityKey(leftCol.getColumnId(), tbl))
-            return null;
-
-        GridH2RowDescriptor desc = tbl.rowDescriptor();
-
-        IndexColumn affKeyCol = tbl.getAffinityKeyColumn();
-
-        int colId = leftCol.getColumnId();
-
-        if ((affKeyCol == null || colId != affKeyCol.column.getColumnId()) && !desc.isKeyColumn(colId))
-            return null;
-
-        if (rightConst != null) {
-            int part = ctx.affinity().partition(tbl.cacheName(), rightConst.value().getObject());
-
-            return new CacheQueryPartitionInfo(
-                part,
-                null,
-                null,
-                -1,
-                -1
-            );
-        }
-        else if (rightParam != null) {
-            return new CacheQueryPartitionInfo(
-                -1,
-                tbl.cacheName(),
-                tbl.getName(),
-                leftCol.getType(),
-                rightParam.index()
-            );
-        }
-        else
-            return null;
-    }
-
-    /**
-     *
-     * @param colId Column ID to check
-     * @param tbl H2 Table
-     * @return is affinity key or not
-     */
-    private static boolean isAffinityKey(int colId, GridH2Table tbl) {
-        GridH2RowDescriptor desc = tbl.rowDescriptor();
-
-        if (desc.isKeyColumn(colId))
-            return true;
-
-        IndexColumn affKeyCol = tbl.getAffinityKeyColumn();
-
-        try {
-            return affKeyCol != null && colId >= DEFAULT_COLUMNS_COUNT && desc.isColumnKeyProperty(colId - DEFAULT_COLUMNS_COUNT) && colId == affKeyCol.column.getColumnId();
-        } catch(IllegalStateException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Merges two partition info arrays, removing duplicates
-     *
-     * @param a Partition info array.
-     * @param b Partition info array.
-     * @return Result.
-     */
-    private static CacheQueryPartitionInfo[] mergePartitionInfo(CacheQueryPartitionInfo[] a, CacheQueryPartitionInfo[] b) {
-        assert a != null;
-        assert b != null;
-
-        if (a.length == 1 && b.length == 1) {
-            if (a[0].equals(b[0]))
-                return new CacheQueryPartitionInfo[] { a[0] };
-
-            return new CacheQueryPartitionInfo[] { a[0], b[0] };
-        }
-
-        ArrayList<CacheQueryPartitionInfo> list = new ArrayList<>(a.length + b.length);
-
-        Collections.addAll(list, a);
-
-        for (CacheQueryPartitionInfo part: b) {
-            int i = 0;
-
-            while (i < list.size() && !list.get(i).equals(part))
-                i++;
-
-            if (i == list.size())
-                list.add(part);
-        }
-
-        CacheQueryPartitionInfo[] result = new CacheQueryPartitionInfo[list.size()];
-
-        for (int i = 0; i < list.size(); i++)
-            result[i] = list.get(i);
-
-        return result;
-    }
-
-    /**
      * @param root Root model.
      * @return Tree as a string.
      */
@@ -2602,33 +2297,6 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * Ensures all given queries have non-empty derived partitions and merges them.
-     *
-     * @param queries Collection of queries.
-     * @return Derived partitions for all queries, or {@code null}.
-     */
-    private static CacheQueryPartitionInfo[] mergePartitionsFromMultipleQueries(List<GridCacheSqlQuery> queries) {
-        CacheQueryPartitionInfo[] result = null;
-
-        for (GridCacheSqlQuery qry : queries) {
-            CacheQueryPartitionInfo[] partInfo = (CacheQueryPartitionInfo[])qry.derivedPartitions();
-
-            if (partInfo == null) {
-                result = null;
-
-                break;
-            }
-
-            if (result == null)
-                result = partInfo;
-            else
-                result = mergePartitionInfo(result, partInfo);
-        }
-
-        return result;
-    }
-
-    /**
      * Simplified tree-like model for a query.
      * - SELECT : All the children are list of joined query models in the FROM clause.
      * - UNION  : All the children are united left and right query models.