Skip to content

Commit 3760ddf

Browse files
Merge 274c9f7 into 96076f0
2 parents 96076f0 + 274c9f7 commit 3760ddf

File tree

10 files changed

+413
-336
lines changed

10 files changed

+413
-336
lines changed

packages/firestore/exp/dependencies.json

Lines changed: 205 additions & 93 deletions
Large diffs are not rendered by default.

packages/firestore/lite/dependencies.json

Lines changed: 73 additions & 125 deletions
Large diffs are not rendered by default.

packages/firestore/lite/src/api/reference.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
import {
3232
Bound,
3333
Direction,
34+
hasLimitToLast,
3435
LimitType,
3536
newQueryForCollectionGroup,
3637
newQueryForPath,
@@ -579,7 +580,7 @@ export function getDocs<T>(
579580
)
580581
);
581582

582-
if (queryImpl._query.hasLimitToLast()) {
583+
if (hasLimitToLast(queryImpl._query)) {
583584
// Limit to last queries reverse the orderBy constraint that was
584585
// specified by the user. As such, we need to reverse the order of the
585586
// results to return the documents in the expected order.

packages/firestore/src/api/database.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ import {
5858
Direction,
5959
FieldFilter,
6060
Filter,
61+
findFilterOperator,
62+
getFirstOrderByField,
63+
getInequalityFilterField,
6164
isCollectionGroupQuery,
6265
LimitType,
6366
newQueryComparator,
@@ -72,7 +75,8 @@ import {
7275
queryWithAddedOrderBy,
7376
queryWithEndAt,
7477
queryWithLimit,
75-
queryWithStartAt
78+
queryWithStartAt,
79+
hasLimitToLast
7680
} from '../core/query';
7781
import { Transaction as InternalTransaction } from '../core/transaction';
7882
import { ChangeType, ViewSnapshot } from '../core/view_snapshot';
@@ -1784,7 +1788,7 @@ function validateNewFilter(query: InternalQuery, filter: Filter): void {
17841788
debugAssert(filter instanceof FieldFilter, 'Only FieldFilters are supported');
17851789

17861790
if (filter.isInequality()) {
1787-
const existingField = query.getInequalityFilterField();
1791+
const existingField = getInequalityFilterField(query);
17881792
if (existingField !== null && !existingField.isEqual(filter.field)) {
17891793
throw new FirestoreError(
17901794
Code.INVALID_ARGUMENT,
@@ -1795,13 +1799,13 @@ function validateNewFilter(query: InternalQuery, filter: Filter): void {
17951799
);
17961800
}
17971801

1798-
const firstOrderByField = query.getFirstOrderByField();
1802+
const firstOrderByField = getFirstOrderByField(query);
17991803
if (firstOrderByField !== null) {
18001804
validateOrderByAndInequalityMatch(query, filter.field, firstOrderByField);
18011805
}
18021806
}
18031807

1804-
const conflictingOp = query.findFilterOperator(conflictingOps(filter.op));
1808+
const conflictingOp = findFilterOperator(query, conflictingOps(filter.op));
18051809
if (conflictingOp !== null) {
18061810
// Special case when it's a duplicate op to give a slightly clearer error message.
18071811
if (conflictingOp === filter.op) {
@@ -1821,9 +1825,9 @@ function validateNewFilter(query: InternalQuery, filter: Filter): void {
18211825
}
18221826

18231827
function validateNewOrderBy(query: InternalQuery, orderBy: OrderBy): void {
1824-
if (query.getFirstOrderByField() === null) {
1828+
if (getFirstOrderByField(query) === null) {
18251829
// This is the first order by. It must match any inequality.
1826-
const inequalityField = query.getInequalityFilterField();
1830+
const inequalityField = getInequalityFilterField(query);
18271831
if (inequalityField !== null) {
18281832
validateOrderByAndInequalityMatch(query, inequalityField, orderBy.field);
18291833
}
@@ -1850,7 +1854,7 @@ function validateOrderByAndInequalityMatch(
18501854
export function validateHasExplicitOrderByForLimitToLast(
18511855
query: InternalQuery
18521856
): void {
1853-
if (query.hasLimitToLast() && query.explicitOrderBy.length === 0) {
1857+
if (hasLimitToLast(query) && query.explicitOrderBy.length === 0) {
18541858
throw new FirestoreError(
18551859
Code.UNIMPLEMENTED,
18561860
'limitToLast() queries require specifying at least one orderBy() clause'

packages/firestore/src/core/query.ts

Lines changed: 89 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,6 @@ export interface Query {
6363
readonly limitType: LimitType;
6464
readonly startAt: Bound | null;
6565
readonly endAt: Bound | null;
66-
67-
hasLimitToFirst(): boolean;
68-
hasLimitToLast(): boolean;
69-
getFirstOrderByField(): FieldPath | null;
70-
getInequalityFilterField(): FieldPath | null;
71-
asCollectionQueryAtPath(path: ResourcePath): Query;
72-
73-
/**
74-
* Returns true if this query does not specify any query constraints that
75-
* could remove results.
76-
*/
77-
matchesAllDocuments(): boolean;
78-
79-
// Checks if any of the provided Operators are included in the query and
80-
// returns the first one that is, or null if none are.
81-
findFilterOperator(operators: Operator[]): Operator | null;
8266
}
8367

8468
/**
@@ -121,77 +105,6 @@ export class QueryImpl implements Query {
121105
);
122106
}
123107
}
124-
125-
/**
126-
* Helper to convert a collection group query into a collection query at a
127-
* specific path. This is used when executing collection group queries, since
128-
* we have to split the query into a set of collection queries at multiple
129-
* paths.
130-
*/
131-
asCollectionQueryAtPath(path: ResourcePath): Query {
132-
return new QueryImpl(
133-
path,
134-
/*collectionGroup=*/ null,
135-
this.explicitOrderBy.slice(),
136-
this.filters.slice(),
137-
this.limit,
138-
this.limitType,
139-
this.startAt,
140-
this.endAt
141-
);
142-
}
143-
144-
matchesAllDocuments(): boolean {
145-
return (
146-
this.filters.length === 0 &&
147-
this.limit === null &&
148-
this.startAt == null &&
149-
this.endAt == null &&
150-
(this.explicitOrderBy.length === 0 ||
151-
(this.explicitOrderBy.length === 1 &&
152-
this.explicitOrderBy[0].field.isKeyField()))
153-
);
154-
}
155-
156-
hasLimitToFirst(): boolean {
157-
return !isNullOrUndefined(this.limit) && this.limitType === LimitType.First;
158-
}
159-
160-
hasLimitToLast(): boolean {
161-
return !isNullOrUndefined(this.limit) && this.limitType === LimitType.Last;
162-
}
163-
164-
getFirstOrderByField(): FieldPath | null {
165-
return this.explicitOrderBy.length > 0
166-
? this.explicitOrderBy[0].field
167-
: null;
168-
}
169-
170-
getInequalityFilterField(): FieldPath | null {
171-
for (const filter of this.filters) {
172-
debugAssert(
173-
filter instanceof FieldFilter,
174-
'Only FieldFilters are supported'
175-
);
176-
if (filter.isInequality()) {
177-
return filter.field;
178-
}
179-
}
180-
return null;
181-
}
182-
183-
findFilterOperator(operators: Operator[]): Operator | null {
184-
for (const filter of this.filters) {
185-
debugAssert(
186-
filter instanceof FieldFilter,
187-
'Only FieldFilters are supported'
188-
);
189-
if (operators.indexOf(filter.op) >= 0) {
190-
return filter.op;
191-
}
192-
}
193-
return null;
194-
}
195108
}
196109

197110
/** Creates a new Query instance with the options provided. */
@@ -222,6 +135,91 @@ export function newQueryForPath(path: ResourcePath): Query {
222135
return new QueryImpl(path);
223136
}
224137

138+
/**
139+
* Helper to convert a collection group query into a collection query at a
140+
* specific path. This is used when executing collection group queries, since
141+
* we have to split the query into a set of collection queries at multiple
142+
* paths.
143+
*/
144+
export function asCollectionQueryAtPath(
145+
query: Query,
146+
path: ResourcePath
147+
): Query {
148+
return new QueryImpl(
149+
path,
150+
/*collectionGroup=*/ null,
151+
query.explicitOrderBy.slice(),
152+
query.filters.slice(),
153+
query.limit,
154+
query.limitType,
155+
query.startAt,
156+
query.endAt
157+
);
158+
}
159+
160+
/**
161+
* Returns true if this query does not specify any query constraints that
162+
* could remove results.
163+
*/
164+
export function matchesAllDocuments(query: Query): boolean {
165+
return (
166+
query.filters.length === 0 &&
167+
query.limit === null &&
168+
query.startAt == null &&
169+
query.endAt == null &&
170+
(query.explicitOrderBy.length === 0 ||
171+
(query.explicitOrderBy.length === 1 &&
172+
query.explicitOrderBy[0].field.isKeyField()))
173+
);
174+
}
175+
176+
export function hasLimitToFirst(query: Query): boolean {
177+
return !isNullOrUndefined(query.limit) && query.limitType === LimitType.First;
178+
}
179+
180+
export function hasLimitToLast(query: Query): boolean {
181+
return !isNullOrUndefined(query.limit) && query.limitType === LimitType.Last;
182+
}
183+
184+
export function getFirstOrderByField(query: Query): FieldPath | null {
185+
return query.explicitOrderBy.length > 0
186+
? query.explicitOrderBy[0].field
187+
: null;
188+
}
189+
190+
export function getInequalityFilterField(query: Query): FieldPath | null {
191+
for (const filter of query.filters) {
192+
debugAssert(
193+
filter instanceof FieldFilter,
194+
'Only FieldFilters are supported'
195+
);
196+
if (filter.isInequality()) {
197+
return filter.field;
198+
}
199+
}
200+
return null;
201+
}
202+
203+
/**
204+
* Checks if any of the provided Operators are included in the query and
205+
* returns the first one that is, or null if none are.
206+
*/
207+
export function findFilterOperator(
208+
query: Query,
209+
operators: Operator[]
210+
): Operator | null {
211+
for (const filter of query.filters) {
212+
debugAssert(
213+
filter instanceof FieldFilter,
214+
'Only FieldFilters are supported'
215+
);
216+
if (operators.indexOf(filter.op) >= 0) {
217+
return filter.op;
218+
}
219+
}
220+
return null;
221+
}
222+
225223
/**
226224
* Creates a new Query for a collection group query that matches all documents
227225
* within the provided collection group.
@@ -260,8 +258,8 @@ export function queryOrderBy(query: Query): OrderBy[] {
260258
if (queryImpl.memoizedOrderBy === null) {
261259
queryImpl.memoizedOrderBy = [];
262260

263-
const inequalityField = queryImpl.getInequalityFilterField();
264-
const firstOrderByField = queryImpl.getFirstOrderByField();
261+
const inequalityField = getInequalityFilterField(queryImpl);
262+
const firstOrderByField = getFirstOrderByField(queryImpl);
265263
if (inequalityField !== null && firstOrderByField === null) {
266264
// In order to implicitly add key ordering, we must also add the
267265
// inequality filter field for it to be a valid query.
@@ -355,10 +353,10 @@ export function queryToTarget(query: Query): Target {
355353

356354
export function queryWithAddedFilter(query: Query, filter: Filter): Query {
357355
debugAssert(
358-
query.getInequalityFilterField() == null ||
356+
getInequalityFilterField(query) == null ||
359357
!(filter instanceof FieldFilter) ||
360358
!filter.isInequality() ||
361-
filter.field.isEqual(query.getInequalityFilterField()!),
359+
filter.field.isEqual(getInequalityFilterField(query)!),
362360
'Query must only have one inequality field.'
363361
);
364362

packages/firestore/src/core/view.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ import { DocumentSet } from '../model/document_set';
2727
import { TargetChange } from '../remote/remote_event';
2828
import { debugAssert, fail } from '../util/assert';
2929

30-
import { newQueryComparator, Query, queryMatches } from './query';
30+
import {
31+
hasLimitToFirst,
32+
hasLimitToLast,
33+
newQueryComparator,
34+
Query,
35+
queryMatches
36+
} from './query';
3137
import { OnlineState } from './types';
3238
import {
3339
ChangeType,
@@ -140,11 +146,11 @@ export class View {
140146
// Note that this should never get used in a refill (when previousChanges is
141147
// set), because there will only be adds -- no deletes or updates.
142148
const lastDocInLimit =
143-
this.query.hasLimitToFirst() && oldDocumentSet.size === this.query.limit
149+
hasLimitToFirst(this.query) && oldDocumentSet.size === this.query.limit
144150
? oldDocumentSet.last()
145151
: null;
146152
const firstDocInLimit =
147-
this.query.hasLimitToLast() && oldDocumentSet.size === this.query.limit
153+
hasLimitToLast(this.query) && oldDocumentSet.size === this.query.limit
148154
? oldDocumentSet.first()
149155
: null;
150156

@@ -234,9 +240,9 @@ export class View {
234240
);
235241

236242
// Drop documents out to meet limit/limitToLast requirement.
237-
if (this.query.hasLimitToFirst() || this.query.hasLimitToLast()) {
243+
if (hasLimitToFirst(this.query) || hasLimitToLast(this.query)) {
238244
while (newDocumentSet.size > this.query.limit!) {
239-
const oldDoc = this.query.hasLimitToFirst()
245+
const oldDoc = hasLimitToFirst(this.query)
240246
? newDocumentSet.last()
241247
: newDocumentSet.first();
242248
newDocumentSet = newDocumentSet.delete(oldDoc!.key);

packages/firestore/src/local/index_free_query_engine.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import { LocalDocumentsView } from './local_documents_view';
2020
import { PersistenceTransaction } from './persistence';
2121
import { PersistencePromise } from './persistence_promise';
2222
import {
23+
hasLimitToFirst,
24+
hasLimitToLast,
2325
LimitType,
26+
matchesAllDocuments,
2427
newQueryComparator,
2528
Query,
2629
queryMatches,
@@ -78,7 +81,7 @@ export class IndexFreeQueryEngine implements QueryEngine {
7881
// Queries that match all documents don't benefit from using
7982
// IndexFreeQueries. It is more efficient to scan all documents in a
8083
// collection, rather than to perform individual lookups.
81-
if (query.matchesAllDocuments()) {
84+
if (matchesAllDocuments(query)) {
8285
return this.executeFullCollectionScan(transaction, query);
8386
}
8487

@@ -93,7 +96,7 @@ export class IndexFreeQueryEngine implements QueryEngine {
9396
const previousResults = this.applyQuery(query, documents);
9497

9598
if (
96-
(query.hasLimitToFirst() || query.hasLimitToLast()) &&
99+
(hasLimitToFirst(query) || hasLimitToLast(query)) &&
97100
this.needsRefill(
98101
query.limitType,
99102
previousResults,

packages/firestore/src/local/local_documents_view.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import {
19+
asCollectionQueryAtPath,
1920
isCollectionGroupQuery,
2021
isDocumentQuery,
2122
Query,
@@ -211,7 +212,8 @@ export class LocalDocumentsView {
211212
// Perform a collection query against each parent that contains the
212213
// collectionId and aggregate the results.
213214
return PersistencePromise.forEach(parents, (parent: ResourcePath) => {
214-
const collectionQuery = query.asCollectionQueryAtPath(
215+
const collectionQuery = asCollectionQueryAtPath(
216+
query,
215217
parent.child(collectionId)
216218
);
217219
return this.getDocumentsMatchingCollectionQuery(

0 commit comments

Comments
 (0)