Skip to content

Commit 55ffe46

Browse files
Expose operation counts from Persistence layer (take 2) (#2165)
1 parent 5e2a1e5 commit 55ffe46

File tree

3 files changed

+268
-6
lines changed

3 files changed

+268
-6
lines changed

packages/firestore/src/local/local_documents_view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ import { RemoteDocumentCache } from './remote_document_cache';
4848
*/
4949
export class LocalDocumentsView {
5050
constructor(
51-
private remoteDocumentCache: RemoteDocumentCache,
52-
private mutationQueue: MutationQueue,
53-
private indexManager: IndexManager
51+
readonly remoteDocumentCache: RemoteDocumentCache,
52+
readonly mutationQueue: MutationQueue,
53+
readonly indexManager: IndexManager
5454
) {}
5555

5656
/**
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { QueryEngine } from '../../../src/local/query_engine';
19+
import { LocalDocumentsView } from '../../../src/local/local_documents_view';
20+
import { PersistenceTransaction } from '../../../src/local/persistence';
21+
import { Query } from '../../../src/core/query';
22+
import { SnapshotVersion } from '../../../src/core/snapshot_version';
23+
import { PersistencePromise } from '../../../src/local/persistence_promise';
24+
import { DocumentMap } from '../../../src/model/collections';
25+
import { RemoteDocumentCache } from '../../../src/local/remote_document_cache';
26+
import { MutationQueue } from '../../../src/local/mutation_queue';
27+
28+
/**
29+
* A test-only query engine that forwards all API calls and exposes the number
30+
* of documents and mutations read.
31+
*/
32+
export class CountingQueryEngine implements QueryEngine {
33+
/**
34+
* The number of mutations returned by the MutationQueue (since the last call
35+
* to `resetCounts()`)
36+
*/
37+
mutationsRead = 0;
38+
39+
/**
40+
* The number of documents returned by the RemoteDocumentCache (since the
41+
* last call to `resetCounts()`)
42+
*/
43+
documentsRead = 0;
44+
45+
constructor(private readonly queryEngine: QueryEngine) {}
46+
47+
resetCounts(): void {
48+
this.mutationsRead = 0;
49+
this.documentsRead = 0;
50+
}
51+
52+
getDocumentsMatchingQuery(
53+
transaction: PersistenceTransaction,
54+
query: Query,
55+
sinceReadTime: SnapshotVersion
56+
): PersistencePromise<DocumentMap> {
57+
return this.queryEngine.getDocumentsMatchingQuery(
58+
transaction,
59+
query,
60+
sinceReadTime
61+
);
62+
}
63+
64+
setLocalDocumentsView(localDocuments: LocalDocumentsView): void {
65+
const view = new LocalDocumentsView(
66+
this.wrapRemoteDocumentCache(localDocuments.remoteDocumentCache),
67+
this.wrapMutationQueue(localDocuments.mutationQueue),
68+
localDocuments.indexManager
69+
);
70+
71+
return this.queryEngine.setLocalDocumentsView(view);
72+
}
73+
74+
private wrapRemoteDocumentCache(
75+
subject: RemoteDocumentCache
76+
): RemoteDocumentCache {
77+
return {
78+
getDocumentsMatchingQuery: (transaction, query, sinceReadTime) => {
79+
return subject
80+
.getDocumentsMatchingQuery(transaction, query, sinceReadTime)
81+
.next(result => {
82+
this.documentsRead += result.size;
83+
return result;
84+
});
85+
},
86+
getEntries: (transaction, documentKeys) => {
87+
return subject.getEntries(transaction, documentKeys).next(result => {
88+
this.documentsRead += result.size;
89+
return result;
90+
});
91+
},
92+
getEntry: (transaction, documentKey) => {
93+
return subject.getEntry(transaction, documentKey).next(result => {
94+
this.documentsRead += result ? 1 : 0;
95+
return result;
96+
});
97+
},
98+
getNewDocumentChanges: subject.getNewDocumentChanges,
99+
getSize: subject.getSize,
100+
newChangeBuffer: subject.newChangeBuffer
101+
};
102+
}
103+
104+
private wrapMutationQueue(subject: MutationQueue): MutationQueue {
105+
return {
106+
acknowledgeBatch: subject.acknowledgeBatch,
107+
addMutationBatch: subject.addMutationBatch,
108+
checkEmpty: subject.checkEmpty,
109+
getAllMutationBatches: transaction => {
110+
return subject.getAllMutationBatches(transaction).next(result => {
111+
this.mutationsRead += result.length;
112+
return result;
113+
});
114+
},
115+
getAllMutationBatchesAffectingDocumentKey: (transaction, documentKey) => {
116+
return subject
117+
.getAllMutationBatchesAffectingDocumentKey(transaction, documentKey)
118+
.next(result => {
119+
this.mutationsRead += result.length;
120+
return result;
121+
});
122+
},
123+
getAllMutationBatchesAffectingDocumentKeys: (
124+
transaction,
125+
documentKeys
126+
) => {
127+
return subject
128+
.getAllMutationBatchesAffectingDocumentKeys(transaction, documentKeys)
129+
.next(result => {
130+
this.mutationsRead += result.length;
131+
return result;
132+
});
133+
},
134+
getAllMutationBatchesAffectingQuery: (transaction, query) => {
135+
return subject
136+
.getAllMutationBatchesAffectingQuery(transaction, query)
137+
.next(result => {
138+
this.mutationsRead += result.length;
139+
return result;
140+
});
141+
},
142+
getHighestUnacknowledgedBatchId: subject.getHighestUnacknowledgedBatchId,
143+
getLastStreamToken: subject.getLastStreamToken,
144+
getNextMutationBatchAfterBatchId: subject.getNextMutationBatchAfterBatchId,
145+
lookupMutationBatch: subject.lookupMutationBatch,
146+
lookupMutationKeys: subject.lookupMutationKeys,
147+
performConsistencyCheck: subject.performConsistencyCheck,
148+
removeCachedMutationKeys: subject.removeCachedMutationKeys,
149+
removeMutationBatch: subject.removeMutationBatch,
150+
setLastStreamToken: subject.setLastStreamToken
151+
};
152+
}
153+
}

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

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
docAddedRemoteEvent,
6060
docUpdateRemoteEvent,
6161
expectEqual,
62+
filter,
6263
key,
6364
localViewChanges,
6465
mapAsArray,
@@ -72,14 +73,28 @@ import {
7273
} from '../../util/helpers';
7374

7475
import { FieldValue, IntegerValue } from '../../../src/model/field_value';
76+
import { CountingQueryEngine } from './forwarding_counting_query_engine';
7577
import * as persistenceHelpers from './persistence_test_helpers';
7678

7779
class LocalStoreTester {
7880
private promiseChain: Promise<void> = Promise.resolve();
7981
private lastChanges: MaybeDocumentMap | null = null;
8082
private lastTargetId: TargetId | null = null;
8183
private batches: MutationBatch[] = [];
82-
constructor(public localStore: LocalStore, readonly gcIsEager: boolean) {}
84+
85+
constructor(
86+
public localStore: LocalStore,
87+
private readonly queryEngine: CountingQueryEngine,
88+
readonly gcIsEager: boolean
89+
) {}
90+
91+
private prepareNextStep(): void {
92+
this.promiseChain = this.promiseChain.then(() => {
93+
this.lastChanges = null;
94+
this.lastTargetId = null;
95+
this.queryEngine.resetCounts();
96+
});
97+
}
8398

8499
after(
85100
op: Mutation | Mutation[] | RemoteEvent | LocalViewChanges
@@ -96,6 +111,8 @@ class LocalStoreTester {
96111
}
97112

98113
afterMutations(mutations: Mutation[]): LocalStoreTester {
114+
this.prepareNextStep();
115+
99116
this.promiseChain = this.promiseChain
100117
.then(() => {
101118
return this.localStore.localWrite(mutations);
@@ -110,6 +127,8 @@ class LocalStoreTester {
110127
}
111128

112129
afterRemoteEvent(remoteEvent: RemoteEvent): LocalStoreTester {
130+
this.prepareNextStep();
131+
113132
this.promiseChain = this.promiseChain
114133
.then(() => {
115134
return this.localStore.applyRemoteEvent(remoteEvent);
@@ -121,6 +140,8 @@ class LocalStoreTester {
121140
}
122141

123142
afterViewChanges(viewChanges: LocalViewChanges): LocalStoreTester {
143+
this.prepareNextStep();
144+
124145
this.promiseChain = this.promiseChain.then(() =>
125146
this.localStore.notifyLocalViewChanges([viewChanges])
126147
);
@@ -131,6 +152,8 @@ class LocalStoreTester {
131152
documentVersion: TestSnapshotVersion;
132153
transformResult?: FieldValue;
133154
}): LocalStoreTester {
155+
this.prepareNextStep();
156+
134157
this.promiseChain = this.promiseChain
135158
.then(() => {
136159
const batch = this.batches.shift()!;
@@ -161,6 +184,8 @@ class LocalStoreTester {
161184
}
162185

163186
afterRejectingMutation(): LocalStoreTester {
187+
this.prepareNextStep();
188+
164189
this.promiseChain = this.promiseChain
165190
.then(() => {
166191
return this.localStore.rejectBatch(this.batches.shift()!.batchId);
@@ -172,6 +197,8 @@ class LocalStoreTester {
172197
}
173198

174199
afterAllocatingQuery(query: Query): LocalStoreTester {
200+
this.prepareNextStep();
201+
175202
this.promiseChain = this.promiseChain.then(() => {
176203
return this.localStore.allocateQuery(query).then(result => {
177204
this.lastTargetId = result.targetId;
@@ -181,6 +208,8 @@ class LocalStoreTester {
181208
}
182209

183210
afterReleasingQuery(query: Query): LocalStoreTester {
211+
this.prepareNextStep();
212+
184213
this.promiseChain = this.promiseChain.then(() => {
185214
return this.localStore.releaseQuery(
186215
query,
@@ -190,6 +219,42 @@ class LocalStoreTester {
190219
return this;
191220
}
192221

222+
afterExecutingQuery(query: Query): LocalStoreTester {
223+
this.prepareNextStep();
224+
225+
this.promiseChain = this.promiseChain.then(() => {
226+
return this.localStore.executeQuery(query).then(results => {
227+
this.lastChanges = results;
228+
});
229+
});
230+
return this;
231+
}
232+
233+
/**
234+
* Asserts the expected number of mutations and documents read by
235+
* the MutationQueue and the RemoteDocumentCache.
236+
*/
237+
toHaveRead(expectedCount: {
238+
mutations?: number;
239+
remoteDocuments?: number;
240+
}): LocalStoreTester {
241+
this.promiseChain = this.promiseChain.then(() => {
242+
if (expectedCount.mutations !== undefined) {
243+
expect(this.queryEngine.mutationsRead).to.be.eq(
244+
expectedCount.mutations,
245+
'Mutations read'
246+
);
247+
}
248+
if (expectedCount.remoteDocuments !== undefined) {
249+
expect(this.queryEngine.documentsRead).to.be.eq(
250+
expectedCount.remoteDocuments,
251+
'Remote documents read'
252+
);
253+
}
254+
});
255+
return this;
256+
}
257+
193258
toReturnTargetId(id: TargetId): LocalStoreTester {
194259
this.promiseChain = this.promiseChain.then(() => {
195260
expect(this.lastTargetId).to.equal(id);
@@ -310,10 +375,16 @@ function genericLocalStoreTests(
310375
): void {
311376
let persistence: Persistence;
312377
let localStore: LocalStore;
378+
let countingQueryEngine: CountingQueryEngine;
313379

314380
beforeEach(async () => {
315381
persistence = await getPersistence();
316-
localStore = new LocalStore(persistence, queryEngine, User.UNAUTHENTICATED);
382+
countingQueryEngine = new CountingQueryEngine(queryEngine);
383+
localStore = new LocalStore(
384+
persistence,
385+
countingQueryEngine,
386+
User.UNAUTHENTICATED
387+
);
317388
});
318389

319390
afterEach(async () => {
@@ -322,7 +393,7 @@ function genericLocalStoreTests(
322393
});
323394

324395
function expectLocalStore(): LocalStoreTester {
325-
return new LocalStoreTester(localStore, gcIsEager);
396+
return new LocalStoreTester(localStore, countingQueryEngine, gcIsEager);
326397
}
327398

328399
it('handles SetMutation', () => {
@@ -940,6 +1011,44 @@ function genericLocalStoreTests(
9401011
]);
9411012
});
9421013

1014+
it('reads all documents for initial collection queries', () => {
1015+
const firstQuery = Query.atPath(path('foo'));
1016+
const secondQuery = Query.atPath(path('foo')).addFilter(
1017+
filter('matches', '==', true)
1018+
);
1019+
1020+
return expectLocalStore()
1021+
.afterAllocatingQuery(firstQuery)
1022+
.toReturnTargetId(2)
1023+
.after(
1024+
docAddedRemoteEvent(
1025+
[
1026+
doc('foo/bar', 10, { matches: true }),
1027+
doc('foo/baz', 20, { matches: true })
1028+
],
1029+
[2]
1030+
)
1031+
)
1032+
.toReturnChanged(
1033+
doc('foo/bar', 10, { matches: true }),
1034+
doc('foo/baz', 20, { matches: true })
1035+
)
1036+
.after(setMutation('foo/bonk', { matches: true }))
1037+
.toReturnChanged(
1038+
doc('foo/bonk', 0, { matches: true }, { hasLocalMutations: true })
1039+
)
1040+
.afterAllocatingQuery(secondQuery)
1041+
.toReturnTargetId(4)
1042+
.afterExecutingQuery(secondQuery)
1043+
.toReturnChanged(
1044+
doc('foo/bar', 10, { matches: true }),
1045+
doc('foo/baz', 20, { matches: true }),
1046+
doc('foo/bonk', 0, { matches: true }, { hasLocalMutations: true })
1047+
)
1048+
.toHaveRead({ remoteDocuments: 2, mutations: 1 })
1049+
.finish();
1050+
});
1051+
9431052
it('persists resume tokens', async () => {
9441053
if (gcIsEager) {
9451054
return;

0 commit comments

Comments
 (0)