Files
db/exec/query_executor_internal_test.go
T

1514 lines
42 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package exec
import (
"context"
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"strings"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/suite"
)
// anyValueConverter 允许任意类型作为 driver.Value 透传,模拟 DuckDB 等返回 map/struct 的驱动
type anyValueConverter struct{}
func (anyValueConverter) ConvertValue(v interface{}) (driver.Value, error) { return v, nil }
var (
testAddr1 = "111 Test Addr"
testAddr2 = "211 Test Addr"
testName1 = "Test1"
testName2 = "Test2"
testPhone1 = "111-111-1111"
testPhone2 = "222-222-2222"
testAge1 int64 = 10
testAge2 int64 = 20
testByteSliceContent = "byte slice result"
otherAddr1 = "111 Test Addr Other"
otherAddr2 = "211 Test Addr Other"
otherName1 = "Test1 Other"
otherName2 = "Test2 Other"
)
type queryExecutorSuite struct {
suite.Suite
}
func (qes *queryExecutorSuite) TestWithError() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
ctx := context.Background()
db, _, err := sqlmock.New()
qes.NoError(err)
expectedErr := fmt.Errorf("crud exec error")
e := newQueryExecutor(db, expectedErr, `SELECT * FROM "items"`)
var items []StructWithTags
qes.EqualError(e.ScanStructs(&items), expectedErr.Error())
qes.EqualError(e.ScanStructsContext(ctx, &items), expectedErr.Error())
found, err := e.ScanStruct(&StructWithTags{})
qes.EqualError(err, expectedErr.Error())
qes.False(found)
found, err = e.ScanStructContext(ctx, &StructWithTags{})
qes.EqualError(err, expectedErr.Error())
qes.False(found)
var vals []string
qes.EqualError(e.ScanVals(&vals), expectedErr.Error())
qes.EqualError(e.ScanValsContext(ctx, &vals), expectedErr.Error())
var val string
found, err = e.ScanVal(&val)
qes.EqualError(err, expectedErr.Error())
qes.False(found)
found, err = e.ScanValContext(ctx, &val)
qes.EqualError(err, expectedErr.Error())
qes.False(found)
}
func (qes *queryExecutorSuite) TestToSQL() {
db, _, err := sqlmock.New()
qes.NoError(err)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
query, args, err := e.ToSQL()
qes.NoError(err)
qes.Equal(`SELECT * FROM "items"`, query)
qes.Empty(args)
}
func (qes *queryExecutorSuite) TestScanStructs_withTaggedFields() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).
AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithTags
qes.NoError(e.ScanStructs(&items))
qes.Equal([]StructWithTags{
{Address: testAddr1, Name: testName1},
{Address: testAddr2, Name: testName2},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructs_withUntaggedFields() {
type StructWithNoTags struct {
Address string
Name string
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).
AddRow(testAddr2, testName2))
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithNoTags
qes.NoError(e.ScanStructs(&items))
qes.Equal([]StructWithNoTags{
{Address: testAddr1, Name: testName1},
{Address: testAddr2, Name: testName2},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructs_withPointerFields() {
type StructWithPointerFields struct {
Str *string
Time *time.Time
Bool *bool
Int *int64
Float *float64
}
db, mock, err := sqlmock.New()
qes.NoError(err)
now := time.Now().UTC().Truncate(time.Second)
str1, str2 := "str1", "str2"
t := true
var i1, i2 int64 = 1, 2
f1, f2 := 1.1, 2.1
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"str", "time", "bool", "int", "float"}).
AddRow(str1, now, true, i1, f1).
AddRow(str2, now, true, i2, f2).
AddRow(nil, nil, nil, nil, nil),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithPointerFields
qes.NoError(e.ScanStructs(&items))
qes.Equal([]StructWithPointerFields{
{Str: &str1, Time: &now, Bool: &t, Int: &i1, Float: &f1},
{Str: &str2, Time: &now, Bool: &t, Int: &i2, Float: &f2},
{},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructs_withPrivateFields() {
type StructWithPrivateTags struct {
private string //nolint:structcheck,unused // need for test
Address string `db:"address"`
Name string `db:"name"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).
AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithPrivateTags
qes.NoError(e.ScanStructs(&items))
qes.Equal([]StructWithPrivateTags{
{Address: testAddr1, Name: testName1},
{Address: testAddr2, Name: testName2},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructs_pointers() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).
AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []*StructWithTags
qes.NoError(e.ScanStructs(&items))
qes.Equal([]*StructWithTags{
{Address: testAddr1, Name: testName1},
{Address: testAddr2, Name: testName2},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructs_withIgnoredEmbeddedStruct() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedIgnoredStruct struct {
StructWithTags `db:"-"`
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"phone_number", "age"}).
AddRow(testPhone1, testAge1).AddRow(testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []ComposedIgnoredStruct
qes.NoError(e.ScanStructs(&composed))
qes.Equal([]ComposedIgnoredStruct{
{StructWithTags: StructWithTags{}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: StructWithTags{}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructs_withEmbeddedStruct() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedStruct struct {
StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1).
AddRow(testAddr2, testName2, testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []ComposedStruct
qes.NoError(e.ScanStructs(&composed))
qes.Equal([]ComposedStruct{
{StructWithTags: StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructs_pointersWithEmbeddedStruct() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedStruct struct {
StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1).
AddRow(testAddr2, testName2, testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []*ComposedStruct
qes.NoError(e.ScanStructs(&composed))
qes.Equal([]*ComposedStruct{
{StructWithTags: StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructs_pointersWithEmbeddedStructDuplicateFields() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedStructWithDuplicateFields struct {
StructWithTags
Address string `db:"other_address"`
Name string `db:"other_name"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "other_address", "other_name"}).
AddRow(testAddr1, testName1, otherAddr1, otherName1).
AddRow(testAddr2, testName2, otherAddr2, otherName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []*ComposedStructWithDuplicateFields
qes.NoError(e.ScanStructs(&composed))
qes.Equal([]*ComposedStructWithDuplicateFields{
{
StructWithTags: StructWithTags{Address: testAddr1, Name: testName1},
Address: otherAddr1,
Name: otherName1,
},
{
StructWithTags: StructWithTags{Address: testAddr2, Name: testName2},
Address: otherAddr2,
Name: otherName2,
},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructs_pointersWithEmbeddedPointerDuplicateFields() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedWithWithPointerWithDuplicateFields struct {
*StructWithTags
Address string `db:"other_address"`
Name string `db:"other_name"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "other_address", "other_name"}).
AddRow(testAddr1, testName1, otherAddr1, otherName1).
AddRow(testAddr2, testName2, otherAddr2, otherName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []*ComposedWithWithPointerWithDuplicateFields
qes.NoError(e.ScanStructs(&composed))
qes.Equal([]*ComposedWithWithPointerWithDuplicateFields{
{
StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1},
Address: otherAddr1,
Name: otherName1,
},
{
StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2},
Address: otherAddr2,
Name: otherName2,
},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructs_withIgnoredEmbeddedPointerStruct() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedIgnoredPointerStruct struct {
*StructWithTags `db:"-"`
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"phone_number", "age"}).
AddRow(testPhone1, testAge1).
AddRow(testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []ComposedIgnoredPointerStruct
qes.NoError(e.ScanStructs(&composed))
qes.Equal([]ComposedIgnoredPointerStruct{
{PhoneNumber: testPhone1, Age: testAge1},
{PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructs_withEmbeddedStructPointer() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedWithPointerStruct struct {
*StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1).
AddRow(testAddr2, testName2, testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []ComposedWithPointerStruct
qes.NoError(e.ScanStructs(&composed))
qes.Equal([]ComposedWithPointerStruct{
{StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructs_pointersWithEmbeddedStructPointer() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedWithPointerStruct struct {
*StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1).
AddRow(testAddr2, testName2, testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []*ComposedWithPointerStruct
qes.NoError(e.ScanStructs(&composed))
qes.Equal([]*ComposedWithPointerStruct{
{StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructs_badValue() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
tests := []struct {
name string
items interface{}
}{
{
name: "non-pointer items",
items: []StructWithTags{},
},
{
name: "non-slice items",
items: &StructWithTags{},
},
}
for i := range tests {
test := tests[i]
qes.Run(test.name, func() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
qes.Equal(errUnsupportedScanStructsType, e.ScanStructs(test.items))
})
}
}
func (qes *queryExecutorSuite) TestScanStructs_queryError() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WillReturnError(fmt.Errorf("queryExecutor error"))
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithTags
qes.EqualError(e.ScanStructs(&items), "queryExecutor error")
}
func (qes *queryExecutorSuite) TestScanStructsContext_withTaggedFields() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).
AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithTags
qes.NoError(e.ScanStructsContext(ctx, &items))
qes.Equal([]StructWithTags{
{Address: testAddr1, Name: testName1},
{Address: testAddr2, Name: testName2},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructsContext_withUntaggedFields() {
type StructWithNoTags struct {
Address string
Name string
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).
AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithNoTags
qes.NoError(e.ScanStructsContext(ctx, &items))
qes.Equal([]StructWithNoTags{
{Address: testAddr1, Name: testName1},
{Address: testAddr2, Name: testName2},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructsContext_withPointerFields() {
type StructWithPointerFields struct {
Address *string
Name *string
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).
AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithPointerFields
qes.NoError(e.ScanStructsContext(ctx, &items))
qes.Equal([]StructWithPointerFields{
{Address: &testAddr1, Name: &testName1},
{Address: &testAddr2, Name: &testName2},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructsContext_pointers() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).
AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []*StructWithTags
qes.NoError(e.ScanStructsContext(ctx, &items))
qes.Equal([]*StructWithTags{
{Address: testAddr1, Name: testName1},
{Address: testAddr2, Name: testName2},
}, items)
}
func (qes *queryExecutorSuite) TestScanStructsContext_withEmbeddedStruct() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedStruct struct {
StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1).
AddRow(testAddr2, testName2, testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []ComposedStruct
qes.NoError(e.ScanStructsContext(ctx, &composed))
qes.Equal([]ComposedStruct{
{StructWithTags: StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructsContext_withIgnoredEmbeddedStruct() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedIgnoredStruct struct {
StructWithTags `db:"-"`
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"phone_number", "age"}).
AddRow(testPhone1, testAge1).
AddRow(testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []ComposedIgnoredStruct
qes.NoError(e.ScanStructsContext(ctx, &composed))
qes.Equal([]ComposedIgnoredStruct{
{StructWithTags: StructWithTags{}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: StructWithTags{}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructsContext_pointersWithEmbeddedStruct() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedStruct struct {
StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1).
AddRow(testAddr2, testName2, testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []*ComposedStruct
qes.NoError(e.ScanStructsContext(ctx, &composed))
qes.Equal([]*ComposedStruct{
{StructWithTags: StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructsContext_withEmbeddedStructPointer() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedWithPointerStruct struct {
*StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1).
AddRow(testAddr2, testName2, testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []ComposedWithPointerStruct
qes.NoError(e.ScanStructsContext(ctx, &composed))
qes.Equal([]ComposedWithPointerStruct{
{StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructsContext_pointersWithEmbeddedStructPointer() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedWithPointerStruct struct {
*StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1).
AddRow(testAddr2, testName2, testPhone2, testAge2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var composed []*ComposedWithPointerStruct
qes.NoError(e.ScanStructsContext(ctx, &composed))
qes.Equal([]*ComposedWithPointerStruct{
{StructWithTags: &StructWithTags{Address: testAddr1, Name: testName1}, PhoneNumber: testPhone1, Age: testAge1},
{StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2}, PhoneNumber: testPhone2, Age: testAge2},
}, composed)
}
func (qes *queryExecutorSuite) TestScanStructsContext_badValue() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
tests := []struct {
name string
items interface{}
}{
{
name: "non-pointer items",
items: []StructWithTags{},
},
{
name: "non-slice items",
items: &StructWithTags{},
},
}
for i := range tests {
test := tests[i]
qes.Run(test.name, func() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1).AddRow(testAddr2, testName2),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
qes.Equal(errUnsupportedScanStructsType, e.ScanStructsContext(context.Background(), test.items))
})
}
}
func (qes *queryExecutorSuite) TestScanStructsContext_queryError() {
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
ctx := context.Background()
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WillReturnError(fmt.Errorf("queryExecutor error"))
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []StructWithTags
qes.EqualError(e.ScanStructsContext(ctx, &items), "queryExecutor error")
}
func (qes *queryExecutorSuite) TestScanStruct() {
type StructWithNoTags struct {
Address string
Name string
}
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedStruct struct {
StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
type ComposedWithPointerStruct struct {
*StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WillReturnError(fmt.Errorf("queryExecutor error"))
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(nil, nil),
)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}))
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).
AddRow(testAddr1, testName1),
)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1),
)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name", "phone_number", "age"}).
AddRow(testAddr1, testName1, testPhone1, testAge1),
)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"address", "name"}).AddRow(testAddr1, testName1))
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var slicePtr []StructWithTags
var item StructWithTags
found, err := e.ScanStruct(item)
qes.Equal(errUnsupportedScanStructType, err)
qes.False(found)
found, err = e.ScanStruct(&slicePtr)
qes.Equal(errUnsupportedScanStructType, err)
qes.False(found)
found, err = e.ScanStruct(&item)
qes.EqualError(err, "queryExecutor error")
qes.False(found)
// NULL 值扫描进 string 字段:通过 **string 中间层正常处理,结果为空字符串
found, err = e.ScanStruct(&item)
qes.NoError(err)
qes.True(found)
qes.Equal(StructWithTags{Address: "", Name: ""}, item)
found, err = e.ScanStruct(&item)
qes.NoError(err)
qes.False(found)
found, err = e.ScanStruct(&item)
qes.NoError(err)
qes.True(found)
qes.Equal(StructWithTags{
Address: testAddr1,
Name: testName1,
}, item)
var composed ComposedStruct
found, err = e.ScanStruct(&composed)
qes.NoError(err)
qes.True(found)
qes.Equal(ComposedStruct{
StructWithTags: StructWithTags{Address: testAddr1, Name: testName1},
PhoneNumber: testPhone1,
Age: testAge1,
}, composed)
var embeddedPtr ComposedWithPointerStruct
found, err = e.ScanStruct(&embeddedPtr)
qes.NoError(err)
qes.True(found)
qes.Equal(ComposedWithPointerStruct{
StructWithTags: &StructWithTags{
Address: testAddr1,
Name: testName1,
},
PhoneNumber: testPhone1,
Age: testAge1,
}, embeddedPtr)
var noTag StructWithNoTags
found, err = e.ScanStruct(&noTag)
qes.NoError(err)
qes.True(found)
qes.Equal(StructWithNoTags{
Address: testAddr1,
Name: testName1,
}, noTag)
}
func (qes *queryExecutorSuite) TestScanStruct_taggedStructs() {
type StructWithNoTags struct {
Address string
Name string
}
type StructWithTags struct {
Address string `db:"address"`
Name string `db:"name"`
}
type ComposedStruct struct {
StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
type ComposedWithPointerStruct struct {
*StructWithTags
PhoneNumber string `db:"phone_number"`
Age int64 `db:"age"`
}
type StructWithTaggedStructs struct {
NoTags StructWithNoTags `db:"notags"`
Tags StructWithTags `db:"tags"`
Composed ComposedStruct `db:"composedstruct"`
ComposedPointer ComposedWithPointerStruct `db:"composedptrstruct"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
cols := []string{
"notags.address", "notags.name",
"tags.address", "tags.name",
"composedstruct.address", "composedstruct.name", "composedstruct.phone_number", "composedstruct.age",
"composedptrstruct.address", "composedptrstruct.name", "composedptrstruct.phone_number", "composedptrstruct.age",
}
q := `SELECT` + strings.Join(cols, ", ") + ` FROM "items"`
mock.ExpectQuery(q).
WithArgs().
WillReturnRows(sqlmock.NewRows(cols).AddRow(
testAddr1, testName1,
testAddr2, testName2,
testAddr1, testName1, testPhone1, testAge1,
testAddr2, testName2, testPhone2, testAge2,
))
e := newQueryExecutor(db, nil, q)
var item StructWithTaggedStructs
found, err := e.ScanStruct(&item)
qes.NoError(err)
qes.True(found)
qes.Equal(StructWithTaggedStructs{
NoTags: StructWithNoTags{Address: testAddr1, Name: testName1},
Tags: StructWithTags{Address: testAddr2, Name: testName2},
Composed: ComposedStruct{
StructWithTags: StructWithTags{Address: testAddr1, Name: testName1},
PhoneNumber: testPhone1,
Age: testAge1,
},
ComposedPointer: ComposedWithPointerStruct{
StructWithTags: &StructWithTags{Address: testAddr2, Name: testName2},
PhoneNumber: testPhone2,
Age: testAge2,
},
}, item)
}
func (qes *queryExecutorSuite) TestScanVals() {
db, mock, err := sqlmock.New()
qes.NoError(err)
var id1, id2 int64 = 1, 2
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WillReturnError(fmt.Errorf("queryExecutor error"))
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1).RowError(0, fmt.Errorf("row error")))
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1).AddRow("a"))
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1).AddRow(id2))
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1).AddRow(id2))
e := newQueryExecutor(db, nil, `SELECT "id" FROM "items"`)
var ids []int64
qes.EqualError(e.ScanVals(&ids), "queryExecutor error")
qes.EqualError(e.ScanVals(&ids), "row error")
qes.Error(e.ScanVals(&ids))
ids = ids[0:0]
qes.NoError(e.ScanVals(&ids))
qes.Equal(ids, []int64{id1, id2})
var pointers []*int64
qes.NoError(e.ScanVals(&pointers))
qes.Len(pointers, 2)
qes.Equal(&id1, pointers[0])
qes.Equal(&id2, pointers[1])
}
func (qes *queryExecutorSuite) TestScanValsError() {
var id int64
tests := []struct {
name string
items interface{}
}{
{
name: "non-pointer items",
items: []int64{},
},
{
name: "non-slice items",
items: &id,
},
}
for i := range tests {
test := tests[i]
qes.Run(test.name, func() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1).AddRow(2))
e := newQueryExecutor(db, nil, `SELECT "id" FROM "items"`)
qes.Equal(errUnsupportedScanValsType, e.ScanVals(test.items))
})
}
}
func (qes *queryExecutorSuite) TestScanVal() {
db, mock, err := sqlmock.New()
qes.NoError(err)
id1 := int64(1)
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WillReturnError(fmt.Errorf("queryExecutor error"))
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"id"}).RowError(0, fmt.Errorf("row error")).AddRow(id1))
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("c"))
mock.ExpectQuery(`SELECT "id" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(id1))
e := newQueryExecutor(db, nil, `SELECT "id" FROM "items"`)
var id int64
var ids []int64
found, err := e.ScanVal(id)
qes.Equal(errScanValPointer, err)
qes.False(found)
found, err = e.ScanVal(&ids)
qes.Equal(errScanValNonSlice, err)
qes.False(found)
found, err = e.ScanVal(&id)
qes.EqualError(err, "queryExecutor error")
qes.False(found)
found, err = e.ScanVal(&id)
qes.EqualError(err, "row error")
qes.False(found)
found, err = e.ScanVal(&id)
qes.Error(err)
qes.False(found)
var ptrID *int64
found, err = e.ScanVal(&ptrID)
qes.NoError(err)
qes.True(found)
qes.Equal(&id1, ptrID)
}
func (qes *queryExecutorSuite) TestScanVal_withByteSlice() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "name" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow(testByteSliceContent))
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
var bytes []byte
found, err := e.ScanVal(bytes)
qes.Equal(errScanValPointer, err)
qes.False(found)
found, err = e.ScanVal(&bytes)
qes.NoError(err)
qes.True(found)
qes.Equal([]byte(testByteSliceContent), bytes)
}
func (qes *queryExecutorSuite) TestScanVal_withRawBytes() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "name" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow(testByteSliceContent))
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
var bytes sql.RawBytes
found, err := e.ScanVal(bytes)
qes.Equal(errScanValPointer, err)
qes.False(found)
found, err = e.ScanVal(&bytes)
qes.NoError(err)
qes.True(found)
qes.Equal(sql.RawBytes(testByteSliceContent), bytes)
}
type JSONBoolArray []bool
func (b *JSONBoolArray) Scan(src interface{}) error {
return json.Unmarshal(src.([]byte), b)
}
func (qes *queryExecutorSuite) TestScanVal_withValuerSlice() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "bools" FROM "items"`).
WithArgs().
WillReturnRows(sqlmock.NewRows([]string{"bools"}).FromCSVString(`"[true, false, true]"`))
e := newQueryExecutor(db, nil, `SELECT "bools" FROM "items"`)
var bools JSONBoolArray
found, err := e.ScanVal(bools)
qes.Equal(errScanValPointer, err)
qes.False(found)
found, err = e.ScanVal(&bools)
qes.NoError(err)
qes.True(found)
qes.Equal(JSONBoolArray{true, false, true}, bools)
}
func (qes *queryExecutorSuite) TestScanVal_withByteSlice_notFound() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "name" FROM "items"`).
WillReturnRows(sqlmock.NewRows([]string{"name"}))
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
var b []byte
found, err := e.ScanVal(&b)
qes.NoError(err)
qes.False(found)
qes.Nil(b)
}
func (qes *queryExecutorSuite) TestScanVal_withByteSlice_queryError() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "name" FROM "items"`).
WillReturnError(fmt.Errorf("query error"))
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
var b []byte
found, err := e.ScanVal(&b)
qes.EqualError(err, "query error")
qes.False(found)
}
func (qes *queryExecutorSuite) TestScanVal_withByteSlice_complexJSON() {
// 模拟 DuckDB 等驱动将 JSON 列直接以 map 形式返回
// 必须使用 mock.NewRows 而非 sqlmock.NewRows,才会使用自定义 converter
db, mock, err := sqlmock.New(sqlmock.ValueConverterOption(anyValueConverter{}))
qes.NoError(err)
mock.ExpectQuery(`SELECT "output" FROM "items"`).
WillReturnRows(mock.NewRows([]string{"output"}).
AddRow(map[string]interface{}{"key": "value", "num": float64(42)}))
e := newQueryExecutor(db, nil, `SELECT "output" FROM "items"`)
var b []byte
found, err := e.ScanVal(&b)
qes.NoError(err)
qes.True(found)
var result map[string]interface{}
qes.NoError(json.Unmarshal(b, &result))
qes.Equal("value", result["key"])
qes.Equal(float64(42), result["num"])
}
func (qes *queryExecutorSuite) TestScanVal_withRawBytes_notFound() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "name" FROM "items"`).
WillReturnRows(sqlmock.NewRows([]string{"name"}))
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
var rb sql.RawBytes
found, err := e.ScanVal(&rb)
qes.NoError(err)
qes.False(found)
qes.Nil(rb)
}
func (qes *queryExecutorSuite) TestScanVal_withRawBytes_queryError() {
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "name" FROM "items"`).
WillReturnError(fmt.Errorf("query error"))
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
var rb sql.RawBytes
found, err := e.ScanVal(&rb)
qes.EqualError(err, "query error")
qes.False(found)
}
func (qes *queryExecutorSuite) TestScanVal_withRawBytes_binaryDriver() {
// 驱动直接返回 []byte(如 BLOB 列),结果需独立拷贝
db, mock, err := sqlmock.New()
qes.NoError(err)
content := []byte("binary \x00 content")
mock.ExpectQuery(`SELECT "data" FROM "items"`).
WillReturnRows(sqlmock.NewRows([]string{"data"}).AddRow(content))
e := newQueryExecutor(db, nil, `SELECT "data" FROM "items"`)
var rb sql.RawBytes
found, err := e.ScanVal(&rb)
qes.NoError(err)
qes.True(found)
qes.Equal(sql.RawBytes(content), rb)
}
func (qes *queryExecutorSuite) TestScanVal_withStruct() {
// 模拟 DuckDB 将 JSON/STRUCT 列以 map[string]interface{} 返回
type DocItem struct {
Title string `json:"title"`
Score int `json:"score"`
}
db, mock, err := sqlmock.New(sqlmock.ValueConverterOption(anyValueConverter{}))
qes.NoError(err)
mock.ExpectQuery(`SELECT "doc" FROM "items"`).
WillReturnRows(mock.NewRows([]string{"doc"}).
AddRow(map[string]interface{}{"title": "hello", "score": float64(99)}))
e := newQueryExecutor(db, nil, `SELECT "doc" FROM "items"`)
var doc DocItem
found, err := e.ScanVal(&doc)
qes.NoError(err)
qes.True(found)
qes.Equal(DocItem{Title: "hello", Score: 99}, doc)
}
func (qes *queryExecutorSuite) TestScanVal_withStruct_null() {
// 驱动返回 NULL,结构体保持零值
type DocItem struct {
Title string `json:"title"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "doc" FROM "items"`).
WillReturnRows(sqlmock.NewRows([]string{"doc"}).AddRow(nil))
e := newQueryExecutor(db, nil, `SELECT "doc" FROM "items"`)
var doc DocItem
found, err := e.ScanVal(&doc)
qes.NoError(err)
qes.True(found)
qes.Equal(DocItem{}, doc)
}
func (qes *queryExecutorSuite) TestScanVal_withStruct_notFound() {
type DocItem struct {
Title string `json:"title"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "doc" FROM "items"`).
WillReturnRows(sqlmock.NewRows([]string{"doc"}))
e := newQueryExecutor(db, nil, `SELECT "doc" FROM "items"`)
var doc DocItem
found, err := e.ScanVal(&doc)
qes.NoError(err)
qes.False(found)
}
func (qes *queryExecutorSuite) TestScanVal_withStruct_queryError() {
type DocItem struct {
Title string `json:"title"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "doc" FROM "items"`).
WillReturnError(fmt.Errorf("query error"))
e := newQueryExecutor(db, nil, `SELECT "doc" FROM "items"`)
var doc DocItem
found, err := e.ScanVal(&doc)
qes.EqualError(err, "query error")
qes.False(found)
}
func (qes *queryExecutorSuite) TestScanVal_withStruct_sqlScanner() {
// 实现了 sql.Scanner 的结构体走原有直接扫描路径
db, mock, err := sqlmock.New()
qes.NoError(err)
mock.ExpectQuery(`SELECT "name" FROM "items"`).
WillReturnRows(sqlmock.NewRows([]string{"name"}).AddRow("hello"))
e := newQueryExecutor(db, nil, `SELECT "name" FROM "items"`)
var ns sql.NullString
found, err := e.ScanVal(&ns)
qes.NoError(err)
qes.True(found)
qes.Equal(sql.NullString{String: "hello", Valid: true}, ns)
}
// TestScanStructs_timeField_fromTime 验证 time.Time 和 *time.Time 字段从 time.Time 驱动值扫描。
// 注意:经过 toJSONRawMessage 格式化(精度到秒)再解析,纳秒和时区会丢失,结果为 UTC 秒精度。
func (qes *queryExecutorSuite) TestScanStructs_timeField_fromTime() {
type Row struct {
T time.Time `db:"t"`
PT *time.Time `db:"pt"`
}
db, mock, err := sqlmock.New()
qes.NoError(err)
now := time.Now().UTC().Truncate(time.Second)
mock.ExpectQuery(`SELECT \* FROM "items"`).
WillReturnRows(sqlmock.NewRows([]string{"t", "pt"}).
AddRow(now, now).
AddRow(now, nil),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []Row
qes.NoError(e.ScanStructs(&items))
qes.Len(items, 2)
qes.True(items[0].T.Equal(now))
qes.NotNil(items[0].PT)
qes.True(items[0].PT.Equal(now))
qes.Nil(items[1].PT)
}
// TestScanStructs_timeField_fromBytes 验证 time.Time 和 *time.Time 字段从 []byte 字符串扫描,
// 模拟 MySQL 驱动不配置 parseTime=true 时返回 []byte 的场景(原 bugscan error on *time.Time)。
func (qes *queryExecutorSuite) TestScanStructs_timeField_fromBytes() {
type Row struct {
CreatedAt time.Time `db:"created_at"`
RetryAt *time.Time `db:"retry_at"`
}
db, mock, err := sqlmock.New(sqlmock.ValueConverterOption(anyValueConverter{}))
qes.NoError(err)
datetimeBytes := []byte("2024-03-15 10:30:45")
mock.ExpectQuery(`SELECT \* FROM "items"`).
WillReturnRows(mock.NewRows([]string{"created_at", "retry_at"}).
AddRow(datetimeBytes, datetimeBytes).
AddRow(datetimeBytes, nil),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []Row
qes.NoError(e.ScanStructs(&items))
qes.Len(items, 2)
want := time.Date(2024, 3, 15, 10, 30, 45, 0, time.UTC)
qes.True(items[0].CreatedAt.Equal(want))
qes.NotNil(items[0].RetryAt)
qes.True(items[0].RetryAt.Equal(want))
qes.Nil(items[1].RetryAt)
}
// TestScanStructs_timeField_fromDateOnlyBytes 验证仅含日期的 []byte(无时分秒)也能正确解析。
func (qes *queryExecutorSuite) TestScanStructs_timeField_fromDateOnlyBytes() {
type Row struct {
Birthday time.Time `db:"birthday"`
ExpiredAt *time.Time `db:"expired_at"`
}
db, mock, err := sqlmock.New(sqlmock.ValueConverterOption(anyValueConverter{}))
qes.NoError(err)
dateBytes := []byte("2024-03-15")
mock.ExpectQuery(`SELECT \* FROM "items"`).
WillReturnRows(mock.NewRows([]string{"birthday", "expired_at"}).
AddRow(dateBytes, dateBytes),
)
e := newQueryExecutor(db, nil, `SELECT * FROM "items"`)
var items []Row
qes.NoError(e.ScanStructs(&items))
qes.Len(items, 1)
want := time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC)
qes.True(items[0].Birthday.Equal(want))
qes.NotNil(items[0].ExpiredAt)
qes.True(items[0].ExpiredAt.Equal(want))
}
func TestQueryExecutorSuite(t *testing.T) {
suite.Run(t, new(queryExecutorSuite))
}