Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 3B16A200C30 for ; Mon, 20 Feb 2017 20:10:03 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 38963160B58; Mon, 20 Feb 2017 19:10:03 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 254DA160B73 for ; Mon, 20 Feb 2017 20:10:00 +0100 (CET) Received: (qmail 76741 invoked by uid 500); 20 Feb 2017 19:10:00 -0000 Mailing-List: contact commits-help@ignite.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ignite.apache.org Delivered-To: mailing list commits@ignite.apache.org Received: (qmail 76706 invoked by uid 99); 20 Feb 2017 19:10:00 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 20 Feb 2017 19:10:00 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 1743DDFAAF; Mon, 20 Feb 2017 19:10:00 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sergi@apache.org To: commits@ignite.apache.org Date: Mon, 20 Feb 2017 19:10:01 -0000 Message-Id: In-Reply-To: <19adf94cd3a447eb98f02a079b160794@git.apache.org> References: <19adf94cd3a447eb98f02a079b160794@git.apache.org> X-Mailer: ASF-Git Admin Mailer 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 archived-at: Mon, 20 Feb 2017 19:10:03 -0000 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 schemas = new HashSet<>(); + + /** */ + private Set tbls = new HashSet<>(); + + /** */ + private boolean rdcQrySimple; + + /** */ + private GridCacheSqlQuery rdcSqlQry; + + /** */ + private List mapSqlQrys = new ArrayList<>(); + + /** */ + private Object[] params; + + /** */ + private boolean collocatedGrpBy; + + /** */ + private IdentityHashMap 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 andConditions = new ArrayList<>(); + + collectAndConditions(andConditions, join, ON_CHILD); + collectAndConditions(andConditions, qrym.ast(), WHERE_CHILD); + + // Collect all the JOIN condition columns for sorting. + List leftOrder = new ArrayList<>(); + List 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 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 tblAliases = newIdentityHashSet(); + Map 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 tblAliases, + Map 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 tblAliases, + Map 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 tblAliases, + Map cols, + GridSqlAlias wrapAlias, + GridSqlAst prnt, + int childIdx + ) { + GridSqlAst child = prnt.child(childIdx); - Set tbls = new HashSet<>(); - Set 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 tblAliases, + Map 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 tblAliases, + Map 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 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 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 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 Set newIdentityHashSet() { + return Collections.newSetFromMap(new IdentityHashMap()); + } + + /** + * @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.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() { - @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.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 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 rdcExps = new ArrayList<>(visibleCols); + List rdcExps = new ArrayList<>(visibleCols); + List mapExps = new ArrayList<>(mapQry.allColumns()); + + mapExps.addAll(mapQry.columns(false)); Set 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 collectColumns(List cols) { + private LinkedHashMap collectColumns(List cols) { LinkedHashMap 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 schemas, Set 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 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 schemas, final Set tbls) { - findTablesInFrom(from, new IgnitePredicate() { - @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 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 schemas, Set 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 findParams(GridSqlSelect qry, Object[] params, ArrayList target, - IntArray paramIdxs) { + private static List findParams( + GridSqlSelect select, + Object[] params, + ArrayList 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 target, + private static void findParams(@Nullable GridSqlAst el, Object[] params, ArrayList 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 mapSelect, List rdcSelect, - Set colNames, final int idx, boolean collocated, boolean isHaving, boolean hasDistinctAggregate) { - GridSqlElement el = mapSelect.get(idx); - + private boolean splitSelectExpression( + List mapSelect, + List rdcSelect, + Set 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 mapSelect, + final List 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 mapSelect, + List 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 { + /** */ + 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 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 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 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 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 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 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 columns(boolean visibleOnly) { + public List 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.emptyList()); + public GridSqlSubquery(GridSqlQuery subQry) { + super(new ArrayList(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