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 32c6773c9be9d1bc80c603a47c609a3be4291929
Author: James Coglan <[email protected]>
AuthorDate: Mon May 11 10:29:06 2026 +0100

    fix: Normalize negations inside $allMatch and other operators
---
 src/mango/src/mango_selector.erl | 68 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 67 insertions(+), 1 deletion(-)

diff --git a/src/mango/src/mango_selector.erl b/src/mango/src/mango_selector.erl
index 5d72ee581..e651f1565 100644
--- a/src/mango/src/mango_selector.erl
+++ b/src/mango/src/mango_selector.erl
@@ -299,9 +299,16 @@ norm_negations({[{<<"$or">>, Args}]}) ->
 norm_negations({[{<<"$elemMatch">>, Arg}]}) ->
     {[{<<"$elemMatch">>, norm_negations(Arg)}]};
 norm_negations({[{<<"$allMatch">>, Arg}]}) ->
-    {[{<<"$allMatch">>, norm_negations(Arg)}]};
+    Out = norm_negations(Arg),
+    {[{<<"$allMatch">>, Out}]};
 norm_negations({[{<<"$keyMapMatch">>, Arg}]}) ->
     {[{<<"$keyMapMatch">>, norm_negations(Arg)}]};
+% Sometimes a top-level key will be a doc field because it
+% can't be pushed down through its corresponding operator,
+% e.g. {x: {$allMatch: S}}. Negation inside the $allMatch
+% should still be normalized.
+norm_negations({[{Field, Cond}]}) when is_list(Field) ->
+    {[{Field, norm_negations(Cond)}]};
 % All other conditions can't introduce negations anywhere
 % further down the operator tree.
 norm_negations(Cond) ->
@@ -1712,4 +1719,63 @@ match_nor_test() ->
     ?assertEqual(false, match_int(SelMulti, {[{<<"x">>, 9}]})),
     ?assertEqual(false, match_int(SelMulti, {[]})).
 
+normalize_nor_test() ->
+    Sel1 = normalize(
+        {[
+            {<<"x">>,
+                {[
+                    {<<"$nor">>, [
+                        {[{<<"$lt">>, 3}]}, {[{<<"$gt">>, 5}]}
+                    ]}
+                ]}}
+        ]}
+    ),
+    ?assertEqual(
+        {[
+            {<<"$and">>, [
+                {[{[<<"x">>], {[{<<"$gte">>, 3}]}}]},
+                {[{[<<"x">>], {[{<<"$lte">>, 5}]}}]}
+            ]}
+        ]},
+        Sel1
+    ),
+    ?assertEqual(true, match_int(Sel1, {[{<<"x">>, 4}]})),
+    ?assertEqual(false, match_int(Sel1, {[{<<"x">>, 40}]})),
+
+    Sel2 = normalize(
+        {[
+            {<<"x">>,
+                {[
+                    {<<"$allMatch">>,
+                        {[
+                            {<<"y">>,
+                                {[
+                                    {<<"$nor">>, [
+                                        {[{<<"$lt">>, 3}]}, {[{<<"$gt">>, 5}]}
+                                    ]}
+                                ]}}
+                        ]}}
+                ]}}
+        ]}
+    ),
+    ?assertEqual(
+        {[
+            {
+                [<<"x">>],
+                {[
+                    {<<"$allMatch">>,
+                        {[
+                            {<<"$and">>, [
+                                {[{[<<"y">>], {[{<<"$gte">>, 3}]}}]},
+                                {[{[<<"y">>], {[{<<"$lte">>, 5}]}}]}
+                            ]}
+                        ]}}
+                ]}
+            }
+        ]},
+        Sel2
+    ),
+    ?assertEqual(true, match_int(Sel2, {[{<<"x">>, [{[{<<"y">>, 4}]}]}]})),
+    ?assertEqual(false, match_int(Sel2, {[{<<"x">>, [{[{<<"y">>, 40}]}]}]})).
+
 -endif.

Reply via email to