Skip to content

Commit 42e32e7

Browse files
authored
Merge pull request #1562 from firebase/wilhuff/cherry-picks
Prepare Firestore release 0.12.6
2 parents acba81f + 9cf8259 commit 42e32e7

21 files changed

+2620
-472
lines changed

Firestore/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Unreleased
22

3+
# v0.12.6
4+
- [fixed] Fixed an issue where queries returned fewer results than they should,
5+
caused by documents that were cached as deleted when they should not have
6+
been (#1548). Some cache data is cleared and so clients may use extra
7+
bandwidth the first time they launch with this version of the SDK.
8+
39
# v0.12.5
410
- [changed] Internal improvements.
511

Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm

Lines changed: 119 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,29 @@
1818

1919
#include <memory>
2020
#include <string>
21+
#include <vector>
2122

2223
#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
2324
#import "Firestore/Source/Local/FSTLevelDB.h"
2425
#import "Firestore/Source/Local/FSTLevelDBKey.h"
2526
#import "Firestore/Source/Local/FSTLevelDBMigrations.h"
27+
#import "Firestore/Source/Local/FSTLevelDBMutationQueue.h"
2628
#import "Firestore/Source/Local/FSTLevelDBQueryCache.h"
2729

2830
#include "Firestore/core/src/firebase/firestore/util/ordered_code.h"
31+
#include "Firestore/core/src/firebase/firestore/util/status.h"
32+
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
33+
#include "absl/strings/match.h"
2934
#include "leveldb/db.h"
3035

3136
#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
3237

3338
NS_ASSUME_NONNULL_BEGIN
3439

40+
using firebase::firestore::FirestoreErrorCode;
3541
using firebase::firestore::local::LevelDbTransaction;
3642
using firebase::firestore::util::OrderedCode;
43+
using firebase::firestore::testutil::Key;
3744
using leveldb::DB;
3845
using leveldb::Options;
3946
using leveldb::Status;
@@ -64,54 +71,136 @@ - (void)tearDown {
6471
- (void)testAddsTargetGlobal {
6572
FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()];
6673
XCTAssertNil(metadata, @"Not expecting metadata yet, we should have an empty db");
67-
LevelDbTransaction transaction(_db.get(), "testAddsTargetGlobal");
68-
[FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
69-
transaction.Commit();
74+
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get()];
75+
7076
metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()];
7177
XCTAssertNotNil(metadata, @"Migrations should have added the metadata");
7278
}
7379

7480
- (void)testSetsVersionNumber {
75-
LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber");
76-
FSTLevelDBSchemaVersion initial =
77-
[FSTLevelDBMigrations schemaVersionWithTransaction:&transaction];
78-
XCTAssertEqual(0, initial, "No version should be equivalent to 0");
79-
80-
// Pick an arbitrary high migration number and migrate to it.
81-
[FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
82-
FSTLevelDBSchemaVersion actual = [FSTLevelDBMigrations schemaVersionWithTransaction:&transaction];
83-
XCTAssertGreaterThan(actual, 0, @"Expected to migrate to a schema version > 0");
81+
{
82+
LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber before");
83+
FSTLevelDBSchemaVersion initial =
84+
[FSTLevelDBMigrations schemaVersionWithTransaction:&transaction];
85+
XCTAssertEqual(0, initial, "No version should be equivalent to 0");
86+
}
87+
88+
{
89+
// Pick an arbitrary high migration number and migrate to it.
90+
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get()];
91+
92+
LevelDbTransaction transaction(_db.get(), "testSetsVersionNumber after");
93+
FSTLevelDBSchemaVersion actual =
94+
[FSTLevelDBMigrations schemaVersionWithTransaction:&transaction];
95+
XCTAssertGreaterThan(actual, 0, @"Expected to migrate to a schema version > 0");
96+
}
8497
}
8598

86-
- (void)testCountsQueries {
87-
NSUInteger expected = 50;
99+
#define ASSERT_NOT_FOUND(transaction, key) \
100+
do { \
101+
std::string unused_result; \
102+
Status status = transaction.Get(key, &unused_result); \
103+
XCTAssertTrue(status.IsNotFound()); \
104+
} while (0)
105+
106+
#define ASSERT_FOUND(transaction, key) \
107+
do { \
108+
std::string unused_result; \
109+
Status status = transaction.Get(key, &unused_result); \
110+
XCTAssertTrue(status.ok()); \
111+
} while (0)
112+
113+
- (void)testDropsTheQueryCache {
114+
NSString *userID = @"user";
115+
FSTBatchID batchID = 1;
116+
FSTTargetID targetID = 2;
117+
118+
FSTDocumentKey *key1 = Key("documents/1");
119+
FSTDocumentKey *key2 = Key("documents/2");
120+
121+
std::string targetKeys[] = {
122+
[FSTLevelDBTargetKey keyWithTargetID:targetID],
123+
[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key1],
124+
[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key2],
125+
[FSTLevelDBDocumentTargetKey keyWithDocumentKey:key1 targetID:targetID],
126+
[FSTLevelDBDocumentTargetKey keyWithDocumentKey:key2 targetID:targetID],
127+
[FSTLevelDBQueryTargetKey keyWithCanonicalID:"foo.bar.baz" targetID:targetID],
128+
};
129+
130+
// Keys that should not be modified by the dropping the query cache
131+
std::string preservedKeys[] = {
132+
[self dummyKeyForTable:"targetA"],
133+
[FSTLevelDBMutationQueueKey keyWithUserID:userID],
134+
[FSTLevelDBMutationKey keyWithUserID:userID batchID:batchID],
135+
};
136+
137+
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:2];
88138
{
89139
// Setup some targets to be counted in the migration.
90-
LevelDbTransaction transaction(_db.get(), "testCountsQueries setup");
91-
for (int i = 0; i < expected; i++) {
92-
std::string key = [FSTLevelDBTargetKey keyWithTargetID:i];
93-
transaction.Put(key, "dummy");
140+
LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCache setup");
141+
for (const std::string &key : targetKeys) {
142+
transaction.Put(key, "target");
143+
}
144+
for (const std::string &key : preservedKeys) {
145+
transaction.Put(key, "preserved");
94146
}
95-
// Add a dummy entry after the targets to make sure the iteration is correctly bounded.
96-
// Use a table that would sort logically right after that table 'target'.
97-
std::string dummyKey;
98-
// Magic number that indicates a table name follows. Needed to mimic the prefix to the target
99-
// table.
100-
OrderedCode::WriteSignedNumIncreasing(&dummyKey, 5);
101-
OrderedCode::WriteString(&dummyKey, "targetA");
102-
transaction.Put(dummyKey, "dummy");
103147
transaction.Commit();
104148
}
105149

150+
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:3];
106151
{
107-
LevelDbTransaction transaction(_db.get(), "testCountsQueries");
108-
[FSTLevelDBMigrations runMigrationsWithTransaction:&transaction];
109-
transaction.Commit();
152+
LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCache");
153+
for (const std::string &key : targetKeys) {
154+
ASSERT_NOT_FOUND(transaction, key);
155+
}
156+
for (const std::string &key : preservedKeys) {
157+
ASSERT_FOUND(transaction, key);
158+
}
159+
110160
FSTPBTargetGlobal *metadata = [FSTLevelDBQueryCache readTargetMetadataFromDB:_db.get()];
111-
XCTAssertEqual(expected, metadata.targetCount, @"Failed to count all of the targets we added");
161+
XCTAssertNotNil(metadata, @"Metadata should have been added");
162+
XCTAssertEqual(metadata.targetCount, 0);
112163
}
113164
}
114165

166+
- (void)testDropsTheQueryCacheWithThousandsOfEntries {
167+
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:2];
168+
{
169+
// Setup some targets to be destroyed.
170+
LevelDbTransaction transaction(_db.get(), "testDropsTheQueryCacheWithThousandsOfEntries setup");
171+
for (int i = 0; i < 10000; ++i) {
172+
transaction.Put([FSTLevelDBTargetKey keyWithTargetID:i], "");
173+
}
174+
transaction.Commit();
175+
}
176+
177+
[FSTLevelDBMigrations runMigrationsWithDatabase:_db.get() upToVersion:3];
178+
{
179+
LevelDbTransaction transaction(_db.get(), "Verify");
180+
std::string prefix = [FSTLevelDBTargetKey keyPrefix];
181+
182+
auto it = transaction.NewIterator();
183+
std::vector<std::string> found_keys;
184+
for (it->Seek(prefix); it->Valid() && absl::StartsWith(it->key(), prefix); it->Next()) {
185+
found_keys.push_back(std::string{it->key()});
186+
}
187+
188+
XCTAssertEqual(found_keys, std::vector<std::string>{});
189+
}
190+
}
191+
192+
/**
193+
* Creates the name of a dummy entry to make sure the iteration is correctly bounded.
194+
*/
195+
- (std::string)dummyKeyForTable:(const char *)tableName {
196+
std::string dummyKey;
197+
// Magic number that indicates a table name follows. Needed to mimic the prefix to the target
198+
// table.
199+
OrderedCode::WriteSignedNumIncreasing(&dummyKey, 5);
200+
OrderedCode::WriteString(&dummyKey, tableName);
201+
return dummyKey;
202+
}
203+
115204
@end
116205

117206
NS_ASSUME_NONNULL_END

Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ - (void)failStreamWithError:(NSError *)error {
138138

139139
#pragma mark - Helper methods.
140140

141-
- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(const SnapshotVersion &)snap {
141+
- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(SnapshotVersion)snap {
142142
if ([change isKindOfClass:[FSTWatchTargetChange class]]) {
143143
FSTWatchTargetChange *targetChange = (FSTWatchTargetChange *)change;
144144
if (targetChange.cause) {
@@ -152,6 +152,11 @@ - (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(const Snapsho
152152
[self.activeTargets removeObjectForKey:targetID];
153153
}
154154
}
155+
if ([targetChange.targetIDs count] != 0) {
156+
// If the list of target IDs is not empty, we reset the snapshot version to NONE as
157+
// done in `FSTSerializerBeta.versionFromListenResponse:`.
158+
snap = SnapshotVersion::None();
159+
}
155160
}
156161
[self.delegate watchStreamDidChange:change snapshotVersion:snap];
157162
}

Firestore/Example/Tests/SpecTests/FSTSpecTests.mm

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -194,25 +194,25 @@ - (void)doDelete:(NSString *)key {
194194
[self.driver writeUserMutation:FSTTestDeleteMutation(key)];
195195
}
196196

197-
- (void)doWatchAck:(NSArray<NSNumber *> *)ackedTargets snapshot:(NSNumber *)watchSnapshot {
197+
- (void)doWatchAck:(NSArray<NSNumber *> *)ackedTargets {
198198
FSTWatchTargetChange *change =
199199
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateAdded
200200
targetIDs:ackedTargets
201201
cause:nil];
202-
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
202+
[self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()];
203203
}
204204

205-
- (void)doWatchCurrent:(NSArray<id> *)currentSpec snapshot:(NSNumber *)watchSnapshot {
205+
- (void)doWatchCurrent:(NSArray<id> *)currentSpec {
206206
NSArray<NSNumber *> *currentTargets = currentSpec[0];
207207
NSData *resumeToken = [currentSpec[1] dataUsingEncoding:NSUTF8StringEncoding];
208208
FSTWatchTargetChange *change =
209209
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
210210
targetIDs:currentTargets
211211
resumeToken:resumeToken];
212-
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
212+
[self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()];
213213
}
214214

215-
- (void)doWatchRemove:(NSDictionary *)watchRemoveSpec snapshot:(NSNumber *)watchSnapshot {
215+
- (void)doWatchRemove:(NSDictionary *)watchRemoveSpec {
216216
NSError *error = nil;
217217
NSDictionary *cause = watchRemoveSpec[@"cause"];
218218
if (cause) {
@@ -226,19 +226,16 @@ - (void)doWatchRemove:(NSDictionary *)watchRemoveSpec snapshot:(NSNumber *)watch
226226
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateRemoved
227227
targetIDs:watchRemoveSpec[@"targetIds"]
228228
cause:error];
229-
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
229+
[self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()];
230230
// Unlike web, the FSTMockDatastore detects a watch removal with cause and will remove active
231231
// targets
232232
}
233233

234-
- (void)doWatchEntity:(NSDictionary *)watchEntity snapshot:(NSNumber *_Nullable)watchSnapshot {
234+
- (void)doWatchEntity:(NSDictionary *)watchEntity {
235235
if (watchEntity[@"docs"]) {
236236
HARD_ASSERT(!watchEntity[@"doc"], "Exactly one of |doc| or |docs| needs to be set.");
237-
int count = 0;
238237
NSArray *docs = watchEntity[@"docs"];
239238
for (NSDictionary *doc in docs) {
240-
count++;
241-
bool isLast = (count == docs.count);
242239
NSMutableDictionary *watchSpec = [NSMutableDictionary dictionary];
243240
watchSpec[@"doc"] = doc;
244241
if (watchEntity[@"targets"]) {
@@ -247,11 +244,7 @@ - (void)doWatchEntity:(NSDictionary *)watchEntity snapshot:(NSNumber *_Nullable)
247244
if (watchEntity[@"removedTargets"]) {
248245
watchSpec[@"removedTargets"] = watchEntity[@"removedTargets"];
249246
}
250-
NSNumber *_Nullable version = nil;
251-
if (isLast) {
252-
version = watchSnapshot;
253-
}
254-
[self doWatchEntity:watchSpec snapshot:version];
247+
[self doWatchEntity:watchSpec];
255248
}
256249
} else if (watchEntity[@"doc"]) {
257250
NSArray *docSpec = watchEntity[@"doc"];
@@ -270,21 +263,21 @@ - (void)doWatchEntity:(NSDictionary *)watchEntity snapshot:(NSNumber *_Nullable)
270263
removedTargetIDs:watchEntity[@"removedTargets"]
271264
documentKey:doc.key
272265
document:doc];
273-
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
266+
[self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()];
274267
} else if (watchEntity[@"key"]) {
275268
FSTDocumentKey *docKey = FSTTestDocKey(watchEntity[@"key"]);
276269
FSTWatchChange *change =
277270
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
278271
removedTargetIDs:watchEntity[@"removedTargets"]
279272
documentKey:docKey
280273
document:nil];
281-
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
274+
[self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()];
282275
} else {
283276
HARD_FAIL("Either key, doc or docs must be set.");
284277
}
285278
}
286279

287-
- (void)doWatchFilter:(NSArray *)watchFilter snapshot:(NSNumber *_Nullable)watchSnapshot {
280+
- (void)doWatchFilter:(NSArray *)watchFilter {
288281
NSArray<NSNumber *> *targets = watchFilter[0];
289282
HARD_ASSERT(targets.count == 1, "ExistenceFilters currently support exactly one target only.");
290283

@@ -294,15 +287,29 @@ - (void)doWatchFilter:(NSArray *)watchFilter snapshot:(NSNumber *_Nullable)watch
294287
FSTExistenceFilter *filter = [FSTExistenceFilter filterWithCount:keyCount];
295288
FSTExistenceFilterWatchChange *change =
296289
[FSTExistenceFilterWatchChange changeWithFilter:filter targetID:targets[0].intValue];
297-
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
290+
[self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()];
298291
}
299292

300-
- (void)doWatchReset:(NSArray<NSNumber *> *)watchReset snapshot:(NSNumber *_Nullable)watchSnapshot {
293+
- (void)doWatchReset:(NSArray<NSNumber *> *)watchReset {
301294
FSTWatchTargetChange *change =
302295
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
303296
targetIDs:watchReset
304297
cause:nil];
305-
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
298+
[self.driver receiveWatchChange:change snapshotVersion:SnapshotVersion::None()];
299+
}
300+
301+
- (void)doWatchSnapshot:(NSDictionary *)watchSnapshot {
302+
// The client will only respond to watchSnapshots if they are on a target change with an empty
303+
// set of target IDs.
304+
NSArray<NSNumber *> *targetIDs =
305+
watchSnapshot[@"targetIds"] ? watchSnapshot[@"targetIds"] : [NSArray array];
306+
NSData *resumeToken = [watchSnapshot[@"resumeToken"] dataUsingEncoding:NSUTF8StringEncoding];
307+
FSTWatchTargetChange *change =
308+
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange
309+
targetIDs:targetIDs
310+
resumeToken:resumeToken];
311+
[self.driver receiveWatchChange:change
312+
snapshotVersion:[self parseVersion:watchSnapshot[@"version"]]];
306313
}
307314

308315
- (void)doWatchStreamClose:(NSDictionary *)closeSpec {
@@ -415,17 +422,19 @@ - (void)doStep:(NSDictionary *)step {
415422
} else if (step[@"userDelete"]) {
416423
[self doDelete:step[@"userDelete"]];
417424
} else if (step[@"watchAck"]) {
418-
[self doWatchAck:step[@"watchAck"] snapshot:step[@"watchSnapshot"]];
425+
[self doWatchAck:step[@"watchAck"]];
419426
} else if (step[@"watchCurrent"]) {
420-
[self doWatchCurrent:step[@"watchCurrent"] snapshot:step[@"watchSnapshot"]];
427+
[self doWatchCurrent:step[@"watchCurrent"]];
421428
} else if (step[@"watchRemove"]) {
422-
[self doWatchRemove:step[@"watchRemove"] snapshot:step[@"watchSnapshot"]];
429+
[self doWatchRemove:step[@"watchRemove"]];
423430
} else if (step[@"watchEntity"]) {
424-
[self doWatchEntity:step[@"watchEntity"] snapshot:step[@"watchSnapshot"]];
431+
[self doWatchEntity:step[@"watchEntity"]];
425432
} else if (step[@"watchFilter"]) {
426-
[self doWatchFilter:step[@"watchFilter"] snapshot:step[@"watchSnapshot"]];
433+
[self doWatchFilter:step[@"watchFilter"]];
427434
} else if (step[@"watchReset"]) {
428-
[self doWatchReset:step[@"watchReset"] snapshot:step[@"watchSnapshot"]];
435+
[self doWatchReset:step[@"watchReset"]];
436+
} else if (step[@"watchSnapshot"]) {
437+
[self doWatchSnapshot:step[@"watchSnapshot"]];
429438
} else if (step[@"watchStreamClose"]) {
430439
[self doWatchStreamClose:step[@"watchStreamClose"]];
431440
} else if (step[@"watchProto"]) {

Firestore/Example/Tests/SpecTests/json/collection_spec_test.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,12 @@
5656
2
5757
],
5858
"resume-token-1001"
59-
],
60-
"watchSnapshot": 1001,
59+
]
60+
},
61+
{
62+
"watchSnapshot": {
63+
"version": 1001
64+
},
6165
"expect": [
6266
{
6367
"query": {

0 commit comments

Comments
 (0)