github.com/rohankumardubey/aresdb@v0.0.2-0.20190517170215-e54e3ca06b9c/query/aql_compiler_test.go (about) 1 // Copyright (c) 2017-2018 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package query 16 17 import ( 18 "github.com/onsi/ginkgo" 19 . "github.com/onsi/gomega" 20 21 "time" 22 "unsafe" 23 24 "github.com/uber-go/tally" 25 "github.com/uber/aresdb/common" 26 "github.com/uber/aresdb/memstore" 27 memCom "github.com/uber/aresdb/memstore/common" 28 "github.com/uber/aresdb/memstore/mocks" 29 metaCom "github.com/uber/aresdb/metastore/common" 30 "github.com/uber/aresdb/query/expr" 31 "github.com/uber/aresdb/utils" 32 ) 33 34 var _ = ginkgo.Describe("AQL compiler", func() { 35 ginkgo.It("parses timezone", func() { 36 qc := &AQLQueryContext{ 37 Query: &AQLQuery{Timezone: "timezone(city_id)"}, 38 } 39 utils.Init(common.AresServerConfig{Query: common.QueryConfig{TimezoneTable: common.TimezoneConfig{ 40 TableName: "api_cities", 41 }}}, common.NewLoggerFactory().GetDefaultLogger(), common.NewLoggerFactory().GetDefaultLogger(), tally.NewTestScope("test", nil)) 42 43 qc.processTimezone() 44 Ω(qc.Error).Should(BeNil()) 45 Ω(qc.timezoneTable.tableColumn).Should(Equal("timezone")) 46 Ω(qc.Query.Joins).Should(HaveLen(1)) 47 Ω(qc.fixedTimezone).Should(BeNil()) 48 }) 49 50 ginkgo.It("parses expressions", func() { 51 q := &AQLQuery{ 52 Table: "trips", 53 Measures: []Measure{ 54 { 55 Expr: "count(*)", 56 }, 57 }, 58 } 59 qc := &AQLQueryContext{ 60 Query: q, 61 } 62 qc.parseExprs() 63 Ω(qc.Error).Should(BeNil()) 64 Ω(q.Measures[0].expr).Should(Equal(&expr.Call{ 65 Name: "count", 66 Args: []expr.Expr{ 67 &expr.Wildcard{}, 68 }, 69 })) 70 71 qc.Query = &AQLQuery{ 72 Table: "trips", 73 Joins: []Join{ 74 { 75 Table: "api_cities", 76 Conditions: []string{ 77 "city_id = api_cities.id", 78 }, 79 }, 80 }, 81 Dimensions: []Dimension{ 82 { 83 Expr: "status", 84 }, 85 }, 86 Measures: []Measure{ 87 { 88 Expr: "count(*)", 89 Filters: []string{ 90 "not is_faresplit", 91 }, 92 }, 93 }, 94 Filters: []string{ 95 "marketplace='personal_transport'", 96 }, 97 } 98 qc.parseExprs() 99 Ω(qc.Error).Should(BeNil()) 100 Ω(qc.Query.Joins[0].conditions[0]).Should(Equal(&expr.BinaryExpr{ 101 Op: expr.EQ, 102 LHS: &expr.VarRef{Val: "city_id"}, 103 RHS: &expr.VarRef{Val: "api_cities.id"}, 104 })) 105 Ω(qc.Query.Dimensions[0].expr).Should(Equal(&expr.VarRef{Val: "status"})) 106 Ω(qc.Query.Measures[0].expr).Should(Equal(&expr.Call{ 107 Name: "count", 108 Args: []expr.Expr{ 109 &expr.Wildcard{}, 110 }, 111 })) 112 Ω(qc.Query.Measures[0].filters[0]).Should(Equal(&expr.UnaryExpr{ 113 Op: expr.NOT, 114 Expr: &expr.VarRef{Val: "is_faresplit"}, 115 })) 116 Ω(qc.Query.filters[0]).Should(Equal(&expr.BinaryExpr{ 117 Op: expr.EQ, 118 LHS: &expr.VarRef{Val: "marketplace"}, 119 RHS: &expr.StringLiteral{Val: "personal_transport"}, 120 })) 121 122 qc.Query = &AQLQuery{ 123 Table: "trips", 124 Measures: []Measure{ 125 { 126 Expr: "count(", 127 }, 128 }, 129 } 130 qc.parseExprs() 131 Ω(qc.Error).ShouldNot(BeNil()) 132 }) 133 134 ginkgo.It("reads schema", func() { 135 store := new(mocks.MemStore) 136 store.On("RLock").Return() 137 store.On("RUnlock").Return() 138 tripsSchema := &memstore.TableSchema{ 139 Schema: metaCom.Table{IsFactTable: true}, 140 } 141 apiCitiesSchema := &memstore.TableSchema{} 142 store.On("GetSchemas").Return(map[string]*memstore.TableSchema{ 143 "trips": tripsSchema, 144 "api_cities": apiCitiesSchema, 145 }) 146 qc := &AQLQueryContext{ 147 Query: &AQLQuery{ 148 Table: "trips", 149 Joins: []Join{ 150 {Table: "api_cities", Alias: "cts"}, 151 {Table: "trips", Alias: "tx"}, 152 }, 153 }, 154 } 155 qc.readSchema(store) 156 Ω(qc.Error).Should(BeNil()) 157 Ω(qc.TableIDByAlias).Should(Equal(map[string]int{ 158 "trips": 0, 159 "cts": 1, 160 "tx": 2, 161 })) 162 Ω(qc.TableScanners).Should(Equal([]*TableScanner{ 163 { 164 Schema: tripsSchema, 165 Shards: []int{0}, 166 ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}, 167 }, 168 { 169 Schema: apiCitiesSchema, 170 Shards: []int{0}, 171 ColumnUsages: map[int]columnUsage{}, 172 }, 173 { 174 Schema: tripsSchema, 175 Shards: []int{0}, 176 ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}, 177 }, 178 })) 179 Ω(qc.TableSchemaByName).Should(Equal(map[string]*memstore.TableSchema{ 180 "trips": tripsSchema, 181 "api_cities": apiCitiesSchema, 182 })) 183 qc.releaseSchema() 184 185 qc = &AQLQueryContext{ 186 Query: &AQLQuery{ 187 Table: "tripsy", 188 }, 189 } 190 qc.readSchema(store) 191 Ω(qc.Error).ShouldNot(BeNil()) 192 qc.releaseSchema() 193 194 qc = &AQLQueryContext{ 195 Query: &AQLQuery{ 196 Table: "trips", 197 Joins: []Join{ 198 {Table: "trips"}, 199 }, 200 }, 201 } 202 qc.readSchema(store) 203 Ω(qc.Error).ShouldNot(BeNil()) 204 qc.releaseSchema() 205 }) 206 207 ginkgo.It("numerical operations on column over 4 bytes long not supported", func() { 208 qc := &AQLQueryContext{ 209 TableIDByAlias: map[string]int{ 210 "trips": 0, 211 }, 212 TableScanners: []*TableScanner{ 213 { 214 Schema: &memstore.TableSchema{ 215 ValueTypeByColumn: []memCom.DataType{ 216 memCom.Int64, 217 }, 218 ColumnIDs: map[string]int{ 219 "hex_id": 0, 220 }, 221 Schema: metaCom.Table{ 222 Columns: []metaCom.Column{ 223 {Name: "hex_id", Type: metaCom.Int64}, 224 }, 225 }, 226 }, 227 }, 228 }, 229 } 230 qc.Query = &AQLQuery{ 231 Table: "trips", 232 Dimensions: []Dimension{ 233 {Expr: "hex_id"}, 234 }, 235 Measures: []Measure{ 236 {Expr: "count(*)"}, 237 }, 238 } 239 qc.parseExprs() 240 Ω(qc.Error).Should(BeNil()) 241 242 qc.resolveTypes() 243 Ω(qc.Error).Should(BeNil()) 244 245 qc.Query = &AQLQuery{ 246 Table: "trips", 247 Dimensions: []Dimension{ 248 {Expr: "hex_id+1"}, 249 }, 250 Measures: []Measure{ 251 {Expr: "count(*)"}, 252 }, 253 } 254 qc.parseExprs() 255 Ω(qc.Error).Should(BeNil()) 256 257 qc.resolveTypes() 258 Ω(qc.Error).ShouldNot(BeNil()) 259 Ω(qc.Error.Error()).Should(ContainSubstring("numeric operations not supported for column over 4 bytes length")) 260 }) 261 262 ginkgo.It("resolves data types", func() { 263 dict := map[string]int{ 264 "completed": 3, 265 } 266 qc := &AQLQueryContext{ 267 TableIDByAlias: map[string]int{ 268 "trips": 0, 269 }, 270 TableScanners: []*TableScanner{ 271 { 272 Schema: &memstore.TableSchema{ 273 ValueTypeByColumn: []memCom.DataType{ 274 memCom.Float32, 275 memCom.Uint16, 276 memCom.SmallEnum, 277 memCom.Bool, 278 }, 279 EnumDicts: map[string]memstore.EnumDict{ 280 "status": { 281 Dict: dict, 282 }, 283 }, 284 ColumnIDs: map[string]int{ 285 "fare": 0, 286 "city_id": 1, 287 "status": 2, 288 "is_first": 3, 289 }, 290 Schema: metaCom.Table{ 291 Columns: []metaCom.Column{ 292 {Name: "fare", Type: metaCom.Float32}, 293 {Name: "city_id", Type: metaCom.Uint16}, 294 {Name: "status", Type: metaCom.SmallEnum}, 295 {Name: "is_first", Type: metaCom.Bool}, 296 }, 297 }, 298 }, 299 }, 300 }, 301 } 302 qc.Query = &AQLQuery{ 303 Table: "trips", 304 Dimensions: []Dimension{ 305 {Expr: "-city_id"}, 306 {Expr: "~fare"}, 307 {Expr: "city_id-city_id"}, 308 {Expr: "city_id*fare"}, 309 {Expr: "1/2"}, 310 {Expr: "1.2|2.3"}, 311 {Expr: "case when 1.3 then 2 else 3.2 end"}, 312 }, 313 Measures: []Measure{ 314 {Expr: "count(*)"}, 315 {Expr: "Sum(fare+1)"}, 316 }, 317 Filters: []string{ 318 "status='completed'", 319 "!is_first", 320 "fare is not null", 321 "is_first is true", 322 "city_id is true", 323 "1.2 or 2.3", 324 "1 < 1.2", 325 "status='incompleted'", 326 "1 != 1.2", 327 "is_first = false", 328 }, 329 } 330 qc.parseExprs() 331 Ω(qc.Error).Should(BeNil()) 332 333 qc.resolveTypes() 334 Ω(qc.Error).Should(BeNil()) 335 336 Ω(qc.Query.Dimensions[0].expr.Type()).Should(Equal(expr.Signed)) 337 Ω(qc.Query.Dimensions[1].expr).Should(Equal(&expr.UnaryExpr{ 338 Op: expr.BITWISE_NOT, 339 ExprType: expr.Unsigned, 340 Expr: &expr.ParenExpr{ // explicit type casting 341 ExprType: expr.Unsigned, 342 Expr: &expr.VarRef{ 343 Val: "fare", 344 ExprType: expr.Float, 345 DataType: memCom.Float32, 346 }, 347 }, 348 })) 349 Ω(qc.Query.Dimensions[2].expr).Should(Equal(&expr.BinaryExpr{ 350 Op: expr.SUB, 351 ExprType: expr.Signed, 352 LHS: &expr.VarRef{ 353 Val: "city_id", 354 ColumnID: 1, 355 ExprType: expr.Unsigned, 356 DataType: memCom.Uint16, 357 }, 358 RHS: &expr.VarRef{ 359 Val: "city_id", 360 ColumnID: 1, 361 ExprType: expr.Unsigned, 362 DataType: memCom.Uint16, 363 }, 364 })) 365 Ω(qc.Query.Dimensions[3].expr).Should(Equal(&expr.BinaryExpr{ 366 Op: expr.MUL, 367 ExprType: expr.Float, 368 LHS: &expr.ParenExpr{ // explicit type casting 369 ExprType: expr.Float, 370 Expr: &expr.VarRef{ 371 Val: "city_id", 372 ColumnID: 1, 373 ExprType: expr.Unsigned, 374 DataType: memCom.Uint16, 375 }, 376 }, 377 RHS: &expr.VarRef{ 378 Val: "fare", 379 ExprType: expr.Float, 380 DataType: memCom.Float32, 381 }, 382 })) 383 Ω(qc.Query.Dimensions[4].expr).Should(Equal(&expr.BinaryExpr{ 384 Op: expr.DIV, 385 ExprType: expr.Float, 386 LHS: &expr.NumberLiteral{ 387 Val: 1, 388 Int: 1, 389 Expr: "1", 390 ExprType: expr.Float, 391 }, 392 RHS: &expr.NumberLiteral{ 393 Val: 2, 394 Int: 2, 395 Expr: "2", 396 ExprType: expr.Float, 397 }, 398 })) 399 Ω(qc.Query.Dimensions[5].expr).Should(Equal(&expr.BinaryExpr{ 400 Op: expr.BITWISE_OR, 401 ExprType: expr.Unsigned, 402 LHS: &expr.NumberLiteral{ 403 Val: 1.2, 404 Int: 1, 405 Expr: "1.2", 406 ExprType: expr.Unsigned, 407 }, 408 RHS: &expr.NumberLiteral{ 409 Val: 2.3, 410 Int: 2, 411 Expr: "2.3", 412 ExprType: expr.Unsigned, 413 }, 414 })) 415 Ω(qc.Query.Dimensions[6].expr).Should(Equal(&expr.Case{ 416 ExprType: expr.Float, 417 Else: &expr.NumberLiteral{ 418 Val: 3.2, 419 Int: 3, 420 Expr: "3.2", 421 ExprType: expr.Float, 422 }, 423 WhenThens: []expr.WhenThen{ 424 { 425 When: &expr.NumberLiteral{ 426 Val: 1.3, 427 Int: 1, 428 Expr: "1.3", 429 ExprType: expr.Boolean, 430 }, 431 Then: &expr.NumberLiteral{ 432 Val: 2, 433 Int: 2, 434 Expr: "2", 435 ExprType: expr.Float, 436 }, 437 }, 438 }, 439 })) 440 441 Ω(qc.Query.Measures[0].expr.Type()).Should(Equal(expr.Unsigned)) 442 Ω(qc.Query.Measures[1].expr).Should(Equal(&expr.Call{ 443 Name: "sum", 444 ExprType: expr.Float, 445 Args: []expr.Expr{ 446 &expr.BinaryExpr{ 447 Op: expr.ADD, 448 ExprType: expr.Float, 449 LHS: &expr.VarRef{ 450 Val: "fare", 451 ExprType: expr.Float, 452 DataType: memCom.Float32, 453 }, 454 RHS: &expr.NumberLiteral{ 455 Expr: "1", 456 Val: 1, 457 Int: 1, 458 ExprType: expr.Float, 459 }, 460 }, 461 }, 462 })) 463 464 Ω(qc.Query.filters[0]).Should(Equal(&expr.BinaryExpr{ 465 Op: expr.EQ, 466 ExprType: expr.Boolean, 467 LHS: &expr.VarRef{ 468 Val: "status", 469 ColumnID: 2, 470 ExprType: expr.Unsigned, 471 EnumDict: dict, 472 DataType: memCom.SmallEnum, 473 }, 474 RHS: &expr.NumberLiteral{Int: 3, ExprType: expr.Unsigned}, 475 })) 476 Ω(qc.Query.filters[1].Type()).Should(Equal(expr.Boolean)) 477 Ω(qc.Query.filters[1].(*expr.UnaryExpr).Op).Should(Equal(expr.NOT)) 478 Ω(qc.Query.filters[2].Type()).Should(Equal(expr.Boolean)) 479 Ω(qc.Query.filters[3]).Should(Equal(&expr.VarRef{ 480 Val: "is_first", 481 ColumnID: 3, 482 ExprType: expr.Boolean, 483 DataType: memCom.Bool, 484 })) 485 Ω(qc.Query.filters[4]).Should(Equal(&expr.UnaryExpr{ 486 Op: expr.NOT, 487 ExprType: expr.Boolean, 488 Expr: &expr.UnaryExpr{ 489 Op: expr.NOT, 490 ExprType: expr.Boolean, 491 Expr: &expr.VarRef{ 492 Val: "city_id", 493 ColumnID: 1, 494 ExprType: expr.Unsigned, 495 DataType: memCom.Uint16, 496 }, 497 }, 498 })) 499 Ω(qc.Query.filters[5]).Should(Equal(&expr.BinaryExpr{ 500 Op: expr.OR, 501 ExprType: expr.Boolean, 502 LHS: &expr.NumberLiteral{ 503 Val: 1.2, 504 Int: 1, 505 Expr: "1.2", 506 ExprType: expr.Boolean, 507 }, 508 RHS: &expr.NumberLiteral{ 509 Val: 2.3, 510 Int: 2, 511 Expr: "2.3", 512 ExprType: expr.Boolean, 513 }, 514 })) 515 Ω(qc.Query.filters[6]).Should(Equal(&expr.BinaryExpr{ 516 Op: expr.LT, 517 ExprType: expr.Boolean, 518 LHS: &expr.NumberLiteral{ 519 Val: 1, 520 Int: 1, 521 Expr: "1", 522 ExprType: expr.Float, 523 }, 524 RHS: &expr.NumberLiteral{ 525 Val: 1.2, 526 Int: 1, 527 Expr: "1.2", 528 ExprType: expr.Float, 529 }, 530 })) 531 Ω(qc.Query.filters[7]).Should(Equal(&expr.BinaryExpr{ 532 Op: expr.EQ, 533 ExprType: expr.Boolean, 534 LHS: &expr.VarRef{ 535 Val: "status", 536 ColumnID: 2, 537 ExprType: expr.Unsigned, 538 EnumDict: dict, 539 DataType: memCom.SmallEnum, 540 }, 541 RHS: &expr.NumberLiteral{Int: -1, ExprType: expr.Unsigned}, 542 })) 543 Ω(qc.Query.filters[8]).Should(Equal(&expr.BinaryExpr{ 544 Op: expr.NEQ, 545 ExprType: expr.Boolean, 546 LHS: &expr.NumberLiteral{ 547 Val: 1, 548 Int: 1, 549 Expr: "1", 550 ExprType: expr.Float, 551 }, 552 RHS: &expr.NumberLiteral{ 553 Val: 1.2, 554 Int: 1, 555 Expr: "1.2", 556 ExprType: expr.Float, 557 }, 558 })) 559 Ω(qc.Query.filters[9]).Should(Equal(&expr.UnaryExpr{ 560 Op: expr.NOT, 561 Expr: &expr.VarRef{Val: "is_first", 562 ColumnID: 3, 563 ExprType: expr.Boolean, 564 DataType: memCom.Bool}, 565 ExprType: expr.Boolean, 566 })) 567 }) 568 569 ginkgo.It("returns error on type resolution failure", func() { 570 qc := &AQLQueryContext{ 571 TableScanners: []*TableScanner{ 572 { 573 Schema: &memstore.TableSchema{}, 574 }, 575 }, 576 } 577 // column not found for main table 578 q := &AQLQuery{ 579 Table: "trips", 580 Measures: []Measure{ 581 {Expr: "sum(columnx)"}, 582 }, 583 } 584 qc.Query = q 585 qc.parseExprs() 586 587 qc.resolveTypes() 588 Ω(qc.Error).ShouldNot(BeNil()) 589 590 // table not found 591 qc.Query = &AQLQuery{ 592 Table: "trips", 593 Measures: []Measure{ 594 {Expr: "sum(tablex.columnx)"}, 595 }, 596 } 597 qc.parseExprs() 598 qc = &AQLQueryContext{ 599 Query: q, 600 } 601 qc.resolveTypes() 602 Ω(qc.Error).ShouldNot(BeNil()) 603 604 // column not found for the specified table 605 qc.Query = &AQLQuery{ 606 Table: "trips", 607 Measures: []Measure{ 608 {Expr: "sum(trips.columnx)"}, 609 }, 610 } 611 qc.parseExprs() 612 qc = &AQLQueryContext{ 613 Query: q, 614 TableScanners: []*TableScanner{ 615 { 616 Schema: &memstore.TableSchema{}, 617 }, 618 }, 619 TableIDByAlias: map[string]int{ 620 "trips": 0, 621 }, 622 } 623 qc.resolveTypes() 624 Ω(qc.Error).ShouldNot(BeNil()) 625 626 // column has been deleted 627 qc.Query = &AQLQuery{ 628 Table: "trips", 629 Measures: []Measure{ 630 {Expr: "sum(columnx)"}, 631 }, 632 } 633 qc.parseExprs() 634 qc = &AQLQueryContext{ 635 Query: q, 636 TableScanners: []*TableScanner{ 637 { 638 Schema: &memstore.TableSchema{ 639 ColumnIDs: map[string]int{ 640 "columnx": 0, 641 }, 642 Schema: metaCom.Table{ 643 Columns: []metaCom.Column{ 644 {Name: "columnx", Deleted: true}, 645 }, 646 }, 647 }, 648 }, 649 }, 650 } 651 qc.resolveTypes() 652 Ω(qc.Error).ShouldNot(BeNil()) 653 654 // too many arguments for sum 655 qc.Query = &AQLQuery{ 656 Table: "trips", 657 Measures: []Measure{ 658 {Expr: "sum(columnx, columnx)"}, 659 }, 660 } 661 qc.parseExprs() 662 qc = &AQLQueryContext{ 663 Query: q, 664 TableScanners: []*TableScanner{ 665 { 666 Schema: &memstore.TableSchema{ 667 ColumnIDs: map[string]int{ 668 "columnx": 0, 669 }, 670 Schema: metaCom.Table{ 671 Columns: []metaCom.Column{ 672 {Name: "columnx", Type: metaCom.Float32}, 673 }, 674 }, 675 }, 676 }, 677 }, 678 } 679 qc.resolveTypes() 680 Ω(qc.Error).ShouldNot(BeNil()) 681 682 // unknown function call 683 qc.Query = &AQLQuery{ 684 Table: "trips", 685 Measures: []Measure{ 686 {Expr: "exit()"}, 687 }, 688 } 689 qc.parseExprs() 690 qc = &AQLQueryContext{ 691 Query: q, 692 TableScanners: []*TableScanner{ 693 { 694 Schema: &memstore.TableSchema{}, 695 }, 696 }, 697 } 698 qc.resolveTypes() 699 Ω(qc.Error).ShouldNot(BeNil()) 700 }) 701 702 ginkgo.It("matches prefilters", func() { 703 schema := &memstore.TableSchema{ 704 ValueTypeByColumn: []memCom.DataType{ 705 memCom.Uint8, 706 memCom.Int16, 707 memCom.Bool, 708 memCom.Float32, 709 }, 710 ColumnIDs: map[string]int{ 711 "status": 0, 712 "city_id": 1, 713 "is_first": 2, 714 "fare": 3, 715 }, 716 Schema: metaCom.Table{ 717 Columns: []metaCom.Column{ 718 {Name: "status", Type: metaCom.Uint8}, 719 {Name: "city_id", Type: metaCom.Int16}, 720 {Name: "is_first", Type: metaCom.Bool}, 721 {Name: "fare", Type: metaCom.Float32}, 722 }, 723 ArchivingSortColumns: []int{1, 2, 0, 3}, 724 }, 725 } 726 727 qc := &AQLQueryContext{ 728 TableIDByAlias: map[string]int{ 729 "trips": 0, 730 }, 731 TableScanners: []*TableScanner{ 732 {Schema: schema}, 733 }, 734 } 735 736 // Unmatched 737 qc.Query = &AQLQuery{ 738 Table: "trips", 739 Measures: []Measure{ 740 {Expr: "count()"}, 741 }, 742 Filters: []string{ 743 "status=3", 744 }, 745 } 746 qc.parseExprs() 747 748 qc.resolveTypes() 749 qc.matchPrefilters() 750 Ω(qc.Error).Should(BeNil()) 751 Ω(qc.Prefilters).Should(BeNil()) 752 Ω(qc.TableScanners[0].EqualityPrefilterValues).Should(BeNil()) 753 Ω(qc.TableScanners[0].RangePrefilterBoundaries[0]).Should(Equal(noBoundary)) 754 Ω(qc.TableScanners[0].RangePrefilterBoundaries[1]).Should(Equal(noBoundary)) 755 756 // Matched one equality 757 qc = &AQLQueryContext{ 758 TableIDByAlias: map[string]int{ 759 "trips": 0, 760 }, 761 TableScanners: []*TableScanner{ 762 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 763 }, 764 } 765 qc.Query = &AQLQuery{ 766 Table: "trips", 767 Measures: []Measure{ 768 {Expr: "count()"}, 769 }, 770 Filters: []string{ 771 "status=3", 772 "is_first = true", 773 "city_id=12", 774 }, 775 } 776 qc.parseExprs() 777 778 qc.resolveTypes() 779 qc.matchPrefilters() 780 Ω(qc.Error).Should(BeNil()) 781 Ω(qc.Prefilters).Should(Equal([]int{2})) 782 Ω(qc.TableScanners[0].EqualityPrefilterValues).Should(Equal([]uint32{12})) 783 Ω(qc.TableScanners[0].RangePrefilterBoundaries[0]).Should(Equal(noBoundary)) 784 Ω(qc.TableScanners[0].RangePrefilterBoundaries[1]).Should(Equal(noBoundary)) 785 786 // Matched one range 787 qc = &AQLQueryContext{ 788 TableIDByAlias: map[string]int{ 789 "trips": 0, 790 }, 791 TableScanners: []*TableScanner{ 792 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 793 }, 794 } 795 qc.Query = &AQLQuery{ 796 Table: "trips", 797 Measures: []Measure{ 798 {Expr: "count()"}, 799 }, 800 Filters: []string{ 801 "is_first", 802 "city_id>=12", 803 "city_id<16", 804 }, 805 } 806 qc.parseExprs() 807 808 qc.resolveTypes() 809 qc.matchPrefilters() 810 Ω(qc.Error).Should(BeNil()) 811 Ω(qc.Prefilters).Should(Equal([]int{1, 2})) 812 Ω(qc.TableScanners[0].EqualityPrefilterValues).Should(BeNil()) 813 Ω(qc.TableScanners[0].RangePrefilterBoundaries[0]).Should( 814 Equal(inclusiveBoundary)) 815 Ω(qc.TableScanners[0].RangePrefilterBoundaries[1]).Should( 816 Equal(exclusiveBoundary)) 817 Ω(qc.TableScanners[0].RangePrefilterValues[0]).Should(Equal(uint32(12))) 818 Ω(qc.TableScanners[0].RangePrefilterValues[1]).Should(Equal(uint32(16))) 819 820 // Matched two equalities 821 qc = &AQLQueryContext{ 822 TableIDByAlias: map[string]int{ 823 "trips": 0, 824 }, 825 TableScanners: []*TableScanner{ 826 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 827 }, 828 } 829 qc.Query = &AQLQuery{ 830 Table: "trips", 831 Measures: []Measure{ 832 {Expr: "count()"}, 833 }, 834 Filters: []string{ 835 "is_first", 836 "city_id=12", 837 "status!=2", 838 "2=status", 839 }, 840 } 841 qc.parseExprs() 842 843 qc.resolveTypes() 844 qc.matchPrefilters() 845 Ω(qc.Error).Should(BeNil()) 846 Ω(qc.Prefilters).Should(Equal([]int{0, 1, 3})) 847 Ω(qc.TableScanners[0].EqualityPrefilterValues).Should(Equal([]uint32{12, 1, 2})) 848 Ω(qc.TableScanners[0].RangePrefilterBoundaries[0]).Should(Equal(noBoundary)) 849 Ω(qc.TableScanners[0].RangePrefilterBoundaries[1]).Should(Equal(noBoundary)) 850 851 // Matched two equalities and one range 852 qc = &AQLQueryContext{ 853 TableIDByAlias: map[string]int{ 854 "trips": 0, 855 }, 856 TableScanners: []*TableScanner{ 857 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 858 }, 859 } 860 qc.Query = &AQLQuery{ 861 Table: "trips", 862 Measures: []Measure{ 863 {Expr: "count()"}, 864 }, 865 Filters: []string{ 866 "not is_first", 867 "city_id=12", 868 "status<10", 869 }, 870 } 871 qc.parseExprs() 872 873 qc.resolveTypes() 874 qc.matchPrefilters() 875 Ω(qc.Error).Should(BeNil()) 876 Ω(qc.Prefilters).Should(Equal([]int{0, 1, 2})) 877 Ω(qc.TableScanners[0].EqualityPrefilterValues).Should(Equal([]uint32{12, 0})) 878 Ω(qc.TableScanners[0].RangePrefilterBoundaries[0]).Should(Equal(noBoundary)) 879 Ω(qc.TableScanners[0].RangePrefilterBoundaries[1]).Should( 880 Equal(exclusiveBoundary)) 881 Ω(qc.TableScanners[0].RangePrefilterValues[1]).Should(Equal(uint32(10))) 882 883 // Matched four equalities 884 qc = &AQLQueryContext{ 885 TableIDByAlias: map[string]int{ 886 "trips": 0, 887 }, 888 TableScanners: []*TableScanner{ 889 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 890 }, 891 } 892 qc.Query = &AQLQuery{ 893 Table: "trips", 894 Measures: []Measure{ 895 {Expr: "count()"}, 896 }, 897 Filters: []string{ 898 "not is_first", 899 "city_id=12", 900 "status=10", 901 "fare=8", 902 }, 903 } 904 qc.parseExprs() 905 906 qc.resolveTypes() 907 qc.matchPrefilters() 908 Ω(qc.Error).Should(BeNil()) 909 Ω(qc.Prefilters).Should(Equal([]int{0, 1, 2, 3})) 910 var f float32 = 8 911 Ω(qc.TableScanners[0].EqualityPrefilterValues).Should( 912 Equal([]uint32{12, 0, 10, *(*uint32)(unsafe.Pointer(&f))})) 913 Ω(qc.TableScanners[0].RangePrefilterBoundaries[0]).Should(Equal(noBoundary)) 914 Ω(qc.TableScanners[0].RangePrefilterBoundaries[1]).Should(Equal(noBoundary)) 915 }) 916 917 ginkgo.It("normalizes filters", func() { 918 Ω(normalizeAndFilters(nil)).Should(BeNil()) 919 920 filters := []expr.Expr{ 921 &expr.VarRef{Val: "city_id"}, 922 } 923 Ω(normalizeAndFilters(filters)).Should(Equal(filters)) 924 925 filters = []expr.Expr{ 926 &expr.BinaryExpr{ 927 Op: expr.AND, 928 LHS: &expr.VarRef{Val: "is_first"}, 929 RHS: &expr.VarRef{Val: "is_last"}, 930 }, 931 } 932 Ω(normalizeAndFilters(filters)).Should(Equal([]expr.Expr{ 933 &expr.VarRef{Val: "is_first"}, 934 &expr.VarRef{Val: "is_last"}, 935 })) 936 937 filters = []expr.Expr{ 938 &expr.BinaryExpr{ 939 Op: expr.AND, 940 LHS: &expr.BinaryExpr{ 941 Op: expr.AND, 942 LHS: &expr.VarRef{Val: "a"}, 943 RHS: &expr.VarRef{Val: "b"}, 944 }, 945 RHS: &expr.VarRef{Val: "is_last"}, 946 }, 947 } 948 Ω(normalizeAndFilters(filters)).Should(Equal([]expr.Expr{ 949 &expr.VarRef{Val: "a"}, 950 &expr.VarRef{Val: "is_last"}, 951 &expr.VarRef{Val: "b"}, 952 })) 953 }) 954 955 ginkgo.It("processes common filters and prefilters", func() { 956 schema := &memstore.TableSchema{ 957 ValueTypeByColumn: []memCom.DataType{ 958 memCom.Uint8, 959 memCom.Int16, 960 memCom.Bool, 961 memCom.Float32, 962 }, 963 ColumnIDs: map[string]int{ 964 "status": 0, 965 "city_id": 1, 966 "is_first": 2, 967 "fare": 3, 968 }, 969 Schema: metaCom.Table{ 970 Columns: []metaCom.Column{ 971 {Name: "status", Type: metaCom.Uint8}, 972 {Name: "city_id", Type: metaCom.Int16}, 973 {Name: "is_first", Type: metaCom.Bool}, 974 {Name: "fare", Type: metaCom.Float32}, 975 }, 976 ArchivingSortColumns: []int{1, 2, 0, 3}, 977 }, 978 } 979 980 qc := &AQLQueryContext{ 981 TableIDByAlias: map[string]int{ 982 "trips": 0, 983 }, 984 TableScanners: []*TableScanner{ 985 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 986 }, 987 } 988 qc.Query = &AQLQuery{ 989 Table: "trips", 990 Measures: []Measure{ 991 {Expr: "count()", Filters: []string{"status"}}, 992 }, 993 Filters: []string{ 994 "is_first", 995 "city_id>=12", 996 "city_id<16", 997 }, 998 } 999 qc.processTimezone() 1000 qc.parseExprs() 1001 1002 qc.resolveTypes() 1003 qc.matchPrefilters() 1004 Ω(qc.Error).Should(BeNil()) 1005 Ω(qc.Prefilters).Should(Equal([]int{1, 2})) 1006 1007 qc.processFilters() 1008 Ω(qc.Error).Should(BeNil()) 1009 Ω(qc.OOPK.MainTableCommonFilters).Should(Equal([]expr.Expr{ 1010 &expr.VarRef{Val: "status", ExprType: expr.Unsigned, ColumnID: 0, DataType: memCom.Uint8}, 1011 &expr.VarRef{Val: "is_first", ExprType: expr.Boolean, ColumnID: 2, DataType: memCom.Bool}, 1012 })) 1013 Ω(qc.OOPK.Prefilters).Should(Equal([]expr.Expr{ 1014 &expr.BinaryExpr{ 1015 ExprType: expr.Boolean, 1016 Op: expr.GTE, 1017 LHS: &expr.VarRef{Val: "city_id", ExprType: expr.Signed, ColumnID: 1, DataType: memCom.Int16}, 1018 RHS: &expr.NumberLiteral{Val: 12, Int: 12, Expr: "12", ExprType: expr.Unsigned}, 1019 }, 1020 &expr.BinaryExpr{ 1021 ExprType: expr.Boolean, 1022 Op: expr.LT, 1023 LHS: &expr.VarRef{Val: "city_id", ExprType: expr.Signed, ColumnID: 1, DataType: memCom.Int16}, 1024 RHS: &expr.NumberLiteral{Val: 16, Int: 16, Expr: "16", ExprType: expr.Unsigned}, 1025 }, 1026 })) 1027 1028 Ω(qc.TableScanners[0].ColumnUsages).Should(Equal(map[int]columnUsage{ 1029 0: columnUsedByAllBatches, 1030 1: columnUsedByLiveBatches | columnUsedByPrefilter, 1031 2: columnUsedByAllBatches, 1032 })) 1033 1034 qc.Query = &AQLQuery{ 1035 Table: "trips", 1036 Measures: []Measure{ 1037 {Expr: "count()", Filters: []string{"status"}}, 1038 }, 1039 Filters: []string{ 1040 "city_id<'a string'", 1041 }, 1042 } 1043 qc.processTimezone() 1044 qc.parseExprs() 1045 1046 qc.resolveTypes() 1047 Ω(qc.Error.Error()).Should(ContainSubstring("string type only support EQ and NEQ operators")) 1048 }) 1049 1050 ginkgo.It("processes matched time filters", func() { 1051 table := metaCom.Table{ 1052 IsFactTable: true, 1053 Columns: []metaCom.Column{ 1054 {Name: "request_at", Type: metaCom.Uint32}, 1055 }, 1056 } 1057 1058 schema := memstore.NewTableSchema(&table) 1059 1060 q := &AQLQuery{ 1061 Table: "trips", 1062 Measures: []Measure{ 1063 {Expr: "count()"}, 1064 }, 1065 Dimensions: []Dimension{Dimension{Expr: "request_at", TimeBucketizer: "week"}}, 1066 TimeFilter: TimeFilter{ 1067 From: "-1d", 1068 To: "0d", 1069 }, 1070 } 1071 qc := &AQLQueryContext{ 1072 Query: q, 1073 TableIDByAlias: map[string]int{ 1074 "trips": 0, 1075 }, 1076 TableScanners: []*TableScanner{ 1077 {Schema: schema, ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}}, 1078 }, 1079 } 1080 utils.SetClockImplementation(func() time.Time { 1081 return time.Date(2017, 9, 20, 16, 51, 0, 0, time.UTC) 1082 }) 1083 qc.processTimezone() 1084 qc.parseExprs() 1085 qc.processFilters() 1086 Ω(qc.Error).Should(BeNil()) 1087 Ω(qc.OOPK.TimeFilters[0]).Should(Equal(&expr.BinaryExpr{ 1088 ExprType: expr.Boolean, 1089 Op: expr.GTE, 1090 LHS: &expr.VarRef{ 1091 Val: "request_at", 1092 ExprType: expr.Unsigned, 1093 DataType: memCom.Uint32, 1094 }, 1095 RHS: &expr.NumberLiteral{ 1096 ExprType: expr.Unsigned, 1097 Int: 1505779200, 1098 Expr: "1505779200", 1099 }, 1100 })) 1101 Ω(qc.OOPK.TimeFilters[1]).Should(Equal(&expr.BinaryExpr{ 1102 ExprType: expr.Boolean, 1103 Op: expr.LT, 1104 LHS: &expr.VarRef{ 1105 Val: "request_at", 1106 ExprType: expr.Unsigned, 1107 DataType: memCom.Uint32, 1108 }, 1109 RHS: &expr.NumberLiteral{ 1110 ExprType: expr.Unsigned, 1111 Int: 1505952000, 1112 Expr: "1505952000", 1113 }, 1114 })) 1115 Ω(qc.TableScanners[0].ColumnUsages).Should(Equal(map[int]columnUsage{ 1116 0: columnUsedByLiveBatches | columnUsedByFirstArchiveBatch | columnUsedByLastArchiveBatch, 1117 })) 1118 Ω(qc.TableScanners[0].ArchiveBatchIDStart).Should(Equal(17428)) 1119 Ω(qc.TableScanners[0].ArchiveBatchIDEnd).Should(Equal(17430)) 1120 1121 // filter with {table}.{column} format 1122 q = &AQLQuery{ 1123 Table: "trips", 1124 Measures: []Measure{ 1125 {Expr: "count()"}, 1126 }, 1127 Dimensions: []Dimension{Dimension{Expr: "request_at", TimeBucketizer: "week"}}, 1128 TimeFilter: TimeFilter{ 1129 Column: "trips.request_at", 1130 From: "-1d", 1131 To: "0d", 1132 }, 1133 } 1134 qc = &AQLQueryContext{ 1135 Query: q, 1136 TableIDByAlias: map[string]int{ 1137 "trips": 0, 1138 }, 1139 TableScanners: []*TableScanner{ 1140 {Schema: schema, ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}}, 1141 }, 1142 } 1143 1144 qc.processTimezone() 1145 qc.parseExprs() 1146 qc.processFilters() 1147 Ω(qc.Error).Should(BeNil()) 1148 Ω(qc.OOPK.TimeFilters[0]).Should(Equal(&expr.BinaryExpr{ 1149 ExprType: expr.Boolean, 1150 Op: expr.GTE, 1151 LHS: &expr.VarRef{ 1152 Val: "request_at", 1153 ExprType: expr.Unsigned, 1154 DataType: memCom.Uint32, 1155 }, 1156 RHS: &expr.NumberLiteral{ 1157 ExprType: expr.Unsigned, 1158 Int: 1505779200, 1159 Expr: "1505779200", 1160 }, 1161 })) 1162 Ω(qc.OOPK.TimeFilters[1]).Should(Equal(&expr.BinaryExpr{ 1163 ExprType: expr.Boolean, 1164 Op: expr.LT, 1165 LHS: &expr.VarRef{ 1166 Val: "request_at", 1167 ExprType: expr.Unsigned, 1168 DataType: memCom.Uint32, 1169 }, 1170 RHS: &expr.NumberLiteral{ 1171 ExprType: expr.Unsigned, 1172 Int: 1505952000, 1173 Expr: "1505952000", 1174 }, 1175 })) 1176 Ω(qc.TableScanners[0].ColumnUsages).Should(Equal(map[int]columnUsage{ 1177 0: columnUsedByLiveBatches | columnUsedByFirstArchiveBatch | columnUsedByLastArchiveBatch, 1178 })) 1179 Ω(qc.TableScanners[0].ArchiveBatchIDStart).Should(Equal(17428)) 1180 Ω(qc.TableScanners[0].ArchiveBatchIDEnd).Should(Equal(17430)) 1181 }) 1182 1183 ginkgo.It("processes unmatched time filters", func() { 1184 table := metaCom.Table{ 1185 Columns: []metaCom.Column{ 1186 {Name: "uuid", Type: metaCom.UUID}, 1187 {Name: "request_at", Type: metaCom.Uint32}, 1188 }, 1189 IsFactTable: false, 1190 } 1191 schema := memstore.NewTableSchema(&table) 1192 q := &AQLQuery{ 1193 Table: "trips", 1194 Measures: []Measure{ 1195 {Expr: "count()"}, 1196 }, 1197 Dimensions: []Dimension{Dimension{Expr: "request_at", TimeBucketizer: "week"}}, 1198 TimeFilter: TimeFilter{ 1199 Column: "request_at", 1200 From: "-1d", 1201 To: "0d", 1202 }, 1203 } 1204 qc := &AQLQueryContext{ 1205 Query: q, 1206 TableIDByAlias: map[string]int{ 1207 "trips": 0, 1208 }, 1209 TableScanners: []*TableScanner{ 1210 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 1211 }, 1212 } 1213 utils.SetClockImplementation(func() time.Time { 1214 return time.Date(2017, 9, 20, 16, 51, 0, 0, time.UTC) 1215 }) 1216 qc.processTimezone() 1217 qc.parseExprs() 1218 qc.processFilters() 1219 Ω(qc.Error).Should(BeNil()) 1220 Ω(qc.OOPK.TimeFilters[0]).Should(BeNil()) 1221 Ω(qc.OOPK.TimeFilters[1]).Should(BeNil()) 1222 Ω(qc.OOPK.MainTableCommonFilters[0]).Should(Equal(&expr.BinaryExpr{ 1223 ExprType: expr.Boolean, 1224 Op: expr.GTE, 1225 LHS: &expr.VarRef{ 1226 Val: "request_at", 1227 ColumnID: 1, 1228 ExprType: expr.Unsigned, 1229 DataType: memCom.Uint32, 1230 }, 1231 RHS: &expr.NumberLiteral{ 1232 ExprType: expr.Unsigned, 1233 Int: 1505779200, 1234 Expr: "1505779200", 1235 }, 1236 })) 1237 Ω(qc.OOPK.MainTableCommonFilters[1]).Should(Equal(&expr.BinaryExpr{ 1238 ExprType: expr.Boolean, 1239 Op: expr.LT, 1240 LHS: &expr.VarRef{ 1241 Val: "request_at", 1242 ExprType: expr.Unsigned, 1243 ColumnID: 1, 1244 DataType: memCom.Uint32, 1245 }, 1246 RHS: &expr.NumberLiteral{ 1247 ExprType: expr.Unsigned, 1248 Int: 1505952000, 1249 Expr: "1505952000", 1250 }, 1251 })) 1252 Ω(qc.TableScanners[0].ArchiveBatchIDStart).Should(Equal(0)) 1253 Ω(qc.TableScanners[0].ArchiveBatchIDEnd).Should(Equal(17430)) 1254 1255 // filter with {table}.{column} format 1256 q = &AQLQuery{ 1257 Table: "trips", 1258 Measures: []Measure{ 1259 {Expr: "count()"}, 1260 }, 1261 Dimensions: []Dimension{Dimension{Expr: "request_at", TimeBucketizer: "week"}}, 1262 TimeFilter: TimeFilter{ 1263 Column: "trips.request_at", 1264 From: "-1d", 1265 To: "0d", 1266 }, 1267 } 1268 qc = &AQLQueryContext{ 1269 Query: q, 1270 TableIDByAlias: map[string]int{ 1271 "trips": 0, 1272 }, 1273 TableScanners: []*TableScanner{ 1274 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 1275 }, 1276 } 1277 qc.processTimezone() 1278 qc.parseExprs() 1279 qc.processFilters() 1280 Ω(qc.Error).Should(BeNil()) 1281 Ω(qc.OOPK.TimeFilters[0]).Should(BeNil()) 1282 Ω(qc.OOPK.TimeFilters[1]).Should(BeNil()) 1283 Ω(qc.OOPK.MainTableCommonFilters[0]).Should(Equal(&expr.BinaryExpr{ 1284 ExprType: expr.Boolean, 1285 Op: expr.GTE, 1286 LHS: &expr.VarRef{ 1287 Val: "request_at", 1288 ExprType: expr.Unsigned, 1289 ColumnID: 1, 1290 DataType: memCom.Uint32, 1291 }, 1292 RHS: &expr.NumberLiteral{ 1293 ExprType: expr.Unsigned, 1294 Int: 1505779200, 1295 Expr: "1505779200", 1296 }, 1297 })) 1298 Ω(qc.OOPK.MainTableCommonFilters[1]).Should(Equal(&expr.BinaryExpr{ 1299 ExprType: expr.Boolean, 1300 Op: expr.LT, 1301 LHS: &expr.VarRef{ 1302 Val: "request_at", 1303 ExprType: expr.Unsigned, 1304 ColumnID: 1, 1305 DataType: memCom.Uint32, 1306 }, 1307 RHS: &expr.NumberLiteral{ 1308 ExprType: expr.Unsigned, 1309 Int: 1505952000, 1310 Expr: "1505952000", 1311 }, 1312 })) 1313 Ω(qc.TableScanners[0].ArchiveBatchIDStart).Should(Equal(0)) 1314 Ω(qc.TableScanners[0].ArchiveBatchIDEnd).Should(Equal(17430)) 1315 }) 1316 1317 ginkgo.It("'from' should be defined for time filters", func() { 1318 schema := &memstore.TableSchema{ 1319 ColumnIDs: map[string]int{ 1320 "request_at": 0, 1321 }, 1322 Schema: metaCom.Table{ 1323 IsFactTable: true, 1324 Columns: []metaCom.Column{ 1325 {Name: "request_at", Type: metaCom.Uint32}, 1326 }, 1327 }, 1328 } 1329 q := &AQLQuery{ 1330 Table: "trips", 1331 Measures: []Measure{ 1332 {Expr: "count()"}, 1333 }, 1334 Dimensions: []Dimension{Dimension{Expr: "request_at", TimeBucketizer: "week"}}, 1335 TimeFilter: TimeFilter{ 1336 To: "now", 1337 }, 1338 } 1339 qc := &AQLQueryContext{ 1340 Query: q, 1341 TableIDByAlias: map[string]int{ 1342 "trips": 0, 1343 }, 1344 TableScanners: []*TableScanner{ 1345 {Schema: schema, ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}}, 1346 }, 1347 } 1348 qc.processTimezone() 1349 qc.parseExprs() 1350 qc.processFilters() 1351 Ω(qc.Error).ShouldNot(BeNil()) 1352 }) 1353 1354 ginkgo.It("processes measure and dimensions", func() { 1355 1356 table := metaCom.Table{ 1357 Columns: []metaCom.Column{ 1358 {Name: "status", Type: metaCom.Uint8}, 1359 {Name: "city_id", Type: metaCom.Uint16}, 1360 {Name: "is_first", Type: metaCom.Bool}, 1361 {Name: "fare", Type: metaCom.Float32}, 1362 {Name: "request_at", Type: metaCom.Uint32}, 1363 }, 1364 } 1365 schema := memstore.NewTableSchema(&table) 1366 1367 qc := &AQLQueryContext{ 1368 TableIDByAlias: map[string]int{ 1369 "trips": 0, 1370 }, 1371 TableScanners: []*TableScanner{ 1372 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 1373 }, 1374 } 1375 qc.Query = &AQLQuery{ 1376 Table: "trips", 1377 Measures: []Measure{ 1378 {Expr: "sum(fare)"}, 1379 }, 1380 Dimensions: []Dimension{ 1381 {Expr: "city_id"}, 1382 {Expr: "request_at", TimeBucketizer: "m"}, 1383 }, 1384 } 1385 qc.parseExprs() 1386 Ω(qc.Error).Should(BeNil()) 1387 qc.resolveTypes() 1388 Ω(qc.Error).Should(BeNil()) 1389 qc.processMeasure() 1390 qc.processDimensions() 1391 Ω(qc.Error).Should(BeNil()) 1392 1393 Ω(qc.OOPK.Measure).Should(Equal(&expr.VarRef{ 1394 Val: "fare", 1395 ColumnID: 3, 1396 ExprType: expr.Float, 1397 DataType: memCom.Float32, 1398 })) 1399 Ω(qc.OOPK.Dimensions).Should(Equal([]expr.Expr{ 1400 &expr.VarRef{ 1401 Val: "city_id", 1402 ColumnID: 1, 1403 ExprType: expr.Unsigned, 1404 DataType: memCom.Uint16, 1405 }, 1406 &expr.BinaryExpr{ 1407 Op: expr.FLOOR, 1408 LHS: &expr.VarRef{ 1409 Val: "request_at", 1410 ColumnID: 4, 1411 ExprType: expr.Unsigned, 1412 DataType: memCom.Uint32, 1413 }, 1414 RHS: &expr.NumberLiteral{ 1415 Int: 60, 1416 Expr: "60", 1417 ExprType: expr.Unsigned, 1418 }, 1419 ExprType: expr.Unsigned, 1420 }, 1421 })) 1422 Ω(qc.TableScanners[0].ColumnUsages).Should(Equal(map[int]columnUsage{ 1423 1: columnUsedByAllBatches, 1424 3: columnUsedByAllBatches, 1425 4: columnUsedByAllBatches, 1426 })) 1427 }) 1428 1429 ginkgo.It("process dimensions non agg", func() { 1430 table := metaCom.Table{ 1431 Columns: []metaCom.Column{ 1432 {Name: "status", Type: metaCom.Uint8}, 1433 {Name: "city_id", Type: metaCom.Uint16}, 1434 {Name: "is_first", Type: metaCom.Bool}, 1435 {Name: "fare", Type: metaCom.Float32}, 1436 {Name: "request_at", Type: metaCom.Uint32}, 1437 }, 1438 } 1439 schema := memstore.NewTableSchema(&table) 1440 1441 qc := &AQLQueryContext{ 1442 TableIDByAlias: map[string]int{ 1443 "trips": 0, 1444 }, 1445 TableScanners: []*TableScanner{ 1446 {Schema: schema, ColumnUsages: map[int]columnUsage{}}, 1447 }, 1448 } 1449 qc.Query = &AQLQuery{ 1450 Table: "trips", 1451 Measures: []Measure{ 1452 {Expr: "1"}, 1453 }, 1454 Dimensions: []Dimension{ 1455 {Expr: "city_id"}, 1456 {Expr: "request_at"}, 1457 {Expr: "*"}, 1458 }, 1459 } 1460 qc.parseExprs() 1461 Ω(qc.Query.Dimensions).Should(HaveLen(7)) 1462 Ω(qc.Error).Should(BeNil()) 1463 qc.resolveTypes() 1464 Ω(qc.Error).Should(BeNil()) 1465 qc.processMeasure() 1466 Ω(qc.isNonAggregationQuery).Should(BeTrue()) 1467 qc.processDimensions() 1468 Ω(qc.OOPK.Dimensions).Should(HaveLen(7)) 1469 }) 1470 1471 ginkgo.It("sorts used columns", func() { 1472 schema := &memstore.TableSchema{ 1473 Schema: metaCom.Table{ 1474 ArchivingSortColumns: []int{1, 3, 5}, 1475 }, 1476 } 1477 1478 qc := &AQLQueryContext{ 1479 TableScanners: []*TableScanner{ 1480 { 1481 Schema: schema, 1482 ColumnUsages: map[int]columnUsage{ 1483 1: columnUsedByAllBatches, 1484 2: columnUsedByAllBatches, 1485 3: columnUsedByAllBatches, 1486 }, 1487 }, 1488 }, 1489 } 1490 1491 qc.sortUsedColumns() 1492 Ω(qc.TableScanners[0].Columns).Should(Equal([]int{2, 3, 1})) 1493 }) 1494 1495 ginkgo.It("sort dimension columns", func() { 1496 qc := &AQLQueryContext{ 1497 OOPK: OOPKContext{ 1498 Dimensions: []expr.Expr{ 1499 &expr.VarRef{ 1500 Val: "a", 1501 DataType: memCom.Uint16, 1502 }, 1503 &expr.VarRef{ 1504 Val: "b", 1505 DataType: memCom.Uint32, 1506 }, 1507 &expr.VarRef{ 1508 Val: "c", 1509 DataType: memCom.Bool, 1510 }, 1511 &expr.Call{ 1512 Name: "d", 1513 }, 1514 }, 1515 }, 1516 } 1517 qc.sortDimensionColumns() 1518 Ω(qc.OOPK.DimensionVectorIndex).Should(Equal([]int{2, 0, 3, 1})) 1519 Ω(qc.OOPK.DimRowBytes).Should(Equal(15)) 1520 }) 1521 1522 ginkgo.It("processJoinConditions", func() { 1523 tripsSchema := &memstore.TableSchema{ 1524 ColumnIDs: map[string]int{ 1525 "request_at": 0, 1526 "city_id": 1, 1527 }, 1528 Schema: metaCom.Table{ 1529 Name: "trips", 1530 IsFactTable: true, 1531 Columns: []metaCom.Column{ 1532 {Name: "request_at", Type: metaCom.Uint32}, 1533 {Name: "city_id", Type: metaCom.Uint16}, 1534 }, 1535 }, 1536 ValueTypeByColumn: []memCom.DataType{ 1537 memCom.Uint32, 1538 memCom.Uint16, 1539 }, 1540 } 1541 1542 badJoinSchema := &memstore.TableSchema{ 1543 ColumnIDs: map[string]int{ 1544 "id": 0, 1545 }, 1546 Schema: metaCom.Table{ 1547 Name: "bad_table", 1548 IsFactTable: true, 1549 Columns: []metaCom.Column{ 1550 {Name: "id", Type: metaCom.Uint32}, 1551 }, 1552 PrimaryKeyColumns: []int{0}, 1553 }, 1554 ValueTypeByColumn: []memCom.DataType{ 1555 memCom.Uint32, 1556 }, 1557 } 1558 1559 apiCitySchema := &memstore.TableSchema{ 1560 ColumnIDs: map[string]int{ 1561 "id": 0, 1562 }, 1563 Schema: metaCom.Table{ 1564 Name: "api_cities", 1565 IsFactTable: false, 1566 Columns: []metaCom.Column{ 1567 {Name: "id", Type: metaCom.Uint32}, 1568 }, 1569 PrimaryKeyColumns: []int{0}, 1570 }, 1571 ValueTypeByColumn: []memCom.DataType{ 1572 memCom.Uint32, 1573 }, 1574 } 1575 1576 qc := &AQLQueryContext{} 1577 goodQuery := &AQLQuery{ 1578 Table: "trips", 1579 Measures: []Measure{ 1580 {Expr: "count()"}, 1581 }, 1582 Joins: []Join{ 1583 { 1584 Table: "api_cities", 1585 Alias: "", 1586 Conditions: []string{ 1587 "trips.city_id = api_cities.id", 1588 }, 1589 }, 1590 }, 1591 } 1592 qc.Query = goodQuery 1593 qc.parseExprs() 1594 Ω(qc.Error).Should(BeNil()) 1595 1596 qc = &AQLQueryContext{ 1597 Query: goodQuery, 1598 TableSchemaByName: map[string]*memstore.TableSchema{ 1599 "trips": tripsSchema, 1600 "api_cities": apiCitySchema, 1601 "bad_table": badJoinSchema, 1602 }, 1603 TableIDByAlias: map[string]int{ 1604 "trips": 0, 1605 "api_cities": 1, 1606 "bad_table": 2, 1607 }, 1608 TableScanners: []*TableScanner{ 1609 {Schema: tripsSchema, ColumnUsages: make(map[int]columnUsage)}, 1610 {Schema: apiCitySchema, ColumnUsages: make(map[int]columnUsage)}, 1611 {Schema: badJoinSchema, ColumnUsages: make(map[int]columnUsage)}, 1612 }, 1613 } 1614 qc.resolveTypes() 1615 qc.processJoinConditions() 1616 Ω(qc.Error).Should(BeNil()) 1617 1618 Ω(qc.TableScanners[0].ColumnUsages[1]).Should(Equal(columnUsedByAllBatches)) 1619 1620 // non dimension table join 1621 badQuery := &AQLQuery{ 1622 Table: "trips", 1623 Measures: []Measure{ 1624 {Expr: "count()"}, 1625 }, 1626 Joins: []Join{ 1627 { 1628 Table: "bad_table", 1629 Alias: "", 1630 Conditions: []string{ 1631 "trips.city_id > bad_table.id", 1632 }, 1633 }, 1634 }, 1635 } 1636 qc.Query = badQuery 1637 qc.parseExprs() 1638 qc.Error = nil 1639 qc.resolveTypes() 1640 qc.processJoinConditions() 1641 Ω(qc.Error).ShouldNot(BeNil()) 1642 1643 // not equal join 1644 qc.Query = &AQLQuery{ 1645 Table: "trips", 1646 Measures: []Measure{ 1647 {Expr: "count()"}, 1648 }, 1649 Joins: []Join{ 1650 { 1651 Table: "api_cities", 1652 Alias: "", 1653 Conditions: []string{ 1654 "trips.city_id > api_cities.id", 1655 }, 1656 }, 1657 }, 1658 } 1659 qc.parseExprs() 1660 qc.Error = nil 1661 qc.resolveTypes() 1662 qc.processJoinConditions() 1663 Ω(qc.Error).ShouldNot(BeNil()) 1664 1665 // more than one join condition specified 1666 badQuery = &AQLQuery{ 1667 Table: "trips", 1668 Measures: []Measure{ 1669 {Expr: "count()"}, 1670 }, 1671 Joins: []Join{ 1672 { 1673 Table: "api_cities", 1674 Alias: "", 1675 Conditions: []string{ 1676 "trips.city_id = api_cities.id", 1677 "trips.city_id = api_cities.id", 1678 }, 1679 }, 1680 }, 1681 } 1682 qc.Query = badQuery 1683 qc.parseExprs() 1684 qc.Error = nil 1685 qc.resolveTypes() 1686 qc.processJoinConditions() 1687 Ω(qc.Error).ShouldNot(BeNil()) 1688 1689 // only column ref should be specified in join condition 1690 qc.Query = &AQLQuery{ 1691 Table: "trips", 1692 Measures: []Measure{ 1693 {Expr: "count()"}, 1694 }, 1695 Joins: []Join{ 1696 { 1697 Table: "api_cities", 1698 Alias: "", 1699 Conditions: []string{ 1700 "api_cities.city_id = 1", 1701 }, 1702 }, 1703 }, 1704 } 1705 qc.parseExprs() 1706 qc.Error = nil 1707 qc.resolveTypes() 1708 qc.processJoinConditions() 1709 Ω(qc.Error).ShouldNot(BeNil()) 1710 1711 // main table column not specified in join condition 1712 qc.Query = &AQLQuery{ 1713 Table: "trips", 1714 Measures: []Measure{ 1715 {Expr: "count()"}, 1716 }, 1717 Joins: []Join{ 1718 { 1719 Table: "api_cities", 1720 Alias: "", 1721 Conditions: []string{ 1722 "api_cities.id = api_cities.id", 1723 }, 1724 }, 1725 }, 1726 } 1727 qc.parseExprs() 1728 qc.Error = nil 1729 qc.resolveTypes() 1730 qc.processJoinConditions() 1731 Ω(qc.Error).ShouldNot(BeNil()) 1732 }) 1733 1734 ginkgo.It("processes foreign table related filters", func() { 1735 tripsSchema := &memstore.TableSchema{ 1736 ValueTypeByColumn: []memCom.DataType{ 1737 memCom.Uint32, 1738 memCom.Uint16, 1739 }, 1740 ColumnIDs: map[string]int{ 1741 "request_at": 0, 1742 "city_id": 1, 1743 }, 1744 Schema: metaCom.Table{ 1745 IsFactTable: true, 1746 Columns: []metaCom.Column{ 1747 {Name: "request_at", Type: metaCom.Uint32}, 1748 {Name: "city_id", Type: metaCom.Uint16}, 1749 }, 1750 }, 1751 } 1752 apiCitiesSchema := &memstore.TableSchema{ 1753 ValueTypeByColumn: []memCom.DataType{ 1754 memCom.Uint16, 1755 memCom.BigEnum, 1756 }, 1757 ColumnIDs: map[string]int{ 1758 "id": 0, 1759 "name": 1, 1760 }, 1761 Schema: metaCom.Table{ 1762 Columns: []metaCom.Column{ 1763 {Name: "id", Type: metaCom.Uint16}, 1764 {Name: "name", Type: metaCom.BigEnum}, 1765 }, 1766 }, 1767 EnumDicts: map[string]memstore.EnumDict{ 1768 "name": { 1769 Capacity: 65535, 1770 Dict: map[string]int{ 1771 "paris": 0, 1772 }, 1773 ReverseDict: []string{"paris"}, 1774 }, 1775 }, 1776 } 1777 qc := &AQLQueryContext{ 1778 TableIDByAlias: map[string]int{ 1779 "trips": 0, 1780 "api_cities": 1, 1781 }, 1782 TableSchemaByName: map[string]*memstore.TableSchema{ 1783 "trips": tripsSchema, 1784 "api_cities": apiCitiesSchema, 1785 }, 1786 TableScanners: []*TableScanner{ 1787 {Schema: tripsSchema, ColumnUsages: map[int]columnUsage{}}, 1788 {Schema: apiCitiesSchema, ColumnUsages: map[int]columnUsage{}}, 1789 }, 1790 } 1791 qc.Query = &AQLQuery{ 1792 Table: "trips", 1793 Joins: []Join{ 1794 { 1795 Table: "api_cities", 1796 Conditions: []string{ 1797 "trips.city_id = api_cities.id", 1798 }, 1799 }, 1800 }, 1801 Measures: []Measure{ 1802 {Expr: "count()"}, 1803 }, 1804 Dimensions: []Dimension{{Expr: "request_at", TimeBucketizer: "m"}}, 1805 Filters: []string{"api_cities.name = 'paris'", "city_id=1"}, 1806 TimeFilter: TimeFilter{ 1807 Column: "request_at", 1808 From: "-1d", 1809 To: "0d", 1810 }, 1811 } 1812 qc.parseExprs() 1813 utils.SetClockImplementation(func() time.Time { 1814 return time.Date(2017, 9, 20, 16, 51, 0, 0, time.UTC) 1815 }) 1816 qc.resolveTypes() 1817 qc.matchPrefilters() 1818 qc.processTimezone() 1819 qc.processFilters() 1820 Ω(qc.Error).Should(BeNil()) 1821 Ω(qc.OOPK.TimeFilters[0]).ShouldNot(BeNil()) 1822 Ω(qc.OOPK.TimeFilters[1]).ShouldNot(BeNil()) 1823 Ω(qc.OOPK.MainTableCommonFilters[0]).Should(Equal(&expr.BinaryExpr{ 1824 ExprType: expr.Boolean, 1825 Op: expr.EQ, 1826 LHS: &expr.VarRef{ 1827 Val: "city_id", 1828 ColumnID: 1, 1829 ExprType: expr.Unsigned, 1830 DataType: memCom.Uint16, 1831 }, 1832 RHS: &expr.NumberLiteral{ 1833 Val: 1, 1834 ExprType: expr.Unsigned, 1835 Int: 1, 1836 Expr: "1", 1837 }, 1838 })) 1839 Ω(qc.OOPK.ForeignTableCommonFilters[0]).Should(Equal(&expr.BinaryExpr{ 1840 ExprType: expr.Boolean, 1841 Op: expr.EQ, 1842 LHS: &expr.VarRef{ 1843 Val: "api_cities.name", 1844 TableID: 1, 1845 ColumnID: 1, 1846 EnumDict: map[string]int{ 1847 "paris": 0, 1848 }, 1849 EnumReverseDict: []string{"paris"}, 1850 DataType: memCom.BigEnum, 1851 ExprType: expr.Unsigned, 1852 }, 1853 RHS: &expr.NumberLiteral{ 1854 ExprType: expr.Unsigned, 1855 Int: 0, 1856 Expr: "", 1857 }, 1858 })) 1859 }) 1860 1861 ginkgo.It("processes hyperloglog", func() { 1862 table := metaCom.Table{ 1863 IsFactTable: true, 1864 Columns: []metaCom.Column{ 1865 {Name: "request_at", Type: metaCom.Uint32}, 1866 {Name: "client_uuid_hll", Type: metaCom.UUID, HLLConfig: metaCom.HLLConfig{IsHLLColumn: true}}, 1867 }, 1868 } 1869 tripsSchema := memstore.NewTableSchema(&table) 1870 1871 qc := &AQLQueryContext{ 1872 TableIDByAlias: map[string]int{ 1873 "trips": 0, 1874 }, 1875 TableSchemaByName: map[string]*memstore.TableSchema{ 1876 "trips": tripsSchema, 1877 }, 1878 TableScanners: []*TableScanner{ 1879 {Schema: tripsSchema, ColumnUsages: map[int]columnUsage{}}, 1880 }, 1881 } 1882 1883 qc.Query = &AQLQuery{ 1884 Table: "trips", 1885 Measures: []Measure{ 1886 {Expr: "countDistinctHll(request_at)"}, 1887 }, 1888 TimeFilter: TimeFilter{ 1889 Column: "request_at", 1890 From: "-1d", 1891 To: "0d", 1892 }, 1893 } 1894 qc.Error = nil 1895 qc.parseExprs() 1896 1897 qc.resolveTypes() 1898 Ω(qc.Error).Should(BeNil()) 1899 Ω(qc.Query.Measures[0].expr.String()).Should(Equal("hll(GET_HLL_VALUE(request_at))")) 1900 1901 qc.Query = &AQLQuery{ 1902 Table: "trips", 1903 Measures: []Measure{ 1904 {Expr: "hll(client_uuid_hll)"}, 1905 }, 1906 TimeFilter: TimeFilter{ 1907 Column: "request_at", 1908 From: "-1d", 1909 To: "0d", 1910 }, 1911 } 1912 qc.Error = nil 1913 qc.parseExprs() 1914 qc.resolveTypes() 1915 Ω(qc.Error).Should(BeNil()) 1916 Ω(qc.Query.Measures[0].expr.String()).Should(Equal("hll(client_uuid_hll)")) 1917 1918 qc.Query = &AQLQuery{ 1919 Table: "trips", 1920 Measures: []Measure{ 1921 {Expr: "countDistinctHLL(client_uuid_hll)"}, 1922 }, 1923 TimeFilter: TimeFilter{ 1924 Column: "request_at", 1925 From: "-1d", 1926 To: "0d", 1927 }, 1928 } 1929 qc.Error = nil 1930 qc.parseExprs() 1931 qc.resolveTypes() 1932 Ω(qc.Error).Should(BeNil()) 1933 Ω(qc.Query.Measures[0].expr.String()).Should(Equal("hll(client_uuid_hll)")) 1934 }) 1935 1936 ginkgo.It("process geo intersection in foreign table", func() { 1937 store := new(mocks.MemStore) 1938 store.On("RLock").Return() 1939 store.On("RUnlock").Return() 1940 1941 orderSchema := &memstore.TableSchema{ 1942 ColumnIDs: map[string]int{ 1943 "request_at": 0, 1944 "restaurant_uuid": 1, 1945 }, 1946 Schema: metaCom.Table{ 1947 Name: "orders", 1948 IsFactTable: true, 1949 Columns: []metaCom.Column{ 1950 {Name: "request_at", Type: metaCom.Uint32}, 1951 {Name: "restaurant_uuid", Type: metaCom.UUID}, 1952 }, 1953 }, 1954 ValueTypeByColumn: []memCom.DataType{ 1955 memCom.Uint32, 1956 memCom.UUID, 1957 }, 1958 } 1959 1960 merchantInfoSchema := &memstore.TableSchema{ 1961 ColumnIDs: map[string]int{ 1962 "uuid": 0, 1963 "location": 1, 1964 }, 1965 Schema: metaCom.Table{ 1966 Name: "merchant_info", 1967 IsFactTable: false, 1968 Columns: []metaCom.Column{ 1969 {Name: "uuid", Type: metaCom.UUID}, 1970 {Name: "location", Type: metaCom.GeoPoint}, 1971 }, 1972 PrimaryKeyColumns: []int{0}, 1973 }, 1974 ValueTypeByColumn: []memCom.DataType{ 1975 memCom.Uint32, 1976 memCom.GeoPoint, 1977 }, 1978 } 1979 1980 geoSchema := &memstore.TableSchema{ 1981 ColumnIDs: map[string]int{ 1982 "geofence_uuid": 0, 1983 "shape": 1, 1984 "count": 2, 1985 }, 1986 Schema: metaCom.Table{ 1987 Name: "geofences_configstore_udr_geofences", 1988 IsFactTable: false, 1989 Columns: []metaCom.Column{ 1990 {Name: "geofence_uuid", Type: metaCom.UUID}, 1991 {Name: "shape", Type: metaCom.GeoShape}, 1992 {Name: "count", Type: metaCom.Uint32}, 1993 }, 1994 PrimaryKeyColumns: []int{0}, 1995 }, 1996 1997 ValueTypeByColumn: []memCom.DataType{ 1998 memCom.UUID, 1999 memCom.GeoShape, 2000 memCom.Uint32, 2001 }, 2002 } 2003 2004 store.On("GetSchemas").Return(map[string]*memstore.TableSchema{ 2005 "orders": orderSchema, 2006 "merchant_info": merchantInfoSchema, 2007 "geofences_configstore_udr_geofences": geoSchema, 2008 }) 2009 2010 qc := AQLQueryContext{} 2011 query := &AQLQuery{ 2012 Table: "orders", 2013 Measures: []Measure{ 2014 {Expr: "count(*)"}, 2015 }, 2016 Joins: []Join{ 2017 { 2018 Table: "merchant_info", 2019 Alias: "m", 2020 Conditions: []string{ 2021 "orders.restaurant_uuid = m.uuid", 2022 }, 2023 }, 2024 { 2025 Alias: "g", 2026 Table: "geofences_configstore_udr_geofences", 2027 Conditions: []string{ 2028 "geography_intersects(g.shape, m.location)", 2029 }, 2030 }, 2031 }, 2032 Dimensions: []Dimension{{Expr: "request_at", TimeBucketizer: "m"}}, 2033 Filters: []string{ 2034 "g.geofence_uuid = 0x00000192F23D460DBE60400C32EA0667", 2035 }, 2036 TimeFilter: TimeFilter{ 2037 Column: "request_at", 2038 From: "-1d", 2039 }, 2040 } 2041 qc.Query = query 2042 qc.readSchema(store) 2043 qc.parseExprs() 2044 qc.resolveTypes() 2045 Ω(qc.Error).Should(BeNil()) 2046 2047 parsedQC := AQLQueryContext{ 2048 Query: query, 2049 TableSchemaByName: map[string]*memstore.TableSchema{ 2050 "orders": orderSchema, 2051 "merchant_info": merchantInfoSchema, 2052 "geofences_configstore_udr_geofences": geoSchema, 2053 }, 2054 TableIDByAlias: map[string]int{ 2055 "orders": 0, 2056 "m": 1, 2057 "g": 2, 2058 }, 2059 TableScanners: []*TableScanner{ 2060 {Schema: orderSchema, ColumnUsages: make(map[int]columnUsage)}, 2061 {Schema: merchantInfoSchema, ColumnUsages: make(map[int]columnUsage)}, 2062 {Schema: geoSchema, ColumnUsages: make(map[int]columnUsage)}, 2063 }, 2064 fromTime: qc.fromTime, 2065 toTime: qc.toTime, 2066 } 2067 2068 qc = parsedQC 2069 qc.resolveTypes() 2070 qc.processJoinConditions() 2071 Ω(qc.Error).Should(BeNil()) 2072 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2073 Ω(*qc.OOPK.geoIntersection).Should( 2074 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 1, pointTableID: 1, 2075 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, 2076 dimIndex: -1, inOrOut: true})) 2077 qc.processFilters() 2078 Ω(qc.Error).Should(BeNil()) 2079 Ω(qc.OOPK.geoIntersection.shapeUUIDs).Should(Equal([]string{"00000192F23D460DBE60400C32EA0667"})) 2080 Ω(qc.TableScanners[1].ColumnUsages[1]).Should(Equal(columnUsedByAllBatches)) 2081 }) 2082 2083 ginkgo.It("process geo intersection", func() { 2084 store := new(mocks.MemStore) 2085 store.On("RLock").Return() 2086 store.On("RUnlock").Return() 2087 2088 tripsSchema := &memstore.TableSchema{ 2089 ColumnIDs: map[string]int{ 2090 "request_at": 0, 2091 "city_id": 1, 2092 "request_point": 2, 2093 }, 2094 Schema: metaCom.Table{ 2095 Name: "trips", 2096 IsFactTable: true, 2097 Columns: []metaCom.Column{ 2098 {Name: "request_at", Type: metaCom.Uint32}, 2099 {Name: "city_id", Type: metaCom.Uint16}, 2100 {Name: "request_point", Type: metaCom.GeoPoint}, 2101 }, 2102 }, 2103 ValueTypeByColumn: []memCom.DataType{ 2104 memCom.Uint32, 2105 memCom.Uint16, 2106 memCom.GeoPoint, 2107 }, 2108 } 2109 2110 apiCitySchema := &memstore.TableSchema{ 2111 ColumnIDs: map[string]int{ 2112 "id": 0, 2113 }, 2114 Schema: metaCom.Table{ 2115 Name: "api_cities", 2116 IsFactTable: false, 2117 Columns: []metaCom.Column{ 2118 {Name: "id", Type: metaCom.Uint32}, 2119 }, 2120 PrimaryKeyColumns: []int{0}, 2121 }, 2122 ValueTypeByColumn: []memCom.DataType{ 2123 memCom.Uint32, 2124 }, 2125 } 2126 2127 geoSchema := &memstore.TableSchema{ 2128 ColumnIDs: map[string]int{ 2129 "geofence_uuid": 0, 2130 "shape": 1, 2131 "count": 2, 2132 }, 2133 Schema: metaCom.Table{ 2134 Name: "geofences_configstore_udr_geofences", 2135 IsFactTable: false, 2136 Columns: []metaCom.Column{ 2137 {Name: "geofence_uuid", Type: metaCom.UUID}, 2138 {Name: "shape", Type: metaCom.GeoShape}, 2139 {Name: "count", Type: metaCom.Uint32}, 2140 }, 2141 PrimaryKeyColumns: []int{0}, 2142 }, 2143 2144 ValueTypeByColumn: []memCom.DataType{ 2145 memCom.UUID, 2146 memCom.GeoShape, 2147 memCom.Uint32, 2148 }, 2149 } 2150 2151 store.On("GetSchemas").Return(map[string]*memstore.TableSchema{ 2152 "trips": tripsSchema, 2153 "api_cities": apiCitySchema, 2154 "geofences_configstore_udr_geofences": geoSchema, 2155 }) 2156 2157 qc := AQLQueryContext{} 2158 query := &AQLQuery{ 2159 Table: "trips", 2160 Measures: []Measure{ 2161 {Expr: "count()"}, 2162 }, 2163 Joins: []Join{ 2164 { 2165 Table: "api_cities", 2166 Alias: "c", 2167 Conditions: []string{ 2168 "trips.city_id = c.id", 2169 }, 2170 }, 2171 { 2172 Alias: "g", 2173 Table: "geofences_configstore_udr_geofences", 2174 Conditions: []string{ 2175 "geography_intersects(g.shape, request_point)", 2176 }, 2177 }, 2178 }, 2179 Dimensions: []Dimension{{Expr: "request_at", TimeBucketizer: "m"}}, 2180 Filters: []string{ 2181 "g.geofence_uuid = 0x00000192F23D460DBE60400C32EA0667", 2182 "c.id>1", 2183 }, 2184 TimeFilter: TimeFilter{ 2185 Column: "request_at", 2186 From: "-1d", 2187 }, 2188 } 2189 qc.Query = query 2190 qc.readSchema(store) 2191 qc.parseExprs() 2192 qc.resolveTypes() 2193 Ω(qc.Error).Should(BeNil()) 2194 2195 parsedQC := AQLQueryContext{ 2196 Query: query, 2197 TableSchemaByName: map[string]*memstore.TableSchema{ 2198 "trips": tripsSchema, 2199 "api_cities": apiCitySchema, 2200 "geofences_configstore_udr_geofences": geoSchema, 2201 }, 2202 TableIDByAlias: map[string]int{ 2203 "trips": 0, 2204 "c": 1, 2205 "g": 2, 2206 }, 2207 TableScanners: []*TableScanner{ 2208 {Schema: tripsSchema, ColumnUsages: make(map[int]columnUsage)}, 2209 {Schema: apiCitySchema, ColumnUsages: make(map[int]columnUsage)}, 2210 {Schema: geoSchema, ColumnUsages: make(map[int]columnUsage)}, 2211 }, 2212 fromTime: qc.fromTime, 2213 toTime: qc.toTime, 2214 } 2215 2216 qc = parsedQC 2217 qc.resolveTypes() 2218 qc.processJoinConditions() 2219 Ω(qc.Error).Should(BeNil()) 2220 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2221 Ω(*qc.OOPK.geoIntersection).Should( 2222 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2223 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, 2224 dimIndex: -1, inOrOut: true})) 2225 qc.processFilters() 2226 Ω(qc.Error).Should(BeNil()) 2227 Ω(len(qc.OOPK.ForeignTableCommonFilters)).Should(Equal(1)) 2228 Ω(qc.OOPK.ForeignTableCommonFilters[0].String()).Should(Equal("c.id > 1")) 2229 Ω(qc.OOPK.geoIntersection.shapeUUIDs).Should(Equal([]string{"00000192F23D460DBE60400C32EA0667"})) 2230 2231 // Geo point on the left. 2232 query = &AQLQuery{ 2233 Table: "trips", 2234 Measures: []Measure{ 2235 {Expr: "count()"}, 2236 }, 2237 Joins: []Join{ 2238 { 2239 Table: "api_cities", 2240 Alias: "c", 2241 Conditions: []string{ 2242 "trips.city_id = c.id", 2243 }, 2244 }, 2245 { 2246 Alias: "g", 2247 Table: "geofences_configstore_udr_geofences", 2248 Conditions: []string{ 2249 "geography_intersects(request_point, g.shape)", 2250 }, 2251 }, 2252 }, 2253 Dimensions: []Dimension{{Expr: "request_at", TimeBucketizer: "m"}}, 2254 Filters: []string{ 2255 "g.geofence_uuid = 0x00000192F23D460DBE60400C32EA0667", 2256 "c.id>1", 2257 }, 2258 TimeFilter: TimeFilter{ 2259 Column: "request_at", 2260 From: "-1d", 2261 }, 2262 } 2263 qc.Query = query 2264 qc.readSchema(store) 2265 qc.parseExprs() 2266 qc.resolveTypes() 2267 Ω(qc.Error).Should(BeNil()) 2268 2269 parsedQC = AQLQueryContext{ 2270 Query: query, 2271 TableSchemaByName: map[string]*memstore.TableSchema{ 2272 "trips": tripsSchema, 2273 "api_cities": apiCitySchema, 2274 "geofences_configstore_udr_geofences": geoSchema, 2275 }, 2276 TableIDByAlias: map[string]int{ 2277 "trips": 0, 2278 "c": 1, 2279 "g": 2, 2280 }, 2281 TableScanners: []*TableScanner{ 2282 {Schema: tripsSchema, ColumnUsages: make(map[int]columnUsage)}, 2283 {Schema: apiCitySchema, ColumnUsages: make(map[int]columnUsage)}, 2284 {Schema: geoSchema, ColumnUsages: make(map[int]columnUsage)}, 2285 }, 2286 fromTime: qc.fromTime, 2287 toTime: qc.toTime, 2288 } 2289 2290 qc = parsedQC 2291 qc.resolveTypes() 2292 qc.processJoinConditions() 2293 Ω(qc.Error).Should(BeNil()) 2294 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2295 Ω(*qc.OOPK.geoIntersection).Should( 2296 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2297 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2298 qc.processFilters() 2299 Ω(qc.Error).Should(BeNil()) 2300 Ω(len(qc.OOPK.ForeignTableCommonFilters)).Should(Equal(1)) 2301 Ω(qc.OOPK.ForeignTableCommonFilters[0].String()).Should(Equal("c.id > 1")) 2302 Ω(qc.OOPK.geoIntersection.shapeUUIDs).Should(Equal([]string{"00000192F23D460DBE60400C32EA0667"})) 2303 2304 // Two geo points in the argument of geography_intersects. 2305 query = &AQLQuery{ 2306 Table: "trips", 2307 Measures: []Measure{ 2308 {Expr: "count()"}, 2309 }, 2310 Joins: []Join{ 2311 { 2312 Table: "api_cities", 2313 Alias: "c", 2314 Conditions: []string{ 2315 "trips.city_id = c.id", 2316 }, 2317 }, 2318 { 2319 Alias: "g", 2320 Table: "geofences_configstore_udr_geofences", 2321 Conditions: []string{ 2322 "geography_intersects(request_point, request_point)", 2323 }, 2324 }, 2325 }, 2326 Dimensions: []Dimension{{Expr: "request_at", TimeBucketizer: "m"}}, 2327 Filters: []string{ 2328 "g.geofence_uuid = 0x00000192F23D460DBE60400C32EA0667", 2329 "c.id>1", 2330 }, 2331 TimeFilter: TimeFilter{ 2332 Column: "request_at", 2333 From: "-1d", 2334 }, 2335 } 2336 qc.Query = query 2337 qc.readSchema(store) 2338 qc.parseExprs() 2339 qc.resolveTypes() 2340 Ω(qc.Error).ShouldNot(BeNil()) 2341 Ω(qc.Error.Error()).Should(ContainSubstring( 2342 "expect exactly one geo shape column and one geo point column for " + 2343 "geography_intersects, got geography_intersects")) 2344 2345 // Two geo shapes in the argument of geography_intersects. 2346 query = &AQLQuery{ 2347 Table: "trips", 2348 Measures: []Measure{ 2349 {Expr: "count()"}, 2350 }, 2351 Joins: []Join{ 2352 { 2353 Table: "api_cities", 2354 Alias: "c", 2355 Conditions: []string{ 2356 "trips.city_id = c.id", 2357 }, 2358 }, 2359 { 2360 Alias: "g", 2361 Table: "geofences_configstore_udr_geofences", 2362 Conditions: []string{ 2363 "geography_intersects(g.shape, g.shape)", 2364 }, 2365 }, 2366 }, 2367 Dimensions: []Dimension{{Expr: "request_at", TimeBucketizer: "m"}}, 2368 Filters: []string{ 2369 "g.geofence_uuid = 0x00000192F23D460DBE60400C32EA0667", 2370 "c.id>1", 2371 }, 2372 TimeFilter: TimeFilter{ 2373 Column: "request_at", 2374 From: "-1d", 2375 }, 2376 } 2377 qc.Query = query 2378 qc.readSchema(store) 2379 qc.parseExprs() 2380 qc.resolveTypes() 2381 Ω(qc.Error).ShouldNot(BeNil()) 2382 Ω(qc.Error.Error()).Should(ContainSubstring( 2383 "expect exactly one geo shape column and one geo point column for " + 2384 "geography_intersects, got geography_intersects")) 2385 2386 // Match geofence_uuid in predicate. 2387 query = &AQLQuery{ 2388 Table: "trips", 2389 Measures: []Measure{ 2390 {Expr: "count()"}, 2391 }, 2392 Joins: []Join{ 2393 { 2394 Table: "api_cities", 2395 Alias: "c", 2396 Conditions: []string{ 2397 "trips.city_id = c.id", 2398 }, 2399 }, 2400 { 2401 Alias: "g", 2402 Table: "geofences_configstore_udr_geofences", 2403 Conditions: []string{ 2404 "geography_intersects(g.shape, request_point)", 2405 }, 2406 }, 2407 }, 2408 Filters: []string{ 2409 "g.geofence_uuid in (0x4c3226b27b1b11e8adc0fa7ae01bbebc,0x4c32295a7b1b11e8adc0fa7ae01bbebc)", 2410 "c.id>1", 2411 }, 2412 TimeFilter: TimeFilter{ 2413 Column: "request_at", 2414 From: "-1d", 2415 }, 2416 } 2417 2418 qc = parsedQC 2419 qc.Error = nil 2420 qc.Query = query 2421 qc.parseExprs() 2422 Ω(qc.Error).Should(BeNil()) 2423 qc.resolveTypes() 2424 qc.processJoinConditions() 2425 Ω(qc.Error).Should(BeNil()) 2426 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2427 Ω(*qc.OOPK.geoIntersection).Should( 2428 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2429 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2430 qc.processFilters() 2431 Ω(qc.Error).Should(BeNil()) 2432 Ω(len(qc.OOPK.ForeignTableCommonFilters)).Should(Equal(1)) 2433 Ω(qc.OOPK.ForeignTableCommonFilters[0].String()).Should(Equal("c.id > 1")) 2434 Ω(qc.OOPK.geoIntersection.shapeUUIDs).Should(Equal([]string{"4C3226B27B1B11E8ADC0FA7AE01BBEBC", 2435 "4C32295A7B1B11E8ADC0FA7AE01BBEBC"})) 2436 2437 // Return error if two geo filters 2438 qc = AQLQueryContext{} 2439 query = &AQLQuery{ 2440 Table: "trips", 2441 Measures: []Measure{ 2442 {Expr: "count()"}, 2443 }, 2444 Joins: []Join{ 2445 { 2446 Table: "api_cities", 2447 Alias: "c", 2448 Conditions: []string{ 2449 "trips.city_id = c.id", 2450 }, 2451 }, 2452 { 2453 Alias: "g", 2454 Table: "geofences_configstore_udr_geofences", 2455 Conditions: []string{ 2456 "geography_intersects(g.shape, request_point)", 2457 }, 2458 }, 2459 }, 2460 Filters: []string{ 2461 "g.geofence_uuid in (0x4c3226b27b1b11e8adc0fa7ae01bbebc,0x4c32295a7b1b11e8adc0fa7ae01bbebc)", 2462 "g.geofence_uuid in (0x4c3226b27b1b11e8adc0fa7ae01bbebc,0x4c32295a7b1b11e8adc0fa7ae01bbebc)", 2463 "c.id>1", 2464 }, 2465 TimeFilter: TimeFilter{ 2466 Column: "request_at", 2467 From: "-1d", 2468 }, 2469 } 2470 2471 qc.Query = query 2472 qc.parseExprs() 2473 Ω(qc.Error).Should(BeNil()) 2474 qc = parsedQC 2475 qc.Query = query 2476 qc.resolveTypes() 2477 qc.processJoinConditions() 2478 Ω(qc.Error).Should(BeNil()) 2479 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2480 Ω(*qc.OOPK.geoIntersection).Should( 2481 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2482 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2483 qc.processFilters() 2484 Ω(qc.Error).ShouldNot(BeNil()) 2485 Ω(qc.Error.Error()).Should(ContainSubstring("Only one geo filter is allowed")) 2486 2487 // Return error if no geo filter 2488 qc = AQLQueryContext{} 2489 query = &AQLQuery{ 2490 Table: "trips", 2491 Measures: []Measure{ 2492 {Expr: "count()"}, 2493 }, 2494 Joins: []Join{ 2495 { 2496 Table: "api_cities", 2497 Alias: "c", 2498 Conditions: []string{ 2499 "trips.city_id = c.id", 2500 }, 2501 }, 2502 { 2503 Alias: "g", 2504 Table: "geofences_configstore_udr_geofences", 2505 Conditions: []string{ 2506 "geography_intersects(g.shape, request_point)", 2507 }, 2508 }, 2509 }, 2510 Filters: []string{ 2511 "c.id>1", 2512 }, 2513 TimeFilter: TimeFilter{ 2514 Column: "request_at", 2515 From: "-1d", 2516 }, 2517 } 2518 2519 qc.Query = query 2520 qc.parseExprs() 2521 Ω(qc.Error).Should(BeNil()) 2522 qc = parsedQC 2523 qc.Query = query 2524 qc.resolveTypes() 2525 qc.processJoinConditions() 2526 Ω(qc.Error).Should(BeNil()) 2527 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2528 Ω(*qc.OOPK.geoIntersection).Should( 2529 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2530 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2531 qc.processFilters() 2532 Ω(qc.Error).ShouldNot(BeNil()) 2533 Ω(qc.Error.Error()).Should(ContainSubstring("Exact one geo filter is needed if geo intersection is used during join")) 2534 2535 // At most one join condition allowed per geo join. 2536 qc = AQLQueryContext{} 2537 query = &AQLQuery{ 2538 Table: "trips", 2539 Measures: []Measure{ 2540 {Expr: "count()"}, 2541 }, 2542 Joins: []Join{ 2543 { 2544 Table: "api_cities", 2545 Alias: "c", 2546 Conditions: []string{ 2547 "trips.city_id = c.id", 2548 }, 2549 }, 2550 { 2551 Alias: "g", 2552 Table: "geofences_configstore_udr_geofences", 2553 Conditions: []string{ 2554 "geography_intersects(g.shape, request_point)", 2555 "geography_intersects(g.shape, request_point)", 2556 }, 2557 }, 2558 }, 2559 Filters: []string{ 2560 "c.id>1", 2561 "g.geofence_uuid in (0x4c3226b27b1b11e8adc0fa7ae01bbebc,0x4c32295a7b1b11e8adc0fa7ae01bbebc)", 2562 }, 2563 TimeFilter: TimeFilter{ 2564 Column: "request_at", 2565 From: "-1d", 2566 }, 2567 } 2568 2569 qc.Query = query 2570 qc.parseExprs() 2571 Ω(qc.Error).Should(BeNil()) 2572 qc = parsedQC 2573 qc.Query = query 2574 qc.resolveTypes() 2575 qc.processJoinConditions() 2576 Ω(qc.Error).ShouldNot(BeNil()) 2577 Ω(qc.Error.Error()).Should(ContainSubstring("At most one join condition allowed per geo join")) 2578 2579 // Only dimension table is allowed in geo join 2580 geoSchema.Schema.IsFactTable = true 2581 qc = AQLQueryContext{} 2582 query = &AQLQuery{ 2583 Table: "trips", 2584 Measures: []Measure{ 2585 {Expr: "count()"}, 2586 }, 2587 Joins: []Join{ 2588 { 2589 Table: "api_cities", 2590 Alias: "c", 2591 Conditions: []string{ 2592 "trips.city_id = c.id", 2593 }, 2594 }, 2595 { 2596 Alias: "g", 2597 Table: "geofences_configstore_udr_geofences", 2598 Conditions: []string{ 2599 "geography_intersects(g.shape, request_point)", 2600 }, 2601 }, 2602 }, 2603 Filters: []string{ 2604 "c.id>1", 2605 "g.geofence_uuid in (0x4c3226b27b1b11e8adc0fa7ae01bbebc,0x4c32295a7b1b11e8adc0fa7ae01bbebc)", 2606 }, 2607 TimeFilter: TimeFilter{ 2608 Column: "request_at", 2609 From: "-1d", 2610 }, 2611 } 2612 qc.Query = query 2613 qc.parseExprs() 2614 Ω(qc.Error).Should(BeNil()) 2615 qc = parsedQC 2616 qc.Query = query 2617 qc.resolveTypes() 2618 qc.processJoinConditions() 2619 Ω(qc.Error).ShouldNot(BeNil()) 2620 Ω(qc.Error.Error()).Should(ContainSubstring("Only dimension table is allowed in geo join")) 2621 geoSchema.Schema.IsFactTable = false 2622 2623 // Composite primary key for geo table is not allowed. 2624 geoSchema.Schema.PrimaryKeyColumns = []int{0, 1} 2625 qc = AQLQueryContext{} 2626 qc.Query = query 2627 qc.parseExprs() 2628 Ω(qc.Error).Should(BeNil()) 2629 qc = parsedQC 2630 qc.resolveTypes() 2631 qc.processJoinConditions() 2632 Ω(qc.Error).ShouldNot(BeNil()) 2633 Ω(qc.Error.Error()).Should(ContainSubstring("Composite primary key for geo table is not allowed")) 2634 2635 // Geo filter column is not the primary key. 2636 geoSchema.Schema.PrimaryKeyColumns = []int{1} 2637 qc = AQLQueryContext{} 2638 qc.Query = query 2639 qc.parseExprs() 2640 Ω(qc.Error).Should(BeNil()) 2641 qc = parsedQC 2642 qc.resolveTypes() 2643 qc.processJoinConditions() 2644 Ω(qc.Error).Should(BeNil()) 2645 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2646 Ω(*qc.OOPK.geoIntersection).Should( 2647 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2648 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2649 qc.processFilters() 2650 Ω(qc.Error).ShouldNot(BeNil()) 2651 Ω(qc.Error.Error()).Should(ContainSubstring("Geo filter column is not the primary key")) 2652 2653 geoSchema.Schema.PrimaryKeyColumns = []int{0} 2654 2655 query = &AQLQuery{ 2656 Table: "trips", 2657 Measures: []Measure{ 2658 {Expr: "count()"}, 2659 }, 2660 Joins: []Join{ 2661 { 2662 Table: "api_cities", 2663 Alias: "c", 2664 Conditions: []string{ 2665 "trips.city_id = c.id", 2666 }, 2667 }, 2668 { 2669 Alias: "g", 2670 Table: "geofences_configstore_udr_geofences", 2671 Conditions: []string{ 2672 "geography_intersects(g.shape, request_point)", 2673 }, 2674 }, 2675 }, 2676 Filters: []string{ 2677 "c.id>1", 2678 "g.geofence_uuid not in (trips.city_id)", 2679 }, 2680 TimeFilter: TimeFilter{ 2681 Column: "request_at", 2682 From: "-1d", 2683 }, 2684 } 2685 qc = parsedQC 2686 qc.Query = query 2687 qc.parseExprs() 2688 Ω(qc.Error).Should(BeNil()) 2689 qc.resolveTypes() 2690 qc.processJoinConditions() 2691 qc.processFilters() 2692 Ω(qc.Error).ShouldNot(BeNil()) 2693 Ω(qc.Error.Error()).Should(ContainSubstring("Unable to extract uuid from expression ")) 2694 2695 query = &AQLQuery{ 2696 Table: "trips", 2697 Measures: []Measure{ 2698 {Expr: "count()"}, 2699 }, 2700 Joins: []Join{ 2701 { 2702 Table: "api_cities", 2703 Alias: "c", 2704 Conditions: []string{ 2705 "trips.city_id = c.id", 2706 }, 2707 }, 2708 { 2709 Alias: "g", 2710 Table: "geofences_configstore_udr_geofences", 2711 Conditions: []string{ 2712 "geography_intersects(g.shape, request_point)", 2713 }, 2714 }, 2715 }, 2716 Filters: []string{ 2717 "c.id>1", 2718 "g.geofence_uuid not in (0x4c3226b27b1b11e8adc0fa7ae01bbebc)", 2719 }, 2720 TimeFilter: TimeFilter{ 2721 Column: "request_at", 2722 From: "-1d", 2723 }, 2724 } 2725 qc = parsedQC 2726 qc.Query = query 2727 qc.parseExprs() 2728 Ω(qc.Error).Should(BeNil()) 2729 qc.resolveTypes() 2730 qc.processJoinConditions() 2731 qc.processFilters() 2732 Ω(qc.Error).ShouldNot(BeNil()) 2733 Ω(qc.Error.Error()).Should(ContainSubstring("Only EQ and IN allowed for geo filters")) 2734 2735 // Geo table column is not allowed to be used in measure. 2736 qc = AQLQueryContext{} 2737 query = &AQLQuery{ 2738 Table: "trips", 2739 Measures: []Measure{ 2740 {Expr: "sum(g.count)"}, 2741 }, 2742 Joins: []Join{ 2743 { 2744 Table: "api_cities", 2745 Alias: "c", 2746 Conditions: []string{ 2747 "trips.city_id = c.id", 2748 }, 2749 }, 2750 { 2751 Alias: "g", 2752 Table: "geofences_configstore_udr_geofences", 2753 Conditions: []string{ 2754 "geography_intersects(g.shape, request_point)", 2755 }, 2756 }, 2757 }, 2758 Filters: []string{ 2759 "c.id>1", 2760 "g.geofence_uuid not in (trips.city_id)", 2761 }, 2762 TimeFilter: TimeFilter{ 2763 Column: "request_at", 2764 From: "-1d", 2765 }, 2766 } 2767 qc.Query = query 2768 qc.parseExprs() 2769 Ω(qc.Error).Should(BeNil()) 2770 qc = parsedQC 2771 qc.Query = query 2772 qc.resolveTypes() 2773 qc.processJoinConditions() 2774 Ω(qc.Error).Should(BeNil()) 2775 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2776 Ω(*qc.OOPK.geoIntersection).Should( 2777 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2778 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2779 qc.processMeasure() 2780 qc.processDimensions() 2781 Ω(qc.Error).ShouldNot(BeNil()) 2782 Ω(qc.Error.Error()).Should(ContainSubstring("Geo table column is not allowed to be used in measure")) 2783 2784 // Only one geo dimension allowed. 2785 qc = AQLQueryContext{} 2786 query = &AQLQuery{ 2787 Table: "trips", 2788 Measures: []Measure{ 2789 {Expr: "count(1)"}, 2790 }, 2791 Joins: []Join{ 2792 { 2793 Table: "api_cities", 2794 Alias: "c", 2795 Conditions: []string{ 2796 "trips.city_id = c.id", 2797 }, 2798 }, 2799 { 2800 Alias: "g", 2801 Table: "geofences_configstore_udr_geofences", 2802 Conditions: []string{ 2803 "geography_intersects(g.shape, request_point)", 2804 }, 2805 }, 2806 }, 2807 Dimensions: []Dimension{ 2808 { 2809 Expr: "g.geofence_uuid", 2810 }, 2811 { 2812 Expr: "g.geofence_uuid", 2813 }, 2814 }, 2815 Filters: []string{ 2816 "c.id>1", 2817 "g.geofence_uuid not in (trips.city_id)", 2818 }, 2819 TimeFilter: TimeFilter{ 2820 Column: "request_at", 2821 From: "-1d", 2822 }, 2823 } 2824 qc.Query = query 2825 qc.parseExprs() 2826 Ω(qc.Error).Should(BeNil()) 2827 qc = parsedQC 2828 qc.Query = query 2829 qc.resolveTypes() 2830 qc.processJoinConditions() 2831 Ω(qc.Error).Should(BeNil()) 2832 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2833 Ω(*qc.OOPK.geoIntersection).Should( 2834 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2835 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2836 qc.processMeasure() 2837 qc.processDimensions() 2838 Ω(qc.Error).ShouldNot(BeNil()) 2839 Ω(qc.Error.Error()).Should(ContainSubstring("Only one geo dimension allowed")) 2840 2841 // Only one geo uuid dimension allowed. 2842 qc = AQLQueryContext{} 2843 query = &AQLQuery{ 2844 Table: "trips", 2845 Measures: []Measure{ 2846 {Expr: "count(1)"}, 2847 }, 2848 Joins: []Join{ 2849 { 2850 Table: "api_cities", 2851 Alias: "c", 2852 Conditions: []string{ 2853 "trips.city_id = c.id", 2854 }, 2855 }, 2856 { 2857 Alias: "g", 2858 Table: "geofences_configstore_udr_geofences", 2859 Conditions: []string{ 2860 "geography_intersects(g.shape, request_point)", 2861 }, 2862 }, 2863 }, 2864 Dimensions: []Dimension{ 2865 { 2866 Expr: "g.count", 2867 }, 2868 }, 2869 Filters: []string{ 2870 "c.id>1", 2871 "g.geofence_uuid not in (trips.city_id)", 2872 }, 2873 TimeFilter: TimeFilter{ 2874 Column: "request_at", 2875 From: "-1d", 2876 }, 2877 } 2878 qc.Query = query 2879 qc.parseExprs() 2880 Ω(qc.Error).Should(BeNil()) 2881 qc = parsedQC 2882 qc.Query = query 2883 qc.resolveTypes() 2884 qc.processJoinConditions() 2885 Ω(qc.Error).Should(BeNil()) 2886 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2887 Ω(*qc.OOPK.geoIntersection).Should( 2888 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2889 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2890 qc.processMeasure() 2891 qc.processDimensions() 2892 Ω(qc.Error).ShouldNot(BeNil()) 2893 Ω(qc.Error.Error()).Should(ContainSubstring("Only geo uuid is allowed in dimensions")) 2894 2895 // Only hex(uuid) or uuid supported. 2896 qc = AQLQueryContext{} 2897 query = &AQLQuery{ 2898 Table: "trips", 2899 Measures: []Measure{ 2900 {Expr: "count(1)"}, 2901 }, 2902 Joins: []Join{ 2903 { 2904 Table: "api_cities", 2905 Alias: "c", 2906 Conditions: []string{ 2907 "trips.city_id = c.id", 2908 }, 2909 }, 2910 { 2911 Alias: "g", 2912 Table: "geofences_configstore_udr_geofences", 2913 Conditions: []string{ 2914 "geography_intersects(g.shape, request_point)", 2915 }, 2916 }, 2917 }, 2918 Dimensions: []Dimension{ 2919 { 2920 Expr: "g.geofence_uuid+1", 2921 }, 2922 }, 2923 Filters: []string{ 2924 "c.id>1", 2925 "g.geofence_uuid in (0x4c3226b27b1b11e8adc0fa7ae01bbebc,0x4c32295a7b1b11e8adc0fa7ae01bbebc)", 2926 }, 2927 TimeFilter: TimeFilter{ 2928 Column: "request_at", 2929 From: "-1d", 2930 }, 2931 } 2932 qc.Query = query 2933 qc.parseExprs() 2934 Ω(qc.Error).Should(BeNil()) 2935 qc = parsedQC 2936 qc.Query = query 2937 qc.resolveTypes() 2938 qc.processJoinConditions() 2939 Ω(qc.Error).ShouldNot(BeNil()) 2940 Ω(qc.Error.Error()).Should(ContainSubstring("numeric operations not supported for column over 4 bytes length")) 2941 2942 // only hex function is supported on UUID type, but got count. 2943 qc = AQLQueryContext{} 2944 query = &AQLQuery{ 2945 Table: "trips", 2946 Measures: []Measure{ 2947 {Expr: "count(1)"}, 2948 }, 2949 Joins: []Join{ 2950 { 2951 Table: "api_cities", 2952 Alias: "c", 2953 Conditions: []string{ 2954 "trips.city_id = c.id", 2955 }, 2956 }, 2957 { 2958 Alias: "g", 2959 Table: "geofences_configstore_udr_geofences", 2960 Conditions: []string{ 2961 "geography_intersects(g.shape, request_point)", 2962 }, 2963 }, 2964 }, 2965 Dimensions: []Dimension{ 2966 { 2967 Expr: "count(g.geofence_uuid)", 2968 }, 2969 }, 2970 Filters: []string{ 2971 "c.id>1", 2972 "g.geofence_uuid in (0x4c3226b27b1b11e8adc0fa7ae01bbebc,0x4c32295a7b1b11e8adc0fa7ae01bbebc)", 2973 }, 2974 TimeFilter: TimeFilter{ 2975 Column: "request_at", 2976 From: "-1d", 2977 }, 2978 } 2979 qc.Query = query 2980 qc.parseExprs() 2981 Ω(qc.Error).Should(BeNil()) 2982 qc = parsedQC 2983 qc.Query = query 2984 qc.resolveTypes() 2985 qc.processJoinConditions() 2986 Ω(qc.Error).Should(BeNil()) 2987 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 2988 Ω(*qc.OOPK.geoIntersection).Should( 2989 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 2990 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 2991 qc.processMeasure() 2992 qc.processDimensions() 2993 Ω(qc.Error).ShouldNot(BeNil()) 2994 Ω(qc.Error.Error()).Should(ContainSubstring("Only hex function is supported on UUID type, but got count")) 2995 2996 // Correct geo join query. 2997 qc = AQLQueryContext{} 2998 query = &AQLQuery{ 2999 Table: "trips", 3000 Measures: []Measure{ 3001 {Expr: "count(1)"}, 3002 }, 3003 Joins: []Join{ 3004 { 3005 Table: "api_cities", 3006 Alias: "c", 3007 Conditions: []string{ 3008 "trips.city_id = c.id", 3009 }, 3010 }, 3011 { 3012 Alias: "g", 3013 Table: "geofences_configstore_udr_geofences", 3014 Conditions: []string{ 3015 "geography_intersects(g.shape, request_point)", 3016 }, 3017 }, 3018 }, 3019 Dimensions: []Dimension{ 3020 { 3021 Expr: "hex(g.geofence_uuid)", 3022 }, 3023 }, 3024 Filters: []string{ 3025 "c.id>1", 3026 "g.geofence_uuid in (0x4c3226b27b1b11e8adc0fa7ae01bbebc,0x4c32295a7b1b11e8adc0fa7ae01bbebc)", 3027 }, 3028 TimeFilter: TimeFilter{ 3029 Column: "request_at", 3030 From: "-1d", 3031 }, 3032 } 3033 qc.Query = query 3034 qc.parseExprs() 3035 Ω(qc.Error).Should(BeNil()) 3036 qc = parsedQC 3037 qc.Query = query 3038 qc.resolveTypes() 3039 qc.processJoinConditions() 3040 Ω(qc.Error).Should(BeNil()) 3041 Ω(qc.OOPK.geoIntersection).ShouldNot(BeNil()) 3042 Ω(*qc.OOPK.geoIntersection).Should( 3043 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 3044 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: -1, inOrOut: true})) 3045 qc.processFilters() 3046 Ω(qc.Error).Should(BeNil()) 3047 qc.processMeasure() 3048 qc.processDimensions() 3049 Ω(qc.Error).Should(BeNil()) 3050 Ω(qc.OOPK.geoIntersection.shapeUUIDs).Should( 3051 Equal([]string{ 3052 "4C3226B27B1B11E8ADC0FA7AE01BBEBC", 3053 "4C32295A7B1B11E8ADC0FA7AE01BBEBC", 3054 })) 3055 3056 qc.OOPK.geoIntersection.shapeUUIDs = nil 3057 3058 Ω(*qc.OOPK.geoIntersection).Should( 3059 Equal(geoIntersection{shapeTableID: 2, shapeColumnID: 1, pointColumnID: 2, 3060 shapeUUIDs: nil, shapeLatLongs: nullDevicePointer, shapeIndexs: nullDevicePointer, validShapeUUIDs: nil, dimIndex: 0, inOrOut: true})) 3061 dimExpr, ok := qc.OOPK.Dimensions[0].(*expr.VarRef) 3062 Ω(ok).Should(BeTrue()) 3063 Ω(dimExpr.TableID).Should(BeEquivalentTo(2)) 3064 Ω(dimExpr.ColumnID).Should(BeEquivalentTo(0)) 3065 Ω(dimExpr.EnumReverseDict).Should(BeNil()) 3066 }) 3067 3068 ginkgo.It("parseTimezoneColumnString should work", func() { 3069 inputs := []string{"timezone(city_id)", "region_timezone(city_id)", "foo", "tz(bla", "timezone)(foo)"} 3070 expectedSucesss := []bool{true, true, false, false, false} 3071 expectedColumns := []string{"timezone", "region_timezone", "", "", ""} 3072 expectedJoinKeys := []string{"city_id", "city_id", "", "", ""} 3073 for i := range inputs { 3074 column, key, ok := parseTimezoneColumnString(inputs[i]) 3075 Ω(ok).Should(Equal(expectedSucesss[i])) 3076 Ω(key).Should(Equal(expectedJoinKeys[i])) 3077 Ω(column).Should(Equal(expectedColumns[i])) 3078 } 3079 }) 3080 3081 ginkgo.It("expandINOp should work", func() { 3082 query := &AQLQuery{ 3083 Table: "table1", 3084 Filters: []string{"id in (1, 2)"}, 3085 } 3086 tableSchema := &memstore.TableSchema{ 3087 ColumnIDs: map[string]int{ 3088 "time_col": 0, 3089 "id": 1, 3090 }, 3091 Schema: metaCom.Table{ 3092 Name: "table1", 3093 IsFactTable: true, 3094 Columns: []metaCom.Column{ 3095 {Name: "time_col", Type: metaCom.Uint32}, 3096 {Name: "id", Type: metaCom.Uint16}, 3097 }, 3098 }, 3099 ValueTypeByColumn: []memCom.DataType{ 3100 memCom.Uint32, 3101 memCom.Uint16, 3102 }, 3103 } 3104 qc := AQLQueryContext{ 3105 Query: query, 3106 TableSchemaByName: map[string]*memstore.TableSchema{ 3107 "table1": tableSchema, 3108 }, 3109 TableIDByAlias: map[string]int{ 3110 "table1": 0, 3111 }, 3112 TableScanners: []*TableScanner{ 3113 {Schema: tableSchema, ColumnUsages: make(map[int]columnUsage)}, 3114 }, 3115 } 3116 qc.parseExprs() 3117 Ω(qc.Error).Should(BeNil()) 3118 qc.resolveTypes() 3119 Ω(qc.Error).Should(BeNil()) 3120 Ω(qc.Query.filters).Should(HaveLen(1)) 3121 Ω(qc.Query.filters[0].String()).Should(Equal("id = 1 OR id = 2")) 3122 3123 qc.Query.Filters[0] = "id in ()" 3124 qc.parseExprs() 3125 qc.resolveTypes() 3126 Ω(qc.Error).ShouldNot(BeNil()) 3127 3128 qc.Error = nil 3129 qc.Query.Filters[0] = "id in (1)" 3130 qc.parseExprs() 3131 qc.resolveTypes() 3132 Ω(qc.Error).Should(BeNil()) 3133 Ω(qc.Query.filters[0].String()).Should(Equal("id = 1")) 3134 3135 qc.Error = nil 3136 qc.Query.Filters[0] = "id in ('1')" 3137 qc.parseExprs() 3138 qc.resolveTypes() 3139 Ω(qc.Error).Should(BeNil()) 3140 Ω(qc.Query.filters[0].String()).Should(Equal("id = '1'")) 3141 3142 qc.Error = nil 3143 qc.Query.Filters[0] = "id in (1,2,3)" 3144 qc.parseExprs() 3145 qc.resolveTypes() 3146 Ω(qc.Error).Should(BeNil()) 3147 Ω(qc.Query.filters[0].String()).Should(Equal("id = 1 OR id = 2 OR id = 3")) 3148 3149 qc.Error = nil 3150 qc.Query.Filters[0] = "id not in (1,2,3)" 3151 qc.parseExprs() 3152 qc.resolveTypes() 3153 Ω(qc.Error).Should(BeNil()) 3154 Ω(qc.Query.filters[0].String()).Should(Equal("NOT(id = 1 OR id = 2 OR id = 3)")) 3155 }) 3156 3157 ginkgo.It("dayofweek and hour should work", func() { 3158 query := &AQLQuery{ 3159 Table: "table1", 3160 Filters: []string{"dayofweek(table1.time_col) = 2", "hour(table1.time_col) = 21"}, 3161 } 3162 tableSchema := &memstore.TableSchema{ 3163 ColumnIDs: map[string]int{ 3164 "time_col": 0, 3165 "id": 1, 3166 }, 3167 Schema: metaCom.Table{ 3168 Name: "table1", 3169 IsFactTable: true, 3170 Columns: []metaCom.Column{ 3171 {Name: "time_col", Type: metaCom.Uint32}, 3172 {Name: "id", Type: metaCom.Uint16}, 3173 }, 3174 }, 3175 ValueTypeByColumn: []memCom.DataType{ 3176 memCom.Uint32, 3177 memCom.Uint16, 3178 }, 3179 } 3180 qc := AQLQueryContext{ 3181 Query: query, 3182 TableSchemaByName: map[string]*memstore.TableSchema{ 3183 "table1": tableSchema, 3184 }, 3185 TableIDByAlias: map[string]int{ 3186 "table1": 0, 3187 }, 3188 TableScanners: []*TableScanner{ 3189 {Schema: tableSchema, ColumnUsages: make(map[int]columnUsage)}, 3190 }, 3191 } 3192 qc.parseExprs() 3193 Ω(qc.Error).Should(BeNil()) 3194 qc.resolveTypes() 3195 Ω(qc.Error).Should(BeNil()) 3196 Ω(qc.Query.filters).Should(HaveLen(2)) 3197 Ω(qc.Query.filters[0].String()).Should(Equal("table1.time_col / 86400 + 4 % 7 + 1 = 2")) 3198 Ω(qc.Query.filters[1].String()).Should(Equal("table1.time_col % 86400 / 3600 = 21")) 3199 }) 3200 3201 ginkgo.It("convert_tz should work", func() { 3202 query := &AQLQuery{ 3203 Table: "table1", 3204 Filters: []string{ 3205 "convert_tz(table1.time_col, 'GMT', 'America/Phoenix') = 2", 3206 "convert_tz(from_unixtime(table1.time_col / 1000), 'GMT', 'America/Phoenix') = 2", 3207 }, 3208 } 3209 tableSchema := &memstore.TableSchema{ 3210 ColumnIDs: map[string]int{ 3211 "time_col": 0, 3212 "id": 1, 3213 }, 3214 Schema: metaCom.Table{ 3215 Name: "table1", 3216 IsFactTable: true, 3217 Columns: []metaCom.Column{ 3218 {Name: "time_col", Type: metaCom.Uint32}, 3219 {Name: "id", Type: metaCom.Uint16}, 3220 }, 3221 }, 3222 ValueTypeByColumn: []memCom.DataType{ 3223 memCom.Uint32, 3224 memCom.Uint16, 3225 }, 3226 } 3227 qc := AQLQueryContext{ 3228 Query: query, 3229 TableSchemaByName: map[string]*memstore.TableSchema{ 3230 "table1": tableSchema, 3231 }, 3232 TableIDByAlias: map[string]int{ 3233 "table1": 0, 3234 }, 3235 TableScanners: []*TableScanner{ 3236 {Schema: tableSchema, ColumnUsages: make(map[int]columnUsage)}, 3237 }, 3238 } 3239 qc.parseExprs() 3240 Ω(qc.Error).Should(BeNil()) 3241 3242 qc.resolveTypes() 3243 Ω(qc.Error).Should(BeNil()) 3244 Ω(qc.Query.filters).Should(HaveLen(2)) 3245 Ω(qc.Query.filters[0].String()).Should(Equal("table1.time_col + -25200 = 2")) 3246 Ω(qc.Query.filters[1].String()).Should(Equal("table1.time_col + -25200 = 2")) 3247 3248 qc.Query.Filters = []string{"convert_tz(from_unixtime(table1.time_col), 'GMT', 'America/Phoenix') = 2"} 3249 qc.parseExprs() 3250 qc.resolveTypes() 3251 Ω(qc.Error).ShouldNot(BeNil()) 3252 Ω(qc.Error.Error()).Should(ContainSubstring("from_unixtime must be time column / 1000")) 3253 }) 3254 3255 ginkgo.It("parses point expressions", func() { 3256 q := &AQLQuery{ 3257 Table: "trips", 3258 Measures: []Measure{ 3259 { 3260 Expr: "count(*)", 3261 }, 3262 }, 3263 } 3264 3265 qc := &AQLQueryContext{ 3266 TableIDByAlias: map[string]int{ 3267 "trips": 0, 3268 }, 3269 TableScanners: []*TableScanner{ 3270 { 3271 Schema: &memstore.TableSchema{ 3272 ValueTypeByColumn: []memCom.DataType{ 3273 memCom.Uint16, 3274 memCom.GeoPoint, 3275 }, 3276 ColumnIDs: map[string]int{ 3277 "city_id": 0, 3278 "request_point": 1, 3279 }, 3280 Schema: metaCom.Table{ 3281 Columns: []metaCom.Column{ 3282 {Name: "city_id", Type: metaCom.Uint16}, 3283 {Name: "request_point", Type: metaCom.GeoPoint}, 3284 }, 3285 }, 3286 }, 3287 }, 3288 }, 3289 } 3290 qc.Query = q 3291 qc.parseExprs() 3292 Ω(qc.Error).Should(BeNil()) 3293 Ω(q.Measures[0].expr).Should(Equal(&expr.Call{ 3294 Name: "count", 3295 Args: []expr.Expr{ 3296 &expr.Wildcard{}, 3297 }, 3298 })) 3299 3300 qc.Query = &AQLQuery{ 3301 Table: "trips", 3302 Measures: []Measure{ 3303 { 3304 Expr: "count(*)", 3305 }, 3306 }, 3307 Filters: []string{ 3308 "request_point = 'point(-122.386177 37.617994)'", 3309 }, 3310 } 3311 qc.parseExprs() 3312 Ω(qc.Error).Should(BeNil()) 3313 Ω(qc.Query.Measures[0].expr).Should(Equal(&expr.Call{ 3314 Name: "count", 3315 Args: []expr.Expr{ 3316 &expr.Wildcard{}, 3317 }, 3318 })) 3319 Ω(qc.Query.filters[0]).Should(Equal(&expr.BinaryExpr{ 3320 Op: expr.EQ, 3321 LHS: &expr.VarRef{Val: "request_point"}, 3322 RHS: &expr.StringLiteral{Val: "point(-122.386177 37.617994)"}, 3323 })) 3324 3325 qc.resolveTypes() 3326 3327 Ω(qc.Query.filters[0]).Should(Equal(&expr.BinaryExpr{ 3328 Op: expr.EQ, 3329 LHS: &expr.VarRef{Val: "request_point", ColumnID: 1, TableID: 0, ExprType: expr.GeoPoint, DataType: memCom.GeoPoint}, 3330 RHS: &expr.GeopointLiteral{Val: [2]float32{37.617994, -122.386177}}, 3331 ExprType: expr.Boolean, 3332 })) 3333 3334 qc.Query = &AQLQuery{ 3335 Table: "trips", 3336 Measures: []Measure{ 3337 { 3338 Expr: "count(*)", 3339 }, 3340 }, 3341 Filters: []string{ 3342 "request_point IN ('point(-12.386177 34.617994)', 'point(-56.386177 78.617994)', 'point(-1.386177 2.617994)')", 3343 }, 3344 } 3345 qc.parseExprs() 3346 Ω(qc.Error).Should(BeNil()) 3347 Ω(qc.Query.Measures[0].expr).Should(Equal(&expr.Call{ 3348 Name: "count", 3349 Args: []expr.Expr{ 3350 &expr.Wildcard{}, 3351 }, 3352 })) 3353 Ω(qc.Query.filters[0]).Should(Equal(&expr.BinaryExpr{ 3354 Op: expr.IN, 3355 LHS: &expr.VarRef{Val: "request_point"}, 3356 RHS: &expr.Call{ 3357 Name: "", 3358 Args: []expr.Expr{ 3359 &expr.StringLiteral{Val: "point(-12.386177 34.617994)"}, 3360 &expr.StringLiteral{Val: "point(-56.386177 78.617994)"}, 3361 &expr.StringLiteral{Val: "point(-1.386177 2.617994)"}, 3362 }, 3363 ExprType: 0, 3364 }, 3365 })) 3366 3367 qc.resolveTypes() 3368 3369 Ω(qc.Query.filters[0]).Should(Equal(&expr.BinaryExpr{ 3370 Op: expr.OR, 3371 LHS: &expr.BinaryExpr{ 3372 Op: expr.OR, 3373 LHS: &expr.BinaryExpr{ 3374 Op: expr.EQ, 3375 LHS: &expr.VarRef{Val: "request_point", ColumnID: 1, TableID: 0, ExprType: expr.GeoPoint, DataType: memCom.GeoPoint}, 3376 RHS: &expr.GeopointLiteral{Val: [2]float32{34.617994, -12.386177}}, 3377 ExprType: expr.Boolean, 3378 }, 3379 RHS: &expr.BinaryExpr{ 3380 Op: expr.EQ, 3381 LHS: &expr.VarRef{Val: "request_point", ColumnID: 1, TableID: 0, ExprType: expr.GeoPoint, DataType: memCom.GeoPoint}, 3382 RHS: &expr.GeopointLiteral{Val: [2]float32{78.617994, -56.386177}}, 3383 ExprType: expr.Boolean, 3384 }, 3385 }, 3386 RHS: &expr.BinaryExpr{ 3387 Op: expr.EQ, 3388 LHS: &expr.VarRef{Val: "request_point", ColumnID: 1, TableID: 0, ExprType: expr.GeoPoint, DataType: memCom.GeoPoint}, 3389 RHS: &expr.GeopointLiteral{Val: [2]float32{2.617994, -1.386177}}, 3390 ExprType: expr.Boolean, 3391 }, 3392 })) 3393 }) 3394 3395 ginkgo.It("adjust filter to time filters", func() { 3396 schema := &memstore.TableSchema{ 3397 ValueTypeByColumn: []memCom.DataType{ 3398 memCom.Uint32, 3399 memCom.Uint16, 3400 }, 3401 ColumnIDs: map[string]int{ 3402 "request_at": 0, 3403 "id": 1, 3404 }, 3405 Schema: metaCom.Table{ 3406 IsFactTable: true, 3407 Columns: []metaCom.Column{ 3408 {Name: "request_at", Type: metaCom.Uint32}, 3409 {Name: "id", Type: metaCom.Uint16}, 3410 }, 3411 }, 3412 } 3413 q := &AQLQuery{ 3414 Table: "trips", 3415 Measures: []Measure{ 3416 {Expr: "count()"}, 3417 }, 3418 Filters: []string{ 3419 "id <= 1000", 3420 "request_at >= 1540399020000", 3421 "request_at < 1540399320000", 3422 "id > 100", 3423 }, 3424 } 3425 3426 qc := &AQLQueryContext{ 3427 Query: q, 3428 TableIDByAlias: map[string]int{ 3429 "trips": 0, 3430 }, 3431 TableScanners: []*TableScanner{ 3432 {Schema: schema, ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}}, 3433 }, 3434 } 3435 qc.processTimezone() 3436 qc.parseExprs() 3437 qc.resolveTypes() 3438 qc.processFilters() 3439 Ω(qc.Error).Should(BeNil()) 3440 3441 Ω(qc.OOPK.TimeFilters[1]).Should(Equal(&expr.BinaryExpr{ 3442 ExprType: expr.Boolean, 3443 Op: expr.LT, 3444 LHS: &expr.VarRef{ 3445 Val: "request_at", 3446 ExprType: expr.Unsigned, 3447 DataType: memCom.Uint32, 3448 }, 3449 RHS: &expr.NumberLiteral{ 3450 ExprType: expr.Unsigned, 3451 Int: 1540399320, 3452 Expr: "1540399320", 3453 }, 3454 })) 3455 Ω(qc.OOPK.TimeFilters[0]).Should(Equal(&expr.BinaryExpr{ 3456 ExprType: expr.Boolean, 3457 Op: expr.GTE, 3458 LHS: &expr.VarRef{ 3459 Val: "request_at", 3460 ExprType: expr.Unsigned, 3461 DataType: memCom.Uint32, 3462 }, 3463 RHS: &expr.NumberLiteral{ 3464 ExprType: expr.Unsigned, 3465 Int: 1540399020, 3466 Expr: "1540399020", 3467 }, 3468 })) 3469 Ω(len(qc.OOPK.MainTableCommonFilters)).Should(Equal(2)) 3470 Ω(qc.OOPK.MainTableCommonFilters[1]).Should(Equal(&expr.BinaryExpr{ 3471 ExprType: expr.Boolean, 3472 Op: expr.GT, 3473 LHS: &expr.VarRef{ 3474 Val: "id", 3475 ColumnID: 1, 3476 ExprType: expr.Unsigned, 3477 DataType: memCom.Uint16, 3478 }, 3479 RHS: &expr.NumberLiteral{ 3480 ExprType: expr.Unsigned, 3481 Val: 100, 3482 Int: 100, 3483 Expr: "100", 3484 }, 3485 })) 3486 3487 // case has no from filter 3488 q = &AQLQuery{ 3489 Table: "trips", 3490 Measures: []Measure{ 3491 {Expr: "count()"}, 3492 }, 3493 Filters: []string{ 3494 "request_at < 1540399320000", 3495 "id > 100", 3496 }, 3497 } 3498 3499 qc = &AQLQueryContext{ 3500 Query: q, 3501 TableIDByAlias: map[string]int{ 3502 "trips": 0, 3503 }, 3504 TableScanners: []*TableScanner{ 3505 {Schema: schema, ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}}, 3506 }, 3507 } 3508 qc.processTimezone() 3509 qc.parseExprs() 3510 qc.resolveTypes() 3511 qc.processFilters() 3512 Ω(qc.Error).ShouldNot(BeNil()) 3513 3514 // case has multiple from filter 3515 q = &AQLQuery{ 3516 Table: "trips", 3517 Measures: []Measure{ 3518 {Expr: "count()"}, 3519 }, 3520 Filters: []string{ 3521 "request_at >= 1540399020000", 3522 "request_at >= 1540399080000", 3523 "request_at < 1540399320000", 3524 "id > 100", 3525 }, 3526 } 3527 3528 qc = &AQLQueryContext{ 3529 Query: q, 3530 TableIDByAlias: map[string]int{ 3531 "trips": 0, 3532 }, 3533 TableScanners: []*TableScanner{ 3534 {Schema: schema, ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}}, 3535 }, 3536 } 3537 qc.processTimezone() 3538 qc.parseExprs() 3539 Ω(qc.Error).ShouldNot(BeNil()) 3540 3541 // case has timezone 3542 q = &AQLQuery{ 3543 Table: "trips", 3544 Measures: []Measure{ 3545 {Expr: "count()"}, 3546 }, 3547 Filters: []string{ 3548 "request_at >= 1540399140000", 3549 "request_at < 1540399320000", 3550 "id > 100", 3551 }, 3552 Timezone: "Pacific/Auckland", 3553 } 3554 3555 qc = &AQLQueryContext{ 3556 Query: q, 3557 TableIDByAlias: map[string]int{ 3558 "trips": 0, 3559 }, 3560 TableScanners: []*TableScanner{ 3561 {Schema: schema, ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}}, 3562 }, 3563 } 3564 qc.processTimezone() 3565 qc.parseExprs() 3566 qc.resolveTypes() 3567 qc.processFilters() 3568 Ω(qc.Error).Should(BeNil()) 3569 3570 // case using stringliteral in filter 3571 q = &AQLQuery{ 3572 Table: "trips", 3573 Measures: []Measure{ 3574 {Expr: "count()"}, 3575 }, 3576 Filters: []string{ 3577 "request_at >= '-1d'", 3578 "request_at < 'now'", 3579 "id > 100", 3580 }, 3581 } 3582 3583 qc = &AQLQueryContext{ 3584 Query: q, 3585 TableIDByAlias: map[string]int{ 3586 "trips": 0, 3587 }, 3588 TableScanners: []*TableScanner{ 3589 {Schema: schema, ColumnUsages: map[int]columnUsage{0: columnUsedByLiveBatches}}, 3590 }, 3591 } 3592 qc.processTimezone() 3593 qc.parseExprs() 3594 Ω(qc.Error).Should(BeNil()) 3595 3596 qc.resolveTypes() 3597 qc.processFilters() 3598 Ω(qc.Error).Should(BeNil()) 3599 }) 3600 })