Skip to content

Commit

Permalink
feat: Capture constant folding errors during expression compilation
Browse files Browse the repository at this point in the history
  • Loading branch information
pramodsatya committed Feb 18, 2025
1 parent 710d449 commit dc758a5
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 23 deletions.
6 changes: 4 additions & 2 deletions velox/expression/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1753,9 +1753,11 @@ std::vector<common::Subfield> Expr::extractSubfields() const {
ExprSet::ExprSet(
const std::vector<core::TypedExprPtr>& sources,
core::ExecCtx* execCtx,
bool enableConstantFolding)
bool enableConstantFolding,
bool ignoreConstantFoldError)
: execCtx_(execCtx) {
exprs_ = compileExpressions(sources, execCtx, this, enableConstantFolding);
exprs_ = compileExpressions(
sources, execCtx, this, enableConstantFolding, ignoreConstantFoldError);
std::vector<FieldReference*> allDistinctFields;
for (auto& expr : exprs_) {
Expr::mergeFields(
Expand Down
20 changes: 19 additions & 1 deletion velox/expression/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -702,12 +702,30 @@ using ExprPtr = std::shared_ptr<Expr>;
// this ExprSet. If however such an association cannot be made with certainty,
// then its advisable to pre-load all lazy vectors to avoid issues associated
// with partial loading.
//
// The input typed expressions are compiled in the constructor to exec::Expr
// using the helper function compileExpressions. Constant folding is enabled by
// default during expression compilation and it can be disabled by setting the
// argument enableConstantFolding to false. Constant folding has a subtle
// gotcha: if folding a constant expression deterministically throws, we can't
// throw at expression compilation time yet because we can't guarantee that this
// expression would actually need to be evaluated.
//
// If folding an expression throws an exception, the user can choose whether to
// just ignore it or receive a FailFunction in place of the expression by
// setting argument ignoreConstantFoldError to either true or false
// respectively. By default, we just ignore such exceptions and leave the
// expression as-is. If this expression is hit at execution time and needs to be
// evaluated, it will throw and fail the query anyway. If not, in case this
// expression is never hit at execution time (for instance, if other arguments
// are all null in a function with default null behavior), the query won't fail.
class ExprSet {
public:
explicit ExprSet(
const std::vector<core::TypedExprPtr>& source,
core::ExecCtx* execCtx,
bool enableConstantFolding = true);
bool enableConstantFolding = true,
bool ignoreConstantFoldError = true);

virtual ~ExprSet();

Expand Down
51 changes: 32 additions & 19 deletions velox/expression/ExprCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ ExprPtr compileExpression(
const core::QueryConfig& config,
memory::MemoryPool* pool,
const std::unordered_set<std::string>& flatteningCandidates,
bool enableConstantFolding);
bool enableConstantFolding,
bool ignoreConstantFoldError = true);

std::vector<ExprPtr> compileInputs(
const TypedExprPtr& expr,
Expand Down Expand Up @@ -299,7 +300,10 @@ std::shared_ptr<Expr> compileLambda(
config.exprTrackCpuUsage());
}

ExprPtr tryFoldIfConstant(const ExprPtr& expr, Scope* scope) {
ExprPtr tryFoldIfConstant(
const ExprPtr& expr,
Scope* scope,
bool ignoreConstantFoldError) {
if (expr->isConstant() && scope->exprSet->execCtx()) {
try {
auto rowType = ROW({}, {});
Expand All @@ -324,18 +328,22 @@ ExprPtr tryFoldIfConstant(const ExprPtr& expr, Scope* scope) {
}
return resultExpr;
}
// Constant folding has a subtle gotcha: if folding a constant expression
// deterministically throws, we can't throw at expression compilation time
// yet because we can't guarantee that this expression would actually need
// to be evaluated.
//
// So, here, if folding an expression throws an exception, we just ignore it
// and leave the expression as-is. If this expression is hit at execution
// time and needs to be evaluated, it will throw and fail the query anyway.
// If not, in case this expression is never hit at execution time (for
// instance, if other arguments are all null in a function with default null
// behavior), the query won't fail.
catch (const VeloxUserError&) {
if (!ignoreConstantFoldError) {
// TODO: Map errors to different error codes as per StandardErrorCode
// in presto-spi.
int errorCode = 8;
std::string errorMessage = "ignore failure message";
auto failTypedExpr = std::make_shared<core::CallTypedExpr>(
UNKNOWN(),
std::vector<core::TypedExprPtr>{
std::make_shared<core::ConstantTypedExpr>(INTEGER(), errorCode),
std::make_shared<core::ConstantTypedExpr>(
VARCHAR(), errorMessage)},
"fail");
auto failExprSet = ExprSet({failTypedExpr}, scope->exprSet->execCtx());
return failExprSet.exprs().front();
}
}
}
return expr;
Expand Down Expand Up @@ -372,7 +380,8 @@ ExprPtr compileRewrittenExpression(
const core::QueryConfig& config,
memory::MemoryPool* pool,
const std::unordered_set<std::string>& flatteningCandidates,
bool enableConstantFolding) {
bool enableConstantFolding,
bool ignoreConstantFoldError) {
ExprPtr alreadyCompiled = getAlreadyCompiled(expr.get(), &scope->visited);
if (alreadyCompiled) {
if (!alreadyCompiled->isMultiplyReferenced()) {
Expand Down Expand Up @@ -522,7 +531,7 @@ ExprPtr compileRewrittenExpression(

// If the expression is constant folding it is redundant.
auto folded = enableConstantFolding && !isConstantExpr
? tryFoldIfConstant(result, scope)
? tryFoldIfConstant(result, scope, ignoreConstantFoldError)
: result;
scope->visited[expr.get()] = folded;
return folded;
Expand All @@ -534,7 +543,8 @@ ExprPtr compileExpression(
const core::QueryConfig& config,
memory::MemoryPool* pool,
const std::unordered_set<std::string>& flatteningCandidates,
bool enableConstantFolding) {
bool enableConstantFolding,
bool ignoreConstantFoldError) {
auto rewritten = rewriteExpression(expr);
if (rewritten.get() != expr.get()) {
scope->rewrittenExpressions.push_back(rewritten);
Expand All @@ -545,7 +555,8 @@ ExprPtr compileExpression(
config,
pool,
flatteningCandidates,
enableConstantFolding);
enableConstantFolding,
ignoreConstantFoldError);
}

/// Walk expression tree and collect names of functions used in CallTypedExpr
Expand Down Expand Up @@ -590,7 +601,8 @@ std::vector<std::shared_ptr<Expr>> compileExpressions(
const std::vector<TypedExprPtr>& sources,
core::ExecCtx* execCtx,
ExprSet* exprSet,
bool enableConstantFolding) {
bool enableConstantFolding,
bool ignoreConstantFoldError) {
Scope scope({}, nullptr, exprSet);
std::vector<std::shared_ptr<Expr>> exprs;
exprs.reserve(sources.size());
Expand All @@ -606,7 +618,8 @@ std::vector<std::shared_ptr<Expr>> compileExpressions(
execCtx->queryCtx()->queryConfig(),
execCtx->pool(),
flatteningCandidates,
enableConstantFolding));
enableConstantFolding,
ignoreConstantFoldError));
}
return exprs;
}
Expand Down
3 changes: 2 additions & 1 deletion velox/expression/ExprCompiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ std::vector<std::shared_ptr<Expr>> compileExpressions(
const std::vector<core::TypedExprPtr>& sources,
core::ExecCtx* execCtx,
ExprSet* exprSet,
bool enableConstantFolding = true);
bool enableConstantFolding = true,
bool ignoreConstantFoldError = true);

} // namespace facebook::velox::exec

0 comments on commit dc758a5

Please sign in to comment.