summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mango/src/mango_selector.erl32
-rw-r--r--src/mango/test/03-operator-test.py9
2 files changed, 41 insertions, 0 deletions
diff --git a/src/mango/src/mango_selector.erl b/src/mango/src/mango_selector.erl
index e884dc55c..fc6a6d1a7 100644
--- a/src/mango/src/mango_selector.erl
+++ b/src/mango/src/mango_selector.erl
@@ -138,6 +138,11 @@ norm_ops({[{<<"$allMatch">>, {_}=Arg}]}) ->
norm_ops({[{<<"$allMatch">>, Arg}]}) ->
?MANGO_ERROR({bad_arg, '$allMatch', Arg});
+norm_ops({[{<<"$keyMapMatch">>, {_}=Arg}]}) ->
+ {[{<<"$keyMapMatch">>, norm_ops(Arg)}]};
+norm_ops({[{<<"$keyMapMatch">>, Arg}]}) ->
+ ?MANGO_ERROR({bad_arg, '$keyMapMatch', Arg});
+
norm_ops({[{<<"$size">>, Arg}]}) when is_integer(Arg), Arg >= 0 ->
{[{<<"$size">>, Arg}]};
norm_ops({[{<<"$size">>, Arg}]}) ->
@@ -253,6 +258,10 @@ norm_fields({[{<<"$allMatch">>, Arg}]}, Path) ->
Cond = {[{<<"$allMatch">>, norm_fields(Arg)}]},
{[{Path, Cond}]};
+norm_fields({[{<<"$keyMapMatch">>, Arg}]}, Path) ->
+ Cond = {[{<<"$keyMapMatch">>, norm_fields(Arg)}]},
+ {[{Path, Cond}]};
+
% The text operator operates against the internal
% $default field. This also asserts that the $default
@@ -334,6 +343,9 @@ norm_negations({[{<<"$elemMatch">>, Arg}]}) ->
norm_negations({[{<<"$allMatch">>, Arg}]}) ->
{[{<<"$allMatch">>, norm_negations(Arg)}]};
+norm_negations({[{<<"$keyMapMatch">>, Arg}]}) ->
+ {[{<<"$keyMapMatch">>, norm_negations(Arg)}]};
+
% All other conditions can't introduce negations anywhere
% further down the operator tree.
norm_negations(Cond) ->
@@ -491,6 +503,26 @@ match({[{<<"$allMatch">>, Arg}]}, [_ | _] = Values, Cmp) ->
match({[{<<"$allMatch">>, _Arg}]}, _Value, _Cmp) ->
false;
+% Matches when any key in the map value matches the
+% sub-selector Arg.
+match({[{<<"$keyMapMatch">>, Arg}]}, Value, Cmp) when is_tuple(Value) ->
+ try
+ lists:foreach(fun(V) ->
+ case match(Arg, V, Cmp) of
+ true -> throw(matched);
+ _ -> ok
+ end
+ end, [Key || {Key, _} <- element(1, Value)]),
+ false
+ catch
+ throw:matched ->
+ true;
+ _:_ ->
+ false
+ end;
+match({[{<<"$keyMapMatch">>, _Arg}]}, _Value, _Cmp) ->
+ false;
+
% Our comparison operators are fairly straight forward
match({[{<<"$lt">>, Arg}]}, Value, Cmp) ->
Cmp(Value, Arg) < 0;
diff --git a/src/mango/test/03-operator-test.py b/src/mango/test/03-operator-test.py
index 935f470bb..a67ef91f3 100644
--- a/src/mango/test/03-operator-test.py
+++ b/src/mango/test/03-operator-test.py
@@ -66,6 +66,15 @@ class OperatorTests:
docs = self.db.find({"emptybang": {"$allMatch": {"foo": {"$eq": 2}}}})
self.assertEqual(len(docs), 0)
+ def test_keymap_match(self):
+ amdocs = [
+ {"foo": {"aa": "bar", "bb": "bang"}},
+ {"foo": {"cc": "bar", "bb": "bang"}},
+ ]
+ self.db.save_docs(amdocs, w=3)
+ docs = self.db.find({"foo": {"$keyMapMatch": {"$eq": "aa"}}})
+ self.assertEqual(len(docs), 1)
+
def test_in_operator_array(self):
docs = self.db.find({"manager": True, "favorites": {"$in": ["Ruby", "Python"]}})
self.assertUserIds([2, 6, 7, 9, 11, 12], docs)