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.
diff --git a/README.md b/README.md
index e9d4a66..4c4bb60 100644
--- a/README.md
+++ b/README.md
@@ -272,6 +272,7 @@
 * "$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
 
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}]}) ->
     ?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 @@
 % 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 @@
     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({[{<<"$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) ->
     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}]}, _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;
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, {[{<<"$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 ->
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 @@
         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,
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 @@
         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")
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 @@
             {"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
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 @@
                 "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
+]