github.com/go-kivik/kivik/v4@v4.3.2/x/mango/match_test.go (about) 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 13 package mango 14 15 import ( 16 "regexp" 17 "testing" 18 19 "gitlab.com/flimzy/testy" 20 ) 21 22 func TestMatch(t *testing.T) { 23 type test struct { 24 sel Node 25 doc interface{} 26 want bool 27 } 28 29 tests := testy.NewTable() 30 tests.Add("nil selector", test{ 31 sel: nil, 32 doc: "foo", 33 want: true, 34 }) 35 tests.Add("equality", test{ 36 sel: &conditionNode{ 37 op: OpEqual, 38 cond: "foo", 39 }, 40 doc: "foo", 41 want: true, 42 }) 43 tests.Add("!equality", test{ 44 sel: &conditionNode{ 45 op: OpEqual, 46 cond: "foo", 47 }, 48 doc: "bar", 49 want: false, 50 }) 51 tests.Add("inequality", test{ 52 sel: &conditionNode{ 53 op: OpNotEqual, 54 cond: "foo", 55 }, 56 doc: "bar", 57 want: true, 58 }) 59 tests.Add("!inequality", test{ 60 sel: &conditionNode{ 61 op: OpNotEqual, 62 cond: "foo", 63 }, 64 doc: "foo", 65 want: false, 66 }) 67 tests.Add("less than", test{ 68 sel: &conditionNode{ 69 op: OpLessThan, 70 cond: float64(5), 71 }, 72 doc: float64(4), 73 want: true, 74 }) 75 tests.Add("!less than", test{ 76 sel: &conditionNode{ 77 op: OpLessThan, 78 cond: float64(5), 79 }, 80 doc: float64(10), 81 want: false, 82 }) 83 tests.Add("less than or equal", test{ 84 sel: &conditionNode{ 85 op: OpLessThanOrEqual, 86 cond: float64(5), 87 }, 88 doc: float64(5), 89 want: true, 90 }) 91 tests.Add("!less than or equal", test{ 92 sel: &conditionNode{ 93 op: OpLessThanOrEqual, 94 cond: float64(5), 95 }, 96 doc: float64(8), 97 want: false, 98 }) 99 tests.Add("greater than", test{ 100 sel: &conditionNode{ 101 op: OpGreaterThan, 102 cond: float64(5), 103 }, 104 doc: float64(10), 105 want: true, 106 }) 107 tests.Add("!greater than", test{ 108 sel: &conditionNode{ 109 op: OpGreaterThan, 110 cond: float64(5), 111 }, 112 doc: float64(2), 113 want: false, 114 }) 115 tests.Add("greater than or equal", test{ 116 sel: &conditionNode{ 117 op: OpGreaterThanOrEqual, 118 cond: float64(5), 119 }, 120 doc: float64(5), 121 want: true, 122 }) 123 tests.Add("!greater than or equal", test{ 124 sel: &conditionNode{ 125 op: OpGreaterThanOrEqual, 126 cond: float64(5), 127 }, 128 doc: float64(2), 129 want: false, 130 }) 131 tests.Add("exists", test{ 132 sel: &fieldNode{ 133 field: "foo", 134 cond: &conditionNode{op: OpExists, cond: true}, 135 }, 136 doc: map[string]interface{}{ 137 "foo": "bar", 138 }, 139 want: true, 140 }) 141 tests.Add("!exists", test{ 142 sel: &fieldNode{ 143 field: "baz", 144 cond: &conditionNode{op: OpExists, cond: true}, 145 }, 146 doc: map[string]interface{}{ 147 "foo": "bar", 148 }, 149 want: false, 150 }) 151 tests.Add("not exists", test{ 152 sel: &fieldNode{ 153 field: "baz", 154 cond: &conditionNode{op: OpExists, cond: false}, 155 }, 156 doc: map[string]interface{}{ 157 "foo": "bar", 158 }, 159 want: true, 160 }) 161 tests.Add("!not exists", test{ 162 sel: &fieldNode{ 163 field: "baz", 164 cond: &conditionNode{op: OpExists, cond: true}, 165 }, 166 doc: map[string]interface{}{ 167 "foo": "bar", 168 }, 169 want: false, 170 }) 171 tests.Add("type, null", test{ 172 sel: &conditionNode{ 173 op: OpType, 174 cond: "null", 175 }, 176 doc: nil, 177 want: true, 178 }) 179 tests.Add("!type, null", test{ 180 sel: &conditionNode{ 181 op: OpType, 182 cond: "null", 183 }, 184 doc: "foo", 185 want: false, 186 }) 187 tests.Add("type, boolean", test{ 188 sel: &conditionNode{ 189 op: OpType, 190 cond: "boolean", 191 }, 192 doc: true, 193 want: true, 194 }) 195 tests.Add("!type, boolean", test{ 196 sel: &conditionNode{ 197 op: OpType, 198 cond: "boolean", 199 }, 200 doc: "foo", 201 want: false, 202 }) 203 tests.Add("type, number", test{ 204 sel: &conditionNode{ 205 op: OpType, 206 cond: "number", 207 }, 208 doc: float64(5), 209 want: true, 210 }) 211 tests.Add("!type, number", test{ 212 sel: &conditionNode{ 213 op: OpType, 214 cond: "number", 215 }, 216 doc: "foo", 217 want: false, 218 }) 219 tests.Add("type, string", test{ 220 sel: &conditionNode{ 221 op: OpType, 222 cond: "string", 223 }, 224 doc: "foo", 225 want: true, 226 }) 227 tests.Add("!type, string", test{ 228 sel: &conditionNode{ 229 op: OpType, 230 cond: "string", 231 }, 232 doc: float64(5), 233 want: false, 234 }) 235 tests.Add("type, array", test{ 236 sel: &conditionNode{ 237 op: OpType, 238 cond: "array", 239 }, 240 doc: []interface{}{"foo"}, 241 want: true, 242 }) 243 tests.Add("!type, array", test{ 244 sel: &conditionNode{ 245 op: OpType, 246 cond: "array", 247 }, 248 doc: "foo", 249 want: false, 250 }) 251 tests.Add("type, object", test{ 252 sel: &conditionNode{ 253 op: OpType, 254 cond: "object", 255 }, 256 doc: map[string]interface{}{"foo": "bar"}, 257 want: true, 258 }) 259 tests.Add("!type, object", test{ 260 sel: &conditionNode{ 261 op: OpType, 262 cond: "object", 263 }, 264 doc: "foo", 265 want: false, 266 }) 267 tests.Add("in", test{ 268 sel: &conditionNode{ 269 op: OpIn, 270 cond: []interface{}{"foo", "bar"}, 271 }, 272 doc: "foo", 273 want: true, 274 }) 275 tests.Add("!in", test{ 276 sel: &conditionNode{ 277 op: OpIn, 278 cond: []interface{}{"foo", "bar"}, 279 }, 280 doc: "baz", 281 want: false, 282 }) 283 tests.Add("not in", test{ 284 sel: &conditionNode{ 285 op: OpNotIn, 286 cond: []interface{}{"foo", "bar"}, 287 }, 288 doc: "baz", 289 want: true, 290 }) 291 tests.Add("!not in", test{ 292 sel: &conditionNode{ 293 op: OpNotIn, 294 cond: []interface{}{"foo", "bar"}, 295 }, 296 doc: "foo", 297 want: false, 298 }) 299 tests.Add("size", test{ 300 sel: &conditionNode{ 301 op: OpSize, 302 cond: float64(3), 303 }, 304 doc: []interface{}{"foo", "bar", "baz"}, 305 want: true, 306 }) 307 tests.Add("!size", test{ 308 sel: &conditionNode{ 309 op: OpSize, 310 cond: float64(3), 311 }, 312 doc: []interface{}{"foo", "bar"}, 313 want: false, 314 }) 315 tests.Add("size, non-array", test{ 316 sel: &conditionNode{ 317 op: OpSize, 318 cond: float64(3), 319 }, 320 doc: "foo", 321 want: false, 322 }) 323 tests.Add("mod", test{ 324 sel: &conditionNode{ 325 op: OpMod, 326 cond: [2]int64{3, 2}, 327 }, 328 doc: float64(8), 329 want: true, 330 }) 331 tests.Add("!mod", test{ 332 sel: &conditionNode{ 333 op: OpMod, 334 cond: [2]int64{3, 2}, 335 }, 336 doc: float64(7), 337 want: false, 338 }) 339 tests.Add("mod, non-integer", test{ 340 sel: &conditionNode{ 341 op: OpMod, 342 cond: [2]int64{3, 2}, 343 }, 344 doc: float64(7.5), 345 want: false, 346 }) 347 tests.Add("mod, non-number", test{ 348 sel: &conditionNode{ 349 op: OpMod, 350 cond: [2]int64{3, 2}, 351 }, 352 doc: "foo", 353 want: false, 354 }) 355 tests.Add("regex", test{ 356 sel: &conditionNode{ 357 op: OpRegex, 358 cond: regexp.MustCompile("^foo$"), 359 }, 360 doc: "foo", 361 want: true, 362 }) 363 tests.Add("!regex", test{ 364 sel: &conditionNode{ 365 op: OpRegex, 366 cond: regexp.MustCompile("^foo$"), 367 }, 368 doc: "bar", 369 want: false, 370 }) 371 tests.Add("regexp, non-string", test{ 372 sel: &conditionNode{ 373 op: OpRegex, 374 cond: regexp.MustCompile("^foo$"), 375 }, 376 doc: float64(5), 377 want: false, 378 }) 379 tests.Add("all", test{ 380 sel: &conditionNode{ 381 op: OpAll, 382 cond: []interface{}{"Comedy", "Short"}, 383 }, 384 doc: []interface{}{ 385 "Comedy", 386 "Short", 387 "Animation", 388 }, 389 want: true, 390 }) 391 tests.Add("!all", test{ 392 sel: &conditionNode{ 393 op: OpAll, 394 cond: []interface{}{"Comedy", "Short"}, 395 }, 396 doc: []interface{}{ 397 "Comedy", 398 "Animation", 399 }, 400 want: false, 401 }) 402 tests.Add("all, non-array", test{ 403 sel: &conditionNode{ 404 op: OpAll, 405 cond: []interface{}{"Comedy", "Short"}, 406 }, 407 doc: "Comedy", 408 want: false, 409 }) 410 tests.Add("field selector", test{ 411 sel: &fieldNode{ 412 field: "foo", 413 cond: &conditionNode{ 414 op: OpEqual, 415 cond: "bar", 416 }, 417 }, 418 doc: map[string]interface{}{ 419 "foo": "bar", 420 }, 421 want: true, 422 }) 423 tests.Add("!field selector", test{ 424 sel: &fieldNode{ 425 field: "asdf", 426 cond: &conditionNode{ 427 op: OpEqual, 428 cond: "foo", 429 }, 430 }, 431 doc: map[string]interface{}{ 432 "foo": "bar", 433 }, 434 want: false, 435 }) 436 tests.Add("field selector, non-object", test{ 437 sel: &fieldNode{ 438 field: "foo", 439 cond: &conditionNode{ 440 op: OpEqual, 441 cond: "bar", 442 }, 443 }, 444 doc: "bar", 445 want: false, 446 }) 447 tests.Add("field selector, nested", test{ 448 sel: &fieldNode{ 449 field: "foo.bar.baz", 450 cond: &conditionNode{ 451 op: OpEqual, 452 cond: "hello", 453 }, 454 }, 455 doc: map[string]interface{}{ 456 "foo": map[string]interface{}{ 457 "bar": map[string]interface{}{ 458 "baz": "hello", 459 }, 460 }, 461 }, 462 want: true, 463 }) 464 tests.Add("field selector, nested, non-object", test{ 465 sel: &fieldNode{ 466 field: "foo.bar.baz", 467 cond: &conditionNode{ 468 op: OpEqual, 469 cond: "hello", 470 }, 471 }, 472 doc: map[string]interface{}{ 473 "foo": "hello", 474 }, 475 want: false, 476 }) 477 tests.Add("!field selector, nested", test{ 478 sel: &fieldNode{ 479 field: "foo.bar.baz", 480 cond: &conditionNode{ 481 op: OpEqual, 482 cond: "hello", 483 }, 484 }, 485 doc: map[string]interface{}{ 486 "foo": map[string]interface{}{ 487 "bar": map[string]interface{}{ 488 "buzz": "hello", 489 }, 490 }, 491 }, 492 want: false, 493 }) 494 tests.Add("elemMatch", test{ 495 sel: &fieldNode{ 496 field: "foo", 497 cond: &elementNode{ 498 op: OpElemMatch, 499 cond: &conditionNode{ 500 op: OpEqual, 501 cond: "Horror", 502 }, 503 }, 504 }, 505 doc: map[string]interface{}{ 506 "foo": []interface{}{ 507 "Comedy", 508 "Horror", 509 }, 510 }, 511 want: true, 512 }) 513 tests.Add("!elemMatch", test{ 514 sel: &fieldNode{ 515 field: "genre", 516 cond: &elementNode{ 517 op: OpElemMatch, 518 cond: &conditionNode{ 519 op: OpEqual, 520 cond: "Horror", 521 }, 522 }, 523 }, 524 doc: map[string]interface{}{ 525 "genre": []interface{}{ 526 "Comedy", 527 }, 528 }, 529 want: false, 530 }) 531 tests.Add("elemMatch, non-array", test{ 532 sel: &fieldNode{ 533 field: "genre", 534 cond: &elementNode{ 535 op: OpElemMatch, 536 cond: &conditionNode{ 537 op: OpEqual, 538 cond: "Horror", 539 }, 540 }, 541 }, 542 doc: map[string]interface{}{ 543 "genre": "Comedy", 544 }, 545 want: false, 546 }) 547 tests.Add("allMatch", test{ 548 sel: &fieldNode{ 549 field: "genre", 550 cond: &elementNode{ 551 op: OpAllMatch, 552 cond: &conditionNode{ 553 op: OpEqual, 554 cond: "Horror", 555 }, 556 }, 557 }, 558 doc: map[string]interface{}{ 559 "genre": []interface{}{ 560 "Horror", 561 "Horror", 562 }, 563 }, 564 want: true, 565 }) 566 tests.Add("!allMatch", test{ 567 sel: &fieldNode{ 568 field: "genre", 569 cond: &elementNode{ 570 op: OpAllMatch, 571 cond: &conditionNode{ 572 op: OpEqual, 573 cond: "Horror", 574 }, 575 }, 576 }, 577 doc: map[string]interface{}{ 578 "genre": []interface{}{ 579 "Horror", 580 "Comedy", 581 }, 582 }, 583 want: false, 584 }) 585 tests.Add("allMatch, non-array", test{ 586 sel: &fieldNode{ 587 field: "genre", 588 cond: &elementNode{ 589 op: OpAllMatch, 590 cond: &conditionNode{ 591 op: OpEqual, 592 cond: "Horror", 593 }, 594 }, 595 }, 596 doc: map[string]interface{}{ 597 "genre": "Horror", 598 }, 599 want: false, 600 }) 601 tests.Add("keyMapMatch", test{ 602 sel: &fieldNode{ 603 field: "cameras", 604 cond: &elementNode{ 605 op: OpKeyMapMatch, 606 cond: &conditionNode{ 607 op: OpEqual, 608 cond: "secondary", 609 }, 610 }, 611 }, 612 doc: map[string]interface{}{ 613 "cameras": map[string]interface{}{ 614 "primary": "Canon", 615 "secondary": "Nikon", 616 }, 617 }, 618 want: true, 619 }) 620 tests.Add("!keyMapMatch", test{ 621 sel: &fieldNode{ 622 field: "cameras", 623 cond: &elementNode{ 624 op: OpKeyMapMatch, 625 cond: &conditionNode{ 626 op: OpEqual, 627 cond: "secondary", 628 }, 629 }, 630 }, 631 doc: map[string]interface{}{ 632 "cameras": map[string]interface{}{ 633 "primary": "Canon", 634 }, 635 }, 636 want: false, 637 }) 638 tests.Add("keyMapMatch, non-object", test{ 639 sel: &fieldNode{ 640 field: "cameras", 641 cond: &elementNode{ 642 op: OpKeyMapMatch, 643 cond: &conditionNode{ 644 op: OpEqual, 645 cond: "secondary", 646 }, 647 }, 648 }, 649 doc: map[string]interface{}{ 650 "cameras": []interface{}{"Canon", "Nikon"}, 651 }, 652 want: false, 653 }) 654 tests.Add("and", test{ 655 sel: &combinationNode{ 656 op: OpAnd, 657 sel: []Node{ 658 &fieldNode{ 659 field: "foo", 660 cond: &conditionNode{ 661 op: OpEqual, 662 cond: "bar", 663 }, 664 }, 665 &fieldNode{ 666 field: "baz", 667 cond: &conditionNode{ 668 op: OpEqual, 669 cond: "qux", 670 }, 671 }, 672 }, 673 }, 674 doc: map[string]interface{}{ 675 "foo": "bar", 676 "baz": "qux", 677 }, 678 want: true, 679 }) 680 tests.Add("!and", test{ 681 sel: &combinationNode{ 682 op: OpAnd, 683 sel: []Node{ 684 &fieldNode{ 685 field: "foo", 686 cond: &conditionNode{ 687 op: OpEqual, 688 cond: "bar", 689 }, 690 }, 691 &fieldNode{ 692 field: "baz", 693 cond: &conditionNode{ 694 op: OpEqual, 695 cond: "qux", 696 }, 697 }, 698 }, 699 }, 700 doc: map[string]interface{}{ 701 "baz": "qux", 702 }, 703 want: false, 704 }) 705 tests.Add("or", test{ 706 sel: &combinationNode{ 707 op: OpOr, 708 sel: []Node{ 709 &fieldNode{ 710 field: "foo", 711 cond: &conditionNode{ 712 op: OpEqual, 713 cond: "bar", 714 }, 715 }, 716 &fieldNode{ 717 field: "baz", 718 cond: &conditionNode{ 719 op: OpEqual, 720 cond: "qux", 721 }, 722 }, 723 }, 724 }, 725 doc: map[string]interface{}{ 726 "foo": "bar", 727 "baz": "quux", 728 }, 729 want: true, 730 }) 731 tests.Add("!or", test{ 732 sel: &combinationNode{ 733 op: OpOr, 734 sel: []Node{ 735 &fieldNode{ 736 field: "foo", 737 cond: &conditionNode{ 738 op: OpEqual, 739 cond: "bar", 740 }, 741 }, 742 &fieldNode{ 743 field: "baz", 744 cond: &conditionNode{ 745 op: OpEqual, 746 cond: "qux", 747 }, 748 }, 749 }, 750 }, 751 doc: map[string]interface{}{ 752 "foo": "baz", 753 "baz": "quux", 754 }, 755 want: false, 756 }) 757 tests.Add("not", test{ 758 sel: ¬Node{ 759 sel: &fieldNode{ 760 field: "foo", 761 cond: &conditionNode{ 762 op: OpEqual, 763 cond: "bar", 764 }, 765 }, 766 }, 767 doc: map[string]interface{}{ 768 "foo": "baz", 769 }, 770 want: true, 771 }) 772 tests.Add("!not", test{ 773 sel: ¬Node{ 774 sel: &fieldNode{ 775 field: "foo", 776 cond: &conditionNode{ 777 op: OpEqual, 778 cond: "bar", 779 }, 780 }, 781 }, 782 doc: map[string]interface{}{ 783 "foo": "bar", 784 }, 785 want: false, 786 }) 787 tests.Add("nor", test{ 788 sel: &combinationNode{ 789 op: OpNor, 790 sel: []Node{ 791 &fieldNode{ 792 field: "foo", 793 cond: &conditionNode{ 794 op: OpEqual, 795 cond: "bar", 796 }, 797 }, 798 &fieldNode{ 799 field: "baz", 800 cond: &conditionNode{ 801 op: OpEqual, 802 cond: "qux", 803 }, 804 }, 805 }, 806 }, 807 doc: map[string]interface{}{ 808 "foo": "baz", 809 "baz": "quux", 810 }, 811 want: true, 812 }) 813 tests.Add("!nor", test{ 814 sel: &combinationNode{ 815 op: OpNor, 816 sel: []Node{ 817 &fieldNode{ 818 field: "foo", 819 cond: &conditionNode{ 820 op: OpEqual, 821 cond: "bar", 822 }, 823 }, 824 &fieldNode{ 825 field: "baz", 826 cond: &conditionNode{ 827 op: OpEqual, 828 cond: "qux", 829 }, 830 }, 831 }, 832 }, 833 doc: map[string]interface{}{ 834 "foo": "bar", 835 "baz": "quux", 836 }, 837 want: false, 838 }) 839 840 tests.Run(t, func(t *testing.T, tt test) { 841 got := Match(tt.sel, tt.doc) 842 if got != tt.want { 843 t.Errorf("Unexpected result: %v", got) 844 } 845 }) 846 }