Skip to content

Commit 0551030

Browse files
author
Brian Chen
authored
Backport updates from Android (multiple changes) (#1912)
1 parent 15a32c6 commit 0551030

File tree

6 files changed

+347
-234
lines changed

6 files changed

+347
-234
lines changed

packages/firebase/index.d.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6182,20 +6182,20 @@ declare namespace firebase.firestore {
61826182
* Clears the persistent storage. This includes pending writes and cached
61836183
* documents.
61846184
*
6185-
* Must be called while the firestore instance is not started (after the app is
6186-
* shutdown or when the app is first initialized). On startup, this method
6187-
* must be called before other methods (other than settings()). If the
6188-
* firestore instance is still running, the promise will be rejected with
6189-
* the error code of `failed-precondition`.
6185+
* Must be called while the firestore instance is not started (after the app
6186+
* is shutdown or when the app is first initialized). On startup, this
6187+
* method must be called before other methods (other than settings()). If
6188+
* the firestore instance is still running, the promise will be rejected
6189+
* with the error code of `failed-precondition`.
61906190
*
61916191
* Note: clearPersistence() is primarily intended to help write reliable
6192-
* tests that use Firestore. It uses the most efficient mechanism possible
6193-
* for dropping existing data but does not attempt to securely overwrite or
6192+
* tests that use Cloud Firestore. It uses an efficient mechanism for
6193+
* dropping existing data but does not attempt to securely overwrite or
61946194
* otherwise make cached data unrecoverable. For applications that are
6195-
* sensitive to the disclosure of cache data in between user sessions we
6196-
* strongly recommend not to enable persistence in the first place.
6195+
* sensitive to the disclosure of cached data in between user sessions, we
6196+
* strongly recommend not enabling persistence at all.
61976197
*
6198-
* @return A promise that is resolved once the persistent storage has been
6198+
* @return A promise that is resolved when the persistent storage is
61996199
* cleared. Otherwise, the promise is rejected with an error.
62006200
*/
62016201
clearPersistence(): Promise<void>;

packages/firestore-types/index.d.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -240,20 +240,20 @@ export class FirebaseFirestore {
240240
* Clears the persistent storage. This includes pending writes and cached
241241
* documents.
242242
*
243-
* Must be called while the firestore instance is not started (after the app is
244-
* shutdown or when the app is first initialized). On startup, this method
245-
* must be called before other methods (other than settings()). If the
246-
* firestore instance is still running, the promise will be rejected with
247-
* the error code of `failed-precondition`.
243+
* Must be called while the firestore instance is not started (after the app
244+
* is shutdown or when the app is first initialized). On startup, this
245+
* method must be called before other methods (other than settings()). If
246+
* the firestore instance is still running, the promise will be rejected
247+
* with the error code of `failed-precondition`.
248248
*
249249
* Note: clearPersistence() is primarily intended to help write reliable
250-
* tests that use Firestore. It uses the most efficient mechanism possible
251-
* for dropping existing data but does not attempt to securely overwrite or
250+
* tests that use Cloud Firestore. It uses an efficient mechanism for
251+
* dropping existing data but does not attempt to securely overwrite or
252252
* otherwise make cached data unrecoverable. For applications that are
253-
* sensitive to the disclosure of cache data in between user sessions we
254-
* strongly recommend not to enable persistence in the first place.
253+
* sensitive to the disclosure of cached data in between user sessions, we
254+
* strongly recommend not enabling persistence at all.
255255
*
256-
* @return A promise that is resolved once the persistent storage has been
256+
* @return A promise that is resolved when the persistent storage is
257257
* cleared. Otherwise, the promise is rejected with an error.
258258
*/
259259
clearPersistence(): Promise<void>;

packages/firestore/src/api/database.ts

Lines changed: 102 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,87 +1428,35 @@ export class Query implements firestore.Query {
14281428
validateStringEnum('Query.where', whereFilterOpEnums, 2, opStr);
14291429
}
14301430

1431-
let fieldValue;
1431+
let fieldValue: FieldValue;
14321432
const fieldPath = fieldPathFromArgument('Query.where', field);
14331433
const operator = Operator.fromString(opStr);
14341434
if (fieldPath.isKeyField()) {
14351435
if (
14361436
operator === Operator.ARRAY_CONTAINS ||
1437-
operator === Operator.ARRAY_CONTAINS_ANY ||
1438-
operator === Operator.IN
1437+
operator === Operator.ARRAY_CONTAINS_ANY
14391438
) {
14401439
throw new FirestoreError(
14411440
Code.INVALID_ARGUMENT,
14421441
`Invalid Query. You can't perform '${operator.toString()}' ` +
14431442
'queries on FieldPath.documentId().'
14441443
);
1445-
}
1446-
if (typeof value === 'string') {
1447-
if (value === '') {
1448-
throw new FirestoreError(
1449-
Code.INVALID_ARGUMENT,
1450-
'Function Query.where() requires its third parameter to be a ' +
1451-
'valid document ID if the first parameter is ' +
1452-
'FieldPath.documentId(), but it was an empty string.'
1453-
);
1444+
} else if (operator === Operator.IN) {
1445+
this.validateDisjunctiveFilterElements(value, operator);
1446+
const referenceList: FieldValue[] = [];
1447+
for (const arrayValue of value as FieldValue[]) {
1448+
referenceList.push(this.parseDocumentIdValue(arrayValue));
14541449
}
1455-
if (
1456-
!this._query.isCollectionGroupQuery() &&
1457-
value.indexOf('/') !== -1
1458-
) {
1459-
throw new FirestoreError(
1460-
Code.INVALID_ARGUMENT,
1461-
`Invalid third parameter to Query.where(). When querying a collection by ` +
1462-
`FieldPath.documentId(), the value provided must be a plain document ID, but ` +
1463-
`'${value}' contains a slash.`
1464-
);
1465-
}
1466-
const path = this._query.path.child(ResourcePath.fromString(value));
1467-
if (!DocumentKey.isDocumentKey(path)) {
1468-
throw new FirestoreError(
1469-
Code.INVALID_ARGUMENT,
1470-
`Invalid third parameter to Query.where(). When querying a collection group by ` +
1471-
`FieldPath.documentId(), the value provided must result in a valid document path, ` +
1472-
`but '${path}' is not because it has an odd number of segments (${
1473-
path.length
1474-
}).`
1475-
);
1476-
}
1477-
fieldValue = new RefValue(
1478-
this.firestore._databaseId,
1479-
new DocumentKey(path)
1480-
);
1481-
} else if (value instanceof DocumentReference) {
1482-
const ref = value as DocumentReference;
1483-
fieldValue = new RefValue(this.firestore._databaseId, ref._key);
1450+
fieldValue = new ArrayValue(referenceList);
14841451
} else {
1485-
throw new FirestoreError(
1486-
Code.INVALID_ARGUMENT,
1487-
`Function Query.where() requires its third parameter to be a ` +
1488-
`string or a DocumentReference if the first parameter is ` +
1489-
`FieldPath.documentId(), but it was: ` +
1490-
`${valueDescription(value)}.`
1491-
);
1452+
fieldValue = this.parseDocumentIdValue(value);
14921453
}
14931454
} else {
14941455
if (
14951456
operator === Operator.IN ||
14961457
operator === Operator.ARRAY_CONTAINS_ANY
14971458
) {
1498-
if (!Array.isArray(value) || value.length === 0) {
1499-
throw new FirestoreError(
1500-
Code.INVALID_ARGUMENT,
1501-
'Invalid Query. A non-empty array is required for ' +
1502-
`'${operator.toString()}' filters.`
1503-
);
1504-
}
1505-
if (value.length > 10) {
1506-
throw new FirestoreError(
1507-
Code.INVALID_ARGUMENT,
1508-
`Invalid Query. '${operator.toString()}' filters support a ` +
1509-
'maximum of 10 elements in the value array.'
1510-
);
1511-
}
1459+
this.validateDisjunctiveFilterElements(value, operator);
15121460
}
15131461
fieldValue = this.firestore._dataConverter.parseQueryValue(
15141462
'Query.where',
@@ -1946,6 +1894,98 @@ export class Query implements firestore.Query {
19461894
);
19471895
}
19481896

1897+
/**
1898+
* Parses the given documentIdValue into a ReferenceValue, throwing
1899+
* appropriate errors if the value is anything other than a DocumentReference
1900+
* or String, or if the string is malformed.
1901+
*/
1902+
private parseDocumentIdValue(documentIdValue: unknown): RefValue {
1903+
if (typeof documentIdValue === 'string') {
1904+
if (documentIdValue === '') {
1905+
throw new FirestoreError(
1906+
Code.INVALID_ARGUMENT,
1907+
'Function Query.where() requires its third parameter to be a ' +
1908+
'valid document ID if the first parameter is ' +
1909+
'FieldPath.documentId(), but it was an empty string.'
1910+
);
1911+
}
1912+
if (
1913+
!this._query.isCollectionGroupQuery() &&
1914+
documentIdValue.indexOf('/') !== -1
1915+
) {
1916+
throw new FirestoreError(
1917+
Code.INVALID_ARGUMENT,
1918+
`Invalid third parameter to Query.where(). When querying a collection by ` +
1919+
`FieldPath.documentId(), the value provided must be a plain document ID, but ` +
1920+
`'${documentIdValue}' contains a slash.`
1921+
);
1922+
}
1923+
const path = this._query.path.child(
1924+
ResourcePath.fromString(documentIdValue)
1925+
);
1926+
if (!DocumentKey.isDocumentKey(path)) {
1927+
throw new FirestoreError(
1928+
Code.INVALID_ARGUMENT,
1929+
`Invalid third parameter to Query.where(). When querying a collection group by ` +
1930+
`FieldPath.documentId(), the value provided must result in a valid document path, ` +
1931+
`but '${path}' is not because it has an odd number of segments (${
1932+
path.length
1933+
}).`
1934+
);
1935+
}
1936+
return new RefValue(this.firestore._databaseId, new DocumentKey(path));
1937+
} else if (documentIdValue instanceof DocumentReference) {
1938+
const ref = documentIdValue as DocumentReference;
1939+
return new RefValue(this.firestore._databaseId, ref._key);
1940+
} else {
1941+
throw new FirestoreError(
1942+
Code.INVALID_ARGUMENT,
1943+
`Function Query.where() requires its third parameter to be a ` +
1944+
`string or a DocumentReference if the first parameter is ` +
1945+
`FieldPath.documentId(), but it was: ` +
1946+
`${valueDescription(documentIdValue)}.`
1947+
);
1948+
}
1949+
}
1950+
1951+
/**
1952+
* Validates that the value passed into a disjunctrive filter satisfies all
1953+
* array requirements.
1954+
*/
1955+
private validateDisjunctiveFilterElements(
1956+
value: unknown,
1957+
operator: Operator
1958+
): void {
1959+
if (!Array.isArray(value) || value.length === 0) {
1960+
throw new FirestoreError(
1961+
Code.INVALID_ARGUMENT,
1962+
'Invalid Query. A non-empty array is required for ' +
1963+
`'${operator.toString()}' filters.`
1964+
);
1965+
}
1966+
if (value.length > 10) {
1967+
throw new FirestoreError(
1968+
Code.INVALID_ARGUMENT,
1969+
`Invalid Query. '${operator.toString()}' filters support a ` +
1970+
'maximum of 10 elements in the value array.'
1971+
);
1972+
}
1973+
if (value.indexOf(null) >= 0) {
1974+
throw new FirestoreError(
1975+
Code.INVALID_ARGUMENT,
1976+
`Invalid Query. '${operator.toString()}' filters cannot contain 'null' ` +
1977+
'in the value array.'
1978+
);
1979+
}
1980+
if (value.filter(element => Number.isNaN(element)).length > 0) {
1981+
throw new FirestoreError(
1982+
Code.INVALID_ARGUMENT,
1983+
`Invalid Query. '${operator.toString()}' filters cannot contain 'NaN' ` +
1984+
'in the value array.'
1985+
);
1986+
}
1987+
}
1988+
19491989
private validateNewFilter(filter: Filter): void {
19501990
if (filter instanceof FieldFilter) {
19511991
const arrayOps = [Operator.ARRAY_CONTAINS, Operator.ARRAY_CONTAINS_ANY];

packages/firestore/src/core/query.ts

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -507,17 +507,29 @@ export class FieldFilter extends Filter {
507507
value: FieldValue
508508
): FieldFilter {
509509
if (field.isKeyField()) {
510-
assert(
511-
value instanceof RefValue,
512-
'Comparing on key, but filter value not a RefValue'
513-
);
514-
assert(
515-
op !== Operator.ARRAY_CONTAINS &&
516-
op !== Operator.ARRAY_CONTAINS_ANY &&
517-
op !== Operator.IN,
518-
`'${op.toString()}' queries don't make sense on document keys.`
519-
);
520-
return new KeyFieldFilter(field, op, value as RefValue);
510+
if (op === Operator.IN) {
511+
assert(
512+
value instanceof ArrayValue,
513+
'Comparing on key with IN, but filter value not an ArrayValue'
514+
);
515+
assert(
516+
(value as ArrayValue).internalValue.every(elem => {
517+
return elem instanceof RefValue;
518+
}),
519+
'Comparing on key with IN, but an array value was not a RefValue'
520+
);
521+
return new KeyFieldInFilter(field, value as ArrayValue);
522+
} else {
523+
assert(
524+
value instanceof RefValue,
525+
'Comparing on key, but filter value not a RefValue'
526+
);
527+
assert(
528+
op !== Operator.ARRAY_CONTAINS && op !== Operator.ARRAY_CONTAINS_ANY,
529+
`'${op.toString()}' queries don't make sense on document keys.`
530+
);
531+
return new KeyFieldFilter(field, op, value as RefValue);
532+
}
521533
} else if (value.isEqual(NullValue.INSTANCE)) {
522534
if (op !== Operator.EQUAL) {
523535
throw new FirestoreError(
@@ -631,6 +643,20 @@ export class KeyFieldFilter extends FieldFilter {
631643
}
632644
}
633645

646+
/** Filter that matches on key fields within an array. */
647+
export class KeyFieldInFilter extends FieldFilter {
648+
constructor(field: FieldPath, public value: ArrayValue) {
649+
super(field, Operator.IN, value);
650+
}
651+
652+
matches(doc: Document): boolean {
653+
const arrayValue = this.value;
654+
return arrayValue.internalValue.some(refValue => {
655+
return doc.key.isEqual((refValue as RefValue).key);
656+
});
657+
}
658+
}
659+
634660
/** A Filter that implements the array-contains operator. */
635661
export class ArrayContainsFilter extends FieldFilter {
636662
constructor(field: FieldPath, value: FieldValue) {
@@ -645,30 +671,29 @@ export class ArrayContainsFilter extends FieldFilter {
645671

646672
/** A Filter that implements the IN operator. */
647673
export class InFilter extends FieldFilter {
648-
constructor(field: FieldPath, value: ArrayValue) {
674+
constructor(field: FieldPath, public value: ArrayValue) {
649675
super(field, Operator.IN, value);
650676
}
651677

652678
matches(doc: Document): boolean {
653-
const arrayValue = this.value as ArrayValue;
679+
const arrayValue = this.value;
654680
const other = doc.field(this.field);
655681
return other !== undefined && arrayValue.contains(other);
656682
}
657683
}
658684

659685
/** A Filter that implements the array-contains-any operator. */
660686
export class ArrayContainsAnyFilter extends FieldFilter {
661-
constructor(field: FieldPath, value: ArrayValue) {
687+
constructor(field: FieldPath, public value: ArrayValue) {
662688
super(field, Operator.ARRAY_CONTAINS_ANY, value);
663689
}
664690

665691
matches(doc: Document): boolean {
666-
const arrayValue = this.value as ArrayValue;
667692
const other = doc.field(this.field);
668693
return (
669694
other instanceof ArrayValue &&
670695
other.internalValue.some(lhsElem => {
671-
return arrayValue.contains(lhsElem);
696+
return this.value.contains(lhsElem);
672697
})
673698
);
674699
}

packages/firestore/test/integration/api/query.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,28 @@ apiDescribe('Queries', (persistence: boolean) => {
585585
}
586586
);
587587

588+
(isRunningAgainstEmulator() ? it : it.skip)(
589+
'can use IN filters by document ID',
590+
async () => {
591+
const testDocs = {
592+
aa: { key: 'aa' },
593+
ab: { key: 'ab' },
594+
ba: { key: 'ba' },
595+
bb: { key: 'bb' }
596+
};
597+
await withTestCollection(persistence, testDocs, async coll => {
598+
const snapshot = await coll
599+
.where(FieldPath.documentId(), inOp, ['aa', 'ab'])
600+
.get();
601+
602+
expect(toDataArray(snapshot)).to.deep.equal([
603+
{ key: 'aa' },
604+
{ key: 'ab' }
605+
]);
606+
});
607+
}
608+
);
609+
588610
// TODO(in-queries): Enable browser tests once backend support is ready.
589611
(isRunningAgainstEmulator() ? it : it.skip)(
590612
'can use array-contains-any filters',

0 commit comments

Comments
 (0)