@@ -1436,7 +1436,7 @@ export function newQueryFilter(
1436
1436
`Invalid Query. You can't perform '${ op } ' ` +
1437
1437
'queries on FieldPath.documentId().'
1438
1438
) ;
1439
- } else if ( op === Operator . IN ) {
1439
+ } else if ( op === Operator . IN || op === Operator . NOT_IN ) {
1440
1440
validateDisjunctiveFilterElements ( value , op ) ;
1441
1441
const referenceList : api . Value [ ] = [ ] ;
1442
1442
for ( const arrayValue of value as api . Value [ ] ) {
@@ -1447,14 +1447,18 @@ export function newQueryFilter(
1447
1447
fieldValue = parseDocumentIdValue ( databaseId , query , value ) ;
1448
1448
}
1449
1449
} else {
1450
- if ( op === Operator . IN || op === Operator . ARRAY_CONTAINS_ANY ) {
1450
+ if (
1451
+ op === Operator . IN ||
1452
+ op === Operator . NOT_IN ||
1453
+ op === Operator . ARRAY_CONTAINS_ANY
1454
+ ) {
1451
1455
validateDisjunctiveFilterElements ( value , op ) ;
1452
1456
}
1453
1457
fieldValue = parseQueryValue (
1454
1458
dataReader ,
1455
1459
methodName ,
1456
1460
value ,
1457
- op === Operator . IN
1461
+ /* allowArrays= */ op === Operator . IN || op === Operator . NOT_IN
1458
1462
) ;
1459
1463
}
1460
1464
const filter = FieldFilter . create ( fieldPath , op , fieldValue ) ;
@@ -1663,7 +1667,7 @@ function parseDocumentIdValue(
1663
1667
}
1664
1668
1665
1669
/**
1666
- * Validates that the value passed into a disjunctrive filter satisfies all
1670
+ * Validates that the value passed into a disjunctive filter satisfies all
1667
1671
* array requirements.
1668
1672
*/
1669
1673
function validateDisjunctiveFilterElements (
@@ -1684,30 +1688,71 @@ function validateDisjunctiveFilterElements(
1684
1688
'maximum of 10 elements in the value array.'
1685
1689
) ;
1686
1690
}
1687
- if ( value . indexOf ( null ) >= 0 ) {
1688
- throw new FirestoreError (
1689
- Code . INVALID_ARGUMENT ,
1690
- `Invalid Query. '${ operator . toString ( ) } ' filters cannot contain 'null' ` +
1691
- 'in the value array.'
1692
- ) ;
1691
+ if ( operator === Operator . IN || operator === Operator . ARRAY_CONTAINS_ANY ) {
1692
+ if ( value . indexOf ( null ) >= 0 ) {
1693
+ throw new FirestoreError (
1694
+ Code . INVALID_ARGUMENT ,
1695
+ `Invalid Query. '${ operator . toString ( ) } ' filters cannot contain 'null' ` +
1696
+ 'in the value array.'
1697
+ ) ;
1698
+ }
1699
+ if ( value . filter ( element => Number . isNaN ( element ) ) . length > 0 ) {
1700
+ throw new FirestoreError (
1701
+ Code . INVALID_ARGUMENT ,
1702
+ `Invalid Query. '${ operator . toString ( ) } ' filters cannot contain 'NaN' ` +
1703
+ 'in the value array.'
1704
+ ) ;
1705
+ }
1693
1706
}
1694
- if ( value . filter ( element => Number . isNaN ( element ) ) . length > 0 ) {
1695
- throw new FirestoreError (
1696
- Code . INVALID_ARGUMENT ,
1697
- `Invalid Query. '${ operator . toString ( ) } ' filters cannot contain 'NaN' ` +
1698
- 'in the value array.'
1699
- ) ;
1707
+ }
1708
+
1709
+ /**
1710
+ * Given an operator, returns the set of operators that cannot be used with it.
1711
+ *
1712
+ * Operators in a query must adhere to the following set of rules:
1713
+ * 1. Only one array operator is allowed.
1714
+ * 2. Only one disjunctive operator is allowed.
1715
+ * 3. NOT_EQUAL cannot be used with another NOT_EQUAL operator.
1716
+ * 4. NOT_IN cannot be used with array, disjunctive, or NOT_EQUAL operators.
1717
+ *
1718
+ * Array operators: ARRAY_CONTAINS, ARRAY_CONTAINS_ANY
1719
+ * Disjunctive operators: IN, ARRAY_CONTAINS_ANY, NOT_IN
1720
+ */
1721
+ function conflictingOps ( op : Operator ) : Operator [ ] {
1722
+ switch ( op ) {
1723
+ case Operator . NOT_EQUAL :
1724
+ return [ Operator . NOT_EQUAL , Operator . NOT_IN ] ;
1725
+ case Operator . ARRAY_CONTAINS :
1726
+ return [
1727
+ Operator . ARRAY_CONTAINS ,
1728
+ Operator . ARRAY_CONTAINS_ANY ,
1729
+ Operator . NOT_IN
1730
+ ] ;
1731
+ case Operator . IN :
1732
+ return [ Operator . ARRAY_CONTAINS_ANY , Operator . IN , Operator . NOT_IN ] ;
1733
+ case Operator . ARRAY_CONTAINS_ANY :
1734
+ return [
1735
+ Operator . ARRAY_CONTAINS ,
1736
+ Operator . ARRAY_CONTAINS_ANY ,
1737
+ Operator . IN ,
1738
+ Operator . NOT_IN
1739
+ ] ;
1740
+ case Operator . NOT_IN :
1741
+ return [
1742
+ Operator . ARRAY_CONTAINS ,
1743
+ Operator . ARRAY_CONTAINS_ANY ,
1744
+ Operator . IN ,
1745
+ Operator . NOT_IN ,
1746
+ Operator . NOT_EQUAL
1747
+ ] ;
1748
+ default :
1749
+ return [ ] ;
1700
1750
}
1701
1751
}
1702
1752
1703
1753
function validateNewFilter ( query : InternalQuery , filter : Filter ) : void {
1704
1754
debugAssert ( filter instanceof FieldFilter , 'Only FieldFilters are supported' ) ;
1705
1755
1706
- const arrayOps = [ Operator . ARRAY_CONTAINS , Operator . ARRAY_CONTAINS_ANY ] ;
1707
- const disjunctiveOps = [ Operator . IN , Operator . ARRAY_CONTAINS_ANY ] ;
1708
- const isArrayOp = arrayOps . indexOf ( filter . op ) >= 0 ;
1709
- const isDisjunctiveOp = disjunctiveOps . indexOf ( filter . op ) >= 0 ;
1710
-
1711
1756
if ( filter . isInequality ( ) ) {
1712
1757
const existingField = query . getInequalityFilterField ( ) ;
1713
1758
if ( existingField !== null && ! existingField . isEqual ( filter . field ) ) {
@@ -1724,31 +1769,23 @@ function validateNewFilter(query: InternalQuery, filter: Filter): void {
1724
1769
if ( firstOrderByField !== null ) {
1725
1770
validateOrderByAndInequalityMatch ( query , filter . field , firstOrderByField ) ;
1726
1771
}
1727
- } else if ( isDisjunctiveOp || isArrayOp ) {
1728
- // You can have at most 1 disjunctive filter and 1 array filter. Check if
1729
- // the new filter conflicts with an existing one.
1730
- let conflictingOp : Operator | null = null ;
1731
- if ( isDisjunctiveOp ) {
1732
- conflictingOp = query . findFilterOperator ( disjunctiveOps ) ;
1733
- }
1734
- if ( conflictingOp === null && isArrayOp ) {
1735
- conflictingOp = query . findFilterOperator ( arrayOps ) ;
1736
- }
1737
- if ( conflictingOp !== null ) {
1738
- // We special case when it's a duplicate op to give a slightly clearer error message.
1739
- if ( conflictingOp === filter . op ) {
1740
- throw new FirestoreError (
1741
- Code . INVALID_ARGUMENT ,
1742
- 'Invalid query. You cannot use more than one ' +
1743
- `'${ filter . op . toString ( ) } ' filter.`
1744
- ) ;
1745
- } else {
1746
- throw new FirestoreError (
1747
- Code . INVALID_ARGUMENT ,
1748
- `Invalid query. You cannot use '${ filter . op . toString ( ) } ' filters ` +
1749
- `with '${ conflictingOp . toString ( ) } ' filters.`
1750
- ) ;
1751
- }
1772
+ }
1773
+
1774
+ const conflictingOp = query . findFilterOperator ( conflictingOps ( filter . op ) ) ;
1775
+ if ( conflictingOp !== null ) {
1776
+ // Special case when it's a duplicate op to give a slightly clearer error message.
1777
+ if ( conflictingOp === filter . op ) {
1778
+ throw new FirestoreError (
1779
+ Code . INVALID_ARGUMENT ,
1780
+ 'Invalid query. You cannot use more than one ' +
1781
+ `'${ filter . op . toString ( ) } ' filter.`
1782
+ ) ;
1783
+ } else {
1784
+ throw new FirestoreError (
1785
+ Code . INVALID_ARGUMENT ,
1786
+ `Invalid query. You cannot use '${ filter . op . toString ( ) } ' filters ` +
1787
+ `with '${ conflictingOp . toString ( ) } ' filters.`
1788
+ ) ;
1752
1789
}
1753
1790
}
1754
1791
}
@@ -1806,18 +1843,25 @@ export class Query<T = firestore.DocumentData> implements firestore.Query<T> {
1806
1843
validateExactNumberOfArgs ( 'Query.where' , arguments , 3 ) ;
1807
1844
validateDefined ( 'Query.where' , 3 , value ) ;
1808
1845
1809
- // Enumerated from the WhereFilterOp type in index.d.ts.
1810
- const whereFilterOpEnums = [
1811
- Operator . LESS_THAN ,
1812
- Operator . LESS_THAN_OR_EQUAL ,
1813
- Operator . EQUAL ,
1814
- Operator . GREATER_THAN_OR_EQUAL ,
1815
- Operator . GREATER_THAN ,
1816
- Operator . ARRAY_CONTAINS ,
1817
- Operator . IN ,
1818
- Operator . ARRAY_CONTAINS_ANY
1819
- ] ;
1820
- const op = validateStringEnum ( 'Query.where' , whereFilterOpEnums , 2 , opStr ) ;
1846
+ // TODO(ne-queries): Add 'not-in' and '!=' to validation.
1847
+ let op : Operator ;
1848
+ if ( ( opStr as unknown ) === 'not-in' || ( opStr as unknown ) === '!=' ) {
1849
+ op = opStr as Operator ;
1850
+ } else {
1851
+ // Enumerated from the WhereFilterOp type in index.d.ts.
1852
+ const whereFilterOpEnums = [
1853
+ Operator . LESS_THAN ,
1854
+ Operator . LESS_THAN_OR_EQUAL ,
1855
+ Operator . EQUAL ,
1856
+ Operator . GREATER_THAN_OR_EQUAL ,
1857
+ Operator . GREATER_THAN ,
1858
+ Operator . ARRAY_CONTAINS ,
1859
+ Operator . IN ,
1860
+ Operator . ARRAY_CONTAINS_ANY
1861
+ ] ;
1862
+ op = validateStringEnum ( 'Query.where' , whereFilterOpEnums , 2 , opStr ) ;
1863
+ }
1864
+
1821
1865
const fieldPath = fieldPathFromArgument ( 'Query.where' , field ) ;
1822
1866
const filter = newQueryFilter (
1823
1867
this . _query ,
0 commit comments