Skip to content

Commit dbc8d70

Browse files
author
Greg Soltis
authored
Add sequence number to QueryData (#1194)
* Add sequence numbers to QueryData * [AUTOMATED]: Prettier Code Styling * Dont used separate db prefix * [AUTOMATED]: Prettier Code Styling
1 parent 5e109b1 commit dbc8d70

14 files changed

+173
-33
lines changed

packages/firestore/src/core/sync_engine.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import { ClientId, SharedClientState } from '../local/shared_client_state';
6969
import { SortedSet } from '../util/sorted_set';
7070
import * as objUtils from '../util/obj';
7171
import { isPrimaryLeaseLostError } from '../local/indexeddb_persistence';
72+
import { ListenSequence } from './listen_sequence';
7273

7374
const LOG_TAG = 'SyncEngine';
7475

@@ -742,7 +743,12 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
742743
const query = Query.atPath(key.path);
743744
this.limboResolutionsByTarget[limboTargetId] = new LimboResolution(key);
744745
this.remoteStore.listen(
745-
new QueryData(query, limboTargetId, QueryPurpose.LimboResolution)
746+
new QueryData(
747+
query,
748+
limboTargetId,
749+
QueryPurpose.LimboResolution,
750+
ListenSequence.INVALID
751+
)
746752
);
747753
this.limboTargetsByKey = this.limboTargetsByKey.insert(
748754
key,

packages/firestore/src/local/indexeddb_query_cache.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ export class IndexedDbQueryCache implements QueryCache {
8686
});
8787
}
8888

89+
getHighestSequenceNumber(
90+
transaction: PersistenceTransaction
91+
): PersistencePromise<ListenSequenceNumber> {
92+
return getHighestListenSequenceNumber(
93+
(transaction as IndexedDbTransaction).simpleDbTransaction
94+
);
95+
}
96+
8997
setTargetsMetadata(
9098
transaction: PersistenceTransaction,
9199
highestListenSequenceNumber: number,
@@ -96,6 +104,9 @@ export class IndexedDbQueryCache implements QueryCache {
96104
if (lastRemoteSnapshotVersion) {
97105
metadata.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion.toTimestamp();
98106
}
107+
if (highestListenSequenceNumber > metadata.highestListenSequenceNumber) {
108+
metadata.highestListenSequenceNumber = highestListenSequenceNumber;
109+
}
99110
return this.saveMetadata(transaction, metadata);
100111
});
101112
}
@@ -165,13 +176,17 @@ export class IndexedDbQueryCache implements QueryCache {
165176
queryData: QueryData,
166177
metadata: DbTargetGlobal
167178
): boolean {
179+
let updated = false;
168180
if (queryData.targetId > metadata.highestTargetId) {
169181
metadata.highestTargetId = queryData.targetId;
170-
return true;
182+
updated = true;
171183
}
172184

173-
// TODO(GC): add sequence number check
174-
return false;
185+
if (queryData.sequenceNumber > metadata.highestListenSequenceNumber) {
186+
metadata.highestListenSequenceNumber = queryData.sequenceNumber;
187+
updated = true;
188+
}
189+
return updated;
175190
}
176191

177192
getQueryCount(

packages/firestore/src/local/local_serializer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ export class LocalSerializer {
175175
query,
176176
dbTarget.targetId,
177177
QueryPurpose.Listen,
178+
dbTarget.lastListenSequenceNumber,
178179
version,
179180
dbTarget.resumeToken
180181
);
@@ -216,7 +217,7 @@ export class LocalSerializer {
216217
queryData.query.canonicalId(),
217218
dbTimestamp,
218219
resumeToken,
219-
0,
220+
queryData.sequenceNumber,
220221
queryProto
221222
);
222223
}

packages/firestore/src/local/local_store.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,8 @@ export class LocalStore {
459459
const oldQueryData = queryData;
460460
queryData = queryData.copy({
461461
resumeToken,
462-
snapshotVersion: remoteEvent.snapshotVersion
462+
snapshotVersion: remoteEvent.snapshotVersion,
463+
sequenceNumber: txn.currentSequenceNumber
463464
});
464465
this.queryDataByTarget[targetId] = queryData;
465466

@@ -527,7 +528,7 @@ export class LocalStore {
527528
);
528529
return this.queryCache.setTargetsMetadata(
529530
txn,
530-
/*highestSequenceNumber=*/ 0,
531+
txn.currentSequenceNumber,
531532
remoteVersion
532533
);
533534
});
@@ -653,7 +654,12 @@ export class LocalStore {
653654
return PersistencePromise.resolve();
654655
} else {
655656
return this.queryCache.allocateTargetId(txn).next(targetId => {
656-
queryData = new QueryData(query, targetId, QueryPurpose.Listen);
657+
queryData = new QueryData(
658+
query,
659+
targetId,
660+
QueryPurpose.Listen,
661+
txn.currentSequenceNumber
662+
);
657663
return this.queryCache.addQueryData(txn, queryData);
658664
});
659665
}

packages/firestore/src/local/memory_query_cache.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import { Query } from '../core/query';
1818
import { SnapshotVersion } from '../core/snapshot_version';
19-
import { TargetId } from '../core/types';
19+
import { ListenSequenceNumber, TargetId } from '../core/types';
2020
import { DocumentKeySet } from '../model/collections';
2121
import { DocumentKey } from '../model/document_key';
2222
import { ObjectMap } from '../util/obj_map';
@@ -40,6 +40,8 @@ export class MemoryQueryCache implements QueryCache {
4040
private lastRemoteSnapshotVersion = SnapshotVersion.MIN;
4141
/** The highest numbered target ID encountered. */
4242
private highestTargetId: TargetId = 0;
43+
/** The highest sequence number encountered. */
44+
private highestSequenceNumber: ListenSequenceNumber = 0;
4345
/**
4446
* A ordered bidirectional mapping between documents and the remote target
4547
* IDs.
@@ -56,6 +58,12 @@ export class MemoryQueryCache implements QueryCache {
5658
return PersistencePromise.resolve(this.lastRemoteSnapshotVersion);
5759
}
5860

61+
getHighestSequenceNumber(
62+
transaction: PersistenceTransaction
63+
): PersistencePromise<ListenSequenceNumber> {
64+
return PersistencePromise.resolve(this.highestSequenceNumber);
65+
}
66+
5967
allocateTargetId(
6068
transaction: PersistenceTransaction
6169
): PersistencePromise<TargetId> {
@@ -72,6 +80,9 @@ export class MemoryQueryCache implements QueryCache {
7280
if (lastRemoteSnapshotVersion) {
7381
this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
7482
}
83+
if (highestListenSequenceNumber > this.highestSequenceNumber) {
84+
this.highestSequenceNumber = highestListenSequenceNumber;
85+
}
7586
return PersistencePromise.resolve();
7687
}
7788

@@ -81,7 +92,9 @@ export class MemoryQueryCache implements QueryCache {
8192
if (targetId > this.highestTargetId) {
8293
this.highestTargetId = targetId;
8394
}
84-
// TODO(GC): track sequence number
95+
if (queryData.sequenceNumber > this.highestSequenceNumber) {
96+
this.highestSequenceNumber = queryData.sequenceNumber;
97+
}
8598
}
8699

87100
addQueryData(

packages/firestore/src/local/query_cache.ts

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

1717
import { Query } from '../core/query';
1818
import { SnapshotVersion } from '../core/snapshot_version';
19-
import { TargetId } from '../core/types';
19+
import { ListenSequenceNumber, TargetId } from '../core/types';
2020
import { DocumentKeySet } from '../model/collections';
2121

2222
import { GarbageSource } from './garbage_source';
@@ -45,6 +45,14 @@ export interface QueryCache extends GarbageSource {
4545
transaction: PersistenceTransaction
4646
): PersistencePromise<SnapshotVersion>;
4747

48+
/**
49+
* @return The highest sequence number observed, including any that might be
50+
* persisted on-disk.
51+
*/
52+
getHighestSequenceNumber(
53+
transaction: PersistenceTransaction
54+
): PersistencePromise<ListenSequenceNumber>;
55+
4856
/**
4957
* Set the highest listen sequence number and optionally updates the
5058
* snapshot version of the last consistent snapshot received from the backend

packages/firestore/src/local/query_data.ts

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

1717
import { Query } from '../core/query';
1818
import { SnapshotVersion } from '../core/snapshot_version';
19-
import { ProtoByteString, TargetId } from '../core/types';
19+
import { ListenSequenceNumber, ProtoByteString, TargetId } from '../core/types';
2020
import { emptyByteString } from '../platform/platform';
2121

2222
/** An enumeration of the different purposes we have for queries. */
@@ -47,6 +47,8 @@ export class QueryData {
4747
readonly targetId: TargetId,
4848
/** The purpose of the query. */
4949
readonly purpose: QueryPurpose,
50+
/** The sequence number of the last transaction during which this query data was modified */
51+
readonly sequenceNumber: ListenSequenceNumber,
5052
/** The latest snapshot version seen for this target. */
5153
readonly snapshotVersion: SnapshotVersion = SnapshotVersion.MIN,
5254
/**
@@ -65,11 +67,15 @@ export class QueryData {
6567
copy(overwrite: {
6668
resumeToken?: ProtoByteString;
6769
snapshotVersion?: SnapshotVersion;
70+
sequenceNumber?: ListenSequenceNumber;
6871
}): QueryData {
6972
return new QueryData(
7073
this.query,
7174
this.targetId,
7275
this.purpose,
76+
overwrite.sequenceNumber === undefined
77+
? this.sequenceNumber
78+
: overwrite.sequenceNumber,
7379
overwrite.snapshotVersion === undefined
7480
? this.snapshotVersion
7581
: overwrite.snapshotVersion,
@@ -83,6 +89,7 @@ export class QueryData {
8389
return (
8490
this.targetId === other.targetId &&
8591
this.purpose === other.purpose &&
92+
this.sequenceNumber === other.sequenceNumber &&
8693
this.snapshotVersion.isEqual(other.snapshotVersion) &&
8794
this.resumeToken === other.resumeToken &&
8895
this.query.isEqual(other.query)

packages/firestore/src/remote/remote_store.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,8 @@ export class RemoteStore implements TargetMetadataProvider {
442442
const requestQueryData = new QueryData(
443443
queryData.query,
444444
targetId,
445-
QueryPurpose.ExistenceFilterMismatch
445+
QueryPurpose.ExistenceFilterMismatch,
446+
queryData.sequenceNumber
446447
);
447448
this.sendWatchRequest(requestQueryData);
448449
});

packages/firestore/test/unit/local/query_cache.test.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
import * as persistenceHelpers from './persistence_test_helpers';
3535
import { TestGarbageCollector } from './test_garbage_collector';
3636
import { TestQueryCache } from './test_query_cache';
37+
import { Timestamp } from '../../../src/api/timestamp';
3738

3839
describe('MemoryQueryCache', () => {
3940
genericQueryCacheTests(persistenceHelpers.testMemoryPersistence);
@@ -51,6 +52,44 @@ describe('IndexedDbQueryCache', () => {
5152
});
5253

5354
genericQueryCacheTests(() => persistencePromise);
55+
56+
it('persists metadata across restarts', async () => {
57+
const db1 = await persistencePromise;
58+
59+
const queryCache1 = new TestQueryCache(db1, db1.getQueryCache());
60+
expect(await queryCache1.getHighestSequenceNumber()).to.equal(0);
61+
62+
const originalSequenceNumber = 1234;
63+
const targetId = 5;
64+
const snapshotVersion = SnapshotVersion.fromTimestamp(new Timestamp(1, 2));
65+
const query = Query.atPath(path('rooms'));
66+
await queryCache1.addQueryData(
67+
new QueryData(
68+
query,
69+
targetId,
70+
QueryPurpose.Listen,
71+
originalSequenceNumber,
72+
snapshotVersion
73+
)
74+
);
75+
// Snapshot version needs to be set separately
76+
await queryCache1.setTargetsMetadata(
77+
originalSequenceNumber,
78+
snapshotVersion
79+
);
80+
await db1.shutdown(/* deleteData= */ false);
81+
82+
const db2 = await persistenceHelpers.testIndexedDbPersistence({
83+
dontPurgeData: true
84+
});
85+
const queryCache2 = new TestQueryCache(db2, db2.getQueryCache());
86+
expect(await queryCache2.getHighestSequenceNumber()).to.equal(
87+
originalSequenceNumber
88+
);
89+
const actualSnapshotVersion = await queryCache2.getLastRemoteSnapshotVersion();
90+
expect(snapshotVersion.isEqual(actualSnapshotVersion)).to.be.true;
91+
await db2.shutdown(/* deleteData= */ true);
92+
});
5493
});
5594

5695
/**
@@ -70,6 +109,7 @@ function genericQueryCacheTests(
70109
* Creates a new QueryData object from the the given parameters, synthesizing
71110
* a resume token from the snapshot version.
72111
*/
112+
let previousSequenceNumber = 0;
73113
function testQueryData(
74114
query: Query,
75115
targetId: TargetId,
@@ -84,6 +124,7 @@ function genericQueryCacheTests(
84124
query,
85125
targetId,
86126
QueryPurpose.Listen,
127+
++previousSequenceNumber,
87128
snapshotVersion,
88129
resumeToken
89130
);
@@ -96,7 +137,9 @@ function genericQueryCacheTests(
96137
});
97138

98139
afterEach(async () => {
99-
await persistence.shutdown(/* deleteData= */ true);
140+
if (persistence.started) {
141+
await persistence.shutdown(/* deleteData= */ true);
142+
}
100143
});
101144

102145
it('returns null for query not in cache', () => {
@@ -344,4 +387,25 @@ function genericQueryCacheTests(
344387
);
345388
});
346389
});
390+
391+
it('sets the highest sequence number', async () => {
392+
const query1 = new QueryData(QUERY_ROOMS, 1, QueryPurpose.Listen, 10);
393+
await cache.addQueryData(query1);
394+
const query2 = new QueryData(QUERY_HALLS, 2, QueryPurpose.Listen, 20);
395+
await cache.addQueryData(query2);
396+
expect(await cache.getHighestSequenceNumber()).to.equal(20);
397+
398+
// Sequence numbers can never come down
399+
await cache.removeQueryData(query2);
400+
expect(await cache.getHighestSequenceNumber()).to.equal(20);
401+
402+
const query3 = new QueryData(QUERY_GARAGES, 3, QueryPurpose.Listen, 100);
403+
await cache.addQueryData(query3);
404+
expect(await cache.getHighestSequenceNumber()).to.equal(100);
405+
406+
await cache.removeQueryData(query1);
407+
expect(await cache.getHighestSequenceNumber()).to.equal(100);
408+
await cache.removeQueryData(query3);
409+
expect(await cache.getHighestSequenceNumber()).to.equal(100);
410+
});
347411
}

packages/firestore/test/unit/local/test_query_cache.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import { Query } from '../../../src/core/query';
1818
import { SnapshotVersion } from '../../../src/core/snapshot_version';
19-
import { TargetId } from '../../../src/core/types';
19+
import { ListenSequenceNumber, TargetId } from '../../../src/core/types';
2020
import { Persistence } from '../../../src/local/persistence';
2121
import { QueryCache } from '../../../src/local/query_cache';
2222
import { QueryData } from '../../../src/local/query_data';
@@ -70,6 +70,16 @@ export class TestQueryCache {
7070
);
7171
}
7272

73+
getHighestSequenceNumber(): Promise<ListenSequenceNumber> {
74+
return this.persistence.runTransaction(
75+
'getHighestSequenceNumber',
76+
false,
77+
txn => {
78+
return this.cache.getHighestSequenceNumber(txn);
79+
}
80+
);
81+
}
82+
7383
allocateTargetId(): Promise<TargetId> {
7484
return this.persistence.runTransaction('allocateTargetId', false, txn => {
7585
return this.cache.allocateTargetId(txn);
@@ -125,7 +135,7 @@ export class TestQueryCache {
125135
}
126136

127137
setTargetsMetadata(
128-
highestListenSequenceNumber: number,
138+
highestListenSequenceNumber: ListenSequenceNumber,
129139
lastRemoteSnapshotVersion?: SnapshotVersion
130140
): Promise<void> {
131141
return this.persistence.runTransaction('setTargetsMetadata', true, txn =>

0 commit comments

Comments
 (0)