diff --git a/presto-native-execution/presto_cpp/main/CMakeLists.txt b/presto-native-execution/presto_cpp/main/CMakeLists.txt index abe71dd6f3fa..44f94bbdf047 100644 --- a/presto-native-execution/presto_cpp/main/CMakeLists.txt +++ b/presto-native-execution/presto_cpp/main/CMakeLists.txt @@ -51,6 +51,7 @@ target_link_libraries( presto_http presto_operators presto_velox_conversion + presto_expression_optimizer velox_aggregates velox_caching velox_common_base diff --git a/presto-native-execution/presto_cpp/main/PrestoServer.cpp b/presto-native-execution/presto_cpp/main/PrestoServer.cpp index 1f316144bbfc..1b04b1fb927e 100644 --- a/presto-native-execution/presto_cpp/main/PrestoServer.cpp +++ b/presto-native-execution/presto_cpp/main/PrestoServer.cpp @@ -1546,6 +1546,26 @@ void PrestoServer::registerSidecarEndpoints() { proxygen::ResponseHandler* downstream) { http::sendOkResponse(downstream, getFunctionsMetadata()); }); + rowExpressionOptimizer_ = + std::make_unique(); + httpServer_->registerPost( + "/v1/expressions", + [&](proxygen::HTTPMessage* message, + const std::vector>& body, + proxygen::ResponseHandler* downstream) { + json::array_t inputRowExpressions = + json::parse(util::extractMessageBody(body)); + auto result = + rowExpressionOptimizer_->optimize(message, inputRowExpressions); + if (result.second) { + VELOX_USER_CHECK( + result.first.is_array(), + "Output json should be an array of row expressions"); + http::sendOkResponse(downstream, result.first); + } else { + http::sendErrorResponse(downstream, result.first); + } + }); httpServer_->registerPost( "/v1/velox/plan", [server = this]( diff --git a/presto-native-execution/presto_cpp/main/PrestoServer.h b/presto-native-execution/presto_cpp/main/PrestoServer.h index 615d6bfb910d..2cab92072352 100644 --- a/presto-native-execution/presto_cpp/main/PrestoServer.h +++ b/presto-native-execution/presto_cpp/main/PrestoServer.h @@ -25,6 +25,7 @@ #include "presto_cpp/main/PeriodicHeartbeatManager.h" #include "presto_cpp/main/PrestoExchangeSource.h" #include "presto_cpp/main/PrestoServerOperations.h" +#include "presto_cpp/main/types/RowExpressionOptimizer.h" #include "presto_cpp/main/types/VeloxPlanValidator.h" #include "velox/common/caching/AsyncDataCache.h" #include "velox/common/memory/MemoryAllocator.h" @@ -291,6 +292,7 @@ class PrestoServer { std::string address_; std::string nodeLocation_; folly::SSLContextPtr sslContext_; + std::unique_ptr rowExpressionOptimizer_; }; } // namespace facebook::presto diff --git a/presto-native-execution/presto_cpp/main/types/CMakeLists.txt b/presto-native-execution/presto_cpp/main/types/CMakeLists.txt index c17e136e9984..174f7b3c9921 100644 --- a/presto-native-execution/presto_cpp/main/types/CMakeLists.txt +++ b/presto-native-execution/presto_cpp/main/types/CMakeLists.txt @@ -34,6 +34,12 @@ add_library(presto_velox_conversion OBJECT VeloxPlanConversion.cpp) target_link_libraries(presto_velox_conversion velox_type) +add_library(presto_expression_optimizer RowExpressionConverter.cpp + RowExpressionOptimizer.cpp) + +target_link_libraries(presto_expression_optimizer presto_type_converter + presto_types presto_protocol) + if(PRESTO_ENABLE_TESTING) add_subdirectory(tests) endif() diff --git a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.cpp b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.cpp index 2c2b2a3c5ea0..791e215fb0f3 100644 --- a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.cpp +++ b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.cpp @@ -33,32 +33,10 @@ std::string toJsonString(const T& value) { } std::string mapScalarFunction(const std::string& name) { - static const std::unordered_map kFunctionNames = { - // Operator overrides: com.facebook.presto.common.function.OperatorType - {"presto.default.$operator$add", "presto.default.plus"}, - {"presto.default.$operator$between", "presto.default.between"}, - {"presto.default.$operator$divide", "presto.default.divide"}, - {"presto.default.$operator$equal", "presto.default.eq"}, - {"presto.default.$operator$greater_than", "presto.default.gt"}, - {"presto.default.$operator$greater_than_or_equal", "presto.default.gte"}, - {"presto.default.$operator$is_distinct_from", - "presto.default.distinct_from"}, - {"presto.default.$operator$less_than", "presto.default.lt"}, - {"presto.default.$operator$less_than_or_equal", "presto.default.lte"}, - {"presto.default.$operator$modulus", "presto.default.mod"}, - {"presto.default.$operator$multiply", "presto.default.multiply"}, - {"presto.default.$operator$negation", "presto.default.negate"}, - {"presto.default.$operator$not_equal", "presto.default.neq"}, - {"presto.default.$operator$subtract", "presto.default.minus"}, - {"presto.default.$operator$subscript", "presto.default.subscript"}, - // Special form function overrides. - {"presto.default.in", "in"}, - }; - std::string lowerCaseName = boost::to_lower_copy(name); - auto it = kFunctionNames.find(lowerCaseName); - if (it != kFunctionNames.end()) { + auto it = kPrestoOperatorMap.find(lowerCaseName); + if (it != kPrestoOperatorMap.end()) { return it->second; } @@ -102,6 +80,15 @@ std::string getFunctionName(const protocol::SqlFunctionId& functionId) { } // namespace +const std::unordered_map veloxToPrestoOperatorMap() { + std::unordered_map veloxToPrestoOperatorMap; + for (const auto& entry : kPrestoOperatorMap) { + veloxToPrestoOperatorMap[entry.second] = entry.first; + } + veloxToPrestoOperatorMap.insert({"cast", "presto.default.$operator$cast"}); + return veloxToPrestoOperatorMap; +} + velox::variant VeloxExprConverter::getConstantValue( const velox::TypePtr& type, const protocol::Block& block) const { diff --git a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.h b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.h index f63a84ec35ad..06c326dfddc1 100644 --- a/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.h +++ b/presto-native-execution/presto_cpp/main/types/PrestoToVeloxExpr.h @@ -20,6 +20,30 @@ namespace facebook::presto { +static const std::unordered_map kPrestoOperatorMap = { + // Operator overrides: com.facebook.presto.common.function.OperatorType + {"presto.default.$operator$add", "presto.default.plus"}, + {"presto.default.$operator$between", "presto.default.between"}, + {"presto.default.$operator$divide", "presto.default.divide"}, + {"presto.default.$operator$equal", "presto.default.eq"}, + {"presto.default.$operator$greater_than", "presto.default.gt"}, + {"presto.default.$operator$greater_than_or_equal", "presto.default.gte"}, + {"presto.default.$operator$is_distinct_from", + "presto.default.distinct_from"}, + {"presto.default.$operator$less_than", "presto.default.lt"}, + {"presto.default.$operator$less_than_or_equal", "presto.default.lte"}, + {"presto.default.$operator$modulus", "presto.default.mod"}, + {"presto.default.$operator$multiply", "presto.default.multiply"}, + {"presto.default.$operator$negation", "presto.default.negate"}, + {"presto.default.$operator$not_equal", "presto.default.neq"}, + {"presto.default.$operator$subtract", "presto.default.minus"}, + {"presto.default.$operator$subscript", "presto.default.subscript"}, + // Special form function overrides. + {"presto.default.in", "in"}, +}; + +const std::unordered_map veloxToPrestoOperatorMap(); + class VeloxExprConverter { public: VeloxExprConverter(velox::memory::MemoryPool* pool, TypeParser* typeParser) diff --git a/presto-native-execution/presto_cpp/main/types/RowExpressionConverter.cpp b/presto-native-execution/presto_cpp/main/types/RowExpressionConverter.cpp new file mode 100644 index 000000000000..0ff5a118e67b --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/RowExpressionConverter.cpp @@ -0,0 +1,388 @@ +/* + * 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 "presto_cpp/main/types/RowExpressionConverter.h" +#include "velox/expression/FieldReference.h" + +using namespace facebook::presto; +using namespace facebook::velox; + +namespace facebook::presto::expression { + +namespace { +const std::string kConstant = "constant"; +const std::string kBoolean = "boolean"; +const std::string kVariable = "variable"; +const std::string kCall = "call"; +const std::string kStatic = "$static"; +const std::string kSpecial = "special"; +const std::string kCoalesce = "COALESCE"; +const std::string kRowConstructor = "ROW_CONSTRUCTOR"; +const std::string kSwitch = "SWITCH"; +const std::string kWhen = "WHEN"; + +protocol::TypeSignature getTypeSignature(const TypePtr& type) { + std::string typeSignature; + if (type->parameters().empty()) { + typeSignature = type->toString(); + boost::algorithm::to_lower(typeSignature); + } else if (type->isDecimal()) { + typeSignature = type->toString(); + } else { + std::string complexTypeString; + std::vector childTypes; + if (type->isRow()) { + complexTypeString = "row"; + childTypes = asRowType(type)->children(); + } else if (type->isArray()) { + complexTypeString = "array"; + childTypes = type->asArray().children(); + } else if (type->isMap()) { + complexTypeString = "map"; + const auto mapType = type->asMap(); + childTypes = {mapType.keyType(), mapType.valueType()}; + } else { + VELOX_USER_FAIL("Invalid type {}", type->toString()); + } + + typeSignature = complexTypeString + "("; + if (!childTypes.empty()) { + auto numChildren = childTypes.size(); + for (auto i = 0; i < numChildren - 1; i++) { + typeSignature += fmt::format("{},", getTypeSignature(childTypes[i])); + } + typeSignature += getTypeSignature(childTypes[numChildren - 1]); + } + typeSignature += ")"; + } + + return typeSignature; +} + +json toVariableReferenceExpression( + const std::shared_ptr& field) { + protocol::VariableReferenceExpression vexpr; + vexpr.name = field->name(); + vexpr._type = kVariable; + vexpr.type = getTypeSignature(field->type()); + json result; + protocol::to_json(result, vexpr); + + return result; +} + +bool isPrestoSpecialForm(const std::string& name) { + static const std::unordered_set kPrestoSpecialForms = { + "if", + "null_if", + "switch", + "when", + "is_null", + "coalesce", + "in", + "and", + "or", + "dereference", + "row_constructor", + "bind"}; + return kPrestoSpecialForms.count(name) != 0; +} + +json getWhenSpecialForm( + const exec::ExprPtr& input, + const json::array_t& whenArgs) { + json when; + when["@type"] = kSpecial; + when["form"] = kWhen; + when["arguments"] = whenArgs; + when["returnType"] = getTypeSignature(input->type()); + return when; +} +} // namespace + +// ValueBlock in ConstantExpression requires only the column from the serialized +// PrestoPage without the page header. +std::string RowExpressionConverter::getValueBlock(const VectorPtr& vector) { + std::ostringstream output; + serde_->serializeSingleColumn(vector, nullptr, pool_, &output); + const auto serialized = output.str(); + const auto serializedSize = serialized.size(); + return encoding::Base64::encode(serialized.c_str(), serializedSize); +} + +std::shared_ptr +RowExpressionConverter::getConstantRowExpression( + const std::shared_ptr& constantExpr) { + protocol::ConstantExpression cexpr; + cexpr.type = getTypeSignature(constantExpr->type()); + cexpr.valueBlock.data = getValueBlock(constantExpr->value()); + return std::make_shared(cexpr); +} + +SwitchFormArguments RowExpressionConverter::getSimpleSwitchFormArgs( + const json::array_t& inputArgs, + const exec::ExprPtr& switchExpr) { + SwitchFormArguments result; + // The switch case's 'expression' in simple form of the conditional cannot be + // inferred from Velox since it could evaluate all 'when' clauses to true or + // false, so we get it from the input json. + result.arguments.emplace_back(inputArgs[0]); + const auto& switchInputs = switchExpr->inputs(); + const auto numInputs = switchInputs.size(); + + for (auto i = 0; i < numInputs - 1; i += 2) { + json::array_t whenArgs; + const vector_size_t argsIdx = i / 2 + 1; + const auto& caseValue = switchInputs[i + 1]; + json::array_t inputWhenArgs = inputArgs[argsIdx].at("arguments"); + + if (switchInputs[i]->isConstant()) { + auto constantExpr = + std::dynamic_pointer_cast(switchInputs[i]); + if (auto constVector = + constantExpr->value()->as>()) { + // If this is the first switch case that evaluates to true, return the + // expression corresponding to this case. + if (constVector->valueAt(0) && result.arguments.size() == 1) { + return { + true, + veloxToPrestoRowExpression(caseValue, inputWhenArgs[1]), + json::array()}; + } else { + // Skip switch cases that evaluate to false. + continue; + } + } else { + whenArgs.emplace_back(getConstantRowExpression(constantExpr)); + } + } else { + VELOX_USER_CHECK(!switchInputs[i]->inputs().empty()); + const auto& matchExpr = switchInputs[i]->inputs().back(); + whenArgs.emplace_back( + veloxToPrestoRowExpression(matchExpr, inputWhenArgs[0])); + } + + whenArgs.emplace_back( + veloxToPrestoRowExpression(caseValue, inputWhenArgs[1])); + result.arguments.emplace_back( + getWhenSpecialForm(switchInputs[i + 1], whenArgs)); + } + + // Else clause. + if (numInputs % 2 != 0) { + result.arguments.emplace_back(veloxToPrestoRowExpression( + switchInputs[numInputs - 1], inputArgs.back())); + } + return result; +} + +SwitchFormArguments RowExpressionConverter::getSwitchSpecialFormArgs( + const exec::ExprPtr& switchExpr, + const json& input) { + json::array_t inputArgs = input["arguments"]; + // The searched form of the conditional expression needs to be handled + // differently from the simple form. The searched form can be detected by the + // presence of a boolean value in the first argument. This default boolean + // argument is not present in the Velox switch expression, so it is added to + // the arguments of output switch expression unchanged. + if (inputArgs[0].at("@type") == kConstant && + inputArgs[0].at("type") == kBoolean) { + SwitchFormArguments result; + const auto& switchInputs = switchExpr->inputs(); + const auto numInputs = switchInputs.size(); + result.arguments = {inputArgs[0]}; + for (auto i = 0; i < numInputs - 1; i += 2) { + const vector_size_t argsIdx = i / 2 + 1; + json::array_t inputWhenArgs = inputArgs[argsIdx].at("arguments"); + json::array_t whenArgs; + whenArgs.emplace_back( + veloxToPrestoRowExpression(switchInputs[i], inputWhenArgs[0])); + whenArgs.emplace_back( + veloxToPrestoRowExpression(switchInputs[i + 1], inputWhenArgs[1])); + result.arguments.emplace_back( + getWhenSpecialForm(switchInputs[i + 1], whenArgs)); + } + + // Else clause. + if (numInputs % 2 != 0) { + result.arguments.emplace_back(veloxToPrestoRowExpression( + switchInputs[numInputs - 1], inputArgs.back())); + } + return result; + } + + return getSimpleSwitchFormArgs(inputArgs, switchExpr); +} + +json RowExpressionConverter::getSpecialForm( + const exec::ExprPtr& expr, + const json& input) { + json result; + result["@type"] = kSpecial; + result["returnType"] = getTypeSignature(expr->type()); + auto form = expr->name(); + // Presto requires the field form to be in upper case. + std::transform(form.begin(), form.end(), form.begin(), ::toupper); + result["form"] = form; + + // Arguments for switch expression include special form expression 'when' + // so they are constructed separately. If the switch expression evaluation + // found a case that always evaluates to true, the field 'isSimplified' in the + // result will be true and the field 'caseExpression' contains the value + // corresponding to the simplified switch case. Otherwise, 'isSimplified' will + // be false and the field 'arguments' will contain the 'when' clauses needed + // by switch SpecialFormExpression in Presto. + if (form == kSwitch) { + auto switchResult = getSwitchSpecialFormArgs(expr, input); + if (switchResult.isSimplified) { + return switchResult.caseExpression; + } else { + result["arguments"] = switchResult.arguments; + } + } else { + json::array_t inputArguments = input["arguments"]; + auto exprInputs = expr->inputs(); + const auto numInputs = exprInputs.size(); + if (form == kCoalesce) { + VELOX_USER_CHECK_LE(numInputs, inputArguments.size()); + } else { + VELOX_USER_CHECK_EQ(numInputs, inputArguments.size()); + } + result["arguments"] = json::array(); + for (auto i = 0; i < numInputs; i++) { + result["arguments"].push_back( + veloxToPrestoRowExpression(exprInputs[i], inputArguments[i])); + } + } + + return result; +} + +json RowExpressionConverter::getRowConstructorSpecialForm( + std::shared_ptr& constantExpr) { + json result; + result["@type"] = kSpecial; + result["form"] = kRowConstructor; + result["returnType"] = getTypeSignature(constantExpr->type()); + auto value = constantExpr->value(); + auto* constVector = value->as>(); + auto* rowVector = constVector->valueVector()->as(); + auto type = asRowType(constantExpr->type()); + auto size = rowVector->children().size(); + + protocol::ConstantExpression cexpr; + json j; + result["arguments"] = json::array(); + for (auto i = 0; i < size; i++) { + cexpr.type = getTypeSignature(type->childAt(i)); + cexpr.valueBlock.data = getValueBlock(rowVector->childAt(i)); + protocol::to_json(j, cexpr); + result["arguments"].push_back(j); + } + return result; +} + +json RowExpressionConverter::toConstantRowExpression( + const exec::ExprPtr& expr) { + auto constantExpr = std::dynamic_pointer_cast(expr); + VELOX_USER_CHECK_NOT_NULL(constantExpr); + // Constant velox expressions of ROW type map to ROW_CONSTRUCTOR special form + // expression in Presto. + if (expr->type()->isRow()) { + return getRowConstructorSpecialForm(constantExpr); + } + + json result; + auto constantRowExpr = getConstantRowExpression(constantExpr); + protocol::to_json(result, constantRowExpr); + return result; +} + +json RowExpressionConverter::toCallRowExpression( + const exec::ExprPtr& expr, + const json& input) { + json result; + result["@type"] = kCall; + protocol::Signature signature; + std::string exprName = expr->name(); + if (veloxToPrestoOperatorMap_.find(exprName) != + veloxToPrestoOperatorMap_.end()) { + exprName = veloxToPrestoOperatorMap_.at(exprName); + } + signature.name = exprName; + result["displayName"] = exprName; + signature.kind = protocol::FunctionKind::SCALAR; + signature.typeVariableConstraints = {}; + signature.longVariableConstraints = {}; + signature.returnType = getTypeSignature(expr->type()); + + std::vector argumentTypes; + auto exprInputs = expr->inputs(); + auto numArgs = exprInputs.size(); + argumentTypes.reserve(numArgs); + for (auto i = 0; i < numArgs; i++) { + argumentTypes.emplace_back(getTypeSignature(exprInputs[i]->type())); + } + signature.argumentTypes = argumentTypes; + signature.variableArity = false; + + protocol::BuiltInFunctionHandle builtInFunctionHandle; + builtInFunctionHandle._type = kStatic; + builtInFunctionHandle.signature = signature; + result["functionHandle"] = builtInFunctionHandle; + result["returnType"] = getTypeSignature(expr->type()); + result["arguments"] = json::array(); + for (const auto& exprInput : exprInputs) { + result["arguments"].push_back(veloxToPrestoRowExpression(exprInput, input)); + } + + return result; +} + +json RowExpressionConverter::veloxToPrestoRowExpression( + const exec::ExprPtr& expr, + const json& input) { + if (expr->isConstant()) { + if (expr->inputs().empty()) { + return toConstantRowExpression(expr); + } else { + // Constant expressions with inputs could result in an exception when + // constant folded in Velox, such as divide by zero, eg: divide(0, 0). + // Such exceptions should be handled by Presto, so the input json is + // returned as is and the expression is not evaluated in Velox. + return input; + } + } + + if (auto field = + std::dynamic_pointer_cast(expr)) { + return toVariableReferenceExpression(field); + } + + if (expr->isSpecialForm() || expr->vectorFunction()) { + // Check if special form expression or call expression. + auto exprName = expr->name(); + boost::algorithm::to_lower(exprName); + if (isPrestoSpecialForm(exprName)) { + return getSpecialForm(expr, input); + } else { + return toCallRowExpression(expr, input); + } + } + + VELOX_NYI( + "Conversion of Velox Expr {} to Presto RowExpression is not supported", + expr->toString()); +} + +} // namespace facebook::presto::expression diff --git a/presto-native-execution/presto_cpp/main/types/RowExpressionConverter.h b/presto-native-execution/presto_cpp/main/types/RowExpressionConverter.h new file mode 100644 index 000000000000..503480338925 --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/RowExpressionConverter.h @@ -0,0 +1,94 @@ +/* + * 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. + */ +#pragma once + +#include "presto_cpp/main/types/PrestoToVeloxExpr.h" +#include "velox/expression/ConstantExpr.h" +#include "velox/expression/Expr.h" +#include "velox/serializers/PrestoSerializer.h" + +using namespace facebook::velox; + +namespace facebook::presto::expression { + +// When 'isSimplified' is true, the cases (or arguments) in switch expression +// have been simplified when the expression was constant folded in Velox. In +// this case, the expression corresponding to the first switch case that always +// evaluates to true is returned in 'caseExpression'. For example, consider the +// switch expression: CASE 1 WHEN 1 THEN 'one' WHEN 2 THEN 'two'. The first +// switch case always evaluates to true here so 'one' is returned in the field +// 'caseExpression'. +// When 'isSimplified' is false, the cases in switch expression have not been +// simplified, and the switch expression arguments required by Presto are +// returned in 'arguments'. Consider the same example from before with a slight +// modification: CASE a WHEN 1 THEN 'one' WHEN 2 THEN 'two'. There is no switch +// case that can be simplified since 'a' is a variable here, so the WHEN clauses +// that are required by Presto as switch expression arguments are returned in +// the field 'arguments'. +struct SwitchFormArguments { + bool isSimplified = false; + json caseExpression; + json::array_t arguments; +}; + +// Helper class to convert Velox expressions of type exec::Expr to their +// corresponding type of RowExpression in Presto. +// 1. Constant velox expressions of type exec::ConstantExpr are converted to +// Presto ConstantExpression. However, if the velox constant expression is of +// ROW type, it is converted to the Presto SpecialFormExpression +// of type ROW_CONSTRUCTOR. +// 2. Velox expressions representing variables are of type exec::FieldReference +// and they are converted to Presto VariableReferenceExpression. +// 3. Special form expressions and expressions with a vector function in Velox +// can map either to a Presto SpecialFormExpression or to a Presto +// CallExpression. Such velox expressions are converted to the appropriate +// Presto RowExpression based on the expression name. +class RowExpressionConverter { + public: + explicit RowExpressionConverter(memory::MemoryPool* pool) + : pool_(pool), veloxToPrestoOperatorMap_(veloxToPrestoOperatorMap()) {} + + std::shared_ptr getConstantRowExpression( + const std::shared_ptr& constantExpr); + + json veloxToPrestoRowExpression( + const exec::ExprPtr& expr, + const json& inputRowExpr); + + private: + std::string getValueBlock(const VectorPtr& vector); + + SwitchFormArguments getSimpleSwitchFormArgs( + const json::array_t& inputArgs, + const exec::ExprPtr& switchExpr); + + SwitchFormArguments getSwitchSpecialFormArgs( + const exec::ExprPtr& switchExpr, + const json& input); + + json getSpecialForm(const exec::ExprPtr& expr, const json& inputRowExpr); + + json getRowConstructorSpecialForm( + std::shared_ptr& constantExpr); + + json toConstantRowExpression(const exec::ExprPtr& expr); + + json toCallRowExpression(const exec::ExprPtr& expr, const json& input); + + memory::MemoryPool* pool_; + const std::unordered_map veloxToPrestoOperatorMap_; + const std::unique_ptr serde_ = + std::make_unique(); +}; +} // namespace facebook::presto::expression diff --git a/presto-native-execution/presto_cpp/main/types/RowExpressionOptimizer.cpp b/presto-native-execution/presto_cpp/main/types/RowExpressionOptimizer.cpp new file mode 100644 index 000000000000..3163f5a65a49 --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/RowExpressionOptimizer.cpp @@ -0,0 +1,320 @@ +/* + * 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 "presto_cpp/main/types/RowExpressionOptimizer.h" +#include "velox/expression/ExprCompiler.h" + +using namespace facebook::presto; +using namespace facebook::velox; + +namespace facebook::presto::expression { + +namespace { + +const std::string kTimezoneHeader = "X-Presto-Time-Zone"; +const std::string kOptimizerLevelHeader = "X-Presto-Expression-Optimizer-Level"; +const std::string kEvaluated = "EVALUATED"; + +template +std::shared_ptr getConstantExpr( + const TypePtr& type, + const DecodedVector& decoded, + memory::MemoryPool* pool) { + if constexpr ( + KIND == TypeKind::ROW || KIND == TypeKind::UNKNOWN || + KIND == TypeKind::ARRAY || KIND == TypeKind::MAP) { + VELOX_USER_FAIL("Invalid result type {}", type->toString()); + } else { + using T = typename TypeTraits::NativeType; + auto constVector = std::make_shared>( + pool, decoded.size(), decoded.isNullAt(0), type, decoded.valueAt(0)); + return std::make_shared(constVector); + } +} +} // namespace + +exec::ExprPtr RowExpressionOptimizer::compileExpression( + const std::shared_ptr& inputRowExpr) { + auto typedExpr = veloxExprConverter_.toVeloxExpr(inputRowExpr); + exec::ExprSet exprSet{{typedExpr}, execCtx_.get()}; + auto compiledExprs = + exec::compileExpressions({typedExpr}, execCtx_.get(), &exprSet, true); + return compiledExprs[0]; +} + +RowExpressionPtr RowExpressionOptimizer::optimizeAndSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr) { + auto left = specialFormExpr->arguments[0]; + auto right = specialFormExpr->arguments[1]; + auto leftExpr = compileExpression(left); + bool isLeftNull; + + if (auto constantExpr = + std::dynamic_pointer_cast(leftExpr)) { + isLeftNull = constantExpr->value()->isNullAt(0); + if (!isLeftNull) { + if (auto constVector = + constantExpr->value()->as>()) { + if (!constVector->valueAt(0)) { + return rowExpressionConverter_.getConstantRowExpression(constantExpr); + } else { + return right; + } + } + } + } + + auto rightExpr = compileExpression(right); + if (auto constantExpr = + std::dynamic_pointer_cast(rightExpr)) { + if (isLeftNull && constantExpr->value()->isNullAt(0)) { + return rowExpressionConverter_.getConstantRowExpression(constantExpr); + } + if (auto constVector = constantExpr->value()->as>()) { + if (constVector->valueAt(0)) { + return left; + } + return right; + } + } + + return specialFormExpr; +} + +RowExpressionPtr RowExpressionOptimizer::optimizeIfSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr) { + auto condition = specialFormExpr->arguments[0]; + auto expr = compileExpression(condition); + + if (auto constantExpr = + std::dynamic_pointer_cast(expr)) { + if (auto constVector = constantExpr->value()->as>()) { + if (constVector->valueAt(0)) { + return specialFormExpr->arguments[1]; + } + return specialFormExpr->arguments[2]; + } + } + + return specialFormExpr; +} + +RowExpressionPtr RowExpressionOptimizer::optimizeIsNullSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr) { + auto expr = compileExpression(specialFormExpr); + if (auto constantExpr = + std::dynamic_pointer_cast(expr)) { + if (constantExpr->value()->isNullAt(0)) { + return rowExpressionConverter_.getConstantRowExpression(constantExpr); + } + } + + return specialFormExpr; +} + +RowExpressionPtr RowExpressionOptimizer::optimizeOrSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr) { + auto left = specialFormExpr->arguments[0]; + auto right = specialFormExpr->arguments[1]; + auto leftExpr = compileExpression(left); + bool isLeftNull; + + if (auto constantExpr = + std::dynamic_pointer_cast(leftExpr)) { + isLeftNull = constantExpr->value()->isNullAt(0); + if (!isLeftNull) { + if (auto constVector = + constantExpr->value()->as>()) { + if (constVector->valueAt(0)) { + return rowExpressionConverter_.getConstantRowExpression(constantExpr); + } + return right; + } + } + } + + auto rightExpr = compileExpression(right); + if (auto constantExpr = + std::dynamic_pointer_cast(rightExpr)) { + if (isLeftNull && constantExpr->value()->isNullAt(0)) { + return rowExpressionConverter_.getConstantRowExpression(constantExpr); + } + if (auto constVector = constantExpr->value()->as>()) { + if (!constVector->valueAt(0)) { + return left; + } + return right; + } + } + + return specialFormExpr; +} + +RowExpressionPtr RowExpressionOptimizer::optimizeCoalesceSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr) { + auto argsNoNulls = specialFormExpr->arguments; + argsNoNulls.erase( + std::remove_if( + argsNoNulls.begin(), + argsNoNulls.end(), + [&](const auto& arg) { + auto compiledExpr = compileExpression(arg); + if (auto constantExpr = + std::dynamic_pointer_cast( + compiledExpr)) { + return constantExpr->value()->isNullAt(0); + } + return false; + }), + argsNoNulls.end()); + + if (argsNoNulls.empty()) { + return specialFormExpr->arguments[0]; + } + specialFormExpr->arguments = argsNoNulls; + return specialFormExpr; +} + +RowExpressionPtr RowExpressionOptimizer::optimizeSpecialForm( + const std::shared_ptr& specialFormExpr) { + switch (specialFormExpr->form) { + case protocol::Form::IF: + return optimizeIfSpecialForm(specialFormExpr); + case protocol::Form::NULL_IF: + VELOX_USER_FAIL("NULL_IF specialForm not supported"); + break; + case protocol::Form::IS_NULL: + return optimizeIsNullSpecialForm(specialFormExpr); + case protocol::Form::AND: + return optimizeAndSpecialForm(specialFormExpr); + case protocol::Form::OR: + return optimizeOrSpecialForm(specialFormExpr); + case protocol::Form::COALESCE: + return optimizeCoalesceSpecialForm(specialFormExpr); + case protocol::Form::IN: + case protocol::Form::DEREFERENCE: + case protocol::Form::SWITCH: + case protocol::Form::WHEN: + case protocol::Form::ROW_CONSTRUCTOR: + case protocol::Form::BIND: + default: + break; + } + + return specialFormExpr; +} + +json::array_t RowExpressionOptimizer::optimizeExpressions( + const json::array_t& input, + const std::string& optimizerLevel) { + const auto numExpr = input.size(); + json::array_t output = json::array(); + for (auto i = 0; i < numExpr; i++) { + std::shared_ptr inputRowExpr = input[i]; + if (const auto special = + std::dynamic_pointer_cast( + inputRowExpr)) { + inputRowExpr = optimizeSpecialForm(special); + } + auto typedExpr = veloxExprConverter_.toVeloxExpr(inputRowExpr); + exec::ExprSet exprSet{{typedExpr}, execCtx_.get()}; + auto compiledExprs = + exec::compileExpressions({typedExpr}, execCtx_.get(), &exprSet, true); + auto compiledExpr = compiledExprs[0]; + json resultJson; + + if (optimizerLevel == kEvaluated) { + if (compiledExpr->isConstant()) { + resultJson = rowExpressionConverter_.veloxToPrestoRowExpression( + compiledExpr, input[i]); + } else { + // Velox does not evaluate expressions that are non-deterministic during + // compilation with constant folding enabled. Presto might require such + // non-deterministic expressions to be evaluated as well, this would be + // indicated by the header field 'X-Presto-Expression-Optimizer-Level' + // in the http request made to the native sidecar. When this field is + // set to 'EVALUATED', non-deterministic expressions with constant + // inputs are also evaluated. + VELOX_USER_CHECK(!compiledExpr->isDeterministic()); + std::vector compiledExprInputTypes; + std::vector compiledExprInputs; + for (const auto& exprInput : compiledExpr->inputs()) { + VELOX_USER_CHECK( + exprInput->isConstant(), + "Inputs to non-deterministic expression to be evaluated must be constant"); + const auto inputAsConstExpr = + std::dynamic_pointer_cast(exprInput); + compiledExprInputs.emplace_back(inputAsConstExpr->value()); + compiledExprInputTypes.emplace_back(exprInput->type()); + } + + const auto inputVector = std::make_shared( + pool_.get(), + ROW(std::move(compiledExprInputTypes)), + nullptr, + 1, + compiledExprInputs); + exec::EvalCtx evalCtx(execCtx_.get(), &exprSet, inputVector.get()); + std::vector results(1); + SelectivityVector rows(1); + exprSet.eval(rows, evalCtx, results); + auto res = results.front(); + DecodedVector decoded(*res, rows); + VELOX_USER_CHECK(decoded.size() == 1); + const auto constExpr = VELOX_DYNAMIC_TYPE_DISPATCH( + getConstantExpr, + res->typeKind(), + res->type(), + decoded, + pool_.get()); + resultJson = + rowExpressionConverter_.getConstantRowExpression(constExpr); + } + } else { + resultJson = rowExpressionConverter_.veloxToPrestoRowExpression( + compiledExpr, input[i]); + } + + output.push_back(resultJson); + } + return output; +} + +std::pair RowExpressionOptimizer::optimize( + proxygen::HTTPMessage* message, + const json::array_t& input) { + try { + auto timezone = message->getHeaders().getSingleOrEmpty(kTimezoneHeader); + auto optimizerLevel = + message->getHeaders().getSingleOrEmpty(kOptimizerLevelHeader); + std::unordered_map config( + {{core::QueryConfig::kSessionTimezone, timezone}, + {core::QueryConfig::kAdjustTimestampToTimezone, "true"}}); + auto queryCtx = + core::QueryCtx::create(nullptr, core::QueryConfig{std::move(config)}); + execCtx_ = std::make_unique(pool_.get(), queryCtx.get()); + + return {optimizeExpressions(input, optimizerLevel), true}; + } catch (const VeloxUserError& e) { + VLOG(1) << "VeloxUserError during expression evaluation: " << e.what(); + return {e.what(), false}; + } catch (const VeloxException& e) { + VLOG(1) << "VeloxException during expression evaluation: " << e.what(); + return {e.what(), false}; + } catch (const std::exception& e) { + VLOG(1) << "std::exception during expression evaluation: " << e.what(); + return {e.what(), false}; + } +} + +} // namespace facebook::presto::expression diff --git a/presto-native-execution/presto_cpp/main/types/RowExpressionOptimizer.h b/presto-native-execution/presto_cpp/main/types/RowExpressionOptimizer.h new file mode 100644 index 000000000000..a83d69e09267 --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/RowExpressionOptimizer.h @@ -0,0 +1,87 @@ +/* + * 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. + */ +#pragma once + +#include +#include "presto_cpp/main/types/RowExpressionConverter.h" + +using namespace facebook::velox; + +namespace facebook::presto::expression { + +using RowExpressionPtr = std::shared_ptr; +using SpecialFormExpressionPtr = + std::shared_ptr; + +class RowExpressionOptimizer { + public: + explicit RowExpressionOptimizer() + : pool_(memory::MemoryManager::getInstance()->addLeafPool( + "RowExpressionOptimizer")), + veloxExprConverter_(pool_.get(), &typeParser_), + rowExpressionConverter_(RowExpressionConverter(pool_.get())) {} + + /// Optimizes all expressions from the input json array. If the expression + /// optimization fails for any of the input expressions, the second value in + /// the returned pair will be false and the returned json would contain the + /// exception. Otherwise, the returned json will be an array of optimized row + /// expressions. + std::pair optimize( + proxygen::HTTPMessage* message, + const json::array_t& input); + + private: + /// Converts protocol::RowExpression into a velox expression with constant + /// folding enabled during velox expression compilation. + exec::ExprPtr compileExpression(const RowExpressionPtr& inputRowExpr); + + RowExpressionPtr optimizeAndSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr); + + RowExpressionPtr optimizeIfSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr); + + RowExpressionPtr optimizeIsNullSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr); + + RowExpressionPtr optimizeOrSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr); + + RowExpressionPtr optimizeCoalesceSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr); + + /// Optimizes special form expressions. Optimization rules borrowed from + /// Presto function visitSpecialForm() in RowExpressionInterpreter.java. + RowExpressionPtr optimizeSpecialForm( + const SpecialFormExpressionPtr& specialFormExpr); + + /// Optimizes and constant folds each expression from input json array and + /// returns an array of expressions that are optimized and constant folded. + /// Each expression in the input array is optimized with helper functions + /// optimizeSpecialForm (applicable only for special form expressions) and + /// optimizeExpression. The optimized expression is also evaluated if the + /// optimization level in the header of http request made to 'v1/expressions' + /// is 'EVALUATED'. optimizeExpression uses RowExpressionConverter to convert + /// Velox expression(s) to their corresponding Presto RowExpression(s). + json::array_t optimizeExpressions( + const json::array_t& input, + const std::string& optimizationLevel); + + const std::shared_ptr pool_; + std::unique_ptr execCtx_; + TypeParser typeParser_; + VeloxExprConverter veloxExprConverter_; + RowExpressionConverter rowExpressionConverter_; +}; +} // namespace facebook::presto::expression diff --git a/presto-native-execution/presto_cpp/main/types/tests/CMakeLists.txt b/presto-native-execution/presto_cpp/main/types/tests/CMakeLists.txt index caa5943ec3d7..3d3bb0cb0037 100644 --- a/presto-native-execution/presto_cpp/main/types/tests/CMakeLists.txt +++ b/presto-native-execution/presto_cpp/main/types/tests/CMakeLists.txt @@ -127,3 +127,20 @@ target_link_libraries( velox_exec_test_lib GTest::gtest GTest::gtest_main) + +add_executable(presto_expression_optimizer_test RowExpressionOptimizerTest.cpp) + +add_test( + NAME presto_expression_optimizer_test + COMMAND presto_expression_optimizer_test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries( + presto_expression_optimizer_test + presto_expression_optimizer + presto_http + presto_type_test_utils + velox_exec_test_lib + velox_parse_utils + GTest::gtest + GTest::gtest_main) diff --git a/presto-native-execution/presto_cpp/main/types/tests/RowExpressionOptimizerTest.cpp b/presto-native-execution/presto_cpp/main/types/tests/RowExpressionOptimizerTest.cpp new file mode 100644 index 000000000000..d9f310846405 --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/tests/RowExpressionOptimizerTest.cpp @@ -0,0 +1,148 @@ +/* + * 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 "presto_cpp/main/types/RowExpressionOptimizer.h" +#include +#include +#include "presto_cpp/main/common/tests/test_json.h" +#include "presto_cpp/main/http/tests/HttpTestBase.h" +#include "presto_cpp/main/types/tests/TestUtils.h" +#include "velox/expression/FieldReference.h" +#include "velox/expression/RegisterSpecialForm.h" +#include "velox/functions/prestosql/registration/RegistrationFunctions.h" +#include "velox/parse/TypeResolver.h" +#include "velox/vector/VectorStream.h" +#include "velox/vector/tests/utils/VectorTestBase.h" + +using namespace facebook::presto; + +// RowExpressionConverter is used only by RowExpressionOptimizer so unit tests +// are only added for simple expressions here. End-to-end tests to validate +// Velox expression to Presto RowExpression conversion for different types of +// expressions can be found in TestDelegatingExpressionOptimizer.java in +// presto-native-sidecar-plugin. +class RowExpressionConverterTest + : public ::testing::Test, + public facebook::velox::test::VectorTestBase { + protected: + static void SetUpTestCase() { + memory::MemoryManager::testingSetInstance({}); + } + + void SetUp() override { + rowExpressionConverter_ = + std::make_unique(pool_.get()); + } + + std::unique_ptr rowExpressionConverter_; +}; + +TEST_F(RowExpressionConverterTest, constant) { + auto constantExpr = + std::make_shared(makeConstant(variant(1234), 1)); + auto constantRowExpr = + rowExpressionConverter_->getConstantRowExpression(constantExpr); + json result; + protocol::to_json(result, constantRowExpr); + auto expected = R"( + { + "@type":"constant", + "type":"integer", + "valueBlock":"CQAAAElOVF9BUlJBWQEAAAAA0gQAAA==" + } + )"; + EXPECT_EQ(result, json::parse(expected)); +} + +TEST_F(RowExpressionConverterTest, variable) { + auto field = std::make_shared( + VARCHAR(), std::vector{}, "c0"); + auto result = + rowExpressionConverter_->veloxToPrestoRowExpression(field, json()); + auto expected = R"( + { + "@type":"variable", + "name":"c0", + "type":"varchar" + } + )"; + EXPECT_EQ(result, json::parse(expected)); +} + +// RowExpressionOptimizerTest only tests for basic expression optimization. +// End-to-end tests for different types of expressions can be found in +// TestDelegatingExpressionOptimizer.java in presto-native-sidecar-plugin. +class RowExpressionOptimizerTest + : public ::testing::Test, + public facebook::velox::test::VectorTestBase { + protected: + static void SetUpTestCase() { + memory::MemoryManager::testingSetInstance({}); + } + + void SetUp() override { + parse::registerTypeResolver(); + functions::prestosql::registerAllScalarFunctions("presto.default."); + exec::registerFunctionCallToSpecialForms(); + rowExpressionOptimizer_ = + std::make_unique(); + } + + void testFile(const std::string& testName) { + auto input = slurp(facebook::presto::test::utils::getDataPath( + fmt::format("{}Input.json", testName))); + json::array_t inputExpressions = json::parse(input); + proxygen::HTTPMessage httpMessage; + httpMessage.getHeaders().set( + "X-Presto-Time-Zone", "America/Bahia_Banderas"); + httpMessage.getHeaders().set( + "X-Presto-Expression-Optimizer-Level", "OPTIMIZED"); + auto result = + rowExpressionOptimizer_->optimize(&httpMessage, inputExpressions); + + EXPECT_EQ(result.second, true); + json resultExpressions = result.first; + EXPECT_EQ(resultExpressions.is_array(), true); + auto expected = slurp(facebook::presto::test::utils::getDataPath( + fmt::format("{}Expected.json", testName))); + json::array_t expectedExpressions = json::parse(expected); + auto numExpressions = resultExpressions.size(); + EXPECT_EQ(numExpressions, expectedExpressions.size()); + for (auto i = 0; i < numExpressions; i++) { + EXPECT_EQ(resultExpressions.at(i), expectedExpressions.at(i)); + } + } + + std::unique_ptr rowExpressionOptimizer_; +}; + +TEST_F(RowExpressionOptimizerTest, simple) { + // Files SimpleExpressions{Input|Expected}.json contain the input and expected + // JSON representing the RowExpressions resulting from the following queries: + // select 1 + 2; + // select abs(-11) + ceil(cast(3.4 as double)) + floor(cast(5.6 as double)); + // select 2 between 1 and 3; + // Simple expression evaluation with constant folding is verified here. + testFile("SimpleExpressions"); +} + +TEST_F(RowExpressionOptimizerTest, specialFormRewrites) { + // Files SpecialExpressions{Input|Expected}.json contain the input and + // expected JSON representing the RowExpressions resulting from the following + // queries: + // select if(1 < 2, 2, 3); + // select (1 < 2) and (2 < 3); + // select (1 < 2) or (2 < 3); + // Special form expression rewrites are verified here. + testFile("SpecialForm"); +} diff --git a/presto-native-execution/presto_cpp/main/types/tests/data/SimpleExpressionsExpected.json b/presto-native-execution/presto_cpp/main/types/tests/data/SimpleExpressionsExpected.json new file mode 100644 index 000000000000..e9b014fecd3b --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/tests/data/SimpleExpressionsExpected.json @@ -0,0 +1,17 @@ +[ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAwAAAA==" + }, + { + "@type": "constant", + "type": "double", + "valueBlock": "CgAAAExPTkdfQVJSQVkBAAAAAAAAAAAAADRA" + }, + { + "@type": "constant", + "type": "boolean", + "valueBlock": "CgAAAEJZVEVfQVJSQVkBAAAAAAE=" + } +] diff --git a/presto-native-execution/presto_cpp/main/types/tests/data/SimpleExpressionsInput.json b/presto-native-execution/presto_cpp/main/types/tests/data/SimpleExpressionsInput.json new file mode 100644 index 000000000000..dd2a0497703e --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/tests/data/SimpleExpressionsInput.json @@ -0,0 +1,278 @@ +[ + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAQAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + } + ], + "displayName": "ADD", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer", + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$add", + "returnType": "integer", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "integer" + }, + { + "@type": "call", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAACwAAAA==" + } + ], + "displayName": "NEGATION", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$negation", + "returnType": "integer", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "integer" + } + ], + "displayName": "abs", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.abs", + "returnType": "integer", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "integer" + } + ], + "displayName": "CAST", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$cast", + "returnType": "double", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "double" + }, + { + "@type": "call", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "decimal(2,1)", + "valueBlock": "CgAAAExPTkdfQVJSQVkBAAAAACIAAAAAAAAA" + } + ], + "displayName": "CAST", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "decimal(2,1)" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$cast", + "returnType": "double", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "double" + } + ], + "displayName": "ceil", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "double" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.ceil", + "returnType": "double", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "double" + } + ], + "displayName": "ADD", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "double", + "double" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$add", + "returnType": "double", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "double" + }, + { + "@type": "call", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "decimal(2,1)", + "valueBlock": "CgAAAExPTkdfQVJSQVkBAAAAADgAAAAAAAAA" + } + ], + "displayName": "CAST", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "decimal(2,1)" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$cast", + "returnType": "double", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "double" + } + ], + "displayName": "floor", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "double" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.floor", + "returnType": "double", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "double" + } + ], + "displayName": "ADD", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "double", + "double" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$add", + "returnType": "double", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "double" + }, + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAQAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAwAAAA==" + } + ], + "displayName": "BETWEEN", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer", + "integer", + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$between", + "returnType": "boolean", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "boolean" + } +] diff --git a/presto-native-execution/presto_cpp/main/types/tests/data/SpecialFormExpected.json b/presto-native-execution/presto_cpp/main/types/tests/data/SpecialFormExpected.json new file mode 100644 index 000000000000..2ce6acb1ab46 --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/tests/data/SpecialFormExpected.json @@ -0,0 +1,17 @@ +[ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + }, + { + "@type": "constant", + "type": "boolean", + "valueBlock": "CgAAAEJZVEVfQVJSQVkBAAAAAAE=" + }, + { + "@type": "constant", + "type": "boolean", + "valueBlock": "CgAAAEJZVEVfQVJSQVkBAAAAAAE=" + } +] diff --git a/presto-native-execution/presto_cpp/main/types/tests/data/SpecialFormInput.json b/presto-native-execution/presto_cpp/main/types/tests/data/SpecialFormInput.json new file mode 100644 index 000000000000..77802722541b --- /dev/null +++ b/presto-native-execution/presto_cpp/main/types/tests/data/SpecialFormInput.json @@ -0,0 +1,193 @@ +[ + { + "@type": "special", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAQAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + } + ], + "displayName": "LESS_THAN", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer", + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$less_than", + "returnType": "boolean", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "boolean" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAwAAAA==" + } + ], + "form": "IF", + "returnType": "integer" + }, + { + "@type": "special", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAQAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + } + ], + "displayName": "LESS_THAN", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer", + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$less_than", + "returnType": "boolean", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "boolean" + }, + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAwAAAA==" + } + ], + "displayName": "LESS_THAN", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer", + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$less_than", + "returnType": "boolean", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "boolean" + } + ], + "form": "AND", + "returnType": "boolean" + }, + { + "@type": "special", + "arguments": [ + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAQAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + } + ], + "displayName": "LESS_THAN", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer", + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$less_than", + "returnType": "boolean", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "boolean" + }, + { + "@type": "call", + "arguments": [ + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAgAAAA==" + }, + { + "@type": "constant", + "type": "integer", + "valueBlock": "CQAAAElOVF9BUlJBWQEAAAAAAwAAAA==" + } + ], + "displayName": "LESS_THAN", + "functionHandle": { + "@type": "$static", + "signature": { + "argumentTypes": [ + "integer", + "integer" + ], + "kind": "SCALAR", + "longVariableConstraints": [], + "name": "presto.default.$operator$less_than", + "returnType": "boolean", + "typeVariableConstraints": [], + "variableArity": false + } + }, + "returnType": "boolean" + } + ], + "form": "OR", + "returnType": "boolean" + } +]