@@ -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 = encodeFilters ( 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 = decodeFilters ( query . where ) ;
877
889
}
878
890
879
891
let orderBy : OrderBy [ ] = [ ] ;
@@ -972,34 +984,35 @@ export function toTarget(
972
984
return result ;
973
985
}
974
986
975
- function toFilter ( filters : Filter [ ] ) : ProtoFilter | undefined {
987
+ function encodeFilters ( 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 encodeFilter ( CompositeFilter . create ( filters , CompositeOperator . AND ) ) ;
993
+ }
994
+
995
+ function decodeFilters ( filter : ProtoFilter ) : Filter [ ] {
996
+ const result = decodeFilter ( 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 ( result instanceof CompositeFilter && compositeFilterIsFlatConjunction ( result ) ) {
1003
+ return result . getFilters ( ) ;
988
1004
}
989
- return { compositeFilter : { op : 'AND' , filters : protos } } ;
1005
+
1006
+ return [ result ] ;
990
1007
}
991
1008
992
- function fromFilter ( filter : ProtoFilter | undefined ) : Filter [ ] {
993
- if ( ! filter ) {
994
- return [ ] ;
995
- } else if ( filter . unaryFilter !== undefined ) {
996
- return [ fromUnaryFilter ( filter ) ] ;
1009
+ function decodeFilter ( filter : ProtoFilter ) : Filter {
1010
+ if ( filter . unaryFilter !== undefined ) {
1011
+ return decodeUnaryFilter ( filter ) ;
997
1012
} else if ( filter . fieldFilter !== undefined ) {
998
- return [ fromFieldFilter ( filter ) ] ;
1013
+ return decodeFieldFilter ( filter ) ;
999
1014
} else if ( filter . compositeFilter !== undefined ) {
1000
- return filter . compositeFilter
1001
- . filters ! . map ( f => fromFilter ( f ) )
1002
- . reduce ( ( accum , current ) => accum . concat ( current ) ) ;
1015
+ return decodeCompositeFilter ( filter ) ;
1003
1016
} else {
1004
1017
return fail ( 'Unknown filter: ' + JSON . stringify ( filter ) ) ;
1005
1018
}
@@ -1066,6 +1079,10 @@ export function toOperatorName(op: Operator): ProtoFieldFilterOp {
1066
1079
return OPERATORS [ op ] ;
1067
1080
}
1068
1081
1082
+ export function toCompositeOperatorName ( op : CompositeOperator ) : ProtoCompositeFilterOp {
1083
+ return COMPOSITE_OPERATORS [ op ] ;
1084
+ }
1085
+
1069
1086
export function fromOperatorName ( op : ProtoFieldFilterOp ) : Operator {
1070
1087
switch ( op ) {
1071
1088
case 'EQUAL' :
@@ -1095,6 +1112,21 @@ export function fromOperatorName(op: ProtoFieldFilterOp): Operator {
1095
1112
}
1096
1113
}
1097
1114
1115
+ export function fromCompositeOperatorName ( op : ProtoCompositeFilterOp ) : CompositeOperator {
1116
+ // TODO(orquery) support OR
1117
+ switch ( op ) {
1118
+ case 'AND' :
1119
+ return CompositeOperator . AND ;
1120
+ // TODO(orquery) update when OR operatore is supported in ProtoCompositeFilterOp
1121
+ // case 'OPERATOR_UNSPECIFIED':
1122
+ // return fail('Unspecified operator');
1123
+ case 'OPERATOR_UNSPECIFIED' :
1124
+ return CompositeOperator . OR ;
1125
+ default :
1126
+ return fail ( 'Unknown operator' ) ;
1127
+ }
1128
+ }
1129
+
1098
1130
export function toFieldPathReference ( path : FieldPath ) : ProtoFieldReference {
1099
1131
return { fieldPath : path . canonicalString ( ) } ;
1100
1132
}
@@ -1120,16 +1152,33 @@ export function fromPropertyOrder(orderBy: ProtoOrder): OrderBy {
1120
1152
) ;
1121
1153
}
1122
1154
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
- ) ;
1155
+ // visible for testing
1156
+ export function encodeFilter ( filter : Filter ) : ProtoFilter {
1157
+ if ( filter instanceof FieldFilter ) {
1158
+ return encodeUnaryOrFieldFilter ( filter ) ;
1159
+ } else if ( filter instanceof CompositeFilter ) {
1160
+ return encodeCompositeFilter ( filter ) ;
1161
+ } else {
1162
+ return fail ( 'Unrecognized filter type ' + JSON . stringify ( filter ) ) ;
1163
+ }
1129
1164
}
1130
1165
1131
- // visible for testing
1132
- export function toUnaryOrFieldFilter ( filter : FieldFilter ) : ProtoFilter {
1166
+ export function encodeCompositeFilter ( filter : CompositeFilter ) : ProtoFilter {
1167
+ const protos = filter . getFilters ( ) . map ( filter => encodeFilter ( filter ) ) ;
1168
+
1169
+ if ( protos . length === 1 ) {
1170
+ return protos [ 0 ] ;
1171
+ }
1172
+
1173
+ return {
1174
+ compositeFilter : {
1175
+ op : toCompositeOperatorName ( filter . op ) ,
1176
+ filters : protos
1177
+ }
1178
+ } ;
1179
+ }
1180
+
1181
+ export function encodeUnaryOrFieldFilter ( filter : FieldFilter ) : ProtoFilter {
1133
1182
if ( filter . op === Operator . EQUAL ) {
1134
1183
if ( isNanValue ( filter . value ) ) {
1135
1184
return {
@@ -1172,7 +1221,7 @@ export function toUnaryOrFieldFilter(filter: FieldFilter): ProtoFilter {
1172
1221
} ;
1173
1222
}
1174
1223
1175
- export function fromUnaryFilter ( filter : ProtoFilter ) : Filter {
1224
+ export function decodeUnaryFilter ( filter : ProtoFilter ) : Filter {
1176
1225
switch ( filter . unaryFilter ! . op ! ) {
1177
1226
case 'IS_NAN' :
1178
1227
const nanField = fromFieldPathReference ( filter . unaryFilter ! . field ! ) ;
@@ -1201,6 +1250,21 @@ export function fromUnaryFilter(filter: ProtoFilter): Filter {
1201
1250
}
1202
1251
}
1203
1252
1253
+ export function decodeFieldFilter ( filter : ProtoFilter ) : FieldFilter {
1254
+ return FieldFilter . create (
1255
+ fromFieldPathReference ( filter . fieldFilter ! . field ! ) ,
1256
+ fromOperatorName ( filter . fieldFilter ! . op ! ) ,
1257
+ filter . fieldFilter ! . value !
1258
+ ) ;
1259
+ }
1260
+
1261
+ export function decodeCompositeFilter ( filter : ProtoFilter ) : CompositeFilter {
1262
+ return CompositeFilter . create (
1263
+ filter . compositeFilter ! . filters ! . map ( filter => decodeFilter ( filter ) ) ,
1264
+ fromCompositeOperatorName ( filter . compositeFilter ! . op ! )
1265
+ ) ;
1266
+ }
1267
+
1204
1268
export function toDocumentMask ( fieldMask : FieldMask ) : ProtoDocumentMask {
1205
1269
const canonicalFields : string [ ] = [ ] ;
1206
1270
fieldMask . fields . forEach ( field =>
0 commit comments