mirror of
https://github.com/apache/impala.git
synced 2025-12-19 18:12:08 -05:00
IMPALA-197: Outer join on constant expressions returns incorrect results.
This commit is contained in:
committed by
Henry Robinson
parent
c9040aee22
commit
861ba05989
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ thirdparty
|
||||
cscope.files
|
||||
cscope.out
|
||||
org.eclipse.jdt.core.prefs
|
||||
org.eclipse.jdt.ui.prefs
|
||||
*benchmark_results.csv*
|
||||
load-trevni-*-generated.sh
|
||||
load-*-generated.sql
|
||||
|
||||
@@ -37,7 +37,7 @@ add_library(Exprs
|
||||
is-null-predicate.cc
|
||||
like-predicate.cc
|
||||
math-functions.cc
|
||||
null-literal.cc
|
||||
null-literal.cc
|
||||
opcode-registry.cc
|
||||
slot-ref.cc
|
||||
string-literal.cc
|
||||
@@ -45,6 +45,7 @@ add_library(Exprs
|
||||
timestamp-functions.cc
|
||||
timestamp-literal.cc
|
||||
timezone_db.cc
|
||||
tuple-is-null-predicate.cc
|
||||
utility-functions.cc
|
||||
)
|
||||
|
||||
|
||||
@@ -36,7 +36,52 @@ void* ConditionalFunctions::IfBool(Expr* e, TupleRow* row) {
|
||||
return &e->result_.bool_val;
|
||||
}
|
||||
|
||||
void* ConditionalFunctions::IfTinyint(Expr* e, TupleRow* row) {
|
||||
DCHECK_EQ(e->GetNumChildren(), 3);
|
||||
bool* cond = reinterpret_cast<bool*>(e->children()[0]->GetValue(row));
|
||||
if (cond == NULL || !*cond) {
|
||||
int8_t* else_val = reinterpret_cast<int8_t*>(e->children()[2]->GetValue(row));
|
||||
if (else_val == NULL) return NULL;
|
||||
e->result_.tinyint_val = *else_val;
|
||||
} else {
|
||||
int8_t* then_val = reinterpret_cast<int8_t*>(e->children()[1]->GetValue(row));
|
||||
if (then_val == NULL) return NULL;
|
||||
e->result_.tinyint_val = *then_val;
|
||||
}
|
||||
return &e->result_.tinyint_val;
|
||||
}
|
||||
|
||||
void* ConditionalFunctions::IfSmallint(Expr* e, TupleRow* row) {
|
||||
DCHECK_EQ(e->GetNumChildren(), 3);
|
||||
bool* cond = reinterpret_cast<bool*>(e->children()[0]->GetValue(row));
|
||||
if (cond == NULL || !*cond) {
|
||||
int16_t* else_val = reinterpret_cast<int16_t*>(e->children()[2]->GetValue(row));
|
||||
if (else_val == NULL) return NULL;
|
||||
e->result_.smallint_val = *else_val;
|
||||
} else {
|
||||
int16_t* then_val = reinterpret_cast<int16_t*>(e->children()[1]->GetValue(row));
|
||||
if (then_val == NULL) return NULL;
|
||||
e->result_.smallint_val = *then_val;
|
||||
}
|
||||
return &e->result_.smallint_val;
|
||||
}
|
||||
|
||||
void* ConditionalFunctions::IfInt(Expr* e, TupleRow* row) {
|
||||
DCHECK_EQ(e->GetNumChildren(), 3);
|
||||
bool* cond = reinterpret_cast<bool*>(e->children()[0]->GetValue(row));
|
||||
if (cond == NULL || !*cond) {
|
||||
int32_t* else_val = reinterpret_cast<int32_t*>(e->children()[2]->GetValue(row));
|
||||
if (else_val == NULL) return NULL;
|
||||
e->result_.int_val = *else_val;
|
||||
} else {
|
||||
int32_t* then_val = reinterpret_cast<int32_t*>(e->children()[1]->GetValue(row));
|
||||
if (then_val == NULL) return NULL;
|
||||
e->result_.int_val = *then_val;
|
||||
}
|
||||
return &e->result_.int_val;
|
||||
}
|
||||
|
||||
void* ConditionalFunctions::IfBigint(Expr* e, TupleRow* row) {
|
||||
DCHECK_EQ(e->GetNumChildren(), 3);
|
||||
bool* cond = reinterpret_cast<bool*>(e->children()[0]->GetValue(row));
|
||||
if (cond == NULL || !*cond) {
|
||||
@@ -52,6 +97,21 @@ void* ConditionalFunctions::IfInt(Expr* e, TupleRow* row) {
|
||||
}
|
||||
|
||||
void* ConditionalFunctions::IfFloat(Expr* e, TupleRow* row) {
|
||||
DCHECK_EQ(e->GetNumChildren(), 3);
|
||||
bool* cond = reinterpret_cast<bool*>(e->children()[0]->GetValue(row));
|
||||
if (cond == NULL || !*cond) {
|
||||
float* else_val = reinterpret_cast<float*>(e->children()[2]->GetValue(row));
|
||||
if (else_val == NULL) return NULL;
|
||||
e->result_.float_val = *else_val;
|
||||
} else {
|
||||
float* then_val = reinterpret_cast<float*>(e->children()[1]->GetValue(row));
|
||||
if (then_val == NULL) return NULL;
|
||||
e->result_.float_val = *then_val;
|
||||
}
|
||||
return &e->result_.float_val;
|
||||
}
|
||||
|
||||
void* ConditionalFunctions::IfDouble(Expr* e, TupleRow* row) {
|
||||
DCHECK_EQ(e->GetNumChildren(), 3);
|
||||
bool* cond = reinterpret_cast<bool*>(e->children()[0]->GetValue(row));
|
||||
if (cond == NULL || !*cond) {
|
||||
|
||||
@@ -26,8 +26,12 @@ class TupleRow;
|
||||
class ConditionalFunctions {
|
||||
public:
|
||||
static void* IfBool(Expr* e, TupleRow* row);
|
||||
static void* IfSmallint(Expr* e, TupleRow* row);
|
||||
static void* IfTinyint(Expr* e, TupleRow* row);
|
||||
static void* IfInt(Expr* e, TupleRow* row);
|
||||
static void* IfBigint(Expr* e, TupleRow* row);
|
||||
static void* IfFloat(Expr* e, TupleRow* row);
|
||||
static void* IfDouble(Expr* e, TupleRow* row);
|
||||
static void* IfString(Expr* e, TupleRow* row);
|
||||
static void* IfTimestamp(Expr* e, TupleRow* row);
|
||||
static void* CoalesceBool(Expr* e, TupleRow* row);
|
||||
|
||||
@@ -2222,8 +2222,8 @@ TEST_F(ExprTest, ConditionalFunctions) {
|
||||
// false or NULL should return the third.
|
||||
TestValue("if(TRUE, FALSE, TRUE)", TYPE_BOOLEAN, false);
|
||||
TestValue("if(FALSE, FALSE, TRUE)", TYPE_BOOLEAN, true);
|
||||
TestValue("if(TRUE, 10, 20)", TYPE_BIGINT, 10);
|
||||
TestValue("if(FALSE, 10, 20)", TYPE_BIGINT, 20);
|
||||
TestValue("if(TRUE, 10, 20)", TYPE_TINYINT, 10);
|
||||
TestValue("if(FALSE, 10, 20)", TYPE_TINYINT, 20);
|
||||
TestValue("if(TRUE, 5.5, 8.8)", TYPE_DOUBLE, 5.5);
|
||||
TestValue("if(FALSE, 5.5, 8.8)", TYPE_DOUBLE, 8.8);
|
||||
TestStringValue("if(TRUE, 'abc', 'defgh')", "abc");
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "exprs/opcode-registry.h"
|
||||
#include "exprs/string-literal.h"
|
||||
#include "exprs/timestamp-literal.h"
|
||||
#include "exprs/tuple-is-null-predicate.h"
|
||||
#include "gen-cpp/Exprs_types.h"
|
||||
#include "gen-cpp/Data_types.h"
|
||||
#include "runtime/runtime-state.h"
|
||||
@@ -350,6 +351,10 @@ Status Expr::CreateExpr(ObjectPool* pool, const TExprNode& texpr_node, Expr** ex
|
||||
*expr = pool->Add(new StringLiteral(texpr_node));
|
||||
return Status::OK;
|
||||
}
|
||||
case TExprNodeType::TUPLE_IS_NULL_PRED: {
|
||||
*expr = pool->Add(new TupleIsNullPredicate(texpr_node));
|
||||
return Status::OK;
|
||||
}
|
||||
default:
|
||||
stringstream os;
|
||||
os << "Unknown expr node type: " << texpr_node.node_type;
|
||||
|
||||
67
be/src/exprs/tuple-is-null-predicate.cc
Normal file
67
be/src/exprs/tuple-is-null-predicate.cc
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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 "exprs/tuple-is-null-predicate.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "gen-cpp/Exprs_types.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace impala {
|
||||
|
||||
void* TupleIsNullPredicate::ComputeFn(Expr* e, TupleRow* row) {
|
||||
TupleIsNullPredicate* p = static_cast<TupleIsNullPredicate*>(e);
|
||||
// Return true if all of the tuples in tuple_idxs_ are NULL.
|
||||
p->result_.bool_val = true;
|
||||
for (int i = 0; i < p->tuple_idxs_.size(); ++i) {
|
||||
if (row->GetTuple(p->tuple_idxs_[i]) != NULL) {
|
||||
p->result_.bool_val = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return &p->result_.bool_val;
|
||||
}
|
||||
|
||||
TupleIsNullPredicate::TupleIsNullPredicate(const TExprNode& node)
|
||||
: Predicate(node),
|
||||
tuple_ids_(node.tuple_is_null_pred.tuple_ids.begin(),
|
||||
node.tuple_is_null_pred.tuple_ids.end()) {
|
||||
}
|
||||
|
||||
Status TupleIsNullPredicate::Prepare(RuntimeState* state, const RowDescriptor& row_desc) {
|
||||
RETURN_IF_ERROR(Expr::PrepareChildren(state, row_desc));
|
||||
DCHECK_EQ(0, children_.size());
|
||||
// Resolve tuple ids to tuple indexes.
|
||||
for (int i = 0; i < tuple_ids_.size(); ++i) {
|
||||
int32_t tuple_idx = row_desc.GetTupleIdx(tuple_ids_[i]);
|
||||
DCHECK(row_desc.TupleIsNullable(tuple_idx));
|
||||
tuple_idxs_.push_back(tuple_idx);
|
||||
}
|
||||
compute_fn_ = ComputeFn;
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
string TupleIsNullPredicate::DebugString() const {
|
||||
stringstream out;
|
||||
out << "TupleIsNullPredicate(tupleids=[";
|
||||
for (int i = 0; i < tuple_ids_.size(); ++i) {
|
||||
out << (i == 0 ? "" : " ") << tuple_ids_[i];
|
||||
}
|
||||
out << "])";
|
||||
return out.str();
|
||||
}
|
||||
|
||||
}
|
||||
42
be/src/exprs/tuple-is-null-predicate.h
Normal file
42
be/src/exprs/tuple-is-null-predicate.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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_EXPRS_TUPLE_IS_NULL_PREDICATE_H_
|
||||
#define IMPALA_EXPRS_TUPLE_IS_NULL_PREDICATE_H_
|
||||
|
||||
#include "exprs/predicate.h"
|
||||
|
||||
namespace impala {
|
||||
|
||||
class TExprNode;
|
||||
|
||||
class TupleIsNullPredicate: public Predicate {
|
||||
protected:
|
||||
friend class Expr;
|
||||
|
||||
TupleIsNullPredicate(const TExprNode& node);
|
||||
|
||||
virtual Status Prepare(RuntimeState* state, const RowDescriptor& row_desc);
|
||||
virtual std::string DebugString() const;
|
||||
|
||||
private:
|
||||
static void* ComputeFn(Expr* e, TupleRow* row);
|
||||
std::vector<TupleId> tuple_ids_;
|
||||
std::vector<int32_t> tuple_idxs_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -199,10 +199,18 @@ functions = [
|
||||
# Conditional Functions
|
||||
['Conditional_If', 'BOOLEAN', ['BOOLEAN', 'BOOLEAN', 'BOOLEAN'], \
|
||||
'ConditionalFunctions::IfBool', ['if']],
|
||||
['Conditional_If', 'BIGINT', ['BOOLEAN', 'BIGINT', 'BIGINT'], \
|
||||
['Conditional_If', 'TINYINT', ['BOOLEAN', 'TINYINT', 'TINYINT'], \
|
||||
'ConditionalFunctions::IfTinyint', ['if']],
|
||||
['Conditional_If', 'SMALLINT', ['BOOLEAN', 'SMALLINT', 'SMALLINT'], \
|
||||
'ConditionalFunctions::IfSmallint', ['if']],
|
||||
['Conditional_If', 'INT', ['BOOLEAN', 'INT', 'INT'], \
|
||||
'ConditionalFunctions::IfInt', ['if']],
|
||||
['Conditional_If', 'DOUBLE', ['BOOLEAN', 'DOUBLE', 'DOUBLE'], \
|
||||
['Conditional_If', 'BIGINT', ['BOOLEAN', 'BIGINT', 'BIGINT'], \
|
||||
'ConditionalFunctions::IfBigint', ['if']],
|
||||
['Conditional_If', 'FLOAT', ['BOOLEAN', 'FLOAT', 'FLOAT'], \
|
||||
'ConditionalFunctions::IfFloat', ['if']],
|
||||
['Conditional_If', 'DOUBLE', ['BOOLEAN', 'DOUBLE', 'DOUBLE'], \
|
||||
'ConditionalFunctions::IfDouble', ['if']],
|
||||
['Conditional_If', 'STRING', ['BOOLEAN', 'STRING', 'STRING'], \
|
||||
'ConditionalFunctions::IfString', ['if']],
|
||||
['Conditional_If', 'TIMESTAMP', ['BOOLEAN', 'TIMESTAMP', 'TIMESTAMP'], \
|
||||
|
||||
@@ -37,6 +37,7 @@ enum TExprNodeType {
|
||||
NULL_LITERAL,
|
||||
SLOT_REF,
|
||||
STRING_LITERAL,
|
||||
TUPLE_IS_NULL_PRED
|
||||
}
|
||||
|
||||
enum TAggregationOp {
|
||||
@@ -95,6 +96,10 @@ struct TLiteralPredicate {
|
||||
2: required bool is_null
|
||||
}
|
||||
|
||||
struct TTupleIsNullPredicate {
|
||||
1: required list<Types.TTupleId> tuple_ids
|
||||
}
|
||||
|
||||
struct TSlotRef {
|
||||
1: required Types.TSlotId slot_id
|
||||
}
|
||||
@@ -122,6 +127,7 @@ struct TExprNode {
|
||||
14: optional TLiteralPredicate literal_pred
|
||||
15: optional TSlotRef slot_ref
|
||||
16: optional TStringLiteral string_literal
|
||||
17: optional TTupleIsNullPredicate tuple_is_null_pred
|
||||
}
|
||||
|
||||
// A flattened representation of a tree of Expr nodes, obtained by depth-first
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
package com.cloudera.impala.analysis;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import com.cloudera.impala.catalog.Catalog;
|
||||
import com.cloudera.impala.common.AnalysisException;
|
||||
@@ -42,7 +40,7 @@ public class AnalysisContext {
|
||||
this.catalog = catalog;
|
||||
this.defaultDatabase = defaultDb;
|
||||
this.user = user;
|
||||
this.queryGlobals = createQueryGlobals();
|
||||
this.queryGlobals = Analyzer.createQueryGlobals();
|
||||
}
|
||||
|
||||
static public class AnalysisResult {
|
||||
@@ -206,17 +204,4 @@ public class AnalysisContext {
|
||||
|
||||
public TQueryGlobals getQueryGlobals() { return queryGlobals; }
|
||||
|
||||
/**
|
||||
* Create query global parameters to be set in each TPlanExecRequest.
|
||||
*/
|
||||
private TQueryGlobals createQueryGlobals() {
|
||||
SimpleDateFormat formatter =
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSSSS");
|
||||
TQueryGlobals queryGlobals = new TQueryGlobals();
|
||||
Calendar currentDate = Calendar.getInstance();
|
||||
String nowStr = formatter.format(currentDate.getTime());
|
||||
queryGlobals.setNow_string(nowStr);
|
||||
return queryGlobals;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
|
||||
package com.cloudera.impala.analysis;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
@@ -112,7 +114,7 @@ public class Analyzer {
|
||||
*/
|
||||
public Analyzer(Catalog catalog) {
|
||||
this(catalog, Catalog.DEFAULT_DB, System.getProperty("user.name"),
|
||||
new TQueryGlobals());
|
||||
createQueryGlobals());
|
||||
}
|
||||
|
||||
public Analyzer(Catalog catalog, String defaultDb, String user,
|
||||
@@ -635,4 +637,17 @@ public class Analyzer {
|
||||
public TQueryGlobals getQueryGlobals() {
|
||||
return queryGlobals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create query global parameters to be set in each TPlanExecRequest.
|
||||
*/
|
||||
public static TQueryGlobals createQueryGlobals() {
|
||||
SimpleDateFormat formatter =
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSSSS");
|
||||
TQueryGlobals queryGlobals = new TQueryGlobals();
|
||||
Calendar currentDate = Calendar.getInstance();
|
||||
String nowStr = formatter.format(currentDate.getTime());
|
||||
queryGlobals.setNow_string(nowStr);
|
||||
return queryGlobals;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.cloudera.impala.analysis;
|
||||
import java.util.List;
|
||||
|
||||
import com.cloudera.impala.common.AnalysisException;
|
||||
import com.cloudera.impala.common.InternalException;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,7 @@ public class BaseTableRef extends TableRef {
|
||||
* Register this table ref and then analyze the Join clause.
|
||||
*/
|
||||
@Override
|
||||
public void analyze(Analyzer analyzer) throws AnalysisException {
|
||||
public void analyze(Analyzer analyzer) throws AnalysisException, InternalException {
|
||||
desc = analyzer.registerBaseTableRef(this);
|
||||
isAnalyzed = true; // true that we have assigned desc
|
||||
analyzeJoin(analyzer);
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.cloudera.impala.common.AnalysisException;
|
||||
import com.cloudera.impala.common.InternalException;
|
||||
import com.cloudera.impala.service.FeSupport;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@@ -46,6 +47,8 @@ public class InlineViewRef extends TableRef {
|
||||
// maps of all enclosed inlined views; in other words, all SlotRefs
|
||||
// contained in rhs exprs reference base tables, not contained inline views
|
||||
// (and therefore can be evaluated at runtime).
|
||||
// Some rhs exprs are wrapped into IF(TupleIsNull(), NULL, expr) by calling
|
||||
// makeOutputNullable() if this inline view is a nullable side of an outer join.
|
||||
private final Expr.SubstitutionMap sMap = new Expr.SubstitutionMap();
|
||||
|
||||
/**
|
||||
@@ -84,18 +87,80 @@ public class InlineViewRef extends TableRef {
|
||||
materializedTupleIds.add(desc.getId());
|
||||
}
|
||||
|
||||
// Now do the remaining join analysis
|
||||
analyzeJoin(analyzer);
|
||||
|
||||
// create sMap
|
||||
for (int i = 0; i < queryStmt.getColLabels().size(); ++i) {
|
||||
String colName = queryStmt.getColLabels().get(i);
|
||||
SlotDescriptor slotD = analyzer.registerColumnRef(getAliasAsName(), colName);
|
||||
Expr colExpr = queryStmt.getResultExprs().get(i);
|
||||
sMap.lhs.add(new SlotRef(slotD));
|
||||
SlotRef slotRef = new SlotRef(slotD);
|
||||
sMap.lhs.add(slotRef);
|
||||
sMap.rhs.add(colExpr);
|
||||
}
|
||||
LOG.debug("inline view smap: " + sMap.debugString());
|
||||
|
||||
// Now do the remaining join analysis
|
||||
analyzeJoin(analyzer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes each rhs expr in sMap nullable if necessary by wrapping as follows:
|
||||
* IF(TupleIsNull(), NULL, rhs expr)
|
||||
* Should be called only if this inline view is a nullable side of an outer join.
|
||||
*
|
||||
* We need to make an rhs exprs nullable if it evaluates to a non-NULL value
|
||||
* when all of its contained SlotRefs evaluate to NULL.
|
||||
* For example, constant exprs need to be wrapped or an expr such as
|
||||
* 'case slotref is null then 1 else 2 end'
|
||||
*/
|
||||
protected void makeOutputNullable(Analyzer analyzer)
|
||||
throws AnalysisException, InternalException {
|
||||
// Gather all unique rhs SlotRefs into rhsSlotRefs
|
||||
List<SlotRef> rhsSlotRefs = Lists.newArrayList();
|
||||
Expr.collectList(sMap.rhs, SlotRef.class, rhsSlotRefs);
|
||||
// Map for substituting SlotRefs with NullLiterals.
|
||||
Expr.SubstitutionMap nullSMap = new Expr.SubstitutionMap();
|
||||
for (SlotRef rhsSlotRef: rhsSlotRefs) {
|
||||
nullSMap.lhs.add(rhsSlotRef.clone());
|
||||
nullSMap.rhs.add(new NullLiteral());
|
||||
}
|
||||
|
||||
// Make rhs exprs nullable if necessary.
|
||||
for (int i = 0; i < sMap.rhs.size(); ++i) {
|
||||
List<Expr> params = Lists.newArrayList();
|
||||
if (!requiresNullWrapping(analyzer, sMap.rhs.get(i), nullSMap)) continue;
|
||||
params.add(new TupleIsNullPredicate(materializedTupleIds));
|
||||
params.add(new NullLiteral());
|
||||
params.add(sMap.rhs.get(i));
|
||||
Expr ifExpr = new FunctionCallExpr("if", params);
|
||||
ifExpr.analyze(analyzer);
|
||||
sMap.rhs.set(i, ifExpr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all SloRefs in expr with a NullLiteral using nullSMap, and evaluates the
|
||||
* resulting constant expr. Returns true if the constant expr yields a non-NULL value,
|
||||
* false otherwise.
|
||||
*/
|
||||
private boolean requiresNullWrapping(Analyzer analyzer, Expr expr,
|
||||
Expr.SubstitutionMap nullSMap) throws InternalException {
|
||||
// If the expr is already wrapped in an IF(TupleIsNull(), NULL, expr)
|
||||
// then do not try to execute it.
|
||||
if (expr.contains(TupleIsNullPredicate.class)) return true;
|
||||
|
||||
// Replace all SlotRefs in expr with NullLiterals, and wrap the result
|
||||
// into an IS NOT NULL predicate.
|
||||
Expr isNotNullLiteralPred = new IsNullPredicate(expr.clone(nullSMap), true);
|
||||
Preconditions.checkState(isNotNullLiteralPred.isConstant());
|
||||
// analyze to insert casts, etc.
|
||||
try {
|
||||
isNotNullLiteralPred.analyze(analyzer);
|
||||
} catch (AnalysisException e) {
|
||||
// this should never happen
|
||||
throw new InternalException(
|
||||
"couldn't analyze predicate " + isNotNullLiteralPred.toSql(), e);
|
||||
}
|
||||
return FeSupport.EvalPredicate(isNotNullLiteralPred, analyzer.getQueryGlobals());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,7 +179,7 @@ public class InlineViewRef extends TableRef {
|
||||
return inlineViewAnalyzer;
|
||||
}
|
||||
|
||||
public Expr.SubstitutionMap getSelectListExprSMap() {
|
||||
public Expr.SubstitutionMap getExprSMap() {
|
||||
Preconditions.checkState(isAnalyzed);
|
||||
return sMap;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public class SelectStmt extends QueryStmt {
|
||||
for (TableRef tblRef: tableRefs) {
|
||||
if (tblRef instanceof InlineViewRef) {
|
||||
InlineViewRef inlineViewRef = (InlineViewRef) tblRef;
|
||||
sMap = Expr.SubstitutionMap.combine(sMap, inlineViewRef.getSelectListExprSMap());
|
||||
sMap = Expr.SubstitutionMap.combine(sMap, inlineViewRef.getExprSMap());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.List;
|
||||
|
||||
import com.cloudera.impala.catalog.Table;
|
||||
import com.cloudera.impala.common.AnalysisException;
|
||||
import com.cloudera.impala.common.InternalException;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -145,7 +146,8 @@ public abstract class TableRef extends ParseNodeBase {
|
||||
* The join clause can only be analyzed after the left table has been analyzed
|
||||
* and the TupleDescriptor (desc) of this table has been created.
|
||||
*/
|
||||
public void analyzeJoin(Analyzer analyzer) throws AnalysisException {
|
||||
public void analyzeJoin(Analyzer analyzer)
|
||||
throws AnalysisException, InternalException {
|
||||
Preconditions.checkState(desc != null);
|
||||
analyzeJoinHints();
|
||||
|
||||
@@ -219,6 +221,13 @@ public abstract class TableRef extends ParseNodeBase {
|
||||
throw new AnalysisException(joinOpToSql() + " requires an ON or USING clause.");
|
||||
}
|
||||
|
||||
// Make constant expressions from inline view refs nullable in its substitution map.
|
||||
if (lhsIsNullable && leftTblRef instanceof InlineViewRef) {
|
||||
((InlineViewRef) leftTblRef).makeOutputNullable(analyzer);
|
||||
}
|
||||
if (rhsIsNullable && this instanceof InlineViewRef) {
|
||||
((InlineViewRef) this).makeOutputNullable(analyzer);
|
||||
}
|
||||
}
|
||||
|
||||
private String joinOpToSql() {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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.analysis;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.cloudera.impala.thrift.TExprNode;
|
||||
import com.cloudera.impala.thrift.TExprNodeType;
|
||||
import com.cloudera.impala.thrift.TTupleIsNullPredicate;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Internal expr that returns true if all of the given tuples are NULL, otherwise false.
|
||||
* Used to make exprs originating from an inline view nullable in an outer join.
|
||||
* The given tupleIds must be materialized and nullable at the appropriate PlanNode.
|
||||
*/
|
||||
public class TupleIsNullPredicate extends Predicate {
|
||||
|
||||
private final List<TupleId> tupleIds;
|
||||
|
||||
public TupleIsNullPredicate(List<TupleId> tupleIds) {
|
||||
Preconditions.checkState(tupleIds != null && !tupleIds.isEmpty());
|
||||
this.tupleIds = tupleIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toThrift(TExprNode msg) {
|
||||
msg.node_type = TExprNodeType.TUPLE_IS_NULL_PRED;
|
||||
msg.tuple_is_null_pred = new TTupleIsNullPredicate();
|
||||
for (TupleId tid: tupleIds) {
|
||||
msg.tuple_is_null_pred.addToTuple_ids(tid.asInt());
|
||||
}
|
||||
}
|
||||
|
||||
public List<TupleId> getTupleIds() {
|
||||
return tupleIds;
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,6 @@ package com.cloudera.impala.planner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.thrift.TException;
|
||||
import org.apache.thrift.TSerializer;
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -29,7 +26,6 @@ import com.cloudera.impala.analysis.SlotRef;
|
||||
import com.cloudera.impala.common.AnalysisException;
|
||||
import com.cloudera.impala.common.InternalException;
|
||||
import com.cloudera.impala.service.FeSupport;
|
||||
import com.cloudera.impala.thrift.TExpr;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@@ -81,17 +77,8 @@ public class SingleColumnFilter {
|
||||
}
|
||||
|
||||
// call backend
|
||||
TExpr thriftExpr = literalConjunct.treeToThrift();
|
||||
TSerializer serializer = new TSerializer(new TBinaryProtocol.Factory());
|
||||
try {
|
||||
if (!FeSupport.EvalPredicate(serializer.serialize(thriftExpr),
|
||||
serializer.serialize(analyzer.getQueryGlobals()))) {
|
||||
return false;
|
||||
}
|
||||
} catch (TException e) {
|
||||
// this should never happen
|
||||
throw new InternalException(
|
||||
"couldn't execute predicate " + literalConjunct.toSql(), e);
|
||||
if (!FeSupport.EvalPredicate(literalConjunct, analyzer.getQueryGlobals())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
|
||||
package com.cloudera.impala.planner;
|
||||
|
||||
import org.apache.thrift.TException;
|
||||
import org.apache.thrift.TSerializer;
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -28,7 +25,6 @@ import com.cloudera.impala.analysis.Predicate;
|
||||
import com.cloudera.impala.common.AnalysisException;
|
||||
import com.cloudera.impala.common.InternalException;
|
||||
import com.cloudera.impala.service.FeSupport;
|
||||
import com.cloudera.impala.thrift.TExpr;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
@@ -104,17 +100,7 @@ public class ValueRange {
|
||||
}
|
||||
|
||||
// call backend
|
||||
TExpr thriftExpr = p.treeToThrift();
|
||||
TSerializer serializer = new TSerializer(new TBinaryProtocol.Factory());
|
||||
try {
|
||||
boolean result = FeSupport.EvalPredicate(serializer.serialize(thriftExpr),
|
||||
serializer.serialize(analyzer.getQueryGlobals()));
|
||||
return result;
|
||||
} catch (TException e) {
|
||||
// this should never happen
|
||||
throw new InternalException(
|
||||
"couldn't execute predicate " + p.toSql() + "\n" + e.toString());
|
||||
}
|
||||
return FeSupport.EvalPredicate(p, analyzer.getQueryGlobals());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,19 @@ package com.cloudera.impala.service;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.thrift.TException;
|
||||
import org.apache.thrift.TSerializer;
|
||||
import org.apache.thrift.protocol.TBinaryProtocol;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.cloudera.impala.analysis.Expr;
|
||||
import com.cloudera.impala.catalog.PrimitiveType;
|
||||
import com.cloudera.impala.common.InternalException;
|
||||
import com.cloudera.impala.thrift.TExpr;
|
||||
import com.cloudera.impala.thrift.TQueryGlobals;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* This class provides the Impala executor functionality to the FE.
|
||||
* fe-support.cc implements all the native calls.
|
||||
@@ -35,7 +45,21 @@ public class FeSupport {
|
||||
public native static boolean NativeEvalPredicate(byte[] thriftPredicate,
|
||||
byte[] thriftQueryGlobals);
|
||||
|
||||
public static boolean EvalPredicate(byte[] thriftPredicate, byte[] thriftQueryGlobals) {
|
||||
public static boolean EvalPredicate(Expr pred, TQueryGlobals queryGlobals)
|
||||
throws InternalException {
|
||||
Preconditions.checkState(pred.getType() == PrimitiveType.BOOLEAN);
|
||||
TExpr thriftPred = pred.treeToThrift();
|
||||
TSerializer serializer = new TSerializer(new TBinaryProtocol.Factory());
|
||||
try {
|
||||
return FeSupport.EvalPredicate(serializer.serialize(thriftPred),
|
||||
serializer.serialize(queryGlobals));
|
||||
} catch (TException e) {
|
||||
// this should never happen
|
||||
throw new InternalException("couldn't execute predicate " + pred.toSql(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean EvalPredicate(byte[] thriftPredicate, byte[] thriftQueryGlobals) {
|
||||
try {
|
||||
return NativeEvalPredicate(thriftPredicate, thriftQueryGlobals);
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
|
||||
@@ -455,6 +455,71 @@ string
|
||||
'1'
|
||||
====
|
||||
---- QUERY
|
||||
# test nullable expressions from inline views in left outer join
|
||||
select x.id, y.a, y.b, y.id from alltypestiny x left outer join
|
||||
(select 10 as a, case when id is null then 11 else 12 end as b, id from alltypestiny)
|
||||
y on x.id = (y.id % 2)
|
||||
order by x.id, y.id limit 100
|
||||
---- TYPES
|
||||
int, tinyint, tinyint, int
|
||||
---- RESULTS
|
||||
0,10,12,0
|
||||
0,10,12,2
|
||||
0,10,12,4
|
||||
0,10,12,6
|
||||
1,10,12,1
|
||||
1,10,12,3
|
||||
1,10,12,5
|
||||
1,10,12,7
|
||||
2,NULL,NULL,NULL
|
||||
3,NULL,NULL,NULL
|
||||
4,NULL,NULL,NULL
|
||||
5,NULL,NULL,NULL
|
||||
6,NULL,NULL,NULL
|
||||
7,NULL,NULL,NULL
|
||||
====
|
||||
---- QUERY
|
||||
# test nullable expressions from inline views in full outer join
|
||||
select x.a, x.b, x.id, y.a, y.b, y.id from
|
||||
(select 10 as a, case when id is null then 11 else 12 end as b, id from alltypestiny) x
|
||||
full outer join
|
||||
(select 20 as a, case when id is null then 21 else 22 end as b, id from alltypestiny) y on x.id = (y.id + 4)
|
||||
order by x.id, y.id limit 100
|
||||
---- TYPES
|
||||
tinyint, tinyint, int, tinyint, tinyint, int
|
||||
---- RESULTS
|
||||
10,12,0,NULL,NULL,NULL
|
||||
10,12,1,NULL,NULL,NULL
|
||||
10,12,2,NULL,NULL,NULL
|
||||
10,12,3,NULL,NULL,NULL
|
||||
10,12,4,20,22,0
|
||||
10,12,5,20,22,1
|
||||
10,12,6,20,22,2
|
||||
10,12,7,20,22,3
|
||||
NULL,NULL,NULL,20,22,4
|
||||
NULL,NULL,NULL,20,22,5
|
||||
NULL,NULL,NULL,20,22,6
|
||||
NULL,NULL,NULL,20,22,7
|
||||
====
|
||||
---- QUERY
|
||||
# test nullable expressions wrapped multiple times
|
||||
select z.xa, z.xid, z.ya, z.yid, k.a, k.id from
|
||||
(select x.a as xa, x.id as xid, y.a as ya, y.id as yid from
|
||||
(select 10 as a, id from alltypestiny where id = 1) x
|
||||
full outer join
|
||||
(select 20 as a, id from alltypestiny where id = 1) y on x.id = (y.id + 2)
|
||||
) z
|
||||
full outer join
|
||||
(select 30 as a, id from alltypestiny where id = 1) k on z.xid = (k.id + 3)
|
||||
order by z.xid, z.yid, k.id limit 100
|
||||
---- TYPES
|
||||
tinyint, int, tinyint, int, tinyint, int
|
||||
---- RESULTS
|
||||
10,1,NULL,NULL,NULL,NULL
|
||||
NULL,NULL,20,1,NULL,NULL
|
||||
NULL,NULL,NULL,NULL,30,1
|
||||
====
|
||||
---- QUERY
|
||||
# right outer join requires the join op to be partitioned, otherwise non-matches cause
|
||||
# duplicates
|
||||
select count(*)
|
||||
|
||||
Reference in New Issue
Block a user