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 649a00d5aeac6bd474afe2c7222a27ba6b822ab2
Author: James Coglan <[email protected]>
AuthorDate: Mon Jul 20 12:08:39 2026 +0100

    fix: Selectors like {"$not":{"$allMatch":S}} (and "$elemMatch" and 
"$keyMapMatch") should be considered successful when applied to values of the 
wrong type
---
 src/mango/src/mango_selector.erl | 28 +++++++++++++++++++++++-----
 1 file changed, 23 insertions(+), 5 deletions(-)

diff --git a/src/mango/src/mango_selector.erl b/src/mango/src/mango_selector.erl
index a5796ed9e..dac0b2e14 100644
--- a/src/mango/src/mango_selector.erl
+++ b/src/mango/src/mango_selector.erl
@@ -500,9 +500,11 @@ match({[{<<"$elemMatch">>, _Arg}]}, [], #ctx{negate = 
false} = Ctx) ->
     [#failure{op = elemMatch, type = empty_list, ctx = Ctx}];
 match({[{<<"$elemMatch">>, _Arg}]}, [], #ctx{negate = true}) ->
     [];
-match({[{<<"$elemMatch">>, Arg}]}, Values, #ctx{negate = true} = Ctx) ->
+match({[{<<"$elemMatch">>, Arg}]}, Values, #ctx{negate = true} = Ctx) when 
is_list(Values) ->
     PosCtx = Ctx#ctx{negate = false},
     match({[{<<"$allMatch">>, {[{<<"$not">>, Arg}]}}]}, Values, PosCtx);
+match({[{<<"$elemMatch">>, _Arg}]}, _Values, #ctx{negate = true}) ->
+    [];
 match({[{<<"$elemMatch">>, Arg}]}, Values, #ctx{path = Path} = Ctx) when 
is_list(Values) ->
     ValueFailures = [
         match(Arg, V, Ctx#ctx{path = [Idx | Path]})
@@ -538,9 +540,11 @@ match({[{<<"$allMatch">>, _Arg}]}, [], #ctx{negate = 
false} = Ctx) ->
     [#failure{op = allMatch, type = empty_list, ctx = Ctx}];
 match({[{<<"$allMatch">>, _Arg}]}, [], #ctx{negate = true}) ->
     [];
-match({[{<<"$allMatch">>, Arg}]}, Values, #ctx{negate = true} = Ctx) ->
+match({[{<<"$allMatch">>, Arg}]}, Values, #ctx{negate = true} = Ctx) when 
is_list(Values) ->
     PosCtx = Ctx#ctx{negate = false},
     match({[{<<"$elemMatch">>, {[{<<"$not">>, Arg}]}}]}, Values, PosCtx);
+match({[{<<"$allMatch">>, _Arg}]}, _Values, #ctx{negate = true}) ->
+    [];
 match({[{<<"$allMatch">>, Arg}]}, [_ | _] = Values, #ctx{path = Path} = Ctx) ->
     MatchValue = fun({Idx, V}) -> match(Arg, V, Ctx#ctx{path = [Idx | Path]}) 
end,
     EnumValues = lists:enumerate(0, Values),
@@ -579,6 +583,8 @@ match({[{<<"$keyMapMatch">>, Arg}]}, Value, #ctx{negate = 
true, path = Path} = C
     Keys = [Key || {Key, _} <- element(1, Value)],
     MatchKey = fun(K) -> match(Arg, K, Ctx#ctx{path = [K | Path]}) end,
     lists:flatmap(MatchKey, Keys);
+match({[{<<"$keyMapMatch">>, _Arg}]}, _Value, #ctx{negate = true}) ->
+    [];
 match({[{<<"$keyMapMatch">>, Arg}]}, Value, #ctx{path = Path} = Ctx) when 
is_tuple(Value) ->
     Keys = [Key || {Key, _} <- element(1, Value)],
     KeyFailures = [match(Arg, K, Ctx#ctx{path = [K | Path]}) || K <- Keys],
@@ -2104,7 +2110,11 @@ match_allmatch_test() ->
     ?assertEqual(
         [{[{<<"path">>, [<<"x">>, 0]}, {<<"message">>, <<"must not be equal to 
0">>}]}],
         FailsNeg
-    ).
+    ),
+
+    % negating an expression that produces an error should produce a success 
response
+    ?assertEqual(match_int(SelNeg, {[{<<"x">>, 0}]}), true),
+    ?assertEqual(match_failures(SelNeg, {[{<<"x">>, 0}]}), []).
 
 match_elemmatch_test() ->
     check_selector({[{<<"$elemMatch">>, {[{<<"$eq">>, 0}]}}]}, [
@@ -2129,7 +2139,11 @@ match_elemmatch_test() ->
     ?assertEqual(
         [{[{<<"path">>, [<<"x">>, 0]}, {<<"message">>, <<"must not be equal to 
0">>}]}],
         FailsNeg
-    ).
+    ),
+
+    % negating an expression that produces an error should produce a success 
response
+    ?assertEqual(match_int(SelNeg, {[{<<"x">>, 0}]}), true),
+    ?assertEqual(match_failures(SelNeg, {[{<<"x">>, 0}]}), []).
 
 match_keymapmatch_test() ->
     check_selector({[{<<"$keyMapMatch">>, {[{<<"$regex">>, 
<<"^[a-z]+$">>}]}}]}, [
@@ -2157,7 +2171,11 @@ match_keymapmatch_test() ->
     ?assertEqual(
         [{[{<<"path">>, [<<"x">>, <<"alfa">>]}, {<<"message">>, <<"must not 
begin with 'a'">>}]}],
         FailsNeg
-    ).
+    ),
+
+    % negating an expression that produces an error should produce a success 
response
+    ?assertEqual(match_int(SelNeg, {[{<<"x">>, 0}]}), true),
+    ?assertEqual(match_failures(SelNeg, {[{<<"x">>, 0}]}), []).
 
 match_object_test() ->
     Doc1 = {[]},

Reply via email to