This is an automated email from the ASF dual-hosted git repository.

jcoglan pushed a commit to branch mango-match-failures
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 43cef4137fa97e64150aeb703889266fa428e329
Author: James Coglan <[email protected]>
AuthorDate: Thu Jan 15 15:13:30 2026 +0000

    chore: Replace `Cmp` argument to `mango_selector:match/3` with a record
    
    We will need to pass other things around between `match` calls as well
    the current `Cmp` function, so here we replace this argument with a
    `#ctx` record that intially just contains a `cmp` field.
---
 src/mango/src/mango_selector.erl | 106 ++++++++++++++++++++-------------------
 1 file changed, 55 insertions(+), 51 deletions(-)

diff --git a/src/mango/src/mango_selector.erl b/src/mango/src/mango_selector.erl
index cfb51d090..8bd1063b4 100644
--- a/src/mango/src/mango_selector.erl
+++ b/src/mango/src/mango_selector.erl
@@ -23,6 +23,10 @@
 -include_lib("couch/include/couch_db.hrl").
 -include("mango.hrl").
 
+-record(ctx, {
+    cmp
+}).
+
 -record(failure, {
     op,
     type = mismatch,
@@ -70,9 +74,9 @@ match_int(Selector, D) ->
 match_failures({[]}, _) ->
     [];
 match_failures(Selector, #doc{body = Body}) ->
-    match(Selector, Body, fun mango_json:cmp/2);
+    match_failures(Selector, Body);
 match_failures(Selector, {Props}) ->
-    match(Selector, {Props}, fun mango_json:cmp/2).
+    match(Selector, {Props}, #ctx{cmp = fun mango_json:cmp/2}).
 
 % Convert each operator into a normalized version as well
 % as convert an implicit operators into their explicit
@@ -385,33 +389,33 @@ negate({[{Field, Cond}]}) ->
 % for $or, $in, $all, $nin as well.
 match({[{<<"$and">>, []}]}, _, _) ->
     [];
-match({[{<<"$and">>, Args}]}, Value, Cmp) ->
-    MatchSubSel = fun(SubSel) -> match(SubSel, Value, Cmp) end,
+match({[{<<"$and">>, Args}]}, Value, Ctx) ->
+    MatchSubSel = fun(SubSel) -> match(SubSel, Value, Ctx) end,
     lists:flatmap(MatchSubSel, Args);
 match({[{<<"$or">>, []}]}, _, _) ->
     [];
-match({[{<<"$or">>, Args}]}, Value, Cmp) ->
-    SubSelFailures = [match(A, Value, Cmp) || A <- Args],
-    case lists:member([], SubSelFailures) of
+match({[{<<"$or">>, Args}]}, Value, Ctx) ->
+    SubSelFailures = [match(A, Value, Ctx) || A <- Args],
+    case lists:any(fun(Res) -> Res == [] end, SubSelFailures) of
         true -> [];
         _ -> lists:flatten(SubSelFailures)
     end;
 % TODO: producing good failure messages requires that normalize/1 fully removes
 % $not from the tree by pushing it to the leaves.
-match({[{<<"$not">>, Arg}]}, Value, Cmp) ->
-    case match(Arg, Value, Cmp) of
+match({[{<<"$not">>, Arg}]}, Value, Ctx) ->
+    case match(Arg, Value, Ctx) of
         [] -> [#failure{op = 'not'}];
         _ -> []
     end;
 % All of the values in Args must exist in Values or
 % Values == hd(Args) if Args is a single element list
 % that contains a list.
-match({[{<<"$all">>, []}]}, _Values, _Cmp) ->
+match({[{<<"$all">>, []}]}, _Values, _Ctx) ->
     % { "$all": [] } is defined to eval to false, so return a failure
     [#failure{op = all, params = [[]]}];
-match({[{<<"$all">>, [A]}]}, Values, _Cmp) when is_list(A), A == Values ->
+match({[{<<"$all">>, [A]}]}, Values, _Ctx) when is_list(A), A == Values ->
     [];
-match({[{<<"$all">>, Args}]}, Values, _Cmp) when is_list(Values) ->
+match({[{<<"$all">>, Args}]}, Values, _Ctx) when is_list(Values) ->
     lists:flatmap(
         fun(Arg) ->
             case lists:member(Arg, Values) of
@@ -421,7 +425,7 @@ match({[{<<"$all">>, Args}]}, Values, _Cmp) when 
is_list(Values) ->
         end,
         Args
     );
-match({[{<<"$all">>, _}]}, Value, _Cmp) ->
+match({[{<<"$all">>, _}]}, Value, _Ctx) ->
     [#failure{op = all, type = bad_value, params = [Value]}];
 %% This is for $elemMatch, $allMatch, and possibly $in because of our 
normalizer.
 %% A selector such as {"field_name": {"$elemMatch": {"$gte": 80, "$lt": 85}}}
@@ -435,53 +439,53 @@ match({[{<<"$all">>, _}]}, Value, _Cmp) ->
 %%     }]}
 %% }]}.
 %% So we filter out the [].
-match({[{[], Arg}]}, Values, Cmp) ->
-    match(Arg, Values, Cmp);
+match({[{[], Arg}]}, Values, Ctx) ->
+    match(Arg, Values, Ctx);
 % Matches when any element in values matches the
 % sub-selector Arg.
-match({[{<<"$elemMatch">>, _Arg}]}, [], _Cmp) ->
+match({[{<<"$elemMatch">>, _Arg}]}, [], _Ctx) ->
     [#failure{op = elemMatch, type = empty_list}];
-match({[{<<"$elemMatch">>, Arg}]}, Values, Cmp) when is_list(Values) ->
-    ValueFailures = [match(Arg, V, Cmp) || V <- Values],
+match({[{<<"$elemMatch">>, Arg}]}, Values, Ctx) when is_list(Values) ->
+    ValueFailures = [match(Arg, V, Ctx) || V <- Values],
     case lists:member([], ValueFailures) of
         true -> [];
         _ -> lists:flatten(ValueFailures)
     end;
-match({[{<<"$elemMatch">>, _}]}, Value, _Cmp) ->
+match({[{<<"$elemMatch">>, _}]}, Value, _Ctx) ->
     [#failure{op = elemMatch, type = bad_value, params = [Value]}];
 % Matches when all elements in values match the
 % sub-selector Arg.
-match({[{<<"$allMatch">>, Arg}]}, [_ | _] = Values, Cmp) ->
-    MatchValue = fun(Value) -> match(Arg, Value, Cmp) end,
+match({[{<<"$allMatch">>, Arg}]}, [_ | _] = Values, Ctx) ->
+    MatchValue = fun(Value) -> match(Arg, Value, Ctx) end,
     lists:flatmap(MatchValue, Values);
-match({[{<<"$allMatch">>, _}]}, Value, _Cmp) ->
+match({[{<<"$allMatch">>, _}]}, Value, _Ctx) ->
     [#failure{op = allMatch, type = bad_value, params = [Value]}];
 % Matches when any key in the map value matches the
 % sub-selector Arg.
-match({[{<<"$keyMapMatch">>, _Arg}]}, {[]}, _Cmp) ->
+match({[{<<"$keyMapMatch">>, _Arg}]}, {[]}, _Ctx) ->
     [#failure{op = keyMapMatch, type = empty_list}];
-match({[{<<"$keyMapMatch">>, Arg}]}, {Value}, Cmp) when is_list(Value) ->
-    KeyFailures = [match(Arg, K, Cmp) || {K, _} <- Value],
+match({[{<<"$keyMapMatch">>, Arg}]}, {Value}, Ctx) when is_list(Value) ->
+    KeyFailures = [match(Arg, K, Ctx) || {K, _} <- Value],
     case lists:member([], KeyFailures) of
         true -> [];
         _ -> lists:flatten(KeyFailures)
     end;
-match({[{<<"$keyMapMatch">>, _}]}, Value, _Cmp) ->
+match({[{<<"$keyMapMatch">>, _}]}, Value, _Ctx) ->
     [#failure{op = keyMapMatch, type = bad_value, params = [Value]}];
 % Our comparison operators are fairly straight forward
-match({[{<<"$lt">>, Arg}]}, Value, Cmp) ->
+match({[{<<"$lt">>, Arg}]}, Value, #ctx{cmp = Cmp}) ->
     compare(lt, Arg, Cmp(Value, Arg) < 0);
-match({[{<<"$lte">>, Arg}]}, Value, Cmp) ->
+match({[{<<"$lte">>, Arg}]}, Value, #ctx{cmp = Cmp}) ->
     compare(lte, Arg, Cmp(Value, Arg) =< 0);
-match({[{<<"$eq">>, Arg}]}, Value, Cmp) ->
+match({[{<<"$eq">>, Arg}]}, Value, #ctx{cmp = Cmp}) ->
     compare(eq, Arg, Cmp(Value, Arg) == 0);
-match({[{<<"$ne">>, Arg}]}, Value, Cmp) ->
+match({[{<<"$ne">>, Arg}]}, Value, #ctx{cmp = Cmp}) ->
     compare(ne, Arg, Cmp(Value, Arg) /= 0);
-match({[{<<"$gte">>, Arg}]}, Value, Cmp) ->
+match({[{<<"$gte">>, Arg}]}, Value, #ctx{cmp = Cmp}) ->
     compare(gte, Arg, Cmp(Value, Arg) >= 0);
-match({[{<<"$gt">>, Arg}]}, Value, Cmp) ->
+match({[{<<"$gt">>, Arg}]}, Value, #ctx{cmp = Cmp}) ->
     compare(gt, Arg, Cmp(Value, Arg) > 0);
-match({[{<<"$in">>, Args}]}, Values, Cmp) when is_list(Values) ->
+match({[{<<"$in">>, Args}]}, Values, #ctx{cmp = Cmp}) when is_list(Values) ->
     Pred = fun(Arg) ->
         lists:foldl(
             fun(Value, Match) ->
@@ -495,13 +499,13 @@ match({[{<<"$in">>, Args}]}, Values, Cmp) when 
is_list(Values) ->
         true -> [];
         _ -> [#failure{op = in, params = [Args]}]
     end;
-match({[{<<"$in">>, Args}]}, Value, Cmp) ->
+match({[{<<"$in">>, Args}]}, Value, #ctx{cmp = Cmp}) ->
     Pred = fun(Arg) -> Cmp(Value, Arg) == 0 end,
     case lists:any(Pred, Args) of
         true -> [];
         _ -> [#failure{op = in, params = [Args]}]
     end;
-match({[{<<"$nin">>, Args}]}, Values, Cmp) when is_list(Values) ->
+match({[{<<"$nin">>, Args}]}, Values, #ctx{cmp = Cmp}) when is_list(Values) ->
     Pred = fun(Arg) ->
         lists:foldl(
             fun(Value, Match) ->
@@ -515,7 +519,7 @@ match({[{<<"$nin">>, Args}]}, Values, Cmp) when 
is_list(Values) ->
         true -> [];
         _ -> [#failure{op = nin, params = [Args]}]
     end;
-match({[{<<"$nin">>, Args}]}, Value, Cmp) ->
+match({[{<<"$nin">>, Args}]}, Value, #ctx{cmp = Cmp}) ->
     Pred = fun(Arg) -> Cmp(Value, Arg) /= 0 end,
     case lists:all(Pred, Args) of
         true -> [];
@@ -523,34 +527,34 @@ match({[{<<"$nin">>, Args}]}, Value, Cmp) ->
     end;
 % This logic is a bit subtle. Basically, if value is
 % not undefined, then it exists.
-match({[{<<"$exists">>, ShouldExist}]}, Value, _Cmp) ->
+match({[{<<"$exists">>, ShouldExist}]}, Value, _Ctx) ->
     case {ShouldExist, Value} of
         {true, undefined} -> [#failure{op = exists, params = [ShouldExist]}];
         {true, _} -> [];
         {false, undefined} -> [];
         {false, _} -> [#failure{op = exists, params = [ShouldExist]}]
     end;
-match({[{<<"$type">>, Arg}]}, Value, _Cmp) when is_binary(Arg) ->
+match({[{<<"$type">>, Arg}]}, Value, _Ctx) when is_binary(Arg) ->
     case mango_json:type(Value) of
         Arg -> [];
         _ -> [#failure{op = type, params = [Arg]}]
     end;
-match({[{<<"$mod">>, [D, R]}]}, Value, _Cmp) when is_integer(Value) ->
+match({[{<<"$mod">>, [D, R]}]}, Value, _Ctx) when is_integer(Value) ->
     case Value rem D of
         R -> [];
         _ -> [#failure{op = mod, params = [D, R]}]
     end;
-match({[{<<"$mod">>, _}]}, Value, _Cmp) ->
+match({[{<<"$mod">>, _}]}, Value, _Ctx) ->
     [#failure{op = mod, type = bad_value, params = [Value]}];
-match({[{<<"$beginsWith">>, Prefix}]}, Value, _Cmp) when is_binary(Prefix), 
is_binary(Value) ->
+match({[{<<"$beginsWith">>, Prefix}]}, Value, _Ctx) when is_binary(Prefix), 
is_binary(Value) ->
     case string:prefix(Value, Prefix) of
         nomatch -> [#failure{op = beginsWith, params = [Prefix]}];
         _ -> []
     end;
 % When Value is not a string, do not match
-match({[{<<"$beginsWith">>, Prefix}]}, Value, _Cmp) when is_binary(Prefix) ->
+match({[{<<"$beginsWith">>, Prefix}]}, Value, _Ctx) when is_binary(Prefix) ->
     [#failure{op = beginsWith, type = bad_value, params = [Value]}];
-match({[{<<"$regex">>, Regex}]}, Value, _Cmp) when is_binary(Value) ->
+match({[{<<"$regex">>, Regex}]}, Value, _Ctx) when is_binary(Value) ->
     try
         case re:run(Value, Regex, [{capture, none}]) of
             match -> [];
@@ -560,18 +564,18 @@ match({[{<<"$regex">>, Regex}]}, Value, _Cmp) when 
is_binary(Value) ->
         _:_ ->
             [#failure{op = regex, params = [Regex]}]
     end;
-match({[{<<"$regex">>, _}]}, Value, _Cmp) ->
+match({[{<<"$regex">>, _}]}, Value, _Ctx) ->
     [#failure{op = regex, type = bad_value, params = [Value]}];
-match({[{<<"$size">>, Arg}]}, Values, _Cmp) when is_list(Values) ->
+match({[{<<"$size">>, Arg}]}, Values, _Ctx) when is_list(Values) ->
     case length(Values) of
         Arg -> [];
         _ -> [#failure{op = size, params = [Arg]}]
     end;
-match({[{<<"$size">>, _}]}, Value, _Cmp) ->
+match({[{<<"$size">>, _}]}, Value, _Ctx) ->
     [#failure{op = size, type = bad_value, params = [Value]}];
 % We don't have any choice but to believe that the text
 % index returned valid matches
-match({[{<<"$default">>, _}]}, _Value, _Cmp) ->
+match({[{<<"$default">>, _}]}, _Value, _Ctx) ->
     [];
 % All other operators are internal assertion errors for
 % matching because we either should've removed them during
@@ -581,7 +585,7 @@ match({[{<<"$", _/binary>> = Op, _}]}, _, _) ->
 % We need to traverse value to find field. The call to
 % mango_doc:get_field/2 may return either not_found or
 % bad_path in which case matching fails.
-match({[{Field, Cond}]}, Value, Cmp) ->
+match({[{Field, Cond}]}, Value, Ctx) ->
     case mango_doc:get_field(Value, Field) of
         not_found when Cond == {[{<<"$exists">>, false}]} ->
             [];
@@ -590,11 +594,11 @@ match({[{Field, Cond}]}, Value, Cmp) ->
         bad_path ->
             [#failure{op = '$'}];
         SubValue when Field == <<"_id">> ->
-            match(Cond, SubValue, fun mango_json:cmp_raw/2);
+            match(Cond, SubValue, Ctx#ctx{cmp = fun mango_json:cmp_raw/2});
         SubValue ->
-            match(Cond, SubValue, Cmp)
+            match(Cond, SubValue, Ctx)
     end;
-match({[_, _ | _] = _Props} = Sel, _Value, _Cmp) ->
+match({[_, _ | _] = _Props} = Sel, _Value, _Ctx) ->
     error({unnormalized_selector, Sel}).
 
 compare(Op, Arg, Cond) ->

Reply via email to