...

Source file src/github.com/go-kivik/kivik/v4/x/mango/match_test.go

Documentation: github.com/go-kivik/kivik/v4/x/mango

     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: &notNode{
   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: &notNode{
   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  }
   847  

View as plain text