diff --git a/store/mysql/mysql.go b/store/mysql/mysql.go index 261807348..03f151c42 100644 --- a/store/mysql/mysql.go +++ b/store/mysql/mysql.go @@ -43,6 +43,7 @@ func Connect(ctx context.Context, dsn string) (store.Storer, error) { cfg.ErrorHandler = errorHandler cfg.UpsertBuilder = UpsertBuilder cfg.CastModuleFieldToColumnType = fieldToColumnTypeCaster + cfg.SqlSortHandler = SqlSortHandler if s.Store, err = rdbms.Connect(ctx, cfg); err != nil { return nil, err diff --git a/store/mysql/sql_order.go b/store/mysql/sql_order.go new file mode 100644 index 000000000..eb17b453c --- /dev/null +++ b/store/mysql/sql_order.go @@ -0,0 +1,11 @@ +package mysql + +import "fmt" + +func SqlSortHandler(exp string, desc bool) string { + if desc { + return fmt.Sprintf("%s DESC", exp) + } else { + return fmt.Sprintf("%s ASC", exp) + } +} diff --git a/store/rdbms/compose_records.go b/store/rdbms/compose_records.go index c71447653..6805ba4b2 100644 --- a/store/rdbms/compose_records.go +++ b/store/rdbms/compose_records.go @@ -321,8 +321,6 @@ func (s Store) composeRecordsSorter(m *types.Module, q squirrel.SelectBuilder, s if sortable[c.Column] { sqlSort[i] = c.Column } else if f := m.Fields.FindByName(c.Column); f != nil { - //sqlSort[i] = fmt.Sprintf("%s%s.value", composeRecordValueAliasPfx, sort[i].Column) - // sqlSort[i] = fmt.Sprintf("COALESCE(%s%s.value, '')", composeRecordValueAliasPfx + sort[i].Column) sqlSort[i], err = s.config.CastModuleFieldToColumnType(f, c.Column) } else { err = fmt.Errorf("could not sort by unknown column: %s", c.Column) @@ -332,9 +330,8 @@ func (s Store) composeRecordsSorter(m *types.Module, q squirrel.SelectBuilder, s return q, err } - if sort[i].Descending { - sqlSort[i] += " DESC" - } + // Apply proper sorting param + sqlSort[i] = s.config.SqlSortHandler(sqlSort[i], sort[i].Descending) } return q.OrderBy(sqlSort...), nil diff --git a/store/rdbms/rdbms.go b/store/rdbms/rdbms.go index ca0a3259f..ea6c4c4e4 100644 --- a/store/rdbms/rdbms.go +++ b/store/rdbms/rdbms.go @@ -445,6 +445,14 @@ func setOrderBy(q squirrel.SelectBuilder, sort filter.SortExprSet, ss ...string) return q.OrderBy(sqlSort...), nil } +func SqlSortHandler(exp string, desc bool) string { + if desc { + return fmt.Sprintf("%s DESC NULLS LAST", exp) + } else { + return fmt.Sprintf("%s ASC NULLS FIRST", exp) + } +} + // TxNoRetry - Transaction retry handler // // Only returns false so transactions will never retry diff --git a/store/rdbms/rdbms_config.go b/store/rdbms/rdbms_config.go index f62f2accc..f3c4efbe7 100644 --- a/store/rdbms/rdbms_config.go +++ b/store/rdbms/rdbms_config.go @@ -111,6 +111,11 @@ type ( // Functions are used in filters and aggregations SqlFunctionHandler func(f ql.Function) (ql.ASTNode, error) + // SqlSortHandler handles construction of sorting expression + // + // MySQL does not support NULLS FIRST/LAST and without it sorts nulls differently then Postgres/SQLite + SqlSortHandler func(exp string, desc bool) string + CastModuleFieldToColumnType func(ModuleFieldTypeDetector, string) (string, error) } ) @@ -184,6 +189,10 @@ func (c *Config) SetDefaults() { if c.TriggerHandlers == nil { c.TriggerHandlers = TriggerHandlers{} } + + if c.SqlSortHandler == nil { + c.SqlSortHandler = SqlSortHandler + } } // ParseExtra parses extra params (params starting with *)