Skip to content

Commit

Permalink
Add support for NULLS_FIRST and NULLS_LAST sorting order.
Browse files Browse the repository at this point in the history
  • Loading branch information
go-jet committed Feb 10, 2024
1 parent 0fc51cf commit dab153a
Show file tree
Hide file tree
Showing 8 changed files with 882 additions and 19 deletions.
10 changes: 8 additions & 2 deletions internal/jet/dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Dialect interface {
IdentifierQuoteChar() byte
ArgumentPlaceholder() QueryPlaceholderFunc
IsReservedWord(name string) bool
SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
}

// SerializerFunc func
Expand All @@ -33,6 +34,7 @@ type DialectParams struct {
IdentifierQuoteChar byte
ArgumentPlaceholder QueryPlaceholderFunc
ReservedWords []string
SerializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
}

// NewDialect creates new dialect with params
Expand All @@ -46,6 +48,7 @@ func NewDialect(params DialectParams) Dialect {
identifierQuoteChar: params.IdentifierQuoteChar,
argumentPlaceholder: params.ArgumentPlaceholder,
reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords),
serializeOrderBy: params.SerializeOrderBy,
}
}

Expand All @@ -58,8 +61,7 @@ type dialectImpl struct {
identifierQuoteChar byte
argumentPlaceholder QueryPlaceholderFunc
reservedWords map[string]bool

supportsReturning bool
serializeOrderBy func(expression Expression, ascending, nullsFirst *bool) SerializerFunc
}

func (d *dialectImpl) Name() string {
Expand Down Expand Up @@ -101,6 +103,10 @@ func (d *dialectImpl) IsReservedWord(name string) bool {
return isReservedWord
}

func (d *dialectImpl) SerializeOrderBy() func(expression Expression, ascending, nullsFirst *bool) SerializerFunc {
return d.serializeOrderBy
}

func arrayOfStringsToMapOfStrings(arr []string) map[string]bool {
ret := map[string]bool{}
for _, elem := range arr {
Expand Down
18 changes: 14 additions & 4 deletions internal/jet/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,24 @@ func (e *ExpressionInterfaceImpl) AS(alias string) Projection {
return newAlias(e.Parent, alias)
}

// ASC expression will be used to sort query result in ascending order
// ASC expression will be used to sort a query result in ascending order
func (e *ExpressionInterfaceImpl) ASC() OrderByClause {
return newOrderByClause(e.Parent, true)
return newOrderByAscending(e.Parent, true)
}

// DESC expression will be used to sort query result in descending order
// DESC expression will be used to sort a query result in descending order
func (e *ExpressionInterfaceImpl) DESC() OrderByClause {
return newOrderByClause(e.Parent, false)
return newOrderByAscending(e.Parent, false)
}

// NULLS_FIRST specifies sort where null values appear before all non-null values
func (e *ExpressionInterfaceImpl) NULLS_FIRST() OrderByClause {
return newOrderByNullsFirst(e.Parent, true)
}

// NULLS_LAST specifies sort where null values appear after all non-null values
func (e *ExpressionInterfaceImpl) NULLS_LAST() OrderByClause {
return newOrderByNullsFirst(e.Parent, false)
}

func (e *ExpressionInterfaceImpl) serializeForGroupBy(statement StatementType, out *SQLBuilder) {
Expand Down
70 changes: 60 additions & 10 deletions internal/jet/order_by_clause.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,78 @@ package jet

// OrderByClause interface
type OrderByClause interface {
// NULLS_FIRST specifies sort where null values appear before all non-null values.
// For some dialects(mysql,mariadb), which do not support NULL_FIRST, NULL_FIRST is simulated
// with additional IS_NOT_NULL expression.
// For instance,
// Rental.ReturnDate.DESC().NULLS_FIRST()
// would translate to,
// rental.return_date IS NOT NULL, rental.return_date DESC
NULLS_FIRST() OrderByClause

// NULLS_LAST specifies sort where null values appear after all non-null values.
// For some dialects(mysql,mariadb), which do not support NULLS_LAST, NULLS_LAST is simulated
// with additional IS_NULL expression.
// For instance,
// Rental.ReturnDate.ASC().NULLS_LAST()
// would translate to,
// rental.return_date IS NULL, rental.return_date ASC
NULLS_LAST() OrderByClause

serializeForOrderBy(statement StatementType, out *SQLBuilder)
}

type orderByClauseImpl struct {
expression Expression
ascent bool
ascending *bool
nullsFirst *bool
}

func (ord *orderByClauseImpl) NULLS_FIRST() OrderByClause {
nullsFirst := true
ord.nullsFirst = &nullsFirst
return ord
}
func (ord *orderByClauseImpl) NULLS_LAST() OrderByClause {
nullsFirst := false
ord.nullsFirst = &nullsFirst
return ord
}

func (o *orderByClauseImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
if o.expression == nil {
func (ord *orderByClauseImpl) serializeForOrderBy(statement StatementType, out *SQLBuilder) {
customSerializer := out.Dialect.SerializeOrderBy()
if customSerializer != nil {
customSerializer(ord.expression, ord.ascending, ord.nullsFirst)(statement, out)
return
}

if ord.expression == nil {
panic("jet: nil expression in ORDER BY clause")
}

o.expression.serializeForOrderBy(statement, out)
ord.expression.serializeForOrderBy(statement, out)

if ord.ascending != nil {
if *ord.ascending {
out.WriteString("ASC")
} else {
out.WriteString("DESC")
}
}

if o.ascent {
out.WriteString("ASC")
} else {
out.WriteString("DESC")
if ord.nullsFirst != nil {
if *ord.nullsFirst {
out.WriteString("NULLS FIRST")
} else {
out.WriteString("NULLS LAST")
}
}
}

func newOrderByClause(expression Expression, ascent bool) OrderByClause {
return &orderByClauseImpl{expression: expression, ascent: ascent}
func newOrderByAscending(expression Expression, ascending bool) OrderByClause {
return &orderByClauseImpl{expression: expression, ascending: &ascending}
}

func newOrderByNullsFirst(expression Expression, nullsFirst bool) OrderByClause {
return &orderByClauseImpl{expression: expression, nullsFirst: &nullsFirst}
}
4 changes: 4 additions & 0 deletions internal/jet/serializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ func Serialize(exp Serializer, statementType StatementType, out *SQLBuilder, opt
exp.serialize(statementType, out, options...)
}

func SerializeForOrderBy(exp Expression, statementType StatementType, out *SQLBuilder) {
exp.serializeForOrderBy(statementType, out)
}

func contains(options []SerializeOption, option SerializeOption) bool {
for _, opt := range options {
if opt == option {
Expand Down
50 changes: 49 additions & 1 deletion mysql/dialect.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ func newDialect() jet.Dialect {
ArgumentPlaceholder: func(int) string {
return "?"
},
ReservedWords: reservedWords,
ReservedWords: reservedWords,
SerializeOrderBy: serializeOrderBy,
}

return jet.NewDialect(mySQLDialectParams)
Expand Down Expand Up @@ -162,6 +163,53 @@ func mysqlNOTREGEXPLIKEoperator(expressions ...jet.Serializer) jet.SerializerFun
}
}

func serializeOrderBy(expression Expression, ascending, nullsFirst *bool) jet.SerializerFunc {
return func(statement jet.StatementType, out *jet.SQLBuilder, options ...jet.SerializeOption) {

if nullsFirst == nil {
jet.SerializeForOrderBy(expression, statement, out)

if ascending != nil {
serializeAscending(*ascending, out)
}
return
}

asc := true

if ascending != nil {
asc = *ascending
}

if asc {
if !*nullsFirst {
jet.SerializeForOrderBy(expression.IS_NULL(), statement, out)
out.WriteString(", ")
}
jet.SerializeForOrderBy(expression, statement, out)
if ascending != nil {
serializeAscending(asc, out)
}
} else {
if *nullsFirst {
jet.SerializeForOrderBy(expression.IS_NOT_NULL(), statement, out)
out.WriteString(", ")
}

jet.SerializeForOrderBy(expression, statement, out)
serializeAscending(asc, out)
}
}
}

func serializeAscending(ascending bool, out *jet.SQLBuilder) {
if ascending {
out.WriteString("ASC")
} else {
out.WriteString("DESC")
}
}

var reservedWords = []string{
"ACCESSIBLE",
"ADD",
Expand Down
Loading

0 comments on commit dab153a

Please sign in to comment.