Skip to content

Commit 983a477

Browse files
feat: OR Queries (#1800)
* WIP: OR Query with unit tests. No OR operator. * Updating documentation and member visibility. * Formatting * Skipping OR Query system test. * Updated copyright holder for new files. * PR fixes. * feat: OR Query support (#1801) * OR operator support and integration tests. * Updating documentation and member visibility. * Remove usage of OPERATOR_UNSPECIFIED standing in for OR. * Removing private and internal tags from OR query public API members. * Ensure that new OR Query features are in firestore.d.ts and are exported from the main entry point. * Update documentation for OR query features to match android PR 4274. * Removing CompositeFilter and UnaryFilter from the type definitions and JS doc. * Corrected the descending order test for OR queries. * fix: update generated proto types; fix the update script (#1825) * Adding OR enum value for composit filter operator. --------- Co-authored-by: Alexander Fenster <fenster@google.com> * Updating header copyright to 2023 * Tests that require an index are configured to only run against the emulator. * Test cleanup based on PR comments. --------- Co-authored-by: Alexander Fenster <fenster@google.com>
1 parent 3737696 commit 983a477

13 files changed

+1231
-97
lines changed

dev/protos/firestore_v1beta1_proto_api.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5700,7 +5700,7 @@ export namespace google {
57005700

57015701
/** Operator enum. */
57025702
type Operator =
5703-
"OPERATOR_UNSPECIFIED"| "AND";
5703+
"OPERATOR_UNSPECIFIED"| "AND"| "OR";
57045704
}
57055705

57065706
/** Properties of a FieldFilter. */

dev/protos/v1.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3212,4 +3212,4 @@
32123212
}
32133213
}
32143214
}
3215-
}
3215+
}

dev/src/filter.ts

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*!
2+
* Copyright 2023 Google LLC. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as firestore from '@google-cloud/firestore';
18+
19+
/**
20+
* A `Filter` represents a restriction on one or more field values and can
21+
* be used to refine the results of a {@link Query}.
22+
* `Filters`s are created by invoking {@link Filter#where}, {@link Filter#or},
23+
* or {@link Filter#and} and can then be passed to {@link Query#where}
24+
* to create a new {@link Query} instance that also contains this `Filter`.
25+
*/
26+
export abstract class Filter {
27+
/**
28+
* Creates and returns a new [Filter]{@link Filter}, which can be
29+
* applied to [Query.where()]{@link Query#where}, [Filter.or()]{@link Filter#or},
30+
* or [Filter.and()]{@link Filter#and}. When applied to a [Query]{@link Query}
31+
* it requires that documents must contain the specified field and that its value should
32+
* satisfy the relation constraint provided.
33+
*
34+
* @param {string|FieldPath} fieldPath The name of a property value to compare.
35+
* @param {string} opStr A comparison operation in the form of a string.
36+
* Acceptable operator strings are "<", "<=", "==", "!=", ">=", ">", "array-contains",
37+
* "in", "not-in", and "array-contains-any".
38+
* @param {*} value The value to which to compare the field for inclusion in
39+
* a query.
40+
* @returns {Filter} The created Filter.
41+
*
42+
* @example
43+
* ```
44+
* let collectionRef = firestore.collection('col');
45+
*
46+
* collectionRef.where(Filter.where('foo', '==', 'bar')).get().then(querySnapshot => {
47+
* querySnapshot.forEach(documentSnapshot => {
48+
* console.log(`Found document at ${documentSnapshot.ref.path}`);
49+
* });
50+
* });
51+
* ```
52+
*/
53+
public static where(
54+
fieldPath: string | firestore.FieldPath,
55+
opStr: firestore.WhereFilterOp,
56+
value: unknown
57+
): Filter {
58+
return new UnaryFilter(fieldPath, opStr, value);
59+
}
60+
61+
/**
62+
* Creates and returns a new [Filter]{@link Filter} that is a
63+
* disjunction of the given {@link Filter}s. A disjunction filter includes
64+
* a document if it satisfies any of the given {@link Filter}s.
65+
*
66+
* The returned Filter can be applied to [Query.where()]{@link Query#where},
67+
* [Filter.or()]{@link Filter#or}, or [Filter.and()]{@link Filter#and}. When
68+
* applied to a [Query]{@link Query} it requires that documents must satisfy
69+
* one of the provided {@link Filter}s.
70+
*
71+
* @param {...Filter} filters Optional. The {@link Filter}s
72+
* for OR operation. These must be created with calls to {@link Filter#where},
73+
* {@link Filter#or}, or {@link Filter#and}.
74+
* @returns {Filter} The created {@link Filter}.
75+
*
76+
* @example
77+
* ```
78+
* let collectionRef = firestore.collection('col');
79+
*
80+
* // doc.foo == 'bar' || doc.baz > 0
81+
* let orFilter = Filter.or(Filter.where('foo', '==', 'bar'), Filter.where('baz', '>', 0));
82+
*
83+
* collectionRef.where(orFilter).get().then(querySnapshot => {
84+
* querySnapshot.forEach(documentSnapshot => {
85+
* console.log(`Found document at ${documentSnapshot.ref.path}`);
86+
* });
87+
* });
88+
* ```
89+
*/
90+
public static or(...filters: Filter[]): Filter {
91+
return new CompositeFilter(filters, 'OR');
92+
}
93+
94+
/**
95+
* Creates and returns a new [Filter]{@link Filter} that is a
96+
* conjunction of the given {@link Filter}s. A conjunction filter includes
97+
* a document if it satisfies any of the given {@link Filter}s.
98+
*
99+
* The returned Filter can be applied to [Query.where()]{@link Query#where},
100+
* [Filter.or()]{@link Filter#or}, or [Filter.and()]{@link Filter#and}. When
101+
* applied to a [Query]{@link Query} it requires that documents must satisfy
102+
* one of the provided {@link Filter}s.
103+
*
104+
* @param {...Filter} filters Optional. The {@link Filter}s
105+
* for AND operation. These must be created with calls to {@link Filter#where},
106+
* {@link Filter#or}, or {@link Filter#and}.
107+
* @returns {Filter} The created {@link Filter}.
108+
*
109+
* @example
110+
* ```
111+
* let collectionRef = firestore.collection('col');
112+
*
113+
* // doc.foo == 'bar' && doc.baz > 0
114+
* let andFilter = Filter.and(Filter.where('foo', '==', 'bar'), Filter.where('baz', '>', 0));
115+
*
116+
* collectionRef.where(andFilter).get().then(querySnapshot => {
117+
* querySnapshot.forEach(documentSnapshot => {
118+
* console.log(`Found document at ${documentSnapshot.ref.path}`);
119+
* });
120+
* });
121+
* ```
122+
*/
123+
public static and(...filters: Filter[]): Filter {
124+
return new CompositeFilter(filters, 'AND');
125+
}
126+
}
127+
128+
/**
129+
* A `UnaryFilter` represents a restriction on one field value and can
130+
* be used to refine the results of a {@link Query}.
131+
* `UnaryFilter`s are created by invoking {@link Filter#where} and can then
132+
* be passed to {@link Query#where} to create a new {@link Query} instance
133+
* that also contains this `UnaryFilter`.
134+
*
135+
* @private
136+
* @internal
137+
*/
138+
export class UnaryFilter extends Filter {
139+
/**
140+
@private
141+
@internal
142+
*/
143+
public constructor(
144+
private field: string | firestore.FieldPath,
145+
private operator: firestore.WhereFilterOp,
146+
private value: unknown
147+
) {
148+
super();
149+
}
150+
151+
/**
152+
@private
153+
@internal
154+
*/
155+
public _getField(): string | firestore.FieldPath {
156+
return this.field;
157+
}
158+
159+
/**
160+
@private
161+
@internal
162+
*/
163+
public _getOperator(): firestore.WhereFilterOp {
164+
return this.operator;
165+
}
166+
167+
/**
168+
@private
169+
@internal
170+
*/
171+
public _getValue(): unknown {
172+
return this.value;
173+
}
174+
}
175+
176+
/**
177+
* A `CompositeFilter` is used to narrow the set of documents returned
178+
* by a Firestore query by performing the logical OR or AND of multiple
179+
* {@link Filters}s. `CompositeFilters`s are created by invoking {@link Filter#or}
180+
* or {@link Filter#and} and can then be passed to {@link Query#where}
181+
* to create a new query instance that also contains the `CompositeFilter`.
182+
*
183+
* @private
184+
* @internal
185+
*/
186+
export class CompositeFilter extends Filter {
187+
/**
188+
@private
189+
@internal
190+
*/
191+
public constructor(
192+
private filters: Filter[],
193+
private operator: CompositeOperator
194+
) {
195+
super();
196+
}
197+
198+
/**
199+
@private
200+
@internal
201+
*/
202+
public _getFilters(): Filter[] {
203+
return this.filters;
204+
}
205+
206+
/**
207+
@private
208+
@internal
209+
*/
210+
public _getOperator(): CompositeOperator {
211+
return this.operator;
212+
}
213+
}
214+
215+
/**
216+
* Composition operator of a `CompositeFilter`. This operator specifies the
217+
* behavior of the `CompositeFilter`.
218+
*
219+
* @private
220+
* @internal
221+
*/
222+
export type CompositeOperator = 'AND' | 'OR';

dev/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export {
9393
export {BulkWriter} from './bulk-writer';
9494
export {DocumentSnapshot, QueryDocumentSnapshot} from './document';
9595
export {FieldValue} from './field-value';
96+
export {Filter} from './filter';
9697
export {WriteBatch, WriteResult} from './write-batch';
9798
export {Transaction} from './transaction';
9899
export {Timestamp} from './timestamp';

0 commit comments

Comments
 (0)