Skip to content

Commit fd5678b

Browse files
Track readTime in the RemoteDocument store
1 parent f03eb7c commit fd5678b

16 files changed

+298
-96
lines changed

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { QueryData } from './query_data';
7070
import { ReferenceSet } from './reference_set';
7171
import { ClientId } from './shared_client_state';
7272
import { SimpleDb, SimpleDbStore, SimpleDbTransaction } from './simple_db';
73+
import { SnapshotVersion } from '../core/snapshot_version';
7374

7475
const LOG_TAG = 'IndexedDbPersistence';
7576

@@ -1263,7 +1264,7 @@ export class IndexedDbLruDelegate implements ReferenceDelegate, LruDelegate {
12631264

12641265
return iteration
12651266
.next(() => PersistencePromise.waitFor(promises))
1266-
.next(() => changeBuffer.apply(txn))
1267+
.next(() => changeBuffer.apply(txn, SnapshotVersion.forDeletedDoc()))
12671268
.next(() => documentCount);
12681269
}
12691270

packages/firestore/src/local/indexeddb_remote_document_cache.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,8 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
462462
}
463463

464464
protected applyChanges(
465-
transaction: PersistenceTransaction
465+
transaction: PersistenceTransaction,
466+
readTime: SnapshotVersion
466467
): PersistencePromise<void> {
467468
const promises: Array<PersistencePromise<void>> = [];
468469

@@ -477,7 +478,8 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
477478
);
478479
if (maybeDocument) {
479480
const doc = this.documentCache.serializer.toDbRemoteDocument(
480-
maybeDocument
481+
maybeDocument,
482+
readTime
481483
);
482484
const size = dbDocumentSize(doc);
483485
sizeDelta += size - previousSize!;

packages/firestore/src/local/indexeddb_schema.ts

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ import { SimpleDbSchemaConverter, SimpleDbTransaction } from './simple_db';
4545
* 6. Create document global for tracking document cache size.
4646
* 7. Ensure every cached document has a sentinel row with a sequence number.
4747
* 8. Add collection-parent index for Collection Group queries.
48+
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
49+
* an auto-incrementing ID. This is required for Index-Free queries.
4850
*/
49-
export const SCHEMA_VERSION = 8;
51+
export const SCHEMA_VERSION = 9;
5052

5153
/** Performs database creation and schema upgrades. */
5254
export class SchemaConverter implements SimpleDbSchemaConverter {
@@ -61,7 +63,7 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
6163
*/
6264
createOrUpgrade(
6365
db: IDBDatabase,
64-
txn: SimpleDbTransaction,
66+
txn: IDBTransaction,
6567
fromVersion: number,
6668
toVersion: number
6769
): PersistencePromise<void> {
@@ -72,6 +74,8 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
7274
`Unexpected schema upgrade from v${fromVersion} to v{toVersion}.`
7375
);
7476

77+
const simpleDbTransaction = new SimpleDbTransaction(txn);
78+
7579
if (fromVersion < 1 && toVersion >= 1) {
7680
createPrimaryClientStore(db);
7781
createMutationQueue(db);
@@ -90,7 +94,7 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
9094
dropQueryCache(db);
9195
createQueryCache(db);
9296
}
93-
p = p.next(() => writeEmptyTargetGlobalEntry(txn));
97+
p = p.next(() => writeEmptyTargetGlobalEntry(simpleDbTransaction));
9498
}
9599

96100
if (fromVersion < 4 && toVersion >= 4) {
@@ -101,7 +105,9 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
101105
// and write them back out. We preserve the existing batch IDs to guarantee
102106
// consistency with other object stores. Any further mutation batch IDs will
103107
// be auto-generated.
104-
p = p.next(() => upgradeMutationBatchSchemaAndMigrateData(db, txn));
108+
p = p.next(() =>
109+
upgradeMutationBatchSchemaAndMigrateData(db, simpleDbTransaction)
110+
);
105111
}
106112

107113
p = p.next(() => {
@@ -111,24 +117,31 @@ export class SchemaConverter implements SimpleDbSchemaConverter {
111117
}
112118

113119
if (fromVersion < 5 && toVersion >= 5) {
114-
p = p.next(() => this.removeAcknowledgedMutations(txn));
120+
p = p.next(() => this.removeAcknowledgedMutations(simpleDbTransaction));
115121
}
116122

117123
if (fromVersion < 6 && toVersion >= 6) {
118124
p = p.next(() => {
119125
createDocumentGlobalStore(db);
120-
return this.addDocumentGlobal(txn);
126+
return this.addDocumentGlobal(simpleDbTransaction);
121127
});
122128
}
123129

124130
if (fromVersion < 7 && toVersion >= 7) {
125-
p = p.next(() => this.ensureSequenceNumbers(txn));
131+
p = p.next(() => this.ensureSequenceNumbers(simpleDbTransaction));
126132
}
127133

128134
if (fromVersion < 8 && toVersion >= 8) {
129-
p = p.next(() => this.createCollectionParentIndex(db, txn));
135+
p = p.next(() =>
136+
this.createCollectionParentIndex(db, simpleDbTransaction)
137+
);
130138
}
131139

140+
if (fromVersion < 9 && toVersion >= 9) {
141+
p = p.next(() => {
142+
createRemoteDocumentReadTimeIndex(txn);
143+
});
144+
}
132145
return p;
133146
}
134147

@@ -294,6 +307,9 @@ export class DbTimestamp {
294307
constructor(public seconds: number, public nanoseconds: number) {}
295308
}
296309

310+
/** A timestamp type that can be used in IndexedDb keys. */
311+
export type DbTimestampKey = number[];
312+
297313
// The key for the singleton object in the DbPrimaryClient is a single string.
298314
export type DbPrimaryClientKey = typeof DbPrimaryClient.key;
299315

@@ -590,6 +606,17 @@ export class DbUnknownDocument {
590606
export class DbRemoteDocument {
591607
static store = 'remoteDocuments';
592608

609+
/**
610+
* An index that provides access to documents in a collection sorted by read
611+
* time.
612+
*
613+
* This index is used to allow Index-Free queries to fetch newly changed
614+
* documents in a collection.
615+
*/
616+
static collectionReadTimeIndex = 'collectionReadTimeIndex';
617+
618+
static collectionReadTimeIndexPath = ['parentPath', 'readTime'];
619+
593620
constructor(
594621
/**
595622
* Set to an instance of DbUnknownDocument if the data for a document is
@@ -613,7 +640,13 @@ export class DbRemoteDocument {
613640
* documents are potentially inconsistent with the backend's copy and use
614641
* the write's commit version as their document version.
615642
*/
616-
public hasCommittedMutations: boolean | undefined
643+
public hasCommittedMutations: boolean | undefined,
644+
645+
/** When the document was read from the backend. */
646+
public readTime: DbTimestampKey | undefined,
647+
648+
/** The path of the collection this document is part of. */
649+
public parentPath: string | undefined
617650
) {}
618651
}
619652

@@ -957,6 +990,19 @@ function createRemoteDocumentChangesStore(db: IDBDatabase): void {
957990
});
958991
}
959992

993+
/**
994+
* Creates indices on the RemoteDocuments store used for both multi-tab
995+
* and Index-Free queries.
996+
*/
997+
function createRemoteDocumentReadTimeIndex(txn: IDBTransaction): void {
998+
const remoteDocumentStore = txn.objectStore(DbRemoteDocument.store);
999+
remoteDocumentStore.createIndex(
1000+
DbRemoteDocument.collectionReadTimeIndex,
1001+
DbRemoteDocument.collectionReadTimeIndexPath,
1002+
{ unique: false }
1003+
);
1004+
}
1005+
9601006
/**
9611007
* A record of the metadata state of each client.
9621008
*
@@ -1028,6 +1074,8 @@ export const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobal.store];
10281074

10291075
export const V8_STORES = [...V6_STORES, DbCollectionParent.store];
10301076

1077+
// V9 does not change the set of stores.
1078+
10311079
/**
10321080
* The list of all default IndexedDB stores used throughout the SDK. This is
10331081
* used when creating transactions so that access across all stores is done

packages/firestore/src/local/local_serializer.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
DbRemoteDocument,
4040
DbTarget,
4141
DbTimestamp,
42+
DbTimestampKey,
4243
DbUnknownDocument
4344
} from './indexeddb_schema';
4445
import { QueryData, QueryPurpose } from './query_data';
@@ -70,7 +71,12 @@ export class LocalSerializer {
7071
}
7172

7273
/** Encodes a document for storage locally. */
73-
toDbRemoteDocument(maybeDoc: MaybeDocument): DbRemoteDocument {
74+
toDbRemoteDocument(
75+
maybeDoc: MaybeDocument,
76+
readTime: SnapshotVersion
77+
): DbRemoteDocument {
78+
const dbReadTime = this.toDbTimestampKey(readTime);
79+
const parentPath = maybeDoc.key.path.popLast().canonicalString();
7480
if (maybeDoc instanceof Document) {
7581
const doc = maybeDoc.proto
7682
? maybeDoc.proto
@@ -80,7 +86,9 @@ export class LocalSerializer {
8086
/* unknownDocument= */ null,
8187
/* noDocument= */ null,
8288
doc,
83-
hasCommittedMutations
89+
hasCommittedMutations,
90+
dbReadTime,
91+
parentPath
8492
);
8593
} else if (maybeDoc instanceof NoDocument) {
8694
const path = maybeDoc.key.path.toArray();
@@ -90,7 +98,9 @@ export class LocalSerializer {
9098
/* unknownDocument= */ null,
9199
new DbNoDocument(path, readTime),
92100
/* document= */ null,
93-
hasCommittedMutations
101+
hasCommittedMutations,
102+
dbReadTime,
103+
parentPath
94104
);
95105
} else if (maybeDoc instanceof UnknownDocument) {
96106
const path = maybeDoc.key.path.toArray();
@@ -99,13 +109,20 @@ export class LocalSerializer {
99109
new DbUnknownDocument(path, readTime),
100110
/* noDocument= */ null,
101111
/* document= */ null,
102-
/* hasCommittedMutations= */ true
112+
/* hasCommittedMutations= */ true,
113+
dbReadTime,
114+
parentPath
103115
);
104116
} else {
105117
return fail('Unexpected MaybeDocumment');
106118
}
107119
}
108120

121+
toDbTimestampKey(snapshotVersion: SnapshotVersion): DbTimestampKey {
122+
const timestamp = snapshotVersion.toTimestamp();
123+
return [timestamp.seconds, timestamp.nanoseconds];
124+
}
125+
109126
private toDbTimestamp(snapshotVersion: SnapshotVersion): DbTimestamp {
110127
const timestamp = snapshotVersion.toTimestamp();
111128
return new DbTimestamp(timestamp.seconds, timestamp.nanoseconds);

packages/firestore/src/local/local_store.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ export class LocalStore {
351351
.next(() =>
352352
this.applyWriteToRemoteDocuments(txn, batchResult, documentBuffer)
353353
)
354-
.next(() => documentBuffer.apply(txn))
354+
.next(() => documentBuffer.apply(txn, batchResult.commitVersion))
355355
.next(() => this.mutationQueue.performConsistencyCheck(txn))
356356
.next(() => this.localDocuments.getDocuments(txn, affected));
357357
}
@@ -449,7 +449,7 @@ export class LocalStore {
449449
*/
450450
applyRemoteEvent(remoteEvent: RemoteEvent): Promise<MaybeDocumentMap> {
451451
const documentBuffer = this.remoteDocuments.newChangeBuffer();
452-
const snapshotVersion = remoteEvent.snapshotVersion;
452+
const remoteVersion = remoteEvent.snapshotVersion;
453453
return this.persistence.runTransaction(
454454
'Apply remote event',
455455
'readwrite-primary',
@@ -483,7 +483,7 @@ export class LocalStore {
483483
if (resumeToken.length > 0) {
484484
const newQueryData = oldQueryData.copy({
485485
resumeToken,
486-
snapshotVersion
486+
snapshotVersion: remoteVersion
487487
});
488488
this.queryDataByTarget[targetId] = newQueryData;
489489

@@ -561,28 +561,28 @@ export class LocalStore {
561561
// can synthesize remote events when we get permission denied errors while
562562
// trying to resolve the state of a locally cached document that is in
563563
// limbo.
564-
if (!snapshotVersion.isEqual(SnapshotVersion.MIN)) {
564+
if (!remoteVersion.isEqual(SnapshotVersion.MIN)) {
565565
const updateRemoteVersion = this.queryCache
566566
.getLastRemoteSnapshotVersion(txn)
567567
.next(lastRemoteSnapshotVersion => {
568568
assert(
569-
snapshotVersion.compareTo(lastRemoteSnapshotVersion) >= 0,
569+
remoteVersion.compareTo(lastRemoteSnapshotVersion) >= 0,
570570
'Watch stream reverted to previous snapshot?? ' +
571-
snapshotVersion +
571+
remoteVersion +
572572
' < ' +
573573
lastRemoteSnapshotVersion
574574
);
575575
return this.queryCache.setTargetsMetadata(
576576
txn,
577577
txn.currentSequenceNumber,
578-
snapshotVersion
578+
remoteVersion
579579
);
580580
});
581581
promises.push(updateRemoteVersion);
582582
}
583583

584584
return PersistencePromise.waitFor(promises)
585-
.next(() => documentBuffer.apply(txn))
585+
.next(() => documentBuffer.apply(txn, remoteVersion))
586586
.next(() => {
587587
return this.localDocuments.getLocalViewOfDocuments(
588588
txn,

packages/firestore/src/local/memory_persistence.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { PersistencePromise } from './persistence_promise';
4949
import { QueryData } from './query_data';
5050
import { ReferenceSet } from './reference_set';
5151
import { ClientId } from './shared_client_state';
52+
import { SnapshotVersion } from '../core/snapshot_version';
5253

5354
const LOG_TAG = 'MemoryPersistence';
5455

@@ -268,6 +269,7 @@ export class MemoryEagerDelegate implements ReferenceDelegate {
268269
onTransactionCommitted(
269270
txn: PersistenceTransaction
270271
): PersistencePromise<void> {
272+
// Remove newly orphaned documents.
271273
const cache = this.persistence.getRemoteDocumentCache();
272274
const changeBuffer = cache.newChangeBuffer();
273275
return PersistencePromise.forEach(
@@ -281,7 +283,7 @@ export class MemoryEagerDelegate implements ReferenceDelegate {
281283
}
282284
).next(() => {
283285
this._orphanedDocuments = null;
284-
return changeBuffer.apply(txn);
286+
return changeBuffer.apply(txn, SnapshotVersion.forDeletedDoc());
285287
});
286288
}
287289

@@ -419,7 +421,9 @@ export class MemoryLruDelegate implements ReferenceDelegate, LruDelegate {
419421
}
420422
});
421423
});
422-
return p.next(() => changeBuffer.apply(txn)).next(() => count);
424+
return p
425+
.next(() => changeBuffer.apply(txn, SnapshotVersion.forDeletedDoc()))
426+
.next(() => count);
423427
}
424428

425429
removeMutationReference(
@@ -465,7 +469,10 @@ export class MemoryLruDelegate implements ReferenceDelegate, LruDelegate {
465469
}
466470

467471
documentSize(maybeDoc: MaybeDocument): number {
468-
const remoteDocument = this.serializer.toDbRemoteDocument(maybeDoc);
472+
const remoteDocument = this.serializer.toDbRemoteDocument(
473+
maybeDoc,
474+
SnapshotVersion.forDeletedDoc()
475+
);
469476
let value: unknown;
470477
if (remoteDocument.document) {
471478
value = remoteDocument.document;

0 commit comments

Comments
 (0)