diff --git a/Firestore/core/src/api/expressions.cc b/Firestore/core/src/api/expressions.cc index 1c8f504b31c..5db7b3a3110 100644 --- a/Firestore/core/src/api/expressions.cc +++ b/Firestore/core/src/api/expressions.cc @@ -41,7 +41,7 @@ google_firestore_v1_Value Field::to_proto() const { return result; } -std::unique_ptr Field::ToEvaluable() const { +std::unique_ptr Field::ToEvaluable() const { return std::make_unique(std::make_unique(*this)); } @@ -54,7 +54,7 @@ const google_firestore_v1_Value& Constant::value() const { return *value_; } -std::unique_ptr Constant::ToEvaluable() const { +std::unique_ptr Constant::ToEvaluable() const { return std::make_unique( std::make_unique(*this)); } @@ -72,7 +72,7 @@ google_firestore_v1_Value FunctionExpr::to_proto() const { return result; } -std::unique_ptr FunctionExpr::ToEvaluable() const { +std::unique_ptr FunctionExpr::ToEvaluable() const { return core::FunctionToEvaluable(*this); } diff --git a/Firestore/core/src/api/expressions.h b/Firestore/core/src/api/expressions.h index c90dcce2eb7..001aba3b522 100644 --- a/Firestore/core/src/api/expressions.h +++ b/Firestore/core/src/api/expressions.h @@ -29,7 +29,7 @@ namespace firebase { namespace firestore { namespace core { -class EvaluableExpr; +class EvaluableExpression; } // namespace core namespace api { @@ -38,7 +38,7 @@ class Expr { Expr() = default; virtual ~Expr() = default; virtual google_firestore_v1_Value to_proto() const = 0; - virtual std::unique_ptr ToEvaluable() const = 0; + virtual std::unique_ptr ToEvaluable() const = 0; }; class Selectable : public Expr { @@ -66,7 +66,7 @@ class Field : public Selectable { return field_path_; } - std::unique_ptr ToEvaluable() const override; + std::unique_ptr ToEvaluable() const override; private: model::FieldPath field_path_; @@ -82,7 +82,7 @@ class Constant : public Expr { const google_firestore_v1_Value& value() const; - std::unique_ptr ToEvaluable() const override; + std::unique_ptr ToEvaluable() const override; private: nanopb::SharedMessage value_; @@ -96,7 +96,7 @@ class FunctionExpr : public Expr { google_firestore_v1_Value to_proto() const override; - std::unique_ptr ToEvaluable() const override; + std::unique_ptr ToEvaluable() const override; const std::string& name() const { return name_; diff --git a/Firestore/core/src/pipeline/aggregates_evaluation.h b/Firestore/core/src/pipeline/aggregates_evaluation.h index b945519a00b..b2b225d6719 100644 --- a/Firestore/core/src/pipeline/aggregates_evaluation.h +++ b/Firestore/core/src/pipeline/aggregates_evaluation.h @@ -24,7 +24,7 @@ namespace firebase { namespace firestore { namespace core { -class CoreMaximum : public EvaluableExpr { +class CoreMaximum : public EvaluableExpression { public: explicit CoreMaximum(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -37,7 +37,7 @@ class CoreMaximum : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreMinimum : public EvaluableExpr { +class CoreMinimum : public EvaluableExpression { public: explicit CoreMinimum(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { diff --git a/Firestore/core/src/pipeline/arithmetic_evaluation.cc b/Firestore/core/src/pipeline/arithmetic_evaluation.cc index 00180d5e3b2..7e3fee660f2 100644 --- a/Firestore/core/src/pipeline/arithmetic_evaluation.cc +++ b/Firestore/core/src/pipeline/arithmetic_evaluation.cc @@ -1,131 +1,18 @@ -/* - * Copyright 2025 Google LLC - * - * 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 "Firestore/core/src/pipeline/arithmetic_evaluation.h" #include #include #include -#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/pipeline/util_evaluation.h" -#include "Firestore/core/src/util/hard_assert.h" #include "absl/types/optional.h" namespace firebase { namespace firestore { namespace core { -namespace { - -// Helper to create a Value proto from double -nanopb::Message DoubleValue(double val) { - google_firestore_v1_Value proto; - proto.which_value_type = google_firestore_v1_Value_double_value_tag; - proto.double_value = val; - return nanopb::MakeMessage(std::move(proto)); -} - -} // anonymous namespace - -// --- Arithmetic Implementations --- -EvaluateResult ArithmeticBase::Evaluate( - const api::EvaluateContext& context, - const model::PipelineInputOutput& document) const { - HARD_ASSERT(expr_->params().size() >= 2, - "%s() function requires at least 2 params", expr_->name()); - - EvaluateResult current_result = - expr_->params()[0]->ToEvaluable()->Evaluate(context, document); - - for (size_t i = 1; i < expr_->params().size(); ++i) { - // Check current accumulated result before evaluating next operand - if (current_result.IsErrorOrUnset()) { - // Propagate error immediately if accumulated result is error/unset - // Note: Unset is treated as Error in arithmetic according to TS logic - return EvaluateResult::NewError(); - } - // Null check happens inside ApplyOperation - - EvaluateResult next_operand = - expr_->params()[i]->ToEvaluable()->Evaluate(context, document); - - // Apply the operation - current_result = ApplyOperation(current_result, next_operand); - - // If ApplyOperation resulted in error or unset, propagate immediately as - // error - if (current_result.IsErrorOrUnset()) { - // Treat Unset from ApplyOperation as Error for propagation - return EvaluateResult::NewError(); - } - // Null is handled within the loop by ApplyOperation in the next iteration - } - - return current_result; -} - -inline EvaluateResult ArithmeticBase::ApplyOperation( - const EvaluateResult& left, const EvaluateResult& right) const { - // Mirroring TypeScript logic: - // 1. Check for Error/Unset first - if (left.IsErrorOrUnset() || right.IsErrorOrUnset()) { - return EvaluateResult::NewError(); - } - // 2. Check for Null - if (left.IsNull() || right.IsNull()) { - return EvaluateResult::NewNull(); - } - - // 3. Type check: Both must be numbers - const google_firestore_v1_Value* left_val = left.value(); - const google_firestore_v1_Value* right_val = right.value(); - if (!model::IsNumber(*left_val) || !model::IsNumber(*right_val)) { - return EvaluateResult::NewError(); // Type error - } - - // 4. Determine operation type (Integer or Double) - if (model::IsDouble(*left_val) || model::IsDouble(*right_val)) { - // Promote to double - double left_double_val = model::IsDouble(*left_val) - ? left_val->double_value - : static_cast(left_val->integer_value); - double right_double_val = - model::IsDouble(*right_val) - ? right_val->double_value - : static_cast(right_val->integer_value); - - // NaN propagation and specific error handling (like div/mod by zero) - // are handled within PerformDoubleOperation. - return PerformDoubleOperation(left_double_val, right_double_val); - - } else { - // Both are integers - absl::optional left_int_opt = model::GetInteger(*left_val); - absl::optional right_int_opt = model::GetInteger(*right_val); - // These should always succeed because we already checked IsNumber and - // excluded IsDouble. - HARD_ASSERT(left_int_opt.has_value() && right_int_opt.has_value(), - "Failed to extract integer values after IsNumber check"); - - return PerformIntegerOperation(left_int_opt.value(), right_int_opt.value()); - } -} - -EvaluateResult CoreAdd::PerformIntegerOperation(int64_t l, int64_t r) const { +EvaluateResult EvaluateAdd::PerformIntegerOperation(int64_t l, + int64_t r) const { auto const result = SafeAdd(l, r); if (result.has_value()) { return EvaluateResult::NewValue(IntValue(result.value())); @@ -134,12 +21,12 @@ EvaluateResult CoreAdd::PerformIntegerOperation(int64_t l, int64_t r) const { return EvaluateResult::NewError(); } -EvaluateResult CoreAdd::PerformDoubleOperation(double l, double r) const { +EvaluateResult EvaluateAdd::PerformDoubleOperation(double l, double r) const { return EvaluateResult::NewValue(DoubleValue(l + r)); } -EvaluateResult CoreSubtract::PerformIntegerOperation(int64_t l, - int64_t r) const { +EvaluateResult EvaluateSubtract::PerformIntegerOperation(int64_t l, + int64_t r) const { auto const result = SafeSubtract(l, r); if (result.has_value()) { return EvaluateResult::NewValue(IntValue(result.value())); @@ -148,12 +35,13 @@ EvaluateResult CoreSubtract::PerformIntegerOperation(int64_t l, return EvaluateResult::NewError(); } -EvaluateResult CoreSubtract::PerformDoubleOperation(double l, double r) const { +EvaluateResult EvaluateSubtract::PerformDoubleOperation(double l, + double r) const { return EvaluateResult::NewValue(DoubleValue(l - r)); } -EvaluateResult CoreMultiply::PerformIntegerOperation(int64_t l, - int64_t r) const { +EvaluateResult EvaluateMultiply::PerformIntegerOperation(int64_t l, + int64_t r) const { auto const result = SafeMultiply(l, r); if (result.has_value()) { return EvaluateResult::NewValue(IntValue(result.value())); @@ -162,11 +50,13 @@ EvaluateResult CoreMultiply::PerformIntegerOperation(int64_t l, return EvaluateResult::NewError(); } -EvaluateResult CoreMultiply::PerformDoubleOperation(double l, double r) const { +EvaluateResult EvaluateMultiply::PerformDoubleOperation(double l, + double r) const { return EvaluateResult::NewValue(DoubleValue(l * r)); } -EvaluateResult CoreDivide::PerformIntegerOperation(int64_t l, int64_t r) const { +EvaluateResult EvaluateDivide::PerformIntegerOperation(int64_t l, + int64_t r) const { auto const result = SafeDivide(l, r); if (result.has_value()) { return EvaluateResult::NewValue(IntValue(result.value())); @@ -175,14 +65,16 @@ EvaluateResult CoreDivide::PerformIntegerOperation(int64_t l, int64_t r) const { return EvaluateResult::NewError(); } -EvaluateResult CoreDivide::PerformDoubleOperation(double l, double r) const { +EvaluateResult EvaluateDivide::PerformDoubleOperation(double l, + double r) const { // C++ double division handles signed zero correctly according to IEEE // 754. +x / +0 -> +Inf -x / +0 -> -Inf +x / -0 -> -Inf -x / -0 -> +Inf // 0 / 0 -> NaN return EvaluateResult::NewValue(DoubleValue(l / r)); } -EvaluateResult CoreMod::PerformIntegerOperation(int64_t l, int64_t r) const { +EvaluateResult EvaluateMod::PerformIntegerOperation(int64_t l, + int64_t r) const { auto const result = SafeMod(l, r); if (result.has_value()) { return EvaluateResult::NewValue(IntValue(result.value())); @@ -191,7 +83,7 @@ EvaluateResult CoreMod::PerformIntegerOperation(int64_t l, int64_t r) const { return EvaluateResult::NewError(); } -EvaluateResult CoreMod::PerformDoubleOperation(double l, double r) const { +EvaluateResult EvaluateMod::PerformDoubleOperation(double l, double r) const { if (r == 0.0) { return EvaluateResult::NewValue( DoubleValue(std::numeric_limits::quiet_NaN())); @@ -200,6 +92,147 @@ EvaluateResult CoreMod::PerformDoubleOperation(double l, double r) const { return EvaluateResult::NewValue(DoubleValue(std::fmod(l, r))); } +EvaluateResult EvaluatePow::PerformIntegerOperation(int64_t l, + int64_t r) const { + // Promote to double, as std::pow for integers is complex and can overflow. + return PerformDoubleOperation(static_cast(l), static_cast(r)); +} + +EvaluateResult EvaluatePow::PerformDoubleOperation(double l, double r) const { + if (r == 0.0 || l == 1.0) { + return EvaluateResult::NewValue(DoubleValue(1.0)); + } + if (l == -1.0 && std::isinf(r)) { + return EvaluateResult::NewValue(DoubleValue(1.0)); + } + if (std::isnan(l) || std::isnan(r)) { + return EvaluateResult::NewValue( + DoubleValue(std::numeric_limits::quiet_NaN())); + } + // Check for non-integer exponent on a negative base + if (l < 0 && std::isfinite(l) && (r != std::floor(r))) { + return EvaluateResult::NewError(); + } + if ((l == 0.0 || l == -0.0) && r < 0) { + return EvaluateResult::NewError(); + } + return EvaluateResult::NewValue(DoubleValue(std::pow(l, r))); +} + +EvaluateResult EvaluateRoundToPrecision::PerformIntegerOperation( + int64_t l, int64_t r) const { + if (r >= 0) { + return EvaluateResult::NewValue(IntValue(l)); + } + double num_digits = + std::floor(std::log10(std::abs(static_cast(l)))) + 1; + if (-r >= num_digits) { + return EvaluateResult::NewValue(IntValue(0)); + } + double rounding_factor_double = std::pow(10.0, -static_cast(r)); + int64_t rounding_factor = static_cast(rounding_factor_double); + + int64_t truncated = l - (l % rounding_factor); + + if (std::abs(l % rounding_factor) < (rounding_factor / 2)) { + return EvaluateResult::NewValue(IntValue(truncated)); + } + + if (l < 0) { + if (l < std::numeric_limits::min() + rounding_factor) + return EvaluateResult::NewError(); + return EvaluateResult::NewValue(IntValue(truncated - rounding_factor)); + } else { + if (l > std::numeric_limits::max() - rounding_factor) + return EvaluateResult::NewError(); + return EvaluateResult::NewValue(IntValue(truncated + rounding_factor)); + } +} + +EvaluateResult EvaluateRoundToPrecision::PerformDoubleOperation( + double l, double r) const { + int64_t places = static_cast(r); + if (places >= 16 || !std::isfinite(l)) { + return EvaluateResult::NewValue(DoubleValue(l)); + } + double num_digits = std::floor(std::log10(std::abs(l))) + 1; + if (-places >= num_digits) { + return EvaluateResult::NewValue(DoubleValue(0.0)); + } + double factor = std::pow(10.0, places); + double result = std::round(l * factor) / factor; + + if (std::isfinite(result)) { + return EvaluateResult::NewValue(DoubleValue(result)); + } + return EvaluateResult::NewError(); // overflow +} + +EvaluateResult EvaluateLog::PerformIntegerOperation(int64_t l, + int64_t r) const { + return PerformDoubleOperation(static_cast(l), static_cast(r)); +} + +EvaluateResult EvaluateLog::PerformDoubleOperation(double l, double r) const { + if (std::isinf(l) && l < 0) { + return EvaluateResult::NewValue( + DoubleValue(std::numeric_limits::quiet_NaN())); + } + if (std::isinf(r)) { + return EvaluateResult::NewValue( + DoubleValue(std::numeric_limits::quiet_NaN())); + } + if (l <= 0 || r <= 0 || r == 1.0) { + return EvaluateResult::NewError(); + } + return EvaluateResult::NewValue(DoubleValue(std::log(l) / std::log(r))); +} + +EvaluateResult EvaluateCeil::PerformOperation(double val) const { + return EvaluateResult::NewValue(DoubleValue(std::ceil(val))); +} + +EvaluateResult EvaluateFloor::PerformOperation(double val) const { + return EvaluateResult::NewValue(DoubleValue(std::floor(val))); +} + +EvaluateResult EvaluateRound::PerformOperation(double val) const { + return EvaluateResult::NewValue(DoubleValue(std::round(val))); +} + +EvaluateResult EvaluateAbs::PerformOperation(double val) const { + return EvaluateResult::NewValue(DoubleValue(std::abs(val))); +} + +EvaluateResult EvaluateExp::PerformOperation(double val) const { + double result = std::exp(val); + if (std::isinf(result) && !std::isinf(val)) { + return EvaluateResult::NewError(); // Overflow + } + return EvaluateResult::NewValue(DoubleValue(result)); +} + +EvaluateResult EvaluateLn::PerformOperation(double val) const { + if (val <= 0) { + return EvaluateResult::NewError(); + } + return EvaluateResult::NewValue(DoubleValue(std::log(val))); +} + +EvaluateResult EvaluateLog10::PerformOperation(double val) const { + if (val <= 0) { + return EvaluateResult::NewError(); + } + return EvaluateResult::NewValue(DoubleValue(std::log10(val))); +} + +EvaluateResult EvaluateSqrt::PerformOperation(double val) const { + if (val < 0) { + return EvaluateResult::NewError(); + } + return EvaluateResult::NewValue(DoubleValue(std::sqrt(val))); +} + } // namespace core } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/pipeline/arithmetic_evaluation.h b/Firestore/core/src/pipeline/arithmetic_evaluation.h index 4b7041a83f1..cf2c07d3e9e 100644 --- a/Firestore/core/src/pipeline/arithmetic_evaluation.h +++ b/Firestore/core/src/pipeline/arithmetic_evaluation.h @@ -19,49 +19,53 @@ #include #include "Firestore/core/src/pipeline/expression_evaluation.h" +#include "Firestore/core/src/pipeline/util_evaluation.h" namespace firebase { namespace firestore { namespace core { -// --- Base Class for Arithmetic Operations --- -class ArithmeticBase : public EvaluableExpr { +// --- End Base Class for Arithmetic Operations --- + +class EvaluateAdd : public BinaryArithmetic { public: - explicit ArithmeticBase(const api::FunctionExpr& expr) - : expr_(std::make_unique(expr)) { + explicit EvaluateAdd(const api::FunctionExpr& expr) : BinaryArithmetic(expr) { } - ~ArithmeticBase() override = default; - - // Implementation is inline below - EvaluateResult Evaluate( - const api::EvaluateContext& context, - const model::PipelineInputOutput& document) const override; protected: - // Performs the specific integer operation (e.g., add, subtract). - // Returns Error result on overflow or invalid operation (like div/mod by - // zero). - virtual EvaluateResult PerformIntegerOperation(int64_t lhs, - int64_t rhs) const = 0; + EvaluateResult PerformIntegerOperation(int64_t lhs, + int64_t rhs) const override; + EvaluateResult PerformDoubleOperation(double lhs, double rhs) const override; +}; + +class EvaluateSubtract : public BinaryArithmetic { + public: + explicit EvaluateSubtract(const api::FunctionExpr& expr) + : BinaryArithmetic(expr) { + } - // Performs the specific double operation. - // Returns Error result on invalid operation (like div/mod by zero). - virtual EvaluateResult PerformDoubleOperation(double lhs, - double rhs) const = 0; + protected: + EvaluateResult PerformIntegerOperation(int64_t lhs, + int64_t rhs) const override; + EvaluateResult PerformDoubleOperation(double lhs, double rhs) const override; +}; - // Applies the arithmetic operation between two evaluated results. - // Mirrors the logic from TypeScript's applyArithmetics. - // Implementation is inline below - EvaluateResult ApplyOperation(const EvaluateResult& left, - const EvaluateResult& right) const; +class EvaluateMultiply : public BinaryArithmetic { + public: + explicit EvaluateMultiply(const api::FunctionExpr& expr) + : BinaryArithmetic(expr) { + } - std::unique_ptr expr_; + protected: + EvaluateResult PerformIntegerOperation(int64_t lhs, + int64_t rhs) const override; + EvaluateResult PerformDoubleOperation(double lhs, double rhs) const override; }; -// --- End Base Class for Arithmetic Operations --- -class CoreAdd : public ArithmeticBase { +class EvaluateDivide : public BinaryArithmetic { public: - explicit CoreAdd(const api::FunctionExpr& expr) : ArithmeticBase(expr) { + explicit EvaluateDivide(const api::FunctionExpr& expr) + : BinaryArithmetic(expr) { } protected: @@ -70,9 +74,9 @@ class CoreAdd : public ArithmeticBase { EvaluateResult PerformDoubleOperation(double lhs, double rhs) const override; }; -class CoreSubtract : public ArithmeticBase { +class EvaluateMod : public BinaryArithmetic { public: - explicit CoreSubtract(const api::FunctionExpr& expr) : ArithmeticBase(expr) { + explicit EvaluateMod(const api::FunctionExpr& expr) : BinaryArithmetic(expr) { } protected: @@ -81,9 +85,9 @@ class CoreSubtract : public ArithmeticBase { EvaluateResult PerformDoubleOperation(double lhs, double rhs) const override; }; -class CoreMultiply : public ArithmeticBase { +class EvaluatePow : public BinaryArithmetic { public: - explicit CoreMultiply(const api::FunctionExpr& expr) : ArithmeticBase(expr) { + explicit EvaluatePow(const api::FunctionExpr& expr) : BinaryArithmetic(expr) { } protected: @@ -92,9 +96,10 @@ class CoreMultiply : public ArithmeticBase { EvaluateResult PerformDoubleOperation(double lhs, double rhs) const override; }; -class CoreDivide : public ArithmeticBase { +class EvaluateRoundToPrecision : public BinaryArithmetic { public: - explicit CoreDivide(const api::FunctionExpr& expr) : ArithmeticBase(expr) { + explicit EvaluateRoundToPrecision(const api::FunctionExpr& expr) + : BinaryArithmetic(expr) { } protected: @@ -103,9 +108,9 @@ class CoreDivide : public ArithmeticBase { EvaluateResult PerformDoubleOperation(double lhs, double rhs) const override; }; -class CoreMod : public ArithmeticBase { +class EvaluateLog : public BinaryArithmetic { public: - explicit CoreMod(const api::FunctionExpr& expr) : ArithmeticBase(expr) { + explicit EvaluateLog(const api::FunctionExpr& expr) : BinaryArithmetic(expr) { } protected: @@ -114,8 +119,83 @@ class CoreMod : public ArithmeticBase { EvaluateResult PerformDoubleOperation(double lhs, double rhs) const override; }; +class EvaluateCeil : public UnaryArithmetic { + public: + explicit EvaluateCeil(const api::FunctionExpr& expr) : UnaryArithmetic(expr) { + } + + protected: + EvaluateResult PerformOperation(double val) const override; +}; + +class EvaluateFloor : public UnaryArithmetic { + public: + explicit EvaluateFloor(const api::FunctionExpr& expr) + : UnaryArithmetic(expr) { + } + + protected: + EvaluateResult PerformOperation(double val) const override; +}; + +class EvaluateRound : public UnaryArithmetic { + public: + explicit EvaluateRound(const api::FunctionExpr& expr) + : UnaryArithmetic(expr) { + } + + protected: + EvaluateResult PerformOperation(double val) const override; +}; + +class EvaluateAbs : public UnaryArithmetic { + public: + explicit EvaluateAbs(const api::FunctionExpr& expr) : UnaryArithmetic(expr) { + } + + protected: + EvaluateResult PerformOperation(double val) const override; +}; + +class EvaluateExp : public UnaryArithmetic { + public: + explicit EvaluateExp(const api::FunctionExpr& expr) : UnaryArithmetic(expr) { + } + + protected: + EvaluateResult PerformOperation(double val) const override; +}; + +class EvaluateLn : public UnaryArithmetic { + public: + explicit EvaluateLn(const api::FunctionExpr& expr) : UnaryArithmetic(expr) { + } + + protected: + EvaluateResult PerformOperation(double val) const override; +}; + +class EvaluateLog10 : public UnaryArithmetic { + public: + explicit EvaluateLog10(const api::FunctionExpr& expr) + : UnaryArithmetic(expr) { + } + + protected: + EvaluateResult PerformOperation(double val) const override; +}; + +class EvaluateSqrt : public UnaryArithmetic { + public: + explicit EvaluateSqrt(const api::FunctionExpr& expr) : UnaryArithmetic(expr) { + } + + protected: + EvaluateResult PerformOperation(double val) const override; +}; + } // namespace core } // namespace firestore } // namespace firebase -#endif // FIRESTORE_CORE_SRC_PIPELINE_ARITHMETIC_EVALUATION_H_ +#endif // FIRESTORE_CORE_SRC_PIPELINE_ARITHMETIC_EVALUATION_H_ \ No newline at end of file diff --git a/Firestore/core/src/pipeline/array_evaluation.cc b/Firestore/core/src/pipeline/array_evaluation.cc index 3b3723b045b..5a2af5dfe7b 100644 --- a/Firestore/core/src/pipeline/array_evaluation.cc +++ b/Firestore/core/src/pipeline/array_evaluation.cc @@ -36,7 +36,7 @@ EvaluateResult CoreArrayReverse::Evaluate( HARD_ASSERT(expr_->params().size() == 1, "array_reverse() function requires exactly 1 param"); - std::unique_ptr operand_evaluable = + std::unique_ptr operand_evaluable = expr_->params()[0]->ToEvaluable(); EvaluateResult evaluated = operand_evaluable->Evaluate(context, document); @@ -86,7 +86,7 @@ EvaluateResult CoreArrayContainsAll::Evaluate( bool found_null = false; // Evaluate the array to search (param 0) - std::unique_ptr array_to_search_evaluable = + std::unique_ptr array_to_search_evaluable = expr_->params()[0]->ToEvaluable(); EvaluateResult array_to_search = array_to_search_evaluable->Evaluate(context, document); @@ -105,7 +105,7 @@ EvaluateResult CoreArrayContainsAll::Evaluate( } // Evaluate the elements to find (param 1) - std::unique_ptr elements_to_find_evaluable = + std::unique_ptr elements_to_find_evaluable = expr_->params()[1]->ToEvaluable(); EvaluateResult elements_to_find = elements_to_find_evaluable->Evaluate(context, document); @@ -208,7 +208,7 @@ EvaluateResult CoreArrayContainsAny::Evaluate( bool found_null = false; // Evaluate the array to search (param 0) - std::unique_ptr array_to_search_evaluable = + std::unique_ptr array_to_search_evaluable = expr_->params()[0]->ToEvaluable(); EvaluateResult array_to_search = array_to_search_evaluable->Evaluate(context, document); @@ -227,7 +227,7 @@ EvaluateResult CoreArrayContainsAny::Evaluate( } // Evaluate the elements to find (param 1) - std::unique_ptr elements_to_find_evaluable = + std::unique_ptr elements_to_find_evaluable = expr_->params()[1]->ToEvaluable(); EvaluateResult elements_to_find = elements_to_find_evaluable->Evaluate(context, document); @@ -305,7 +305,7 @@ EvaluateResult CoreArrayLength::Evaluate( HARD_ASSERT(expr_->params().size() == 1, "array_length() function requires exactly 1 param"); - std::unique_ptr operand_evaluable = + std::unique_ptr operand_evaluable = expr_->params()[0]->ToEvaluable(); EvaluateResult operand_result = operand_evaluable->Evaluate(context, document); diff --git a/Firestore/core/src/pipeline/array_evaluation.h b/Firestore/core/src/pipeline/array_evaluation.h index 5de10eb81f7..4b1700078ef 100644 --- a/Firestore/core/src/pipeline/array_evaluation.h +++ b/Firestore/core/src/pipeline/array_evaluation.h @@ -24,7 +24,7 @@ namespace firebase { namespace firestore { namespace core { -class CoreArrayReverse : public EvaluableExpr { +class CoreArrayReverse : public EvaluableExpression { public: explicit CoreArrayReverse(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -37,7 +37,7 @@ class CoreArrayReverse : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreArrayContains : public EvaluableExpr { +class CoreArrayContains : public EvaluableExpression { public: explicit CoreArrayContains(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -50,7 +50,7 @@ class CoreArrayContains : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreArrayContainsAll : public EvaluableExpr { +class CoreArrayContainsAll : public EvaluableExpression { public: explicit CoreArrayContainsAll(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -63,7 +63,7 @@ class CoreArrayContainsAll : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreArrayContainsAny : public EvaluableExpr { +class CoreArrayContainsAny : public EvaluableExpression { public: explicit CoreArrayContainsAny(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -76,7 +76,7 @@ class CoreArrayContainsAny : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreArrayLength : public EvaluableExpr { +class CoreArrayLength : public EvaluableExpression { public: explicit CoreArrayLength(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { diff --git a/Firestore/core/src/pipeline/comparison_evaluation.cc b/Firestore/core/src/pipeline/comparison_evaluation.cc index 481fa5983b6..7f3cef696ea 100644 --- a/Firestore/core/src/pipeline/comparison_evaluation.cc +++ b/Firestore/core/src/pipeline/comparison_evaluation.cc @@ -32,7 +32,7 @@ EvaluateResult ComparisonBase::Evaluate( HARD_ASSERT(expr_->params().size() == 2, "%s() function requires exactly 2 params", expr_->name()); - std::unique_ptr left_evaluable = + std::unique_ptr left_evaluable = expr_->params()[0]->ToEvaluable(); EvaluateResult left = left_evaluable->Evaluate(context, document); @@ -45,7 +45,7 @@ EvaluateResult ComparisonBase::Evaluate( break; } - std::unique_ptr right_evaluable = + std::unique_ptr right_evaluable = expr_->params()[1]->ToEvaluable(); EvaluateResult right = right_evaluable->Evaluate(context, document); switch (right.type()) { diff --git a/Firestore/core/src/pipeline/comparison_evaluation.h b/Firestore/core/src/pipeline/comparison_evaluation.h index 0770df87d1e..7e713a07e91 100644 --- a/Firestore/core/src/pipeline/comparison_evaluation.h +++ b/Firestore/core/src/pipeline/comparison_evaluation.h @@ -25,7 +25,7 @@ namespace firestore { namespace core { /** Base class for binary comparison expressions (==, !=, <, <=, >, >=). */ -class ComparisonBase : public EvaluableExpr { +class ComparisonBase : public EvaluableExpression { public: explicit ComparisonBase(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { diff --git a/Firestore/core/src/pipeline/expression_evaluation.cc b/Firestore/core/src/pipeline/expression_evaluation.cc index 8f738bfca98..ecd64088e35 100644 --- a/Firestore/core/src/pipeline/expression_evaluation.cc +++ b/Firestore/core/src/pipeline/expression_evaluation.cc @@ -161,20 +161,20 @@ EvaluateResult CoreConstant::Evaluate(const api::EvaluateContext&, return EvaluateResult::NewValue(nanopb::MakeMessage(constant->to_proto())); } -std::unique_ptr FunctionToEvaluable( +std::unique_ptr FunctionToEvaluable( const api::FunctionExpr& function) { if (function.name() == "equal") { return std::make_unique(function); } else if (function.name() == "add") { - return std::make_unique(function); + return std::make_unique(function); } else if (function.name() == "subtract") { - return std::make_unique(function); + return std::make_unique(function); } else if (function.name() == "multiply") { - return std::make_unique(function); + return std::make_unique(function); } else if (function.name() == "divide") { - return std::make_unique(function); + return std::make_unique(function); } else if (function.name() == "mod") { - return std::make_unique(function); + return std::make_unique(function); } else if (function.name() == "not_equal") { return std::make_unique(function); } else if (function.name() == "less_than") { diff --git a/Firestore/core/src/pipeline/expression_evaluation.h b/Firestore/core/src/pipeline/expression_evaluation.h index c8b341e27f3..dd0ce6b70dd 100644 --- a/Firestore/core/src/pipeline/expression_evaluation.h +++ b/Firestore/core/src/pipeline/expression_evaluation.h @@ -32,7 +32,7 @@ namespace firestore { namespace core { // Forward declaration -class EvaluableExpr; +class EvaluableExpression; /** Represents the result of evaluating an expression. */ class EvaluateResult { @@ -99,9 +99,9 @@ class EvaluateResult { }; /** An interface representing an expression that can be evaluated. */ -class EvaluableExpr { +class EvaluableExpression { public: - virtual ~EvaluableExpr() = default; + virtual ~EvaluableExpression() = default; /** * Evaluates the expression against the given document within the provided @@ -115,7 +115,7 @@ class EvaluableExpr { const model::PipelineInputOutput& document) const = 0; }; -class CoreField : public EvaluableExpr { +class CoreField : public EvaluableExpression { public: explicit CoreField(std::unique_ptr expr) : expr_(std::move(expr)) { } @@ -128,7 +128,7 @@ class CoreField : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreConstant : public EvaluableExpr { +class CoreConstant : public EvaluableExpression { public: explicit CoreConstant(std::unique_ptr expr) : expr_(std::move(expr)) { @@ -145,7 +145,7 @@ class CoreConstant : public EvaluableExpr { /** * Converts a high-level expression representation into an evaluable one. */ -std::unique_ptr FunctionToEvaluable( +std::unique_ptr FunctionToEvaluable( const api::FunctionExpr& function); } // namespace core diff --git a/Firestore/core/src/pipeline/logical_evaluation.cc b/Firestore/core/src/pipeline/logical_evaluation.cc index 50ae7483fd3..accc8274d9f 100644 --- a/Firestore/core/src/pipeline/logical_evaluation.cc +++ b/Firestore/core/src/pipeline/logical_evaluation.cc @@ -255,7 +255,7 @@ EvaluateResult CoreNot::Evaluate( HARD_ASSERT(expr_->params().size() == 1, "not() function requires exactly 1 param"); - std::unique_ptr operand_evaluable = + std::unique_ptr operand_evaluable = expr_->params()[0]->ToEvaluable(); EvaluateResult evaluated = operand_evaluable->Evaluate(context, document); diff --git a/Firestore/core/src/pipeline/logical_evaluation.h b/Firestore/core/src/pipeline/logical_evaluation.h index 8ae7e53bb9d..ed7e1ac7e6a 100644 --- a/Firestore/core/src/pipeline/logical_evaluation.h +++ b/Firestore/core/src/pipeline/logical_evaluation.h @@ -24,7 +24,7 @@ namespace firebase { namespace firestore { namespace core { -class CoreAnd : public EvaluableExpr { +class CoreAnd : public EvaluableExpression { public: explicit CoreAnd(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -37,7 +37,7 @@ class CoreAnd : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreOr : public EvaluableExpr { +class CoreOr : public EvaluableExpression { public: explicit CoreOr(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -50,7 +50,7 @@ class CoreOr : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreXor : public EvaluableExpr { +class CoreXor : public EvaluableExpression { public: explicit CoreXor(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -63,7 +63,7 @@ class CoreXor : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreCond : public EvaluableExpr { +class CoreCond : public EvaluableExpression { public: explicit CoreCond(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -76,7 +76,7 @@ class CoreCond : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreEqAny : public EvaluableExpr { +class CoreEqAny : public EvaluableExpression { public: explicit CoreEqAny(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -89,7 +89,7 @@ class CoreEqAny : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreNotEqAny : public EvaluableExpr { +class CoreNotEqAny : public EvaluableExpression { public: explicit CoreNotEqAny(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -102,7 +102,7 @@ class CoreNotEqAny : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreNot : public EvaluableExpr { +class CoreNot : public EvaluableExpression { public: explicit CoreNot(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { diff --git a/Firestore/core/src/pipeline/map_evaluation.cc b/Firestore/core/src/pipeline/map_evaluation.cc index b923dad860a..e357f39c703 100644 --- a/Firestore/core/src/pipeline/map_evaluation.cc +++ b/Firestore/core/src/pipeline/map_evaluation.cc @@ -34,7 +34,7 @@ EvaluateResult CoreMapGet::Evaluate( "map_get() function requires exactly 2 params (map and key)"); // Evaluate the map operand (param 0) - std::unique_ptr map_evaluable = + std::unique_ptr map_evaluable = expr_->params()[0]->ToEvaluable(); EvaluateResult map_result = map_evaluable->Evaluate(context, document); @@ -54,7 +54,7 @@ EvaluateResult CoreMapGet::Evaluate( } // Evaluate the key operand (param 1) - std::unique_ptr key_evaluable = + std::unique_ptr key_evaluable = expr_->params()[1]->ToEvaluable(); EvaluateResult key_result = key_evaluable->Evaluate(context, document); diff --git a/Firestore/core/src/pipeline/map_evaluation.h b/Firestore/core/src/pipeline/map_evaluation.h index 5b6adaa80bd..2a7c04726a4 100644 --- a/Firestore/core/src/pipeline/map_evaluation.h +++ b/Firestore/core/src/pipeline/map_evaluation.h @@ -24,7 +24,7 @@ namespace firebase { namespace firestore { namespace core { -class CoreMapGet : public EvaluableExpr { +class CoreMapGet : public EvaluableExpression { public: explicit CoreMapGet(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { diff --git a/Firestore/core/src/pipeline/string_evaluation.h b/Firestore/core/src/pipeline/string_evaluation.h index 5e0028ac10d..bf430397d68 100644 --- a/Firestore/core/src/pipeline/string_evaluation.h +++ b/Firestore/core/src/pipeline/string_evaluation.h @@ -27,7 +27,7 @@ namespace core { /** Base class for binary string search functions (starts_with, ends_with, * str_contains). */ -class StringSearchBase : public EvaluableExpr { +class StringSearchBase : public EvaluableExpression { public: explicit StringSearchBase(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -48,7 +48,7 @@ class StringSearchBase : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreByteLength : public EvaluableExpr { +class CoreByteLength : public EvaluableExpression { public: explicit CoreByteLength(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -61,7 +61,7 @@ class CoreByteLength : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreCharLength : public EvaluableExpr { +class CoreCharLength : public EvaluableExpression { public: explicit CoreCharLength(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -74,7 +74,7 @@ class CoreCharLength : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreStringConcat : public EvaluableExpr { +class CoreStringConcat : public EvaluableExpression { public: explicit CoreStringConcat(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -120,7 +120,7 @@ class CoreStringContains : public StringSearchBase { const std::string& search) const override; }; -class CoreToLower : public EvaluableExpr { +class CoreToLower : public EvaluableExpression { public: explicit CoreToLower(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -133,7 +133,7 @@ class CoreToLower : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreToUpper : public EvaluableExpr { +class CoreToUpper : public EvaluableExpression { public: explicit CoreToUpper(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -146,7 +146,7 @@ class CoreToUpper : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreTrim : public EvaluableExpr { +class CoreTrim : public EvaluableExpression { public: explicit CoreTrim(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -159,7 +159,7 @@ class CoreTrim : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreStringReverse : public EvaluableExpr { +class CoreStringReverse : public EvaluableExpression { public: explicit CoreStringReverse(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { diff --git a/Firestore/core/src/pipeline/timestamp_evaluation.h b/Firestore/core/src/pipeline/timestamp_evaluation.h index 3e2cb53b9f5..f9173de2777 100644 --- a/Firestore/core/src/pipeline/timestamp_evaluation.h +++ b/Firestore/core/src/pipeline/timestamp_evaluation.h @@ -25,7 +25,7 @@ namespace firestore { namespace core { /** Base class for converting Unix time (micros/millis/seconds) to Timestamp. */ -class UnixToTimestampBase : public EvaluableExpr { +class UnixToTimestampBase : public EvaluableExpression { public: explicit UnixToTimestampBase(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -68,7 +68,7 @@ class CoreUnixSecondsToTimestamp : public UnixToTimestampBase { }; /** Base class for converting Timestamp to Unix time (micros/millis/seconds). */ -class TimestampToUnixBase : public EvaluableExpr { +class TimestampToUnixBase : public EvaluableExpression { public: explicit TimestampToUnixBase(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -112,7 +112,7 @@ class CoreTimestampToUnixSeconds : public TimestampToUnixBase { }; /** Base class for timestamp arithmetic (add/sub). */ -class TimestampArithmeticBase : public EvaluableExpr { +class TimestampArithmeticBase : public EvaluableExpression { public: explicit TimestampArithmeticBase(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { diff --git a/Firestore/core/src/pipeline/type_evaluation.cc b/Firestore/core/src/pipeline/type_evaluation.cc index 7673f9273dd..7d05aca2de7 100644 --- a/Firestore/core/src/pipeline/type_evaluation.cc +++ b/Firestore/core/src/pipeline/type_evaluation.cc @@ -115,7 +115,7 @@ EvaluateResult CoreExists::Evaluate( HARD_ASSERT(expr_->params().size() == 1, "exists() function requires exactly 1 param"); - std::unique_ptr operand_evaluable = + std::unique_ptr operand_evaluable = expr_->params()[0]->ToEvaluable(); EvaluateResult evaluated = operand_evaluable->Evaluate(context, document); diff --git a/Firestore/core/src/pipeline/type_evaluation.h b/Firestore/core/src/pipeline/type_evaluation.h index 1a05f9f330f..94b5e725653 100644 --- a/Firestore/core/src/pipeline/type_evaluation.h +++ b/Firestore/core/src/pipeline/type_evaluation.h @@ -24,7 +24,7 @@ namespace firebase { namespace firestore { namespace core { -class CoreIsNan : public EvaluableExpr { +class CoreIsNan : public EvaluableExpression { public: explicit CoreIsNan(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -37,7 +37,7 @@ class CoreIsNan : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreIsNotNan : public EvaluableExpr { +class CoreIsNotNan : public EvaluableExpression { public: explicit CoreIsNotNan(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -50,7 +50,7 @@ class CoreIsNotNan : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreIsNull : public EvaluableExpr { +class CoreIsNull : public EvaluableExpression { public: explicit CoreIsNull(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -63,7 +63,7 @@ class CoreIsNull : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreIsNotNull : public EvaluableExpr { +class CoreIsNotNull : public EvaluableExpression { public: explicit CoreIsNotNull(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -76,7 +76,7 @@ class CoreIsNotNull : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreIsError : public EvaluableExpr { +class CoreIsError : public EvaluableExpression { public: explicit CoreIsError(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { @@ -89,7 +89,7 @@ class CoreIsError : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreExists : public EvaluableExpr { +class CoreExists : public EvaluableExpression { public: explicit CoreExists(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { diff --git a/Firestore/core/src/pipeline/util_evaluation.cc b/Firestore/core/src/pipeline/util_evaluation.cc index 1b5a9076c99..a637e0e201f 100644 --- a/Firestore/core/src/pipeline/util_evaluation.cc +++ b/Firestore/core/src/pipeline/util_evaluation.cc @@ -19,10 +19,21 @@ #include #include +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/util/hard_assert.h" + namespace firebase { namespace firestore { namespace core { +// Helper to create a Value proto from double +nanopb::Message DoubleValue(double val) { + google_firestore_v1_Value proto; + proto.which_value_type = google_firestore_v1_Value_double_value_tag; + proto.double_value = val; + return nanopb::MakeMessage(std::move(proto)); +} + nanopb::Message IntValue(int64_t val) { google_firestore_v1_Value proto; proto.which_value_type = google_firestore_v1_Value_integer_value_tag; @@ -100,6 +111,119 @@ absl::optional SafeMod(int64_t lhs, int64_t rhs) { return lhs % rhs; } +// --- Unary Arithmetic Implementation --- +EvaluateResult UnaryArithmetic::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "%s() function requires exactly 1 param", expr_->name()); + + EvaluateResult result = + expr_->params()[0]->ToEvaluable()->Evaluate(context, document); + + if (result.IsErrorOrUnset()) { + return EvaluateResult::NewError(); + } + if (result.IsNull()) { + return EvaluateResult::NewNull(); + } + + const google_firestore_v1_Value* val = result.value(); + if (!model::IsNumber(*val)) { + return EvaluateResult::NewError(); // Type error + } + + double double_val = model::IsDouble(*val) + ? val->double_value + : static_cast(val->integer_value); + + return PerformOperation(double_val); +} + +// --- Arithmetic Implementations --- +EvaluateResult BinaryArithmetic::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() >= 2, + "%s() function requires at least 2 params", expr_->name()); + + EvaluateResult current_result = + expr_->params()[0]->ToEvaluable()->Evaluate(context, document); + + for (size_t i = 1; i < expr_->params().size(); ++i) { + // Check current accumulated result before evaluating next operand + if (current_result.IsErrorOrUnset()) { + // Propagate error immediately if accumulated result is error/unset + // Note: Unset is treated as Error in arithmetic according to TS logic + return EvaluateResult::NewError(); + } + // Null check happens inside ApplyOperation + + EvaluateResult next_operand = + expr_->params()[i]->ToEvaluable()->Evaluate(context, document); + + // Apply the operation + current_result = ApplyOperation(current_result, next_operand); + + // If ApplyOperation resulted in error or unset, propagate immediately as + // error + if (current_result.IsErrorOrUnset()) { + // Treat Unset from ApplyOperation as Error for propagation + return EvaluateResult::NewError(); + } + // Null is handled within the loop by ApplyOperation in the next iteration + } + + return current_result; +} + +inline EvaluateResult BinaryArithmetic::ApplyOperation( + const EvaluateResult& left, const EvaluateResult& right) const { + // Mirroring TypeScript logic: + // 1. Check for Error/Unset first + if (left.IsErrorOrUnset() || right.IsErrorOrUnset()) { + return EvaluateResult::NewError(); + } + // 2. Check for Null + if (left.IsNull() || right.IsNull()) { + return EvaluateResult::NewNull(); + } + + // 3. Type check: Both must be numbers + const google_firestore_v1_Value* left_val = left.value(); + const google_firestore_v1_Value* right_val = right.value(); + if (!model::IsNumber(*left_val) || !model::IsNumber(*right_val)) { + return EvaluateResult::NewError(); // Type error + } + + // 4. Determine operation type (Integer or Double) + if (model::IsDouble(*left_val) || model::IsDouble(*right_val)) { + // Promote to double + double left_double_val = model::IsDouble(*left_val) + ? left_val->double_value + : static_cast(left_val->integer_value); + double right_double_val = + model::IsDouble(*right_val) + ? right_val->double_value + : static_cast(right_val->integer_value); + + // NaN propagation and specific error handling (like div/mod by zero) + // are handled within PerformDoubleOperation. + return PerformDoubleOperation(left_double_val, right_double_val); + + } else { + // Both are integers + absl::optional left_int_opt = model::GetInteger(*left_val); + absl::optional right_int_opt = model::GetInteger(*right_val); + // These should always succeed because we already checked IsNumber and + // excluded IsDouble. + HARD_ASSERT(left_int_opt.has_value() && right_int_opt.has_value(), + "Failed to extract integer values after IsNumber check"); + + return PerformIntegerOperation(left_int_opt.value(), right_int_opt.value()); + } +} + } // namespace core } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/pipeline/util_evaluation.h b/Firestore/core/src/pipeline/util_evaluation.h index f04a5077e44..a2c0b09de30 100644 --- a/Firestore/core/src/pipeline/util_evaluation.h +++ b/Firestore/core/src/pipeline/util_evaluation.h @@ -17,8 +17,11 @@ #ifndef FIRESTORE_CORE_SRC_PIPELINE_UTIL_EVALUATION_H_ #define FIRESTORE_CORE_SRC_PIPELINE_UTIL_EVALUATION_H_ +#include + #include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/pipeline/expression_evaluation.h" #include "absl/types/optional.h" namespace firebase { @@ -26,6 +29,7 @@ namespace firestore { namespace core { nanopb::Message IntValue(int64_t val); +nanopb::Message DoubleValue(double val); absl::optional SafeAdd(int64_t lhs, int64_t rhs); absl::optional SafeSubtract(int64_t lhs, int64_t rhs); @@ -33,6 +37,60 @@ absl::optional SafeMultiply(int64_t lhs, int64_t rhs); absl::optional SafeDivide(int64_t lhs, int64_t rhs); absl::optional SafeMod(int64_t lhs, int64_t rhs); +// --- Base Class for Unary Arithmetic Operations --- +class UnaryArithmetic : public EvaluableExpression { + public: + explicit UnaryArithmetic(const api::FunctionExpr& expr) + : expr_(std::make_unique(expr)) { + } + ~UnaryArithmetic() override = default; + + EvaluateResult Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const override; + + protected: + // Performs the specific double operation. + virtual EvaluateResult PerformOperation(double val) const = 0; + + std::unique_ptr expr_; +}; + +// --- Base Class for Arithmetic Operations --- +class BinaryArithmetic : public EvaluableExpression { + public: + explicit BinaryArithmetic(const api::FunctionExpr& expr) + : expr_(std::make_unique(expr)) { + } + ~BinaryArithmetic() override = default; + + // Implementation is inline below + EvaluateResult Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const override; + + protected: + // Performs the specific integer operation (e.g., add, subtract). + // Returns Error result on overflow or invalid operation (like div/mod by + // zero). + virtual EvaluateResult PerformIntegerOperation(int64_t lhs, + int64_t rhs) const = 0; + + // Performs the specific double operation. + // Returns Error result on invalid operation (like div/mod by zero). + virtual EvaluateResult PerformDoubleOperation(double lhs, + double rhs) const = 0; + + // Applies the arithmetic operation between two evaluated results. + // Mirrors the logic from TypeScript's applyArithmetics. + // Implementation is inline below + EvaluateResult ApplyOperation(const EvaluateResult& left, + const EvaluateResult& right) const; + + std::unique_ptr expr_; +}; +// --- End Base Class for Arithmetic Operations --- + } // namespace core } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/unit/testutil/expression_test_util.h b/Firestore/core/test/unit/testutil/expression_test_util.h index cef812ef1a1..c9e236ceba0 100644 --- a/Firestore/core/test/unit/testutil/expression_test_util.h +++ b/Firestore/core/test/unit/testutil/expression_test_util.h @@ -64,7 +64,7 @@ using api::Constant; using api::EvaluateContext; using api::Expr; using api::FunctionExpr; -using core::EvaluableExpr; +using core::EvaluableExpression; using core::EvaluateResult; using model::DatabaseId; using model::DocumentKey; @@ -559,7 +559,7 @@ inline EvaluateResult EvaluateExpr(const Expr& expr) { // Use a dummy input document (FoundDocument with empty data) model::PipelineInputOutput input = testutil::Doc("coll/doc", 1); - std::unique_ptr evaluable = expr.ToEvaluable(); + std::unique_ptr evaluable = expr.ToEvaluable(); HARD_ASSERT(evaluable != nullptr, "Failed to create evaluable expression"); return evaluable->Evaluate(NewContext(), input); } @@ -567,7 +567,7 @@ inline EvaluateResult EvaluateExpr(const Expr& expr) { // Helper function to evaluate an expression with a specific input. inline EvaluateResult EvaluateExpr(const Expr& expr, const model::PipelineInputOutput& input) { - std::unique_ptr evaluable = expr.ToEvaluable(); + std::unique_ptr evaluable = expr.ToEvaluable(); HARD_ASSERT(evaluable != nullptr, "Failed to create evaluable expression"); return evaluable->Evaluate(NewContext(), input); }