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 copy
14import mango
15import unittest
16
17DOCS = [
18    {"_id": "100", "name": "Jimi", "location": "AUS", "user_id": 1, "same": "value"},
19    {"_id": "200", "name": "Eddie", "location": "BRA", "user_id": 2, "same": "value"},
20    {"_id": "300", "name": "Harry", "location": "CAN", "user_id": 3, "same": "value"},
21    {"_id": "400", "name": "Eddie", "location": "DEN", "user_id": 4, "same": "value"},
22    {"_id": "500", "name": "Jones", "location": "ETH", "user_id": 5, "same": "value"},
23    {
24        "_id": "600",
25        "name": "Winnifried",
26        "location": "FRA",
27        "user_id": 6,
28        "same": "value",
29    },
30    {"_id": "700", "name": "Marilyn", "location": "GHA", "user_id": 7, "same": "value"},
31    {"_id": "800", "name": "Sandra", "location": "ZAR", "user_id": 8, "same": "value"},
32]
33
34oldschoolnoselectorddoc = {
35    "_id": "_design/oldschoolnoselector",
36    "language": "query",
37    "views": {
38        "oldschoolnoselector": {
39            "map": {"fields": {"location": "asc"}},
40            "reduce": "_count",
41            "options": {"def": {"fields": ["location"]}},
42        }
43    },
44}
45
46oldschoolddoc = {
47    "_id": "_design/oldschool",
48    "language": "query",
49    "views": {
50        "oldschool": {
51            "map": {
52                "fields": {"location": "asc"},
53                "selector": {"location": {"$gte": "FRA"}},
54            },
55            "reduce": "_count",
56            "options": {"def": {"fields": ["location"]}},
57        }
58    },
59}
60
61oldschoolddoctext = {
62    "_id": "_design/oldschooltext",
63    "language": "query",
64    "indexes": {
65        "oldschooltext": {
66            "index": {
67                "default_analyzer": "keyword",
68                "default_field": {},
69                "selector": {"location": {"$gte": "FRA"}},
70                "fields": [{"name": "location", "type": "string"}],
71                "index_array_lengths": True,
72            },
73            "analyzer": {
74                "name": "perfield",
75                "default": "keyword",
76                "fields": {"$default": "standard"},
77            },
78        }
79    },
80}
81
82
83class IndexSelectorJson(mango.DbPerClass):
84    def setUp(self):
85        self.db.recreate()
86        self.db.save_docs(copy.deepcopy(DOCS))
87
88    def test_saves_partial_filter_selector_in_index(self):
89        selector = {"location": {"$gte": "FRA"}}
90        self.db.create_index(["location"], partial_filter_selector=selector)
91        indexes = self.db.list_indexes()
92        self.assertEqual(indexes[1]["def"]["partial_filter_selector"], selector)
93
94    def test_partial_filter_only_in_return_if_not_default(self):
95        self.db.create_index(["location"])
96        index = self.db.list_indexes()[1]
97        self.assertEqual("partial_filter_selector" in index["def"], False)
98
99    def test_saves_selector_in_index_throws(self):
100        selector = {"location": {"$gte": "FRA"}}
101        try:
102            self.db.create_index(["location"], selector=selector)
103        except Exception as e:
104            assert e.response.status_code == 400
105        else:
106            raise AssertionError("bad index creation")
107
108    def test_uses_partial_index_for_query_selector(self):
109        selector = {"location": {"$gte": "FRA"}}
110        self.db.create_index(
111            ["location"],
112            partial_filter_selector=selector,
113            ddoc="Selected",
114            name="Selected",
115        )
116        resp = self.db.find(selector, explain=True, use_index="Selected")
117        self.assertEqual(resp["index"]["name"], "Selected")
118        docs = self.db.find(selector, use_index="Selected")
119        self.assertEqual(len(docs), 3)
120
121    def test_uses_partial_index_with_different_selector(self):
122        selector = {"location": {"$gte": "FRA"}}
123        selector2 = {"location": {"$gte": "A"}}
124        self.db.create_index(
125            ["location"],
126            partial_filter_selector=selector,
127            ddoc="Selected",
128            name="Selected",
129        )
130        resp = self.db.find(selector2, explain=True, use_index="Selected")
131        self.assertEqual(resp["index"]["name"], "Selected")
132        docs = self.db.find(selector2, use_index="Selected")
133        self.assertEqual(len(docs), 3)
134
135    def test_doesnot_use_selector_when_not_specified(self):
136        selector = {"location": {"$gte": "FRA"}}
137        self.db.create_index(
138            ["location"],
139            partial_filter_selector=selector,
140            ddoc="Selected",
141            name="Selected",
142        )
143        resp = self.db.find(selector, explain=True)
144        self.assertEqual(resp["index"]["name"], "_all_docs")
145
146    def test_doesnot_use_selector_when_not_specified_with_index(self):
147        selector = {"location": {"$gte": "FRA"}}
148        self.db.create_index(
149            ["location"],
150            partial_filter_selector=selector,
151            ddoc="Selected",
152            name="Selected",
153        )
154        self.db.create_index(["location"], name="NotSelected")
155        resp = self.db.find(selector, explain=True)
156        self.assertEqual(resp["index"]["name"], "NotSelected")
157
158    def test_old_selector_with_no_selector_still_supported(self):
159        selector = {"location": {"$gte": "FRA"}}
160        self.db.save_doc(oldschoolnoselectorddoc)
161        resp = self.db.find(selector, explain=True, use_index="oldschoolnoselector")
162        self.assertEqual(resp["index"]["name"], "oldschoolnoselector")
163        docs = self.db.find(selector, use_index="oldschoolnoselector")
164        self.assertEqual(len(docs), 3)
165
166    def test_old_selector_still_supported(self):
167        selector = {"location": {"$gte": "FRA"}}
168        self.db.save_doc(oldschoolddoc)
169        resp = self.db.find(selector, explain=True, use_index="oldschool")
170        self.assertEqual(resp["index"]["name"], "oldschool")
171        docs = self.db.find(selector, use_index="oldschool")
172        self.assertEqual(len(docs), 3)
173
174    @unittest.skipUnless(mango.has_text_service(), "requires text service")
175    def test_text_saves_partialfilterselector_in_index(self):
176        selector = {"location": {"$gte": "FRA"}}
177        self.db.create_text_index(
178            fields=[{"name": "location", "type": "string"}],
179            partial_filter_selector=selector,
180        )
181        indexes = self.db.list_indexes()
182        self.assertEqual(indexes[1]["def"]["partial_filter_selector"], selector)
183
184    @unittest.skipUnless(mango.has_text_service(), "requires text service")
185    def test_text_uses_partial_index_for_query_selector(self):
186        selector = {"location": {"$gte": "FRA"}}
187        self.db.create_text_index(
188            fields=[{"name": "location", "type": "string"}],
189            partial_filter_selector=selector,
190            ddoc="Selected",
191            name="Selected",
192        )
193        resp = self.db.find(selector, explain=True, use_index="Selected")
194        self.assertEqual(resp["index"]["name"], "Selected")
195        docs = self.db.find(selector, use_index="Selected", fields=["_id", "location"])
196        self.assertEqual(len(docs), 3)
197
198    @unittest.skipUnless(mango.has_text_service(), "requires text service")
199    def test_text_uses_partial_index_with_different_selector(self):
200        selector = {"location": {"$gte": "FRA"}}
201        selector2 = {"location": {"$gte": "A"}}
202        self.db.create_text_index(
203            fields=[{"name": "location", "type": "string"}],
204            partial_filter_selector=selector,
205            ddoc="Selected",
206            name="Selected",
207        )
208        resp = self.db.find(selector2, explain=True, use_index="Selected")
209        self.assertEqual(resp["index"]["name"], "Selected")
210        docs = self.db.find(selector2, use_index="Selected")
211        self.assertEqual(len(docs), 3)
212
213    @unittest.skipUnless(mango.has_text_service(), "requires text service")
214    def test_text_doesnot_use_selector_when_not_specified(self):
215        selector = {"location": {"$gte": "FRA"}}
216        self.db.create_text_index(
217            fields=[{"name": "location", "type": "string"}],
218            partial_filter_selector=selector,
219            ddoc="Selected",
220            name="Selected",
221        )
222        resp = self.db.find(selector, explain=True)
223        self.assertEqual(resp["index"]["name"], "_all_docs")
224
225    @unittest.skipUnless(mango.has_text_service(), "requires text service")
226    def test_text_doesnot_use_selector_when_not_specified_with_index(self):
227        selector = {"location": {"$gte": "FRA"}}
228        self.db.create_text_index(
229            fields=[{"name": "location", "type": "string"}],
230            partial_filter_selector=selector,
231            ddoc="Selected",
232            name="Selected",
233        )
234        self.db.create_text_index(
235            fields=[{"name": "location", "type": "string"}], name="NotSelected"
236        )
237        resp = self.db.find(selector, explain=True)
238        self.assertEqual(resp["index"]["name"], "NotSelected")
239
240    @unittest.skipUnless(mango.has_text_service(), "requires text service")
241    def test_text_old_selector_still_supported(self):
242        selector = {"location": {"$gte": "FRA"}}
243        self.db.save_doc(oldschoolddoctext)
244        resp = self.db.find(selector, explain=True, use_index="oldschooltext")
245        self.assertEqual(resp["index"]["name"], "oldschooltext")
246        docs = self.db.find(selector, use_index="oldschooltext")
247        self.assertEqual(len(docs), 3)
248
249    @unittest.skipUnless(mango.has_text_service(), "requires text service")
250    def test_text_old_selector_still_supported_via_api(self):
251        selector = {"location": {"$gte": "FRA"}}
252        self.db.create_text_index(
253            fields=[{"name": "location", "type": "string"}],
254            selector=selector,
255            ddoc="Selected",
256            name="Selected",
257        )
258        docs = self.db.find({"location": {"$exists": True}}, use_index="Selected")
259        self.assertEqual(len(docs), 3)
260
261    @unittest.skipUnless(mango.has_text_service(), "requires text service")
262    def test_text_partial_filter_only_in_return_if_not_default(self):
263        self.db.create_text_index(fields=[{"name": "location", "type": "string"}])
264        index = self.db.list_indexes()[1]
265        self.assertEqual("partial_filter_selector" in index["def"], False)
266