1# Licensed under the Apache License, Version 2.0 (the "License"); you may not
2# use this file except in compliance with the License. You may obtain a copy of
3# the License at
4#
5#   http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations under
11# the License.
12
13import mango
14import unittest
15
16
17class OperatorTests:
18    def assertUserIds(self, user_ids, docs):
19        user_ids_returned = list(d["user_id"] for d in docs)
20        user_ids.sort()
21        user_ids_returned.sort()
22        self.assertEqual(user_ids, user_ids_returned)
23
24    def test_all(self):
25        docs = self.db.find(
26            {"manager": True, "favorites": {"$all": ["Lisp", "Python"]}}
27        )
28        self.assertEqual(len(docs), 3)
29        user_ids = [2, 12, 9]
30        self.assertUserIds(user_ids, docs)
31
32    def test_all_non_array(self):
33        docs = self.db.find({"manager": True, "location": {"$all": ["Ohai"]}})
34        self.assertEqual(len(docs), 0)
35
36    def test_elem_match(self):
37        emdocs = [
38            {"user_id": "a", "bang": [{"foo": 1, "bar": 2}]},
39            {"user_id": "b", "bang": [{"foo": 2, "bam": True}]},
40        ]
41        self.db.save_docs(emdocs, w=3)
42        docs = self.db.find(
43            {
44                "_id": {"$gt": None},
45                "bang": {"$elemMatch": {"foo": {"$gte": 1}, "bam": True}},
46            }
47        )
48        self.assertEqual(len(docs), 1)
49        self.assertEqual(docs[0]["user_id"], "b")
50
51    def test_all_match(self):
52        amdocs = [
53            {"user_id": "a", "bang": [{"foo": 1, "bar": 2}, {"foo": 3, "bar": 4}]},
54            {"user_id": "b", "bang": [{"foo": 1, "bar": 2}, {"foo": 4, "bar": 4}]},
55        ]
56        self.db.save_docs(amdocs, w=3)
57        docs = self.db.find(
58            {"bang": {"$allMatch": {"foo": {"$mod": [2, 1]}, "bar": {"$mod": [2, 0]}}}}
59        )
60        self.assertEqual(len(docs), 1)
61        self.assertEqual(docs[0]["user_id"], "a")
62
63    def test_empty_all_match(self):
64        amdocs = [{"bad_doc": "a", "emptybang": []}]
65        self.db.save_docs(amdocs, w=3)
66        docs = self.db.find({"emptybang": {"$allMatch": {"foo": {"$eq": 2}}}})
67        self.assertEqual(len(docs), 0)
68
69    def test_keymap_match(self):
70        amdocs = [
71            {"foo": {"aa": "bar", "bb": "bang"}},
72            {"foo": {"cc": "bar", "bb": "bang"}},
73        ]
74        self.db.save_docs(amdocs, w=3)
75        docs = self.db.find({"foo": {"$keyMapMatch": {"$eq": "aa"}}})
76        self.assertEqual(len(docs), 1)
77
78    def test_in_operator_array(self):
79        docs = self.db.find({"manager": True, "favorites": {"$in": ["Ruby", "Python"]}})
80        self.assertUserIds([2, 6, 7, 9, 11, 12], docs)
81
82    def test_nin_operator_array(self):
83        docs = self.db.find(
84            {"manager": True, "favorites": {"$nin": ["Erlang", "Python"]}}
85        )
86        self.assertEqual(len(docs), 4)
87        for doc in docs:
88            if isinstance(doc["favorites"], list):
89                self.assertNotIn("Erlang", doc["favorites"])
90                self.assertNotIn("Python", doc["favorites"])
91
92    def test_regex(self):
93        docs = self.db.find(
94            {"age": {"$gt": 40}, "location.state": {"$regex": "(?i)new.*"}}
95        )
96        self.assertEqual(len(docs), 2)
97        self.assertUserIds([2, 10], docs)
98
99    def test_exists_false(self):
100        docs = self.db.find({"age": {"$gt": 0}, "twitter": {"$exists": False}})
101        user_ids = [2, 3, 5, 6, 7, 8, 10, 11, 12, 14]
102        self.assertUserIds(user_ids, docs)
103        for d in docs:
104            self.assertNotIn("twitter", d)
105
106    def test_eq_null_does_not_include_missing(self):
107        docs = self.db.find({"age": {"$gt": 0}, "twitter": None})
108        user_ids = [9]
109        self.assertUserIds(user_ids, docs)
110        for d in docs:
111            self.assertEqual(d["twitter"], None)
112
113    def test_ne_includes_null_but_not_missing(self):
114        docs = self.db.find({"twitter": {"$ne": "notamatch"}})
115        user_ids = [0, 1, 4, 9, 13]
116        self.assertUserIds(user_ids, docs)
117        for d in docs:
118            self.assertIn("twitter", d)
119
120    # ideally this work be consistent across index types but, alas, it is not
121    @unittest.skipUnless(
122        not mango.has_text_service(),
123        "text indexes do not support range queries across type boundaries",
124    )
125    def test_lt_includes_null_but_not_missing(self):
126        docs = self.db.find({"twitter": {"$lt": 1}})
127        user_ids = [9]
128        self.assertUserIds(user_ids, docs)
129        for d in docs:
130            self.assertEqual(d["twitter"], None)
131
132    @unittest.skipUnless(
133        not mango.has_text_service(),
134        "text indexes do not support range queries across type boundaries",
135    )
136    def test_lte_includes_null_but_not_missing(self):
137        docs = self.db.find({"twitter": {"$lt": 1}})
138        user_ids = [9]
139        self.assertUserIds(user_ids, docs)
140        for d in docs:
141            self.assertEqual(d["twitter"], None)
142
143    def test_lte_null_includes_null_but_not_missing(self):
144        docs = self.db.find({"twitter": {"$lte": None}})
145        user_ids = [9]
146        self.assertUserIds(user_ids, docs)
147        for d in docs:
148            self.assertEqual(d["twitter"], None)
149
150    def test_lte_at_z_except_null_excludes_null_and_missing(self):
151        docs = self.db.find({"twitter": {"$and": [{"$lte": "@z"}, {"$ne": None}]}})
152        user_ids = [0, 1, 4, 13]
153        self.assertUserIds(user_ids, docs)
154        for d in docs:
155            self.assertNotEqual(d["twitter"], None)
156
157    def test_range_gte_null_includes_null_but_not_missing(self):
158        docs = self.db.find({"twitter": {"$gte": None}})
159        self.assertGreater(len(docs), 0)
160        for d in docs:
161            self.assertIn("twitter", d)
162
163    def test_exists_false_returns_missing_but_not_null(self):
164        docs = self.db.find({"twitter": {"$exists": False}})
165        self.assertGreater(len(docs), 0)
166        for d in docs:
167            self.assertNotIn("twitter", d)
168
169    @unittest.skipUnless(
170        not mango.has_text_service(),
171        "text indexes do not support range queries across type boundaries",
172    )
173    def test_lte_respsects_unicode_collation(self):
174        docs = self.db.find({"ordered": {"$lte": "a"}})
175        user_ids = [7, 8, 9, 10, 11, 12]
176        self.assertUserIds(user_ids, docs)
177
178    @unittest.skipUnless(
179        not mango.has_text_service(),
180        "text indexes do not support range queries across type boundaries",
181    )
182    def test_gte_respsects_unicode_collation(self):
183        docs = self.db.find({"ordered": {"$gte": "a"}})
184        user_ids = [12, 13, 14]
185        self.assertUserIds(user_ids, docs)
186
187
188class OperatorJSONTests(mango.UserDocsTests, OperatorTests):
189    pass
190
191
192@unittest.skipUnless(mango.has_text_service(), "requires text service")
193class OperatorTextTests(mango.UserDocsTextTests, OperatorTests):
194    pass
195
196
197class OperatorAllDocsTests(mango.UserDocsTestsNoIndexes, OperatorTests):
198    def test_range_id_eq(self):
199        doc_id = "8e1c90c0-ac18-4832-8081-40d14325bde0"
200        r = self.db.find({"_id": doc_id}, explain=True, return_raw=True)
201
202        self.assertEqual(r["mrargs"]["end_key"], doc_id)
203        self.assertEqual(r["mrargs"]["start_key"], doc_id)
204