Skip to content

Commit a8db28d

Browse files
Feedback
1 parent 680cf4a commit a8db28d

File tree

5 files changed

+129
-113
lines changed

5 files changed

+129
-113
lines changed

FirebaseFirestore.podspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling,
8989
abseil_version = '0.20200225.0'
9090
s.dependency 'abseil/algorithm', abseil_version
9191
s.dependency 'abseil/base', abseil_version
92+
s.dependency 'abseil/container/flat_hash_map', abseil_version
93+
s.dependency 'abseil/container/flat_hash_set', abseil_version
9294
s.dependency 'abseil/memory', abseil_version
9395
s.dependency 'abseil/meta', abseil_version
9496
s.dependency 'abseil/strings/strings', abseil_version

Firestore/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ target_compile_definitions(
132132
target_link_libraries(
133133
firestore_util PUBLIC
134134
absl::base
135+
absl::flat_hash_map
136+
absl::flat_hash_set
135137
absl::memory
136138
absl::meta
137139
absl::optional

Firestore/core/src/model/object_value.cc

Lines changed: 110 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
#include "Firestore/core/src/model/object_value.h"
1818

1919
#include <algorithm>
20-
#include <map>
2120
#include <set>
2221

2322
#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
2423
#include "Firestore/core/src/nanopb/fields_array.h"
2524
#include "Firestore/core/src/nanopb/message.h"
2625
#include "Firestore/core/src/nanopb/nanopb_util.h"
26+
#include "absl/container/flat_hash_map.h"
27+
#include "absl/container/flat_hash_set.h"
2728

2829
namespace firebase {
2930
namespace firestore {
@@ -40,11 +41,11 @@ using nanopb::MakeStringView;
4041

4142
struct MapEntryKeyCompare {
4243
bool operator()(const google_firestore_v1_MapValue_FieldsEntry& entry,
43-
absl::string_view segment) {
44+
absl::string_view segment) const {
4445
return nanopb::MakeStringView(entry.key) < segment;
4546
}
4647
bool operator()(absl::string_view segment,
47-
const google_firestore_v1_MapValue_FieldsEntry& entry) {
48+
const google_firestore_v1_MapValue_FieldsEntry& entry) const {
4849
return segment < nanopb::MakeStringView(entry.key);
4950
}
5051
};
@@ -53,7 +54,7 @@ struct MapEntryKeyCompare {
5354
* Finds an entry by key in the provided map value. Returns `nullptr` if the
5455
* entry does not exist.
5556
*/
56-
static google_firestore_v1_MapValue_FieldsEntry* FindEntry(
57+
google_firestore_v1_MapValue_FieldsEntry* FindEntry(
5758
const google_firestore_v1_Value& value, absl::string_view segment) {
5859
if (value.which_value_type != google_firestore_v1_Value_map_value_tag) {
5960
return nullptr;
@@ -72,6 +73,97 @@ static google_firestore_v1_MapValue_FieldsEntry* FindEntry(
7273
return found.first;
7374
}
7475

76+
size_t CalculateSizeOfUnion(
77+
google_firestore_v1_MapValue* map_value,
78+
const absl::flat_hash_map<std::string, google_firestore_v1_Value>& upserts,
79+
const absl::flat_hash_set<std::string>& deletes) {
80+
// Compute the size of the map after applying all mutations. The final size is
81+
// the number of existing entries, plus the number of new entries
82+
// minus the number of deleted entries.
83+
return upserts.size() +
84+
std::count_if(
85+
map_value->fields, map_value->fields + map_value->fields_count,
86+
[&](const google_firestore_v1_MapValue_FieldsEntry& entry) {
87+
absl::string_view field = MakeStringView(entry.key);
88+
// Don't count if entry is deleted or if it is a replacement
89+
// rather than an insert.
90+
return deletes.find(field) == deletes.end() &&
91+
upserts.find(field) == upserts.end();
92+
});
93+
}
94+
95+
/**
96+
* Modifies `parent_map` by adding, replacing or deleting the specified
97+
* entries.
98+
*/
99+
void ApplyChanges(
100+
google_firestore_v1_MapValue* parent,
101+
const absl::flat_hash_map<std::string, google_firestore_v1_Value>& upserts,
102+
const absl::flat_hash_set<std::string>& deletes) {
103+
// TODO(mrschmidt): Consider using `absl::btree_map` and `absl::btree_set` for
104+
// potentially better performance.
105+
auto source_count = parent->fields_count;
106+
auto* source_fields = parent->fields;
107+
108+
size_t target_count = CalculateSizeOfUnion(parent, upserts, deletes);
109+
auto* target_fields =
110+
MakeArray<google_firestore_v1_MapValue_FieldsEntry>(target_count);
111+
112+
auto delete_it = deletes.begin();
113+
auto upsert_it = upserts.begin();
114+
115+
// Merge the existing data with the deletes and updates
116+
for (pb_size_t source_index = 0, target_index = 0;
117+
target_index < target_count;) {
118+
if (source_index < source_count) {
119+
absl::string_view key = MakeString(source_fields[source_index].key);
120+
121+
// Check if the source key is deleted
122+
if (delete_it != deletes.end() && *delete_it == key) {
123+
FreeNanopbMessage(google_firestore_v1_MapValue_FieldsEntry_fields,
124+
source_fields + source_index);
125+
126+
++delete_it;
127+
++source_index;
128+
continue;
129+
}
130+
131+
// Check if the source key is updated by the next upsert
132+
if (upsert_it != upserts.end() && upsert_it->first == key) {
133+
FreeNanopbMessage(google_firestore_v1_Value_fields,
134+
&source_fields[source_index].value);
135+
target_fields[target_index].key = source_fields[source_index].key;
136+
target_fields[target_index].value = DeepClone(upsert_it->second);
137+
138+
++upsert_it;
139+
++source_index;
140+
++target_index;
141+
continue;
142+
}
143+
144+
// Check if the source key comes before the next upsert
145+
if (upsert_it == upserts.end() || upsert_it->first > key) {
146+
target_fields[target_index] = source_fields[source_index];
147+
148+
++source_index;
149+
++target_index;
150+
continue;
151+
}
152+
}
153+
154+
// Otherwise, insert the next upsert.
155+
target_fields[target_index].key = MakeBytesArray(upsert_it->first);
156+
target_fields[target_index].value = DeepClone(upsert_it->second);
157+
158+
++upsert_it;
159+
++target_index;
160+
}
161+
162+
free(parent->fields);
163+
parent->fields = target_fields;
164+
parent->fields_count = target_count;
165+
}
166+
75167
} // namespace
76168

77169
MutableObjectValue::MutableObjectValue() {
@@ -80,29 +172,29 @@ MutableObjectValue::MutableObjectValue() {
80172
value_->map_value.fields = nullptr;
81173
}
82174

83-
model::FieldMask MutableObjectValue::ToFieldMask() const {
175+
FieldMask MutableObjectValue::ToFieldMask() const {
84176
return ExtractFieldMask(value_->map_value);
85177
}
86178

87-
model::FieldMask MutableObjectValue::ExtractFieldMask(
179+
FieldMask MutableObjectValue::ExtractFieldMask(
88180
const google_firestore_v1_MapValue& value) const {
89181
std::set<FieldPath> fields;
90182

91183
for (size_t i = 0; i < value.fields_count; ++i) {
92-
google_firestore_v1_MapValue_FieldsEntry& entry = value.fields[i];
184+
const google_firestore_v1_MapValue_FieldsEntry& entry = value.fields[i];
93185
FieldPath current_path({MakeString(entry.key)});
94186

95187
if (entry.value.which_value_type !=
96188
google_firestore_v1_Value_map_value_tag) {
97-
fields.insert(current_path);
189+
fields.insert(std::move((current_path)));
98190
continue;
99191
}
100192

101193
// Recursively extract the nested map
102194
FieldMask nested_mask = ExtractFieldMask(entry.value.map_value);
103195
if (nested_mask.begin() == nested_mask.end()) {
104-
// Preserve the empty map by adding it to the FieldMask.
105-
fields.insert(current_path);
196+
// Preserve the empty map by adding it to the FieldMask
197+
fields.insert(std::move(current_path));
106198
} else {
107199
for (const FieldPath& nested_path : nested_mask) {
108200
fields.insert(current_path.Append(nested_path));
@@ -136,7 +228,7 @@ void MutableObjectValue::Set(const FieldPath& path,
136228
google_firestore_v1_MapValue* parent_map = ParentMap(path.PopLast());
137229

138230
std::string last_segment = path.last_segment();
139-
std::map<std::string, google_firestore_v1_Value> upserts{
231+
absl::flat_hash_map<std::string, google_firestore_v1_Value> upserts{
140232
{std::move(last_segment), value}};
141233

142234
ApplyChanges(parent_map, upserts, /*deletes=*/{});
@@ -146,8 +238,8 @@ void MutableObjectValue::SetAll(const FieldMask& field_mask,
146238
const MutableObjectValue& data) {
147239
FieldPath parent;
148240

149-
std::map<std::string, google_firestore_v1_Value> upserts;
150-
std::set<std::string> deletes;
241+
absl::flat_hash_map<std::string, google_firestore_v1_Value> upserts;
242+
absl::flat_hash_set<std::string> deletes;
151243

152244
for (const FieldPath& path : field_mask) {
153245
if (!parent.IsImmediateParentOf(path)) {
@@ -188,7 +280,7 @@ void MutableObjectValue::Delete(const FieldPath& path) {
188280
// We can only delete a leaf entry if its parent is a map.
189281
if (nested_value->which_value_type ==
190282
google_firestore_v1_Value_map_value_tag) {
191-
std::set<std::string> deletes{path.last_segment()};
283+
absl::flat_hash_set<std::string> deletes{path.last_segment()};
192284
ApplyChanges(&nested_value->map_value, /*upserts=*/{}, deletes);
193285
}
194286
}
@@ -210,21 +302,21 @@ google_firestore_v1_MapValue* MutableObjectValue::ParentMap(
210302
if (entry->value.which_value_type !=
211303
google_firestore_v1_Value_map_value_tag) {
212304
// Since the element is not a map value, free all existing data and
213-
// change it to a map type
305+
// change it to a map type.
214306
FreeNanopbMessage(FieldsArray<google_firestore_v1_Value>(),
215307
&entry->value);
216308
entry->value.which_value_type = google_firestore_v1_Value_map_value_tag;
217309
}
218310

219311
parent = &entry->value;
220312
} else {
221-
// Create a new map value for the current segment
313+
// Create a new map value for the current segment.
222314
google_firestore_v1_Value new_entry{};
223315
new_entry.which_value_type = google_firestore_v1_Value_map_value_tag;
224316

225-
std::map<std::string, google_firestore_v1_Value> upserts{
317+
absl::flat_hash_map<std::string, google_firestore_v1_Value> upserts{
226318
{segment, new_entry}};
227-
ApplyChanges(&(parent->map_value), upserts, /*deletes=*/{});
319+
ApplyChanges(&parent->map_value, upserts, /*deletes=*/{});
228320

229321
parent = &(FindEntry(*parent, segment)->value);
230322
}
@@ -233,82 +325,6 @@ google_firestore_v1_MapValue* MutableObjectValue::ParentMap(
233325
return &parent->map_value;
234326
}
235327

236-
void MutableObjectValue::ApplyChanges(
237-
google_firestore_v1_MapValue* parent,
238-
const std::map<std::string, google_firestore_v1_Value>& upserts,
239-
const std::set<std::string>& deletes) const {
240-
auto source_count = parent->fields_count;
241-
auto* source_fields = parent->fields;
242-
243-
// Compute the size of the map after applying all mutations. The final size is
244-
// the number of existing entries, plus the number of new entries
245-
// minus the number of deleted entries.
246-
size_t target_count =
247-
upserts.size() +
248-
std::count_if(source_fields, source_fields + source_count,
249-
[&](const google_firestore_v1_MapValue_FieldsEntry& entry) {
250-
std::string field = MakeString(entry.key);
251-
// Don't count if entry is deleted or if it is a
252-
// replacement rather than an insert.
253-
return deletes.find(field) == deletes.end() &&
254-
upserts.find(field) == upserts.end();
255-
});
256-
257-
auto* target_fields =
258-
MakeArray<google_firestore_v1_MapValue_FieldsEntry>(target_count);
259-
260-
auto delete_it = deletes.begin();
261-
auto upsert_it = upserts.begin();
262-
263-
// Merge the existing data with the deletes and updates.
264-
for (pb_size_t source_index = 0, target_index = 0;
265-
target_index < target_count;) {
266-
if (source_index < source_count) {
267-
std::string key = MakeString(source_fields[source_index].key);
268-
269-
// Check if the source key is deleted
270-
if (delete_it != deletes.end() && *delete_it == key) {
271-
FreeNanopbMessage(google_firestore_v1_MapValue_FieldsEntry_fields,
272-
source_fields + source_index);
273-
++delete_it;
274-
++source_index;
275-
continue;
276-
}
277-
278-
// Check if the source key is updated by the next upsert
279-
if (upsert_it != upserts.end() && upsert_it->first == key) {
280-
FreeNanopbMessage(google_firestore_v1_Value_fields,
281-
&source_fields[source_index].value);
282-
target_fields[target_index].key = source_fields[source_index].key;
283-
target_fields[target_index].value = DeepClone(upsert_it->second);
284-
++upsert_it;
285-
++target_index;
286-
++source_index;
287-
continue;
288-
}
289-
290-
// Check if the source key comes before the next upsert
291-
if (upsert_it == upserts.end() || upsert_it->first > key) {
292-
target_fields[target_index] = source_fields[source_index];
293-
++target_index;
294-
++source_index;
295-
continue;
296-
}
297-
}
298-
299-
// Otherwise, insert the next upsert.
300-
target_fields[target_index].key = MakeBytesArray(upsert_it->first);
301-
target_fields[target_index].value = DeepClone(upsert_it->second);
302-
++upsert_it;
303-
++target_index;
304-
}
305-
306-
free(source_fields);
307-
308-
parent->fields = target_fields;
309-
parent->fields_count = target_count;
310-
}
311-
312328
} // namespace model
313329
} // namespace firestore
314330
} // namespace firebase

Firestore/core/src/model/object_value.h

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class MutableObjectValue {
4343
public:
4444
MutableObjectValue();
4545

46+
/** Creates a new MutableObjectValue and takes ownership of `value`. */
4647
explicit MutableObjectValue(const google_firestore_v1_Value& value)
4748
: value_(value) {
4849
HARD_ASSERT(
@@ -71,7 +72,7 @@ class MutableObjectValue {
7172
/**
7273
* Sets the field to the provided value.
7374
*
74-
* @param path The field path to set.
75+
* @param path The field path to set. The path must not be empty.
7576
* @param value The value to set.
7677
*/
7778
void Set(const FieldPath& path, const google_firestore_v1_Value& value);
@@ -90,9 +91,7 @@ class MutableObjectValue {
9091
* Removes the field at the specified path. If there is no field at the
9192
* specified path, nothing is changed.
9293
*
93-
* The path must not be empty.
94-
*
95-
* @param path The field path to remove.
94+
* @param path The field path to remove. The path must not be empty.
9695
*/
9796
void Delete(const FieldPath& path);
9897

@@ -111,15 +110,6 @@ class MutableObjectValue {
111110
*/
112111
google_firestore_v1_MapValue* ParentMap(const FieldPath& path);
113112

114-
/**
115-
* Modifies `parent_map` by adding, replacing or deleting the specified
116-
* entries.
117-
*/
118-
void ApplyChanges(
119-
google_firestore_v1_MapValue* parent,
120-
const std::map<std::string, google_firestore_v1_Value>& upserts,
121-
const std::set<std::string>& deletes) const;
122-
123113
nanopb::Message<google_firestore_v1_Value> value_;
124114
};
125115

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,18 @@ class ValueUtilTest : public ::testing::Test {
134134
}
135135

136136
void VerifyDeepClone(const google_firestore_v1_Value& value) {
137-
// `Message` takes ownership of the proto value and destroys it. We use
138-
// it to verify that all values own their own memory.
139-
nanopb::Message<google_firestore_v1_Value> clone1{DeepClone(value)};
140-
nanopb::Message<google_firestore_v1_Value> clone2{DeepClone(*clone1)};
141-
EXPECT_TRUE(value == *clone1);
142-
EXPECT_TRUE(value == *clone2);
137+
nanopb::Message<google_firestore_v1_Value> clone1;
138+
139+
[&] {
140+
nanopb::Message<google_firestore_v1_Value> clone2{DeepClone(value)};
141+
EXPECT_TRUE(value == *clone2)
142+
<< "Equality check failed for '" << value << "' (before cleanup)";
143+
clone1 = nanopb::Message<google_firestore_v1_Value>{DeepClone(*clone2)};
144+
}();
145+
146+
// `clone2` is destroyed at this point, but `clone1` should be still valid.
147+
EXPECT_TRUE(value == *clone1)
148+
<< "Equality check failed for '" << value << "' (after cleanup)";
143149
}
144150

145151
private:

0 commit comments

Comments
 (0)