diff --git a/be/src/exec/CMakeLists.txt b/be/src/exec/CMakeLists.txt index 474b241c5..d3f92e5a9 100644 --- a/be/src/exec/CMakeLists.txt +++ b/be/src/exec/CMakeLists.txt @@ -31,6 +31,7 @@ add_library(Exec STATIC data-sink.cc data-source-scan-node.cc delimited-text-parser.cc + empty-set-node.cc exec-node.cc external-data-source-executor.cc exchange-node.cc diff --git a/be/src/exec/empty-set-node.cc b/be/src/exec/empty-set-node.cc new file mode 100644 index 000000000..a28d3d66b --- /dev/null +++ b/be/src/exec/empty-set-node.cc @@ -0,0 +1,31 @@ +// Copyright 2012 Cloudera Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "exec/empty-set-node.h" + +using namespace std; + +namespace impala { + +EmptySetNode::EmptySetNode(ObjectPool* pool, const TPlanNode& tnode, + const DescriptorTbl& descs) + : ExecNode(pool, tnode, descs) { +} + +Status EmptySetNode::GetNext(RuntimeState* state, RowBatch* row_batch, bool* eos) { + *eos = true; + return Status::OK; +} + +} diff --git a/be/src/exec/empty-set-node.h b/be/src/exec/empty-set-node.h new file mode 100644 index 000000000..648e43151 --- /dev/null +++ b/be/src/exec/empty-set-node.h @@ -0,0 +1,33 @@ +// Copyright 2012 Cloudera Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#ifndef IMPALA_EXEC_EMPTY_SET_NODE_H_ +#define IMPALA_EXEC_EMPTY_SET_NODE_H_ + +#include "exec/exec-node.h" + +namespace impala { + +// Node that returns an empty result set, i.e., just sets eos_ in GetNext(). +// Corresponds to EmptySetNode.java in the FE. +class EmptySetNode : public ExecNode { + public: + EmptySetNode(ObjectPool* pool, const TPlanNode& tnode, const DescriptorTbl& descs); + virtual Status GetNext(RuntimeState* state, RowBatch* row_batch, bool* eos); +}; + +} + +#endif diff --git a/be/src/exec/exec-node.cc b/be/src/exec/exec-node.cc index b96cc47df..988566646 100644 --- a/be/src/exec/exec-node.cc +++ b/be/src/exec/exec-node.cc @@ -26,6 +26,7 @@ #include "exec/aggregation-node.h" #include "exec/cross-join-node.h" #include "exec/data-source-scan-node.h" +#include "exec/empty-set-node.h" #include "exec/exchange-node.h" #include "exec/hash-join-node.h" #include "exec/hdfs-scan-node.h" @@ -263,6 +264,9 @@ Status ExecNode::CreateNode(ObjectPool* pool, const TPlanNode& tnode, case TPlanNodeType::CROSS_JOIN_NODE: *node = pool->Add(new CrossJoinNode(pool, tnode, descs)); break; + case TPlanNodeType::EMPTY_SET_NODE: + *node = pool->Add(new EmptySetNode(pool, tnode, descs)); + break; case TPlanNodeType::EXCHANGE_NODE: *node = pool->Add(new ExchangeNode(pool, tnode, descs)); break; diff --git a/be/src/service/query-exec-state.cc b/be/src/service/query-exec-state.cc index 0e3733851..b1e0c5b8e 100644 --- a/be/src/service/query-exec-state.cc +++ b/be/src/service/query-exec-state.cc @@ -318,18 +318,6 @@ Status ImpalaServer::QueryExecState::ExecQueryOrDmlRequest( query_exec_request.fragments[0].partition.type == TPartitionType::UNPARTITIONED; DCHECK(has_coordinator_fragment || query_exec_request.__isset.desc_tbl); - // If the first fragment has a "limit 0" and this is a query, simply set eos_ - // to true and return. - // TODO: To be compatible with Hive, INSERT OVERWRITE must clear out target - // tables / static partitions even if no rows are written. - DCHECK(query_exec_request.fragments[0].__isset.plan); - if (query_exec_request.fragments[0].plan.nodes[0].limit == 0 && - query_exec_request.stmt_type == TStmtType::QUERY) { - eos_ = true; - query_state_ = QueryState::FINISHED; - return Status::OK; - } - if (FLAGS_enable_rm) { DCHECK(exec_env_->resource_broker() != NULL); } diff --git a/common/thrift/PlanNodes.thrift b/common/thrift/PlanNodes.thrift index 76e7b60a7..fb3cb1ea4 100644 --- a/common/thrift/PlanNodes.thrift +++ b/common/thrift/PlanNodes.thrift @@ -33,6 +33,7 @@ enum TPlanNodeType { HASH_JOIN_NODE, AGGREGATION_NODE, SORT_NODE, + EMPTY_SET_NODE, EXCHANGE_NODE, UNION_NODE, SELECT_NODE, diff --git a/fe/src/main/java/com/cloudera/impala/analysis/Analyzer.java b/fe/src/main/java/com/cloudera/impala/analysis/Analyzer.java index 730179140..a185daf96 100644 --- a/fe/src/main/java/com/cloudera/impala/analysis/Analyzer.java +++ b/fe/src/main/java/com/cloudera/impala/analysis/Analyzer.java @@ -144,7 +144,7 @@ public class Analyzer { // whether to use Hive's auto-generated column labels public boolean useHiveColLabels = false; - // all registered conjuncts (map from id to Predicate) + // all registered conjuncts (map from expr id to conjunct) public final Map conjuncts = Maps.newHashMap(); // all registered conjuncts bound by a single tuple id; used in getBoundPredicates() @@ -172,7 +172,7 @@ public class Analyzer { // map from registered conjunct to its containing outer join On clause (represented // by its right-hand side table ref); this is limited to conjuncts that can only be - // correctly evaluated by the originating outer join + // correctly evaluated by the originating outer join, including constant conjuncts public final Map ojClauseByConjunct = Maps.newHashMap(); // map from slot id to the analyzer/block in which it was registered @@ -245,6 +245,15 @@ public class Analyzer { // Tracks the all tables/views found during analysis that were missing metadata. private Set missingTbls_ = new HashSet(); + // Indicates whether this analyzer/block is guaranteed to have an empty result set + // due to a limit 0 or constant conjunct evaluating to false. + private boolean hasEmptyResultSet_ = false; + + // Indicates whether the select-project-join (spj) portion of this query block + // is guaranteed to return an empty result set. Set due to a constant non-Having + // conjunct evaluating to false. + private boolean hasEmptySpjResultSet_ = false; + public Analyzer(ImpaladCatalog catalog, TQueryCtx queryCtx) { this.ancestors_ = Lists.newArrayList(); this.globalState_ = new GlobalState(catalog, queryCtx); @@ -418,6 +427,20 @@ public class Analyzer { */ public boolean isRootAnalyzer() { return ancestors_.isEmpty(); } + /** + * Returns true if the query block corresponding to this analyzer is guaranteed + * to return an empty result set, e.g., due to a limit 0 or a constant predicate + * that evaluates to false. + */ + public boolean hasEmptyResultSet() { return hasEmptyResultSet_; } + public void setHasEmptyResultSet() { hasEmptyResultSet_ = true; } + + /** + * Returns true if the select-project-join portion of this query block returns + * an empty result set. + */ + public boolean hasEmptySpjResultSet() { return hasEmptySpjResultSet_; } + /** * Checks that 'colName' references an existing column for a registered table alias; * if alias is empty, tries to resolve the column name in the context of any of the @@ -544,24 +567,25 @@ public class Analyzer { } /** - * Register all conjuncts in a list of predicates as Where clause conjuncts. + * Register all conjuncts in a list of predicates as Having-clause conjuncts. */ public void registerConjuncts(List l) { for (Expr e: l) { - registerConjuncts(e, null, true); + registerConjuncts(e, true); } } /** - * Register all conjuncts that make up the predicate and assign each conjunct an id. - * If rhsRef != null, rhsRef is expected to be the right-hand side of an outer join, - * and the conjuncts of p can only be evaluated by the node implementing that join - * (p is the On clause). + * Register all conjuncts that make up the On-clause 'e' of the given right-hand side + * of a join. Assigns each conjunct a unique id. If rhsRef is the right-hand side of + * an outer join, then the conjuncts conjuncts are registered such that they can only + * be evaluated by the node implementing that join. */ - public void registerConjuncts(Expr e, TableRef rhsRef, boolean fromWhereClause) { + public void registerOnClauseConjuncts(Expr e, TableRef rhsRef) { + Preconditions.checkNotNull(rhsRef); + Preconditions.checkNotNull(e); List ojConjuncts = null; - if (rhsRef != null) { - Preconditions.checkState(rhsRef.getJoinOp().isOuterJoin()); + if (rhsRef.getJoinOp().isOuterJoin()) { ojConjuncts = globalState_.conjunctsByOjClause.get(rhsRef); if (ojConjuncts == null) { ojConjuncts = Lists.newArrayList(); @@ -570,19 +594,58 @@ public class Analyzer { } for (Expr conjunct: e.getConjuncts()) { registerConjunct(conjunct); - if (rhsRef != null) { + if (rhsRef.getJoinOp().isOuterJoin()) { globalState_.ojClauseByConjunct.put(conjunct.getId(), rhsRef); ojConjuncts.add(conjunct.getId()); } - if (fromWhereClause) conjunct.setIsWhereClauseConjunct(); + markConstantConjunct(conjunct, false); } } /** - * Register individual conjunct with all tuple and slot ids it references - * and with the global conjunct list. Also assigns a new id. + * Register all conjuncts that make up 'e'. If fromHavingClause is false, this conjunct + * is assumed to originate from a Where clause. */ - public void registerConjunct(Expr e) { + public void registerConjuncts(Expr e, boolean fromHavingClause) { + for (Expr conjunct: e.getConjuncts()) { + registerConjunct(conjunct); + if (!fromHavingClause) conjunct.setIsWhereClauseConjunct(); + markConstantConjunct(conjunct, fromHavingClause); + } + } + + /** + * If the given conjunct is a constant non-oj conjunct, marks it as assigned, and + * evaluates the conjunct. If the conjunct evaluates to false, marks this query + * block as having an empty result set or as having an empty select-project-join + * portion, if fromHavingClause is true or false, respectively. + * No-op if the conjunct is not constant or is outer joined. + */ + private void markConstantConjunct(Expr conjunct, boolean fromHavingClause) { + if (!conjunct.isConstant() || isOjConjunct(conjunct)) return; + markConjunctAssigned(conjunct); + if ((!fromHavingClause && !hasEmptySpjResultSet_) + || (fromHavingClause && !hasEmptyResultSet_)) { + try { + if (!FeSupport.EvalPredicate(conjunct, globalState_.queryCtx)) { + if (fromHavingClause) { + hasEmptyResultSet_ = true; + } else { + hasEmptySpjResultSet_ = true; + } + } + } catch (InternalException ex) { + // Should never happen. + throw new IllegalStateException(ex); + } + } + } + + /** + * Assigns a new id to the given conjunct and registers it with all tuple and slot ids + * it references and with the global conjunct list. + */ + private void registerConjunct(Expr e) { // always generate a new expr id; this might be a cloned conjunct that already // has the id of its origin set e.setId(globalState_.conjunctIdGenerator.getNextId()); @@ -664,9 +727,9 @@ public class Analyzer { } /** - * Return all unassigned registered conjuncts that are fully bound by given - * list of tuple ids. If 'inclOjConjuncts' is false, conjuncts tied to an Outer Join - * clause are excluded. + * Return all unassigned non-constant registered conjuncts that are fully bound by + * given list of tuple ids. If 'inclOjConjuncts' is false, conjuncts tied to an + * Outer Join clause are excluded. */ public List getUnassignedConjuncts( List tupleIds, boolean inclOjConjuncts) { @@ -676,7 +739,7 @@ public class Analyzer { if (e.isBoundByTupleIds(tupleIds) && !e.isAuxExpr() && !globalState_.assignedConjuncts.contains(e.getId()) - && (inclOjConjuncts + && ((inclOjConjuncts && !e.isConstant()) || !globalState_.ojClauseByConjunct.containsKey(e.getId()))) { result.add(e); LOG.trace("getUnassignedConjunct: " + e.toSql()); @@ -734,7 +797,7 @@ public class Analyzer { for (ExprId conjunctId: candidates) { if (!globalState_.assignedConjuncts.contains(conjunctId)) { Expr e = globalState_.conjuncts.get(conjunctId); - Preconditions.checkState(e != null); + Preconditions.checkNotNull(e); result.add(e); LOG.trace("getUnassignedOjConjunct: " + e.toSql()); } diff --git a/fe/src/main/java/com/cloudera/impala/analysis/Expr.java b/fe/src/main/java/com/cloudera/impala/analysis/Expr.java index 820829c83..c23e12974 100644 --- a/fe/src/main/java/com/cloudera/impala/analysis/Expr.java +++ b/fe/src/main/java/com/cloudera/impala/analysis/Expr.java @@ -78,7 +78,7 @@ abstract public class Expr extends TreeNode implements ParseNode, Cloneabl protected Type type_; // result of analysis protected boolean isAnalyzed_; // true after analyze() has been called - protected boolean isWhereClauseConjunct_; // set by Analyzer + protected boolean isWhereClauseConjunct_; // set by analyzer // Flag to indicate whether to wrap this expr's toSql() in parenthesis. Set by parser. // Needed for properly capturing expr precedences in the SQL string. diff --git a/fe/src/main/java/com/cloudera/impala/analysis/LimitElement.java b/fe/src/main/java/com/cloudera/impala/analysis/LimitElement.java index 16903b4ec..c34e910bb 100644 --- a/fe/src/main/java/com/cloudera/impala/analysis/LimitElement.java +++ b/fe/src/main/java/com/cloudera/impala/analysis/LimitElement.java @@ -110,6 +110,7 @@ class LimitElement { } limit_ = evalIntegerExpr(analyzer, limitExpr_, "LIMIT"); } + if (limit_ == 0) analyzer.setHasEmptyResultSet(); if (offsetExpr_ != null) { if (!offsetExpr_.isConstant()) { diff --git a/fe/src/main/java/com/cloudera/impala/analysis/SelectStmt.java b/fe/src/main/java/com/cloudera/impala/analysis/SelectStmt.java index 82951b89e..8c4ed23f7 100644 --- a/fe/src/main/java/com/cloudera/impala/analysis/SelectStmt.java +++ b/fe/src/main/java/com/cloudera/impala/analysis/SelectStmt.java @@ -173,7 +173,7 @@ public class SelectStmt extends QueryStmt { "aggregate function not allowed in WHERE clause"); } whereClause_.checkReturnsBool("WHERE clause", false); - analyzer.registerConjuncts(whereClause_, null, true); + analyzer.registerConjuncts(whereClause_, false); } createSortInfo(analyzer); @@ -184,6 +184,12 @@ public class SelectStmt extends QueryStmt { sqlString_ = toSql(); resolveInlineViewRefs(analyzer); + // If this block's select-project-join portion returns an empty result set and the + // block has no aggregation, then mark this block as returning an empty result set. + if (analyzer.hasEmptySpjResultSet() && aggInfo_ == null) { + analyzer.setHasEmptyResultSet(); + } + if (aggInfo_ != null) LOG.debug("post-analysis " + aggInfo_.debugString()); } @@ -449,7 +455,7 @@ public class SelectStmt extends QueryStmt { Preconditions.checkState(!havingPred_.contains( Predicates.instanceOf(Subquery.class))); havingPred_ = havingPred_.substitute(combinedSMap, analyzer); - analyzer.registerConjuncts(havingPred_, null, false); + analyzer.registerConjuncts(havingPred_, true); LOG.debug("post-agg havingPred: " + havingPred_.debugString()); } if (sortInfo_ != null) { diff --git a/fe/src/main/java/com/cloudera/impala/analysis/TableRef.java b/fe/src/main/java/com/cloudera/impala/analysis/TableRef.java index 630053c7f..69f01c5cd 100644 --- a/fe/src/main/java/com/cloudera/impala/analysis/TableRef.java +++ b/fe/src/main/java/com/cloudera/impala/analysis/TableRef.java @@ -315,17 +315,7 @@ public class TableRef implements ParseNode { // The exception are conjuncts that only pertain to the nullable side // of the outer join; those can be evaluated directly when materializing tuples // without violating outer join semantics. - // TODO: remove this special case - if (getJoinOp().isOuterJoin()) { - if (lhsIsNullable && e.isBound(leftTblRef_.getId()) - || rhsIsNullable && e.isBound(getId())) { - analyzer.registerConjuncts(e, this, false); - } else { - analyzer.registerConjuncts(e, this, false); - } - } else { - analyzer.registerConjuncts(e, null, false); - } + analyzer.registerOnClauseConjuncts(e, this); List tupleIds = Lists.newArrayList(); e.getIds(tupleIds, null); onClauseTupleIds.addAll(tupleIds); diff --git a/fe/src/main/java/com/cloudera/impala/planner/EmptySetNode.java b/fe/src/main/java/com/cloudera/impala/planner/EmptySetNode.java new file mode 100644 index 000000000..aabae161c --- /dev/null +++ b/fe/src/main/java/com/cloudera/impala/planner/EmptySetNode.java @@ -0,0 +1,59 @@ +// Copyright 2012 Cloudera Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.cloudera.impala.planner; + +import java.util.ArrayList; + +import com.cloudera.impala.analysis.Analyzer; +import com.cloudera.impala.analysis.TupleId; +import com.cloudera.impala.common.InternalException; +import com.cloudera.impala.thrift.TExplainLevel; +import com.cloudera.impala.thrift.TPlanNode; +import com.cloudera.impala.thrift.TPlanNodeType; + +/** + * Node that returns an empty result set. Used for planning query blocks with + * a constant predicate evaluating to false or a limit 0. + */ +public class EmptySetNode extends PlanNode { + public EmptySetNode(PlanNodeId id, ArrayList tupleIds) { + super(id, tupleIds, "EMPTYSET"); + } + + @Override + public void computeStats(Analyzer analyzer) { + avgRowSize_ = 0; + cardinality_ = 0; + perHostMemCost_ = 0; + numNodes_ = 1; + } + + @Override + public void init(Analyzer analyzer) throws InternalException { + computeMemLayout(analyzer); + computeStats(analyzer); + } + + @Override + protected String getNodeExplainString(String prefix, String detailPrefix, + TExplainLevel detailLevel) { + return String.format("%s%s:%s\n", prefix, id_.toString(), displayName_); + } + + @Override + protected void toThrift(TPlanNode msg) { + msg.node_type = TPlanNodeType.EMPTY_SET_NODE; + } +} diff --git a/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java b/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java index 3a4129300..cf6a8f172 100644 --- a/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java +++ b/fe/src/main/java/com/cloudera/impala/planner/HBaseScanNode.java @@ -37,10 +37,10 @@ import com.cloudera.impala.analysis.Expr; import com.cloudera.impala.analysis.SlotDescriptor; import com.cloudera.impala.analysis.StringLiteral; import com.cloudera.impala.analysis.TupleDescriptor; -import com.cloudera.impala.catalog.Type; import com.cloudera.impala.catalog.HBaseColumn; import com.cloudera.impala.catalog.HBaseTable; import com.cloudera.impala.catalog.PrimitiveType; +import com.cloudera.impala.catalog.Type; import com.cloudera.impala.common.InternalException; import com.cloudera.impala.common.Pair; import com.cloudera.impala.service.FeSupport; diff --git a/fe/src/main/java/com/cloudera/impala/planner/Planner.java b/fe/src/main/java/com/cloudera/impala/planner/Planner.java index 95ba32b99..160038e71 100644 --- a/fe/src/main/java/com/cloudera/impala/planner/Planner.java +++ b/fe/src/main/java/com/cloudera/impala/planner/Planner.java @@ -57,7 +57,6 @@ import com.cloudera.impala.common.NotImplementedException; import com.cloudera.impala.common.Pair; import com.cloudera.impala.common.PrintUtils; import com.cloudera.impala.common.RuntimeEnv; -import com.cloudera.impala.service.FeSupport; import com.cloudera.impala.thrift.TExplainLevel; import com.cloudera.impala.thrift.TPartitionType; import com.cloudera.impala.thrift.TQueryExecRequest; @@ -272,6 +271,9 @@ public class Planner { result = createOrderByFragment((SortNode) root, childFragments.get(0), fragments, analyzer); + } else if (root instanceof EmptySetNode) { + result = new PlanFragment( + fragmentIdGenerator_.getNextId(), root, DataPartition.UNPARTITIONED); } else { throw new InternalException( "Cannot create plan fragment for this node type: " + root.getExplainString()); @@ -909,6 +911,14 @@ public class Planner { */ private PlanNode createQueryPlan(QueryStmt stmt, Analyzer analyzer, boolean disableTopN) throws ImpalaException { + if (analyzer.hasEmptyResultSet()) { + ArrayList tupleIds = Lists.newArrayList(); + stmt.getMaterializedTupleIds(tupleIds); + EmptySetNode node = new EmptySetNode(nodeIdGenerator_.getNextId(), tupleIds); + node.init(analyzer); + return node; + } + PlanNode root; if (stmt instanceof SelectStmt) { root = createSelectPlan((SelectStmt) stmt, analyzer); @@ -942,6 +952,8 @@ public class Planner { private PlanNode addUnassignedConjuncts( Analyzer analyzer, List tupleIds, PlanNode root) throws InternalException { + // No point in adding SelectNode on top of an EmptyNode. + if (root instanceof EmptySetNode) return root; Preconditions.checkNotNull(root); // TODO: standardize on logical tuple ids? List conjuncts = analyzer.getUnassignedConjuncts(root); @@ -1177,6 +1189,14 @@ public class Planner { // repeats itself. selectStmt.materializeRequiredSlots(analyzer); + // return a plan that feeds the aggregation of selectStmt with an empty set, + // if the selectStmt's select-project-join portion returns an empty result set + if (analyzer.hasEmptySpjResultSet()) { + PlanNode emptySetNode = new EmptySetNode(nodeIdGenerator_.getNextId(), rowTuples); + emptySetNode.init(analyzer); + return createAggregationPlan(selectStmt, analyzer, emptySetNode); + } + // create plans for our table refs; use a list here instead of a map to // maintain a deterministic order of traversing the TableRefs during join // plan generation (helps with tests) @@ -1209,24 +1229,9 @@ public class Planner { root = addUnassignedConjuncts(analyzer, root.getTupleIds(), root); } - // add aggregation, if required - AggregateInfo aggInfo = selectStmt.getAggInfo(); - if (aggInfo != null) { - root = new AggregationNode(nodeIdGenerator_.getNextId(), root, aggInfo); - root.init(analyzer); - Preconditions.checkState(root.hasValidStats()); - // if we're computing DISTINCT agg fns, the analyzer already created the - // 2nd phase agginfo - if (aggInfo.isDistinctAgg()) { - ((AggregationNode)root).unsetNeedsFinalize(); - root = new AggregationNode( - nodeIdGenerator_.getNextId(), root, - aggInfo.getSecondPhaseDistinctAggInfo()); - root.init(analyzer); - Preconditions.checkState(root.hasValidStats()); - } - // add Having clause - root.assignConjuncts(analyzer); + // add aggregation, if any + if (selectStmt.getAggInfo() != null) { + root = createAggregationPlan(selectStmt, analyzer, root); } // All the conjuncts_ should be assigned at this point. @@ -1235,6 +1240,33 @@ public class Planner { return root; } + /** + * Returns a new AggregationNode that materializes the aggregation of the given stmt. + * Assigns conjuncts from the Having clause to the returned node. + */ + private PlanNode createAggregationPlan(SelectStmt selectStmt, Analyzer analyzer, + PlanNode root) throws InternalException { + Preconditions.checkState(selectStmt.getAggInfo() != null); + // add aggregation, if required + AggregateInfo aggInfo = selectStmt.getAggInfo(); + root = new AggregationNode(nodeIdGenerator_.getNextId(), root, aggInfo); + root.init(analyzer); + Preconditions.checkState(root.hasValidStats()); + // if we're computing DISTINCT agg fns, the analyzer already created the + // 2nd phase agginfo + if (aggInfo.isDistinctAgg()) { + ((AggregationNode)root).unsetNeedsFinalize(); + root = new AggregationNode( + nodeIdGenerator_.getNextId(), root, + aggInfo.getSecondPhaseDistinctAggInfo()); + root.init(analyzer); + Preconditions.checkState(root.hasValidStats()); + } + // add Having clause + root.assignConjuncts(analyzer); + return root; + } + /** * Returns a UnionNode that materializes the exprs of the constant selectStmt. * Replaces the resultExprs of the selectStmt with SlotRefs into the materialized tuple. @@ -1248,6 +1280,7 @@ public class Planner { TupleDescriptor tupleDesc = analyzer.getDescTbl().createTupleDescriptor(); tupleDesc.setIsMaterialized(true); UnionNode unionNode = new UnionNode(nodeIdGenerator_.getNextId(), tupleDesc.getId()); + // Analysis guarantees that selects without a FROM clause only have constant exprs. unionNode.addConstExprList(Lists.newArrayList(resultExprs)); @@ -1382,6 +1415,7 @@ public class Planner { analyzer.getTupleDesc(inlineViewRef.getId()).materializeSlots(); UnionNode unionNode = new UnionNode(nodeIdGenerator_.getNextId(), inlineViewRef.getMaterializedTupleIds().get(0)); + if (analyzer.hasEmptyResultSet()) return unionNode; unionNode.setTblRefIds(Lists.newArrayList(inlineViewRef.getId())); unionNode.addConstExprList(selectStmt.getBaseTblResultExprs()); unionNode.init(analyzer); @@ -1659,26 +1693,14 @@ public class Planner { // the individual operands. // Do this prior to creating the operands' plan trees so they get a chance to // pick up propagated predicates. - // Drop operands that have constant conjuncts evaluating to false, and drop - // constant conjuncts evaluating to true. List conjuncts = analyzer.getUnassignedConjuncts(unionStmt.getTupleId().asList(), false); for (UnionOperand op: unionStmt.getOperands()) { List opConjuncts = Expr.substituteList(conjuncts, op.getSmap(), analyzer); - List nonConstOpConjuncts = Lists.newArrayList(); - for (int i = 0; i < opConjuncts.size(); ++i) { - Expr opConjunct = opConjuncts.get(i); - if (!opConjunct.isConstant()) { - nonConstOpConjuncts.add(opConjunct); - continue; - } - // Evaluate constant conjunct and drop operand if it evals to false. - if (!FeSupport.EvalPredicate(opConjunct, analyzer.getQueryCtx())) { - op.drop(); - break; - } - } - if (!op.isDropped()) analyzer.registerConjuncts(nonConstOpConjuncts); + op.getAnalyzer().registerConjuncts(opConjuncts); + // Some of the opConjuncts have become constant and eval'd to false, or an ancestor + // block is already guaranteed to return empty results. + if (op.getAnalyzer().hasEmptyResultSet()) op.drop(); } analyzer.markConjunctsAssigned(conjuncts); diff --git a/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java b/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java index 681eddde1..2dab220eb 100644 --- a/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java +++ b/fe/src/main/java/com/cloudera/impala/planner/UnionNode.java @@ -151,20 +151,12 @@ public class UnionNode extends PlanNode { * result/const expr lists based on the materialized slots of this UnionNode's * produced tuple. The UnionNode doesn't need an smap: like a ScanNode, it * materializes an original tuple. + * There is no need to call assignConjuncts() because all non-constant conjuncts + * have already been assigned to the union operands, and all constant conjuncts have + * been evaluated during registration to set analyzer.hasEmptyResultSet_. */ @Override public void init(Analyzer analyzer) throws InternalException { - assignConjuncts(analyzer); - // All non-constant conjuncts should have been assigned to children. - // This requirement is important to guarantee that conjuncts do not trigger - // materialization of slots in this UnionNode's tuple. - // TODO: It's possible to get constant conjuncts if a union operand - // consists of a select stmt on a constant inline view. We should - // drop the operand in such cases. - for (Expr conjunct: conjuncts_) { - Preconditions.checkState(conjunct.isConstant()); - } - computeMemLayout(analyzer); computeStats(analyzer); diff --git a/fe/src/main/java/com/cloudera/impala/service/FeSupport.java b/fe/src/main/java/com/cloudera/impala/service/FeSupport.java index 1f8026521..df0c5a554 100644 --- a/fe/src/main/java/com/cloudera/impala/service/FeSupport.java +++ b/fe/src/main/java/com/cloudera/impala/service/FeSupport.java @@ -25,7 +25,9 @@ import org.apache.thrift.protocol.TBinaryProtocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.cloudera.impala.analysis.BoolLiteral; import com.cloudera.impala.analysis.Expr; +import com.cloudera.impala.analysis.NullLiteral; import com.cloudera.impala.analysis.TableName; import com.cloudera.impala.common.InternalException; import com.cloudera.impala.thrift.TCacheJarParams; @@ -171,6 +173,9 @@ public class FeSupport { public static boolean EvalPredicate(Expr pred, TQueryCtx queryCtx) throws InternalException { + // Shortcuts to avoid expensive BE evaluation. + if (pred instanceof BoolLiteral) return ((BoolLiteral) pred).getValue(); + if (pred instanceof NullLiteral) return false; Preconditions.checkState(pred.getType().isBoolean()); TColumnValue val = EvalConstExpr(pred, queryCtx); // Return false if pred evaluated to false or NULL. True otherwise. diff --git a/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java b/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java index 1020ca69f..c676f4117 100644 --- a/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java +++ b/fe/src/test/java/com/cloudera/impala/planner/PlannerTest.java @@ -397,6 +397,11 @@ public class PlannerTest { runPlannerTestFile("constant"); } + @Test + public void testEmpty() { + runPlannerTestFile("empty"); + } + @Test public void testDistinct() { runPlannerTestFile("distinct"); diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/empty.test b/testdata/workloads/functional-planner/queries/PlannerTest/empty.test new file mode 100644 index 000000000..ee0f39358 --- /dev/null +++ b/testdata/workloads/functional-planner/queries/PlannerTest/empty.test @@ -0,0 +1,229 @@ +# Constant conjunct in WHERE clause turns query block into an empty-set node. +select t1.id, t2.id +from functional.alltypestiny t1 +left outer join functional.alltypes t2 +on t1.id = t2.id +where false +---- PLAN +00:EMPTYSET +==== +# HBase scan turns into empty-set node due to a constant conjunct. +select * from functional_hbase.alltypessmall where false +---- PLAN +00:EMPTYSET +==== +# Data source scan turns into empty-set node due to a constant conjunct. +select * +from functional.alltypes_datasource a +inner join functional.alltypestiny b +on a.id = b.id +where length("a") > 7 +---- PLAN +00:EMPTYSET +==== +# Constant conjunct in ON clause turns query block into an empty-set node. +select * +from functional.alltypestiny t1 +inner join functional.alltypes t2 +on (t1.id = t2.id and (false or false)) +---- PLAN +00:EMPTYSET +==== +# Constant conjunct in WHERE clause turns query block into an aggregation +# fed by an empty-set node. +select count(int_col), avg(double_col), count(*) +from functional.alltypes +where null +---- PLAN +01:AGGREGATE [FINALIZE] +| output: count(int_col), sum(double_col), count(double_col), count(*) +| +00:EMPTYSET +==== +# Constant conjunct in ON clause turns query block into an aggregation +# fed by an empty-sed node. +select count(*) +from functional.alltypestiny t1 +inner join functional.alltypes t2 +on (t1.id = t2.id and (false or false)) +---- PLAN +01:AGGREGATE [FINALIZE] +| output: count(*) +| +00:EMPTYSET +==== +# Constant conjunct in HAVING clause turns query block into an empty-set node, +# regardless of aggregation. +select t1.int_col, count(*) +from functional.alltypestiny t1 +left outer join functional.alltypes t2 +on t1.id = t2.id +group by t1.int_col +having ifnull(null, false) +---- PLAN +00:EMPTYSET +==== +# Constant conjunct causes empty-set inline view. +select e.id, f.id +from functional.alltypessmall f +inner join + (select t1.id + from functional.alltypestiny t1 + left outer join functional.alltypes t2 + on t1.id = t2.id + where 1 + 3 > 10) e +on e.id = f.id +---- PLAN +02:HASH JOIN [INNER JOIN] +| hash predicates: f.id = t1.id +| +|--01:EMPTYSET +| +00:SCAN HDFS [functional.alltypessmall f] + partitions=4/4 size=6.32KB +==== +# Constant conjunct causes union operand to be dropped. +select * from functional.alltypessmall +union all +select * from functional.alltypes where "abc" = "cde" +union all +select * from functional.alltypestiny +---- PLAN +00:UNION +| +|--02:SCAN HDFS [functional.alltypestiny] +| partitions=4/4 size=460B +| +01:SCAN HDFS [functional.alltypessmall] + partitions=4/4 size=6.32KB +==== +# Constant conjunct turns union into an empty-set node. +select * +from functional.alltypes a +full outer join + (select * from + (select * from functional.alltypestiny + union all + select * from functional.alltypessmall) t1 + where null) t2 +on a.id = t2.id +---- PLAN +02:HASH JOIN [FULL OUTER JOIN] +| hash predicates: a.id = id +| +|--01:EMPTYSET +| +00:SCAN HDFS [functional.alltypes a] + partitions=24/24 size=478.45KB +==== +# Constant conjunct in the ON-clause of an outer join is +# assigned to the join. +select * +from functional.alltypessmall a +left outer join functional.alltypestiny b +on (a.id = b.id and 1 + 1 > 10) +---- PLAN +02:HASH JOIN [LEFT OUTER JOIN] +| hash predicates: a.id = b.id +| other join predicates: 1 + 1 > 10 +| +|--01:SCAN HDFS [functional.alltypestiny b] +| partitions=4/4 size=460B compact +| +00:SCAN HDFS [functional.alltypessmall a] + partitions=4/4 size=6.32KB +==== +# Constant conjunct in the ON-clause of an outer join is +# assigned to the join. +select * +from functional.alltypessmall a +right outer join functional.alltypestiny b +on (a.id = b.id and !true) +---- PLAN +02:HASH JOIN [RIGHT OUTER JOIN] +| hash predicates: a.id = b.id +| other join predicates: NOT TRUE +| +|--01:SCAN HDFS [functional.alltypestiny b] +| partitions=4/4 size=460B compact +| +00:SCAN HDFS [functional.alltypessmall a] + partitions=4/4 size=6.32KB +==== +# Constant conjunct in the ON-clause of an outer join is +# assigned to the join. +select * +from functional.alltypessmall a +full outer join functional.alltypestiny b +on (a.id = b.id and null = "abc") +---- PLAN +02:HASH JOIN [FULL OUTER JOIN] +| hash predicates: a.id = b.id +| other join predicates: NULL = 'abc' +| +|--01:SCAN HDFS [functional.alltypestiny b] +| partitions=4/4 size=460B compact +| +00:SCAN HDFS [functional.alltypessmall a] + partitions=4/4 size=6.32KB +==== +# Limit 0 turns query block into an empty-set node. +select t1.id, t2.id +from functional.alltypestiny t1 +left outer join functional.alltypes t2 +on t1.id = t2.id +limit 0 +---- PLAN +00:EMPTYSET +==== +# Limit 0 turns query block into an empty-set node. +select count(int_col), avg(double_col), count(*) +from functional.alltypes +limit 0 +---- PLAN +00:EMPTYSET +==== +# Limit 0 causes empty-set inline view. +select e.id, f.id +from functional.alltypessmall f +inner join + (select t1.id + from functional.alltypestiny t1 + left outer join functional.alltypes t2 + on t1.id = t2.id + limit 0) e +on e.id = f.id +---- PLAN +02:HASH JOIN [INNER JOIN] +| hash predicates: f.id = t1.id +| +|--01:EMPTYSET +| +00:SCAN HDFS [functional.alltypessmall f] + partitions=4/4 size=6.32KB +==== +# Limit 0 causes union operand to be dropped. +select * from functional.alltypessmall +union all +select * from functional.alltypes limit 0 +union all +select * from functional.alltypestiny +---- PLAN +00:UNION +| +|--02:SCAN HDFS [functional.alltypestiny] +| partitions=4/4 size=460B +| +01:SCAN HDFS [functional.alltypessmall] + partitions=4/4 size=6.32KB +==== +# Limit 0 causes empty-set union. +select * from functional.alltypessmall +union all +select * from functional.alltypes where "abc" = "cde" +union all +(select * from functional.alltypestiny) +limit 0 +---- PLAN +00:EMPTYSET +==== diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/subquery.test b/testdata/workloads/functional-planner/queries/PlannerTest/subquery.test index 6447f0241..7cf4fa7dd 100644 --- a/testdata/workloads/functional-planner/queries/PlannerTest/subquery.test +++ b/testdata/workloads/functional-planner/queries/PlannerTest/subquery.test @@ -132,8 +132,8 @@ NODE 0: HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=8/100108.txt 0:76263 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=9/100109.txt 0:76263 NODE 1: - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=4/090401.txt 0:1621 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=3/090301.txt 0:1620 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=4/090401.txt 0:1621 ---- DISTRIBUTEDPLAN 05:EXCHANGE [UNPARTITIONED] | @@ -243,8 +243,8 @@ NODE 0: HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=8/100108.txt 0:76263 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=9/100109.txt 0:76263 NODE 1: - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=4/090401.txt 0:1621 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=3/090301.txt 0:1620 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=4/090401.txt 0:1621 ---- DISTRIBUTEDPLAN 05:EXCHANGE [UNPARTITIONED] | @@ -301,17 +301,17 @@ and x.int_col + x.float_col + cast(c.string_col as float) < 1000 predicates: a.int_col > 899 ---- SCANRANGELOCATIONS NODE 0: - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=2/090201.txt 0:1621 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=1/090101.txt 0:1610 - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=4/090401.txt 0:1621 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=2/090201.txt 0:1621 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=3/090301.txt 0:1620 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=4/090401.txt 0:1621 NODE 1: HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=1/100101.txt 0:75153 NODE 2: - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=2/090201.txt 0:1621 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=1/090101.txt 0:1610 - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=4/090401.txt 0:1621 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=2/090201.txt 0:1621 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=3/090301.txt 0:1620 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypessmall/year=2009/month=4/090401.txt 0:1621 ---- DISTRIBUTEDPLAN 07:EXCHANGE [UNPARTITIONED] | @@ -352,17 +352,17 @@ group by 1 partitions=11/11 size=814.73KB ---- SCANRANGELOCATIONS NODE 0: - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=2/100102.txt 0:76263 - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=3/100103.txt 0:76263 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=1/100101.txt 0:75153 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=10/100110.txt 0:76263 - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=6/100106.txt 0:76263 - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=7/100107.txt 0:76263 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=2/100102.txt 0:76263 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=3/100103.txt 0:76263 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=4/100104.txt 0:76263 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=5/100105.txt 0:76263 - HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=__HIVE_DEFAULT_PARTITION__/000000_0 0:72759 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=6/100106.txt 0:76263 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=7/100107.txt 0:76263 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=8/100108.txt 0:76263 HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=9/100109.txt 0:76263 + HDFS SPLIT hdfs://localhost:20500/test-warehouse/alltypesagg/year=2010/month=1/day=__HIVE_DEFAULT_PARTITION__/000000_0 0:72759 ---- DISTRIBUTEDPLAN 04:EXCHANGE [UNPARTITIONED] | @@ -466,47 +466,9 @@ from ( order by 2,1 desc limit 0 ---- PLAN -04:TOP-N [LIMIT=0] -| order by: x2 ASC, c1 DESC -| -03:AGGREGATE [FINALIZE] -| output: min(tinyint_col) -| group by: int_col -| -02:TOP-N [LIMIT=1] -| order by: int_col ASC, tinyint_col ASC -| -01:AGGREGATE [FINALIZE] -| output: min(float_col) -| group by: int_col, tinyint_col -| -00:SCAN HBASE [functional_hbase.alltypessmall] +00:EMPTYSET ---- DISTRIBUTEDPLAN -04:TOP-N [LIMIT=0] -| order by: x2 ASC, c1 DESC -| -03:AGGREGATE [FINALIZE] -| output: min(tinyint_col) -| group by: int_col -| -07:MERGING-EXCHANGE [UNPARTITIONED] -| order by: int_col ASC, tinyint_col ASC -| limit: 1 -| -02:TOP-N [LIMIT=1] -| order by: int_col ASC, tinyint_col ASC -| -06:AGGREGATE [MERGE FINALIZE] -| output: min(min(float_col)) -| group by: int_col, tinyint_col -| -05:EXCHANGE [HASH(int_col,tinyint_col)] -| -01:AGGREGATE -| output: min(float_col) -| group by: int_col, tinyint_col -| -00:SCAN HBASE [functional_hbase.alltypessmall] +00:EMPTYSET ==== # distinct * select distinct * @@ -912,15 +874,11 @@ where y < 10 | constant-operands=2 | 01:UNION - predicates: 11 < 10 - constant-operands=1 ---- DISTRIBUTEDPLAN 00:UNION | constant-operands=2 | 01:UNION - predicates: 11 < 10 - constant-operands=1 ==== # Union of values statements in subquery # TODO: We could combine the merge nodes below. diff --git a/testdata/workloads/functional-planner/queries/PlannerTest/topn.test b/testdata/workloads/functional-planner/queries/PlannerTest/topn.test index 52b09ad90..c039bf3f4 100644 --- a/testdata/workloads/functional-planner/queries/PlannerTest/topn.test +++ b/testdata/workloads/functional-planner/queries/PlannerTest/topn.test @@ -101,33 +101,9 @@ group by 1 order by 2,3 desc limit 0 ---- PLAN -02:TOP-N [LIMIT=0] -| order by: sum(float_col) ASC, min(float_col) DESC -| -01:AGGREGATE [FINALIZE] -| output: sum(float_col), min(float_col) -| group by: int_col -| -00:SCAN HBASE [functional_hbase.alltypessmall] +00:EMPTYSET ---- DISTRIBUTEDPLAN -05:MERGING-EXCHANGE [UNPARTITIONED] -| order by: sum(float_col) ASC, min(float_col) DESC -| limit: 0 -| -02:TOP-N [LIMIT=0] -| order by: sum(float_col) ASC, min(float_col) DESC -| -04:AGGREGATE [MERGE FINALIZE] -| output: sum(sum(float_col)), min(min(float_col)) -| group by: int_col -| -03:EXCHANGE [HASH(int_col)] -| -01:AGGREGATE -| output: sum(float_col), min(float_col) -| group by: int_col -| -00:SCAN HBASE [functional_hbase.alltypessmall] +00:EMPTYSET ==== # Test correct identification of the implicit aliasing of int_col in the select # list to t1.int_col; diff --git a/testdata/workloads/functional-query/queries/QueryTest/empty.test b/testdata/workloads/functional-query/queries/QueryTest/empty.test index 510955a6e..f6ec3db96 100644 --- a/testdata/workloads/functional-query/queries/QueryTest/empty.test +++ b/testdata/workloads/functional-query/queries/QueryTest/empty.test @@ -20,3 +20,74 @@ select field from emptytable string ---- RESULTS ==== +---- QUERY +# Constant conjunct. +select t1.id, t2.id +from functional.alltypestiny t1 +left outer join functional.alltypes t2 +on t1.id = t2.id +where false +---- TYPES +int, int +---- RESULTS +==== +---- QUERY +# Constant conjunct in query block with an aggregation. +select count(int_col), avg(double_col), count(*) +from functional.alltypes +where null +---- TYPES +bigint, double, bigint +---- RESULTS +0,NULL,0 +==== +---- QUERY +# Constant conjunct in inline view. +select e.id, f.id +from functional.alltypessmall f +inner join + (select t1.id + from functional.alltypestiny t1 + left outer join functional.alltypes t2 + on t1.id = t2.id + where 1 + 3 > 10) e +on e.id = f.id +---- TYPES +int, int +---- RESULTS +==== +---- QUERY +# Limit 0 +select t1.id, t2.id +from functional.alltypestiny t1 +left outer join functional.alltypes t2 +on t1.id = t2.id +limit 0 +---- TYPES +int, int +---- RESULTS +==== +---- QUERY +# Limit 0 in query block with an aggregation +select count(int_col), avg(double_col), count(*) +from functional.alltypes +limit 0 +---- TYPES +bigint, double, bigint +---- RESULTS +==== +---- QUERY +# Limit 0 in inline view +select e.id, f.id +from functional.alltypessmall f +inner join + (select t1.id + from functional.alltypestiny t1 + left outer join functional.alltypes t2 + on t1.id = t2.id + limit 0) e +on e.id = f.id +---- TYPES +int, int +---- RESULTS +==== diff --git a/testdata/workloads/functional-query/queries/QueryTest/insert.test b/testdata/workloads/functional-query/queries/QueryTest/insert.test index 6cbf10472..ca58f7f8e 100644 --- a/testdata/workloads/functional-query/queries/QueryTest/insert.test +++ b/testdata/workloads/functional-query/queries/QueryTest/insert.test @@ -669,4 +669,35 @@ STRING, STRING 'nan','nan' '-inf','-inf' 'inf','inf' -==== \ No newline at end of file +==== +---- QUERY +# Test that insert overwrite with a limit 0 clears the table. +insert overwrite table alltypesinsert +partition (year, month) +select id, bool_col, tinyint_col, smallint_col, int_col, bigint_col, +float_col, double_col, date_string_col, string_col, timestamp_col, year, month +from alltypessmall limit 0 +---- RESULTS +==== +---- QUERY +select count(*) from alltypesinsert +---- TYPES +BIGINT +---- RESULTS +0 +==== +---- QUERY +# Test that insert overwrite with a limit 0 clears the table. +insert overwrite table alltypesnopart_insert +select 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +from alltypessmall limit 0 +---- RESULTS +: 0 +==== +---- QUERY +select count(*) from alltypesnopart_insert +---- TYPES +BIGINT +---- RESULTS +0 +====