Skip to content

Commit 593fcef

Browse files
authored
Complete implementation of C++ MutationBatch (#3672)
* Implement the rest of MutationBatch * Teach LocalSerializer about base_mutations
1 parent 07a6493 commit 593fcef

File tree

10 files changed

+386
-22
lines changed

10 files changed

+386
-22
lines changed

Firestore/core/src/firebase/firestore/local/local_serializer.cc

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,15 +257,25 @@ firestore_client_WriteBatch LocalSerializer::EncodeMutationBatch(
257257
firestore_client_WriteBatch result{};
258258

259259
result.batch_id = mutation_batch.batch_id();
260-
pb_size_t count = CheckedSize(mutation_batch.mutations().size());
260+
261+
pb_size_t count = CheckedSize(mutation_batch.base_mutations().size());
262+
result.base_writes_count = count;
263+
result.base_writes = MakeArray<google_firestore_v1_Write>(count);
264+
int i = 0;
265+
for (const auto& mutation : mutation_batch.base_mutations()) {
266+
result.base_writes[i] = rpc_serializer_.EncodeMutation(mutation);
267+
i++;
268+
}
269+
270+
count = CheckedSize(mutation_batch.mutations().size());
261271
result.writes_count = count;
262272
result.writes = MakeArray<google_firestore_v1_Write>(count);
263-
int i = 0;
273+
i = 0;
264274
for (const auto& mutation : mutation_batch.mutations()) {
265-
HARD_ASSERT(mutation.is_valid(), "Invalid mutation encountered.");
266275
result.writes[i] = rpc_serializer_.EncodeMutation(mutation);
267276
i++;
268277
}
278+
269279
result.local_write_time =
270280
rpc_serializer_.EncodeTimestamp(mutation_batch.local_write_time());
271281

@@ -277,13 +287,21 @@ MutationBatch LocalSerializer::DecodeMutationBatch(
277287
int batch_id = proto.batch_id;
278288
Timestamp local_write_time =
279289
rpc_serializer_.DecodeTimestamp(reader, proto.local_write_time);
290+
291+
std::vector<Mutation> base_mutations;
292+
for (size_t i = 0; i < proto.base_writes_count; i++) {
293+
base_mutations.push_back(
294+
rpc_serializer_.DecodeMutation(reader, proto.base_writes[i]));
295+
}
296+
280297
std::vector<Mutation> mutations;
281298
for (size_t i = 0; i < proto.writes_count; i++) {
282299
mutations.push_back(
283300
rpc_serializer_.DecodeMutation(reader, proto.writes[i]));
284301
}
285302

286-
return MutationBatch(batch_id, local_write_time, std::move(mutations));
303+
return MutationBatch(batch_id, local_write_time, std::move(base_mutations),
304+
std::move(mutations));
287305
}
288306

289307
} // namespace local

Firestore/core/src/firebase/firestore/model/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ cc_library(
4444
mutation.h
4545
mutation_batch.cc
4646
mutation_batch.h
47+
mutation_batch_result.cc
48+
mutation_batch_result.h
4749
no_document.cc
4850
no_document.h
4951
patch_mutation.cc

Firestore/core/src/firebase/firestore/model/mutation.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,31 @@
1818

1919
#include <cstdlib>
2020
#include <ostream>
21+
#include <sstream>
2122
#include <utility>
2223

2324
#include "Firestore/core/src/firebase/firestore/model/document.h"
2425
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
2526
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
2627
#include "Firestore/core/src/firebase/firestore/model/no_document.h"
2728
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
29+
#include "Firestore/core/src/firebase/firestore/util/to_string.h"
30+
#include "absl/strings/str_cat.h"
2831

2932
namespace firebase {
3033
namespace firestore {
3134
namespace model {
3235

36+
std::string MutationResult::ToString() const {
37+
return absl::StrCat(
38+
"MutationResult(version=", version_.ToString(),
39+
", transform_results=", util::ToString(transform_results_), ")");
40+
}
41+
42+
std::ostream& operator<<(std::ostream& os, const MutationResult& result) {
43+
return os << result.ToString();
44+
}
45+
3346
Mutation::Rep::Rep(DocumentKey&& key, Precondition&& precondition)
3447
: key_(std::move(key)), precondition_(std::move(precondition)) {
3548
}

Firestore/core/src/firebase/firestore/model/mutation.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ class MutationResult {
7878
return transform_results_;
7979
}
8080

81+
std::string ToString() const;
82+
83+
friend std::ostream& operator<<(std::ostream& os,
84+
const MutationResult& result);
85+
8186
private:
8287
SnapshotVersion version_;
8388
absl::optional<const std::vector<FieldValue>> transform_results_;

Firestore/core/src/firebase/firestore/model/mutation_batch.cc

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <ostream>
2020
#include <utility>
2121

22+
#include "Firestore/core/src/firebase/firestore/model/mutation_batch_result.h"
2223
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
2324
#include "Firestore/core/src/firebase/firestore/util/to_string.h"
2425

@@ -28,23 +29,109 @@ namespace model {
2829

2930
MutationBatch::MutationBatch(int batch_id,
3031
Timestamp local_write_time,
31-
std::vector<Mutation>&& mutations)
32+
std::vector<Mutation> base_mutations,
33+
std::vector<Mutation> mutations)
3234
: batch_id_(batch_id),
3335
local_write_time_(std::move(local_write_time)),
36+
base_mutations_(std::move(base_mutations)),
3437
mutations_(std::move(mutations)) {
3538
HARD_ASSERT(!mutations_.empty(), "Cannot create an empty mutation batch");
3639
}
3740

41+
absl::optional<MaybeDocument> MutationBatch::ApplyToRemoteDocument(
42+
absl::optional<MaybeDocument> maybe_doc,
43+
const DocumentKey& document_key,
44+
const MutationBatchResult& mutation_batch_result) {
45+
HARD_ASSERT(!maybe_doc || maybe_doc->key() == document_key,
46+
"ApplyTo: key %s doesn't match maybe_doc key %s",
47+
document_key.ToString(), maybe_doc->key().ToString());
48+
49+
const auto& mutation_results = mutation_batch_result.mutation_results();
50+
HARD_ASSERT(mutation_results.size() == mutations_.size(),
51+
"Mismatch between mutations length (%s) and results length (%s)",
52+
mutations_.size(), mutation_results.size());
53+
54+
for (size_t i = 0; i < mutations_.size(); i++) {
55+
const Mutation& mutation = mutations_[i];
56+
const MutationResult& mutation_result = mutation_results[i];
57+
if (mutation.key() == document_key) {
58+
maybe_doc = mutation.ApplyToRemoteDocument(maybe_doc, mutation_result);
59+
}
60+
}
61+
return maybe_doc;
62+
}
63+
64+
absl::optional<MaybeDocument> MutationBatch::ApplyToLocalDocument(
65+
absl::optional<MaybeDocument> maybe_doc, const DocumentKey& document_key) {
66+
HARD_ASSERT(!maybe_doc || maybe_doc->key() == document_key,
67+
"key %s doesn't match maybe_doc key %s", document_key.ToString(),
68+
maybe_doc->key().ToString());
69+
70+
// First, apply the base state. This allows us to apply non-idempotent
71+
// transform against a consistent set of values.
72+
for (const Mutation& mutation : base_mutations_) {
73+
if (mutation.key() == document_key) {
74+
maybe_doc =
75+
mutation.ApplyToLocalView(maybe_doc, maybe_doc, local_write_time_);
76+
}
77+
}
78+
79+
absl::optional<MaybeDocument> base_doc = maybe_doc;
80+
81+
// Second, apply all user-provided mutations.
82+
for (const Mutation& mutation : mutations_) {
83+
if (mutation.key() == document_key) {
84+
maybe_doc =
85+
mutation.ApplyToLocalView(maybe_doc, base_doc, local_write_time_);
86+
}
87+
}
88+
return maybe_doc;
89+
}
90+
91+
MaybeDocumentMap MutationBatch::ApplyToLocalDocumentSet(
92+
const MaybeDocumentMap& document_set) {
93+
// TODO(mrschmidt): This implementation is O(n^2). If we iterate through the
94+
// mutations first (as done in `applyToLocalDocument:documentKey:`), we can
95+
// reduce the complexity to O(n).
96+
97+
MaybeDocumentMap mutated_documents = document_set;
98+
for (const Mutation& mutation : mutations_) {
99+
const DocumentKey& key = mutation.key();
100+
101+
absl::optional<MaybeDocument> previous_document =
102+
mutated_documents.get(key);
103+
absl::optional<MaybeDocument> mutated_document =
104+
ApplyToLocalDocument(std::move(previous_document), key);
105+
if (mutated_document) {
106+
mutated_documents = mutated_documents.insert(key, *mutated_document);
107+
}
108+
}
109+
return mutated_documents;
110+
}
111+
112+
DocumentKeySet MutationBatch::keys() const {
113+
DocumentKeySet set;
114+
for (const Mutation& mutation : mutations_) {
115+
set = set.insert(mutation.key());
116+
}
117+
return set;
118+
}
119+
38120
bool operator==(const MutationBatch& lhs, const MutationBatch& rhs) {
39121
return lhs.batch_id() == rhs.batch_id() &&
40122
lhs.local_write_time() == rhs.local_write_time() &&
123+
lhs.base_mutations() == rhs.base_mutations() &&
41124
lhs.mutations() == rhs.mutations();
42125
}
43126

127+
std::string MutationBatch::ToString() const {
128+
return absl::StrCat("MutationBatch(id=", batch_id_,
129+
", local_write_time=", local_write_time_.ToString(),
130+
", mutations=", util::ToString(mutations_), ")");
131+
}
132+
44133
std::ostream& operator<<(std::ostream& os, const MutationBatch& batch) {
45-
return os << "MutationBatch(id=" << batch.batch_id_
46-
<< ", local_write_time=" << batch.local_write_time_
47-
<< ", mutations=" << util::ToString(batch.mutations_) << ")";
134+
return os << batch.ToString();
48135
}
49136

50137
} // namespace model

Firestore/core/src/firebase/firestore/model/mutation_batch.h

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,24 @@
1919

2020
#include <iosfwd>
2121
#include <memory>
22+
#include <string>
2223
#include <vector>
2324

2425
#include "Firestore/core/include/firebase/firestore/timestamp.h"
26+
#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
27+
#include "Firestore/core/src/firebase/firestore/model/document_map.h"
2528
#include "Firestore/core/src/firebase/firestore/model/mutation.h"
2629
#include "Firestore/core/src/firebase/firestore/model/types.h"
2730

2831
namespace firebase {
2932
namespace firestore {
3033
namespace model {
3134

35+
class MutationBatchResult;
36+
3237
/**
33-
* A BatchID that was searched for and not found or a batch ID value known to be
34-
* before all known batches.
38+
* A BatchID that was searched for and not found or a batch ID value known to
39+
* be before all known batches.
3540
*
3641
* BatchId values from the local store are non-negative so this value is before
3742
* all batches.
@@ -48,8 +53,8 @@ constexpr BatchId kBatchIdUnknown = -1;
4853
class MutationBatch {
4954
public:
5055
/**
51-
* A batch ID that was searched for and not found or a batch ID value known to
52-
* be before all known batches.
56+
* A batch ID that was searched for and not found or a batch ID value known
57+
* to be before all known batches.
5358
*
5459
* Batch ID values from the local store are non-negative so this value is
5560
* before all batches.
@@ -58,12 +63,10 @@ class MutationBatch {
5863

5964
MutationBatch(int batch_id,
6065
Timestamp local_write_time,
61-
std::vector<Mutation>&& mutations);
62-
63-
// TODO(rsgowman): Port ApplyToRemoteDocument()
64-
// TODO(rsgowman): Port ApplyToLocalView()
65-
// TODO(rsgowman): Port GetKeys()
66+
std::vector<Mutation> base_mutations,
67+
std::vector<Mutation> mutations);
6668

69+
/** The unique ID of this mutation batch. */
6770
int batch_id() const {
6871
return batch_id_;
6972
}
@@ -76,17 +79,76 @@ class MutationBatch {
7679
return local_write_time_;
7780
}
7881

82+
/**
83+
* Mutations that are used to populate the base values when this mutation is
84+
* applied locally. This can be used to locally overwrite values that are
85+
* persisted in the remote document cache. Base mutations are never sent to
86+
* the backend.
87+
*/
88+
const std::vector<Mutation>& base_mutations() const {
89+
return base_mutations_;
90+
}
91+
92+
/**
93+
* The user-provided mutations in this mutation batch. User-provided
94+
* mutations are applied both locally and remotely on the backend.
95+
*/
7996
const std::vector<Mutation>& mutations() const {
8097
return mutations_;
8198
}
8299

100+
/**
101+
* Applies all the mutations in this MutationBatch to the specified document
102+
* to create a new remote document.
103+
*
104+
* @param maybe_doc The document to which to apply mutations or nullopt if
105+
* there's no existing document.
106+
* @param document_key The key of the document to apply mutations to.
107+
* @param mutation_batch_result The result of applying the MutationBatch to
108+
* the backend.
109+
*/
110+
absl::optional<MaybeDocument> ApplyToRemoteDocument(
111+
absl::optional<MaybeDocument> maybe_doc,
112+
const DocumentKey& document_key,
113+
const MutationBatchResult& mutation_batch_result);
114+
115+
/**
116+
* Estimates the latency compensated view of all the mutations in this batch
117+
* applied to the given MaybeDocument.
118+
*
119+
* Unlike ApplyToRemoteDocument, this method is used before the mutation has
120+
* been committed and so it's possible that the mutation is operating on a
121+
* locally non-existent document and may produce a non-existent document.
122+
*
123+
* @param maybe_doc The document to which to apply mutations or nullopt if
124+
* there's no existing document.
125+
* @param document_key The key of the document to apply mutations to.
126+
*/
127+
absl::optional<MaybeDocument> ApplyToLocalDocument(
128+
absl::optional<MaybeDocument> maybe_doc, const DocumentKey& document_key);
129+
130+
/**
131+
* Computes the local view for all provided documents given the mutations in
132+
* this batch.
133+
*/
134+
MaybeDocumentMap ApplyToLocalDocumentSet(
135+
const MaybeDocumentMap& document_set);
136+
137+
/**
138+
* Returns the set of unique keys referenced by all mutations in the batch.
139+
*/
140+
DocumentKeySet keys() const;
141+
83142
friend bool operator==(const MutationBatch& lhs, const MutationBatch& rhs);
84143

144+
std::string ToString() const;
145+
85146
friend std::ostream& operator<<(std::ostream& os, const MutationBatch& batch);
86147

87148
private:
88149
int batch_id_;
89150
const Timestamp local_write_time_;
151+
std::vector<Mutation> base_mutations_;
90152
std::vector<Mutation> mutations_;
91153
};
92154

0 commit comments

Comments
 (0)