Skip to content

Commit 5ebf8c3

Browse files
authored
Bundle's named query resume. (#3395)
1 parent 8b8a097 commit 5ebf8c3

30 files changed

+514
-168
lines changed

packages/firestore-types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ export class FirebaseFirestore {
9797
bundleData: ArrayBuffer | ReadableStream<ArrayBuffer> | string
9898
): LoadBundleTask;
9999

100+
namedQuery(name: string): Promise<Query | null>;
101+
100102
INTERNAL: { delete: () => Promise<void> };
101103
}
102104

packages/firestore/exp/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export {
3232
waitForPendingWrites,
3333
disableNetwork,
3434
enableNetwork,
35+
namedQuery,
3536
loadBundle,
3637
terminate
3738
} from './src/api/database';

packages/firestore/exp/src/api/database.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
indexedDbClearPersistence
5252
} from '../../../src/local/indexeddb_persistence';
5353
import { LoadBundleTask } from '../../../src/api/bundle';
54+
import { Query } from '../../../lite';
5455

5556
/**
5657
* The root reference to the Firestore database and the entry point for the
@@ -310,3 +311,17 @@ export function loadBundle(
310311

311312
return resultTask;
312313
}
314+
315+
export async function namedQuery(
316+
firestore: firestore.FirebaseFirestore,
317+
name: string
318+
): Promise<firestore.Query | null> {
319+
const firestoreImpl = cast(firestore, Firestore);
320+
const client = await firestoreImpl._getFirestoreClient();
321+
const namedQuery = await client.getNamedQuery(name);
322+
if (!namedQuery) {
323+
return null;
324+
}
325+
326+
return new Query(firestoreImpl, null, namedQuery.query);
327+
}

packages/firestore/exp/test/shim.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
increment,
4747
initializeFirestore,
4848
loadBundle,
49+
namedQuery,
4950
onSnapshot,
5051
onSnapshotsInSync,
5152
parent,
@@ -173,6 +174,12 @@ export class FirebaseFirestore implements legacy.FirebaseFirestore {
173174
return loadBundle(this._delegate, bundleData)!;
174175
}
175176

177+
async namedQuery(name: string): Promise<legacy.Query | null> {
178+
return namedQuery(this._delegate, name).then(query => {
179+
return query ? new Query(this, query) : null;
180+
});
181+
}
182+
176183
INTERNAL = {
177184
delete: () => terminate(this._delegate)
178185
};

packages/firestore/src/api/database.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,16 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
504504
return resultTask;
505505
}
506506

507+
async namedQuery(name: string): Promise<firestore.Query | null> {
508+
this.ensureClientConfigured();
509+
const namedQuery = await this._firestoreClient!.getNamedQuery(name);
510+
if (!namedQuery) {
511+
return null;
512+
}
513+
514+
return new Query(namedQuery.query, this, null);
515+
}
516+
507517
ensureClientConfigured(): FirestoreClient {
508518
if (!this._firestoreClient) {
509519
// Kick off starting the client but don't actually wait for it.

packages/firestore/src/core/firestore_client.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import { CredentialsProvider } from '../api/credentials';
1919
import { User } from '../auth/user';
20-
import { LocalStore } from '../local/local_store';
20+
import { getNamedQuery, LocalStore } from '../local/local_store';
2121
import { GarbageCollectionScheduler, Persistence } from '../local/persistence';
2222
import { Document, NoDocument } from '../model/document';
2323
import { DocumentKey } from '../model/document_key';
@@ -50,6 +50,7 @@ import { BundleReader } from '../util/bundle_reader';
5050
import { LoadBundleTask } from '../api/bundle';
5151
import { newTextEncoder } from '../platform/serializer';
5252
import { toByteStreamReader } from '../platform/byte_stream_reader';
53+
import { NamedQuery } from './bundle';
5354

5455
const LOG_TAG = 'FirestoreClient';
5556
const MAX_CONCURRENT_LIMBO_RESOLUTIONS = 100;
@@ -533,4 +534,9 @@ export class FirestoreClient {
533534
});
534535
});
535536
}
537+
538+
getNamedQuery(queryName: string): Promise<NamedQuery | undefined> {
539+
this.verifyNotTerminated();
540+
return getNamedQuery(this.localStore, queryName);
541+
}
536542
}

packages/firestore/src/local/indexeddb_bundle_cache.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class IndexedDbBundleCache implements BundleCache {
4747
.get(bundleId)
4848
.next(bundle => {
4949
if (bundle) {
50-
return fromDbBundle(this.serializer, bundle);
50+
return fromDbBundle(bundle);
5151
}
5252
return undefined;
5353
});
@@ -57,9 +57,7 @@ export class IndexedDbBundleCache implements BundleCache {
5757
transaction: PersistenceTransaction,
5858
bundleMetadata: bundleProto.BundleMetadata
5959
): PersistencePromise<void> {
60-
return bundlesStore(transaction).put(
61-
toDbBundle(this.serializer, bundleMetadata)
62-
);
60+
return bundlesStore(transaction).put(toDbBundle(bundleMetadata));
6361
}
6462

6563
getNamedQuery(
@@ -70,7 +68,7 @@ export class IndexedDbBundleCache implements BundleCache {
7068
.get(queryName)
7169
.next(query => {
7270
if (query) {
73-
return fromDbNamedQuery(this.serializer, query);
71+
return fromDbNamedQuery(query);
7472
}
7573
return undefined;
7674
});
@@ -80,9 +78,7 @@ export class IndexedDbBundleCache implements BundleCache {
8078
transaction: PersistenceTransaction,
8179
query: bundleProto.NamedQuery
8280
): PersistencePromise<void> {
83-
return namedQueriesStore(transaction).put(
84-
toDbNamedQuery(this.serializer, query)
85-
);
81+
return namedQueriesStore(transaction).put(toDbNamedQuery(query));
8682
}
8783
}
8884

packages/firestore/src/local/local_serializer.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,7 @@ function isDocumentQuery(dbQuery: DbQuery): dbQuery is api.DocumentsTarget {
280280
}
281281

282282
/** Encodes a DbBundle to a Bundle. */
283-
export function fromDbBundle(
284-
serializer: LocalSerializer,
285-
dbBundle: DbBundle
286-
): Bundle {
283+
export function fromDbBundle(dbBundle: DbBundle): Bundle {
287284
return {
288285
id: dbBundle.bundleId,
289286
createTime: fromDbTimestamp(dbBundle.createTime),
@@ -292,10 +289,7 @@ export function fromDbBundle(
292289
}
293290

294291
/** Encodes a BundleMetadata to a DbBundle. */
295-
export function toDbBundle(
296-
serializer: LocalSerializer,
297-
metadata: bundleProto.BundleMetadata
298-
): DbBundle {
292+
export function toDbBundle(metadata: bundleProto.BundleMetadata): DbBundle {
299293
return {
300294
bundleId: metadata.id!,
301295
createTime: toDbTimestamp(fromVersion(metadata.createTime!)),
@@ -304,22 +298,16 @@ export function toDbBundle(
304298
}
305299

306300
/** Encodes a DbNamedQuery to a NamedQuery. */
307-
export function fromDbNamedQuery(
308-
serializer: LocalSerializer,
309-
dbNamedQuery: DbNamedQuery
310-
): NamedQuery {
301+
export function fromDbNamedQuery(dbNamedQuery: DbNamedQuery): NamedQuery {
311302
return {
312303
name: dbNamedQuery.name,
313-
query: fromBundledQuery(serializer, dbNamedQuery.bundledQuery),
304+
query: fromBundledQuery(dbNamedQuery.bundledQuery),
314305
readTime: fromDbTimestamp(dbNamedQuery.readTime)
315306
};
316307
}
317308

318309
/** Encodes a NamedQuery from a bundle proto to a DbNamedQuery. */
319-
export function toDbNamedQuery(
320-
serializer: LocalSerializer,
321-
query: bundleProto.NamedQuery
322-
): DbNamedQuery {
310+
export function toDbNamedQuery(query: bundleProto.NamedQuery): DbNamedQuery {
323311
return {
324312
name: query.name!,
325313
readTime: toDbTimestamp(fromVersion(query.readTime!)),
@@ -334,7 +322,6 @@ export function toDbNamedQuery(
334322
* including features exists only in SDKs (for example: limit-to-last).
335323
*/
336324
export function fromBundledQuery(
337-
serializer: LocalSerializer,
338325
bundledQuery: bundleProto.BundledQuery
339326
): Query {
340327
const query = convertQueryTargetToQuery({
@@ -353,19 +340,17 @@ export function fromBundledQuery(
353340

354341
/** Encodes a NamedQuery proto object to a NamedQuery model object. */
355342
export function fromProtoNamedQuery(
356-
serializer: LocalSerializer,
357343
namedQuery: bundleProto.NamedQuery
358344
): NamedQuery {
359345
return {
360346
name: namedQuery.name!,
361-
query: fromBundledQuery(serializer, namedQuery.bundledQuery!),
347+
query: fromBundledQuery(namedQuery.bundledQuery!),
362348
readTime: fromVersion(namedQuery.readTime!)
363349
};
364350
}
365351

366352
/** Encodes a BundleMetadata proto object to a Bundle model object. */
367353
export function fromBundleMetadata(
368-
serializer: LocalSerializer,
369354
metadata: bundleProto.BundleMetadata
370355
): Bundle {
371356
return {

packages/firestore/src/local/local_store.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ import { isIndexedDbTransactionError } from './simple_db';
7676
import * as bundleProto from '../protos/firestore_bundle_proto';
7777
import { BundleConverter, BundledDocuments, NamedQuery } from '../core/bundle';
7878
import { BundleCache } from './bundle_cache';
79-
import { JsonProtoSerializer } from '../remote/serializer';
79+
import { fromVersion, JsonProtoSerializer } from '../remote/serializer';
80+
import { fromBundledQuery } from './local_serializer';
81+
import { ByteString } from '../util/byte_string';
8082

8183
const LOG_TAG = 'LocalStore';
8284

@@ -898,8 +900,6 @@ class LocalStoreImpl implements LocalStore {
898900
.getTargetData(txn, target)
899901
.next((cached: TargetData | null) => {
900902
if (cached) {
901-
// This target has been listened to previously, so reuse the
902-
// previous targetID.
903903
// TODO(mcg): freshen last accessed date?
904904
targetData = cached;
905905
return PersistencePromise.resolve(targetData);
@@ -1371,14 +1371,39 @@ export function getNamedQuery(
13711371
/**
13721372
* Saves the given `NamedQuery` to local persistence.
13731373
*/
1374-
export function saveNamedQuery(
1374+
export async function saveNamedQuery(
13751375
localStore: LocalStore,
13761376
query: bundleProto.NamedQuery
13771377
): Promise<void> {
1378+
// Allocate a target for the named query such that it can be resumed
1379+
// from associated read time if users use it to listen.
1380+
// NOTE: this also means if no corresponding target exists, the new target
1381+
// will remain active and will not get collected, unless users happen to
1382+
// unlisten the query somehow.
1383+
const allocated = await localStore.allocateTarget(
1384+
queryToTarget(fromBundledQuery(query.bundledQuery!))
1385+
);
13781386
const localStoreImpl = debugCast(localStore, LocalStoreImpl);
13791387
return localStoreImpl.persistence.runTransaction(
13801388
'Save named query',
13811389
'readwrite',
1382-
transaction => localStoreImpl.bundleCache.saveNamedQuery(transaction, query)
1390+
transaction => {
1391+
// Update allocated target's read time, if the bundle's read time is newer.
1392+
let updateReadTime = PersistencePromise.resolve();
1393+
const readTime = fromVersion(query.readTime!);
1394+
if (allocated.snapshotVersion.compareTo(readTime) < 0) {
1395+
const newTargetData = allocated.withResumeToken(
1396+
ByteString.EMPTY_BYTE_STRING,
1397+
readTime
1398+
);
1399+
updateReadTime = localStoreImpl.targetCache.updateTargetData(
1400+
transaction,
1401+
newTargetData
1402+
);
1403+
}
1404+
return updateReadTime.next(() =>
1405+
localStoreImpl.bundleCache.saveNamedQuery(transaction, query)
1406+
);
1407+
}
13831408
);
13841409
}

packages/firestore/src/local/memory_bundle_cache.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,7 @@ export class MemoryBundleCache implements BundleCache {
4343
transaction: PersistenceTransaction,
4444
bundleMetadata: bundleProto.BundleMetadata
4545
): PersistencePromise<void> {
46-
this.bundles.set(
47-
bundleMetadata.id!,
48-
fromBundleMetadata(this.serializer, bundleMetadata)
49-
);
46+
this.bundles.set(bundleMetadata.id!, fromBundleMetadata(bundleMetadata));
5047
return PersistencePromise.resolve();
5148
}
5249

@@ -61,10 +58,7 @@ export class MemoryBundleCache implements BundleCache {
6158
transaction: PersistenceTransaction,
6259
query: bundleProto.NamedQuery
6360
): PersistencePromise<void> {
64-
this.namedQueries.set(
65-
query.name!,
66-
fromProtoNamedQuery(this.serializer, query)
67-
);
61+
this.namedQueries.set(query.name!, fromProtoNamedQuery(query));
6862
return PersistencePromise.resolve();
6963
}
7064
}

packages/firestore/src/protos/firestore_bundle_proto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface BundledQuery {
3030
}
3131

3232
/** LimitType enum. */
33-
type LimitType = 'FIRST' | 'LAST';
33+
export type LimitType = 'FIRST' | 'LAST';
3434

3535
/** Properties of a NamedQuery. */
3636
export interface NamedQuery {

packages/firestore/src/protos/firestore_proto_api.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ export declare namespace firestoreV1ApiClientInterfaces {
345345
query?: QueryTarget;
346346
documents?: DocumentsTarget;
347347
resumeToken?: string | Uint8Array;
348-
readTime?: string;
348+
readTime?: Timestamp;
349349
targetId?: number;
350350
once?: boolean;
351351
}

packages/firestore/src/remote/serializer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,14 @@ export function toTarget(
975975

976976
if (targetData.resumeToken.approximateByteSize() > 0) {
977977
result.resumeToken = toBytes(serializer, targetData.resumeToken);
978+
} else if (targetData.snapshotVersion.compareTo(SnapshotVersion.min()) > 0) {
979+
// TODO(wuandy): Consider removing above check because it is most likely true.
980+
// Right now, many tests depend on this behaviour though (leaving min() out
981+
// of serialization).
982+
result.readTime = toTimestamp(
983+
serializer,
984+
targetData.snapshotVersion.toTimestamp()
985+
);
978986
}
979987

980988
return result;

0 commit comments

Comments
 (0)