Add support for strict scan.

If there are unused columns in query result set Query method panics.
This commit is contained in:
go-jet 2025-03-11 10:50:06 +01:00
parent cfc264221b
commit d86f14e665
9 changed files with 402 additions and 207 deletions

View file

@ -145,7 +145,11 @@ LIMIT 30;
testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(30))
var dest []model.Payment
var dest []struct {
model.Payment
Customer model.Customer
}
err := query.Query(db, &dest)
@ -246,8 +250,10 @@ LIMIT 12;
testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(1), int64(1), int64(10), int64(1), int64(2), int64(1), int64(12))
var dest []struct{}
err := query.Query(db, &dest)
require.NoError(t, err)
allowUnusedColumns(func() {
err := query.Query(db, &dest)
require.NoError(t, err)
})
}
func TestSelectFetchFirst(t *testing.T) {
@ -369,10 +375,7 @@ OFFSET (
func TestSelectJoinQueryStruct(t *testing.T) {
expectedSQL := `
SELECT film_actor.actor_id AS "film_actor.actor_id",
film_actor.film_id AS "film_actor.film_id",
film_actor.last_update AS "film_actor.last_update",
film.film_id AS "film.film_id",
SELECT film.film_id AS "film.film_id",
film.title AS "film.title",
film.description AS "film.description",
film.release_year AS "film.release_year",
@ -420,7 +423,6 @@ LIMIT 1000;
INNER_JOIN(Inventory, Inventory.FilmID.EQ(Film.FilmID)).
INNER_JOIN(Rental, Rental.InventoryID.EQ(Inventory.InventoryID)).
SELECT(
FilmActor.AllColumns,
Film.AllColumns,
Language.AllColumns,
Actor.AllColumns,
@ -1235,6 +1237,8 @@ func TestSelectOrderByAscDesc(t *testing.T) {
func TestSelectOrderBy(t *testing.T) {
var destRentals []model.Rental
t.Run("default", func(t *testing.T) {
stmt := SELECT(
Rental.AllColumns,
@ -1256,7 +1260,8 @@ FROM dvds.rental
ORDER BY rental.return_date
LIMIT 200;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
t.Run("NULLS FIRST", func(t *testing.T) {
@ -1280,7 +1285,7 @@ FROM dvds.rental
ORDER BY rental.return_date NULLS FIRST
LIMIT 200;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
t.Run("NULLS LAST", func(t *testing.T) {
@ -1304,7 +1309,7 @@ FROM dvds.rental
ORDER BY rental.return_date NULLS LAST
LIMIT 200;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
t.Run("ASC", func(t *testing.T) {
@ -1328,7 +1333,7 @@ FROM dvds.rental
ORDER BY rental.return_date ASC
LIMIT 200;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
t.Run("ASC NULLS FIRST", func(t *testing.T) {
@ -1353,7 +1358,7 @@ ORDER BY rental.return_date ASC NULLS FIRST
LIMIT 200;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
t.Run("ASC NULLS LAST", func(t *testing.T) {
@ -1379,7 +1384,7 @@ LIMIT 200
OFFSET 15800;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
t.Run("DESC", func(t *testing.T) {
@ -1405,7 +1410,7 @@ LIMIT 200
OFFSET 15800;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
t.Run("DESC NULLS LAST", func(t *testing.T) {
@ -1431,7 +1436,7 @@ LIMIT 200
OFFSET 15800;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
t.Run("DESC NULLS FIRST", func(t *testing.T) {
@ -1456,7 +1461,7 @@ ORDER BY rental.return_date DESC NULLS FIRST
LIMIT 200;
`)
require.NoError(t, stmt.Query(db, &struct{}{}))
require.NoError(t, stmt.Query(db, &destRentals))
})
}
@ -1552,14 +1557,14 @@ LIMIT 1000;
testutils.AssertDebugStatementSql(t, query, expectedSQL, int64(1000))
var customerAddresCrosJoined []struct {
var customerAddersCrossJoined []struct {
model.Customer
model.Address
}
err := query.Query(db, &customerAddresCrosJoined)
err := query.Query(db, &customerAddersCrossJoined)
require.Equal(t, len(customerAddresCrosJoined), 1000)
require.Equal(t, len(customerAddersCrossJoined), 1000)
require.NoError(t, err)
}
@ -1682,7 +1687,6 @@ func TestSelectSubQuery(t *testing.T) {
SELECT(
rRatingFilms.AllColumns(),
Actor.AllColumns,
FilmActor.AllColumns,
).FROM(
rRatingFilms.
INNER_JOIN(FilmActor, FilmActor.FilmID.EQ(rFilmID)).
@ -1701,10 +1705,7 @@ SELECT "rFilms"."film.film_id" AS "film.film_id",
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",
film_actor.actor_id AS "film_actor.actor_id",
film_actor.film_id AS "film_actor.film_id",
film_actor.last_update AS "film_actor.last_update"
actor.last_update AS "actor.last_update"
FROM (
SELECT film.film_id AS "film.film_id",
film.title AS "film.title",
@ -1871,9 +1872,7 @@ SELECT customer.customer_id AS "customer.customer_id",
customer.active AS "customer.active",
SUM(payment.amount) AS "amount.sum",
AVG(payment.amount) AS "amount.avg",
MAX(payment.payment_date) AS "amount.max_date",
MAX(payment.amount) AS "amount.max",
MIN(payment.payment_date) AS "amount.min_date",
MIN(payment.amount) AS "amount.min",
COUNT(payment.amount) AS "amount.count"
FROM dvds.payment
@ -1887,9 +1886,7 @@ ORDER BY customer.customer_id, SUM(payment.amount) ASC;
SUMf(Payment.Amount).AS("amount.sum"),
AVG(Payment.Amount).AS("amount.avg"),
MAX(Payment.PaymentDate).AS("amount.max_date"),
MAXf(Payment.Amount).AS("amount.max"),
MIN(Payment.PaymentDate).AS("amount.min_date"),
MINf(Payment.Amount).AS("amount.min"),
COUNT(Payment.Amount).AS("amount.count"),
).FROM(
@ -2795,8 +2792,10 @@ ORDER BY actor.actor_id ASC, film.film_id ASC;
Actors []model.Actor
}
err = stmt.Query(db, &dest2)
require.NoError(t, err)
allowUnusedColumns(func() {
err = stmt.Query(db, &dest2)
require.NoError(t, err)
})
//testutils.SaveJSONFile(dest2, "./testdata/results/postgres/quick-start-dest2.json")
testutils.AssertJSONFile(t, dest2, "./testdata/results/postgres/quick-start-dest2.json")
@ -2819,18 +2818,19 @@ func TestSelectQuickStartWithSubQueries(t *testing.T) {
categoryID := Category.CategoryID.From(categoriesNotAction)
stmt := Actor.
INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)).
INNER_JOIN(filmLogerThan180, filmID.EQ(FilmActor.FilmID)).
INNER_JOIN(Language, Language.LanguageID.EQ(filmLanguageID)).
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(filmID)).
INNER_JOIN(categoriesNotAction, categoryID.EQ(FilmCategory.CategoryID)).
SELECT(
Actor.AllColumns,
filmLogerThan180.AllColumns(),
Language.AllColumns,
categoriesNotAction.AllColumns(),
).ORDER_BY(
stmt := SELECT(
Actor.AllColumns,
filmLogerThan180.AllColumns(),
Language.AllColumns,
categoriesNotAction.AllColumns(),
).FROM(
Actor.
INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)).
INNER_JOIN(filmLogerThan180, filmID.EQ(FilmActor.FilmID)).
INNER_JOIN(Language, Language.LanguageID.EQ(filmLanguageID)).
INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(filmID)).
INNER_JOIN(categoriesNotAction, categoryID.EQ(FilmCategory.CategoryID)),
).ORDER_BY(
Actor.ActorID.ASC(),
filmID.ASC(),
)
@ -2860,8 +2860,10 @@ func TestSelectQuickStartWithSubQueries(t *testing.T) {
Actors []model.Actor
}
err = stmt.Query(db, &dest2)
require.NoError(t, err)
allowUnusedColumns(func() {
err = stmt.Query(db, &dest2)
require.NoError(t, err)
})
//jsonSave("./testdata/quick-start-dest2.json", dest2)
testutils.AssertJSONFile(t, dest2, "./testdata/results/postgres/quick-start-dest2.json")
@ -2892,9 +2894,10 @@ SELECT true,
'date';
`)
dest := []struct{}{}
err := query.Query(db, &dest)
require.NoError(t, err)
allowUnusedColumns(func() {
err := query.Query(db, &[]struct{}{})
require.NoError(t, err)
})
}
func TestSelectWindowFunction(t *testing.T) {
@ -2928,45 +2931,52 @@ FROM dvds.payment
WHERE payment.payment_id < $3
GROUP BY payment.amount, payment.customer_id, payment.payment_date;
`
query := Payment.
SELECT(
AVG(Payment.Amount).OVER(),
AVG(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID)),
MAXf(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate.DESC())),
MINf(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID).ORDER_BY(Payment.PaymentDate.DESC())),
SUMf(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID).
ORDER_BY(Payment.PaymentDate.DESC()).ROWS(PRECEDING(1), FOLLOWING(6))),
SUMf(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID).
ORDER_BY(Payment.PaymentDate.DESC()).RANGE(PRECEDING(UNBOUNDED), FOLLOWING(UNBOUNDED))),
MAXi(Payment.CustomerID).OVER(ORDER_BY(Payment.PaymentDate.DESC()).ROWS(CURRENT_ROW, FOLLOWING(UNBOUNDED))),
MINi(Payment.CustomerID).OVER(PARTITION_BY(Payment.CustomerID).ORDER_BY(Payment.PaymentDate.DESC())),
SUMi(Payment.CustomerID).OVER(PARTITION_BY(Payment.CustomerID).ORDER_BY(Payment.PaymentDate.DESC())),
ROW_NUMBER().OVER(ORDER_BY(Payment.PaymentDate)),
RANK().OVER(ORDER_BY(Payment.PaymentDate)),
DENSE_RANK().OVER(ORDER_BY(Payment.PaymentDate)),
CUME_DIST().OVER(ORDER_BY(Payment.PaymentDate)),
NTILE(11).OVER(ORDER_BY(Payment.PaymentDate)),
LAG(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LAG(Payment.Amount, 2).OVER(ORDER_BY(Payment.PaymentDate)),
LAG(Payment.Amount, 2, Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LAG(Payment.Amount, 2, 100).OVER(ORDER_BY(Payment.PaymentDate)),
LEAD(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LEAD(Payment.Amount, 2).OVER(ORDER_BY(Payment.PaymentDate)),
LEAD(Payment.Amount, 2, Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LEAD(Payment.Amount, 2, 100).OVER(ORDER_BY(Payment.PaymentDate)),
FIRST_VALUE(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LAST_VALUE(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
NTH_VALUE(Payment.Amount, 3).OVER(ORDER_BY(Payment.PaymentDate)),
).GROUP_BY(Payment.Amount, Payment.CustomerID, Payment.PaymentDate).
WHERE(Payment.PaymentID.LT(Int(10)))
query := SELECT(
AVG(Payment.Amount).OVER(),
AVG(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID)),
MAXf(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate.DESC())),
MINf(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID).ORDER_BY(Payment.PaymentDate.DESC())),
SUMf(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID).
ORDER_BY(Payment.PaymentDate.DESC()).ROWS(PRECEDING(1), FOLLOWING(6))),
SUMf(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID).
ORDER_BY(Payment.PaymentDate.DESC()).RANGE(PRECEDING(UNBOUNDED), FOLLOWING(UNBOUNDED))),
MAXi(Payment.CustomerID).OVER(ORDER_BY(Payment.PaymentDate.DESC()).ROWS(CURRENT_ROW, FOLLOWING(UNBOUNDED))),
MINi(Payment.CustomerID).OVER(PARTITION_BY(Payment.CustomerID).ORDER_BY(Payment.PaymentDate.DESC())),
SUMi(Payment.CustomerID).OVER(PARTITION_BY(Payment.CustomerID).ORDER_BY(Payment.PaymentDate.DESC())),
ROW_NUMBER().OVER(ORDER_BY(Payment.PaymentDate)),
RANK().OVER(ORDER_BY(Payment.PaymentDate)),
DENSE_RANK().OVER(ORDER_BY(Payment.PaymentDate)),
CUME_DIST().OVER(ORDER_BY(Payment.PaymentDate)),
NTILE(11).OVER(ORDER_BY(Payment.PaymentDate)),
LAG(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LAG(Payment.Amount, 2).OVER(ORDER_BY(Payment.PaymentDate)),
LAG(Payment.Amount, 2, Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LAG(Payment.Amount, 2, 100).OVER(ORDER_BY(Payment.PaymentDate)),
LEAD(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LEAD(Payment.Amount, 2).OVER(ORDER_BY(Payment.PaymentDate)),
LEAD(Payment.Amount, 2, Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LEAD(Payment.Amount, 2, 100).OVER(ORDER_BY(Payment.PaymentDate)),
FIRST_VALUE(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
LAST_VALUE(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate)),
NTH_VALUE(Payment.Amount, 3).OVER(ORDER_BY(Payment.PaymentDate)),
).FROM(
Payment,
).WHERE(
Payment.PaymentID.LT(Int(10)),
).GROUP_BY(
Payment.Amount,
Payment.CustomerID,
Payment.PaymentDate,
)
//fmt.Println(query.Sql())
testutils.AssertStatementSql(t, query, expectedSQL, 100, 100, int64(10))
dest := []struct{}{}
err := query.Query(db, &dest)
require.NoError(t, err)
allowUnusedColumns(func() {
err := query.Query(db, &[]struct{}{})
require.NoError(t, err)
})
}
func TestSelectWindowClause(t *testing.T) {
@ -3001,10 +3011,11 @@ ORDER BY payment.customer_id;
testutils.AssertStatementSql(t, query, expectedSQL, int64(10))
dest := []struct{}{}
err := query.Query(db, &dest)
allowUnusedColumns(func() {
err := query.Query(db, &[]struct{}{})
require.NoError(t, err)
require.NoError(t, err)
})
}
func TestSelectSimpleView(t *testing.T) {
@ -3050,12 +3061,14 @@ func TestSelectJoinViewWithTable(t *testing.T) {
query := SELECT(
view.CustomerList.AllColumns,
Rental.AllColumns,
).
FROM(view.CustomerList.
).FROM(
view.CustomerList.
INNER_JOIN(Rental, view.CustomerList.ID.EQ(Rental.CustomerID)),
).
ORDER_BY(view.CustomerList.ID).
WHERE(view.CustomerList.ID.LT_EQ(Int(2)))
).WHERE(
view.CustomerList.ID.LT_EQ(Int(2)),
).ORDER_BY(
view.CustomerList.ID,
)
var dest []struct {
model.CustomerList `sql:"primary_key=ID"`
@ -3162,7 +3175,7 @@ FROM dvds.customer
WHERE ($1::boolean AND (customer.customer_id = $2)) AND (customer.activebool = $3::boolean);
`, true, int64(1), true)
dest := []model.Customer{}
var dest []model.Customer
err := stmt.Query(db, &dest)
require.NoError(t, err)
require.Len(t, dest, 1)
@ -3710,8 +3723,10 @@ SELECT SUM((CASE WHEN staff.active IS TRUE THEN $1::smallint ELSE $2::integer EN
FROM dvds.staff;
`)
err := stmt.Query(db, &struct{}{})
require.NoError(t, err)
allowUnusedColumns(func() {
err := stmt.Query(db, &struct{}{})
require.NoError(t, err)
})
}
func GET_FILM_COUNT(lenFrom, lenTo IntegerExpression) IntegerExpression {