Skip to content

Commit 647a748

Browse files
committed
[1/3] Query/Target split -- create Target and make remote/ and local/ work with Target. (#2254)
1 parent ce867b0 commit 647a748

19 files changed

+382
-240
lines changed

packages/firestore/src/core/query.ts

Lines changed: 28 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,23 @@ import { FieldPath, ResourcePath } from '../model/path';
2828
import { assert, fail } from '../util/assert';
2929
import { Code, FirestoreError } from '../util/error';
3030
import { isNullOrUndefined } from '../util/types';
31+
import { Target } from './target';
3132

33+
/**
34+
* Query encapsulates all the query attributes we support in the SDK. It can
35+
* be run against the LocalStore, as well as be converted to a `Target` to
36+
* query the RemoteStore results.
37+
*/
3238
export class Query {
3339
static atPath(path: ResourcePath): Query {
3440
return new Query(path);
3541
}
3642

37-
private memoizedCanonicalId: string | null = null;
3843
private memoizedOrderBy: OrderBy[] | null = null;
3944

45+
// The corresponding `Target` of this `Query` instance.
46+
private target: Target | null = null;
47+
4048
/**
4149
* Initializes a Query with a path and optional additional query constraints.
4250
* Path must currently be empty if this is a collection group query.
@@ -219,107 +227,15 @@ export class Query {
219227
// example, use as a dictionary key, but the implementation is subject to
220228
// collisions. Make it collision-free.
221229
canonicalId(): string {
222-
if (this.memoizedCanonicalId === null) {
223-
let canonicalId = this.path.canonicalString();
224-
if (this.isCollectionGroupQuery()) {
225-
canonicalId += '|cg:' + this.collectionGroup;
226-
}
227-
canonicalId += '|f:';
228-
for (const filter of this.filters) {
229-
canonicalId += filter.canonicalId();
230-
canonicalId += ',';
231-
}
232-
canonicalId += '|ob:';
233-
// TODO(dimond): make this collision resistant
234-
for (const orderBy of this.orderBy) {
235-
canonicalId += orderBy.canonicalId();
236-
canonicalId += ',';
237-
}
238-
if (!isNullOrUndefined(this.limit)) {
239-
canonicalId += '|l:';
240-
canonicalId += this.limit!;
241-
}
242-
if (this.startAt) {
243-
canonicalId += '|lb:';
244-
canonicalId += this.startAt.canonicalId();
245-
}
246-
if (this.endAt) {
247-
canonicalId += '|ub:';
248-
canonicalId += this.endAt.canonicalId();
249-
}
250-
this.memoizedCanonicalId = canonicalId;
251-
}
252-
return this.memoizedCanonicalId;
230+
return this.toTarget().canonicalId();
253231
}
254232

255233
toString(): string {
256-
let str = 'Query(' + this.path.canonicalString();
257-
if (this.isCollectionGroupQuery()) {
258-
str += ' collectionGroup=' + this.collectionGroup;
259-
}
260-
if (this.filters.length > 0) {
261-
str += `, filters: [${this.filters.join(', ')}]`;
262-
}
263-
if (!isNullOrUndefined(this.limit)) {
264-
str += ', limit: ' + this.limit;
265-
}
266-
if (this.explicitOrderBy.length > 0) {
267-
str += `, orderBy: [${this.explicitOrderBy.join(', ')}]`;
268-
}
269-
if (this.startAt) {
270-
str += ', startAt: ' + this.startAt.canonicalId();
271-
}
272-
if (this.endAt) {
273-
str += ', endAt: ' + this.endAt.canonicalId();
274-
}
275-
276-
return str + ')';
234+
return `Query(target=${this.toTarget().toString()})`;
277235
}
278236

279237
isEqual(other: Query): boolean {
280-
if (this.limit !== other.limit) {
281-
return false;
282-
}
283-
284-
if (this.orderBy.length !== other.orderBy.length) {
285-
return false;
286-
}
287-
288-
for (let i = 0; i < this.orderBy.length; i++) {
289-
if (!this.orderBy[i].isEqual(other.orderBy[i])) {
290-
return false;
291-
}
292-
}
293-
294-
if (this.filters.length !== other.filters.length) {
295-
return false;
296-
}
297-
298-
for (let i = 0; i < this.filters.length; i++) {
299-
if (!this.filters[i].isEqual(other.filters[i])) {
300-
return false;
301-
}
302-
}
303-
304-
if (this.collectionGroup !== other.collectionGroup) {
305-
return false;
306-
}
307-
308-
if (!this.path.isEqual(other.path)) {
309-
return false;
310-
}
311-
312-
if (
313-
this.startAt !== null
314-
? !this.startAt.isEqual(other.startAt)
315-
: other.startAt !== null
316-
) {
317-
return false;
318-
}
319-
320-
return this.endAt !== null
321-
? this.endAt.isEqual(other.endAt)
322-
: other.endAt === null;
238+
return this.toTarget().isEqual(other.toTarget());
323239
}
324240

325241
docComparator(d1: Document, d2: Document): number {
@@ -381,17 +297,28 @@ export class Query {
381297
}
382298

383299
isDocumentQuery(): boolean {
384-
return (
385-
DocumentKey.isDocumentKey(this.path) &&
386-
this.collectionGroup === null &&
387-
this.filters.length === 0
388-
);
300+
return this.toTarget().isDocumentQuery();
389301
}
390302

391303
isCollectionGroupQuery(): boolean {
392304
return this.collectionGroup !== null;
393305
}
394306

307+
toTarget(): Target {
308+
if (!this.target) {
309+
this.target = new Target(
310+
this.path,
311+
this.collectionGroup,
312+
this.orderBy,
313+
this.filters,
314+
this.limit,
315+
this.startAt,
316+
this.endAt
317+
);
318+
}
319+
return this.target!;
320+
}
321+
395322
private matchesPathAndCollectionGroup(doc: Document): boolean {
396323
const docPath = doc.key.path;
397324
if (this.collectionGroup !== null) {

packages/firestore/src/core/sync_engine.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
222222
);
223223
targetId = queryData.targetId;
224224
viewSnapshot = await this.initializeViewAndComputeSnapshot(
225-
queryData,
225+
query,
226+
queryData.targetId,
226227
status === 'current'
227228
);
228229
if (this.isPrimary) {
@@ -239,18 +240,18 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
239240
* snapshot.
240241
*/
241242
private async initializeViewAndComputeSnapshot(
242-
queryData: QueryData,
243+
query: Query,
244+
targetId: TargetId,
243245
current: boolean
244246
): Promise<ViewSnapshot> {
245-
const query = queryData.query;
246247
const queryResult = await this.localStore.executeQuery(
247248
query,
248249
/* usePreviousResults= */ true
249250
);
250251
const view = new View(query, queryResult.remoteKeys);
251252
const viewDocChanges = view.computeDocChanges(queryResult.documents);
252253
const synthesizedTargetChange = TargetChange.createSynthesizedTargetChangeForCurrentChange(
253-
queryData.targetId,
254+
targetId,
254255
current && this.onlineState !== OnlineState.Offline
255256
);
256257
const viewChange = view.applyChanges(
@@ -267,9 +268,9 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
267268
'applyChanges for new view should always return a snapshot'
268269
);
269270

270-
const data = new QueryView(query, queryData.targetId, view);
271+
const data = new QueryView(query, targetId, view);
271272
this.queryViewsByQuery.set(query, data);
272-
this.queryViewsByTarget[queryData.targetId] = data;
273+
this.queryViewsByTarget[targetId] = data;
273274
return viewChange.snapshot!;
274275
}
275276

@@ -747,7 +748,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
747748
this.limboResolutionsByTarget[limboTargetId] = new LimboResolution(key);
748749
this.remoteStore.listen(
749750
new QueryData(
750-
query,
751+
query.toTarget(),
751752
limboTargetId,
752753
QueryPurpose.LimboResolution,
753754
ListenSequence.INVALID
@@ -948,12 +949,13 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
948949
'A secondary tab should never have an active query without an active view.'
949950
);
950951
// For queries that never executed on this client, we need to
951-
// allocate the query in LocalStore and initialize a new View.
952-
const query = await this.localStore.getQueryForTarget(targetId);
953-
assert(!!query, `Query data for target ${targetId} not found`);
954-
queryData = await this.localStore.allocateQuery(query!);
952+
// allocate the target in LocalStore and initialize a new View.
953+
const target = await this.localStore.getTarget(targetId);
954+
assert(!!target, `Target for id ${targetId} not found`);
955+
queryData = await this.localStore.allocateTarget(target!);
955956
await this.initializeViewAndComputeSnapshot(
956-
queryData,
957+
target!.toTargetQuery(),
958+
targetId,
957959
/*current=*/ false
958960
);
959961
}
@@ -1026,11 +1028,12 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
10261028
!this.queryViewsByTarget[targetId],
10271029
'Trying to add an already active target'
10281030
);
1029-
const query = await this.localStore.getQueryForTarget(targetId);
1030-
assert(!!query, `Query data for active target ${targetId} not found`);
1031-
const queryData = await this.localStore.allocateQuery(query!);
1031+
const target = await this.localStore.getTarget(targetId);
1032+
assert(!!target, `Query data for active target ${targetId} not found`);
1033+
const queryData = await this.localStore.allocateTarget(target!);
10321034
await this.initializeViewAndComputeSnapshot(
1033-
queryData,
1035+
target!.toTargetQuery(),
1036+
queryData.targetId,
10341037
/*current=*/ false
10351038
);
10361039
this.remoteStore.listen(queryData);

0 commit comments

Comments
 (0)