diff --git a/internal/jet/dialect.go b/internal/jet/dialect.go index 434e3b84..f3ad2b40 100644 --- a/internal/jet/dialect.go +++ b/internal/jet/dialect.go @@ -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 @@ -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 @@ -46,6 +48,7 @@ func NewDialect(params DialectParams) Dialect { identifierQuoteChar: params.IdentifierQuoteChar, argumentPlaceholder: params.ArgumentPlaceholder, reservedWords: arrayOfStringsToMapOfStrings(params.ReservedWords), + serializeOrderBy: params.SerializeOrderBy, } } @@ -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 { @@ -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 { diff --git a/internal/jet/expression.go b/internal/jet/expression.go index 436b9d62..d62920c9 100644 --- a/internal/jet/expression.go +++ b/internal/jet/expression.go @@ -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) { diff --git a/internal/jet/order_by_clause.go b/internal/jet/order_by_clause.go index 55fb7d24..09d3ea60 100644 --- a/internal/jet/order_by_clause.go +++ b/internal/jet/order_by_clause.go @@ -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} } diff --git a/internal/jet/serializer.go b/internal/jet/serializer.go index 93a1d3ba..9d36de45 100644 --- a/internal/jet/serializer.go +++ b/internal/jet/serializer.go @@ -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 { diff --git a/mysql/dialect.go b/mysql/dialect.go index 24b8755d..18d2eec7 100644 --- a/mysql/dialect.go +++ b/mysql/dialect.go @@ -26,7 +26,8 @@ func newDialect() jet.Dialect { ArgumentPlaceholder: func(int) string { return "?" }, - ReservedWords: reservedWords, + ReservedWords: reservedWords, + SerializeOrderBy: serializeOrderBy, } return jet.NewDialect(mySQLDialectParams) @@ -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", diff --git a/tests/mysql/select_test.go b/tests/mysql/select_test.go index deb45295..8e03f829 100644 --- a/tests/mysql/select_test.go +++ b/tests/mysql/select_test.go @@ -3,6 +3,7 @@ package mysql import ( "context" "database/sql" + "github.com/go-jet/jet/v2/postgres" "strings" "testing" "time" @@ -296,6 +297,296 @@ ORDER BY inventory.film_id, inventory.store_id; `) } +func TestOrderBy(t *testing.T) { + // NULLS_FIRST and NULLS_LAST are simulated using IS_NULL, ... + + ensureNullsFirstRentalResult := func(t *testing.T, stmt postgres.Statement, asc bool) { + var dest []model.Rental + + err := stmt.Query(db, &dest) + require.NoError(t, err) + require.Len(t, dest, 200) + require.Nil(t, dest[0].ReturnDate) + require.NotNil(t, dest[199].ReturnDate) + + if asc { + require.True(t, dest[198].ReturnDate.Before(*dest[199].ReturnDate)) + } else { + require.True(t, dest[199].ReturnDate.Before(*dest[198].ReturnDate)) + } + } + + ensureNullsLastRentalResult := func(t *testing.T, stmt postgres.Statement, asc bool) { + var dest []model.Rental + + err := stmt.Query(db, &dest) + require.NoError(t, err) + require.Len(t, dest, 200) + require.NotNil(t, dest[0].ReturnDate) + require.Nil(t, dest[199].ReturnDate) + + if asc { + require.True(t, dest[0].ReturnDate.Before(*dest[1].ReturnDate)) + } else { + require.True(t, dest[1].ReturnDate.Before(*dest[0].ReturnDate)) + } + } + + t.Run("default", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate, + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date +LIMIT 200; +`) + ensureNullsFirstRentalResult(t, stmt, true) + }) + + t.Run("NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date +LIMIT 200; +`) + ensureNullsFirstRentalResult(t, stmt, true) + }) + + t.Run("NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.NULLS_LAST(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date IS NULL, rental.return_date +LIMIT 200 +OFFSET 15800; +`) + ensureNullsLastRentalResult(t, stmt, true) + }) + + t.Run("ASC", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date ASC +LIMIT 200; +`) + ensureNullsFirstRentalResult(t, stmt, true) + }) + + t.Run("ASC NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC().NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date ASC +LIMIT 200; +`) + + ensureNullsFirstRentalResult(t, stmt, true) + }) + + t.Run("ASC NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC().NULLS_LAST(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date IS NULL, rental.return_date ASC +LIMIT 200 +OFFSET 15800; +`) + + ensureNullsLastRentalResult(t, stmt, true) + }) + + t.Run("DESC", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date DESC +LIMIT 200 +OFFSET 15800; +`) + + ensureNullsLastRentalResult(t, stmt, false) + }) + + t.Run("DESC NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC().NULLS_LAST(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date DESC +LIMIT 200 +OFFSET 15800; +`) + + ensureNullsLastRentalResult(t, stmt, false) + }) + + t.Run("DESC NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC().NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date IS NOT NULL, rental.return_date DESC +LIMIT 200; +`) + + ensureNullsFirstRentalResult(t, stmt, false) + }) + + t.Run("complex", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.RentalID.DESC(), + Rental.ReturnDate.DESC().NULLS_FIRST(), + Rental.LastUpdate.ASC(), + Rental.InventoryID.ADD(Rental.RentalID).ASC().NULLS_LAST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.rental_id DESC, rental.return_date IS NOT NULL, rental.return_date DESC, rental.last_update ASC, (rental.inventory_id + rental.rental_id) IS NULL, rental.inventory_id + rental.rental_id ASC +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + + }) + +} + func TestSubQuery(t *testing.T) { rRatingFilms := SELECT( diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index d9b30ad2..8c7e4bcf 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -1151,7 +1151,7 @@ func TestSelectOrderByAscDesc(t *testing.T) { firstCustomerAsc := customersAsc[0] lastCustomerAsc := customersAsc[len(customersAsc)-1] - customersDesc := []model.Customer{} + var customersDesc []model.Customer err = Customer.SELECT(Customer.CustomerID, Customer.FirstName, Customer.LastName). ORDER_BY(Customer.FirstName.DESC()). Query(db, &customersDesc) @@ -1164,7 +1164,7 @@ func TestSelectOrderByAscDesc(t *testing.T) { testutils.AssertDeepEqual(t, firstCustomerAsc, lastCustomerDesc) testutils.AssertDeepEqual(t, lastCustomerAsc, firstCustomerDesc) - customersAscDesc := []model.Customer{} + var customersAscDesc []model.Customer err = Customer.SELECT(Customer.CustomerID, Customer.FirstName, Customer.LastName). ORDER_BY(Customer.FirstName.ASC(), Customer.LastName.DESC()). Query(db, &customersAscDesc) @@ -1187,6 +1187,233 @@ func TestSelectOrderByAscDesc(t *testing.T) { testutils.AssertDeepEqual(t, customerAscDesc327, customersAscDesc[327]) } +func TestOrderBy(t *testing.T) { + + t.Run("default", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate, + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date NULLS FIRST +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.NULLS_LAST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date NULLS LAST +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("ASC", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date ASC +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("ASC NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC().NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date ASC NULLS FIRST +LIMIT 200; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("ASC NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC().NULLS_LAST(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date ASC NULLS LAST +LIMIT 200 +OFFSET 15800; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("DESC", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date DESC +LIMIT 200 +OFFSET 15800; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("DESC NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC().NULLS_LAST(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date DESC NULLS LAST +LIMIT 200 +OFFSET 15800; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("DESC NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC().NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM dvds.rental +ORDER BY rental.return_date DESC NULLS FIRST +LIMIT 200; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) +} + func TestSelectFullJoin(t *testing.T) { expectedSQL := ` SELECT customer.customer_id AS "customer.customer_id", diff --git a/tests/sqlite/select_test.go b/tests/sqlite/select_test.go index 838cb694..63d43a94 100644 --- a/tests/sqlite/select_test.go +++ b/tests/sqlite/select_test.go @@ -145,6 +145,233 @@ ORDER BY payment.customer_id, SUM(payment.amount) ASC; requireLogged(t, query) } +func TestOrderBy(t *testing.T) { + + t.Run("default", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate, + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date NULLS FIRST +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.NULLS_LAST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date NULLS LAST +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("ASC", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date ASC +LIMIT 200; +`) + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("ASC NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC().NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date ASC NULLS FIRST +LIMIT 200; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("ASC NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.ASC().NULLS_LAST(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date ASC NULLS LAST +LIMIT 200 +OFFSET 15800; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("DESC", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date DESC +LIMIT 200 +OFFSET 15800; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("DESC NULLS LAST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC().NULLS_LAST(), + ).LIMIT(200).OFFSET(15800) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date DESC NULLS LAST +LIMIT 200 +OFFSET 15800; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) + + t.Run("DESC NULLS FIRST", func(t *testing.T) { + stmt := SELECT( + Rental.AllColumns, + ).FROM( + Rental, + ).ORDER_BY( + Rental.ReturnDate.DESC().NULLS_FIRST(), + ).LIMIT(200) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT rental.rental_id AS "rental.rental_id", + rental.rental_date AS "rental.rental_date", + rental.inventory_id AS "rental.inventory_id", + rental.customer_id AS "rental.customer_id", + rental.return_date AS "rental.return_date", + rental.staff_id AS "rental.staff_id", + rental.last_update AS "rental.last_update" +FROM rental +ORDER BY rental.return_date DESC NULLS FIRST +LIMIT 200; +`) + + require.NoError(t, stmt.Query(db, &struct{}{})) + }) +} + func TestAggregateFunctionDistinct(t *testing.T) { stmt := SELECT( Payment.CustomerID,