Skip to content

Commit 2c76938

Browse files
authored
Bug fix for flaky behaviour when using Map in arrayRemove (#12378)
1 parent 7b9125a commit 2c76938

File tree

4 files changed

+76
-63
lines changed

4 files changed

+76
-63
lines changed

Firestore/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Unreleased
2+
- [fixed] Fix the flaky offline behaviour when using `arrayRemove` on `Map` object. (#12378)
3+
14
# 10.21.0
25
- Add an error when trying to build Firestore's binary SPM distribution for
36
visionOS (#12279). See Firestore's 10.12.0 release note for a supported

Firestore/core/src/model/value_util.cc

Lines changed: 51 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,22 @@ void SortFields(google_firestore_v1_ArrayValue& value) {
109109
}
110110
}
111111

112+
void SortFields(google_firestore_v1_MapValue& value) {
113+
std::sort(value.fields, value.fields + value.fields_count,
114+
[](const google_firestore_v1_MapValue_FieldsEntry& lhs,
115+
const google_firestore_v1_MapValue_FieldsEntry& rhs) {
116+
return nanopb::MakeStringView(lhs.key) <
117+
nanopb::MakeStringView(rhs.key);
118+
});
119+
120+
for (pb_size_t i = 0; i < value.fields_count; ++i) {
121+
SortFields(value.fields[i].value);
122+
}
123+
}
124+
112125
void SortFields(google_firestore_v1_Value& value) {
113126
if (IsMap(value)) {
114-
google_firestore_v1_MapValue& map_value = value.map_value;
115-
std::sort(map_value.fields, map_value.fields + map_value.fields_count,
116-
[](const google_firestore_v1_MapValue_FieldsEntry& lhs,
117-
const google_firestore_v1_MapValue_FieldsEntry& rhs) {
118-
return nanopb::MakeStringView(lhs.key) <
119-
nanopb::MakeStringView(rhs.key);
120-
});
121-
122-
for (pb_size_t i = 0; i < map_value.fields_count; ++i) {
123-
SortFields(map_value.fields[i].value);
124-
}
127+
SortFields(value.map_value);
125128
} else if (IsArray(value)) {
126129
SortFields(value.array_value);
127130
}
@@ -223,30 +226,31 @@ ComparisonResult CompareArrays(const google_firestore_v1_Value& left,
223226
right.array_value.values_count);
224227
}
225228

226-
ComparisonResult CompareObjects(const google_firestore_v1_Value& left,
227-
const google_firestore_v1_Value& right) {
228-
google_firestore_v1_MapValue left_map = left.map_value;
229-
google_firestore_v1_MapValue right_map = right.map_value;
229+
ComparisonResult CompareMaps(const google_firestore_v1_MapValue& left,
230+
const google_firestore_v1_MapValue& right) {
231+
// Sort the given MapValues
232+
auto left_map = DeepClone(left);
233+
auto right_map = DeepClone(right);
234+
SortFields(*left_map);
235+
SortFields(*right_map);
230236

231-
// Porting Note: MapValues in iOS are always kept in sorted order. We
232-
// therefore do no need to sort them before comparing.
233-
for (pb_size_t i = 0; i < left_map.fields_count && i < right_map.fields_count;
234-
++i) {
235-
ComparisonResult key_cmp =
236-
util::Compare(nanopb::MakeStringView(left_map.fields[i].key),
237-
nanopb::MakeStringView(right_map.fields[i].key));
237+
for (pb_size_t i = 0;
238+
i < left_map->fields_count && i < right_map->fields_count; ++i) {
239+
const ComparisonResult key_cmp =
240+
util::Compare(nanopb::MakeStringView(left_map->fields[i].key),
241+
nanopb::MakeStringView(right_map->fields[i].key));
238242
if (key_cmp != ComparisonResult::Same) {
239243
return key_cmp;
240244
}
241245

242-
ComparisonResult value_cmp =
243-
Compare(left_map.fields[i].value, right.map_value.fields[i].value);
246+
const ComparisonResult value_cmp =
247+
Compare(left_map->fields[i].value, right_map->fields[i].value);
244248
if (value_cmp != ComparisonResult::Same) {
245249
return value_cmp;
246250
}
247251
}
248252

249-
return util::Compare(left_map.fields_count, right_map.fields_count);
253+
return util::Compare(left_map->fields_count, right_map->fields_count);
250254
}
251255

252256
ComparisonResult Compare(const google_firestore_v1_Value& left,
@@ -291,7 +295,7 @@ ComparisonResult Compare(const google_firestore_v1_Value& left,
291295
return CompareArrays(left, right);
292296

293297
case TypeOrder::kMap:
294-
return CompareObjects(left, right);
298+
return CompareMaps(left.map_value, right.map_value);
295299

296300
case TypeOrder::kMaxValue:
297301
return util::ComparisonResult::Same;
@@ -366,26 +370,12 @@ bool ArrayEquals(const google_firestore_v1_ArrayValue& left,
366370
return true;
367371
}
368372

369-
bool ObjectEquals(const google_firestore_v1_MapValue& left,
370-
const google_firestore_v1_MapValue& right) {
373+
bool MapValueEquals(const google_firestore_v1_MapValue& left,
374+
const google_firestore_v1_MapValue& right) {
371375
if (left.fields_count != right.fields_count) {
372376
return false;
373377
}
374-
375-
// Porting Note: MapValues in iOS are always kept in sorted order. We
376-
// therefore do no need to sort them before comparing.
377-
for (size_t i = 0; i < right.fields_count; ++i) {
378-
if (nanopb::MakeStringView(left.fields[i].key) !=
379-
nanopb::MakeStringView(right.fields[i].key)) {
380-
return false;
381-
}
382-
383-
if (left.fields[i].value != right.fields[i].value) {
384-
return false;
385-
}
386-
}
387-
388-
return true;
378+
return CompareMaps(left, right) == ComparisonResult::Same;
389379
}
390380

391381
bool Equals(const google_firestore_v1_Value& lhs,
@@ -436,10 +426,10 @@ bool Equals(const google_firestore_v1_Value& lhs,
436426
return ArrayEquals(lhs.array_value, rhs.array_value);
437427

438428
case TypeOrder::kMap:
439-
return ObjectEquals(lhs.map_value, rhs.map_value);
429+
return MapValueEquals(lhs.map_value, rhs.map_value);
440430

441431
case TypeOrder::kMaxValue:
442-
return ObjectEquals(lhs.map_value, rhs.map_value);
432+
return MapValueEquals(lhs.map_value, rhs.map_value);
443433

444434
default:
445435
HARD_FAIL("Invalid type value: %s", left_type);
@@ -794,27 +784,11 @@ Message<google_firestore_v1_Value> DeepClone(
794784
break;
795785

796786
case google_firestore_v1_Value_array_value_tag:
797-
target->array_value.values_count = source.array_value.values_count;
798-
target->array_value.values = nanopb::MakeArray<google_firestore_v1_Value>(
799-
source.array_value.values_count);
800-
for (pb_size_t i = 0; i < source.array_value.values_count; ++i) {
801-
target->array_value.values[i] =
802-
*DeepClone(source.array_value.values[i]).release();
803-
}
787+
target->array_value = *DeepClone(source.array_value).release();
804788
break;
805789

806790
case google_firestore_v1_Value_map_value_tag:
807-
target->map_value.fields_count = source.map_value.fields_count;
808-
target->map_value.fields =
809-
nanopb::MakeArray<google_firestore_v1_MapValue_FieldsEntry>(
810-
source.map_value.fields_count);
811-
for (pb_size_t i = 0; i < source.map_value.fields_count; ++i) {
812-
target->map_value.fields[i].key =
813-
nanopb::MakeBytesArray(source.map_value.fields[i].key->bytes,
814-
source.map_value.fields[i].key->size);
815-
target->map_value.fields[i].value =
816-
*DeepClone(source.map_value.fields[i].value).release();
817-
}
791+
target->map_value = *DeepClone(source.map_value).release();
818792
break;
819793
}
820794
return target;
@@ -832,6 +806,20 @@ Message<google_firestore_v1_ArrayValue> DeepClone(
832806
return target;
833807
}
834808

809+
Message<google_firestore_v1_MapValue> DeepClone(
810+
const google_firestore_v1_MapValue& source) {
811+
Message<google_firestore_v1_MapValue> target{source};
812+
target->fields_count = source.fields_count;
813+
target->fields = nanopb::MakeArray<google_firestore_v1_MapValue_FieldsEntry>(
814+
source.fields_count);
815+
for (pb_size_t i = 0; i < source.fields_count; ++i) {
816+
target->fields[i].key = nanopb::MakeBytesArray(source.fields[i].key->bytes,
817+
source.fields[i].key->size);
818+
target->fields[i].value = *DeepClone(source.fields[i].value).release();
819+
}
820+
return target;
821+
}
822+
835823
} // namespace model
836824
} // namespace firestore
837825
} // namespace firebase

Firestore/core/src/model/value_util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ nanopb::Message<google_firestore_v1_Value> DeepClone(
183183
nanopb::Message<google_firestore_v1_ArrayValue> DeepClone(
184184
const google_firestore_v1_ArrayValue& source);
185185

186+
/** Creates a copy of the contents of the MapValue proto. */
187+
nanopb::Message<google_firestore_v1_MapValue> DeepClone(
188+
const google_firestore_v1_MapValue& source);
189+
186190
/** Returns true if `value` is a INTEGER_VALUE. */
187191
inline bool IsInteger(const absl::optional<google_firestore_v1_Value>& value) {
188192
return value &&

Firestore/core/test/unit/model/value_util_test.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,24 @@ TEST_F(ValueUtilTest, DeepClone) {
545545
VerifyDeepClone(Map("a", Array("b", Map("c", GeoPoint(30, 60)))));
546546
}
547547

548+
TEST_F(ValueUtilTest, CompareMaps) {
549+
auto left_1 = Map("a", 7, "b", 0);
550+
auto right_1 = Map("a", 7, "b", 0);
551+
EXPECT_EQ(model::Compare(*left_1, *right_1), ComparisonResult::Same);
552+
553+
auto left_2 = Map("a", 3, "b", 5);
554+
auto right_2 = Map("b", 5, "a", 3);
555+
EXPECT_EQ(model::Compare(*left_2, *right_2), ComparisonResult::Same);
556+
557+
auto left_3 = Map("a", 8, "b", 10, "c", 5);
558+
auto right_3 = Map("a", 8, "b", 10);
559+
EXPECT_EQ(model::Compare(*left_3, *right_3), ComparisonResult::Descending);
560+
561+
auto left_4 = Map("a", 7, "b", 0);
562+
auto right_4 = Map("a", 7, "b", 10);
563+
EXPECT_EQ(model::Compare(*left_4, *right_4), ComparisonResult::Ascending);
564+
}
565+
548566
} // namespace
549567

550568
} // namespace model

0 commit comments

Comments
 (0)