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 json 14import mango 15import unittest 16import user_docs 17import math 18from hypothesis import given, assume, example 19import hypothesis.strategies as st 20 21 22@unittest.skipIf(mango.has_text_service(), "text service exists") 23class TextIndexCheckTests(mango.DbPerClass): 24 def test_create_text_index(self): 25 body = json.dumps({"index": {}, "type": "text"}) 26 resp = self.db.sess.post(self.db.path("_index"), data=body) 27 assert resp.status_code == 503, resp 28 29 30@unittest.skipUnless(mango.has_text_service(), "requires text service") 31class BasicTextTests(mango.UserDocsTextTests): 32 def test_simple(self): 33 docs = self.db.find({"$text": "Stephanie"}) 34 assert len(docs) == 1 35 assert docs[0]["name"]["first"] == "Stephanie" 36 37 def test_with_integer(self): 38 docs = self.db.find({"name.first": "Stephanie", "age": 48}) 39 assert len(docs) == 1 40 assert docs[0]["name"]["first"] == "Stephanie" 41 assert docs[0]["age"] == 48 42 43 def test_with_boolean(self): 44 docs = self.db.find({"name.first": "Stephanie", "manager": False}) 45 assert len(docs) == 1 46 assert docs[0]["name"]["first"] == "Stephanie" 47 assert docs[0]["manager"] == False 48 49 def test_with_array(self): 50 faves = ["Ruby", "C", "Python"] 51 docs = self.db.find({"name.first": "Stephanie", "favorites": faves}) 52 assert docs[0]["name"]["first"] == "Stephanie" 53 assert docs[0]["favorites"] == faves 54 55 def test_array_ref(self): 56 docs = self.db.find({"favorites.1": "Python"}) 57 assert len(docs) == 4 58 for d in docs: 59 assert "Python" in d["favorites"] 60 61 # Nested Level 62 docs = self.db.find({"favorites.0.2": "Python"}) 63 assert len(docs) == 1 64 for d in docs: 65 assert "Python" in d["favorites"][0][2] 66 67 def test_number_ref(self): 68 docs = self.db.find({"11111": "number_field"}) 69 assert len(docs) == 1 70 assert docs[0]["11111"] == "number_field" 71 72 docs = self.db.find({"22222.33333": "nested_number_field"}) 73 assert len(docs) == 1 74 assert docs[0]["22222"]["33333"] == "nested_number_field" 75 76 def test_lt(self): 77 docs = self.db.find({"age": {"$lt": 22}}) 78 assert len(docs) == 0 79 80 docs = self.db.find({"age": {"$lt": 23}}) 81 assert len(docs) == 1 82 assert docs[0]["user_id"] == 9 83 84 docs = self.db.find({"age": {"$lt": 33}}) 85 assert len(docs) == 2 86 for d in docs: 87 assert d["user_id"] in (1, 9) 88 89 docs = self.db.find({"age": {"$lt": 34}}) 90 assert len(docs) == 3 91 for d in docs: 92 assert d["user_id"] in (1, 7, 9) 93 94 docs = self.db.find({"company": {"$lt": "Dreamia"}}) 95 assert len(docs) == 1 96 assert docs[0]["company"] == "Affluex" 97 98 docs = self.db.find({"foo": {"$lt": "bar car apple"}}) 99 assert len(docs) == 0 100 101 def test_lte(self): 102 docs = self.db.find({"age": {"$lte": 21}}) 103 assert len(docs) == 0 104 105 docs = self.db.find({"age": {"$lte": 22}}) 106 assert len(docs) == 1 107 assert docs[0]["user_id"] == 9 108 109 docs = self.db.find({"age": {"$lte": 33}}) 110 assert len(docs) == 3 111 for d in docs: 112 assert d["user_id"] in (1, 7, 9) 113 114 docs = self.db.find({"company": {"$lte": "Dreamia"}}) 115 assert len(docs) == 2 116 for d in docs: 117 assert d["user_id"] in (0, 11) 118 119 docs = self.db.find({"foo": {"$lte": "bar car apple"}}) 120 assert len(docs) == 1 121 assert docs[0]["user_id"] == 14 122 123 def test_eq(self): 124 docs = self.db.find({"age": 21}) 125 assert len(docs) == 0 126 127 docs = self.db.find({"age": 22}) 128 assert len(docs) == 1 129 assert docs[0]["user_id"] == 9 130 131 docs = self.db.find({"age": {"$eq": 22}}) 132 assert len(docs) == 1 133 assert docs[0]["user_id"] == 9 134 135 docs = self.db.find({"age": 33}) 136 assert len(docs) == 1 137 assert docs[0]["user_id"] == 7 138 139 def test_ne(self): 140 docs = self.db.find({"age": {"$ne": 22}}) 141 assert len(docs) == len(user_docs.DOCS) - 1 142 for d in docs: 143 assert d["age"] != 22 144 145 docs = self.db.find({"$not": {"age": 22}}) 146 assert len(docs) == len(user_docs.DOCS) - 1 147 for d in docs: 148 assert d["age"] != 22 149 150 def test_gt(self): 151 docs = self.db.find({"age": {"$gt": 77}}) 152 assert len(docs) == 2 153 for d in docs: 154 assert d["user_id"] in (3, 13) 155 156 docs = self.db.find({"age": {"$gt": 78}}) 157 assert len(docs) == 1 158 assert docs[0]["user_id"] == 3 159 160 docs = self.db.find({"age": {"$gt": 79}}) 161 assert len(docs) == 0 162 163 docs = self.db.find({"company": {"$gt": "Zialactic"}}) 164 assert len(docs) == 0 165 166 docs = self.db.find({"foo": {"$gt": "bar car apple"}}) 167 assert len(docs) == 0 168 169 docs = self.db.find({"foo": {"$gt": "bar car"}}) 170 assert len(docs) == 1 171 assert docs[0]["user_id"] == 14 172 173 def test_gte(self): 174 docs = self.db.find({"age": {"$gte": 77}}) 175 assert len(docs) == 2 176 for d in docs: 177 assert d["user_id"] in (3, 13) 178 179 docs = self.db.find({"age": {"$gte": 78}}) 180 assert len(docs) == 2 181 for d in docs: 182 assert d["user_id"] in (3, 13) 183 184 docs = self.db.find({"age": {"$gte": 79}}) 185 assert len(docs) == 1 186 assert docs[0]["user_id"] == 3 187 188 docs = self.db.find({"age": {"$gte": 80}}) 189 assert len(docs) == 0 190 191 docs = self.db.find({"company": {"$gte": "Zialactic"}}) 192 assert len(docs) == 1 193 assert docs[0]["company"] == "Zialactic" 194 195 docs = self.db.find({"foo": {"$gte": "bar car apple"}}) 196 assert len(docs) == 1 197 assert docs[0]["user_id"] == 14 198 199 def test_and(self): 200 docs = self.db.find({"age": 22, "manager": True}) 201 assert len(docs) == 1 202 assert docs[0]["user_id"] == 9 203 204 docs = self.db.find({"age": 22, "manager": False}) 205 assert len(docs) == 0 206 207 docs = self.db.find({"$and": [{"age": 22}, {"manager": True}]}) 208 assert len(docs) == 1 209 assert docs[0]["user_id"] == 9 210 211 docs = self.db.find({"$and": [{"age": 22}, {"manager": False}]}) 212 assert len(docs) == 0 213 214 docs = self.db.find({"$text": "Ramona", "age": 22}) 215 assert len(docs) == 1 216 assert docs[0]["user_id"] == 9 217 218 docs = self.db.find({"$and": [{"$text": "Ramona"}, {"age": 22}]}) 219 assert len(docs) == 1 220 assert docs[0]["user_id"] == 9 221 222 docs = self.db.find({"$and": [{"$text": "Ramona"}, {"$text": "Floyd"}]}) 223 assert len(docs) == 1 224 assert docs[0]["user_id"] == 9 225 226 def test_or(self): 227 docs = self.db.find({"$or": [{"age": 22}, {"age": 33}]}) 228 assert len(docs) == 2 229 for d in docs: 230 assert d["user_id"] in (7, 9) 231 232 q = {"$or": [{"$text": "Ramona"}, {"$text": "Stephanie"}]} 233 docs = self.db.find(q) 234 assert len(docs) == 2 235 for d in docs: 236 assert d["user_id"] in (0, 9) 237 238 q = {"$or": [{"$text": "Ramona"}, {"age": 22}]} 239 docs = self.db.find(q) 240 assert len(docs) == 1 241 assert docs[0]["user_id"] == 9 242 243 def test_and_or(self): 244 q = {"age": 22, "$or": [{"manager": False}, {"location.state": "Missouri"}]} 245 docs = self.db.find(q) 246 assert len(docs) == 1 247 assert docs[0]["user_id"] == 9 248 249 q = {"$or": [{"age": 22}, {"age": 43, "manager": True}]} 250 docs = self.db.find(q) 251 assert len(docs) == 2 252 for d in docs: 253 assert d["user_id"] in (9, 10) 254 255 q = {"$or": [{"$text": "Ramona"}, {"age": 43, "manager": True}]} 256 docs = self.db.find(q) 257 assert len(docs) == 2 258 for d in docs: 259 assert d["user_id"] in (9, 10) 260 261 def test_nor(self): 262 docs = self.db.find({"$nor": [{"age": 22}, {"age": 33}]}) 263 assert len(docs) == 13 264 for d in docs: 265 assert d["user_id"] not in (7, 9) 266 267 def test_in_with_value(self): 268 docs = self.db.find({"age": {"$in": [1, 5]}}) 269 assert len(docs) == 0 270 271 docs = self.db.find({"age": {"$in": [1, 5, 22]}}) 272 assert len(docs) == 1 273 assert docs[0]["user_id"] == 9 274 275 docs = self.db.find({"age": {"$in": [1, 5, 22, 31]}}) 276 assert len(docs) == 2 277 for d in docs: 278 assert d["user_id"] in (1, 9) 279 280 docs = self.db.find({"age": {"$in": [22, 31]}}) 281 assert len(docs) == 2 282 for d in docs: 283 assert d["user_id"] in (1, 9) 284 285 # Limits on boolean clauses? 286 docs = self.db.find({"age": {"$in": list(range(1000))}}) 287 assert len(docs) == 15 288 289 def test_in_with_array(self): 290 vals = ["Random Garbage", 52, {"Versions": {"Alpha": "Beta"}}] 291 docs = self.db.find({"favorites": {"$in": vals}}) 292 assert len(docs) == 1 293 assert docs[0]["user_id"] == 1 294 295 vals = ["Lisp", "Python"] 296 docs = self.db.find({"favorites": {"$in": vals}}) 297 assert len(docs) == 10 298 299 vals = [{"val1": 1, "val2": "val2"}] 300 docs = self.db.find({"test_in": {"$in": vals}}) 301 assert len(docs) == 1 302 assert docs[0]["user_id"] == 2 303 304 def test_nin_with_value(self): 305 docs = self.db.find({"age": {"$nin": [1, 5]}}) 306 assert len(docs) == len(user_docs.DOCS) 307 308 docs = self.db.find({"age": {"$nin": [1, 5, 22]}}) 309 assert len(docs) == len(user_docs.DOCS) - 1 310 for d in docs: 311 assert d["user_id"] != 9 312 313 docs = self.db.find({"age": {"$nin": [1, 5, 22, 31]}}) 314 assert len(docs) == len(user_docs.DOCS) - 2 315 for d in docs: 316 assert d["user_id"] not in (1, 9) 317 318 docs = self.db.find({"age": {"$nin": [22, 31]}}) 319 assert len(docs) == len(user_docs.DOCS) - 2 320 for d in docs: 321 assert d["user_id"] not in (1, 9) 322 323 # Limits on boolean clauses? 324 docs = self.db.find({"age": {"$nin": list(range(1000))}}) 325 assert len(docs) == 0 326 327 def test_nin_with_array(self): 328 vals = ["Random Garbage", 52, {"Versions": {"Alpha": "Beta"}}] 329 docs = self.db.find({"favorites": {"$nin": vals}}) 330 assert len(docs) == len(user_docs.DOCS) - 1 331 for d in docs: 332 assert d["user_id"] != 1 333 334 vals = ["Lisp", "Python"] 335 docs = self.db.find({"favorites": {"$nin": vals}}) 336 assert len(docs) == 5 337 338 vals = [{"val1": 1, "val2": "val2"}] 339 docs = self.db.find({"test_in": {"$nin": vals}}) 340 assert len(docs) == 0 341 342 def test_all(self): 343 vals = ["Ruby", "C", "Python", {"Versions": {"Alpha": "Beta"}}] 344 docs = self.db.find({"favorites": {"$all": vals}}) 345 assert len(docs) == 1 346 assert docs[0]["user_id"] == 1 347 348 # This matches where favorites either contains 349 # the nested array, or is the nested array. This is 350 # notably different than the non-nested array in that 351 # it does not match a re-ordered version of the array. 352 # The fact that user_id 14 isn't included demonstrates 353 # this behavior. 354 vals = [["Lisp", "Erlang", "Python"]] 355 docs = self.db.find({"favorites": {"$all": vals}}) 356 assert len(docs) == 2 357 for d in docs: 358 assert d["user_id"] in (3, 9) 359 360 def test_exists_field(self): 361 docs = self.db.find({"exists_field": {"$exists": True}}) 362 assert len(docs) == 2 363 for d in docs: 364 assert d["user_id"] in (7, 8) 365 366 docs = self.db.find({"exists_field": {"$exists": False}}) 367 assert len(docs) == len(user_docs.DOCS) - 2 368 for d in docs: 369 assert d["user_id"] not in (7, 8) 370 371 def test_exists_array(self): 372 docs = self.db.find({"exists_array": {"$exists": True}}) 373 assert len(docs) == 2 374 for d in docs: 375 assert d["user_id"] in (9, 10) 376 377 docs = self.db.find({"exists_array": {"$exists": False}}) 378 assert len(docs) == len(user_docs.DOCS) - 2 379 for d in docs: 380 assert d["user_id"] not in (9, 10) 381 382 def test_exists_object(self): 383 docs = self.db.find({"exists_object": {"$exists": True}}) 384 assert len(docs) == 2 385 for d in docs: 386 assert d["user_id"] in (11, 12) 387 388 docs = self.db.find({"exists_object": {"$exists": False}}) 389 assert len(docs) == len(user_docs.DOCS) - 2 390 for d in docs: 391 assert d["user_id"] not in (11, 12) 392 393 def test_exists_object_member(self): 394 docs = self.db.find({"exists_object.should": {"$exists": True}}) 395 assert len(docs) == 1 396 assert docs[0]["user_id"] == 11 397 398 docs = self.db.find({"exists_object.should": {"$exists": False}}) 399 assert len(docs) == len(user_docs.DOCS) - 1 400 for d in docs: 401 assert d["user_id"] != 11 402 403 def test_exists_and(self): 404 q = { 405 "$and": [ 406 {"manager": {"$exists": True}}, 407 {"exists_object.should": {"$exists": True}}, 408 ] 409 } 410 docs = self.db.find(q) 411 assert len(docs) == 1 412 assert docs[0]["user_id"] == 11 413 414 q = { 415 "$and": [ 416 {"manager": {"$exists": False}}, 417 {"exists_object.should": {"$exists": True}}, 418 ] 419 } 420 docs = self.db.find(q) 421 assert len(docs) == 0 422 423 # Translates to manager exists or exists_object.should doesn't 424 # exist, which will match all docs 425 q = {"$not": q} 426 docs = self.db.find(q) 427 assert len(docs) == len(user_docs.DOCS) 428 429 def test_value_chars(self): 430 q = {"complex_field_value": '+-(){}[]^~&&*||"\\/?:!'} 431 docs = self.db.find(q) 432 assert len(docs) == 1 433 434 def test_regex(self): 435 docs = self.db.find( 436 {"age": {"$gt": 40}, "location.state": {"$regex": "(?i)new.*"}} 437 ) 438 assert len(docs) == 2 439 assert docs[0]["user_id"] == 2 440 assert docs[1]["user_id"] == 10 441 442 # test lucene syntax in $text 443 444 445@unittest.skipUnless(mango.has_text_service(), "requires text service") 446class ElemMatchTests(mango.FriendDocsTextTests): 447 def test_elem_match_non_object(self): 448 q = {"bestfriends": {"$elemMatch": {"$eq": "Wolverine", "$eq": "Cyclops"}}} 449 docs = self.db.find(q) 450 self.assertEqual(len(docs), 1) 451 self.assertEqual(docs[0]["bestfriends"], ["Wolverine", "Cyclops"]) 452 453 q = {"results": {"$elemMatch": {"$gte": 80, "$lt": 85}}} 454 455 docs = self.db.find(q) 456 self.assertEqual(len(docs), 1) 457 self.assertEqual(docs[0]["results"], [82, 85, 88]) 458 459 def test_elem_match(self): 460 q = {"friends": {"$elemMatch": {"name.first": "Vargas"}}} 461 docs = self.db.find(q) 462 self.assertEqual(len(docs), 2) 463 for d in docs: 464 self.assertIn(d["user_id"], (0, 1)) 465 466 q = {"friends": {"$elemMatch": {"name.first": "Ochoa", "name.last": "Burch"}}} 467 docs = self.db.find(q) 468 self.assertEqual(len(docs), 1) 469 self.assertEqual(docs[0]["user_id"], 4) 470 471 # Check that we can do logic in elemMatch 472 q = {"friends": {"$elemMatch": {"name.first": "Ochoa", "type": "work"}}} 473 docs = self.db.find(q) 474 self.assertEqual(len(docs), 2) 475 for d in docs: 476 self.assertIn(d["user_id"], (1, 15)) 477 478 q = { 479 "friends": { 480 "$elemMatch": { 481 "name.first": "Ochoa", 482 "$or": [{"type": "work"}, {"type": "personal"}], 483 } 484 } 485 } 486 docs = self.db.find(q) 487 self.assertEqual(len(docs), 3) 488 for d in docs: 489 self.assertIn(d["user_id"], (1, 4, 15)) 490 491 # Same as last, but using $in 492 q = { 493 "friends": { 494 "$elemMatch": { 495 "name.first": "Ochoa", 496 "type": {"$in": ["work", "personal"]}, 497 } 498 } 499 } 500 docs = self.db.find(q) 501 self.assertEqual(len(docs), 3) 502 for d in docs: 503 self.assertIn(d["user_id"], (1, 4, 15)) 504 505 q = { 506 "$and": [ 507 {"friends": {"$elemMatch": {"id": 0, "name": {"$exists": True}}}}, 508 { 509 "friends": { 510 "$elemMatch": { 511 "$or": [ 512 {"name": {"first": "Campos", "last": "Freeman"}}, 513 { 514 "name": { 515 "$in": [ 516 {"first": "Gibbs", "last": "Mccarty"}, 517 {"first": "Wilkins", "last": "Chang"}, 518 ] 519 } 520 }, 521 ] 522 } 523 } 524 }, 525 ] 526 } 527 docs = self.db.find(q) 528 self.assertEqual(len(docs), 3) 529 for d in docs: 530 self.assertIn(d["user_id"], (10, 11, 12)) 531 532 533@unittest.skipUnless(mango.has_text_service(), "requires text service") 534class AllMatchTests(mango.FriendDocsTextTests): 535 def test_all_match(self): 536 q = {"friends": {"$allMatch": {"type": "personal"}}} 537 docs = self.db.find(q) 538 assert len(docs) == 2 539 for d in docs: 540 assert d["user_id"] in (8, 5) 541 542 # Check that we can do logic in allMatch 543 q = { 544 "friends": { 545 "$allMatch": { 546 "name.first": "Ochoa", 547 "$or": [{"type": "work"}, {"type": "personal"}], 548 } 549 } 550 } 551 docs = self.db.find(q) 552 assert len(docs) == 1 553 assert docs[0]["user_id"] == 15 554 555 # Same as last, but using $in 556 q = { 557 "friends": { 558 "$allMatch": { 559 "name.first": "Ochoa", 560 "type": {"$in": ["work", "personal"]}, 561 } 562 } 563 } 564 docs = self.db.find(q) 565 assert len(docs) == 1 566 assert docs[0]["user_id"] == 15 567 568 569# Test numeric strings for $text 570@unittest.skipUnless(mango.has_text_service(), "requires text service") 571class NumStringTests(mango.DbPerClass): 572 @classmethod 573 def setUpClass(klass): 574 super(NumStringTests, klass).setUpClass() 575 klass.db.recreate() 576 if mango.has_text_service(): 577 klass.db.create_text_index() 578 579 # not available for python 2.7.x 580 def isFinite(num): 581 not (math.isinf(num) or math.isnan(num)) 582 583 @given(f=st.floats().filter(isFinite).map(str) | st.floats().map(lambda f: f.hex())) 584 @example("NaN") 585 @example("Infinity") 586 def test_floating_point_val(self, f): 587 doc = {"number_string": f} 588 self.db.save_doc(doc) 589 q = {"$text": f} 590 docs = self.db.find(q) 591 if len(docs) == 1: 592 assert docs[0]["number_string"] == f 593 if len(docs) == 2: 594 if docs[0]["number_string"] != f: 595 assert docs[1]["number_string"] == f 596 q = {"number_string": f} 597 docs = self.db.find(q) 598 if len(docs) == 1: 599 assert docs[0]["number_string"] == f 600 if len(docs) == 2: 601 if docs[0]["number_string"] != f: 602 assert docs[1]["number_string"] == f 603