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

@ -10,6 +10,21 @@ import (
"reflect" "reflect"
) )
// Config holds the configuration settings for QRM scanning behavior.
type Config struct {
// StrictScan, when true, causes the scanning function to panic if it encounters any
// unused columns in the SQL query result. This ensures that every column is mapped
// to a field in the destination struct.
// Does not apply to statements build with SELECT_JSON_OBJ or SELECT_JSON_ARR
StrictScan bool
}
// GlobalConfig is the package-wide configuration for SQL scanning.
// This variable should be modified only once, for instance, during application initialization.
var GlobalConfig = Config{
StrictScan: false,
}
// ErrNoRows is returned by Query when query result set is empty // ErrNoRows is returned by Query when query result set is empty
var ErrNoRows = errors.New("qrm: no rows in result set") var ErrNoRows = errors.New("qrm: no rows in result set")
@ -199,12 +214,16 @@ func ScanOneRowToDest(scanContext *ScanContext, rows *sql.Rows, destPtr interfac
destValuePtr := reflect.ValueOf(destPtr) destValuePtr := reflect.ValueOf(destPtr)
scanContext.rowNum++
_, err = mapRowToStruct(scanContext, "", destValuePtr, nil) _, err = mapRowToStruct(scanContext, "", destValuePtr, nil)
if err != nil { if err != nil {
return fmt.Errorf("jet: failed to scan a row into destination, %w", err) return fmt.Errorf("jet: failed to scan a row into destination, %w", err)
} }
scanContext.EnsureEveryColumnRead() // can panic
return nil return nil
} }
@ -246,6 +265,10 @@ func queryToSlice(ctx context.Context, db Queryable, query string, args []interf
if err != nil { if err != nil {
return scanContext.rowNum, err return scanContext.rowNum, err
} }
if scanContext.rowNum == 1 && GlobalConfig.StrictScan {
scanContext.EnsureEveryColumnRead()
}
} }
err = rows.Close() err = rows.Close()

View file

@ -17,7 +17,9 @@ type ScanContext struct {
groupKeyInfoCache map[string]groupKeyInfo groupKeyInfoCache map[string]groupKeyInfo
typeInfoMap map[string]typeInfo typeInfoMap map[string]typeInfo
typesVisited typeStack // to prevent circular dependency scan typesVisited typeStack // to prevent circular dependency scan
columnAlias []string
columnIndexRead []bool
} }
// NewScanContext creates new ScanContext from rows // NewScanContext creates new ScanContext from rows
@ -57,9 +59,26 @@ func NewScanContext(rows *sql.Rows) (*ScanContext, error) {
typeInfoMap: make(map[string]typeInfo), typeInfoMap: make(map[string]typeInfo),
typesVisited: newTypeStack(), typesVisited: newTypeStack(),
columnAlias: aliases,
columnIndexRead: make([]bool, len(aliases)),
}, nil }, nil
} }
func (s *ScanContext) EnsureEveryColumnRead() {
var neverUsedColumns []string
for index, read := range s.columnIndexRead {
if !read {
neverUsedColumns = append(neverUsedColumns, `'`+s.columnAlias[index]+`'`)
}
}
if len(neverUsedColumns) > 0 {
panic("jet: columns never used: " + strings.Join(neverUsedColumns, ", "))
}
}
func createScanSlice(columnCount int) []interface{} { func createScanSlice(columnCount int) []interface{} {
scanPtrSlice := make([]interface{}, columnCount) scanPtrSlice := make([]interface{}, columnCount)
@ -244,6 +263,9 @@ func (s *ScanContext) typeToColumnIndex(typeName, fieldName string) int {
// rowElemValue always returns non-ptr value, // rowElemValue always returns non-ptr value,
// invalid value is nil // invalid value is nil
func (s *ScanContext) rowElemValue(index int) reflect.Value { func (s *ScanContext) rowElemValue(index int) reflect.Value {
if s.rowNum == 1 {
s.columnIndexRead[index] = true
}
scannedValue := reflect.ValueOf(s.row[index]) scannedValue := reflect.ValueOf(s.row[index])
return scannedValue.Elem().Elem() // no need to check validity of Elem, because s.row[index] always contains interface in interface return scannedValue.Elem().Elem() // no need to check validity of Elem, because s.row[index] always contains interface in interface
} }

View file

@ -591,10 +591,12 @@ func TestExpressionCast(t *testing.T) {
Raw("current_database()"), Raw("current_database()"),
) )
var dest []struct{} allowUnusedColumns(func() {
err := query.Query(db, &dest) var dest []struct{}
err := query.Query(db, &dest)
require.NoError(t, err)
})
require.NoError(t, err)
} }
func TestStringOperators(t *testing.T) { func TestStringOperators(t *testing.T) {
@ -673,10 +675,11 @@ func TestStringOperators(t *testing.T) {
TO_HEX(AllTypes.IntegerPtr), TO_HEX(AllTypes.IntegerPtr),
) )
var dest []struct{} allowUnusedColumns(func() {
err := query.Query(db, &dest) var dest []struct{}
err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
})
} }
func TestBytea(t *testing.T) { func TestBytea(t *testing.T) {
@ -792,10 +795,12 @@ FROM test_sample.all_types;
`) `)
} }
var dest []struct{} allowUnusedColumns(func() {
err := stmt.Query(db, &dest) var dest []struct{}
err := stmt.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
})
} }
func TestBlobConversion(t *testing.T) { func TestBlobConversion(t *testing.T) {
@ -1185,9 +1190,11 @@ LIMIT $27;
common.AllTypesIntegerExpResult `alias:"."` common.AllTypesIntegerExpResult `alias:"."`
} }
err := query.Query(db, &dest) allowUnusedColumns(func() {
err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
})
//testutils.SaveJSONFile(dest, "./testdata/results/common/int_operators.json") //testutils.SaveJSONFile(dest, "./testdata/results/common/int_operators.json")
//testutils.PrintJson(dest) //testutils.PrintJson(dest)
@ -1271,9 +1278,12 @@ func TestTimeExpression(t *testing.T) {
// fmt.Println(query.DebugSql()) // fmt.Println(query.DebugSql())
var dest []struct{} var dest []struct{}
err := query.Query(db, &dest)
require.NoError(t, err) allowUnusedColumns(func() {
err := query.Query(db, &dest)
require.NoError(t, err)
})
} }
func TestTimeScan(t *testing.T) { func TestTimeScan(t *testing.T) {
@ -1616,8 +1626,11 @@ SELECT INTERVAL '1 YEAR',
FROM test_sample.all_types; FROM test_sample.all_types;
`) `)
err := stmt.Query(db, &struct{}{}) allowUnusedColumns(func() {
require.NoError(t, err) err := stmt.Query(db, &struct{}{})
require.NoError(t, err)
})
requireLogged(t, stmt) requireLogged(t, stmt)
} }
@ -1677,8 +1690,10 @@ SELECT EXTRACT(CENTURY FROM all_types.timestampz),
FROM test_sample.all_types; FROM test_sample.all_types;
`) `)
err := stmt.Query(db, &struct{}{}) allowUnusedColumns(func() {
require.NoError(t, err) err := stmt.Query(db, &struct{}{})
require.NoError(t, err)
})
} }
func TestRowExpression(t *testing.T) { func TestRowExpression(t *testing.T) {
@ -1717,8 +1732,10 @@ SELECT ROW($1::integer, $2::real, $3::text) AS "row",
ROW($26::timestamp with time zone) <= ROW($27::timestamp with time zone); ROW($26::timestamp with time zone) <= ROW($27::timestamp with time zone);
`) `)
err := stmt.Query(db, &struct{}{}) allowUnusedColumns(func() {
require.NoError(t, err) err := stmt.Query(db, &struct{}{})
require.NoError(t, err)
})
} }
func TestAllTypesSubQueryFrom(t *testing.T) { func TestAllTypesSubQueryFrom(t *testing.T) {
@ -2048,7 +2065,11 @@ FROM`
testutils.AssertDebugStatementSql(t, stmt1, expectedSQL+expected.sql+";\n", expected.args...) testutils.AssertDebugStatementSql(t, stmt1, expectedSQL+expected.sql+";\n", expected.args...)
dest1 := []model.AllTypes{} var dest1 []struct {
model.AllTypes
AliasedColumn []byte
}
err := stmt1.Query(db, &dest1) err := stmt1.Query(db, &dest1)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(dest1), 2) require.Equal(t, len(dest1), 2)
@ -2064,12 +2085,17 @@ FROM`
stmt2 := SELECT( stmt2 := SELECT(
subQuery.AllColumns(), subQuery.AllColumns(),
). ).FROM(
FROM(subQuery) subQuery,
)
testutils.AssertDebugStatementSql(t, stmt2, expectedSQL+expected.sql+";\n", expected.args...) testutils.AssertDebugStatementSql(t, stmt2, expectedSQL+expected.sql+";\n", expected.args...)
dest2 := []model.AllTypes{} var dest2 []struct {
model.AllTypes
AliasedColumn []byte
}
err = stmt2.Query(db, &dest2) err = stmt2.Query(db, &dest2)
require.NoError(t, err) require.NoError(t, err)

View file

@ -320,7 +320,6 @@ func testJoinEverything(t require.TestingT) {
Track.AllColumns, Track.AllColumns,
Genre.AllColumns, Genre.AllColumns,
MediaType.AllColumns, MediaType.AllColumns,
PlaylistTrack.AllColumns,
Playlist.AllColumns, Playlist.AllColumns,
Invoice.AllColumns, Invoice.AllColumns,
Customer.AllColumns, Customer.AllColumns,
@ -365,8 +364,6 @@ SELECT "Artist"."ArtistId" AS "Artist.ArtistId",
"Genre"."Name" AS "Genre.Name", "Genre"."Name" AS "Genre.Name",
"MediaType"."MediaTypeId" AS "MediaType.MediaTypeId", "MediaType"."MediaTypeId" AS "MediaType.MediaTypeId",
"MediaType"."Name" AS "MediaType.Name", "MediaType"."Name" AS "MediaType.Name",
"PlaylistTrack"."PlaylistId" AS "PlaylistTrack.PlaylistId",
"PlaylistTrack"."TrackId" AS "PlaylistTrack.TrackId",
"Playlist"."PlaylistId" AS "Playlist.PlaylistId", "Playlist"."PlaylistId" AS "Playlist.PlaylistId",
"Playlist"."Name" AS "Playlist.Name", "Playlist"."Name" AS "Playlist.Name",
"Invoice"."InvoiceId" AS "Invoice.InvoiceId", "Invoice"."InvoiceId" AS "Invoice.InvoiceId",
@ -1295,7 +1292,7 @@ func TestAggregateFunc(t *testing.T) {
WITHIN_GROUP_ORDER_BY(Invoice.BillingAddress.DESC()).AS("percentile_disc_3"), WITHIN_GROUP_ORDER_BY(Invoice.BillingAddress.DESC()).AS("percentile_disc_3"),
PERCENTILE_CONT(Float(0.3)).WITHIN_GROUP_ORDER_BY(Invoice.Total).AS("percentile_cont_1"), PERCENTILE_CONT(Float(0.3)).WITHIN_GROUP_ORDER_BY(Invoice.Total).AS("percentile_cont_1"),
PERCENTILE_CONT(Float(0.2)).WITHIN_GROUP_ORDER_BY(INTERVAL(1, HOUR).DESC()).AS("percentile_cont_int"), PERCENTILE_CONT(Float(0.2)).WITHIN_GROUP_ORDER_BY(INTERVAL(1, HOUR).DESC()).AS("percentile_cont_interval"),
MODE().WITHIN_GROUP_ORDER_BY(Invoice.BillingPostalCode.DESC()).AS("mode_1"), MODE().WITHIN_GROUP_ORDER_BY(Invoice.BillingPostalCode.DESC()).AS("mode_1"),
).FROM( ).FROM(
@ -1309,18 +1306,19 @@ SELECT PERCENTILE_DISC ($1::double precision) WITHIN GROUP (ORDER BY "Invoice"."
PERCENTILE_DISC ("Invoice"."Total" / $2) WITHIN GROUP (ORDER BY "Invoice"."InvoiceDate" ASC) AS "percentile_disc_2", PERCENTILE_DISC ("Invoice"."Total" / $2) WITHIN GROUP (ORDER BY "Invoice"."InvoiceDate" ASC) AS "percentile_disc_2",
PERCENTILE_DISC ((select array_agg(s) from generate_series(0, 1, 0.2) as s)) WITHIN GROUP (ORDER BY "Invoice"."BillingAddress" DESC) AS "percentile_disc_3", PERCENTILE_DISC ((select array_agg(s) from generate_series(0, 1, 0.2) as s)) WITHIN GROUP (ORDER BY "Invoice"."BillingAddress" DESC) AS "percentile_disc_3",
PERCENTILE_CONT ($3::double precision) WITHIN GROUP (ORDER BY "Invoice"."Total") AS "percentile_cont_1", PERCENTILE_CONT ($3::double precision) WITHIN GROUP (ORDER BY "Invoice"."Total") AS "percentile_cont_1",
PERCENTILE_CONT ($4::double precision) WITHIN GROUP (ORDER BY INTERVAL '1 HOUR' DESC) AS "percentile_cont_int", PERCENTILE_CONT ($4::double precision) WITHIN GROUP (ORDER BY INTERVAL '1 HOUR' DESC) AS "percentile_cont_interval",
MODE () WITHIN GROUP (ORDER BY "Invoice"."BillingPostalCode" DESC) AS "mode_1" MODE () WITHIN GROUP (ORDER BY "Invoice"."BillingPostalCode" DESC) AS "mode_1"
FROM chinook."Invoice" FROM chinook."Invoice"
GROUP BY "Invoice"."Total"; GROUP BY "Invoice"."Total";
`, 0.1, 100.0, 0.3, 0.2) `, 0.1, 100.0, 0.3, 0.2)
var dest struct { var dest struct {
PercentileDisc1 string PercentileDisc1 string
PercentileDisc2 string PercentileDisc2 string
PercentileDisc3 string PercentileDisc3 string
PercentileCont1 string PercentileCont1 string
Mode1 string PercentileContInterval string
Mode1 string
} }
err := stmt.Query(db, &dest) err := stmt.Query(db, &dest)
@ -1332,6 +1330,7 @@ GROUP BY "Invoice"."Total";
"PercentileDisc2": "2009-01-19T00:00:00Z", "PercentileDisc2": "2009-01-19T00:00:00Z",
"PercentileDisc3": "{\"Via Degli Scipioni, 43\",\"Qe 7 Bloco G\",\"Berger Stra<72>e 10\",\"696 Osborne Street\",\"2211 W Berry Street\",\"1033 N Park Ave\"}", "PercentileDisc3": "{\"Via Degli Scipioni, 43\",\"Qe 7 Bloco G\",\"Berger Stra<72>e 10\",\"696 Osborne Street\",\"2211 W Berry Street\",\"1033 N Park Ave\"}",
"PercentileCont1": "0.99", "PercentileCont1": "0.99",
"PercentileContInterval": "01:00:00",
"Mode1": "X1A 1N6" "Mode1": "X1A 1N6"
} }
`) `)

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/go-jet/jet/v2/qrm"
"github.com/go-jet/jet/v2/stmtcache" "github.com/go-jet/jet/v2/stmtcache"
"github.com/go-jet/jet/v2/tests/internal/utils/repo" "github.com/go-jet/jet/v2/tests/internal/utils/repo"
"github.com/jackc/pgx/v4/stdlib" "github.com/jackc/pgx/v4/stdlib"
@ -49,6 +50,8 @@ func skipForCockroachDB(t *testing.T) {
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
defer profile.Start().Stop() defer profile.Start().Stop()
qrm.GlobalConfig.StrictScan = true
for _, driverName := range []string{"postgres", "pgx"} { for _, driverName := range []string{"postgres", "pgx"} {
fmt.Printf("\nRunning postgres tests for driver: %s, caching enabled: %t \n", driverName, withStatementCaching) fmt.Printf("\nRunning postgres tests for driver: %s, caching enabled: %t \n", driverName, withStatementCaching)
@ -95,6 +98,16 @@ func getConnectionString() string {
return dbconfig.PostgresConnectString return dbconfig.PostgresConnectString
} }
func allowUnusedColumns(f func()) {
defer func() {
qrm.GlobalConfig.StrictScan = true
}()
qrm.GlobalConfig.StrictScan = false
f()
}
var loggedSQL string var loggedSQL string
var loggedSQLArgs []interface{} var loggedSQLArgs []interface{}
var loggedDebugSQL string var loggedDebugSQL string

View file

@ -137,8 +137,10 @@ WHERE sample_ranges.date_range @> $36::date;
} }
var dest sample var dest sample
err := query.Query(db, &dest) allowUnusedColumns(func() {
require.NoError(t, err) err := query.Query(db, &dest)
require.NoError(t, err)
})
expectedRow := sample{ expectedRow := sample{
SampleRanges: sampleRangeRow, SampleRanges: sampleRangeRow,
@ -219,7 +221,7 @@ func TestRangeSelectColumnsFromSubQuery(t *testing.T) {
int4Range := Int4RangeColumn("range4").From(subQuery) int4Range := Int4RangeColumn("range4").From(subQuery)
stmt := SELECT( stmt := SELECT(
subQuery.AllColumns(), subQuery.AllColumns().Except(int4Range),
int4Range, int4Range,
).FROM( ).FROM(
subQuery, subQuery,
@ -232,7 +234,6 @@ SELECT sub_query."sample_ranges.date_range" AS "sample_ranges.date_range",
sub_query."sample_ranges.int4_range" AS "sample_ranges.int4_range", sub_query."sample_ranges.int4_range" AS "sample_ranges.int4_range",
sub_query."sample_ranges.int8_range" AS "sample_ranges.int8_range", sub_query."sample_ranges.int8_range" AS "sample_ranges.int8_range",
sub_query."sample_ranges.num_range" AS "sample_ranges.num_range", sub_query."sample_ranges.num_range" AS "sample_ranges.num_range",
sub_query.range4 AS "range4",
sub_query.range4 AS "range4" sub_query.range4 AS "range4"
FROM ( FROM (
SELECT sample_ranges.date_range AS "sample_ranges.date_range", SELECT sample_ranges.date_range AS "sample_ranges.date_range",

View file

@ -54,8 +54,18 @@ func TestScanToInvalidDestination(t *testing.T) {
} }
func TestScanToValidDestination(t *testing.T) { func TestScanToValidDestination(t *testing.T) {
t.Run("pointer to empty struct - non strict scan", func(t *testing.T) {
allowUnusedColumns(func() {
var dest []struct{}
err := oneInventoryQuery.Query(db, &dest)
require.NoError(t, err)
})
})
t.Run("pointer to struct", func(t *testing.T) { t.Run("pointer to struct", func(t *testing.T) {
dest := []struct{}{} var dest model.Inventory
err := oneInventoryQuery.Query(db, &dest) err := oneInventoryQuery.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
@ -63,20 +73,24 @@ func TestScanToValidDestination(t *testing.T) {
t.Run("global query function scan", func(t *testing.T) { t.Run("global query function scan", func(t *testing.T) {
queryStr, args := oneInventoryQuery.Sql() queryStr, args := oneInventoryQuery.Sql()
dest := []struct{}{} var dest model.Inventory
rowProcessed, err := qrm.Query(nil, db, queryStr, args, &dest) rowProcessed, err := qrm.Query(nil, db, queryStr, args, &dest)
require.Equal(t, rowProcessed, int64(1)) require.Equal(t, rowProcessed, int64(1))
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("pointer to slice", func(t *testing.T) { t.Run("pointer to slice", func(t *testing.T) {
err := oneInventoryQuery.Query(db, &[]struct{}{}) var dest []model.Inventory
err := oneInventoryQuery.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("pointer to slice of pointer to structs", func(t *testing.T) { t.Run("pointer to slice of pointer to structs", func(t *testing.T) {
err := oneInventoryQuery.Query(db, &[]*struct{}{}) var dest []*model.Inventory
err := oneInventoryQuery.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -84,7 +98,7 @@ func TestScanToValidDestination(t *testing.T) {
t.Run("pointer to slice of integers", func(t *testing.T) { t.Run("pointer to slice of integers", func(t *testing.T) {
var dest []int32 var dest []int32
err := oneInventoryQuery.Query(db, &dest) err := Inventory.SELECT(Inventory.InventoryID).Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, dest[0], int32(1)) require.Equal(t, dest[0], int32(1))
}) })
@ -92,7 +106,7 @@ func TestScanToValidDestination(t *testing.T) {
t.Run("pointer to slice integer pointers", func(t *testing.T) { t.Run("pointer to slice integer pointers", func(t *testing.T) {
var dest []*int32 var dest []*int32
err := oneInventoryQuery.Query(db, &dest) err := Inventory.SELECT(Inventory.InventoryID).Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, dest[0], ptr.Of(int32(1))) require.Equal(t, dest[0], ptr.Of(int32(1)))
}) })
@ -114,8 +128,6 @@ func TestScanToStruct(t *testing.T) {
SELECT(Inventory.AllColumns). SELECT(Inventory.AllColumns).
ORDER_BY(Inventory.InventoryID) ORDER_BY(Inventory.InventoryID)
//fmt.Println(query.DebugSql())
t.Run("one struct", func(t *testing.T) { t.Run("one struct", func(t *testing.T) {
dest := model.Inventory{} dest := model.Inventory{}
err := query.LIMIT(1).Query(db, &dest) err := query.LIMIT(1).Query(db, &dest)
@ -173,6 +185,7 @@ func TestScanToStruct(t *testing.T) {
InventoryID *int32 `sql:"primary_key"` InventoryID *int32 `sql:"primary_key"`
FilmID int16 FilmID int16
StoreID *int16 StoreID *int16
LastUpdate time.Time
} }
dest := Inventory{} dest := Inventory{}
@ -184,12 +197,15 @@ func TestScanToStruct(t *testing.T) {
require.Equal(t, *dest.InventoryID, int32(1)) require.Equal(t, *dest.InventoryID, int32(1))
require.Equal(t, dest.FilmID, int16(1)) require.Equal(t, dest.FilmID, int16(1))
require.Equal(t, *dest.StoreID, int16(1)) require.Equal(t, *dest.StoreID, int16(1))
require.NotEmpty(t, dest.LastUpdate)
}) })
t.Run("type convert int32 to int", func(t *testing.T) { t.Run("type convert int32 to int", func(t *testing.T) {
type Inventory struct { type Inventory struct {
InventoryID int InventoryID *int32 `sql:"primary_key"`
FilmID string FilmID int16
StoreID *int16
LastUpdate time.Time
} }
dest := Inventory{} dest := Inventory{}
@ -267,6 +283,8 @@ func TestScanToNestedStruct(t *testing.T) {
dest := struct { dest := struct {
model.Inventory model.Inventory
model.Actor //unused model.Actor //unused
model.Film
model.Store
}{} }{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -280,6 +298,8 @@ func TestScanToNestedStruct(t *testing.T) {
dest := struct { dest := struct {
model.Inventory model.Inventory
*model.Actor //unused *model.Actor //unused
model.Film
model.Store
}{} }{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -293,6 +313,8 @@ func TestScanToNestedStruct(t *testing.T) {
dest := struct { dest := struct {
model.Inventory model.Inventory
Actor *model.Actor //unused Actor *model.Actor //unused
model.Film
model.Store
}{} }{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -312,6 +334,8 @@ func TestScanToNestedStruct(t *testing.T) {
dest := struct { dest := struct {
model.Inventory model.Inventory
Actor *model.Actor //unused Actor *model.Actor //unused
model.Film
model.Store
}{} }{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -327,6 +351,8 @@ func TestScanToNestedStruct(t *testing.T) {
Actor *struct { Actor *struct {
model.Actor model.Actor
} //unused } //unused
model.Film
model.Store
}{} }{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -343,6 +369,8 @@ func TestScanToNestedStruct(t *testing.T) {
model.Actor //unused model.Actor //unused
model.Language //unesed model.Language //unesed
} }
model.Film
model.Store
}{} }{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -362,6 +390,7 @@ func TestScanToNestedStruct(t *testing.T) {
model.Actor //unselected model.Actor //unselected
model.Film //selected model.Film //selected
} }
model.Store
}{} }{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -382,6 +411,7 @@ func TestScanToNestedStruct(t *testing.T) {
*model.Film //selected *model.Film //selected
} }
} }
model.Store
}{} }{}
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -443,8 +473,13 @@ func TestScanToSlice(t *testing.T) {
ORDER_BY(Inventory.InventoryID). ORDER_BY(Inventory.InventoryID).
LIMIT(10) LIMIT(10)
justIDs := Inventory.
SELECT(Inventory.InventoryID).
ORDER_BY(Inventory.InventoryID).
LIMIT(10)
t.Run("slice od inventory", func(t *testing.T) { t.Run("slice od inventory", func(t *testing.T) {
dest := []model.Inventory{} var dest []model.Inventory
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -454,95 +489,111 @@ func TestScanToSlice(t *testing.T) {
testutils.AssertDeepEqual(t, dest[1], inventory2) testutils.AssertDeepEqual(t, dest[1], inventory2)
}) })
t.Run("slice of ints", func(t *testing.T) { t.Run("slice of int32 non strict scan", func(t *testing.T) {
allowUnusedColumns(func() {
var dest []int32
err := query.Query(db, &dest)
require.NoError(t, err)
testutils.AssertDeepEqual(t, dest, []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
})
})
t.Run("slice of int32 strict scan", func(t *testing.T) {
var dest []int32 var dest []int32
err := query.Query(db, &dest) err := justIDs.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
testutils.AssertDeepEqual(t, dest, []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) testutils.AssertDeepEqual(t, dest, []int32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
}) })
t.Run("slice type convertible", func(t *testing.T) { t.Run("slice type convertible", func(t *testing.T) {
var dest []int var dest []int
err := query.Query(db, &dest) err := justIDs.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("slice type mismatch", func(t *testing.T) { t.Run("slice type mismatch", func(t *testing.T) {
var dest []bool var dest []bool
err := query.Query(db, &dest) err := justIDs.Query(db, &dest)
require.Error(t, err) require.Error(t, err)
require.EqualError(t, err, `jet: can't append int64 to []bool slice: can't assign int64(2) to bool`) require.EqualError(t, err, `jet: can't append int64 to []bool slice: can't assign int64(2) to bool`)
}) })
}) })
t.Run("slice of complex structs", func(t *testing.T) { t.Run("slice of complex structs", func(t *testing.T) {
query := Inventory. query := SELECT(
INNER_JOIN(Film, Inventory.FilmID.EQ(Film.FilmID)). Inventory.AllColumns,
INNER_JOIN(Store, Inventory.StoreID.EQ(Store.StoreID)). Film.AllColumns,
SELECT( Store.AllColumns,
Inventory.AllColumns, ).FROM(
Film.AllColumns, Inventory.
Store.AllColumns, INNER_JOIN(Film, Inventory.FilmID.EQ(Film.FilmID)).
). INNER_JOIN(Store, Inventory.StoreID.EQ(Store.StoreID)),
ORDER_BY(Inventory.InventoryID). ).ORDER_BY(
LIMIT(10) Inventory.InventoryID,
).LIMIT(10)
t.Run("struct with slice of ints", func(t *testing.T) { t.Run("struct with slice of ints - non strict scan", func(t *testing.T) {
var dest struct { allowUnusedColumns(func() {
model.Film var dest struct {
IDs []int32 `alias:"inventory.inventory_id"` model.Film
} IDs []int32 `alias:"inventory.inventory_id"`
}
err := query.Query(db, &dest) err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
testutils.AssertDeepEqual(t, dest.Film, film1) testutils.AssertDeepEqual(t, dest.Film, film1)
testutils.AssertDeepEqual(t, dest.IDs, []int32{1, 2, 3, 4, 5, 6, 7, 8}) testutils.AssertDeepEqual(t, dest.IDs, []int32{1, 2, 3, 4, 5, 6, 7, 8})
})
}) })
t.Run("slice of structs with slice of ints", func(t *testing.T) { t.Run("slice of structs with slice of ints", func(t *testing.T) {
var dest []struct { allowUnusedColumns(func() {
model.Film var dest []struct {
IDs []int32 `alias:"inventory.inventory_id"` model.Film
} IDs []int32 `alias:"inventory.inventory_id"`
}
err := query.Query(db, &dest) err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(dest), 2) require.Equal(t, len(dest), 2)
testutils.AssertDeepEqual(t, dest[0].Film, film1) testutils.AssertDeepEqual(t, dest[0].Film, film1)
testutils.AssertDeepEqual(t, dest[0].IDs, []int32{1, 2, 3, 4, 5, 6, 7, 8}) testutils.AssertDeepEqual(t, dest[0].IDs, []int32{1, 2, 3, 4, 5, 6, 7, 8})
testutils.AssertDeepEqual(t, dest[1].Film, film2) testutils.AssertDeepEqual(t, dest[1].Film, film2)
testutils.AssertDeepEqual(t, dest[1].IDs, []int32{9, 10}) testutils.AssertDeepEqual(t, dest[1].IDs, []int32{9, 10})
})
}) })
t.Run("slice of structs with slice of pointer to ints", func(t *testing.T) { t.Run("slice of structs with slice of pointer to ints", func(t *testing.T) {
var dest []struct { allowUnusedColumns(func() {
model.Film var dest []struct {
IDs []*int32 `alias:"inventory.inventory_id"` model.Film
} IDs []*int32 `alias:"inventory.inventory_id"`
}
err := query.Query(db, &dest) err := query.Query(db, &dest)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(dest), 2) require.Equal(t, len(dest), 2)
testutils.AssertDeepEqual(t, dest[0].Film, film1) testutils.AssertDeepEqual(t, dest[0].Film, film1)
testutils.AssertDeepEqual(t, dest[0].IDs, []*int32{ptr.Of(int32(1)), ptr.Of(int32(2)), ptr.Of(int32(3)), ptr.Of(int32(4)), testutils.AssertDeepEqual(t, dest[0].IDs, []*int32{ptr.Of(int32(1)), ptr.Of(int32(2)), ptr.Of(int32(3)), ptr.Of(int32(4)),
ptr.Of(int32(5)), ptr.Of(int32(6)), ptr.Of(int32(7)), ptr.Of(int32(8))}) ptr.Of(int32(5)), ptr.Of(int32(6)), ptr.Of(int32(7)), ptr.Of(int32(8))})
testutils.AssertDeepEqual(t, dest[1].Film, film2) testutils.AssertDeepEqual(t, dest[1].Film, film2)
testutils.AssertDeepEqual(t, dest[1].IDs, []*int32{ptr.Of(int32(9)), ptr.Of(int32(10))}) testutils.AssertDeepEqual(t, dest[1].IDs, []*int32{ptr.Of(int32(9)), ptr.Of(int32(10))})
})
}) })
t.Run("complex struct 1", func(t *testing.T) { t.Run("complex struct 1", func(t *testing.T) {
dest := []struct { var dest []struct {
model.Inventory model.Inventory
model.Film model.Film
model.Store model.Store
}{} }
err := query.Query(db, &dest) err := query.Query(db, &dest)
@ -619,6 +670,7 @@ func TestScanToSlice(t *testing.T) {
Inventories []struct { Inventories []struct {
model.Inventory model.Inventory
model.Store
Rentals *[]model.Rental Rentals *[]model.Rental
Rentals2 []model.Rental Rentals2 []model.Rental
@ -639,11 +691,11 @@ func TestScanToSlice(t *testing.T) {
}) })
t.Run("slice of complex structs 2", func(t *testing.T) { t.Run("slice of complex structs 2", func(t *testing.T) {
query := Country. query := SELECT(Country.AllColumns, City.AllColumns, Address.AllColumns, Customer.AllColumns).
INNER_JOIN(City, City.CountryID.EQ(Country.CountryID)). FROM(Country.
INNER_JOIN(Address, Address.CityID.EQ(City.CityID)). INNER_JOIN(City, City.CountryID.EQ(Country.CountryID)).
INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID)). INNER_JOIN(Address, Address.CityID.EQ(City.CityID)).
SELECT(Country.AllColumns, City.AllColumns, Address.AllColumns, Customer.AllColumns). INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID))).
ORDER_BY(Country.CountryID.ASC(), City.CityID.ASC(), Address.AddressID.ASC(), Customer.CustomerID.ASC()). ORDER_BY(Country.CountryID.ASC(), City.CityID.ASC(), Address.AddressID.ASC(), Customer.CustomerID.ASC()).
LIMIT(1000) LIMIT(1000)
@ -654,7 +706,7 @@ func TestScanToSlice(t *testing.T) {
Cities []struct { Cities []struct {
model.City model.City
Adresses []struct { Addresses []struct {
model.Address model.Address
Customer model.Customer Customer model.Customer
@ -669,14 +721,14 @@ func TestScanToSlice(t *testing.T) {
testutils.AssertDeepEqual(t, dest[100].Country, countryUk) testutils.AssertDeepEqual(t, dest[100].Country, countryUk)
require.Equal(t, len(dest[100].Cities), 8) require.Equal(t, len(dest[100].Cities), 8)
testutils.AssertDeepEqual(t, dest[100].Cities[2].City, cityLondon) testutils.AssertDeepEqual(t, dest[100].Cities[2].City, cityLondon)
require.Equal(t, len(dest[100].Cities[2].Adresses), 2) require.Equal(t, len(dest[100].Cities[2].Addresses), 2)
testutils.AssertDeepEqual(t, dest[100].Cities[2].Adresses[0].Address, address256) testutils.AssertDeepEqual(t, dest[100].Cities[2].Addresses[0].Address, address256)
testutils.AssertDeepEqual(t, dest[100].Cities[2].Adresses[0].Customer, customer256) testutils.AssertDeepEqual(t, dest[100].Cities[2].Addresses[0].Customer, customer256)
testutils.AssertDeepEqual(t, dest[100].Cities[2].Adresses[1].Address, addres517) testutils.AssertDeepEqual(t, dest[100].Cities[2].Addresses[1].Address, addres517)
testutils.AssertDeepEqual(t, dest[100].Cities[2].Adresses[1].Customer, customer512) testutils.AssertDeepEqual(t, dest[100].Cities[2].Addresses[1].Customer, customer512)
}) })
t.Run("dest1", func(t *testing.T) { t.Run("dest2", func(t *testing.T) {
var dest []*struct { var dest []*struct {
*model.Country *model.Country
@ -707,7 +759,7 @@ func TestScanToSlice(t *testing.T) {
}) })
t.Run("dest1", func(t *testing.T) { t.Run("dest3", func(t *testing.T) {
var dest []*struct { var dest []*struct {
*model.Country *model.Country
@ -1074,6 +1126,47 @@ VALUES (1234, 0, 'Joe', '', NULL, 1, TRUE, '2020-02-02 10:00:00Z', NULL, 1);
testutils.AssertExecAndRollback(t, stmt, db) testutils.AssertExecAndRollback(t, stmt, db)
} }
func TestStrictScan(t *testing.T) {
stmt := SELECT(
Actor.AllColumns,
).FROM(
Actor,
).LIMIT(10)
type Actor struct {
ActorID int32 `sql:"primary_key"`
FirstName string
//LastName string
//LastUpdate time.Time
}
var dest []Actor
require.PanicsWithValue(t, "jet: columns never used: 'actor.last_name', 'actor.last_update'", func() {
err := stmt.Query(db, &dest)
require.NoError(t, err)
})
var dest2 []model.Actor
err := stmt.Query(db, &dest2)
require.NoError(t, err)
t.Run("using_rows", func(t *testing.T) {
rows, err := stmt.Rows(ctx, db)
require.NoError(t, err)
require.True(t, rows.Next())
require.PanicsWithValue(t, "jet: columns never used: 'actor.last_name', 'actor.last_update'", func() {
var actor Actor
err = rows.Scan(&actor)
require.NoError(t, err)
})
})
}
var address256 = model.Address{ var address256 = model.Address{
AddressID: 256, AddressID: 256,
Address: "1497 Yuzhou Drive", Address: "1497 Yuzhou Drive",

View file

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

View file

@ -454,8 +454,11 @@ FROM cte2;
} `alias:"territories4.*"` } `alias:"territories4.*"`
} }
err := stmt.Query(db, &dest) allowUnusedColumns(func() {
require.NoError(t, err) err := stmt.Query(db, &dest)
require.NoError(t, err)
})
require.Len(t, dest, 53) require.Len(t, dest, 53)
require.Equal(t, dest[0].Territories1.Territories, model.Territories{ require.Equal(t, dest[0].Territories1.Territories, model.Territories{
TerritoryID: "01581", TerritoryID: "01581",