Skip to content

Commit 3dbef65

Browse files
jackcseankhliao
authored andcommitted
database/sql: allow drivers to override Scan behavior
Implementing RowsColumnScanner allows the driver to completely control how values are scanned. Fixes golang#67546 Change-Id: Id8e7c3a973479c9665e4476fe2d29e1255aee687 GitHub-Last-Rev: ed0caca GitHub-Pull-Request: golang#67648 Reviewed-on: https://go-review.googlesource.com/c/go/+/588435 Reviewed-by: David Chase <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Reviewed-by: Sean Liao <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2b804ab commit 3dbef65

File tree

5 files changed

+124
-1
lines changed

5 files changed

+124
-1
lines changed

api/next/67546.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pkg database/sql/driver, type RowsColumnScanner interface { Close, Columns, Next, ScanColumn } #67546
2+
pkg database/sql/driver, type RowsColumnScanner interface, Close() error #67546
3+
pkg database/sql/driver, type RowsColumnScanner interface, Columns() []string #67546
4+
pkg database/sql/driver, type RowsColumnScanner interface, Next([]Value) error #67546
5+
pkg database/sql/driver, type RowsColumnScanner interface, ScanColumn(interface{}, int) error #67546
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A database driver may implement [RowsColumnScanner] to entirely override `Scan` behavior.

src/database/sql/driver/driver.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,18 @@ type RowsColumnTypePrecisionScale interface {
515515
ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool)
516516
}
517517

518+
// RowsColumnScanner may be implemented by [Rows]. It allows the driver to completely
519+
// take responsibility for how values are scanned and replace the normal [database/sql].
520+
// scanning path. This allows drivers to directly support types that do not implement
521+
// [database/sql.Scanner].
522+
type RowsColumnScanner interface {
523+
Rows
524+
525+
// ScanColumn copies the column in the current row into the value pointed at by
526+
// dest. It returns [ErrSkip] to fall back to the normal [database/sql] scanning path.
527+
ScanColumn(dest any, index int) error
528+
}
529+
518530
// Tx is a transaction.
519531
type Tx interface {
520532
Commit() error

src/database/sql/sql.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3396,7 +3396,16 @@ func (rs *Rows) scanLocked(dest ...any) error {
33963396
}
33973397

33983398
for i, sv := range rs.lastcols {
3399-
err := convertAssignRows(dest[i], sv, rs)
3399+
err := driver.ErrSkip
3400+
3401+
if rcs, ok := rs.rowsi.(driver.RowsColumnScanner); ok {
3402+
err = rcs.ScanColumn(dest[i], i)
3403+
}
3404+
3405+
if err == driver.ErrSkip {
3406+
err = convertAssignRows(dest[i], sv, rs)
3407+
}
3408+
34003409
if err != nil {
34013410
return fmt.Errorf(`sql: Scan error on column index %d, name %q: %w`, i, rs.rowsi.Columns()[i], err)
34023411
}

src/database/sql/sql_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4201,6 +4201,102 @@ func TestNamedValueCheckerSkip(t *testing.T) {
42014201
}
42024202
}
42034203

4204+
type rcsDriver struct {
4205+
fakeDriver
4206+
}
4207+
4208+
func (d *rcsDriver) Open(dsn string) (driver.Conn, error) {
4209+
c, err := d.fakeDriver.Open(dsn)
4210+
fc := c.(*fakeConn)
4211+
fc.db.allowAny = true
4212+
return &rcsConn{fc}, err
4213+
}
4214+
4215+
type rcsConn struct {
4216+
*fakeConn
4217+
}
4218+
4219+
func (c *rcsConn) PrepareContext(ctx context.Context, q string) (driver.Stmt, error) {
4220+
stmt, err := c.fakeConn.PrepareContext(ctx, q)
4221+
if err != nil {
4222+
return stmt, err
4223+
}
4224+
return &rcsStmt{stmt.(*fakeStmt)}, nil
4225+
}
4226+
4227+
type rcsStmt struct {
4228+
*fakeStmt
4229+
}
4230+
4231+
func (s *rcsStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
4232+
rows, err := s.fakeStmt.QueryContext(ctx, args)
4233+
if err != nil {
4234+
return rows, err
4235+
}
4236+
return &rcsRows{rows.(*rowsCursor)}, nil
4237+
}
4238+
4239+
type rcsRows struct {
4240+
*rowsCursor
4241+
}
4242+
4243+
func (r *rcsRows) ScanColumn(dest any, index int) error {
4244+
switch d := dest.(type) {
4245+
case *int64:
4246+
*d = 42
4247+
return nil
4248+
}
4249+
4250+
return driver.ErrSkip
4251+
}
4252+
4253+
func TestRowsColumnScanner(t *testing.T) {
4254+
Register("RowsColumnScanner", &rcsDriver{})
4255+
db, err := Open("RowsColumnScanner", "")
4256+
if err != nil {
4257+
t.Fatal(err)
4258+
}
4259+
defer db.Close()
4260+
4261+
ctx, cancel := context.WithCancel(context.Background())
4262+
defer cancel()
4263+
4264+
_, err = db.ExecContext(ctx, "CREATE|t|str=string,n=int64")
4265+
if err != nil {
4266+
t.Fatal("exec create", err)
4267+
}
4268+
4269+
_, err = db.ExecContext(ctx, "INSERT|t|str=?,n=?", "foo", int64(1))
4270+
if err != nil {
4271+
t.Fatal("exec insert", err)
4272+
}
4273+
var (
4274+
str string
4275+
i64 int64
4276+
i int
4277+
f64 float64
4278+
ui uint
4279+
)
4280+
err = db.QueryRowContext(ctx, "SELECT|t|str,n,n,n,n|").Scan(&str, &i64, &i, &f64, &ui)
4281+
if err != nil {
4282+
t.Fatal("select", err)
4283+
}
4284+
4285+
list := []struct{ got, want any }{
4286+
{str, "foo"},
4287+
{i64, int64(42)},
4288+
{i, int(1)},
4289+
{f64, float64(1)},
4290+
{ui, uint(1)},
4291+
}
4292+
4293+
for index, item := range list {
4294+
if !reflect.DeepEqual(item.got, item.want) {
4295+
t.Errorf("got %#v wanted %#v for index %d", item.got, item.want, index)
4296+
}
4297+
}
4298+
}
4299+
42044300
func TestOpenConnector(t *testing.T) {
42054301
Register("testctx", &fakeDriverCtx{})
42064302
db, err := Open("testctx", "people")

0 commit comments

Comments
 (0)