ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From se...@apache.org
Subject [2/4] ignite git commit: ignite-3860 - merge to 1.9 # Conflicts: # modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java # modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/Gri
Date Mon, 20 Feb 2017 19:10:01 GMT
http://git-wip-us.apache.org/repos/asf/ignite/blob/3737407b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlQuerySplitter.java
index e164315..5352c87 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
@@ -17,34 +17,53 @@
 
 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.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import javax.cache.CacheException;
+import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
-import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.query.GridCacheSqlQuery;
 import org.apache.ignite.internal.processors.cache.query.GridCacheTwoStepQuery;
-import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
-import org.apache.ignite.lang.IgnitePredicate;
+import org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing;
+import org.apache.ignite.internal.util.typedef.F;
 import org.h2.command.Prepared;
+import org.h2.command.dml.Query;
+import org.h2.command.dml.SelectUnion;
 import org.h2.jdbc.JdbcPreparedStatement;
-import org.h2.table.Column;
-import org.h2.table.IndexColumn;
 import org.h2.util.IntArray;
 import org.jetbrains.annotations.Nullable;
 
+import static org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing.setupConnection;
 import static org.apache.ignite.internal.processors.query.h2.opt.GridH2CollocationModel.isCollocated;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlConst.TRUE;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.AVG;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.CAST;
 import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlFunctionType.COUNT;
 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;
+import static org.apache.ignite.internal.processors.query.h2.sql.GridSqlJoin.RIGHT_TABLE_CHILD;
 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.GridSqlQueryParser.query;
+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;
 import static org.apache.ignite.internal.processors.query.h2.twostep.GridReduceQueryExecutor.toArray;
 
 /**
@@ -52,10 +71,10 @@ import static org.apache.ignite.internal.processors.query.h2.twostep.GridReduceQ
  */
 public class GridSqlQuerySplitter {
     /** */
-    private static final String TABLE_SCHEMA = "PUBLIC";
+    private static final String MERGE_TABLE_SCHEMA = "PUBLIC"; // Schema PUBLIC must always exist.
 
     /** */
-    private static final String TABLE_PREFIX = "__T";
+    private static final String MERGE_TABLE_PREFIX = "__T";
 
     /** */
     private static final String COLUMN_PREFIX = "__C";
@@ -63,264 +82,1129 @@ public class GridSqlQuerySplitter {
     /** */
     private static final String HAVING_COLUMN = "__H";
 
+    /** */
+    private static final String UNIQUE_TABLE_ALIAS_SUFFIX = "__Z";
+
+    /** */
+    private int nextTblAliasId;
+
+    /** */
+    private int splitId = -1; // The first one will be 0.
+
+    /** */
+    private Set<String> schemas = new HashSet<>();
+
+    /** */
+    private Set<String> tbls = new HashSet<>();
+
+    /** */
+    private boolean rdcQrySimple;
+
+    /** */
+    private GridCacheSqlQuery rdcSqlQry;
+
+    /** */
+    private List<GridCacheSqlQuery> mapSqlQrys = new ArrayList<>();
+
+    /** */
+    private Object[] params;
+
+    /** */
+    private boolean collocatedGrpBy;
+
+    /** */
+    private IdentityHashMap<GridSqlAst, GridSqlAlias> uniqueFromAliases = new IdentityHashMap<>();
+
+    /**
+     * @param params Query parameters.
+     * @param collocatedGrpBy If it is a collocated GROUP BY query.
+     */
+    public GridSqlQuerySplitter(Object[] params, boolean collocatedGrpBy) {
+        this.params = params;
+        this.collocatedGrpBy = collocatedGrpBy;
+    }
+
     /**
      * @param idx Index of table.
-     * @return Table.
+     * @return Merge table.
      */
-    public static GridSqlTable table(int idx) {
-        return new GridSqlTable(TABLE_SCHEMA, TABLE_PREFIX + idx);
+    private static GridSqlTable mergeTable(int idx) {
+        return new GridSqlTable(MERGE_TABLE_SCHEMA, MERGE_TABLE_PREFIX + idx);
+    }
+
+    /**
+     * @param idx Table index.
+     * @return Merge table name.
+     */
+    public static String mergeTableIdentifier(int idx) {
+        return mergeTable(idx).getSQL();
     }
 
     /**
      * @param idx Index of column.
      * @return Generated by index column alias.
      */
-    private static String columnName(int idx) {
-        return COLUMN_PREFIX + idx;
+    private String columnName(int idx) {
+        // We must have unique columns for each split to avoid name clashes.
+        return COLUMN_PREFIX + splitId + '_' + idx;
+    }
+
+    /**
+     * @param stmt Prepared statement.
+     * @param params Parameters.
+     * @param collocatedGrpBy Whether the query has collocated GROUP BY keys.
+     * @param distributedJoins If distributed joins enabled.
+     * @param enforceJoinOrder Enforce join order.
+     * @param h2 Indexing.
+     * @return Two step query.
+     * @throws SQLException If failed.
+     * @throws IgniteCheckedException If failed.
+     */
+    public static GridCacheTwoStepQuery split(
+        JdbcPreparedStatement stmt,
+        Object[] params,
+        boolean collocatedGrpBy,
+        boolean distributedJoins,
+        boolean enforceJoinOrder,
+        IgniteH2Indexing h2
+    ) 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(stmt), false);
+
+        String originalSql = qry.getSQL();
+
+        final boolean explain = qry.explain();
+
+        qry.explain(false);
+
+        GridSqlQuerySplitter splitter = new GridSqlQuerySplitter(params, collocatedGrpBy);
+
+        // 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);
+
+        Connection conn = stmt.getConnection();
+
+        // 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);
+
+        // Do the actual query split. We will update the original query AST, need to be careful.
+        splitter.splitQuery(qry);
+
+        assert !F.isEmpty(splitter.mapSqlQrys): "map"; // We must have at least one map query.
+        assert splitter.rdcSqlQry != null: "rdc"; // We must have a reduce query.
+
+        // If we have distributed joins, then we have to optimize all MAP side queries
+        // to have a correct join order with respect to batched joins and check if we need
+        // distributed joins at all.
+        // TODO Also we need to have a list of table aliases to filter by primary or explicit partitions.
+        if (distributedJoins) {
+            boolean allCollocated = true;
+
+            for (GridCacheSqlQuery mapSqlQry : splitter.mapSqlQrys) {
+                Prepared prepared = optimize(h2, conn, mapSqlQry.query(), params, true, enforceJoinOrder);
+
+                allCollocated &= isCollocated((Query)prepared);
+
+                mapSqlQry.query(parse(prepared, true).getSQL());
+            }
+
+            // We do not need distributed joins if all MAP queries are colocated.
+            if (allCollocated)
+                distributedJoins = false;
+        }
+
+        // Setup resulting two step query and return it.
+        GridCacheTwoStepQuery twoStepQry = new GridCacheTwoStepQuery(originalSql, splitter.schemas, splitter.tbls);
+
+        twoStepQry.reduceQuery(splitter.rdcSqlQry);
+
+        for (GridCacheSqlQuery mapSqlQry : splitter.mapSqlQrys)
+            twoStepQry.addMapQuery(mapSqlQry);
+
+        twoStepQry.skipMergeTable(splitter.rdcQrySimple);
+        twoStepQry.explain(explain);
+        twoStepQry.distributedJoins(distributedJoins);
+
+        return twoStepQry;
+    }
+
+    /**
+     * @param qry Optimized and normalized query to split.
+     */
+    private void splitQuery(GridSqlQuery qry) {
+        // Create a fake parent AST element for the query to allow replacing the query in the parent by split.
+        GridSqlSubquery fakeQryPrnt = 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);
+
+        // Build a simplified query model. We need it because navigation over the original AST is too complex.
+        buildQueryModel(fakeQrymPrnt, fakeQryPrnt, 0, null);
+
+        assert fakeQrymPrnt.size() == 1;
+
+        // Get the built query model from the fake parent.
+        QueryModel qrym = fakeQrymPrnt.get(0);
+
+        // Setup the needed information for split.
+        analyzeQueryModel(qrym);
+
+        // If we have child queries to split, then go hard way.
+        if (qrym.needSplitChild) {
+            // All the siblings to selects we are going to split must be also wrapped into subqueries.
+            pushDownQueryModel(qrym);
+
+            // Need to make all the joined subqueries to be ordered by join conditions.
+            setupMergeJoinSorting(qrym);
+        }
+        else if (!qrym.needSplit)  // Just split the top level query.
+            setNeedSplit(qrym);
+
+        // Split the query model into multiple map queries and a single reduce query.
+        splitQueryModel(qrym);
+
+        // Get back the updated query from the fake parent. It will be our reduce query.
+        qry = fakeQryPrnt.subquery();
+
+        // Setup a resulting reduce query.
+        rdcSqlQry = new GridCacheSqlQuery(qry.getSQL());
+        rdcQrySimple = qry.simpleQuery();
+
+        setupParameters(rdcSqlQry, qry, params);
+    }
+
+    /**
+     * @param qrym Query model.
+     */
+    private void pushDownQueryModel(QueryModel qrym) {
+        if (qrym.type == Type.UNION) {
+            assert qrym.needSplitChild; // Otherwise we should not get here.
+
+            for (int i = 0; i < qrym.size(); i++)
+                pushDownQueryModel(qrym.get(i));
+        }
+        else if (qrym.type == Type.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.
+
+                pushDownQueryModelSelect(qrym);
+            }
+        }
+        else
+            throw new IllegalStateException("Type: " + qrym.type);
+    }
+
+    /**
+     * @param qrym Query model for the SELECT.
+     */
+    private void pushDownQueryModelSelect(QueryModel qrym) {
+        int begin = -1;
+
+        // Here we iterate over joined FROM table filters.
+        for (int i = 0; i < qrym.size(); i++) {
+            QueryModel child = qrym.get(i);
+
+            if (child.isQuery() && (child.needSplitChild || child.needSplit)) {
+                // Push down the currently collected range.
+                if (begin != -1) {
+                    pushDownQueryModelRange(qrym, begin, i - 1);
+
+                    i = begin + 1; // We've modified qrym by this range push down, need to adjust counter.
+
+                    assert qrym.get(i) == child; // Adjustment check: we have to return to the same point.
+
+                    // Reset range begin.
+                    begin = -1;
+                }
+
+                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.
+            // We need to find all the joined elements like this in chain to push them down.
+            else if (begin == -1)
+                begin = i;
+        }
+
+        // Push down the remaining range.
+        if (begin != -1)
+            pushDownQueryModelRange(qrym, begin, qrym.size() - 1);
+    }
+
+    /**
+     * @param qrym Query model.
+     */
+    private void setupMergeJoinSorting(QueryModel qrym) {
+        if (qrym.type == Type.UNION) {
+            for (int i = 0; i < qrym.size(); i++)
+                setupMergeJoinSorting(qrym.get(i));
+        }
+        else if (qrym.type == Type.SELECT) {
+            if (!qrym.needSplit) {
+                boolean needSplitChild = false;
+
+                for (int i = 0; i < qrym.size(); i++) {
+                    QueryModel child = qrym.get(i);
+
+                    assert child.isQuery() : qrym.type;
+
+                    if (child.needSplit)
+                        needSplitChild = true;
+                    else
+                        setupMergeJoinSorting(child); // Go deeper.
+                }
+
+                if (needSplitChild && qrym.size() > 1)
+                    setupMergeJoinSortingSelect(qrym); // Setup merge join hierarchy for the SELECT.
+            }
+        }
+        else
+            throw new IllegalStateException("Type: " + qrym.type);
+    }
+
+    /**
+     * @param qrym Query model.
+     */
+    private void setupMergeJoinSortingSelect(QueryModel qrym) {
+        // After pushing down we can have the following variants:
+        //  - splittable SELECT
+        //  - subquery with splittable child (including UNION)
+
+        // We will push down chains of sequential subqueries between
+        // 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);
+
+            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);
+
+                    i = 1;
+
+                    assert qrym.get(i) == child; // We must remain at the same position.
+                }
+
+                injectSortingFirstJoin(qrym);
+            }
+        }
+    }
+
+    /**
+     * @param qrym Query model.
+     */
+    private void injectSortingFirstJoin(QueryModel qrym) {
+        // Must always be generated unique aliases here.
+        GridSqlAlias leftTbl = qrym.get(0).ast();
+        GridSqlAlias rightTbl = qrym.get(1).ast();
+
+        GridSqlJoin join = findJoin(qrym, 0);
+
+        // We are at the beginning, thus left and right AST must be children of the same join AST.
+        assert join.leftTable() == leftTbl: join.getSQL();
+        assert join.rightTable() == rightTbl: join.getSQL();
+
+        // Collect all AND conditions.
+        List<AndCondition> andConditions = new ArrayList<>();
+
+        collectAndConditions(andConditions, join, ON_CHILD);
+        collectAndConditions(andConditions, qrym.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);
+            GridSqlOperation op = and.ast();
+
+            if (op.operationType() == GridSqlOperationType.EQUAL) {
+                GridSqlAst leftExpr = op.child(0);
+                GridSqlAst rightExpr = op.child(1);
+
+                if (leftExpr instanceof GridSqlColumn && rightExpr instanceof GridSqlColumn) {
+                    GridSqlAst leftFrom = ((GridSqlColumn)leftExpr).expressionInFrom();
+                    GridSqlAst rightFrom = ((GridSqlColumn)rightExpr).expressionInFrom();
+
+                    if (leftFrom == leftTbl && rightFrom == rightTbl) {
+                        leftOrder.add((GridSqlColumn)leftExpr);
+                        rightOrder.add((GridSqlColumn)rightExpr);
+                    }
+                    else if (leftFrom == rightTbl && rightFrom == leftTbl) {
+                        leftOrder.add((GridSqlColumn)rightExpr);
+                        rightOrder.add((GridSqlColumn)leftExpr);
+                    }
+                }
+            }
+        }
+
+        // Do inject ORDER BY to the both sides.
+        injectOrderBy(leftTbl, leftOrder);
+        injectOrderBy(rightTbl, rightOrder);
+    }
+
+    /**
+     * @param subQryAlias Subquery alias.
+     * @param orderByCols Columns for ORDER BY.
+     */
+    private void injectOrderBy(GridSqlAlias subQryAlias, List<GridSqlColumn> orderByCols) {
+        // Structure: alias -> subquery -> query
+        GridSqlQuery qry = subQryAlias.child().child();
+        GridSqlSelect select = leftmostSelect(qry); // The leftmost SELECT in UNION defines column names.
+
+        for (int i = 0; i < orderByCols.size(); i++) {
+            GridSqlColumn col = orderByCols.get(i);
+
+            int colIdx = 0;
+
+            for (;;) {
+                GridSqlAst expr = select.column(colIdx);
+
+                String colName;
+
+                if (expr instanceof GridSqlAlias)
+                    colName = ((GridSqlAlias)expr).alias();
+                else if (expr instanceof GridSqlColumn)
+                    colName = ((GridSqlColumn)expr).columnName();
+                else
+                    throw new IllegalStateException(); // It must be impossible to join by this column then.
+
+                if (colName.equals(col.columnName()))
+                    break; // Found the needed column index.
+            }
+
+            // Add sort column to the query.
+            qry.addSort(new GridSqlSortColumn(colIdx, true, false, false));
+        }
     }
 
     /**
      * @param qry Query.
-     * @return Leftest simple query if this is UNION.
+     * @return Leftmost select if it is a UNION query.
      */
-    private static GridSqlSelect leftest(GridSqlQuery qry) {
-        if (qry instanceof GridSqlUnion)
-            return leftest(((GridSqlUnion)qry).left());
+    private GridSqlSelect leftmostSelect(GridSqlQuery qry) {
+        while (qry instanceof GridSqlUnion)
+            qry = ((GridSqlUnion)qry).left();
 
         return (GridSqlSelect)qry;
     }
 
     /**
-     * @param qry Query.
-     * @return Select query.
+     * @param qrym 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 static GridSqlSelect wrapUnion(GridSqlQuery qry) {
-        if (qry instanceof GridSqlSelect)
-            return (GridSqlSelect)qry;
+    private void pushDownQueryModelRange(QueryModel qrym, int begin, int end) {
+        assert end >= begin;
 
-        // Handle UNION.
-        GridSqlSelect wrapQry = new GridSqlSelect().from(new GridSqlSubquery(qry));
+        if (begin == end && qrym.get(end).isQuery()) {
+            // Simple case when we have a single subquery to push down, just mark it to be splittable.
+            setNeedSplit(qrym.get(end));
+        }
+        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);
+        }
+    }
 
-        wrapQry.explain(qry.explain());
-        qry.explain(false);
+    /**
+     * @param qrym Query model.
+     */
+    private void setNeedSplit(QueryModel qrym) {
+        if (qrym.type == Type.SELECT)
+            qrym.needSplit = true;
+        else if (qrym.type == Type.UNION) {
+            qrym.needSplitChild = true;
 
-        GridSqlSelect left = leftest(qry);
+            // Mark all the selects in the UNION to be splittable.
+            for (QueryModel s : qrym) {
+                assert s.type == Type.SELECT: s.type;
 
-        int c = 0;
+                s.needSplit = true;
+            }
+        }
+        else
+            throw new IllegalStateException("Type: " + qrym.type);
+    }
 
-        for (GridSqlElement expr : left.columns(true)) {
-            GridSqlType type = expr.resultType();
+    /**
+     * @param qrym 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) {
+        // 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);
 
-            String colName;
+        QueryModel wrapQrym = new QueryModel(Type.SELECT, wrapSubqry, 0, wrapAlias);
 
-            if (expr instanceof GridSqlAlias)
-                colName = ((GridSqlAlias)expr).alias();
-            else if (expr instanceof GridSqlColumn)
-                colName = ((GridSqlColumn)expr).columnName();
-            else {
-                colName = columnName(c);
+        wrapQrym.needSplit = needSplit;
 
-                expr = alias(colName, expr);
+        // Prepare all the prerequisites.
+        GridSqlSelect select = qrym.ast();
 
-                // Set generated alias to the expression.
-                left.setColumn(c, expr);
-            }
+        Set<GridSqlAlias> tblAliases = 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;
+
+            assert uniqueTblAlias != null: select.getSQL();
+
+            tblAliases.add(uniqueTblAlias);
+        }
+
+        // Push down columns in SELECT clause.
+        pushDownSelectColumns(tblAliases, cols, wrapAlias, select);
+
+        // Move all the related WHERE conditions to wrap query.
+        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);
+
+        // Add all the collected columns to the wrap query.
+        for (GridSqlAlias col : cols.values())
+            wrapSelect.addColumn(col, true);
 
-            GridSqlColumn col = column(colName);
+        // Adjust query models to a new AST structure.
 
-            col.resultType(type);
+        // Move pushed down child models to the newly created model.
+        for (int i = begin; i <= end; i++) {
+            QueryModel child = qrym.get(i);
 
-            wrapQry.addColumn(col, true);
+            assert !child.needSplit && !child.needSplitChild;
 
-            c++;
+            wrapQrym.add(child);
         }
 
-        // ORDER BY
-        if (!qry.sort().isEmpty()) {
-            for (GridSqlSortColumn col : qry.sort())
-                wrapQry.addSort(col);
+        // 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);
+    }
+
+    /**
+     * @param tblAliases Table aliases for push down.
+     * @param cols Columns with generated aliases.
+     * @param qrym 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.
+     */
+    private void pushDownJoins(
+        Set<GridSqlAlias> tblAliases,
+        Map<String,GridSqlAlias> cols,
+        QueryModel qrym,
+        int begin,
+        int end,
+        GridSqlAlias wrapAlias
+    ) {
+        GridSqlSelect wrapSelect = wrapAlias.child();
+
+        final int last = qrym.size() - 1;
+
+        if (begin == end) {
+            //  Simple case when we have to push down only a single table and no joins.
+
+            //       join3
+            //        / \
+            //     join2 \
+            //      / \   \
+            //   join1 \   \
+            //    / \   \   \
+            //  T0   T1  T2  T3
+            //           ^^
+
+            // - Push down T2 to the wrap query W and replace T2 with W:
+
+            //       join3
+            //        / \
+            //     join2 \
+            //      / \   \
+            //   join1 \   \
+            //    / \   \   \
+            //  T0   T1  W   T3
+
+            //  W: T2
+
+            GridSqlJoin endJoin = findJoin(qrym, end);
+
+            wrapSelect.from(qrym.get(end).uniqueAlias);
+            endJoin.child(end == 0 ? LEFT_TABLE_CHILD : RIGHT_TABLE_CHILD, wrapAlias);
+        }
+        else if (end == last) {
+            // Here we need to push down chain from `begin` to the last child in the model (T3).
+            // Thus we have something to split in the beginning and `begin` can not be 0.
+            assert begin > 0;
+
+            //       join3
+            //        / \
+            //     join2 \
+            //      / \   \
+            //   join1 \   \
+            //    / \   \   \
+            //  T0   T1  T2  T3
+            //           ^----^
+
+            // We have to push down T2 and T3.
+            // Also may be T1, but this does not change much the logic.
+
+            // - Add join3 to W,
+            // - replace left branch in join3 with T2,
+            // - replace right branch in join2 with W:
+
+            //     join2
+            //      / \
+            //   join1 \
+            //    / \   \
+            //  T0   T1  W
+
+            //  W:      join3
+            //           / \
+            //         T2   T3
+
+            GridSqlJoin beginJoin = findJoin(qrym, begin);
+            GridSqlJoin afterBeginJoin = findJoin(qrym, begin + 1);
+            GridSqlJoin endJoin = findJoin(qrym, end);
+
+            wrapSelect.from(endJoin);
+            afterBeginJoin.leftTable(beginJoin.rightTable());
+            beginJoin.rightTable(wrapAlias);
+        }
+        else if (begin == 0) {
+            //  From the first model to some middle one.
+
+            //       join3
+            //        / \
+            //     join2 \
+            //      / \   \
+            //   join1 \   \
+            //    / \   \   \
+            //  T0   T1  T2  T3
+            //  ^-----^
+
+
+            //     join3
+            //      / \
+            //   join2 \
+            //    / \   \
+            //   W  T2  T3
+
+            //  W:    join1
+            //         / \
+            //       T0   T1
+
+            GridSqlJoin endJoin = findJoin(qrym, end);
+            GridSqlJoin afterEndJoin = findJoin(qrym, end + 1);
+
+            wrapSelect.from(endJoin);
+            afterEndJoin.leftTable(wrapAlias);
+        }
+        else {
+            //  Push down some middle range.
+
+            //       join3
+            //        / \
+            //     join2 \
+            //      / \   \
+            //   join1 \   \
+            //    / \   \   \
+            //  T0   T1  T2  T3
+            //       ^----^
+
+
+            //        join3
+            //         / \
+            //      join1 \
+            //       / \   \
+            //     T0   W   T3
+
+            //  W:      join2
+            //           / \
+            //         T1   T2
+
+            GridSqlJoin beginJoin = findJoin(qrym, begin);
+            GridSqlJoin afterBeginJoin = findJoin(qrym, begin + 1);
+            GridSqlJoin endJoin = findJoin(qrym, end);
+            GridSqlJoin afterEndJoin = findJoin(qrym, end + 1);
+
+            wrapSelect.from(endJoin);
+            afterEndJoin.leftTable(beginJoin);
+            afterBeginJoin.leftTable(beginJoin.rightTable());
+            beginJoin.rightTable(wrapAlias);
         }
 
-        return wrapQry;
+        // Get the original SELECT.
+        GridSqlSelect select = qrym.ast();
+        GridSqlAst from = select.from();
+
+        // Push down related ON conditions for all the related joins.
+        while (from instanceof GridSqlJoin) {
+            pushDownColumnsInExpression(tblAliases, cols, wrapAlias, from, ON_CHILD);
+
+            from = from.child(LEFT_TABLE_CHILD);
+        }
     }
 
     /**
-     * @param stmt Prepared statement.
-     * @param params Parameters.
-     * @param collocatedGrpBy Whether the query has collocated GROUP BY keys.
-     * @param distributedJoins If distributed joins enabled.
-     * @return Two step query.
+     * @param tblAliases Table aliases for push down.
+     * @param cols Columns with generated aliases.
+     * @param wrapAlias Alias of the wrap query.
+     * @param select The original select.
      */
-    public static GridCacheTwoStepQuery split(
-        JdbcPreparedStatement stmt,
-        Object[] params,
-        final boolean collocatedGrpBy,
-        final boolean distributedJoins
+    private void pushDownSelectColumns(
+        Set<GridSqlAlias> tblAliases,
+        Map<String,GridSqlAlias> cols,
+        GridSqlAlias wrapAlias,
+        GridSqlSelect select
     ) {
-        if (params == null)
-            params = GridCacheSqlQuery.EMPTY_PARAMS;
+        for (int i = 0; i < select.allColumns(); i++) {
+            GridSqlAst expr = select.column(i);
+
+            if (expr instanceof GridSqlColumn) {
+                // If this is a column with no expression and without alias, we need replace it with an alias,
+                // because in the wrapQuery we will generate unique aliases for all the columns to avoid duplicates
+                // and all the columns in the will be replaced.
+                expr = alias(((GridSqlColumn)expr).columnName(), expr);
+
+                select.setColumn(i, expr);
+            }
+
+            for (int c = 0; c < expr.size(); c++)
+                pushDownColumnsInExpression(tblAliases, cols, wrapAlias, expr, c);
+        }
+    }
+
+    /**
+     * @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 childIdx Child index.
+     */
+    @SuppressWarnings("SuspiciousMethodCalls")
+    private void pushDownColumnsInExpression(
+        Set<GridSqlAlias> tblAliases,
+        Map<String,GridSqlAlias> cols,
+        GridSqlAlias wrapAlias,
+        GridSqlAst prnt,
+        int childIdx
+    ) {
+        GridSqlAst child = prnt.child(childIdx);
 
-        Set<String> tbls = new HashSet<>();
-        Set<String> schemas = new HashSet<>();
+        if (child instanceof GridSqlColumn)
+            pushDownColumn(tblAliases, cols, wrapAlias, prnt, childIdx);
+        else {
+            for (int i = 0; i < child.size(); i++)
+                pushDownColumnsInExpression(tblAliases, cols, wrapAlias, child, i);
+        }
+    }
 
-        final Prepared prepared = prepared(stmt);
+    /**
+     * @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 childIdx Child index.
+     */
+    private void pushDownColumn(
+        Set<GridSqlAlias> tblAliases,
+        Map<String,GridSqlAlias> cols,
+        GridSqlAlias wrapAlias,
+        GridSqlAst prnt,
+        int childIdx
+    ) {
+        GridSqlColumn col = prnt.child(childIdx);
 
-        GridSqlStatement gridStmt = new GridSqlQueryParser().parse(prepared);
+        // It must always be unique table alias.
+        GridSqlAlias tblAlias = (GridSqlAlias)col.expressionInFrom();
 
-        assert gridStmt instanceof GridSqlQuery;
+        assert tblAlias != null; // The query is normalized.
 
-        GridSqlQuery qry = (GridSqlQuery) gridStmt;
+        if (!tblAliases.contains(tblAlias))
+            return;
 
-        qry = collectAllTables(qry, schemas, tbls);
+        String uniqueColAlias = uniqueColumnAlias(col);
+        GridSqlAlias colAlias = cols.get(uniqueColAlias);
 
-        // Build resulting two step query.
-        GridCacheTwoStepQuery res = new GridCacheTwoStepQuery(qry.getSQL(), schemas, tbls);
+        if (colAlias == null) {
+            colAlias = alias(uniqueColAlias, col);
 
-        // Map query will be direct reference to the original query AST.
-        // Thus all the modifications will be performed on the original AST, so we should be careful when
-        // nullifying or updating things, have to make sure that we will not need them in the original form later.
-        final GridSqlSelect mapQry = wrapUnion(qry);
+            // We have this map to avoid column duplicates in wrap query.
+            cols.put(uniqueColAlias, colAlias);
+        }
 
-        GridCacheSqlQuery rdc = split(res, 0, mapQry, params, collocatedGrpBy);
+        col = column(uniqueColAlias);
+        // col.tableAlias(wrapAlias.alias());
+        col.expressionInFrom(wrapAlias);
 
-        res.reduceQuery(rdc);
+        prnt.child(childIdx, col);
+    }
 
-        // We do not have to look at each map query separately here, because if
-        // the whole initial query is collocated, then all the map sub-queries
-        // will be collocated as well.
-        res.distributedJoins(distributedJoins && !isCollocated(query(prepared)));
+    /**
+     * @param col Column.
+     * @return Unique column alias based on generated unique table alias.
+     */
+    private String uniqueColumnAlias(GridSqlColumn col) {
+        GridSqlAlias uniqueTblAlias = (GridSqlAlias)col.expressionInFrom();
 
-        return res;
+        return uniqueTblAlias.alias() + "__" + col.columnName();
     }
 
     /**
-     * @param el Either {@link GridSqlSelect#from()} or {@link GridSqlSelect#where()} elements.
+     * @param tblAliases Table aliases for push down.
+     * @param cols Columns with generated aliases.
+     * @param wrapAlias Alias of the wrap query.
+     * @param select The original select.
      */
-    private static void findAffinityColumnConditions(GridSqlElement el) {
-        if (el == null)
+    private void pushDownWhereConditions(
+        Set<GridSqlAlias> tblAliases,
+        Map<String,GridSqlAlias> cols,
+        GridSqlAlias wrapAlias,
+        GridSqlSelect select
+    ) {
+        if (select.where() == null)
             return;
 
-        el = GridSqlAlias.unwrap(el);
+        GridSqlSelect wrapSelect = wrapAlias.child();
 
-        if (el instanceof GridSqlJoin) {
-            GridSqlJoin join = (GridSqlJoin)el;
+        List<AndCondition> andConditions = new ArrayList<>();
 
-            findAffinityColumnConditions(join.leftTable());
-            findAffinityColumnConditions(join.rightTable());
-            findAffinityColumnConditions(join.on());
+        collectAndConditions(andConditions, select, WHERE_CHILD);
+
+        for (int i = 0; i < andConditions.size(); i++) {
+            AndCondition c = andConditions.get(i);
+            GridSqlAst condition = c.ast();
+
+            if (isAllRelatedToTables(tblAliases, condition)) {
+                if (!isTrue(condition)) {
+                    // Replace the original condition with `true` and move it to the wrap query.
+                    c.prnt.child(c.childIdx, TRUE);
+                    wrapSelect.whereAnd(condition);
+                }
+            }
+            else
+                pushDownColumnsInExpression(tblAliases, cols, wrapAlias, c.prnt, c.childIdx);
         }
-        else if (el instanceof GridSqlOperation) {
-            GridSqlOperationType type = ((GridSqlOperation)el).operationType();
+    }
 
-            switch(type) {
-                case AND:
-                    findAffinityColumnConditions(el.child(0));
-                    findAffinityColumnConditions(el.child(1));
+    /**
+     * @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();
+    }
 
-                    break;
+    /**
+     * @param tblAliases Table aliases for push down.
+     * @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, GridSqlAst ast) {
+        if (ast instanceof GridSqlColumn) {
+            GridSqlColumn col = (GridSqlColumn)ast;
 
-                case EQUAL:
-                    findAffinityColumn(el.child(0));
-                    findAffinityColumn(el.child(1));
+            if (!tblAliases.contains(col.expressionInFrom()))
+                return false;
+        }
+        else {
+            for (int i = 0; i < ast.size(); i++) {
+                if (!isAllRelatedToTables(tblAliases, ast))
+                    return false;
             }
         }
+
+        return true;
     }
 
     /**
-     * @param exp Possible affinity column expression.
+     * @param andConditions Conditions in AND.
+     * @param prnt Parent Parent element.
+     * @param childIdx Child index.
      */
-    private static void findAffinityColumn(GridSqlElement exp) {
-        if (exp instanceof GridSqlColumn) {
-            GridSqlColumn col = (GridSqlColumn)exp;
+    private void collectAndConditions(List<AndCondition> andConditions, GridSqlAst prnt, int childIdx) {
+        GridSqlAst child = prnt.child(childIdx);
 
-            GridSqlElement from = col.expressionInFrom();
+        if (child instanceof GridSqlOperation) {
+            GridSqlOperation op = (GridSqlOperation)child;
 
-            if (from instanceof GridSqlTable) {
-                GridSqlTable fromTbl = (GridSqlTable)from;
+            if (op.operationType() == GridSqlOperationType.AND) {
+                collectAndConditions(andConditions, op, 0);
+                collectAndConditions(andConditions, op, 1);
 
-                GridH2Table tbl = fromTbl.dataTable();
+                return;
+            }
+        }
 
-                if (tbl != null) {
-                    IndexColumn affKeyCol = tbl.getAffinityKeyColumn();
-                    Column expCol = col.column();
+        if (!isTrue(child))
+            andConditions.add(new AndCondition(prnt, childIdx));
+    }
 
-                    if (affKeyCol != null && expCol != null &&
-                        affKeyCol.column.getColumnId() == expCol.getColumnId()) {
-                        // Mark that table lookup will use affinity key.
-                        fromTbl.affinityKeyCondition(true);
-                    }
+    /**
+     * @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();
+
+        assert join.rightTable() == qrym.get(idx).ast();
+
+        return join;
+    }
+
+    /**
+     * @param qrym Query model.
+     */
+    private void splitQueryModel(QueryModel qrym) {
+        switch (qrym.type) {
+            case SELECT:
+                if (qrym.needSplit) {
+                    splitSelect(qrym.prnt, qrym.childIdx);
+
+                    break;
                 }
-            }
+
+                // Intentional fallthrough to go deeper.
+            case UNION:
+                for (int i = 0; i < qrym.size(); i++)
+                    splitQueryModel(qrym.get(i));
+
+                break;
+
+            default:
+                throw new IllegalStateException("Type: " + qrym.type);
         }
     }
 
     /**
-     * @param qry Select.
-     * @return {@code true} If there is at least one partitioned table in FROM clause.
+     * @param qrym Query model.
      */
-    private static boolean hasPartitionedTableInFrom(GridSqlSelect qry) {
-        return findTablesInFrom(qry.from(), new IgnitePredicate<GridSqlElement>() {
-            @Override public boolean apply(GridSqlElement el) {
-                if (el instanceof GridSqlTable) {
-                    GridH2Table tbl = ((GridSqlTable)el).dataTable();
+    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);
 
-                    assert tbl != null : el;
+            analyzeQueryModel(child);
 
-                    GridCacheContext<?,?> cctx = tbl.rowDescriptor().context();
+            // Pull up information about the splitting child.
+            if (child.needSplit || child.needSplitChild)
+                qrym.needSplitChild = true; // We have a child to split.
+        }
 
-                    return !cctx.isLocal() && !cctx.isReplicated();
+        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)
+                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);
+    }
 
-                return false;
+    /**
+     * @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.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 res Resulting two step query.
-     * @param splitIdx Split index.
-     * @param mapQry Map query to be split.
-     * @param params Query parameters.
-     * @param collocatedGroupBy Whether the query has collocated GROUP BY keys.
-     * @return Reduce query for the given map query.
+     * @param select Select to check.
+     * @return {@code true} If we need to split this select.
      */
-    private static GridCacheSqlQuery split(GridCacheTwoStepQuery res, int splitIdx, final GridSqlSelect mapQry,
-        Object[] params, boolean collocatedGroupBy) {
-        final boolean explain = mapQry.explain();
+    private boolean needSplitSelect(GridSqlSelect select) {
+        if (select.distinct())
+            return true;
 
-        mapQry.explain(false);
+        if (collocatedGrpBy)
+            return false;
 
-        GridSqlSelect rdcQry = new GridSqlSelect().from(table(splitIdx));
+        if (select.groupColumns() != null)
+            return true;
 
-        // Split all select expressions into map-reduce parts.
-        List<GridSqlElement> mapExps = new ArrayList<>(mapQry.allColumns());
+        for (int i = 0; i < select.allColumns(); i++) {
+            if (hasAggregates(select.column(i)))
+                return true;
+        }
 
-        mapExps.addAll(mapQry.columns(false));
+        return false;
+    }
+
+    /**
+     * !!! Notice that here we will modify the original query AST in this method.
+     *
+     * @param prnt Parent AST element.
+     * @param childIdx Index of child select.
+     */
+    private void splitSelect(
+        final GridSqlAst prnt,
+        final int childIdx
+    ) {
+        if (++splitId > 99)
+            throw new CacheException("Too complex query to process.");
+
+        final GridSqlSelect mapQry = prnt.child(childIdx);
 
         final int visibleCols = mapQry.visibleColumns();
-        final int havingCol = mapQry.havingColumn();
 
-        List<GridSqlElement> rdcExps = new ArrayList<>(visibleCols);
+        List<GridSqlAst> rdcExps = new ArrayList<>(visibleCols);
+        List<GridSqlAst> mapExps = new ArrayList<>(mapQry.allColumns());
+
+        mapExps.addAll(mapQry.columns(false));
 
         Set<String> colNames = new HashSet<>();
+        final int havingCol = mapQry.havingColumn();
 
         boolean distinctAggregateFound = false;
 
-        if (!collocatedGroupBy) {
+        if (!collocatedGrpBy) {
             for (int i = 0, len = mapExps.size(); i < len; i++)
                 distinctAggregateFound |= hasDistinctAggregates(mapExps.get(i));
         }
 
         boolean aggregateFound = distinctAggregateFound;
 
+        // Split all select expressions into map-reduce parts.
         for (int i = 0, len = mapExps.size(); i < len; i++) // Remember len because mapExps list can grow.
-            aggregateFound |= splitSelectExpression(mapExps, rdcExps, colNames, i, collocatedGroupBy, i == havingCol,
+            aggregateFound |= splitSelectExpression(mapExps, rdcExps, colNames, i, collocatedGrpBy, i == havingCol,
                 distinctAggregateFound);
 
+        assert !(collocatedGrpBy && aggregateFound); // We do not split aggregates when collocatedGrpBy is true.
+
+        // Create reduce query AST. Use unique merge table for this split.
+        GridSqlSelect rdcQry = new GridSqlSelect().from(mergeTable(splitId));
+
         // -- SELECT
         mapQry.clearColumns();
 
-        for (GridSqlElement exp : mapExps) // Add all map expressions as visible.
+        for (GridSqlAst exp : mapExps) // Add all map expressions as visible.
             mapQry.addColumn(exp, true);
 
         for (int i = 0; i < visibleCols; i++) // Add visible reduce columns.
@@ -332,14 +1216,10 @@ public class GridSqlQuerySplitter {
         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);
 
-        // -- FROM
-        findAffinityColumnConditions(mapQry.from());
-
-        // -- WHERE
-        findAffinityColumnConditions(mapQry.where());
+        // -- FROM WHERE: do nothing
 
         // -- GROUP BY
-        if (mapQry.groupColumns() != null && !collocatedGroupBy) {
+        if (mapQry.groupColumns() != null && !collocatedGrpBy) {
             rdcQry.groupColumns(mapQry.groupColumns());
 
             // Grouping with distinct aggregates cannot be performed on map phase
@@ -348,11 +1228,11 @@ public class GridSqlQuerySplitter {
         }
 
         // -- HAVING
-        if (havingCol >= 0 && !collocatedGroupBy) {
+        if (havingCol >= 0 && !collocatedGrpBy) {
             // TODO IGNITE-1140 - Find aggregate functions in HAVING clause or rewrite query to put all aggregates to SELECT clause.
             // We need to find HAVING column in reduce query.
             for (int i = visibleCols; i < rdcQry.allColumns(); i++) {
-                GridSqlElement c = rdcQry.column(i);
+                GridSqlAst c = rdcQry.column(i);
 
                 if (c instanceof GridSqlAlias && HAVING_COLUMN.equals(((GridSqlAlias)c).alias())) {
                     rdcQry.havingColumn(i);
@@ -369,6 +1249,7 @@ public class GridSqlQuerySplitter {
             for (GridSqlSortColumn sortCol : mapQry.sort())
                 rdcQry.addSort(sortCol);
 
+            // If collocatedGrpBy is true, then aggregateFound is always false.
             if (aggregateFound) // Ordering over aggregates does not make sense.
                 mapQry.clearSort(); // Otherwise map sort will be used by offset-limit.
             // TODO IGNITE-1141 - Check if sorting is done over aggregated expression, otherwise we can sort and use offset-limit.
@@ -378,6 +1259,8 @@ public class GridSqlQuerySplitter {
         if (mapQry.limit() != null) {
             rdcQry.limit(mapQry.limit());
 
+            // Will keep limits on map side when collocatedGrpBy is true,
+            // because in this case aggregateFound is always false.
             if (aggregateFound)
                 mapQry.limit(null);
         }
@@ -398,38 +1281,40 @@ public class GridSqlQuerySplitter {
             rdcQry.distinct(true);
         }
 
-        IntArray paramIdxs = new IntArray(params.length);
+        // Replace the given select with generated reduce query in the parent.
+        prnt.child(childIdx, rdcQry);
 
-        GridCacheSqlQuery map = new GridCacheSqlQuery(mapQry.getSQL(),
-            findParams(mapQry, params, new ArrayList<>(params.length), paramIdxs).toArray());
+        // Setup resulting map query.
+        GridCacheSqlQuery map = new GridCacheSqlQuery(mapQry.getSQL());
 
+        setupParameters(map, mapQry, params);
         map.columns(collectColumns(mapExps));
-        map.parameterIndexes(toArray(paramIdxs));
-
-        res.addMapQuery(map);
-
-        res.explain(explain);
 
-        paramIdxs = new IntArray(params.length);
+        mapSqlQrys.add(map);
+    }
 
-        GridCacheSqlQuery rdc = new GridCacheSqlQuery(rdcQry.getSQL(),
-            findParams(rdcQry, params, new ArrayList<>(), paramIdxs).toArray());
+    /**
+     * @param sqlQry Query.
+     * @param qryAst Select AST.
+     * @param params All parameters.
+     */
+    private static void setupParameters(GridCacheSqlQuery sqlQry, GridSqlQuery qryAst, Object[] params) {
+        IntArray paramIdxs = new IntArray(params.length);
 
-        rdc.parameterIndexes(toArray(paramIdxs));
-        res.skipMergeTable(rdcQry.simpleQuery());
+        params = findParams(qryAst, params, new ArrayList<>(params.length), paramIdxs).toArray();
 
-        return rdc;
+        sqlQry.parameters(params, toArray(paramIdxs));
     }
 
     /**
      * @param cols Columns from SELECT clause.
      * @return Map of columns with types.
      */
-    private static LinkedHashMap<String,?> collectColumns(List<GridSqlElement> cols) {
+    private LinkedHashMap<String,?> collectColumns(List<GridSqlAst> cols) {
         LinkedHashMap<String, GridSqlType> res = new LinkedHashMap<>(cols.size(), 1f, false);
 
         for (int i = 0; i < cols.size(); i++) {
-            GridSqlElement col = cols.get(i);
+            GridSqlAst col = cols.get(i);
             GridSqlType t = col.resultType();
 
             if (t == null)
@@ -453,113 +1338,204 @@ 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 {
+        setupConnection(c, distributedJoins, enforceJoinOrder);
+
+        try (PreparedStatement s = c.prepareStatement(qry)) {
+            h2.bindParameters(s, F.asList(params));
+
+            return prepared(s);
+        }
+    }
+
+    /**
      * @param qry Query.
-     * @param schemas Schema names.
-     * @param tbls Tables.
-     * @return Query.
      */
-    private static GridSqlQuery collectAllTables(GridSqlQuery qry, Set<String> schemas, Set<String> tbls) {
+    private void normalizeQuery(GridSqlQuery qry) {
         if (qry instanceof GridSqlUnion) {
             GridSqlUnion union = (GridSqlUnion)qry;
 
-            collectAllTables(union.left(), schemas, tbls);
-            collectAllTables(union.right(), schemas, tbls);
+            normalizeQuery(union.left());
+            normalizeQuery(union.right());
         }
         else {
             GridSqlSelect select = (GridSqlSelect)qry;
 
-            collectAllTablesInFrom(select.from(), schemas, tbls);
+            // Normalize FROM first to update column aliases after.
+            normalizeFrom(select, FROM_CHILD, false);
 
-            for (GridSqlElement el : select.columns(false))
-                collectAllTablesInSubqueries(el, schemas, tbls);
+            List<GridSqlAst> cols = select.columns(false);
 
-            collectAllTablesInSubqueries(select.where(), schemas, tbls);
+            for (int i = 0; i < cols.size(); i++)
+                normalizeExpression(select, childIndexForColumn(i));
+
+            normalizeExpression(select, WHERE_CHILD);
+
+            // ORDER BY and HAVING are in SELECT expressions.
         }
 
-        return qry;
+        normalizeExpression(qry, OFFSET_CHILD);
+        normalizeExpression(qry, LIMIT_CHILD);
     }
 
     /**
-     * @param from From element.
-     * @param schemas Schema names.
-     * @param tbls Tables.
+     * @param prnt Table parent element.
+     * @param childIdx Child index for the table or alias containing the table.
+     * @return Generated alias.
      */
-    private static void collectAllTablesInFrom(GridSqlElement from, final Set<String> schemas, final Set<String> tbls) {
-        findTablesInFrom(from, new IgnitePredicate<GridSqlElement>() {
-            @Override public boolean apply(GridSqlElement el) {
-                if (el instanceof GridSqlTable) {
-                    GridSqlTable tbl = (GridSqlTable)el;
+    private GridSqlAlias generateUniqueAlias(GridSqlAst prnt, int childIdx) {
+        GridSqlAst child = prnt.child(childIdx);
+        GridSqlAst tbl = GridSqlAlias.unwrap(child);
 
-                    String schema = tbl.schema();
+        assert tbl instanceof GridSqlTable || tbl instanceof GridSqlSubquery ||
+            tbl instanceof GridSqlFunction: tbl.getClass();
 
-                    boolean addSchema = tbls == null;
+        String uniqueAlias = nextUniqueTableAlias(tbl != child ? ((GridSqlAlias)child).alias() : null);
 
-                    if (tbls != null)
-                        addSchema = tbls.add(tbl.dataTable().identifier());
+        GridSqlAlias uniqueAliasAst = new GridSqlAlias(uniqueAlias, tbl);
 
-                    if (addSchema && schema != null && schemas != null)
-                        schemas.add(schema);
-                }
-                else if (el instanceof GridSqlSubquery)
-                    collectAllTables(((GridSqlSubquery)el).select(), schemas, tbls);
+        uniqueFromAliases.put(tbl, uniqueAliasAst);
 
-                return false;
-            }
-        });
+        // Replace the child in the parent.
+        prnt.child(childIdx, uniqueAliasAst);
+
+        return uniqueAliasAst;
     }
 
     /**
-     * Processes all the tables and subqueries using the given closure.
-     *
-     * @param from FROM element.
-     * @param c Closure each found table and subquery will be passed to. If returns {@code true} the we need to stop.
-     * @return {@code true} If we have found.
+     * @param origAlias Original alias.
+     * @return Generated unique alias.
      */
-    private static boolean findTablesInFrom(GridSqlElement from, IgnitePredicate<GridSqlElement> c) {
-        if (from == null)
-            return false;
+    private String nextUniqueTableAlias(String origAlias) {
+        String uniqueAlias = UNIQUE_TABLE_ALIAS_SUFFIX + nextTblAliasId++;
 
-        if (from instanceof GridSqlTable || from instanceof GridSqlSubquery)
-            return c.apply(from);
+        if (origAlias != null) // Prepend with the original table alias for better plan readability.
+            uniqueAlias = origAlias + uniqueAlias;
 
-        if (from instanceof GridSqlJoin) {
-            // Left and right.
-            if (findTablesInFrom(from.child(0), c))
-                return true;
+        return uniqueAlias;
+    }
 
-            if (findTablesInFrom(from.child(1), c))
-                return true;
 
-            // We don't process ON condition because it is not a joining part of from here.
-            return false;
+    /**
+     * @param prnt Parent element.
+     * @param childIdx Child index.
+     * @param prntAlias If the parent is {@link GridSqlAlias}.
+     */
+    private void normalizeFrom(GridSqlAst prnt, int childIdx, boolean prntAlias) {
+        GridSqlElement from = prnt.child(childIdx);
+
+        if (from instanceof GridSqlTable) {
+            GridSqlTable tbl = (GridSqlTable)from;
+
+            String schema = tbl.schema();
+
+            boolean addSchema = tbls == null;
+
+            if (tbls != null)
+                addSchema = tbls.add(tbl.dataTable().identifier());
+
+            if (addSchema && schema != null && schemas != null)
+                schemas.add(schema);
+
+            // In case of alias parent we need to replace the alias itself.
+            if (!prntAlias)
+                generateUniqueAlias(prnt, childIdx);
         }
-        else if (from instanceof GridSqlAlias)
-            return findTablesInFrom(from.child(), c);
-        else if (from instanceof GridSqlFunction)
-            return false;
+        else if (from instanceof GridSqlAlias) {
+            // Replace current alias with generated unique alias.
+            normalizeFrom(from, 0, true);
+            generateUniqueAlias(prnt, childIdx);
+        }
+        else if (from instanceof GridSqlSubquery) {
+            // We do not need to wrap simple functional subqueries into filtering function,
+            // because we can not have any other tables than Ignite (which are already filtered)
+            // and functions we have to filter explicitly as well.
+            normalizeQuery(((GridSqlSubquery)from).subquery());
+
+            if (!prntAlias) // H2 generates aliases for subqueries in FROM clause.
+                throw new IllegalStateException("No alias for subquery: " + from.getSQL());
+        }
+        else if (from instanceof GridSqlJoin) {
+            // Left and right.
+            normalizeFrom(from, 0, false);
+            normalizeFrom(from, 1, false);
+
+            // Join condition (after ON keyword).
+            normalizeExpression(from, 2);
+        }
+        else if (from instanceof GridSqlFunction) {
+            // TODO generate filtering function around the given function
+            // TODO SYSTEM_RANGE is a special case, it can not be wrapped
 
-        throw new IllegalStateException(from.getClass().getName() + " : " + from.getSQL());
+            // In case of alias parent we need to replace the alias itself.
+            if (!prntAlias)
+                generateUniqueAlias(prnt, childIdx);
+        }
+        else
+            throw new IllegalStateException(from.getClass().getName() + " : " + from.getSQL());
     }
 
     /**
-     * Searches schema names and tables in subqueries in SELECT and WHERE clauses.
-     *
-     * @param el Element.
-     * @param schemas Schema names.
-     * @param tbls Tables.
+     * @param prnt Parent element.
+     * @param childIdx Child index.
      */
-    private static void collectAllTablesInSubqueries(GridSqlElement el, Set<String> schemas, Set<String> tbls) {
-        if (el == null)
-            return;
+    private void normalizeExpression(GridSqlAst prnt, int childIdx) {
+        GridSqlAst el = prnt.child(childIdx);
+
+        if (el instanceof GridSqlAlias ||
+            el instanceof GridSqlOperation ||
+            el instanceof GridSqlFunction ||
+            el instanceof GridSqlArray) {
+            for (int i = 0; i < el.size(); i++)
+                normalizeExpression(el, i);
+        }
+        else if (el instanceof GridSqlSubquery)
+            normalizeQuery(((GridSqlSubquery)el).subquery());
+        else if (el instanceof GridSqlColumn) {
+            GridSqlColumn col = (GridSqlColumn)el;
+            GridSqlAst tbl = GridSqlAlias.unwrap(col.expressionInFrom());
+
+            // Change table alias part of the column to the generated unique table alias.
+            GridSqlAlias uniqueAlias = uniqueFromAliases.get(tbl);
 
-        el = GridSqlAlias.unwrap(el);
+            // Unique aliases must be generated for all the table filters already.
+            assert uniqueAlias != null: childIdx + "\n" + prnt.getSQL();
 
-        if (el instanceof GridSqlOperation || el instanceof GridSqlFunction) {
-            for (GridSqlElement child : el)
-                collectAllTablesInSubqueries(child, schemas, tbls);
+            col.tableAlias(uniqueAlias.alias());
+            col.expressionInFrom(uniqueAlias);
         }
-        else if (el instanceof GridSqlSubquery)
-            collectAllTables(((GridSqlSubquery)el).select(), schemas, tbls);
+        else if (el instanceof GridSqlParameter ||
+            el instanceof GridSqlPlaceholder ||
+            el instanceof GridSqlConst) {
+            // No-op for simple expressions.
+        }
+        else
+            throw new IllegalStateException(el + ": " + el.getClass());
     }
 
     /**
@@ -586,27 +1562,31 @@ public class GridSqlQuerySplitter {
     }
 
     /**
-     * @param qry Select.
+     * @param select Select.
      * @param params Parameters.
      * @param target Extracted parameters.
      * @param paramIdxs Parameter indexes.
      * @return Extracted parameters list.
      */
-    private static List<Object> findParams(GridSqlSelect qry, Object[] params, ArrayList<Object> target,
-        IntArray paramIdxs) {
+    private static List<Object> findParams(
+        GridSqlSelect select,
+        Object[] params,
+        ArrayList<Object> target,
+        IntArray paramIdxs
+    ) {
         if (params.length == 0)
             return target;
 
-        for (GridSqlElement el : qry.columns(false))
+        for (GridSqlAst el : select.columns(false))
             findParams(el, params, target, paramIdxs);
 
-        findParams(qry.from(), params, target, paramIdxs);
-        findParams(qry.where(), params, target, paramIdxs);
+        findParams(select.from(), params, target, paramIdxs);
+        findParams(select.where(), params, target, paramIdxs);
 
         // Don't search in GROUP BY and HAVING since they expected to be in select list.
 
-        findParams(qry.limit(), params, target, paramIdxs);
-        findParams(qry.offset(), params, target, paramIdxs);
+        findParams(select.limit(), params, target, paramIdxs);
+        findParams(select.offset(), params, target, paramIdxs);
 
         return target;
     }
@@ -617,7 +1597,7 @@ public class GridSqlQuerySplitter {
      * @param target Extracted parameters.
      * @param paramIdxs Parameter indexes.
      */
-    private static void findParams(@Nullable GridSqlElement el, Object[] params, ArrayList<Object> target,
+    private static void findParams(@Nullable GridSqlAst el, Object[] params, ArrayList<Object> target,
         IntArray paramIdxs) {
         if (el == null)
             return;
@@ -644,10 +1624,11 @@ public class GridSqlQuerySplitter {
             paramIdxs.add(idx);
         }
         else if (el instanceof GridSqlSubquery)
-            findParams(((GridSqlSubquery)el).select(), params, target, paramIdxs);
-        else
-            for (GridSqlElement child : el)
-                findParams(child, params, target, paramIdxs);
+            findParams(((GridSqlSubquery)el).subquery(), params, target, paramIdxs);
+        else {
+            for (int i = 0; i < el.size(); i++)
+                findParams(el.child(i), params, target, paramIdxs);
+        }
     }
 
     /**
@@ -655,15 +1636,21 @@ public class GridSqlQuerySplitter {
      * @param rdcSelect Selects for reduce query.
      * @param colNames Set of unique top level column names.
      * @param idx Index.
-     * @param collocated If it is a collocated query.
+     * @param collocatedGrpBy If it is a collocated GROUP BY query.
      * @param isHaving If it is a HAVING expression.
      * @param hasDistinctAggregate If query has distinct aggregate expression.
      * @return {@code true} If aggregate was found.
      */
-    private static boolean splitSelectExpression(List<GridSqlElement> mapSelect, List<GridSqlElement> rdcSelect,
-        Set<String> colNames, final int idx, boolean collocated, boolean isHaving, boolean hasDistinctAggregate) {
-        GridSqlElement el = mapSelect.get(idx);
-
+    private boolean splitSelectExpression(
+        List<GridSqlAst> mapSelect,
+        List<GridSqlAst> rdcSelect,
+        Set<String> colNames,
+        final int idx,
+        boolean collocatedGrpBy,
+        boolean isHaving,
+        boolean hasDistinctAggregate
+    ) {
+        GridSqlAst el = mapSelect.get(idx);
         GridSqlAlias alias = null;
 
         boolean aggregateFound = false;
@@ -673,7 +1660,7 @@ public class GridSqlQuerySplitter {
             el = alias.child();
         }
 
-        if (!collocated && hasAggregates(el)) {
+        if (!collocatedGrpBy && hasAggregates(el)) {
             aggregateFound = true;
 
             if (alias == null)
@@ -722,12 +1709,12 @@ public class GridSqlQuerySplitter {
      * @param el Expression.
      * @return {@code true} If expression contains aggregates.
      */
-    private static boolean hasAggregates(GridSqlElement el) {
+    private static boolean hasAggregates(GridSqlAst el) {
         if (el instanceof GridSqlAggregateFunction)
             return true;
 
-        for (GridSqlElement child : el) {
-            if (hasAggregates(child))
+        for (int i = 0; i < el.size(); i++) {
+            if (hasAggregates(el.child(i)))
                 return true;
         }
 
@@ -741,15 +1728,15 @@ public class GridSqlQuerySplitter {
      * @param el Expression.
      * @return {@code true} If expression contains distinct aggregates.
      */
-    private static boolean hasDistinctAggregates(GridSqlElement el) {
+    private static boolean hasDistinctAggregates(GridSqlAst el) {
         if (el instanceof GridSqlAggregateFunction) {
             GridSqlFunctionType type = ((GridSqlAggregateFunction)el).type();
 
             return ((GridSqlAggregateFunction)el).distinct() && type != MIN && type != MAX;
         }
 
-        for (GridSqlElement child : el) {
-            if (hasDistinctAggregates(child))
+        for (int i = 0; i < el.size(); i++) {
+            if (hasDistinctAggregates(el.child(i)))
                 return true;
         }
 
@@ -765,14 +1752,15 @@ public class GridSqlQuerySplitter {
      * @param first If the first aggregate is already found in this expression.
      * @return {@code true} If the first aggregate is already found.
      */
-    private static boolean splitAggregates(
-        final GridSqlElement parentExpr,
+    private boolean splitAggregates(
+        final GridSqlAst parentExpr,
         final int childIdx,
-        final List<GridSqlElement> mapSelect,
+        final List<GridSqlAst> mapSelect,
         final int exprIdx,
         boolean hasDistinctAggregate,
-        boolean first) {
-        GridSqlElement el = parentExpr.child(childIdx);
+        boolean first
+    ) {
+        GridSqlAst el = parentExpr.child(childIdx);
 
         if (el instanceof GridSqlAggregateFunction) {
             splitAggregate(parentExpr, childIdx, mapSelect, exprIdx, hasDistinctAggregate, first);
@@ -796,10 +1784,10 @@ public class GridSqlQuerySplitter {
      * @param hasDistinctAggregate If query has distinct aggregate expression.
      * @param first If this is the first aggregate found in this expression.
      */
-    private static void splitAggregate(
-        GridSqlElement parentExpr,
+    private void splitAggregate(
+        GridSqlAst parentExpr,
         int aggIdx,
-        List<GridSqlElement> mapSelect,
+        List<GridSqlAst> mapSelect,
         int exprIdx,
         boolean hasDistinctAggregate,
         boolean first
@@ -925,7 +1913,7 @@ public class GridSqlQuerySplitter {
      * @return Column.
      */
     private static GridSqlColumn column(String name) {
-        return new GridSqlColumn(null, null, name, name);
+        return new GridSqlColumn(null, null, null, null, name);
     }
 
     /**
@@ -933,7 +1921,7 @@ public class GridSqlQuerySplitter {
      * @param child Child.
      * @return Alias.
      */
-    private static GridSqlAlias alias(String alias, GridSqlElement child) {
+    private static GridSqlAlias alias(String alias, GridSqlAst child) {
         GridSqlAlias res = new GridSqlAlias(alias, child);
 
         res.resultType(child.resultType());
@@ -947,7 +1935,7 @@ public class GridSqlQuerySplitter {
      * @param right Right expression.
      * @return Binary operator.
      */
-    private static GridSqlOperation op(GridSqlOperationType type, GridSqlElement left, GridSqlElement right) {
+    private static GridSqlOperation op(GridSqlOperationType type, GridSqlAst left, GridSqlAst right) {
         return new GridSqlOperation(type, left, right);
     }
 
@@ -958,4 +1946,95 @@ public class GridSqlQuerySplitter {
     private static GridSqlFunction function(GridSqlFunctionType type) {
         return new GridSqlFunction(type);
     }
+
+    /**
+     * 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> {
+        /** */
+        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. */
+        boolean needSplit;
+
+        /** If we have a child SELECT that we should split. */
+        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.
+         */
+        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;
+        }
+    }
+
+    /**
+     * 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.
+         */
+        private <X extends GridSqlAst> X ast() {
+            return prnt.child(childIdx);
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/3737407b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSelect.java
----------------------------------------------------------------------
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 f49a714..bfa0089 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
@@ -27,23 +27,90 @@ import org.h2.util.StringUtils;
  */
 public class GridSqlSelect extends GridSqlQuery {
     /** */
-    private List<GridSqlElement> cols = new ArrayList<>();
+    public static final int FROM_CHILD = 2;
+
+    /** */
+    public static final int WHERE_CHILD = 3;
+
+    /** */
+    private static final int COLS_CHILD = 4;
+
+    /** */
+    private List<GridSqlAst> cols = new ArrayList<>();
 
     /** */
     private int visibleCols;
 
     /** */
+    private boolean distinct;
+
+    /** */
     private int[] grpCols;
 
     /** */
-    private GridSqlElement from;
+    private GridSqlAst from;
 
     /** */
-    private GridSqlElement where;
+    private GridSqlAst where;
 
     /** */
     private int havingCol = -1;
 
+    /**
+     * @param colIdx Column index as for {@link #column(int)}.
+     * @return Child index for {@link #child(int)}.
+     */
+    public static int childIndexForColumn(int colIdx) {
+        return colIdx + COLS_CHILD;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int size() {
+        return 4 + cols.size(); // + FROM + WHERE + OFFSET + LIMIT
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public <E extends GridSqlAst> E child(int childIdx) {
+        if (childIdx < FROM_CHILD)
+            return super.child(childIdx);
+
+        switch (childIdx) {
+            case FROM_CHILD:
+                return maskNull(from, GridSqlPlaceholder.EMPTY);
+
+            case WHERE_CHILD:
+                return maskNull(where, GridSqlConst.TRUE);
+
+            default:
+                return (E)cols.get(childIdx - COLS_CHILD);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public <E extends GridSqlAst> void child(int childIdx, E child) {
+        if (childIdx < FROM_CHILD) {
+            super.child(childIdx, child);
+
+            return;
+        }
+
+        switch (childIdx) {
+            case FROM_CHILD:
+                from = child;
+
+                break;
+
+            case WHERE_CHILD:
+                where = child;
+
+                break;
+
+            default:
+                cols.set(childIdx - COLS_CHILD, child);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public int visibleColumns() {
         return visibleCols;
@@ -57,7 +124,7 @@ public class GridSqlSelect extends GridSqlQuery {
     }
 
     /** {@inheritDoc} */
-    @Override protected GridSqlElement column(int col) {
+    @Override protected GridSqlAst column(int col) {
         return cols.get(col);
     }
 
@@ -68,7 +135,7 @@ public class GridSqlSelect extends GridSqlQuery {
         if (distinct)
             buff.append(" DISTINCT");
 
-        for (GridSqlElement expression : columns(true)) {
+        for (GridSqlAst expression : columns(true)) {
             buff.appendExceptFirst(",");
             buff.append('\n');
             buff.append(expression.getSQL());
@@ -107,18 +174,18 @@ 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.
      */
-    public boolean simpleQuery() {
+    @Override public boolean simpleQuery() {
         boolean simple = !distinct &&
             from instanceof GridSqlTable &&
             where == null &&
             grpCols == null &&
             havingCol < 0 &&
             sort.isEmpty() &&
-            limit == null &&
-            offset == null;
+            limit() == null &&
+            offset() == null;
 
         if (simple) {
-            for (GridSqlElement expression : columns(true)) {
+            for (GridSqlAst expression : columns(true)) {
                 if (expression instanceof GridSqlAlias)
                     expression = expression.child();
 
@@ -134,7 +201,7 @@ public class GridSqlSelect extends GridSqlQuery {
      * @param buff Statement builder.
      * @param exp Alias expression.
      */
-    private static void addAlias(StatementBuilder buff, GridSqlElement exp) {
+    private static void addAlias(StatementBuilder buff, GridSqlAst exp) {
         exp = GridSqlAlias.unwrap(exp);
 
         buff.append(StringUtils.unEnclose(exp.getSQL()));
@@ -144,7 +211,7 @@ public class GridSqlSelect extends GridSqlQuery {
      * @param visibleOnly If only visible expressions needed.
      * @return Select clause expressions.
      */
-    public List<GridSqlElement> columns(boolean visibleOnly) {
+    public List<GridSqlAst> columns(boolean visibleOnly) {
         assert visibleCols <= cols.size();
 
         return visibleOnly && visibleCols != cols.size() ?
@@ -167,7 +234,7 @@ public class GridSqlSelect extends GridSqlQuery {
      * @param visible Expression is visible in select phrase.
      * @return {@code this}.
      */
-    public GridSqlSelect addColumn(GridSqlElement expression, boolean visible) {
+    public GridSqlSelect addColumn(GridSqlAst expression, boolean visible) {
         if (expression == null)
             throw new NullPointerException();
 
@@ -188,7 +255,7 @@ public class GridSqlSelect extends GridSqlQuery {
      * @param expression Expression.
      * @return {@code this}.
      */
-    public GridSqlSelect setColumn(int colIdx, GridSqlElement expression) {
+    public GridSqlSelect setColumn(int colIdx, GridSqlAst expression) {
         if (expression == null)
             throw new NullPointerException();
 
@@ -217,7 +284,7 @@ public class GridSqlSelect extends GridSqlQuery {
     /**
      * @return Tables.
      */
-    public GridSqlElement from() {
+    public GridSqlAst from() {
         return from;
     }
 
@@ -225,16 +292,30 @@ public class GridSqlSelect extends GridSqlQuery {
      * @param from From element.
      * @return {@code this}.
      */
-    public GridSqlSelect from(GridSqlElement from) {
+    public GridSqlSelect from(GridSqlAst from) {
         this.from = from;
 
         return this;
     }
 
     /**
+     * @return Distinct.
+     */
+    public boolean distinct() {
+        return distinct;
+    }
+
+    /**
+     * @param distinct New distinct.
+     */
+    public void distinct(boolean distinct) {
+        this.distinct = distinct;
+    }
+
+    /**
      * @return Where.
      */
-    public GridSqlElement where() {
+    public GridSqlAst where() {
         return where;
     }
 
@@ -242,7 +323,7 @@ public class GridSqlSelect extends GridSqlQuery {
      * @param where New where.
      * @return {@code this}.
      */
-    public GridSqlSelect where(GridSqlElement where) {
+    public GridSqlSelect where(GridSqlAst where) {
         this.where = where;
 
         return this;
@@ -252,11 +333,11 @@ public class GridSqlSelect extends GridSqlQuery {
      * @param cond Adds new WHERE condition using AND operator.
      * @return {@code this}.
      */
-    public GridSqlSelect whereAnd(GridSqlElement cond) {
+    public GridSqlSelect whereAnd(GridSqlAst cond) {
         if (cond == null)
             throw new NullPointerException();
 
-        GridSqlElement old = where();
+        GridSqlAst old = where();
 
         where(old == null ? cond : new GridSqlOperation(GridSqlOperationType.AND, old, cond));
 
@@ -266,7 +347,7 @@ public class GridSqlSelect extends GridSqlQuery {
     /**
      * @return Having.
      */
-    public GridSqlElement having() {
+    public GridSqlAst having() {
         return havingCol >= 0 ? column(havingCol) : null;
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/3737407b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlStatement.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlStatement.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlStatement.java
index 6eda0d7..21cf596 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlStatement.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlStatement.java
@@ -22,7 +22,7 @@ package org.apache.ignite.internal.processors.query.h2.sql;
  */
 public abstract class GridSqlStatement {
     /** */
-    protected GridSqlElement limit;
+    protected GridSqlAst limit;
     /** */
     private boolean explain;
 
@@ -51,14 +51,14 @@ public abstract class GridSqlStatement {
     /**
      * @param limit Limit.
      */
-    public void limit(GridSqlElement limit) {
+    public void limit(GridSqlAst limit) {
         this.limit = limit;
     }
 
     /**
      * @return Limit.
      */
-    public GridSqlElement limit() {
+    public GridSqlAst limit() {
         return limit;
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/3737407b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSubquery.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSubquery.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSubquery.java
index 7eb9eab..887e427 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSubquery.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/sql/GridSqlSubquery.java
@@ -17,40 +17,31 @@
 
 package org.apache.ignite.internal.processors.query.h2.sql;
 
-import java.util.Collections;
+import java.util.ArrayList;
+
 
 /**
- * Subquery.
+ * Subquery expression.
  */
 public class GridSqlSubquery extends GridSqlElement {
-    /** */
-    private GridSqlQuery select;
-
     /**
-     * @param select Select.
+     * @param subQry Subquery.
      */
-    public GridSqlSubquery(GridSqlQuery select) {
-        super(Collections.<GridSqlElement>emptyList());
+    public GridSqlSubquery(GridSqlQuery subQry) {
+        super(new ArrayList<GridSqlAst>(1));
 
-        this.select = select;
+        addChild(subQry);
     }
 
     /** {@inheritDoc} */
     @Override public String getSQL() {
-        return "(" + select.getSQL() + ")";
-    }
-
-    /**
-     * @return Select.
-     */
-    public GridSqlQuery select() {
-        return select;
+        return "(" + subquery().getSQL() + ")";
     }
 
     /**
-     * @param select New select.
+     * @return Subquery AST.
      */
-    public void select(GridSqlQuery select) {
-        this.select = select;
+    public GridSqlQuery subquery() {
+        return child(0);
     }
 }
\ No newline at end of file


Mime
View raw message