/* Copyright (C) 2014 InfiniDB, Inc. Copyright (C) 2019 MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /*********************************************************************** * $Id$ * * ***********************************************************************/ /** @file */ #pragma once #include #include #include #include #include "mcs_int128.h" #include "operator.h" #include "parsetree.h" namespace messageqcpp { class ByteStream; } namespace execplan { class ArithmeticOperator : public Operator { using cscType = execplan::CalpontSystemCatalog::ColType; public: ArithmeticOperator(); explicit ArithmeticOperator(const std::string& operatorName); ArithmeticOperator(const ArithmeticOperator& rhs); ~ArithmeticOperator() override; /** return a copy of this pointer * * deep copy of this pointer and return the copy */ inline ArithmeticOperator* clone() const override { return new ArithmeticOperator(*this); } inline long timeZone() const { return fTimeZone; } inline void timeZone(const long timeZone) { fTimeZone = timeZone; } /** * The serialization interface */ void serialize(messageqcpp::ByteStream&) const override; void unserialize(messageqcpp::ByteStream&) override; /** @brief Do a deep, strict (as opposed to semantic) equivalence test * * Do a deep, strict (as opposed to semantic) equivalence test. * @return true iff every member of t is a duplicate copy of every member of this; false otherwise */ bool operator==(const TreeNode* t) const override; /** @brief Do a deep, strict (as opposed to semantic) equivalence test * * Do a deep, strict (as opposed to semantic) equivalence test. * @return true iff every member of t is a duplicate copy of every member of this; false otherwise */ bool operator==(const ArithmeticOperator& t) const; /** @brief Do a deep, strict (as opposed to semantic) equivalence test * * Do a deep, strict (as opposed to semantic) equivalence test. * @return false iff every member of t is a duplicate copy of every member of this; true otherwise */ bool operator!=(const TreeNode* t) const override; /** @brief Do a deep, strict (as opposed to semantic) equivalence test * * Do a deep, strict (as opposed to semantic) equivalence test. * @return false iff every member of t is a duplicate copy of every member of this; true otherwise */ bool operator!=(const ArithmeticOperator& t) const; /*********************************************************** * F&E framework * ***********************************************************/ using Operator::evaluate; inline void evaluate(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override; using Operator::getStrVal; const utils::NullString& getStrVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { bool localIsNull = false; evaluate(row, localIsNull, lop, rop); isNull = isNull || localIsNull; return localIsNull ? fResult.strVal.dropString() : TreeNode::getStrVal(fTimeZone); } using Operator::getIntVal; int64_t getIntVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getIntVal(); } using Operator::getUintVal; uint64_t getUintVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getUintVal(); } using Operator::getFloatVal; float getFloatVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getFloatVal(); } using Operator::getDoubleVal; double getDoubleVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getDoubleVal(); } using Operator::getLongDoubleVal; long double getLongDoubleVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getLongDoubleVal(); } using Operator::getDecimalVal; IDB_Decimal getDecimalVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); // @bug5736, double type with precision -1 indicates that this type is for decimal math, // the original decimal scale is stored in scale field, which is no use for double. if (fResultType.colDataType == CalpontSystemCatalog::DOUBLE && fResultType.precision == -1) { IDB_Decimal rv; rv.scale = fResultType.scale; rv.precision = 15; rv.value = (int64_t)(TreeNode::getDoubleVal() * IDB_pow[rv.scale]); return rv; } return TreeNode::getDecimalVal(); } using Operator::getDateIntVal; int32_t getDateIntVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getDateIntVal(); } using Operator::getDatetimeIntVal; int64_t getDatetimeIntVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getDatetimeIntVal(); } using Operator::getTimestampIntVal; int64_t getTimestampIntVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getTimestampIntVal(); } using Operator::getTimeIntVal; int64_t getTimeIntVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getTimeIntVal(); } using Operator::getBoolVal; bool getBoolVal(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) override { evaluate(row, isNull, lop, rop); return TreeNode::getBoolVal(); } void adjustResultType(const CalpontSystemCatalog::ColType& m); inline bool getOverflowCheck() const { return fDecimalOverflowCheck; } inline void setOverflowCheck(bool check) { fDecimalOverflowCheck = check; } inline std::string toCppCode(IncludeSet& includes) const override { includes.insert("arithmeticoperator.h"); std::stringstream ss; ss << "ArithmeticOperator(" << std::quoted(fData) << ")"; return ss.str(); } private: template inline result_t execute(result_t op1, result_t op2, bool& isNull); inline void execute(IDB_Decimal& result, IDB_Decimal op1, IDB_Decimal op2, bool& isNull); long fTimeZone; bool fDecimalOverflowCheck; }; // Can be easily replaced with a template over T if MDB changes the result return type. inline uint64_t rangesCheck(const datatypes::TSInt128 x, const OpType op, const bool isNull) { auto result = x.toUBIGINTWithDomainCheck(); if (!isNull && !result) { logging::Message::Args args; static const std::string sqlType{"BIGINT UNSIGNED"}; args.add(sqlType); switch (op) { case OP_ADD: args.add("\"+\""); break; case OP_SUB: args.add("\"-\""); break; case OP_MUL: args.add("\"*\""); break; case OP_DIV: args.add("\"/\""); break; default: args.add(""); break; } const auto errcode = logging::ERR_MATH_PRODUCES_OUT_OF_RANGE_RESULT; throw logging::IDBExcept(logging::IDBErrorInfo::instance()->errorMsg(errcode, args), errcode); } return result.value(); // if isNull returns some value } inline void ArithmeticOperator::evaluate(rowgroup::Row& row, bool& isNull, ParseTree* lop, ParseTree* rop) { // fOpType should have already been set on the connector during parsing switch (fOperationType.colDataType) { case execplan::CalpontSystemCatalog::BIGINT: case execplan::CalpontSystemCatalog::INT: case execplan::CalpontSystemCatalog::MEDINT: case execplan::CalpontSystemCatalog::SMALLINT: case execplan::CalpontSystemCatalog::TINYINT: fResult.intVal = execute(lop->getIntVal(row, isNull), rop->getIntVal(row, isNull), isNull); if (isNull) { fResult.intVal = joblist::INTNULL; } break; case execplan::CalpontSystemCatalog::UBIGINT: { // XXX: this is bandaid solution for specific customer case (MCOL-5568). // Despite that I tried to implement a proper solution: to have operations // performed using int128_t amd then check the result. bool signedLeft = lop->data()->resultType().isSignedInteger(); bool signedRight = rop->data()->resultType().isSignedInteger(); const datatypes::TSInt128 x((signedLeft) ? static_cast(lop->getIntVal(row, isNull)) : lop->getUintVal(row, isNull)); const datatypes::TSInt128 y((signedRight) ? static_cast(rop->getIntVal(row, isNull)) : rop->getUintVal(row, isNull)); fResult.uintVal = rangesCheck(execute(x, y, isNull), fOp, isNull); // throws } break; case execplan::CalpontSystemCatalog::UINT: case execplan::CalpontSystemCatalog::UMEDINT: case execplan::CalpontSystemCatalog::USMALLINT: case execplan::CalpontSystemCatalog::UTINYINT: fResult.uintVal = execute(lop->getUintVal(row, isNull), rop->getUintVal(row, isNull), isNull); if (isNull) { fResult.uintVal = joblist::UBIGINTNULL; } break; case execplan::CalpontSystemCatalog::DOUBLE: case execplan::CalpontSystemCatalog::FLOAT: case execplan::CalpontSystemCatalog::UDOUBLE: case execplan::CalpontSystemCatalog::UFLOAT: fResult.doubleVal = execute(lop->getDoubleVal(row, isNull), rop->getDoubleVal(row, isNull), isNull); break; case execplan::CalpontSystemCatalog::LONGDOUBLE: fResult.longDoubleVal = execute(lop->getLongDoubleVal(row, isNull), rop->getLongDoubleVal(row, isNull), isNull); break; case execplan::CalpontSystemCatalog::DECIMAL: case execplan::CalpontSystemCatalog::UDECIMAL: execute(fResult.decimalVal, lop->getDecimalVal(row, isNull), rop->getDecimalVal(row, isNull), isNull); break; default: { std::ostringstream oss; oss << "invalid arithmetic operand type: " << fOperationType.colDataType; throw logging::InvalidArgumentExcept(oss.str()); } } } template inline T ArithmeticOperator::execute(T op1, T op2, bool& isNull) { if (isNull) { // at least one operand is NULL. // do nothing, return 0. if constexpr (std::is_same::value) { return datatypes::TSInt128(); // returns 0 } else { return T{0}; } } switch (fOp) { case OP_ADD: return op1 + op2; case OP_SUB: return op1 - op2; case OP_MUL: return op1 * op2; case OP_DIV: if (op2) { return op1 / op2; } else { isNull = true; } if constexpr (std::is_same::value) { return datatypes::TSInt128(); // returns 0 } else { return T{0}; } default: { std::ostringstream oss; oss << "invalid arithmetic operation: " << fOp; throw logging::InvalidOperationExcept(oss.str()); } } } inline void ArithmeticOperator::execute(IDB_Decimal& result, IDB_Decimal op1, IDB_Decimal op2, bool& isNull) { switch (fOp) { case OP_ADD: if (fOperationType.colWidth == datatypes::MAXDECIMALWIDTH) { if (LIKELY(!fDecimalOverflowCheck)) { datatypes::Decimal::addition(op1, op2, result); } else { datatypes::Decimal::addition(op1, op2, result); } } else if (fOperationType.colWidth == utils::MAXLEGACYWIDTH) { if (LIKELY(!fDecimalOverflowCheck)) { datatypes::Decimal::addition(op1, op2, result); } else { datatypes::Decimal::addition(op1, op2, result); } } else { throw logging::InvalidArgumentExcept("Unexpected result width"); } break; case OP_SUB: if (fOperationType.colWidth == datatypes::MAXDECIMALWIDTH) { if (LIKELY(!fDecimalOverflowCheck)) { datatypes::Decimal::subtraction(op1, op2, result); } else { datatypes::Decimal::subtraction(op1, op2, result); } } else if (fOperationType.colWidth == utils::MAXLEGACYWIDTH) { if (LIKELY(!fDecimalOverflowCheck)) { datatypes::Decimal::subtraction(op1, op2, result); } else { datatypes::Decimal::subtraction(op1, op2, result); } } else { throw logging::InvalidArgumentExcept("Unexpected result width"); } break; case OP_MUL: if (fOperationType.colWidth == datatypes::MAXDECIMALWIDTH) { if (LIKELY(!fDecimalOverflowCheck)) { datatypes::Decimal::multiplication(op1, op2, result); } else { datatypes::Decimal::multiplication(op1, op2, result); } } else if (fOperationType.colWidth == utils::MAXLEGACYWIDTH) { if (LIKELY(!fDecimalOverflowCheck)) { datatypes::Decimal::multiplication(op1, op2, result); } else { datatypes::Decimal::multiplication(op1, op2, result); } } else { throw logging::InvalidArgumentExcept("Unexpected result width"); } break; case OP_DIV: if (fOperationType.colWidth == datatypes::MAXDECIMALWIDTH) { if ((datatypes::Decimal::isWideDecimalTypeByPrecision(op2.precision) && op2.s128Value == 0) || (!datatypes::Decimal::isWideDecimalTypeByPrecision(op2.precision) && op2.value == 0)) { isNull = true; break; } if (LIKELY(!fDecimalOverflowCheck)) { datatypes::Decimal::division(op1, op2, result); } else { datatypes::Decimal::division(op1, op2, result); } } else if (fOperationType.colWidth == utils::MAXLEGACYWIDTH) { if (op2.value == 0) { isNull = true; break; } if (LIKELY(!fDecimalOverflowCheck)) { datatypes::Decimal::division(op1, op2, result); } else { datatypes::Decimal::division(op1, op2, result); } } else { throw logging::InvalidArgumentExcept("Unexpected result width"); } break; default: { std::ostringstream oss; oss << "invalid arithmetic operation: " << fOp; throw logging::InvalidOperationExcept(oss.str()); } } } std::ostream& operator<<(std::ostream& os, const ArithmeticOperator& rhs); } // namespace execplan