1198 lines
31 KiB
Go
1198 lines
31 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"github.com/go-jet/jet/v2/internal/utils/ptr"
|
|
"github.com/volatiletech/null/v8"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/go-jet/jet/v2/internal/testutils"
|
|
. "github.com/go-jet/jet/v2/postgres"
|
|
"github.com/go-jet/jet/v2/qrm"
|
|
"github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/model"
|
|
. "github.com/go-jet/jet/v2/tests/.gentestdata/jetdb/dvds/table"
|
|
)
|
|
|
|
var oneInventoryQuery = Inventory.
|
|
SELECT(Inventory.AllColumns).
|
|
LIMIT(1).
|
|
ORDER_BY(Inventory.InventoryID)
|
|
|
|
func TestScanToInvalidDestination(t *testing.T) {
|
|
|
|
t.Run("nil dest", func(t *testing.T) {
|
|
testutils.AssertQueryPanicErr(t, oneInventoryQuery, db, nil, "jet: destination is nil")
|
|
})
|
|
|
|
t.Run("struct dest", func(t *testing.T) {
|
|
testutils.AssertQueryPanicErr(t, oneInventoryQuery, db, struct{}{}, "jet: destination has to be a pointer to slice or pointer to struct")
|
|
})
|
|
|
|
t.Run("slice dest", func(t *testing.T) {
|
|
testutils.AssertQueryPanicErr(t, oneInventoryQuery, db, []struct{}{}, "jet: destination has to be a pointer to slice or pointer to struct")
|
|
})
|
|
|
|
t.Run("slice of pointers to pointer dest", func(t *testing.T) {
|
|
testutils.AssertQueryPanicErr(t, oneInventoryQuery, db, []**struct{}{}, "jet: destination has to be a pointer to slice or pointer to struct")
|
|
})
|
|
|
|
t.Run("map dest", func(t *testing.T) {
|
|
testutils.AssertQueryPanicErr(t, oneInventoryQuery, db, &map[string]string{}, "jet: destination has to be a pointer to slice or pointer to struct")
|
|
})
|
|
|
|
t.Run("map dest", func(t *testing.T) {
|
|
testutils.AssertQueryPanicErr(t, oneInventoryQuery, db, []map[string]string{}, "jet: destination has to be a pointer to slice or pointer to struct")
|
|
})
|
|
|
|
t.Run("map dest", func(t *testing.T) {
|
|
testutils.AssertQueryPanicErr(t, oneInventoryQuery, db, &[]map[string]string{}, "jet: unsupported slice element type")
|
|
})
|
|
}
|
|
|
|
func TestScanToValidDestination(t *testing.T) {
|
|
t.Run("pointer to struct", func(t *testing.T) {
|
|
dest := []struct{}{}
|
|
err := oneInventoryQuery.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("global query function scan", func(t *testing.T) {
|
|
queryStr, args := oneInventoryQuery.Sql()
|
|
dest := []struct{}{}
|
|
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{}{})
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("pointer to slice of pointer to structs", func(t *testing.T) {
|
|
err := oneInventoryQuery.Query(db, &[]*struct{}{})
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("pointer to slice of integers", func(t *testing.T) {
|
|
var dest []int32
|
|
|
|
err := oneInventoryQuery.Query(db, &dest)
|
|
require.NoError(t, err)
|
|
require.Equal(t, dest[0], int32(1))
|
|
})
|
|
|
|
t.Run("pointer to slice integer pointers", func(t *testing.T) {
|
|
var dest []*int32
|
|
|
|
err := oneInventoryQuery.Query(db, &dest)
|
|
require.NoError(t, err)
|
|
require.Equal(t, dest[0], ptr.Of(int32(1)))
|
|
})
|
|
|
|
t.Run("NULL to integer", func(t *testing.T) {
|
|
var dest struct {
|
|
Int64 int64
|
|
UInt64 uint64
|
|
}
|
|
err := SELECT(NULL.AS("int64"), NULL.AS("uint64")).Query(db, &dest)
|
|
require.NoError(t, err)
|
|
require.Equal(t, dest.Int64, int64(0))
|
|
require.Equal(t, dest.UInt64, uint64(0))
|
|
})
|
|
}
|
|
|
|
func TestScanToStruct(t *testing.T) {
|
|
query := Inventory.
|
|
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)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, inventory1, dest)
|
|
})
|
|
|
|
t.Run("multiple structs, just first one used", func(t *testing.T) {
|
|
dest := model.Inventory{}
|
|
err := query.LIMIT(10).Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, inventory1, dest)
|
|
})
|
|
|
|
t.Run("one struct", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
}{}
|
|
err := query.LIMIT(1).Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, inventory1, dest.Inventory)
|
|
})
|
|
|
|
t.Run("one struct", func(t *testing.T) {
|
|
dest := struct {
|
|
*model.Inventory
|
|
}{}
|
|
err := query.LIMIT(1).Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, inventory1, *dest.Inventory)
|
|
})
|
|
|
|
t.Run("invalid dest", func(t *testing.T) {
|
|
dest := struct {
|
|
Inventory **model.Inventory
|
|
}{}
|
|
|
|
testutils.AssertQueryPanicErr(t, query, db, &dest, "jet: unsupported dest type: Inventory **model.Inventory")
|
|
})
|
|
|
|
t.Run("invalid dest 2", func(t *testing.T) {
|
|
dest := struct {
|
|
Inventory ***model.Inventory
|
|
}{}
|
|
|
|
testutils.AssertQueryPanicErr(t, query, db, &dest, "jet: unsupported dest type: Inventory ***model.Inventory")
|
|
})
|
|
|
|
t.Run("custom struct", func(t *testing.T) {
|
|
type Inventory struct {
|
|
InventoryID *int32 `sql:"primary_key"`
|
|
FilmID int16
|
|
StoreID *int16
|
|
}
|
|
|
|
dest := Inventory{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, *dest.InventoryID, int32(1))
|
|
require.Equal(t, dest.FilmID, int16(1))
|
|
require.Equal(t, *dest.StoreID, int16(1))
|
|
})
|
|
|
|
t.Run("type convert int32 to int", func(t *testing.T) {
|
|
type Inventory struct {
|
|
InventoryID int
|
|
FilmID string
|
|
}
|
|
|
|
dest := Inventory{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("type mismatch scanner type", func(t *testing.T) {
|
|
type Inventory struct {
|
|
InventoryID uuid.UUID
|
|
FilmID string
|
|
}
|
|
|
|
dest := Inventory{}
|
|
|
|
err := query.Query(db, &dest)
|
|
require.Error(t, err)
|
|
require.EqualError(t, err, "jet: can't assign int64('\\x01') to 'InventoryID uuid.UUID': Scan: unable to scan type int64 into UUID")
|
|
})
|
|
|
|
t.Run("type mismatch base type", func(t *testing.T) {
|
|
type Inventory struct {
|
|
InventoryID int32
|
|
FilmID bool
|
|
}
|
|
|
|
dest := []Inventory{}
|
|
|
|
err := query.OFFSET(10).Query(db, &dest)
|
|
require.Error(t, err)
|
|
require.EqualError(t, err, "jet: can't assign int64('\\x02') to 'FilmID bool': can't assign int64(2) to bool")
|
|
})
|
|
}
|
|
|
|
func TestScanToNestedStruct(t *testing.T) {
|
|
query := Inventory.
|
|
INNER_JOIN(Film, Inventory.FilmID.EQ(Film.FilmID)).
|
|
INNER_JOIN(Store, Inventory.StoreID.EQ(Store.StoreID)).
|
|
SELECT(Inventory.AllColumns, Film.AllColumns, Store.AllColumns).
|
|
WHERE(Inventory.InventoryID.EQ(Int(1)))
|
|
|
|
t.Run("embedded structs", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
model.Film
|
|
model.Store
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest.Film, film1)
|
|
testutils.AssertDeepEqual(t, dest.Store, store1)
|
|
})
|
|
|
|
t.Run("embedded pointer structs", func(t *testing.T) {
|
|
dest := struct {
|
|
*model.Inventory
|
|
*model.Film
|
|
*model.Store
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, *dest.Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, *dest.Film, film1)
|
|
testutils.AssertDeepEqual(t, *dest.Store, store1)
|
|
})
|
|
|
|
t.Run("embedded unused structs", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
model.Actor //unused
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest.Actor, model.Actor{})
|
|
})
|
|
|
|
t.Run("embedded unused pointer structs", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
*model.Actor //unused
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest.Actor, (*model.Actor)(nil))
|
|
})
|
|
|
|
t.Run("embedded unused pointer structs", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
Actor *model.Actor //unused
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest.Actor, (*model.Actor)(nil))
|
|
})
|
|
|
|
t.Run("embedded pointer to selected column", func(t *testing.T) {
|
|
query := Inventory.
|
|
INNER_JOIN(Film, Inventory.FilmID.EQ(Film.FilmID)).
|
|
INNER_JOIN(Store, Inventory.StoreID.EQ(Store.StoreID)).
|
|
SELECT(Inventory.AllColumns, Film.AllColumns, Store.AllColumns, String("").AS("actor.first_name")).
|
|
WHERE(Inventory.InventoryID.EQ(Int(1)))
|
|
|
|
dest := struct {
|
|
model.Inventory
|
|
Actor *model.Actor //unused
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
require.True(t, dest.Actor != nil)
|
|
})
|
|
|
|
t.Run("struct embedded unused pointer", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
Actor *struct {
|
|
model.Actor
|
|
} //unused
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest.Actor, (*struct{ model.Actor })(nil))
|
|
})
|
|
|
|
t.Run("multiple embedded unused pointer", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
Actor *struct {
|
|
model.Actor //unused
|
|
model.Language //unesed
|
|
}
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest.Actor, (*struct {
|
|
model.Actor
|
|
model.Language
|
|
})(nil))
|
|
})
|
|
|
|
t.Run("field not nil, embedded selected model", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
Actor *struct {
|
|
model.Actor //unselected
|
|
model.Film //selected
|
|
}
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
require.True(t, dest.Actor != nil)
|
|
testutils.AssertDeepEqual(t, dest.Actor.Actor, model.Actor{})
|
|
testutils.AssertDeepEqual(t, dest.Actor.Film, film1)
|
|
})
|
|
|
|
t.Run("field not nil, deeply nested selected model", func(t *testing.T) {
|
|
dest := struct {
|
|
model.Inventory
|
|
Actor *struct {
|
|
model.Actor //unselected
|
|
Film *struct {
|
|
*model.Film //selected
|
|
}
|
|
}
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
require.True(t, dest.Actor != nil)
|
|
require.True(t, dest.Actor.Film != nil)
|
|
testutils.AssertDeepEqual(t, dest.Actor.Film.Film, &film1)
|
|
})
|
|
|
|
t.Run("embedded structs", func(t *testing.T) {
|
|
query := Inventory.
|
|
INNER_JOIN(Film, Inventory.FilmID.EQ(Film.FilmID)).
|
|
INNER_JOIN(Store, Inventory.StoreID.EQ(Store.StoreID)).
|
|
INNER_JOIN(Language, Film.LanguageID.EQ(Language.LanguageID)).
|
|
SELECT(Inventory.AllColumns, Film.AllColumns, Store.AllColumns, Language.AllColumns).
|
|
WHERE(Inventory.InventoryID.EQ(Int(1)))
|
|
|
|
type Language3 model.Language
|
|
|
|
dest := struct {
|
|
model.Inventory
|
|
Film struct {
|
|
model.Film
|
|
|
|
Language model.Language
|
|
Language2 *model.Language `alias:"Language.*"`
|
|
Language3 *Language3 `alias:"language"`
|
|
Lang struct {
|
|
model.Language
|
|
}
|
|
Lang2 *struct {
|
|
model.Language
|
|
}
|
|
}
|
|
Store model.Store
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest.Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest.Film.Film, film1)
|
|
testutils.AssertDeepEqual(t, dest.Store, store1)
|
|
testutils.AssertDeepEqual(t, dest.Film.Language, language1)
|
|
testutils.AssertDeepEqual(t, dest.Film.Lang.Language, language1)
|
|
testutils.AssertDeepEqual(t, dest.Film.Lang2.Language, language1)
|
|
testutils.AssertDeepEqual(t, dest.Film.Language2, &language1)
|
|
testutils.AssertDeepEqual(t, model.Language(*dest.Film.Language3), language1)
|
|
})
|
|
}
|
|
|
|
func TestScanToSlice(t *testing.T) {
|
|
|
|
t.Run("slice of structs", func(t *testing.T) {
|
|
query := Inventory.
|
|
SELECT(Inventory.AllColumns).
|
|
ORDER_BY(Inventory.InventoryID).
|
|
LIMIT(10)
|
|
|
|
t.Run("slice od inventory", func(t *testing.T) {
|
|
dest := []model.Inventory{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 10)
|
|
testutils.AssertDeepEqual(t, dest[0], inventory1)
|
|
testutils.AssertDeepEqual(t, dest[1], inventory2)
|
|
})
|
|
|
|
t.Run("slice of ints", func(t *testing.T) {
|
|
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 type convertible", func(t *testing.T) {
|
|
var dest []int
|
|
|
|
err := query.Query(db, &dest)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("slice type mismatch", func(t *testing.T) {
|
|
var dest []bool
|
|
|
|
err := query.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(
|
|
Inventory.AllColumns,
|
|
Film.AllColumns,
|
|
Store.AllColumns,
|
|
).
|
|
ORDER_BY(Inventory.InventoryID).
|
|
LIMIT(10)
|
|
|
|
t.Run("struct with slice of ints", func(t *testing.T) {
|
|
var dest struct {
|
|
model.Film
|
|
IDs []int32 `alias:"inventory.inventory_id"`
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
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) {
|
|
var dest []struct {
|
|
model.Film
|
|
IDs []int32 `alias:"inventory.inventory_id"`
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 2)
|
|
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[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) {
|
|
var dest []struct {
|
|
model.Film
|
|
IDs []*int32 `alias:"inventory.inventory_id"`
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 2)
|
|
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)),
|
|
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].IDs, []*int32{ptr.Of(int32(9)), ptr.Of(int32(10))})
|
|
})
|
|
|
|
t.Run("complex struct 1", func(t *testing.T) {
|
|
dest := []struct {
|
|
model.Inventory
|
|
model.Film
|
|
model.Store
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 10)
|
|
testutils.AssertDeepEqual(t, dest[0].Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest[0].Film, film1)
|
|
testutils.AssertDeepEqual(t, dest[0].Store, store1)
|
|
|
|
testutils.AssertDeepEqual(t, dest[1].Inventory, inventory2)
|
|
})
|
|
|
|
t.Run("complex struct 2", func(t *testing.T) {
|
|
var dest []struct {
|
|
*model.Inventory
|
|
model.Film
|
|
*model.Store
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 10)
|
|
testutils.AssertDeepEqual(t, dest[0].Inventory, &inventory1)
|
|
testutils.AssertDeepEqual(t, dest[0].Film, film1)
|
|
testutils.AssertDeepEqual(t, dest[0].Store, &store1)
|
|
|
|
testutils.AssertDeepEqual(t, dest[1].Inventory, &inventory2)
|
|
})
|
|
|
|
t.Run("complex struct 3", func(t *testing.T) {
|
|
var dest []struct {
|
|
Inventory model.Inventory
|
|
Film *model.Film
|
|
Store struct {
|
|
*model.Store
|
|
}
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 10)
|
|
testutils.AssertDeepEqual(t, dest[0].Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest[0].Film, &film1)
|
|
testutils.AssertDeepEqual(t, dest[0].Store.Store, &store1)
|
|
|
|
testutils.AssertDeepEqual(t, dest[1].Inventory, inventory2)
|
|
})
|
|
|
|
t.Run("complex struct 4", func(t *testing.T) {
|
|
var dest []struct {
|
|
model.Film
|
|
|
|
Inventories []struct {
|
|
model.Inventory
|
|
model.Store
|
|
}
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 2)
|
|
testutils.AssertDeepEqual(t, dest[0].Film, film1)
|
|
testutils.AssertDeepEqual(t, len(dest[0].Inventories), 8)
|
|
testutils.AssertDeepEqual(t, dest[0].Inventories[0].Inventory, inventory1)
|
|
testutils.AssertDeepEqual(t, dest[0].Inventories[0].Store, store1)
|
|
})
|
|
|
|
t.Run("complex struct 5", func(t *testing.T) {
|
|
var dest []struct {
|
|
model.Film
|
|
|
|
Inventories []struct {
|
|
model.Inventory
|
|
|
|
Rentals *[]model.Rental
|
|
Rentals2 []model.Rental
|
|
}
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, len(dest), 2)
|
|
testutils.AssertDeepEqual(t, dest[0].Film, film1)
|
|
require.Equal(t, len(dest[0].Inventories), 8)
|
|
testutils.AssertDeepEqual(t, dest[0].Inventories[0].Inventory, inventory1)
|
|
require.True(t, dest[0].Inventories[0].Rentals == nil)
|
|
require.True(t, dest[0].Inventories[0].Rentals2 == nil)
|
|
})
|
|
})
|
|
|
|
t.Run("slice of complex structs 2", func(t *testing.T) {
|
|
query := 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).
|
|
ORDER_BY(Country.CountryID.ASC(), City.CityID.ASC(), Address.AddressID.ASC(), Customer.CustomerID.ASC()).
|
|
LIMIT(1000)
|
|
|
|
t.Run("dest1", func(t *testing.T) {
|
|
var dest []struct {
|
|
model.Country
|
|
|
|
Cities []struct {
|
|
model.City
|
|
|
|
Adresses []struct {
|
|
model.Address
|
|
|
|
Customer model.Customer
|
|
}
|
|
}
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 108)
|
|
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)
|
|
})
|
|
|
|
t.Run("dest1", func(t *testing.T) {
|
|
var dest []*struct {
|
|
*model.Country
|
|
|
|
Cities []*struct {
|
|
*model.City
|
|
|
|
Adresses *[]*struct {
|
|
*model.Address
|
|
|
|
Customer *model.Customer
|
|
}
|
|
}
|
|
}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(dest), 108)
|
|
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)
|
|
})
|
|
|
|
})
|
|
|
|
t.Run("dest1", func(t *testing.T) {
|
|
var dest []*struct {
|
|
*model.Country
|
|
|
|
Cities []**struct {
|
|
*model.City
|
|
}
|
|
}
|
|
|
|
testutils.AssertQueryPanicErr(t, oneInventoryQuery, db, &dest, "jet: unsupported slice element type at 'Cities []**struct { *model.City }'")
|
|
})
|
|
}
|
|
|
|
func TestStructScanErrNoRows(t *testing.T) {
|
|
query := SELECT(Customer.AllColumns).
|
|
FROM(Customer).
|
|
WHERE(Customer.CustomerID.EQ(Int(-1)))
|
|
|
|
customer := model.Customer{}
|
|
|
|
err := query.Query(db, &customer)
|
|
|
|
require.Error(t, err, qrm.ErrNoRows.Error())
|
|
}
|
|
|
|
func TestStructScanAllNull(t *testing.T) {
|
|
query := SELECT(NULL.AS("null1"), NULL.AS("null2"))
|
|
|
|
dest := struct {
|
|
Null1 *int
|
|
Null2 *int
|
|
}{}
|
|
|
|
err := query.Query(db, &dest)
|
|
|
|
require.NoError(t, err)
|
|
testutils.AssertDeepEqual(t, dest, struct {
|
|
Null1 *int
|
|
Null2 *int
|
|
}{})
|
|
}
|
|
|
|
func TestRowsScan(t *testing.T) {
|
|
|
|
stmt := SELECT(
|
|
Inventory.AllColumns,
|
|
).FROM(
|
|
Inventory,
|
|
).ORDER_BY(
|
|
Inventory.InventoryID.ASC(),
|
|
)
|
|
|
|
rows, err := stmt.Rows(context.Background(), db)
|
|
require.NoError(t, err)
|
|
|
|
for rows.Next() {
|
|
var inventory model.Inventory
|
|
err = rows.Scan(&inventory)
|
|
require.NoError(t, err)
|
|
|
|
require.NotEqual(t, inventory.InventoryID, int32(0))
|
|
require.NotEqual(t, inventory.FilmID, int16(0))
|
|
require.NotEqual(t, inventory.StoreID, int16(0))
|
|
require.NotEqual(t, inventory.LastUpdate, time.Time{})
|
|
|
|
if inventory.InventoryID == 2103 {
|
|
require.Equal(t, inventory.FilmID, int16(456))
|
|
require.Equal(t, inventory.StoreID, int16(2))
|
|
require.Equal(t, inventory.LastUpdate.Format(time.RFC3339), "2006-02-15T10:09:17Z")
|
|
}
|
|
}
|
|
|
|
err = rows.Close()
|
|
require.NoError(t, err)
|
|
err = rows.Err()
|
|
require.NoError(t, err)
|
|
|
|
requireLogged(t, stmt)
|
|
requireQueryLogged(t, stmt, 0)
|
|
}
|
|
|
|
func TestScanNullColumn(t *testing.T) {
|
|
stmt := SELECT(
|
|
Address.AllColumns,
|
|
).FROM(
|
|
Address,
|
|
).WHERE(
|
|
Address.Address2.IS_NULL(),
|
|
)
|
|
|
|
var dest []model.Address
|
|
|
|
err := stmt.Query(db, &dest)
|
|
require.NoError(t, err)
|
|
testutils.AssertJSON(t, dest, `
|
|
[
|
|
{
|
|
"AddressID": 1,
|
|
"Address": "47 MySakila Drive",
|
|
"Address2": null,
|
|
"District": "Alberta",
|
|
"CityID": 300,
|
|
"PostalCode": "",
|
|
"Phone": "",
|
|
"LastUpdate": "2006-02-15T09:45:30Z"
|
|
},
|
|
{
|
|
"AddressID": 2,
|
|
"Address": "28 MySQL Boulevard",
|
|
"Address2": null,
|
|
"District": "QLD",
|
|
"CityID": 576,
|
|
"PostalCode": "",
|
|
"Phone": "",
|
|
"LastUpdate": "2006-02-15T09:45:30Z"
|
|
},
|
|
{
|
|
"AddressID": 3,
|
|
"Address": "23 Workhaven Lane",
|
|
"Address2": null,
|
|
"District": "Alberta",
|
|
"CityID": 300,
|
|
"PostalCode": "",
|
|
"Phone": "14033335568",
|
|
"LastUpdate": "2006-02-15T09:45:30Z"
|
|
},
|
|
{
|
|
"AddressID": 4,
|
|
"Address": "1411 Lillydale Drive",
|
|
"Address2": null,
|
|
"District": "QLD",
|
|
"CityID": 576,
|
|
"PostalCode": "",
|
|
"Phone": "6172235589",
|
|
"LastUpdate": "2006-02-15T09:45:30Z"
|
|
}
|
|
]
|
|
`)
|
|
}
|
|
|
|
func TestRowsScanSetZeroValue(t *testing.T) {
|
|
stmt := SELECT(
|
|
Rental.AllColumns,
|
|
).FROM(
|
|
Rental,
|
|
).WHERE(
|
|
Rental.RentalID.IN(Int(16049), Int(15966)),
|
|
).ORDER_BY(
|
|
Rental.RentalID.DESC(),
|
|
)
|
|
|
|
rows, err := stmt.Rows(context.Background(), db)
|
|
require.NoError(t, err)
|
|
|
|
defer rows.Close()
|
|
|
|
// destination object is used as destination for all rows scan.
|
|
// this tests checks that ReturnedDate is set to nil with the second call
|
|
// check qrm.setZeroValue
|
|
var dest model.Rental
|
|
|
|
for rows.Next() {
|
|
err := rows.Scan(&dest)
|
|
require.NoError(t, err)
|
|
|
|
if dest.RentalID == 16049 {
|
|
testutils.AssertJSON(t, dest, `
|
|
{
|
|
"RentalID": 16049,
|
|
"RentalDate": "2005-08-23T22:50:12Z",
|
|
"InventoryID": 2666,
|
|
"CustomerID": 393,
|
|
"ReturnDate": "2005-08-30T01:01:12Z",
|
|
"StaffID": 2,
|
|
"LastUpdate": "2006-02-16T02:30:53Z"
|
|
}
|
|
`)
|
|
} else {
|
|
testutils.AssertJSON(t, dest, `
|
|
{
|
|
"RentalID": 15966,
|
|
"RentalDate": "2006-02-14T15:16:03Z",
|
|
"InventoryID": 4472,
|
|
"CustomerID": 374,
|
|
"ReturnDate": null,
|
|
"StaffID": 1,
|
|
"LastUpdate": "2006-02-16T02:30:53Z"
|
|
}
|
|
`)
|
|
}
|
|
}
|
|
|
|
err = rows.Close()
|
|
require.NoError(t, err)
|
|
err = rows.Err()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestScanNumericToFloat(t *testing.T) {
|
|
type Number struct {
|
|
Float32 float32
|
|
Float64 float64
|
|
}
|
|
|
|
numeric := CAST(Decimal("1234567890.111")).AS_NUMERIC()
|
|
|
|
stmt := SELECT(
|
|
numeric.AS("number.float32"),
|
|
numeric.AS("number.float64"),
|
|
)
|
|
|
|
var number Number
|
|
err := stmt.Query(db, &number)
|
|
require.NoError(t, err)
|
|
require.Equal(t, number.Float32, float32(1.234568e+09))
|
|
require.Equal(t, number.Float64, float64(1.234567890111e+09))
|
|
}
|
|
|
|
func TestScanNumericToIntegerError(t *testing.T) {
|
|
|
|
var dest struct {
|
|
Integer int32
|
|
}
|
|
|
|
err := SELECT(
|
|
CAST(Decimal("1234567890.111")).AS_NUMERIC().AS("integer"),
|
|
).Query(db, &dest)
|
|
|
|
require.Error(t, err)
|
|
|
|
if isPgxDriver() {
|
|
require.Contains(t, err.Error(), `jet: can't assign string("1234567890.111") to 'Integer int32': converting driver.Value type string ("1234567890.111") to a int64: invalid syntax`)
|
|
} else {
|
|
require.Contains(t, err.Error(), `jet: can't assign []uint8("1234567890.111") to 'Integer int32': converting driver.Value type []uint8 ("1234567890.111") to a int64: invalid syntax`)
|
|
}
|
|
|
|
}
|
|
|
|
func TestScanIntoCustomBaseTypes(t *testing.T) {
|
|
|
|
type MyUint8 uint8
|
|
type MyUint16 uint16
|
|
type MyUint32 uint32
|
|
type MyInt16 int16
|
|
type MyFloat32 float32
|
|
type MyFloat64 float64
|
|
type MyString string
|
|
type MyTime = time.Time
|
|
|
|
type film struct {
|
|
FilmID MyUint16 `sql:"primary_key"`
|
|
Title MyString
|
|
Description *MyString
|
|
ReleaseYear *MyInt16
|
|
LanguageID MyUint8
|
|
RentalDuration MyUint8
|
|
RentalRate MyFloat32
|
|
Length *MyUint32
|
|
ReplacementCost MyFloat64
|
|
Rating *model.MpaaRating
|
|
LastUpdate MyTime
|
|
SpecialFeatures *MyString
|
|
Fulltext MyString
|
|
}
|
|
|
|
stmt := SELECT(
|
|
Film.AllColumns,
|
|
).FROM(
|
|
Film,
|
|
).ORDER_BY(
|
|
Film.FilmID.ASC(),
|
|
).LIMIT(3)
|
|
|
|
var films []model.Film
|
|
|
|
err := stmt.Query(db, &films)
|
|
require.NoError(t, err)
|
|
|
|
var myFilms []film
|
|
|
|
err = stmt.Query(db, &myFilms)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, testutils.ToJSON(films), testutils.ToJSON(myFilms))
|
|
}
|
|
|
|
// QueryContext panic when the scanned value is nil and the destination is a slice of primitive
|
|
// https://github.com/go-jet/jet/issues/91
|
|
func TestScanToPrimitiveElementsSlice(t *testing.T) {
|
|
tx, err := db.Begin()
|
|
require.NoError(t, err)
|
|
defer tx.Rollback()
|
|
|
|
// add actor without associated film (so that destination Title array is NULL).
|
|
_, err = Actor.INSERT().
|
|
MODEL(
|
|
model.Actor{
|
|
ActorID: 201,
|
|
FirstName: "Brigitte",
|
|
LastName: "Bardot",
|
|
LastUpdate: time.Time{},
|
|
},
|
|
).Exec(tx)
|
|
require.NoError(t, err)
|
|
|
|
stmt := SELECT(
|
|
Actor.ActorID.AS("actor_id"),
|
|
Film.Title.AS("title"),
|
|
).FROM(
|
|
Actor.
|
|
LEFT_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)).
|
|
LEFT_JOIN(Film, Film.FilmID.EQ(FilmActor.FilmID)),
|
|
).WHERE(
|
|
Actor.ActorID.GT(Int(199)),
|
|
).ORDER_BY(Actor.ActorID.DESC())
|
|
|
|
var dest []struct {
|
|
ActorID int `sql:"primary_key"`
|
|
Title []string
|
|
}
|
|
|
|
err = stmt.Query(tx, &dest)
|
|
require.NoError(t, err)
|
|
require.Equal(t, dest[0].ActorID, 201)
|
|
require.Equal(t, dest[0].Title, []string(nil))
|
|
require.Equal(t, dest[1].ActorID, 200)
|
|
require.Len(t, dest[1].Title, 20)
|
|
}
|
|
|
|
// https://github.com/go-jet/jet/issues/127
|
|
func TestValuerTypeDebugSQL(t *testing.T) {
|
|
type customer struct {
|
|
CustomerID null.Int32 `sql:"primary_key"`
|
|
StoreID null.Int16
|
|
FirstName null.String
|
|
LastName string
|
|
Email null.String
|
|
AddressID int16
|
|
Activebool null.Bool
|
|
CreateDate null.Time
|
|
LastUpdate null.Time
|
|
Active null.Int8
|
|
}
|
|
|
|
stmt := Customer.INSERT().
|
|
MODEL(
|
|
customer{
|
|
CustomerID: null.Int32From(1234),
|
|
StoreID: null.Int16From(0),
|
|
FirstName: null.StringFrom("Joe"),
|
|
LastName: "",
|
|
Email: null.StringFromPtr(nil),
|
|
AddressID: 1,
|
|
Activebool: null.BoolFrom(true),
|
|
CreateDate: null.TimeFrom(time.Date(2020, 2, 2, 10, 0, 0, 0, time.UTC)),
|
|
LastUpdate: null.TimeFromPtr(nil),
|
|
Active: null.Int8From(1),
|
|
},
|
|
)
|
|
|
|
testutils.AssertDebugStatementSql(t, stmt, `
|
|
INSERT INTO dvds.customer
|
|
VALUES (1234, 0, 'Joe', '', NULL, 1, TRUE, '2020-02-02 10:00:00Z', NULL, 1);
|
|
`)
|
|
testutils.AssertExecAndRollback(t, stmt, db)
|
|
}
|
|
|
|
var address256 = model.Address{
|
|
AddressID: 256,
|
|
Address: "1497 Yuzhou Drive",
|
|
Address2: ptr.Of(""),
|
|
District: "England",
|
|
CityID: 312,
|
|
PostalCode: ptr.Of("3433"),
|
|
Phone: "246810237916",
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 09:45:30", 0),
|
|
}
|
|
|
|
var addres517 = model.Address{
|
|
AddressID: 517,
|
|
Address: "548 Uruapan Street",
|
|
Address2: ptr.Of(""),
|
|
District: "Ontario",
|
|
CityID: 312,
|
|
PostalCode: ptr.Of("35653"),
|
|
Phone: "879347453467",
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 09:45:30", 0),
|
|
}
|
|
|
|
var customer256 = model.Customer{
|
|
CustomerID: 252,
|
|
StoreID: 2,
|
|
FirstName: "Mattie",
|
|
LastName: "Hoffman",
|
|
Email: ptr.Of("mattie.hoffman@sakilacustomer.org"),
|
|
AddressID: 256,
|
|
Activebool: true,
|
|
CreateDate: *testutils.TimestampWithoutTimeZone("2006-02-14 00:00:00", 0),
|
|
LastUpdate: testutils.TimestampWithoutTimeZone("2013-05-26 14:49:45.738", 0),
|
|
Active: ptr.Of(int32(1)),
|
|
}
|
|
|
|
var customer512 = model.Customer{
|
|
CustomerID: 512,
|
|
StoreID: 1,
|
|
FirstName: "Cecil",
|
|
LastName: "Vines",
|
|
Email: ptr.Of("cecil.vines@sakilacustomer.org"),
|
|
AddressID: 517,
|
|
Activebool: true,
|
|
CreateDate: *testutils.TimestampWithoutTimeZone("2006-02-14 00:00:00", 0),
|
|
LastUpdate: testutils.TimestampWithoutTimeZone("2013-05-26 14:49:45.738", 0),
|
|
Active: ptr.Of(int32(1)),
|
|
}
|
|
|
|
var countryUk = model.Country{
|
|
CountryID: 102,
|
|
Country: "United Kingdom",
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 09:44:00", 0),
|
|
}
|
|
|
|
var cityLondon = model.City{
|
|
CityID: 312,
|
|
City: "London",
|
|
CountryID: 102,
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 09:45:25", 0),
|
|
}
|
|
|
|
var inventory1 = model.Inventory{
|
|
InventoryID: 1,
|
|
FilmID: 1,
|
|
StoreID: 1,
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 10:09:17", 0),
|
|
}
|
|
|
|
var inventory2 = model.Inventory{
|
|
InventoryID: 2,
|
|
FilmID: 1,
|
|
StoreID: 1,
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 10:09:17", 0),
|
|
}
|
|
|
|
var film1 = model.Film{
|
|
FilmID: 1,
|
|
Title: "Academy Dinosaur",
|
|
Description: ptr.Of("A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies"),
|
|
ReleaseYear: ptr.Of(int32(2006)),
|
|
LanguageID: 1,
|
|
RentalDuration: 6,
|
|
RentalRate: 0.99,
|
|
Length: ptr.Of(int16(86)),
|
|
ReplacementCost: 20.99,
|
|
Rating: &pgRating,
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
|
SpecialFeatures: ptr.Of("{\"Deleted Scenes\",\"Behind the Scenes\"}"),
|
|
Fulltext: "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
|
|
}
|
|
|
|
var film2 = model.Film{
|
|
FilmID: 2,
|
|
Title: "Ace Goldfinger",
|
|
Description: ptr.Of("A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China"),
|
|
ReleaseYear: ptr.Of(int32(2006)),
|
|
LanguageID: 1,
|
|
RentalDuration: 3,
|
|
RentalRate: 4.99,
|
|
Length: ptr.Of(int16(48)),
|
|
ReplacementCost: 12.99,
|
|
Rating: &gRating,
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2013-05-26 14:50:58.951", 3),
|
|
SpecialFeatures: ptr.Of(`{Trailers,"Deleted Scenes"}`),
|
|
Fulltext: `'ace':1 'administr':9 'ancient':19 'astound':4 'car':17 'china':20 'databas':8 'epistl':5 'explor':12 'find':15 'goldfing':2 'must':14`,
|
|
}
|
|
|
|
var store1 = model.Store{
|
|
StoreID: 1,
|
|
ManagerStaffID: 1,
|
|
AddressID: 1,
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 09:57:12", 0),
|
|
}
|
|
|
|
var pgRating = model.MpaaRating_Pg
|
|
var gRating = model.MpaaRating_G
|
|
|
|
var language1 = model.Language{
|
|
LanguageID: 1,
|
|
Name: "English ",
|
|
LastUpdate: *testutils.TimestampWithoutTimeZone("2006-02-15 10:02:19", 0),
|
|
}
|