Add support for strict scan.
If there are unused columns in query result set Query method panics.
This commit is contained in:
parent
cfc264221b
commit
d86f14e665
9 changed files with 402 additions and 207 deletions
23
qrm/qrm.go
23
qrm/qrm.go
|
|
@ -10,6 +10,21 @@ import (
|
|||
"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
|
||||
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)
|
||||
|
||||
scanContext.rowNum++
|
||||
|
||||
_, err = mapRowToStruct(scanContext, "", destValuePtr, nil)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("jet: failed to scan a row into destination, %w", err)
|
||||
}
|
||||
|
||||
scanContext.EnsureEveryColumnRead() // can panic
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +265,10 @@ func queryToSlice(ctx context.Context, db Queryable, query string, args []interf
|
|||
if err != nil {
|
||||
return scanContext.rowNum, err
|
||||
}
|
||||
|
||||
if scanContext.rowNum == 1 && GlobalConfig.StrictScan {
|
||||
scanContext.EnsureEveryColumnRead()
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ type ScanContext struct {
|
|||
typeInfoMap map[string]typeInfo
|
||||
|
||||
typesVisited typeStack // to prevent circular dependency scan
|
||||
columnAlias []string
|
||||
columnIndexRead []bool
|
||||
}
|
||||
|
||||
// NewScanContext creates new ScanContext from rows
|
||||
|
|
@ -57,9 +59,26 @@ func NewScanContext(rows *sql.Rows) (*ScanContext, error) {
|
|||
typeInfoMap: make(map[string]typeInfo),
|
||||
|
||||
typesVisited: newTypeStack(),
|
||||
|
||||
columnAlias: aliases,
|
||||
columnIndexRead: make([]bool, len(aliases)),
|
||||
}, 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{} {
|
||||
scanPtrSlice := make([]interface{}, columnCount)
|
||||
|
||||
|
|
@ -244,6 +263,9 @@ func (s *ScanContext) typeToColumnIndex(typeName, fieldName string) int {
|
|||
// rowElemValue always returns non-ptr value,
|
||||
// invalid value is nil
|
||||
func (s *ScanContext) rowElemValue(index int) reflect.Value {
|
||||
if s.rowNum == 1 {
|
||||
s.columnIndexRead[index] = true
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -591,10 +591,12 @@ func TestExpressionCast(t *testing.T) {
|
|||
Raw("current_database()"),
|
||||
)
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
var dest []struct{}
|
||||
err := query.Query(db, &dest)
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestStringOperators(t *testing.T) {
|
||||
|
|
@ -673,10 +675,11 @@ func TestStringOperators(t *testing.T) {
|
|||
TO_HEX(AllTypes.IntegerPtr),
|
||||
)
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
var dest []struct{}
|
||||
err := query.Query(db, &dest)
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBytea(t *testing.T) {
|
||||
|
|
@ -792,10 +795,12 @@ FROM test_sample.all_types;
|
|||
`)
|
||||
}
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
var dest []struct{}
|
||||
err := stmt.Query(db, &dest)
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlobConversion(t *testing.T) {
|
||||
|
|
@ -1185,9 +1190,11 @@ LIMIT $27;
|
|||
common.AllTypesIntegerExpResult `alias:"."`
|
||||
}
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
err := query.Query(db, &dest)
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
//testutils.SaveJSONFile(dest, "./testdata/results/common/int_operators.json")
|
||||
//testutils.PrintJson(dest)
|
||||
|
|
@ -1271,9 +1278,12 @@ func TestTimeExpression(t *testing.T) {
|
|||
// fmt.Println(query.DebugSql())
|
||||
|
||||
var dest []struct{}
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
err := query.Query(db, &dest)
|
||||
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimeScan(t *testing.T) {
|
||||
|
|
@ -1616,8 +1626,11 @@ SELECT INTERVAL '1 YEAR',
|
|||
FROM test_sample.all_types;
|
||||
`)
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
err := stmt.Query(db, &struct{}{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
requireLogged(t, stmt)
|
||||
}
|
||||
|
||||
|
|
@ -1677,8 +1690,10 @@ SELECT EXTRACT(CENTURY FROM all_types.timestampz),
|
|||
FROM test_sample.all_types;
|
||||
`)
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
err := stmt.Query(db, &struct{}{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
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);
|
||||
`)
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
err := stmt.Query(db, &struct{}{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAllTypesSubQueryFrom(t *testing.T) {
|
||||
|
|
@ -2048,7 +2065,11 @@ FROM`
|
|||
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(dest1), 2)
|
||||
|
|
@ -2064,12 +2085,17 @@ FROM`
|
|||
|
||||
stmt2 := SELECT(
|
||||
subQuery.AllColumns(),
|
||||
).
|
||||
FROM(subQuery)
|
||||
).FROM(
|
||||
subQuery,
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -320,7 +320,6 @@ func testJoinEverything(t require.TestingT) {
|
|||
Track.AllColumns,
|
||||
Genre.AllColumns,
|
||||
MediaType.AllColumns,
|
||||
PlaylistTrack.AllColumns,
|
||||
Playlist.AllColumns,
|
||||
Invoice.AllColumns,
|
||||
Customer.AllColumns,
|
||||
|
|
@ -365,8 +364,6 @@ SELECT "Artist"."ArtistId" AS "Artist.ArtistId",
|
|||
"Genre"."Name" AS "Genre.Name",
|
||||
"MediaType"."MediaTypeId" AS "MediaType.MediaTypeId",
|
||||
"MediaType"."Name" AS "MediaType.Name",
|
||||
"PlaylistTrack"."PlaylistId" AS "PlaylistTrack.PlaylistId",
|
||||
"PlaylistTrack"."TrackId" AS "PlaylistTrack.TrackId",
|
||||
"Playlist"."PlaylistId" AS "Playlist.PlaylistId",
|
||||
"Playlist"."Name" AS "Playlist.Name",
|
||||
"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"),
|
||||
|
||||
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"),
|
||||
).FROM(
|
||||
|
|
@ -1309,7 +1306,7 @@ 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 ((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 ($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"
|
||||
FROM chinook."Invoice"
|
||||
GROUP BY "Invoice"."Total";
|
||||
|
|
@ -1320,6 +1317,7 @@ GROUP BY "Invoice"."Total";
|
|||
PercentileDisc2 string
|
||||
PercentileDisc3 string
|
||||
PercentileCont1 string
|
||||
PercentileContInterval string
|
||||
Mode1 string
|
||||
}
|
||||
|
||||
|
|
@ -1332,6 +1330,7 @@ GROUP BY "Invoice"."Total";
|
|||
"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\"}",
|
||||
"PercentileCont1": "0.99",
|
||||
"PercentileContInterval": "01:00:00",
|
||||
"Mode1": "X1A 1N6"
|
||||
}
|
||||
`)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/go-jet/jet/v2/qrm"
|
||||
"github.com/go-jet/jet/v2/stmtcache"
|
||||
"github.com/go-jet/jet/v2/tests/internal/utils/repo"
|
||||
"github.com/jackc/pgx/v4/stdlib"
|
||||
|
|
@ -49,6 +50,8 @@ func skipForCockroachDB(t *testing.T) {
|
|||
func TestMain(m *testing.M) {
|
||||
defer profile.Start().Stop()
|
||||
|
||||
qrm.GlobalConfig.StrictScan = true
|
||||
|
||||
for _, driverName := range []string{"postgres", "pgx"} {
|
||||
|
||||
fmt.Printf("\nRunning postgres tests for driver: %s, caching enabled: %t \n", driverName, withStatementCaching)
|
||||
|
|
@ -95,6 +98,16 @@ func getConnectionString() string {
|
|||
return dbconfig.PostgresConnectString
|
||||
}
|
||||
|
||||
func allowUnusedColumns(f func()) {
|
||||
defer func() {
|
||||
qrm.GlobalConfig.StrictScan = true
|
||||
}()
|
||||
|
||||
qrm.GlobalConfig.StrictScan = false
|
||||
|
||||
f()
|
||||
}
|
||||
|
||||
var loggedSQL string
|
||||
var loggedSQLArgs []interface{}
|
||||
var loggedDebugSQL string
|
||||
|
|
|
|||
|
|
@ -137,8 +137,10 @@ WHERE sample_ranges.date_range @> $36::date;
|
|||
}
|
||||
|
||||
var dest sample
|
||||
allowUnusedColumns(func() {
|
||||
err := query.Query(db, &dest)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
expectedRow := sample{
|
||||
SampleRanges: sampleRangeRow,
|
||||
|
|
@ -219,7 +221,7 @@ func TestRangeSelectColumnsFromSubQuery(t *testing.T) {
|
|||
int4Range := Int4RangeColumn("range4").From(subQuery)
|
||||
|
||||
stmt := SELECT(
|
||||
subQuery.AllColumns(),
|
||||
subQuery.AllColumns().Except(int4Range),
|
||||
int4Range,
|
||||
).FROM(
|
||||
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.int8_range" AS "sample_ranges.int8_range",
|
||||
sub_query."sample_ranges.num_range" AS "sample_ranges.num_range",
|
||||
sub_query.range4 AS "range4",
|
||||
sub_query.range4 AS "range4"
|
||||
FROM (
|
||||
SELECT sample_ranges.date_range AS "sample_ranges.date_range",
|
||||
|
|
|
|||
|
|
@ -54,8 +54,18 @@ func TestScanToInvalidDestination(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) {
|
||||
dest := []struct{}{}
|
||||
var dest model.Inventory
|
||||
err := oneInventoryQuery.Query(db, &dest)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
|
@ -63,20 +73,24 @@ func TestScanToValidDestination(t *testing.T) {
|
|||
|
||||
t.Run("global query function scan", func(t *testing.T) {
|
||||
queryStr, args := oneInventoryQuery.Sql()
|
||||
dest := []struct{}{}
|
||||
var dest model.Inventory
|
||||
rowProcessed, err := qrm.Query(nil, db, queryStr, args, &dest)
|
||||
require.Equal(t, rowProcessed, int64(1))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
|
@ -84,7 +98,7 @@ func TestScanToValidDestination(t *testing.T) {
|
|||
t.Run("pointer to slice of integers", func(t *testing.T) {
|
||||
var dest []int32
|
||||
|
||||
err := oneInventoryQuery.Query(db, &dest)
|
||||
err := Inventory.SELECT(Inventory.InventoryID).Query(db, &dest)
|
||||
require.NoError(t, err)
|
||||
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) {
|
||||
var dest []*int32
|
||||
|
||||
err := oneInventoryQuery.Query(db, &dest)
|
||||
err := Inventory.SELECT(Inventory.InventoryID).Query(db, &dest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dest[0], ptr.Of(int32(1)))
|
||||
})
|
||||
|
|
@ -114,8 +128,6 @@ func TestScanToStruct(t *testing.T) {
|
|||
SELECT(Inventory.AllColumns).
|
||||
ORDER_BY(Inventory.InventoryID)
|
||||
|
||||
//fmt.Println(query.DebugSql())
|
||||
|
||||
t.Run("one struct", func(t *testing.T) {
|
||||
dest := model.Inventory{}
|
||||
err := query.LIMIT(1).Query(db, &dest)
|
||||
|
|
@ -173,6 +185,7 @@ func TestScanToStruct(t *testing.T) {
|
|||
InventoryID *int32 `sql:"primary_key"`
|
||||
FilmID int16
|
||||
StoreID *int16
|
||||
LastUpdate time.Time
|
||||
}
|
||||
|
||||
dest := Inventory{}
|
||||
|
|
@ -184,12 +197,15 @@ func TestScanToStruct(t *testing.T) {
|
|||
require.Equal(t, *dest.InventoryID, int32(1))
|
||||
require.Equal(t, dest.FilmID, 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) {
|
||||
type Inventory struct {
|
||||
InventoryID int
|
||||
FilmID string
|
||||
InventoryID *int32 `sql:"primary_key"`
|
||||
FilmID int16
|
||||
StoreID *int16
|
||||
LastUpdate time.Time
|
||||
}
|
||||
|
||||
dest := Inventory{}
|
||||
|
|
@ -267,6 +283,8 @@ func TestScanToNestedStruct(t *testing.T) {
|
|||
dest := struct {
|
||||
model.Inventory
|
||||
model.Actor //unused
|
||||
model.Film
|
||||
model.Store
|
||||
}{}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
|
@ -280,6 +298,8 @@ func TestScanToNestedStruct(t *testing.T) {
|
|||
dest := struct {
|
||||
model.Inventory
|
||||
*model.Actor //unused
|
||||
model.Film
|
||||
model.Store
|
||||
}{}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
|
@ -293,6 +313,8 @@ func TestScanToNestedStruct(t *testing.T) {
|
|||
dest := struct {
|
||||
model.Inventory
|
||||
Actor *model.Actor //unused
|
||||
model.Film
|
||||
model.Store
|
||||
}{}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
|
@ -312,6 +334,8 @@ func TestScanToNestedStruct(t *testing.T) {
|
|||
dest := struct {
|
||||
model.Inventory
|
||||
Actor *model.Actor //unused
|
||||
model.Film
|
||||
model.Store
|
||||
}{}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
|
@ -327,6 +351,8 @@ func TestScanToNestedStruct(t *testing.T) {
|
|||
Actor *struct {
|
||||
model.Actor
|
||||
} //unused
|
||||
model.Film
|
||||
model.Store
|
||||
}{}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
|
@ -343,6 +369,8 @@ func TestScanToNestedStruct(t *testing.T) {
|
|||
model.Actor //unused
|
||||
model.Language //unesed
|
||||
}
|
||||
model.Film
|
||||
model.Store
|
||||
}{}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
|
@ -362,6 +390,7 @@ func TestScanToNestedStruct(t *testing.T) {
|
|||
model.Actor //unselected
|
||||
model.Film //selected
|
||||
}
|
||||
model.Store
|
||||
}{}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
|
@ -382,6 +411,7 @@ func TestScanToNestedStruct(t *testing.T) {
|
|||
*model.Film //selected
|
||||
}
|
||||
}
|
||||
model.Store
|
||||
}{}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
|
@ -443,8 +473,13 @@ func TestScanToSlice(t *testing.T) {
|
|||
ORDER_BY(Inventory.InventoryID).
|
||||
LIMIT(10)
|
||||
|
||||
justIDs := Inventory.
|
||||
SELECT(Inventory.InventoryID).
|
||||
ORDER_BY(Inventory.InventoryID).
|
||||
LIMIT(10)
|
||||
|
||||
t.Run("slice od inventory", func(t *testing.T) {
|
||||
dest := []model.Inventory{}
|
||||
var dest []model.Inventory
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
||||
|
|
@ -454,44 +489,55 @@ func TestScanToSlice(t *testing.T) {
|
|||
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
|
||||
|
||||
err := justIDs.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 type convertible", func(t *testing.T) {
|
||||
var dest []int
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
err := justIDs.Query(db, &dest)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("slice type mismatch", func(t *testing.T) {
|
||||
var dest []bool
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
err := justIDs.Query(db, &dest)
|
||||
require.Error(t, err)
|
||||
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) {
|
||||
query := Inventory.
|
||||
INNER_JOIN(Film, Inventory.FilmID.EQ(Film.FilmID)).
|
||||
INNER_JOIN(Store, Inventory.StoreID.EQ(Store.StoreID)).
|
||||
SELECT(
|
||||
query := SELECT(
|
||||
Inventory.AllColumns,
|
||||
Film.AllColumns,
|
||||
Store.AllColumns,
|
||||
).
|
||||
ORDER_BY(Inventory.InventoryID).
|
||||
LIMIT(10)
|
||||
).FROM(
|
||||
Inventory.
|
||||
INNER_JOIN(Film, Inventory.FilmID.EQ(Film.FilmID)).
|
||||
INNER_JOIN(Store, Inventory.StoreID.EQ(Store.StoreID)),
|
||||
).ORDER_BY(
|
||||
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) {
|
||||
allowUnusedColumns(func() {
|
||||
var dest struct {
|
||||
model.Film
|
||||
IDs []int32 `alias:"inventory.inventory_id"`
|
||||
|
|
@ -503,8 +549,10 @@ func TestScanToSlice(t *testing.T) {
|
|||
testutils.AssertDeepEqual(t, dest.Film, film1)
|
||||
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) {
|
||||
allowUnusedColumns(func() {
|
||||
var dest []struct {
|
||||
model.Film
|
||||
IDs []int32 `alias:"inventory.inventory_id"`
|
||||
|
|
@ -519,8 +567,10 @@ func TestScanToSlice(t *testing.T) {
|
|||
testutils.AssertDeepEqual(t, dest[1].Film, film2)
|
||||
testutils.AssertDeepEqual(t, dest[1].IDs, []int32{9, 10})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("slice of structs with slice of pointer to ints", func(t *testing.T) {
|
||||
allowUnusedColumns(func() {
|
||||
var dest []struct {
|
||||
model.Film
|
||||
IDs []*int32 `alias:"inventory.inventory_id"`
|
||||
|
|
@ -536,13 +586,14 @@ func TestScanToSlice(t *testing.T) {
|
|||
testutils.AssertDeepEqual(t, dest[1].Film, film2)
|
||||
testutils.AssertDeepEqual(t, dest[1].IDs, []*int32{ptr.Of(int32(9)), ptr.Of(int32(10))})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("complex struct 1", func(t *testing.T) {
|
||||
dest := []struct {
|
||||
var dest []struct {
|
||||
model.Inventory
|
||||
model.Film
|
||||
model.Store
|
||||
}{}
|
||||
}
|
||||
|
||||
err := query.Query(db, &dest)
|
||||
|
||||
|
|
@ -619,6 +670,7 @@ func TestScanToSlice(t *testing.T) {
|
|||
|
||||
Inventories []struct {
|
||||
model.Inventory
|
||||
model.Store
|
||||
|
||||
Rentals *[]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) {
|
||||
query := Country.
|
||||
query := SELECT(Country.AllColumns, City.AllColumns, Address.AllColumns, Customer.AllColumns).
|
||||
FROM(Country.
|
||||
INNER_JOIN(City, City.CountryID.EQ(Country.CountryID)).
|
||||
INNER_JOIN(Address, Address.CityID.EQ(City.CityID)).
|
||||
INNER_JOIN(Customer, Customer.AddressID.EQ(Address.AddressID)).
|
||||
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()).
|
||||
LIMIT(1000)
|
||||
|
||||
|
|
@ -654,7 +706,7 @@ func TestScanToSlice(t *testing.T) {
|
|||
Cities []struct {
|
||||
model.City
|
||||
|
||||
Adresses []struct {
|
||||
Addresses []struct {
|
||||
model.Address
|
||||
|
||||
Customer model.Customer
|
||||
|
|
@ -669,14 +721,14 @@ func TestScanToSlice(t *testing.T) {
|
|||
testutils.AssertDeepEqual(t, dest[100].Country, countryUk)
|
||||
require.Equal(t, len(dest[100].Cities), 8)
|
||||
testutils.AssertDeepEqual(t, dest[100].Cities[2].City, cityLondon)
|
||||
require.Equal(t, len(dest[100].Cities[2].Adresses), 2)
|
||||
testutils.AssertDeepEqual(t, dest[100].Cities[2].Adresses[0].Address, address256)
|
||||
testutils.AssertDeepEqual(t, dest[100].Cities[2].Adresses[0].Customer, customer256)
|
||||
testutils.AssertDeepEqual(t, dest[100].Cities[2].Adresses[1].Address, addres517)
|
||||
testutils.AssertDeepEqual(t, dest[100].Cities[2].Adresses[1].Customer, customer512)
|
||||
require.Equal(t, len(dest[100].Cities[2].Addresses), 2)
|
||||
testutils.AssertDeepEqual(t, dest[100].Cities[2].Addresses[0].Address, address256)
|
||||
testutils.AssertDeepEqual(t, dest[100].Cities[2].Addresses[0].Customer, customer256)
|
||||
testutils.AssertDeepEqual(t, dest[100].Cities[2].Addresses[1].Address, addres517)
|
||||
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 {
|
||||
*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 {
|
||||
*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)
|
||||
}
|
||||
|
||||
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{
|
||||
AddressID: 256,
|
||||
Address: "1497 Yuzhou Drive",
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
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
|
||||
}
|
||||
|
||||
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,17 +2818,18 @@ 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(
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
allowUnusedColumns(func() {
|
||||
err := query.Query(db, &[]struct{}{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSelectWindowFunction(t *testing.T) {
|
||||
|
|
@ -2928,8 +2931,7 @@ FROM dvds.payment
|
|||
WHERE payment.payment_id < $3
|
||||
GROUP BY payment.amount, payment.customer_id, payment.payment_date;
|
||||
`
|
||||
query := Payment.
|
||||
SELECT(
|
||||
query := SELECT(
|
||||
AVG(Payment.Amount).OVER(),
|
||||
AVG(Payment.Amount).OVER(PARTITION_BY(Payment.CustomerID)),
|
||||
MAXf(Payment.Amount).OVER(ORDER_BY(Payment.PaymentDate.DESC())),
|
||||
|
|
@ -2957,16 +2959,24 @@ GROUP BY payment.amount, payment.customer_id, payment.payment_date;
|
|||
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)))
|
||||
).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)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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;
|
||||
`)
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
err := stmt.Query(db, &struct{}{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func GET_FILM_COUNT(lenFrom, lenTo IntegerExpression) IntegerExpression {
|
||||
|
|
|
|||
|
|
@ -454,8 +454,11 @@ FROM cte2;
|
|||
} `alias:"territories4.*"`
|
||||
}
|
||||
|
||||
allowUnusedColumns(func() {
|
||||
err := stmt.Query(db, &dest)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
require.Len(t, dest, 53)
|
||||
require.Equal(t, dest[0].Territories1.Territories, model.Territories{
|
||||
TerritoryID: "01581",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue