@@ -257,10 +257,10 @@ - (FIRExprBridge *)parseExpression:(NSDictionary<NSString *, id> *)map error:(NS
257257 }
258258
259259 // -------------------------------------------------------------------------
260- // N-ary logical (expressions array): and, or, xor
260+ // N-ary logical (expressions array): and, or, xor, nor
261261 // -------------------------------------------------------------------------
262262 if ([name isEqualToString: @" and" ] || [name isEqualToString: @" or" ] ||
263- [name isEqualToString: @" xor" ]) {
263+ [name isEqualToString: @" xor" ] || [name isEqualToString: @" nor " ] ) {
264264 NSArray *exprMaps = args[@" expressions" ];
265265 if (![exprMaps isKindOfClass: [NSArray class ]] || exprMaps.count == 0 ) {
266266 if (error)
@@ -741,6 +741,173 @@ - (FIRExprBridge *)parseExpression:(NSDictionary<NSString *, id> *)map error:(NS
741741 return FLTNewFunctionExprBridge (@" timestamp_trunc" , @[ timestampExpr, unitExpr ]);
742742 }
743743
744+ // -------------------------------------------------------------------------
745+ // map_keys, map_values (unary on map expression)
746+ // -------------------------------------------------------------------------
747+ if ([name isEqualToString: @" map_keys" ] || [name isEqualToString: @" map_values" ]) {
748+ id exprMap = args[@" expression" ];
749+ if (![exprMap isKindOfClass: [NSDictionary class ]]) {
750+ if (error) *error = parseError ([NSString stringWithFormat: @" %@ requires expression" , name]);
751+ return nil ;
752+ }
753+ FIRExprBridge *expr = [self parseExpression: (NSDictionary *)exprMap error: error];
754+ if (!expr) return nil ;
755+ return FLTNewFunctionExprBridge (name, @[ expr ]);
756+ }
757+
758+ // -------------------------------------------------------------------------
759+ // parent: doc_ref path or expression
760+ // -------------------------------------------------------------------------
761+ if ([name isEqualToString: @" parent" ]) {
762+ NSString *docPath = args[@" doc_ref" ];
763+ if ([docPath isKindOfClass: [NSString class ]] && docPath.length > 0 ) {
764+ FIRDocumentReference *ref = [self .firestore documentWithPath: docPath];
765+ FIRExprBridge *refExpr = [[FIRConstantBridge alloc ] init: ref];
766+ return FLTNewFunctionExprBridge (@" parent" , @[ refExpr ]);
767+ }
768+ id exprMap = args[@" expression" ];
769+ if (![exprMap isKindOfClass: [NSDictionary class ]]) {
770+ if (error) *error = parseError (@" parent requires expression or doc_ref" );
771+ return nil ;
772+ }
773+ FIRExprBridge *expr = [self parseExpression: (NSDictionary *)exprMap error: error];
774+ if (!expr) return nil ;
775+ return FLTNewFunctionExprBridge (@" parent" , @[ expr ]);
776+ }
777+
778+ // -------------------------------------------------------------------------
779+ // timestamp_diff: end, start, unit (unit = string or expression map)
780+ // -------------------------------------------------------------------------
781+ if ([name isEqualToString: @" timestamp_diff" ]) {
782+ id endMap = args[@" end" ];
783+ id startMap = args[@" start" ];
784+ id unitObj = args[@" unit" ];
785+ if (![endMap isKindOfClass: [NSDictionary class ]] ||
786+ ![startMap isKindOfClass: [NSDictionary class ]] || !unitObj) {
787+ if (error) *error = parseError (@" timestamp_diff requires end, start, and unit" );
788+ return nil ;
789+ }
790+ FIRExprBridge *endExpr = [self parseExpression: (NSDictionary *)endMap error: error];
791+ FIRExprBridge *startExpr = [self parseExpression: (NSDictionary *)startMap error: error];
792+ if (!endExpr || !startExpr) return nil ;
793+ FIRExprBridge *unitExpr = nil ;
794+ if ([unitObj isKindOfClass: [NSString class ]]) {
795+ unitExpr = [[FIRConstantBridge alloc ] init: unitObj];
796+ } else if ([unitObj isKindOfClass: [NSDictionary class ]]) {
797+ unitExpr = [self parseExpression: (NSDictionary *)unitObj error: error];
798+ } else {
799+ if (error) *error = parseError (@" timestamp_diff unit must be string or expression" );
800+ return nil ;
801+ }
802+ if (!unitExpr) return nil ;
803+ return FLTNewFunctionExprBridge (@" timestamp_diff" , @[ endExpr, startExpr, unitExpr ]);
804+ }
805+
806+ // -------------------------------------------------------------------------
807+ // timestamp_extract: timestamp, part; optional timezone (string or expression)
808+ // -------------------------------------------------------------------------
809+ if ([name isEqualToString: @" timestamp_extract" ]) {
810+ id timestampMap = args[@" timestamp" ];
811+ id partMap = args[@" part" ];
812+ if (![timestampMap isKindOfClass: [NSDictionary class ]] ||
813+ ![partMap isKindOfClass: [NSDictionary class ]]) {
814+ if (error) *error = parseError (@" timestamp_extract requires timestamp and part" );
815+ return nil ;
816+ }
817+ FIRExprBridge *tsExpr = [self parseExpression: (NSDictionary *)timestampMap error: error];
818+ FIRExprBridge *partExpr = [self parseExpression: (NSDictionary *)partMap error: error];
819+ if (!tsExpr || !partExpr) return nil ;
820+ id tzRaw = args[@" timezone" ];
821+ if (tzRaw == nil ) {
822+ return FLTNewFunctionExprBridge (@" timestamp_extract" , @[ tsExpr, partExpr ]);
823+ }
824+ FIRExprBridge *tzExpr = nil ;
825+ if ([tzRaw isKindOfClass: [NSString class ]]) {
826+ tzExpr = [[FIRConstantBridge alloc ] init: tzRaw];
827+ } else if ([tzRaw isKindOfClass: [NSDictionary class ]]) {
828+ tzExpr = [self parseExpression: (NSDictionary *)tzRaw error: error];
829+ } else {
830+ if (error) *error = parseError (@" timestamp_extract timezone must be string or expression" );
831+ return nil ;
832+ }
833+ if (!tzExpr) return nil ;
834+ return FLTNewFunctionExprBridge (@" timestamp_extract" , @[ tsExpr, partExpr, tzExpr ]);
835+ }
836+
837+ // -------------------------------------------------------------------------
838+ // if_null: expression + replacement
839+ // -------------------------------------------------------------------------
840+ if ([name isEqualToString: @" if_null" ]) {
841+ id exprMap = args[@" expression" ];
842+ id replMap = args[@" replacement" ];
843+ if (![exprMap isKindOfClass: [NSDictionary class ]] ||
844+ ![replMap isKindOfClass: [NSDictionary class ]]) {
845+ if (error) *error = parseError (@" if_null requires expression and replacement" );
846+ return nil ;
847+ }
848+ FIRExprBridge *ifExpr = [self parseExpression: (NSDictionary *)exprMap error: error];
849+ FIRExprBridge *replExpr = [self parseExpression: (NSDictionary *)replMap error: error];
850+ if (!ifExpr || !replExpr) return nil ;
851+ return FLTNewFunctionExprBridge (@" if_null" , @[ ifExpr, replExpr ]);
852+ }
853+
854+ // -------------------------------------------------------------------------
855+ // coalesce: expressions[] (>= 2)
856+ // -------------------------------------------------------------------------
857+ if ([name isEqualToString: @" coalesce" ]) {
858+ NSArray *exprMaps = args[@" expressions" ];
859+ if (![exprMaps isKindOfClass: [NSArray class ]] || exprMaps.count < 2 ) {
860+ if (error) *error = parseError (@" coalesce requires at least two expressions" );
861+ return nil ;
862+ }
863+ NSMutableArray <FIRExprBridge *> *exprs = [NSMutableArray array ];
864+ for (id em in exprMaps) {
865+ if (![em isKindOfClass: [NSDictionary class ]]) continue ;
866+ FIRExprBridge *e = [self parseExpression: (NSDictionary *)em error: error];
867+ if (!e) return nil ;
868+ [exprs addObject: e];
869+ }
870+ if (exprs.count < 2 ) {
871+ if (error) *error = parseError (@" coalesce requires at least two expressions" );
872+ return nil ;
873+ }
874+ return FLTNewFunctionExprBridge (@" coalesce" , exprs);
875+ }
876+
877+ // -------------------------------------------------------------------------
878+ // switch_on: alternating condition (bool), result (expr), optional default
879+ // -------------------------------------------------------------------------
880+ if ([name isEqualToString: @" switch_on" ]) {
881+ NSArray *exprMaps = args[@" expressions" ];
882+ if (![exprMaps isKindOfClass: [NSArray class ]] || exprMaps.count < 2 ) {
883+ if (error) *error = parseError (@" switch_on requires at least two expressions" );
884+ return nil ;
885+ }
886+ NSUInteger n = exprMaps.count ;
887+ FIRExprBridge *first = [self parseBooleanExpression: exprMaps[0 ] error: error];
888+ FIRExprBridge *second = [self parseExpression: exprMaps[1 ] error: error];
889+ if (!first || !second) return nil ;
890+ if (n == 2 ) {
891+ return FLTNewFunctionExprBridge (@" switch_on" , @[ first, second ]);
892+ }
893+ NSMutableArray <FIRExprBridge *> *rest = [NSMutableArray array ];
894+ for (NSUInteger i = 2 ; i < n; i++) {
895+ FIRExprBridge *e = nil ;
896+ if ((n % 2 == 1 ) && (i == n - 1 )) {
897+ e = [self parseExpression: exprMaps[i] error: error];
898+ } else if (i % 2 == 0 ) {
899+ e = [self parseBooleanExpression: exprMaps[i] error: error];
900+ } else {
901+ e = [self parseExpression: exprMaps[i] error: error];
902+ }
903+ if (!e) return nil ;
904+ [rest addObject: e];
905+ }
906+ NSMutableArray <FIRExprBridge *> *all = [NSMutableArray arrayWithObjects: first, second, nil ];
907+ [all addObjectsFromArray: rest];
908+ return FLTNewFunctionExprBridge (@" switch_on" , all);
909+ }
910+
744911 // -------------------------------------------------------------------------
745912 // PipelineFilter (name "filter"): operator-based (and/or) or field-based
746913 // -------------------------------------------------------------------------
0 commit comments