Skip to content

Commit c4b4ded

Browse files
authored
Implement SetMutation::ApplyToLocalView (#1928)
Basically, this ports the first mutation test (from java) along with all the necessary bits to make this work.
1 parent dceda76 commit c4b4ded

File tree

7 files changed

+290
-3
lines changed

7 files changed

+290
-3
lines changed

Firestore/Example/Firestore.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; };
2929
132E3EE56C143B2C9ACB6187 /* FSTLevelDBBenchmarkTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E3BB3D5C42282B4ACFB20 /* FSTLevelDBBenchmarkTests.mm */; };
3030
1CAA9012B25F975D445D5978 /* strerror_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */; };
31+
32F022CB75AEE48CDDAF2982 /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; };
3132
333FCB7BB0C9986B5DF28FC8 /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = B1A7E1959AF8141FA7E6B888 /* grpc_stream_tester.cc */; };
3233
36FD4CE79613D18BC783C55B /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; };
3334
3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; };
@@ -542,6 +543,7 @@
542543
B79CA87A1A01FC5329031C9B /* Pods_Firestore_FuzzTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_FuzzTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
543544
B9C261C26C5D311E1E3C0CB9 /* query_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = query_test.cc; sourceTree = "<group>"; };
544545
BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
546+
C8522DE226C467C54E6788D8 /* mutation_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = mutation_test.cc; sourceTree = "<group>"; };
545547
D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
546548
D5B2593BCB52957D62F1C9D3 /* perf_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = perf_spec_test.json; sourceTree = "<group>"; };
547549
DE03B2E91F2149D600A30B9C /* Firestore_IntegrationTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_IntegrationTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1035,6 +1037,7 @@
10351037
B686F2AD2023DDB20028D6BE /* field_path_test.cc */,
10361038
AB356EF6200EA5EB0089B766 /* field_value_test.cc */,
10371039
AB6B908520322E6D00CC290A /* maybe_document_test.cc */,
1040+
C8522DE226C467C54E6788D8 /* mutation_test.cc */,
10381041
AB6B908720322E8800CC290A /* no_document_test.cc */,
10391042
549CCA5520A36E1F00BCEB75 /* precondition_test.cc */,
10401043
B686F2B02024FFD70028D6BE /* resource_path_test.cc */,
@@ -1935,6 +1938,7 @@
19351938
618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */,
19361939
AB6B908620322E6D00CC290A /* maybe_document_test.cc in Sources */,
19371940
618BBEA820B89AAC00B5BCE7 /* mutation.pb.cc in Sources */,
1941+
32F022CB75AEE48CDDAF2982 /* mutation_test.cc in Sources */,
19381942
AB6B908820322E8800CC290A /* no_document_test.cc in Sources */,
19391943
AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */,
19401944
5A080105CCBFDB6BF3F3772D /* path_test.cc in Sources */,

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

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ cc_library(
3030
field_value.h
3131
maybe_document.cc
3232
maybe_document.h
33+
mutation.cc
34+
mutation.h
3335
no_document.cc
3436
no_document.h
3537
precondition.cc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2018 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "Firestore/core/src/firebase/firestore/model/mutation.h"
18+
19+
#include <utility>
20+
21+
#include "Firestore/core/src/firebase/firestore/model/document.h"
22+
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
23+
24+
namespace firebase {
25+
namespace firestore {
26+
namespace model {
27+
28+
Mutation::Mutation(DocumentKey&& key, Precondition&& precondition)
29+
: key_(std::move(key)), precondition_(std::move(precondition)) {
30+
}
31+
32+
void Mutation::VerifyKeyMatches(const MaybeDocument* maybe_doc) const {
33+
if (maybe_doc) {
34+
HARD_ASSERT(maybe_doc->key() == key(),
35+
"Can only apply a mutation to a document with the same key");
36+
}
37+
}
38+
39+
SnapshotVersion Mutation::GetPostMutationVersion(
40+
const MaybeDocument* maybe_doc) {
41+
if (maybe_doc && maybe_doc->type() == MaybeDocument::Type::Document) {
42+
return maybe_doc->version();
43+
} else {
44+
return SnapshotVersion::None();
45+
}
46+
}
47+
48+
SetMutation::SetMutation(DocumentKey&& key,
49+
FieldValue&& value,
50+
Precondition&& precondition)
51+
: Mutation(std::move(key), std::move(precondition)),
52+
value_(std::move(value)) {
53+
}
54+
55+
std::shared_ptr<const MaybeDocument> SetMutation::ApplyToLocalView(
56+
const std::shared_ptr<const MaybeDocument>& maybe_doc,
57+
const MaybeDocument*,
58+
const Timestamp&) const {
59+
VerifyKeyMatches(maybe_doc.get());
60+
61+
if (!precondition().IsValidFor(maybe_doc.get())) {
62+
return maybe_doc;
63+
}
64+
65+
SnapshotVersion version = GetPostMutationVersion(maybe_doc.get());
66+
return absl::make_unique<Document>(FieldValue(value_), key(), version,
67+
/*has_local_mutations=*/true);
68+
}
69+
70+
} // namespace model
71+
} // namespace firestore
72+
} // namespace firebase
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2018 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_MUTATION_H_
18+
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_MUTATION_H_
19+
20+
#include <memory>
21+
22+
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
23+
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
24+
#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
25+
#include "Firestore/core/src/firebase/firestore/model/precondition.h"
26+
#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
27+
28+
namespace firebase {
29+
namespace firestore {
30+
namespace model {
31+
32+
/**
33+
* Represents a Mutation of a document. Different subclasses of Mutation will
34+
* perform different kinds of changes to a base document. For example, a
35+
* SetMutation replaces the value of a document and a DeleteMutation deletes a
36+
* document.
37+
*
38+
* In addition to the value of the document mutations also operate on the
39+
* version. For local mutations (mutations that haven't been committed yet), we
40+
* preserve the existing version for Set, Patch, and Transform mutations. For
41+
* local deletes, we reset the version to 0.
42+
*
43+
* Here's the expected transition table.
44+
*
45+
* MUTATION APPLIED TO RESULTS IN
46+
* SetMutation Document(v3) Document(v3)
47+
* SetMutation NoDocument(v3) Document(v0)
48+
* SetMutation null Document(v0)
49+
* PatchMutation Document(v3) Document(v3)
50+
* PatchMutation NoDocument(v3) NoDocument(v3)
51+
* PatchMutation null null
52+
* TransformMutation Document(v3) Document(v3)
53+
* TransformMutation NoDocument(v3) NoDocument(v3)
54+
* TransformMutation null null
55+
* DeleteMutation Document(v3) NoDocument(v0)
56+
* DeleteMutation NoDocument(v3) NoDocument(v0)
57+
* DeleteMutation null NoDocument(v0)
58+
*
59+
* For acknowledged mutations, we use the updateTime of the WriteResponse as the
60+
* resulting version for Set, Patch, and Transform mutations. As deletes have no
61+
* explicit update time, we use the commitTime of the WriteResponse for
62+
* acknowledged deletes.
63+
*
64+
* If a mutation is acknowledged by the backend but fails the precondition check
65+
* locally, we return an `UnknownDocument` and rely on Watch to send us the
66+
* updated version.
67+
*
68+
* Note that TransformMutations don't create Documents (in the case of being
69+
* applied to a NoDocument), even though they would on the backend. This is
70+
* because the client always combines the TransformMutation with a SetMutation
71+
* or PatchMutation and we only want to apply the transform if the prior
72+
* mutation resulted in a Document (always true for a SetMutation, but not
73+
* necessarily for an PatchMutation).
74+
*/
75+
class Mutation {
76+
public:
77+
virtual ~Mutation() {
78+
}
79+
80+
const DocumentKey& key() const {
81+
return key_;
82+
}
83+
const Precondition& precondition() const {
84+
return precondition_;
85+
}
86+
87+
// TODO(rsgowman): ApplyToRemoteDocument()
88+
89+
/**
90+
* Applies this mutation to the given MaybeDocument for the purposes of
91+
* computing the new local view of a document. Both the input and returned
92+
* documents can be nullptr.
93+
*
94+
* @param maybe_doc The document to mutate. The input document can be nullptr
95+
* if the client has no knowledge of the pre-mutation state of the
96+
* document.
97+
* @param base_doc The state of the document prior to this mutation batch. The
98+
* input document can be nullptr if the client has no knowledge of the
99+
* pre-mutation state of the document.
100+
* @param local_write_time A timestamp indicating the local write time of the
101+
* batch this mutation is a part of.
102+
* @return The mutated document. The returned document may be nullptr, but
103+
* only if maybe_doc was nullptr and the mutation would not create a new
104+
* document.
105+
*/
106+
virtual std::shared_ptr<const MaybeDocument> ApplyToLocalView(
107+
const std::shared_ptr<const MaybeDocument>& maybe_doc,
108+
const MaybeDocument* base_doc,
109+
const Timestamp& local_write_time) const = 0;
110+
111+
protected:
112+
Mutation(DocumentKey&& key, Precondition&& precondition);
113+
114+
void VerifyKeyMatches(const MaybeDocument* maybe_doc) const;
115+
116+
static SnapshotVersion GetPostMutationVersion(const MaybeDocument* maybe_doc);
117+
118+
private:
119+
const DocumentKey key_;
120+
const Precondition precondition_;
121+
};
122+
123+
/**
124+
* A mutation that creates or replaces the document at the given key with the
125+
* object value contents.
126+
*/
127+
class SetMutation : public Mutation {
128+
public:
129+
SetMutation(DocumentKey&& key,
130+
FieldValue&& value,
131+
Precondition&& precondition);
132+
133+
// TODO(rsgowman): ApplyToRemoteDocument()
134+
135+
std::shared_ptr<const MaybeDocument> ApplyToLocalView(
136+
const std::shared_ptr<const MaybeDocument>& maybe_doc,
137+
const MaybeDocument* base_doc,
138+
const Timestamp& local_write_time) const override;
139+
140+
private:
141+
const FieldValue value_;
142+
};
143+
144+
} // namespace model
145+
} // namespace firestore
146+
} // namespace firebase
147+
148+
#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_MUTATION_H_

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

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ cc_test(
2222
field_path_test.cc
2323
field_value_test.cc
2424
maybe_document_test.cc
25+
mutation_test.cc
2526
no_document_test.cc
2627
precondition_test.cc
2728
resource_path_test.cc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2018 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "Firestore/core/src/firebase/firestore/model/mutation.h"
18+
19+
#include <utility>
20+
21+
#include "Firestore/core/src/firebase/firestore/model/document.h"
22+
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
23+
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
24+
#include "gtest/gtest.h"
25+
26+
namespace firebase {
27+
namespace firestore {
28+
namespace model {
29+
30+
using testutil::Doc;
31+
using testutil::SetMutation;
32+
33+
TEST(Mutation, AppliesSetsToDocuments) {
34+
auto base_doc = std::make_shared<Document>(
35+
Doc("collection/key", 0,
36+
{{"foo", FieldValue::FromString("foo-value")},
37+
{"baz", FieldValue::FromString("baz-value")}}));
38+
39+
std::unique_ptr<Mutation> set = SetMutation(
40+
"collection/key", {{"bar", FieldValue::FromString("bar-value")}});
41+
std::shared_ptr<const MaybeDocument> set_doc =
42+
set->ApplyToLocalView(base_doc, base_doc.get(), Timestamp::Now());
43+
ASSERT_TRUE(set_doc);
44+
EXPECT_EQ(
45+
Doc("collection/key", 0, {{"bar", FieldValue::FromString("bar-value")}},
46+
/*has_local_mutations=*/true),
47+
*set_doc.get());
48+
}
49+
50+
} // namespace model
51+
} // namespace firestore
52+
} // namespace firebase

Firestore/core/test/firebase/firestore/testutil/testutil.h

+11-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
3232
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
3333
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
34+
#include "Firestore/core/src/firebase/firestore/model/mutation.h"
3435
#include "Firestore/core/src/firebase/firestore/model/no_document.h"
3536
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
3637
#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@@ -70,10 +71,10 @@ inline model::SnapshotVersion Version(int64_t version) {
7071

7172
inline model::Document Doc(absl::string_view key,
7273
int64_t version = 0,
73-
const model::ObjectValue::Map& data = {}) {
74+
const model::ObjectValue::Map& data = {},
75+
bool has_local_mutations = false) {
7476
return model::Document{model::FieldValue::FromMap(data), Key(key),
75-
Version(version),
76-
/* has_local_mutations= */ false};
77+
Version(version), has_local_mutations};
7778
}
7879

7980
inline model::NoDocument DeletedDoc(absl::string_view key, int64_t version) {
@@ -123,6 +124,13 @@ inline core::Query Query(absl::string_view path) {
123124
return core::Query::AtPath(Resource(path));
124125
}
125126

127+
inline std::unique_ptr<model::SetMutation> SetMutation(
128+
absl::string_view path, const model::ObjectValue::Map& values = {}) {
129+
return absl::make_unique<model::SetMutation>(
130+
Key(path), model::FieldValue::FromMap(values),
131+
model::Precondition::None());
132+
}
133+
126134
inline std::vector<uint8_t> ResumeToken(int64_t snapshot_version) {
127135
if (snapshot_version == 0) {
128136
// TODO(rsgowman): The other platforms return null here, though I'm not sure

0 commit comments

Comments
 (0)