support intersect and except syntax (#2882)
This commit is contained in:
@ -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; :}
|
||||
;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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());
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user