33package frontend_connectors
44
55import (
6+ "context"
67 "fmt"
78 "github.com/QuesmaOrg/quesma/platform/clickhouse"
89 "github.com/QuesmaOrg/quesma/platform/common_table"
910 "github.com/QuesmaOrg/quesma/platform/config"
1011 "github.com/QuesmaOrg/quesma/platform/logger"
1112 "github.com/QuesmaOrg/quesma/platform/model"
1213 "github.com/QuesmaOrg/quesma/platform/model/typical_queries"
14+ "github.com/QuesmaOrg/quesma/platform/parsers/elastic_query_dsl"
1315 "github.com/QuesmaOrg/quesma/platform/schema"
1416 "github.com/QuesmaOrg/quesma/platform/transformations"
1517 "sort"
@@ -58,7 +60,8 @@ func (s *SchemaCheckPass) applyBooleanLiteralLowering(index schema.Schema, query
5860 if strings .Contains (boolLiteral , "true" ) || strings .Contains (boolLiteral , "false" ) {
5961 boolLiteral = strings .TrimLeft (boolLiteral , "'" )
6062 boolLiteral = strings .TrimRight (boolLiteral , "'" )
61- return model .NewLiteralWithEscapeType (boolLiteral , e .EscapeType )
63+ var asAny any = boolLiteral
64+ return e .CloneAndOverride (& asAny , nil , nil )
6265 }
6366 }
6467 return e .Clone ()
@@ -235,7 +238,7 @@ func (s *SchemaCheckPass) applyGeoTransformations(schemaInstance schema.Schema,
235238 }
236239 }
237240
238- return model . NewFunction ( e . Name , b . VisitChildren ( e . Args ) ... )
241+ return visitFunction ( b , e )
239242 }
240243
241244 visitor .OverrideVisitSelectCommand = func (v * model.BaseExprVisitor , query model.SelectCommand ) interface {} {
@@ -534,7 +537,7 @@ func (s *SchemaCheckPass) applyWildcardExpansion(indexSchema schema.Schema, quer
534537
535538 for _ , selectColumn := range query .SelectCommand .Columns {
536539
537- if selectColumn == model .NewWildcardExpr {
540+ if model .IsWildcard ( selectColumn ) {
538541 hasWildcard = true
539542 } else {
540543 newColumns = append (newColumns , selectColumn )
@@ -615,9 +618,9 @@ func (s *SchemaCheckPass) applyFullTextField(indexSchema schema.Schema, query *m
615618
616619 if len (fullTextFields ) == 0 {
617620 if (strings .ToUpper (e .Op ) == "LIKE" || strings .ToUpper (e .Op ) == "ILIKE" ) && model .AsString (e .Right ) == "'%'" {
618- return model .NewLiteral ( true )
621+ return model .TrueExpr
619622 }
620- return model .NewLiteral ( false )
623+ return model .FalseExpr
621624 }
622625
623626 var expressions []model.Expr
@@ -632,7 +635,7 @@ func (s *SchemaCheckPass) applyFullTextField(indexSchema schema.Schema, query *m
632635 }
633636 }
634637
635- return model . NewInfixExpr ( e . Left . Accept ( b ).(model. Expr ) , e . Op , e . Right . Accept ( b ).(model. Expr ) )
638+ return visitInfix ( b , e )
636639 }
637640
638641 expr := query .SelectCommand .Accept (visitor )
@@ -750,7 +753,7 @@ func (s *SchemaCheckPass) applyFieldEncoding(indexSchema schema.Schema, query *m
750753 if hasAttributesValuesColumn {
751754 return model .NewArrayAccess (model .NewColumnRef (clickhouse .AttributesValuesColumn ), model .NewLiteral (fmt .Sprintf ("'%s'" , e .ColumnName )))
752755 } else {
753- return model .NewLiteral ( "NULL" )
756+ return model .NullExpr
754757 }
755758 }
756759 }
@@ -870,7 +873,7 @@ func (s *SchemaCheckPass) convertQueryDateTimeFunctionToClickhouse(indexSchema s
870873 // add more
871874
872875 default :
873- return model . NewFunction ( e . Name , b . VisitChildren ( e . Args ) ... )
876+ return visitFunction ( b , e )
874877 }
875878 }
876879
@@ -904,7 +907,7 @@ func (s *SchemaCheckPass) checkAggOverUnsupportedType(indexSchema schema.Schema,
904907 if strings .HasPrefix (col .InternalPropertyType , dbTypePrefix ) {
905908 logger .Warn ().Msgf ("Aggregation '%s' over unsupported type '%s' in column '%s'" , e .Name , dbTypePrefix , col .InternalPropertyName .AsString ())
906909 args := b .VisitChildren (e .Args )
907- args [0 ] = model .NewLiteral ( "NULL" )
910+ args [0 ] = model .NullExpr
908911 return model .NewFunction (e .Name , args ... )
909912 }
910913 }
@@ -915,15 +918,15 @@ func (s *SchemaCheckPass) checkAggOverUnsupportedType(indexSchema schema.Schema,
915918 if access .ColumnRef .ColumnName == clickhouse .AttributesValuesColumn {
916919 logger .Warn ().Msgf ("Unsupported case. Aggregation '%s' over attribute named: '%s'" , e .Name , access .Index )
917920 args := b .VisitChildren (e .Args )
918- args [0 ] = model .NewLiteral ( "NULL" )
921+ args [0 ] = model .NullExpr
919922 return model .NewFunction (e .Name , args ... )
920923 }
921924 }
922925 }
923926 }
924927 }
925928
926- return model . NewFunction ( e . Name , b . VisitChildren ( e . Args ) ... )
929+ return visitFunction ( b , e )
927930 }
928931
929932 expr := query .SelectCommand .Accept (visitor )
@@ -974,6 +977,76 @@ func (s *SchemaCheckPass) applyAliasColumns(indexSchema schema.Schema, query *mo
974977 return query , nil
975978}
976979
980+ func visitFunction (b * model.BaseExprVisitor , f model.FunctionExpr ) interface {} {
981+ return model .NewFunction (f .Name , b .VisitChildren (f .Args )... )
982+ }
983+
984+ func visitInfix (b * model.BaseExprVisitor , e model.InfixExpr ) interface {} {
985+ return model .NewInfixExpr (e .Left .Accept (b ).(model.Expr ), e .Op , e .Right .Accept (b ).(model.Expr ))
986+ }
987+
988+ func (s * SchemaCheckPass ) acceptIntsAsTimestamps (indexSchema schema.Schema , query * model.Query ) (* model.Query , error ) {
989+ table , exists := s .tableDiscovery .TableDefinitions ().Load (query .TableName )
990+ if ! exists {
991+ return nil , fmt .Errorf ("table %s not found" , query .TableName )
992+ }
993+
994+ dateManager := elastic_query_dsl .NewDateManager (context .Background ())
995+ visitor := model .NewBaseVisitor ()
996+
997+ visitor .OverrideVisitInfix = func (b * model.BaseExprVisitor , e model.InfixExpr ) interface {} {
998+ col , okLeft := model .ExtractColRef (e .Left )
999+ lit , _ := model .ToLiteral (e .Right )
1000+ ts , okRight := model .ToLiteralsValue (e .Right )
1001+ if okLeft && okRight && table .IsInt (col .ColumnName ) {
1002+ format := ""
1003+ if f , ok := lit .Format (); ok {
1004+ format = f
1005+ }
1006+ expr , ok := dateManager .ParseDateUsualFormat (ts , clickhouse .DateTime64 , format )
1007+ if ! ok {
1008+ // FIXME hacky but seems working
1009+ if tsStr , ok_ := ts .(string ); ok_ && len (tsStr ) > 2 {
1010+ expr , ok = dateManager .ParseDateUsualFormat (tsStr [1 :len (tsStr )- 1 ], clickhouse .DateTime64 , format )
1011+ }
1012+ }
1013+ if ok {
1014+ if f , okF := model .ToFunction (expr ); okF && f .Name == "fromUnixTimestamp64Milli" && len (f .Args ) == 1 {
1015+ if l , okL := model .ToLiteral (f .Args [0 ]); okL {
1016+ if _ , exists := l .Format (); exists { // heuristics: it's a date <=> it has a format
1017+ return model .NewInfixExpr (col , e .Op , f .Args [0 ])
1018+ }
1019+ }
1020+ }
1021+ }
1022+ }
1023+ return visitInfix (b , e )
1024+ }
1025+
1026+ visitor .OverrideVisitFunction = func (b * model.BaseExprVisitor , f model.FunctionExpr ) interface {} {
1027+ if f .Name == "toUnixTimestamp64Milli" && len (f .Args ) == 1 {
1028+ if col , ok := model .ExtractColRef (f .Args [0 ]); ok && table .IsInt (col .ColumnName ) {
1029+ // erases toUnixTimestamp64Milli
1030+ return f .Args [0 ]
1031+ }
1032+ }
1033+ if f .Name == "toTimezone" && len (f .Args ) == 2 {
1034+ if col , ok := model .ExtractColRef (f .Args [0 ]); ok && table .IsInt (col .ColumnName ) {
1035+ // adds fromUnixTimestamp64Milli
1036+ return model .NewFunction ("toTimezone" , model .NewFunction ("fromUnixTimestamp64Milli" , f .Args [0 ]), f .Args [1 ])
1037+ }
1038+ }
1039+ return visitFunction (b , f )
1040+ }
1041+
1042+ expr := query .SelectCommand .Accept (visitor )
1043+ if _ , ok := expr .(* model.SelectCommand ); ok {
1044+ query .SelectCommand = * expr .(* model.SelectCommand )
1045+ }
1046+
1047+ return query , nil
1048+ }
1049+
9771050func (s * SchemaCheckPass ) Transform (plan * model.ExecutionPlan ) (* model.ExecutionPlan , error ) {
9781051
9791052 transformationChain := []struct {
@@ -988,6 +1061,7 @@ func (s *SchemaCheckPass) Transform(plan *model.ExecutionPlan) (*model.Execution
9881061 return transformations .ApplyAllNecessaryCommonTransformations (query , schema , s .cfg .MapFieldsDiscoveringEnabled )
9891062 }},
9901063 {TransformationName : "AliasColumnsTransformation" , Transformation : s .applyAliasColumns },
1064+ {TransformationName : "AcceptIntsAsTimestamps" , Transformation : s .acceptIntsAsTimestamps },
9911065
9921066 // Section 2: generic schema based transformations
9931067 //
@@ -1082,7 +1156,7 @@ func (s *SchemaCheckPass) applyMatchOperator(indexSchema schema.Schema, query *m
10821156 okLeft = true
10831157 lhsCol = lhsT .ColumnRef
10841158 default :
1085- return model . NewInfixExpr ( e . Left . Accept ( b ).(model. Expr ) , e . Op , e . Right . Accept ( b ).(model. Expr ) )
1159+ return visitInfix ( b , e )
10861160 }
10871161
10881162 rhsValue , ok := rhs .Value .(string )
@@ -1091,7 +1165,7 @@ func (s *SchemaCheckPass) applyMatchOperator(indexSchema schema.Schema, query *m
10911165 // only strings can be ILIKEd, everything else is a simple =
10921166 return model .NewInfixExpr (e .Left .Accept (b ).(model.Expr ), "=" , e .Right .Accept (b ).(model.Expr ))
10931167 } else {
1094- return model . NewInfixExpr ( e . Left . Accept ( b ).(model. Expr ) , e . Op , e . Right . Accept ( b ).(model. Expr ) )
1168+ return visitInfix ( b , e )
10951169 }
10961170 }
10971171
@@ -1131,7 +1205,7 @@ func (s *SchemaCheckPass) applyMatchOperator(indexSchema schema.Schema, query *m
11311205 switch field .Type .String () {
11321206 case schema .QuesmaTypeInteger .Name , schema .QuesmaTypeLong .Name , schema .QuesmaTypeUnsignedLong .Name , schema .QuesmaTypeFloat .Name , schema .QuesmaTypeBoolean .Name :
11331207 rhs .Value = strings .Trim (rhsValue , "%" )
1134- rhs .EscapeType = model .NormalNotEscaped
1208+ rhs .Attrs [ model . EscapeKey ] = model .NormalNotEscaped
11351209 return equal ()
11361210 case schema .QuesmaTypeKeyword .Name :
11371211 return equal ()
@@ -1168,14 +1242,17 @@ func (s *SchemaCheckPass) applyMatchOperator(indexSchema schema.Schema, query *m
11681242
11691243 // sanity check for map type with two elements
11701244 if len (kvTypes ) == 2 {
1171- rhsValue : = rhs .Value .(string )
1245+ rhsValue = rhs .Value .(string )
11721246 rhsValue = strings .TrimPrefix (rhsValue , "'" )
11731247 rhsValue = strings .TrimSuffix (rhsValue , "'" )
11741248
11751249 // here we check if the value of the map is string or not
11761250
11771251 if strings .Contains (kvTypes [1 ], "String" ) {
1178- return model .NewInfixExpr (arrayElementFn .Accept (b ).(model.Expr ), "iLIKE" , model .NewLiteralWithEscapeType (rhsValue , model .NotEscapedLikeFull ))
1252+ newRhs := rhs .Clone ()
1253+ newRhs .Value = rhsValue
1254+ newRhs .Attrs [model .EscapeKey ] = model .NotEscapedLikeFull
1255+ return model .NewInfixExpr (arrayElementFn .Accept (b ).(model.Expr ), "iLIKE" , newRhs )
11791256 } else {
11801257 return model .NewInfixExpr (arrayElementFn .Accept (b ).(model.Expr ), "=" , e .Right .Accept (b ).(model.Expr ))
11811258 }
@@ -1190,7 +1267,7 @@ func (s *SchemaCheckPass) applyMatchOperator(indexSchema schema.Schema, query *m
11901267 if e .Op == model .MatchOperator {
11911268 logger .Error ().Msgf ("Match operator is not supported for column %v (expr: %v)" , lhsCol , e )
11921269 }
1193- return model . NewInfixExpr ( e . Left . Accept ( b ).(model. Expr ) , e . Op , e . Right . Accept ( b ).(model. Expr ) )
1270+ return visitInfix ( b , e )
11941271 }
11951272
11961273 expr := query .SelectCommand .Accept (visitor )
0 commit comments