remove the db and dbimpl packages; now in the Go core as database/sql and database/sql/driver

Change-Id: Ie88df6466f9b81e4320895049dd3611c43e0ea4a
This commit is contained in:
Brad Fitzpatrick 2012-03-05 07:34:01 -08:00
parent a1e4d063f4
commit 1026c32f06
8 changed files with 0 additions and 1726 deletions

View File

@ -1,118 +0,0 @@
/*
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Type conversions for Scan.
package db
import (
"errors"
"fmt"
"reflect"
"strconv"
)
// copyConvert 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 copyConvert(dest, src interface{}) 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 errors.New("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.ParseInt(s, 10, 64)
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.ParseUint(s, 10, 64)
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
}

View File

@ -1,120 +0,0 @@
/*
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
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 := copyConvert(ct.d, ct.s)
errstr := ""
if err != nil {
errstr = err.String()
}
errf := func(format string, args ...interface{}) {
base := fmt.Sprintf("copyConvert #%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
copyConvert(&ns, []byte("foo"))
if !ns.Ok {
t.Errorf("expecting ok")
}
if ns.String != "foo" {
t.Errorf("expecting foo; got %q", ns.String)
}
copyConvert(&ns, nil)
if ns.Ok {
t.Errorf("expecting not ok on nil")
}
if ns.String != "" {
t.Errorf("expecting blank on nil; got %q", ns.String)
}
}

View File

@ -1,541 +0,0 @@
/*
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package db provides a generic interface around SQL (or SQL-like)
// databases.
package db
import (
"errors"
"fmt"
"io"
"runtime"
"sync"
"camlistore.org/pkg/db/dbimpl"
)
var drivers = make(map[string]dbimpl.Driver)
// Register makes a database driver available by the provided name.
// It's a fatal error to register the same or different drivers with
// a duplicate name.
func Register(name string, driver dbimpl.Driver) {
if driver == nil {
panic("db: Register driver is nil")
}
if _, dup := drivers[name]; dup {
panic("db: Register called twice for driver " + name)
}
drivers[name] = driver
}
// NullableString is type representing a string which may be
// null. NullableString implements the ScannerInto inteface so can be
// used as a scan destination:
//
// var s NullableString
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
// ...
// if !s.Ok {
// // NULL value
// } else {
// // use s.String
// }
//
// TODO(bradfitz): add other types.
type NullableString struct {
String string
Ok bool // Ok is true if the String is not NULL
}
// ScanInto implements the ScannerInto interface.
func (ms *NullableString) ScanInto(value interface{}) error {
if value == nil {
ms.String, ms.Ok = "", false
return nil
}
ms.Ok = true
return copyConvert(&ms.String, value)
}
// ScannerInto is an interface used by Scan.
type ScannerInto interface {
// value is nil for NULL database columns.
ScanInto(value interface{}) error
}
// ErrNoRows is returned by Scan when QueryRow doesn't return a
// row. In such a case, QueryRow returns a placeholder *Row value that
// defers this error until a Scan.
var ErrNoRows = errors.New("db: no rows in result set")
// DB is a database handle. It's safe for concurrent use by multiple
// goroutines.
type DB struct {
driver dbimpl.Driver
dsn string
mu sync.Mutex
freeConn []dbimpl.Conn
}
// Open opens a database specified by its database driver name and a
// driver-specific data source name, usually consisting of at least a
// database name and connection information.
//
// Most users will open a database via a driver-specific connection
// helper function that returns a *DB.
func Open(driverName, dataSourceName string) (*DB, error) {
driver, ok := drivers[driverName]
if !ok {
return nil, fmt.Errorf("db: unknown driver %q (forgotten import?)", driverName)
}
return &DB{driver: driver, dsn: dataSourceName}, nil
}
func (db *DB) maxIdleConns() int {
const defaultMaxIdleConns = 2
// TODO(bradfitz): ask driver, if supported, for its default preference
// TODO(bradfitz): let users override?
return defaultMaxIdleConns
}
// conn returns a newly-opened or cached dbimpl.Conn
func (db *DB) conn() (dbimpl.Conn, error) {
db.mu.Lock()
if n := len(db.freeConn); n > 0 {
conn := db.freeConn[n-1]
db.freeConn = db.freeConn[:n-1]
db.mu.Unlock()
return conn, nil
}
db.mu.Unlock()
return db.driver.Open(db.dsn)
}
func (db *DB) connIfFree(wanted dbimpl.Conn) (conn dbimpl.Conn, ok bool) {
db.mu.Lock()
defer db.mu.Unlock()
for n, conn := range db.freeConn {
if conn == wanted {
db.freeConn[n] = db.freeConn[len(db.freeConn)-1]
db.freeConn = db.freeConn[:len(db.freeConn)-1]
return wanted, true
}
}
return nil, false
}
func (db *DB) putConn(c dbimpl.Conn) {
if n := len(db.freeConn); n < db.maxIdleConns() {
db.freeConn = append(db.freeConn, c)
return
}
db.closeConn(c)
}
func (db *DB) closeConn(c dbimpl.Conn) {
// TODO: check to see if we need this Conn for any prepared statements
// that are active.
c.Close()
}
func (db *DB) Prepare(query string) (*Stmt, error) {
// TODO: check if db.driver supports an optional
// dbimpl.Preparer interface and call that instead, if so,
// otherwise we make a prepared statement that's bound
// to a connection, and to execute this prepared statement
// we either need to use this connection (if it's free), else
// get a new connection + re-prepare + execute on that one.
ci, err := db.conn()
if err != nil {
return nil, err
}
defer db.putConn(ci)
si, err := ci.Prepare(query)
if err != nil {
return nil, err
}
stmt := &Stmt{
db: db,
query: query,
css: []connStmt{{ci, si}},
}
return stmt, nil
}
func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
// Optional fast path, if the driver implements dbimpl.Execer.
if execer, ok := db.driver.(dbimpl.Execer); ok {
resi, err := execer.Exec(query, args)
if err != nil {
return nil, err
}
return result{resi}, nil
}
// If the driver does not implement dbimpl.Execer, we need
// a connection.
conn, err := db.conn()
if err != nil {
return nil, err
}
defer db.putConn(conn)
if execer, ok := conn.(dbimpl.Execer); ok {
resi, err := execer.Exec(query, args)
if err != nil {
return nil, err
}
return result{resi}, nil
}
sti, err := conn.Prepare(query)
if err != nil {
return nil, err
}
defer sti.Close()
resi, err := sti.Exec(args)
if err != nil {
return nil, err
}
return result{resi}, nil
}
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
stmt, err := db.Prepare(query)
if err != nil {
return nil, err
}
defer stmt.Close()
return stmt.Query(args...)
}
func (db *DB) QueryRow(query string, args ...interface{}) *Row {
rows, err := db.Query(query, args...)
if err != nil {
return &Row{err: err}
}
return &Row{rows: rows}
}
func (db *DB) Begin() (*Tx, error) {
panic(todo())
}
// DriverDatabase returns the database's underlying driver.
// This is non-portable and should only be used when
// needed.
func (db *DB) Driver() dbimpl.Driver {
return db.driver
}
// Tx is an in-progress database transaction.
type Tx struct {
}
func (tx *Tx) Commit() error {
panic(todo())
}
func (tx *Tx) Rollback() error {
panic(todo())
}
func (tx *Tx) Prepare(query string) (*Stmt, error) {
panic(todo())
}
func (tx *Tx) Exec(query string, args ...interface{}) {
panic(todo())
}
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {
panic(todo())
}
func (tx *Tx) QueryRow(query string, args ...interface{}) *Row {
panic(todo())
}
// connStmt is a prepared statement on a particular connection.
type connStmt struct {
ci dbimpl.Conn
si dbimpl.Stmt
}
// Stmt is a prepared statement. Stmt is safe for concurrent use by multiple goroutines.
type Stmt struct {
// Immutable:
db *DB // where we came from
query string // that created the Sttm
mu sync.Mutex
closed bool
css []connStmt // can use any that have idle connections
}
func todo() string {
_, file, line, _ := runtime.Caller(1)
return fmt.Sprintf("%s:%d: TODO: implement", file, line)
}
func (s *Stmt) Exec(args ...interface{}) (Result, error) {
ci, si, err := s.connStmt()
if err != nil {
return nil, err
}
defer s.db.putConn(ci)
if want := si.NumInput(); len(args) != want {
return nil, fmt.Errorf("db: expected %d arguments, got %d", want, len(args))
}
// Convert args if the driver knows its own types.
if cc, ok := si.(dbimpl.ColumnConverter); ok {
for n, arg := range args {
args[n], err = cc.ColumnCoverter(n).ConvertValue(arg)
if err != nil {
return nil, fmt.Errorf("db: converting Exec column index %d: %v", n, err)
}
}
}
// Then convert everything into the restricted subset
// of types that the dbimpl package needs to know about.
// all integers -> int64, etc
for n, arg := range args {
var err error
args[n], err = dbimpl.SubsetValue(arg)
if err != nil {
return nil, fmt.Errorf("db: error converting index %d: %v", n, err)
}
}
resi, err := si.Exec(args)
if err != nil {
return nil, err
}
return result{resi}, nil
}
func (s *Stmt) connStmt(args ...interface{}) (dbimpl.Conn, dbimpl.Stmt, error) {
s.mu.Lock()
if s.closed {
return nil, nil, errors.New("db: statement is closed")
}
var cs connStmt
match := false
for _, v := range s.css {
// TODO(bradfitz): lazily clean up entries in this
// list with dead conns while enumerating
if _, match = s.db.connIfFree(cs.ci); match {
cs = v
break
}
}
s.mu.Unlock()
// Make a new conn if all are busy.
// TODO(bradfitz): or wait for one? make configurable later?
if !match {
ci, err := s.db.conn()
if err != nil {
return nil, nil, err
}
si, err := ci.Prepare(s.query)
if err != nil {
return nil, nil, err
}
s.mu.Lock()
cs = connStmt{ci, si}
s.css = append(s.css, cs)
s.mu.Unlock()
}
return cs.ci, cs.si, nil
}
// Query executes a prepared statement with the provided placeholder arguments
// and returns the result.
func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
ci, si, err := s.connStmt(args...)
if err != nil {
return nil, err
}
if len(args) != si.NumInput() {
return nil, fmt.Errorf("db: statement expects %d inputs; got %d", si.NumInput(), len(args))
}
rowsi, err := si.Query(args)
if err != nil {
s.db.putConn(ci)
return nil, err
}
// Note: ownership of ci passes to the *Rows
rows := &Rows{
db: s.db,
ci: ci,
rowsi: rowsi,
}
return rows, nil
}
// QueryRow wraps Query, for use in selecting a single row. Any errors
// are deferred until the returned row is scanned. The returned row is
// always non-nil.
func (s *Stmt) QueryRow(args ...interface{}) *Row {
rows, err := s.Query(args...)
if err != nil {
return &Row{err: err}
}
return &Row{rows: rows}
}
func (s *Stmt) Close() error {
s.mu.Lock()
defer s.mu.Unlock() // TODO(bradfitz): move this unlock after 'closed = true'?
if s.closed {
return nil
}
s.closed = true
for _, v := range s.css {
if ci, match := s.db.connIfFree(v.ci); match {
v.si.Close()
s.db.putConn(ci)
} else {
// TODO(bradfitz): care that we can't close
// this statement because the statement's
// connection is in use?
}
}
return nil
}
// Rows is the result of a query. Its cursor starts before the first row
// of the result set. Use Next to advance through the rows:
//
// rows, err := db.Query("SELECT ...")
// ...
// for rows.Next() {
// var id int
// var name string
// err = rows.Scan(&id, &name)
// ...
// }
// err = rows.Error() // get any Error encountered during iteration
// ...
type Rows struct {
db *DB
ci dbimpl.Conn // owned; must be returned when Rows is closed
rowsi dbimpl.Rows
closed bool
lastcols []interface{}
lasterr error
}
// Next advances the Rows' cursor (which starts before the first
// row). Next returns true if there's another row and the cursor was
// moved.
func (rs *Rows) Next() bool {
if rs.closed {
return false
}
if rs.lasterr != nil {
return false
}
if rs.lastcols == nil {
rs.lastcols = make([]interface{}, len(rs.rowsi.Columns()))
}
rs.lasterr = rs.rowsi.Next(rs.lastcols)
return rs.lasterr == nil
}
// Error returns the error, if any, that was encountered during iteration.
func (rs *Rows) Error() error {
if rs.lasterr == io.EOF {
return nil
}
return rs.lasterr
}
// Scan copies the columns in the current row into variables referened in dest.
func (rs *Rows) Scan(dest ...interface{}) error {
if rs.closed {
return errors.New("db: Rows closed")
}
if rs.lasterr != nil {
return rs.lasterr
}
if rs.lastcols == nil {
return errors.New("db: Scan called without calling Next")
}
if len(dest) != len(rs.lastcols) {
return fmt.Errorf("db: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
}
for i, sv := range rs.lastcols {
err := copyConvert(dest[i], sv)
if err != nil {
return fmt.Errorf("db: Scan error on column index %d: %v", i, err)
}
}
return nil
}
// Close closes the Rows, preventing further enumeration. If the end
// if encountered normally the Rows are closed automatically. Calling
// Close is idempotent.
func (rs *Rows) Close() error {
if rs.closed {
return nil
}
rs.closed = true
err := rs.rowsi.Close()
rs.db.putConn(rs.ci)
return err
}
// Row is the result of calling QueryRow to select a single row. If no
// matching row was found, an error is deferred until the Scan method
// is called.
type Row struct {
// One of these two will be non-nil:
err error // deferred error for easy chaining
rows *Rows
}
// Scan copies the columns from the matched row into the variables
// pointed at in dest. If no row was matched, ErrNoRows is returned.
func (r *Row) Scan(dest ...interface{}) error {
if r.err != nil {
return r.err
}
defer r.rows.Close()
if !r.rows.Next() {
return ErrNoRows
}
return r.rows.Scan(dest...)
}
type Result interface {
AutoIncrementId() (int64, error)
RowsAffected() (int64, error)
}
type result struct {
dbimpl.Result
}

View File

@ -1,154 +0,0 @@
/*
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
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 column index 1: value 4294967295 overflows int32"},
{[]interface{}{"Brad", "strconv fail"}, "db: converting Exec column index 1: 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)
}
}
}

View File

@ -1,131 +0,0 @@
/*
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package dbimpl defines interfaces to be implemented by database
// drivers as used by package db.
//
// Code simply using databases should use package db.
package dbimpl
import "errors"
// must only deal with values:
// int64 (no uint64 support, for now?)
// float64
// bool
// nil
// []byte
// Driver is the interface that must be implemented by database
// driver.
type Driver interface {
// Open returns a new or cached connection to the database.
// The dsn parameter, the Data Source Name, contains a
// driver-specific string containing the database name,
// connection parameters, authentication parameters, etc.
//
// The returned connection is only used by one goroutine at a
// time.
Open(dsn string) (Conn, error)
}
// Execer is an optional interface that may be implemented by a Driver
// or a Conn.
//
// If not implemented by a Driver, the db 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
// which can provide a more effcient implementation.
//
// If not implemented by a Conn, the db package's DB.Exec will first
// prepare a query, execute the statement, and then close the
// statement.
type Execer interface {
Exec(query string, args []interface{}) (Result, error)
}
type Conn interface {
// Prepare returns a prepared statement, bound to this connection.
Prepare(query string) (Stmt, 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() error
// Begin starts and returns a new transaction.
Begin() (Tx, error)
}
type Result interface {
AutoIncrementId() (int64, error)
RowsAffected() (int64, error)
}
// Stmt is a prepared statement. It is bound to a Conn and not
// used by multiple goroutines concurrently.
type Stmt interface {
Close() error
NumInput() int
Exec(args []interface{}) (Result, error)
Query(args []interface{}) (Rows, error)
}
// ColumnConverter may be optionally implemented by Stmt to signal
// to the db package to do type conversions.
type ColumnConverter interface {
ColumnCoverter(idx int) ValueConverter
}
type ValueConverter interface {
ConvertValue(v interface{}) (interface{}, error)
}
type Rows interface {
Columns() []string
Close() error
// Returns os.EOF at end of cursor
Next(dest []interface{}) error
}
type Tx interface {
Commit() error
Rollback() error
}
type RowsAffected int64
func (RowsAffected) AutoIncrementId() (int64, error) {
return 0, errors.New("no AutoIncrementId available")
}
func (v RowsAffected) RowsAffected() (int64, error) {
return int64(v), nil
}
type ddlSuccess struct{}
var DDLSuccess Result = ddlSuccess{}
func (ddlSuccess) AutoIncrementId() (int64, error) {
return 0, errors.New("no AutoIncrementId available after DDL statement")
}
func (ddlSuccess) RowsAffected() (int64, error) {
return 0, errors.New("no RowsAffected available after DDL statement")
}

View File

@ -1,105 +0,0 @@
/*
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dbimpl
import (
"fmt"
"reflect"
"strconv"
)
type boolType struct{}
var Bool = boolType{}
func (boolType) ConvertValue(v interface{}) (interface{}, error) {
return nil, fmt.Errorf("TODO(bradfitz): bool conversions")
}
type int32Type struct{}
var Int32 = int32Type{}
func (int32Type) ConvertValue(v interface{}) (interface{}, 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("value %d overflows int32", v)
}
return int32(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("value %d overflows int32", v)
}
return int32(u64), nil
case reflect.String:
i, err := strconv.Atoi(rv.String())
if err != nil {
return nil, fmt.Errorf("value %q can't be converted to int32", v)
}
return int32(i), nil
}
return nil, fmt.Errorf("unsupported value %v (type %T) converting to int32", v, v)
}
type stringType struct{}
var String = stringType{}
func (stringType) ConvertValue(v interface{}) (interface{}, error) {
if s, ok := v.(string); ok {
return s, nil
}
return fmt.Sprintf("%v", v), nil
}
// SubsetValue converts v to one of the restricted subset types that
// dbimpl drivers need to support: int64, float64, bool, nil, []byte
func SubsetValue(v interface{}) (interface{}, error) {
if v == nil {
return nil, nil
}
if _, ok := v.([]byte); ok {
return v, nil
}
if _, ok := v.(bool); ok {
return v, nil
}
if s, ok := v.(string); ok {
return []byte(s), 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())
}

View File

@ -1,46 +0,0 @@
Goals of the db and db/dbimpl 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 db/dbimpl interfaces) vs the implementation
of all the user-level types and convenience methods.
In a nutshell:
User Code ---> db package (concrete types) ---> db/dbimpl (interfaces)
Database Driver -> db (to register) + dbimpl (implement interfaces)
* To type casting/conversions consistently between all drivers. To
achieve this, most of the type 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
a *db.DB, it should be possible to share that instance between
multiple goroutines, without any extra synchronization.
* Push complexity, where necessary, down into the db+dbimpl packages,
rather than exposing it to users. Said otherwise, the db 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 dbimpl for drivers to implement
for special cases or fastpaths. But the only party that knows about
those is the 'db' package. To user code, some stuff just might start
working or start working slightly faster.

View File

@ -1,511 +0,0 @@
/*
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"errors"
"fmt"
"io"
"log"
"strconv"
"strings"
"sync"
"camlistore.org/pkg/db/dbimpl"
)
var _ = log.Printf
// fakeDriver is a fake database that implements Go's dbimpl.Driver
// interface, just for testing.
//
// It speaks a query language that's semantically similar to but
// syntantically different and simpler than SQL. The syntax is as
// follows:
//
// WIPE
// CREATE|<tablename>|<col>=<type>,<col>=<type>,...
// where types are: "string", [u]int{8,16,32,64}, "bool"
// INSERT|<tablename>|col=val,col2=val2,col3=?
// SELECT|<tablename>|projectcol1,projectcol2|filtercol=?,filtercol2=?
//
// When opening a a fakeDriver's database, it starts empty with no
// tables. All tables and data are stored in memory only.
type fakeDriver struct {
mu sync.Mutex
openCount int
dbs map[string]*fakeDB
}
type fakeDB struct {
name string
mu sync.Mutex
free []*fakeConn
tables map[string]*table
}
type table struct {
mu sync.Mutex
colname []string
coltype []string
rows []*row
}
func (t *table) columnIndex(name string) int {
for n, nname := range t.colname {
if name == nname {
return n
}
}
return -1
}
type row struct {
cols []interface{} // must be same size as its table colname + coltype
}
func (r *row) clone() *row {
nrow := &row{cols: make([]interface{}, len(r.cols))}
copy(nrow.cols, r.cols)
return nrow
}
type fakeConn struct {
db *fakeDB // where to return ourselves to
currTx *fakeTx
}
type fakeTx struct {
c *fakeConn
}
type fakeStmt struct {
c *fakeConn
q string // just for debugging
cmd string
table string
colName []string // used by CREATE, INSERT, SELECT (selected columns)
colType []string // used by CREATE
colValue []interface{} // used by INSERT (mix of strings and "?" for bound params)
placeholders int // used by INSERT/SELECT: number of ? params
whereCol []string // used by SELECT (all placeholders)
placeholderConverter []dbimpl.ValueConverter // used by INSERT
}
var driver dbimpl.Driver = &fakeDriver{}
func init() {
Register("test", driver)
}
// Supports dsn forms:
// <dbname>
// <dbname>;wipe
func (d *fakeDriver) Open(dsn string) (dbimpl.Conn, error) {
d.mu.Lock()
defer d.mu.Unlock()
d.openCount++
if d.dbs == nil {
d.dbs = make(map[string]*fakeDB)
}
parts := strings.Split(dsn, ";")
if len(parts) < 1 {
return nil, errors.New("fakedb: no database name")
}
name := parts[0]
db, ok := d.dbs[name]
if !ok {
db = &fakeDB{name: name}
d.dbs[name] = db
}
return &fakeConn{db: db}, nil
}
func (db *fakeDB) wipe() {
db.mu.Lock()
defer db.mu.Unlock()
db.tables = nil
}
func (db *fakeDB) createTable(name string, columnNames, columnTypes []string) error {
db.mu.Lock()
defer db.mu.Unlock()
if db.tables == nil {
db.tables = make(map[string]*table)
}
if _, exist := db.tables[name]; exist {
return fmt.Errorf("table %q already exists", name)
}
if len(columnNames) != len(columnTypes) {
return fmt.Errorf("create table of %q len(names) != len(types): %d vs %d",
len(columnNames), len(columnTypes))
}
db.tables[name] = &table{colname: columnNames, coltype: columnTypes}
return nil
}
// must be called with db.mu lock held
func (db *fakeDB) table(table string) (*table, bool) {
if db.tables == nil {
return nil, false
}
t, ok := db.tables[table]
return t, ok
}
func (db *fakeDB) columnType(table, column string) (typ string, ok bool) {
db.mu.Lock()
defer db.mu.Unlock()
t, ok := db.table(table)
if !ok {
return
}
for n, cname := range t.colname {
if cname == column {
return t.coltype[n], true
}
}
return "", false
}
func (c *fakeConn) Begin() (dbimpl.Tx, error) {
if c.currTx != nil {
return nil, errors.New("already in a transaction")
}
c.currTx = &fakeTx{c: c}
return c.currTx, nil
}
func (c *fakeConn) Close() error {
if c.currTx != nil {
return errors.New("can't close; in a Transaction")
}
if c.db == nil {
return errors.New("can't close; already closed")
}
c.db = nil
return nil
}
func errf(msg string, args ...interface{}) error {
return errors.New("fakedb: " + fmt.Sprintf(msg, args...))
}
// parts are table|selectCol1,selectCol2|whereCol=?,whereCol2=?
// (note that where where columns must always contain ? marks,
// just a limitation for fakedb)
func (c *fakeConn) prepareSelect(stmt *fakeStmt, parts []string) (dbimpl.Stmt, error) {
if len(parts) != 3 {
return nil, errf("invalid SELECT syntax with %d parts; want 3", len(parts))
}
stmt.table = parts[0]
stmt.colName = strings.Split(parts[1], ",")
for n, colspec := range strings.Split(parts[2], ",") {
nameVal := strings.Split(colspec, "=")
if len(nameVal) != 2 {
return nil, errf("SELECT on table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n)
}
column, value := nameVal[0], nameVal[1]
_, ok := c.db.columnType(stmt.table, column)
if !ok {
return nil, errf("SELECT on table %q references non-existent column %q", stmt.table, column)
}
if value != "?" {
return nil, errf("SELECT on table %q has pre-bound value for where column %q; need a question mark",
stmt.table, column)
}
stmt.whereCol = append(stmt.whereCol, column)
stmt.placeholders++
}
return stmt, nil
}
// parts are table|col=type,col2=type2
func (c *fakeConn) prepareCreate(stmt *fakeStmt, parts []string) (dbimpl.Stmt, error) {
if len(parts) != 2 {
return nil, errf("invalid CREATE syntax with %d parts; want 2", len(parts))
}
stmt.table = parts[0]
for n, colspec := range strings.Split(parts[1], ",") {
nameType := strings.Split(colspec, "=")
if len(nameType) != 2 {
return nil, errf("CREATE table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n)
}
stmt.colName = append(stmt.colName, nameType[0])
stmt.colType = append(stmt.colType, nameType[1])
}
return stmt, nil
}
// parts are table|col=?,col2=val
func (c *fakeConn) prepareInsert(stmt *fakeStmt, parts []string) (dbimpl.Stmt, error) {
if len(parts) != 2 {
return nil, errf("invalid INSERT syntax with %d parts; want 2", len(parts))
}
stmt.table = parts[0]
for n, colspec := range strings.Split(parts[1], ",") {
nameVal := strings.Split(colspec, "=")
if len(nameVal) != 2 {
return nil, errf("INSERT table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n)
}
column, value := nameVal[0], nameVal[1]
ctype, ok := c.db.columnType(stmt.table, column)
if !ok {
return nil, errf("INSERT table %q references non-existent column %q", stmt.table, column)
}
stmt.colName = append(stmt.colName, column)
if value != "?" {
var subsetVal interface{}
// Convert to dbimpl subset type
switch ctype {
case "string":
subsetVal = []byte(value)
case "int32":
i, err := strconv.Atoi(value)
if err != nil {
return nil, errf("invalid conversion to int32 from %q", value)
}
subsetVal = int64(i) // int64 is a subset type, but not int32
default:
return nil, errf("unsupported conversion for pre-bound parameter %q to type %q", value, ctype)
}
stmt.colValue = append(stmt.colValue, subsetVal)
} else {
stmt.placeholders++
stmt.placeholderConverter = append(stmt.placeholderConverter, converterForType(ctype))
stmt.colValue = append(stmt.colValue, "?")
}
}
return stmt, nil
}
func (c *fakeConn) Prepare(query string) (dbimpl.Stmt, error) {
if c.db == nil {
panic("nil c.db; conn = " + fmt.Sprintf("%#v", c))
}
parts := strings.Split(query, "|")
if len(parts) < 1 {
return nil, errf("empty query")
}
cmd := parts[0]
parts = parts[1:]
stmt := &fakeStmt{q: query, c: c, cmd: cmd}
switch cmd {
case "WIPE":
// Nothing
case "SELECT":
return c.prepareSelect(stmt, parts)
case "CREATE":
return c.prepareCreate(stmt, parts)
case "INSERT":
return c.prepareInsert(stmt, parts)
default:
return nil, errf("unsupported command type %q", cmd)
}
return stmt, nil
}
func (s *fakeStmt) ColumnCoverter(idx int) dbimpl.ValueConverter {
return s.placeholderConverter[idx]
}
func (s *fakeStmt) Close() error {
return nil
}
func (s *fakeStmt) Exec(args []interface{}) (dbimpl.Result, error) {
db := s.c.db
switch s.cmd {
case "WIPE":
db.wipe()
return dbimpl.DDLSuccess, nil
case "CREATE":
if err := db.createTable(s.table, s.colName, s.colType); err != nil {
return nil, err
}
return dbimpl.DDLSuccess, nil
case "INSERT":
return s.execInsert(args)
}
fmt.Printf("EXEC statement, cmd=%q: %#v\n", s.cmd, s)
return nil, fmt.Errorf("unimplemented statement Exec command type of %q", s.cmd)
}
func (s *fakeStmt) execInsert(args []interface{}) (dbimpl.Result, error) {
db := s.c.db
if len(args) != s.placeholders {
panic("error in pkg db; should only get here if size is correct")
}
db.mu.Lock()
t, ok := db.table(s.table)
db.mu.Unlock()
if !ok {
return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table)
}
t.mu.Lock()
defer t.mu.Unlock()
cols := make([]interface{}, len(t.colname))
argPos := 0
for n, colname := range s.colName {
colidx := t.columnIndex(colname)
if colidx == -1 {
return nil, fmt.Errorf("fakedb: column %q doesn't exist or dropped since prepared statement was created", colname)
}
var val interface{}
if strvalue, ok := s.colValue[n].(string); ok && strvalue == "?" {
val = args[argPos]
argPos++
} else {
val = s.colValue[n]
}
cols[colidx] = val
}
t.rows = append(t.rows, &row{cols: cols})
return dbimpl.RowsAffected(1), nil
}
func (s *fakeStmt) Query(args []interface{}) (dbimpl.Rows, error) {
db := s.c.db
if len(args) != s.placeholders {
panic("error in pkg db; should only get here if size is correct")
}
db.mu.Lock()
t, ok := db.table(s.table)
db.mu.Unlock()
if !ok {
return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table)
}
t.mu.Lock()
defer t.mu.Unlock()
colIdx := make(map[string]int) // select column name -> column index in table
for _, name := range s.colName {
idx := t.columnIndex(name)
if idx == -1 {
return nil, fmt.Errorf("fakedb: unknown column name %q", name)
}
colIdx[name] = idx
}
mrows := []*row{}
rows:
for _, trow := range t.rows {
// Process the where clause, skipping non-match rows. This is lazy
// and just uses fmt.Sprintf("%v") to test equality. Good enough
// for test code.
for widx, wcol := range s.whereCol {
idx := t.columnIndex(wcol)
if idx == -1 {
return nil, fmt.Errorf("db: invalid where clause column %q", wcol)
}
tcol := trow.cols[idx]
if bs, ok := tcol.([]byte); ok {
// lazy hack to avoid sprintf %v on a []byte
tcol = string(bs)
}
if fmt.Sprintf("%v", tcol) != fmt.Sprintf("%v", args[widx]) {
continue rows
}
}
mrow := &row{cols: make([]interface{}, len(s.colName))}
for seli, name := range s.colName {
mrow.cols[seli] = trow.cols[colIdx[name]]
}
mrows = append(mrows, mrow)
}
cursor := &rowsCursor{
pos: -1,
rows: mrows,
cols: s.colName,
}
return cursor, nil
}
func (s *fakeStmt) NumInput() int {
return s.placeholders
}
func (tx *fakeTx) Commit() error {
tx.c.currTx = nil
return nil
}
func (tx *fakeTx) Rollback() error {
tx.c.currTx = nil
return nil
}
type rowsCursor struct {
cols []string
pos int
rows []*row
closed bool
}
func (rc *rowsCursor) Close() error {
rc.closed = true
return nil
}
func (rc *rowsCursor) Columns() []string {
return rc.cols
}
func (rc *rowsCursor) Next(dest []interface{}) error {
if rc.closed {
return errors.New("fakedb: cursor is closed")
}
rc.pos++
if rc.pos >= len(rc.rows) {
return io.EOF // per interface spec
}
for i, v := range rc.rows[rc.pos].cols {
// TODO(bradfitz): convert to subset types? naah, I
// think the subset types should only be input to
// dbimpl, but the db package should be able to handle
// a wider range of types coming out of dbimpl
// drivers. all for ease of drivers, and to prevent
// drivers from messing up conversions or doing them
// differently.
dest[i] = v
}
return nil
}
func converterForType(typ string) dbimpl.ValueConverter {
switch typ {
case "bool":
return dbimpl.Bool
case "int32":
return dbimpl.Int32
case "string":
return dbimpl.String
}
panic("invalid fakedb column type of " + typ)
}