Skip to content

[realppl 3] Arithmetic and comparison expressions #14849

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: wuandy/RealPpl_2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 50 additions & 20 deletions Firestore/Example/Firestore.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

454 changes: 427 additions & 27 deletions Firestore/core/src/core/expressions_eval.cc

Large diffs are not rendered by default.

142 changes: 140 additions & 2 deletions Firestore/core/src/core/expressions_eval.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,147 @@ class CoreConstant : public EvaluableExpr {
std::unique_ptr<api::Expr> expr_;
};

class CoreEq : public EvaluableExpr {
/** Base class for binary comparison expressions (==, !=, <, <=, >, >=). */
class ComparisonBase : public EvaluableExpr {
public:
explicit CoreEq(const api::FunctionExpr& expr)
explicit ComparisonBase(const api::FunctionExpr& expr)
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
}

EvaluateResult Evaluate(
const api::EvaluateContext& context,
const model::PipelineInputOutput& document) const override;

protected:
/**
* Performs the specific comparison logic after operands have been evaluated
* and basic checks (Error, Unset, Null) have passed.
*/
virtual EvaluateResult CompareToResult(const EvaluateResult& left,
const EvaluateResult& right) const = 0;

std::unique_ptr<api::FunctionExpr> expr_;
};

class CoreEq : public ComparisonBase {
public:
explicit CoreEq(const api::FunctionExpr& expr) : ComparisonBase(expr) {
}

protected:
EvaluateResult CompareToResult(const EvaluateResult& left,
const EvaluateResult& right) const override;
};

class CoreNeq : public ComparisonBase {
public:
explicit CoreNeq(const api::FunctionExpr& expr) : ComparisonBase(expr) {
}

protected:
EvaluateResult CompareToResult(const EvaluateResult& left,
const EvaluateResult& right) const override;
};

class CoreLt : public ComparisonBase {
public:
explicit CoreLt(const api::FunctionExpr& expr) : ComparisonBase(expr) {
}

protected:
EvaluateResult CompareToResult(const EvaluateResult& left,
const EvaluateResult& right) const override;
};

class CoreLte : public ComparisonBase {
public:
explicit CoreLte(const api::FunctionExpr& expr) : ComparisonBase(expr) {
}

protected:
EvaluateResult CompareToResult(const EvaluateResult& left,
const EvaluateResult& right) const override;
};

class CoreGt : public ComparisonBase {
public:
explicit CoreGt(const api::FunctionExpr& expr) : ComparisonBase(expr) {
}

protected:
EvaluateResult CompareToResult(const EvaluateResult& left,
const EvaluateResult& right) const override;
};

class CoreGte : public ComparisonBase {
public:
explicit CoreGte(const api::FunctionExpr& expr) : ComparisonBase(expr) {
}

protected:
EvaluateResult CompareToResult(const EvaluateResult& left,
const EvaluateResult& right) const override;
};

class CoreAdd : public EvaluableExpr {
public:
explicit CoreAdd(const api::FunctionExpr& expr)
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
}

EvaluateResult Evaluate(
const api::EvaluateContext& context,
const model::PipelineInputOutput& document) const override;

private:
std::unique_ptr<api::FunctionExpr> expr_;
};

class CoreSubtract : public EvaluableExpr {
public:
explicit CoreSubtract(const api::FunctionExpr& expr)
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
}

EvaluateResult Evaluate(
const api::EvaluateContext& context,
const model::PipelineInputOutput& document) const override;

private:
std::unique_ptr<api::FunctionExpr> expr_;
};

class CoreMultiply : public EvaluableExpr {
public:
explicit CoreMultiply(const api::FunctionExpr& expr)
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
}

EvaluateResult Evaluate(
const api::EvaluateContext& context,
const model::PipelineInputOutput& document) const override;

private:
std::unique_ptr<api::FunctionExpr> expr_;
};

class CoreDivide : public EvaluableExpr {
public:
explicit CoreDivide(const api::FunctionExpr& expr)
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
}

EvaluateResult Evaluate(
const api::EvaluateContext& context,
const model::PipelineInputOutput& document) const override;

private:
std::unique_ptr<api::FunctionExpr> expr_;
};

class CoreMod : public EvaluableExpr {
public:
explicit CoreMod(const api::FunctionExpr& expr)
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
}

Expand Down
138 changes: 138 additions & 0 deletions Firestore/core/src/model/value_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,144 @@ Message<google_firestore_v1_MapValue> DeepClone(
return target;
}

absl::optional<int64_t> GetInteger(const google_firestore_v1_Value& value) {
if (value.which_value_type == google_firestore_v1_Value_integer_value_tag) {
return value.integer_value;
}
return absl::nullopt;
}

namespace {

StrictEqualsResult StrictArrayEquals(
const google_firestore_v1_ArrayValue& left,
const google_firestore_v1_ArrayValue& right) {
if (left.values_count != right.values_count) {
return StrictEqualsResult::kNotEq;
}

bool found_null = false;
for (pb_size_t i = 0; i < left.values_count; ++i) {
StrictEqualsResult element_result =
StrictEquals(left.values[i], right.values[i]);
switch (element_result) {
case StrictEqualsResult::kNotEq:
return StrictEqualsResult::kNotEq;
case StrictEqualsResult::kNull:
found_null = true;
break;
case StrictEqualsResult::kEq:
// Continue checking other elements
break;
}
}

return found_null ? StrictEqualsResult::kNull : StrictEqualsResult::kEq;
}

StrictEqualsResult StrictMapEquals(const google_firestore_v1_MapValue& left,
const google_firestore_v1_MapValue& right) {
if (left.fields_count != right.fields_count) {
return StrictEqualsResult::kNotEq;
}

// Sort copies to compare map content regardless of original order.
auto left_map = DeepClone(left);
auto right_map = DeepClone(right);
SortFields(*left_map);
SortFields(*right_map);

bool found_null = false;
for (pb_size_t i = 0; i < left_map->fields_count; ++i) {
// Compare keys first
if (nanopb::MakeStringView(left_map->fields[i].key) !=
nanopb::MakeStringView(right_map->fields[i].key)) {
return StrictEqualsResult::kNotEq;
}

// Compare values recursively
StrictEqualsResult value_result =
StrictEquals(left_map->fields[i].value, right_map->fields[i].value);
switch (value_result) {
case StrictEqualsResult::kNotEq:
return StrictEqualsResult::kNotEq;
case StrictEqualsResult::kNull:
found_null = true;
break;
case StrictEqualsResult::kEq:
// Continue checking other fields
break;
}
}

return found_null ? StrictEqualsResult::kNull : StrictEqualsResult::kEq;
}

StrictEqualsResult StrictNumberEquals(const google_firestore_v1_Value& left,
const google_firestore_v1_Value& right) {
if (left.which_value_type == google_firestore_v1_Value_integer_value_tag &&
right.which_value_type == google_firestore_v1_Value_integer_value_tag) {
// Case 1: Both are longs
return left.integer_value == right.integer_value
? StrictEqualsResult::kEq
: StrictEqualsResult::kNotEq;
} else if (left.which_value_type ==
google_firestore_v1_Value_double_value_tag &&
right.which_value_type ==
google_firestore_v1_Value_double_value_tag) {
// Case 2: Both are doubles
// Standard double comparison handles 0.0 == -0.0 and NaN != NaN.
return left.double_value == right.double_value ? StrictEqualsResult::kEq
: StrictEqualsResult::kNotEq;
} else {
// Case 3: Mixed integer and double
// Promote integer to double for comparison.
double left_double =
(left.which_value_type == google_firestore_v1_Value_integer_value_tag)
? static_cast<double>(left.integer_value)
: left.double_value;
double right_double =
(right.which_value_type == google_firestore_v1_Value_integer_value_tag)
? static_cast<double>(right.integer_value)
: right.double_value;
return left_double == right_double ? StrictEqualsResult::kEq
: StrictEqualsResult::kNotEq;
}
}

} // namespace

StrictEqualsResult StrictEquals(const google_firestore_v1_Value& left,
const google_firestore_v1_Value& right) {
if (IsNullValue(left) || IsNullValue(right)) {
return StrictEqualsResult::kNull;
}

TypeOrder left_type = GetTypeOrder(left);
TypeOrder right_type = GetTypeOrder(right);
if (left_type != right_type) {
return StrictEqualsResult::kNotEq;
}

switch (left_type) {
case TypeOrder::kNumber:
return StrictNumberEquals(left, right);
case TypeOrder::kArray:
return StrictArrayEquals(left.array_value, right.array_value);
case TypeOrder::kVector:
case TypeOrder::kMap:
// Note: MaxValue is also a map, but should be handled by TypeOrder check
// if compared against a non-MaxValue. MaxValue == MaxValue is handled
// by the Equals call below. Vector equality is map equality.
return StrictMapEquals(left.map_value, right.map_value);
default:
// For all other types (Null, Boolean, Number, Timestamp, String, Blob,
// Ref, GeoPoint, MaxValue), the standard Equals function works.
return Equals(left, right) ? StrictEqualsResult::kEq
: StrictEqualsResult::kNotEq;
}
}

} // namespace model
} // namespace firestore
} // namespace firebase
18 changes: 18 additions & 0 deletions Firestore/core/src/model/value_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ enum class TypeOrder {
kMaxValue = 12
};

/** Result type for StrictEquals comparison. */
enum class StrictEqualsResult { kEq, kNotEq, kNull };

/** Returns the backend's type order of the given Value type. */
TypeOrder GetTypeOrder(const google_firestore_v1_Value& value);

Expand All @@ -103,6 +106,15 @@ bool Equals(const google_firestore_v1_Value& left,
bool Equals(const google_firestore_v1_ArrayValue& left,
const google_firestore_v1_ArrayValue& right);

/**
* Performs a strict equality comparison used in Pipeline expressions
* evaluations. The main difference to Equals is its handling of null
* propagation, and it uses direct double value comparison (as opposed to Equals
* which use bits comparison).
*/
StrictEqualsResult StrictEquals(const google_firestore_v1_Value& left,
const google_firestore_v1_Value& right);

/**
* Generates the canonical ID for the provided field value (as used in Target
* serialization).
Expand Down Expand Up @@ -277,6 +289,12 @@ inline bool IsMap(const absl::optional<google_firestore_v1_Value>& value) {
value->which_value_type == google_firestore_v1_Value_map_value_tag;
}

/**
* Extracts the integer value if the input is an integer type.
* Returns nullopt otherwise.
*/
absl::optional<int64_t> GetInteger(const google_firestore_v1_Value& value);

} // namespace model

inline bool operator==(const google_firestore_v1_Value& lhs,
Expand Down
Loading
Loading