@@ -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
+ op === Operator . IN || op === Operator . NOT_IN
1458
1462
) ;
1459
1463
}
1460
1464
const filter = FieldFilter . create ( fieldPath , op , fieldValue ) ;
@@ -1684,14 +1688,17 @@ function validateDisjunctiveFilterElements(
1684
1688
'maximum of 10 elements in the value array.'
1685
1689
) ;
1686
1690
}
1687
- if ( value . indexOf ( null ) >= 0 ) {
1691
+ if ( value . indexOf ( null ) >= 0 && operator !== Operator . NOT_IN ) {
1688
1692
throw new FirestoreError (
1689
1693
Code . INVALID_ARGUMENT ,
1690
1694
`Invalid Query. '${ operator . toString ( ) } ' filters cannot contain 'null' ` +
1691
1695
'in the value array.'
1692
1696
) ;
1693
1697
}
1694
- if ( value . filter ( element => Number . isNaN ( element ) ) . length > 0 ) {
1698
+ if (
1699
+ value . filter ( element => Number . isNaN ( element ) ) . length > 0 &&
1700
+ operator !== Operator . NOT_IN
1701
+ ) {
1695
1702
throw new FirestoreError (
1696
1703
Code . INVALID_ARGUMENT ,
1697
1704
`Invalid Query. '${ operator . toString ( ) } ' filters cannot contain 'NaN' ` +
@@ -1700,14 +1707,41 @@ function validateDisjunctiveFilterElements(
1700
1707
}
1701
1708
}
1702
1709
1710
+ function conflictingOps ( op : Operator ) : Operator [ ] {
1711
+ switch ( op ) {
1712
+ case Operator . NOT_EQUAL :
1713
+ return [ Operator . NOT_IN , Operator . NOT_EQUAL ] ;
1714
+ case Operator . ARRAY_CONTAINS :
1715
+ return [
1716
+ Operator . ARRAY_CONTAINS ,
1717
+ Operator . ARRAY_CONTAINS_ANY ,
1718
+ Operator . NOT_IN
1719
+ ] ;
1720
+ case Operator . IN :
1721
+ return [ Operator . ARRAY_CONTAINS_ANY , Operator . IN , Operator . NOT_IN ] ;
1722
+ case Operator . ARRAY_CONTAINS_ANY :
1723
+ return [
1724
+ Operator . ARRAY_CONTAINS ,
1725
+ Operator . ARRAY_CONTAINS_ANY ,
1726
+ Operator . IN ,
1727
+ Operator . NOT_IN
1728
+ ] ;
1729
+ case Operator . NOT_IN :
1730
+ return [
1731
+ Operator . ARRAY_CONTAINS ,
1732
+ Operator . ARRAY_CONTAINS_ANY ,
1733
+ Operator . IN ,
1734
+ Operator . NOT_IN ,
1735
+ Operator . NOT_EQUAL
1736
+ ] ;
1737
+ default :
1738
+ return [ ] ;
1739
+ }
1740
+ }
1741
+
1703
1742
function validateNewFilter ( query : InternalQuery , filter : Filter ) : void {
1704
1743
debugAssert ( filter instanceof FieldFilter , 'Only FieldFilters are supported' ) ;
1705
1744
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
1745
if ( filter . isInequality ( ) ) {
1712
1746
const existingField = query . getInequalityFilterField ( ) ;
1713
1747
if ( existingField !== null && ! existingField . isEqual ( filter . field ) ) {
@@ -1724,31 +1758,23 @@ function validateNewFilter(query: InternalQuery, filter: Filter): void {
1724
1758
if ( firstOrderByField !== null ) {
1725
1759
validateOrderByAndInequalityMatch ( query , filter . field , firstOrderByField ) ;
1726
1760
}
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
- }
1761
+ }
1762
+
1763
+ const conflictingOp = query . findFilterOperator ( conflictingOps ( filter . op ) ) ;
1764
+ if ( conflictingOp !== null ) {
1765
+ // We special case when it's a duplicate op to give a slightly clearer error message.
1766
+ if ( conflictingOp === filter . op ) {
1767
+ throw new FirestoreError (
1768
+ Code . INVALID_ARGUMENT ,
1769
+ 'Invalid query. You cannot use more than one ' +
1770
+ `'${ filter . op . toString ( ) } ' filter.`
1771
+ ) ;
1772
+ } else {
1773
+ throw new FirestoreError (
1774
+ Code . INVALID_ARGUMENT ,
1775
+ `Invalid query. You cannot use '${ filter . op . toString ( ) } ' filters ` +
1776
+ `with '${ conflictingOp . toString ( ) } ' filters.`
1777
+ ) ;
1752
1778
}
1753
1779
}
1754
1780
}
@@ -1806,18 +1832,25 @@ export class Query<T = firestore.DocumentData> implements firestore.Query<T> {
1806
1832
validateExactNumberOfArgs ( 'Query.where' , arguments , 3 ) ;
1807
1833
validateDefined ( 'Query.where' , 3 , value ) ;
1808
1834
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 ) ;
1835
+ // TODO(ne-queries): Add 'not-in' and '!=' to validation.
1836
+ let op : Operator ;
1837
+ if ( ( opStr as unknown ) === 'not-in' || ( opStr as unknown ) === '!=' ) {
1838
+ op = opStr as Operator ;
1839
+ } else {
1840
+ // Enumerated from the WhereFilterOp type in index.d.ts.
1841
+ const whereFilterOpEnums = [
1842
+ Operator . LESS_THAN ,
1843
+ Operator . LESS_THAN_OR_EQUAL ,
1844
+ Operator . EQUAL ,
1845
+ Operator . GREATER_THAN_OR_EQUAL ,
1846
+ Operator . GREATER_THAN ,
1847
+ Operator . ARRAY_CONTAINS ,
1848
+ Operator . IN ,
1849
+ Operator . ARRAY_CONTAINS_ANY
1850
+ ] ;
1851
+ op = validateStringEnum ( 'Query.where' , whereFilterOpEnums , 2 , opStr ) ;
1852
+ }
1853
+
1821
1854
const fieldPath = fieldPathFromArgument ( 'Query.where' , field ) ;
1822
1855
const filter = newQueryFilter (
1823
1856
this . _query ,
0 commit comments