ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From voze...@apache.org
Subject [ignite] branch master updated: IGNITE-11083: SQL: Extracted query model from splitter. This closes #5928.
Date Mon, 28 Jan 2019 08:28:26 GMT
This is an automated email from the ASF dual-hosted git repository.

vozerov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 9511fbf  IGNITE-11083: SQL: Extracted query model from splitter. This closes #5928.
9511fbf is described below

commit 9511fbfff4d4ac67575ec5b9b8bc2d4065e87211
Author: devozerov <vozerov@gridgain.com>
AuthorDate: Mon Jan 28 11:28:17 2019 +0300

    IGNITE-11083: SQL: Extracted query model from splitter. This closes #5928.
---
 .../apache/ignite/internal/util/IgniteUtils.java   |    8 +
 .../internal/processors/query/h2/H2Utils.java      |   73 ++
 .../processors/query/h2/IgniteH2Indexing.java      |   51 +-
 .../processors/query/h2/dml/UpdatePlanBuilder.java |    4 +-
 .../processors/query/h2/sql/GridSqlQuery.java      |   11 +-
 .../query/h2/sql/GridSqlQueryParser.java           |   11 +
 .../query/h2/sql/GridSqlQuerySplitter.java         | 1203 ++++----------------
 .../processors/query/h2/sql/GridSqlSelect.java     |   26 +-
 .../processors/query/h2/sql/GridSqlUnion.java      |    4 +-
 .../query/h2/sql/SplitterAndCondition.java         |   87 ++
 .../query/h2/sql/SplitterQueryModel.java           |  410 +++++++
 .../query/h2/sql/SplitterQueryModelType.java       |   35 +
 .../processors/query/h2/sql/SplitterUtils.java     |  308 +++++
 .../query/h2/twostep/GridMapQueryExecutor.java     |    2 +-
 14 files changed, 1209 insertions(+), 1024 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
index b9e01cc..09bf683 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
@@ -105,6 +105,7 @@ import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -11190,4 +11191,11 @@ public abstract class IgniteUtils {
             totalWritten += written;
         }
     }
+
+    /**
+     * @return New identity hash set.
+     */
+    public static <X> Set<X> newIdentityHashSet() {
+        return Collections.newSetFromMap(new IdentityHashMap<>());
+    }
 }
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
index 2a5f33c..04820a65 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
@@ -19,10 +19,13 @@ package org.apache.ignite.internal.processors.query.h2;
 
 import java.lang.reflect.Constructor;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.sql.Connection;
 import java.sql.Date;
+import java.sql.PreparedStatement;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
+import java.sql.Types;
 import java.text.MessageFormat;
 import java.sql.Time;
 import java.sql.Timestamp;
@@ -58,6 +61,7 @@ import org.apache.ignite.internal.util.lang.GridCursor;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.SB;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.h2.command.Prepared;
 import org.h2.engine.Session;
 import org.h2.jdbc.JdbcConnection;
 import org.h2.result.Row;
@@ -84,6 +88,7 @@ import org.h2.value.ValueString;
 import org.h2.value.ValueTime;
 import org.h2.value.ValueTimestamp;
 import org.h2.value.ValueUuid;
+import org.jetbrains.annotations.Nullable;
 
 import javax.cache.CacheException;
 
@@ -91,6 +96,7 @@ import static org.apache.ignite.internal.processors.query.QueryUtils.KEY_FIELD_N
 import static org.apache.ignite.internal.processors.query.QueryUtils.VAL_FIELD_NAME;
 import static org.apache.ignite.internal.processors.query.QueryUtils.VER_FIELD_NAME;
 import static org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing.UPDATE_RESULT_META;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser.prepared;
 
 /**
  * H2 utility methods.
@@ -130,6 +136,7 @@ public class H2Utils {
      * @param col Column to find.
      * @return {@code true} If found.
      */
+    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
     public static boolean containsColumn(List<IndexColumn> cols, IndexColumn col) {
         for (int i = cols.size() - 1; i >= 0; i--) {
             if (equals(cols.get(i), col))
@@ -271,6 +278,7 @@ public class H2Utils {
      * @param idxName Index name.
      * @param cols Columns.
      */
+    @SuppressWarnings("ConstantConditions")
     public static GridH2IndexBase createSpatialIndex(GridH2Table tbl, String idxName, IndexColumn[] cols) {
         try {
             Class<?> cls = Class.forName(SPATIAL_IDX_CLS);
@@ -410,6 +418,7 @@ public class H2Utils {
      * @param tbl Table to check on not started cache.
      * @return {@code true} in case not started and has been started.
      */
+    @SuppressWarnings({"ConstantConditions", "UnusedReturnValue"})
     public static boolean checkAndStartNotStartedCache(GridKernalContext ctx, GridH2Table tbl) {
         if (tbl != null && tbl.isCacheLazy()) {
             String cacheName = tbl.cacheInfo().config().getName();
@@ -660,4 +669,68 @@ public class H2Utils {
     public static GridH2RetryException retryException(String msg) {
         return new GridH2RetryException(msg);
     }
+
+    /**
+     * Binds parameters to prepared statement.
+     *
+     * @param stmt Prepared statement.
+     * @param params Parameters collection.
+     * @throws IgniteCheckedException If failed.
+     */
+    public static void bindParameters(PreparedStatement stmt, @Nullable Collection<Object> params)
+        throws IgniteCheckedException {
+        if (!F.isEmpty(params)) {
+            int idx = 1;
+
+            for (Object arg : params)
+                bindObject(stmt, idx++, arg);
+        }
+    }
+
+    /**
+     * Binds object to prepared statement.
+     *
+     * @param stmt SQL statement.
+     * @param idx Index.
+     * @param obj Value to store.
+     * @throws IgniteCheckedException If failed.
+     */
+    private static void bindObject(PreparedStatement stmt, int idx, @Nullable Object obj) throws IgniteCheckedException {
+        try {
+            if (obj == null)
+                stmt.setNull(idx, Types.VARCHAR);
+            else if (obj instanceof BigInteger)
+                stmt.setObject(idx, obj, Types.JAVA_OBJECT);
+            else if (obj instanceof BigDecimal)
+                stmt.setObject(idx, obj, Types.DECIMAL);
+            else
+                stmt.setObject(idx, obj);
+        }
+        catch (SQLException e) {
+            throw new IgniteCheckedException("Failed to bind parameter [idx=" + idx + ", obj=" + obj + ", stmt=" +
+                stmt + ']', e);
+        }
+    }
+
+    /**
+     * Get optimized prepared statement.
+     *
+     * @param c Connection.
+     * @param qry Parsed query.
+     * @param params Query parameters.
+     * @param enforceJoinOrder Enforce join order.
+     * @return Optimized prepared command.
+     * @throws SQLException If failed.
+     * @throws IgniteCheckedException If failed.
+     */
+    public static Prepared optimize(Connection c, String qry, Object[] params, boolean distributedJoins,
+        boolean enforceJoinOrder) throws SQLException, IgniteCheckedException {
+        setupConnection(c, distributedJoins, enforceJoinOrder);
+
+        try (PreparedStatement s = c.prepareStatement(qry)) {
+            bindParameters(s, F.asList(params));
+
+            return prepared(s);
+        }
+    }
 }
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 a1b7ff9..d4598e6 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
@@ -17,13 +17,10 @@
 
 package org.apache.ignite.internal.processors.query.h2;
 
-import java.math.BigDecimal;
-import java.math.BigInteger;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.Types;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -313,31 +310,6 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         return prepareStatementAndCaches(conn, sql);
     }
 
-    /**
-     * Binds object to prepared statement.
-     *
-     * @param stmt SQL statement.
-     * @param idx Index.
-     * @param obj Value to store.
-     * @throws IgniteCheckedException If failed.
-     */
-    private void bindObject(PreparedStatement stmt, int idx, @Nullable Object obj) throws IgniteCheckedException {
-        try {
-            if (obj == null)
-                stmt.setNull(idx, Types.VARCHAR);
-            else if (obj instanceof BigInteger)
-                stmt.setObject(idx, obj, Types.JAVA_OBJECT);
-            else if (obj instanceof BigDecimal)
-                stmt.setObject(idx, obj, Types.DECIMAL);
-            else
-                stmt.setObject(idx, obj);
-        }
-        catch (SQLException e) {
-            throw new IgniteCheckedException("Failed to bind parameter [idx=" + idx + ", obj=" + obj + ", stmt=" +
-                stmt + ']', e);
-        }
-    }
-
     /** {@inheritDoc} */
     @Override public void store(GridCacheContext cctx,
         GridQueryTypeDescriptor type,
@@ -849,7 +821,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
             throw new IgniteCheckedException("Failed to parse SQL query: " + sql, e);
         }
 
-        bindParameters(stmt, params);
+        H2Utils.bindParameters(stmt, params);
 
         return stmt;
     }
@@ -1016,23 +988,6 @@ public class IgniteH2Indexing implements GridQueryIndexing {
         }
     }
 
-    /**
-     * Binds parameters to prepared statement.
-     *
-     * @param stmt Prepared statement.
-     * @param params Parameters collection.
-     * @throws IgniteCheckedException If failed.
-     */
-    public void bindParameters(PreparedStatement stmt,
-        @Nullable Collection<Object> params) throws IgniteCheckedException {
-        if (!F.isEmpty(params)) {
-            int idx = 1;
-
-            for (Object arg : params)
-                bindObject(stmt, idx++, arg);
-        }
-    }
-
     /** {@inheritDoc} */
     @Override public FieldsQueryCursor<List<?>> queryLocalSqlFields(String schemaName, SqlFieldsQuery qry,
         final boolean keepBinary, IndexingQueryFilter filter, GridQueryCancel cancel,
@@ -1837,7 +1792,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
 
        if (prepared.isQuery()) {
             try {
-                bindParameters(stmt, F.asList(args));
+                H2Utils.bindParameters(stmt, F.asList(args));
             }
             catch (IgniteCheckedException e) {
                 U.closeQuiet(stmt);
@@ -1958,7 +1913,7 @@ public class IgniteH2Indexing implements GridQueryIndexing {
             qry.isCollocated(),
             qry.isDistributedJoins(),
             qry.isEnforceJoinOrder(),
-            this);
+            partExtractor);
 
         List<Integer> cacheIds = collectCacheIds(null, res);
 
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
index 0c55f54..221da20 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
@@ -922,7 +922,7 @@ public final class UpdatePlanBuilder {
         try {
             // Get a new prepared statement for derived select query.
             try (PreparedStatement stmt = conn.prepareStatement(selectQry)) {
-                idx.bindParameters(stmt, F.asList(fieldsQry.getArgs()));
+                H2Utils.bindParameters(stmt, F.asList(fieldsQry.getArgs()));
 
                 GridCacheTwoStepQuery qry = GridSqlQuerySplitter.split(conn,
                     GridSqlQueryParser.prepared(stmt),
@@ -930,7 +930,7 @@ public final class UpdatePlanBuilder {
                     fieldsQry.isCollocated(),
                     fieldsQry.isDistributedJoins(),
                     fieldsQry.isEnforceJoinOrder(),
-                    idx);
+                    idx.partitionExtractor());
 
                 boolean distributed = qry.skipMergeTable() &&  qry.mapQueries().size() == 1 &&
                     !qry.mapQueries().get(0).hasSubQueries();
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuery.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuery.java
index 9511866..e49e182 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuery.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuery.java
@@ -138,7 +138,7 @@ public abstract class GridSqlQuery extends GridSqlStatement implements GridSqlAs
     /**
      * @return If this is a simple query with no conditions, expressions, sorting, etc...
      */
-    public abstract boolean simpleQuery();
+    public abstract boolean skipMergeTable();
 
     /**
      * @param buff Statement builder.
@@ -188,4 +188,13 @@ public abstract class GridSqlQuery extends GridSqlStatement implements GridSqlAs
         if (offset != null)
             buff.append(" OFFSET ").append(StringUtils.unEnclose(offset.getSQL()));
     }
+
+    /**
+     * Whether offset or limit exists.
+     *
+     * @return {@code true} If we have OFFSET LIMIT.
+     */
+    public boolean hasOffsetLimit() {
+        return limit() != null || offset() != null;
+    }
 }
\ No newline at end of file
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 34e223c..f66a289 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
@@ -1793,6 +1793,17 @@ public class GridSqlQueryParser {
     }
 
     /**
+     * Parse query.
+     *
+     * @param prepared Prepared statement.
+     * @param useOptimizedSubqry Whether to user optimized subquery.
+     * @return Parsed query.
+     */
+    public static GridSqlQuery parseQuery(Prepared prepared, boolean useOptimizedSubqry) {
+        return (GridSqlQuery)new GridSqlQueryParser(useOptimizedSubqry).parse(prepared);
+    }
+
+    /**
      * @param stmt Prepared statement.
      * @return Parsed AST.
      */
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 818777c..741f859 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
@@ -18,11 +18,9 @@
 package org.apache.ignite.internal.processors.query.h2.sql;
 
 import java.sql.Connection;
-import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.BitSet;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -40,18 +38,11 @@ import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 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.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;
-import org.apache.ignite.internal.util.typedef.X;
-import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
 import org.h2.command.Prepared;
 import org.h2.command.dml.Query;
-import org.h2.command.dml.SelectUnion;
-import org.h2.value.Value;
-import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.query.h2.opt.join.CollocationModel.isCollocated;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlConst.TRUE;
@@ -59,8 +50,6 @@ import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunction
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.CAST;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.COUNT;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.GROUP_CONCAT;
-import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.MAX;
-import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.MIN;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.SUM;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlJoin.LEFT_TABLE_CHILD;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlJoin.ON_CHILD;
@@ -68,12 +57,9 @@ import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlJoin.RIG
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlPlaceholder.EMPTY;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlQuery.LIMIT_CHILD;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlQuery.OFFSET_CHILD;
-import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser.prepared;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlSelect.FROM_CHILD;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlSelect.WHERE_CHILD;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlSelect.childIndexForColumn;
-import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlUnion.LEFT_CHILD;
-import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlUnion.RIGHT_CHILD;
 
 /**
  * Splits a single SQL query into two step map-reduce query.
@@ -108,31 +94,31 @@ public class GridSqlQuerySplitter {
     private int splitId = -1; // The first one will be 0.
 
     /** Query tables. */
-    private Set<QueryTable> tbls = new HashSet<>();
+    private final Set<QueryTable> tbls = new HashSet<>();
 
     /** */
-    private Set<String> pushedDownCols = new HashSet<>();
+    private final Set<String> pushedDownCols = new HashSet<>();
 
     /** */
-    private boolean rdcQrySimple;
+    private boolean skipMergeTbl;
 
     /** */
     private GridCacheSqlQuery rdcSqlQry;
 
     /** */
-    private List<GridCacheSqlQuery> mapSqlQrys = new ArrayList<>();
+    private final List<GridCacheSqlQuery> mapSqlQrys = new ArrayList<>();
 
     /** */
     private Object[] params;
 
     /** */
-    private boolean collocatedGrpBy;
+    private final boolean collocatedGrpBy;
 
     /** */
-    private boolean distributedJoins;
+    private final boolean distributedJoins;
 
     /** */
-    private IdentityHashMap<GridSqlAst, GridSqlAlias> uniqueFromAliases = new IdentityHashMap<>();
+    private final IdentityHashMap<GridSqlAst, GridSqlAlias> uniqueFromAliases = new IdentityHashMap<>();
 
     /** Partition extractor. */
     private final PartitionExtractor extractor;
@@ -183,7 +169,7 @@ public class GridSqlQuerySplitter {
      * @param collocatedGrpBy Whether the query has collocated GROUP BY keys.
      * @param distributedJoins If distributed joins enabled.
      * @param enforceJoinOrder Enforce join order.
-     * @param h2 Indexing.
+     * @param partExtractor Partition extractor.
      * @return Two step query.
      * @throws SQLException If failed.
      * @throws IgniteCheckedException If failed.
@@ -195,37 +181,35 @@ public class GridSqlQuerySplitter {
         boolean collocatedGrpBy,
         boolean distributedJoins,
         boolean enforceJoinOrder,
-        IgniteH2Indexing h2
+        PartitionExtractor partExtractor
     ) throws SQLException, IgniteCheckedException {
         if (params == null)
             params = GridCacheSqlQuery.EMPTY_PARAMS;
 
         // Here we will just do initial query parsing. Do not use optimized
         // subqueries because we do not have unique FROM aliases yet.
-        GridSqlQuery qry = parse(prepared, false);
+        GridSqlQuery qry = GridSqlQueryParser.parseQuery(prepared, false);
 
         String originalSql = prepared.getSQL();
 
-//        debug("ORIGINAL", originalSql);
-
         final boolean explain = qry.explain();
 
         qry.explain(false);
 
         GridSqlQuerySplitter splitter = new GridSqlQuerySplitter(params, collocatedGrpBy, distributedJoins,
-            h2.partitionExtractor());
+            partExtractor);
 
         // Normalization will generate unique aliases for all the table filters in FROM.
         // Also it will collect all tables and schemas from the query.
         splitter.normalizeQuery(qry);
 
-//        debug("NORMALIZED", qry.getSQL());
-
         // Here we will have correct normalized AST with optimized join order.
         // The distributedJoins parameter is ignored because it is not relevant for
         // the REDUCE query optimization.
-        qry = parse(optimize(h2, conn, qry.getSQL(), params, false, enforceJoinOrder),
-            true);
+        qry = GridSqlQueryParser.parseQuery(
+            H2Utils.optimize(conn, qry.getSQL(), params, false, enforceJoinOrder),
+            true
+        );
 
         boolean forUpdate = GridSqlQueryParser.isForUpdateQuery(prepared);
 
@@ -242,12 +226,12 @@ public class GridSqlQuerySplitter {
             boolean allCollocated = true;
 
             for (GridCacheSqlQuery mapSqlQry : splitter.mapSqlQrys) {
-                Prepared prepared0 = optimize(h2, conn, mapSqlQry.query(), mapSqlQry.parameters(params),
+                Prepared prepared0 = H2Utils.optimize(conn, mapSqlQry.query(), mapSqlQry.parameters(params),
                     true, enforceJoinOrder);
 
                 allCollocated &= isCollocated((Query)prepared0);
 
-                mapSqlQry.query(parse(prepared0, true).getSQL());
+                mapSqlQry.query(GridSqlQueryParser.parseQuery(prepared0, true).getSQL());
             }
 
             // We do not need distributed joins if all MAP queries are collocated.
@@ -263,7 +247,7 @@ public class GridSqlQuerySplitter {
         for (GridCacheSqlQuery mapSqlQry : splitter.mapSqlQrys)
             twoStepQry.addMapQuery(mapSqlQry);
 
-        twoStepQry.skipMergeTable(splitter.rdcQrySimple);
+        twoStepQry.skipMergeTable(splitter.skipMergeTbl);
         twoStepQry.explain(explain);
         twoStepQry.distributedJoins(distributedJoins);
 
@@ -281,44 +265,38 @@ public class GridSqlQuerySplitter {
      */
     private void splitQuery(GridSqlQuery qry, boolean forUpdate) throws IgniteCheckedException {
         // Create a fake parent AST element for the query to allow replacing the query in the parent by split.
-        GridSqlSubquery fakeQryPrnt = new GridSqlSubquery(qry);
+        GridSqlSubquery fakeQryParent = new GridSqlSubquery(qry);
 
         // Fake parent query model. We need it just for convenience because buildQueryModel needs parent model.
-        QueryModel fakeQrymPrnt = new QueryModel(null, null, -1, null);
+        SplitterQueryModel fakeModelParent = new SplitterQueryModel(null, null, -1, null);
 
         // Build a simplified query model. We need it because navigation over the original AST is too complex.
-        buildQueryModel(fakeQrymPrnt, fakeQryPrnt, 0, null);
+        fakeModelParent.buildQueryModel(fakeQryParent, 0, null);
 
-        assert fakeQrymPrnt.size() == 1;
+        assert fakeModelParent.childModelsCount() == 1;
 
         // Get the built query model from the fake parent.
-        QueryModel qrym = fakeQrymPrnt.get(0);
+        SplitterQueryModel model = fakeModelParent.childModel(0);
 
         // Setup the needed information for split.
-        analyzeQueryModel(qrym);
-
-//        debug("ANALYZED", printQueryModel(qrym));
+        model.analyzeQueryModel(collocatedGrpBy);
 
         // If we have child queries to split, then go hard way.
-        if (qrym.needSplitChild) {
+        if (model.needSplitChild()) {
             // All the siblings to selects we are going to split must be also wrapped into subqueries.
-            pushDownQueryModel(qrym);
-
-//            debug("PUSHED_DOWN", printQueryModel(qrym));
+            pushDownQueryModel(model);
 
             // Need to make all the joined subqueries to be ordered by join conditions.
-            setupMergeJoinSorting(qrym);
-
-//            debug("SETUP_MERGE_JOIN", printQueryModel(qrym));
+            setupMergeJoinSorting(model);
         }
-        else if (!qrym.needSplit)  // Just split the top level query.
-            setNeedSplit(qrym);
+        else if (!model.needSplit())  // Just split the top level query.
+            model.forceSplit();
 
         // Split the query model into multiple map queries and a single reduce query.
-        splitQueryModel(qrym);
+        splitQueryModel(model);
 
         // Get back the updated query from the fake parent. It will be our reduce query.
-        qry = fakeQryPrnt.subquery();
+        qry = fakeQryParent.subquery();
 
         // Reset SELECT FOR UPDATE flag for reduce query.
         if (forUpdate) {
@@ -331,64 +309,36 @@ public class GridSqlQuerySplitter {
 
         String rdcQry = qry.getSQL();
 
-        checkNoDataTablesInReduceQuery(qry, rdcQry);
+        SplitterUtils.checkNoDataTablesInReduceQuery(qry, rdcQry);
 
         // Setup a resulting reduce query.
         rdcSqlQry = new GridCacheSqlQuery(rdcQry);
-        rdcQrySimple = qry.simpleQuery();
+
+        skipMergeTbl = qry.skipMergeTable();
 
         setupParameters(rdcSqlQry, qry, params);
     }
 
     /**
-     * @param qrym Query model.
+     * @param model Query model.
      */
-    private void pushDownQueryModel(QueryModel qrym) {
-        if (qrym.type == Type.UNION) {
-            assert qrym.needSplitChild; // Otherwise we should not get here.
+    private void pushDownQueryModel(SplitterQueryModel model) {
+        if (model.type() == SplitterQueryModelType.UNION) {
+            assert model.needSplitChild(); // Otherwise we should not get here.
 
-            for (int i = 0; i < qrym.size(); i++)
-                pushDownQueryModel(qrym.get(i));
+            for (int i = 0; i < model.childModelsCount(); i++)
+                pushDownQueryModel(model.childModel(i));
         }
-        else if (qrym.type == Type.SELECT) {
+        else if (model.type() == SplitterQueryModelType.SELECT) {
             // If we already need to split, then no need to push down here.
-            if (!qrym.needSplit) {
-                assert qrym.needSplitChild; // Otherwise we should not get here.
+            if (!model.needSplit()) {
+                assert model.needSplitChild(); // Otherwise we should not get here.
 
-                pushDownQueryModelSelect(qrym);
+                pushDownQueryModelSelect(model);
             }
         }
         else
-            throw new IllegalStateException("Type: " + qrym.type);
-    }
-
-    /**
-     * @param label Label.
-     * @param info Info.
-     * @deprecated Must be commented out.
-     */
-    @Deprecated
-    @SuppressWarnings("unused")
-    private static void debug(String label, String info) {
-        X.println();
-        X.println("  == " + label + " == ");
-        X.println(info);
-        X.println("  ======================= ");
-    }
-
-    /**
-     * @param ast Reduce query AST.
-     * @param rdcQry Reduce query string.
-     */
-    private static void checkNoDataTablesInReduceQuery(GridSqlAst ast, String rdcQry) {
-        if (ast instanceof GridSqlTable) {
-            if (((GridSqlTable)ast).dataTable() != null)
-                throw new IgniteException("Failed to generate REDUCE query. Data table found: " + ast.getSQL() + " \n" + rdcQry);
-        }
-        else {
-            for (int i = 0; i < ast.size(); i++)
-                checkNoDataTablesInReduceQuery(ast.child(i), rdcQry);
-        }
+            throw new IllegalStateException("Type: " + model.type());
     }
 
     /**
@@ -408,60 +358,41 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * @param from FROM clause.
-     * @return {@code true} If contains LEFT OUTER JOIN.
+     * @param model Query model for the SELECT.
      */
-    private static boolean hasLeftJoin(GridSqlAst from) {
-        while (from instanceof GridSqlJoin) {
-            GridSqlJoin join = (GridSqlJoin)from;
-
-            assert !(join.rightTable() instanceof GridSqlJoin);
+    private void pushDownQueryModelSelect(SplitterQueryModel model) {
+        assert model.type() == SplitterQueryModelType.SELECT : model.type();
 
-            if (join.isLeftOuter())
-                return true;
-
-            from = join.leftTable();
-        }
-
-        return false;
-    }
-
-    /**
-     * @param qrym Query model for the SELECT.
-     */
-    private void pushDownQueryModelSelect(QueryModel qrym) {
-        assert qrym.type == Type.SELECT: qrym.type;
-
-        boolean hasLeftJoin = hasLeftJoin(qrym.<GridSqlSelect>ast().from());
+        boolean hasLeftJoin = SplitterUtils.hasLeftJoin(model.<GridSqlSelect>ast().from());
 
         int begin = -1;
 
         // Here we iterate over joined FROM table filters.
-        // !!! qrym.size() can change, never assign it to a variable.
-        for (int i = 0; i < qrym.size(); i++) {
-            QueryModel child = qrym.get(i);
+        // !!! model.size() can change, never assign it to a variable.
+        for (int i = 0; i < model.childModelsCount(); i++) {
+            SplitterQueryModel child = model.childModel(i);
 
             boolean hasPushedDownCol = false;
 
             // It is either splittable subquery (it must remain in REDUCE query)
             // or left join condition with pushed down columns (this condition
             // can not be pushed down into a wrap query).
-            if ((child.isQuery() && (child.needSplitChild || child.needSplit)) ||
+            if ((child.isQuery() && (child.needSplitChild() || child.needSplit())) ||
                 // We always must be at the right side of the join here to push down
                 // range on the left side. If there are no LEFT JOINs in the SELECT, then
                 // we will never have ON conditions, they are getting moved to WHERE clause.
-                (hasPushedDownCol = (hasLeftJoin && i != 0 && hasPushedDownColumn(findJoin(qrym, i).on())))) {
+                (hasPushedDownCol = (hasLeftJoin && i != 0 && hasPushedDownColumn(model.findJoin(i).on())))) {
                 // Handle a single table push down case.
                 if (hasPushedDownCol && begin == -1)
                     begin = i - 1;
 
                 // Push down the currently collected range.
                 if (begin != -1) {
-                    pushDownQueryModelRange(qrym, begin, i - 1);
+                    pushDownQueryModelRange(model, begin, i - 1);
 
-                    i = begin + 1; // We've modified qrym by this range push down, need to adjust counter.
+                    i = begin + 1; // We've modified model by this range push down, need to adjust counter.
 
-                    assert qrym.get(i) == child; // Adjustment check: we have to return to the same point.
+                    assert model.childModel(i) == child; // Adjustment check: we have to return to the same point.
 
                     // Reset range begin: in case of pushed down column we can assume current child as
                     // as new begin (because it is not a splittable subquery), otherwise reset begin
@@ -469,7 +400,7 @@ public class GridSqlQuerySplitter {
                     begin = hasPushedDownCol ? i : -1;
                 }
 
-                if (child.needSplitChild)
+                if (child.needSplitChild())
                     pushDownQueryModel(child);
             }
             // It is a table or a function or a subquery that we do not need to split or split child.
@@ -480,44 +411,44 @@ public class GridSqlQuerySplitter {
 
         // Push down the remaining range.
         if (begin != -1)
-            pushDownQueryModelRange(qrym, begin, qrym.size() - 1);
+            pushDownQueryModelRange(model, begin, model.childModelsCount() - 1);
     }
 
     /**
-     * @param qrym Query model.
+     * @param model Query model.
      */
-    private void setupMergeJoinSorting(QueryModel qrym) {
-        if (qrym.type == Type.UNION) {
-            for (int i = 0; i < qrym.size(); i++)
-                setupMergeJoinSorting(qrym.get(i));
+    private void setupMergeJoinSorting(SplitterQueryModel model) {
+        if (model.type() == SplitterQueryModelType.UNION) {
+            for (int i = 0; i < model.childModelsCount(); i++)
+                setupMergeJoinSorting(model.childModel(i));
         }
-        else if (qrym.type == Type.SELECT) {
-            if (!qrym.needSplit) {
+        else if (model.type() == SplitterQueryModelType.SELECT) {
+            if (!model.needSplit()) {
                 boolean needSplitChild = false;
 
-                for (int i = 0; i < qrym.size(); i++) {
-                    QueryModel child = qrym.get(i);
+                for (int i = 0; i < model.childModelsCount(); i++) {
+                    SplitterQueryModel child = model.childModel(i);
 
-                    assert child.isQuery() : child.type;
+                    assert child.isQuery() : child.type();
 
-                    if (child.needSplit)
+                    if (child.needSplit())
                         needSplitChild = true;
                     else
                         setupMergeJoinSorting(child); // Go deeper.
                 }
 
-                if (needSplitChild && qrym.size() > 1)
-                    setupMergeJoinSortingSelect(qrym); // Setup merge join hierarchy for the SELECT.
+                if (needSplitChild && model.childModelsCount() > 1)
+                    setupMergeJoinSortingSelect(model); // Setup merge join hierarchy for the SELECT.
             }
         }
         else
-            throw new IllegalStateException("Type: " + qrym.type);
+            throw new IllegalStateException("Type: " + model.type());
     }
 
     /**
-     * @param qrym Query model.
+     * @param model Query model.
      */
-    private void setupMergeJoinSortingSelect(QueryModel qrym) {
+    private void setupMergeJoinSortingSelect(SplitterQueryModel model) {
         // After pushing down we can have the following variants:
         //  - splittable SELECT
         //  - subquery with splittable child (including UNION)
@@ -526,30 +457,30 @@ public class GridSqlQuerySplitter {
         // the splittable SELECT like in "push down" and
         // setup sorting for all of them by joined fields.
 
-        for (int i = 1; i < qrym.size(); i++) {
-            QueryModel child = qrym.get(i);
+        for (int i = 1; i < model.childModelsCount(); i++) {
+            SplitterQueryModel child = model.childModel(i);
 
-            if (child.needSplit) {
+            if (child.needSplit()) {
                 if (i > 1) {
                     // If we have multiple joined non-splittable subqueries before, then
                     // push down them into a single subquery.
-                    doPushDownQueryModelRange(qrym, 0, i - 1, false);
+                    doPushDownQueryModelRange(model, 0, i - 1, false);
 
                     i = 1;
 
-                    assert qrym.get(i) == child; // We must remain at the same position.
+                    assert model.childModel(i) == child; // We must remain at the same position.
                 }
 
-                injectSortingFirstJoin(qrym);
+                injectSortingFirstJoin(model);
             }
         }
     }
 
     /**
-     * @param qrym Query model.
+     * @param model Query model.
      */
-    private void injectSortingFirstJoin(QueryModel qrym) {
-        GridSqlJoin join = findJoin(qrym, 0);
+    private void injectSortingFirstJoin(SplitterQueryModel model) {
+        GridSqlJoin join = model.findJoin(0);
 
         // We are at the beginning, thus left and right AST must be children of the same join AST.
         //     join2
@@ -561,17 +492,17 @@ public class GridSqlQuerySplitter {
         GridSqlAlias rightTbl = (GridSqlAlias)join.rightTable();
 
         // Collect all AND conditions.
-        List<AndCondition> andConditions = new ArrayList<>();
+        List<SplitterAndCondition> andConditions = new ArrayList<>();
 
-        collectAndConditions(andConditions, join, ON_CHILD);
-        collectAndConditions(andConditions, qrym.ast(), WHERE_CHILD);
+        SplitterAndCondition.collectAndConditions(andConditions, join, ON_CHILD);
+        SplitterAndCondition.collectAndConditions(andConditions, model.ast(), WHERE_CHILD);
 
         // Collect all the JOIN condition columns for sorting.
         List<GridSqlColumn> leftOrder = new ArrayList<>();
         List<GridSqlColumn> rightOrder = new ArrayList<>();
 
         for (int i = 0; i < andConditions.size(); i++) {
-            AndCondition and = andConditions.get(i);
+            SplitterAndCondition and = andConditions.get(i);
             GridSqlOperation op = and.ast();
 
             if (op.operationType() == GridSqlOperationType.EQUAL) {
@@ -660,95 +591,53 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * @param qrym Query model.
+     * @param model Query model.
      * @param begin The first child model in range to push down.
      * @param end The last child model in range to push down.
      */
-    private void pushDownQueryModelRange(QueryModel qrym, int begin, int end) {
+    private void pushDownQueryModelRange(SplitterQueryModel model, int begin, int end) {
         assert end >= begin;
 
-        if (begin == end && qrym.get(end).isQuery()) {
+        if (begin == end && model.childModel(end).isQuery()) {
             // Simple case when we have a single subquery to push down, just mark it to be splittable.
-            setNeedSplit(qrym.get(end));
+            model.childModel(end).forceSplit();
         }
         else {
             // Here we have to generate a subquery for all the joined elements and
             // and mark that subquery as splittable.
-            doPushDownQueryModelRange(qrym, begin, end, true);
+            doPushDownQueryModelRange(model, begin, end, true);
         }
-
-//        debug("PUSH_DOWN_PARTIAL", printQueryModel(qrym));
     }
 
     /**
-     * @param qrym Query model.
-     */
-    private static void setNeedSplit(QueryModel qrym) {
-        if (qrym.type == Type.SELECT) {
-            assert !qrym.needSplitChild;
-
-            qrym.needSplit = true;
-        }
-        else if (qrym.type == Type.UNION) {
-            qrym.needSplitChild = true;
-
-            // Mark all the selects in the UNION to be splittable.
-            for (QueryModel s : qrym) {
-                assert s.type == Type.SELECT: s.type;
-
-                s.needSplit = true;
-            }
-        }
-        else
-            throw new IllegalStateException("Type: " + qrym.type);
-    }
-
-    /**
-     * @param select Select.
-     * @param aliases Table aliases in FROM.
-     */
-    private static void collectFromAliases(GridSqlSelect select, Set<GridSqlAlias> aliases) {
-        GridSqlAst from = select.from();
-
-        if (from == null)
-            return;
-
-        while (from instanceof GridSqlJoin) {
-            GridSqlElement right = ((GridSqlJoin)from).rightTable();
-
-            aliases.add((GridSqlAlias)right);
-
-            from = ((GridSqlJoin)from).leftTable();
-        }
-
-        aliases.add((GridSqlAlias)from);
-    }
-
-    /**
-     * @param qrym Query model.
+     * @param model Query model.
      * @param begin The first child model in range to push down.
      * @param end The last child model in range to push down.
      * @param needSplit If we will need to split the created subquery model.
      */
-    private void doPushDownQueryModelRange(QueryModel qrym, int begin, int end, boolean needSplit) {
+    private void doPushDownQueryModelRange(SplitterQueryModel model, int begin, int end, boolean needSplit) {
         // Create wrap query where we will push all the needed joined elements, columns and conditions.
         GridSqlSelect wrapSelect = new GridSqlSelect();
         GridSqlSubquery wrapSubqry = new GridSqlSubquery(wrapSelect);
-        GridSqlAlias wrapAlias = alias(nextUniqueTableAlias(null), wrapSubqry);
+        GridSqlAlias wrapAlias = SplitterUtils.alias(nextUniqueTableAlias(null), wrapSubqry);
 
-        QueryModel wrapQrym = new QueryModel(Type.SELECT, wrapSubqry, 0, wrapAlias);
-
-        wrapQrym.needSplit = needSplit;
+        SplitterQueryModel wrapModel = new SplitterQueryModel(
+            SplitterQueryModelType.SELECT,
+            wrapSubqry,
+            0,
+            wrapAlias,
+            needSplit
+        );
 
         // Prepare all the prerequisites.
-        GridSqlSelect select = qrym.ast();
+        GridSqlSelect select = model.ast();
 
-        Set<GridSqlAlias> tblAliases = newIdentityHashSet();
+        Set<GridSqlAlias> tblAliases = U.newIdentityHashSet();
         Map<String,GridSqlAlias> cols = new HashMap<>();
 
         // Collect all the tables for push down.
         for (int i = begin; i <= end; i++) {
-            GridSqlAlias uniqueTblAlias = qrym.get(i).uniqueAlias;
+            GridSqlAlias uniqueTblAlias = model.childModel(i).uniqueAlias();
 
             assert uniqueTblAlias != null: select.getSQL();
 
@@ -762,7 +651,7 @@ public class GridSqlQuerySplitter {
         pushDownWhereConditions(tblAliases, cols, wrapAlias, select);
 
         // Push down to a subquery all the JOIN elements and process ON conditions.
-        pushDownJoins(tblAliases, cols, qrym, begin, end, wrapAlias);
+        pushDownJoins(tblAliases, cols, model, begin, end, wrapAlias);
 
         // Add all the collected columns to the wrap query.
         for (GridSqlAlias col : cols.values())
@@ -771,24 +660,13 @@ public class GridSqlQuerySplitter {
         // Adjust query models to a new AST structure.
 
         // Move pushed down child models to the newly created model.
-        for (int i = begin; i <= end; i++) {
-            QueryModel child = qrym.get(i);
-
-            wrapQrym.add(child);
-        }
-
-        // Replace the first child model with the created one.
-        qrym.set(begin, wrapQrym);
-
-        // Drop others.
-        for (int x = begin + 1, i = x; i <= end; i++)
-            qrym.remove(x);
+        model.moveChildModelsToWrapModel(wrapModel, begin, end);
     }
 
     /**
      * @param tblAliases Table aliases for push down.
      * @param cols Columns with generated aliases.
-     * @param qrym Query model.
+     * @param model Query model.
      * @param begin The first child model in range to push down.
      * @param end The last child model in range to push down.
      * @param wrapAlias Alias of the wrap query.
@@ -796,17 +674,17 @@ public class GridSqlQuerySplitter {
     private void pushDownJoins(
         Set<GridSqlAlias> tblAliases,
         Map<String,GridSqlAlias> cols,
-        QueryModel qrym,
+        SplitterQueryModel model,
         int begin,
         int end,
         GridSqlAlias wrapAlias
     ) {
         // Get the original SELECT.
-        GridSqlSelect select = qrym.ast();
+        GridSqlSelect select = model.ast();
 
         GridSqlSelect wrapSelect = GridSqlAlias.<GridSqlSubquery>unwrap(wrapAlias).subquery();
 
-        final int last = qrym.size() - 1;
+        final int last = model.childModelsCount() - 1;
 
         if (begin == end) {
             //  Simple case when we have to push down only a single table and no joins.
@@ -832,9 +710,9 @@ public class GridSqlQuerySplitter {
 
             //  W: T2
 
-            GridSqlJoin endJoin = findJoin(qrym, end);
+            GridSqlJoin endJoin = model.findJoin(end);
 
-            wrapSelect.from(qrym.get(end).uniqueAlias);
+            wrapSelect.from(model.childModel(end).uniqueAlias());
             endJoin.child(end == 0 ? LEFT_TABLE_CHILD : RIGHT_TABLE_CHILD, wrapAlias);
         }
         else if (end == last) {
@@ -868,9 +746,9 @@ public class GridSqlQuerySplitter {
             //           / \
             //         T2   T3
 
-            GridSqlJoin beginJoin = findJoin(qrym, begin);
-            GridSqlJoin afterBeginJoin = findJoin(qrym, begin + 1);
-            GridSqlJoin endJoin = findJoin(qrym, end);
+            GridSqlJoin beginJoin = model.findJoin(begin);
+            GridSqlJoin afterBeginJoin = model.findJoin(begin + 1);
+            GridSqlJoin endJoin = model.findJoin(end);
 
             wrapSelect.from(endJoin);
             afterBeginJoin.leftTable(beginJoin.rightTable());
@@ -900,8 +778,8 @@ public class GridSqlQuerySplitter {
             //         / \
             //       T0   T1
 
-            GridSqlJoin endJoin = findJoin(qrym, end);
-            GridSqlJoin afterEndJoin = findJoin(qrym, end + 1);
+            GridSqlJoin endJoin = model.findJoin(end);
+            GridSqlJoin afterEndJoin = model.findJoin(end + 1);
 
             wrapSelect.from(endJoin);
             afterEndJoin.leftTable(wrapAlias);
@@ -929,10 +807,10 @@ public class GridSqlQuerySplitter {
             //           / \
             //         T1   T2
 
-            GridSqlJoin beginJoin = findJoin(qrym, begin);
-            GridSqlJoin afterBeginJoin = findJoin(qrym, begin + 1);
-            GridSqlJoin endJoin = findJoin(qrym, end);
-            GridSqlJoin afterEndJoin = findJoin(qrym, end + 1);
+            GridSqlJoin beginJoin = model.findJoin(begin);
+            GridSqlJoin afterBeginJoin = model.findJoin(begin + 1);
+            GridSqlJoin endJoin = model.findJoin(end);
+            GridSqlJoin afterEndJoin = model.findJoin(end + 1);
 
             wrapSelect.from(endJoin);
             afterEndJoin.leftTable(beginJoin);
@@ -978,13 +856,13 @@ public class GridSqlQuerySplitter {
                 else
                     alias = EXPR_ALIAS_PREFIX + i;
 
-                expr = alias(alias, expr);
+                expr = SplitterUtils.alias(alias, expr);
 
                 select.setColumn(i, expr);
             }
 
-            if (isAllRelatedToTables(tblAliases, GridSqlQuerySplitter.<GridSqlAlias>newIdentityHashSet(), expr)
-                && !hasAggregates(expr)) {
+            if (isAllRelatedToTables(tblAliases, U.newIdentityHashSet(), expr)
+                && !SplitterUtils.hasAggregates(expr)) {
                 // Push down the whole expression.
                 pushDownColumn(tblAliases, cols, wrapAlias, expr, 0);
             }
@@ -999,20 +877,20 @@ public class GridSqlQuerySplitter {
      * @param tblAliases Table aliases to push down.
      * @param cols Columns with generated aliases.
      * @param wrapAlias Alias of the wrap query.
-     * @param prnt Parent expression.
+     * @param parent Parent expression.
      * @param childIdx Child index.
      */
     private void pushDownColumnsInExpression(
         Set<GridSqlAlias> tblAliases,
         Map<String,GridSqlAlias> cols,
         GridSqlAlias wrapAlias,
-        GridSqlAst prnt,
+        GridSqlAst parent,
         int childIdx
     ) {
-        GridSqlAst child = prnt.child(childIdx);
+        GridSqlAst child = parent.child(childIdx);
 
         if (child instanceof GridSqlColumn)
-            pushDownColumn(tblAliases, cols, wrapAlias, prnt, childIdx);
+            pushDownColumn(tblAliases, cols, wrapAlias, parent, childIdx);
         else {
             for (int i = 0; i < child.size(); i++)
                 pushDownColumnsInExpression(tblAliases, cols, wrapAlias, child, i);
@@ -1023,22 +901,22 @@ public class GridSqlQuerySplitter {
      * @param tblAliases Table aliases for push down.
      * @param cols Columns with generated aliases.
      * @param wrapAlias Alias of the wrap query.
-     * @param prnt Parent element.
+     * @param parent Parent element.
      * @param childIdx Child index.
      */
     private void pushDownColumn(
         Set<GridSqlAlias> tblAliases,
         Map<String,GridSqlAlias> cols,
         GridSqlAlias wrapAlias,
-        GridSqlAst prnt,
+        GridSqlAst parent,
         int childIdx
     ) {
-        GridSqlAst expr = prnt.child(childIdx);
+        GridSqlAst expr = parent.child(childIdx);
 
         String uniqueColAlias;
 
         if (expr instanceof GridSqlColumn) {
-            GridSqlColumn col = prnt.child(childIdx);
+            GridSqlColumn col = parent.child(childIdx);
 
             // It must always be unique table alias.
             GridSqlAlias tblAlias = (GridSqlAlias)col.expressionInFrom();
@@ -1051,14 +929,14 @@ public class GridSqlQuerySplitter {
             uniqueColAlias = uniquePushDownColumnAlias(col);
         }
         else {
-            uniqueColAlias = EXPR_ALIAS_PREFIX + nextExprAliasId++ + "__" + ((GridSqlAlias)prnt).alias();
+            uniqueColAlias = EXPR_ALIAS_PREFIX + nextExprAliasId++ + "__" + ((GridSqlAlias)parent).alias();
         }
 
         GridSqlType resType = expr.resultType();
         GridSqlAlias colAlias = cols.get(uniqueColAlias);
 
         if (colAlias == null) {
-            colAlias = alias(uniqueColAlias, expr);
+            colAlias = SplitterUtils.alias(uniqueColAlias, expr);
 
             // We have this map to avoid column duplicates in wrap query.
             cols.put(uniqueColAlias, colAlias);
@@ -1066,12 +944,12 @@ public class GridSqlQuerySplitter {
             pushedDownCols.add(uniqueColAlias);
         }
 
-        GridSqlColumn col = column(uniqueColAlias);
-        // col.tableAlias(wrapAlias.alias());
+        GridSqlColumn col = SplitterUtils.column(uniqueColAlias);
+
         col.expressionInFrom(wrapAlias);
         col.resultType(resType);
 
-        prnt.child(childIdx, col);
+        parent.child(childIdx, col);
     }
 
     /**
@@ -1118,44 +996,36 @@ public class GridSqlQuerySplitter {
 
         GridSqlSelect wrapSelect = GridSqlAlias.<GridSqlSubquery>unwrap(wrapAlias).subquery();
 
-        List<AndCondition> andConditions = new ArrayList<>();
+        List<SplitterAndCondition> andConditions = new ArrayList<>();
 
-        collectAndConditions(andConditions, select, WHERE_CHILD);
+        SplitterAndCondition.collectAndConditions(andConditions, select, WHERE_CHILD);
 
         for (int i = 0; i < andConditions.size(); i++) {
-            AndCondition c = andConditions.get(i);
+            SplitterAndCondition c = andConditions.get(i);
             GridSqlAst condition = c.ast();
 
-            if (isAllRelatedToTables(tblAliases,
-                GridSqlQuerySplitter.<GridSqlAlias>newIdentityHashSet(),
-                condition)) {
-                if (!isTrue(condition)) {
+            if (isAllRelatedToTables(tblAliases, U.newIdentityHashSet(), condition)) {
+                if (!SplitterUtils.isTrue(condition)) {
                     // Replace the original condition with `true` and move it to the wrap query.
-                    c.prnt.child(c.childIdx, TRUE);
+                    c.parent().child(c.childIndex(), TRUE);
+
                     wrapSelect.whereAnd(condition);
                 }
             }
             else
-                pushDownColumnsInExpression(tblAliases, cols, wrapAlias, c.prnt, c.childIdx);
+                pushDownColumnsInExpression(tblAliases, cols, wrapAlias, c.parent(), c.childIndex());
         }
     }
 
     /**
-     * @param expr Expression.
-     * @return {@code true} If this expression represents a constant value `TRUE`.
-     */
-    private static boolean isTrue(GridSqlAst expr) {
-        return expr instanceof GridSqlConst && ((GridSqlConst)expr).value() == TRUE.value();
-    }
-
-    /**
      * @param tblAliases Table aliases for push down.
      * @param locSubQryTblAliases Local subquery tables.
      * @param ast AST.
      * @return {@code true} If all the columns in the given expression are related to the given tables.
      */
-    @SuppressWarnings("SuspiciousMethodCalls")
-    private boolean isAllRelatedToTables(Set<GridSqlAlias> tblAliases, Set<GridSqlAlias> locSubQryTblAliases, GridSqlAst ast) {
+    @SuppressWarnings({"SuspiciousMethodCalls", "RedundantIfStatement"})
+    private static boolean isAllRelatedToTables(Set<GridSqlAlias> tblAliases, Set<GridSqlAlias> locSubQryTblAliases,
+        GridSqlAst ast) {
         if (ast instanceof GridSqlColumn) {
             GridSqlColumn col = (GridSqlColumn)ast;
 
@@ -1168,7 +1038,7 @@ public class GridSqlQuerySplitter {
         else {
             // If it is a subquery, collect all the table aliases to ignore them later.
             if (ast instanceof GridSqlSelect)
-                collectFromAliases((GridSqlSelect)ast, locSubQryTblAliases);
+                ((GridSqlSelect)ast).collectFromAliases(locSubQryTblAliases);
 
             for (int i = 0; i < ast.size(); i++) {
                 if (!isAllRelatedToTables(tblAliases, locSubQryTblAliases, ast.child(i)))
@@ -1180,246 +1050,40 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * @param andConditions Conditions in AND.
-     * @param prnt Parent Parent element.
-     * @param childIdx Child index.
+     * @param model Query model.
      */
-    private void collectAndConditions(List<AndCondition> andConditions, GridSqlAst prnt, int childIdx) {
-        GridSqlAst child = prnt.child(childIdx);
-
-        if (child instanceof GridSqlOperation) {
-            GridSqlOperation op = (GridSqlOperation)child;
-
-            if (op.operationType() == GridSqlOperationType.AND) {
-                collectAndConditions(andConditions, op, 0);
-                collectAndConditions(andConditions, op, 1);
-
-                return;
-            }
-        }
-
-        if (!isTrue(child))
-            andConditions.add(new AndCondition(prnt, childIdx));
-    }
-
-    /**
-     * @return New identity hash set.
-     */
-    private static <X> Set<X> newIdentityHashSet() {
-        return Collections.newSetFromMap(new IdentityHashMap<X,Boolean>());
-    }
-
-    /**
-     * @param qrym Query model for the SELECT.
-     * @param idx Index of the child model for which we need to find a respective JOIN element.
-     * @return JOIN.
-     */
-    private static GridSqlJoin findJoin(QueryModel qrym, int idx) {
-        assert qrym.type == Type.SELECT: qrym.type;
-        assert qrym.size() > 1; // It must be at least one join with at least two child tables.
-        assert idx < qrym.size(): idx;
-
-        //     join2
-        //      / \
-        //   join1 \
-        //    / \   \
-        //  T0   T1  T2
-
-        // If we need to find JOIN for T0, it is the same as for T1.
-        if (idx == 0)
-            idx = 1;
-
-        GridSqlJoin join = (GridSqlJoin)qrym.<GridSqlSelect>ast().from();
-
-        for (int i = qrym.size() - 1; i > idx; i--)
-            join = (GridSqlJoin)join.leftTable();
-
-        return join;
-    }
-
-    /**
-     * @param qrym Query model.
-     */
-    private void splitQueryModel(QueryModel qrym) throws IgniteCheckedException {
-        switch (qrym.type) {
+    private void splitQueryModel(SplitterQueryModel model) throws IgniteCheckedException {
+        switch (model.type()) {
             case SELECT:
-                if (qrym.needSplit) {
-                    splitSelect(qrym.prnt, qrym.childIdx);
+                if (model.needSplit()) {
+                    splitSelect(model.parent(), model.childIndex());
 
                     break;
                 }
 
                 // Intentional fallthrough to go deeper.
             case UNION:
-                for (int i = 0; i < qrym.size(); i++)
-                    splitQueryModel(qrym.get(i));
+                for (int i = 0; i < model.childModelsCount(); i++)
+                    splitQueryModel(model.childModel(i));
 
                 break;
 
             default:
-                throw new IllegalStateException("Type: " + qrym.type);
-        }
-    }
-
-    /**
-     * @param qrym Query model.
-     */
-    private void analyzeQueryModel(QueryModel qrym) {
-        if (!qrym.isQuery())
-            return;
-
-        // Process all the children at the beginning: depth first analysis.
-        for (int i = 0; i < qrym.size(); i++) {
-            QueryModel child = qrym.get(i);
-
-            analyzeQueryModel(child);
-
-            // Pull up information about the splitting child.
-            if (child.needSplit || child.needSplitChild)
-                qrym.needSplitChild = true; // We have a child to split.
-        }
-
-        if (qrym.type == Type.SELECT) {
-            // We may need to split the SELECT only if it has no splittable children,
-            // because only the downmost child can be split, the parents will be the part of
-            // the reduce query.
-            if (!qrym.needSplitChild)
-                qrym.needSplit = needSplitSelect(qrym.<GridSqlSelect>ast()); // Only SELECT can have this flag in true.
-        }
-        else if (qrym.type == Type.UNION) {
-            // If it is not a UNION ALL, then we have to split because otherwise we can produce duplicates or
-            // wrong results for UNION DISTINCT, EXCEPT, INTERSECT queries.
-            if (!qrym.needSplitChild && (!qrym.unionAll || hasOffsetLimit(qrym.<GridSqlUnion>ast())))
-                qrym.needSplitChild = true;
-
-            // If we have to split some child SELECT in this UNION, then we have to enforce split
-            // for all other united selects, because this UNION has to be a part of the reduce query,
-            // thus each SELECT needs to have a reduce part for this UNION, but the whole SELECT can not
-            // be a reduce part (usually).
-            if (qrym.needSplitChild) {
-                for (int i = 0; i < qrym.size(); i++) {
-                    QueryModel child = qrym.get(i);
-
-                    assert child.type == Type.SELECT : child.type;
-
-                    if (!child.needSplitChild && !child.needSplit)
-                        child.needSplit = true;
-                }
-            }
-        }
-        else
-            throw new IllegalStateException("Type: " + qrym.type);
-    }
-
-    /**
-     * @param prntModel Parent model.
-     * @param prnt Parent AST element.
-     * @param childIdx Child index.
-     * @param uniqueAlias Unique parent alias of the current element.
-     */
-    private void buildQueryModel(QueryModel prntModel, GridSqlAst prnt, int childIdx, GridSqlAlias uniqueAlias) {
-        GridSqlAst child = prnt.child(childIdx);
-
-        assert child != null;
-
-        if (child instanceof GridSqlSelect) {
-            QueryModel model = new QueryModel(Type.SELECT, prnt, childIdx, uniqueAlias);
-
-            prntModel.add(model);
-
-            buildQueryModel(model, child, FROM_CHILD, null);
-        }
-        else if (child instanceof GridSqlUnion) {
-            QueryModel model;
-
-            // We will collect all the selects into a single UNION model.
-            if (prntModel.type == Type.UNION)
-                model = prntModel;
-            else {
-                model = new QueryModel(Type.UNION, prnt, childIdx, uniqueAlias);
-
-                prntModel.add(model);
-            }
-
-            if (((GridSqlUnion)child).unionType() != SelectUnion.UnionType.UNION_ALL)
-                model.unionAll = false;
-
-            buildQueryModel(model, child, LEFT_CHILD, null);
-            buildQueryModel(model, child, RIGHT_CHILD, null);
-        }
-        else {
-            // Here we must be in FROM clause of the SELECT.
-            assert prntModel.type == Type.SELECT : prntModel.type;
-
-            if (child instanceof GridSqlAlias)
-                buildQueryModel(prntModel, child, 0, (GridSqlAlias)child);
-            else if (child instanceof GridSqlJoin) {
-                buildQueryModel(prntModel, child, LEFT_TABLE_CHILD, uniqueAlias);
-                buildQueryModel(prntModel, child, RIGHT_TABLE_CHILD, uniqueAlias);
-            }
-            else {
-                // Here we must be inside of generated unique alias for FROM clause element.
-                assert prnt == uniqueAlias: prnt.getClass();
-
-                if (child instanceof GridSqlTable)
-                    prntModel.add(new QueryModel(Type.TABLE, prnt, childIdx, uniqueAlias));
-                else if (child instanceof GridSqlSubquery)
-                    buildQueryModel(prntModel, child, 0, uniqueAlias);
-                else if (child instanceof GridSqlFunction)
-                    prntModel.add(new QueryModel(Type.FUNCTION, prnt, childIdx, uniqueAlias));
-                else
-                    throw new IllegalStateException("Unknown child type: " + child.getClass());
-            }
-        }
-    }
-
-    /**
-     * @param qry Query.
-     * @return {@code true} If we have OFFSET LIMIT.
-     */
-    private static boolean hasOffsetLimit(GridSqlQuery qry) {
-        return qry.limit() != null || qry.offset() != null;
-    }
-
-    /**
-     * @param select Select to check.
-     * @return {@code true} If we need to split this select.
-     */
-    private boolean needSplitSelect(GridSqlSelect select) {
-        if (select.distinct())
-            return true;
-
-        if (hasOffsetLimit(select))
-            return true;
-
-        if (collocatedGrpBy)
-            return false;
-
-        if (select.groupColumns() != null)
-            return true;
-
-        for (int i = 0; i < select.allColumns(); i++) {
-            if (hasAggregates(select.column(i)))
-                return true;
+                throw new IllegalStateException("Type: " + model.type());
         }
-
-        return false;
     }
 
     /**
      * !!! Notice that here we will modify the original query AST in this method.
      *
-     * @param prnt Parent AST element.
+     * @param parent Parent AST element.
      * @param childIdx Index of child select.
      */
-    private void splitSelect(
-        final GridSqlAst prnt,
-        final int childIdx
-    ) throws IgniteCheckedException {
+    private void splitSelect(GridSqlAst parent, int childIdx) throws IgniteCheckedException {
         if (++splitId > 99)
             throw new CacheException("Too complex query to process.");
 
-        final GridSqlSelect mapQry = prnt.child(childIdx);
+        final GridSqlSelect mapQry = parent.child(childIdx);
 
         final int visibleCols = mapQry.visibleColumns();
 
@@ -1435,7 +1099,7 @@ public class GridSqlQuerySplitter {
 
         if (!collocatedGrpBy) {
             for (int i = 0, len = mapExps.size(); i < len; i++)
-                distinctAggregateFound |= hasDistinctAggregates(mapExps.get(i));
+                distinctAggregateFound |= SplitterUtils.hasDistinctAggregates(mapExps.get(i));
         }
 
         boolean aggregateFound = distinctAggregateFound;
@@ -1463,7 +1127,7 @@ public class GridSqlQuerySplitter {
             rdcQry.addColumn(rdcExps.get(i), false);
 
         for (int i = rdcExps.size(); i < mapExps.size(); i++)  // Add all extra map columns as invisible reduce columns.
-            rdcQry.addColumn(column(((GridSqlAlias)mapExps.get(i)).alias()), false);
+            rdcQry.addColumn(SplitterUtils.column(((GridSqlAlias)mapExps.get(i)).alias()), false);
 
         // -- FROM WHERE: do nothing
 
@@ -1519,7 +1183,7 @@ public class GridSqlQuerySplitter {
             rdcQry.offset(mapQry.offset());
 
             if (mapQry.limit() != null) // LIMIT off + lim
-                mapQry.limit(op(GridSqlOperationType.PLUS, mapQry.offset(), mapQry.limit()));
+                mapQry.limit(SplitterUtils.op(GridSqlOperationType.PLUS, mapQry.offset(), mapQry.limit()));
 
             mapQry.offset(null);
         }
@@ -1530,30 +1194,18 @@ public class GridSqlQuerySplitter {
             rdcQry.distinct(true);
         }
 
-        // -- SUB-QUERIES
-        boolean hasSubQueries = hasSubQueries(mapQry.where()) || hasSubQueries(mapQry.from());
-
-        if (!hasSubQueries) {
-            for (int i = 0; i < mapQry.columns(false).size(); i++) {
-                if (hasSubQueries(mapQry.column(i))) {
-                    hasSubQueries = true;
-
-                    break;
-                }
-            }
-        }
-
         // Replace the given select with generated reduce query in the parent.
-        prnt.child(childIdx, rdcQry);
+        parent.child(childIdx, rdcQry);
 
         // Setup resulting map query.
         GridCacheSqlQuery map = new GridCacheSqlQuery(mapQry.getSQL());
 
         setupParameters(map, mapQry, params);
+
         map.columns(collectColumns(mapExps));
         map.sortColumns(mapQry.sort());
-        map.partitioned(hasPartitionedTables(mapQry));
-        map.hasSubQueries(hasSubQueries);
+        map.partitioned(SplitterUtils.hasPartitionedTables(mapQry));
+        map.hasSubQueries(SplitterUtils.hasSubQueries(mapQry));
 
         if (map.isPartitioned() && !distributedJoins)
             map.derivedPartitions(extractor.extract(mapQry));
@@ -1562,45 +1214,6 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * @param ast Map query AST.
-     * @return {@code true} If the given AST has partitioned tables.
-     */
-    private static boolean hasPartitionedTables(GridSqlAst ast) {
-        if (ast instanceof GridSqlTable) {
-            if (((GridSqlTable)ast).dataTable() != null)
-                return ((GridSqlTable)ast).dataTable().isPartitioned();
-            else
-                return false;
-        }
-
-        for (int i = 0; i < ast.size(); i++) {
-            if (hasPartitionedTables(ast.child(i)))
-                return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * @param ast Map query AST.
-     * @return {@code true} If the given AST has sub-queries.
-     */
-    private boolean hasSubQueries(GridSqlAst ast) {
-        if (ast == null)
-            return false;
-
-        if (ast instanceof GridSqlSubquery)
-            return true;
-
-        for (int childIdx = 0; childIdx < ast.size(); childIdx++) {
-            if (hasSubQueries(ast.child(childIdx)))
-                return true;
-        }
-
-        return false;
-    }
-
-    /**
      * @param sqlQry Query.
      * @param qryAst Select AST.
      * @param params All parameters.
@@ -1608,7 +1221,7 @@ public class GridSqlQuerySplitter {
     private static void setupParameters(GridCacheSqlQuery sqlQry, GridSqlQuery qryAst, Object[] params) {
         TreeSet<Integer> paramIdxs = new TreeSet<>();
 
-        findParamsQuery(qryAst, params, paramIdxs);
+        SplitterUtils.findParamsQuery(qryAst, params, paramIdxs);
 
         int[] paramIdxsArr = new int[paramIdxs.size()];
 
@@ -1652,42 +1265,6 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * @param prepared Prepared command.
-     * @param useOptimizedSubqry Use optimized subqueries order for table filters.
-     * @return Parsed SQL query AST.
-     */
-    private static GridSqlQuery parse(Prepared prepared, boolean useOptimizedSubqry) {
-        return (GridSqlQuery)new GridSqlQueryParser(useOptimizedSubqry).parse(prepared);
-    }
-
-    /**
-     * @param h2 Indexing.
-     * @param c Connection.
-     * @param qry Parsed query.
-     * @param params Query parameters.
-     * @param enforceJoinOrder Enforce join order.
-     * @return Optimized prepared command.
-     * @throws SQLException If failed.
-     * @throws IgniteCheckedException If failed.
-     */
-    private static Prepared optimize(
-        IgniteH2Indexing h2,
-        Connection c,
-        String qry,
-        Object[] params,
-        boolean distributedJoins,
-        boolean enforceJoinOrder
-    ) throws SQLException, IgniteCheckedException {
-        H2Utils.setupConnection(c, distributedJoins, enforceJoinOrder);
-
-        try (PreparedStatement s = c.prepareStatement(qry)) {
-            h2.bindParameters(s, F.asList(params));
-
-            return prepared(s);
-        }
-    }
-
-    /**
      * @param qry Query.
      */
     private void normalizeQuery(GridSqlQuery qry) {
@@ -1718,11 +1295,11 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * @param prnt Table parent element.
+     * @param parent Table parent element.
      * @param childIdx Child index for the table or alias containing the table.
      */
-    private void generateUniqueAlias(GridSqlAst prnt, int childIdx) {
-        GridSqlAst child = prnt.child(childIdx);
+    private void generateUniqueAlias(GridSqlAst parent, int childIdx) {
+        GridSqlAst child = parent.child(childIdx);
         GridSqlAst tbl = GridSqlAlias.unwrap(child);
 
         assert tbl instanceof GridSqlTable || tbl instanceof GridSqlSubquery ||
@@ -1735,7 +1312,7 @@ public class GridSqlQuerySplitter {
         uniqueFromAliases.put(tbl, uniqueAliasAst);
 
         // Replace the child in the parent.
-        prnt.child(childIdx, uniqueAliasAst);
+        parent.child(childIdx, uniqueAliasAst);
     }
 
     /**
@@ -1751,14 +1328,13 @@ public class GridSqlQuerySplitter {
         return uniqueAlias;
     }
 
-
     /**
-     * @param prnt Parent element.
+     * @param parent Parent element.
      * @param childIdx Child index.
-     * @param prntAlias If the parent is {@link GridSqlAlias}.
+     * @param parentAlias If the parent is {@link GridSqlAlias}.
      */
-    private void normalizeFrom(GridSqlAst prnt, int childIdx, boolean prntAlias) {
-        GridSqlElement from = prnt.child(childIdx);
+    private void normalizeFrom(GridSqlAst parent, int childIdx, boolean parentAlias) {
+        GridSqlElement from = parent.child(childIdx);
 
         if (from instanceof GridSqlTable) {
             GridSqlTable tbl = (GridSqlTable)from;
@@ -1769,13 +1345,13 @@ public class GridSqlQuerySplitter {
             tbls.add(new QueryTable(schemaName, tblName));
 
             // In case of alias parent we need to replace the alias itself.
-            if (!prntAlias)
-                generateUniqueAlias(prnt, childIdx);
+            if (!parentAlias)
+                generateUniqueAlias(parent, childIdx);
         }
         else if (from instanceof GridSqlAlias) {
             // Replace current alias with generated unique alias.
             normalizeFrom(from, 0, true);
-            generateUniqueAlias(prnt, childIdx);
+            generateUniqueAlias(parent, childIdx);
         }
         else if (from instanceof GridSqlSubquery) {
             // We do not need to wrap simple functional subqueries into filtering function,
@@ -1783,7 +1359,7 @@ public class GridSqlQuerySplitter {
             // and functions we have to filter explicitly as well.
             normalizeQuery(((GridSqlSubquery)from).subquery());
 
-            if (!prntAlias) // H2 generates aliases for subqueries in FROM clause.
+            if (!parentAlias) // H2 generates aliases for subqueries in FROM clause.
                 throw new IllegalStateException("No alias for subquery: " + from.getSQL());
         }
         else if (from instanceof GridSqlJoin) {
@@ -1796,20 +1372,20 @@ public class GridSqlQuerySplitter {
         }
         else if (from instanceof GridSqlFunction) {
             // In case of alias parent we need to replace the alias itself.
-            if (!prntAlias)
-                generateUniqueAlias(prnt, childIdx);
+            if (!parentAlias)
+                generateUniqueAlias(parent, childIdx);
         }
         else
             throw new IllegalStateException(from.getClass().getName() + " : " + from.getSQL());
     }
 
     /**
-     * @param prnt Parent element.
+     * @param parent Parent element.
      * @param childIdx Child index.
      */
     @SuppressWarnings("StatementWithEmptyBody")
-    private void normalizeExpression(GridSqlAst prnt, int childIdx) {
-        GridSqlAst el = prnt.child(childIdx);
+    private void normalizeExpression(GridSqlAst parent, int childIdx) {
+        GridSqlAst el = parent.child(childIdx);
 
         if (el instanceof GridSqlAlias ||
             el instanceof GridSqlOperation ||
@@ -1828,7 +1404,7 @@ public class GridSqlQuerySplitter {
             GridSqlAlias uniqueAlias = uniqueFromAliases.get(tbl);
 
             // Unique aliases must be generated for all the table filters already.
-            assert uniqueAlias != null: childIdx + "\n" + prnt.getSQL();
+            assert uniqueAlias != null: childIdx + "\n" + parent.getSQL();
 
             col.tableAlias(uniqueAlias.alias());
             col.expressionInFrom(uniqueAlias);
@@ -1843,79 +1419,6 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * @param qry Select.
-     * @param params Parameters.
-     * @param paramIdxs Parameter indexes.
-     */
-    private static void findParamsQuery(GridSqlQuery qry, Object[] params, TreeSet<Integer> paramIdxs) {
-        if (qry instanceof GridSqlSelect)
-            findParamsSelect((GridSqlSelect)qry, params, paramIdxs);
-        else {
-            GridSqlUnion union = (GridSqlUnion)qry;
-
-            findParamsQuery(union.left(), params, paramIdxs);
-            findParamsQuery(union.right(), params, paramIdxs);
-
-            findParams(qry.limit(), params, paramIdxs);
-            findParams(qry.offset(), params, paramIdxs);
-        }
-    }
-
-    /**
-     * @param select Select.
-     * @param params Parameters.
-     * @param paramIdxs Parameter indexes.
-     */
-    private static void findParamsSelect(
-        GridSqlSelect select,
-        Object[] params,
-        TreeSet<Integer> paramIdxs
-    ) {
-        if (params.length == 0)
-            return;
-
-        for (GridSqlAst el : select.columns(false))
-            findParams(el, params, paramIdxs);
-
-        findParams(select.from(), params, paramIdxs);
-        findParams(select.where(), params, paramIdxs);
-
-        // Don't search in GROUP BY and HAVING since they expected to be in select list.
-
-        findParams(select.limit(), params, paramIdxs);
-        findParams(select.offset(), params, paramIdxs);
-    }
-
-    /**
-     * @param el Element.
-     * @param params Parameters.
-     * @param paramIdxs Parameter indexes.
-     */
-    private static void findParams(@Nullable GridSqlAst el, Object[] params,
-        TreeSet<Integer> paramIdxs) {
-        if (el == null)
-            return;
-
-        if (el instanceof GridSqlParameter) {
-            // H2 Supports queries like "select ?5" but first 4 non-existing parameters are need to be set to any value.
-            // Here we will set them to NULL.
-            final int idx = ((GridSqlParameter)el).index();
-
-            if (params.length <= idx)
-                throw new IgniteException("Invalid number of query parameters. " +
-                    "Cannot find " + idx + " parameter.");
-
-            paramIdxs.add(idx);
-        }
-        else if (el instanceof GridSqlSubquery)
-            findParamsQuery(((GridSqlSubquery)el).subquery(), params, paramIdxs);
-        else {
-            for (int i = 0; i < el.size(); i++)
-                findParams(el.child(i), params, paramIdxs);
-        }
-    }
-
-    /**
      * @param mapSelect Selects for map query.
      * @param rdcSelect Selects for reduce query.
      * @param colNames Set of unique top level column names.
@@ -1944,16 +1447,16 @@ public class GridSqlQuerySplitter {
             el = alias.child();
         }
 
-        if (!collocatedGrpBy && hasAggregates(el)) {
+        if (!collocatedGrpBy && SplitterUtils.hasAggregates(el)) {
             aggregateFound = true;
 
             if (alias == null)
-                alias = alias(isHaving ? HAVING_COLUMN : columnName(idx), el);
+                alias = SplitterUtils.alias(isHaving ? HAVING_COLUMN : columnName(idx), el);
 
             // We can update original alias here as well since it will be dropped from mapSelect.
             splitAggregates(alias, 0, mapSelect, idx, hasDistinctAggregate, true);
 
-            set(rdcSelect, idx, alias);
+            rdcSelect.add(alias);
         }
         else {
             String mapColAlias = isHaving ? HAVING_COLUMN : columnName(idx);
@@ -1965,74 +1468,21 @@ public class GridSqlQuerySplitter {
                 rdcColAlias = alias.alias();
 
             // Always wrap map column into generated alias.
-            mapSelect.set(idx, alias(mapColAlias, el)); // `el` is known not to be an alias.
+            mapSelect.set(idx, SplitterUtils.alias(mapColAlias, el)); // `el` is known not to be an alias.
 
             // SELECT __C0 AS original_alias
-            GridSqlElement rdcEl = column(mapColAlias);
+            GridSqlElement rdcEl = SplitterUtils.column(mapColAlias);
 
             if (colNames.add(rdcColAlias)) // To handle column name duplication (usually wildcard for few tables).
-                rdcEl = alias(rdcColAlias, rdcEl);
+                rdcEl = SplitterUtils.alias(rdcColAlias, rdcEl);
 
-            set(rdcSelect, idx, rdcEl);
+            rdcSelect.add(rdcEl);
         }
 
         return aggregateFound;
     }
 
     /**
-     * @param list List.
-     * @param idx Index.
-     * @param item Element.
-     */
-    private static <Z> void set(List<Z> list, int idx, Z item) {
-        assert list.size() == idx;
-        list.add(item);
-    }
-
-    /**
-     * @param el Expression part in SELECT clause.
-     * @return {@code true} If expression contains aggregates.
-     */
-    private static boolean hasAggregates(GridSqlAst el) {
-        if (el instanceof GridSqlAggregateFunction)
-            return true;
-
-        // If in SELECT clause we have a subquery expression with aggregate,
-        // we should not split it. Run the whole subquery on MAP stage.
-        if (el instanceof GridSqlSubquery)
-            return false;
-
-        for (int i = 0; i < el.size(); i++) {
-            if (hasAggregates(el.child(i)))
-                return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Lookup for distinct aggregates.
-     * Note, DISTINCT make no sense for MIN and MAX aggregates, so its will be ignored.
-     *
-     * @param el Expression.
-     * @return {@code true} If expression contains distinct aggregates.
-     */
-    private static boolean hasDistinctAggregates(GridSqlAst el) {
-        if (el instanceof GridSqlAggregateFunction) {
-            GridSqlFunctionType type = ((GridSqlAggregateFunction)el).type();
-
-            return ((GridSqlAggregateFunction)el).distinct() && type != MIN && type != MAX;
-        }
-
-        for (int i = 0; i < el.size(); i++) {
-            if (hasDistinctAggregates(el.child(i)))
-                return true;
-        }
-
-        return false;
-    }
-
-    /**
      * @param parentExpr Parent expression.
      * @param childIdx Child index to try to split.
      * @param mapSelect List of expressions in map SELECT clause.
@@ -2088,7 +1538,7 @@ public class GridSqlQuerySplitter {
         GridSqlElement mapAgg, rdcAgg;
 
         // Create stubbed map alias to fill it with correct expression later.
-        GridSqlAlias mapAggAlias = alias(columnName(first ? exprIdx : mapSelect.size()), EMPTY);
+        GridSqlAlias mapAggAlias = SplitterUtils.alias(columnName(first ? exprIdx : mapSelect.size()), EMPTY);
 
         // Replace original expression if it is the first aggregate in expression or add to the end.
         if (first)
@@ -2103,41 +1553,42 @@ public class GridSqlQuerySplitter {
                 if (hasDistinctAggregate) /* and has no collocated group by */ {
                     mapAgg = agg.child();
 
-                    rdcAgg = aggregate(agg.distinct(), agg.type()).resultType(agg.resultType())
-                        .addChild(column(mapAggAlias.alias()));
+                    rdcAgg = SplitterUtils.aggregate(agg.distinct(), agg.type()).resultType(agg.resultType())
+                        .addChild(SplitterUtils.column(mapAggAlias.alias()));
                 }
                 else {
                     //-- COUNT(x) map
-                    GridSqlElement cntMapAgg = aggregate(agg.distinct(), COUNT)
+                    GridSqlElement cntMapAgg = SplitterUtils.aggregate(agg.distinct(), COUNT)
                         .resultType(GridSqlType.BIGINT).addChild(agg.child());
 
                     // Add generated alias to COUNT(x).
                     // Using size as index since COUNT will be added as the last select element to the map query.
                     String cntMapAggAlias = columnName(mapSelect.size());
 
-                    cntMapAgg = alias(cntMapAggAlias, cntMapAgg);
+                    cntMapAgg = SplitterUtils.alias(cntMapAggAlias, cntMapAgg);
 
                     mapSelect.add(cntMapAgg);
 
                     //-- AVG(CAST(x AS DOUBLE)) map
-                    mapAgg = aggregate(agg.distinct(), AVG).resultType(GridSqlType.DOUBLE).addChild(
-                        function(CAST).resultType(GridSqlType.DOUBLE).addChild(agg.child()));
+                    mapAgg = SplitterUtils.aggregate(agg.distinct(), AVG).resultType(GridSqlType.DOUBLE).addChild(
+                        new GridSqlFunction(CAST).resultType(GridSqlType.DOUBLE).addChild(agg.child()));
 
                     //-- SUM( AVG(x)*COUNT(x) )/SUM( COUNT(x) ) reduce
-                    GridSqlElement sumUpRdc = aggregate(false, SUM).addChild(
-                        op(GridSqlOperationType.MULTIPLY,
-                            column(mapAggAlias.alias()),
-                            column(cntMapAggAlias)));
+                    GridSqlElement sumUpRdc = SplitterUtils.aggregate(false, SUM).addChild(
+                        SplitterUtils.op(GridSqlOperationType.MULTIPLY,
+                            SplitterUtils.column(mapAggAlias.alias()),
+                            SplitterUtils.column(cntMapAggAlias)));
 
-                    GridSqlElement sumDownRdc = aggregate(false, SUM).addChild(column(cntMapAggAlias));
+                    GridSqlElement sumDownRdc =
+                        SplitterUtils.aggregate(false, SUM).addChild(SplitterUtils.column(cntMapAggAlias));
 
-                    if (!isFractionalType(agg.resultType().type())) {
-                        sumUpRdc =  function(CAST).resultType(GridSqlType.BIGINT).addChild(sumUpRdc);
-                        sumDownRdc = function(CAST).resultType(GridSqlType.BIGINT).addChild(sumDownRdc);
+                    if (!SplitterUtils.isFractionalType(agg.resultType().type())) {
+                        sumUpRdc =  new GridSqlFunction(CAST).resultType(GridSqlType.BIGINT).addChild(sumUpRdc);
+                        sumDownRdc = new GridSqlFunction(CAST).resultType(GridSqlType.BIGINT).addChild(sumDownRdc);
                     }
 
-                    rdcAgg = function(CAST).resultType(agg.resultType())
-                        .addChild(op(GridSqlOperationType.DIVIDE, sumUpRdc, sumDownRdc));
+                    rdcAgg = new GridSqlFunction(CAST).resultType(agg.resultType())
+                        .addChild(SplitterUtils.op(GridSqlOperationType.DIVIDE, sumUpRdc, sumDownRdc));
                 }
 
                 break;
@@ -2150,17 +1601,21 @@ public class GridSqlQuerySplitter {
                 if (hasDistinctAggregate) /* and has no collocated group by */ {
                     mapAgg = agg.child();
 
-                    rdcAgg0 = aggregate(agg.distinct(), agg.type()).addChild(column(mapAggAlias.alias()));
+                    rdcAgg0 = SplitterUtils.aggregate(agg.distinct(), agg.type())
+                        .addChild(SplitterUtils.column(mapAggAlias.alias()));
                 }
                 else {
-                    mapAgg = aggregate(agg.distinct(), agg.type()).resultType(agg.resultType()).addChild(agg.child());
+                    mapAgg = SplitterUtils.aggregate(agg.distinct(), agg.type()).resultType(agg.resultType())
+                        .addChild(agg.child());
 
-                    rdcAgg0 = function(CAST).resultType(agg.resultType())
-                        .addChild(aggregate(agg.distinct(), agg.type()).addChild(column(mapAggAlias.alias())));
+                    rdcAgg0 = new GridSqlFunction(CAST).resultType(agg.resultType())
+                        .addChild(SplitterUtils.aggregate(agg.distinct(), agg.type())
+                            .addChild(SplitterUtils.column(mapAggAlias.alias()))
+                        );
                 }
 
                 // Avoid second type upcast on reducer (e.g. Int -> (map) -> Long -> (reduce) -> BigDecimal).
-                rdcAgg = function(CAST).resultType(agg.resultType()).addChild(rdcAgg0);
+                rdcAgg = new GridSqlFunction(CAST).resultType(agg.resultType()).addChild(rdcAgg0);
 
                 break;
 
@@ -2171,17 +1626,17 @@ public class GridSqlQuerySplitter {
 
                     mapAgg = agg.child();
 
-                    rdcAgg = aggregate(agg.distinct(), agg.type()).resultType(GridSqlType.BIGINT)
-                        .addChild(column(mapAggAlias.alias()));
+                    rdcAgg = SplitterUtils.aggregate(agg.distinct(), agg.type()).resultType(GridSqlType.BIGINT)
+                        .addChild(SplitterUtils.column(mapAggAlias.alias()));
                 }
                 else {
-                    mapAgg = aggregate(agg.distinct(), agg.type()).resultType(GridSqlType.BIGINT);
+                    mapAgg = SplitterUtils.aggregate(agg.distinct(), agg.type()).resultType(GridSqlType.BIGINT);
 
                     if (agg.type() == COUNT)
                         mapAgg.addChild(agg.child());
 
-                    rdcAgg = aggregate(false, SUM).addChild(column(mapAggAlias.alias()));
-                    rdcAgg = function(CAST).resultType(GridSqlType.BIGINT).addChild(rdcAgg);
+                    rdcAgg = SplitterUtils.aggregate(false, SUM).addChild(SplitterUtils.column(mapAggAlias.alias()));
+                    rdcAgg = new GridSqlFunction(CAST).resultType(GridSqlType.BIGINT).addChild(rdcAgg);
                 }
 
                 break;
@@ -2194,13 +1649,15 @@ public class GridSqlQuerySplitter {
 
                 if (hasDistinctAggregate)
                     mapAgg = agg.child();
-                else
-                    mapAgg = aggregate(agg.distinct(), agg.type()).resultType(GridSqlType.STRING).addChild(agg.child());
+                else {
+                    mapAgg = SplitterUtils.aggregate(agg.distinct(), agg.type()).resultType(GridSqlType.STRING)
+                        .addChild(agg.child());
+                }
 
-                rdcAgg = aggregate(false, GROUP_CONCAT)
+                rdcAgg = SplitterUtils.aggregate(false, GROUP_CONCAT)
                     .setGroupConcatSeparator(agg.getGroupConcatSeparator())
                     .resultType(GridSqlType.STRING)
-                    .addChild(column(mapAggAlias.alias()));
+                    .addChild(SplitterUtils.column(mapAggAlias.alias()));
 
                 break;
 
@@ -2218,196 +1675,4 @@ public class GridSqlQuerySplitter {
         // Replace in original expression aggregate with reduce aggregate.
         parentExpr.child(aggIdx, rdcAgg);
     }
-
-    /**
-     * @param distinct Distinct.
-     * @param type Type.
-     * @return Aggregate function.
-     */
-    private static GridSqlAggregateFunction aggregate(boolean distinct, GridSqlFunctionType type) {
-        return new GridSqlAggregateFunction(distinct, type);
-    }
-
-    /**
-     * @param name Column name.
-     * @return Column.
-     */
-    private static GridSqlColumn column(String name) {
-        return new GridSqlColumn(null, null, null, null, name);
-    }
-
-    /**
-     * @param alias Alias.
-     * @param child Child.
-     * @return Alias.
-     */
-    private static GridSqlAlias alias(String alias, GridSqlAst child) {
-        GridSqlAlias res = new GridSqlAlias(alias, child);
-
-        res.resultType(child.resultType());
-
-        return res;
-    }
-
-    /**
-     * @param type Type.
-     * @param left Left expression.
-     * @param right Right expression.
-     * @return Binary operator.
-     */
-    private static GridSqlOperation op(GridSqlOperationType type, GridSqlAst left, GridSqlAst right) {
-        return new GridSqlOperation(type, left, right);
-    }
-
-    /**
-     * @param type Type.
-     * @return Function.
-     */
-    private static GridSqlFunction function(GridSqlFunctionType type) {
-        return new GridSqlFunction(type);
-    }
-
-    /**
-     * @param type data type id
-     * @return true if given type is fractional
-     */
-    private static boolean isFractionalType(int type) {
-        return type == Value.DECIMAL || type == Value.FLOAT || type == Value.DOUBLE;
-    }
-
-    /**
-     * @param root Root model.
-     * @return Tree as a string.
-     */
-    @SuppressWarnings("unused")
-    private String printQueryModel(QueryModel root) {
-        GridTreePrinter<QueryModel> mp = new GridTreePrinter<QueryModel>() {
-            /** {@inheritDoc} */
-            @Override protected List<QueryModel> getChildren(QueryModel m) {
-                return m;
-            }
-
-            /** {@inheritDoc} */
-            @Override protected String formatTreeNode(QueryModel m) {
-                return "[ " +(m.uniqueAlias == null ? "+" : m.uniqueAlias.alias()) +
-                    " -> " + m.type +
-                    " ns:" + m.needSplit + " nsch:" + m.needSplitChild +
-                    " ast: " + ast(m) +" ]";
-            }
-
-            private String ast(QueryModel m) {
-                if (m.prnt == null)
-                    return "-+-+-";
-
-                String ast = m.ast().getSQL().replace('\n', ' ');
-
-                int maxLen = 2000;
-
-                return ast.length() <= maxLen ? ast : ast.substring(0, maxLen);
-            }
-        };
-
-        return mp.print(root);
-    }
-
-    /**
-     * 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.
-     * - TABLE and FUNCTION : Never have child models.
-     */
-    private static final class QueryModel extends ArrayList<QueryModel> {
-        /** */
-        @GridToStringInclude
-        final Type type;
-
-        /** */
-        GridSqlAlias uniqueAlias;
-
-        /** */
-        GridSqlAst prnt;
-
-        /** */
-        int childIdx;
-
-        /** If it is a SELECT and we need to split it. Makes sense only for type SELECT. */
-        @GridToStringInclude
-        boolean needSplit;
-
-        /** If we have a child SELECT that we should split. */
-        @GridToStringInclude
-        boolean needSplitChild;
-
-        /** If this is UNION ALL. Makes sense only for type UNION.*/
-        boolean unionAll = true;
-
-        /**
-         * @param type Type.
-         * @param prnt Parent element.
-         * @param childIdx Child index.
-         * @param uniqueAlias Unique parent alias of the current element.
-         *                    May be {@code null} for selects inside of unions or top level queries.
-         */
-        QueryModel(Type type, GridSqlAst prnt, int childIdx, GridSqlAlias uniqueAlias) {
-            this.type = type;
-            this.prnt = prnt;
-            this.childIdx = childIdx;
-            this.uniqueAlias = uniqueAlias;
-        }
-
-        /**
-         * @return The actual AST element for this model.
-         */
-        @SuppressWarnings("TypeParameterHidesVisibleType")
-        private <X extends GridSqlAst> X ast() {
-            return prnt.child(childIdx);
-        }
-
-        /**
-         * @return {@code true} If this is a SELECT or UNION query model.
-         */
-        private boolean isQuery() {
-            return type == Type.SELECT || type == Type.UNION;
-        }
-
-        /** {@inheritDoc} */
-        @Override public String toString() {
-            return S.toString(QueryModel.class, this);
-        }
-    }
-
-    /**
-     * Allowed types for {@link QueryModel}.
-     */
-    private enum Type {
-        SELECT, UNION, TABLE, FUNCTION
-    }
-
-    /**
-     * Condition in AND.
-     */
-    private static class AndCondition {
-        /** */
-        GridSqlAst prnt;
-
-        /** */
-        int childIdx;
-
-        /**
-         * @param prnt Parent element.
-         * @param childIdx Child index.
-         */
-        AndCondition(GridSqlAst prnt, int childIdx) {
-            this.prnt = prnt;
-            this.childIdx = childIdx;
-        }
-
-        /**
-         * @return The actual AST element for this expression.
-         */
-        @SuppressWarnings("TypeParameterHidesVisibleType")
-        private <X extends GridSqlAst> X ast() {
-            return prnt.child(childIdx);
-        }
-    }
 }
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java
index 8ea61e1..aa91f17 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java
@@ -19,6 +19,8 @@ package org.apache.ignite.internal.processors.query.h2.sql;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
+
 import org.h2.util.StatementBuilder;
 import org.h2.util.StringUtils;
 
@@ -180,7 +182,7 @@ public class GridSqlSelect extends GridSqlQuery {
      * @return {@code True} if this simple SQL query like 'SELECT A, B, C from SOME_TABLE' without any conditions
      *      and expressions.
      */
-    @Override public boolean simpleQuery() {
+    @Override public boolean skipMergeTable() {
         boolean simple = !distinct &&
             from instanceof GridSqlTable &&
             where == null &&
@@ -389,4 +391,26 @@ public class GridSqlSelect extends GridSqlQuery {
     public int havingColumn() {
         return havingCol;
     }
+
+    /**
+     * Collect aliases from FROM part.
+     *
+     * @param aliases Table aliases in FROM.
+     */
+    public void collectFromAliases(Set<GridSqlAlias> aliases) {
+        GridSqlAst from = from();
+
+        if (from == null)
+            return;
+
+        while (from instanceof GridSqlJoin) {
+            GridSqlElement right = ((GridSqlJoin)from).rightTable();
+
+            aliases.add((GridSqlAlias)right);
+
+            from = ((GridSqlJoin)from).leftTable();
+        }
+
+        aliases.add((GridSqlAlias)from);
+    }
 }
\ No newline at end of file
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlUnion.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlUnion.java
index 8d2ba42..44c85db 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlUnion.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlUnion.java
@@ -136,10 +136,10 @@ public class GridSqlUnion extends GridSqlQuery {
     }
 
     /** {@inheritDoc} */
-    @Override public boolean simpleQuery() {
+    @Override public boolean skipMergeTable() {
         return unionType() == SelectUnion.UnionType.UNION_ALL && sort().isEmpty() &&
             offset() == null && limit() == null &&
-            left().simpleQuery() && right().simpleQuery();
+            left().skipMergeTable() && right().skipMergeTable();
     }
 
     /**
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterAndCondition.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterAndCondition.java
new file mode 100644
index 0000000..3c10ea8
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterAndCondition.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sql;
+
+import java.util.List;
+
+/**
+ * AND condition for splitter.
+ */
+public class SplitterAndCondition {
+    /** Parent node. */
+    private final GridSqlAst parent;
+
+    /** Child index. */
+    private final int childIdx;
+
+    /**
+     * Collect and conditions from the given element.
+     *
+     * @param res Conditions in AND.
+     * @param parent Parent element.
+     * @param childIdx Child index.
+     */
+    public static void collectAndConditions(List<SplitterAndCondition> res, GridSqlAst parent, int childIdx) {
+        GridSqlAst child = parent.child(childIdx);
+
+        if (child instanceof GridSqlOperation) {
+            GridSqlOperation op = (GridSqlOperation)child;
+
+            if (op.operationType() == GridSqlOperationType.AND) {
+                collectAndConditions(res, op, 0);
+                collectAndConditions(res, op, 1);
+
+                return;
+            }
+        }
+
+        if (!SplitterUtils.isTrue(child))
+            res.add(new SplitterAndCondition(parent, childIdx));
+    }
+
+    /**
+     * @param parent Parent element.
+     * @param childIdx Child index.
+     */
+    private SplitterAndCondition(GridSqlAst parent, int childIdx) {
+        this.parent = parent;
+        this.childIdx = childIdx;
+    }
+
+    /**
+     * @return The actual AST element for this expression.
+     */
+    @SuppressWarnings("TypeParameterHidesVisibleType")
+    public <X extends GridSqlAst> X ast() {
+        return parent.child(childIdx);
+    }
+
+    /**
+     * @return Parent node.
+     */
+    public GridSqlAst parent() {
+        return parent;
+    }
+
+    /**
+     * @return Child index.
+     */
+    public int childIndex() {
+        return childIdx;
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterQueryModel.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterQueryModel.java
new file mode 100644
index 0000000..d19a714
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterQueryModel.java
@@ -0,0 +1,410 @@
+/*
+ * 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.sql;
+
+import org.apache.ignite.internal.util.tostring.GridToStringInclude;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.h2.command.dml.SelectUnion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlJoin.LEFT_TABLE_CHILD;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlJoin.RIGHT_TABLE_CHILD;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlSelect.FROM_CHILD;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlUnion.LEFT_CHILD;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlUnion.RIGHT_CHILD;
+
+/**
+ * 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.
+ * - TABLE and FUNCTION : Never have child models.
+ */
+public final class SplitterQueryModel {
+    /** */
+    @GridToStringInclude
+    private final SplitterQueryModelType type;
+
+    /** */
+    private final GridSqlAlias uniqueAlias;
+
+    /** */
+    private final GridSqlAst parent;
+
+    /** */
+    private final int childIdx;
+
+    /** Child models. */
+    private final List<SplitterQueryModel> childModels = new ArrayList<>();
+
+    /** If it is a SELECT and we need to split it. Makes sense only for type SELECT. */
+    @GridToStringInclude
+    private boolean needSplit;
+
+    /** If we have a child SELECT that we should split. */
+    @GridToStringInclude
+    private boolean needSplitChild;
+
+    /** If this is UNION ALL. Makes sense only for type UNION.*/
+    private boolean unionAll = true;
+
+    /**
+     * Constructor (no split).
+     *
+     * @param type Type.
+     * @param parent Parent element.
+     * @param childIdx Child index.
+     * @param uniqueAlias Unique parent alias of the current element.
+     *     May be {@code null} for selects inside of unions or top level queries.
+     */
+    public SplitterQueryModel(
+        SplitterQueryModelType type,
+        GridSqlAst parent,
+        int childIdx,
+        GridSqlAlias uniqueAlias
+    ) {
+        this(type, parent, childIdx, uniqueAlias, false);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param type Type.
+     * @param parent Parent element.
+     * @param childIdx Child index.
+     * @param uniqueAlias Unique parent alias of the current element.
+     *     May be {@code null} for selects inside of unions or top level queries.
+     * @param needSplit Need split flag.
+     */
+    public SplitterQueryModel(
+        SplitterQueryModelType type,
+        GridSqlAst parent,
+        int childIdx,
+        GridSqlAlias uniqueAlias,
+        boolean needSplit
+    ) {
+        this.type = type;
+        this.parent = parent;
+        this.childIdx = childIdx;
+        this.uniqueAlias = uniqueAlias;
+        this.needSplit = needSplit;
+    }
+
+    /**
+     * @return Type.
+     */
+    public SplitterQueryModelType type() {
+        return type;
+    }
+
+    /**
+     * @return {@code true} If this is a SELECT or UNION query model.
+     */
+    public boolean isQuery() {
+        return type == SplitterQueryModelType.SELECT || type == SplitterQueryModelType.UNION;
+    }
+
+    /**
+     * @return Unique alias.
+     */
+    public GridSqlAlias uniqueAlias() {
+        return uniqueAlias;
+    }
+
+    /**
+     * @return Parent AST element.
+     */
+    public GridSqlAst parent() {
+        return parent;
+    }
+
+    /**
+     * @return Child index.
+     */
+    public int childIndex() {
+        return childIdx;
+    }
+
+    /**
+     * @return The actual AST element for this model.
+     */
+    @SuppressWarnings("TypeParameterHidesVisibleType")
+    public <X extends GridSqlAst> X ast() {
+        return parent.child(childIdx);
+    }
+
+    /**
+     * @return Whether split is needed.
+     */
+    public boolean needSplit() {
+        return needSplit;
+    }
+
+    /**
+     * @return Whether split of children is needed.
+     */
+    public boolean needSplitChild() {
+        return needSplitChild;
+    }
+
+    /**
+     * Move child models to wrap model.
+     *
+     * @param wrapModel Wrap model.
+     * @param begin Child begin index.
+     * @param end Child end index.
+     */
+    public void moveChildModelsToWrapModel(SplitterQueryModel wrapModel, int begin, int end) {
+        for (int i = begin; i <= end; i++) {
+            SplitterQueryModel child = childModels.get(i);
+
+            wrapModel.childModels.add(child);
+        }
+
+        // Replace the first child model with the created one.
+        childModels.set(begin, wrapModel);
+
+        // Drop others.
+        for (int x = begin + 1, i = x; i <= end; i++)
+            childModels.remove(x);
+    }
+
+    /**
+     * Force split flag on a model.
+     */
+    public void forceSplit() {
+        if (type == SplitterQueryModelType.SELECT) {
+            assert !needSplitChild;
+
+            needSplit = true;
+        }
+        else if (type == SplitterQueryModelType.UNION) {
+            needSplitChild = true;
+
+            // Mark all the selects in the UNION to be splittable.
+            for (SplitterQueryModel childModel : childModels) {
+                assert childModel.type() == SplitterQueryModelType.SELECT : childModel.type();
+
+                childModel.needSplit = true;
+            }
+        }
+        else
+            throw new IllegalStateException("Type: " + type);
+    }
+
+    /**
+     * @param unionAll UNION ALL flag.
+     */
+    @SuppressWarnings("SameParameterValue")
+    public void unionAll(boolean unionAll) {
+        this.unionAll = unionAll;
+    }
+
+    /**
+     * @return Number of child models.
+     */
+    public int childModelsCount() {
+        return childModels.size();
+    }
+
+    /**
+     * Get child model by index.
+     *
+     * @param idx Index.
+     * @return Child model.
+     */
+    public SplitterQueryModel childModel(int idx) {
+        return childModels.get(idx);
+    }
+
+    /**
+     * Prepare query model.
+     *
+     * @param prnt Parent AST element.
+     * @param childIdx Child index.
+     * @param uniqueAlias Unique parent alias of the current element.
+     */
+    public void buildQueryModel(GridSqlAst prnt, int childIdx, GridSqlAlias uniqueAlias) {
+        GridSqlAst child = prnt.child(childIdx);
+
+        assert child != null;
+
+        if (child instanceof GridSqlSelect) {
+            SplitterQueryModel model = new SplitterQueryModel(SplitterQueryModelType.SELECT, prnt, childIdx,
+                uniqueAlias);
+
+            childModels.add(model);
+
+            model.buildQueryModel(child, FROM_CHILD, null);
+        }
+        else if (child instanceof GridSqlUnion) {
+            SplitterQueryModel model;
+
+            // We will collect all the selects into a single UNION model.
+            if (type == SplitterQueryModelType.UNION)
+                model = this;
+            else {
+                model = new SplitterQueryModel(SplitterQueryModelType.UNION, prnt, childIdx, uniqueAlias);
+
+                childModels.add(model);
+            }
+
+            if (((GridSqlUnion)child).unionType() != SelectUnion.UnionType.UNION_ALL)
+                model.unionAll(false);
+
+            model.buildQueryModel(child, LEFT_CHILD, null);
+            model.buildQueryModel(child, RIGHT_CHILD, null);
+        }
+        else {
+            // Here we must be in FROM clause of the SELECT.
+            assert type == SplitterQueryModelType.SELECT : type;
+
+            if (child instanceof GridSqlAlias)
+                buildQueryModel(child, 0, (GridSqlAlias)child);
+            else if (child instanceof GridSqlJoin) {
+                buildQueryModel(child, LEFT_TABLE_CHILD, uniqueAlias);
+                buildQueryModel(child, RIGHT_TABLE_CHILD, uniqueAlias);
+            }
+            else {
+                // Here we must be inside of generated unique alias for FROM clause element.
+                assert prnt == uniqueAlias: prnt.getClass();
+
+                if (child instanceof GridSqlTable)
+                    childModels.add(new SplitterQueryModel(SplitterQueryModelType.TABLE, prnt, childIdx, uniqueAlias));
+                else if (child instanceof GridSqlSubquery)
+                    buildQueryModel(child, 0, uniqueAlias);
+                else if (child instanceof GridSqlFunction)
+                    childModels.add(new SplitterQueryModel(SplitterQueryModelType.FUNCTION, prnt, childIdx, uniqueAlias));
+                else
+                    throw new IllegalStateException("Unknown child type: " + child.getClass());
+            }
+        }
+    }
+
+    /**
+     * Analyze query model, setting split flags as needed.
+     *
+     * @param collocatedGrpBy Collocated GROUP BY flag.
+     */
+    @SuppressWarnings("ForLoopReplaceableByForEach")
+    public void analyzeQueryModel(boolean collocatedGrpBy) {
+        if (!isQuery())
+            return;
+
+        // Process all the children at the beginning: depth first analysis.
+        for (int i = 0; i < childModels.size(); i++) {
+            SplitterQueryModel child = childModels.get(i);
+
+            child.analyzeQueryModel(collocatedGrpBy);
+
+            // Pull up information about the splitting child.
+            if (child.needSplit || child.needSplitChild)
+                needSplitChild = true; // We have a child to split.
+        }
+
+        if (type == SplitterQueryModelType.SELECT) {
+            // We may need to split the SELECT only if it has no splittable children,
+            // because only the downmost child can be split, the parents will be the part of
+            // the reduce query.
+            if (!needSplitChild)
+                needSplit = needSplitSelect(ast(), collocatedGrpBy); // Only SELECT can have this flag in true.
+        }
+        else if (type == SplitterQueryModelType.UNION) {
+            // If it is not a UNION ALL, then we have to split because otherwise we can produce duplicates or
+            // wrong results for UNION DISTINCT, EXCEPT, INTERSECT queries.
+            if (!needSplitChild && (!unionAll || ((GridSqlUnion)ast()).hasOffsetLimit()))
+                needSplitChild = true;
+
+            // If we have to split some child SELECT in this UNION, then we have to enforce split
+            // for all other united selects, because this UNION has to be a part of the reduce query,
+            // thus each SELECT needs to have a reduce part for this UNION, but the whole SELECT can not
+            // be a reduce part (usually).
+            if (needSplitChild) {
+                for (int i = 0; i < childModels.size(); i++) {
+                    SplitterQueryModel child = childModels.get(i);
+
+                    assert child.type() == SplitterQueryModelType.SELECT : child.type();
+
+                    if (!child.needSplitChild && !child.needSplit)
+                        child.needSplit = true;
+                }
+            }
+        }
+        else
+            throw new IllegalStateException("Type: " + type);
+    }
+
+    /**
+     * @param idx Index of the child model for which we need to find a respective JOIN element.
+     * @return JOIN.
+     */
+    public GridSqlJoin findJoin(int idx) {
+        assert type == SplitterQueryModelType.SELECT : type;
+        assert childModels.size() > 1; // It must be at least one join with at least two child tables.
+        assert idx < childModels.size(): idx;
+
+        //     join2
+        //      / \
+        //   join1 \
+        //    / \   \
+        //  T0   T1  T2
+
+        // If we need to find JOIN for T0, it is the same as for T1.
+        if (idx == 0)
+            idx = 1;
+
+        GridSqlJoin join = (GridSqlJoin)((GridSqlSelect)ast()).from();
+
+        for (int i = childModels.size() - 1; i > idx; i--)
+            join = (GridSqlJoin)join.leftTable();
+
+        return join;
+    }
+
+    /**
+     * @param select Select to check.
+     * @param collocatedGrpBy Collocated GROUP BY flag.
+     * @return {@code true} If we need to split this select.
+     */
+    private static boolean needSplitSelect(GridSqlSelect select, boolean collocatedGrpBy) {
+        if (select.distinct())
+            return true;
+
+        if (select.hasOffsetLimit())
+            return true;
+
+        if (collocatedGrpBy)
+            return false;
+
+        if (select.groupColumns() != null)
+            return true;
+
+        for (int i = 0; i < select.allColumns(); i++) {
+            if (SplitterUtils.hasAggregates(select.column(i)))
+                return true;
+        }
+
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(SplitterQueryModel.class, this);
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterQueryModelType.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterQueryModelType.java
new file mode 100644
index 0000000..277790e
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterQueryModelType.java
@@ -0,0 +1,35 @@
+/*
+ * 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.sql;
+
+/**
+ * Splitter query model type.
+ */
+public enum SplitterQueryModelType {
+    /** Normal select. */
+    SELECT,
+
+    /** Union. */
+    UNION,
+
+    /** Table. */
+    TABLE,
+
+    /** Function. */
+    FUNCTION
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterUtils.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterUtils.java
new file mode 100644
index 0000000..bb86638
--- /dev/null
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/SplitterUtils.java
@@ -0,0 +1,308 @@
+/*
+ * 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.sql;
+
+import org.apache.ignite.IgniteException;
+import org.h2.value.Value;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.TreeSet;
+
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlConst.TRUE;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.MAX;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.MIN;
+
+/**
+ * Utility methods for splitter.
+ */
+public class SplitterUtils {
+    /**
+     * Check whether AST element has aggregates.
+     *
+     * @param el Expression part in SELECT clause.
+     * @return {@code true} If expression contains aggregates.
+     */
+    public static boolean hasAggregates(GridSqlAst el) {
+        if (el instanceof GridSqlAggregateFunction)
+            return true;
+
+        // If in SELECT clause we have a subquery expression with aggregate,
+        // we should not split it. Run the whole subquery on MAP stage.
+        if (el instanceof GridSqlSubquery)
+            return false;
+
+        for (int i = 0; i < el.size(); i++) {
+            if (hasAggregates(el.child(i)))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param qry Select.
+     * @param params Parameters.
+     * @param paramIdxs Parameter indexes.
+     */
+    public static void findParamsQuery(GridSqlQuery qry, Object[] params, TreeSet<Integer> paramIdxs) {
+        if (qry instanceof GridSqlSelect)
+            findParamsSelect((GridSqlSelect)qry, params, paramIdxs);
+        else {
+            GridSqlUnion union = (GridSqlUnion)qry;
+
+            findParamsQuery(union.left(), params, paramIdxs);
+            findParamsQuery(union.right(), params, paramIdxs);
+
+            findParams(qry.limit(), params, paramIdxs);
+            findParams(qry.offset(), params, paramIdxs);
+        }
+    }
+
+    /**
+     * @param select Select.
+     * @param params Parameters.
+     * @param paramIdxs Parameter indexes.
+     */
+    private static void findParamsSelect(GridSqlSelect select, Object[] params, TreeSet<Integer> paramIdxs) {
+        if (params.length == 0)
+            return;
+
+        for (GridSqlAst el : select.columns(false))
+            findParams(el, params, paramIdxs);
+
+        findParams(select.from(), params, paramIdxs);
+        findParams(select.where(), params, paramIdxs);
+
+        // Don't search in GROUP BY and HAVING since they expected to be in select list.
+
+        findParams(select.limit(), params, paramIdxs);
+        findParams(select.offset(), params, paramIdxs);
+    }
+
+    /**
+     * @param el Element.
+     * @param params Parameters.
+     * @param paramIdxs Parameter indexes.
+     */
+    private static void findParams(@Nullable GridSqlAst el, Object[] params, TreeSet<Integer> paramIdxs) {
+        if (el == null)
+            return;
+
+        if (el instanceof GridSqlParameter) {
+            // H2 Supports queries like "select ?5" but first 4 non-existing parameters are need to be set to any value.
+            // Here we will set them to NULL.
+            final int idx = ((GridSqlParameter)el).index();
+
+            if (params.length <= idx)
+                throw new IgniteException("Invalid number of query parameters. " +
+                    "Cannot find " + idx + " parameter.");
+
+            paramIdxs.add(idx);
+        }
+        else if (el instanceof GridSqlSubquery)
+            findParamsQuery(((GridSqlSubquery)el).subquery(), params, paramIdxs);
+        else {
+            for (int i = 0; i < el.size(); i++)
+                findParams(el.child(i), params, paramIdxs);
+        }
+    }
+
+    /**
+     * Lookup for distinct aggregates.
+     * Note, DISTINCT make no sense for MIN and MAX aggregates, so its will be ignored.
+     *
+     * @param el Expression.
+     * @return {@code true} If expression contains distinct aggregates.
+     */
+    public static boolean hasDistinctAggregates(GridSqlAst el) {
+        if (el instanceof GridSqlAggregateFunction) {
+            GridSqlFunctionType type = ((GridSqlAggregateFunction)el).type();
+
+            return ((GridSqlAggregateFunction)el).distinct() && type != MIN && type != MAX;
+        }
+
+        for (int i = 0; i < el.size(); i++) {
+            if (hasDistinctAggregates(el.child(i)))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check whether LEFT OUTER join exist.
+     *
+     * @param from FROM clause.
+     * @return {@code true} If contains LEFT OUTER JOIN.
+     */
+    public static boolean hasLeftJoin(GridSqlAst from) {
+        while (from instanceof GridSqlJoin) {
+            GridSqlJoin join = (GridSqlJoin)from;
+
+            assert !(join.rightTable() instanceof GridSqlJoin);
+
+            if (join.isLeftOuter())
+                return true;
+
+            from = join.leftTable();
+        }
+
+        return false;
+    }
+
+    /**
+     * @param ast Reduce query AST.
+     * @param rdcQry Reduce query string.
+     */
+    public static void checkNoDataTablesInReduceQuery(GridSqlAst ast, String rdcQry) {
+        if (ast instanceof GridSqlTable) {
+            if (((GridSqlTable)ast).dataTable() != null)
+                throw new IgniteException("Failed to generate REDUCE query. Data table found: " + ast.getSQL() +
+                    " \n" + rdcQry);
+        }
+        else {
+            for (int i = 0; i < ast.size(); i++)
+                checkNoDataTablesInReduceQuery(ast.child(i), rdcQry);
+        }
+    }
+
+    /**
+     * @param expr Expression.
+     * @return {@code true} If this expression represents a constant value `TRUE`.
+     */
+    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+    public static boolean isTrue(GridSqlAst expr) {
+        return expr instanceof GridSqlConst && ((GridSqlConst)expr).value() == TRUE.value();
+    }
+
+    /**
+     * @param type data type id
+     * @return true if given type is fractional
+     */
+    public static boolean isFractionalType(int type) {
+        return type == Value.DECIMAL || type == Value.FLOAT || type == Value.DOUBLE;
+    }
+
+    /**
+     * @param distinct Distinct.
+     * @param type Type.
+     * @return Aggregate function.
+     */
+    public static GridSqlAggregateFunction aggregate(boolean distinct, GridSqlFunctionType type) {
+        return new GridSqlAggregateFunction(distinct, type);
+    }
+
+    /**
+     * @param name Column name.
+     * @return Column.
+     */
+    public static GridSqlColumn column(String name) {
+        return new GridSqlColumn(null, null, null, null, name);
+    }
+
+    /**
+     * @param alias Alias.
+     * @param child Child.
+     * @return Alias.
+     */
+    public static GridSqlAlias alias(String alias, GridSqlAst child) {
+        GridSqlAlias res = new GridSqlAlias(alias, child);
+
+        res.resultType(child.resultType());
+
+        return res;
+    }
+
+    /**
+     * @param type Type.
+     * @param left Left expression.
+     * @param right Right expression.
+     * @return Binary operator.
+     */
+    public static GridSqlOperation op(GridSqlOperationType type, GridSqlAst left, GridSqlAst right) {
+        return new GridSqlOperation(type, left, right);
+    }
+
+    /**
+     * @param ast Map query AST.
+     * @return {@code true} If the given AST has partitioned tables.
+     */
+    public static boolean hasPartitionedTables(GridSqlAst ast) {
+        if (ast instanceof GridSqlTable) {
+            if (((GridSqlTable)ast).dataTable() != null)
+                return ((GridSqlTable)ast).dataTable().isPartitioned();
+            else
+                return false;
+        }
+
+        for (int i = 0; i < ast.size(); i++) {
+            if (hasPartitionedTables(ast.child(i)))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check whether the given SELECT has subqueries.
+     *
+     * @param qry Query.
+     * @return {@code True} if subqueries are found.
+     */
+    public static boolean hasSubQueries(GridSqlSelect qry) {
+        boolean res = hasSubQueries0(qry.where()) || hasSubQueries0(qry.from());
+
+        if (!res) {
+            for (int i = 0; i < qry.columns(false).size(); i++) {
+                if (hasSubQueries0(qry.column(i))) {
+                    res = true;
+
+                    break;
+                }
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * @param ast Query AST.
+     * @return {@code true} If the given AST has sub-queries.
+     */
+    private static boolean hasSubQueries0(GridSqlAst ast) {
+        if (ast == null)
+            return false;
+
+        if (ast instanceof GridSqlSubquery)
+            return true;
+
+        for (int childIdx = 0; childIdx < ast.size(); childIdx++) {
+            if (hasSubQueries0(ast.child(childIdx)))
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Private constructor.
+     */
+    private SplitterUtils() {
+        // No-op.
+    }
+}
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
index 9625898..e67294d 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
@@ -934,7 +934,7 @@ public class GridMapQueryExecutor {
                             stmt = h2.connections().prepareStatement(conn, sql);
                         }
 
-                        h2.bindParameters(stmt, params0);
+                        H2Utils.bindParameters(stmt, params0);
 
                         int opTimeout = IgniteH2Indexing.operationTimeout(timeout, tx);
 


Mime
View raw message