From 255f4a8eaf0088d493bce5a912aad55f3c2f5cdf Mon Sep 17 00:00:00 2001 From: go-jet Date: Tue, 13 Feb 2024 14:01:13 +0100 Subject: [PATCH] Add support for expression in OFFSET clause. --- README.md | 2 +- internal/jet/clause.go | 12 +++-- mysql/select_statement.go | 3 +- mysql/set_statement.go | 3 +- postgres/select_statement.go | 8 ++- postgres/set_statement.go | 8 ++- sqlite/select_statement.go | 3 +- sqlite/set_statement.go | 3 +- tests/postgres/select_test.go | 94 ++++++++++++++++++++++++++++++++--- 9 files changed, 113 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 847208c..15fd887 100644 --- a/README.md +++ b/README.md @@ -579,5 +579,5 @@ To run the tests, additional dependencies are required: ## License -Copyright 2019-2023 Goran Bjelanovic +Copyright 2019-2024 Goran Bjelanovic Licensed under the Apache License, Version 2.0. diff --git a/internal/jet/clause.go b/internal/jet/clause.go index ed899b3..533d223 100644 --- a/internal/jet/clause.go +++ b/internal/jet/clause.go @@ -222,16 +222,18 @@ func (l *ClauseLimit) Serialize(statementType StatementType, out *SQLBuilder, op // ClauseOffset struct type ClauseOffset struct { - Count int64 + Count IntegerExpression } // Serialize serializes clause into SQLBuilder func (o *ClauseOffset) Serialize(statementType StatementType, out *SQLBuilder, options ...SerializeOption) { - if o.Count >= 0 { - out.NewLine() - out.WriteString("OFFSET") - out.insertParametrizedArgument(o.Count) + if is.Nil(o.Count) { + return } + + out.NewLine() + out.WriteString("OFFSET") + o.Count.serialize(statementType, out, options...) } // ClauseFetch struct diff --git a/mysql/select_statement.go b/mysql/select_statement.go index 45c7782..6c5f345 100644 --- a/mysql/select_statement.go +++ b/mysql/select_statement.go @@ -86,7 +86,6 @@ func newSelectStatement(table ReadableTable, projections []Projection) SelectSta newSelect.From.Tables = []jet.Serializer{table} } newSelect.Limit.Count = -1 - newSelect.Offset.Count = -1 newSelect.ShareLock.Name = "LOCK IN SHARE MODE" newSelect.ShareLock.InNewLine = true @@ -158,7 +157,7 @@ func (s *selectStatementImpl) LIMIT(limit int64) SelectStatement { } func (s *selectStatementImpl) OFFSET(offset int64) SelectStatement { - s.Offset.Count = offset + s.Offset.Count = Int(offset) return s } diff --git a/mysql/set_statement.go b/mysql/set_statement.go index ec9f8fa..2df75a0 100644 --- a/mysql/set_statement.go +++ b/mysql/set_statement.go @@ -63,7 +63,6 @@ func newSetStatementImpl(operator string, all bool, selects []jet.SerializerStat newSetStatement.setOperator.All = all newSetStatement.setOperator.Selects = selects newSetStatement.setOperator.Limit.Count = -1 - newSetStatement.setOperator.Offset.Count = -1 newSetStatement.setOperatorsImpl.parent = newSetStatement @@ -81,7 +80,7 @@ func (s *setStatementImpl) LIMIT(limit int64) setStatement { } func (s *setStatementImpl) OFFSET(offset int64) setStatement { - s.setOperator.Offset.Count = offset + s.setOperator.Offset.Count = Int(offset) return s } diff --git a/postgres/select_statement.go b/postgres/select_statement.go index 0ee80bf..70a9a50 100644 --- a/postgres/select_statement.go +++ b/postgres/select_statement.go @@ -53,6 +53,8 @@ type SelectStatement interface { ORDER_BY(orderByClauses ...OrderByClause) SelectStatement LIMIT(limit int64) SelectStatement OFFSET(offset int64) SelectStatement + // OFFSET_e can be used when an integer expression is needed as offset, otherwise OFFSET can be used + OFFSET_e(offset IntegerExpression) SelectStatement FETCH_FIRST(count IntegerExpression) fetchExpand FOR(lock RowLock) SelectStatement @@ -91,7 +93,6 @@ func newSelectStatement(table ReadableTable, projections []Projection) SelectSta newSelect.From.Tables = []jet.Serializer{table} } newSelect.Limit.Count = -1 - newSelect.Offset.Count = -1 newSelect.setOperatorsImpl.parent = newSelect @@ -157,6 +158,11 @@ func (s *selectStatementImpl) LIMIT(limit int64) SelectStatement { } func (s *selectStatementImpl) OFFSET(offset int64) SelectStatement { + s.Offset.Count = Int(offset) + return s +} + +func (s *selectStatementImpl) OFFSET_e(offset IntegerExpression) SelectStatement { s.Offset.Count = offset return s } diff --git a/postgres/set_statement.go b/postgres/set_statement.go index 236f8de..834560d 100644 --- a/postgres/set_statement.go +++ b/postgres/set_statement.go @@ -45,6 +45,8 @@ type setStatement interface { LIMIT(limit int64) setStatement OFFSET(offset int64) setStatement + // OFFSET_e can be used when an integer expression is needed as offset, otherwise OFFSET can be used + OFFSET_e(offset IntegerExpression) setStatement AsTable(alias string) SelectTable } @@ -107,7 +109,6 @@ func newSetStatementImpl(operator string, all bool, selects []jet.SerializerStat newSetStatement.setOperator.All = all newSetStatement.setOperator.Selects = selects newSetStatement.setOperator.Limit.Count = -1 - newSetStatement.setOperator.Offset.Count = -1 newSetStatement.setOperatorsImpl.parent = newSetStatement @@ -125,6 +126,11 @@ func (s *setStatementImpl) LIMIT(limit int64) setStatement { } func (s *setStatementImpl) OFFSET(offset int64) setStatement { + s.setOperator.Offset.Count = Int(offset) + return s +} + +func (s *setStatementImpl) OFFSET_e(offset IntegerExpression) setStatement { s.setOperator.Offset.Count = offset return s } diff --git a/sqlite/select_statement.go b/sqlite/select_statement.go index 5e92d52..e74cda6 100644 --- a/sqlite/select_statement.go +++ b/sqlite/select_statement.go @@ -74,7 +74,6 @@ func newSelectStatement(table ReadableTable, projections []Projection) SelectSta newSelect.From.Tables = []jet.Serializer{table} } newSelect.Limit.Count = -1 - newSelect.Offset.Count = -1 newSelect.ShareLock.Name = "LOCK IN SHARE MODE" newSelect.ShareLock.InNewLine = true @@ -141,7 +140,7 @@ func (s *selectStatementImpl) LIMIT(limit int64) SelectStatement { } func (s *selectStatementImpl) OFFSET(offset int64) SelectStatement { - s.Offset.Count = offset + s.Offset.Count = Int(offset) return s } diff --git a/sqlite/set_statement.go b/sqlite/set_statement.go index 18bcca5..0a004bf 100644 --- a/sqlite/set_statement.go +++ b/sqlite/set_statement.go @@ -63,7 +63,6 @@ func newSetStatementImpl(operator string, all bool, selects []jet.SerializerStat newSetStatement.setOperator.All = all newSetStatement.setOperator.Selects = selects newSetStatement.setOperator.Limit.Count = -1 - newSetStatement.setOperator.Offset.Count = -1 newSetStatement.setOperator.SkipSelectWrap = true newSetStatement.setOperatorsImpl.parent = newSetStatement @@ -82,7 +81,7 @@ func (s *setStatementImpl) LIMIT(limit int64) setStatement { } func (s *setStatementImpl) OFFSET(offset int64) setStatement { - s.setOperator.Offset.Count = offset + s.setOperator.Offset.Count = Int(offset) return s } diff --git a/tests/postgres/select_test.go b/tests/postgres/select_test.go index 8c7e4bc..048db14 100644 --- a/tests/postgres/select_test.go +++ b/tests/postgres/select_test.go @@ -320,6 +320,38 @@ FETCH FIRST ( }) } +func TestOffsetExpression(t *testing.T) { + + stmt := SELECT(Actor.AllColumns). + FROM(Actor). + ORDER_BY(Actor.ActorID). + OFFSET_e(IntExp( + SELECT(MAX(Store.StoreID)). + FROM(Store), + )).LIMIT(10) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT actor.actor_id AS "actor.actor_id", + actor.first_name AS "actor.first_name", + actor.last_name AS "actor.last_name", + actor.last_update AS "actor.last_update" +FROM dvds.actor +ORDER BY actor.actor_id +LIMIT 10 +OFFSET ( + SELECT MAX(store.store_id) + FROM dvds.store +); +`) + + var dest []model.Actor + + err := stmt.Query(db, &dest) + require.NoError(t, err) + require.Len(t, dest, 10) + require.Equal(t, dest[0].ActorID, int32(3)) +} + func TestJoinQueryStruct(t *testing.T) { expectedSQL := ` @@ -2365,16 +2397,14 @@ OFFSET 20; Payment. SELECT(Payment.PaymentID, Payment.Amount). WHERE(Payment.Amount.GT_EQ(Float(200))), - ). - ORDER_BY(IntegerColumn("payment.payment_id").ASC(), Payment.Amount.DESC()). - LIMIT(10). - OFFSET(20) - - //fmt.Println(query.DebugSql()) + ).ORDER_BY( + IntegerColumn("payment.payment_id").ASC(), + Payment.Amount.DESC(), + ).LIMIT(10).OFFSET(20) testutils.AssertDebugStatementSql(t, query, expectedQuery, float64(100), float64(200), int64(10), int64(20)) - dest := []model.Payment{} + var dest []model.Payment err := query.Query(db, &dest) @@ -2394,6 +2424,56 @@ OFFSET 20; }) } +func TestUnionOffsetWithExpression(t *testing.T) { + stmt := UNION( + SELECT(Rental.AllColumns). + FROM(Rental). + WHERE(Rental.ReturnDate.IS_NULL()), + + SELECT(Rental.AllColumns). + FROM(Rental). + WHERE(Rental.LastUpdate.GT(LOCALTIMESTAMP())), + ).OFFSET_e(IntExp( + SELECT(Int32(3)), + )).LIMIT(10) + + testutils.AssertStatementSql(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 + WHERE rental.return_date IS NULL +) +UNION +( + 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 + WHERE rental.last_update > LOCALTIMESTAMP +) +LIMIT $1 +OFFSET ( + SELECT $2::integer +); +`) + + var dest []model.Rental + + err := stmt.Query(db, &dest) + require.NoError(t, err) + require.Len(t, dest, 10) +} + func TestAllSetOperators(t *testing.T) { var select1 = Payment.SELECT(Payment.AllColumns).WHERE(Payment.PaymentID.GT_EQ(Int(17600)).AND(Payment.PaymentID.LT(Int(17610)))) var select2 = Payment.SELECT(Payment.AllColumns).WHERE(Payment.PaymentID.GT_EQ(Int(17620)).AND(Payment.PaymentID.LT(Int(17630))))