You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2017/04/04 21:20:58 UTC

[3/4] couchdb-mango git commit: Add `$allMatch` selector

Add `$allMatch` selector

This selector is similar to the existing `$elemMatch` one but requires
all elements of an array value to match the inner selector.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mango/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mango/commit/312e2c45
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mango/tree/312e2c45
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mango/diff/312e2c45

Branch: refs/heads/COUCHDB-3288-remove-public-db-record
Commit: 312e2c45535913c190cdef51f6ea65066ccd89dc
Parents: a319d92
Author: Lucas Satabin <lu...@gnieh.org>
Authored: Tue Feb 7 17:40:09 2017 +0100
Committer: Garren Smith <ga...@gmail.com>
Committed: Fri Mar 31 19:25:17 2017 +0200

----------------------------------------------------------------------
 README.md                              |  1 +
 src/mango_selector.erl                 | 37 +++++++++++++++++++++++--
 src/mango_selector_text.erl            |  3 ++
 test/03-operator-test.py               | 40 +++++++++++++++++++++++++++
 test/06-basic-text-test.py             | 43 +++++++++++++++++++++++++++++
 test/07-text-custom-field-list-test.py | 11 ++++++++
 test/friend_docs.py                    | 36 +++++++++++++++++++++++-
 7 files changed, 167 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/312e2c45/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index e9d4a66..4c4bb60 100644
--- a/README.md
+++ b/README.md
@@ -272,6 +272,7 @@ The list of combining characters:
 * "$nor" - array argument
 * "$all" - array argument (special operator for array values)
 * "$elemMatch" - single argument (special operator for array values)
+* "$allMatch" - single argument (special operator for array values)
 
 ### Condition Operators
 

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/312e2c45/src/mango_selector.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector.erl b/src/mango_selector.erl
index c6004cd..691aac7 100644
--- a/src/mango_selector.erl
+++ b/src/mango_selector.erl
@@ -127,6 +127,11 @@ norm_ops({[{<<"$elemMatch">>, {_}=Arg}]}) ->
 norm_ops({[{<<"$elemMatch">>, Arg}]}) ->
     ?MANGO_ERROR({bad_arg, '$elemMatch', Arg});
 
+norm_ops({[{<<"$allMatch">>, {_}=Arg}]}) ->
+    {[{<<"$allMatch">>, norm_ops(Arg)}]};
+norm_ops({[{<<"$allMatch">>, Arg}]}) ->
+    ?MANGO_ERROR({bad_arg, '$allMatch', Arg});
+
 norm_ops({[{<<"$size">>, Arg}]}) when is_integer(Arg), Arg >= 0 ->
     {[{<<"$size">>, Arg}]};
 norm_ops({[{<<"$size">>, Arg}]}) ->
@@ -209,8 +214,9 @@ norm_ops(Value) ->
 % Its important to note that we can only normalize
 % field names like this through boolean operators where
 % we can gaurantee commutativity. We can't necessarily
-% do the same through the '$elemMatch' operators but we
-% can apply the same algorithm to its arguments.
+% do the same through the '$elemMatch' or '$allMatch'
+% operators but we can apply the same algorithm to its
+% arguments.
 norm_fields({[]}) ->
     {[]};
 norm_fields(Selector) ->
@@ -237,6 +243,10 @@ norm_fields({[{<<"$elemMatch">>, Arg}]}, Path) ->
     Cond = {[{<<"$elemMatch">>, norm_fields(Arg)}]},
     {[{Path, Cond}]};
 
+norm_fields({[{<<"$allMatch">>, Arg}]}, Path) ->
+    Cond = {[{<<"$allMatch">>, norm_fields(Arg)}]},
+    {[{Path, Cond}]};
+
 
 % The text operator operates against the internal
 % $default field. This also asserts that the $default
@@ -315,6 +325,9 @@ norm_negations({[{<<"$or">>, Args}]}) ->
 norm_negations({[{<<"$elemMatch">>, Arg}]}) ->
     {[{<<"$elemMatch">>, norm_negations(Arg)}]};
 
+norm_negations({[{<<"$allMatch">>, Arg}]}) ->
+    {[{<<"$allMatch">>, norm_negations(Arg)}]};
+
 % All other conditions can't introduce negations anywhere
 % further down the operator tree.
 norm_negations(Cond) ->
@@ -411,7 +424,7 @@ match({[{<<"$all">>, Args}]}, Values, _Cmp) when is_list(Values) ->
 match({[{<<"$all">>, _Args}]}, _Values, _Cmp) ->
     false;
 
-%% This is for $elemMatch and possibly $in because of our normalizer.
+%% This is for $elemMatch, $allMatch, and possibly $in because of our normalizer.
 %% A selector such as {"field_name": {"$elemMatch": {"$gte": 80, "$lt": 85}}}
 %% gets normalized to:
 %% {[{<<"field_name">>,
@@ -446,6 +459,24 @@ match({[{<<"$elemMatch">>, Arg}]}, Values, Cmp) when is_list(Values) ->
 match({[{<<"$elemMatch">>, _Arg}]}, _Value, _Cmp) ->
     false;
 
+% Matches when all elements in values match the
+% sub-selector Arg.
+match({[{<<"$allMatch">>, Arg}]}, Values, Cmp) when is_list(Values) ->
+    try
+        lists:foreach(fun(V) ->
+            case match(Arg, V, Cmp) of
+              false -> throw(unmatched);
+              _ -> ok
+            end
+        end, Values),
+        true
+    catch
+        _:_ ->
+            false
+    end;
+match({[{<<"$allMatch">>, _Arg}]}, _Value, _Cmp) ->
+    false;
+
 % Our comparison operators are fairly straight forward
 match({[{<<"$lt">>, Arg}]}, Value, Cmp) ->
     Cmp(Value, Arg) < 0;

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/312e2c45/src/mango_selector_text.erl
----------------------------------------------------------------------
diff --git a/src/mango_selector_text.erl b/src/mango_selector_text.erl
index b6e1f09..cfa3baf 100644
--- a/src/mango_selector_text.erl
+++ b/src/mango_selector_text.erl
@@ -86,6 +86,9 @@ convert(Path, {[{<<"$all">>, Args}]}) ->
 convert(Path, {[{<<"$elemMatch">>, Arg}]}) ->
     convert([<<"[]">> | Path], Arg);
 
+convert(Path, {[{<<"$allMatch">>, Arg}]}) ->
+    convert([<<"[]">> | Path], Arg);
+
 % Our comparison operators are fairly straight forward
 convert(Path, {[{<<"$lt">>, Arg}]}) when is_list(Arg); is_tuple(Arg);
         Arg =:= null ->

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/312e2c45/test/03-operator-test.py
----------------------------------------------------------------------
diff --git a/test/03-operator-test.py b/test/03-operator-test.py
index 50d5bd2..56c2862 100644
--- a/test/03-operator-test.py
+++ b/test/03-operator-test.py
@@ -63,6 +63,46 @@ class OperatorTests(mango.UserDocsTests):
         assert len(docs) == 1
         assert docs[0]["user_id"] == "b"
 
+    def test_all_match(self):
+        amdocs = [
+            {
+                "user_id": "a",
+                "bang": [
+                    {
+                        "foo": 1,
+                        "bar": 2
+                    },
+                    {
+                        "foo": 3,
+                        "bar": 4
+                    }
+                ]
+            },
+            {
+                "user_id": "b",
+                "bang": [
+                    {
+                        "foo": 1,
+                        "bar": 2
+                    },
+                    {
+                        "foo": 4,
+                        "bar": 4
+                    }
+                ]
+            }
+        ]
+        self.db.save_docs(amdocs, w=3)
+        docs = self.db.find({
+            "_id": {"$gt": None},
+            "bang": {"$allMatch": {
+                "foo": {"$mod": [2,1]},
+                "bar": {"$mod": [2,0]}
+            }}
+        })
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == "a"
+
     def test_in_operator_array(self):
         docs = self.db.find({
                 "manager": True,

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/312e2c45/test/06-basic-text-test.py
----------------------------------------------------------------------
diff --git a/test/06-basic-text-test.py b/test/06-basic-text-test.py
index 1e3d5df..7f5ce63 100644
--- a/test/06-basic-text-test.py
+++ b/test/06-basic-text-test.py
@@ -571,6 +571,49 @@ class ElemMatchTests(mango.FriendDocsTextTests):
         for d in docs:
             assert d["user_id"] in (10, 11,12)
 
+@unittest.skipUnless(mango.has_text_service(), "requires text service")
+class AllMatchTests(mango.FriendDocsTextTests):
+
+    def test_all_match(self):
+        q = {"friends": {
+                "$allMatch":
+                    {"type": "personal"}
+            }
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 2
+        for d in docs:
+            assert d["user_id"] in (8, 5)
+
+        # Check that we can do logic in allMatch
+        q = {
+            "friends": {
+                "$allMatch": {
+                    "name.first": "Ochoa",
+                    "$or": [
+                        {"type": "work"},
+                        {"type": "personal"}
+                    ]
+                }
+            }
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 15
+
+        # Same as last, but using $in
+        q = {
+            "friends": {
+                "$allMatch": {
+                    "name.first": "Ochoa",
+                    "type": {"$in": ["work", "personal"]}
+                }
+            }
+        }
+        docs = self.db.find(q)
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 15
+
 
 # Test numeric strings for $text
 @unittest.skipUnless(mango.has_text_service(), "requires text service")

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/312e2c45/test/07-text-custom-field-list-test.py
----------------------------------------------------------------------
diff --git a/test/07-text-custom-field-list-test.py b/test/07-text-custom-field-list-test.py
index 029c91c..4db11a5 100644
--- a/test/07-text-custom-field-list-test.py
+++ b/test/07-text-custom-field-list-test.py
@@ -145,3 +145,14 @@ class CustomFieldsTest(mango.UserDocsTextTests):
             {"location.state": "Don't Exist"}]})
         assert len(docs) == 1
         assert docs[0]["user_id"] == 10
+
+    def test_all_match(self):
+        docs = self.db.find({
+            "favorites": {
+                "$allMatch": {
+                    "$eq": "Erlang"
+                }
+            }
+        })
+        assert len(docs) == 1
+        assert docs[0]["user_id"] == 10

http://git-wip-us.apache.org/repos/asf/couchdb-mango/blob/312e2c45/test/friend_docs.py
----------------------------------------------------------------------
diff --git a/test/friend_docs.py b/test/friend_docs.py
index e0cf60e..0757961 100644
--- a/test/friend_docs.py
+++ b/test/friend_docs.py
@@ -566,5 +566,39 @@ DOCS =  [
                 "type": "work"
             }
         ]
+    },
+    {
+        "_id": "589f32af493145f890e1b051",
+        "user_id": 15,
+        "name": {
+            "first": "Tanisha",
+            "last": "Bowers"
+        },
+        "friends": [
+            {
+                "id": 0,
+                "name": {
+                    "first": "Ochoa",
+                    "last": "Pratt"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 1,
+                "name": {
+                    "first": "Ochoa",
+                    "last": "Romero"
+                },
+                "type": "personal"
+            },
+            {
+                "id": 2,
+                "name": {
+                    "first": "Ochoa",
+                    "last": "Bowman"
+                },
+                "type": "work"
+            }
+        ]
     }
-]
\ No newline at end of file
+]