Skip to content

Commit 11e07ae

Browse files
author
Brian Chen
committed
WIP: failing LocalSerializerTest
1 parent 5dcaafd commit 11e07ae

File tree

3 files changed

+212
-48
lines changed

3 files changed

+212
-48
lines changed

Firestore/core/src/local/local_serializer.cc

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ namespace {
4848
using core::Target;
4949
using model::Document;
5050
using model::DocumentState;
51+
using model::FieldTransform;
5152
using model::FieldValue;
5253
using model::MaybeDocument;
5354
using model::Mutation;
@@ -328,11 +329,47 @@ MutationBatch LocalSerializer::DecodeMutationBatch(
328329
}
329330

330331
std::vector<Mutation> mutations;
331-
for (size_t i = 0; i < proto.writes_count; i++) {
332-
mutations.push_back(
333-
rpc_serializer_.DecodeMutation(reader, proto.writes[i]));
332+
333+
// Squash old transform mutations into existing patch of set mutations. The
334+
// replacement of representing `transforms` with `update_transforms` on the
335+
// SDK means that old `transform` mutations stored in LevelDB need to be
336+
// updated to `update_transforms`.
337+
// TODO(b/174608374): Remove this code once we perform a schema migration.
338+
for (size_t i = proto.writes_count - 1; i >= 0; --i) {
339+
_google_firestore_v1_Write mutation = proto.writes[i];
340+
if (mutation.which_operation == google_firestore_v1_Write_transform_tag) {
341+
HARD_ASSERT(
342+
i >= 1 && proto.writes[i - 1].which_operation ==
343+
google_firestore_v1_Write_update_tag,
344+
"TransformMutation should be preceded by a patch or set mutation");
345+
_google_firestore_v1_Write mutation_to_join = proto.writes[i - 1];
346+
_google_firestore_v1_Write new_mutation{mutation_to_join};
347+
new_mutation.update_transforms_count =
348+
mutation.transform.field_transforms_count;
349+
new_mutation.update_transforms =
350+
MakeArray<_google_firestore_v1_DocumentTransform_FieldTransform>(
351+
mutation.transform.field_transforms_count);
352+
for (size_t j = 0; j < mutation.transform.field_transforms_count; ++j) {
353+
new_mutation.update_transforms[j] =
354+
mutation.transform.field_transforms[j];
355+
}
356+
mutations.push_back(rpc_serializer_.DecodeMutation(reader, new_mutation));
357+
--i;
358+
} else {
359+
mutations.push_back(rpc_serializer_.DecodeMutation(reader, mutation));
360+
}
361+
362+
// TODO: figure out how to exit the loop without size_t looping around
363+
if (i == 0) {
364+
break;
365+
}
334366
}
335367

368+
// Reverse the mutations to preserve the original ordering since the above
369+
// for-loop iterates in reverse order. We use reverse() instead of prepending
370+
// the elements into the mutations array since prepending to a List is O(n).
371+
std::reverse(mutations.begin(), mutations.end());
372+
336373
return MutationBatch(batch_id, local_write_time, std::move(base_mutations),
337374
std::move(mutations));
338375
}

Firestore/core/src/remote/serializer.cc

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,11 +591,9 @@ google_firestore_v1_Write Serializer::EncodeMutation(
591591
result.update_transforms =
592592
MakeArray<google_firestore_v1_DocumentTransform_FieldTransform>(count);
593593
int i = 0;
594-
auto crazy = mutation.field_transforms();
595-
auto crazy2 = mutation.key();
596594
for (const FieldTransform& field_transform : mutation.field_transforms()) {
597595
result.update_transforms[i] = EncodeFieldTransform(field_transform);
598-
i++;
596+
++i;
599597
}
600598

601599
switch (mutation.type()) {

Firestore/core/test/unit/local/local_serializer_test.cc

Lines changed: 171 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ class LocalSerializerTest : public ::testing::Test {
101101
remote::Serializer remote_serializer;
102102
local::LocalSerializer serializer;
103103

104+
Timestamp write_time_ = Timestamp::Now();
105+
104106
template <typename... Args>
105107
void ExpectRoundTrip(const Args&... args) {
106108
// First, serialize model with our (nanopb based) serializer, then
@@ -114,6 +116,75 @@ class LocalSerializerTest : public ::testing::Test {
114116
ExpectDeserializationRoundTrip(args...);
115117
}
116118

119+
static v1::Write SetProto() {
120+
v1::Value b_value{};
121+
*b_value.mutable_string_value() = "b";
122+
v1::Value one_value{};
123+
one_value.set_integer_value(1);
124+
125+
v1::Write set_proto{};
126+
*set_proto.mutable_update()->mutable_name() =
127+
"projects/p/databases/d/documents/docs/1";
128+
(*set_proto.mutable_update()->mutable_fields())["a"] = b_value;
129+
(*set_proto.mutable_update()->mutable_fields())["num"] = one_value;
130+
return set_proto;
131+
}
132+
133+
static v1::Write PatchProto() {
134+
v1::Value b_value{};
135+
*b_value.mutable_string_value() = "b";
136+
v1::Value one_value{};
137+
one_value.set_integer_value(1);
138+
139+
v1::Write patch_proto{};
140+
*patch_proto.mutable_update()->mutable_name() =
141+
"projects/p/databases/d/documents/docs/1";
142+
(*patch_proto.mutable_update()->mutable_fields())["a"] = b_value;
143+
(*patch_proto.mutable_update()->mutable_fields())["num"] = one_value;
144+
patch_proto.mutable_update_mask()->add_field_paths("a");
145+
patch_proto.mutable_current_document()->set_exists(true);
146+
return patch_proto;
147+
}
148+
149+
static v1::Write DeleteProto() {
150+
v1::Write delete_proto{};
151+
*delete_proto.mutable_delete_() = "projects/p/databases/d/documents/docs/1";
152+
return delete_proto;
153+
}
154+
155+
static v1::Write LegacyTransformProto() {
156+
v1::Write transform_proto{};
157+
158+
v1::DocumentTransform::FieldTransform inc_proto1;
159+
v1::Value inc1_value{};
160+
inc1_value.set_integer_value(42);
161+
inc_proto1.set_field_path("integer");
162+
*inc_proto1.mutable_increment() = inc1_value;
163+
164+
v1::DocumentTransform::FieldTransform inc_proto2;
165+
v1::Value inc2_value{};
166+
inc2_value.set_double_value(13.37);
167+
inc_proto2.set_field_path("double");
168+
*inc_proto2.mutable_increment() = inc2_value;
169+
170+
*transform_proto.mutable_transform()->add_field_transforms() =
171+
std::move(inc_proto1);
172+
*transform_proto.mutable_transform()->add_field_transforms() =
173+
std::move(inc_proto2);
174+
175+
transform_proto.mutable_current_document()->set_exists(true);
176+
transform_proto.mutable_transform()->set_document(
177+
"projects/p/databases/d/documents/docs/1");
178+
return transform_proto;
179+
}
180+
181+
::google::protobuf::Timestamp WriteTimeProto() {
182+
::google::protobuf::Timestamp write_time_proto{};
183+
write_time_proto.set_seconds(write_time_.seconds());
184+
write_time_proto.set_nanos(write_time_.nanoseconds());
185+
return write_time_proto;
186+
}
187+
117188
private:
118189
void ExpectSerializationRoundTrip(
119190
const MaybeDocument& model,
@@ -138,9 +209,9 @@ class LocalSerializerTest : public ::testing::Test {
138209
EXPECT_EQ(model, actual_model);
139210
}
140211

141-
ByteString EncodeMaybeDocument(local::LocalSerializer* serializer,
212+
ByteString EncodeMaybeDocument(local::LocalSerializer* localSerializer,
142213
const MaybeDocument& maybe_doc) {
143-
return MakeByteString(serializer->EncodeMaybeDocument(maybe_doc));
214+
return MakeByteString(localSerializer->EncodeMaybeDocument(maybe_doc));
144215
}
145216

146217
void ExpectSerializationRoundTrip(const TargetData& target_data,
@@ -163,10 +234,10 @@ class LocalSerializerTest : public ::testing::Test {
163234
EXPECT_EQ(target_data, actual_target_data);
164235
}
165236

166-
ByteString EncodeTargetData(local::LocalSerializer* serializer,
237+
ByteString EncodeTargetData(local::LocalSerializer* localSerializer,
167238
const TargetData& target_data) {
168239
EXPECT_EQ(target_data.purpose(), QueryPurpose::Listen);
169-
return MakeByteString(serializer->EncodeTargetData(target_data));
240+
return MakeByteString(localSerializer->EncodeTargetData(target_data));
170241
}
171242

172243
void ExpectSerializationRoundTrip(
@@ -191,71 +262,129 @@ class LocalSerializerTest : public ::testing::Test {
191262
EXPECT_EQ(model, actual_mutation_batch);
192263
}
193264

194-
ByteString EncodeMutationBatch(local::LocalSerializer* serializer,
265+
ByteString EncodeMutationBatch(local::LocalSerializer* localSerializer,
195266
const MutationBatch& mutation_batch) {
196-
return MakeByteString(serializer->EncodeMutationBatch(mutation_batch));
267+
return MakeByteString(localSerializer->EncodeMutationBatch(mutation_batch));
197268
}
198269

199270
std::string message_differences;
200271
MessageDifferencer msg_diff;
201272
};
202273

274+
// TODO(b/174608374): Remove these tests once we perform a schema migration.
275+
TEST_F(LocalSerializerTest, SetMutationAndTransformMutationAreSquashed) {
276+
::firestore::client::WriteBatch batch_proto{};
277+
batch_proto.set_batch_id(42);
278+
*batch_proto.add_writes() = SetProto();
279+
*batch_proto.add_writes() = LegacyTransformProto();
280+
*batch_proto.mutable_local_write_time() = WriteTimeProto();
281+
282+
ByteString bytes = ProtobufSerialize(batch_proto);
283+
StringReader reader(bytes);
284+
auto message = Message<firestore_client_WriteBatch>::TryParse(&reader);
285+
MutationBatch decoded = serializer.DecodeMutationBatch(&reader, *message);
286+
ASSERT_EQ(1, decoded.mutations().size());
287+
ASSERT_EQ(Mutation::Type::Set, decoded.mutations()[0].type());
288+
google_firestore_v1_Write encoded =
289+
remote_serializer.EncodeMutation(decoded.mutations()[0]);
290+
291+
google_firestore_v1_Write expected{};
292+
expected.which_operation = google_firestore_v1_Write_update_tag;
293+
// TODO: How should I compare these two types?
294+
EXPECT_EQ(expected, encoded);
295+
}
296+
297+
// TODO(b/174608374): Remove these tests once we perform a schema migration.
298+
TEST_F(LocalSerializerTest, TransformAndTransformThrowError) {
299+
::firestore::client::WriteBatch batch_proto{};
300+
batch_proto.set_batch_id(42);
301+
*batch_proto.add_writes() = LegacyTransformProto();
302+
*batch_proto.add_writes() = LegacyTransformProto();
303+
*batch_proto.mutable_local_write_time() = WriteTimeProto();
304+
305+
ByteString bytes = ProtobufSerialize(batch_proto);
306+
StringReader reader(bytes);
307+
auto message = Message<firestore_client_WriteBatch>::TryParse(&reader);
308+
EXPECT_ANY_THROW(serializer.DecodeMutationBatch(&reader, *message));
309+
}
310+
311+
// TODO(b/174608374): Remove these tests once we perform a schema migration.
312+
TEST_F(LocalSerializerTest, OnlyTransformThrowsError) {
313+
::firestore::client::WriteBatch batch_proto{};
314+
batch_proto.set_batch_id(42);
315+
*batch_proto.add_writes() = LegacyTransformProto();
316+
*batch_proto.mutable_local_write_time() = WriteTimeProto();
317+
318+
ByteString bytes = ProtobufSerialize(batch_proto);
319+
StringReader reader(bytes);
320+
auto message = Message<firestore_client_WriteBatch>::TryParse(&reader);
321+
EXPECT_ANY_THROW(serializer.DecodeMutationBatch(&reader, *message));
322+
}
323+
324+
// TODO(b/174608374): Remove these tests once we perform a schema migration.
325+
TEST_F(LocalSerializerTest, DeleteAndTransformThrowError) {
326+
::firestore::client::WriteBatch batch_proto{};
327+
batch_proto.set_batch_id(42);
328+
*batch_proto.add_writes() = DeleteProto();
329+
*batch_proto.add_writes() = LegacyTransformProto();
330+
*batch_proto.mutable_local_write_time() = WriteTimeProto();
331+
332+
ByteString bytes = ProtobufSerialize(batch_proto);
333+
StringReader reader(bytes);
334+
auto message = Message<firestore_client_WriteBatch>::TryParse(&reader);
335+
EXPECT_ANY_THROW(serializer.DecodeMutationBatch(&reader, *message));
336+
}
337+
338+
// TODO(b/174608374): Remove these tests once we perform a schema migration.
339+
TEST_F(LocalSerializerTest, MultipleMutationsAreSquashed) {
340+
::firestore::client::WriteBatch batch_proto{};
341+
batch_proto.set_batch_id(42);
342+
*batch_proto.add_writes() = SetProto();
343+
*batch_proto.add_writes() = SetProto();
344+
*batch_proto.add_writes() = LegacyTransformProto();
345+
*batch_proto.add_writes() = DeleteProto();
346+
*batch_proto.add_writes() = PatchProto();
347+
*batch_proto.add_writes() = LegacyTransformProto();
348+
*batch_proto.add_writes() = PatchProto();
349+
*batch_proto.mutable_local_write_time() = WriteTimeProto();
350+
351+
ByteString bytes = ProtobufSerialize(batch_proto);
352+
StringReader reader(bytes);
353+
auto message = Message<firestore_client_WriteBatch>::TryParse(&reader);
354+
MutationBatch decoded = serializer.DecodeMutationBatch(&reader, *message);
355+
ASSERT_EQ(5, decoded.mutations().size());
356+
}
357+
203358
TEST_F(LocalSerializerTest, EncodesMutationBatch) {
204359
Mutation base =
205-
PatchMutation(Key("bar/baz"), WrapObject("a", "b"), FieldMask{Field("a")},
360+
PatchMutation(Key("docs/1"), WrapObject("a", "b"), FieldMask{Field("a")},
206361
Precondition::Exists(true));
207362

208-
Mutation set = testutil::SetMutation("foo/bar", Map("a", "b", "num", 1));
363+
Mutation set = testutil::SetMutation("docs/1", Map("a", "b", "num", 1));
209364
Mutation patch =
210-
PatchMutation(Key("bar/baz"), WrapObject("a", "b", "num", 1),
365+
PatchMutation(Key("docs/1"), WrapObject("a", "b", "num", 1),
211366
FieldMask{Field("a")}, Precondition::Exists(true));
212-
Mutation del = testutil::DeleteMutation("baz/quux");
367+
Mutation del = testutil::DeleteMutation("docs/1");
213368

214-
Timestamp write_time = Timestamp::Now();
215-
MutationBatch model(42, write_time, {base}, {set, patch, del});
369+
MutationBatch model(42, write_time_, {base}, {set, patch, del});
216370

217371
v1::Value b_value{};
218372
*b_value.mutable_string_value() = "b";
219-
v1::Value one_value{};
220-
one_value.set_integer_value(1);
221373

222374
v1::Write base_proto{};
223375
*base_proto.mutable_update()->mutable_name() =
224-
"projects/p/databases/d/documents/bar/baz";
376+
"projects/p/databases/d/documents/docs/1";
225377
(*base_proto.mutable_update()->mutable_fields())["a"] = b_value;
226378
base_proto.mutable_update_mask()->add_field_paths("a");
227379
base_proto.mutable_current_document()->set_exists(true);
228380

229-
v1::Write set_proto{};
230-
*set_proto.mutable_update()->mutable_name() =
231-
"projects/p/databases/d/documents/foo/bar";
232-
(*set_proto.mutable_update()->mutable_fields())["a"] = b_value;
233-
(*set_proto.mutable_update()->mutable_fields())["num"] = one_value;
234-
235-
v1::Write patch_proto{};
236-
*patch_proto.mutable_update()->mutable_name() =
237-
"projects/p/databases/d/documents/bar/baz";
238-
(*patch_proto.mutable_update()->mutable_fields())["a"] = b_value;
239-
(*patch_proto.mutable_update()->mutable_fields())["num"] = one_value;
240-
patch_proto.mutable_update_mask()->add_field_paths("a");
241-
patch_proto.mutable_current_document()->set_exists(true);
242-
243-
v1::Write del_proto{};
244-
*del_proto.mutable_delete_() = "projects/p/databases/d/documents/baz/quux";
245-
246-
::google::protobuf::Timestamp write_time_proto{};
247-
write_time_proto.set_seconds(write_time.seconds());
248-
write_time_proto.set_nanos(write_time.nanoseconds());
249-
250381
::firestore::client::WriteBatch batch_proto{};
251382
batch_proto.set_batch_id(42);
252383
*batch_proto.add_base_writes() = base_proto;
253-
*batch_proto.add_writes() = set_proto;
254-
assert(batch_proto.writes(0).update().name() ==
255-
"projects/p/databases/d/documents/foo/bar");
256-
*batch_proto.add_writes() = patch_proto;
257-
*batch_proto.add_writes() = del_proto;
258-
*batch_proto.mutable_local_write_time() = write_time_proto;
384+
*batch_proto.add_writes() = SetProto();
385+
*batch_proto.add_writes() = PatchProto();
386+
*batch_proto.add_writes() = DeleteProto();
387+
*batch_proto.mutable_local_write_time() = WriteTimeProto();
259388

260389
ExpectRoundTrip(model, batch_proto);
261390
}

0 commit comments

Comments
 (0)