Commit 357f2cb1 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

exp/sql{,/driver}: new database packages

R=gustavo, rsc, borman, dave, kevlar, nigeltao, dvyukov, kardianos, fw, r, r, david.crawshaw
CC=golang-dev
https://golang.org/cl/4973055
parent 51b09190
......@@ -82,6 +82,8 @@ DIRS=\
exp/gui\
exp/gui/x11\
exp/norm\
exp/sql\
exp/sql/driver\
exp/template/html\
expvar\
flag\
......@@ -202,6 +204,7 @@ NOTEST+=\
crypto/x509/pkix\
exp/gui\
exp/gui/x11\
exp/sql/driver\
go/ast\
go/doc\
go/token\
......
# Copyright 2011 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include ../../../Make.inc
TARG=exp/sql
GOFILES=\
convert.go\
sql.go\
include ../../../Make.pkg
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Type conversions for Scan.
package sql
import (
"fmt"
"os"
"reflect"
"strconv"
)
// convertAssign copies to dest the value in src, converting it if possible.
// An error is returned if the copy would result in loss of information.
// dest should be a pointer type.
func convertAssign(dest, src interface{}) os.Error {
// Common cases, without reflect. Fall through.
switch s := src.(type) {
case string:
switch d := dest.(type) {
case *string:
*d = s
return nil
}
case []byte:
switch d := dest.(type) {
case *string:
*d = string(s)
return nil
case *[]byte:
*d = s
return nil
}
}
sv := reflect.ValueOf(src)
switch d := dest.(type) {
case *string:
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
*d = fmt.Sprintf("%v", src)
return nil
}
}
if scanner, ok := dest.(ScannerInto); ok {
return scanner.ScanInto(src)
}
dpv := reflect.ValueOf(dest)
if dpv.Kind() != reflect.Ptr {
return os.NewError("destination not a pointer")
}
dv := reflect.Indirect(dpv)
if dv.Kind() == sv.Kind() {
dv.Set(sv)
return nil
}
switch dv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if s, ok := asString(src); ok {
i64, err := strconv.Atoi64(s)
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
if dv.OverflowInt(i64) {
return fmt.Errorf("string %q overflows %s", s, dv.Kind())
}
dv.SetInt(i64)
return nil
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if s, ok := asString(src); ok {
u64, err := strconv.Atoui64(s)
if err != nil {
return fmt.Errorf("converting string %q to a %s: %v", s, dv.Kind(), err)
}
if dv.OverflowUint(u64) {
return fmt.Errorf("string %q overflows %s", s, dv.Kind())
}
dv.SetUint(u64)
return nil
}
}
return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest)
}
func asString(src interface{}) (s string, ok bool) {
switch v := src.(type) {
case string:
return v, true
case []byte:
return string(v), true
}
return "", false
}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sql
import (
"fmt"
"reflect"
"testing"
)
type conversionTest struct {
s, d interface{} // source and destination
// following are used if they're non-zero
wantint int64
wantuint uint64
wantstr string
wanterr string
}
// Target variables for scanning into.
var (
scanstr string
scanint int
scanint8 int8
scanint16 int16
scanint32 int32
scanuint8 uint8
scanuint16 uint16
)
var conversionTests = []conversionTest{
// Exact conversions (destination pointer type matches source type)
{s: "foo", d: &scanstr, wantstr: "foo"},
{s: 123, d: &scanint, wantint: 123},
// To strings
{s: []byte("byteslice"), d: &scanstr, wantstr: "byteslice"},
{s: 123, d: &scanstr, wantstr: "123"},
{s: int8(123), d: &scanstr, wantstr: "123"},
{s: int64(123), d: &scanstr, wantstr: "123"},
{s: uint8(123), d: &scanstr, wantstr: "123"},
{s: uint16(123), d: &scanstr, wantstr: "123"},
{s: uint32(123), d: &scanstr, wantstr: "123"},
{s: uint64(123), d: &scanstr, wantstr: "123"},
{s: 1.5, d: &scanstr, wantstr: "1.5"},
// Strings to integers
{s: "255", d: &scanuint8, wantuint: 255},
{s: "256", d: &scanuint8, wanterr: `string "256" overflows uint8`},
{s: "256", d: &scanuint16, wantuint: 256},
{s: "-1", d: &scanint, wantint: -1},
{s: "foo", d: &scanint, wanterr: `converting string "foo" to a int: parsing "foo": invalid argument`},
}
func intValue(intptr interface{}) int64 {
return reflect.Indirect(reflect.ValueOf(intptr)).Int()
}
func uintValue(intptr interface{}) uint64 {
return reflect.Indirect(reflect.ValueOf(intptr)).Uint()
}
func TestConversions(t *testing.T) {
for n, ct := range conversionTests {
err := convertAssign(ct.d, ct.s)
errstr := ""
if err != nil {
errstr = err.String()
}
errf := func(format string, args ...interface{}) {
base := fmt.Sprintf("convertAssign #%d: for %v (%T) -> %T, ", n, ct.s, ct.s, ct.d)
t.Errorf(base+format, args...)
}
if errstr != ct.wanterr {
errf("got error %q, want error %q", errstr, ct.wanterr)
}
if ct.wantstr != "" && ct.wantstr != scanstr {
errf("want string %q, got %q", ct.wantstr, scanstr)
}
if ct.wantint != 0 && ct.wantint != intValue(ct.d) {
errf("want int %d, got %d", ct.wantint, intValue(ct.d))
}
if ct.wantuint != 0 && ct.wantuint != uintValue(ct.d) {
errf("want uint %d, got %d", ct.wantuint, uintValue(ct.d))
}
}
}
func TestNullableString(t *testing.T) {
var ns NullableString
convertAssign(&ns, []byte("foo"))
if !ns.Valid {
t.Errorf("expecting not null")
}
if ns.String != "foo" {
t.Errorf("expecting foo; got %q", ns.String)
}
convertAssign(&ns, nil)
if ns.Valid {
t.Errorf("expecting null on nil")
}
if ns.String != "" {
t.Errorf("expecting blank on nil; got %q", ns.String)
}
}
Goals of the sql and sql/driver packages:
* Provide a generic database API for a variety of SQL or SQL-like
databases. There currently exist Go libraries for SQLite, MySQL,
and Postgres, but all with a very different feel, and often
a non-Go-like feel.
* Feel like Go.
* Care mostly about the common cases. Common SQL should be portable.
SQL edge cases or db-specific extensions can be detected and
conditionally used by the application. It is a non-goal to care
about every particular db's extension or quirk.
* Separate out the basic implementation of a database driver
(implementing the sql/driver interfaces) vs the implementation
of all the user-level types and convenience methods.
In a nutshell:
User Code ---> sql package (concrete types) ---> sql/driver (interfaces)
Database Driver -> sql (to register) + sql/driver (implement interfaces)
* Make type casting/conversions consistent between all drivers. To
achieve this, most of the conversions are done in the db package,
not in each driver. The drivers then only have to deal with a
smaller set of types.
* Be flexible with type conversions, but be paranoid about silent
truncation or other loss of precision.
* Handle concurrency well. Users shouldn't need to care about the
database's per-connection thread safety issues (or lack thereof),
and shouldn't have to maintain their own free pools of connections.
The 'db' package should deal with that bookkeeping as needed. Given
an *sql.DB, it should be possible to share that instance between
multiple goroutines, without any extra synchronization.
* Push complexity, where necessary, down into the sql+driver packages,
rather than exposing it to users. Said otherwise, the sql package
should expose an ideal database that's not finnicky about how it's
accessed, even if that's not true.
* Provide optional interfaces in sql/driver for drivers to implement
for special cases or fastpaths. But the only party that knows about
those is the sql package. To user code, some stuff just might start
working or start working slightly faster.
# Copyright 2011 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
include ../../../../Make.inc
TARG=exp/sql/driver
GOFILES=\
driver.go\
types.go\
include ../../../../Make.pkg
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package driver defines interfaces to be implemented by database
// drivers as used by package sql.
//
// Code simply using databases should use package sql.
//
// Drivers only need to be aware of a subset of Go's types. The db package
// will convert all types into one of the following:
//
// int64
// float64
// bool
// nil
// []byte
// string [*] everywhere except from Rows.Next.
//
package driver
import (
"os"
)
// Driver is the interface that must be implemented by a database
// driver.
type Driver interface {
// Open returns a new or cached connection to the database.
// The name is a string in a driver-specific format.
//
// The returned connection is only used by one goroutine at a
// time.
Open(name string) (Conn, os.Error)
}
// Execer is an optional interface that may be implemented by a Driver
// or a Conn.
//
// If a Driver does not implement Execer, the sql package's DB.Exec
// method first obtains a free connection from its free pool or from
// the driver's Open method. Execer should only be implemented by
// drivers that can provide a more efficient implementation.
//
// If a Conn does not implement Execer, the db package's DB.Exec will
// first prepare a query, execute the statement, and then close the
// statement.
//
// All arguments are of a subset type as defined in the package docs.
type Execer interface {
Exec(query string, args []interface{}) (Result, os.Error)
}
// Conn is a connection to a database. It is not used concurrently
// by multiple goroutines.
//
// Conn is assumed to be stateful.
type Conn interface {
// Prepare returns a prepared statement, bound to this connection.
Prepare(query string) (Stmt, os.Error)
// Close invalidates and potentially stops any current
// prepared statements and transactions, marking this
// connection as no longer in use. The driver may cache or
// close its underlying connection to its database.
Close() os.Error
// Begin starts and returns a new transaction.
Begin() (Tx, os.Error)
}
// Result is the result of a query execution.
type Result interface {
// LastInsertId returns the database's auto-generated ID
// after, for example, an INSERT into a table with primary
// key.
LastInsertId() (int64, os.Error)
// RowsAffected returns the number of rows affected by the
// query.
RowsAffected() (int64, os.Error)
}
// Stmt is a prepared statement. It is bound to a Conn and not
// used by multiple goroutines concurrently.
type Stmt interface {
// Close closes the statement.
Close() os.Error
// NumInput returns the number of placeholder parameters.
NumInput() int
// Exec executes a query that doesn't return rows, such
// as an INSERT or UPDATE. The args are all of a subset
// type as defined above.
Exec(args []interface{}) (Result, os.Error)
// Exec executes a query that may return rows, such as a
// SELECT. The args of all of a subset type as defined above.
Query(args []interface{}) (Rows, os.Error)
}
// ColumnConverter may be optionally implemented by Stmt if the
// the statement is aware of its own columns' types and can
// convert from any type to a driver subset type.
type ColumnConverter interface {
// ColumnConverter returns a ValueConverter for the provided
// column index. If the type of a specific column isn't known
// or shouldn't be handled specially, DefaultValueConverter
// can be returned.
ColumnConverter(idx int) ValueConverter
}
// Rows is an iterator over an executed query's results.
type Rows interface {
// Columns returns the names of the columns. The number of
// columns of the result is inferred from the length of the
// slice. If a particular column name isn't known, an empty
// string should be returned for that entry.
Columns() []string
// Close closes the rows iterator.
Close() os.Error
// Next is called to populate the next row of data into
// the provided slice. The provided slice will be the same
// size as the Columns() are wide.
//
// The dest slice may be populated with only with values
// of subset types defined above, but excluding string.
// All string values must be converted to []byte.
Next(dest []interface{}) os.Error
}
// Tx is a transaction.
type Tx interface {
Commit() os.Error
Rollback() os.Error
}
// RowsAffected implements Result for an INSERT or UPDATE operation
// which mutates a number of rows.
type RowsAffected int64
var _ Result = RowsAffected(0)
func (RowsAffected) LastInsertId() (int64, os.Error) {
return 0, os.NewError("no LastInsertId available")
}
func (v RowsAffected) RowsAffected() (int64, os.Error) {
return int64(v), nil
}
// DDLSuccess is a pre-defined Result for drivers to return when a DDL
// command succeeds.
var DDLSuccess ddlSuccess
type ddlSuccess struct{}
var _ Result = ddlSuccess{}
func (ddlSuccess) LastInsertId() (int64, os.Error) {
return 0, os.NewError("no LastInsertId available after DDL statement")
}
func (ddlSuccess) RowsAffected() (int64, os.Error) {
return 0, os.NewError("no RowsAffected available after DDL statement")
}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package driver
import (
"fmt"
"os"
"reflect"
"strconv"
)
// ValueConverter is the interface providing the ConvertValue method.
type ValueConverter interface {
// ConvertValue converts a value to a restricted subset type.
ConvertValue(v interface{}) (interface{}, os.Error)
}
// Bool is a ValueConverter that converts input values to bools.
//
// The conversion rules are:
// - .... TODO(bradfitz): TBD
var Bool boolType
type boolType struct{}
var _ ValueConverter = boolType{}
func (boolType) ConvertValue(v interface{}) (interface{}, os.Error) {
return nil, fmt.Errorf("TODO(bradfitz): bool conversions")
}
// Int32 is a ValueConverter that converts input values to int64,
// respecting the limits of an int32 value.
var Int32 int32Type
type int32Type struct{}
var _ ValueConverter = int32Type{}
func (int32Type) ConvertValue(v interface{}) (interface{}, os.Error) {
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i64 := rv.Int()
if i64 > (1<<31)-1 || i64 < -(1<<31) {
return nil, fmt.Errorf("sql/driver: value %d overflows int32", v)
}
return i64, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
u64 := rv.Uint()
if u64 > (1<<31)-1 {
return nil, fmt.Errorf("sql/driver: value %d overflows int32", v)
}
return int64(u64), nil
case reflect.String:
i, err := strconv.Atoi(rv.String())
if err != nil {
return nil, fmt.Errorf("sql/driver: value %q can't be converted to int32", v)
}
return int64(i), nil
}
return nil, fmt.Errorf("sql/driver: unsupported value %v (type %T) converting to int32", v, v)
}
// String is a ValueConverter that converts its input to a string.
// If the value is already a string or []byte, it's unchanged.
// If the value is of another type, conversion to string is done
// with fmt.Sprintf("%v", v).
var String stringType
type stringType struct{}
func (stringType) ConvertValue(v interface{}) (interface{}, os.Error) {
switch v.(type) {
case string, []byte:
return v, nil
}
return fmt.Sprintf("%v", v), nil
}
// IsParameterSubsetType reports whether v is of a valid type for a
// parameter. These types are:
//
// int64
// float64
// bool
// nil
// []byte
// string
//
// This is the ame list as IsScanSubsetType, with the addition of
// string.
func IsParameterSubsetType(v interface{}) bool {
if IsScanSubsetType(v) {
return true
}
if _, ok := v.(string); ok {
return true
}
return false
}
// IsScanSubsetType reports whether v is of a valid type for a
// value populated by Rows.Next. These types are:
//
// int64
// float64
// bool
// nil
// []byte
//
// This is the same list as IsParameterSubsetType, without string.
func IsScanSubsetType(v interface{}) bool {
if v == nil {
return true
}
switch v.(type) {
case int64, float64, []byte, bool:
return true
}
return false
}
// DefaultParameterConverter is the default implementation of
// ValueConverter that's used when a Stmt doesn't implement
// ColumnConverter.
//
// DefaultParameterConverter returns the given value directly if
// IsSubsetType(value). Otherwise integer type are converted to
// int64, floats to float64, and strings to []byte. Other types are
// an error.
var DefaultParameterConverter defaultConverter
type defaultConverter struct{}
var _ ValueConverter = defaultConverter{}
func (defaultConverter) ConvertValue(v interface{}) (interface{}, os.Error) {
if IsParameterSubsetType(v) {
return v, nil
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int(), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
return int64(rv.Uint()), nil
case reflect.Uint64:
u64 := rv.Uint()
if u64 >= 1<<63 {
return nil, fmt.Errorf("uint64 values with high bit set are not supported")
}
return int64(u64), nil
case reflect.Float32, reflect.Float64:
return rv.Float(), nil
}
return nil, fmt.Errorf("unsupported type %s", rv.Kind())
}
This diff is collapsed.
This diff is collapsed.
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sql
import (
"strings"
"testing"
)
func newTestDB(t *testing.T, name string) *DB {
db, err := Open("test", "foo")
if err != nil {
t.Fatalf("Open: %v", err)
}
if _, err := db.Exec("WIPE"); err != nil {
t.Fatalf("exec wipe: %v", err)
}
if name == "people" {
exec(t, db, "CREATE|people|name=string,age=int32,dead=bool")
exec(t, db, "INSERT|people|name=Alice,age=?", 1)
exec(t, db, "INSERT|people|name=Bob,age=?", 2)
exec(t, db, "INSERT|people|name=Chris,age=?", 3)
}
return db
}
func exec(t *testing.T, db *DB, query string, args ...interface{}) {
_, err := db.Exec(query, args...)
if err != nil {
t.Fatalf("Exec of %q: %v", query, err)
}
}
func TestQuery(t *testing.T) {
db := newTestDB(t, "people")
var name string
var age int
err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age)
if err == nil || !strings.Contains(err.String(), "expected 2 destination arguments") {
t.Errorf("expected error from wrong number of arguments; actually got: %v", err)
}
err = db.QueryRow("SELECT|people|age,name|age=?", 2).Scan(&age, &name)
if err != nil {
t.Fatalf("age QueryRow+Scan: %v", err)
}
if name != "Bob" {
t.Errorf("expected name Bob, got %q", name)
}
if age != 2 {
t.Errorf("expected age 2, got %d", age)
}
err = db.QueryRow("SELECT|people|age,name|name=?", "Alice").Scan(&age, &name)
if err != nil {
t.Fatalf("name QueryRow+Scan: %v", err)
}
if name != "Alice" {
t.Errorf("expected name Alice, got %q", name)
}
if age != 1 {
t.Errorf("expected age 1, got %d", age)
}
}
func TestStatementQueryRow(t *testing.T) {
db := newTestDB(t, "people")
stmt, err := db.Prepare("SELECT|people|age|name=?")
if err != nil {
t.Fatalf("Prepare: %v", err)
}
var age int
for n, tt := range []struct {
name string
want int
}{
{"Alice", 1},
{"Bob", 2},
{"Chris", 3},
} {
if err := stmt.QueryRow(tt.name).Scan(&age); err != nil {
t.Errorf("%d: on %q, QueryRow/Scan: %v", n, tt.name, err)
} else if age != tt.want {
t.Errorf("%d: age=%d, want %d", age, tt.want)
}
}
}
// just a test of fakedb itself
func TestBogusPreboundParameters(t *testing.T) {
db := newTestDB(t, "foo")
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
_, err := db.Prepare("INSERT|t1|name=?,age=bogusconversion")
if err == nil {
t.Fatalf("expected error")
}
if err.String() != `fakedb: invalid conversion to int32 from "bogusconversion"` {
t.Errorf("unexpected error: %v", err)
}
}
func TestDb(t *testing.T) {
db := newTestDB(t, "foo")
exec(t, db, "CREATE|t1|name=string,age=int32,dead=bool")
stmt, err := db.Prepare("INSERT|t1|name=?,age=?")
if err != nil {
t.Errorf("Stmt, err = %v, %v", stmt, err)
}
type execTest struct {
args []interface{}
wantErr string
}
execTests := []execTest{
// Okay:
{[]interface{}{"Brad", 31}, ""},
{[]interface{}{"Brad", int64(31)}, ""},
{[]interface{}{"Bob", "32"}, ""},
{[]interface{}{7, 9}, ""},
// Invalid conversions:
{[]interface{}{"Brad", int64(0xFFFFFFFF)}, "db: converting Exec argument #1's type: sql/driver: value 4294967295 overflows int32"},
{[]interface{}{"Brad", "strconv fail"}, "db: converting Exec argument #1's type: sql/driver: value \"strconv fail\" can't be converted to int32"},
// Wrong number of args:
{[]interface{}{}, "db: expected 2 arguments, got 0"},
{[]interface{}{1, 2, 3}, "db: expected 2 arguments, got 3"},
}
for n, et := range execTests {
_, err := stmt.Exec(et.args...)
errStr := ""
if err != nil {
errStr = err.String()
}
if errStr != et.wantErr {
t.Errorf("stmt.Execute #%d: for %v, got error %q, want error %q",
n, et.args, errStr, et.wantErr)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment