Merge pull request #84 from bobbyrullo/gorpy

Move gorp to its new location.
This commit is contained in:
bobbyrullo 2015-08-24 18:02:21 -07:00
commit ba7c5d5cd7
47 changed files with 4016 additions and 2965 deletions

10
Godeps/Godeps.json generated
View file

@ -19,11 +19,6 @@
"ImportPath": "github.com/andybalholm/cascadia", "ImportPath": "github.com/andybalholm/cascadia",
"Rev": "6122e68c2642b7b75c538a63b15168c6c80fb757" "Rev": "6122e68c2642b7b75c538a63b15168c6c80fb757"
}, },
{
"ImportPath": "github.com/coopernurse/gorp",
"Comment": "v1.7",
"Rev": "5d19ebd22fdcf3b9fb1c8cbfcf1d4e78e102f1e0"
},
{ {
"ImportPath": "github.com/coreos/go-oidc/http", "ImportPath": "github.com/coreos/go-oidc/http",
"Rev": "ee7cb1fb480df22f7d8c4c90199e438e454ca3b6" "Rev": "ee7cb1fb480df22f7d8c4c90199e438e454ca3b6"
@ -64,6 +59,11 @@
"ImportPath": "github.com/coreos/pkg/timeutil", "ImportPath": "github.com/coreos/pkg/timeutil",
"Rev": "fa94270d4bac0d8ae5dc6b71894e251aada93f74" "Rev": "fa94270d4bac0d8ae5dc6b71894e251aada93f74"
}, },
{
"ImportPath": "github.com/go-gorp/gorp",
"Comment": "v1.7-138-gc44345f",
"Rev": "c44345f52fc81d27c47e6b592dedaf8c28a972eb"
},
{ {
"ImportPath": "github.com/gorilla/handlers", "ImportPath": "github.com/gorilla/handlers",
"Rev": "60c7bfde3e33c201519a200a4507a158cc03a17b" "Rev": "60c7bfde3e33c201519a200a4507a158cc03a17b"

View file

@ -1,21 +0,0 @@
language: go
go:
- 1.1
- tip
services:
- mysql
- postgres
- sqlite3
before_script:
- mysql -e "CREATE DATABASE gorptest;"
- mysql -u root -e "GRANT ALL ON gorptest.* TO gorptest@localhost IDENTIFIED BY 'gorptest'"
- psql -c "CREATE DATABASE gorptest;" -U postgres
- psql -c "CREATE USER "gorptest" WITH SUPERUSER PASSWORD 'gorptest';" -U postgres
- go get github.com/lib/pq
- go get github.com/mattn/go-sqlite3
- go get github.com/ziutek/mymysql/godrv
- go get github.com/go-sql-driver/mysql
script: ./test_all.sh

View file

@ -1,6 +0,0 @@
include $(GOROOT)/src/Make.inc
TARG = github.com/coopernurse/gorp
GOFILES = gorp.go dialect.go
include $(GOROOT)/src/Make.pkg

View file

@ -1,692 +0,0 @@
package gorp
import (
"errors"
"fmt"
"reflect"
"strings"
)
// The Dialect interface encapsulates behaviors that differ across
// SQL databases. At present the Dialect is only used by CreateTables()
// but this could change in the future
type Dialect interface {
// adds a suffix to any query, usually ";"
QuerySuffix() string
// ToSqlType returns the SQL column type to use when creating a
// table of the given Go Type. maxsize can be used to switch based on
// size. For example, in MySQL []byte could map to BLOB, MEDIUMBLOB,
// or LONGBLOB depending on the maxsize
ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string
// string to append to primary key column definitions
AutoIncrStr() string
// string to bind autoincrement columns to. Empty string will
// remove reference to those columns in the INSERT statement.
AutoIncrBindValue() string
AutoIncrInsertSuffix(col *ColumnMap) string
// string to append to "create table" statement for vendor specific
// table attributes
CreateTableSuffix() string
// string to truncate tables
TruncateClause() string
// bind variable string to use when forming SQL statements
// in many dbs it is "?", but Postgres appears to use $1
//
// i is a zero based index of the bind variable in this statement
//
BindVar(i int) string
// Handles quoting of a field name to ensure that it doesn't raise any
// SQL parsing exceptions by using a reserved word as a field name.
QuoteField(field string) string
// Handles building up of a schema.database string that is compatible with
// the given dialect
//
// schema - The schema that <table> lives in
// table - The table name
QuotedTableForQuery(schema string, table string) string
// Existance clause for table creation / deletion
IfSchemaNotExists(command, schema string) string
IfTableExists(command, schema, table string) string
IfTableNotExists(command, schema, table string) string
}
// IntegerAutoIncrInserter is implemented by dialects that can perform
// inserts with automatically incremented integer primary keys. If
// the dialect can handle automatic assignment of more than just
// integers, see TargetedAutoIncrInserter.
type IntegerAutoIncrInserter interface {
InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error)
}
// TargetedAutoIncrInserter is implemented by dialects that can
// perform automatic assignment of any primary key type (i.e. strings
// for uuids, integers for serials, etc).
type TargetedAutoIncrInserter interface {
// InsertAutoIncrToTarget runs an insert operation and assigns the
// automatically generated primary key directly to the passed in
// target. The target should be a pointer to the primary key
// field of the value being inserted.
InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error
}
func standardInsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
res, err := exec.Exec(insertSql, params...)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
///////////////////////////////////////////////////////
// sqlite3 //
/////////////
type SqliteDialect struct {
suffix string
}
func (d SqliteDialect) QuerySuffix() string { return ";" }
func (d SqliteDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "integer"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "integer"
case reflect.Float64, reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "blob"
}
}
switch val.Name() {
case "NullInt64":
return "integer"
case "NullFloat64":
return "real"
case "NullBool":
return "integer"
case "Time":
return "datetime"
}
if maxsize < 1 {
maxsize = 255
}
return fmt.Sprintf("varchar(%d)", maxsize)
}
// Returns autoincrement
func (d SqliteDialect) AutoIncrStr() string {
return "autoincrement"
}
func (d SqliteDialect) AutoIncrBindValue() string {
return "null"
}
func (d SqliteDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
// Returns suffix
func (d SqliteDialect) CreateTableSuffix() string {
return d.suffix
}
// With sqlite, there technically isn't a TRUNCATE statement,
// but a DELETE FROM uses a truncate optimization:
// http://www.sqlite.org/lang_delete.html
func (d SqliteDialect) TruncateClause() string {
return "delete from"
}
// Returns "?"
func (d SqliteDialect) BindVar(i int) string {
return "?"
}
func (d SqliteDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
}
func (d SqliteDialect) QuoteField(f string) string {
return `"` + f + `"`
}
// sqlite does not have schemas like PostgreSQL does, so just escape it like normal
func (d SqliteDialect) QuotedTableForQuery(schema string, table string) string {
return d.QuoteField(table)
}
func (d SqliteDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d SqliteDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d SqliteDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}
///////////////////////////////////////////////////////
// PostgreSQL //
////////////////
type PostgresDialect struct {
suffix string
}
func (d PostgresDialect) QuerySuffix() string { return ";" }
func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
if isAutoIncr {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if isAutoIncr {
return "bigserial"
}
return "bigint"
case reflect.Float64:
return "double precision"
case reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "bytea"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double precision"
case "NullBool":
return "boolean"
case "Time":
return "timestamp with time zone"
}
if maxsize > 0 {
return fmt.Sprintf("varchar(%d)", maxsize)
} else {
return "text"
}
}
// Returns empty string
func (d PostgresDialect) AutoIncrStr() string {
return ""
}
func (d PostgresDialect) AutoIncrBindValue() string {
return "default"
}
func (d PostgresDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return " returning " + col.ColumnName
}
// Returns suffix
func (d PostgresDialect) CreateTableSuffix() string {
return d.suffix
}
func (d PostgresDialect) TruncateClause() string {
return "truncate"
}
// Returns "$(i+1)"
func (d PostgresDialect) BindVar(i int) string {
return fmt.Sprintf("$%d", i+1)
}
func (d PostgresDialect) InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error {
rows, err := exec.query(insertSql, params...)
if err != nil {
return err
}
defer rows.Close()
if rows.Next() {
err := rows.Scan(target)
return err
}
return errors.New("No serial value returned for insert: " + insertSql + " Encountered error: " + rows.Err().Error())
}
func (d PostgresDialect) QuoteField(f string) string {
return `"` + strings.ToLower(f) + `"`
}
func (d PostgresDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d PostgresDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d PostgresDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d PostgresDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}
///////////////////////////////////////////////////////
// MySQL //
///////////
// Implementation of Dialect for MySQL databases.
type MySQLDialect struct {
// Engine is the storage engine to use "InnoDB" vs "MyISAM" for example
Engine string
// Encoding is the character encoding to use for created tables
Encoding string
}
func (d MySQLDialect) QuerySuffix() string { return ";" }
func (d MySQLDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int8:
return "tinyint"
case reflect.Uint8:
return "tinyint unsigned"
case reflect.Int16:
return "smallint"
case reflect.Uint16:
return "smallint unsigned"
case reflect.Int, reflect.Int32:
return "int"
case reflect.Uint, reflect.Uint32:
return "int unsigned"
case reflect.Int64:
return "bigint"
case reflect.Uint64:
return "bigint unsigned"
case reflect.Float64, reflect.Float32:
return "double"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "mediumblob"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double"
case "NullBool":
return "tinyint"
case "Time":
return "datetime"
}
if maxsize < 1 {
maxsize = 255
}
return fmt.Sprintf("varchar(%d)", maxsize)
}
// Returns auto_increment
func (d MySQLDialect) AutoIncrStr() string {
return "auto_increment"
}
func (d MySQLDialect) AutoIncrBindValue() string {
return "null"
}
func (d MySQLDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
// Returns engine=%s charset=%s based on values stored on struct
func (d MySQLDialect) CreateTableSuffix() string {
if d.Engine == "" || d.Encoding == "" {
msg := "gorp - undefined"
if d.Engine == "" {
msg += " MySQLDialect.Engine"
}
if d.Engine == "" && d.Encoding == "" {
msg += ","
}
if d.Encoding == "" {
msg += " MySQLDialect.Encoding"
}
msg += ". Check that your MySQLDialect was correctly initialized when declared."
panic(msg)
}
return fmt.Sprintf(" engine=%s charset=%s", d.Engine, d.Encoding)
}
func (d MySQLDialect) TruncateClause() string {
return "truncate"
}
// Returns "?"
func (d MySQLDialect) BindVar(i int) string {
return "?"
}
func (d MySQLDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
}
func (d MySQLDialect) QuoteField(f string) string {
return "`" + f + "`"
}
func (d MySQLDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d MySQLDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d MySQLDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d MySQLDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}
///////////////////////////////////////////////////////
// Sql Server //
////////////////
// Implementation of Dialect for Microsoft SQL Server databases.
// Tested on SQL Server 2008 with driver: github.com/denisenkom/go-mssqldb
type SqlServerDialect struct {
suffix string
}
func (d SqlServerDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "bit"
case reflect.Int8:
return "tinyint"
case reflect.Uint8:
return "smallint"
case reflect.Int16:
return "smallint"
case reflect.Uint16:
return "int"
case reflect.Int, reflect.Int32:
return "int"
case reflect.Uint, reflect.Uint32:
return "bigint"
case reflect.Int64:
return "bigint"
case reflect.Uint64:
return "bigint"
case reflect.Float32:
return "real"
case reflect.Float64:
return "float(53)"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "varbinary"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "float(53)"
case "NullBool":
return "tinyint"
case "Time":
return "datetime"
}
if maxsize < 1 {
maxsize = 255
}
return fmt.Sprintf("varchar(%d)", maxsize)
}
// Returns auto_increment
func (d SqlServerDialect) AutoIncrStr() string {
return "identity(0,1)"
}
// Empty string removes autoincrement columns from the INSERT statements.
func (d SqlServerDialect) AutoIncrBindValue() string {
return ""
}
func (d SqlServerDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
// Returns suffix
func (d SqlServerDialect) CreateTableSuffix() string {
return d.suffix
}
func (d SqlServerDialect) TruncateClause() string {
return "delete from"
}
// Returns "?"
func (d SqlServerDialect) BindVar(i int) string {
return "?"
}
func (d SqlServerDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
}
func (d SqlServerDialect) QuoteField(f string) string {
return `"` + f + `"`
}
func (d SqlServerDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return table
}
return schema + "." + table
}
func (d SqlServerDialect) QuerySuffix() string { return ";" }
func (d SqlServerDialect) IfSchemaNotExists(command, schema string) string {
s := fmt.Sprintf("if not exists (select name from sys.schemas where name = '%s') %s", schema, command)
return s
}
func (d SqlServerDialect) IfTableExists(command, schema, table string) string {
var schema_clause string
if strings.TrimSpace(schema) != "" {
schema_clause = fmt.Sprintf("table_schema = '%s' and ", schema)
}
s := fmt.Sprintf("if exists (select * from information_schema.tables where %stable_name = '%s') %s", schema_clause, table, command)
return s
}
func (d SqlServerDialect) IfTableNotExists(command, schema, table string) string {
var schema_clause string
if strings.TrimSpace(schema) != "" {
schema_clause = fmt.Sprintf("table_schema = '%s' and ", schema)
}
s := fmt.Sprintf("if not exists (select * from information_schema.tables where %stable_name = '%s') %s", schema_clause, table, command)
return s
}
///////////////////////////////////////////////////////
// Oracle //
///////////
// Implementation of Dialect for Oracle databases.
type OracleDialect struct{}
func (d OracleDialect) QuerySuffix() string { return "" }
func (d OracleDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
if isAutoIncr {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if isAutoIncr {
return "bigserial"
}
return "bigint"
case reflect.Float64:
return "double precision"
case reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "bytea"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double precision"
case "NullBool":
return "boolean"
case "NullTime", "Time":
return "timestamp with time zone"
}
if maxsize > 0 {
return fmt.Sprintf("varchar(%d)", maxsize)
} else {
return "text"
}
}
// Returns empty string
func (d OracleDialect) AutoIncrStr() string {
return ""
}
func (d OracleDialect) AutoIncrBindValue() string {
return "default"
}
func (d OracleDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return " returning " + col.ColumnName
}
// Returns suffix
func (d OracleDialect) CreateTableSuffix() string {
return ""
}
func (d OracleDialect) TruncateClause() string {
return "truncate"
}
// Returns "$(i+1)"
func (d OracleDialect) BindVar(i int) string {
return fmt.Sprintf(":%d", i+1)
}
func (d OracleDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
rows, err := exec.query(insertSql, params...)
if err != nil {
return 0, err
}
defer rows.Close()
if rows.Next() {
var id int64
err := rows.Scan(&id)
return id, err
}
return 0, errors.New("No serial value returned for insert: " + insertSql + " Encountered error: " + rows.Err().Error())
}
func (d OracleDialect) QuoteField(f string) string {
return `"` + strings.ToUpper(f) + `"`
}
func (d OracleDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d OracleDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d OracleDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d OracleDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}

View file

@ -1,26 +0,0 @@
package gorp
import (
"fmt"
)
// A non-fatal error, when a select query returns columns that do not exist
// as fields in the struct it is being mapped to
type NoFieldInTypeError struct {
TypeName string
MissingColNames []string
}
func (err *NoFieldInTypeError) Error() string {
return fmt.Sprintf("gorp: No fields %+v in type %s", err.MissingColNames, err.TypeName)
}
// returns true if the error is non-fatal (ie, we shouldn't immediately return)
func NonFatalError(err error) bool {
switch err.(type) {
case *NoFieldInTypeError:
return true
default:
return false
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,22 +0,0 @@
#!/bin/sh
# on macs, you may need to:
# export GOBUILDFLAG=-ldflags -linkmode=external
set -e
export GORP_TEST_DSN=gorptest/gorptest/gorptest
export GORP_TEST_DIALECT=mysql
go test $GOBUILDFLAG .
export GORP_TEST_DSN=gorptest:gorptest@/gorptest
export GORP_TEST_DIALECT=gomysql
go test $GOBUILDFLAG .
export GORP_TEST_DSN="user=gorptest password=gorptest dbname=gorptest sslmode=disable"
export GORP_TEST_DIALECT=postgres
go test $GOBUILDFLAG .
export GORP_TEST_DSN=/tmp/gorptest.bin
export GORP_TEST_DIALECT=sqlite
go test $GOBUILDFLAG .

View file

@ -0,0 +1,29 @@
language: go
go:
- 1.2
- 1.3
- 1.4
- tip
services:
- mysql
- postgres
- sqlite3
env:
global:
- secure: RriLxF6+2yMl67hdVv8ImXlu0h62mhcpqjaOgYNU+IEbUQ7hx96CKY6gkpYubW3BgApvF5RH6j3+HKvh2kGp0XhDOYOQCODfBSaSipZ5Aa5RKjsEYLtuVIobvJ80awR9hUeql69+WXs0/s72WThG0qTbOUY4pqHWfteeY235hWM=
before_script:
- mysql -e "CREATE DATABASE gorptest;"
- mysql -u root -e "GRANT ALL ON gorptest.* TO gorptest@localhost IDENTIFIED BY 'gorptest'"
- psql -c "CREATE DATABASE gorptest;" -U postgres
- psql -c "CREATE USER "gorptest" WITH SUPERUSER PASSWORD 'gorptest';" -U postgres
- go get github.com/lib/pq
- go get github.com/mattn/go-sqlite3
- go get github.com/ziutek/mymysql/godrv
- go get github.com/go-sql-driver/mysql
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script: ./test_all.sh

View file

@ -1,19 +1,26 @@
# Go Relational Persistence # # Go Relational Persistence
[![build status](https://secure.travis-ci.org/coopernurse/gorp.png)](http://travis-ci.org/coopernurse/gorp) [![build status](https://img.shields.io/travis/go-gorp/gorp.svg)](http://travis-ci.org/go-gorp/gorp)
[![code coverage](https://img.shields.io/coveralls/go-gorp/gorp.svg)](https://coveralls.io/r/go-gorp/gorp)
[![issues](https://img.shields.io/github/issues/go-gorp/gorp.svg)](https://github.com/go-gorp/gorp/issues)
[![godoc v1](https://img.shields.io/badge/godoc-v1-375EAB.svg)](https://godoc.org/gopkg.in/gorp.v1)
[![godoc bleeding edge](https://img.shields.io/badge/godoc-bleeding--edge-375EAB.svg)](https://godoc.org/github.com/go-gorp/gorp)
I hesitate to call gorp an ORM. Go doesn't really have objects, at least ### Update 2015-07-01 Cleanup & feature freeze ([#270](https://github.com/go-gorp/gorp/issues/270))
not in the classic Smalltalk/Java sense. There goes the "O". gorp doesn't
know anything about the relationships between your structs (at least not
yet). So the "R" is questionable too (but I use it in the name because,
well, it seemed more clever).
The "M" is alive and well. Given some Go structs and a database, gorp We are currently cleaning up the backlog of issues and PR's. When this is done the codebase will be split into separate files and there will be breaking changes to the API's. We're also adding better tests and documentation. As a result of these changes the `master` branch will be unstable. Please use `gopkg.in/gorp.v1`. When the cleanup and changes are done, we will release `v2.0`.
should remove a fair amount of boilerplate busy-work from your code.
I hope that gorp saves you time, minimizes the drudgery of getting data At this time we won't accept new feature-related pull-requests because of changes to the codebase. Please create an issue for your feature and wait until `v2.0` has been released.
in and out of your database, and helps your code focus on algorithms,
not infrastructure. For more information, please read [#270](https://github.com/go-gorp/gorp/issues/270).
## Introduction
I hesitate to call gorp an ORM. Go doesn't really have objects, at least not in the classic Smalltalk/Java sense. There goes the "O". gorp doesn't know anything about the relationships between your structs (at least not yet). So the "R" is questionable too (but I use it in the name because, well, it seemed more clever).
The "M" is alive and well. Given some Go structs and a database, gorp should remove a fair amount of boilerplate busy-work from your code.
I hope that gorp saves you time, minimizes the drudgery of getting data in and out of your database, and helps your code focus on algorithms, not infrastructure.
* Bind struct fields to table columns via API or tag * Bind struct fields to table columns via API or tag
* Support for embedded structs * Support for embedded structs
@ -30,21 +37,40 @@ not infrastructure.
* Use positional or named bind parameters in custom SELECT queries * Use positional or named bind parameters in custom SELECT queries
* Optional optimistic locking using a version column (for update/deletes) * Optional optimistic locking using a version column (for update/deletes)
## Installation ## ## Installation
# install the library: # install the library:
go get github.com/coopernurse/gorp go get gopkg.in/gorp.v1
// use in your .go code: // use in your .go code:
import ( import (
"github.com/coopernurse/gorp" "gopkg.in/gorp.v1"
) )
## API Documentation ## ## Versioning
Full godoc output from the latest code in master is available here: This project provides a stable release (v1.x tags) and a bleeding edge codebase (master).
http://godoc.org/github.com/coopernurse/gorp `gopkg.in/gorp.v1` points to the latest v1.x tag. The API's for v1 are stable and shouldn't change. Development takes place at the master branch. Althought the code in master should always compile and test successfully, it might break API's. We aim to maintain backwards compatibility, but API's and behaviour might be changed to fix a bug. Also note that API's that are new in the master branch can change until released as v2.
If you want to use bleeding edge, use `github.com/go-gorp/gorp` as import path.
## API Documentation
Full godoc output from the latest v1 release is available here:
https://godoc.org/gopkg.in/gorp.v1
For the latest code in master:
https://godoc.org/github.com/go-gorp/gorp
## Supported Go versions
This package is compatible with the last 2 major versions of Go, at this time `1.3` and `1.4`.
Any earlier versions are only supported on a best effort basis and can be dropped any time.
Go has a great compatibility promise. Upgrading your program to a newer version of Go should never really be a problem.
## Quickstart ## Quickstart
@ -53,7 +79,7 @@ package main
import ( import (
"database/sql" "database/sql"
"github.com/coopernurse/gorp" "gopkg.in/gorp.v1"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"log" "log"
"time" "time"
@ -126,8 +152,8 @@ type Post struct {
// db tag lets you specify the column name if it differs from the struct field // db tag lets you specify the column name if it differs from the struct field
Id int64 `db:"post_id"` Id int64 `db:"post_id"`
Created int64 Created int64
Title string Title string `db:",size:50"` // Column size set to 50
Body string Body string `db:"article_body,size:1024"` // Set both column name and size
} }
func newPost(title, body string) Post { func newPost(title, body string) Post {
@ -166,9 +192,9 @@ func checkErr(err error, msg string) {
} }
``` ```
## Examples ## ## Examples
### Mapping structs to tables ### ### Mapping structs to tables
First define some types: First define some types:
@ -202,8 +228,10 @@ type Person struct {
// table.ColMap("Price").Rename("unit_price") // table.ColMap("Price").Rename("unit_price")
// table.ColMap("IgnoreMe").SetTransient(true) // table.ColMap("IgnoreMe").SetTransient(true)
// //
// You can optionally declare the field to be a primary key and/or autoincrement
//
type Product struct { type Product struct {
Id int64 `db:"product_id"` Id int64 `db:"product_id, primarykey, autoincrement"`
Price int64 `db:"unit_price"` Price int64 `db:"unit_price"`
IgnoreMe string `db:"-"` IgnoreMe string `db:"-"`
} }
@ -231,7 +259,7 @@ t2 := dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id")
t3 := dbmap.AddTableWithName(Product{}, "product_test").SetKeys(true, "Id") t3 := dbmap.AddTableWithName(Product{}, "product_test").SetKeys(true, "Id")
``` ```
### Struct Embedding ### ### Struct Embedding
gorp supports embedding structs. For example: gorp supports embedding structs. For example:
@ -256,7 +284,7 @@ See the `TestWithEmbeddedStruct` function in `gorp_test.go` for a full example.
Automatically create / drop registered tables. This is useful for unit tests Automatically create / drop registered tables. This is useful for unit tests
but is entirely optional. You can of course use gorp with tables created manually, but is entirely optional. You can of course use gorp with tables created manually,
or with a separate migration tool (like goose: https://bitbucket.org/liamstask/goose). or with a separate migration tool (like [goose](https://bitbucket.org/liamstask/goose) or [migrate](https://github.com/mattes/migrate)).
```go ```go
// create all registered tables // create all registered tables
@ -270,7 +298,7 @@ dbmap.CreateTablesIfNotExists()
dbmap.DropTables() dbmap.DropTables()
``` ```
### SQL Logging ### ### SQL Logging
Optionally you can pass in a logger to trace all SQL statements. Optionally you can pass in a logger to trace all SQL statements.
I recommend enabling this initially while you're getting the feel for what I recommend enabling this initially while you're getting the feel for what
@ -289,7 +317,7 @@ dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds))
dbmap.TraceOff() dbmap.TraceOff()
``` ```
### Insert ### ### Insert
```go ```go
// Must declare as pointers so optional callback hooks // Must declare as pointers so optional callback hooks
@ -305,7 +333,7 @@ err := dbmap.Insert(inv1, inv2)
fmt.Printf("inv1.Id=%d inv2.Id=%d\n", inv1.Id, inv2.Id) fmt.Printf("inv1.Id=%d inv2.Id=%d\n", inv1.Id, inv2.Id)
``` ```
### Update ### ### Update
Continuing the above example, use the `Update` method to modify an Invoice: Continuing the above example, use the `Update` method to modify an Invoice:
@ -314,7 +342,7 @@ Continuing the above example, use the `Update` method to modify an Invoice:
count, err := dbmap.Update(inv1) count, err := dbmap.Update(inv1)
``` ```
### Delete ### ### Delete
If you have primary key(s) defined for a struct, you can use the `Delete` If you have primary key(s) defined for a struct, you can use the `Delete`
method to remove rows: method to remove rows:
@ -323,7 +351,7 @@ method to remove rows:
count, err := dbmap.Delete(inv1) count, err := dbmap.Delete(inv1)
``` ```
### Select by Key ### ### Select by Key
Use the `Get` method to fetch a single row by primary key. It returns Use the `Get` method to fetch a single row by primary key. It returns
nil if no row is found. nil if no row is found.
@ -334,9 +362,9 @@ obj, err := dbmap.Get(Invoice{}, 99)
inv := obj.(*Invoice) inv := obj.(*Invoice)
``` ```
### Ad Hoc SQL ### ### Ad Hoc SQL
#### SELECT #### #### SELECT
`Select()` and `SelectOne()` provide a simple way to bind arbitrary queries to a slice `Select()` and `SelectOne()` provide a simple way to bind arbitrary queries to a slice
or a single struct. or a single struct.
@ -396,7 +424,7 @@ if reflect.DeepEqual(list[0], expected) {
} }
``` ```
#### SELECT string or int64 #### #### SELECT string or int64
gorp provides a few convenience methods for selecting a single string or int64. gorp provides a few convenience methods for selecting a single string or int64.
@ -409,7 +437,7 @@ s, err := dbmap.SelectStr("select name from foo where blah=?", blahVal)
``` ```
#### Named bind parameters #### #### Named bind parameters
You may use a map or struct to bind parameters by name. This is currently You may use a map or struct to bind parameters by name. This is currently
only supported in SELECT queries. only supported in SELECT queries.
@ -421,7 +449,7 @@ _, err := dbm.Select(&dest, "select * from Foo where name = :name and age = :age
}) })
``` ```
#### UPDATE / DELETE #### #### UPDATE / DELETE
You can execute raw SQL if you wish. Particularly good for batch operations. You can execute raw SQL if you wish. Particularly good for batch operations.
@ -429,7 +457,7 @@ You can execute raw SQL if you wish. Particularly good for batch operations.
res, err := dbmap.Exec("delete from invoice_test where PersonId=?", 10) res, err := dbmap.Exec("delete from invoice_test where PersonId=?", 10)
``` ```
### Transactions ### ### Transactions
You can batch operations into a transaction: You can batch operations into a transaction:
@ -450,7 +478,7 @@ func InsertInv(dbmap *DbMap, inv *Invoice, per *Person) error {
} }
``` ```
### Hooks ### ### Hooks
Use hooks to update data before/after saving to the db. Good for timestamps: Use hooks to update data before/after saving to the db. Good for timestamps:
@ -495,7 +523,9 @@ Full list of hooks that you can implement:
func (p *MyStruct) PostUpdate(s gorp.SqlExecutor) error func (p *MyStruct) PostUpdate(s gorp.SqlExecutor) error
### Optimistic Locking ### ### Optimistic Locking
#### Note that this behaviour has changed in v2. See [Migration Guide](#migration-guide).
gorp provides a simple optimistic locking feature, similar to Java's JPA, that gorp provides a simple optimistic locking feature, similar to Java's JPA, that
will raise an error if you try to update/delete a row whose `version` column will raise an error if you try to update/delete a row whose `version` column
@ -547,8 +577,51 @@ if ok {
fmt.Printf("Unknown db err: %v\n", err) fmt.Printf("Unknown db err: %v\n", err)
} }
``` ```
### Adding INDEX(es) on column(s) beyond the primary key ###
## Database Drivers ## Indexes are frequently critical for performance. Here is how to add them to your tables.
NB: SqlServer and Oracle need testing and possible adjustment to the
CreateIndexSuffix() and DropIndexSuffix() methods to make AddIndex()
work for them.
In the example below we put an index both on the Id field, and on the AcctId field.
```
type Account struct {
Id int64
AcctId string // e.g. this might be a long uuid for portability
}
// indexType (the 2nd param to AddIndex call) is "Btree" or "Hash" for MySQL.
// demonstrate adding a second index on AcctId, and constrain that field to have unique values.
dbm.AddTable(iptab.Account{}).SetKeys(true, "Id").AddIndex("AcctIdIndex", "Btree", []string{"AcctId"}).SetUnique(true)
err = dbm.CreateTablesIfNotExists()
checkErr(err, "CreateTablesIfNotExists failed")
err = dbm.CreateIndex()
checkErr(err, "CreateIndex failed")
```
Check the effect of the CreateIndex() call in mysql:
```
$ mysql
MariaDB [test]> show create table Account;
+---------+--------------------------+
| Account | CREATE TABLE `Account` (
`Id` bigint(20) NOT NULL AUTO_INCREMENT,
`AcctId` varchar(255) DEFAULT NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `AcctIdIndex` (`AcctId`) USING BTREE <<<--- yes! index added.
) ENGINE=InnoDB DEFAULT CHARSET=utf8
+---------+--------------------------+
```
## Database Drivers
gorp uses the Go 1 `database/sql` package. A full list of compliant drivers is available here: gorp uses the Go 1 `database/sql` package. A full list of compliant drivers is available here:
@ -573,9 +646,9 @@ Note that these databases are not covered by CI and I (@coopernurse) have no goo
test them locally. So please try them and send patches as needed, but expect a bit more test them locally. So please try them and send patches as needed, but expect a bit more
unpredicability. unpredicability.
## Known Issues ## ## Known Issues
### SQL placeholder portability ### ### SQL placeholder portability
Different databases use different strings to indicate variable placeholders in Different databases use different strings to indicate variable placeholders in
prepared SQL statements. Unlike some database abstraction layers (such as JDBC), prepared SQL statements. Unlike some database abstraction layers (such as JDBC),
@ -600,7 +673,12 @@ err := dbmap.SelectOne(&val, "select * from foo where id = :id",
map[string]interface{} { "id": 30}) map[string]interface{} { "id": 30})
``` ```
### time.Time and time zones ### Additionally, when using Postgres as your database, you should utilize `$1` instead
of `?` placeholders as utilizing `?` placeholders when querying Postgres will result
in `pq: operator does not exist` errors. Alternatively, use
`dbMap.Dialect.BindVar(varIdx)` to get the proper variable binding for your dialect.
### time.Time and time zones
gorp will pass `time.Time` fields through to the `database/sql` driver, but note that gorp will pass `time.Time` fields through to the `database/sql` driver, but note that
the behavior of this type varies across database drivers. the behavior of this type varies across database drivers.
@ -610,7 +688,7 @@ MySQL users should be especially cautious. See: https://github.com/ziutek/mymys
To avoid any potential issues with timezone/DST, consider using an integer field for time To avoid any potential issues with timezone/DST, consider using an integer field for time
data and storing UNIX time. data and storing UNIX time.
## Running the tests ## ## Running the tests
The included tests may be run against MySQL, Postgresql, or sqlite3. The included tests may be run against MySQL, Postgresql, or sqlite3.
You must set two environment variables so the test code knows which driver to You must set two environment variables so the test code knows which driver to
@ -628,20 +706,29 @@ go test
go test -bench="Bench" -benchtime 10 go test -bench="Bench" -benchtime 10
``` ```
Valid `GORP_TEST_DIALECT` values are: "mysql", "postgres", "sqlite3" Valid `GORP_TEST_DIALECT` values are: "mysql"(for mymysql), "gomysql"(for go-sql-driver), "postgres", "sqlite"
See the `test_all.sh` script for examples of all 3 databases. This is the script I run See the `test_all.sh` script for examples of all 3 databases. This is the script I run
locally to test the library. locally to test the library.
## Performance ## ## Performance
gorp uses reflection to construct SQL queries and bind parameters. See the BenchmarkNativeCrud vs BenchmarkGorpCrud in gorp_test.go for a simple perf test. On my MacBook Pro gorp is about 2-3% slower than hand written SQL. gorp uses reflection to construct SQL queries and bind parameters. See the BenchmarkNativeCrud vs BenchmarkGorpCrud in gorp_test.go for a simple perf test. On my MacBook Pro gorp is about 2-3% slower than hand written SQL.
## Migration guide
#### Pre-v2 to v2
Automatic mapping of the version column used in optimistic locking has been removed as it could cause problems if the type was not int. The version column must now explicitly be set with tablemap.SetVersionCol().
## Help/Support
IRC: #gorp
Mailing list: gorp-dev@googlegroups.com
Bugs/Enhancements: Create a github issue
## Pull requests / Contributions ## Pull requests / Contributions
Contributions are very welcome. Please follow these guidelines: Contributions are very welcome. Please follow these guidelines:
* Fork the `develop` branch and issue pull requests targeting the `develop` branch * Fork the `master` branch and issue pull requests targeting the `master` branch
* If you don't do this, I'll likely cherry pick your commit into develop
* If you are adding an enhancement, please open an issue first with your proposed change. * If you are adding an enhancement, please open an issue first with your proposed change.
* Changes that break backwards compatibility in the public API are only accepted after we * Changes that break backwards compatibility in the public API are only accepted after we
discuss on a GitHub issue for a while. discuss on a GitHub issue for a while.

View file

@ -0,0 +1,83 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import "reflect"
// ColumnMap represents a mapping between a Go struct field and a single
// column in a table.
// Unique and MaxSize only inform the
// CreateTables() function and are not used by Insert/Update/Delete/Get.
type ColumnMap struct {
// Column name in db table
ColumnName string
// If true, this column is skipped in generated SQL statements
Transient bool
// If true, " unique" is added to create table statements.
// Not used elsewhere
Unique bool
// Query used for getting generated id after insert
GeneratedIdQuery string
// Passed to Dialect.ToSqlType() to assist in informing the
// correct column type to map to in CreateTables()
MaxSize int
DefaultValue string
fieldName string
gotype reflect.Type
isPK bool
isAutoIncr bool
isNotNull bool
}
// Rename allows you to specify the column name in the table
//
// Example: table.ColMap("Updated").Rename("date_updated")
//
func (c *ColumnMap) Rename(colname string) *ColumnMap {
c.ColumnName = colname
return c
}
// SetTransient allows you to mark the column as transient. If true
// this column will be skipped when SQL statements are generated
func (c *ColumnMap) SetTransient(b bool) *ColumnMap {
c.Transient = b
return c
}
// SetUnique adds "unique" to the create table statements for this
// column, if b is true.
func (c *ColumnMap) SetUnique(b bool) *ColumnMap {
c.Unique = b
return c
}
// SetNotNull adds "not null" to the create table statements for this
// column, if nn is true.
func (c *ColumnMap) SetNotNull(nn bool) *ColumnMap {
c.isNotNull = nn
return c
}
// SetMaxSize specifies the max length of values of this column. This is
// passed to the dialect.ToSqlType() function, which can use the value
// to alter the generated type for "create table" statements
func (c *ColumnMap) SetMaxSize(size int) *ColumnMap {
c.MaxSize = size
return c
}

604
Godeps/_workspace/src/github.com/go-gorp/gorp/db.go generated vendored Normal file
View file

@ -0,0 +1,604 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"bytes"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
// DbMap is the root gorp mapping object. Create one of these for each
// database schema you wish to map. Each DbMap contains a list of
// mapped tables.
//
// Example:
//
// dialect := gorp.MySQLDialect{"InnoDB", "UTF8"}
// dbmap := &gorp.DbMap{Db: db, Dialect: dialect}
//
type DbMap struct {
// Db handle to use with this map
Db *sql.DB
// Dialect implementation to use with this map
Dialect Dialect
TypeConverter TypeConverter
tables []*TableMap
logger GorpLogger
logPrefix string
}
func (m *DbMap) CreateIndex() error {
var err error
dialect := reflect.TypeOf(m.Dialect)
for _, table := range m.tables {
for _, index := range table.indexes {
s := bytes.Buffer{}
s.WriteString("create")
if index.Unique {
s.WriteString(" unique")
}
s.WriteString(" index")
s.WriteString(fmt.Sprintf(" %s on %s", index.IndexName, table.TableName))
if dname := dialect.Name(); dname == "PostgresDialect" && index.IndexType != "" {
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
}
s.WriteString(" (")
x := 0
for _, col := range index.columns {
if x > 0 {
s.WriteString(", ")
}
s.WriteString(m.Dialect.QuoteField(col))
}
s.WriteString(")")
if dname := dialect.Name(); dname == "MySQLDialect" && index.IndexType != "" {
s.WriteString(fmt.Sprintf(" %s %s", m.Dialect.CreateIndexSuffix(), index.IndexType))
}
s.WriteString(";")
_, err = m.Exec(s.String())
if err != nil {
break
}
}
}
return err
}
func (t *TableMap) DropIndex(name string) error {
var err error
dialect := reflect.TypeOf(t.dbmap.Dialect)
for _, idx := range t.indexes {
if idx.IndexName == name {
s := bytes.Buffer{}
s.WriteString(fmt.Sprintf("DROP INDEX %s", idx.IndexName))
if dname := dialect.Name(); dname == "MySQLDialect" {
s.WriteString(fmt.Sprintf(" %s %s", t.dbmap.Dialect.DropIndexSuffix(), t.TableName))
}
s.WriteString(";")
_, e := t.dbmap.Exec(s.String())
if e != nil {
err = e
}
break
}
}
t.ResetSql()
return err
}
// AddTable registers the given interface type with gorp. The table name
// will be given the name of the TypeOf(i). You must call this function,
// or AddTableWithName, for any struct type you wish to persist with
// the given DbMap.
//
// This operation is idempotent. If i's type is already mapped, the
// existing *TableMap is returned
func (m *DbMap) AddTable(i interface{}) *TableMap {
return m.AddTableWithName(i, "")
}
// AddTableWithName has the same behavior as AddTable, but sets
// table.TableName to name.
func (m *DbMap) AddTableWithName(i interface{}, name string) *TableMap {
return m.AddTableWithNameAndSchema(i, "", name)
}
// AddTableWithNameAndSchema has the same behavior as AddTable, but sets
// table.TableName to name.
func (m *DbMap) AddTableWithNameAndSchema(i interface{}, schema string, name string) *TableMap {
t := reflect.TypeOf(i)
if name == "" {
name = t.Name()
}
// check if we have a table for this type already
// if so, update the name and return the existing pointer
for i := range m.tables {
table := m.tables[i]
if table.gotype == t {
table.TableName = name
return table
}
}
tmap := &TableMap{gotype: t, TableName: name, SchemaName: schema, dbmap: m}
var primaryKey []*ColumnMap
tmap.Columns, primaryKey = m.readStructColumns(t)
m.tables = append(m.tables, tmap)
if len(primaryKey) > 0 {
tmap.keys = append(tmap.keys, primaryKey...)
}
return tmap
}
func (m *DbMap) readStructColumns(t reflect.Type) (cols []*ColumnMap, primaryKey []*ColumnMap) {
primaryKey = make([]*ColumnMap, 0)
n := t.NumField()
for i := 0; i < n; i++ {
f := t.Field(i)
if f.Anonymous && f.Type.Kind() == reflect.Struct {
// Recursively add nested fields in embedded structs.
subcols, subpk := m.readStructColumns(f.Type)
// Don't append nested fields that have the same field
// name as an already-mapped field.
for _, subcol := range subcols {
shouldAppend := true
for _, col := range cols {
if !subcol.Transient && subcol.fieldName == col.fieldName {
shouldAppend = false
break
}
}
if shouldAppend {
cols = append(cols, subcol)
}
}
if subpk != nil {
primaryKey = append(primaryKey, subpk...)
}
} else {
// Tag = Name { ',' Option }
// Option = OptionKey [ ':' OptionValue ]
cArguments := strings.Split(f.Tag.Get("db"), ",")
columnName := cArguments[0]
var maxSize int
var defaultValue string
var isAuto bool
var isPK bool
for _, argString := range cArguments[1:] {
argString = strings.TrimSpace(argString)
arg := strings.SplitN(argString, ":", 2)
// check mandatory/unexpected option values
switch arg[0] {
case "size", "default":
// options requiring value
if len(arg) == 1 {
panic(fmt.Sprintf("missing option value for option %v on field %v", arg[0], f.Name))
}
default:
// options where value is invalid (currently all other options)
if len(arg) == 2 {
panic(fmt.Sprintf("unexpected option value for option %v on field %v", arg[0], f.Name))
}
}
switch arg[0] {
case "size":
maxSize, _ = strconv.Atoi(arg[1])
case "default":
defaultValue = arg[1]
case "primarykey":
isPK = true
case "autoincrement":
isAuto = true
default:
panic(fmt.Sprintf("Unrecognized tag option for field %v: %v", f.Name, arg))
}
}
if columnName == "" {
columnName = f.Name
}
gotype := f.Type
value := reflect.New(gotype).Interface()
if m.TypeConverter != nil {
// Make a new pointer to a value of type gotype and
// pass it to the TypeConverter's FromDb method to see
// if a different type should be used for the column
// type during table creation.
scanner, useHolder := m.TypeConverter.FromDb(value)
if useHolder {
value = scanner.Holder
gotype = reflect.TypeOf(value)
}
}
if typer, ok := value.(SqlTyper); ok {
gotype = reflect.TypeOf(typer.SqlType())
} else if valuer, ok := value.(driver.Valuer); ok {
// Only check for driver.Valuer if SqlTyper wasn't
// found.
v, err := valuer.Value()
if err == nil && v != nil {
gotype = reflect.TypeOf(v)
}
}
cm := &ColumnMap{
ColumnName: columnName,
DefaultValue: defaultValue,
Transient: columnName == "-",
fieldName: f.Name,
gotype: gotype,
isPK: isPK,
isAutoIncr: isAuto,
MaxSize: maxSize,
}
if isPK {
primaryKey = append(primaryKey, cm)
}
// Check for nested fields of the same field name and
// override them.
shouldAppend := true
for index, col := range cols {
if !col.Transient && col.fieldName == cm.fieldName {
cols[index] = cm
shouldAppend = false
break
}
}
if shouldAppend {
cols = append(cols, cm)
}
}
}
return
}
// CreateTables iterates through TableMaps registered to this DbMap and
// executes "create table" statements against the database for each.
//
// This is particularly useful in unit tests where you want to create
// and destroy the schema automatically.
func (m *DbMap) CreateTables() error {
return m.createTables(false)
}
// CreateTablesIfNotExists is similar to CreateTables, but starts
// each statement with "create table if not exists" so that existing
// tables do not raise errors
func (m *DbMap) CreateTablesIfNotExists() error {
return m.createTables(true)
}
func (m *DbMap) createTables(ifNotExists bool) error {
var err error
for i := range m.tables {
table := m.tables[i]
sql := table.SqlForCreate(ifNotExists)
_, err = m.Exec(sql)
if err != nil {
break
}
}
return err
}
// DropTable drops an individual table.
// Returns an error when the table does not exist.
func (m *DbMap) DropTable(table interface{}) error {
t := reflect.TypeOf(table)
return m.dropTable(t, false)
}
// DropTableIfExists drops an individual table when the table exists.
func (m *DbMap) DropTableIfExists(table interface{}) error {
t := reflect.TypeOf(table)
return m.dropTable(t, true)
}
// DropTables iterates through TableMaps registered to this DbMap and
// executes "drop table" statements against the database for each.
func (m *DbMap) DropTables() error {
return m.dropTables(false)
}
// DropTablesIfExists is the same as DropTables, but uses the "if exists" clause to
// avoid errors for tables that do not exist.
func (m *DbMap) DropTablesIfExists() error {
return m.dropTables(true)
}
// Goes through all the registered tables, dropping them one by one.
// If an error is encountered, then it is returned and the rest of
// the tables are not dropped.
func (m *DbMap) dropTables(addIfExists bool) (err error) {
for _, table := range m.tables {
err = m.dropTableImpl(table, addIfExists)
if err != nil {
return err
}
}
return err
}
// Implementation of dropping a single table.
func (m *DbMap) dropTable(t reflect.Type, addIfExists bool) error {
table := tableOrNil(m, t)
if table == nil {
return fmt.Errorf("table %s was not registered", table.TableName)
}
return m.dropTableImpl(table, addIfExists)
}
func (m *DbMap) dropTableImpl(table *TableMap, ifExists bool) (err error) {
tableDrop := "drop table"
if ifExists {
tableDrop = m.Dialect.IfTableExists(tableDrop, table.SchemaName, table.TableName)
}
_, err = m.Exec(fmt.Sprintf("%s %s;", tableDrop, m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
return err
}
// TruncateTables iterates through TableMaps registered to this DbMap and
// executes "truncate table" statements against the database for each, or in the case of
// sqlite, a "delete from" with no "where" clause, which uses the truncate optimization
// (http://www.sqlite.org/lang_delete.html)
func (m *DbMap) TruncateTables() error {
var err error
for i := range m.tables {
table := m.tables[i]
_, e := m.Exec(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
if e != nil {
err = e
}
}
return err
}
// Insert runs a SQL INSERT statement for each element in list. List
// items must be pointers.
//
// Any interface whose TableMap has an auto-increment primary key will
// have its last insert id bound to the PK field on the struct.
//
// The hook functions PreInsert() and/or PostInsert() will be executed
// before/after the INSERT statement if the interface defines them.
//
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) Insert(list ...interface{}) error {
return insert(m, m, list...)
}
// Update runs a SQL UPDATE statement for each element in list. List
// items must be pointers.
//
// The hook functions PreUpdate() and/or PostUpdate() will be executed
// before/after the UPDATE statement if the interface defines them.
//
// Returns the number of rows updated.
//
// Returns an error if SetKeys has not been called on the TableMap
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) Update(list ...interface{}) (int64, error) {
return update(m, m, list...)
}
// Delete runs a SQL DELETE statement for each element in list. List
// items must be pointers.
//
// The hook functions PreDelete() and/or PostDelete() will be executed
// before/after the DELETE statement if the interface defines them.
//
// Returns the number of rows deleted.
//
// Returns an error if SetKeys has not been called on the TableMap
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) Delete(list ...interface{}) (int64, error) {
return delete(m, m, list...)
}
// Get runs a SQL SELECT to fetch a single row from the table based on the
// primary key(s)
//
// i should be an empty value for the struct to load. keys should be
// the primary key value(s) for the row to load. If multiple keys
// exist on the table, the order should match the column order
// specified in SetKeys() when the table mapping was defined.
//
// The hook function PostGet() will be executed after the SELECT
// statement if the interface defines them.
//
// Returns a pointer to a struct that matches or nil if no row is found.
//
// Returns an error if SetKeys has not been called on the TableMap
// Panics if any interface in the list has not been registered with AddTable
func (m *DbMap) Get(i interface{}, keys ...interface{}) (interface{}, error) {
return get(m, m, i, keys...)
}
// Select runs an arbitrary SQL query, binding the columns in the result
// to fields on the struct specified by i. args represent the bind
// parameters for the SQL statement.
//
// Column names on the SELECT statement should be aliased to the field names
// on the struct i. Returns an error if one or more columns in the result
// do not match. It is OK if fields on i are not part of the SQL
// statement.
//
// The hook function PostGet() will be executed after the SELECT
// statement if the interface defines them.
//
// Values are returned in one of two ways:
// 1. If i is a struct or a pointer to a struct, returns a slice of pointers to
// matching rows of type i.
// 2. If i is a pointer to a slice, the results will be appended to that slice
// and nil returned.
//
// i does NOT need to be registered with AddTable()
func (m *DbMap) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) {
return hookedselect(m, m, i, query, args...)
}
// Exec runs an arbitrary SQL statement. args represent the bind parameters.
// This is equivalent to running: Exec() using database/sql
func (m *DbMap) Exec(query string, args ...interface{}) (sql.Result, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return exec(m, query, args...)
}
// SelectInt is a convenience wrapper around the gorp.SelectInt function
func (m *DbMap) SelectInt(query string, args ...interface{}) (int64, error) {
return SelectInt(m, query, args...)
}
// SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function
func (m *DbMap) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) {
return SelectNullInt(m, query, args...)
}
// SelectFloat is a convenience wrapper around the gorp.SelectFloat function
func (m *DbMap) SelectFloat(query string, args ...interface{}) (float64, error) {
return SelectFloat(m, query, args...)
}
// SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function
func (m *DbMap) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) {
return SelectNullFloat(m, query, args...)
}
// SelectStr is a convenience wrapper around the gorp.SelectStr function
func (m *DbMap) SelectStr(query string, args ...interface{}) (string, error) {
return SelectStr(m, query, args...)
}
// SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function
func (m *DbMap) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) {
return SelectNullStr(m, query, args...)
}
// SelectOne is a convenience wrapper around the gorp.SelectOne function
func (m *DbMap) SelectOne(holder interface{}, query string, args ...interface{}) error {
return SelectOne(m, m, holder, query, args...)
}
// Begin starts a gorp Transaction
func (m *DbMap) Begin() (*Transaction, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, "begin;")
}
tx, err := m.Db.Begin()
if err != nil {
return nil, err
}
return &Transaction{m, tx, false}, nil
}
// TableFor returns the *TableMap corresponding to the given Go Type
// If no table is mapped to that type an error is returned.
// If checkPK is true and the mapped table has no registered PKs, an error is returned.
func (m *DbMap) TableFor(t reflect.Type, checkPK bool) (*TableMap, error) {
table := tableOrNil(m, t)
if table == nil {
return nil, fmt.Errorf("no table found for type: %v", t.Name())
}
if checkPK && len(table.keys) < 1 {
e := fmt.Sprintf("gorp: no keys defined for table: %s",
table.TableName)
return nil, errors.New(e)
}
return table, nil
}
// Prepare creates a prepared statement for later queries or executions.
// Multiple queries or executions may be run concurrently from the returned statement.
// This is equivalent to running: Prepare() using database/sql
func (m *DbMap) Prepare(query string) (*sql.Stmt, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, nil)
}
return m.Db.Prepare(query)
}
func tableOrNil(m *DbMap, t reflect.Type) *TableMap {
for i := range m.tables {
table := m.tables[i]
if table.gotype == t {
return table
}
}
return nil
}
func (m *DbMap) tableForPointer(ptr interface{}, checkPK bool) (*TableMap, reflect.Value, error) {
ptrv := reflect.ValueOf(ptr)
if ptrv.Kind() != reflect.Ptr {
e := fmt.Sprintf("gorp: passed non-pointer: %v (kind=%v)", ptr,
ptrv.Kind())
return nil, reflect.Value{}, errors.New(e)
}
elem := ptrv.Elem()
etype := reflect.TypeOf(elem.Interface())
t, err := m.TableFor(etype, checkPK)
if err != nil {
return nil, reflect.Value{}, err
}
return t, elem, nil
}
func (m *DbMap) queryRow(query string, args ...interface{}) *sql.Row {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return m.Db.QueryRow(query, args...)
}
func (m *DbMap) query(query string, args ...interface{}) (*sql.Rows, error) {
if m.logger != nil {
now := time.Now()
defer m.trace(now, query, args...)
}
return m.Db.Query(query, args...)
}
func (m *DbMap) trace(started time.Time, query string, args ...interface{}) {
if m.logger != nil {
var margs = argsString(args...)
m.logger.Printf("%s%s [%s] (%v)", m.logPrefix, query, margs, (time.Now().Sub(started)))
}
}

View file

@ -0,0 +1,111 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import "reflect"
// The Dialect interface encapsulates behaviors that differ across
// SQL databases. At present the Dialect is only used by CreateTables()
// but this could change in the future
type Dialect interface {
// adds a suffix to any query, usually ";"
QuerySuffix() string
// ToSqlType returns the SQL column type to use when creating a
// table of the given Go Type. maxsize can be used to switch based on
// size. For example, in MySQL []byte could map to BLOB, MEDIUMBLOB,
// or LONGBLOB depending on the maxsize
ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string
// string to append to primary key column definitions
AutoIncrStr() string
// string to bind autoincrement columns to. Empty string will
// remove reference to those columns in the INSERT statement.
AutoIncrBindValue() string
AutoIncrInsertSuffix(col *ColumnMap) string
// string to append to "create table" statement for vendor specific
// table attributes
CreateTableSuffix() string
// string to append to "create index" statement
CreateIndexSuffix() string
// string to append to "drop index" statement
DropIndexSuffix() string
// string to truncate tables
TruncateClause() string
// bind variable string to use when forming SQL statements
// in many dbs it is "?", but Postgres appears to use $1
//
// i is a zero based index of the bind variable in this statement
//
BindVar(i int) string
// Handles quoting of a field name to ensure that it doesn't raise any
// SQL parsing exceptions by using a reserved word as a field name.
QuoteField(field string) string
// Handles building up of a schema.database string that is compatible with
// the given dialect
//
// schema - The schema that <table> lives in
// table - The table name
QuotedTableForQuery(schema string, table string) string
// Existance clause for table creation / deletion
IfSchemaNotExists(command, schema string) string
IfTableExists(command, schema, table string) string
IfTableNotExists(command, schema, table string) string
}
// IntegerAutoIncrInserter is implemented by dialects that can perform
// inserts with automatically incremented integer primary keys. If
// the dialect can handle automatic assignment of more than just
// integers, see TargetedAutoIncrInserter.
type IntegerAutoIncrInserter interface {
InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error)
}
// TargetedAutoIncrInserter is implemented by dialects that can
// perform automatic assignment of any primary key type (i.e. strings
// for uuids, integers for serials, etc).
type TargetedAutoIncrInserter interface {
// InsertAutoIncrToTarget runs an insert operation and assigns the
// automatically generated primary key directly to the passed in
// target. The target should be a pointer to the primary key
// field of the value being inserted.
InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error
}
// TargetQueryInserter is implemented by dialects that can perform
// assignment of integer primary key type by executing a query
// like "select sequence.currval from dual".
type TargetQueryInserter interface {
// TargetQueryInserter runs an insert operation and assigns the
// automatically generated primary key retrived by the query
// extracted from the GeneratedIdQuery field of the id column.
InsertQueryToTarget(exec SqlExecutor, insertSql, idSql string, target interface{}, params ...interface{}) error
}
func standardInsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
res, err := exec.Exec(insertSql, params...)
if err != nil {
return 0, err
}
return res.LastInsertId()
}

View file

@ -0,0 +1,171 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"fmt"
"reflect"
"strings"
)
// Implementation of Dialect for MySQL databases.
type MySQLDialect struct {
// Engine is the storage engine to use "InnoDB" vs "MyISAM" for example
Engine string
// Encoding is the character encoding to use for created tables
Encoding string
}
func (d MySQLDialect) QuerySuffix() string { return ";" }
func (d MySQLDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int8:
return "tinyint"
case reflect.Uint8:
return "tinyint unsigned"
case reflect.Int16:
return "smallint"
case reflect.Uint16:
return "smallint unsigned"
case reflect.Int, reflect.Int32:
return "int"
case reflect.Uint, reflect.Uint32:
return "int unsigned"
case reflect.Int64:
return "bigint"
case reflect.Uint64:
return "bigint unsigned"
case reflect.Float64, reflect.Float32:
return "double"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "mediumblob"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double"
case "NullBool":
return "tinyint"
case "Time":
return "datetime"
}
if maxsize < 1 {
maxsize = 255
}
/* == About varchar(N) ==
* N is number of characters.
* A varchar column can store up to 65535 bytes.
* Remember that 1 character is 3 bytes in utf-8 charset.
* Also remember that each row can store up to 65535 bytes,
* and you have some overheads, so it's not possible for a
* varchar column to have 65535/3 characters really.
* So it would be better to use 'text' type in stead of
* large varchar type.
*/
if maxsize < 256 {
return fmt.Sprintf("varchar(%d)", maxsize)
} else {
return "text"
}
}
// Returns auto_increment
func (d MySQLDialect) AutoIncrStr() string {
return "auto_increment"
}
func (d MySQLDialect) AutoIncrBindValue() string {
return "null"
}
func (d MySQLDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
// Returns engine=%s charset=%s based on values stored on struct
func (d MySQLDialect) CreateTableSuffix() string {
if d.Engine == "" || d.Encoding == "" {
msg := "gorp - undefined"
if d.Engine == "" {
msg += " MySQLDialect.Engine"
}
if d.Engine == "" && d.Encoding == "" {
msg += ","
}
if d.Encoding == "" {
msg += " MySQLDialect.Encoding"
}
msg += ". Check that your MySQLDialect was correctly initialized when declared."
panic(msg)
}
return fmt.Sprintf(" engine=%s charset=%s", d.Engine, d.Encoding)
}
func (m MySQLDialect) CreateIndexSuffix() string {
return "using"
}
func (m MySQLDialect) DropIndexSuffix() string {
return "on"
}
func (m MySQLDialect) TruncateClause() string {
return "truncate"
}
// Returns "?"
func (d MySQLDialect) BindVar(i int) string {
return "?"
}
func (d MySQLDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
}
func (d MySQLDialect) QuoteField(f string) string {
return "`" + f + "`"
}
func (d MySQLDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d MySQLDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d MySQLDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d MySQLDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}

View file

@ -0,0 +1,146 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"fmt"
"reflect"
"strings"
)
// Implementation of Dialect for Oracle databases.
type OracleDialect struct{}
func (d OracleDialect) QuerySuffix() string { return "" }
func (d OracleDialect) CreateIndexSuffix() string { return "" }
func (d OracleDialect) DropIndexSuffix() string { return "" }
func (d OracleDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
if isAutoIncr {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if isAutoIncr {
return "bigserial"
}
return "bigint"
case reflect.Float64:
return "double precision"
case reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "bytea"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double precision"
case "NullBool":
return "boolean"
case "NullTime", "Time":
return "timestamp with time zone"
}
if maxsize > 0 {
return fmt.Sprintf("varchar(%d)", maxsize)
} else {
return "text"
}
}
// Returns empty string
func (d OracleDialect) AutoIncrStr() string {
return ""
}
func (d OracleDialect) AutoIncrBindValue() string {
return "NULL"
}
func (d OracleDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
// Returns suffix
func (d OracleDialect) CreateTableSuffix() string {
return ""
}
func (d OracleDialect) TruncateClause() string {
return "truncate"
}
// Returns "$(i+1)"
func (d OracleDialect) BindVar(i int) string {
return fmt.Sprintf(":%d", i+1)
}
// After executing the insert uses the ColMap IdQuery to get the generated id
func (d OracleDialect) InsertQueryToTarget(exec SqlExecutor, insertSql, idSql string, target interface{}, params ...interface{}) error {
_, err := exec.Exec(insertSql, params...)
if err != nil {
return err
}
id, err := exec.SelectInt(idSql)
if err != nil {
return err
}
switch target.(type) {
case *int64:
*(target.(*int64)) = id
case *int32:
*(target.(*int32)) = int32(id)
case int:
*(target.(*int)) = int(id)
default:
return fmt.Errorf("Id field can be int, int32 or int64")
}
return nil
}
func (d OracleDialect) QuoteField(f string) string {
return `"` + strings.ToUpper(f) + `"`
}
func (d OracleDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d OracleDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d OracleDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d OracleDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}

View file

@ -0,0 +1,147 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"fmt"
"reflect"
"strings"
)
type PostgresDialect struct {
suffix string
}
func (d PostgresDialect) QuerySuffix() string { return ";" }
func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
if isAutoIncr {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if isAutoIncr {
return "bigserial"
}
return "bigint"
case reflect.Float64:
return "double precision"
case reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "bytea"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "double precision"
case "NullBool":
return "boolean"
case "Time", "NullTime":
return "timestamp with time zone"
}
if maxsize > 0 {
return fmt.Sprintf("varchar(%d)", maxsize)
} else {
return "text"
}
}
// Returns empty string
func (d PostgresDialect) AutoIncrStr() string {
return ""
}
func (d PostgresDialect) AutoIncrBindValue() string {
return "default"
}
func (d PostgresDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return " returning " + col.ColumnName
}
// Returns suffix
func (d PostgresDialect) CreateTableSuffix() string {
return d.suffix
}
func (d PostgresDialect) CreateIndexSuffix() string {
return "using"
}
func (d PostgresDialect) DropIndexSuffix() string {
return ""
}
func (d PostgresDialect) TruncateClause() string {
return "truncate"
}
// Returns "$(i+1)"
func (d PostgresDialect) BindVar(i int) string {
return fmt.Sprintf("$%d", i+1)
}
func (d PostgresDialect) InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error {
rows, err := exec.query(insertSql, params...)
if err != nil {
return err
}
defer rows.Close()
if !rows.Next() {
return fmt.Errorf("No serial value returned for insert: %s Encountered error: %s", insertSql, rows.Err())
}
if err := rows.Scan(target); err != nil {
return err
}
if rows.Next() {
return fmt.Errorf("more than two serial value returned for insert: %s", insertSql)
}
return rows.Err()
}
func (d PostgresDialect) QuoteField(f string) string {
return `"` + strings.ToLower(f) + `"`
}
func (d PostgresDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return schema + "." + d.QuoteField(table)
}
func (d PostgresDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d PostgresDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d PostgresDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}

View file

@ -0,0 +1,119 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"fmt"
"reflect"
)
type SqliteDialect struct {
suffix string
}
func (d SqliteDialect) QuerySuffix() string { return ";" }
func (d SqliteDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "integer"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "integer"
case reflect.Float64, reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "blob"
}
}
switch val.Name() {
case "NullInt64":
return "integer"
case "NullFloat64":
return "real"
case "NullBool":
return "integer"
case "Time":
return "datetime"
}
if maxsize < 1 {
maxsize = 255
}
return fmt.Sprintf("varchar(%d)", maxsize)
}
// Returns autoincrement
func (d SqliteDialect) AutoIncrStr() string {
return "autoincrement"
}
func (d SqliteDialect) AutoIncrBindValue() string {
return "null"
}
func (d SqliteDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
// Returns suffix
func (d SqliteDialect) CreateTableSuffix() string {
return d.suffix
}
func (d SqliteDialect) CreateIndexSuffix() string {
return ""
}
func (d SqliteDialect) DropIndexSuffix() string {
return ""
}
// With sqlite, there technically isn't a TRUNCATE statement,
// but a DELETE FROM uses a truncate optimization:
// http://www.sqlite.org/lang_delete.html
func (d SqliteDialect) TruncateClause() string {
return "delete from"
}
// Returns "?"
func (d SqliteDialect) BindVar(i int) string {
return "?"
}
func (d SqliteDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
}
func (d SqliteDialect) QuoteField(f string) string {
return `"` + f + `"`
}
// sqlite does not have schemas like PostgreSQL does, so just escape it like normal
func (d SqliteDialect) QuotedTableForQuery(schema string, table string) string {
return d.QuoteField(table)
}
func (d SqliteDialect) IfSchemaNotExists(command, schema string) string {
return fmt.Sprintf("%s if not exists", command)
}
func (d SqliteDialect) IfTableExists(command, schema, table string) string {
return fmt.Sprintf("%s if exists", command)
}
func (d SqliteDialect) IfTableNotExists(command, schema, table string) string {
return fmt.Sprintf("%s if not exists", command)
}

View file

@ -0,0 +1,152 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"fmt"
"reflect"
"strings"
)
// Implementation of Dialect for Microsoft SQL Server databases.
// Use gorp.SqlServerDialect{"2005"} for legacy datatypes.
// Tested with driver: github.com/denisenkom/go-mssqldb
type SqlServerDialect struct {
// If set to "2005" legacy datatypes will be used
Version string
}
func (d SqlServerDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Ptr:
return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
case reflect.Bool:
return "bit"
case reflect.Int8:
return "tinyint"
case reflect.Uint8:
return "smallint"
case reflect.Int16:
return "smallint"
case reflect.Uint16:
return "int"
case reflect.Int, reflect.Int32:
return "int"
case reflect.Uint, reflect.Uint32:
return "bigint"
case reflect.Int64:
return "bigint"
case reflect.Uint64:
return "numeric(20,0)"
case reflect.Float32:
return "float(24)"
case reflect.Float64:
return "float(53)"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "varbinary"
}
}
switch val.Name() {
case "NullInt64":
return "bigint"
case "NullFloat64":
return "float(53)"
case "NullBool":
return "bit"
case "NullTime", "Time":
if d.Version == "2005" {
return "datetime"
}
return "datetime2"
}
if maxsize < 1 {
if d.Version == "2005" {
maxsize = 255
} else {
return fmt.Sprintf("nvarchar(max)")
}
}
return fmt.Sprintf("nvarchar(%d)", maxsize)
}
// Returns auto_increment
func (d SqlServerDialect) AutoIncrStr() string {
return "identity(0,1)"
}
// Empty string removes autoincrement columns from the INSERT statements.
func (d SqlServerDialect) AutoIncrBindValue() string {
return ""
}
func (d SqlServerDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
return ""
}
func (d SqlServerDialect) CreateTableSuffix() string { return ";" }
func (d SqlServerDialect) TruncateClause() string {
return "truncate table"
}
// Returns "?"
func (d SqlServerDialect) BindVar(i int) string {
return "?"
}
func (d SqlServerDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
return standardInsertAutoIncr(exec, insertSql, params...)
}
func (d SqlServerDialect) QuoteField(f string) string {
return "[" + strings.Replace(f, "]", "]]", -1) + "]"
}
func (d SqlServerDialect) QuotedTableForQuery(schema string, table string) string {
if strings.TrimSpace(schema) == "" {
return d.QuoteField(table)
}
return d.QuoteField(schema) + "." + d.QuoteField(table)
}
func (d SqlServerDialect) QuerySuffix() string { return ";" }
func (d SqlServerDialect) IfSchemaNotExists(command, schema string) string {
s := fmt.Sprintf("if schema_id(N'%s') is null %s", schema, command)
return s
}
func (d SqlServerDialect) IfTableExists(command, schema, table string) string {
var schema_clause string
if strings.TrimSpace(schema) != "" {
schema_clause = fmt.Sprintf("%s.", d.QuoteField(schema))
}
s := fmt.Sprintf("if object_id('%s%s') is not null %s", schema_clause, d.QuoteField(table), command)
return s
}
func (d SqlServerDialect) IfTableNotExists(command, schema, table string) string {
var schema_clause string
if strings.TrimSpace(schema) != "" {
schema_clause = fmt.Sprintf("%s.", schema)
}
s := fmt.Sprintf("if object_id('%s%s') is null %s", schema_clause, table, command)
return s
}
func (d SqlServerDialect) CreateIndexSuffix() string { return "" }
func (d SqlServerDialect) DropIndexSuffix() string { return "" }

View file

@ -0,0 +1,38 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"fmt"
)
// A non-fatal error, when a select query returns columns that do not exist
// as fields in the struct it is being mapped to
// TODO: discuss wether this needs an error. encoding/json silently ignores missing fields
type NoFieldInTypeError struct {
TypeName string
MissingColNames []string
}
func (err *NoFieldInTypeError) Error() string {
return fmt.Sprintf("gorp: no fields %+v in type %s", err.MissingColNames, err.TypeName)
}
// returns true if the error is non-fatal (ie, we shouldn't immediately return)
func NonFatalError(err error) bool {
switch err.(type) {
case *NoFieldInTypeError:
return true
default:
return false
}
}

558
Godeps/_workspace/src/github.com/go-gorp/gorp/gorp.go generated vendored Normal file
View file

@ -0,0 +1,558 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"database/sql"
"database/sql/driver"
"fmt"
"reflect"
"regexp"
"strings"
"time"
)
// OracleString (empty string is null)
// TODO: move to dialect/oracle?, rename to String?
type OracleString struct {
sql.NullString
}
// Scan implements the Scanner interface.
func (os *OracleString) Scan(value interface{}) error {
if value == nil {
os.String, os.Valid = "", false
return nil
}
os.Valid = true
return os.NullString.Scan(value)
}
// Value implements the driver Valuer interface.
func (os OracleString) Value() (driver.Value, error) {
if !os.Valid || os.String == "" {
return nil, nil
}
return os.String, nil
}
// SqlTyper is a type that returns its database type. Most of the
// time, the type can just use "database/sql/driver".Valuer; but when
// it returns nil for its empty value, it needs to implement SqlTyper
// to have its column type detected properly during table creation.
type SqlTyper interface {
SqlType() driver.Valuer
}
// for fields that exists in DB table, but not exists in struct
type dummyField struct{}
// Scan implements the Scanner interface.
func (nt *dummyField) Scan(value interface{}) error {
return nil
}
var zeroVal reflect.Value
var versFieldConst = "[gorp_ver_field]"
// The TypeConverter interface provides a way to map a value of one
// type to another type when persisting to, or loading from, a database.
//
// Example use cases: Implement type converter to convert bool types to "y"/"n" strings,
// or serialize a struct member as a JSON blob.
type TypeConverter interface {
// ToDb converts val to another type. Called before INSERT/UPDATE operations
ToDb(val interface{}) (interface{}, error)
// FromDb returns a CustomScanner appropriate for this type. This will be used
// to hold values returned from SELECT queries.
//
// In particular the CustomScanner returned should implement a Binder
// function appropriate for the Go type you wish to convert the db value to
//
// If bool==false, then no custom scanner will be used for this field.
FromDb(target interface{}) (CustomScanner, bool)
}
// Executor exposes the sql.DB and sql.Tx Exec function so that it can be used
// on internal functions that convert named parameters for the Exec function.
type executor interface {
Exec(query string, args ...interface{}) (sql.Result, error)
}
// SqlExecutor exposes gorp operations that can be run from Pre/Post
// hooks. This hides whether the current operation that triggered the
// hook is in a transaction.
//
// See the DbMap function docs for each of the functions below for more
// information.
type SqlExecutor interface {
Get(i interface{}, keys ...interface{}) (interface{}, error)
Insert(list ...interface{}) error
Update(list ...interface{}) (int64, error)
Delete(list ...interface{}) (int64, error)
Exec(query string, args ...interface{}) (sql.Result, error)
Select(i interface{}, query string,
args ...interface{}) ([]interface{}, error)
SelectInt(query string, args ...interface{}) (int64, error)
SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error)
SelectFloat(query string, args ...interface{}) (float64, error)
SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error)
SelectStr(query string, args ...interface{}) (string, error)
SelectNullStr(query string, args ...interface{}) (sql.NullString, error)
SelectOne(holder interface{}, query string, args ...interface{}) error
query(query string, args ...interface{}) (*sql.Rows, error)
queryRow(query string, args ...interface{}) *sql.Row
}
// Compile-time check that DbMap and Transaction implement the SqlExecutor
// interface.
var _, _ SqlExecutor = &DbMap{}, &Transaction{}
func argsString(args ...interface{}) string {
var margs string
for i, a := range args {
var v interface{} = a
if x, ok := v.(driver.Valuer); ok {
y, err := x.Value()
if err == nil {
v = y
}
}
switch v.(type) {
case string:
v = fmt.Sprintf("%q", v)
default:
v = fmt.Sprintf("%v", v)
}
margs += fmt.Sprintf("%d:%s", i+1, v)
if i+1 < len(args) {
margs += " "
}
}
return margs
}
// Calls the Exec function on the executor, but attempts to expand any eligible named
// query arguments first.
func exec(e SqlExecutor, query string, args ...interface{}) (sql.Result, error) {
var dbMap *DbMap
var executor executor
switch m := e.(type) {
case *DbMap:
executor = m.Db
dbMap = m
case *Transaction:
executor = m.tx
dbMap = m.dbmap
}
if len(args) == 1 {
query, args = maybeExpandNamedQuery(dbMap, query, args)
}
return executor.Exec(query, args...)
}
// maybeExpandNamedQuery checks the given arg to see if it's eligible to be used
// as input to a named query. If so, it rewrites the query to use
// dialect-dependent bindvars and instantiates the corresponding slice of
// parameters by extracting data from the map / struct.
// If not, returns the input values unchanged.
func maybeExpandNamedQuery(m *DbMap, query string, args []interface{}) (string, []interface{}) {
var (
arg = args[0]
argval = reflect.ValueOf(arg)
)
if argval.Kind() == reflect.Ptr {
argval = argval.Elem()
}
if argval.Kind() == reflect.Map && argval.Type().Key().Kind() == reflect.String {
return expandNamedQuery(m, query, func(key string) reflect.Value {
return argval.MapIndex(reflect.ValueOf(key))
})
}
if argval.Kind() != reflect.Struct {
return query, args
}
if _, ok := arg.(time.Time); ok {
// time.Time is driver.Value
return query, args
}
if _, ok := arg.(driver.Valuer); ok {
// driver.Valuer will be converted to driver.Value.
return query, args
}
return expandNamedQuery(m, query, argval.FieldByName)
}
var keyRegexp = regexp.MustCompile(`:[[:word:]]+`)
// expandNamedQuery accepts a query with placeholders of the form ":key", and a
// single arg of Kind Struct or Map[string]. It returns the query with the
// dialect's placeholders, and a slice of args ready for positional insertion
// into the query.
func expandNamedQuery(m *DbMap, query string, keyGetter func(key string) reflect.Value) (string, []interface{}) {
var (
n int
args []interface{}
)
return keyRegexp.ReplaceAllStringFunc(query, func(key string) string {
val := keyGetter(key[1:])
if !val.IsValid() {
return key
}
args = append(args, val.Interface())
newVar := m.Dialect.BindVar(n)
n++
return newVar
}), args
}
func columnToFieldIndex(m *DbMap, t reflect.Type, cols []string) ([][]int, error) {
colToFieldIndex := make([][]int, len(cols))
// check if type t is a mapped table - if so we'll
// check the table for column aliasing below
tableMapped := false
table := tableOrNil(m, t)
if table != nil {
tableMapped = true
}
// Loop over column names and find field in i to bind to
// based on column name. all returned columns must match
// a field in the i struct
missingColNames := []string{}
for x := range cols {
colName := strings.ToLower(cols[x])
field, found := t.FieldByNameFunc(func(fieldName string) bool {
field, _ := t.FieldByName(fieldName)
cArguments := strings.Split(field.Tag.Get("db"), ",")
fieldName = cArguments[0]
if fieldName == "-" {
return false
} else if fieldName == "" {
fieldName = field.Name
}
if tableMapped {
colMap := colMapOrNil(table, fieldName)
if colMap != nil {
fieldName = colMap.ColumnName
}
}
return colName == strings.ToLower(fieldName)
})
if found {
colToFieldIndex[x] = field.Index
}
if colToFieldIndex[x] == nil {
missingColNames = append(missingColNames, colName)
}
}
if len(missingColNames) > 0 {
return colToFieldIndex, &NoFieldInTypeError{
TypeName: t.Name(),
MissingColNames: missingColNames,
}
}
return colToFieldIndex, nil
}
func fieldByName(val reflect.Value, fieldName string) *reflect.Value {
// try to find field by exact match
f := val.FieldByName(fieldName)
if f != zeroVal {
return &f
}
// try to find by case insensitive match - only the Postgres driver
// seems to require this - in the case where columns are aliased in the sql
fieldNameL := strings.ToLower(fieldName)
fieldCount := val.NumField()
t := val.Type()
for i := 0; i < fieldCount; i++ {
sf := t.Field(i)
if strings.ToLower(sf.Name) == fieldNameL {
f := val.Field(i)
return &f
}
}
return nil
}
// toSliceType returns the element type of the given object, if the object is a
// "*[]*Element" or "*[]Element". If not, returns nil.
// err is returned if the user was trying to pass a pointer-to-slice but failed.
func toSliceType(i interface{}) (reflect.Type, error) {
t := reflect.TypeOf(i)
if t.Kind() != reflect.Ptr {
// If it's a slice, return a more helpful error message
if t.Kind() == reflect.Slice {
return nil, fmt.Errorf("gorp: cannot SELECT into a non-pointer slice: %v", t)
}
return nil, nil
}
if t = t.Elem(); t.Kind() != reflect.Slice {
return nil, nil
}
return t.Elem(), nil
}
func toType(i interface{}) (reflect.Type, error) {
t := reflect.TypeOf(i)
// If a Pointer to a type, follow
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("gorp: cannot SELECT into this type: %v", reflect.TypeOf(i))
}
return t, nil
}
func get(m *DbMap, exec SqlExecutor, i interface{},
keys ...interface{}) (interface{}, error) {
t, err := toType(i)
if err != nil {
return nil, err
}
table, err := m.TableFor(t, true)
if err != nil {
return nil, err
}
plan := table.bindGet()
v := reflect.New(t)
dest := make([]interface{}, len(plan.argFields))
conv := m.TypeConverter
custScan := make([]CustomScanner, 0)
for x, fieldName := range plan.argFields {
f := v.Elem().FieldByName(fieldName)
target := f.Addr().Interface()
if conv != nil {
scanner, ok := conv.FromDb(target)
if ok {
target = scanner.Holder
custScan = append(custScan, scanner)
}
}
dest[x] = target
}
row := exec.queryRow(plan.query, keys...)
err = row.Scan(dest...)
if err != nil {
if err == sql.ErrNoRows {
err = nil
}
return nil, err
}
for _, c := range custScan {
err = c.Bind()
if err != nil {
return nil, err
}
}
if v, ok := v.Interface().(HasPostGet); ok {
err := v.PostGet(exec)
if err != nil {
return nil, err
}
}
return v.Interface(), nil
}
func delete(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
count := int64(0)
for _, ptr := range list {
table, elem, err := m.tableForPointer(ptr, true)
if err != nil {
return -1, err
}
eval := elem.Addr().Interface()
if v, ok := eval.(HasPreDelete); ok {
err = v.PreDelete(exec)
if err != nil {
return -1, err
}
}
bi, err := table.bindDelete(elem)
if err != nil {
return -1, err
}
res, err := exec.Exec(bi.query, bi.args...)
if err != nil {
return -1, err
}
rows, err := res.RowsAffected()
if err != nil {
return -1, err
}
if rows == 0 && bi.existingVersion > 0 {
return lockError(m, exec, table.TableName,
bi.existingVersion, elem, bi.keys...)
}
count += rows
if v, ok := eval.(HasPostDelete); ok {
err := v.PostDelete(exec)
if err != nil {
return -1, err
}
}
}
return count, nil
}
func update(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
count := int64(0)
for _, ptr := range list {
table, elem, err := m.tableForPointer(ptr, true)
if err != nil {
return -1, err
}
eval := elem.Addr().Interface()
if v, ok := eval.(HasPreUpdate); ok {
err = v.PreUpdate(exec)
if err != nil {
return -1, err
}
}
bi, err := table.bindUpdate(elem)
if err != nil {
return -1, err
}
res, err := exec.Exec(bi.query, bi.args...)
if err != nil {
return -1, err
}
rows, err := res.RowsAffected()
if err != nil {
return -1, err
}
if rows == 0 && bi.existingVersion > 0 {
return lockError(m, exec, table.TableName,
bi.existingVersion, elem, bi.keys...)
}
if bi.versField != "" {
elem.FieldByName(bi.versField).SetInt(bi.existingVersion + 1)
}
count += rows
if v, ok := eval.(HasPostUpdate); ok {
err = v.PostUpdate(exec)
if err != nil {
return -1, err
}
}
}
return count, nil
}
func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error {
for _, ptr := range list {
table, elem, err := m.tableForPointer(ptr, false)
if err != nil {
return err
}
eval := elem.Addr().Interface()
if v, ok := eval.(HasPreInsert); ok {
err := v.PreInsert(exec)
if err != nil {
return err
}
}
bi, err := table.bindInsert(elem)
if err != nil {
return err
}
if bi.autoIncrIdx > -1 {
f := elem.FieldByName(bi.autoIncrFieldName)
switch inserter := m.Dialect.(type) {
case IntegerAutoIncrInserter:
id, err := inserter.InsertAutoIncr(exec, bi.query, bi.args...)
if err != nil {
return err
}
k := f.Kind()
if (k == reflect.Int) || (k == reflect.Int16) || (k == reflect.Int32) || (k == reflect.Int64) {
f.SetInt(id)
} else if (k == reflect.Uint) || (k == reflect.Uint16) || (k == reflect.Uint32) || (k == reflect.Uint64) {
f.SetUint(uint64(id))
} else {
return fmt.Errorf("gorp: cannot set autoincrement value on non-Int field. SQL=%s autoIncrIdx=%d autoIncrFieldName=%s", bi.query, bi.autoIncrIdx, bi.autoIncrFieldName)
}
case TargetedAutoIncrInserter:
err := inserter.InsertAutoIncrToTarget(exec, bi.query, f.Addr().Interface(), bi.args...)
if err != nil {
return err
}
case TargetQueryInserter:
var idQuery = table.ColMap(bi.autoIncrFieldName).GeneratedIdQuery
if idQuery == "" {
return fmt.Errorf("gorp: cannot set %s value if its ColumnMap.GeneratedIdQuery is empty", bi.autoIncrFieldName)
}
err := inserter.InsertQueryToTarget(exec, bi.query, idQuery, f.Addr().Interface(), bi.args...)
if err != nil {
return err
}
default:
return fmt.Errorf("gorp: cannot use autoincrement fields on dialects that do not implement an autoincrementing interface")
}
} else {
_, err := exec.Exec(bi.query, bi.args...)
if err != nil {
return err
}
}
if v, ok := eval.(HasPostInsert); ok {
err := v.PostInsert(exec)
if err != nil {
return err
}
}
}
return nil
}

View file

@ -1,30 +1,56 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp package gorp
import ( import (
"bytes" "bytes"
"database/sql" "database/sql"
"database/sql/driver"
"encoding/json" "encoding/json"
"errors" "errors"
"flag"
"fmt" "fmt"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
_ "github.com/ziutek/mymysql/godrv"
"log" "log"
"math/rand" "math/rand"
"os" "os"
"reflect" "reflect"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
_ "github.com/ziutek/mymysql/godrv"
) )
var (
// verify interface compliance // verify interface compliance
var _ Dialect = SqliteDialect{} _ = []Dialect{
var _ Dialect = PostgresDialect{} SqliteDialect{},
var _ Dialect = MySQLDialect{} PostgresDialect{},
var _ Dialect = SqlServerDialect{} MySQLDialect{},
var _ Dialect = OracleDialect{} SqlServerDialect{},
OracleDialect{},
}
debug bool
)
func init() {
flag.BoolVar(&debug, "trace", true, "Turn on or off database tracing (DbMap.TraceOn)")
flag.Parse()
}
type testable interface { type testable interface {
GetId() int64 GetId() int64
@ -40,6 +66,15 @@ type Invoice struct {
IsPaid bool IsPaid bool
} }
type InvoiceWithValuer struct {
Id int64
Created int64
Updated int64
Memo string
Person PersonValuerScanner `db:"personid"`
IsPaid bool
}
func (me *Invoice) GetId() int64 { return me.Id } func (me *Invoice) GetId() int64 { return me.Id }
func (me *Invoice) Rand() { func (me *Invoice) Rand() {
me.Memo = fmt.Sprintf("random %d", rand.Int63()) me.Memo = fmt.Sprintf("random %d", rand.Int63())
@ -48,7 +83,7 @@ func (me *Invoice) Rand() {
} }
type InvoiceTag struct { type InvoiceTag struct {
Id int64 `db:"myid"` Id int64 `db:"myid, primarykey, autoincrement"`
Created int64 `db:"myCreated"` Created int64 `db:"myCreated"`
Updated int64 `db:"date_updated"` Updated int64 `db:"date_updated"`
Memo string Memo string
@ -63,7 +98,7 @@ func (me *InvoiceTag) Rand() {
me.Updated = rand.Int63() me.Updated = rand.Int63()
} }
// See: https://github.com/coopernurse/gorp/issues/175 // See: https://github.com/go-gorp/gorp/issues/175
type AliasTransientField struct { type AliasTransientField struct {
Id int64 `db:"id"` Id int64 `db:"id"`
Bar int64 `db:"-"` Bar int64 `db:"-"`
@ -89,6 +124,34 @@ type Person struct {
Version int64 Version int64
} }
type PersonValuerScanner struct {
Person
}
func (p PersonValuerScanner) Value() (driver.Value, error) {
return p.Id, nil
}
// FIXME: this Scan is never actually used in the tests?
// Also: if the comments below on the mysql driver are true, then that should be fixed by the dialect when scanning values into structs.
func (p *PersonValuerScanner) Scan(value interface{}) (err error) {
switch src := value.(type) {
case []byte:
// The mysql driver seems to return a []byte, even though the
// type in the database is bigint. Note that this case is
// *only* used by the mysql driver.
p.Id, err = strconv.ParseInt(string(src), 10, 64)
case int64:
// postgres, gomysql, and sqlite drivers all return an int64,
// as you'd expect.
p.Id = src
default:
typ := reflect.TypeOf(value)
return fmt.Errorf("Expected person value to be convertible to int64, got %v (type %s)", value, typ)
}
return
}
type FNameOnly struct { type FNameOnly struct {
FName string FName string
} }
@ -159,6 +222,17 @@ type WithEmbeddedStruct struct {
Names Names
} }
type WithEmbeddedStructConflictingEmbeddedMemberNames struct {
Id int64
Names
NamesConflict
}
type WithEmbeddedStructSameMemberName struct {
Id int64
SameName
}
type WithEmbeddedStructBeforeAutoincrField struct { type WithEmbeddedStructBeforeAutoincrField struct {
Names Names
Id int64 Id int64
@ -174,6 +248,15 @@ type Names struct {
LastName string LastName string
} }
type NamesConflict struct {
FirstName string
Surname string
}
type SameName struct {
SameName string
}
type UniqueColumns struct { type UniqueColumns struct {
FirstName string FirstName string
LastName string LastName string
@ -194,6 +277,11 @@ type WithCustomDate struct {
Added CustomDate Added CustomDate
} }
type WithNullTime struct {
Id int64
Time NullTime
}
type testTypeConverter struct{} type testTypeConverter struct{}
func (me testTypeConverter) ToDb(val interface{}) (interface{}, error) { func (me testTypeConverter) ToDb(val interface{}) (interface{}, error) {
@ -347,7 +435,6 @@ func TestTruncateTables(t *testing.T) {
func TestCustomDateType(t *testing.T) { func TestCustomDateType(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.TypeConverter = testTypeConverter{} dbmap.TypeConverter = testTypeConverter{}
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
dbmap.AddTable(WithCustomDate{}).SetKeys(true, "Id") dbmap.AddTable(WithCustomDate{}).SetKeys(true, "Id")
err := dbmap.CreateTables() err := dbmap.CreateTables()
if err != nil { if err != nil {
@ -383,7 +470,6 @@ func TestCustomDateType(t *testing.T) {
func TestUIntPrimaryKey(t *testing.T) { func TestUIntPrimaryKey(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
dbmap.AddTable(PersonUInt64{}).SetKeys(true, "Id") dbmap.AddTable(PersonUInt64{}).SetKeys(true, "Id")
dbmap.AddTable(PersonUInt32{}).SetKeys(true, "Id") dbmap.AddTable(PersonUInt32{}).SetKeys(true, "Id")
dbmap.AddTable(PersonUInt16{}).SetKeys(true, "Id") dbmap.AddTable(PersonUInt16{}).SetKeys(true, "Id")
@ -413,7 +499,6 @@ func TestUIntPrimaryKey(t *testing.T) {
func TestSetUniqueTogether(t *testing.T) { func TestSetUniqueTogether(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
dbmap.AddTable(UniqueColumns{}).SetUniqueTogether("FirstName", "LastName").SetUniqueTogether("City", "ZipCode") dbmap.AddTable(UniqueColumns{}).SetUniqueTogether("FirstName", "LastName").SetUniqueTogether("City", "ZipCode")
err := dbmap.CreateTablesIfNotExists() err := dbmap.CreateTablesIfNotExists()
if err != nil { if err != nil {
@ -462,7 +547,6 @@ func TestSetUniqueTogether(t *testing.T) {
func TestPersistentUser(t *testing.T) { func TestPersistentUser(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.Exec("drop table if exists PersistentUser") dbmap.Exec("drop table if exists PersistentUser")
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key") table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key")
table.ColMap("Key").Rename("mykey") table.ColMap("Key").Rename("mykey")
err := dbmap.CreateTablesIfNotExists() err := dbmap.CreateTablesIfNotExists()
@ -575,7 +659,6 @@ func TestPersistentUser(t *testing.T) {
func TestNamedQueryMap(t *testing.T) { func TestNamedQueryMap(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.Exec("drop table if exists PersistentUser") dbmap.Exec("drop table if exists PersistentUser")
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key") table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key")
table.ColMap("Key").Rename("mykey") table.ColMap("Key").Rename("mykey")
err := dbmap.CreateTablesIfNotExists() err := dbmap.CreateTablesIfNotExists()
@ -655,12 +738,24 @@ select * from PersistentUser
if len(puArr) != 1 { if len(puArr) != 1 {
t.Errorf("Expected one persistentuser, found none") t.Errorf("Expected one persistentuser, found none")
} }
// Test to delete with Exec and named params.
result, err := dbmap.Exec("delete from PersistentUser where mykey = :Key", map[string]interface{}{
"Key": 43,
})
count, err := result.RowsAffected()
if err != nil {
t.Errorf("Failed to exec: %s", err)
t.FailNow()
}
if count != 1 {
t.Errorf("Expected 1 persistentuser to be deleted, but %d deleted", count)
}
} }
func TestNamedQueryStruct(t *testing.T) { func TestNamedQueryStruct(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.Exec("drop table if exists PersistentUser") dbmap.Exec("drop table if exists PersistentUser")
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key") table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key")
table.ColMap("Key").Rename("mykey") table.ColMap("Key").Rename("mykey")
err := dbmap.CreateTablesIfNotExists() err := dbmap.CreateTablesIfNotExists()
@ -692,6 +787,21 @@ select * from PersistentUser
if !reflect.DeepEqual(pu, puArr[0]) { if !reflect.DeepEqual(pu, puArr[0]) {
t.Errorf("%v!=%v", pu, puArr[0]) t.Errorf("%v!=%v", pu, puArr[0])
} }
// Test delete self.
result, err := dbmap.Exec(`
delete from PersistentUser
where mykey = :Key
and PassedTraining = :PassedTraining
and Id = :Id`, pu)
count, err := result.RowsAffected()
if err != nil {
t.Errorf("Failed to exec: %s", err)
t.FailNow()
}
if count != 1 {
t.Errorf("Expected 1 persistentuser to be deleted, but %d deleted", count)
}
} }
// Ensure that the slices containing SQL results are non-nil when the result set is empty. // Ensure that the slices containing SQL results are non-nil when the result set is empty.
@ -825,9 +935,48 @@ func TestNullValues(t *testing.T) {
} }
} }
func TestScannerValuer(t *testing.T) {
dbmap := newDbMap()
dbmap.AddTableWithName(PersonValuerScanner{}, "person_test").SetKeys(true, "Id")
dbmap.AddTableWithName(InvoiceWithValuer{}, "invoice_test").SetKeys(true, "Id")
err := dbmap.CreateTables()
if err != nil {
panic(err)
}
defer dropAndClose(dbmap)
pv := PersonValuerScanner{}
pv.FName = "foo"
pv.LName = "bar"
err = dbmap.Insert(&pv)
if err != nil {
t.Errorf("Could not insert PersonValuerScanner using Person table: %v", err)
t.FailNow()
}
inv := InvoiceWithValuer{}
inv.Memo = "foo"
inv.Person = pv
err = dbmap.Insert(&inv)
if err != nil {
t.Errorf("Could not insert InvoiceWithValuer using Invoice table: %v", err)
t.FailNow()
}
res, err := dbmap.Get(InvoiceWithValuer{}, inv.Id)
if err != nil {
t.Errorf("Could not get InvoiceWithValuer: %v", err)
t.FailNow()
}
dbInv := res.(*InvoiceWithValuer)
if dbInv.Person.Id != pv.Id {
t.Errorf("InvoiceWithValuer got wrong person ID: %d (expected) != %d (actual)", pv.Id, dbInv.Person.Id)
}
}
func TestColumnProps(t *testing.T) { func TestColumnProps(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
t1 := dbmap.AddTable(Invoice{}).SetKeys(true, "Id") t1 := dbmap.AddTable(Invoice{}).SetKeys(true, "Id")
t1.ColMap("Created").Rename("date_created") t1.ColMap("Created").Rename("date_created")
t1.ColMap("Updated").SetTransient(true) t1.ColMap("Updated").SetTransient(true)
@ -1183,6 +1332,60 @@ func TestWithEmbeddedStruct(t *testing.T) {
} }
} }
/*
func TestWithEmbeddedStructConflictingEmbeddedMemberNames(t *testing.T) {
dbmap := initDbMap()
defer dropAndClose(dbmap)
es := &WithEmbeddedStructConflictingEmbeddedMemberNames{-1, Names{FirstName: "Alice", LastName: "Smith"}, NamesConflict{FirstName: "Andrew", Surname: "Wiggin"}}
_insert(dbmap, es)
expected := &WithEmbeddedStructConflictingEmbeddedMemberNames{-1, Names{FirstName: "Alice", LastName: "Smith"}, NamesConflict{FirstName: "Andrew", Surname: "Wiggin"}}
es2 := _get(dbmap, WithEmbeddedStructConflictingEmbeddedMemberNames{}, es.Id).(*WithEmbeddedStructConflictingEmbeddedMemberNames)
if !reflect.DeepEqual(expected, es2) {
t.Errorf("%v != %v", expected, es2)
}
es2.Names.FirstName = "Bob"
expected.Names.FirstName = "Bob"
_update(dbmap, es2)
es2 = _get(dbmap, WithEmbeddedStructConflictingEmbeddedMemberNames{}, es.Id).(*WithEmbeddedStructConflictingEmbeddedMemberNames)
if !reflect.DeepEqual(expected, es2) {
t.Errorf("%v != %v", expected, es2)
}
ess := _rawselect(dbmap, WithEmbeddedStructConflictingEmbeddedMemberNames{}, "select * from embedded_struct_conflict_name_test")
if !reflect.DeepEqual(es2, ess[0]) {
t.Errorf("%v != %v", es2, ess[0])
}
}
func TestWithEmbeddedStructSameMemberName(t *testing.T) {
dbmap := initDbMap()
defer dropAndClose(dbmap)
es := &WithEmbeddedStructSameMemberName{-1, SameName{SameName: "Alice"}}
_insert(dbmap, es)
expected := &WithEmbeddedStructSameMemberName{-1, SameName{SameName: "Alice"}}
es2 := _get(dbmap, WithEmbeddedStructSameMemberName{}, es.Id).(*WithEmbeddedStructSameMemberName)
if !reflect.DeepEqual(expected, es2) {
t.Errorf("%v != %v", expected, es2)
}
es2.SameName = SameName{"Bob"}
expected.SameName = SameName{"Bob"}
_update(dbmap, es2)
es2 = _get(dbmap, WithEmbeddedStructSameMemberName{}, es.Id).(*WithEmbeddedStructSameMemberName)
if !reflect.DeepEqual(expected, es2) {
t.Errorf("%v != %v", expected, es2)
}
ess := _rawselect(dbmap, WithEmbeddedStructSameMemberName{}, "select * from embedded_struct_same_member_name_test")
if !reflect.DeepEqual(es2, ess[0]) {
t.Errorf("%v != %v", es2, ess[0])
}
}
//*/
func TestWithEmbeddedStructBeforeAutoincr(t *testing.T) { func TestWithEmbeddedStructBeforeAutoincr(t *testing.T) {
dbmap := initDbMap() dbmap := initDbMap()
defer dropAndClose(dbmap) defer dropAndClose(dbmap)
@ -1325,7 +1528,6 @@ func TestVersionMultipleRows(t *testing.T) {
func TestWithStringPk(t *testing.T) { func TestWithStringPk(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
dbmap.AddTableWithName(WithStringPk{}, "string_pk_test").SetKeys(true, "Id") dbmap.AddTableWithName(WithStringPk{}, "string_pk_test").SetKeys(true, "Id")
_, err := dbmap.Exec("create table string_pk_test (Id varchar(255), Name varchar(255));") _, err := dbmap.Exec("create table string_pk_test (Id varchar(255), Name varchar(255));")
if err != nil { if err != nil {
@ -1359,6 +1561,58 @@ func TestSqlExecutorInterfaceSelects(t *testing.T) {
} }
} }
func TestNullTime(t *testing.T) {
dbmap := initDbMap()
defer dropAndClose(dbmap)
// if time is null
ent := &WithNullTime{
Id: 0,
Time: NullTime{
Valid: false,
}}
err := dbmap.Insert(ent)
if err != nil {
t.Error("failed insert on %s", err.Error())
}
err = dbmap.SelectOne(ent, `select * from nulltime_test where Id=:Id`, map[string]interface{}{
"Id": ent.Id,
})
if err != nil {
t.Error("failed select on %s", err.Error())
}
if ent.Time.Valid {
t.Error("NullTime returns valid but expected null.")
}
// if time is not null
ts, err := time.Parse(time.Stamp, "Jan 2 15:04:05")
ent = &WithNullTime{
Id: 1,
Time: NullTime{
Valid: true,
Time: ts,
}}
err = dbmap.Insert(ent)
if err != nil {
t.Error("failed insert on %s", err.Error())
}
err = dbmap.SelectOne(ent, `select * from nulltime_test where Id=:Id`, map[string]interface{}{
"Id": ent.Id,
})
if err != nil {
t.Error("failed select on %s", err.Error())
}
if !ent.Time.Valid {
t.Error("NullTime returns invalid but expected valid.")
}
if ent.Time.Time.UTC() != ts.UTC() {
t.Errorf("expect %v but got %v.", ts, ent.Time.Time)
}
return
}
type WithTime struct { type WithTime struct {
Id int64 Id int64
Time time.Time Time time.Time
@ -1402,10 +1656,9 @@ func testWithTime(t *testing.T) {
} }
} }
// See: https://github.com/coopernurse/gorp/issues/86 // See: https://github.com/go-gorp/gorp/issues/86
func testEmbeddedTime(t *testing.T) { func testEmbeddedTime(t *testing.T) {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
dbmap.AddTable(EmbeddedTime{}).SetKeys(false, "Id") dbmap.AddTable(EmbeddedTime{}).SetKeys(false, "Id")
defer dropAndClose(dbmap) defer dropAndClose(dbmap)
err := dbmap.CreateTables() err := dbmap.CreateTables()
@ -1901,17 +2154,20 @@ func initDbMapBench() *DbMap {
func initDbMap() *DbMap { func initDbMap() *DbMap {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id") dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id")
dbmap.AddTableWithName(InvoiceTag{}, "invoice_tag_test").SetKeys(true, "myid") dbmap.AddTableWithName(InvoiceTag{}, "invoice_tag_test") //key is set via primarykey attribute
dbmap.AddTableWithName(AliasTransientField{}, "alias_trans_field_test").SetKeys(true, "id") dbmap.AddTableWithName(AliasTransientField{}, "alias_trans_field_test").SetKeys(true, "id")
dbmap.AddTableWithName(OverriddenInvoice{}, "invoice_override_test").SetKeys(false, "Id") dbmap.AddTableWithName(OverriddenInvoice{}, "invoice_override_test").SetKeys(false, "Id")
dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id") dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id").SetVersionCol("Version")
dbmap.AddTableWithName(WithIgnoredColumn{}, "ignored_column_test").SetKeys(true, "Id") dbmap.AddTableWithName(WithIgnoredColumn{}, "ignored_column_test").SetKeys(true, "Id")
dbmap.AddTableWithName(IdCreated{}, "id_created_test").SetKeys(true, "Id") dbmap.AddTableWithName(IdCreated{}, "id_created_test").SetKeys(true, "Id")
dbmap.AddTableWithName(TypeConversionExample{}, "type_conv_test").SetKeys(true, "Id") dbmap.AddTableWithName(TypeConversionExample{}, "type_conv_test").SetKeys(true, "Id")
dbmap.AddTableWithName(WithEmbeddedStruct{}, "embedded_struct_test").SetKeys(true, "Id") dbmap.AddTableWithName(WithEmbeddedStruct{}, "embedded_struct_test").SetKeys(true, "Id")
//dbmap.AddTableWithName(WithEmbeddedStructConflictingEmbeddedMemberNames{}, "embedded_struct_conflict_name_test").SetKeys(true, "Id")
//dbmap.AddTableWithName(WithEmbeddedStructSameMemberName{}, "embedded_struct_same_member_name_test").SetKeys(true, "Id")
dbmap.AddTableWithName(WithEmbeddedStructBeforeAutoincrField{}, "embedded_struct_before_autoincr_test").SetKeys(true, "Id") dbmap.AddTableWithName(WithEmbeddedStructBeforeAutoincrField{}, "embedded_struct_before_autoincr_test").SetKeys(true, "Id")
dbmap.AddTableWithName(WithEmbeddedAutoincr{}, "embedded_autoincr_test").SetKeys(true, "Id") dbmap.AddTableWithName(WithEmbeddedAutoincr{}, "embedded_autoincr_test").SetKeys(true, "Id")
dbmap.AddTableWithName(WithTime{}, "time_test").SetKeys(true, "Id") dbmap.AddTableWithName(WithTime{}, "time_test").SetKeys(true, "Id")
dbmap.AddTableWithName(WithNullTime{}, "nulltime_test").SetKeys(false, "Id")
dbmap.TypeConverter = testTypeConverter{} dbmap.TypeConverter = testTypeConverter{}
err := dbmap.DropTablesIfExists() err := dbmap.DropTablesIfExists()
if err != nil { if err != nil {
@ -1931,7 +2187,6 @@ func initDbMap() *DbMap {
func initDbMapNulls() *DbMap { func initDbMapNulls() *DbMap {
dbmap := newDbMap() dbmap := newDbMap()
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
dbmap.AddTable(TableWithNull{}).SetKeys(false, "Id") dbmap.AddTable(TableWithNull{}).SetKeys(false, "Id")
err := dbmap.CreateTables() err := dbmap.CreateTables()
if err != nil { if err != nil {
@ -1943,7 +2198,9 @@ func initDbMapNulls() *DbMap {
func newDbMap() *DbMap { func newDbMap() *DbMap {
dialect, driver := dialectAndDriver() dialect, driver := dialectAndDriver()
dbmap := &DbMap{Db: connect(driver), Dialect: dialect} dbmap := &DbMap{Db: connect(driver), Dialect: dialect}
if debug {
dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds))
}
return dbmap return dbmap
} }

49
Godeps/_workspace/src/github.com/go-gorp/gorp/hooks.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
//++ TODO v2-phase3: HasPostGet => PostGetter, HasPostDelete => PostDeleter, etc.
// PostUpdate() will be executed after the GET statement.
type HasPostGet interface {
PostGet(SqlExecutor) error
}
// PostUpdate() will be executed after the DELETE statement
type HasPostDelete interface {
PostDelete(SqlExecutor) error
}
// PostUpdate() will be executed after the UPDATE statement
type HasPostUpdate interface {
PostUpdate(SqlExecutor) error
}
// PostInsert() will be executed after the INSERT statement
type HasPostInsert interface {
PostInsert(SqlExecutor) error
}
// PreDelete() will be executed before the DELETE statement.
type HasPreDelete interface {
PreDelete(SqlExecutor) error
}
// PreUpdate() will be executed before UPDATE statement.
type HasPreUpdate interface {
PreUpdate(SqlExecutor) error
}
// PreInsert() will be executed before INSERT statement.
type HasPreInsert interface {
PreInsert(SqlExecutor) error
}

56
Godeps/_workspace/src/github.com/go-gorp/gorp/index.go generated vendored Normal file
View file

@ -0,0 +1,56 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
// IndexMap represents a mapping between a Go struct field and a single
// index in a table.
// Unique and MaxSize only inform the
// CreateTables() function and are not used by Insert/Update/Delete/Get.
type IndexMap struct {
// Index name in db table
IndexName string
// If true, " unique" is added to create index statements.
// Not used elsewhere
Unique bool
// Index type supported by Dialect
// Postgres: B-tree, Hash, GiST and GIN.
// Mysql: Btree, Hash.
// Sqlite: nil.
IndexType string
// Columns name for single and multiple indexes
columns []string
}
// Rename allows you to specify the index name in the table
//
// Example: table.IndMap("customer_test_idx").Rename("customer_idx")
//
func (idx *IndexMap) Rename(indname string) *IndexMap {
idx.IndexName = indname
return idx
}
// SetUnique adds "unique" to the create index statements for this
// index, if b is true.
func (idx *IndexMap) SetUnique(b bool) *IndexMap {
idx.Unique = b
return idx
}
// SetIndexType specifies the index type supported by chousen SQL Dialect
func (idx *IndexMap) SetIndexType(indtype string) *IndexMap {
idx.IndexType = indtype
return idx
}

View file

@ -0,0 +1,63 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"fmt"
"reflect"
)
// OptimisticLockError is returned by Update() or Delete() if the
// struct being modified has a Version field and the value is not equal to
// the current value in the database
type OptimisticLockError struct {
// Table name where the lock error occurred
TableName string
// Primary key values of the row being updated/deleted
Keys []interface{}
// true if a row was found with those keys, indicating the
// LocalVersion is stale. false if no value was found with those
// keys, suggesting the row has been deleted since loaded, or
// was never inserted to begin with
RowExists bool
// Version value on the struct passed to Update/Delete. This value is
// out of sync with the database.
LocalVersion int64
}
// Error returns a description of the cause of the lock error
func (e OptimisticLockError) Error() string {
if e.RowExists {
return fmt.Sprintf("gorp: OptimisticLockError table=%s keys=%v out of date version=%d", e.TableName, e.Keys, e.LocalVersion)
}
return fmt.Sprintf("gorp: OptimisticLockError no row found for table=%s keys=%v", e.TableName, e.Keys)
}
func lockError(m *DbMap, exec SqlExecutor, tableName string,
existingVer int64, elem reflect.Value,
keys ...interface{}) (int64, error) {
existing, err := get(m, exec, elem.Interface(), keys...)
if err != nil {
return -1, err
}
ole := OptimisticLockError{tableName, keys, true, existingVer}
if existing == nil {
ole.RowExists = false
}
return -1, ole
}

View file

@ -0,0 +1,44 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import "fmt"
type GorpLogger interface {
Printf(format string, v ...interface{})
}
// TraceOn turns on SQL statement logging for this DbMap. After this is
// called, all SQL statements will be sent to the logger. If prefix is
// a non-empty string, it will be written to the front of all logged
// strings, which can aid in filtering log lines.
//
// Use TraceOn if you want to spy on the SQL statements that gorp
// generates.
//
// Note that the base log.Logger type satisfies GorpLogger, but adapters can
// easily be written for other logging packages (e.g., the golang-sanctioned
// glog framework).
func (m *DbMap) TraceOn(prefix string, logger GorpLogger) {
m.logger = logger
if prefix == "" {
m.logPrefix = prefix
} else {
m.logPrefix = fmt.Sprintf("%s ", prefix)
}
}
// TraceOff turns off tracing. It is idempotent.
func (m *DbMap) TraceOff() {
m.logger = nil
m.logPrefix = ""
}

View file

@ -0,0 +1,58 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"database/sql/driver"
"time"
)
// A nullable Time value
type NullTime struct {
Time time.Time
Valid bool // Valid is true if Time is not NULL
}
// Scan implements the Scanner interface.
func (nt *NullTime) Scan(value interface{}) error {
switch t := value.(type) {
case time.Time:
nt.Time, nt.Valid = t, true
case []byte:
nt.Valid = false
for _, dtfmt := range []string{
"2006-01-02 15:04:05.999999999",
"2006-01-02T15:04:05.999999999",
"2006-01-02 15:04:05",
"2006-01-02T15:04:05",
"2006-01-02 15:04",
"2006-01-02T15:04",
"2006-01-02",
"2006-01-02 15:04:05-07:00",
} {
var err error
if nt.Time, err = time.Parse(dtfmt, string(t)); err == nil {
nt.Valid = true
break
}
}
}
return nil
}
// Value implements the driver Valuer interface.
func (nt NullTime) Value() (driver.Value, error) {
if !nt.Valid {
return nil, nil
}
return nt.Time, nil
}

351
Godeps/_workspace/src/github.com/go-gorp/gorp/select.go generated vendored Normal file
View file

@ -0,0 +1,351 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"database/sql"
"fmt"
"reflect"
)
// SelectInt executes the given query, which should be a SELECT statement for a single
// integer column, and returns the value of the first row returned. If no rows are
// found, zero is returned.
func SelectInt(e SqlExecutor, query string, args ...interface{}) (int64, error) {
var h int64
err := selectVal(e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return 0, err
}
return h, nil
}
// SelectNullInt executes the given query, which should be a SELECT statement for a single
// integer column, and returns the value of the first row returned. If no rows are
// found, the empty sql.NullInt64 value is returned.
func SelectNullInt(e SqlExecutor, query string, args ...interface{}) (sql.NullInt64, error) {
var h sql.NullInt64
err := selectVal(e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return h, err
}
return h, nil
}
// SelectFloat executes the given query, which should be a SELECT statement for a single
// float column, and returns the value of the first row returned. If no rows are
// found, zero is returned.
func SelectFloat(e SqlExecutor, query string, args ...interface{}) (float64, error) {
var h float64
err := selectVal(e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return 0, err
}
return h, nil
}
// SelectNullFloat executes the given query, which should be a SELECT statement for a single
// float column, and returns the value of the first row returned. If no rows are
// found, the empty sql.NullInt64 value is returned.
func SelectNullFloat(e SqlExecutor, query string, args ...interface{}) (sql.NullFloat64, error) {
var h sql.NullFloat64
err := selectVal(e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return h, err
}
return h, nil
}
// SelectStr executes the given query, which should be a SELECT statement for a single
// char/varchar column, and returns the value of the first row returned. If no rows are
// found, an empty string is returned.
func SelectStr(e SqlExecutor, query string, args ...interface{}) (string, error) {
var h string
err := selectVal(e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return "", err
}
return h, nil
}
// SelectNullStr executes the given query, which should be a SELECT
// statement for a single char/varchar column, and returns the value
// of the first row returned. If no rows are found, the empty
// sql.NullString is returned.
func SelectNullStr(e SqlExecutor, query string, args ...interface{}) (sql.NullString, error) {
var h sql.NullString
err := selectVal(e, &h, query, args...)
if err != nil && err != sql.ErrNoRows {
return h, err
}
return h, nil
}
// SelectOne executes the given query (which should be a SELECT statement)
// and binds the result to holder, which must be a pointer.
//
// If no row is found, an error (sql.ErrNoRows specifically) will be returned
//
// If more than one row is found, an error will be returned.
//
func SelectOne(m *DbMap, e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
t := reflect.TypeOf(holder)
if t.Kind() == reflect.Ptr {
t = t.Elem()
} else {
return fmt.Errorf("gorp: SelectOne holder must be a pointer, but got: %t", holder)
}
// Handle pointer to pointer
isptr := false
if t.Kind() == reflect.Ptr {
isptr = true
t = t.Elem()
}
if t.Kind() == reflect.Struct {
var nonFatalErr error
list, err := hookedselect(m, e, holder, query, args...)
if err != nil {
if !NonFatalError(err) { // FIXME: double negative, rename NonFatalError to FatalError
return err
}
nonFatalErr = err
}
dest := reflect.ValueOf(holder)
if isptr {
dest = dest.Elem()
}
if list != nil && len(list) > 0 { // FIXME: invert if/else
// check for multiple rows
if len(list) > 1 {
return fmt.Errorf("gorp: multiple rows returned for: %s - %v", query, args)
}
// Initialize if nil
if dest.IsNil() {
dest.Set(reflect.New(t))
}
// only one row found
src := reflect.ValueOf(list[0])
dest.Elem().Set(src.Elem())
} else {
// No rows found, return a proper error.
return sql.ErrNoRows
}
return nonFatalErr
}
return selectVal(e, holder, query, args...)
}
func selectVal(e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
if len(args) == 1 {
switch m := e.(type) {
case *DbMap:
query, args = maybeExpandNamedQuery(m, query, args)
case *Transaction:
query, args = maybeExpandNamedQuery(m.dbmap, query, args)
}
}
rows, err := e.query(query, args...)
if err != nil {
return err
}
defer rows.Close()
if !rows.Next() {
return sql.ErrNoRows
}
return rows.Scan(holder)
}
func hookedselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
args ...interface{}) ([]interface{}, error) {
var nonFatalErr error
list, err := rawselect(m, exec, i, query, args...)
if err != nil {
if !NonFatalError(err) {
return nil, err
}
nonFatalErr = err
}
// Determine where the results are: written to i, or returned in list
if t, _ := toSliceType(i); t == nil {
for _, v := range list {
if v, ok := v.(HasPostGet); ok {
err := v.PostGet(exec)
if err != nil {
return nil, err
}
}
}
} else {
resultsValue := reflect.Indirect(reflect.ValueOf(i))
for i := 0; i < resultsValue.Len(); i++ {
if v, ok := resultsValue.Index(i).Interface().(HasPostGet); ok {
err := v.PostGet(exec)
if err != nil {
return nil, err
}
}
}
}
return list, nonFatalErr
}
func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
args ...interface{}) ([]interface{}, error) {
var (
appendToSlice = false // Write results to i directly?
intoStruct = true // Selecting into a struct?
pointerElements = true // Are the slice elements pointers (vs values)?
)
var nonFatalErr error
// get type for i, verifying it's a supported destination
t, err := toType(i)
if err != nil {
var err2 error
if t, err2 = toSliceType(i); t == nil {
if err2 != nil {
return nil, err2
}
return nil, err
}
pointerElements = t.Kind() == reflect.Ptr
if pointerElements {
t = t.Elem()
}
appendToSlice = true
intoStruct = t.Kind() == reflect.Struct
}
// If the caller supplied a single struct/map argument, assume a "named
// parameter" query. Extract the named arguments from the struct/map, create
// the flat arg slice, and rewrite the query to use the dialect's placeholder.
if len(args) == 1 {
query, args = maybeExpandNamedQuery(m, query, args)
}
// Run the query
rows, err := exec.query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
// Fetch the column names as returned from db
cols, err := rows.Columns()
if err != nil {
return nil, err
}
if !intoStruct && len(cols) > 1 {
return nil, fmt.Errorf("gorp: select into non-struct slice requires 1 column, got %d", len(cols))
}
var colToFieldIndex [][]int
if intoStruct {
colToFieldIndex, err = columnToFieldIndex(m, t, cols)
if err != nil {
if !NonFatalError(err) {
return nil, err
}
nonFatalErr = err
}
}
conv := m.TypeConverter
// Add results to one of these two slices.
var (
list = make([]interface{}, 0)
sliceValue = reflect.Indirect(reflect.ValueOf(i))
)
for {
if !rows.Next() {
// if error occured return rawselect
if rows.Err() != nil {
return nil, rows.Err()
}
// time to exit from outer "for" loop
break
}
v := reflect.New(t)
dest := make([]interface{}, len(cols))
custScan := make([]CustomScanner, 0)
for x := range cols {
f := v.Elem()
if intoStruct {
index := colToFieldIndex[x]
if index == nil {
// this field is not present in the struct, so create a dummy
// value for rows.Scan to scan into
var dummy dummyField
dest[x] = &dummy
continue
}
f = f.FieldByIndex(index)
}
target := f.Addr().Interface()
if conv != nil {
scanner, ok := conv.FromDb(target)
if ok {
target = scanner.Holder
custScan = append(custScan, scanner)
}
}
dest[x] = target
}
err = rows.Scan(dest...)
if err != nil {
return nil, err
}
for _, c := range custScan {
err = c.Bind()
if err != nil {
return nil, err
}
}
if appendToSlice {
if !pointerElements {
v = v.Elem()
}
sliceValue.Set(reflect.Append(sliceValue, v))
} else {
list = append(list, v.Interface())
}
}
if appendToSlice && sliceValue.IsNil() {
sliceValue.Set(reflect.MakeSlice(sliceValue.Type(), 0, 0))
}
return list, nonFatalErr
}

247
Godeps/_workspace/src/github.com/go-gorp/gorp/table.go generated vendored Normal file
View file

@ -0,0 +1,247 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"bytes"
"fmt"
"reflect"
"strings"
)
// TableMap represents a mapping between a Go struct and a database table
// Use dbmap.AddTable() or dbmap.AddTableWithName() to create these
type TableMap struct {
// Name of database table.
TableName string
SchemaName string
gotype reflect.Type
Columns []*ColumnMap
keys []*ColumnMap
indexes []*IndexMap
uniqueTogether [][]string
version *ColumnMap
insertPlan bindPlan
updatePlan bindPlan
deletePlan bindPlan
getPlan bindPlan
dbmap *DbMap
}
// ResetSql removes cached insert/update/select/delete SQL strings
// associated with this TableMap. Call this if you've modified
// any column names or the table name itself.
func (t *TableMap) ResetSql() {
t.insertPlan = bindPlan{}
t.updatePlan = bindPlan{}
t.deletePlan = bindPlan{}
t.getPlan = bindPlan{}
}
// SetKeys lets you specify the fields on a struct that map to primary
// key columns on the table. If isAutoIncr is set, result.LastInsertId()
// will be used after INSERT to bind the generated id to the Go struct.
//
// Automatically calls ResetSql() to ensure SQL statements are regenerated.
//
// Panics if isAutoIncr is true, and fieldNames length != 1
//
func (t *TableMap) SetKeys(isAutoIncr bool, fieldNames ...string) *TableMap {
if isAutoIncr && len(fieldNames) != 1 {
panic(fmt.Sprintf(
"gorp: SetKeys: fieldNames length must be 1 if key is auto-increment. (Saw %v fieldNames)",
len(fieldNames)))
}
t.keys = make([]*ColumnMap, 0)
for _, name := range fieldNames {
colmap := t.ColMap(name)
colmap.isPK = true
colmap.isAutoIncr = isAutoIncr
t.keys = append(t.keys, colmap)
}
t.ResetSql()
return t
}
// SetUniqueTogether lets you specify uniqueness constraints across multiple
// columns on the table. Each call adds an additional constraint for the
// specified columns.
//
// Automatically calls ResetSql() to ensure SQL statements are regenerated.
//
// Panics if fieldNames length < 2.
//
func (t *TableMap) SetUniqueTogether(fieldNames ...string) *TableMap {
if len(fieldNames) < 2 {
panic(fmt.Sprintf(
"gorp: SetUniqueTogether: must provide at least two fieldNames to set uniqueness constraint."))
}
columns := make([]string, 0)
for _, name := range fieldNames {
columns = append(columns, name)
}
t.uniqueTogether = append(t.uniqueTogether, columns)
t.ResetSql()
return t
}
// ColMap returns the ColumnMap pointer matching the given struct field
// name. It panics if the struct does not contain a field matching this
// name.
func (t *TableMap) ColMap(field string) *ColumnMap {
col := colMapOrNil(t, field)
if col == nil {
e := fmt.Sprintf("No ColumnMap in table %s type %s with field %s",
t.TableName, t.gotype.Name(), field)
panic(e)
}
return col
}
func colMapOrNil(t *TableMap, field string) *ColumnMap {
for _, col := range t.Columns {
if col.fieldName == field || col.ColumnName == field {
return col
}
}
return nil
}
// IdxMap returns the IndexMap pointer matching the given index name.
func (t *TableMap) IdxMap(field string) *IndexMap {
for _, idx := range t.indexes {
if idx.IndexName == field {
return idx
}
}
return nil
}
// AddIndex registers the index with gorp for specified table with given parameters.
// This operation is idempotent. If index is already mapped, the
// existing *IndexMap is returned
// Function will panic if one of the given for index columns does not exists
//
// Automatically calls ResetSql() to ensure SQL statements are regenerated.
//
func (t *TableMap) AddIndex(name string, idxtype string, columns []string) *IndexMap {
// check if we have a index with this name already
for _, idx := range t.indexes {
if idx.IndexName == name {
return idx
}
}
for _, icol := range columns {
if res := t.ColMap(icol); res == nil {
e := fmt.Sprintf("No ColumnName in table %s to create index on", t.TableName)
panic(e)
}
}
idx := &IndexMap{IndexName: name, Unique: false, IndexType: idxtype, columns: columns}
t.indexes = append(t.indexes, idx)
t.ResetSql()
return idx
}
// SetVersionCol sets the column to use as the Version field. By default
// the "Version" field is used. Returns the column found, or panics
// if the struct does not contain a field matching this name.
//
// Automatically calls ResetSql() to ensure SQL statements are regenerated.
func (t *TableMap) SetVersionCol(field string) *ColumnMap {
c := t.ColMap(field)
t.version = c
t.ResetSql()
return c
}
// SqlForCreateTable gets a sequence of SQL commands that will create
// the specified table and any associated schema
func (t *TableMap) SqlForCreate(ifNotExists bool) string {
s := bytes.Buffer{}
dialect := t.dbmap.Dialect
if strings.TrimSpace(t.SchemaName) != "" {
schemaCreate := "create schema"
if ifNotExists {
s.WriteString(dialect.IfSchemaNotExists(schemaCreate, t.SchemaName))
} else {
s.WriteString(schemaCreate)
}
s.WriteString(fmt.Sprintf(" %s;", t.SchemaName))
}
tableCreate := "create table"
if ifNotExists {
s.WriteString(dialect.IfTableNotExists(tableCreate, t.SchemaName, t.TableName))
} else {
s.WriteString(tableCreate)
}
s.WriteString(fmt.Sprintf(" %s (", dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))
x := 0
for _, col := range t.Columns {
if !col.Transient {
if x > 0 {
s.WriteString(", ")
}
stype := dialect.ToSqlType(col.gotype, col.MaxSize, col.isAutoIncr)
s.WriteString(fmt.Sprintf("%s %s", dialect.QuoteField(col.ColumnName), stype))
if col.isPK || col.isNotNull {
s.WriteString(" not null")
}
if col.isPK && len(t.keys) == 1 {
s.WriteString(" primary key")
}
if col.Unique {
s.WriteString(" unique")
}
if col.isAutoIncr {
s.WriteString(fmt.Sprintf(" %s", dialect.AutoIncrStr()))
}
x++
}
}
if len(t.keys) > 1 {
s.WriteString(", primary key (")
for x := range t.keys {
if x > 0 {
s.WriteString(", ")
}
s.WriteString(dialect.QuoteField(t.keys[x].ColumnName))
}
s.WriteString(")")
}
if len(t.uniqueTogether) > 0 {
for _, columns := range t.uniqueTogether {
s.WriteString(", unique (")
for i, column := range columns {
if i > 0 {
s.WriteString(", ")
}
s.WriteString(dialect.QuoteField(column))
}
s.WriteString(")")
}
}
s.WriteString(") ")
s.WriteString(dialect.CreateTableSuffix())
s.WriteString(dialect.QuerySuffix())
return s.String()
}

View file

@ -0,0 +1,306 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"bytes"
"fmt"
"reflect"
)
// CustomScanner binds a database column value to a Go type
type CustomScanner struct {
// After a row is scanned, Holder will contain the value from the database column.
// Initialize the CustomScanner with the concrete Go type you wish the database
// driver to scan the raw column into.
Holder interface{}
// Target typically holds a pointer to the target struct field to bind the Holder
// value to.
Target interface{}
// Binder is a custom function that converts the holder value to the target type
// and sets target accordingly. This function should return error if a problem
// occurs converting the holder to the target.
Binder func(holder interface{}, target interface{}) error
}
// Bind is called automatically by gorp after Scan()
func (me CustomScanner) Bind() error {
return me.Binder(me.Holder, me.Target)
}
type bindPlan struct {
query string
argFields []string
keyFields []string
versField string
autoIncrIdx int
autoIncrFieldName string
}
func (plan bindPlan) createBindInstance(elem reflect.Value, conv TypeConverter) (bindInstance, error) {
bi := bindInstance{query: plan.query, autoIncrIdx: plan.autoIncrIdx, autoIncrFieldName: plan.autoIncrFieldName, versField: plan.versField}
if plan.versField != "" {
bi.existingVersion = elem.FieldByName(plan.versField).Int()
}
var err error
for i := 0; i < len(plan.argFields); i++ {
k := plan.argFields[i]
if k == versFieldConst {
newVer := bi.existingVersion + 1
bi.args = append(bi.args, newVer)
if bi.existingVersion == 0 {
elem.FieldByName(plan.versField).SetInt(int64(newVer))
}
} else {
val := elem.FieldByName(k).Interface()
if conv != nil {
val, err = conv.ToDb(val)
if err != nil {
return bindInstance{}, err
}
}
bi.args = append(bi.args, val)
}
}
for i := 0; i < len(plan.keyFields); i++ {
k := plan.keyFields[i]
val := elem.FieldByName(k).Interface()
if conv != nil {
val, err = conv.ToDb(val)
if err != nil {
return bindInstance{}, err
}
}
bi.keys = append(bi.keys, val)
}
return bi, nil
}
type bindInstance struct {
query string
args []interface{}
keys []interface{}
existingVersion int64
versField string
autoIncrIdx int
autoIncrFieldName string
}
func (t *TableMap) bindInsert(elem reflect.Value) (bindInstance, error) {
plan := t.insertPlan
if plan.query == "" {
plan.autoIncrIdx = -1
s := bytes.Buffer{}
s2 := bytes.Buffer{}
s.WriteString(fmt.Sprintf("insert into %s (", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))
x := 0
first := true
for y := range t.Columns {
col := t.Columns[y]
if !(col.isAutoIncr && t.dbmap.Dialect.AutoIncrBindValue() == "") {
if !col.Transient {
if !first {
s.WriteString(",")
s2.WriteString(",")
}
s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
if col.isAutoIncr {
s2.WriteString(t.dbmap.Dialect.AutoIncrBindValue())
plan.autoIncrIdx = y
plan.autoIncrFieldName = col.fieldName
} else {
if col.DefaultValue == "" {
s2.WriteString(t.dbmap.Dialect.BindVar(x))
if col == t.version {
plan.versField = col.fieldName
plan.argFields = append(plan.argFields, versFieldConst)
} else {
plan.argFields = append(plan.argFields, col.fieldName)
}
x++
} else {
s2.WriteString(col.DefaultValue)
}
}
first = false
}
} else {
plan.autoIncrIdx = y
plan.autoIncrFieldName = col.fieldName
}
}
s.WriteString(") values (")
s.WriteString(s2.String())
s.WriteString(")")
if plan.autoIncrIdx > -1 {
s.WriteString(t.dbmap.Dialect.AutoIncrInsertSuffix(t.Columns[plan.autoIncrIdx]))
}
s.WriteString(t.dbmap.Dialect.QuerySuffix())
plan.query = s.String()
t.insertPlan = plan
}
return plan.createBindInstance(elem, t.dbmap.TypeConverter)
}
func (t *TableMap) bindUpdate(elem reflect.Value) (bindInstance, error) {
plan := t.updatePlan
if plan.query == "" {
s := bytes.Buffer{}
s.WriteString(fmt.Sprintf("update %s set ", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))
x := 0
for y := range t.Columns {
col := t.Columns[y]
if !col.isAutoIncr && !col.Transient {
if x > 0 {
s.WriteString(", ")
}
s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
s.WriteString("=")
s.WriteString(t.dbmap.Dialect.BindVar(x))
if col == t.version {
plan.versField = col.fieldName
plan.argFields = append(plan.argFields, versFieldConst)
} else {
plan.argFields = append(plan.argFields, col.fieldName)
}
x++
}
}
s.WriteString(" where ")
for y := range t.keys {
col := t.keys[y]
if y > 0 {
s.WriteString(" and ")
}
s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
s.WriteString("=")
s.WriteString(t.dbmap.Dialect.BindVar(x))
plan.argFields = append(plan.argFields, col.fieldName)
plan.keyFields = append(plan.keyFields, col.fieldName)
x++
}
if plan.versField != "" {
s.WriteString(" and ")
s.WriteString(t.dbmap.Dialect.QuoteField(t.version.ColumnName))
s.WriteString("=")
s.WriteString(t.dbmap.Dialect.BindVar(x))
plan.argFields = append(plan.argFields, plan.versField)
}
s.WriteString(t.dbmap.Dialect.QuerySuffix())
plan.query = s.String()
t.updatePlan = plan
}
return plan.createBindInstance(elem, t.dbmap.TypeConverter)
}
func (t *TableMap) bindDelete(elem reflect.Value) (bindInstance, error) {
plan := t.deletePlan
if plan.query == "" {
s := bytes.Buffer{}
s.WriteString(fmt.Sprintf("delete from %s", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))
for y := range t.Columns {
col := t.Columns[y]
if !col.Transient {
if col == t.version {
plan.versField = col.fieldName
}
}
}
s.WriteString(" where ")
for x := range t.keys {
k := t.keys[x]
if x > 0 {
s.WriteString(" and ")
}
s.WriteString(t.dbmap.Dialect.QuoteField(k.ColumnName))
s.WriteString("=")
s.WriteString(t.dbmap.Dialect.BindVar(x))
plan.keyFields = append(plan.keyFields, k.fieldName)
plan.argFields = append(plan.argFields, k.fieldName)
}
if plan.versField != "" {
s.WriteString(" and ")
s.WriteString(t.dbmap.Dialect.QuoteField(t.version.ColumnName))
s.WriteString("=")
s.WriteString(t.dbmap.Dialect.BindVar(len(plan.argFields)))
plan.argFields = append(plan.argFields, plan.versField)
}
s.WriteString(t.dbmap.Dialect.QuerySuffix())
plan.query = s.String()
t.deletePlan = plan
}
return plan.createBindInstance(elem, t.dbmap.TypeConverter)
}
func (t *TableMap) bindGet() bindPlan {
plan := t.getPlan
if plan.query == "" {
s := bytes.Buffer{}
s.WriteString("select ")
x := 0
for _, col := range t.Columns {
if !col.Transient {
if x > 0 {
s.WriteString(",")
}
s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
plan.argFields = append(plan.argFields, col.fieldName)
x++
}
}
s.WriteString(" from ")
s.WriteString(t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName))
s.WriteString(" where ")
for x := range t.keys {
col := t.keys[x]
if x > 0 {
s.WriteString(" and ")
}
s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
s.WriteString("=")
s.WriteString(t.dbmap.Dialect.BindVar(x))
plan.keyFields = append(plan.keyFields, col.fieldName)
}
s.WriteString(t.dbmap.Dialect.QuerySuffix())
plan.query = s.String()
t.getPlan = plan
}
return plan
}

View file

@ -0,0 +1,38 @@
#!/bin/bash -e
# on macs, you may need to:
# export GOBUILDFLAG=-ldflags -linkmode=external
coveralls_testflags="-v -covermode=count -coverprofile=coverage.out"
echo "Testing against mysql"
export GORP_TEST_DSN=gorptest/gorptest/gorptest
export GORP_TEST_DIALECT=mysql
go test $coveralls_testflags $GOBUILDFLAG $@ .
echo "Testing against gomysql"
export GORP_TEST_DSN=gorptest:gorptest@/gorptest
export GORP_TEST_DIALECT=gomysql
go test $coveralls_testflags $GOBUILDFLAG $@ .
echo "Testing against postgres"
export GORP_TEST_DSN="user=gorptest password=gorptest dbname=gorptest sslmode=disable"
export GORP_TEST_DIALECT=postgres
go test $coveralls_testflags $GOBUILDFLAG $@ .
echo "Testing against sqlite"
export GORP_TEST_DSN=/tmp/gorptest.bin
export GORP_TEST_DIALECT=sqlite
go test $coveralls_testflags $GOBUILDFLAG $@ .
rm -f /tmp/gorptest.bin
case $(go version) in
*go1.4*)
if [ "$(type -p goveralls)" != "" ]; then
goveralls -covermode=count -coverprofile=coverage.out -service=travis-ci
elif [ -x $HOME/gopath/bin/goveralls ]; then
$HOME/gopath/bin/goveralls -covermode=count -coverprofile=coverage.out -service=travis-ci
fi
;;
*) ;;
esac

View file

@ -0,0 +1,188 @@
// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases. It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp
//
package gorp
import (
"database/sql"
"time"
)
// Transaction represents a database transaction.
// Insert/Update/Delete/Get/Exec operations will be run in the context
// of that transaction. Transactions should be terminated with
// a call to Commit() or Rollback()
type Transaction struct {
dbmap *DbMap
tx *sql.Tx
closed bool
}
// Insert has the same behavior as DbMap.Insert(), but runs in a transaction.
func (t *Transaction) Insert(list ...interface{}) error {
return insert(t.dbmap, t, list...)
}
// Update had the same behavior as DbMap.Update(), but runs in a transaction.
func (t *Transaction) Update(list ...interface{}) (int64, error) {
return update(t.dbmap, t, list...)
}
// Delete has the same behavior as DbMap.Delete(), but runs in a transaction.
func (t *Transaction) Delete(list ...interface{}) (int64, error) {
return delete(t.dbmap, t, list...)
}
// Get has the same behavior as DbMap.Get(), but runs in a transaction.
func (t *Transaction) Get(i interface{}, keys ...interface{}) (interface{}, error) {
return get(t.dbmap, t, i, keys...)
}
// Select has the same behavior as DbMap.Select(), but runs in a transaction.
func (t *Transaction) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) {
return hookedselect(t.dbmap, t, i, query, args...)
}
// Exec has the same behavior as DbMap.Exec(), but runs in a transaction.
func (t *Transaction) Exec(query string, args ...interface{}) (sql.Result, error) {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return exec(t, query, args...)
}
// SelectInt is a convenience wrapper around the gorp.SelectInt function.
func (t *Transaction) SelectInt(query string, args ...interface{}) (int64, error) {
return SelectInt(t, query, args...)
}
// SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function.
func (t *Transaction) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) {
return SelectNullInt(t, query, args...)
}
// SelectFloat is a convenience wrapper around the gorp.SelectFloat function.
func (t *Transaction) SelectFloat(query string, args ...interface{}) (float64, error) {
return SelectFloat(t, query, args...)
}
// SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function.
func (t *Transaction) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) {
return SelectNullFloat(t, query, args...)
}
// SelectStr is a convenience wrapper around the gorp.SelectStr function.
func (t *Transaction) SelectStr(query string, args ...interface{}) (string, error) {
return SelectStr(t, query, args...)
}
// SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function.
func (t *Transaction) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) {
return SelectNullStr(t, query, args...)
}
// SelectOne is a convenience wrapper around the gorp.SelectOne function.
func (t *Transaction) SelectOne(holder interface{}, query string, args ...interface{}) error {
return SelectOne(t.dbmap, t, holder, query, args...)
}
// Commit commits the underlying database transaction.
func (t *Transaction) Commit() error {
if !t.closed {
t.closed = true
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, "commit;")
}
return t.tx.Commit()
}
return sql.ErrTxDone
}
// Rollback rolls back the underlying database transaction.
func (t *Transaction) Rollback() error {
if !t.closed {
t.closed = true
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, "rollback;")
}
return t.tx.Rollback()
}
return sql.ErrTxDone
}
// Savepoint creates a savepoint with the given name. The name is interpolated
// directly into the SQL SAVEPOINT statement, so you must sanitize it if it is
// derived from user input.
func (t *Transaction) Savepoint(name string) error {
query := "savepoint " + t.dbmap.Dialect.QuoteField(name)
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, nil)
}
_, err := t.tx.Exec(query)
return err
}
// RollbackToSavepoint rolls back to the savepoint with the given name. The
// name is interpolated directly into the SQL SAVEPOINT statement, so you must
// sanitize it if it is derived from user input.
func (t *Transaction) RollbackToSavepoint(savepoint string) error {
query := "rollback to savepoint " + t.dbmap.Dialect.QuoteField(savepoint)
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, nil)
}
_, err := t.tx.Exec(query)
return err
}
// ReleaseSavepint releases the savepoint with the given name. The name is
// interpolated directly into the SQL SAVEPOINT statement, so you must sanitize
// it if it is derived from user input.
func (t *Transaction) ReleaseSavepoint(savepoint string) error {
query := "release savepoint " + t.dbmap.Dialect.QuoteField(savepoint)
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, nil)
}
_, err := t.tx.Exec(query)
return err
}
// Prepare has the same behavior as DbMap.Prepare(), but runs in a transaction.
func (t *Transaction) Prepare(query string) (*sql.Stmt, error) {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, nil)
}
return t.tx.Prepare(query)
}
func (t *Transaction) queryRow(query string, args ...interface{}) *sql.Row {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return t.tx.QueryRow(query, args...)
}
func (t *Transaction) query(query string, args ...interface{}) (*sql.Rows, error) {
if t.dbmap.logger != nil {
now := time.Now()
defer t.dbmap.trace(now, query, args...)
}
return t.tx.Query(query, args...)
}

View file

@ -8,8 +8,8 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"github.com/coopernurse/gorp"
"github.com/coreos/go-oidc/oidc" "github.com/coreos/go-oidc/oidc"
"github.com/go-gorp/gorp"
"github.com/lib/pq" "github.com/lib/pq"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"

View file

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"github.com/coreos/dex/pkg/log" "github.com/coreos/dex/pkg/log"

View file

@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/coreos/dex/connector" "github.com/coreos/dex/connector"

View file

@ -3,7 +3,7 @@ package db
import ( import (
"time" "time"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
"github.com/coreos/dex/pkg/log" "github.com/coreos/dex/pkg/log"

View file

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
) )
func NewHealthChecker(dbm *gorp.DbMap) *healthChecker { func NewHealthChecker(dbm *gorp.DbMap) *healthChecker {

View file

@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
"github.com/lib/pq" "github.com/lib/pq"
pcrypto "github.com/coreos/dex/pkg/crypto" pcrypto "github.com/coreos/dex/pkg/crypto"

View file

@ -3,7 +3,7 @@ package db
import ( import (
"fmt" "fmt"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
"github.com/lib/pq" "github.com/lib/pq"
migrate "github.com/rubenv/sql-migrate" migrate "github.com/rubenv/sql-migrate"

View file

@ -5,7 +5,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
) )
func initDB(dsn string) *gorp.DbMap { func initDB(dsn string) *gorp.DbMap {

View file

@ -4,10 +4,9 @@ import (
"errors" "errors"
"time" "time"
"github.com/coopernurse/gorp"
"github.com/coreos/dex/repo" "github.com/coreos/dex/repo"
"github.com/coreos/dex/user" "github.com/coreos/dex/user"
"github.com/go-gorp/gorp"
) )
const ( const (

View file

@ -6,8 +6,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/coopernurse/gorp"
"github.com/coreos/dex/refresh" "github.com/coreos/dex/refresh"
"github.com/go-gorp/gorp"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )

View file

@ -7,7 +7,7 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
"github.com/lib/pq" "github.com/lib/pq"

View file

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
"github.com/lib/pq" "github.com/lib/pq"

View file

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/coopernurse/gorp" "github.com/go-gorp/gorp"
"github.com/lib/pq" "github.com/lib/pq"
"github.com/coreos/dex/repo" "github.com/coreos/dex/repo"

View file

@ -7,9 +7,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/coopernurse/gorp"
"github.com/coreos/go-oidc/key" "github.com/coreos/go-oidc/key"
"github.com/coreos/go-oidc/oidc" "github.com/coreos/go-oidc/oidc"
"github.com/go-gorp/gorp"
"github.com/kylelemons/godebug/pretty" "github.com/kylelemons/godebug/pretty"
"github.com/coreos/dex/client" "github.com/coreos/dex/client"

View file

@ -3,9 +3,8 @@ package repo
import ( import (
"fmt" "fmt"
"github.com/coopernurse/gorp"
"github.com/coreos/dex/db" "github.com/coreos/dex/db"
"github.com/go-gorp/gorp"
) )
func initDB(dsn string) *gorp.DbMap { func initDB(dsn string) *gorp.DbMap {