Skip to content

Commit 1d8d86e

Browse files
author
Michael Lehenbauer
committed
LevelDB schema changes for CollectionGroup queries.
1 parent 0f32878 commit 1d8d86e

File tree

5 files changed

+267
-11
lines changed

5 files changed

+267
-11
lines changed

Firestore/Example/Tests/Local/FSTLevelDBMigrationsTests.mm

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#import <XCTest/XCTest.h>
1818

19+
#include <map>
1920
#include <memory>
2021
#include <string>
2122
#include <vector>
@@ -37,6 +38,7 @@
3738
NS_ASSUME_NONNULL_BEGIN
3839

3940
using firebase::firestore::FirestoreErrorCode;
41+
using firebase::firestore::local::LevelDbCollectionParentKey;
4042
using firebase::firestore::local::LevelDbDocumentMutationKey;
4143
using firebase::firestore::local::LevelDbDocumentTargetKey;
4244
using firebase::firestore::local::LevelDbMigrations;
@@ -334,7 +336,6 @@ - (void)testRemovesMutationBatches {
334336
// Verify
335337
std::string buffer;
336338
LevelDbTransaction transaction(_db.get(), "Verify");
337-
auto it = transaction.NewIterator();
338339
// verify that we deleted the correct batches
339340
XCTAssertTrue(transaction.Get(LevelDbMutationKey::Key("foo", 1), &buffer).IsNotFound());
340341
XCTAssertTrue(transaction.Get(LevelDbMutationKey::Key("foo", 2), &buffer).IsNotFound());
@@ -360,6 +361,60 @@ - (void)testRemovesMutationBatches {
360361
}
361362
}
362363

364+
- (void)testCreateCollectionParentsIndex {
365+
// This test creates a database with schema version 5 that has a few
366+
// mutations and a few remote documents and then ensures that appropriate
367+
// entries are written to the collectionParentIndex.
368+
std::vector<std::string> write_paths{"cg1/x", "cg1/y", "cg1/x/cg1/x", "cg2/x", "cg1/x/cg2/x"};
369+
std::vector<std::string> remote_doc_paths{"cg1/z", "cg1/y/cg1/x", "cg2/x/cg3/x",
370+
"blah/x/blah/x/cg3/x"};
371+
std::map<std::string, std::vector<std::string>> expected_parents{
372+
{"cg1", {"", "cg1/x", "cg1/y"}}, {"cg2", {"", "cg1/x"}}, {"cg3", {"blah/x/blah/x", "cg2/x"}}};
373+
374+
std::string empty_buffer;
375+
LevelDbMigrations::RunMigrations(_db.get(), 5);
376+
{
377+
LevelDbTransaction transaction(_db.get(), "Write Mutations and Remote Documents");
378+
// Write mutations.
379+
for (auto write_path : write_paths) {
380+
// We "cheat" and only write the DbDocumentMutation index entries, since
381+
// that's all the migration uses.
382+
DocumentKey key = DocumentKey::FromPathString(write_path);
383+
transaction.Put(LevelDbDocumentMutationKey::Key("dummy-uid", key, /*dummy batchId=*/123),
384+
empty_buffer);
385+
}
386+
387+
// Write remote document entries.
388+
for (auto remote_doc_path : remote_doc_paths) {
389+
DocumentKey key = DocumentKey::FromPathString(remote_doc_path);
390+
transaction.Put(LevelDbRemoteDocumentKey::Key(key), empty_buffer);
391+
}
392+
393+
transaction.Commit();
394+
}
395+
396+
// Migrate to v6 and verify index entries.
397+
LevelDbMigrations::RunMigrations(_db.get(), 6);
398+
{
399+
LevelDbTransaction transaction(_db.get(), "Verify");
400+
401+
std::map<std::string, std::vector<std::string>> actual_parents;
402+
auto index_iterator = transaction.NewIterator();
403+
std::string index_prefix = LevelDbCollectionParentKey::KeyPrefix();
404+
LevelDbCollectionParentKey row_key;
405+
for (index_iterator->Seek(index_prefix); index_iterator->Valid(); index_iterator->Next()) {
406+
if (!absl::StartsWith(index_iterator->key(), index_prefix) ||
407+
!row_key.Decode(index_iterator->key()))
408+
break;
409+
410+
std::vector<std::string> &parents = actual_parents[row_key.collection_id()];
411+
parents.push_back(row_key.parent().CanonicalString());
412+
}
413+
414+
XCTAssertEqual(actual_parents, expected_parents);
415+
}
416+
}
417+
363418
- (void)testCanDowngrade {
364419
// First, run all of the migrations
365420
LevelDbMigrations::RunMigrations(_db.get());

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

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const char* kQueryTargetsTable = "query_target";
4545
const char* kTargetDocumentsTable = "target_document";
4646
const char* kDocumentTargetsTable = "document_target";
4747
const char* kRemoteDocumentsTable = "remote_document";
48+
const char* kCollectionParentsTable = "collection_parent";
4849

4950
/**
5051
* Labels for the components of keys. These serve to make keys self-describing.
@@ -89,6 +90,12 @@ enum ComponentLabel {
8990
/** A component containing a user Id. */
9091
UserId = 13,
9192

93+
/**
94+
* A component containing a standalone collection ID (e.g. as used by the
95+
* collection_parent table, but not for collection IDs within paths).
96+
*/
97+
CollectionId = 14,
98+
9299
/**
93100
* A path segment describes just a single segment in a resource path. Path
94101
* segments that occur sequentially in a key represent successive segments in
@@ -159,6 +166,23 @@ class Reader {
159166
return ReadLabeledString(ComponentLabel::UserId);
160167
}
161168

169+
std::string ReadCollectionId() {
170+
return ReadLabeledString(ComponentLabel::CollectionId);
171+
}
172+
173+
/**
174+
* Reads component labels and strings from the key until it finds a component
175+
* label other than ComponentLabel::PathSegment (or the key is exhausted).
176+
* All matched path segments are assembled into a ResourcePath.
177+
*
178+
* If the read is unsuccessful, returns a default ResourcePath and fails the
179+
* Reader.
180+
*
181+
* Otherwise returns the decoded ResourcePath and the Reader advances to the
182+
* next unread byte.
183+
*/
184+
ResourcePath ReadResourcePath();
185+
162186
/**
163187
* Reads component labels and strings from the key until it finds a component
164188
* label other than ComponentLabel::PathSegment (or the key is exhausted).
@@ -358,7 +382,7 @@ class Reader {
358382
bool ok_;
359383
};
360384

361-
DocumentKey Reader::ReadDocumentKey() {
385+
ResourcePath Reader::ReadResourcePath() {
362386
std::vector<std::string> path_segments;
363387
while (!empty()) {
364388
// Advance a temporary slice to avoid advancing contents into the next key
@@ -375,7 +399,16 @@ DocumentKey Reader::ReadDocumentKey() {
375399
path_segments.push_back(std::move(segment));
376400
}
377401

378-
ResourcePath path{std::move(path_segments)};
402+
if (ok_) {
403+
return ResourcePath{std::move(path_segments)};
404+
} else {
405+
return ResourcePath{};
406+
}
407+
}
408+
409+
DocumentKey Reader::ReadDocumentKey() {
410+
ResourcePath path = ReadResourcePath();
411+
379412
if (ok_ && !path.empty() && DocumentKey::IsDocumentKey(path)) {
380413
return DocumentKey{std::move(path)};
381414
}
@@ -419,10 +452,10 @@ std::string Reader::Describe() {
419452
src_ = saved_source;
420453

421454
if (label == ComponentLabel::PathSegment) {
422-
DocumentKey document_key = ReadDocumentKey();
455+
ResourcePath resource_path = ReadResourcePath();
423456
if (ok_) {
424457
absl::StrAppend(&description,
425-
" key=", document_key.path().CanonicalString());
458+
" path=", resource_path.CanonicalString());
426459
}
427460

428461
} else if (label == ComponentLabel::TableName) {
@@ -455,6 +488,12 @@ std::string Reader::Describe() {
455488
absl::StrAppend(&description, " user_id=", user_id);
456489
}
457490

491+
} else if (label == ComponentLabel::CollectionId) {
492+
std::string collection_id = ReadCollectionId();
493+
if (ok_) {
494+
absl::StrAppend(&description, " collection_id=", collection_id);
495+
}
496+
458497
} else {
459498
absl::StrAppend(&description, " unknown label=", static_cast<int>(label));
460499
Fail();
@@ -502,6 +541,10 @@ class Writer {
502541
WriteLabeledString(ComponentLabel::UserId, user_id);
503542
}
504543

544+
void WriteCollectionId(absl::string_view collection_id) {
545+
WriteLabeledString(ComponentLabel::CollectionId, collection_id);
546+
}
547+
505548
/**
506549
* For each segment in the given resource path writes a
507550
* ComponentLabel::PathSegment component label and a string containing the
@@ -848,6 +891,39 @@ bool LevelDbRemoteDocumentKey::Decode(absl::string_view key) {
848891
return reader.ok();
849892
}
850893

894+
std::string LevelDbCollectionParentKey::KeyPrefix() {
895+
Writer writer;
896+
writer.WriteTableName(kCollectionParentsTable);
897+
return writer.result();
898+
}
899+
900+
std::string LevelDbCollectionParentKey::KeyPrefix(
901+
absl::string_view collection_id) {
902+
Writer writer;
903+
writer.WriteTableName(kCollectionParentsTable);
904+
writer.WriteCollectionId(collection_id);
905+
return writer.result();
906+
}
907+
908+
std::string LevelDbCollectionParentKey::Key(absl::string_view collection_id,
909+
const ResourcePath& parent) {
910+
Writer writer;
911+
writer.WriteTableName(kCollectionParentsTable);
912+
writer.WriteCollectionId(collection_id);
913+
writer.WriteResourcePath(parent);
914+
writer.WriteTerminator();
915+
return writer.result();
916+
}
917+
918+
bool LevelDbCollectionParentKey::Decode(absl::string_view key) {
919+
Reader reader{key};
920+
reader.ReadTableNameMatching(kCollectionParentsTable);
921+
collection_id_ = reader.ReadCollectionId();
922+
parent_ = reader.ReadResourcePath();
923+
reader.ReadTerminator();
924+
return reader.ok();
925+
}
926+
851927
} // namespace local
852928
} // namespace firestore
853929
} // namespace firebase

Firestore/core/src/firebase/firestore/local/leveldb_key.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ namespace local {
7777
// remote_documents:
7878
// - table_name: string = "remote_document"
7979
// - path: ResourcePath
80+
//
81+
// collection_parents:
82+
// - table_name: string = "collection_parent"
83+
// - collectionId: string
84+
// - parent: ResourcePath
8085

8186
/**
8287
* Parses the given key and returns a human readable description of its
@@ -520,6 +525,58 @@ class LevelDbRemoteDocumentKey {
520525
model::DocumentKey document_key_;
521526
};
522527

528+
/**
529+
* A key in the collection parents index, which stores an association between a
530+
* Collection ID (e.g. 'messages') to a parent path (e.g. '/chats/123') that
531+
* contains it as a (sub)collection. This is used to efficiently find all
532+
* collections to query when performing a Collection Group query.
533+
*/
534+
class LevelDbCollectionParentKey {
535+
public:
536+
/**
537+
* Creates a key prefix that points just before the first key in the table.
538+
*/
539+
static std::string KeyPrefix();
540+
541+
/**
542+
* Creates a key prefix that points just before the first key for the given
543+
* collection_id.
544+
*/
545+
static std::string KeyPrefix(absl::string_view collection_id);
546+
547+
/**
548+
* Creates a complete key that points to a specific collection_id and parent.
549+
*/
550+
static std::string Key(absl::string_view collection_id,
551+
const model::ResourcePath& parent);
552+
553+
/**
554+
* Decodes the given complete key, storing the decoded values in this
555+
* instance.
556+
*
557+
* @return true if the key successfully decoded, false otherwise. If false is
558+
* returned, this instance is in an undefined state until the next call to
559+
* `Decode()`.
560+
*/
561+
ABSL_MUST_USE_RESULT
562+
bool Decode(absl::string_view key);
563+
564+
/** The collection_id, as encoded in the key. */
565+
const std::string& collection_id() const {
566+
return collection_id_;
567+
}
568+
569+
/** The parent path, as encoded in the key. */
570+
const model::ResourcePath& parent() const {
571+
return parent_;
572+
}
573+
574+
private:
575+
// Deliberately uninitialized: will be assigned in Decode
576+
std::string collection_id_;
577+
model::ResourcePath parent_;
578+
};
579+
523580
} // namespace local
524581
} // namespace firestore
525582
} // namespace firebase

0 commit comments

Comments
 (0)