github.com/CiscoM31/godata@v1.0.10/filter_parser_test.go (about) 1 package godata 2 3 import ( 4 "context" 5 "strings" 6 "testing" 7 ) 8 9 func TestFilterDateTime(t *testing.T) { 10 ctx := context.Background() 11 tokenizer := NewExpressionTokenizer() 12 tokens := map[string]TokenType{ 13 "2011-08-29T21:58Z": ExpressionTokenDateTime, 14 "2011-08-29T21:58:33Z": ExpressionTokenDateTime, 15 "2011-08-29T21:58:33.123Z": ExpressionTokenDateTime, 16 "2011-08-29T21:58+11:23": ExpressionTokenDateTime, 17 "2011-08-29T21:58:33+11:23": ExpressionTokenDateTime, 18 "2011-08-29T21:58:33.123+11:23": ExpressionTokenDateTime, 19 "2011-08-29T21:58:33-11:23": ExpressionTokenDateTime, 20 "2011-08-29": ExpressionTokenDate, 21 "21:58:33": ExpressionTokenTime, 22 } 23 for tokenValue, tokenType := range tokens { 24 // Previously, the unit test had no space character after 'gt' 25 // E.g. 'CreateTime gt2011-08-29T21:58Z' was considered valid. 26 // However the ABNF notation for ODATA logical operators is: 27 // gtExpr = RWS "gt" RWS commonExpr 28 // RWS = 1*( SP / HTAB / "%20" / "%09" ) ; "required" whitespace 29 // 30 // See http://docs.oasis-open.org/odata/odata/v4.01/csprd03/abnf/odata-abnf-construction-rules.txt 31 input := "CreateTime gt " + tokenValue 32 expect := []*Token{ 33 {Value: "CreateTime", Type: ExpressionTokenLiteral}, 34 {Value: "gt", Type: ExpressionTokenLogical}, 35 {Value: tokenValue, Type: tokenType}, 36 } 37 output, err := tokenizer.Tokenize(ctx, input) 38 if err != nil { 39 t.Errorf("Failed to tokenize input %s. Error: %v", input, err) 40 } 41 42 result, err := CompareTokens(expect, output) 43 if !result { 44 var a []string 45 for _, t := range output { 46 a = append(a, t.Value) 47 } 48 t.Errorf("Unexpected tokens for input '%s'. Tokens: %s Error: %v", input, strings.Join(a, ", "), err) 49 } 50 } 51 } 52 53 func TestFilterAnyArrayOfObjects(t *testing.T) { 54 ctx := context.Background() 55 tokenizer := NewExpressionTokenizer() 56 input := "Tags/any(d:d/Key eq 'Site' and d/Value lt 10)" 57 expect := []*Token{ 58 {Value: "Tags", Type: ExpressionTokenLiteral}, 59 {Value: "/", Type: ExpressionTokenLambdaNav}, 60 {Value: "any", Type: ExpressionTokenLambda}, 61 {Value: "(", Type: ExpressionTokenOpenParen}, 62 {Value: "d", Type: ExpressionTokenLiteral}, 63 {Value: ",", Type: ExpressionTokenColon}, // ':' is replaced by ',' which is the function argument separator. 64 {Value: "d", Type: ExpressionTokenLiteral}, 65 {Value: "/", Type: ExpressionTokenNav}, 66 {Value: "Key", Type: ExpressionTokenLiteral}, 67 {Value: "eq", Type: ExpressionTokenLogical}, 68 {Value: "'Site'", Type: ExpressionTokenString}, 69 {Value: "and", Type: ExpressionTokenLogical}, 70 {Value: "d", Type: ExpressionTokenLiteral}, 71 {Value: "/", Type: ExpressionTokenNav}, 72 {Value: "Value", Type: ExpressionTokenLiteral}, 73 {Value: "lt", Type: ExpressionTokenLogical}, 74 {Value: "10", Type: ExpressionTokenInteger}, 75 {Value: ")", Type: ExpressionTokenCloseParen}, 76 } 77 output, err := tokenizer.Tokenize(ctx, input) 78 if err != nil { 79 t.Error(err) 80 } 81 82 result, err := CompareTokens(expect, output) 83 if !result { 84 t.Error(err) 85 } 86 } 87 88 func TestFilterAnyArrayOfPrimitiveTypes(t *testing.T) { 89 ctx := context.Background() 90 tokenizer := NewExpressionTokenizer() 91 input := "Tags/any(d:d eq 'Site')" 92 { 93 expect := []*Token{ 94 {Value: "Tags", Type: ExpressionTokenLiteral}, 95 {Value: "/", Type: ExpressionTokenLambdaNav}, 96 {Value: "any", Type: ExpressionTokenLambda}, 97 {Value: "(", Type: ExpressionTokenOpenParen}, 98 {Value: "d", Type: ExpressionTokenLiteral}, 99 {Value: ",", Type: ExpressionTokenColon}, 100 {Value: "d", Type: ExpressionTokenLiteral}, 101 {Value: "eq", Type: ExpressionTokenLogical}, 102 {Value: "'Site'", Type: ExpressionTokenString}, 103 {Value: ")", Type: ExpressionTokenCloseParen}, 104 } 105 output, err := tokenizer.Tokenize(ctx, input) 106 if err != nil { 107 t.Error(err) 108 } 109 110 result, err := CompareTokens(expect, output) 111 if !result { 112 t.Error(err) 113 } 114 } 115 q, err := ParseFilterString(ctx, input) 116 if err != nil { 117 t.Errorf("Error parsing query %s. Error: %v", input, err) 118 return 119 } 120 var expect []expectedParseNode = []expectedParseNode{ 121 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 122 {Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral}, 123 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 124 {Value: "d", Depth: 2, Type: ExpressionTokenLiteral}, 125 {Value: "eq", Depth: 2, Type: ExpressionTokenLogical}, 126 {Value: "d", Depth: 3, Type: ExpressionTokenLiteral}, 127 {Value: "'Site'", Depth: 3, Type: ExpressionTokenString}, 128 } 129 pos := 0 130 err = CompareTree(q.Tree, expect, &pos, 0) 131 if err != nil { 132 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, q.Tree) 133 } 134 } 135 136 // geographyPolygon = geographyPrefix SQUOTE fullPolygonLiteral SQUOTE 137 // geographyPrefix = "geography" 138 // fullPolygonLiteral = sridLiteral polygonLiteral 139 // sridLiteral = "SRID" EQ 1*5DIGIT SEMI 140 // polygonLiteral = "Polygon" polygonData 141 // polygonData = OPEN ringLiteral *( COMMA ringLiteral ) CLOSE 142 // positionLiteral = doubleValue SP doubleValue ; longitude, then latitude 143 /* 144 func TestFilterGeographyPolygon(t *testing.T) { 145 input := "geo.intersects(location, geography'SRID=0;Polygon(-122.031577 47.578581, -122.031577 47.678581, -122.131577 47.678581, -122.031577 47.578581)')" 146 q, err := ParseFilterString(input) 147 if err != nil { 148 t.Errorf("Error parsing query %s. Error: %s", input, err.Error()) 149 return 150 } 151 var expect []expectedParseNode = []expectedParseNode{ 152 {Value:"geo.intersects", Depth:0, Type: 0}, 153 {Value:"location", Depth:1, Type: 0}, 154 {Value:"geography'SRID=0;Polygon(-122.031577 47.578581, -122.031577 47.678581, -122.131577 47.678581, -122.031577 47.578581)'", Depth:1, Type: 0}, 155 } 156 pos := 0 157 err = CompareTree(q.Tree, expect, &pos, 0) 158 if err != nil { 159 fmt.Printf("Got tree:\n%v\n", q.Tree.String()) 160 t.Errorf("Tree representation does not match expected value. error: %s", err.Error()) 161 } 162 } 163 */ 164 165 // TestFilterAnyGeography matches documents where any of the geo coordinates in the locations field is within the given polygon. 166 /* 167 func TestFilterAnyGeography(t *testing.T) { 168 input := "locations/any(loc: geo.intersects(loc, geography'Polygon((-122.031577 47.578581, -122.031577 47.678581, -122.131577 47.678581, -122.031577 47.578581))'))" 169 q, err := ParseFilterString(input) 170 if err != nil { 171 t.Errorf("Error parsing query %s. Error: %s", input, err.Error()) 172 return 173 } 174 var expect []expectedParseNode = []expectedParseNode{ 175 {Value:"/", Depth:0, Type: 0}, 176 {Value:"Tags", Depth:1, Type: 0}, 177 {Value:"any", Depth:1, Type: 0}, 178 {Value:"d", Depth:2, Type: 0}, 179 {Value:"or", Depth:2, Type: 0}, 180 {Value:"or", Depth:3, Type: 0}, 181 {Value:"or", Depth:4, Type: 0}, 182 {Value:"eq", Depth:5, Type: 0}, 183 {Value:"d", Depth:6, Type: 0}, 184 {Value:"'Site'", Depth:6, Type: 0}, 185 {Value:"eq", Depth:5, Type: 0}, 186 {Value:"'Environment'", Depth:6, Type: 0}, 187 {Value:"/", Depth:6, Type: 0}, 188 {Value:"d", Depth:7, Type: 0}, 189 {Value:"Key", Depth:7, Type: 0}, 190 {Value:"eq", Depth:4, Type: 0}, 191 {Value:"/", Depth:5, Type: 0}, 192 {Value:"/", Depth:6, Type: 0}, 193 {Value:"d", Depth:7, Type: 0}, 194 {Value:"d", Depth:7, Type: 0}, 195 {Value:"d", Depth:6, Type: 0}, 196 {Value:"123456", Depth:5, Type: 0}, 197 {Value:"eq", Depth:3, Type: 0}, 198 {Value:"concat", Depth:4, Type: 0}, 199 {Value:"/", Depth:5, Type: 0}, 200 {Value:"d", Depth:6, Type: 0}, 201 {Value:"FirstName", Depth:6, Type: 0}, 202 {Value:"/", Depth:5, Type: 0}, 203 {Value:"d", Depth:6, Type: 0}, 204 {Value:"LastName", Depth:6, Type: 0}, 205 {Value:"/", Depth:4, Type: 0}, 206 {Value:"$it", Depth:5, Type: 0}, 207 {Value:"FullName", Depth:5, Type: 0}, 208 } 209 pos := 0 210 err = CompareTree(q.Tree, expect, &pos, 0) 211 if err != nil { 212 fmt.Printf("Got tree:\n%v\n", q.Tree.String()) 213 t.Errorf("Tree representation does not match expected value. error: %s", err.Error()) 214 } 215 } 216 */ 217 218 func TestFilterAnyMixedQuery(t *testing.T) { 219 /* 220 { 221 "Tags": [ 222 "Site", 223 { "Key": "Environment" }, 224 { "d" : { "d": 123456 }}, 225 { "FirstName" : "Bob", "LastName": "Smith"} 226 ], 227 "FullName": "BobSmith" 228 } 229 */ 230 // The argument of a lambda operator is a case-sensitive lambda variable name followed by a colon (:) and a Boolean expression that 231 // uses the lambda variable name to refer to properties of members of the collection identified by the navigation path. 232 // If the name chosen for the lambda variable matches a property name of the current resource referenced by the resource path, the lambda variable takes precedence. 233 // Clients can prefix properties of the current resource referenced by the resource path with $it. 234 // Other path expressions in the Boolean expression neither prefixed with the lambda variable nor $it are evaluated in the scope of 235 // the collection instances at the origin of the navigation path prepended to the lambda operator. 236 input := "Tags/any(d:d eq 'Site' or 'Environment' eq d/Key or d/d/d eq 123456 or concat(d/FirstName, d/LastName) eq $it/FullName)" 237 ctx := context.Background() 238 q, err := ParseFilterString(ctx, input) 239 if err != nil { 240 t.Errorf("Error parsing query %s. Error: %v", input, err) 241 return 242 } 243 var expect []expectedParseNode = []expectedParseNode{ 244 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 245 {Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral}, 246 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 247 {Value: "d", Depth: 2, Type: ExpressionTokenLiteral}, 248 {Value: "or", Depth: 2, Type: ExpressionTokenLogical}, 249 {Value: "or", Depth: 3, Type: ExpressionTokenLogical}, 250 {Value: "or", Depth: 4, Type: ExpressionTokenLogical}, 251 {Value: "eq", Depth: 5, Type: ExpressionTokenLogical}, 252 {Value: "d", Depth: 6, Type: ExpressionTokenLiteral}, 253 {Value: "'Site'", Depth: 6, Type: ExpressionTokenString}, 254 {Value: "eq", Depth: 5, Type: ExpressionTokenLogical}, 255 {Value: "'Environment'", Depth: 6, Type: ExpressionTokenString}, 256 {Value: "/", Depth: 6, Type: ExpressionTokenNav}, 257 {Value: "d", Depth: 7, Type: ExpressionTokenLiteral}, 258 {Value: "Key", Depth: 7, Type: ExpressionTokenLiteral}, 259 {Value: "eq", Depth: 4, Type: ExpressionTokenLogical}, 260 {Value: "/", Depth: 5, Type: ExpressionTokenNav}, 261 {Value: "/", Depth: 6, Type: ExpressionTokenNav}, 262 {Value: "d", Depth: 7, Type: ExpressionTokenLiteral}, 263 {Value: "d", Depth: 7, Type: ExpressionTokenLiteral}, 264 {Value: "d", Depth: 6, Type: ExpressionTokenLiteral}, 265 {Value: "123456", Depth: 5, Type: ExpressionTokenInteger}, 266 {Value: "eq", Depth: 3, Type: ExpressionTokenLogical}, 267 {Value: "concat", Depth: 4, Type: ExpressionTokenFunc}, 268 {Value: "/", Depth: 5, Type: ExpressionTokenNav}, 269 {Value: "d", Depth: 6, Type: ExpressionTokenLiteral}, 270 {Value: "FirstName", Depth: 6, Type: ExpressionTokenLiteral}, 271 {Value: "/", Depth: 5, Type: ExpressionTokenNav}, 272 {Value: "d", Depth: 6, Type: ExpressionTokenLiteral}, 273 {Value: "LastName", Depth: 6, Type: ExpressionTokenLiteral}, 274 {Value: "/", Depth: 4, Type: ExpressionTokenNav}, 275 {Value: "$it", Depth: 5, Type: ExpressionTokenIt}, 276 {Value: "FullName", Depth: 5, Type: ExpressionTokenLiteral}, 277 } 278 pos := 0 279 err = CompareTree(q.Tree, expect, &pos, 0) 280 if err != nil { 281 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, q.Tree) 282 } 283 } 284 285 func TestFilterGuid(t *testing.T) { 286 ctx := context.Background() 287 tokenizer := NewExpressionTokenizer() 288 input := "GuidValue eq 01234567-89ab-cdef-0123-456789abcdef" 289 290 expect := []*Token{ 291 {Value: "GuidValue", Type: ExpressionTokenLiteral}, 292 {Value: "eq", Type: ExpressionTokenLogical}, 293 {Value: "01234567-89ab-cdef-0123-456789abcdef", Type: ExpressionTokenGuid}, 294 } 295 output, err := tokenizer.Tokenize(ctx, input) 296 if err != nil { 297 t.Error(err) 298 } 299 result, err := CompareTokens(expect, output) 300 if !result { 301 t.Error(err) 302 } 303 } 304 305 func TestFilterDurationWithType(t *testing.T) { 306 ctx := context.Background() 307 tokenizer := NewExpressionTokenizer() 308 input := "Task eq duration'P12DT23H59M59.999999999999S'" 309 310 expect := []*Token{ 311 {Value: "Task", Type: ExpressionTokenLiteral}, 312 {Value: "eq", Type: ExpressionTokenLogical}, 313 // Note the duration token is extracted. 314 {Value: "P12DT23H59M59.999999999999S", Type: ExpressionTokenDuration}, 315 } 316 output, err := tokenizer.Tokenize(ctx, input) 317 if err != nil { 318 t.Error(err) 319 } 320 result, err := CompareTokens(expect, output) 321 if !result { 322 printTokens(output) 323 t.Error(err) 324 } 325 } 326 327 func TestFilterDurationWithoutType(t *testing.T) { 328 ctx := context.Background() 329 tokenizer := NewExpressionTokenizer() 330 input := "Task eq 'P12DT23H59M59.999999999999S'" 331 332 expect := []*Token{ 333 {Value: "Task", Type: ExpressionTokenLiteral}, 334 {Value: "eq", Type: ExpressionTokenLogical}, 335 {Value: "P12DT23H59M59.999999999999S", Type: ExpressionTokenDuration}, 336 } 337 output, err := tokenizer.Tokenize(ctx, input) 338 if err != nil { 339 t.Error(err) 340 } 341 result, err := CompareTokens(expect, output) 342 if !result { 343 printTokens(output) 344 t.Error(err) 345 } 346 } 347 348 func TestFilterAnyWithNoArgs(t *testing.T) { 349 ctx := context.Background() 350 tokenizer := NewExpressionTokenizer() 351 input := "Tags/any()" 352 { 353 expect := []*Token{ 354 {Value: "Tags", Type: ExpressionTokenLiteral}, 355 {Value: "/", Type: ExpressionTokenLambdaNav}, 356 {Value: "any", Type: ExpressionTokenLambda}, 357 {Value: "(", Type: ExpressionTokenOpenParen}, 358 {Value: ")", Type: ExpressionTokenCloseParen}, 359 } 360 output, err := tokenizer.Tokenize(ctx, input) 361 if err != nil { 362 t.Error(err) 363 } 364 365 result, err := CompareTokens(expect, output) 366 if !result { 367 t.Error(err) 368 } 369 } 370 q, err := ParseFilterString(ctx, input) 371 if err != nil { 372 t.Errorf("Error parsing query %s. Error: %v", input, err) 373 return 374 } 375 var expect []expectedParseNode = []expectedParseNode{ 376 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 377 {Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral}, 378 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 379 } 380 pos := 0 381 err = CompareTree(q.Tree, expect, &pos, 0) 382 if err != nil { 383 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, q.Tree) 384 } 385 } 386 func TestFilterDivby(t *testing.T) { 387 ctx := context.Background() 388 { 389 tokenizer := NewExpressionTokenizer() 390 input := "Price div 2 gt 3.5" 391 expect := []*Token{ 392 {Value: "Price", Type: ExpressionTokenLiteral}, 393 {Value: "div", Type: ExpressionTokenOp}, 394 {Value: "2", Type: ExpressionTokenInteger}, 395 {Value: "gt", Type: ExpressionTokenLogical}, 396 {Value: "3.5", Type: ExpressionTokenFloat}, 397 } 398 output, err := tokenizer.Tokenize(ctx, input) 399 if err != nil { 400 t.Error(err) 401 } 402 403 result, err := CompareTokens(expect, output) 404 if !result { 405 t.Error(err) 406 } 407 } 408 { 409 tokenizer := NewExpressionTokenizer() 410 input := "Price divby 2 gt 3.5" 411 expect := []*Token{ 412 {Value: "Price", Type: ExpressionTokenLiteral}, 413 {Value: "divby", Type: ExpressionTokenOp}, 414 {Value: "2", Type: ExpressionTokenInteger}, 415 {Value: "gt", Type: ExpressionTokenLogical}, 416 {Value: "3.5", Type: ExpressionTokenFloat}, 417 } 418 output, err := tokenizer.Tokenize(ctx, input) 419 if err != nil { 420 t.Error(err) 421 } 422 423 result, err := CompareTokens(expect, output) 424 if !result { 425 t.Error(err) 426 } 427 } 428 } 429 430 func TestFilterNotBooleanProperty(t *testing.T) { 431 ctx := context.Background() 432 tokenizer := NewExpressionTokenizer() 433 input := "not Enabled" 434 { 435 expect := []*Token{ 436 {Value: "not", Type: ExpressionTokenLogical}, 437 {Value: "Enabled", Type: ExpressionTokenLiteral}, 438 } 439 output, err := tokenizer.Tokenize(ctx, input) 440 if err != nil { 441 t.Error(err) 442 } 443 result, err := CompareTokens(expect, output) 444 if !result { 445 t.Error(err) 446 } 447 } 448 q, err := ParseFilterString(ctx, input) 449 if err != nil { 450 t.Errorf("Error parsing query %s. Error: %v", input, err) 451 return 452 } 453 var expect []expectedParseNode = []expectedParseNode{ 454 {Value: "not", Depth: 0, Type: ExpressionTokenLogical}, 455 {Value: "Enabled", Depth: 1, Type: ExpressionTokenLiteral}, 456 } 457 pos := 0 458 err = CompareTree(q.Tree, expect, &pos, 0) 459 if err != nil { 460 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, q.Tree) 461 } 462 463 } 464 465 func TestFilterEmptyStringToken(t *testing.T) { 466 ctx := context.Background() 467 tokenizer := NewExpressionTokenizer() 468 input := "City eq ''" 469 expect := []*Token{ 470 {Value: "City", Type: ExpressionTokenLiteral}, 471 {Value: "eq", Type: ExpressionTokenLogical}, 472 {Value: "''", Type: ExpressionTokenString}, 473 } 474 output, err := tokenizer.Tokenize(ctx, input) 475 if err != nil { 476 t.Error(err) 477 } 478 result, err := CompareTokens(expect, output) 479 if !result { 480 t.Error(err) 481 } 482 } 483 484 // Note: according to ODATA ABNF notation, there must be a space between not and open parenthesis. 485 // http://docs.oasis-open.org/odata/odata/v4.01/csprd03/abnf/odata-abnf-construction-rules.txt 486 func TestFilterNotWithNoSpace(t *testing.T) { 487 ctx := context.Background() 488 tokenizer := NewExpressionTokenizer() 489 input := "not(City eq 'Seattle')" 490 { 491 expect := []*Token{ 492 {Value: "not", Type: ExpressionTokenLogical}, 493 {Value: "(", Type: ExpressionTokenOpenParen}, 494 {Value: "City", Type: ExpressionTokenLiteral}, 495 {Value: "eq", Type: ExpressionTokenLogical}, 496 {Value: "'Seattle'", Type: ExpressionTokenString}, 497 {Value: ")", Type: ExpressionTokenCloseParen}, 498 } 499 output, err := tokenizer.Tokenize(ctx, input) 500 if err != nil { 501 t.Error(err) 502 } 503 result, err := CompareTokens(expect, output) 504 if !result { 505 t.Error(err) 506 } 507 } 508 509 q, err := ParseFilterString(ctx, input) 510 if err != nil { 511 t.Errorf("Error parsing query %s. Error: %v", input, err) 512 return 513 } 514 var expect []expectedParseNode = []expectedParseNode{ 515 {Value: "not", Depth: 0, Type: ExpressionTokenLogical}, 516 {Value: "eq", Depth: 1, Type: ExpressionTokenLogical}, 517 {Value: "City", Depth: 2, Type: ExpressionTokenLiteral}, 518 {Value: "'Seattle'", Depth: 2, Type: ExpressionTokenString}, 519 } 520 pos := 0 521 err = CompareTree(q.Tree, expect, &pos, 0) 522 if err != nil { 523 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, q.Tree) 524 } 525 } 526 527 // TestFilterInOperator tests the "IN" operator with a comma-separated list of values. 528 func TestFilterInOperator(t *testing.T) { 529 ctx := context.Background() 530 tokenizer := NewExpressionTokenizer() 531 input := "City in ( 'Seattle', 'Atlanta', 'Paris' )" 532 533 expect := []*Token{ 534 {Value: "City", Type: ExpressionTokenLiteral}, 535 {Value: "in", Type: ExpressionTokenLogical}, 536 {Value: "(", Type: ExpressionTokenOpenParen}, 537 {Value: "'Seattle'", Type: ExpressionTokenString}, 538 {Value: ",", Type: ExpressionTokenComma}, 539 {Value: "'Atlanta'", Type: ExpressionTokenString}, 540 {Value: ",", Type: ExpressionTokenComma}, 541 {Value: "'Paris'", Type: ExpressionTokenString}, 542 {Value: ")", Type: ExpressionTokenCloseParen}, 543 } 544 tokens, err := tokenizer.Tokenize(ctx, input) 545 if err != nil { 546 t.Error(err) 547 } 548 result, err := CompareTokens(expect, tokens) 549 if !result { 550 t.Error(err) 551 } 552 var postfix *tokenQueue 553 postfix, err = GlobalFilterParser.InfixToPostfix(ctx, tokens) 554 if err != nil { 555 t.Error(err) 556 } 557 expect = []*Token{ 558 {Value: "City", Type: ExpressionTokenLiteral}, 559 {Value: "'Seattle'", Type: ExpressionTokenString}, 560 {Value: "'Atlanta'", Type: ExpressionTokenString}, 561 {Value: "'Paris'", Type: ExpressionTokenString}, 562 {Value: "3", Type: TokenTypeArgCount}, 563 {Value: TokenListExpr, Type: TokenTypeListExpr}, 564 {Value: "in", Type: ExpressionTokenLogical}, 565 } 566 if err := CompareQueue(expect, postfix); err != nil { 567 t.Error(err) 568 } 569 570 tree, err := GlobalFilterParser.PostfixToTree(ctx, postfix) 571 if err != nil { 572 t.Error(err) 573 } 574 575 var treeExpect []expectedParseNode = []expectedParseNode{ 576 {Value: "in", Depth: 0, Type: ExpressionTokenLogical}, 577 {Value: "City", Depth: 1, Type: ExpressionTokenLiteral}, 578 {Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr}, 579 {Value: "'Seattle'", Depth: 2, Type: ExpressionTokenString}, 580 {Value: "'Atlanta'", Depth: 2, Type: ExpressionTokenString}, 581 {Value: "'Paris'", Depth: 2, Type: ExpressionTokenString}, 582 } 583 pos := 0 584 err = CompareTree(tree, treeExpect, &pos, 0) 585 if err != nil { 586 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 587 } 588 } 589 590 // TestFilterInOperatorSingleValue tests the "IN" operator with a list containing a single value. 591 func TestFilterInOperatorSingleValue(t *testing.T) { 592 ctx := context.Background() 593 tokenizer := NewExpressionTokenizer() 594 input := "City in ( 'Seattle' )" 595 596 expect := []*Token{ 597 {Value: "City", Type: ExpressionTokenLiteral}, 598 {Value: "in", Type: ExpressionTokenLogical}, 599 {Value: "(", Type: ExpressionTokenOpenParen}, 600 {Value: "'Seattle'", Type: ExpressionTokenString}, 601 {Value: ")", Type: ExpressionTokenCloseParen}, 602 } 603 tokens, err := tokenizer.Tokenize(ctx, input) 604 if err != nil { 605 t.Error(err) 606 } 607 result, err := CompareTokens(expect, tokens) 608 if !result { 609 t.Error(err) 610 } 611 var postfix *tokenQueue 612 postfix, err = GlobalFilterParser.InfixToPostfix(ctx, tokens) 613 if err != nil { 614 t.Error(err) 615 } 616 expect = []*Token{ 617 {Value: "City", Type: ExpressionTokenLiteral}, 618 {Value: "'Seattle'", Type: ExpressionTokenString}, 619 {Value: "1", Type: TokenTypeArgCount}, 620 {Value: TokenListExpr, Type: TokenTypeListExpr}, 621 {Value: "in", Type: ExpressionTokenLogical}, 622 } 623 if err := CompareQueue(expect, postfix); err != nil { 624 t.Error(err) 625 } 626 627 tree, err := GlobalFilterParser.PostfixToTree(ctx, postfix) 628 if err != nil { 629 t.Error(err) 630 } 631 632 var treeExpect []expectedParseNode = []expectedParseNode{ 633 {Value: "in", Depth: 0, Type: ExpressionTokenLogical}, 634 {Value: "City", Depth: 1, Type: ExpressionTokenLiteral}, 635 {Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr}, 636 {Value: "'Seattle'", Depth: 2, Type: ExpressionTokenString}, 637 } 638 pos := 0 639 err = CompareTree(tree, treeExpect, &pos, 0) 640 if err != nil { 641 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 642 } 643 } 644 645 // TestFilterInOperatorEmptyList tests the "IN" operator with a list containing no value. 646 func TestFilterInOperatorEmptyList(t *testing.T) { 647 ctx := context.Background() 648 tokenizer := NewExpressionTokenizer() 649 input := "City in ( )" 650 651 expect := []*Token{ 652 {Value: "City", Type: ExpressionTokenLiteral}, 653 {Value: "in", Type: ExpressionTokenLogical}, 654 {Value: "(", Type: ExpressionTokenOpenParen}, 655 {Value: ")", Type: ExpressionTokenCloseParen}, 656 } 657 tokens, err := tokenizer.Tokenize(ctx, input) 658 if err != nil { 659 t.Fatal(err) 660 } 661 result, err := CompareTokens(expect, tokens) 662 if !result { 663 t.Fatal(err) 664 } 665 var postfix *tokenQueue 666 postfix, err = GlobalFilterParser.InfixToPostfix(ctx, tokens) 667 if err != nil { 668 t.Fatal(err) 669 } 670 expect = []*Token{ 671 {Value: "City", Type: ExpressionTokenLiteral}, 672 {Value: "0", Type: TokenTypeArgCount}, 673 {Value: TokenListExpr, Type: TokenTypeListExpr}, 674 {Value: "in", Type: ExpressionTokenLogical}, 675 } 676 if err := CompareQueue(expect, postfix); err != nil { 677 t.Fatal(err) 678 } 679 680 tree, err := GlobalFilterParser.PostfixToTree(ctx, postfix) 681 if err != nil { 682 t.Fatal(err) 683 } 684 685 var treeExpect []expectedParseNode = []expectedParseNode{ 686 {Value: "in", Depth: 0, Type: ExpressionTokenLogical}, 687 {Value: "City", Depth: 1, Type: ExpressionTokenLiteral}, 688 {Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr}, 689 } 690 pos := 0 691 err = CompareTree(tree, treeExpect, &pos, 0) 692 if err != nil { 693 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 694 } 695 } 696 697 // TestFilterInOperatorBothSides tests the "IN" operator. 698 // Use a listExpr on both sides of the IN operator. 699 // listExpr = OPEN BWS commonExpr BWS *( COMMA BWS commonExpr BWS ) CLOSE 700 // Validate if a list is within another list. 701 func TestFilterInOperatorBothSides(t *testing.T) { 702 ctx := context.Background() 703 tokenizer := NewExpressionTokenizer() 704 input := "(1, 2) in ( ('ab', 'cd'), (1, 2), ('abc', 'def') )" 705 706 expect := []*Token{ 707 {Value: "(", Type: ExpressionTokenOpenParen}, 708 {Value: "1", Type: ExpressionTokenInteger}, 709 {Value: ",", Type: ExpressionTokenComma}, 710 {Value: "2", Type: ExpressionTokenInteger}, 711 {Value: ")", Type: ExpressionTokenCloseParen}, 712 {Value: "in", Type: ExpressionTokenLogical}, 713 {Value: "(", Type: ExpressionTokenOpenParen}, 714 715 {Value: "(", Type: ExpressionTokenOpenParen}, 716 {Value: "'ab'", Type: ExpressionTokenString}, 717 {Value: ",", Type: ExpressionTokenComma}, 718 {Value: "'cd'", Type: ExpressionTokenString}, 719 {Value: ")", Type: ExpressionTokenCloseParen}, 720 {Value: ",", Type: ExpressionTokenComma}, 721 722 {Value: "(", Type: ExpressionTokenOpenParen}, 723 {Value: "1", Type: ExpressionTokenInteger}, 724 {Value: ",", Type: ExpressionTokenComma}, 725 {Value: "2", Type: ExpressionTokenInteger}, 726 {Value: ")", Type: ExpressionTokenCloseParen}, 727 {Value: ",", Type: ExpressionTokenComma}, 728 729 {Value: "(", Type: ExpressionTokenOpenParen}, 730 {Value: "'abc'", Type: ExpressionTokenString}, 731 {Value: ",", Type: ExpressionTokenComma}, 732 {Value: "'def'", Type: ExpressionTokenString}, 733 {Value: ")", Type: ExpressionTokenCloseParen}, 734 {Value: ")", Type: ExpressionTokenCloseParen}, 735 } 736 tokens, err := tokenizer.Tokenize(ctx, input) 737 if err != nil { 738 t.Fatal(err) 739 } 740 result, err := CompareTokens(expect, tokens) 741 if !result { 742 t.Fatal(err) 743 } 744 var postfix *tokenQueue 745 postfix, err = GlobalFilterParser.InfixToPostfix(ctx, tokens) 746 if err != nil { 747 t.Fatalf("failed to convert from infix to postfix: %v", err) 748 } 749 expect = []*Token{ 750 {Value: "1", Type: ExpressionTokenInteger}, 751 {Value: "2", Type: ExpressionTokenInteger}, 752 {Value: "2", Type: TokenTypeArgCount}, 753 {Value: TokenListExpr, Type: TokenTypeListExpr}, 754 755 {Value: "'ab'", Type: ExpressionTokenString}, 756 {Value: "'cd'", Type: ExpressionTokenString}, // idx 5 757 {Value: "2", Type: TokenTypeArgCount}, 758 {Value: TokenListExpr, Type: TokenTypeListExpr}, 759 760 {Value: "1", Type: ExpressionTokenInteger}, 761 {Value: "2", Type: ExpressionTokenInteger}, 762 {Value: "2", Type: TokenTypeArgCount}, // idx 10 763 {Value: TokenListExpr, Type: TokenTypeListExpr}, 764 765 {Value: "'abc'", Type: ExpressionTokenString}, 766 {Value: "'def'", Type: ExpressionTokenString}, 767 {Value: "2", Type: TokenTypeArgCount}, 768 {Value: TokenListExpr, Type: TokenTypeListExpr}, // idx 15 769 770 {Value: "3", Type: TokenTypeArgCount}, 771 {Value: TokenListExpr, Type: TokenTypeListExpr}, 772 773 {Value: "in", Type: ExpressionTokenLogical}, 774 } 775 if err := CompareQueue(expect, postfix); err != nil { 776 t.Fatalf("Unexpected postfix notation: %v. Error: %v", postfix, err) 777 } 778 779 tree, err := GlobalFilterParser.PostfixToTree(ctx, postfix) 780 if err != nil { 781 t.Fatalf("Failed to convert postfix to tree: %v", err) 782 } 783 784 var treeExpect []expectedParseNode = []expectedParseNode{ 785 {Value: "in", Depth: 0, Type: ExpressionTokenLogical}, 786 {Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr}, 787 {Value: "1", Depth: 2, Type: ExpressionTokenInteger}, 788 {Value: "2", Depth: 2, Type: ExpressionTokenInteger}, 789 // ('ab', 'cd'), (1, 2), ('abc', 'def') 790 {Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr}, 791 {Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr}, 792 {Value: "'ab'", Depth: 3, Type: ExpressionTokenString}, 793 {Value: "'cd'", Depth: 3, Type: ExpressionTokenString}, 794 {Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr}, 795 {Value: "1", Depth: 3, Type: ExpressionTokenInteger}, 796 {Value: "2", Depth: 3, Type: ExpressionTokenInteger}, 797 {Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr}, 798 {Value: "'abc'", Depth: 3, Type: ExpressionTokenString}, 799 {Value: "'def'", Depth: 3, Type: ExpressionTokenString}, 800 } 801 pos := 0 802 err = CompareTree(tree, treeExpect, &pos, 0) 803 if err != nil { 804 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 805 } 806 } 807 808 // TestFilterInOperatorWithFunc tests the "IN" operator with a comma-separated list 809 // of values, one of which is a function call which itself has a comma-separated list of values. 810 func TestFilterInOperatorWithFunc(t *testing.T) { 811 ctx := context.Background() 812 tokenizer := NewExpressionTokenizer() 813 // 'Atlanta' is enclosed in a unecessary parenExpr to validate the expression is properly unwrapped. 814 input := "City in ( 'Seattle', concat('San', 'Francisco'), ('Atlanta') )" 815 816 { 817 expect := []*Token{ 818 {Value: "City", Type: ExpressionTokenLiteral}, 819 {Value: "in", Type: ExpressionTokenLogical}, 820 {Value: "(", Type: ExpressionTokenOpenParen}, 821 {Value: "'Seattle'", Type: ExpressionTokenString}, 822 {Value: ",", Type: ExpressionTokenComma}, 823 {Value: "concat", Type: ExpressionTokenFunc}, 824 {Value: "(", Type: ExpressionTokenOpenParen}, 825 {Value: "'San'", Type: ExpressionTokenString}, 826 {Value: ",", Type: ExpressionTokenComma}, 827 {Value: "'Francisco'", Type: ExpressionTokenString}, 828 {Value: ")", Type: ExpressionTokenCloseParen}, 829 {Value: ",", Type: ExpressionTokenComma}, 830 {Value: "(", Type: ExpressionTokenOpenParen}, 831 {Value: "'Atlanta'", Type: ExpressionTokenString}, 832 {Value: ")", Type: ExpressionTokenCloseParen}, 833 {Value: ")", Type: ExpressionTokenCloseParen}, 834 } 835 output, err := tokenizer.Tokenize(ctx, input) 836 if err != nil { 837 t.Error(err) 838 } 839 result, err := CompareTokens(expect, output) 840 if !result { 841 t.Error(err) 842 } 843 } 844 q, err := ParseFilterString(ctx, input) 845 if err != nil { 846 t.Fatalf("Error parsing filter: %v", err) 847 } 848 var expect []expectedParseNode = []expectedParseNode{ 849 {Value: "in", Depth: 0, Type: ExpressionTokenLogical}, 850 {Value: "City", Depth: 1, Type: ExpressionTokenLiteral}, 851 {Value: TokenListExpr, Depth: 1, Type: TokenTypeListExpr}, 852 {Value: "'Seattle'", Depth: 2, Type: ExpressionTokenString}, 853 {Value: "concat", Depth: 2, Type: ExpressionTokenFunc}, 854 {Value: "'San'", Depth: 3, Type: ExpressionTokenString}, 855 {Value: "'Francisco'", Depth: 3, Type: ExpressionTokenString}, 856 {Value: "'Atlanta'", Depth: 2, Type: ExpressionTokenString}, 857 } 858 pos := 0 859 err = CompareTree(q.Tree, expect, &pos, 0) 860 if err != nil { 861 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, q.Tree) 862 } 863 } 864 865 func TestFilterNotInListExpr(t *testing.T) { 866 ctx := context.Background() 867 tokenizer := NewExpressionTokenizer() 868 input := "not ( City in ( 'Seattle', 'Atlanta' ) )" 869 870 { 871 expect := []*Token{ 872 {Value: "not", Type: ExpressionTokenLogical}, 873 {Value: "(", Type: ExpressionTokenOpenParen}, 874 {Value: "City", Type: ExpressionTokenLiteral}, 875 {Value: "in", Type: ExpressionTokenLogical}, 876 {Value: "(", Type: ExpressionTokenOpenParen}, 877 {Value: "'Seattle'", Type: ExpressionTokenString}, 878 {Value: ",", Type: ExpressionTokenComma}, 879 {Value: "'Atlanta'", Type: ExpressionTokenString}, 880 {Value: ")", Type: ExpressionTokenCloseParen}, 881 {Value: ")", Type: ExpressionTokenCloseParen}, 882 } 883 output, err := tokenizer.Tokenize(ctx, input) 884 if err != nil { 885 t.Error(err) 886 } 887 result, err := CompareTokens(expect, output) 888 if !result { 889 t.Error(err) 890 } 891 } 892 { 893 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 894 if err != nil { 895 t.Error(err) 896 return 897 } 898 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 899 if err != nil { 900 t.Error(err) 901 return 902 } 903 904 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 905 if err != nil { 906 t.Error(err) 907 return 908 } 909 var expect []expectedParseNode = []expectedParseNode{ 910 {Value: "not", Depth: 0, Type: ExpressionTokenLogical}, 911 {Value: "in", Depth: 1, Type: ExpressionTokenLogical}, 912 {Value: "City", Depth: 2, Type: ExpressionTokenLiteral}, 913 {Value: TokenListExpr, Depth: 2, Type: TokenTypeListExpr}, 914 {Value: "'Seattle'", Depth: 3, Type: ExpressionTokenString}, 915 {Value: "'Atlanta'", Depth: 3, Type: ExpressionTokenString}, 916 } 917 pos := 0 918 err = CompareTree(tree, expect, &pos, 0) 919 if err != nil { 920 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 921 } 922 923 } 924 } 925 926 func TestFilterAll(t *testing.T) { 927 ctx := context.Background() 928 tokenizer := NewExpressionTokenizer() 929 input := "Tags/all(d:d/Key eq 'Site')" 930 expect := []*Token{ 931 {Value: "Tags", Type: ExpressionTokenLiteral}, 932 {Value: "/", Type: ExpressionTokenLambdaNav}, 933 {Value: "all", Type: ExpressionTokenLambda}, 934 {Value: "(", Type: ExpressionTokenOpenParen}, 935 {Value: "d", Type: ExpressionTokenLiteral}, 936 {Value: ",", Type: ExpressionTokenColon}, 937 {Value: "d", Type: ExpressionTokenLiteral}, 938 {Value: "/", Type: ExpressionTokenNav}, 939 {Value: "Key", Type: ExpressionTokenLiteral}, 940 {Value: "eq", Type: ExpressionTokenLogical}, 941 {Value: "'Site'", Type: ExpressionTokenString}, 942 {Value: ")", Type: ExpressionTokenCloseParen}, 943 } 944 output, err := tokenizer.Tokenize(ctx, input) 945 if err != nil { 946 t.Error(err) 947 } 948 949 result, err := CompareTokens(expect, output) 950 if !result { 951 t.Error(err) 952 } 953 } 954 955 func TestExpressionTokenizer(t *testing.T) { 956 ctx := context.Background() 957 tokenizer := NewExpressionTokenizer() 958 input := "Name eq 'Milk' and Price lt 2.55" 959 expect := []*Token{ 960 {Value: "Name", Type: ExpressionTokenLiteral}, 961 {Value: "eq", Type: ExpressionTokenLogical}, 962 {Value: "'Milk'", Type: ExpressionTokenString}, 963 {Value: "and", Type: ExpressionTokenLogical}, 964 {Value: "Price", Type: ExpressionTokenLiteral}, 965 {Value: "lt", Type: ExpressionTokenLogical}, 966 {Value: "2.55", Type: ExpressionTokenFloat}, 967 } 968 output, err := tokenizer.Tokenize(ctx, input) 969 if err != nil { 970 t.Error(err) 971 } 972 973 result, err := CompareTokens(expect, output) 974 if !result { 975 t.Error(err) 976 } 977 } 978 979 func TestFilterFunction(t *testing.T) { 980 ctx := context.Background() 981 tokenizer := NewExpressionTokenizer() 982 // The syntax for ODATA functions follows the inline parameter syntax. The function name must be followed 983 // by an opening parenthesis, followed by a comma-separated list of parameters, followed by a closing parenthesis. 984 // For example: 985 // GET serviceRoot/Airports?$filter=contains(Location/Address, 'San Francisco') 986 input := "contains(LastName, 'Smith') and FirstName eq 'John' and City eq 'Houston'" 987 expect := []*Token{ 988 {Value: "contains", Type: ExpressionTokenFunc}, 989 {Value: "(", Type: ExpressionTokenOpenParen}, 990 {Value: "LastName", Type: ExpressionTokenLiteral}, 991 {Value: ",", Type: ExpressionTokenComma}, 992 {Value: "'Smith'", Type: ExpressionTokenString}, 993 {Value: ")", Type: ExpressionTokenCloseParen}, 994 {Value: "and", Type: ExpressionTokenLogical}, 995 {Value: "FirstName", Type: ExpressionTokenLiteral}, 996 {Value: "eq", Type: ExpressionTokenLogical}, 997 {Value: "'John'", Type: ExpressionTokenString}, 998 {Value: "and", Type: ExpressionTokenLogical}, 999 {Value: "City", Type: ExpressionTokenLiteral}, 1000 {Value: "eq", Type: ExpressionTokenLogical}, 1001 {Value: "'Houston'", Type: ExpressionTokenString}, 1002 } 1003 { 1004 output, err := tokenizer.Tokenize(ctx, input) 1005 if err != nil { 1006 t.Error(err) 1007 } 1008 result, err := CompareTokens(expect, output) 1009 if !result { 1010 t.Error(err) 1011 } 1012 } 1013 { 1014 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1015 if err != nil { 1016 t.Error(err) 1017 return 1018 } 1019 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1020 if err != nil { 1021 t.Error(err) 1022 return 1023 } 1024 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1025 if err != nil { 1026 t.Error(err) 1027 return 1028 } 1029 if tree.Token.Value != "and" { 1030 t.Errorf("Root is '%v', not 'and'", tree.Token.Value) 1031 } 1032 if len(tree.Children) != 2 { 1033 t.Errorf("Unexpected number of operators. Expected 2, got %d", len(tree.Children)) 1034 } 1035 if tree.Children[0].Token.Value != "and" { 1036 t.Errorf("First child is '%v', not 'and'", tree.Children[0].Token.Value) 1037 } 1038 if len(tree.Children[0].Children) != 2 { 1039 t.Errorf("Unexpected number of operators. Expected 2, got %d", len(tree.Children)) 1040 } 1041 if tree.Children[0].Children[0].Token.Value != "contains" { 1042 t.Errorf("First child is '%v', not 'contains'", tree.Children[0].Children[0].Token.Value) 1043 } 1044 if tree.Children[1].Token.Value != "eq" { 1045 t.Errorf("First child is '%v', not 'eq'", tree.Children[1].Token.Value) 1046 } 1047 } 1048 } 1049 1050 func TestFilterNestedFunction(t *testing.T) { 1051 ctx := context.Background() 1052 tokenizer := NewExpressionTokenizer() 1053 // Test ODATA syntax with nested function calls 1054 input := "contains(LastName, toupper('Smith')) or FirstName eq 'John'" 1055 expect := []*Token{ 1056 {Value: "contains", Type: ExpressionTokenFunc}, 1057 {Value: "(", Type: ExpressionTokenOpenParen}, 1058 {Value: "LastName", Type: ExpressionTokenLiteral}, 1059 {Value: ",", Type: ExpressionTokenComma}, 1060 {Value: "toupper", Type: ExpressionTokenFunc}, 1061 {Value: "(", Type: ExpressionTokenOpenParen}, 1062 {Value: "'Smith'", Type: ExpressionTokenString}, 1063 {Value: ")", Type: ExpressionTokenCloseParen}, 1064 {Value: ")", Type: ExpressionTokenCloseParen}, 1065 {Value: "or", Type: ExpressionTokenLogical}, 1066 {Value: "FirstName", Type: ExpressionTokenLiteral}, 1067 {Value: "eq", Type: ExpressionTokenLogical}, 1068 {Value: "'John'", Type: ExpressionTokenString}, 1069 } 1070 { 1071 output, err := tokenizer.Tokenize(ctx, input) 1072 if err != nil { 1073 t.Error(err) 1074 } 1075 result, err := CompareTokens(expect, output) 1076 if !result { 1077 t.Error(err) 1078 } 1079 } 1080 { 1081 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1082 if err != nil { 1083 t.Error(err) 1084 return 1085 } 1086 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1087 if err != nil { 1088 t.Error(err) 1089 return 1090 } 1091 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1092 if err != nil { 1093 t.Error(err) 1094 return 1095 } 1096 if tree.Token.Value != "or" { 1097 t.Errorf("Root is '%v', not 'or'", tree.Token.Value) 1098 } 1099 if len(tree.Children) != 2 { 1100 t.Errorf("Unexpected number of operators. Expected 2, got %d", len(tree.Children)) 1101 } 1102 if tree.Children[0].Token.Value != "contains" { 1103 t.Errorf("First child is '%v', not 'contains'", tree.Children[0].Token.Value) 1104 } 1105 if len(tree.Children[0].Children) != 2 { 1106 t.Errorf("Unexpected number of nested children. Expected 2, got %d", len(tree.Children[0].Children)) 1107 } 1108 if tree.Children[0].Children[1].Token.Value != "toupper" { 1109 t.Errorf("First child is '%v', not 'toupper'", tree.Children[0].Children[1].Token.Value) 1110 } 1111 if tree.Children[1].Token.Value != "eq" { 1112 t.Errorf("First child is '%v', not 'eq'", tree.Children[1].Token.Value) 1113 } 1114 } 1115 } 1116 1117 func TestValidFilterSyntax(t *testing.T) { 1118 queries := []string{ 1119 "substring(CompanyName,1,2) eq 'lf'", // substring with 3 arguments. 1120 // Bolean values 1121 "true", 1122 "false", 1123 "(true)", 1124 "((true))", 1125 "((true)) or false", 1126 "not true", 1127 "not false", 1128 "not (not true)", 1129 //"not not true", // TODO: I think this should work. 'not not true' is true 1130 // String functions 1131 "contains(CompanyName,'freds')", 1132 "endswith(CompanyName,'Futterkiste')", 1133 "startswith(CompanyName,'Alfr')", 1134 "length(CompanyName) eq 19", 1135 "indexof(CompanyName,'lfreds') eq 1", 1136 "substring(CompanyName,1) eq 'lfreds Futterkiste'", // substring() with 2 arguments. 1137 "'lfreds Futterkiste' eq substring(CompanyName,1)", // Same as above, but order of operands is reversed. 1138 "substring(CompanyName,1,2) eq 'lf'", // substring() with 3 arguments. 1139 "'lf' eq substring(CompanyName,1,2) ", // Same as above, but order of operands is reversed. 1140 "substringof('Alfreds', CompanyName) eq true", 1141 "tolower(CompanyName) eq 'alfreds futterkiste'", 1142 "toupper(CompanyName) eq 'ALFREDS FUTTERKISTE'", 1143 "trim(CompanyName) eq 'Alfreds Futterkiste'", 1144 "concat(concat(City,', '), Country) eq 'Berlin, Germany'", 1145 // GUID 1146 "GuidValue eq 01234567-89ab-cdef-0123-456789abcdef", // TODO According to ODATA ABNF notation, GUID values do not have quotes. 1147 // Date and Time functions 1148 "StartDate eq 2012-12-03", 1149 "DateTimeOffsetValue eq 2012-12-03T07:16:23Z", 1150 // duration = [ "duration" ] SQUOTE durationValue SQUOTE 1151 // "DurationValue eq duration'P12DT23H59M59.999999999999S'", // TODO See ODATA ABNF notation 1152 "TimeOfDayValue eq 07:59:59.999", 1153 "year(BirthDate) eq 0", 1154 "month(BirthDate) eq 12", 1155 "day(StartTime) eq 8", 1156 "hour(StartTime) eq 1", 1157 "hour (StartTime) eq 12", // function followed by space characters 1158 "hour ( StartTime ) eq 15", // function followed by space characters 1159 "minute(StartTime) eq 0", 1160 "totaloffsetminutes(StartTime) eq 0", 1161 "second(StartTime) eq 0", 1162 "fractionalseconds(StartTime) lt 0.123456", // The fractionalseconds function returns the fractional seconds component of the 1163 // DateTimeOffset or TimeOfDay parameter value as a non-negative decimal value less than 1. 1164 "date(StartTime) ne date(EndTime)", 1165 "totaloffsetminutes(StartTime) eq 60", 1166 "StartTime eq mindatetime()", 1167 "totalseconds(EndTime sub StartTime) lt duration'PT23H59M'", // TODO The totalseconds function returns the duration of the value in total seconds, including fractional seconds. 1168 "EndTime eq maxdatetime()", 1169 "time(StartTime) le StartOfDay", 1170 "time('2015-10-14T23:30:00.104+02:00') lt now()", 1171 "time(2015-10-14T23:30:00.104+02:00) lt now()", 1172 // Math functions 1173 "round(Freight) eq 32", 1174 "floor(Freight) eq 32", 1175 "ceiling(Freight) eq 33", 1176 "Rating mod 5 eq 0", 1177 "Price div 2 eq 3", 1178 // Type functions 1179 "isof(ShipCountry,Edm.String)", 1180 "isof(NorthwindModel.BigOrder)", 1181 "cast(ShipCountry,Edm.String)", 1182 // Parameter aliases 1183 // See http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part1-protocol/odata-v4.0-errata03-os-part1-protocol-complete.html#_Toc453752288 1184 "Region eq @p1", // Aliases start with @ 1185 // Geo functions 1186 "geo.distance(CurrentPosition,TargetPosition)", 1187 "geo.length(DirectRoute)", 1188 "geo.intersects(Position,TargetArea)", 1189 "GEO.INTERSECTS(Position,TargetArea)", // functions are case insensitive in ODATA 4.0.1 1190 // Logical operators 1191 "'Milk' eq 'Milk'", // Compare two literals 1192 "'Water' ne 'Milk'", // Compare two literals 1193 "Name eq 'Milk'", 1194 "Name EQ 'Milk'", // operators are case insensitive in ODATA 4.0.1 1195 "Name ne 'Milk'", 1196 "Name NE 'Milk'", 1197 "Name gt 'Milk'", 1198 "Name ge 'Milk'", 1199 "Name lt 'Milk'", 1200 "Name le 'Milk'", 1201 "Name eq Name", // parameter equals to itself 1202 "Name eq 'Milk' and Price lt 2.55", 1203 "not endswith(Name,'ilk')", 1204 "Name eq 'Milk' or Price lt 2.55", 1205 "City eq 'Dallas' or City eq 'Houston'", 1206 // Nested properties 1207 "Product/Name eq 'Milk'", 1208 "Region/Product/Name eq 'Milk'", 1209 "Country/Region/Product/Name eq 'Milk'", 1210 //"style has Sales.Pattern'Yellow'", // TODO 1211 // Arithmetic operators 1212 "Price add 2.45 eq 5.00", 1213 "2.46 add Price eq 5.00", 1214 "Price add (2.47) eq 5.00", 1215 "(Price add (2.48)) eq 5.00", 1216 "Price ADD 2.49 eq 5.00", // 4.01 Services MUST support case-insensitive operator names. 1217 "Price sub 0.55 eq 2.00", 1218 "Price SUB 0.56 EQ 2.00", // 4.01 Services MUST support case-insensitive operator names. 1219 "Price mul 2.0 eq 5.10", 1220 "Price div 2.55 eq 1", 1221 "Rating div 2 eq 2", 1222 "Rating mod 5 eq 0", 1223 // Grouping 1224 "(4 add 5) mod (4 sub 1) eq 0", 1225 "not (City eq 'Dallas') or Name in ('a', 'b', 'c') and not (State eq 'California')", 1226 // Nested functions 1227 "length(trim(CompanyName)) eq length(CompanyName)", 1228 "concat(concat(City, ', '), Country) eq 'Berlin, Germany'", 1229 // Various parenthesis combinations 1230 "City eq 'Dallas'", 1231 "City eq ('Dallas')", 1232 "'Dallas' eq City", 1233 "not (City eq 'Dallas')", 1234 "City in ('Dallas')", 1235 "(City in ('Dallas'))", 1236 "(City in ('Dallas', 'Houston'))", 1237 "not (City in ('Dallas'))", 1238 "not (City in ('Dallas', 'Houston'))", 1239 "not (((City eq 'Dallas')))", 1240 "not(S1 eq 'foo')", 1241 // Lambda operators 1242 "Tags/any()", // The any operator without an argument returns true if the collection is not empty 1243 "Tags/any(tag:tag eq 'London')", // 'Tags' is array of strings 1244 "Tags/any(tag:tag eq 'London' or tag eq 'Berlin')", // 'Tags' is array of strings 1245 "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London')", // 'Tags' is array of {"Key": "abc", "Value": "def"} 1246 "Tags/ANY(var:var/Key eq 'Site' AND var/Value eq 'London')", 1247 "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London') and not (City in ('Dallas'))", 1248 "Tags/all(var:var/Key eq 'Site' and var/Value eq 'London')", 1249 "Price/any(t:not (12345 eq t))", 1250 // A long query. 1251 "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London') or " + 1252 "Tags/any(var:var/Key eq 'Site' and var/Value eq 'Berlin') or " + 1253 "Tags/any(var:var/Key eq 'Site' and var/Value eq 'Paris') or " + 1254 "Tags/any(var:var/Key eq 'Site' and var/Value eq 'New York City') or " + 1255 "Tags/any(var:var/Key eq 'Site' and var/Value eq 'San Francisco')", 1256 } 1257 ctx := context.Background() 1258 for _, input := range queries { 1259 q, err := ParseFilterString(ctx, input) 1260 if err != nil { 1261 t.Errorf("Error parsing query %s. Error: %v", input, err) 1262 return 1263 } else if q.Tree == nil { 1264 t.Errorf("Error parsing query %s. Tree is nil", input) 1265 } 1266 if q.Tree.Token == nil { 1267 t.Errorf("Error parsing query %s. Root token is nil", input) 1268 } 1269 if q.Tree.Token.Type == ExpressionTokenLiteral { 1270 t.Errorf("Error parsing query %s. Unexpected root token type: %+v", input, q.Tree.Token) 1271 } 1272 //printTree(q.Tree) 1273 } 1274 } 1275 1276 // The URLs below are not valid ODATA syntax, the parser should return an error. 1277 func TestInvalidFilterSyntax(t *testing.T) { 1278 queries := []string{ 1279 "()", // It's not a boolean expression 1280 "(TRUE)", 1281 "(City)", 1282 "(", 1283 "((((", 1284 ")", 1285 "12345", // Number 12345 is not a boolean expression 1286 "0", // Number 0 is not a boolean expression 1287 "'123'", // String '123' is not a boolean expression 1288 "TRUE", // Should be 'true' lowercase 1289 "FALSE", // Should be 'false' lowercase 1290 "yes", // yes is not a boolean expression 1291 "no", // yes is not a boolean expression 1292 "", // Empty string. 1293 "eq", // Just a single logical operator 1294 "and", // Just a single logical operator 1295 "add", // Just a single arithmetic operator 1296 "add ", // Just a single arithmetic operator 1297 "add 2", // Missing operands 1298 "City", // Just a single literal 1299 "City City", // Sequence of two literals 1300 "City City City City", // Sequence of literals 1301 "eq eq", // Two consecutive operators 1302 "City eq", // Missing operand 1303 "City eq (", // Wrong operand 1304 "City eq )", // Wrong operand 1305 "City equals 'Dallas'", // Unknown operator that starts with the same letters as a known operator 1306 "City near 'Dallas'", // Unknown operator that starts with the same letters as a known operator 1307 "City isNot 'Dallas'", // Unknown operator 1308 "not [City eq 'Dallas']", // Wrong delimiter 1309 "not (City eq )", // Missing operand 1310 "not ((City eq 'Dallas'", // Missing closing parenthesis 1311 "not (City eq 'Dallas'", // Missing closing parenthesis 1312 "not (City eq 'Dallas'))", // Extraneous closing parenthesis 1313 "not City eq 'Dallas')", // Missing open parenthesis 1314 "City eq 'Dallas' orCity eq 'Houston'", // missing space between or and City 1315 // TODO: the query below should fail. 1316 //"Tags/any(var:var/Key eq 'Site') orTags/any(var:var/Key eq 'Site')", 1317 "not (City eq 'Dallas') and Name eq 'Houston')", 1318 "Tags/all()", // The all operator cannot be used without an argument expression. 1319 "LastName contains 'Smith'", // Previously the godata library was not returning an error. 1320 "contains", // Function with missing parenthesis and arguments 1321 "contains()", // Function with missing arguments 1322 "contains LastName, 'Smith'", // Missing parenthesis 1323 "contains(LastName)", // Insufficent number of function arguments 1324 "contains(LastName, 'Smith'))", // Extraneous closing parenthesis 1325 "contains(LastName, 'Smith'", // Missing closing parenthesis 1326 "contains LastName, 'Smith')", // Missing open parenthesis 1327 "City eq 'Dallas' 'Houston'", // extraneous string value 1328 "(numCore neq 12)", // Invalid operator. It should be 'ne' 1329 "(a b c d)", // Invalid list 1330 "numCore neq 12", // Invalid operator. It should be 'ne' 1331 //"contains(Name, 'a', 'b', 'c', 'd')", // Too many function arguments 1332 } 1333 ctx := context.Background() 1334 for _, input := range queries { 1335 q, err := ParseFilterString(ctx, input) 1336 if err == nil { 1337 // The parser has incorrectly determined the syntax is valid. 1338 t.Errorf("The query '$filter=%s' is not valid ODATA syntax. The ODATA parser should return an error. Tree:\n%v", input, q.Tree) 1339 } 1340 } 1341 } 1342 1343 // See http://docs.oasis-open.org/odata/odata/v4.01/csprd02/part1-protocol/odata-v4.01-csprd02-part1-protocol.html#_Toc486263411 1344 // Test 'in', which is the 'Is a member of' operator. 1345 func TestFilterIn(t *testing.T) { 1346 ctx := context.Background() 1347 tokenizer := NewExpressionTokenizer() 1348 input := "contains(LastName, 'Smith') and Site in ('London', 'Paris', 'San Francisco', 'Dallas') and FirstName eq 'John'" 1349 expect := []*Token{ 1350 {Value: "contains", Type: ExpressionTokenFunc}, 1351 {Value: "(", Type: ExpressionTokenOpenParen}, 1352 {Value: "LastName", Type: ExpressionTokenLiteral}, 1353 {Value: ",", Type: ExpressionTokenComma}, 1354 {Value: "'Smith'", Type: ExpressionTokenString}, 1355 {Value: ")", Type: ExpressionTokenCloseParen}, 1356 {Value: "and", Type: ExpressionTokenLogical}, 1357 {Value: "Site", Type: ExpressionTokenLiteral}, 1358 {Value: "in", Type: ExpressionTokenLogical}, 1359 {Value: "(", Type: ExpressionTokenOpenParen}, 1360 {Value: "'London'", Type: ExpressionTokenString}, 1361 {Value: ",", Type: ExpressionTokenComma}, 1362 {Value: "'Paris'", Type: ExpressionTokenString}, 1363 {Value: ",", Type: ExpressionTokenComma}, 1364 {Value: "'San Francisco'", Type: ExpressionTokenString}, 1365 {Value: ",", Type: ExpressionTokenComma}, 1366 {Value: "'Dallas'", Type: ExpressionTokenString}, 1367 {Value: ")", Type: ExpressionTokenCloseParen}, 1368 {Value: "and", Type: ExpressionTokenLogical}, 1369 {Value: "FirstName", Type: ExpressionTokenLiteral}, 1370 {Value: "eq", Type: ExpressionTokenLogical}, 1371 {Value: "'John'", Type: ExpressionTokenString}, 1372 } 1373 { 1374 output, err := tokenizer.Tokenize(ctx, input) 1375 if err != nil { 1376 t.Error(err) 1377 } 1378 result, err := CompareTokens(expect, output) 1379 if !result { 1380 t.Error(err) 1381 } 1382 } 1383 { 1384 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1385 if err != nil { 1386 t.Error(err) 1387 return 1388 } 1389 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1390 if err != nil { 1391 t.Error(err) 1392 return 1393 } 1394 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1395 if err != nil { 1396 t.Error(err) 1397 return 1398 } 1399 1400 /* 1401 The expected tree is: 1402 and 6 1403 and 6 1404 contains 8 1405 LastName 20 1406 'Smith' 15 1407 in 6 1408 Site 20 1409 ( 0 1410 'London' 15 1411 'Paris' 15 1412 'San Francisco' 15 1413 'Dallas' 15 1414 eq 6 1415 FirstName 20 1416 'John' 15 1417 1418 */ 1419 if tree.Token.Value != "and" { 1420 t.Errorf("Root is '%v', not 'and'", tree.Token.Value) 1421 } 1422 if len(tree.Children) != 2 { 1423 t.Errorf("Unexpected number of operators. Expected 2, got %d", len(tree.Children)) 1424 } 1425 if tree.Children[0].Token.Value != "and" { 1426 t.Errorf("First child is '%v', not 'and'", tree.Children[0].Token.Value) 1427 } 1428 if len(tree.Children[0].Children) != 2 { 1429 t.Errorf("Unexpected number of operators. Expected 2, got %d", len(tree.Children)) 1430 } 1431 if tree.Children[0].Children[0].Token.Value != "contains" { 1432 t.Errorf("First child is '%v', not 'contains'", tree.Children[0].Children[0].Token.Value) 1433 } 1434 if tree.Children[0].Children[1].Token.Value != "in" { 1435 t.Errorf("First child is '%v', not 'in'", tree.Children[0].Children[1].Token.Value) 1436 } 1437 if len(tree.Children[0].Children[1].Children) != 2 { 1438 t.Errorf("Unexpected number of operands for the 'in' operator. Expected 2, got %d", 1439 len(tree.Children[0].Children[1].Children)) 1440 } 1441 if tree.Children[0].Children[1].Children[0].Token.Value != "Site" { 1442 t.Errorf("Unexpected operand for the 'in' operator. Expected 'Site', got %s", 1443 tree.Children[0].Children[1].Children[0].Token.Value) 1444 } 1445 if tree.Children[0].Children[1].Children[1].Token.Value != TokenListExpr { 1446 t.Errorf("Unexpected operand for the 'in' operator. Expected 'list', got %s", 1447 tree.Children[0].Children[1].Children[1].Token.Value) 1448 } 1449 if len(tree.Children[0].Children[1].Children[1].Children) != 4 { 1450 t.Errorf("Unexpected number of operands for the 'in' operator. Expected 4, got %d", 1451 len(tree.Children[0].Children[1].Children[1].Token.Value)) 1452 } 1453 if tree.Children[1].Token.Value != "eq" { 1454 t.Errorf("First child is '%v', not 'eq'", tree.Children[1].Token.Value) 1455 } 1456 } 1457 } 1458 1459 func TestExpressionTokenizerFunc(t *testing.T) { 1460 ctx := context.Background() 1461 tokenizer := NewExpressionTokenizer() 1462 input := "not endswith(Name,'ilk')" 1463 expect := []*Token{ 1464 {Value: "not", Type: ExpressionTokenLogical}, 1465 {Value: "endswith", Type: ExpressionTokenFunc}, 1466 {Value: "(", Type: ExpressionTokenOpenParen}, 1467 {Value: "Name", Type: ExpressionTokenLiteral}, 1468 {Value: ",", Type: ExpressionTokenComma}, 1469 {Value: "'ilk'", Type: ExpressionTokenString}, 1470 {Value: ")", Type: ExpressionTokenCloseParen}, 1471 } 1472 output, err := tokenizer.Tokenize(ctx, input) 1473 if err != nil { 1474 t.Error(err) 1475 } 1476 1477 result, err := CompareTokens(expect, output) 1478 if !result { 1479 t.Error(err) 1480 } 1481 } 1482 1483 func BenchmarkFilterTokenizer(b *testing.B) { 1484 ctx := context.Background() 1485 t := NewExpressionTokenizer() 1486 for i := 0; i < b.N; i++ { 1487 input := "Name eq 'Milk' and Price lt 2.55" 1488 if _, err := t.Tokenize(ctx, input); err != nil { 1489 b.Fatalf("Failed to tokenize filter: %v", err) 1490 } 1491 } 1492 } 1493 1494 func TestFilterParserTree(t *testing.T) { 1495 ctx := context.Background() 1496 input := "not (A eq B)" 1497 1498 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1499 if err != nil { 1500 t.Error(err) 1501 return 1502 } 1503 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1504 1505 if err != nil { 1506 t.Error(err) 1507 return 1508 } 1509 1510 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1511 1512 if err != nil { 1513 t.Error(err) 1514 return 1515 } 1516 1517 if tree.Token.Value != "not" { 1518 t.Error("Root is '" + tree.Token.Value + "' not 'not'") 1519 } 1520 if tree.Children[0].Token.Value != "eq" { 1521 t.Error("First child is '" + tree.Children[1].Token.Value + "' not 'eq'") 1522 } 1523 1524 } 1525 1526 func TestFilterNestedPath(t *testing.T) { 1527 ctx := context.Background() 1528 input := "Address/City eq 'Redmond'" 1529 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1530 if err != nil { 1531 t.Error(err) 1532 return 1533 } 1534 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1535 if err != nil { 1536 t.Error(err) 1537 return 1538 } 1539 1540 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1541 if err != nil { 1542 t.Error(err) 1543 return 1544 } 1545 1546 var expect []expectedParseNode = []expectedParseNode{ 1547 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 1548 {Value: "/", Depth: 1, Type: ExpressionTokenNav}, 1549 {Value: "Address", Depth: 2, Type: ExpressionTokenLiteral}, 1550 {Value: "City", Depth: 2, Type: ExpressionTokenLiteral}, 1551 {Value: "'Redmond'", Depth: 1, Type: ExpressionTokenString}, 1552 } 1553 pos := 0 1554 err = CompareTree(tree, expect, &pos, 0) 1555 if err != nil { 1556 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1557 } 1558 } 1559 1560 func TestFilterMultipleNestedPath(t *testing.T) { 1561 ctx := context.Background() 1562 input := "Product/Address/City eq 'Redmond'" 1563 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1564 if err != nil { 1565 t.Error(err) 1566 return 1567 } 1568 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1569 if err != nil { 1570 t.Error(err) 1571 return 1572 } 1573 1574 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1575 if err != nil { 1576 t.Error(err) 1577 return 1578 } 1579 1580 var expect []expectedParseNode = []expectedParseNode{ 1581 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 1582 {Value: "/", Depth: 1, Type: ExpressionTokenNav}, 1583 {Value: "/", Depth: 2, Type: ExpressionTokenNav}, 1584 {Value: "Product", Depth: 3, Type: ExpressionTokenLiteral}, 1585 {Value: "Address", Depth: 3, Type: ExpressionTokenLiteral}, 1586 {Value: "City", Depth: 2, Type: ExpressionTokenLiteral}, 1587 {Value: "'Redmond'", Depth: 1, Type: ExpressionTokenString}, 1588 } 1589 pos := 0 1590 err = CompareTree(tree, expect, &pos, 0) 1591 if err != nil { 1592 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1593 } 1594 } 1595 1596 func TestFilterSubstringFunction(t *testing.T) { 1597 ctx := context.Background() 1598 // substring can take 2 or 3 arguments. 1599 { 1600 input := "substring(CompanyName,1) eq 'Foo'" 1601 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1602 if err != nil { 1603 t.Error(err) 1604 return 1605 } 1606 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1607 if err != nil { 1608 t.Error(err) 1609 return 1610 } 1611 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1612 if err != nil { 1613 t.Error(err) 1614 return 1615 } 1616 var expect []expectedParseNode = []expectedParseNode{ 1617 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 1618 {Value: "substring", Depth: 1, Type: ExpressionTokenFunc}, 1619 {Value: "CompanyName", Depth: 2, Type: ExpressionTokenLiteral}, 1620 {Value: "1", Depth: 2, Type: ExpressionTokenInteger}, 1621 {Value: "'Foo'", Depth: 1, Type: ExpressionTokenString}, 1622 } 1623 pos := 0 1624 err = CompareTree(tree, expect, &pos, 0) 1625 if err != nil { 1626 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1627 } 1628 } 1629 { 1630 input := "substring(CompanyName,1,2) eq 'lf'" 1631 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1632 if err != nil { 1633 t.Error(err) 1634 return 1635 } 1636 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1637 if err != nil { 1638 t.Error(err) 1639 return 1640 } 1641 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1642 if err != nil { 1643 t.Error(err) 1644 return 1645 } 1646 var expect []expectedParseNode = []expectedParseNode{ 1647 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 1648 {Value: "substring", Depth: 1, Type: ExpressionTokenFunc}, 1649 {Value: "CompanyName", Depth: 2, Type: ExpressionTokenLiteral}, 1650 {Value: "1", Depth: 2, Type: ExpressionTokenInteger}, 1651 {Value: "2", Depth: 2, Type: ExpressionTokenInteger}, 1652 {Value: "'lf'", Depth: 1, Type: ExpressionTokenString}, 1653 } 1654 pos := 0 1655 err = CompareTree(tree, expect, &pos, 0) 1656 if err != nil { 1657 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1658 } 1659 } 1660 } 1661 1662 func TestFilterSubstringofFunction(t *testing.T) { 1663 ctx := context.Background() 1664 // Previously, the parser was incorrectly interpreting the 'substringof' function as the 'sub' operator. 1665 input := "substringof('Alfreds', CompanyName) eq true" 1666 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1667 if err != nil { 1668 t.Error(err) 1669 return 1670 } 1671 { 1672 expect := []*Token{ 1673 {Value: "substringof", Type: ExpressionTokenFunc}, 1674 {Value: "(", Type: ExpressionTokenOpenParen}, 1675 {Value: "'Alfreds'", Type: ExpressionTokenString}, 1676 {Value: ",", Type: ExpressionTokenComma}, 1677 {Value: "CompanyName", Type: ExpressionTokenLiteral}, 1678 {Value: ")", Type: ExpressionTokenCloseParen}, 1679 {Value: "eq", Type: ExpressionTokenLogical}, 1680 {Value: "true", Type: ExpressionTokenBoolean}, 1681 } 1682 result, err := CompareTokens(expect, tokens) 1683 if !result { 1684 t.Error(err) 1685 } 1686 } 1687 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1688 if err != nil { 1689 t.Error(err) 1690 return 1691 } 1692 { 1693 expect := []*Token{ 1694 {Value: "'Alfreds'", Type: ExpressionTokenString}, 1695 {Value: "CompanyName", Type: ExpressionTokenLiteral}, 1696 {Value: "2", Type: TokenTypeArgCount}, // The number of function arguments. 1697 {Value: TokenListExpr, Type: TokenTypeListExpr}, 1698 {Value: "substringof", Type: ExpressionTokenFunc}, 1699 {Value: "true", Type: ExpressionTokenBoolean}, 1700 {Value: "eq", Type: ExpressionTokenLogical}, 1701 } 1702 if err := CompareQueue(expect, output); err != nil { 1703 t.Error(err) 1704 } 1705 } 1706 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1707 if err != nil { 1708 t.Error(err) 1709 return 1710 } 1711 var expect []expectedParseNode = []expectedParseNode{ 1712 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 1713 {Value: "substringof", Depth: 1, Type: ExpressionTokenFunc}, 1714 {Value: "'Alfreds'", Depth: 2, Type: ExpressionTokenString}, 1715 {Value: "CompanyName", Depth: 2, Type: ExpressionTokenLiteral}, 1716 {Value: "true", Depth: 1, Type: ExpressionTokenBoolean}, 1717 } 1718 pos := 0 1719 err = CompareTree(tree, expect, &pos, 0) 1720 if err != nil { 1721 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1722 } 1723 } 1724 1725 // TestSubstringNestedFunction tests the substring function with a nested call 1726 // to substring, with the use of 2-argument and 3-argument substring. 1727 func TestFilterSubstringNestedFunction(t *testing.T) { 1728 ctx := context.Background() 1729 // Previously, the parser was incorrectly interpreting the 'substringof' function as the 'sub' operator. 1730 input := "substring(substring('Francisco', 1), 3, 2) eq 'ci'" 1731 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1732 if err != nil { 1733 t.Error(err) 1734 return 1735 } 1736 { 1737 expect := []*Token{ 1738 {Value: "substring", Type: ExpressionTokenFunc}, 1739 {Value: "(", Type: ExpressionTokenOpenParen}, 1740 {Value: "substring", Type: ExpressionTokenFunc}, 1741 {Value: "(", Type: ExpressionTokenOpenParen}, 1742 {Value: "'Francisco'", Type: ExpressionTokenString}, 1743 {Value: ",", Type: ExpressionTokenComma}, 1744 {Value: "1", Type: ExpressionTokenInteger}, 1745 {Value: ")", Type: ExpressionTokenCloseParen}, 1746 {Value: ",", Type: ExpressionTokenComma}, 1747 {Value: "3", Type: ExpressionTokenInteger}, 1748 {Value: ",", Type: ExpressionTokenComma}, 1749 {Value: "2", Type: ExpressionTokenInteger}, 1750 {Value: ")", Type: ExpressionTokenCloseParen}, 1751 {Value: "eq", Type: ExpressionTokenLogical}, 1752 {Value: "'ci'", Type: ExpressionTokenString}, 1753 } 1754 result, err := CompareTokens(expect, tokens) 1755 if !result { 1756 t.Error(err) 1757 } 1758 } 1759 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1760 if err != nil { 1761 t.Error(err) 1762 return 1763 } 1764 { 1765 expect := []*Token{ 1766 {Value: "'Francisco'", Type: ExpressionTokenString}, 1767 {Value: "1", Type: ExpressionTokenInteger}, 1768 {Value: "2", Type: TokenTypeArgCount}, // The number of function arguments. 1769 {Value: TokenListExpr, Type: TokenTypeListExpr}, 1770 {Value: "substring", Type: ExpressionTokenFunc}, 1771 {Value: "3", Type: ExpressionTokenInteger}, 1772 {Value: "2", Type: ExpressionTokenInteger}, 1773 {Value: "3", Type: TokenTypeArgCount}, // The number of function arguments. 1774 {Value: TokenListExpr, Type: TokenTypeListExpr}, 1775 {Value: "substring", Type: ExpressionTokenFunc}, 1776 {Value: "'ci'", Type: ExpressionTokenString}, 1777 {Value: "eq", Type: ExpressionTokenLogical}, 1778 } 1779 if err := CompareQueue(expect, output); err != nil { 1780 t.Error(err) 1781 } 1782 } 1783 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1784 if err != nil { 1785 t.Error(err) 1786 return 1787 } 1788 var expect []expectedParseNode = []expectedParseNode{ 1789 {Value: "eq", Depth: 0, Type: ExpressionTokenLogical}, 1790 {Value: "substring", Depth: 1, Type: ExpressionTokenFunc}, 1791 {Value: "substring", Depth: 2, Type: ExpressionTokenFunc}, 1792 {Value: "'Francisco'", Depth: 3, Type: ExpressionTokenString}, 1793 {Value: "1", Depth: 3, Type: ExpressionTokenInteger}, 1794 {Value: "3", Depth: 2, Type: ExpressionTokenInteger}, 1795 {Value: "2", Depth: 2, Type: ExpressionTokenInteger}, 1796 {Value: "'ci'", Depth: 1, Type: ExpressionTokenString}, 1797 } 1798 pos := 0 1799 err = CompareTree(tree, expect, &pos, 0) 1800 if err != nil { 1801 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1802 } 1803 } 1804 func TestFilterGeoFunctions(t *testing.T) { 1805 ctx := context.Background() 1806 // Previously, the parser was incorrectly interpreting the 'geo.xxx' functions as the 'ge' operator. 1807 input := "geo.distance(CurrentPosition,TargetPosition)" 1808 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1809 if err != nil { 1810 t.Error(err) 1811 return 1812 } 1813 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1814 if err != nil { 1815 t.Error(err) 1816 return 1817 } 1818 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1819 if err != nil { 1820 t.Error(err) 1821 return 1822 } 1823 var expect []expectedParseNode = []expectedParseNode{ 1824 {Value: "geo.distance", Depth: 0, Type: ExpressionTokenFunc}, 1825 {Value: "CurrentPosition", Depth: 1, Type: ExpressionTokenLiteral}, 1826 {Value: "TargetPosition", Depth: 1, Type: ExpressionTokenLiteral}, 1827 } 1828 pos := 0 1829 err = CompareTree(tree, expect, &pos, 0) 1830 if err != nil { 1831 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1832 } 1833 } 1834 1835 func TestFilterLambdaAny(t *testing.T) { 1836 ctx := context.Background() 1837 input := "Tags/any(var:var/Key eq 'Site')" 1838 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1839 if err != nil { 1840 t.Error(err) 1841 return 1842 } 1843 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1844 if err != nil { 1845 t.Error(err) 1846 return 1847 } 1848 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1849 if err != nil { 1850 t.Error(err) 1851 return 1852 } 1853 1854 var expect []expectedParseNode = []expectedParseNode{ 1855 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 1856 {Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral}, 1857 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 1858 {Value: "var", Depth: 2, Type: ExpressionTokenLiteral}, 1859 {Value: "eq", Depth: 2, Type: ExpressionTokenLogical}, 1860 {Value: "/", Depth: 3, Type: ExpressionTokenNav}, 1861 {Value: "var", Depth: 4, Type: ExpressionTokenLiteral}, 1862 {Value: "Key", Depth: 4, Type: ExpressionTokenLiteral}, 1863 {Value: "'Site'", Depth: 3, Type: ExpressionTokenString}, 1864 } 1865 pos := 0 1866 err = CompareTree(tree, expect, &pos, 0) 1867 if err != nil { 1868 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1869 } 1870 } 1871 1872 func TestFilterLambdaAnyNot(t *testing.T) { 1873 ctx := context.Background() 1874 input := "Price/any(t:not (12345 eq t ))" 1875 1876 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1877 if err != nil { 1878 t.Error(err) 1879 return 1880 } 1881 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1882 if err != nil { 1883 t.Error(err) 1884 return 1885 } 1886 { 1887 expect := []*Token{ 1888 {Value: "Price", Type: ExpressionTokenLiteral}, 1889 {Value: "t", Type: ExpressionTokenLiteral}, 1890 {Value: "12345", Type: ExpressionTokenInteger}, 1891 {Value: "t", Type: ExpressionTokenLiteral}, 1892 {Value: "eq", Type: ExpressionTokenLogical}, 1893 {Value: "not", Type: ExpressionTokenLogical}, 1894 {Value: "2", Type: TokenTypeArgCount}, 1895 {Value: TokenListExpr, Type: TokenTypeListExpr}, 1896 {Value: "any", Type: ExpressionTokenLambda}, 1897 {Value: "/", Type: ExpressionTokenLambdaNav}, 1898 } 1899 if err = CompareQueue(expect, output); err != nil { 1900 t.Error(err) 1901 } 1902 } 1903 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1904 if err != nil { 1905 t.Error(err) 1906 return 1907 } 1908 var expect []expectedParseNode = []expectedParseNode{ 1909 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 1910 {Value: "Price", Depth: 1, Type: ExpressionTokenLiteral}, 1911 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 1912 {Value: "t", Depth: 2, Type: ExpressionTokenLiteral}, 1913 {Value: "not", Depth: 2, Type: ExpressionTokenLogical}, 1914 {Value: "eq", Depth: 3, Type: ExpressionTokenLogical}, 1915 {Value: "12345", Depth: 4, Type: ExpressionTokenInteger}, 1916 {Value: "t", Depth: 4, Type: ExpressionTokenLiteral}, 1917 } 1918 pos := 0 1919 err = CompareTree(tree, expect, &pos, 0) 1920 if err != nil { 1921 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1922 } 1923 } 1924 1925 func TestFilterLambdaAnyAnd(t *testing.T) { 1926 ctx := context.Background() 1927 input := "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London')" 1928 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 1929 if err != nil { 1930 t.Error(err) 1931 return 1932 } 1933 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 1934 if err != nil { 1935 t.Error(err) 1936 return 1937 } 1938 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 1939 if err != nil { 1940 t.Error(err) 1941 return 1942 } 1943 1944 var expect []expectedParseNode = []expectedParseNode{ 1945 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 1946 {Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral}, 1947 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 1948 {Value: "var", Depth: 2, Type: ExpressionTokenLiteral}, 1949 {Value: "and", Depth: 2, Type: ExpressionTokenLogical}, 1950 {Value: "eq", Depth: 3, Type: ExpressionTokenLogical}, 1951 {Value: "/", Depth: 4, Type: ExpressionTokenNav}, 1952 {Value: "var", Depth: 5, Type: ExpressionTokenLiteral}, 1953 {Value: "Key", Depth: 5, Type: ExpressionTokenLiteral}, 1954 {Value: "'Site'", Depth: 4, Type: ExpressionTokenString}, 1955 {Value: "eq", Depth: 3, Type: ExpressionTokenLogical}, 1956 {Value: "/", Depth: 4, Type: ExpressionTokenNav}, 1957 {Value: "var", Depth: 5, Type: ExpressionTokenLiteral}, 1958 {Value: "Value", Depth: 5, Type: ExpressionTokenLiteral}, 1959 {Value: "'London'", Depth: 4, Type: ExpressionTokenString}, 1960 } 1961 pos := 0 1962 err = CompareTree(tree, expect, &pos, 0) 1963 if err != nil { 1964 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 1965 } 1966 } 1967 1968 func TestFilterLambdaNestedAny(t *testing.T) { 1969 input := "Enabled/any(t:t/Value eq Config/any(c:c/AdminState eq 'TRUE'))" 1970 ctx := context.Background() 1971 q, err := ParseFilterString(ctx, input) 1972 if err != nil { 1973 t.Errorf("Error parsing query %s. Error: %v", input, err) 1974 return 1975 } 1976 var expect []expectedParseNode = []expectedParseNode{ 1977 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 1978 {Value: "Enabled", Depth: 1, Type: ExpressionTokenLiteral}, 1979 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 1980 {Value: "t", Depth: 2, Type: ExpressionTokenLiteral}, 1981 {Value: "eq", Depth: 2, Type: ExpressionTokenLogical}, 1982 {Value: "/", Depth: 3, Type: ExpressionTokenNav}, 1983 {Value: "t", Depth: 4, Type: ExpressionTokenLiteral}, 1984 {Value: "Value", Depth: 4, Type: ExpressionTokenLiteral}, 1985 {Value: "/", Depth: 3, Type: ExpressionTokenLambdaNav}, 1986 {Value: "Config", Depth: 4, Type: ExpressionTokenLiteral}, 1987 {Value: "any", Depth: 4, Type: ExpressionTokenLambda}, 1988 {Value: "c", Depth: 5, Type: ExpressionTokenLiteral}, 1989 {Value: "eq", Depth: 5, Type: ExpressionTokenLogical}, 1990 {Value: "/", Depth: 6, Type: ExpressionTokenNav}, 1991 {Value: "c", Depth: 7, Type: ExpressionTokenLiteral}, 1992 {Value: "AdminState", Depth: 7, Type: ExpressionTokenLiteral}, 1993 {Value: "'TRUE'", Depth: 6, Type: ExpressionTokenString}, 1994 } 1995 pos := 0 1996 err = CompareTree(q.Tree, expect, &pos, 0) 1997 if err != nil { 1998 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, q.Tree) 1999 } 2000 } 2001 2002 // TestLambdaAnyNested validates the any() lambda function with multiple nested properties. 2003 func TestFilterLambdaAnyNestedProperties(t *testing.T) { 2004 ctx := context.Background() 2005 input := "Config/any(var:var/Config/Priority eq 123)" 2006 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 2007 if err != nil { 2008 t.Error(err) 2009 return 2010 } 2011 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 2012 if err != nil { 2013 t.Error(err) 2014 return 2015 } 2016 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 2017 if err != nil { 2018 t.Error(err) 2019 return 2020 } 2021 2022 var expect []expectedParseNode = []expectedParseNode{ 2023 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 2024 {Value: "Config", Depth: 1, Type: ExpressionTokenLiteral}, 2025 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 2026 {Value: "var", Depth: 2, Type: ExpressionTokenLiteral}, 2027 {Value: "eq", Depth: 2, Type: ExpressionTokenLogical}, 2028 {Value: "/", Depth: 3, Type: ExpressionTokenNav}, 2029 {Value: "/", Depth: 4, Type: ExpressionTokenNav}, 2030 {Value: "var", Depth: 5, Type: ExpressionTokenLiteral}, 2031 {Value: "Config", Depth: 5, Type: ExpressionTokenLiteral}, 2032 {Value: "Priority", Depth: 4, Type: ExpressionTokenLiteral}, 2033 {Value: "123", Depth: 3, Type: ExpressionTokenInteger}, 2034 } 2035 pos := 0 2036 err = CompareTree(tree, expect, &pos, 0) 2037 if err != nil { 2038 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 2039 } 2040 } 2041 2042 func TestFilterLambda2(t *testing.T) { 2043 ctx := context.Background() 2044 input := "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London' or Price gt 1.0)" 2045 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 2046 if err != nil { 2047 t.Error(err) 2048 return 2049 } 2050 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 2051 if err != nil { 2052 t.Error(err) 2053 return 2054 } 2055 2056 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 2057 if err != nil { 2058 t.Error(err) 2059 return 2060 } 2061 var expect []expectedParseNode = []expectedParseNode{ 2062 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 2063 {Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral}, 2064 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 2065 {Value: "var", Depth: 2, Type: ExpressionTokenLiteral}, 2066 {Value: "or", Depth: 2, Type: ExpressionTokenLogical}, 2067 {Value: "and", Depth: 3, Type: ExpressionTokenLogical}, 2068 {Value: "eq", Depth: 4, Type: ExpressionTokenLogical}, 2069 {Value: "/", Depth: 5, Type: ExpressionTokenNav}, 2070 {Value: "var", Depth: 6, Type: ExpressionTokenLiteral}, 2071 {Value: "Key", Depth: 6, Type: ExpressionTokenLiteral}, 2072 {Value: "'Site'", Depth: 5, Type: ExpressionTokenString}, 2073 {Value: "eq", Depth: 4, Type: ExpressionTokenLogical}, 2074 {Value: "/", Depth: 5, Type: ExpressionTokenNav}, 2075 {Value: "var", Depth: 6, Type: ExpressionTokenLiteral}, 2076 {Value: "Value", Depth: 6, Type: ExpressionTokenLiteral}, 2077 {Value: "'London'", Depth: 5, Type: ExpressionTokenString}, 2078 {Value: "gt", Depth: 3, Type: ExpressionTokenLogical}, 2079 {Value: "Price", Depth: 4, Type: ExpressionTokenLiteral}, 2080 {Value: "1.0", Depth: 4, Type: ExpressionTokenFloat}, 2081 } 2082 pos := 0 2083 err = CompareTree(tree, expect, &pos, 0) 2084 if err != nil { 2085 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 2086 } 2087 } 2088 2089 func TestFilterLambda3(t *testing.T) { 2090 ctx := context.Background() 2091 input := "Tags/any(var:var/Key eq 'Site' and var/Value eq 'London' or Price gt 1.0 or contains(var/Value, 'Smith'))" 2092 tokens, err := GlobalExpressionTokenizer.Tokenize(ctx, input) 2093 if err != nil { 2094 t.Error(err) 2095 return 2096 } 2097 output, err := GlobalFilterParser.InfixToPostfix(ctx, tokens) 2098 if err != nil { 2099 t.Error(err) 2100 return 2101 } 2102 2103 tree, err := GlobalFilterParser.PostfixToTree(ctx, output) 2104 if err != nil { 2105 t.Error(err) 2106 return 2107 } 2108 var expect []expectedParseNode = []expectedParseNode{ 2109 {Value: "/", Depth: 0, Type: ExpressionTokenLambdaNav}, 2110 {Value: "Tags", Depth: 1, Type: ExpressionTokenLiteral}, 2111 {Value: "any", Depth: 1, Type: ExpressionTokenLambda}, 2112 {Value: "var", Depth: 2, Type: ExpressionTokenLiteral}, 2113 {Value: "or", Depth: 2, Type: ExpressionTokenLogical}, 2114 {Value: "or", Depth: 3, Type: ExpressionTokenLogical}, 2115 {Value: "and", Depth: 4, Type: ExpressionTokenLogical}, 2116 {Value: "eq", Depth: 5, Type: ExpressionTokenLogical}, 2117 {Value: "/", Depth: 6, Type: ExpressionTokenNav}, 2118 {Value: "var", Depth: 7, Type: ExpressionTokenLiteral}, 2119 {Value: "Key", Depth: 7, Type: ExpressionTokenLiteral}, 2120 {Value: "'Site'", Depth: 6, Type: ExpressionTokenString}, 2121 {Value: "eq", Depth: 5, Type: ExpressionTokenLogical}, 2122 {Value: "/", Depth: 6, Type: ExpressionTokenNav}, 2123 {Value: "var", Depth: 7, Type: ExpressionTokenLiteral}, 2124 {Value: "Value", Depth: 7, Type: ExpressionTokenLiteral}, 2125 {Value: "'London'", Depth: 6, Type: ExpressionTokenString}, 2126 {Value: "gt", Depth: 4, Type: ExpressionTokenLogical}, 2127 {Value: "Price", Depth: 5, Type: ExpressionTokenLiteral}, 2128 {Value: "1.0", Depth: 5, Type: ExpressionTokenFloat}, 2129 {Value: "contains", Depth: 3, Type: ExpressionTokenFunc}, 2130 {Value: "/", Depth: 4, Type: ExpressionTokenNav}, 2131 {Value: "var", Depth: 5, Type: ExpressionTokenLiteral}, 2132 {Value: "Value", Depth: 5, Type: ExpressionTokenLiteral}, 2133 {Value: "'Smith'", Depth: 4, Type: ExpressionTokenString}, 2134 } 2135 pos := 0 2136 err = CompareTree(tree, expect, &pos, 0) 2137 if err != nil { 2138 t.Errorf("Tree representation does not match expected value. error: %v. Tree:\n%v", err, tree) 2139 } 2140 } 2141 2142 func TestExpressionTokenizerExists(t *testing.T) { 2143 ctx := context.Background() 2144 tokenizer := NewExpressionTokenizer() 2145 input := "exists(Name,false)" 2146 expect := []*Token{ 2147 {Value: "exists", Type: ExpressionTokenFunc}, 2148 {Value: "(", Type: ExpressionTokenOpenParen}, 2149 {Value: "Name", Type: ExpressionTokenLiteral}, 2150 {Value: ",", Type: ExpressionTokenComma}, 2151 {Value: "false", Type: ExpressionTokenBoolean}, 2152 {Value: ")", Type: ExpressionTokenCloseParen}, 2153 } 2154 output, err := tokenizer.Tokenize(ctx, input) 2155 if err != nil { 2156 t.Error(err) 2157 } 2158 2159 result, err := CompareTokens(expect, output) 2160 if !result { 2161 t.Error(err) 2162 } 2163 }