Skip to content
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

Bug fix for flaky behaviour when using Map in arrayRemove #12378

Merged
merged 4 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions Firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [fixed] Fix the flaky offline behaviour when using `arrayRemove` on `Map` object. (#12378)

# 10.21.0
- Add an error when trying to build Firestore's binary SPM distribution for
visionOS (#12279). See Firestore's 10.12.0 release note for a supported
Expand Down
78 changes: 53 additions & 25 deletions Firestore/core/src/model/value_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,22 @@ void SortFields(google_firestore_v1_ArrayValue& value) {
}
}

void SortFields(google_firestore_v1_MapValue& value) {
std::sort(value.fields, value.fields + value.fields_count,
[](const google_firestore_v1_MapValue_FieldsEntry& lhs,
const google_firestore_v1_MapValue_FieldsEntry& rhs) {
return nanopb::MakeStringView(lhs.key) <
nanopb::MakeStringView(rhs.key);
});

for (pb_size_t i = 0; i < value.fields_count; ++i) {
SortFields(value.fields[i].value);
}
}

void SortFields(google_firestore_v1_Value& value) {
if (IsMap(value)) {
google_firestore_v1_MapValue& map_value = value.map_value;
std::sort(map_value.fields, map_value.fields + map_value.fields_count,
[](const google_firestore_v1_MapValue_FieldsEntry& lhs,
const google_firestore_v1_MapValue_FieldsEntry& rhs) {
return nanopb::MakeStringView(lhs.key) <
nanopb::MakeStringView(rhs.key);
});

for (pb_size_t i = 0; i < map_value.fields_count; ++i) {
SortFields(map_value.fields[i].value);
}
SortFields(value.map_value);
} else if (IsArray(value)) {
SortFields(value.array_value);
}
Expand Down Expand Up @@ -223,13 +226,21 @@ ComparisonResult CompareArrays(const google_firestore_v1_Value& left,
right.array_value.values_count);
}

ComparisonResult CompareObjects(const google_firestore_v1_Value& left,
const google_firestore_v1_Value& right) {
google_firestore_v1_MapValue left_map = left.map_value;
google_firestore_v1_MapValue right_map = right.map_value;
ComparisonResult CompareMaps(const google_firestore_v1_Value& left,
const google_firestore_v1_Value& right) {
HARD_ASSERT(left.which_value_type == google_firestore_v1_Value_map_value_tag,
"In function CompareMaps(), left side value is not a map");
HARD_ASSERT(right.which_value_type == google_firestore_v1_Value_map_value_tag,
"In function CompareMaps(), right side value is not a map");

// Sort the given MapValues
auto left_copy = DeepClone(left);
auto right_copy = DeepClone(right);
SortFields(*left_copy);
SortFields(*right_copy);
google_firestore_v1_MapValue left_map = left_copy->map_value;
google_firestore_v1_MapValue right_map = right_copy->map_value;

// Porting Note: MapValues in iOS are always kept in sorted order. We
// therefore do no need to sort them before comparing.
for (pb_size_t i = 0; i < left_map.fields_count && i < right_map.fields_count;
++i) {
ComparisonResult key_cmp =
Expand All @@ -240,7 +251,7 @@ ComparisonResult CompareObjects(const google_firestore_v1_Value& left,
}

ComparisonResult value_cmp =
Compare(left_map.fields[i].value, right.map_value.fields[i].value);
Compare(left_map.fields[i].value, right_map.fields[i].value);
if (value_cmp != ComparisonResult::Same) {
return value_cmp;
}
Expand Down Expand Up @@ -291,7 +302,7 @@ ComparisonResult Compare(const google_firestore_v1_Value& left,
return CompareArrays(left, right);

case TypeOrder::kMap:
return CompareObjects(left, right);
return CompareMaps(left, right);

case TypeOrder::kMaxValue:
return util::ComparisonResult::Same;
Expand Down Expand Up @@ -371,16 +382,19 @@ bool ObjectEquals(const google_firestore_v1_MapValue& left,
if (left.fields_count != right.fields_count) {
ehsannas marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
// Sort the given MapValues
auto left_copy = DeepClone(left);
auto right_copy = DeepClone(right);
SortFields(*left_copy);
SortFields(*right_copy);

// Porting Note: MapValues in iOS are always kept in sorted order. We
// therefore do no need to sort them before comparing.
for (size_t i = 0; i < right.fields_count; ++i) {
if (nanopb::MakeStringView(left.fields[i].key) !=
nanopb::MakeStringView(right.fields[i].key)) {
for (size_t i = 0; i < right_copy->fields_count; ++i) {
if (nanopb::MakeStringView(left_copy->fields[i].key) !=
nanopb::MakeStringView(right_copy->fields[i].key)) {
return false;
}

if (left.fields[i].value != right.fields[i].value) {
if (left_copy->fields[i].value != right_copy->fields[i].value) {
return false;
}
}
Expand Down Expand Up @@ -832,6 +846,20 @@ Message<google_firestore_v1_ArrayValue> DeepClone(
return target;
}

Message<google_firestore_v1_MapValue> DeepClone(
const google_firestore_v1_MapValue& source) {
Message<google_firestore_v1_MapValue> target{source};
target->fields_count = source.fields_count;
target->fields = nanopb::MakeArray<google_firestore_v1_MapValue_FieldsEntry>(
source.fields_count);
for (pb_size_t i = 0; i < source.fields_count; ++i) {
target->fields[i].key = nanopb::MakeBytesArray(source.fields[i].key->bytes,
source.fields[i].key->size);
target->fields[i].value = *DeepClone(source.fields[i].value).release();
}
return target;
}
ehsannas marked this conversation as resolved.
Show resolved Hide resolved

} // namespace model
} // namespace firestore
} // namespace firebase
4 changes: 4 additions & 0 deletions Firestore/core/src/model/value_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ nanopb::Message<google_firestore_v1_Value> DeepClone(
nanopb::Message<google_firestore_v1_ArrayValue> DeepClone(
const google_firestore_v1_ArrayValue& source);

/** Creates a copy of the contents of the MapValue proto. */
nanopb::Message<google_firestore_v1_MapValue> DeepClone(
const google_firestore_v1_MapValue& source);

/** Returns true if `value` is a INTEGER_VALUE. */
inline bool IsInteger(const absl::optional<google_firestore_v1_Value>& value) {
return value &&
Expand Down
6 changes: 6 additions & 0 deletions Firestore/core/test/unit/model/value_util_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,12 @@ TEST_F(ValueUtilTest, DeepClone) {
VerifyDeepClone(Map("a", Array("b", Map("c", GeoPoint(30, 60)))));
}

TEST_F(ValueUtilTest, CompareMapsWithDifferentKeyOrders) {
auto left = Map("a", 3, "b", 5);
auto right = Map("b", 5, "a", 3);
EXPECT_EQ(model::Compare(*left, *right), ComparisonResult::Same);
}
cherylEnkidu marked this conversation as resolved.
Show resolved Hide resolved

} // namespace

} // namespace model
Expand Down
Loading