@@ -31,8 +31,11 @@ import {
31
31
Filter ,
32
32
targetIsDocumentTarget ,
33
33
Operator ,
34
+ CompositeOperator ,
34
35
OrderBy ,
35
- Target
36
+ Target ,
37
+ CompositeFilter ,
38
+ compositeFilterIsFlatConjunction
36
39
} from '../core/target' ;
37
40
import { TargetId } from '../core/types' ;
38
41
import { Timestamp } from '../lite-api/timestamp' ;
@@ -64,6 +67,7 @@ import { isNanValue, isNullValue } from '../model/values';
64
67
import {
65
68
ApiClientObjectMap as ProtoApiClientObjectMap ,
66
69
BatchGetDocumentsResponse as ProtoBatchGetDocumentsResponse ,
70
+ CompositeFilterOp as ProtoCompositeFilterOp ,
67
71
Cursor as ProtoCursor ,
68
72
Document as ProtoDocument ,
69
73
DocumentMask as ProtoDocumentMask ,
@@ -122,6 +126,14 @@ const OPERATORS = (() => {
122
126
return ops ;
123
127
} ) ( ) ;
124
128
129
+ const COMPOSITE_OPERATORS = ( ( ) => {
130
+ const ops : { [ op : string ] : ProtoCompositeFilterOp } = { } ;
131
+ ops [ CompositeOperator . AND ] = 'AND' ;
132
+ // TODO(orquery) change 'OPERATOR_UNSPECIFIED' to 'OR' when the updated protos are published
133
+ ops [ CompositeOperator . OR ] = 'OPERATOR_UNSPECIFIED' ;
134
+ return ops ;
135
+ } ) ( ) ;
136
+
125
137
function assertPresent ( value : unknown , description : string ) : asserts value {
126
138
debugAssert ( ! isNullOrUndefined ( value ) , description + ' is missing' ) ;
127
139
}
@@ -827,7 +839,7 @@ export function toQueryTarget(
827
839
result . structuredQuery ! . from = [ { collectionId : path . lastSegment ( ) } ] ;
828
840
}
829
841
830
- const where = toFilter ( target . filters ) ;
842
+ const where = toFilters ( target . filters ) ;
831
843
if ( where ) {
832
844
result . structuredQuery ! . where = where ;
833
845
}
@@ -873,7 +885,7 @@ export function convertQueryTargetToQuery(target: ProtoQueryTarget): Query {
873
885
874
886
let filterBy : Filter [ ] = [ ] ;
875
887
if ( query . where ) {
876
- filterBy = fromFilter ( query . where ) ;
888
+ filterBy = fromFilters ( query . where ) ;
877
889
}
878
890
879
891
let orderBy : OrderBy [ ] = [ ] ;
@@ -972,34 +984,39 @@ export function toTarget(
972
984
return result ;
973
985
}
974
986
975
- function toFilter ( filters : Filter [ ] ) : ProtoFilter | undefined {
987
+ function toFilters ( filters : Filter [ ] ) : ProtoFilter | undefined {
976
988
if ( filters . length === 0 ) {
977
989
return ;
978
990
}
979
- const protos = filters . map ( filter => {
980
- debugAssert (
981
- filter instanceof FieldFilter ,
982
- 'Only FieldFilters are supported'
983
- ) ;
984
- return toUnaryOrFieldFilter ( filter ) ;
985
- } ) ;
986
- if ( protos . length === 1 ) {
987
- return protos [ 0 ] ;
991
+
992
+ return toFilter ( CompositeFilter . create ( filters , CompositeOperator . AND ) ) ;
993
+ }
994
+
995
+ function fromFilters ( filter : ProtoFilter ) : Filter [ ] {
996
+ const result = fromFilter ( filter ) ;
997
+
998
+ // Instead of a singletonList containing AND(F1, F2, ...), we can return a list containing F1,
999
+ // F2, ...
1000
+ // TODO(orquery): Once proper support for composite filters has been completed, we can remove
1001
+ // this flattening from here.
1002
+ if (
1003
+ result instanceof CompositeFilter &&
1004
+ compositeFilterIsFlatConjunction ( result )
1005
+ ) {
1006
+ // Copy the readonly array into a mutable array
1007
+ return Object . assign ( [ ] , result . getFilters ( ) ) ;
988
1008
}
989
- return { compositeFilter : { op : 'AND' , filters : protos } } ;
1009
+
1010
+ return [ result ] ;
990
1011
}
991
1012
992
- function fromFilter ( filter : ProtoFilter | undefined ) : Filter [ ] {
993
- if ( ! filter ) {
994
- return [ ] ;
995
- } else if ( filter . unaryFilter !== undefined ) {
996
- return [ fromUnaryFilter ( filter ) ] ;
1013
+ function fromFilter ( filter : ProtoFilter ) : Filter {
1014
+ if ( filter . unaryFilter !== undefined ) {
1015
+ return fromUnaryFilter ( filter ) ;
997
1016
} else if ( filter . fieldFilter !== undefined ) {
998
- return [ fromFieldFilter ( filter ) ] ;
1017
+ return fromFieldFilter ( filter ) ;
999
1018
} else if ( filter . compositeFilter !== undefined ) {
1000
- return filter . compositeFilter
1001
- . filters ! . map ( f => fromFilter ( f ) )
1002
- . reduce ( ( accum , current ) => accum . concat ( current ) ) ;
1019
+ return fromCompositeFilter ( filter ) ;
1003
1020
} else {
1004
1021
return fail ( 'Unknown filter: ' + JSON . stringify ( filter ) ) ;
1005
1022
}
@@ -1066,6 +1083,12 @@ export function toOperatorName(op: Operator): ProtoFieldFilterOp {
1066
1083
return OPERATORS [ op ] ;
1067
1084
}
1068
1085
1086
+ export function toCompositeOperatorName (
1087
+ op : CompositeOperator
1088
+ ) : ProtoCompositeFilterOp {
1089
+ return COMPOSITE_OPERATORS [ op ] ;
1090
+ }
1091
+
1069
1092
export function fromOperatorName ( op : ProtoFieldFilterOp ) : Operator {
1070
1093
switch ( op ) {
1071
1094
case 'EQUAL' :
@@ -1095,6 +1118,22 @@ export function fromOperatorName(op: ProtoFieldFilterOp): Operator {
1095
1118
}
1096
1119
}
1097
1120
1121
+ export function fromCompositeOperatorName (
1122
+ op : ProtoCompositeFilterOp
1123
+ ) : CompositeOperator {
1124
+ // TODO(orquery) support OR
1125
+ switch ( op ) {
1126
+ case 'AND' :
1127
+ return CompositeOperator . AND ;
1128
+ // TODO(orquery) update when OR operator is supported in ProtoCompositeFilterOp
1129
+ // OPERATOR_UNSPECIFIED should fail and OR should return OR
1130
+ case 'OPERATOR_UNSPECIFIED' :
1131
+ return CompositeOperator . OR ;
1132
+ default :
1133
+ return fail ( 'Unknown operator' ) ;
1134
+ }
1135
+ }
1136
+
1098
1137
export function toFieldPathReference ( path : FieldPath ) : ProtoFieldReference {
1099
1138
return { fieldPath : path . canonicalString ( ) } ;
1100
1139
}
@@ -1120,15 +1159,32 @@ export function fromPropertyOrder(orderBy: ProtoOrder): OrderBy {
1120
1159
) ;
1121
1160
}
1122
1161
1123
- export function fromFieldFilter ( filter : ProtoFilter ) : Filter {
1124
- return FieldFilter . create (
1125
- fromFieldPathReference ( filter . fieldFilter ! . field ! ) ,
1126
- fromOperatorName ( filter . fieldFilter ! . op ! ) ,
1127
- filter . fieldFilter ! . value !
1128
- ) ;
1162
+ // visible for testing
1163
+ export function toFilter ( filter : Filter ) : ProtoFilter {
1164
+ if ( filter instanceof FieldFilter ) {
1165
+ return toUnaryOrFieldFilter ( filter ) ;
1166
+ } else if ( filter instanceof CompositeFilter ) {
1167
+ return toCompositeFilter ( filter ) ;
1168
+ } else {
1169
+ return fail ( 'Unrecognized filter type ' + JSON . stringify ( filter ) ) ;
1170
+ }
1171
+ }
1172
+
1173
+ export function toCompositeFilter ( filter : CompositeFilter ) : ProtoFilter {
1174
+ const protos = filter . getFilters ( ) . map ( filter => toFilter ( filter ) ) ;
1175
+
1176
+ if ( protos . length === 1 ) {
1177
+ return protos [ 0 ] ;
1178
+ }
1179
+
1180
+ return {
1181
+ compositeFilter : {
1182
+ op : toCompositeOperatorName ( filter . op ) ,
1183
+ filters : protos
1184
+ }
1185
+ } ;
1129
1186
}
1130
1187
1131
- // visible for testing
1132
1188
export function toUnaryOrFieldFilter ( filter : FieldFilter ) : ProtoFilter {
1133
1189
if ( filter . op === Operator . EQUAL ) {
1134
1190
if ( isNanValue ( filter . value ) ) {
@@ -1201,6 +1257,21 @@ export function fromUnaryFilter(filter: ProtoFilter): Filter {
1201
1257
}
1202
1258
}
1203
1259
1260
+ export function fromFieldFilter ( filter : ProtoFilter ) : FieldFilter {
1261
+ return FieldFilter . create (
1262
+ fromFieldPathReference ( filter . fieldFilter ! . field ! ) ,
1263
+ fromOperatorName ( filter . fieldFilter ! . op ! ) ,
1264
+ filter . fieldFilter ! . value !
1265
+ ) ;
1266
+ }
1267
+
1268
+ export function fromCompositeFilter ( filter : ProtoFilter ) : CompositeFilter {
1269
+ return CompositeFilter . create (
1270
+ filter . compositeFilter ! . filters ! . map ( filter => fromFilter ( filter ) ) ,
1271
+ fromCompositeOperatorName ( filter . compositeFilter ! . op ! )
1272
+ ) ;
1273
+ }
1274
+
1204
1275
export function toDocumentMask ( fieldMask : FieldMask ) : ProtoDocumentMask {
1205
1276
const canonicalFields : string [ ] = [ ] ;
1206
1277
fieldMask . fields . forEach ( field =>
0 commit comments