Skip to content

Commit ab40f41

Browse files
committed
[realppl 3] Arithmetic and comparison expressions
1 parent b5389b0 commit ab40f41

File tree

10 files changed

+3052
-1161
lines changed

10 files changed

+3052
-1161
lines changed

Firestore/Protos/cpp/firestore/local/maybe_document.pb.cc

-1,097
This file was deleted.

Firestore/core/src/core/expressions_eval.cc

+427-27
Large diffs are not rendered by default.

Firestore/core/src/core/expressions_eval.h

+140-2
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,147 @@ class CoreConstant : public EvaluableExpr {
135135
std::unique_ptr<api::Expr> expr_;
136136
};
137137

138-
class CoreEq : public EvaluableExpr {
138+
/** Base class for binary comparison expressions (==, !=, <, <=, >, >=). */
139+
class ComparisonBase : public EvaluableExpr {
139140
public:
140-
explicit CoreEq(const api::FunctionExpr& expr)
141+
explicit ComparisonBase(const api::FunctionExpr& expr)
142+
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
143+
}
144+
145+
EvaluateResult Evaluate(
146+
const api::EvaluateContext& context,
147+
const model::PipelineInputOutput& document) const override;
148+
149+
protected:
150+
/**
151+
* Performs the specific comparison logic after operands have been evaluated
152+
* and basic checks (Error, Unset, Null) have passed.
153+
*/
154+
virtual EvaluateResult CompareToResult(const EvaluateResult& left,
155+
const EvaluateResult& right) const = 0;
156+
157+
std::unique_ptr<api::FunctionExpr> expr_;
158+
};
159+
160+
class CoreEq : public ComparisonBase {
161+
public:
162+
explicit CoreEq(const api::FunctionExpr& expr) : ComparisonBase(expr) {
163+
}
164+
165+
protected:
166+
EvaluateResult CompareToResult(const EvaluateResult& left,
167+
const EvaluateResult& right) const override;
168+
};
169+
170+
class CoreNeq : public ComparisonBase {
171+
public:
172+
explicit CoreNeq(const api::FunctionExpr& expr) : ComparisonBase(expr) {
173+
}
174+
175+
protected:
176+
EvaluateResult CompareToResult(const EvaluateResult& left,
177+
const EvaluateResult& right) const override;
178+
};
179+
180+
class CoreLt : public ComparisonBase {
181+
public:
182+
explicit CoreLt(const api::FunctionExpr& expr) : ComparisonBase(expr) {
183+
}
184+
185+
protected:
186+
EvaluateResult CompareToResult(const EvaluateResult& left,
187+
const EvaluateResult& right) const override;
188+
};
189+
190+
class CoreLte : public ComparisonBase {
191+
public:
192+
explicit CoreLte(const api::FunctionExpr& expr) : ComparisonBase(expr) {
193+
}
194+
195+
protected:
196+
EvaluateResult CompareToResult(const EvaluateResult& left,
197+
const EvaluateResult& right) const override;
198+
};
199+
200+
class CoreGt : public ComparisonBase {
201+
public:
202+
explicit CoreGt(const api::FunctionExpr& expr) : ComparisonBase(expr) {
203+
}
204+
205+
protected:
206+
EvaluateResult CompareToResult(const EvaluateResult& left,
207+
const EvaluateResult& right) const override;
208+
};
209+
210+
class CoreGte : public ComparisonBase {
211+
public:
212+
explicit CoreGte(const api::FunctionExpr& expr) : ComparisonBase(expr) {
213+
}
214+
215+
protected:
216+
EvaluateResult CompareToResult(const EvaluateResult& left,
217+
const EvaluateResult& right) const override;
218+
};
219+
220+
class CoreAdd : public EvaluableExpr {
221+
public:
222+
explicit CoreAdd(const api::FunctionExpr& expr)
223+
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
224+
}
225+
226+
EvaluateResult Evaluate(
227+
const api::EvaluateContext& context,
228+
const model::PipelineInputOutput& document) const override;
229+
230+
private:
231+
std::unique_ptr<api::FunctionExpr> expr_;
232+
};
233+
234+
class CoreSubtract : public EvaluableExpr {
235+
public:
236+
explicit CoreSubtract(const api::FunctionExpr& expr)
237+
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
238+
}
239+
240+
EvaluateResult Evaluate(
241+
const api::EvaluateContext& context,
242+
const model::PipelineInputOutput& document) const override;
243+
244+
private:
245+
std::unique_ptr<api::FunctionExpr> expr_;
246+
};
247+
248+
class CoreMultiply : public EvaluableExpr {
249+
public:
250+
explicit CoreMultiply(const api::FunctionExpr& expr)
251+
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
252+
}
253+
254+
EvaluateResult Evaluate(
255+
const api::EvaluateContext& context,
256+
const model::PipelineInputOutput& document) const override;
257+
258+
private:
259+
std::unique_ptr<api::FunctionExpr> expr_;
260+
};
261+
262+
class CoreDivide : public EvaluableExpr {
263+
public:
264+
explicit CoreDivide(const api::FunctionExpr& expr)
265+
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
266+
}
267+
268+
EvaluateResult Evaluate(
269+
const api::EvaluateContext& context,
270+
const model::PipelineInputOutput& document) const override;
271+
272+
private:
273+
std::unique_ptr<api::FunctionExpr> expr_;
274+
};
275+
276+
class CoreMod : public EvaluableExpr {
277+
public:
278+
explicit CoreMod(const api::FunctionExpr& expr)
141279
: expr_(std::make_unique<api::FunctionExpr>(expr)) {
142280
}
143281

Firestore/core/src/model/value_util.cc

+138
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,144 @@ Message<google_firestore_v1_MapValue> DeepClone(
10161016
return target;
10171017
}
10181018

1019+
absl::optional<int64_t> GetInteger(const google_firestore_v1_Value& value) {
1020+
if (value.which_value_type == google_firestore_v1_Value_integer_value_tag) {
1021+
return value.integer_value;
1022+
}
1023+
return absl::nullopt;
1024+
}
1025+
1026+
namespace {
1027+
1028+
StrictEqualsResult StrictArrayEquals(
1029+
const google_firestore_v1_ArrayValue& left,
1030+
const google_firestore_v1_ArrayValue& right) {
1031+
if (left.values_count != right.values_count) {
1032+
return StrictEqualsResult::kNotEq;
1033+
}
1034+
1035+
bool found_null = false;
1036+
for (pb_size_t i = 0; i < left.values_count; ++i) {
1037+
StrictEqualsResult element_result =
1038+
StrictEquals(left.values[i], right.values[i]);
1039+
switch (element_result) {
1040+
case StrictEqualsResult::kNotEq:
1041+
return StrictEqualsResult::kNotEq;
1042+
case StrictEqualsResult::kNull:
1043+
found_null = true;
1044+
break;
1045+
case StrictEqualsResult::kEq:
1046+
// Continue checking other elements
1047+
break;
1048+
}
1049+
}
1050+
1051+
return found_null ? StrictEqualsResult::kNull : StrictEqualsResult::kEq;
1052+
}
1053+
1054+
StrictEqualsResult StrictMapEquals(const google_firestore_v1_MapValue& left,
1055+
const google_firestore_v1_MapValue& right) {
1056+
if (left.fields_count != right.fields_count) {
1057+
return StrictEqualsResult::kNotEq;
1058+
}
1059+
1060+
// Sort copies to compare map content regardless of original order.
1061+
auto left_map = DeepClone(left);
1062+
auto right_map = DeepClone(right);
1063+
SortFields(*left_map);
1064+
SortFields(*right_map);
1065+
1066+
bool found_null = false;
1067+
for (pb_size_t i = 0; i < left_map->fields_count; ++i) {
1068+
// Compare keys first
1069+
if (nanopb::MakeStringView(left_map->fields[i].key) !=
1070+
nanopb::MakeStringView(right_map->fields[i].key)) {
1071+
return StrictEqualsResult::kNotEq;
1072+
}
1073+
1074+
// Compare values recursively
1075+
StrictEqualsResult value_result =
1076+
StrictEquals(left_map->fields[i].value, right_map->fields[i].value);
1077+
switch (value_result) {
1078+
case StrictEqualsResult::kNotEq:
1079+
return StrictEqualsResult::kNotEq;
1080+
case StrictEqualsResult::kNull:
1081+
found_null = true;
1082+
break;
1083+
case StrictEqualsResult::kEq:
1084+
// Continue checking other fields
1085+
break;
1086+
}
1087+
}
1088+
1089+
return found_null ? StrictEqualsResult::kNull : StrictEqualsResult::kEq;
1090+
}
1091+
1092+
StrictEqualsResult StrictNumberEquals(const google_firestore_v1_Value& left,
1093+
const google_firestore_v1_Value& right) {
1094+
if (left.which_value_type == google_firestore_v1_Value_integer_value_tag &&
1095+
right.which_value_type == google_firestore_v1_Value_integer_value_tag) {
1096+
// Case 1: Both are longs
1097+
return left.integer_value == right.integer_value
1098+
? StrictEqualsResult::kEq
1099+
: StrictEqualsResult::kNotEq;
1100+
} else if (left.which_value_type ==
1101+
google_firestore_v1_Value_double_value_tag &&
1102+
right.which_value_type ==
1103+
google_firestore_v1_Value_double_value_tag) {
1104+
// Case 2: Both are doubles
1105+
// Standard double comparison handles 0.0 == -0.0 and NaN != NaN.
1106+
return left.double_value == right.double_value ? StrictEqualsResult::kEq
1107+
: StrictEqualsResult::kNotEq;
1108+
} else {
1109+
// Case 3: Mixed integer and double
1110+
// Promote integer to double for comparison.
1111+
double left_double =
1112+
(left.which_value_type == google_firestore_v1_Value_integer_value_tag)
1113+
? static_cast<double>(left.integer_value)
1114+
: left.double_value;
1115+
double right_double =
1116+
(right.which_value_type == google_firestore_v1_Value_integer_value_tag)
1117+
? static_cast<double>(right.integer_value)
1118+
: right.double_value;
1119+
return left_double == right_double ? StrictEqualsResult::kEq
1120+
: StrictEqualsResult::kNotEq;
1121+
}
1122+
}
1123+
1124+
} // namespace
1125+
1126+
StrictEqualsResult StrictEquals(const google_firestore_v1_Value& left,
1127+
const google_firestore_v1_Value& right) {
1128+
if (IsNullValue(left) || IsNullValue(right)) {
1129+
return StrictEqualsResult::kNull;
1130+
}
1131+
1132+
TypeOrder left_type = GetTypeOrder(left);
1133+
TypeOrder right_type = GetTypeOrder(right);
1134+
if (left_type != right_type) {
1135+
return StrictEqualsResult::kNotEq;
1136+
}
1137+
1138+
switch (left_type) {
1139+
case TypeOrder::kNumber:
1140+
return StrictNumberEquals(left, right);
1141+
case TypeOrder::kArray:
1142+
return StrictArrayEquals(left.array_value, right.array_value);
1143+
case TypeOrder::kVector:
1144+
case TypeOrder::kMap:
1145+
// Note: MaxValue is also a map, but should be handled by TypeOrder check
1146+
// if compared against a non-MaxValue. MaxValue == MaxValue is handled
1147+
// by the Equals call below. Vector equality is map equality.
1148+
return StrictMapEquals(left.map_value, right.map_value);
1149+
default:
1150+
// For all other types (Null, Boolean, Number, Timestamp, String, Blob,
1151+
// Ref, GeoPoint, MaxValue), the standard Equals function works.
1152+
return Equals(left, right) ? StrictEqualsResult::kEq
1153+
: StrictEqualsResult::kNotEq;
1154+
}
1155+
}
1156+
10191157
} // namespace model
10201158
} // namespace firestore
10211159
} // namespace firebase

Firestore/core/src/model/value_util.h

+18
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ enum class TypeOrder {
7777
kMaxValue = 12
7878
};
7979

80+
/** Result type for StrictEquals comparison. */
81+
enum class StrictEqualsResult { kEq, kNotEq, kNull };
82+
8083
/** Returns the backend's type order of the given Value type. */
8184
TypeOrder GetTypeOrder(const google_firestore_v1_Value& value);
8285

@@ -103,6 +106,15 @@ bool Equals(const google_firestore_v1_Value& left,
103106
bool Equals(const google_firestore_v1_ArrayValue& left,
104107
const google_firestore_v1_ArrayValue& right);
105108

109+
/**
110+
* Performs a strict equality comparison used in Pipeline expressions
111+
* evaluations. The main difference to Equals is its handling of null
112+
* propagation, and it uses direct double value comparison (as opposed to Equals
113+
* which use bits comparison).
114+
*/
115+
StrictEqualsResult StrictEquals(const google_firestore_v1_Value& left,
116+
const google_firestore_v1_Value& right);
117+
106118
/**
107119
* Generates the canonical ID for the provided field value (as used in Target
108120
* serialization).
@@ -277,6 +289,12 @@ inline bool IsMap(const absl::optional<google_firestore_v1_Value>& value) {
277289
value->which_value_type == google_firestore_v1_Value_map_value_tag;
278290
}
279291

292+
/**
293+
* Extracts the integer value if the input is an integer type.
294+
* Returns nullopt otherwise.
295+
*/
296+
absl::optional<int64_t> GetInteger(const google_firestore_v1_Value& value);
297+
280298
} // namespace model
281299

282300
inline bool operator==(const google_firestore_v1_Value& lhs,

0 commit comments

Comments
 (0)