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  })