diff --git a/velox/docs/develop/scalar-functions.rst b/velox/docs/develop/scalar-functions.rst index d9063787781bc..78a65d7425b85 100644 --- a/velox/docs/develop/scalar-functions.rst +++ b/velox/docs/develop/scalar-functions.rst @@ -998,8 +998,16 @@ element of the array and returns a new array of the results. The signature of a function that handles DECIMAL types can additionally take variables and constraints to represent the precision and scale values. +The variables of input decimal types store the input precisions and scales. +Their names begin with an incrementing prefix starting from 'a', and followed +by '_precision' or '_scale'. Variables of output decimal types store the output +precision and scale. Their names begin with 'r', and followed by '_precision' +or '_scale'. When there is only one input decimal type, and the output type +holds the same precision and scale with the input type, the variables could be +named as 'precision' and 'scale'. The constraints are evaluated using a type calculator built from Flex and Bison -tools. The decimal arithmetic addition function has the following signature: +tools. +The decimal arithmetic addition function has the following signature: .. code-block:: c++ diff --git a/velox/expression/ReverseSignatureBinder.cpp b/velox/expression/ReverseSignatureBinder.cpp index afd4bd311edc7..10319a88403e1 100644 --- a/velox/expression/ReverseSignatureBinder.cpp +++ b/velox/expression/ReverseSignatureBinder.cpp @@ -18,23 +18,6 @@ namespace facebook::velox::exec { -bool ReverseSignatureBinder::hasConstrainedIntegerVariable( - const TypeSignature& type) const { - if (type.parameters().empty()) { - auto it = variables().find(type.baseName()); - return it != variables().end() && it->second.isIntegerParameter() && - it->second.constraint() != ""; - } - - const auto& parameters = type.parameters(); - for (const auto& parameter : parameters) { - if (hasConstrainedIntegerVariable(parameter)) { - return true; - } - } - return false; -} - bool ReverseSignatureBinder::tryBind() { return SignatureBinderBase::tryBind(signature_.returnType(), returnType_); } diff --git a/velox/expression/ReverseSignatureBinder.h b/velox/expression/ReverseSignatureBinder.h index 02b92d217cf41..d23dc547dc77c 100644 --- a/velox/expression/ReverseSignatureBinder.h +++ b/velox/expression/ReverseSignatureBinder.h @@ -44,10 +44,6 @@ class ReverseSignatureBinder : private SignatureBinderBase { } private: - /// Return whether there is a constraint on an integer variable in type - /// signature. - bool hasConstrainedIntegerVariable(const TypeSignature& type) const; - const TypePtr returnType_; }; diff --git a/velox/expression/tests/ArgumentTypeFuzzerTest.cpp b/velox/expression/tests/ArgumentTypeFuzzerTest.cpp index ba3396d1f386d..d6aa048a7fc50 100644 --- a/velox/expression/tests/ArgumentTypeFuzzerTest.cpp +++ b/velox/expression/tests/ArgumentTypeFuzzerTest.cpp @@ -62,12 +62,16 @@ class ArgumentTypeFuzzerTest : public testing::Test { void testFuzzingDecimalSuccess( const std::shared_ptr& signature, int32_t expectedArguments, - std::optional outputKind = std::nullopt) { + const std::function&, const TypePtr&)>& + returnTypeVerifier) { std::mt19937 seed{0}; ArgumentTypeFuzzer fuzzer{*signature, seed}; ASSERT_TRUE(fuzzer.fuzzArgumentTypes(kMaxVariadicArgs)); auto& argumentTypes = fuzzer.argumentTypes(); + for (const auto& argType : argumentTypes) { + SCOPED_TRACE(fmt::format("argType: {}", argType->toString())); + } ASSERT_GE(argumentTypes.size(), expectedArguments); auto& argumentSignatures = signature->argumentTypes(); @@ -87,11 +91,10 @@ class ArgumentTypeFuzzerTest : public testing::Test { } } - const auto outputType = fuzzer.fuzzReturnType(); - if (outputKind.has_value()) { - ASSERT_TRUE(outputType->kind() == outputKind); - } else { - ASSERT_TRUE(outputType->isDecimal()); + const auto returnType = fuzzer.fuzzReturnType(); + SCOPED_TRACE(fmt::format("returnType: {}", returnType->toString())); + if (returnTypeVerifier) { + returnTypeVerifier(argumentTypes, returnType); } } @@ -268,7 +271,11 @@ TEST_F(ArgumentTypeFuzzerTest, decimal) { .argumentType("decimal(a_precision, a_scale)") .build(); - testFuzzingDecimalSuccess(signature, 3, TypeKind::BOOLEAN); + std::function&, const TypePtr&)> verifier = + [](const std::vector& argumentTypes, const TypePtr& returnType) { + ASSERT_EQ(returnType->kind(), TypeKind::BOOLEAN); + }; + testFuzzingDecimalSuccess(signature, 3, verifier); signature = exec::FunctionSignatureBuilder() @@ -285,7 +292,23 @@ TEST_F(ArgumentTypeFuzzerTest, decimal) { .argumentType("DECIMAL(b_precision, b_scale)") .build(); - testFuzzingDecimalSuccess(signature, 2); + verifier = [](const std::vector& argumentTypes, + const TypePtr& returnType) { + ASSERT_TRUE(returnType->isDecimal()); + const auto [aPrecision, aScale] = + getDecimalPrecisionScale(*argumentTypes[0]); + const auto [bPrecision, bScale] = + getDecimalPrecisionScale(*argumentTypes[1]); + const auto [rPrecision, rScale] = getDecimalPrecisionScale(*returnType); + ASSERT_EQ( + rPrecision, + std::min( + 38, + std::max(aPrecision - aScale, bPrecision - bScale) + + std::max(aScale, bScale) + 1)); + ASSERT_EQ(rScale, std::max(aScale, bScale)); + }; + testFuzzingDecimalSuccess(signature, 2, verifier); signature = exec::FunctionSignatureBuilder() @@ -302,7 +325,26 @@ TEST_F(ArgumentTypeFuzzerTest, decimal) { .argumentType("decimal(b_precision, b_scale)") .build(); - testFuzzingDecimalSuccess(signature, 2, TypeKind::ROW); + verifier = [](const std::vector& argumentTypes, + const TypePtr& returnType) { + ASSERT_EQ(returnType->kind(), TypeKind::ROW); + const auto [aPrecision, aScale] = + getDecimalPrecisionScale(*argumentTypes[0]); + const auto [bPrecision, bScale] = + getDecimalPrecisionScale(*argumentTypes[1]); + const auto arrayType = std::dynamic_pointer_cast( + asRowType(returnType)->childAt(0)); + const auto [rPrecision, rScale] = + getDecimalPrecisionScale(*(arrayType->elementType())); + ASSERT_EQ( + rPrecision, + std::min( + 38, + std::max(aPrecision - aScale, bPrecision - bScale) + + std::max(aScale, bScale) + 1)); + ASSERT_EQ(rScale, std::max(aScale, bScale)); + }; + testFuzzingDecimalSuccess(signature, 2, verifier); signature = exec::FunctionSignatureBuilder() .integerVariable("i1") @@ -316,7 +358,23 @@ TEST_F(ArgumentTypeFuzzerTest, decimal) { .argumentType("decimal(i1,i5)") .argumentType("decimal(i2,i6)") .build(); - testFuzzingDecimalSuccess(signature, 2); + verifier = [](const std::vector& argumentTypes, + const TypePtr& returnType) { + ASSERT_TRUE(returnType->isDecimal()); + const auto [aPrecision, aScale] = + getDecimalPrecisionScale(*argumentTypes[0]); + const auto [bPrecision, bScale] = + getDecimalPrecisionScale(*argumentTypes[1]); + const auto [rPrecision, rScale] = getDecimalPrecisionScale(*returnType); + ASSERT_EQ( + rPrecision, + std::min( + 38, + std::max(aPrecision - aScale, bPrecision - bScale) + + std::max(aScale, bScale) + 1)); + ASSERT_EQ(rScale, std::max(aScale, bScale)); + }; + testFuzzingDecimalSuccess(signature, 2, verifier); signature = exec::FunctionSignatureBuilder() .integerVariable("i1") @@ -326,7 +384,11 @@ TEST_F(ArgumentTypeFuzzerTest, decimal) { .argumentType("decimal(i1,i5)") .argumentType("decimal(i1,i5)") .build(); - testFuzzingDecimalSuccess(signature, 3, TypeKind::BOOLEAN); + verifier = [](const std::vector& argumentTypes, + const TypePtr& returnType) { + ASSERT_EQ(returnType->kind(), TypeKind::BOOLEAN); + }; + testFuzzingDecimalSuccess(signature, 3, verifier); signature = exec::FunctionSignatureBuilder() .integerVariable("precision") @@ -335,14 +397,29 @@ TEST_F(ArgumentTypeFuzzerTest, decimal) { .argumentType("DECIMAL(precision, scale)") .variableArity() .build(); - testFuzzingDecimalSuccess(signature, 1); + verifier = [](const std::vector& argumentTypes, + const TypePtr& returnType) { + ASSERT_TRUE(returnType->isDecimal()); + const auto [aPrecision, aScale] = + getDecimalPrecisionScale(*argumentTypes[0]); + const auto [rPrecision, rScale] = getDecimalPrecisionScale(*returnType); + ASSERT_EQ(rPrecision, aPrecision); + ASSERT_EQ(rScale, aScale); + }; + testFuzzingDecimalSuccess(signature, 1, verifier); signature = exec::FunctionSignatureBuilder() .integerVariable("precision", "min(max(6, precision), 18)") .returnType("timestamp") .argumentType("decimal(precision, 6)") .build(); - testFuzzingDecimalSuccess(signature, 1, TypeKind::TIMESTAMP); + verifier = [](const std::vector& argumentTypes, + const TypePtr& returnType) { + ASSERT_EQ(returnType->kind(), TypeKind::TIMESTAMP); + const auto [precision, scale] = getDecimalPrecisionScale(*argumentTypes[0]); + ASSERT_EQ(scale, 6); + }; + testFuzzingDecimalSuccess(signature, 1, verifier); } TEST_F(ArgumentTypeFuzzerTest, lambda) { diff --git a/velox/expression/tests/ExpressionFuzzer.cpp b/velox/expression/tests/ExpressionFuzzer.cpp index 6fa316b7dd78f..dc6b21dfe9fa8 100644 --- a/velox/expression/tests/ExpressionFuzzer.cpp +++ b/velox/expression/tests/ExpressionFuzzer.cpp @@ -692,10 +692,10 @@ ExpressionFuzzer::ExpressionFuzzer( for (const auto& it : signatureTemplates_) { auto& returnType = it.signature->returnType().baseName(); - std::string typeName = returnType; - folly::toLowerAscii(typeName); - const auto* returnTypeKey = &typeName; - if (it.typeVariables.find(typeName) != it.typeVariables.end()) { + const auto sanitizedName = exec::sanitizeName(returnType); + + const auto* returnTypeKey = &sanitizedName; + if (it.typeVariables.find(sanitizedName) != it.typeVariables.end()) { // Return type is a template variable. returnTypeKey = &kTypeParameterName; } @@ -766,11 +766,10 @@ int ExpressionFuzzer::getTickets(const std::string& funcName) { void ExpressionFuzzer::addToTypeToExpressionListByTicketTimes( const std::string& type, const std::string& funcName) { - std::string typeName = type; - folly::toLowerAscii(typeName); + const auto sanitizedName = exec::sanitizeName(type); int tickets = getTickets(funcName); for (int i = 0; i < tickets; i++) { - typeToExpressionList_[typeName].push_back(funcName); + typeToExpressionList_[sanitizedName].push_back(funcName); } } @@ -1073,97 +1072,33 @@ std::vector ExpressionFuzzer::getArgsForCallable( return funcIt->second(callable); } -TypePtr ExpressionFuzzer::getConstrainedOutputType( - const std::vector& args, - const exec::FunctionSignature* signature) { - if (signature == nullptr) { - return nullptr; - } - // Checks if any variable is integer constrained, and get the decimal name - // style. - bool integerConstrained = false; - char decimalNameStyle = 0; - for (const auto& [variableName, variableInfo] : signature->variables()) { - if (variableInfo.isIntegerParameter()) { - // If constraints are empty, the integer variable is also regarded to be - // constrained as variables are shared across argument and return types. - integerConstrained = true; - if (variableName == "precision" || variableName == "scale") { - decimalNameStyle = 'a'; - break; - } - if (variableName.find("precision") != std::string::npos || - variableName.find("scale") != std::string::npos) { - decimalNameStyle = 'b'; - break; - } - if (variableName.find("i") != std::string::npos) { - decimalNameStyle = 'c'; - break; - } - } - } - - // To handle the constraints between input types and output types of a decimal - // function, extracts the input precisions and scales from decimal arguments, - // and bind them to integer variables. - std::unordered_map decimalVariablesBindings; - column_index_t decimalColIndex = 1; - for (column_index_t i = 0; i < args.size(); ++i) { - const auto argType = args[i]->type(); - if (argType->isDecimal()) { - const auto [p, s] = getDecimalPrecisionScale(*argType); - switch (decimalNameStyle) { - case 'a': { - decimalVariablesBindings["precision"] = p; - decimalVariablesBindings["scale"] = s; - break; - } - case 'b': { - const auto column = std::string(1, 'a' + i); - decimalVariablesBindings[column + "_precision"] = p; - decimalVariablesBindings[column + "_scale"] = s; - break; - } - case 'c': { - decimalVariablesBindings["i" + std::to_string(decimalColIndex)] = p; - decimalVariablesBindings - ["i" + std::to_string(decimalColIndex + kIntegerPairSize)] = s; - decimalColIndex++; - break; - } - default: - VELOX_UNSUPPORTED( - "Unsupported decimal name style {}.", decimalNameStyle); - } - } - } - - // Calculates the matched return type through the argument types with argument - // type fuzzer, which evaluates the constraints internally. - if (integerConstrained && decimalVariablesBindings.size() > 0 && signature) { - ArgumentTypeFuzzer fuzzer{*signature, rng_, decimalVariablesBindings}; - return fuzzer.fuzzReturnType(); - } - return nullptr; -} - core::TypedExprPtr ExpressionFuzzer::getCallExprFromCallable( const CallableSignature& callable, const TypePtr& type, const exec::FunctionSignature* signature) { auto args = getArgsForCallable(callable); - // For a decimal function (especially a nested one), as argument precisions - // and scales are randomly generated, callable.returnType does not follow the - // required constraints, and the matched result type needs to be recalculated - // from the argument types. If function signature is provided, generates a - // constrained type to avoid breaking the constraints between input types and - // output types. Otherwise, generate a CallTypedExpr with type because - // callable.returnType may not have the required field names. - const auto constrainedType = getConstrainedOutputType(args, signature); - return std::make_shared( - constrainedType ? constrainedType : type, args, callable.name); + // Generate a CallTypedExpr with type because callable.returnType may not have + // the required field names. + auto outputType = type; + // If signature is provided, for a decimal function (especially a nested one), + // as argument precisions and scales are randomly generated, + // callable.returnType does not follow the required constraints, and the + // matched result type needs to be recalculated from the argument types. Use + // ArgumentTypeFuzzer to generate a constrained type to avoid breaking the + // constraints between input types and output types. + if (signature) { + std::vector argTypes; + argTypes.reserve(args.size()); + for (const auto& arg : args) { + argTypes.emplace_back(arg->type()); + } + ArgumentTypeFuzzer fuzzer{*signature, rng_, argTypes}; + if (auto constrainedType = fuzzer.fuzzReturnType()) { + outputType = constrainedType; + } + } + return std::make_shared(outputType, args, callable.name); } const CallableSignature* ExpressionFuzzer::chooseRandomConcreteSignature( diff --git a/velox/expression/tests/ExpressionFuzzer.h b/velox/expression/tests/ExpressionFuzzer.h index ce235737a9014..6c01d1026b8c2 100644 --- a/velox/expression/tests/ExpressionFuzzer.h +++ b/velox/expression/tests/ExpressionFuzzer.h @@ -262,12 +262,6 @@ class ExpressionFuzzer { std::vector generateSwitchArgs( const CallableSignature& input); - /// Given the argument types, calculates the return type of a decimal function - /// by evaluating constraints. - TypePtr getConstrainedOutputType( - const std::vector& args, - const exec::FunctionSignature* signature); - core::TypedExprPtr getCallExprFromCallable( const CallableSignature& callable, const TypePtr& type, diff --git a/velox/expression/tests/utils/ArgumentTypeFuzzer.cpp b/velox/expression/tests/utils/ArgumentTypeFuzzer.cpp index 4eb9ce3449059..335a891aabb39 100644 --- a/velox/expression/tests/utils/ArgumentTypeFuzzer.cpp +++ b/velox/expression/tests/utils/ArgumentTypeFuzzer.cpp @@ -39,6 +39,79 @@ std::optional baseNameToTypeKind(const std::string& typeName) { return tryMapNameToTypeKind(kindName); } +ArgumentTypeFuzzer::ArgumentTypeFuzzer( + const exec::FunctionSignature& signature, + std::mt19937& rng, + const std::vector& argumentTypes) + : ArgumentTypeFuzzer(signature, nullptr, rng) { + if (argumentTypes.empty()) { + return; + } + // Checks if any variable is integer constrained, and get the decimal name + // style. + bool integerConstrained = false; + char decimalNameStyle = 0; + for (const auto& [variableName, variableInfo] : signature.variables()) { + if (variableInfo.isIntegerParameter()) { + // If constraints are empty, the integer variable is also regarded to be + // constrained as variables are shared across argument and return types. + integerConstrained = true; + if (variableName == "precision" || variableName == "scale") { + decimalNameStyle = 'a'; + break; + } + if (variableName.find("precision") != std::string::npos || + variableName.find("scale") != std::string::npos) { + decimalNameStyle = 'b'; + break; + } + if (variableName.find("i") != std::string::npos) { + decimalNameStyle = 'c'; + break; + } + } + } + + // To handle the constraints between input types and output types of a decimal + // function, extracts the input precisions and scales from decimal arguments, + // and bind them to integer variables. + column_index_t decimalColIndex = 1; + for (column_index_t i = 0; i < argumentTypes.size(); ++i) { + const auto argType = argumentTypes[i]; + if (argType->isDecimal()) { + const auto [p, s] = getDecimalPrecisionScale(*argType); + switch (decimalNameStyle) { + case 'a': { + integerVariablesBindings_["precision"] = p; + integerVariablesBindings_["scale"] = s; + break; + } + case 'b': { + const auto column = std::string(1, 'a' + i); + integerVariablesBindings_[column + "_precision"] = p; + integerVariablesBindings_[column + "_scale"] = s; + break; + } + case 'c': { + integerVariablesBindings_["i" + std::to_string(decimalColIndex)] = p; + integerVariablesBindings_ + ["i" + std::to_string(decimalColIndex + kIntegerPairSize)] = s; + decimalColIndex++; + break; + } + default: + VELOX_UNSUPPORTED( + "Unsupported decimal name style {}.", decimalNameStyle); + } + } + } + // Calculates the matched return type through the argument types with argument + // type fuzzer, which evaluates the constraints internally. + if (integerConstrained && argumentTypes.size() > 0) { + returnType_ = fuzzReturnType(); + } +} + void ArgumentTypeFuzzer::determineUnboundedIntegerVariables() { // Assign a random value for all integer values. for (const auto& [variableName, variableInfo] : variables()) { @@ -48,12 +121,13 @@ void ArgumentTypeFuzzer::determineUnboundedIntegerVariables() { } // When decimal function is registered as vector function, the variable name - // contains 'precision' like 'a_precision'. + // contains 'precision' like 'a_precision' as + // docs/develop/scalar-functions.rst illustrates. if (auto pos = variableName.find("precision"); pos != std::string::npos) { // Generate a random precision, and corresponding scale should not exceed // the precision. - const auto precision = - boost::random::uniform_int_distribution(1, 38)(rng_); + const auto precision = boost::random::uniform_int_distribution( + 1, LongDecimalType::kMaxPrecision)(rng_); integerVariablesBindings_[variableName] = precision; const auto colName = variableName.substr(0, pos); integerVariablesBindings_[colName + "scale"] = @@ -62,7 +136,7 @@ void ArgumentTypeFuzzer::determineUnboundedIntegerVariables() { } // When decimal function is registered as simple function, the variable name - // contains 'i' like 'i1'. + // contains 'i' like 'i1' as method name() of IntegerVariable returns. if (auto pos = variableName.find("i"); pos != std::string::npos) { VELOX_USER_CHECK_GE(variableName.size(), 2); const auto index = @@ -71,7 +145,8 @@ void ArgumentTypeFuzzer::determineUnboundedIntegerVariables() { // Generate a random precision, and corresponding scale should not // exceed the precision. const auto precision = - boost::random::uniform_int_distribution(1, 38)(rng_); + boost::random::uniform_int_distribution( + 1, LongDecimalType::kMaxPrecision)(rng_); integerVariablesBindings_[variableName] = precision; const auto scaleIndex = index + kIntegerPairSize; const auto scaleName = "i" + std::to_string(scaleIndex); diff --git a/velox/expression/tests/utils/ArgumentTypeFuzzer.h b/velox/expression/tests/utils/ArgumentTypeFuzzer.h index d6427c4b1500d..94a44f23cf820 100644 --- a/velox/expression/tests/utils/ArgumentTypeFuzzer.h +++ b/velox/expression/tests/utils/ArgumentTypeFuzzer.h @@ -28,21 +28,15 @@ namespace facebook::velox::test { /// arguments types. Optionally, allows to specify a desired return type. If /// specified, the return type acts as a constraint on the possible set of /// argument types. If no return type is specified, it also allows generate a -/// random type that can bind to the function's return type. +/// random type that can bind to the function's return type. Optionally, allows +/// to specify integer variable bindings. When specified, a random return type +/// that can bind to the integer constraints can be generated based on them. class ArgumentTypeFuzzer { public: - ArgumentTypeFuzzer( - const exec::FunctionSignature& signature, - std::mt19937& rng) - : ArgumentTypeFuzzer(signature, nullptr, rng) {} - ArgumentTypeFuzzer( const exec::FunctionSignature& signature, std::mt19937& rng, - const std::unordered_map& integerVariablesBindings) - : ArgumentTypeFuzzer(signature, nullptr, rng) { - integerVariablesBindings_ = integerVariablesBindings; - } + const std::vector& argumentTypes = {}); ArgumentTypeFuzzer( const exec::FunctionSignature& signature, @@ -62,6 +56,12 @@ class ArgumentTypeFuzzer { return argumentTypes_; } + /// Return the generated return type. This function could return nullptr if + /// return type has not been set. + const TypePtr& returnType() const { + return returnType_; + } + /// Return a random type that can bind to the function signature's return /// type and set returnType_ to this type. This function can only be called /// when returnType_ is uninitialized. diff --git a/velox/functions/prestosql/DecimalFunctions.cpp b/velox/functions/prestosql/DecimalFunctions.cpp index 25d371a9db764..d4ec90ca26696 100644 --- a/velox/functions/prestosql/DecimalFunctions.cpp +++ b/velox/functions/prestosql/DecimalFunctions.cpp @@ -152,6 +152,25 @@ template struct DecimalMultiplyFunction { VELOX_DEFINE_FUNCTION_TYPES(TExec); + template + void initialize( + const std::vector& inputTypes, + const core::QueryConfig& /*config*/, + A* /*a*/, + B* /*b*/) { + const auto aType = inputTypes[0]; + const auto bType = inputTypes[1]; + const auto [aPrecision, aScale] = getDecimalPrecisionScale(*aType); + const auto [bPrecision, bScale] = getDecimalPrecisionScale(*bType); + const auto rPrecision = std::min(38, aPrecision + bPrecision); + const auto rScale = aScale + bScale; + VELOX_USER_CHECK_LE( + rScale, + rPrecision, + "DECIMAL scale must be in range [0, {}].", + rPrecision); + } + template void call(R& out, const A& a, const B& b) { out = checkedMultiply(checkedMultiply(R(a), R(b)), R(1)); diff --git a/velox/functions/sparksql/fuzzer/MakeTimestampArgumentGenerator.cpp b/velox/functions/sparksql/fuzzer/MakeTimestampArgumentGenerator.cpp index ab88eeea51794..bd11283280c2a 100644 --- a/velox/functions/sparksql/fuzzer/MakeTimestampArgumentGenerator.cpp +++ b/velox/functions/sparksql/fuzzer/MakeTimestampArgumentGenerator.cpp @@ -26,7 +26,6 @@ std::vector MakeTimestampArgumentGenerator::generate( input.args.size(), 6, "At least six inputs are expected from the template signature."); - bool useTimezone = expressionFuzzer->rand32(0, 1); std::vector inputExpressions; inputExpressions.reserve(6); for (int index = 0; index < 5; ++index) { diff --git a/velox/type/SimpleFunctionApi.h b/velox/type/SimpleFunctionApi.h index 62028532964ee..4ad4e3d734e3d 100644 --- a/velox/type/SimpleFunctionApi.h +++ b/velox/type/SimpleFunctionApi.h @@ -63,6 +63,8 @@ using S1 = IntegerVariable<5>; using S2 = IntegerVariable<6>; using S3 = IntegerVariable<7>; using S4 = IntegerVariable<8>; +// The pair size of precisions and scales can be represented with integer +// variables. const uint8_t kIntegerPairSize = 4; template