This is an automated email from the ASF dual-hosted git repository. lingmiao pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-doris.git
The following commit(s) were added to refs/heads/master by this push: new ed95352 support intersect and except syntax (#2882) ed95352 is described below commit ed95352ecdb8363063d9ba1cd6efe05ab99d90e8 Author: yangzhg <780531...@qq.com> AuthorDate: Thu Feb 13 16:48:46 2020 +0800 support intersect and except syntax (#2882) --- fe/src/main/cup/sql_parser.cup | 101 +++++----- .../java/org/apache/doris/analysis/Analyzer.java | 2 +- .../{UnionStmt.java => SetOperationStmt.java} | 204 +++++++++++---------- .../org/apache/doris/analysis/StmtRewriter.java | 8 +- .../apache/doris/planner/SingleNodePlanner.java | 56 +++--- fe/src/main/jflex/sql_scanner.flex | 3 + .../doris/analysis/SetOperationStmtTest.java | 64 +++++++ 7 files changed, 269 insertions(+), 169 deletions(-) diff --git a/fe/src/main/cup/sql_parser.cup b/fe/src/main/cup/sql_parser.cup index cb050c2..bfc5bf3 100644 --- a/fe/src/main/cup/sql_parser.cup +++ b/fe/src/main/cup/sql_parser.cup @@ -23,8 +23,9 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import org.apache.doris.analysis.UnionStmt.Qualifier; -import org.apache.doris.analysis.UnionStmt.UnionOperand; +import org.apache.doris.analysis.SetOperationStmt.Qualifier; +import org.apache.doris.analysis.SetOperationStmt.Operation; +import org.apache.doris.analysis.SetOperationStmt.SetOperand; import org.apache.doris.catalog.AccessPrivilege; import org.apache.doris.catalog.AggregateType; import org.apache.doris.catalog.Column; @@ -200,17 +201,18 @@ terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_A KW_CONFIG, KW_CONNECTION, KW_CONNECTION_ID, KW_CONSISTENT, KW_COUNT, KW_CREATE, KW_CROSS, KW_CUBE, KW_CURRENT, KW_CURRENT_USER, KW_DATA, KW_DATABASE, KW_DATABASES, KW_DATE, KW_DATETIME, KW_DAY, KW_DECIMAL, KW_DECOMMISSION, KW_DEFAULT, KW_DESC, KW_DESCRIBE, KW_DELETE, KW_DISTINCT, KW_DISTINCTPC, KW_DISTINCTPCSA, KW_DISTRIBUTED, KW_DISTRIBUTION, KW_DYNAMIC, KW_BUCKETS, KW_DIV, KW_DOUBLE, KW_DROP, KW_DROPP, KW_DUPLICATE, - KW_ELSE, KW_END, KW_ENGINE, KW_ENGINES, KW_ENTER, KW_ERRORS, KW_EVENTS, KW_EXISTS, KW_EXPORT, KW_EXTERNAL, KW_EXTRACT, + KW_ELSE, KW_END, KW_ENGINE, KW_ENGINES, KW_ENTER, KW_ERRORS, KW_EVENTS, KW_EXCEPT, KW_EXISTS, KW_EXPORT, + KW_EXTERNAL, KW_EXTRACT, KW_FALSE, KW_FOLLOWER, KW_FOLLOWING, KW_FREE, KW_FROM, KW_FILE, KW_FIRST, KW_FLOAT, KW_FOR, KW_FORMAT, KW_FRONTEND, KW_FRONTENDS, KW_FULL, KW_FUNCTION, KW_FUNCTIONS, KW_GLOBAL, KW_GRANT, KW_GRANTS, KW_GROUP, KW_GROUPING, KW_HASH, KW_HAVING, KW_HELP,KW_HLL, KW_HLL_UNION, KW_HOUR, KW_HUB, KW_IDENTIFIED, KW_IF, KW_IN, KW_INDEX, KW_INDEXES, KW_INFILE, - KW_INNER, KW_INSERT, KW_INT, KW_INTERMEDIATE, KW_INTERVAL, KW_INTO, KW_IS, KW_ISNULL, KW_ISOLATION, + KW_INNER, KW_INSERT, KW_INT, KW_INTERMEDIATE, KW_INTERSECT, KW_INTERVAL, KW_INTO, KW_IS, KW_ISNULL, KW_ISOLATION, KW_JOIN, KW_KEY, KW_KILL, KW_LABEL, KW_LARGEINT, KW_LAST, KW_LEFT, KW_LESS, KW_LEVEL, KW_LIKE, KW_LIMIT, KW_LINK, KW_LOAD, KW_LOCAL, KW_LOCATION, - KW_MAX, KW_MAX_VALUE, KW_MERGE, KW_MIN, KW_MINUTE, KW_MIGRATE, KW_MIGRATIONS, KW_MODIFY, KW_MONTH, + KW_MAX, KW_MAX_VALUE, KW_MERGE, KW_MIN, KW_MINUTE, KW_MINUS, KW_MIGRATE, KW_MIGRATIONS, KW_MODIFY, KW_MONTH, KW_NAME, KW_NAMES, KW_NEGATIVE, KW_NO, KW_NOT, KW_NULL, KW_NULLS, KW_OBSERVER, KW_OFFSET, KW_ON, KW_ONLY, KW_OPEN, KW_OR, KW_ORDER, KW_OUTER, KW_OVER, KW_PARTITION, KW_PARTITIONS, KW_PASSWORD, KW_PATH, KW_PAUSE, KW_PIPE, KW_PRECEDING, @@ -276,14 +278,14 @@ nonterminal String quantity; // Description of user nonterminal UserDesc grant_user; -// Select or union statement. +// Select or set operation(union/intersect/except) statement. nonterminal QueryStmt query_stmt; // Single select_stmt or parenthesized query_stmt. -nonterminal QueryStmt union_operand; -// List of select or union blocks connected by UNION operators or a single select block. -nonterminal List<UnionOperand> union_operand_list; -// List of select blocks connected by UNION operators, with order by or limit. -nonterminal QueryStmt union_with_order_by_or_limit; +nonterminal QueryStmt set_operand; +// List of select or set operation(union/intersect/except) blocks connected by set operators or a single select block. +nonterminal List<SetOperand> set_operand_list; +// List of select blocks connected by set operators, with order by or limit. +nonterminal QueryStmt set_operation_with_order_by_or_limit; nonterminal InsertStmt insert_stmt; nonterminal InsertTarget insert_target; nonterminal InsertSource insert_source; @@ -342,7 +344,8 @@ nonterminal JoinOperator join_operator; nonterminal ArrayList<String> opt_plan_hints; nonterminal ArrayList<String> opt_sort_hints; nonterminal Expr sign_chain_expr; -nonterminal Qualifier union_op; +nonterminal Qualifier opt_set_qualifier; +nonterminal Operation set_op; nonterminal ArrayList<String> opt_common_hints; nonterminal ArrayList<PartitionName> opt_partition_name_list, partition_name_list; @@ -2406,21 +2409,21 @@ delete_stmt ::= // even if the union has order by and limit. // ORDER BY and LIMIT bind to the preceding select statement by default. query_stmt ::= - opt_with_clause:w union_operand_list:operands + opt_with_clause:w set_operand_list:operands {: QueryStmt queryStmt = null; if (operands.size() == 1) { queryStmt = operands.get(0).getQueryStmt(); } else { - queryStmt = new UnionStmt(operands, null, LimitElement.NO_LIMIT); + queryStmt = new SetOperationStmt(operands, null, LimitElement.NO_LIMIT); } queryStmt.setWithClause(w); RESULT = queryStmt; :} - | opt_with_clause:w union_with_order_by_or_limit:union + | opt_with_clause:w set_operation_with_order_by_or_limit:set_operation {: - union.setWithClause(w); - RESULT = union; + set_operation.setWithClause(w); + RESULT = set_operation; :} ; @@ -2465,80 +2468,80 @@ with_view_def_list ::= // making this issue unresolvable. // We rely on the left precedence of KW_ORDER, KW_BY, and KW_LIMIT, // to resolve the ambiguity with select_stmt in favor of select_stmt -// (i.e., ORDER BY and LIMIT bind to the select_stmt by default, and not the union). -// There must be at least two union operands for ORDER BY or LIMIT to bind to a union, +// (i.e., ORDER BY and LIMIT bind to the select_stmt by default, and not the set operation). +// There must be at least two set operands for ORDER BY or LIMIT to bind to a set operation, // and we manually throw a parse error if we reach this production // with only a single operand. -union_with_order_by_or_limit ::= - union_operand_list:operands +set_operation_with_order_by_or_limit ::= + set_operand_list:operands KW_LIMIT INTEGER_LITERAL:limit {: if (operands.size() == 1) { parser.parseError("limit", SqlParserSymbols.KW_LIMIT); } - RESULT = new UnionStmt(operands, null, new LimitElement(limit.longValue())); + RESULT = new SetOperationStmt(operands, null, new LimitElement(limit.longValue())); :} | - union_operand_list:operands + set_operand_list:operands KW_LIMIT INTEGER_LITERAL:offset COMMA INTEGER_LITERAL:limit {: if (operands.size() == 1) { parser.parseError("limit", SqlParserSymbols.KW_LIMIT); } - RESULT = new UnionStmt(operands, null, new LimitElement(offset.longValue(), limit.longValue())); + RESULT = new SetOperationStmt(operands, null, new LimitElement(offset.longValue(), limit.longValue())); :} | - union_operand_list:operands + set_operand_list:operands KW_LIMIT INTEGER_LITERAL:limit KW_OFFSET INTEGER_LITERAL:offset {: if (operands.size() == 1) { parser.parseError("limit", SqlParserSymbols.KW_LIMIT); } - RESULT = new UnionStmt(operands, null, new LimitElement(offset.longValue(), limit.longValue())); + RESULT = new SetOperationStmt(operands, null, new LimitElement(offset.longValue(), limit.longValue())); :} | - union_operand_list:operands + set_operand_list:operands KW_ORDER KW_BY order_by_elements:orderByClause {: if (operands.size() == 1) { parser.parseError("order", SqlParserSymbols.KW_ORDER); } - RESULT = new UnionStmt(operands, orderByClause, LimitElement.NO_LIMIT); + RESULT = new SetOperationStmt(operands, orderByClause, LimitElement.NO_LIMIT); :} | - union_operand_list:operands + set_operand_list:operands KW_ORDER KW_BY order_by_elements:orderByClause KW_LIMIT INTEGER_LITERAL:limit {: if (operands.size() == 1) { parser.parseError("order", SqlParserSymbols.KW_ORDER); } - RESULT = new UnionStmt(operands, orderByClause, new LimitElement(limit.longValue())); + RESULT = new SetOperationStmt(operands, orderByClause, new LimitElement(limit.longValue())); :} | - union_operand_list:operands + set_operand_list:operands KW_ORDER KW_BY order_by_elements:orderByClause KW_LIMIT INTEGER_LITERAL:offset COMMA INTEGER_LITERAL:limit {: if (operands.size() == 1) { parser.parseError("order", SqlParserSymbols.KW_ORDER); } - RESULT = new UnionStmt(operands, orderByClause, new LimitElement(offset.longValue(), limit.longValue())); + RESULT = new SetOperationStmt(operands, orderByClause, new LimitElement(offset.longValue(), limit.longValue())); :} | - union_operand_list:operands + set_operand_list:operands KW_ORDER KW_BY order_by_elements:orderByClause KW_LIMIT INTEGER_LITERAL:limit KW_OFFSET INTEGER_LITERAL:offset {: if (operands.size() == 1) { parser.parseError("order", SqlParserSymbols.KW_ORDER); } - RESULT = new UnionStmt(operands, orderByClause, new LimitElement(offset.longValue(), limit.longValue())); + RESULT = new SetOperationStmt(operands, orderByClause, new LimitElement(offset.longValue(), limit.longValue())); :} ; -union_operand ::= +set_operand ::= select_stmt:select {: RESULT = select; @@ -2549,26 +2552,36 @@ union_operand ::= :} ; -union_operand_list ::= - union_operand:operand +set_operand_list ::= + set_operand:operand {: - List<UnionOperand> operands = new ArrayList<UnionOperand>(); - operands.add(new UnionOperand(operand, null)); + List<SetOperand> operands = new ArrayList<SetOperand>(); + operands.add(new SetOperand(operand, null, null)); RESULT = operands; :} - | union_operand_list:operands union_op:op union_operand:operand + | set_operand_list:operands set_op:op opt_set_qualifier:qualifier set_operand:operand {: - operands.add(new UnionOperand(operand, op)); + operands.add(new SetOperand(operand, op, qualifier)); RESULT = operands; :} ; -union_op ::= +set_op ::= KW_UNION + {: RESULT = Operation.UNION; :} + | KW_INTERSECT + {: RESULT = Operation.INTERSECT; :} + | KW_EXCEPT + {: RESULT = Operation.EXCEPT; :} + | KW_MINUS + {: RESULT = Operation.EXCEPT; :} + ; + +opt_set_qualifier ::= {: RESULT = Qualifier.DISTINCT; :} - | KW_UNION KW_DISTINCT + | KW_DISTINCT {: RESULT = Qualifier.DISTINCT; :} - | KW_UNION KW_ALL + | KW_ALL {: RESULT = Qualifier.ALL; :} ; diff --git a/fe/src/main/java/org/apache/doris/analysis/Analyzer.java b/fe/src/main/java/org/apache/doris/analysis/Analyzer.java index 728f939..04e0dd7 100644 --- a/fe/src/main/java/org/apache/doris/analysis/Analyzer.java +++ b/fe/src/main/java/org/apache/doris/analysis/Analyzer.java @@ -1425,7 +1425,7 @@ public class Analyzer { * the i-th expr among all expr lists is compatible. * Throw an AnalysisException if the types are incompatible. */ - public void castToUnionCompatibleTypes(List<List<Expr>> exprLists) + public void castToSetOpsCompatibleTypes(List<List<Expr>> exprLists) throws AnalysisException { if (exprLists == null || exprLists.size() < 2) return; diff --git a/fe/src/main/java/org/apache/doris/analysis/UnionStmt.java b/fe/src/main/java/org/apache/doris/analysis/SetOperationStmt.java similarity index 78% rename from fe/src/main/java/org/apache/doris/analysis/UnionStmt.java rename to fe/src/main/java/org/apache/doris/analysis/SetOperationStmt.java index f123db3..597d783 100644 --- a/fe/src/main/java/org/apache/doris/analysis/UnionStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/SetOperationStmt.java @@ -33,21 +33,27 @@ import java.util.List; import java.util.Map; /** - * Representation of a union with its list of operands, and optional order by and limit. - * A union materializes its results, and its resultExprs are SlotRefs into a new + * Representation of a set ops with its list of operands, and optional order by and limit. + * A set ops materializes its results, and its resultExprs are SlotRefs into a new * materialized tuple. * During analysis, the operands are normalized (separated into a single sequence of * DISTINCT followed by a single sequence of ALL operands) and unnested to the extent * possible. This also creates the AggregationInfo for DISTINCT operands. * * Use of resultExprs vs. baseTblResultExprs: - * We consistently use/cast the resultExprs of union operands because the final expr + * We consistently use/cast the resultExprs of set operands because the final expr * substitution happens during planning. The only place where baseTblResultExprs are * used is in materializeRequiredSlots() because that is called before plan generation * and we need to mark the slots of resolved exprs as materialized. */ -public class UnionStmt extends QueryStmt { - private final static Logger LOG = LogManager.getLogger(UnionStmt.class); +public class SetOperationStmt extends QueryStmt { + private final static Logger LOG = LogManager.getLogger(SetOperationStmt.class); + + public enum Operation { + UNION, + INTERSECT, + EXCEPT + } public enum Qualifier { ALL, @@ -57,25 +63,25 @@ public class UnionStmt extends QueryStmt { ///////////////////////////////////////// // BEGIN: Members that need to be reset() - // before analysis, this contains the list of union operands derived verbatim + // before analysis, this contains the list of set operands derived verbatim // from the query; // after analysis, this contains all of distinctOperands followed by allOperands - private final List<UnionOperand> operands; + private final List<SetOperand> operands; // filled during analyze(); contains all operands that need to go through // distinct aggregation - protected final List<UnionOperand> distinctOperands_ = Lists.newArrayList(); + protected final List<SetOperand> distinctOperands_ = Lists.newArrayList(); // filled during analyze(); contains all operands that can be aggregated with // a simple merge without duplicate elimination (also needs to merge the output // of the DISTINCT operands) - protected final List<UnionOperand> allOperands_ = Lists.newArrayList(); + protected final List<SetOperand> allOperands_ = Lists.newArrayList(); private AggregateInfo distinctAggInfo; // only set if we have DISTINCT ops private boolean hasDistinct = false; - // Single tuple materialized by the union. Set in analyze(). + // Single tuple materialized by the set operation. Set in analyze(). private TupleId tupleId; // set prior to unnesting @@ -84,15 +90,15 @@ public class UnionStmt extends QueryStmt { // true if any of the operands_ references an AnalyticExpr private boolean hasAnalyticExprs_ = false; - // List of output expressions produced by the union without the ORDER BY portion + // List of output expressions produced by the set operation without the ORDER BY portion // (if any). Same as resultExprs_ if there is no ORDER BY. - private List<Expr> unionResultExprs_ = Lists.newArrayList(); - + private List<Expr> setOpsResultExprs_ = Lists.newArrayList(); + // END: Members that need to be reset() ///////////////////////////////////////// - public UnionStmt( - List<UnionOperand> operands, + public SetOperationStmt( + List<SetOperand> operands, ArrayList<OrderByElement> orderByElements, LimitElement limitElement) { super(orderByElements, limitElement); @@ -102,17 +108,17 @@ public class UnionStmt extends QueryStmt { /** * C'tor for cloning. */ - protected UnionStmt(UnionStmt other) { + protected SetOperationStmt(SetOperationStmt other) { super(other.cloneOrderByElements(), (other.limitElement == null) ? null : other.limitElement.clone()); operands = Lists.newArrayList(); if (analyzer != null) { - for (UnionOperand o: other.distinctOperands_) distinctOperands_.add(o.clone()); - for (UnionOperand o: other.allOperands_) allOperands_.add(o.clone()); + for (SetOperand o: other.distinctOperands_) distinctOperands_.add(o.clone()); + for (SetOperand o: other.allOperands_) allOperands_.add(o.clone()); operands.addAll(distinctOperands_); operands.addAll(allOperands_); } else { - for (UnionOperand operand: other.operands) operands.add(operand.clone()); + for (SetOperand operand: other.operands) operands.add(operand.clone()); } analyzer = other.analyzer; distinctAggInfo = @@ -121,11 +127,11 @@ public class UnionStmt extends QueryStmt { toSqlString = (other.toSqlString != null) ? new String(other.toSqlString) : null; hasAnalyticExprs_ = other.hasAnalyticExprs_; withClause_ = (other.withClause_ != null) ? other.withClause_.clone() : null; - unionResultExprs_ = Expr.cloneList(other.unionResultExprs_); + setOpsResultExprs_ = Expr.cloneList(other.setOpsResultExprs_); } @Override - public UnionStmt clone() { return new UnionStmt(this); } + public SetOperationStmt clone() { return new SetOperationStmt(this); } /** * Undoes all changes made by analyze() except distinct propagation and unnesting. @@ -137,20 +143,20 @@ public class UnionStmt extends QueryStmt { @Override public void reset() { super.reset(); - for (UnionOperand op: operands) op.reset(); + for (SetOperand op: operands) op.reset(); distinctOperands_.clear(); allOperands_.clear(); distinctAggInfo = null; tupleId = null; toSqlString = null; hasAnalyticExprs_ = false; - unionResultExprs_.clear(); + setOpsResultExprs_.clear(); } - public List<UnionOperand> getOperands() { return operands; } - public List<UnionOperand> getDistinctOperands() { return distinctOperands_; } + public List<SetOperand> getOperands() { return operands; } + public List<SetOperand> getDistinctOperands() { return distinctOperands_; } public boolean hasDistinctOps() { return !distinctOperands_.isEmpty(); } - public List<UnionOperand> getAllOperands() { return allOperands_; } + public List<SetOperand> getAllOperands() { return allOperands_; } public boolean hasAllOps() { return !allOperands_.isEmpty(); } public AggregateInfo getDistinctAggInfo() { return distinctAggInfo; } public boolean hasAnalyticExprs() { return hasAnalyticExprs_; } @@ -161,24 +167,19 @@ public class UnionStmt extends QueryStmt { allOperands_.clear(); } - - public List<UnionOperand> getUnionOperands() { - return operands; - } - - public List<Expr> getUnionResultExprs() { return unionResultExprs_; } + public List<Expr> getSetOpsResultExprs() { return setOpsResultExprs_; } @Override public void getDbs(Analyzer analyzer, Map<String, Database> dbs) throws AnalysisException { getWithClauseDbs(analyzer, dbs); - for (UnionOperand op : operands) { + for (SetOperand op : operands) { op.getQueryStmt().getDbs(analyzer, dbs); } } /** * Propagates DISTINCT from left to right, and checks that all - * union operands are union compatible, adding implicit casts if necessary. + * set operands are set compatible, adding implicit casts if necessary. */ @Override public void analyze(Analyzer analyzer) throws AnalysisException, UserException { @@ -186,6 +187,11 @@ public class UnionStmt extends QueryStmt { super.analyze(analyzer); Preconditions.checkState(operands.size() > 0); + for (SetOperand op : operands) { + if (op.getOperation() != null && op.getOperation() != Operation.UNION) { + throw new AnalysisException("INTERSECT/EXCEPT is not implemented yet."); + } + } // Propagates DISTINCT from left to right, propagateDistinct(); @@ -204,7 +210,7 @@ public class UnionStmt extends QueryStmt { // Compute hasAnalyticExprs_ hasAnalyticExprs_ = false; - for (UnionOperand op: operands) { + for (SetOperand op: operands) { if (op.hasAnalyticExprs()) { hasAnalyticExprs_ = true; break; @@ -213,33 +219,33 @@ public class UnionStmt extends QueryStmt { // Collect all result expr lists and cast the exprs as necessary. List<List<Expr>> resultExprLists = Lists.newArrayList(); - for (UnionOperand op: operands) { + for (SetOperand op: operands) { resultExprLists.add(op.getQueryStmt().getResultExprs()); } - analyzer.castToUnionCompatibleTypes(resultExprLists); + analyzer.castToSetOpsCompatibleTypes(resultExprLists); - // Create tuple descriptor materialized by this UnionStmt, its resultExprs, and + // Create tuple descriptor materialized by this SetOperationStmt, its resultExprs, and // its sortInfo if necessary. createMetadata(analyzer); createSortInfo(analyzer); // Create unnested operands' smaps. - for (UnionOperand operand: operands) setOperandSmap(operand, analyzer); + for (SetOperand operand: operands) setOperandSmap(operand, analyzer); // Create distinctAggInfo, if necessary. if (!distinctOperands_.isEmpty()) { - // Aggregate produces exactly the same tuple as the original union stmt. + // Aggregate produces exactly the same tuple as the original setOp stmt. ArrayList<Expr> groupingExprs = Expr.cloneList(resultExprs); try { distinctAggInfo = AggregateInfo.create( groupingExprs, null, analyzer.getDescTbl().getTupleDesc(tupleId), analyzer); } catch (AnalysisException e) { // Should never happen. - throw new IllegalStateException("Error creating agg info in UnionStmt.analyze()", e); + throw new IllegalStateException("Error creating agg info in SetOperationStmt.analyze()", e); } } - unionResultExprs_ = Expr.cloneList(resultExprs); + setOpsResultExprs_ = Expr.cloneList(resultExprs); if (evaluateOrderBy) createSortTupleInfo(analyzer); baseTblResultExprs = resultExprs; } @@ -266,7 +272,7 @@ public class UnionStmt extends QueryStmt { } /** - * Fill distinct-/allOperands and performs possible unnesting of UnionStmt + * Fill distinct-/allOperands and performs possible unnesting of SetOperationStmt * operands in the process. */ private void unnestOperands(Analyzer analyzer) throws AnalysisException { @@ -279,7 +285,7 @@ public class UnionStmt extends QueryStmt { // find index of first ALL operand int firstUnionAllIdx = operands.size(); for (int i = 1; i < operands.size(); ++i) { - UnionOperand operand = operands.get(i); + SetOperand operand = operands.get(i); if (operand.getQualifier() == Qualifier.ALL) { firstUnionAllIdx = (i == 1 ? 0 : i); break; @@ -301,8 +307,8 @@ public class UnionStmt extends QueryStmt { unnestOperand(allOperands_, Qualifier.ALL, operands.get(i)); } - for (UnionOperand op: distinctOperands_) op.setQualifier(Qualifier.DISTINCT); - for (UnionOperand op: allOperands_) op.setQualifier(Qualifier.ALL); + for (SetOperand op: distinctOperands_) op.setQualifier(Qualifier.DISTINCT); + for (SetOperand op: allOperands_) op.setQualifier(Qualifier.ALL); operands.clear(); operands.addAll(distinctOperands_); @@ -310,11 +316,11 @@ public class UnionStmt extends QueryStmt { } /** - * Add a single operand to the target list; if the operand itself is a UnionStmt, apply + * Add a single operand to the target list; if the operand itself is a SetOperationStmt, apply * unnesting to the extent possible (possibly modifying 'operand' in the process). */ private void unnestOperand( - List<UnionOperand> target, Qualifier targetQualifier, UnionOperand operand) { + List<SetOperand> target, Qualifier targetQualifier, SetOperand operand) { Preconditions.checkState(operand.isAnalyzed()); QueryStmt queryStmt = operand.getQueryStmt(); if (queryStmt instanceof SelectStmt) { @@ -322,30 +328,30 @@ public class UnionStmt extends QueryStmt { return; } - Preconditions.checkState(queryStmt instanceof UnionStmt); - UnionStmt unionStmt = (UnionStmt) queryStmt; - if (unionStmt.hasLimit() || unionStmt.hasOffset()) { - // we must preserve the nested Union + Preconditions.checkState(queryStmt instanceof SetOperationStmt); + SetOperationStmt setOperationStmt = (SetOperationStmt) queryStmt; + if (setOperationStmt.hasLimit() || setOperationStmt.hasOffset()) { + // we must preserve the nested SetOps target.add(operand); - } else if (targetQualifier == Qualifier.DISTINCT || !unionStmt.hasDistinctOps()) { - // there is no limit in the nested Union and we can absorb all of its + } else if (targetQualifier == Qualifier.DISTINCT || !setOperationStmt.hasDistinctOps()) { + // there is no limit in the nested SetOps and we can absorb all of its // operands as-is - target.addAll(unionStmt.getDistinctOperands()); - target.addAll(unionStmt.getAllOperands()); + target.addAll(setOperationStmt.getDistinctOperands()); + target.addAll(setOperationStmt.getAllOperands()); } else { - // the nested Union contains some Distinct ops and we're accumulating + // the nested SetOps contains some Distinct ops and we're accumulating // into our All ops; unnest only the All ops and leave the rest in place - target.addAll(unionStmt.getAllOperands()); - unionStmt.removeAllOperands(); + target.addAll(setOperationStmt.getAllOperands()); + setOperationStmt.removeAllOperands(); target.add(operand); } } /** - * Sets the smap for the given operand. It maps from the output slots this union's + * Sets the smap for the given operand. It maps from the output slots this SetOps's * tuple to the corresponding result exprs of the operand. */ - private void setOperandSmap(UnionOperand operand, Analyzer analyzer) { + private void setOperandSmap(SetOperand operand, Analyzer analyzer) { TupleDescriptor tupleDesc = analyzer.getDescTbl().getTupleDesc(tupleId); // operands' smaps were already set in the operands' analyze() operand.getSmap().clear(); @@ -376,7 +382,7 @@ public class UnionStmt extends QueryStmt { private void propagateDistinct() { int firstDistinctPos = -1; for (int i = operands.size() - 1; i > 0; --i) { - UnionOperand operand = operands.get(i); + SetOperand operand = operands.get(i); if (firstDistinctPos != -1) { // There is a DISTINCT somewhere to the right. operand.setQualifier(Qualifier.DISTINCT); @@ -387,18 +393,18 @@ public class UnionStmt extends QueryStmt { } /** - * Create a descriptor for the tuple materialized by the union. + * Create a descriptor for the tuple materialized by the setOps. * Set resultExprs to be slot refs into that tuple. * Also fills the substitution map, such that "order by" can properly resolve - * column references from the result of the union. + * column references from the result of the setOps. */ private void createMetadata(Analyzer analyzer) throws AnalysisException { - // Create tuple descriptor for materialized tuple created by the union. - TupleDescriptor tupleDesc = analyzer.getDescTbl().createTupleDescriptor("union"); + // Create tuple descriptor for materialized tuple created by the setOps. + TupleDescriptor tupleDesc = analyzer.getDescTbl().createTupleDescriptor("SetOps"); tupleDesc.setIsMaterialized(true); tupleId = tupleDesc.getId(); if (LOG.isTraceEnabled()) { - LOG.trace("UnionStmt.createMetadata: tupleId=" + tupleId.toString()); + LOG.trace("SetOperationStmt.createMetadata: tupleId=" + tupleId.toString()); } // One slot per expr in the select blocks. Use first select block as representative. @@ -447,7 +453,7 @@ public class UnionStmt extends QueryStmt { // to operands' result exprs (if those happen to be slotrefs); // don't do that if the operand computes analytic exprs // (see Planner.createInlineViewPlan() for the reasoning) - for (UnionOperand op: operands) { + for (SetOperand op: operands) { Expr resultExpr = op.getQueryStmt().getResultExprs().get(i); slotDesc.addSourceExpr(resultExpr); SlotRef slotRef = resultExpr.unwrapSlotRef(false); @@ -458,7 +464,7 @@ public class UnionStmt extends QueryStmt { if (slotRef == null) continue; // analyzer.registerValueTransfer(outputSlotRef.getSlotId(), slotRef.getSlotId()); } - // If all the child slots are not nullable, then the union output slot should not + // If all the child slots are not nullable, then the SetOps output slot should not // be nullable as well. slotDesc.setIsNullable(isNullable); } @@ -486,7 +492,7 @@ public class UnionStmt extends QueryStmt { for (int i = 0; i < outputSlots.size(); ++i) { SlotDescriptor slotDesc = outputSlots.get(i); if (!slotDesc.isMaterialized()) continue; - for (UnionOperand op: operands) { + for (SetOperand op: operands) { exprs.add(op.getQueryStmt().getBaseTblResultExprs().get(i)); } if (distinctAggInfo != null) { @@ -497,14 +503,14 @@ public class UnionStmt extends QueryStmt { } materializeSlots(analyzer, exprs); - for (UnionOperand op: operands) { + for (SetOperand op: operands) { op.getQueryStmt().materializeRequiredSlots(analyzer); } } @Override public void rewriteExprs(ExprRewriter rewriter) throws AnalysisException { - for (UnionOperand op: operands) op.getQueryStmt().rewriteExprs(rewriter); + for (SetOperand op: operands) op.getQueryStmt().rewriteExprs(rewriter); if (orderByElements != null) { for (OrderByElement orderByElem: orderByElements) { orderByElem.setExpr(rewriter.rewrite(orderByElem.getExpr(), analyzer)); @@ -524,7 +530,7 @@ public class UnionStmt extends QueryStmt { @Override public void collectTableRefs(List<TableRef> tblRefs) { - for (UnionOperand op: operands) op.getQueryStmt().collectTableRefs(tblRefs); + for (SetOperand op: operands) op.getQueryStmt().collectTableRefs(tblRefs); } @Override @@ -537,20 +543,22 @@ public class UnionStmt extends QueryStmt { strBuilder.append(operands.get(0).getQueryStmt().toSql()); for (int i = 1; i < operands.size() - 1; ++i) { strBuilder.append( - " UNION " + ((operands.get(i).getQualifier() == Qualifier.ALL) ? "ALL " : "")); - if (operands.get(i).getQueryStmt() instanceof UnionStmt) { + " " + operands.get(i).getOperation().toString() + " " + + ((operands.get(i).getQualifier() == Qualifier.ALL) ? "ALL " : "")); + if (operands.get(i).getQueryStmt() instanceof SetOperationStmt) { strBuilder.append("("); } strBuilder.append(operands.get(i).getQueryStmt().toSql()); - if (operands.get(i).getQueryStmt() instanceof UnionStmt) { + if (operands.get(i).getQueryStmt() instanceof SetOperationStmt) { strBuilder.append(")"); } } - // Determine whether we need parenthesis around the last union operand. - UnionOperand lastOperand = operands.get(operands.size() - 1); + // Determine whether we need parenthesis around the last Set operand. + SetOperand lastOperand = operands.get(operands.size() - 1); QueryStmt lastQueryStmt = lastOperand.getQueryStmt(); - strBuilder.append(" UNION " + ((lastOperand.getQualifier() == Qualifier.ALL) ? "ALL " : "")); - if (lastQueryStmt instanceof UnionStmt || ((hasOrderByClause() || hasLimitClause()) && + strBuilder.append(" " + lastOperand.getOperation().toString() + " " + + ((lastOperand.getQualifier() == Qualifier.ALL) ? "ALL " : "")); + if (lastQueryStmt instanceof SetOperationStmt || ((hasOrderByClause() || hasLimitClause()) && !lastQueryStmt.hasLimitClause() && !lastQueryStmt.hasOrderByClause())) { strBuilder.append("("); @@ -584,7 +592,7 @@ public class UnionStmt extends QueryStmt { @Override public void setNeedToSql(boolean needToSql) { super.setNeedToSql(needToSql); - for (UnionOperand operand : operands) { + for (SetOperand operand : operands) { operand.getQueryStmt().setNeedToSql(needToSql); } } @@ -601,14 +609,17 @@ public class UnionStmt extends QueryStmt { } /** - * Represents an operand to a union. It consists of a query statement and its left + * Represents an operand to a SetOperand. It consists of a query statement and its left * all/distinct qualifier (null for the first operand). */ - public static class UnionOperand { + public static class SetOperand { + // Operand indicate this SetOperand is union/intersect/except + private Operation operation; + // Effective qualifier. Should not be reset() to preserve changes made during // distinct propagation and unnesting that are needed after rewriting Subqueries. private Qualifier qualifier_; - + // /////////////////////////////////////// // BEGIN: Members that need to be reset() @@ -618,14 +629,15 @@ public class UnionStmt extends QueryStmt { // We must preserve the conjuncts registered in the analyzer for partition pruning. private Analyzer analyzer; - // Map from UnionStmt's result slots to our resultExprs. Used during plan generation. + // Map from SetOperationStmt's result slots to our resultExprs. Used during plan generation. private final ExprSubstitutionMap smap_; // END: Members that need to be reset() // /////////////////////////////////////// - - public UnionOperand(QueryStmt queryStmt, Qualifier qualifier) { + + public SetOperand(QueryStmt queryStmt, Operation operation, Qualifier qualifier) { this.queryStmt = queryStmt; + this.operation = operation; qualifier_ = qualifier; smap_ = new ExprSubstitutionMap(); } @@ -639,8 +651,15 @@ public class UnionStmt extends QueryStmt { public boolean isAnalyzed() { return analyzer != null; } public QueryStmt getQueryStmt() { return queryStmt; } public Qualifier getQualifier() { return qualifier_; } + public Operation getOperation() { + return operation; + } // Used for propagating DISTINCT. public void setQualifier(Qualifier qualifier) { qualifier_ = qualifier; } + + public void setOperation(Operation operation) { + this.operation =operation; + } public Analyzer getAnalyzer() { return analyzer; } public ExprSubstitutionMap getSmap() { return smap_; } @@ -648,16 +667,17 @@ public class UnionStmt extends QueryStmt { if (queryStmt instanceof SelectStmt) { return ((SelectStmt) queryStmt).hasAnalyticInfo(); } else { - Preconditions.checkState(queryStmt instanceof UnionStmt); - return ((UnionStmt) queryStmt).hasAnalyticExprs(); + Preconditions.checkState(queryStmt instanceof SetOperationStmt); + return ((SetOperationStmt) queryStmt).hasAnalyticExprs(); } } /** * C'tor for cloning. */ - private UnionOperand(UnionOperand other) { + private SetOperand(SetOperand other) { queryStmt = other.queryStmt.clone(); + this.operation = other.operation; qualifier_ = other.qualifier_; analyzer = other.analyzer; smap_ = other.smap_.clone(); @@ -670,8 +690,8 @@ public class UnionStmt extends QueryStmt { } @Override - public UnionOperand clone() { - return new UnionOperand(this); + public SetOperand clone() { + return new SetOperand(this); } } } diff --git a/fe/src/main/java/org/apache/doris/analysis/StmtRewriter.java b/fe/src/main/java/org/apache/doris/analysis/StmtRewriter.java index b48fbbd..1a8c2c3 100644 --- a/fe/src/main/java/org/apache/doris/analysis/StmtRewriter.java +++ b/fe/src/main/java/org/apache/doris/analysis/StmtRewriter.java @@ -69,8 +69,8 @@ public class StmtRewriter { Preconditions.checkNotNull(stmt); if (stmt instanceof SelectStmt) { rewriteSelectStatement((SelectStmt) stmt, analyzer); - } else if (stmt instanceof UnionStmt) { - rewriteUnionStatement((UnionStmt) stmt, analyzer); + } else if (stmt instanceof SetOperationStmt) { + rewriteUnionStatement((SetOperationStmt) stmt, analyzer); } else { throw new AnalysisException("Subqueries not supported for " + stmt.getClass().getSimpleName() + " statements"); @@ -105,9 +105,9 @@ public class StmtRewriter { * Rewrite all operands in a UNION. The conditions that apply to SelectStmt rewriting * also apply here. */ - private static void rewriteUnionStatement(UnionStmt stmt, Analyzer analyzer) + private static void rewriteUnionStatement(SetOperationStmt stmt, Analyzer analyzer) throws AnalysisException { - for (UnionStmt.UnionOperand operand: stmt.getOperands()) { + for (SetOperationStmt.SetOperand operand: stmt.getOperands()) { Preconditions.checkState(operand.getQueryStmt() instanceof SelectStmt); StmtRewriter.rewriteSelectStatement( (SelectStmt)operand.getQueryStmt(), operand.getAnalyzer()); diff --git a/fe/src/main/java/org/apache/doris/planner/SingleNodePlanner.java b/fe/src/main/java/org/apache/doris/planner/SingleNodePlanner.java index 9b013e1..e90028c 100644 --- a/fe/src/main/java/org/apache/doris/planner/SingleNodePlanner.java +++ b/fe/src/main/java/org/apache/doris/planner/SingleNodePlanner.java @@ -39,6 +39,7 @@ import org.apache.doris.analysis.LiteralExpr; import org.apache.doris.analysis.NullLiteral; import org.apache.doris.analysis.QueryStmt; import org.apache.doris.analysis.SelectStmt; +import org.apache.doris.analysis.SetOperationStmt; import org.apache.doris.analysis.SlotDescriptor; import org.apache.doris.analysis.SlotId; import org.apache.doris.analysis.SlotRef; @@ -46,7 +47,6 @@ import org.apache.doris.analysis.TableRef; import org.apache.doris.analysis.TupleDescriptor; import org.apache.doris.analysis.TupleId; import org.apache.doris.analysis.TupleIsNullPredicate; -import org.apache.doris.analysis.UnionStmt; import org.apache.doris.catalog.AggregateFunction; import org.apache.doris.catalog.AggregateType; import org.apache.doris.catalog.Column; @@ -235,8 +235,8 @@ public class SingleNodePlanner { } } } else { - Preconditions.checkState(stmt instanceof UnionStmt); - root = createUnionPlan((UnionStmt) stmt, analyzer, newDefaultOrderByLimit); + Preconditions.checkState(stmt instanceof SetOperationStmt); + root = createSetOperationPlan((SetOperationStmt) stmt, analyzer, newDefaultOrderByLimit); } // Avoid adding a sort node if the sort tuple has no materialized slots. @@ -1175,8 +1175,8 @@ public class SingleNodePlanner { viewAnalyzer.registerConjuncts(newConjuncts, select.getTableRefs().get(0).getDesc().getId().asList()); } } else { - Preconditions.checkArgument(stmt instanceof UnionStmt); - final UnionStmt union = (UnionStmt) stmt; + Preconditions.checkArgument(stmt instanceof SetOperationStmt); + final SetOperationStmt union = (SetOperationStmt) stmt; viewAnalyzer.registerConjuncts(newConjuncts, union.getTupleId().asList()); } } @@ -1204,11 +1204,11 @@ public class SingleNodePlanner { // UnionNode will handle predicates and assigns predicates to it's children. final List<Expr> candicatePredicates = Expr.substituteList(viewPredicates, inlineViewRef.getSmap(), analyzer, false); - if (inlineViewRef.getViewStmt() instanceof UnionStmt) { - final UnionStmt unionStmt = (UnionStmt) inlineViewRef.getViewStmt(); + if (inlineViewRef.getViewStmt() instanceof SetOperationStmt) { + final SetOperationStmt setOperationStmt = (SetOperationStmt) inlineViewRef.getViewStmt(); for (int i = 0; i < candicatePredicates.size(); i++) { final Expr predicate = candicatePredicates.get(i); - if (predicate.isBound(unionStmt.getTupleId())) { + if (predicate.isBound(setOperationStmt.getTupleId())) { pushDownPredicates.add(predicate); } else { pushDownFailedPredicates.add(viewPredicates.get(i)); @@ -1483,12 +1483,12 @@ public class SingleNodePlanner { * as a child of the returned UnionNode. */ private UnionNode createUnionPlan( - Analyzer analyzer, UnionStmt unionStmt, List<UnionStmt.UnionOperand> unionOperands, + Analyzer analyzer, SetOperationStmt setOperationStmt, List<SetOperationStmt.SetOperand> setOperands, PlanNode unionDistinctPlan, long defaultOrderByLimit) throws UserException, AnalysisException { - UnionNode unionNode = new UnionNode(ctx_.getNextNodeId(), unionStmt.getTupleId(), - unionStmt.getUnionResultExprs(), false); - for (UnionStmt.UnionOperand op : unionOperands) { + UnionNode unionNode = new UnionNode(ctx_.getNextNodeId(), setOperationStmt.getTupleId(), + setOperationStmt.getSetOpsResultExprs(), false); + for (SetOperationStmt.SetOperand op : setOperands) { if (op.getAnalyzer().hasEmptyResultSet()) { unmarkCollectionSlots(op.getQueryStmt()); continue; @@ -1512,10 +1512,10 @@ public class SingleNodePlanner { } if (unionDistinctPlan != null) { - Preconditions.checkState(unionStmt.hasDistinctOps()); + Preconditions.checkState(setOperationStmt.hasDistinctOps()); Preconditions.checkState(unionDistinctPlan instanceof AggregationNode); unionNode.addChild(unionDistinctPlan, - unionStmt.getDistinctAggInfo().getGroupingExprs()); + setOperationStmt.getDistinctAggInfo().getGroupingExprs()); } unionNode.init(analyzer); return unionNode; @@ -1537,23 +1537,23 @@ public class SingleNodePlanner { * TODO: Simplify the plan of unions with only a single non-empty operand to not * use a union node (this is tricky because a union materializes a new tuple). */ - private PlanNode createUnionPlan(UnionStmt unionStmt, Analyzer analyzer, long defaultOrderByLimit) + private PlanNode createSetOperationPlan(SetOperationStmt setOperationStmt, Analyzer analyzer, long defaultOrderByLimit) throws UserException, AnalysisException { // TODO(zc): get unassigned conjuncts // List<Expr> conjuncts = // analyzer.getUnassignedConjuncts(unionStmt.getTupleId().asList(), false); - List<Expr> conjuncts = analyzer.getUnassignedConjuncts(unionStmt.getTupleId().asList()); + List<Expr> conjuncts = analyzer.getUnassignedConjuncts(setOperationStmt.getTupleId().asList()); // TODO chenhao // Because Conjuncts can't be assigned to UnionNode and Palo's fe can't evaluate conjuncts, // it needs to add SelectNode as UnionNode's parent, when UnionStmt's Ops contains constant // Select. boolean hasConstantOp = false; - if (!unionStmt.hasAnalyticExprs()) { + if (!setOperationStmt.hasAnalyticExprs()) { // Turn unassigned predicates for unionStmt's tupleId_ into predicates for // the individual operands. // Do this prior to creating the operands' plan trees so they get a chance to // pick up propagated predicates. - for (UnionStmt.UnionOperand op : unionStmt.getOperands()) { + for (SetOperationStmt.SetOperand op : setOperationStmt.getOperands()) { List<Expr> opConjuncts = Expr.substituteList(conjuncts, op.getSmap(), analyzer, false); boolean selectHasTableRef = true; @@ -1570,8 +1570,8 @@ public class SingleNodePlanner { if ((queryStmt instanceof SelectStmt) && selectHasTableRef) { final SelectStmt select = (SelectStmt) queryStmt; op.getAnalyzer().registerConjuncts(opConjuncts, select.getTableRefIds()); - } else if (queryStmt instanceof UnionStmt) { - final UnionStmt union = (UnionStmt) queryStmt; + } else if (queryStmt instanceof SetOperationStmt) { + final SetOperationStmt union = (SetOperationStmt) queryStmt; op.getAnalyzer().registerConjuncts(opConjuncts, union.getTupleId().asList()); } else { if (selectHasTableRef) { @@ -1587,24 +1587,24 @@ public class SingleNodePlanner { analyzer.materializeSlots(conjuncts); } // mark slots after predicate propagation but prior to plan tree generation - unionStmt.materializeRequiredSlots(analyzer); + setOperationStmt.materializeRequiredSlots(analyzer); PlanNode result = null; // create DISTINCT tree - if (unionStmt.hasDistinctOps()) { + if (setOperationStmt.hasDistinctOps()) { result = createUnionPlan( - analyzer, unionStmt, unionStmt.getDistinctOperands(), null, defaultOrderByLimit); - result = new AggregationNode(ctx_.getNextNodeId(), result, unionStmt.getDistinctAggInfo()); + analyzer, setOperationStmt, setOperationStmt.getDistinctOperands(), null, defaultOrderByLimit); + result = new AggregationNode(ctx_.getNextNodeId(), result, setOperationStmt.getDistinctAggInfo()); result.init(analyzer); } // create ALL tree - if (unionStmt.hasAllOps()) { - result = createUnionPlan(analyzer, unionStmt, unionStmt.getAllOperands(), result, defaultOrderByLimit); + if (setOperationStmt.hasAllOps()) { + result = createUnionPlan(analyzer, setOperationStmt, setOperationStmt.getAllOperands(), result, defaultOrderByLimit); } - if (unionStmt.hasAnalyticExprs() || hasConstantOp) { + if (setOperationStmt.hasAnalyticExprs() || hasConstantOp) { result = addUnassignedConjuncts( - analyzer, unionStmt.getTupleId().asList(), result); + analyzer, setOperationStmt.getTupleId().asList(), result); } return result; } diff --git a/fe/src/main/jflex/sql_scanner.flex b/fe/src/main/jflex/sql_scanner.flex index d5b4942..aba90b4 100644 --- a/fe/src/main/jflex/sql_scanner.flex +++ b/fe/src/main/jflex/sql_scanner.flex @@ -171,6 +171,7 @@ import org.apache.doris.qe.SqlModeHelper; keywordMap.put("enter", new Integer(SqlParserSymbols.KW_ENTER)); keywordMap.put("errors", new Integer(SqlParserSymbols.KW_ERRORS)); keywordMap.put("events", new Integer(SqlParserSymbols.KW_EVENTS)); + keywordMap.put("except", new Integer(SqlParserSymbols.KW_EXCEPT)); keywordMap.put("exists", new Integer(SqlParserSymbols.KW_EXISTS)); keywordMap.put("explain", new Integer(SqlParserSymbols.KW_DESCRIBE)); keywordMap.put("export", new Integer(SqlParserSymbols.KW_EXPORT)); @@ -214,6 +215,7 @@ import org.apache.doris.qe.SqlModeHelper; keywordMap.put("int", new Integer(SqlParserSymbols.KW_INT)); keywordMap.put("integer", new Integer(SqlParserSymbols.KW_INT)); keywordMap.put("intermediate", new Integer(SqlParserSymbols.KW_INTERMEDIATE)); + keywordMap.put("intersect", new Integer(SqlParserSymbols.KW_INTERSECT)); keywordMap.put("interval", new Integer(SqlParserSymbols.KW_INTERVAL)); keywordMap.put("into", new Integer(SqlParserSymbols.KW_INTO)); keywordMap.put("is", new Integer(SqlParserSymbols.KW_IS)); @@ -240,6 +242,7 @@ import org.apache.doris.qe.SqlModeHelper; keywordMap.put("migrate", new Integer(SqlParserSymbols.KW_MIGRATE)); keywordMap.put("migrations", new Integer(SqlParserSymbols.KW_MIGRATIONS)); keywordMap.put("min", new Integer(SqlParserSymbols.KW_MIN)); + keywordMap.put("minus", new Integer(SqlParserSymbols.KW_MINUS)); keywordMap.put("minute", new Integer(SqlParserSymbols.KW_MINUTE)); keywordMap.put("modify", new Integer(SqlParserSymbols.KW_MODIFY)); keywordMap.put("month", new Integer(SqlParserSymbols.KW_MONTH)); diff --git a/fe/src/test/java/org/apache/doris/analysis/SetOperationStmtTest.java b/fe/src/test/java/org/apache/doris/analysis/SetOperationStmtTest.java new file mode 100644 index 0000000..ff5a2a0 --- /dev/null +++ b/fe/src/test/java/org/apache/doris/analysis/SetOperationStmtTest.java @@ -0,0 +1,64 @@ +package org.apache.doris.analysis; + +import java.io.StringReader; + +import org.apache.doris.common.AnalysisException; +import org.apache.doris.mysql.privilege.MockedAuth; +import org.apache.doris.mysql.privilege.PaloAuth; +import org.apache.doris.qe.ConnectContext; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import mockit.Mocked; + +public class SetOperationStmtTest { + private Analyzer analyzer; + + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + @Before + public void setUp() { + analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); + } + @Test + public void testNormal() throws Exception { + String sql = "select k1,k2 from t where k1='a' union select k1,k2 from t where k1='b';"; + SqlScanner input = new SqlScanner(new StringReader(sql)); + SqlParser parser = new SqlParser(input); + SetOperationStmt stmt = (SetOperationStmt) parser.parse().value; + Assert.assertEquals(SetOperationStmt.Operation.UNION, stmt.getOperands().get(1).getOperation()); + sql = "select k1,k2 from t where k1='a' intersect select k1,k2 from t where k1='b';"; + input = new SqlScanner(new StringReader(sql)); + parser = new SqlParser(input); + stmt = (SetOperationStmt) parser.parse().value; + Assert.assertEquals(SetOperationStmt.Operation.INTERSECT, stmt.getOperands().get(1).getOperation()); + sql = "select k1,k2 from t where k1='a' except select k1,k2 from t where k1='b';"; + input = new SqlScanner(new StringReader(sql)); + parser = new SqlParser(input); + stmt = (SetOperationStmt) parser.parse().value; + Assert.assertEquals(SetOperationStmt.Operation.EXCEPT, stmt.getOperands().get(1).getOperation()); + sql = "select k1,k2 from t where k1='a' minus select k1,k2 from t where k1='b';"; + input = new SqlScanner(new StringReader(sql)); + parser = new SqlParser(input); + stmt = (SetOperationStmt) parser.parse().value; + Assert.assertEquals(SetOperationStmt.Operation.EXCEPT, stmt.getOperands().get(1).getOperation()); + sql = "select k1,k2 from t where k1='a' union select k1,k2 from t where k1='b' intersect select k1,k2 from t " + + "where k1='c' except select k1,k2 from t where k1='d';"; + input = new SqlScanner(new StringReader(sql)); + parser = new SqlParser(input); + stmt = (SetOperationStmt) parser.parse().value; + Assert.assertEquals(SetOperationStmt.Operation.UNION, stmt.getOperands().get(1).getOperation()); + Assert.assertEquals(SetOperationStmt.Operation.INTERSECT, stmt.getOperands().get(2).getOperation()); + Assert.assertEquals(SetOperationStmt.Operation.EXCEPT, stmt.getOperands().get(3).getOperation()); + Assert.assertEquals(4, stmt.getOperands().size()); + + + } + +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org