diff --git a/internal/jet/clause.go b/internal/jet/clause.go index 924a303..ce99a6b 100644 --- a/internal/jet/clause.go +++ b/internal/jet/clause.go @@ -18,8 +18,9 @@ type ClauseWithProjections interface { // ClauseSelect struct type ClauseSelect struct { - Distinct bool - ProjectionList []Projection + Distinct bool + DistinctOnColumns []ColumnExpression + ProjectionList []Projection } // Projections returns list of projections for select clause @@ -36,6 +37,12 @@ func (s *ClauseSelect) Serialize(statementType StatementType, out *SQLBuilder, o out.WriteString("DISTINCT") } + if len(s.DistinctOnColumns) > 0 { + out.WriteString("ON (") + SerializeColumnExpressions(s.DistinctOnColumns, statementType, out) + out.WriteByte(')') + } + if len(s.ProjectionList) == 0 { panic("jet: SELECT clause has to have at least one projection") } diff --git a/postgres/select_statement.go b/postgres/select_statement.go index 518ebd5..ff553fb 100644 --- a/postgres/select_statement.go +++ b/postgres/select_statement.go @@ -44,7 +44,7 @@ type SelectStatement interface { jet.HasProjections Expression - DISTINCT() SelectStatement + DISTINCT(on ...jet.ColumnExpression) SelectStatement FROM(tables ...ReadableTable) SelectStatement WHERE(expression BoolExpression) SelectStatement GROUP_BY(groupByClauses ...GroupByClause) SelectStatement @@ -104,8 +104,9 @@ type selectStatementImpl struct { For jet.ClauseFor } -func (s *selectStatementImpl) DISTINCT() SelectStatement { +func (s *selectStatementImpl) DISTINCT(on ...jet.ColumnExpression) SelectStatement { s.Select.Distinct = true + s.Select.DistinctOnColumns = on return s } diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index 37ca365..87edaf7 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -48,6 +48,63 @@ WHERE actor.actor_id = 2; requireLogged(t, query) } +func TestSelectDistinctOn(t *testing.T) { + + stmt := SELECT( + Rental.StaffID, + Rental.CustomerID, + Rental.RentalID, + ).DISTINCT( + Rental.StaffID, + Rental.CustomerID, + ).FROM( + Rental, + ).WHERE( + Rental.CustomerID.LT(Int(2)), + ).ORDER_BY( + Rental.StaffID.ASC(), + Rental.CustomerID.ASC(), + Rental.RentalID.ASC(), + ) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT DISTINCT ON (rental.staff_id, rental.customer_id) rental.staff_id AS "rental.staff_id", + rental.customer_id AS "rental.customer_id", + rental.rental_id AS "rental.rental_id" +FROM dvds.rental +WHERE rental.customer_id < 2 +ORDER BY rental.staff_id ASC, rental.customer_id ASC, rental.rental_id ASC; +`) + + var dest []model.Rental + + err := stmt.Query(db, &dest) + require.NoError(t, err) + + testutils.AssertJSON(t, dest, ` +[ + { + "RentalID": 573, + "RentalDate": "0001-01-01T00:00:00Z", + "InventoryID": 0, + "CustomerID": 1, + "ReturnDate": null, + "StaffID": 1, + "LastUpdate": "0001-01-01T00:00:00Z" + }, + { + "RentalID": 76, + "RentalDate": "0001-01-01T00:00:00Z", + "InventoryID": 0, + "CustomerID": 1, + "ReturnDate": null, + "StaffID": 2, + "LastUpdate": "0001-01-01T00:00:00Z" + } +] +`) +} + func TestClassicSelect(t *testing.T) { expectedSQL := ` SELECT payment.payment_id AS "payment.payment_id",