diff --git a/store/postgres/postgres.go b/store/postgres/postgres.go index 80db2256b..5419ed7ed 100644 --- a/store/postgres/postgres.go +++ b/store/postgres/postgres.go @@ -4,6 +4,9 @@ import ( "context" "database/sql" "fmt" + "net/url" + "strings" + "github.com/Masterminds/squirrel" "github.com/cortezaproject/corteza-server/pkg/ql" "github.com/cortezaproject/corteza-server/store" @@ -12,8 +15,6 @@ import ( "github.com/lib/pq" "github.com/ngrok/sqlmw" "go.uber.org/zap" - "net/url" - "strings" ) type ( @@ -42,6 +43,7 @@ func Connect(ctx context.Context, dsn string) (store.Storer, error) { cfg.PlaceholderFormat = squirrel.Dollar cfg.ErrorHandler = errorHandler cfg.SqlFunctionHandler = sqlFunctionHandler + cfg.ASTFormatter = sqlASTFormatter cfg.CastModuleFieldToColumnType = fieldToColumnTypeCaster if s.Store, err = rdbms.Connect(ctx, cfg); err != nil { diff --git a/store/postgres/sql_functions.go b/store/postgres/sql_functions.go index bd9f2f757..e2ebfe7fb 100644 --- a/store/postgres/sql_functions.go +++ b/store/postgres/sql_functions.go @@ -2,10 +2,41 @@ package postgres import ( "fmt" - "github.com/cortezaproject/corteza-server/pkg/ql" "strings" + + "github.com/cortezaproject/corteza-server/pkg/ql" + "github.com/cortezaproject/corteza-server/pkg/qlng" + "github.com/cortezaproject/corteza-server/store/rdbms" ) +var ( + sqlExprRegistry = map[string]rdbms.HandlerSig{ + // functions + // - filtering + "quarter": makeGenericExtrFncHandler("QUARTER"), + "year": makeGenericExtrFncHandler("YEAR"), + "month": makeGenericExtrFncHandler("MONTH"), + "date": makeGenericExtrFncHandler("DAY"), + } +) + +func makeGenericExtrFncHandler(extr string) rdbms.HandlerSig { + return func(aa ...rdbms.FormattedASTArgs) (out string, args []interface{}, selfEnclosed bool, err error) { + if len(aa) != 1 { + err = fmt.Errorf("expecting 1 arguments, got %d", len(aa)) + return + } + + out = fmt.Sprintf("EXTRACT(%s FROM %s)", extr, aa[0].S) + args = aa[0].Args + return + } +} + +func sqlASTFormatter(n *qlng.ASTNode) rdbms.HandlerSig { + return sqlExprRegistry[n.Ref] +} + func sqlFunctionHandler(f ql.Function) (ql.ASTNode, error) { switch strings.ToUpper(f.Name) { case "QUARTER", "YEAR": diff --git a/store/rdbms/compose_record_datasource.go b/store/rdbms/compose_record_datasource.go index ecc1bf25c..c9f48f9fb 100644 --- a/store/rdbms/compose_record_datasource.go +++ b/store/rdbms/compose_record_datasource.go @@ -114,7 +114,7 @@ func (r *recordDatasource) Group(d report.GroupDefinition, name string) (bool, e }() var ( - q = squirrel.Select() + q = squirrel.Select().PlaceholderFormat(r.store.config.PlaceholderFormat) err error ) @@ -258,14 +258,17 @@ func (r *recordDatasource) partition(ctx context.Context, partitionSize uint, pa var prt squirrel.SelectBuilder if len(ss) > 0 { prt = squirrel.Select(fmt.Sprintf("*, row_number() over(partition by %s order by %s) as pp_rank", partitionCol, strings.Join(ss, ","))). + PlaceholderFormat(r.store.config.PlaceholderFormat). FromSelect(q, "partition_base") } else { prt = squirrel.Select(fmt.Sprintf("*, row_number() over(partition by %s) as pp_rank", partitionCol)). + PlaceholderFormat(r.store.config.PlaceholderFormat). FromSelect(q, "partition_base") } // the sort is already defined when partitioning so it's unneeded here q = squirrel.Select("*"). + PlaceholderFormat(r.store.config.PlaceholderFormat). FromSelect(prt, "partition_wrap"). Where(fmt.Sprintf("pp_rank <= %d", partitionSize)) @@ -295,7 +298,7 @@ func (r *recordDatasource) preloadQuery(def *report.FrameDefinition) (squirrel.S // when filtering/sorting, wrap the base query in a sub-select, so we don't need to // worry about exact column names. if def.Filter != nil && def.Filter.ASTNode != nil || def.Sort != nil { - q = squirrel.Select("*").FromSelect(q, "w_base") + q = squirrel.Select("*").PlaceholderFormat(r.store.config.PlaceholderFormat).FromSelect(q, "w_base") } // - filtering @@ -468,7 +471,9 @@ func (r *recordDatasource) baseQuery(f *report.Filter) (sqb squirrel.SelectBuild } // @todo this is temporary!! - sqb = squirrel.Select("*").FromSelect(sqb, "q_base") + sqb = squirrel.Select("*"). + PlaceholderFormat(r.store.config.PlaceholderFormat). + FromSelect(sqb, "q_base") // - any initial filtering we may need to do //