@@ -795,6 +795,7 @@ impl Expr for ComparisonExpr {
795795#[ allow( clippy:: bool_assert_comparison) ]
796796mod tests {
797797 use super :: * ;
798+ use crate :: ast:: ValueExpr ;
798799 use crate :: ast:: function_expr:: { FunctionCallArgExpr , FunctionCallExpr } ;
799800 use crate :: ast:: logical_expr:: LogicalExpr ;
800801 use crate :: execution_context:: ExecutionContext ;
@@ -1034,6 +1035,26 @@ mod tests {
10341035 } ,
10351036 )
10361037 . unwrap ( ) ;
1038+ builder
1039+ . add_function (
1040+ "concat_fields" ,
1041+ SimpleFunctionDefinition {
1042+ params : vec ! [
1043+ SimpleFunctionParam {
1044+ arg_kind: SimpleFunctionArgKind :: Field ,
1045+ val_type: Type :: Bytes ,
1046+ } ,
1047+ SimpleFunctionParam {
1048+ arg_kind: SimpleFunctionArgKind :: Field ,
1049+ val_type: Type :: Bytes ,
1050+ } ,
1051+ ] ,
1052+ opt_params : vec ! [ ] ,
1053+ return_type : Type :: Bytes ,
1054+ implementation : SimpleFunctionImpl :: new ( concat_function) ,
1055+ } ,
1056+ )
1057+ . unwrap ( ) ;
10371058 builder
10381059 . add_function ( "filter" , FilterFunction :: new ( ) )
10391060 . unwrap ( ) ;
@@ -2207,6 +2228,86 @@ mod tests {
22072228 assert_eq ! ( expr. execute_one( ctx) , true ) ;
22082229 }
22092230
2231+ // The non-mapped argument (the "-cf" literal) must be applied to *every*
2232+ // mapped element, even though it is now evaluated only once per call.
2233+ #[ test]
2234+ fn test_map_each_function_non_mapped_arg_applied_to_all_elements ( ) {
2235+ let ( expr, rest) = FilterParser :: new ( & SCHEME )
2236+ . lex_as :: < FunctionCallExpr > ( r#"concat(http.cookies[*], "-cf")"# )
2237+ . unwrap ( ) ;
2238+ assert_eq ! ( rest, "" ) ;
2239+
2240+ let expr = expr. compile ( ) ;
2241+ let ctx = & mut ExecutionContext :: new ( & SCHEME ) ;
2242+ ctx. set_field_value (
2243+ field ( "http.cookies" ) ,
2244+ Array :: from_iter ( [ "one" , "two" , "three" ] ) ,
2245+ )
2246+ . unwrap ( ) ;
2247+
2248+ assert_eq ! (
2249+ expr. execute( ctx) ,
2250+ Ok ( LhsValue :: Array ( Array :: from_iter( [
2251+ "one-cf" , "two-cf" , "three-cf"
2252+ ] ) ) )
2253+ ) ;
2254+ }
2255+
2256+ // A non-mapped argument that is expensive to re-evaluate (here a nested
2257+ // function call) is evaluated once and reused for every mapped element.
2258+ #[ test]
2259+ fn test_map_each_memoizes_expensive_non_mapped_arg ( ) {
2260+ let ( expr, rest) = FilterParser :: new ( & SCHEME )
2261+ . lex_as :: < FunctionCallExpr > ( r#"concat_fields(http.cookies[*], lowercase(http.host))"# )
2262+ . unwrap ( ) ;
2263+ assert_eq ! ( rest, "" ) ;
2264+
2265+ let expr = expr. compile ( ) ;
2266+ let ctx = & mut ExecutionContext :: new ( & SCHEME ) ;
2267+ ctx. set_field_value (
2268+ field ( "http.cookies" ) ,
2269+ Array :: from_iter ( [ "one" , "two" , "three" ] ) ,
2270+ )
2271+ . unwrap ( ) ;
2272+ ctx. set_field_value ( field ( "http.host" ) , "SUFFIX" ) . unwrap ( ) ;
2273+
2274+ // `lowercase(http.host)` == "suffix" is appended to every element.
2275+ assert_eq ! (
2276+ expr. execute( ctx) ,
2277+ Ok ( LhsValue :: Array ( Array :: from_iter( [
2278+ "onesuffix" ,
2279+ "twosuffix" ,
2280+ "threesuffix"
2281+ ] ) ) )
2282+ ) ;
2283+ }
2284+
2285+ // map_each over a Map with no extra args: exercises the fused Map -> Array
2286+ // path (no intermediate array allocation) and the empty-args fast path.
2287+ #[ test]
2288+ fn test_map_each_on_map_no_extra_args ( ) {
2289+ let ( expr, rest) = FilterParser :: new ( & SCHEME )
2290+ . lex_as :: < FunctionCallExpr > ( r#"lowercase(http.headers[*])"# )
2291+ . unwrap ( ) ;
2292+ assert_eq ! ( rest, "" ) ;
2293+
2294+ let expr = expr. compile ( ) ;
2295+ let ctx = & mut ExecutionContext :: new ( & SCHEME ) ;
2296+ let headers = LhsValue :: from ( {
2297+ let mut map = TypedMap :: new ( ) ;
2298+ map. insert ( b"0" . to_vec ( ) . into ( ) , "ONE" ) ;
2299+ map. insert ( b"1" . to_vec ( ) . into ( ) , "TWO" ) ;
2300+ map. insert ( b"2" . to_vec ( ) . into ( ) , "THREE" ) ;
2301+ map
2302+ } ) ;
2303+ ctx. set_field_value ( field ( "http.headers" ) , headers) . unwrap ( ) ;
2304+
2305+ assert_eq ! (
2306+ expr. execute( ctx) ,
2307+ Ok ( LhsValue :: Array ( Array :: from_iter( [ "one" , "two" , "three" ] ) ) )
2308+ ) ;
2309+ }
2310+
22102311 #[ test]
22112312 fn test_map_each_on_array_for_cmp ( ) {
22122313 let expr = assert_ok ! (
0 commit comments