├── .gitignore ├── Makefile ├── .travis.yml ├── test_all.sh ├── LICENSE ├── dialect.go ├── README.md ├── gorp_test.go └── gorp.go /.gitignore: -------------------------------------------------------------------------------- 1 | _test 2 | _testmain.go 3 | _obj 4 | *~ 5 | *.6 6 | 6.out 7 | gorptest.bin 8 | tmp 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | TARG = github.com/coopernurse/gorp 4 | GOFILES = gorp.go dialect.go 5 | 6 | include $(GOROOT)/src/Make.pkg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - tip 5 | 6 | services: 7 | - mysql 8 | - postgres 9 | - sqlite3 10 | 11 | before_script: 12 | - mysql -e "CREATE DATABASE gorptest;" 13 | - mysql -u root -e "GRANT ALL ON gorptest.* TO gorptest@localhost IDENTIFIED BY 'gorptest'" 14 | - psql -c "CREATE DATABASE gorptest;" -U postgres 15 | - psql -c "CREATE USER "gorptest" WITH SUPERUSER PASSWORD 'gorptest';" -U postgres 16 | - go get github.com/lib/pq 17 | - go get github.com/mattn/go-sqlite3 18 | - go get github.com/ziutek/mymysql/godrv 19 | - go get github.com/go-sql-driver/mysql 20 | 21 | script: ./test_all.sh 22 | -------------------------------------------------------------------------------- /test_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # on macs, you may need to: 4 | # export GOBUILDFLAG=-ldflags -linkmode=external 5 | 6 | set -e 7 | 8 | export GORP_TEST_DSN=gorptest/gorptest/gorptest 9 | export GORP_TEST_DIALECT=mysql 10 | go test $GOBUILDFLAG . 11 | 12 | export GORP_TEST_DSN=gorptest:gorptest@/gorptest 13 | export GORP_TEST_DIALECT=gomysql 14 | go test $GOBUILDFLAG . 15 | 16 | export GORP_TEST_DSN="user=gorptest password=gorptest dbname=gorptest sslmode=disable" 17 | export GORP_TEST_DIALECT=postgres 18 | go test $GOBUILDFLAG . 19 | 20 | export GORP_TEST_DSN=/tmp/gorptest.bin 21 | export GORP_TEST_DIALECT=sqlite 22 | go test $GOBUILDFLAG . 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 James Cooper 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /dialect.go: -------------------------------------------------------------------------------- 1 | package gorp 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | // The Dialect interface encapsulates behaviors that differ across 11 | // SQL databases. At present the Dialect is only used by CreateTables() 12 | // but this could change in the future 13 | type Dialect interface { 14 | 15 | // ToSqlType returns the SQL column type to use when creating a 16 | // table of the given Go Type. maxsize can be used to switch based on 17 | // size. For example, in MySQL []byte could map to BLOB, MEDIUMBLOB, 18 | // or LONGBLOB depending on the maxsize 19 | ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string 20 | 21 | // string to append to primary key column definitions 22 | AutoIncrStr() string 23 | 24 | AutoIncrBindValue() string 25 | 26 | AutoIncrInsertSuffix(col *ColumnMap) string 27 | 28 | // string to append to "create table" statement for vendor specific 29 | // table attributes 30 | CreateTableSuffix() string 31 | 32 | // string to truncate tables 33 | TruncateClause() string 34 | 35 | InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) 36 | 37 | // bind variable string to use when forming SQL statements 38 | // in many dbs it is "?", but Postgres appears to use $1 39 | // 40 | // i is a zero based index of the bind variable in this statement 41 | // 42 | BindVar(i int) string 43 | 44 | // Handles quoting of a field name to ensure that it doesn't raise any 45 | // SQL parsing exceptions by using a reserved word as a field name. 46 | QuoteField(field string) string 47 | 48 | // Handles building up of a schema.database string that is compatible with 49 | // the given dialect 50 | // 51 | // schema - The schema that lives in 52 | // table - The table name 53 | QuotedTableForQuery(schema string, table string) string 54 | } 55 | 56 | func standardInsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) { 57 | res, err := exec.Exec(insertSql, params...) 58 | if err != nil { 59 | return 0, err 60 | } 61 | return res.LastInsertId() 62 | } 63 | 64 | /////////////////////////////////////////////////////// 65 | // sqlite3 // 66 | ///////////// 67 | 68 | type SqliteDialect struct { 69 | suffix string 70 | } 71 | 72 | func (d SqliteDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string { 73 | switch val.Kind() { 74 | case reflect.Ptr: 75 | return d.ToSqlType(val.Elem(), maxsize, isAutoIncr) 76 | case reflect.Bool: 77 | return "integer" 78 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 79 | return "integer" 80 | case reflect.Float64, reflect.Float32: 81 | return "real" 82 | case reflect.Slice: 83 | if val.Elem().Kind() == reflect.Uint8 { 84 | return "blob" 85 | } 86 | } 87 | 88 | switch val.Name() { 89 | case "NullInt64": 90 | return "integer" 91 | case "NullFloat64": 92 | return "real" 93 | case "NullBool": 94 | return "integer" 95 | case "Time": 96 | return "datetime" 97 | } 98 | 99 | if maxsize < 1 { 100 | maxsize = 255 101 | } 102 | return fmt.Sprintf("varchar(%d)", maxsize) 103 | } 104 | 105 | // Returns autoincrement 106 | func (d SqliteDialect) AutoIncrStr() string { 107 | return "autoincrement" 108 | } 109 | 110 | func (d SqliteDialect) AutoIncrBindValue() string { 111 | return "null" 112 | } 113 | 114 | func (d SqliteDialect) AutoIncrInsertSuffix(col *ColumnMap) string { 115 | return "" 116 | } 117 | 118 | // Returns suffix 119 | func (d SqliteDialect) CreateTableSuffix() string { 120 | return d.suffix 121 | } 122 | 123 | // With sqlite, there technically isn't a TRUNCATE statement, 124 | // but a DELETE FROM uses a truncate optimization: 125 | // http://www.sqlite.org/lang_delete.html 126 | func (d SqliteDialect) TruncateClause() string { 127 | return "delete from" 128 | } 129 | 130 | // Returns "?" 131 | func (d SqliteDialect) BindVar(i int) string { 132 | return "?" 133 | } 134 | 135 | func (d SqliteDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) { 136 | return standardInsertAutoIncr(exec, insertSql, params...) 137 | } 138 | 139 | func (d SqliteDialect) QuoteField(f string) string { 140 | return `"` + f + `"` 141 | } 142 | 143 | // sqlite does not have schemas like PostgreSQL does, so just escape it like normal 144 | func (d SqliteDialect) QuotedTableForQuery(schema string, table string) string { 145 | return d.QuoteField(table) 146 | } 147 | 148 | /////////////////////////////////////////////////////// 149 | // PostgreSQL // 150 | //////////////// 151 | 152 | type PostgresDialect struct { 153 | suffix string 154 | } 155 | 156 | func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string { 157 | switch val.Kind() { 158 | case reflect.Ptr: 159 | return d.ToSqlType(val.Elem(), maxsize, isAutoIncr) 160 | case reflect.Bool: 161 | return "boolean" 162 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32: 163 | if isAutoIncr { 164 | return "serial" 165 | } 166 | return "integer" 167 | case reflect.Int64, reflect.Uint64: 168 | if isAutoIncr { 169 | return "bigserial" 170 | } 171 | return "bigint" 172 | case reflect.Float64: 173 | return "double precision" 174 | case reflect.Float32: 175 | return "real" 176 | case reflect.Slice: 177 | if val.Elem().Kind() == reflect.Uint8 { 178 | return "bytea" 179 | } 180 | } 181 | 182 | switch val.Name() { 183 | case "NullInt64": 184 | return "bigint" 185 | case "NullFloat64": 186 | return "double precision" 187 | case "NullBool": 188 | return "boolean" 189 | case "Time": 190 | return "timestamp with time zone" 191 | } 192 | 193 | if maxsize > 0 { 194 | return fmt.Sprintf("varchar(%d)", maxsize) 195 | } else { 196 | return "text" 197 | } 198 | 199 | } 200 | 201 | // Returns empty string 202 | func (d PostgresDialect) AutoIncrStr() string { 203 | return "" 204 | } 205 | 206 | func (d PostgresDialect) AutoIncrBindValue() string { 207 | return "default" 208 | } 209 | 210 | func (d PostgresDialect) AutoIncrInsertSuffix(col *ColumnMap) string { 211 | return " returning " + col.ColumnName 212 | } 213 | 214 | // Returns suffix 215 | func (d PostgresDialect) CreateTableSuffix() string { 216 | return d.suffix 217 | } 218 | 219 | func (d PostgresDialect) TruncateClause() string { 220 | return "truncate" 221 | } 222 | 223 | // Returns "$(i+1)" 224 | func (d PostgresDialect) BindVar(i int) string { 225 | return fmt.Sprintf("$%d", i+1) 226 | } 227 | 228 | func (d PostgresDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) { 229 | rows, err := exec.query(insertSql, params...) 230 | if err != nil { 231 | return 0, err 232 | } 233 | defer rows.Close() 234 | 235 | if rows.Next() { 236 | var id int64 237 | err := rows.Scan(&id) 238 | return id, err 239 | } 240 | 241 | return 0, errors.New("No serial value returned for insert: " + insertSql + " Encountered error: " + rows.Err().Error()) 242 | } 243 | 244 | func (d PostgresDialect) QuoteField(f string) string { 245 | return `"` + strings.ToLower(f) + `"` 246 | } 247 | 248 | func (d PostgresDialect) QuotedTableForQuery(schema string, table string) string { 249 | if strings.TrimSpace(schema) == "" { 250 | return d.QuoteField(table) 251 | } 252 | 253 | return schema + "." + d.QuoteField(table) 254 | } 255 | 256 | /////////////////////////////////////////////////////// 257 | // MySQL // 258 | /////////// 259 | 260 | // Implementation of Dialect for MySQL databases. 261 | type MySQLDialect struct { 262 | 263 | // Engine is the storage engine to use "InnoDB" vs "MyISAM" for example 264 | Engine string 265 | 266 | // Encoding is the character encoding to use for created tables 267 | Encoding string 268 | } 269 | 270 | func (m MySQLDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string { 271 | switch val.Kind() { 272 | case reflect.Ptr: 273 | return m.ToSqlType(val.Elem(), maxsize, isAutoIncr) 274 | case reflect.Bool: 275 | return "boolean" 276 | case reflect.Int8: 277 | return "tinyint" 278 | case reflect.Uint8: 279 | return "tinyint unsigned" 280 | case reflect.Int16: 281 | return "smallint" 282 | case reflect.Uint16: 283 | return "smallint unsigned" 284 | case reflect.Int, reflect.Int32: 285 | return "int" 286 | case reflect.Uint, reflect.Uint32: 287 | return "int unsigned" 288 | case reflect.Int64: 289 | return "bigint" 290 | case reflect.Uint64: 291 | return "bigint unsigned" 292 | case reflect.Float64, reflect.Float32: 293 | return "double" 294 | case reflect.Slice: 295 | if val.Elem().Kind() == reflect.Uint8 { 296 | return "mediumblob" 297 | } 298 | } 299 | 300 | switch val.Name() { 301 | case "NullInt64": 302 | return "bigint" 303 | case "NullFloat64": 304 | return "double" 305 | case "NullBool": 306 | return "tinyint" 307 | case "Time": 308 | return "datetime" 309 | } 310 | 311 | if maxsize < 1 { 312 | maxsize = 255 313 | } 314 | return fmt.Sprintf("varchar(%d)", maxsize) 315 | } 316 | 317 | // Returns auto_increment 318 | func (m MySQLDialect) AutoIncrStr() string { 319 | return "auto_increment" 320 | } 321 | 322 | func (m MySQLDialect) AutoIncrBindValue() string { 323 | return "null" 324 | } 325 | 326 | func (m MySQLDialect) AutoIncrInsertSuffix(col *ColumnMap) string { 327 | return "" 328 | } 329 | 330 | // Returns engine=%s charset=%s based on values stored on struct 331 | func (m MySQLDialect) CreateTableSuffix() string { 332 | if m.Engine == "" || m.Encoding == "" { 333 | msg := "gorp - undefined" 334 | 335 | if m.Engine == "" { 336 | msg += " MySQLDialect.Engine" 337 | } 338 | if m.Engine == "" && m.Encoding == "" { 339 | msg += "," 340 | } 341 | if m.Encoding == "" { 342 | msg += " MySQLDialect.Encoding" 343 | } 344 | msg += ". Check that your MySQLDialect was correctly initialized when declared." 345 | panic(msg) 346 | } 347 | 348 | return fmt.Sprintf(" engine=%s charset=%s", m.Engine, m.Encoding) 349 | } 350 | 351 | func (m MySQLDialect) TruncateClause() string { 352 | return "truncate" 353 | } 354 | 355 | // Returns "?" 356 | func (m MySQLDialect) BindVar(i int) string { 357 | return "?" 358 | } 359 | 360 | func (m MySQLDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) { 361 | return standardInsertAutoIncr(exec, insertSql, params...) 362 | } 363 | 364 | func (d MySQLDialect) QuoteField(f string) string { 365 | return "`" + f + "`" 366 | } 367 | 368 | // MySQL does not have schemas like PostgreSQL does, so just escape it like normal 369 | func (d MySQLDialect) QuotedTableForQuery(schema string, table string) string { 370 | return d.QuoteField(table) 371 | } 372 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Relational Persistence # 2 | 3 | [![build status](https://secure.travis-ci.org/coopernurse/gorp.png)](http://travis-ci.org/coopernurse/gorp) 4 | 5 | I hesitate to call gorp an ORM. Go doesn't really have objects, at least 6 | not in the classic Smalltalk/Java sense. There goes the "O". gorp doesn't 7 | know anything about the relationships between your structs (at least not 8 | yet). So the "R" is questionable too (but I use it in the name because, 9 | well, it seemed more clever). 10 | 11 | The "M" is alive and well. Given some Go structs and a database, gorp 12 | should remove a fair amount of boilerplate busy-work from your code. 13 | 14 | I hope that gorp saves you time, minimizes the drudgery of getting data 15 | in and out of your database, and helps your code focus on algorithms, 16 | not infrastructure. 17 | 18 | * Bind struct fields to table columns via API or tag 19 | * Support for embedded structs 20 | * Support for transactions 21 | * Forward engineer db schema from structs (great for unit tests) 22 | * Pre/post insert/update/delete hooks 23 | * Automatically generate insert/update/delete statements for a struct 24 | * Automatic binding of auto increment PKs back to struct after insert 25 | * Delete by primary key(s) 26 | * Select by primary key(s) 27 | * Optional trace sql logging 28 | * Bind arbitrary SQL queries to a struct 29 | * Bind slice to SELECT query results without type assertions 30 | * Use positional or named bind parameters in custom SELECT queries 31 | * Optional optimistic locking using a version column (for update/deletes) 32 | 33 | ## Installation ## 34 | 35 | # install the library: 36 | go get github.com/coopernurse/gorp 37 | 38 | // use in your .go code: 39 | import ( 40 | "github.com/coopernurse/gorp" 41 | ) 42 | 43 | ## API Documentation ## 44 | 45 | Full godoc output from the latest code in master is available here: 46 | 47 | http://godoc.org/github.com/coopernurse/gorp 48 | 49 | ## Quickstart 50 | 51 | ```go 52 | package main 53 | 54 | import ( 55 | "database/sql" 56 | "github.com/coopernurse/gorp" 57 | _ "github.com/mattn/go-sqlite3" 58 | "log" 59 | "time" 60 | ) 61 | 62 | func main() { 63 | // initialize the DbMap 64 | dbmap := initDb() 65 | defer dbmap.Db.Close() 66 | 67 | // delete any existing rows 68 | err := dbmap.TruncateTables() 69 | checkErr(err, "TruncateTables failed") 70 | 71 | // create two posts 72 | p1 := newPost("Go 1.1 released!", "Lorem ipsum lorem ipsum") 73 | p2 := newPost("Go 1.2 released!", "Lorem ipsum lorem ipsum") 74 | 75 | // insert rows - auto increment PKs will be set properly after the insert 76 | err = dbmap.Insert(&p1, &p2) 77 | checkErr(err, "Insert failed") 78 | 79 | // use convenience SelectInt 80 | count, err := dbmap.SelectInt("select count(*) from posts") 81 | checkErr(err, "select count(*) failed") 82 | log.Println("Rows after inserting:", count) 83 | 84 | // update a row 85 | p2.Title = "Go 1.2 is better than ever" 86 | count, err = dbmap.Update(&p2) 87 | checkErr(err, "Update failed") 88 | log.Println("Rows updated:", count) 89 | 90 | // fetch one row - note use of "post_id" instead of "Id" since column is aliased 91 | // 92 | // Postgres users should use $1 instead of ? placeholders 93 | // See 'Known Issues' below 94 | // 95 | err = dbmap.SelectOne(&p2, "select * from posts where post_id=?", p2.Id) 96 | checkErr(err, "SelectOne failed") 97 | log.Println("p2 row:", p2) 98 | 99 | // fetch all rows 100 | var posts []Post 101 | _, err = dbmap.Select(&posts, "select * from posts order by post_id") 102 | checkErr(err, "Select failed") 103 | log.Println("All rows:") 104 | for x, p := range posts { 105 | log.Printf(" %d: %v\n", x, p) 106 | } 107 | 108 | // delete row by PK 109 | count, err = dbmap.Delete(&p1) 110 | checkErr(err, "Delete failed") 111 | log.Println("Rows deleted:", count) 112 | 113 | // delete row manually via Exec 114 | _, err = dbmap.Exec("delete from posts where post_id=?", p2.Id) 115 | checkErr(err, "Exec failed") 116 | 117 | // confirm count is zero 118 | count, err = dbmap.SelectInt("select count(*) from posts") 119 | checkErr(err, "select count(*) failed") 120 | log.Println("Row count - should be zero:", count) 121 | 122 | log.Println("Done!") 123 | } 124 | 125 | type Post struct { 126 | // db tag lets you specify the column name if it differs from the struct field 127 | Id int64 `db:"post_id"` 128 | Created int64 129 | Title string 130 | Body string 131 | } 132 | 133 | func newPost(title, body string) Post { 134 | return Post{ 135 | Created: time.Now().UnixNano(), 136 | Title: title, 137 | Body: body, 138 | } 139 | } 140 | 141 | func initDb() *gorp.DbMap { 142 | // connect to db using standard Go database/sql API 143 | // use whatever database/sql driver you wish 144 | db, err := sql.Open("sqlite3", "/tmp/post_db.bin") 145 | checkErr(err, "sql.Open failed") 146 | 147 | // construct a gorp DbMap 148 | dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} 149 | 150 | // add a table, setting the table name to 'posts' and 151 | // specifying that the Id property is an auto incrementing PK 152 | dbmap.AddTableWithName(Post{}, "posts").SetKeys(true, "Id") 153 | 154 | // create the table. in a production system you'd generally 155 | // use a migration tool, or create the tables via scripts 156 | err = dbmap.CreateTablesIfNotExists() 157 | checkErr(err, "Create tables failed") 158 | 159 | return dbmap 160 | } 161 | 162 | func checkErr(err error, msg string) { 163 | if err != nil { 164 | log.Fatalln(msg, err) 165 | } 166 | } 167 | ``` 168 | 169 | ## Examples ## 170 | 171 | ### Mapping structs to tables ### 172 | 173 | First define some types: 174 | 175 | ```go 176 | type Invoice struct { 177 | Id int64 178 | Created int64 179 | Updated int64 180 | Memo string 181 | PersonId int64 182 | } 183 | 184 | type Person struct { 185 | Id int64 186 | Created int64 187 | Updated int64 188 | FName string 189 | LName string 190 | } 191 | 192 | // Example of using tags to alias fields to column names 193 | // The 'db' value is the column name 194 | // 195 | // A hyphen will cause gorp to skip this field, similar to the 196 | // Go json package. 197 | // 198 | // This is equivalent to using the ColMap methods: 199 | // 200 | // table := dbmap.AddTableWithName(Product{}, "product") 201 | // table.ColMap("Id").Rename("product_id") 202 | // table.ColMap("Price").Rename("unit_price") 203 | // table.ColMap("IgnoreMe").SetTransient(true) 204 | // 205 | type Product struct { 206 | Id int64 `db:"product_id"` 207 | Price int64 `db:"unit_price"` 208 | IgnoreMe string `db:"-"` 209 | } 210 | ``` 211 | 212 | Then create a mapper, typically you'd do this one time at app startup: 213 | 214 | ```go 215 | // connect to db using standard Go database/sql API 216 | // use whatever database/sql driver you wish 217 | db, err := sql.Open("mymysql", "tcp:localhost:3306*mydb/myuser/mypassword") 218 | 219 | // construct a gorp DbMap 220 | dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}} 221 | 222 | // register the structs you wish to use with gorp 223 | // you can also use the shorter dbmap.AddTable() if you 224 | // don't want to override the table name 225 | // 226 | // SetKeys(true) means we have a auto increment primary key, which 227 | // will get automatically bound to your struct post-insert 228 | // 229 | t1 := dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id") 230 | t2 := dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id") 231 | t3 := dbmap.AddTableWithName(Product{}, "product_test").SetKeys(true, "Id") 232 | ``` 233 | 234 | ### Struct Embedding ### 235 | 236 | gorp supports embedding structs. For example: 237 | 238 | ```go 239 | type Names struct { 240 | FirstName string 241 | LastName string 242 | } 243 | 244 | type WithEmbeddedStruct struct { 245 | Id int64 246 | Names 247 | } 248 | 249 | es := &WithEmbeddedStruct{-1, Names{FirstName: "Alice", LastName: "Smith"}} 250 | err := dbmap.Insert(es) 251 | ``` 252 | 253 | See the `TestWithEmbeddedStruct` function in `gorp_test.go` for a full example. 254 | 255 | ### Create/Drop Tables ### 256 | 257 | Automatically create / drop registered tables. This is useful for unit tests 258 | but is entirely optional. You can of course use gorp with tables created manually, 259 | or with a separate migration tool (like goose: https://bitbucket.org/liamstask/goose). 260 | 261 | ```go 262 | // create all registered tables 263 | dbmap.CreateTables() 264 | 265 | // same as above, but uses "if not exists" clause to skip tables that are 266 | // already defined 267 | dbmap.CreateTablesIfNotExists() 268 | 269 | // drop 270 | dbmap.DropTables() 271 | ``` 272 | 273 | ### SQL Logging ### 274 | 275 | Optionally you can pass in a logger to trace all SQL statements. 276 | I recommend enabling this initially while you're getting the feel for what 277 | gorp is doing on your behalf. 278 | 279 | Gorp defines a `GorpLogger` interface that Go's built in `log.Logger` satisfies. 280 | However, you can write your own `GorpLogger` implementation, or use a package such 281 | as `glog` if you want more control over how statements are logged. 282 | 283 | ```go 284 | // Will log all SQL statements + args as they are run 285 | // The first arg is a string prefix to prepend to all log messages 286 | dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds)) 287 | 288 | // Turn off tracing 289 | dbmap.TraceOff() 290 | ``` 291 | 292 | ### Insert ### 293 | 294 | ```go 295 | // Must declare as pointers so optional callback hooks 296 | // can operate on your data, not copies 297 | inv1 := &Invoice{0, 100, 200, "first order", 0} 298 | inv2 := &Invoice{0, 100, 200, "second order", 0} 299 | 300 | // Insert your rows 301 | err := dbmap.Insert(inv1, inv2) 302 | 303 | // Because we called SetKeys(true) on Invoice, the Id field 304 | // will be populated after the Insert() automatically 305 | fmt.Printf("inv1.Id=%d inv2.Id=%d\n", inv1.Id, inv2.Id) 306 | ``` 307 | 308 | ### Update ### 309 | 310 | Continuing the above example, use the `Update` method to modify an Invoice: 311 | 312 | ```go 313 | // count is the # of rows updated, which should be 1 in this example 314 | count, err := dbmap.Update(inv1) 315 | ``` 316 | 317 | ### Delete ### 318 | 319 | If you have primary key(s) defined for a struct, you can use the `Delete` 320 | method to remove rows: 321 | 322 | ```go 323 | count, err := dbmap.Delete(inv1) 324 | ``` 325 | 326 | ### Select by Key ### 327 | 328 | Use the `Get` method to fetch a single row by primary key. It returns 329 | nil if no row is found. 330 | 331 | ```go 332 | // fetch Invoice with Id=99 333 | obj, err := dbmap.Get(Invoice{}, 99) 334 | inv := obj.(*Invoice) 335 | ``` 336 | 337 | ### Ad Hoc SQL ### 338 | 339 | #### SELECT #### 340 | 341 | `Select()` and `SelectOne()` provide a simple way to bind arbitrary queries to a slice 342 | or a single struct. 343 | 344 | ```go 345 | // Select a slice - first return value is not needed when a slice pointer is passed to Select() 346 | var posts []Post 347 | _, err := dbmap.Select(&posts, "select * from post order by id") 348 | 349 | // You can also use primitive types 350 | var ids []string 351 | _, err := dbmap.Select(&ids, "select id from post") 352 | 353 | // Select a single row. 354 | // Returns an error if no row found, or if more than one row is found 355 | var post Post 356 | err := dbmap.SelectOne(&post, "select * from post where id=?", id) 357 | ``` 358 | 359 | Want to do joins? Just write the SQL and the struct. gorp will bind them: 360 | 361 | ```go 362 | // Define a type for your join 363 | // It *must* contain all the columns in your SELECT statement 364 | // 365 | // The names here should match the aliased column names you specify 366 | // in your SQL - no additional binding work required. simple. 367 | // 368 | type InvoicePersonView struct { 369 | InvoiceId int64 370 | PersonId int64 371 | Memo string 372 | FName string 373 | } 374 | 375 | // Create some rows 376 | p1 := &Person{0, 0, 0, "bob", "smith"} 377 | dbmap.Insert(p1) 378 | 379 | // notice how we can wire up p1.Id to the invoice easily 380 | inv1 := &Invoice{0, 0, 0, "xmas order", p1.Id} 381 | dbmap.Insert(inv1) 382 | 383 | // Run your query 384 | query := "select i.Id InvoiceId, p.Id PersonId, i.Memo, p.FName " + 385 | "from invoice_test i, person_test p " + 386 | "where i.PersonId = p.Id" 387 | 388 | // pass a slice to Select() 389 | var list []InvoicePersonView 390 | _, err := dbmap.Select(&list, query) 391 | 392 | // this should test true 393 | expected := InvoicePersonView{inv1.Id, p1.Id, inv1.Memo, p1.FName} 394 | if reflect.DeepEqual(list[0], expected) { 395 | fmt.Println("Woot! My join worked!") 396 | } 397 | ``` 398 | 399 | #### SELECT string or int64 #### 400 | 401 | gorp provides a few convenience methods for selecting a single string or int64. 402 | 403 | ```go 404 | // select single int64 from db (use $1 instead of ? for postgresql) 405 | i64, err := dbmap.SelectInt("select count(*) from foo where blah=?", blahVal) 406 | 407 | // select single string from db: 408 | s, err := dbmap.SelectStr("select name from foo where blah=?", blahVal) 409 | 410 | ``` 411 | 412 | #### Named bind parameters #### 413 | 414 | You may use a map or struct to bind parameters by name. This is currently 415 | only supported in SELECT queries. 416 | 417 | ```go 418 | _, err := dbm.Select(&dest, "select * from Foo where name = :name and age = :age", map[string]interface{}{ 419 | "name": "Rob", 420 | "age": 31, 421 | }) 422 | ``` 423 | 424 | #### UPDATE / DELETE #### 425 | 426 | You can execute raw SQL if you wish. Particularly good for batch operations. 427 | 428 | ```go 429 | res, err := dbmap.Exec("delete from invoice_test where PersonId=?", 10) 430 | ``` 431 | 432 | ### Transactions ### 433 | 434 | You can batch operations into a transaction: 435 | 436 | ```go 437 | func InsertInv(dbmap *DbMap, inv *Invoice, per *Person) error { 438 | // Start a new transaction 439 | trans, err := dbmap.Begin() 440 | if err != nil { 441 | return err 442 | } 443 | 444 | trans.Insert(per) 445 | inv.PersonId = per.Id 446 | trans.Insert(inv) 447 | 448 | // if the commit is successful, a nil error is returned 449 | return trans.Commit() 450 | } 451 | ``` 452 | 453 | ### Hooks ### 454 | 455 | Use hooks to update data before/after saving to the db. Good for timestamps: 456 | 457 | ```go 458 | // implement the PreInsert and PreUpdate hooks 459 | func (i *Invoice) PreInsert(s gorp.SqlExecutor) error { 460 | i.Created = time.Now().UnixNano() 461 | i.Updated = i.Created 462 | return nil 463 | } 464 | 465 | func (i *Invoice) PreUpdate(s gorp.SqlExecutor) error { 466 | i.Updated = time.Now().UnixNano() 467 | return nil 468 | } 469 | 470 | // You can use the SqlExecutor to cascade additional SQL 471 | // Take care to avoid cycles. gorp won't prevent them. 472 | // 473 | // Here's an example of a cascading delete 474 | // 475 | func (p *Person) PreDelete(s gorp.SqlExecutor) error { 476 | query := "delete from invoice_test where PersonId=?" 477 | err := s.Exec(query, p.Id); if err != nil { 478 | return err 479 | } 480 | return nil 481 | } 482 | ``` 483 | 484 | Full list of hooks that you can implement: 485 | 486 | PostGet 487 | PreInsert 488 | PostInsert 489 | PreUpdate 490 | PostUpdate 491 | PreDelete 492 | PostDelete 493 | 494 | All have the same signature. for example: 495 | 496 | func (p *MyStruct) PostUpdate(s gorp.SqlExecutor) error 497 | 498 | ### Optimistic Locking ### 499 | 500 | gorp provides a simple optimistic locking feature, similar to Java's JPA, that 501 | will raise an error if you try to update/delete a row whose `version` column 502 | has a value different than the one in memory. This provides a safe way to do 503 | "select then update" style operations without explicit read and write locks. 504 | 505 | ```go 506 | // Version is an auto-incremented number, managed by gorp 507 | // If this property is present on your struct, update 508 | // operations will be constrained 509 | // 510 | // For example, say we defined Person as: 511 | 512 | type Person struct { 513 | Id int64 514 | Created int64 515 | Updated int64 516 | FName string 517 | LName string 518 | 519 | // automatically used as the Version col 520 | // use table.SetVersionCol("columnName") to map a different 521 | // struct field as the version field 522 | Version int64 523 | } 524 | 525 | p1 := &Person{0, 0, 0, "Bob", "Smith", 0} 526 | dbmap.Insert(p1) // Version is now 1 527 | 528 | obj, err := dbmap.Get(Person{}, p1.Id) 529 | p2 := obj.(*Person) 530 | p2.LName = "Edwards" 531 | dbmap.Update(p2) // Version is now 2 532 | 533 | p1.LName = "Howard" 534 | 535 | // Raises error because p1.Version == 1, which is out of date 536 | count, err := dbmap.Update(p1) 537 | _, ok := err.(gorp.OptimisticLockError) 538 | if ok { 539 | // should reach this statement 540 | 541 | // in a real app you might reload the row and retry, or 542 | // you might propegate this to the user, depending on the desired 543 | // semantics 544 | fmt.Printf("Tried to update row with stale data: %v\n", err) 545 | } else { 546 | // some other db error occurred - log or return up the stack 547 | fmt.Printf("Unknown db err: %v\n", err) 548 | } 549 | ``` 550 | 551 | ## Database Drivers ## 552 | 553 | gorp uses the Go 1 `database/sql` package. A full list of compliant drivers is available here: 554 | 555 | http://code.google.com/p/go-wiki/wiki/SQLDrivers 556 | 557 | Sadly, SQL databases differ on various issues. gorp provides a Dialect interface that should be 558 | implemented per database vendor. Dialects are provided for: 559 | 560 | * MySQL 561 | * PostgreSQL 562 | * sqlite3 563 | 564 | Each of these three databases pass the test suite. See `gorp_test.go` for example 565 | DSNs for these three databases. 566 | 567 | ## Known Issues ## 568 | 569 | ### SQL placeholder portability ### 570 | 571 | Different databases use different strings to indicate variable placeholders in 572 | prepared SQL statements. Unlike some database abstraction layers (such as JDBC), 573 | Go's `database/sql` does not standardize this. 574 | 575 | SQL generated by gorp in the `Insert`, `Update`, `Delete`, and `Get` methods delegates 576 | to a Dialect implementation for each database, and will generate portable SQL. 577 | 578 | Raw SQL strings passed to `Exec`, `Select`, `SelectOne`, `SelectInt`, etc will not be 579 | parsed. Consequently you may have portability issues if you write a query like this: 580 | 581 | ```go 582 | // works on MySQL and Sqlite3, but not with Postgresql 583 | err := dbmap.SelectOne(&val, "select * from foo where id = ?", 30) 584 | ``` 585 | 586 | In `Select` and `SelectOne` you can use named parameters to work around this. 587 | The following is portable: 588 | 589 | ```go 590 | err := dbmap.SelectOne(&val, "select * from foo where id = :id", 591 | map[string]interface{} { "id": 30}) 592 | ``` 593 | 594 | ### time.Time and time zones ### 595 | 596 | gorp will pass `time.Time` fields through to the `database/sql` driver, but note that 597 | the behavior of this type varies across database drivers. 598 | 599 | MySQL users should be especially cautious. See: https://github.com/ziutek/mymysql/pull/77 600 | 601 | To avoid any potential issues with timezone/DST, consider using an integer field for time 602 | data and storing UNIX time. 603 | 604 | ## Running the tests ## 605 | 606 | The included tests may be run against MySQL, Postgresql, or sqlite3. 607 | You must set two environment variables so the test code knows which driver to 608 | use, and how to connect to your database. 609 | 610 | ```sh 611 | # MySQL example: 612 | export GORP_TEST_DSN=gomysql_test/gomysql_test/abc123 613 | export GORP_TEST_DIALECT=mysql 614 | 615 | # run the tests 616 | go test 617 | 618 | # run the tests and benchmarks 619 | go test -bench="Bench" -benchtime 10 620 | ``` 621 | 622 | Valid `GORP_TEST_DIALECT` values are: "mysql", "postgres", "sqlite3" 623 | See the `test_all.sh` script for examples of all 3 databases. This is the script I run 624 | locally to test the library. 625 | 626 | ## Performance ## 627 | 628 | 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. 629 | 630 | ## Pull requests / Contributions 631 | 632 | Contributions are very welcome. Please follow these guidelines: 633 | 634 | * Fork the `develop` branch and issue pull requests targeting the `develop` branch 635 | * If you don't do this, I'll likely cherry pick your commit into develop 636 | * If you are adding an enhancement, please open an issue first with your proposed change. 637 | * Changes that break backwards compatibility in the public API are only accepted after we 638 | discuss on a GitHub issue for a while. 639 | 640 | Thanks! 641 | 642 | ## Contributors 643 | 644 | * matthias-margush - column aliasing via tags 645 | * Rob Figueiredo - @robfig 646 | * Quinn Slack - @sqs 647 | -------------------------------------------------------------------------------- /gorp_test.go: -------------------------------------------------------------------------------- 1 | package gorp 2 | 3 | import ( 4 | "bytes" 5 | "database/sql" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | _ "github.com/go-sql-driver/mysql" 10 | _ "github.com/lib/pq" 11 | _ "github.com/mattn/go-sqlite3" 12 | _ "github.com/ziutek/mymysql/godrv" 13 | "log" 14 | "os" 15 | "reflect" 16 | "strings" 17 | "testing" 18 | "time" 19 | ) 20 | 21 | type Invoice struct { 22 | Id int64 23 | Created int64 24 | Updated int64 25 | Memo string 26 | PersonId int64 27 | IsPaid bool 28 | } 29 | 30 | type OverriddenInvoice struct { 31 | Invoice 32 | Id string 33 | } 34 | 35 | type Person struct { 36 | Id int64 37 | Created int64 38 | Updated int64 39 | FName string 40 | LName string 41 | Version int64 42 | } 43 | 44 | type InvoicePersonView struct { 45 | InvoiceId int64 46 | PersonId int64 47 | Memo string 48 | FName string 49 | LegacyVersion int64 50 | } 51 | 52 | type TableWithNull struct { 53 | Id int64 54 | Str sql.NullString 55 | Int64 sql.NullInt64 56 | Float64 sql.NullFloat64 57 | Bool sql.NullBool 58 | Bytes []byte 59 | } 60 | 61 | type WithIgnoredColumn struct { 62 | internal int64 `db:"-"` 63 | Id int64 64 | Created int64 65 | } 66 | 67 | type WithStringPk struct { 68 | Id string 69 | Name string 70 | } 71 | 72 | type CustomStringType string 73 | 74 | type TypeConversionExample struct { 75 | Id int64 76 | PersonJSON Person 77 | Name CustomStringType 78 | } 79 | 80 | type PersonUInt32 struct { 81 | Id uint32 82 | Name string 83 | } 84 | 85 | type PersonUInt64 struct { 86 | Id uint64 87 | Name string 88 | } 89 | 90 | type PersonUInt16 struct { 91 | Id uint16 92 | Name string 93 | } 94 | 95 | type WithEmbeddedStruct struct { 96 | Id int64 97 | Names 98 | } 99 | 100 | type WithEmbeddedStructBeforeAutoincrField struct { 101 | Names 102 | Id int64 103 | } 104 | 105 | type WithEmbeddedAutoincr struct { 106 | WithEmbeddedStruct 107 | MiddleName string 108 | } 109 | 110 | type Names struct { 111 | FirstName string 112 | LastName string 113 | } 114 | 115 | type UniqueColumns struct { 116 | FirstName string 117 | LastName string 118 | City string 119 | ZipCode int64 120 | } 121 | 122 | type testTypeConverter struct{} 123 | 124 | func (me testTypeConverter) ToDb(val interface{}) (interface{}, error) { 125 | 126 | switch t := val.(type) { 127 | case Person: 128 | b, err := json.Marshal(t) 129 | if err != nil { 130 | return "", err 131 | } 132 | return string(b), nil 133 | case CustomStringType: 134 | return string(t), nil 135 | } 136 | 137 | return val, nil 138 | } 139 | 140 | func (me testTypeConverter) FromDb(target interface{}) (CustomScanner, bool) { 141 | switch target.(type) { 142 | case *Person: 143 | binder := func(holder, target interface{}) error { 144 | s, ok := holder.(*string) 145 | if !ok { 146 | return errors.New("FromDb: Unable to convert Person to *string") 147 | } 148 | b := []byte(*s) 149 | return json.Unmarshal(b, target) 150 | } 151 | return CustomScanner{new(string), target, binder}, true 152 | case *CustomStringType: 153 | binder := func(holder, target interface{}) error { 154 | s, ok := holder.(*string) 155 | if !ok { 156 | return errors.New("FromDb: Unable to convert CustomStringType to *string") 157 | } 158 | st, ok := target.(*CustomStringType) 159 | if !ok { 160 | return errors.New(fmt.Sprint("FromDb: Unable to convert target to *CustomStringType: ", reflect.TypeOf(target))) 161 | } 162 | *st = CustomStringType(*s) 163 | return nil 164 | } 165 | return CustomScanner{new(string), target, binder}, true 166 | } 167 | 168 | return CustomScanner{}, false 169 | } 170 | 171 | func (p *Person) PreInsert(s SqlExecutor) error { 172 | p.Created = time.Now().UnixNano() 173 | p.Updated = p.Created 174 | if p.FName == "badname" { 175 | return fmt.Errorf("Invalid name: %s", p.FName) 176 | } 177 | return nil 178 | } 179 | 180 | func (p *Person) PostInsert(s SqlExecutor) error { 181 | p.LName = "postinsert" 182 | return nil 183 | } 184 | 185 | func (p *Person) PreUpdate(s SqlExecutor) error { 186 | p.FName = "preupdate" 187 | return nil 188 | } 189 | 190 | func (p *Person) PostUpdate(s SqlExecutor) error { 191 | p.LName = "postupdate" 192 | return nil 193 | } 194 | 195 | func (p *Person) PreDelete(s SqlExecutor) error { 196 | p.FName = "predelete" 197 | return nil 198 | } 199 | 200 | func (p *Person) PostDelete(s SqlExecutor) error { 201 | p.LName = "postdelete" 202 | return nil 203 | } 204 | 205 | func (p *Person) PostGet(s SqlExecutor) error { 206 | p.LName = "postget" 207 | return nil 208 | } 209 | 210 | type PersistentUser struct { 211 | Key int32 212 | Id string 213 | PassedTraining bool 214 | } 215 | 216 | func TestCreateTablesIfNotExists(t *testing.T) { 217 | dbmap := initDbMap() 218 | defer dropAndClose(dbmap) 219 | 220 | err := dbmap.CreateTablesIfNotExists() 221 | if err != nil { 222 | t.Error(err) 223 | } 224 | } 225 | 226 | func TestTruncateTables(t *testing.T) { 227 | dbmap := initDbMap() 228 | defer dropAndClose(dbmap) 229 | err := dbmap.CreateTablesIfNotExists() 230 | if err != nil { 231 | t.Error(err) 232 | } 233 | 234 | // Insert some data 235 | p1 := &Person{0, 0, 0, "Bob", "Smith", 0} 236 | dbmap.Insert(p1) 237 | inv := &Invoice{0, 0, 1, "my invoice", 0, true} 238 | dbmap.Insert(inv) 239 | 240 | err = dbmap.TruncateTables() 241 | if err != nil { 242 | t.Error(err) 243 | } 244 | 245 | // Make sure all rows are deleted 246 | rows, _ := dbmap.Select(Person{}, "SELECT * FROM person_test") 247 | if len(rows) != 0 { 248 | t.Errorf("Expected 0 person rows, got %d", len(rows)) 249 | } 250 | rows, _ = dbmap.Select(Invoice{}, "SELECT * FROM invoice_test") 251 | if len(rows) != 0 { 252 | t.Errorf("Expected 0 invoice rows, got %d", len(rows)) 253 | } 254 | } 255 | 256 | func TestUIntPrimaryKey(t *testing.T) { 257 | dbmap := newDbMap() 258 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 259 | dbmap.AddTable(PersonUInt64{}).SetKeys(true, "Id") 260 | dbmap.AddTable(PersonUInt32{}).SetKeys(true, "Id") 261 | dbmap.AddTable(PersonUInt16{}).SetKeys(true, "Id") 262 | err := dbmap.CreateTablesIfNotExists() 263 | if err != nil { 264 | panic(err) 265 | } 266 | defer dropAndClose(dbmap) 267 | 268 | p1 := &PersonUInt64{0, "name1"} 269 | p2 := &PersonUInt32{0, "name2"} 270 | p3 := &PersonUInt16{0, "name3"} 271 | err = dbmap.Insert(p1, p2, p3) 272 | if err != nil { 273 | t.Error(err) 274 | } 275 | if p1.Id != 1 { 276 | t.Errorf("%d != 1", p1.Id) 277 | } 278 | if p2.Id != 1 { 279 | t.Errorf("%d != 1", p2.Id) 280 | } 281 | if p3.Id != 1 { 282 | t.Errorf("%d != 1", p3.Id) 283 | } 284 | } 285 | 286 | func TestSetUniqueTogether(t *testing.T) { 287 | dbmap := newDbMap() 288 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 289 | dbmap.AddTable(UniqueColumns{}).SetUniqueTogether("FirstName", "LastName").SetUniqueTogether("City", "ZipCode") 290 | err := dbmap.CreateTablesIfNotExists() 291 | if err != nil { 292 | panic(err) 293 | } 294 | defer dropAndClose(dbmap) 295 | 296 | n1 := &UniqueColumns{"Steve", "Jobs", "Cupertino", 95014} 297 | err = dbmap.Insert(n1) 298 | if err != nil { 299 | t.Error(err) 300 | } 301 | 302 | // Should fail because of the first constraint 303 | n2 := &UniqueColumns{"Steve", "Jobs", "Sunnyvale", 94085} 304 | err = dbmap.Insert(n2) 305 | if err == nil { 306 | t.Error(err) 307 | } 308 | // "unique" for Postgres/SQLite, "Duplicate entry" for MySQL 309 | if !strings.Contains(err.Error(), "unique") && !strings.Contains(err.Error(), "Duplicate entry") { 310 | t.Error(err) 311 | } 312 | 313 | // Should also fail because of the second unique-together 314 | n3 := &UniqueColumns{"Steve", "Wozniak", "Cupertino", 95014} 315 | err = dbmap.Insert(n3) 316 | if err == nil { 317 | t.Error(err) 318 | } 319 | // "unique" for Postgres/SQLite, "Duplicate entry" for MySQL 320 | if !strings.Contains(err.Error(), "unique") && !strings.Contains(err.Error(), "Duplicate entry") { 321 | t.Error(err) 322 | } 323 | 324 | // This one should finally succeed 325 | n4 := &UniqueColumns{"Steve", "Wozniak", "Sunnyvale", 94085} 326 | err = dbmap.Insert(n4) 327 | if err != nil { 328 | t.Error(err) 329 | } 330 | } 331 | 332 | func TestPersistentUser(t *testing.T) { 333 | dbmap := newDbMap() 334 | dbmap.Exec("drop table if exists PersistentUser") 335 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 336 | table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key") 337 | table.ColMap("Key").Rename("mykey") 338 | err := dbmap.CreateTablesIfNotExists() 339 | if err != nil { 340 | panic(err) 341 | } 342 | defer dropAndClose(dbmap) 343 | pu := &PersistentUser{43, "33r", false} 344 | err = dbmap.Insert(pu) 345 | if err != nil { 346 | panic(err) 347 | } 348 | 349 | // prove we can pass a pointer into Get 350 | pu2, err := dbmap.Get(pu, pu.Key) 351 | if err != nil { 352 | panic(err) 353 | } 354 | if !reflect.DeepEqual(pu, pu2) { 355 | t.Errorf("%v!=%v", pu, pu2) 356 | } 357 | 358 | arr, err := dbmap.Select(pu, "select * from PersistentUser") 359 | if err != nil { 360 | panic(err) 361 | } 362 | if !reflect.DeepEqual(pu, arr[0]) { 363 | t.Errorf("%v!=%v", pu, arr[0]) 364 | } 365 | 366 | // prove we can get the results back in a slice 367 | var puArr []*PersistentUser 368 | _, err = dbmap.Select(&puArr, "select * from PersistentUser") 369 | if err != nil { 370 | panic(err) 371 | } 372 | if len(puArr) != 1 { 373 | t.Errorf("Expected one persistentuser, found none") 374 | } 375 | if !reflect.DeepEqual(pu, puArr[0]) { 376 | t.Errorf("%v!=%v", pu, puArr[0]) 377 | } 378 | 379 | // prove we can get the results back in a non-pointer slice 380 | var puValues []PersistentUser 381 | _, err = dbmap.Select(&puValues, "select * from PersistentUser") 382 | if err != nil { 383 | panic(err) 384 | } 385 | if len(puValues) != 1 { 386 | t.Errorf("Expected one persistentuser, found none") 387 | } 388 | if !reflect.DeepEqual(*pu, puValues[0]) { 389 | t.Errorf("%v!=%v", *pu, puValues[0]) 390 | } 391 | 392 | // prove we can get the results back in a string slice 393 | var idArr []*string 394 | _, err = dbmap.Select(&idArr, "select Id from PersistentUser") 395 | if err != nil { 396 | panic(err) 397 | } 398 | if len(idArr) != 1 { 399 | t.Errorf("Expected one persistentuser, found none") 400 | } 401 | if !reflect.DeepEqual(pu.Id, *idArr[0]) { 402 | t.Errorf("%v!=%v", pu.Id, *idArr[0]) 403 | } 404 | 405 | // prove we can get the results back in an int slice 406 | var keyArr []*int32 407 | _, err = dbmap.Select(&keyArr, "select mykey from PersistentUser") 408 | if err != nil { 409 | panic(err) 410 | } 411 | if len(keyArr) != 1 { 412 | t.Errorf("Expected one persistentuser, found none") 413 | } 414 | if !reflect.DeepEqual(pu.Key, *keyArr[0]) { 415 | t.Errorf("%v!=%v", pu.Key, *keyArr[0]) 416 | } 417 | 418 | // prove we can get the results back in a bool slice 419 | var passedArr []*bool 420 | _, err = dbmap.Select(&passedArr, "select PassedTraining from PersistentUser") 421 | if err != nil { 422 | panic(err) 423 | } 424 | if len(passedArr) != 1 { 425 | t.Errorf("Expected one persistentuser, found none") 426 | } 427 | if !reflect.DeepEqual(pu.PassedTraining, *passedArr[0]) { 428 | t.Errorf("%v!=%v", pu.PassedTraining, *passedArr[0]) 429 | } 430 | 431 | // prove we can get the results back in a non-pointer slice 432 | var stringArr []string 433 | _, err = dbmap.Select(&stringArr, "select Id from PersistentUser") 434 | if err != nil { 435 | panic(err) 436 | } 437 | if len(stringArr) != 1 { 438 | t.Errorf("Expected one persistentuser, found none") 439 | } 440 | if !reflect.DeepEqual(pu.Id, stringArr[0]) { 441 | t.Errorf("%v!=%v", pu.Id, stringArr[0]) 442 | } 443 | } 444 | 445 | func TestNamedQueryMap(t *testing.T) { 446 | dbmap := newDbMap() 447 | dbmap.Exec("drop table if exists PersistentUser") 448 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 449 | table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key") 450 | table.ColMap("Key").Rename("mykey") 451 | err := dbmap.CreateTablesIfNotExists() 452 | if err != nil { 453 | panic(err) 454 | } 455 | defer dropAndClose(dbmap) 456 | pu := &PersistentUser{43, "33r", false} 457 | pu2 := &PersistentUser{500, "abc", false} 458 | err = dbmap.Insert(pu, pu2) 459 | if err != nil { 460 | panic(err) 461 | } 462 | 463 | // Test simple case 464 | var puArr []*PersistentUser 465 | _, err = dbmap.Select(&puArr, "select * from PersistentUser where mykey = :Key", map[string]interface{}{ 466 | "Key": 43, 467 | }) 468 | if err != nil { 469 | t.Errorf("Failed to select: %s", err) 470 | t.FailNow() 471 | } 472 | if len(puArr) != 1 { 473 | t.Errorf("Expected one persistentuser, found none") 474 | } 475 | if !reflect.DeepEqual(pu, puArr[0]) { 476 | t.Errorf("%v!=%v", pu, puArr[0]) 477 | } 478 | 479 | // Test more specific map value type is ok 480 | puArr = nil 481 | _, err = dbmap.Select(&puArr, "select * from PersistentUser where mykey = :Key", map[string]int{ 482 | "Key": 43, 483 | }) 484 | if err != nil { 485 | t.Errorf("Failed to select: %s", err) 486 | t.FailNow() 487 | } 488 | if len(puArr) != 1 { 489 | t.Errorf("Expected one persistentuser, found none") 490 | } 491 | 492 | // Test multiple parameters set. 493 | puArr = nil 494 | _, err = dbmap.Select(&puArr, ` 495 | select * from PersistentUser 496 | where mykey = :Key 497 | and PassedTraining = :PassedTraining 498 | and Id = :Id`, map[string]interface{}{ 499 | "Key": 43, 500 | "PassedTraining": false, 501 | "Id": "33r", 502 | }) 503 | if err != nil { 504 | t.Errorf("Failed to select: %s", err) 505 | t.FailNow() 506 | } 507 | if len(puArr) != 1 { 508 | t.Errorf("Expected one persistentuser, found none") 509 | } 510 | 511 | // Test colon within a non-key string 512 | // Test having extra, unused properties in the map. 513 | puArr = nil 514 | _, err = dbmap.Select(&puArr, ` 515 | select * from PersistentUser 516 | where mykey = :Key 517 | and Id != 'abc:def'`, map[string]interface{}{ 518 | "Key": 43, 519 | "PassedTraining": false, 520 | }) 521 | if err != nil { 522 | t.Errorf("Failed to select: %s", err) 523 | t.FailNow() 524 | } 525 | if len(puArr) != 1 { 526 | t.Errorf("Expected one persistentuser, found none") 527 | } 528 | } 529 | 530 | func TestNamedQueryStruct(t *testing.T) { 531 | dbmap := newDbMap() 532 | dbmap.Exec("drop table if exists PersistentUser") 533 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 534 | table := dbmap.AddTable(PersistentUser{}).SetKeys(false, "Key") 535 | table.ColMap("Key").Rename("mykey") 536 | err := dbmap.CreateTablesIfNotExists() 537 | if err != nil { 538 | panic(err) 539 | } 540 | defer dropAndClose(dbmap) 541 | pu := &PersistentUser{43, "33r", false} 542 | pu2 := &PersistentUser{500, "abc", false} 543 | err = dbmap.Insert(pu, pu2) 544 | if err != nil { 545 | panic(err) 546 | } 547 | 548 | // Test select self 549 | var puArr []*PersistentUser 550 | _, err = dbmap.Select(&puArr, ` 551 | select * from PersistentUser 552 | where mykey = :Key 553 | and PassedTraining = :PassedTraining 554 | and Id = :Id`, pu) 555 | if err != nil { 556 | t.Errorf("Failed to select: %s", err) 557 | t.FailNow() 558 | } 559 | if len(puArr) != 1 { 560 | t.Errorf("Expected one persistentuser, found none") 561 | } 562 | if !reflect.DeepEqual(pu, puArr[0]) { 563 | t.Errorf("%v!=%v", pu, puArr[0]) 564 | } 565 | } 566 | 567 | // Ensure that the slices containing SQL results are non-nil when the result set is empty. 568 | func TestReturnsNonNilSlice(t *testing.T) { 569 | dbmap := initDbMap() 570 | defer dropAndClose(dbmap) 571 | noResultsSQL := "select * from invoice_test where id=99999" 572 | var r1 []*Invoice 573 | _rawselect(dbmap, &r1, noResultsSQL) 574 | if r1 == nil { 575 | t.Errorf("r1==nil") 576 | } 577 | 578 | r2 := _rawselect(dbmap, Invoice{}, noResultsSQL) 579 | if r2 == nil { 580 | t.Errorf("r2==nil") 581 | } 582 | } 583 | 584 | func TestOverrideVersionCol(t *testing.T) { 585 | dbmap := initDbMap() 586 | dbmap.DropTables() 587 | t1 := dbmap.AddTable(InvoicePersonView{}).SetKeys(false, "InvoiceId", "PersonId") 588 | err := dbmap.CreateTables() 589 | 590 | if err != nil { 591 | panic(err) 592 | } 593 | defer dropAndClose(dbmap) 594 | c1 := t1.SetVersionCol("LegacyVersion") 595 | if c1.ColumnName != "LegacyVersion" { 596 | t.Errorf("Wrong col returned: %v", c1) 597 | } 598 | 599 | ipv := &InvoicePersonView{1, 2, "memo", "fname", 0} 600 | _update(dbmap, ipv) 601 | if ipv.LegacyVersion != 1 { 602 | t.Errorf("LegacyVersion not updated: %d", ipv.LegacyVersion) 603 | } 604 | } 605 | 606 | func TestOptimisticLocking(t *testing.T) { 607 | dbmap := initDbMap() 608 | defer dropAndClose(dbmap) 609 | 610 | p1 := &Person{0, 0, 0, "Bob", "Smith", 0} 611 | dbmap.Insert(p1) // Version is now 1 612 | if p1.Version != 1 { 613 | t.Errorf("Insert didn't incr Version: %d != %d", 1, p1.Version) 614 | return 615 | } 616 | if p1.Id == 0 { 617 | t.Errorf("Insert didn't return a generated PK") 618 | return 619 | } 620 | 621 | obj, err := dbmap.Get(Person{}, p1.Id) 622 | if err != nil { 623 | panic(err) 624 | } 625 | p2 := obj.(*Person) 626 | p2.LName = "Edwards" 627 | dbmap.Update(p2) // Version is now 2 628 | if p2.Version != 2 { 629 | t.Errorf("Update didn't incr Version: %d != %d", 2, p2.Version) 630 | } 631 | 632 | p1.LName = "Howard" 633 | count, err := dbmap.Update(p1) 634 | if _, ok := err.(OptimisticLockError); !ok { 635 | t.Errorf("update - Expected OptimisticLockError, got: %v", err) 636 | } 637 | if count != -1 { 638 | t.Errorf("update - Expected -1 count, got: %d", count) 639 | } 640 | 641 | count, err = dbmap.Delete(p1) 642 | if _, ok := err.(OptimisticLockError); !ok { 643 | t.Errorf("delete - Expected OptimisticLockError, got: %v", err) 644 | } 645 | if count != -1 { 646 | t.Errorf("delete - Expected -1 count, got: %d", count) 647 | } 648 | } 649 | 650 | // what happens if a legacy table has a null value? 651 | func TestDoubleAddTable(t *testing.T) { 652 | dbmap := newDbMap() 653 | t1 := dbmap.AddTable(TableWithNull{}).SetKeys(false, "Id") 654 | t2 := dbmap.AddTable(TableWithNull{}) 655 | if t1 != t2 { 656 | t.Errorf("%v != %v", t1, t2) 657 | } 658 | } 659 | 660 | // what happens if a legacy table has a null value? 661 | func TestNullValues(t *testing.T) { 662 | dbmap := initDbMapNulls() 663 | defer dropAndClose(dbmap) 664 | 665 | // insert a row directly 666 | _rawexec(dbmap, "insert into TableWithNull values (10, null, "+ 667 | "null, null, null, null)") 668 | 669 | // try to load it 670 | expected := &TableWithNull{Id: 10} 671 | obj := _get(dbmap, TableWithNull{}, 10) 672 | t1 := obj.(*TableWithNull) 673 | if !reflect.DeepEqual(expected, t1) { 674 | t.Errorf("%v != %v", expected, t1) 675 | } 676 | 677 | // update it 678 | t1.Str = sql.NullString{"hi", true} 679 | expected.Str = t1.Str 680 | t1.Int64 = sql.NullInt64{999, true} 681 | expected.Int64 = t1.Int64 682 | t1.Float64 = sql.NullFloat64{53.33, true} 683 | expected.Float64 = t1.Float64 684 | t1.Bool = sql.NullBool{true, true} 685 | expected.Bool = t1.Bool 686 | t1.Bytes = []byte{1, 30, 31, 33} 687 | expected.Bytes = t1.Bytes 688 | _update(dbmap, t1) 689 | 690 | obj = _get(dbmap, TableWithNull{}, 10) 691 | t1 = obj.(*TableWithNull) 692 | if t1.Str.String != "hi" { 693 | t.Errorf("%s != hi", t1.Str.String) 694 | } 695 | if !reflect.DeepEqual(expected, t1) { 696 | t.Errorf("%v != %v", expected, t1) 697 | } 698 | } 699 | 700 | func TestColumnProps(t *testing.T) { 701 | dbmap := newDbMap() 702 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 703 | t1 := dbmap.AddTable(Invoice{}).SetKeys(true, "Id") 704 | t1.ColMap("Created").Rename("date_created") 705 | t1.ColMap("Updated").SetTransient(true) 706 | t1.ColMap("Memo").SetMaxSize(10) 707 | t1.ColMap("PersonId").SetUnique(true) 708 | 709 | err := dbmap.CreateTables() 710 | if err != nil { 711 | panic(err) 712 | } 713 | defer dropAndClose(dbmap) 714 | 715 | // test transient 716 | inv := &Invoice{0, 0, 1, "my invoice", 0, true} 717 | _insert(dbmap, inv) 718 | obj := _get(dbmap, Invoice{}, inv.Id) 719 | inv = obj.(*Invoice) 720 | if inv.Updated != 0 { 721 | t.Errorf("Saved transient column 'Updated'") 722 | } 723 | 724 | // test max size 725 | inv.Memo = "this memo is too long" 726 | err = dbmap.Insert(inv) 727 | if err == nil { 728 | t.Errorf("max size exceeded, but Insert did not fail.") 729 | } 730 | 731 | // test unique - same person id 732 | inv = &Invoice{0, 0, 1, "my invoice2", 0, false} 733 | err = dbmap.Insert(inv) 734 | if err == nil { 735 | t.Errorf("same PersonId inserted, but Insert did not fail.") 736 | } 737 | } 738 | 739 | func TestRawSelect(t *testing.T) { 740 | dbmap := initDbMap() 741 | defer dropAndClose(dbmap) 742 | 743 | p1 := &Person{0, 0, 0, "bob", "smith", 0} 744 | _insert(dbmap, p1) 745 | 746 | inv1 := &Invoice{0, 0, 0, "xmas order", p1.Id, true} 747 | _insert(dbmap, inv1) 748 | 749 | expected := &InvoicePersonView{inv1.Id, p1.Id, inv1.Memo, p1.FName, 0} 750 | 751 | query := "select i.Id InvoiceId, p.Id PersonId, i.Memo, p.FName " + 752 | "from invoice_test i, person_test p " + 753 | "where i.PersonId = p.Id" 754 | list := _rawselect(dbmap, InvoicePersonView{}, query) 755 | if len(list) != 1 { 756 | t.Errorf("len(list) != 1: %d", len(list)) 757 | } else if !reflect.DeepEqual(expected, list[0]) { 758 | t.Errorf("%v != %v", expected, list[0]) 759 | } 760 | } 761 | 762 | func TestHooks(t *testing.T) { 763 | dbmap := initDbMap() 764 | defer dropAndClose(dbmap) 765 | 766 | p1 := &Person{0, 0, 0, "bob", "smith", 0} 767 | _insert(dbmap, p1) 768 | if p1.Created == 0 || p1.Updated == 0 { 769 | t.Errorf("p1.PreInsert() didn't run: %v", p1) 770 | } else if p1.LName != "postinsert" { 771 | t.Errorf("p1.PostInsert() didn't run: %v", p1) 772 | } 773 | 774 | obj := _get(dbmap, Person{}, p1.Id) 775 | p1 = obj.(*Person) 776 | if p1.LName != "postget" { 777 | t.Errorf("p1.PostGet() didn't run: %v", p1) 778 | } 779 | 780 | _update(dbmap, p1) 781 | if p1.FName != "preupdate" { 782 | t.Errorf("p1.PreUpdate() didn't run: %v", p1) 783 | } else if p1.LName != "postupdate" { 784 | t.Errorf("p1.PostUpdate() didn't run: %v", p1) 785 | } 786 | 787 | var persons []*Person 788 | bindVar := dbmap.Dialect.BindVar(0) 789 | _rawselect(dbmap, &persons, "select * from person_test where id = "+bindVar, p1.Id) 790 | if persons[0].LName != "postget" { 791 | t.Errorf("p1.PostGet() didn't run after select: %v", p1) 792 | } 793 | 794 | _del(dbmap, p1) 795 | if p1.FName != "predelete" { 796 | t.Errorf("p1.PreDelete() didn't run: %v", p1) 797 | } else if p1.LName != "postdelete" { 798 | t.Errorf("p1.PostDelete() didn't run: %v", p1) 799 | } 800 | 801 | // Test error case 802 | p2 := &Person{0, 0, 0, "badname", "", 0} 803 | err := dbmap.Insert(p2) 804 | if err == nil { 805 | t.Errorf("p2.PreInsert() didn't return an error") 806 | } 807 | } 808 | 809 | func TestTransaction(t *testing.T) { 810 | dbmap := initDbMap() 811 | defer dropAndClose(dbmap) 812 | 813 | inv1 := &Invoice{0, 100, 200, "t1", 0, true} 814 | inv2 := &Invoice{0, 100, 200, "t2", 0, false} 815 | 816 | trans, err := dbmap.Begin() 817 | if err != nil { 818 | panic(err) 819 | } 820 | trans.Insert(inv1, inv2) 821 | err = trans.Commit() 822 | if err != nil { 823 | panic(err) 824 | } 825 | 826 | obj, err := dbmap.Get(Invoice{}, inv1.Id) 827 | if err != nil { 828 | panic(err) 829 | } 830 | if !reflect.DeepEqual(inv1, obj) { 831 | t.Errorf("%v != %v", inv1, obj) 832 | } 833 | obj, err = dbmap.Get(Invoice{}, inv2.Id) 834 | if err != nil { 835 | panic(err) 836 | } 837 | if !reflect.DeepEqual(inv2, obj) { 838 | t.Errorf("%v != %v", inv2, obj) 839 | } 840 | } 841 | 842 | func TestSavepoint(t *testing.T) { 843 | dbmap := initDbMap() 844 | defer dropAndClose(dbmap) 845 | 846 | inv1 := &Invoice{0, 100, 200, "unpaid", 0, false} 847 | 848 | trans, err := dbmap.Begin() 849 | if err != nil { 850 | panic(err) 851 | } 852 | trans.Insert(inv1) 853 | 854 | var checkMemo = func(want string) { 855 | memo, err := trans.SelectStr("select memo from invoice_test") 856 | if err != nil { 857 | panic(err) 858 | } 859 | if memo != want { 860 | t.Errorf("%q != %q", want, memo) 861 | } 862 | } 863 | checkMemo("unpaid") 864 | 865 | err = trans.Savepoint("foo") 866 | if err != nil { 867 | panic(err) 868 | } 869 | checkMemo("unpaid") 870 | 871 | inv1.Memo = "paid" 872 | _, err = trans.Update(inv1) 873 | if err != nil { 874 | panic(err) 875 | } 876 | checkMemo("paid") 877 | 878 | err = trans.RollbackToSavepoint("foo") 879 | if err != nil { 880 | panic(err) 881 | } 882 | checkMemo("unpaid") 883 | 884 | err = trans.Rollback() 885 | if err != nil { 886 | panic(err) 887 | } 888 | } 889 | 890 | func TestMultiple(t *testing.T) { 891 | dbmap := initDbMap() 892 | defer dropAndClose(dbmap) 893 | 894 | inv1 := &Invoice{0, 100, 200, "a", 0, false} 895 | inv2 := &Invoice{0, 100, 200, "b", 0, true} 896 | _insert(dbmap, inv1, inv2) 897 | 898 | inv1.Memo = "c" 899 | inv2.Memo = "d" 900 | _update(dbmap, inv1, inv2) 901 | 902 | count := _del(dbmap, inv1, inv2) 903 | if count != 2 { 904 | t.Errorf("%d != 2", count) 905 | } 906 | } 907 | 908 | func TestCrud(t *testing.T) { 909 | dbmap := initDbMap() 910 | defer dropAndClose(dbmap) 911 | 912 | inv := &Invoice{0, 100, 200, "first order", 0, true} 913 | 914 | // INSERT row 915 | _insert(dbmap, inv) 916 | if inv.Id == 0 { 917 | t.Errorf("inv.Id was not set on INSERT") 918 | return 919 | } 920 | 921 | // SELECT row 922 | obj := _get(dbmap, Invoice{}, inv.Id) 923 | inv2 := obj.(*Invoice) 924 | if !reflect.DeepEqual(inv, inv2) { 925 | t.Errorf("%v != %v", inv, inv2) 926 | } 927 | 928 | // UPDATE row and SELECT 929 | inv.Memo = "second order" 930 | inv.Created = 999 931 | inv.Updated = 11111 932 | count := _update(dbmap, inv) 933 | if count != 1 { 934 | t.Errorf("update 1 != %d", count) 935 | } 936 | obj = _get(dbmap, Invoice{}, inv.Id) 937 | inv2 = obj.(*Invoice) 938 | if !reflect.DeepEqual(inv, inv2) { 939 | t.Errorf("%v != %v", inv, inv2) 940 | } 941 | 942 | // DELETE row 943 | deleted := _del(dbmap, inv) 944 | if deleted != 1 { 945 | t.Errorf("Did not delete row with Id: %d", inv.Id) 946 | return 947 | } 948 | 949 | // VERIFY deleted 950 | obj = _get(dbmap, Invoice{}, inv.Id) 951 | if obj != nil { 952 | t.Errorf("Found invoice with id: %d after Delete()", inv.Id) 953 | } 954 | } 955 | 956 | func TestWithIgnoredColumn(t *testing.T) { 957 | dbmap := initDbMap() 958 | defer dropAndClose(dbmap) 959 | 960 | ic := &WithIgnoredColumn{-1, 0, 1} 961 | _insert(dbmap, ic) 962 | expected := &WithIgnoredColumn{0, 1, 1} 963 | ic2 := _get(dbmap, WithIgnoredColumn{}, ic.Id).(*WithIgnoredColumn) 964 | 965 | if !reflect.DeepEqual(expected, ic2) { 966 | t.Errorf("%v != %v", expected, ic2) 967 | } 968 | if _del(dbmap, ic) != 1 { 969 | t.Errorf("Did not delete row with Id: %d", ic.Id) 970 | return 971 | } 972 | if _get(dbmap, WithIgnoredColumn{}, ic.Id) != nil { 973 | t.Errorf("Found id: %d after Delete()", ic.Id) 974 | } 975 | } 976 | 977 | func TestTypeConversionExample(t *testing.T) { 978 | dbmap := initDbMap() 979 | defer dropAndClose(dbmap) 980 | 981 | p := Person{FName: "Bob", LName: "Smith"} 982 | tc := &TypeConversionExample{-1, p, CustomStringType("hi")} 983 | _insert(dbmap, tc) 984 | 985 | expected := &TypeConversionExample{1, p, CustomStringType("hi")} 986 | tc2 := _get(dbmap, TypeConversionExample{}, tc.Id).(*TypeConversionExample) 987 | if !reflect.DeepEqual(expected, tc2) { 988 | t.Errorf("tc2 %v != %v", expected, tc2) 989 | } 990 | 991 | tc2.Name = CustomStringType("hi2") 992 | tc2.PersonJSON = Person{FName: "Jane", LName: "Doe"} 993 | _update(dbmap, tc2) 994 | 995 | expected = &TypeConversionExample{1, tc2.PersonJSON, CustomStringType("hi2")} 996 | tc3 := _get(dbmap, TypeConversionExample{}, tc.Id).(*TypeConversionExample) 997 | if !reflect.DeepEqual(expected, tc3) { 998 | t.Errorf("tc3 %v != %v", expected, tc3) 999 | } 1000 | 1001 | if _del(dbmap, tc) != 1 { 1002 | t.Errorf("Did not delete row with Id: %d", tc.Id) 1003 | } 1004 | 1005 | } 1006 | 1007 | func TestWithEmbeddedStruct(t *testing.T) { 1008 | dbmap := initDbMap() 1009 | defer dropAndClose(dbmap) 1010 | 1011 | es := &WithEmbeddedStruct{-1, Names{FirstName: "Alice", LastName: "Smith"}} 1012 | _insert(dbmap, es) 1013 | expected := &WithEmbeddedStruct{1, Names{FirstName: "Alice", LastName: "Smith"}} 1014 | es2 := _get(dbmap, WithEmbeddedStruct{}, es.Id).(*WithEmbeddedStruct) 1015 | if !reflect.DeepEqual(expected, es2) { 1016 | t.Errorf("%v != %v", expected, es2) 1017 | } 1018 | 1019 | es2.FirstName = "Bob" 1020 | expected.FirstName = "Bob" 1021 | _update(dbmap, es2) 1022 | es2 = _get(dbmap, WithEmbeddedStruct{}, es.Id).(*WithEmbeddedStruct) 1023 | if !reflect.DeepEqual(expected, es2) { 1024 | t.Errorf("%v != %v", expected, es2) 1025 | } 1026 | 1027 | ess := _rawselect(dbmap, WithEmbeddedStruct{}, "select * from embedded_struct_test") 1028 | if !reflect.DeepEqual(es2, ess[0]) { 1029 | t.Errorf("%v != %v", es2, ess[0]) 1030 | } 1031 | } 1032 | 1033 | func TestWithEmbeddedStructBeforeAutoincr(t *testing.T) { 1034 | dbmap := initDbMap() 1035 | defer dropAndClose(dbmap) 1036 | 1037 | esba := &WithEmbeddedStructBeforeAutoincrField{Names: Names{FirstName: "Alice", LastName: "Smith"}} 1038 | _insert(dbmap, esba) 1039 | var expectedAutoincrId int64 = 1 1040 | if esba.Id != expectedAutoincrId { 1041 | t.Errorf("%d != %d", expectedAutoincrId, esba.Id) 1042 | } 1043 | } 1044 | 1045 | func TestWithEmbeddedAutoincr(t *testing.T) { 1046 | dbmap := initDbMap() 1047 | defer dropAndClose(dbmap) 1048 | 1049 | esa := &WithEmbeddedAutoincr{ 1050 | WithEmbeddedStruct: WithEmbeddedStruct{Names: Names{FirstName: "Alice", LastName: "Smith"}}, 1051 | MiddleName: "Rose", 1052 | } 1053 | _insert(dbmap, esa) 1054 | var expectedAutoincrId int64 = 1 1055 | if esa.Id != expectedAutoincrId { 1056 | t.Errorf("%d != %d", expectedAutoincrId, esa.Id) 1057 | } 1058 | } 1059 | 1060 | func TestSelectVal(t *testing.T) { 1061 | dbmap := initDbMapNulls() 1062 | defer dropAndClose(dbmap) 1063 | 1064 | bindVar := dbmap.Dialect.BindVar(0) 1065 | 1066 | t1 := TableWithNull{Str: sql.NullString{"abc", true}, 1067 | Int64: sql.NullInt64{78, true}, 1068 | Float64: sql.NullFloat64{32.2, true}, 1069 | Bool: sql.NullBool{true, true}, 1070 | Bytes: []byte("hi")} 1071 | _insert(dbmap, &t1) 1072 | 1073 | // SelectInt 1074 | i64 := selectInt(dbmap, "select Int64 from TableWithNull where Str='abc'") 1075 | if i64 != 78 { 1076 | t.Errorf("int64 %d != 78", i64) 1077 | } 1078 | i64 = selectInt(dbmap, "select count(*) from TableWithNull") 1079 | if i64 != 1 { 1080 | t.Errorf("int64 count %d != 1", i64) 1081 | } 1082 | i64 = selectInt(dbmap, "select count(*) from TableWithNull where Str="+bindVar, "asdfasdf") 1083 | if i64 != 0 { 1084 | t.Errorf("int64 no rows %d != 0", i64) 1085 | } 1086 | 1087 | // SelectNullInt 1088 | n := selectNullInt(dbmap, "select Int64 from TableWithNull where Str='notfound'") 1089 | if !reflect.DeepEqual(n, sql.NullInt64{0, false}) { 1090 | t.Errorf("nullint %v != 0,false", n) 1091 | } 1092 | 1093 | n = selectNullInt(dbmap, "select Int64 from TableWithNull where Str='abc'") 1094 | if !reflect.DeepEqual(n, sql.NullInt64{78, true}) { 1095 | t.Errorf("nullint %v != 78, true", n) 1096 | } 1097 | 1098 | // SelectFloat 1099 | f64 := selectFloat(dbmap, "select Float64 from TableWithNull where Str='abc'") 1100 | if f64 != 32.2 { 1101 | t.Errorf("float64 %d != 32.2", f64) 1102 | } 1103 | f64 = selectFloat(dbmap, "select min(Float64) from TableWithNull") 1104 | if f64 != 32.2 { 1105 | t.Errorf("float64 min %d != 32.2", f64) 1106 | } 1107 | f64 = selectFloat(dbmap, "select count(*) from TableWithNull where Str="+bindVar, "asdfasdf") 1108 | if f64 != 0 { 1109 | t.Errorf("float64 no rows %d != 0", f64) 1110 | } 1111 | 1112 | // SelectNullFloat 1113 | nf := selectNullFloat(dbmap, "select Float64 from TableWithNull where Str='notfound'") 1114 | if !reflect.DeepEqual(nf, sql.NullFloat64{0, false}) { 1115 | t.Errorf("nullfloat %v != 0,false", nf) 1116 | } 1117 | 1118 | nf = selectNullFloat(dbmap, "select Float64 from TableWithNull where Str='abc'") 1119 | if !reflect.DeepEqual(nf, sql.NullFloat64{32.2, true}) { 1120 | t.Errorf("nullfloat %v != 32.2, true", nf) 1121 | } 1122 | 1123 | // SelectStr 1124 | s := selectStr(dbmap, "select Str from TableWithNull where Int64="+bindVar, 78) 1125 | if s != "abc" { 1126 | t.Errorf("s %s != abc", s) 1127 | } 1128 | s = selectStr(dbmap, "select Str from TableWithNull where Str='asdfasdf'") 1129 | if s != "" { 1130 | t.Errorf("s no rows %s != ''", s) 1131 | } 1132 | 1133 | // SelectNullStr 1134 | ns := selectNullStr(dbmap, "select Str from TableWithNull where Int64="+bindVar, 78) 1135 | if !reflect.DeepEqual(ns, sql.NullString{"abc", true}) { 1136 | t.Errorf("nullstr %v != abc,true", ns) 1137 | } 1138 | ns = selectNullStr(dbmap, "select Str from TableWithNull where Str='asdfasdf'") 1139 | if !reflect.DeepEqual(ns, sql.NullString{"", false}) { 1140 | t.Errorf("nullstr no rows %v != '',false", ns) 1141 | } 1142 | 1143 | // SelectInt/Str with named parameters 1144 | i64 = selectInt(dbmap, "select Int64 from TableWithNull where Str=:abc", map[string]string{"abc": "abc"}) 1145 | if i64 != 78 { 1146 | t.Errorf("int64 %d != 78", i64) 1147 | } 1148 | ns = selectNullStr(dbmap, "select Str from TableWithNull where Int64=:num", map[string]int{"num": 78}) 1149 | if !reflect.DeepEqual(ns, sql.NullString{"abc", true}) { 1150 | t.Errorf("nullstr %v != abc,true", ns) 1151 | } 1152 | } 1153 | 1154 | func TestVersionMultipleRows(t *testing.T) { 1155 | dbmap := initDbMap() 1156 | defer dropAndClose(dbmap) 1157 | 1158 | persons := []*Person{ 1159 | &Person{0, 0, 0, "Bob", "Smith", 0}, 1160 | &Person{0, 0, 0, "Jane", "Smith", 0}, 1161 | &Person{0, 0, 0, "Mike", "Smith", 0}, 1162 | } 1163 | 1164 | _insert(dbmap, persons[0], persons[1], persons[2]) 1165 | 1166 | for x, p := range persons { 1167 | if p.Version != 1 { 1168 | t.Errorf("person[%d].Version != 1: %d", x, p.Version) 1169 | } 1170 | } 1171 | } 1172 | 1173 | func TestWithStringPk(t *testing.T) { 1174 | dbmap := newDbMap() 1175 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 1176 | dbmap.AddTableWithName(WithStringPk{}, "string_pk_test").SetKeys(true, "Id") 1177 | _, err := dbmap.Exec("create table string_pk_test (Id varchar(255), Name varchar(255));") 1178 | if err != nil { 1179 | t.Errorf("couldn't create string_pk_test: %v", err) 1180 | } 1181 | defer dropAndClose(dbmap) 1182 | 1183 | row := &WithStringPk{"1", "foo"} 1184 | err = dbmap.Insert(row) 1185 | if err == nil { 1186 | t.Errorf("Expected error when inserting into table w/non Int PK and autoincr set true") 1187 | } 1188 | } 1189 | 1190 | // TestSqlExecutorInterfaceSelects ensures that all DbMap methods starting with Select... 1191 | // are also exposed in the SqlExecutor interface. Select... functions can always 1192 | // run on Pre/Post hooks. 1193 | func TestSqlExecutorInterfaceSelects(t *testing.T) { 1194 | dbMapType := reflect.TypeOf(&DbMap{}) 1195 | sqlExecutorType := reflect.TypeOf((*SqlExecutor)(nil)).Elem() 1196 | numDbMapMethods := dbMapType.NumMethod() 1197 | for i := 0; i < numDbMapMethods; i += 1 { 1198 | dbMapMethod := dbMapType.Method(i) 1199 | if !strings.HasPrefix(dbMapMethod.Name, "Select") { 1200 | continue 1201 | } 1202 | if _, found := sqlExecutorType.MethodByName(dbMapMethod.Name); !found { 1203 | t.Errorf("Method %s is defined on DbMap but not implemented in SqlExecutor", 1204 | dbMapMethod.Name) 1205 | } 1206 | } 1207 | } 1208 | 1209 | type WithTime struct { 1210 | Id int64 1211 | Time time.Time 1212 | } 1213 | 1214 | type Times struct { 1215 | One time.Time 1216 | Two time.Time 1217 | } 1218 | 1219 | type EmbeddedTime struct { 1220 | Id string 1221 | Times 1222 | } 1223 | 1224 | func parseTimeOrPanic(format, date string) time.Time { 1225 | t1, err := time.Parse(format, date) 1226 | if err != nil { 1227 | panic(err) 1228 | } 1229 | return t1 1230 | } 1231 | 1232 | // TODO: re-enable next two tests when this is merged: 1233 | // https://github.com/ziutek/mymysql/pull/77 1234 | // 1235 | // This test currently fails w/MySQL b/c tz info is lost 1236 | func testWithTime(t *testing.T) { 1237 | dbmap := initDbMap() 1238 | defer dropAndClose(dbmap) 1239 | 1240 | t1 := parseTimeOrPanic("2006-01-02 15:04:05 -0700 MST", 1241 | "2013-08-09 21:30:43 +0800 CST") 1242 | w1 := WithTime{1, t1} 1243 | _insert(dbmap, &w1) 1244 | 1245 | obj := _get(dbmap, WithTime{}, w1.Id) 1246 | w2 := obj.(*WithTime) 1247 | if w1.Time.UnixNano() != w2.Time.UnixNano() { 1248 | t.Errorf("%v != %v", w1, w2) 1249 | } 1250 | } 1251 | 1252 | // See: https://github.com/coopernurse/gorp/issues/86 1253 | func testEmbeddedTime(t *testing.T) { 1254 | dbmap := newDbMap() 1255 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 1256 | dbmap.AddTable(EmbeddedTime{}).SetKeys(false, "Id") 1257 | defer dropAndClose(dbmap) 1258 | err := dbmap.CreateTables() 1259 | if err != nil { 1260 | t.Fatal(err) 1261 | } 1262 | 1263 | time1 := parseTimeOrPanic("2006-01-02 15:04:05", "2013-08-09 21:30:43") 1264 | 1265 | t1 := &EmbeddedTime{Id: "abc", Times: Times{One: time1, Two: time1.Add(10 * time.Second)}} 1266 | _insert(dbmap, t1) 1267 | 1268 | x := _get(dbmap, EmbeddedTime{}, t1.Id) 1269 | t2, _ := x.(*EmbeddedTime) 1270 | if t1.One.UnixNano() != t2.One.UnixNano() || t1.Two.UnixNano() != t2.Two.UnixNano() { 1271 | t.Errorf("%v != %v", t1, t2) 1272 | } 1273 | } 1274 | 1275 | func TestWithTimeSelect(t *testing.T) { 1276 | dbmap := initDbMap() 1277 | defer dropAndClose(dbmap) 1278 | 1279 | halfhourago := time.Now().UTC().Add(-30 * time.Minute) 1280 | 1281 | w1 := WithTime{1, halfhourago.Add(time.Minute * -1)} 1282 | w2 := WithTime{2, halfhourago.Add(time.Second)} 1283 | _insert(dbmap, &w1, &w2) 1284 | 1285 | var caseIds []int64 1286 | _, err := dbmap.Select(&caseIds, "SELECT id FROM time_test WHERE Time < "+dbmap.Dialect.BindVar(0), halfhourago) 1287 | 1288 | if err != nil { 1289 | t.Error(err) 1290 | } 1291 | if len(caseIds) != 1 { 1292 | t.Errorf("%d != 1", len(caseIds)) 1293 | } 1294 | if caseIds[0] != w1.Id { 1295 | t.Errorf("%d != %d", caseIds[0], w1.Id) 1296 | } 1297 | } 1298 | 1299 | func TestInvoicePersonView(t *testing.T) { 1300 | dbmap := initDbMap() 1301 | defer dropAndClose(dbmap) 1302 | 1303 | // Create some rows 1304 | p1 := &Person{0, 0, 0, "bob", "smith", 0} 1305 | dbmap.Insert(p1) 1306 | 1307 | // notice how we can wire up p1.Id to the invoice easily 1308 | inv1 := &Invoice{0, 0, 0, "xmas order", p1.Id, false} 1309 | dbmap.Insert(inv1) 1310 | 1311 | // Run your query 1312 | query := "select i.Id InvoiceId, p.Id PersonId, i.Memo, p.FName " + 1313 | "from invoice_test i, person_test p " + 1314 | "where i.PersonId = p.Id" 1315 | 1316 | // pass a slice of pointers to Select() 1317 | // this avoids the need to type assert after the query is run 1318 | var list []*InvoicePersonView 1319 | _, err := dbmap.Select(&list, query) 1320 | if err != nil { 1321 | panic(err) 1322 | } 1323 | 1324 | // this should test true 1325 | expected := &InvoicePersonView{inv1.Id, p1.Id, inv1.Memo, p1.FName, 0} 1326 | if !reflect.DeepEqual(list[0], expected) { 1327 | t.Errorf("%v != %v", list[0], expected) 1328 | } 1329 | } 1330 | 1331 | func TestQuoteTableNames(t *testing.T) { 1332 | dbmap := initDbMap() 1333 | defer dropAndClose(dbmap) 1334 | 1335 | quotedTableName := dbmap.Dialect.QuoteField("person_test") 1336 | 1337 | // Use a buffer to hold the log to check generated queries 1338 | logBuffer := &bytes.Buffer{} 1339 | dbmap.TraceOn("", log.New(logBuffer, "gorptest:", log.Lmicroseconds)) 1340 | 1341 | // Create some rows 1342 | p1 := &Person{0, 0, 0, "bob", "smith", 0} 1343 | errorTemplate := "Expected quoted table name %v in query but didn't find it" 1344 | 1345 | // Check if Insert quotes the table name 1346 | id := dbmap.Insert(p1) 1347 | if !bytes.Contains(logBuffer.Bytes(), []byte(quotedTableName)) { 1348 | t.Errorf(errorTemplate, quotedTableName) 1349 | } 1350 | logBuffer.Reset() 1351 | 1352 | // Check if Get quotes the table name 1353 | dbmap.Get(Person{}, id) 1354 | if !bytes.Contains(logBuffer.Bytes(), []byte(quotedTableName)) { 1355 | t.Errorf(errorTemplate, quotedTableName) 1356 | } 1357 | logBuffer.Reset() 1358 | } 1359 | 1360 | func TestSelectSingleVal(t *testing.T) { 1361 | dbmap := initDbMap() 1362 | defer dropAndClose(dbmap) 1363 | 1364 | p1 := &Person{0, 0, 0, "bob", "smith", 0} 1365 | _insert(dbmap, p1) 1366 | 1367 | obj := _get(dbmap, Person{}, p1.Id) 1368 | p1 = obj.(*Person) 1369 | 1370 | params := map[string]interface{}{ 1371 | "Id": p1.Id, 1372 | } 1373 | 1374 | var p2 Person 1375 | err := dbmap.SelectOne(&p2, "select * from person_test where Id=:Id", params) 1376 | if err != nil { 1377 | t.Error(err) 1378 | } 1379 | 1380 | if !reflect.DeepEqual(p1, &p2) { 1381 | t.Errorf("%v != %v", p1, &p2) 1382 | } 1383 | 1384 | // verify SelectOne allows non-struct holders 1385 | var s string 1386 | err = dbmap.SelectOne(&s, "select FName from person_test where Id=:Id", params) 1387 | if err != nil { 1388 | t.Error(err) 1389 | } 1390 | if s != "bob" { 1391 | t.Error("Expected bob but got: " + s) 1392 | } 1393 | 1394 | // verify SelectOne requires pointer receiver 1395 | err = dbmap.SelectOne(s, "select FName from person_test where Id=:Id", params) 1396 | if err == nil { 1397 | t.Error("SelectOne should have returned error for non-pointer holder") 1398 | } 1399 | 1400 | // verify that the error is set to sql.ErrNoRows if not found 1401 | err = dbmap.SelectOne(&p2, "select * from person_test where Id=:Id", map[string]interface{}{ 1402 | "Id": -2222, 1403 | }) 1404 | if err == nil || err != sql.ErrNoRows { 1405 | t.Error("SelectOne should have returned an sql.ErrNoRows") 1406 | } 1407 | 1408 | _insert(dbmap, &Person{0, 0, 0, "bob", "smith", 0}) 1409 | err = dbmap.SelectOne(&p2, "select * from person_test where Fname='bob'") 1410 | if err == nil { 1411 | t.Error("Expected nil when two rows found") 1412 | } 1413 | } 1414 | 1415 | func TestMysqlPanicIfDialectNotInitialized(t *testing.T) { 1416 | _, driver := dialectAndDriver() 1417 | // this test only applies to MySQL 1418 | if os.Getenv("GORP_TEST_DIALECT") != "mysql" { 1419 | return 1420 | } 1421 | 1422 | // The expected behaviour is to catch a panic. 1423 | // Here is the deferred function which will check if a panic has indeed occurred : 1424 | defer func() { 1425 | r := recover() 1426 | if r == nil { 1427 | t.Error("db.CreateTables() should panic if db is initialized with an incorrect MySQLDialect") 1428 | } 1429 | }() 1430 | 1431 | // invalid MySQLDialect : does not contain Engine or Encoding specification 1432 | dialect := MySQLDialect{} 1433 | db := &DbMap{Db: connect(driver), Dialect: dialect} 1434 | db.AddTableWithName(Invoice{}, "invoice") 1435 | // the following call should panic : 1436 | db.CreateTables() 1437 | } 1438 | 1439 | func BenchmarkNativeCrud(b *testing.B) { 1440 | b.StopTimer() 1441 | dbmap := initDbMapBench() 1442 | defer dropAndClose(dbmap) 1443 | b.StartTimer() 1444 | 1445 | insert := "insert into invoice_test (Created, Updated, Memo, PersonId) values (?, ?, ?, ?)" 1446 | sel := "select Id, Created, Updated, Memo, PersonId from invoice_test where Id=?" 1447 | update := "update invoice_test set Created=?, Updated=?, Memo=?, PersonId=? where Id=?" 1448 | delete := "delete from invoice_test where Id=?" 1449 | 1450 | inv := &Invoice{0, 100, 200, "my memo", 0, false} 1451 | 1452 | for i := 0; i < b.N; i++ { 1453 | res, err := dbmap.Db.Exec(insert, inv.Created, inv.Updated, 1454 | inv.Memo, inv.PersonId) 1455 | if err != nil { 1456 | panic(err) 1457 | } 1458 | 1459 | newid, err := res.LastInsertId() 1460 | if err != nil { 1461 | panic(err) 1462 | } 1463 | inv.Id = newid 1464 | 1465 | row := dbmap.Db.QueryRow(sel, inv.Id) 1466 | err = row.Scan(&inv.Id, &inv.Created, &inv.Updated, &inv.Memo, 1467 | &inv.PersonId) 1468 | if err != nil { 1469 | panic(err) 1470 | } 1471 | 1472 | inv.Created = 1000 1473 | inv.Updated = 2000 1474 | inv.Memo = "my memo 2" 1475 | inv.PersonId = 3000 1476 | 1477 | _, err = dbmap.Db.Exec(update, inv.Created, inv.Updated, inv.Memo, 1478 | inv.PersonId, inv.Id) 1479 | if err != nil { 1480 | panic(err) 1481 | } 1482 | 1483 | _, err = dbmap.Db.Exec(delete, inv.Id) 1484 | if err != nil { 1485 | panic(err) 1486 | } 1487 | } 1488 | 1489 | } 1490 | 1491 | func BenchmarkGorpCrud(b *testing.B) { 1492 | b.StopTimer() 1493 | dbmap := initDbMapBench() 1494 | defer dropAndClose(dbmap) 1495 | b.StartTimer() 1496 | 1497 | inv := &Invoice{0, 100, 200, "my memo", 0, true} 1498 | for i := 0; i < b.N; i++ { 1499 | err := dbmap.Insert(inv) 1500 | if err != nil { 1501 | panic(err) 1502 | } 1503 | 1504 | obj, err := dbmap.Get(Invoice{}, inv.Id) 1505 | if err != nil { 1506 | panic(err) 1507 | } 1508 | 1509 | inv2, ok := obj.(*Invoice) 1510 | if !ok { 1511 | panic(fmt.Sprintf("expected *Invoice, got: %v", obj)) 1512 | } 1513 | 1514 | inv2.Created = 1000 1515 | inv2.Updated = 2000 1516 | inv2.Memo = "my memo 2" 1517 | inv2.PersonId = 3000 1518 | _, err = dbmap.Update(inv2) 1519 | if err != nil { 1520 | panic(err) 1521 | } 1522 | 1523 | _, err = dbmap.Delete(inv2) 1524 | if err != nil { 1525 | panic(err) 1526 | } 1527 | 1528 | } 1529 | } 1530 | 1531 | func initDbMapBench() *DbMap { 1532 | dbmap := newDbMap() 1533 | dbmap.Db.Exec("drop table if exists invoice_test") 1534 | dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id") 1535 | err := dbmap.CreateTables() 1536 | if err != nil { 1537 | panic(err) 1538 | } 1539 | return dbmap 1540 | } 1541 | 1542 | func initDbMap() *DbMap { 1543 | dbmap := newDbMap() 1544 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 1545 | dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id") 1546 | dbmap.AddTableWithName(OverriddenInvoice{}, "invoice_override_test").SetKeys(false, "Id") 1547 | dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id") 1548 | dbmap.AddTableWithName(WithIgnoredColumn{}, "ignored_column_test").SetKeys(true, "Id") 1549 | dbmap.AddTableWithName(TypeConversionExample{}, "type_conv_test").SetKeys(true, "Id") 1550 | dbmap.AddTableWithName(WithEmbeddedStruct{}, "embedded_struct_test").SetKeys(true, "Id") 1551 | dbmap.AddTableWithName(WithEmbeddedStructBeforeAutoincrField{}, "embedded_struct_before_autoincr_test").SetKeys(true, "Id") 1552 | dbmap.AddTableWithName(WithEmbeddedAutoincr{}, "embedded_autoincr_test").SetKeys(true, "Id") 1553 | dbmap.AddTableWithName(WithTime{}, "time_test").SetKeys(true, "Id") 1554 | dbmap.TypeConverter = testTypeConverter{} 1555 | err := dbmap.CreateTables() 1556 | if err != nil { 1557 | panic(err) 1558 | } 1559 | 1560 | return dbmap 1561 | } 1562 | 1563 | func initDbMapNulls() *DbMap { 1564 | dbmap := newDbMap() 1565 | dbmap.TraceOn("", log.New(os.Stdout, "gorptest: ", log.Lmicroseconds)) 1566 | dbmap.AddTable(TableWithNull{}).SetKeys(false, "Id") 1567 | err := dbmap.CreateTables() 1568 | if err != nil { 1569 | panic(err) 1570 | } 1571 | return dbmap 1572 | } 1573 | 1574 | func newDbMap() *DbMap { 1575 | dialect, driver := dialectAndDriver() 1576 | return &DbMap{Db: connect(driver), Dialect: dialect} 1577 | } 1578 | 1579 | func dropAndClose(dbmap *DbMap) { 1580 | dbmap.DropTablesIfExists() 1581 | dbmap.Db.Close() 1582 | } 1583 | 1584 | func connect(driver string) *sql.DB { 1585 | dsn := os.Getenv("GORP_TEST_DSN") 1586 | if dsn == "" { 1587 | panic("GORP_TEST_DSN env variable is not set. Please see README.md") 1588 | } 1589 | 1590 | db, err := sql.Open(driver, dsn) 1591 | if err != nil { 1592 | panic("Error connecting to db: " + err.Error()) 1593 | } 1594 | return db 1595 | } 1596 | 1597 | func dialectAndDriver() (Dialect, string) { 1598 | switch os.Getenv("GORP_TEST_DIALECT") { 1599 | case "mysql": 1600 | return MySQLDialect{"InnoDB", "UTF8"}, "mymysql" 1601 | case "gomysql": 1602 | return MySQLDialect{"InnoDB", "UTF8"}, "mysql" 1603 | case "postgres": 1604 | return PostgresDialect{}, "postgres" 1605 | case "sqlite": 1606 | return SqliteDialect{}, "sqlite3" 1607 | } 1608 | panic("GORP_TEST_DIALECT env variable is not set or is invalid. Please see README.md") 1609 | } 1610 | 1611 | func _insert(dbmap *DbMap, list ...interface{}) { 1612 | err := dbmap.Insert(list...) 1613 | if err != nil { 1614 | panic(err) 1615 | } 1616 | } 1617 | 1618 | func _update(dbmap *DbMap, list ...interface{}) int64 { 1619 | count, err := dbmap.Update(list...) 1620 | if err != nil { 1621 | panic(err) 1622 | } 1623 | return count 1624 | } 1625 | 1626 | func _del(dbmap *DbMap, list ...interface{}) int64 { 1627 | count, err := dbmap.Delete(list...) 1628 | if err != nil { 1629 | panic(err) 1630 | } 1631 | 1632 | return count 1633 | } 1634 | 1635 | func _get(dbmap *DbMap, i interface{}, keys ...interface{}) interface{} { 1636 | obj, err := dbmap.Get(i, keys...) 1637 | if err != nil { 1638 | panic(err) 1639 | } 1640 | 1641 | return obj 1642 | } 1643 | 1644 | func selectInt(dbmap *DbMap, query string, args ...interface{}) int64 { 1645 | i64, err := SelectInt(dbmap, query, args...) 1646 | if err != nil { 1647 | panic(err) 1648 | } 1649 | 1650 | return i64 1651 | } 1652 | 1653 | func selectNullInt(dbmap *DbMap, query string, args ...interface{}) sql.NullInt64 { 1654 | i64, err := SelectNullInt(dbmap, query, args...) 1655 | if err != nil { 1656 | panic(err) 1657 | } 1658 | 1659 | return i64 1660 | } 1661 | 1662 | func selectFloat(dbmap *DbMap, query string, args ...interface{}) float64 { 1663 | f64, err := SelectFloat(dbmap, query, args...) 1664 | if err != nil { 1665 | panic(err) 1666 | } 1667 | 1668 | return f64 1669 | } 1670 | 1671 | func selectNullFloat(dbmap *DbMap, query string, args ...interface{}) sql.NullFloat64 { 1672 | f64, err := SelectNullFloat(dbmap, query, args...) 1673 | if err != nil { 1674 | panic(err) 1675 | } 1676 | 1677 | return f64 1678 | } 1679 | 1680 | func selectStr(dbmap *DbMap, query string, args ...interface{}) string { 1681 | s, err := SelectStr(dbmap, query, args...) 1682 | if err != nil { 1683 | panic(err) 1684 | } 1685 | 1686 | return s 1687 | } 1688 | 1689 | func selectNullStr(dbmap *DbMap, query string, args ...interface{}) sql.NullString { 1690 | s, err := SelectNullStr(dbmap, query, args...) 1691 | if err != nil { 1692 | panic(err) 1693 | } 1694 | 1695 | return s 1696 | } 1697 | 1698 | func _rawexec(dbmap *DbMap, query string, args ...interface{}) sql.Result { 1699 | res, err := dbmap.Exec(query, args...) 1700 | if err != nil { 1701 | panic(err) 1702 | } 1703 | return res 1704 | } 1705 | 1706 | func _rawselect(dbmap *DbMap, i interface{}, query string, args ...interface{}) []interface{} { 1707 | list, err := dbmap.Select(i, query, args...) 1708 | if err != nil { 1709 | panic(err) 1710 | } 1711 | return list 1712 | } 1713 | -------------------------------------------------------------------------------- /gorp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 James Cooper. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package gorp provides a simple way to marshal Go structs to and from 6 | // SQL databases. It uses the database/sql package, and should work with any 7 | // compliant database/sql driver. 8 | // 9 | // Source code and project home: 10 | // https://github.com/coopernurse/gorp 11 | // 12 | package gorp 13 | 14 | import ( 15 | "bytes" 16 | "database/sql" 17 | "errors" 18 | "fmt" 19 | "reflect" 20 | "regexp" 21 | "strings" 22 | ) 23 | 24 | var zeroVal reflect.Value 25 | var versFieldConst = "[gorp_ver_field]" 26 | 27 | // OptimisticLockError is returned by Update() or Delete() if the 28 | // struct being modified has a Version field and the value is not equal to 29 | // the current value in the database 30 | type OptimisticLockError struct { 31 | // Table name where the lock error occurred 32 | TableName string 33 | 34 | // Primary key values of the row being updated/deleted 35 | Keys []interface{} 36 | 37 | // true if a row was found with those keys, indicating the 38 | // LocalVersion is stale. false if no value was found with those 39 | // keys, suggesting the row has been deleted since loaded, or 40 | // was never inserted to begin with 41 | RowExists bool 42 | 43 | // Version value on the struct passed to Update/Delete. This value is 44 | // out of sync with the database. 45 | LocalVersion int64 46 | } 47 | 48 | // Error returns a description of the cause of the lock error 49 | func (e OptimisticLockError) Error() string { 50 | if e.RowExists { 51 | return fmt.Sprintf("gorp: OptimisticLockError table=%s keys=%v out of date version=%d", e.TableName, e.Keys, e.LocalVersion) 52 | } 53 | 54 | return fmt.Sprintf("gorp: OptimisticLockError no row found for table=%s keys=%v", e.TableName, e.Keys) 55 | } 56 | 57 | // The TypeConverter interface provides a way to map a value of one 58 | // type to another type when persisting to, or loading from, a database. 59 | // 60 | // Example use cases: Implement type converter to convert bool types to "y"/"n" strings, 61 | // or serialize a struct member as a JSON blob. 62 | type TypeConverter interface { 63 | // ToDb converts val to another type. Called before INSERT/UPDATE operations 64 | ToDb(val interface{}) (interface{}, error) 65 | 66 | // FromDb returns a CustomScanner appropriate for this type. This will be used 67 | // to hold values returned from SELECT queries. 68 | // 69 | // In particular the CustomScanner returned should implement a Binder 70 | // function appropriate for the Go type you wish to convert the db value to 71 | // 72 | // If bool==false, then no custom scanner will be used for this field. 73 | FromDb(target interface{}) (CustomScanner, bool) 74 | } 75 | 76 | // CustomScanner binds a database column value to a Go type 77 | type CustomScanner struct { 78 | // After a row is scanned, Holder will contain the value from the database column. 79 | // Initialize the CustomScanner with the concrete Go type you wish the database 80 | // driver to scan the raw column into. 81 | Holder interface{} 82 | // Target typically holds a pointer to the target struct field to bind the Holder 83 | // value to. 84 | Target interface{} 85 | // Binder is a custom function that converts the holder value to the target type 86 | // and sets target accordingly. This function should return error if a problem 87 | // occurs converting the holder to the target. 88 | Binder func(holder interface{}, target interface{}) error 89 | } 90 | 91 | // Bind is called automatically by gorp after Scan() 92 | func (me CustomScanner) Bind() error { 93 | return me.Binder(me.Holder, me.Target) 94 | } 95 | 96 | // DbMap is the root gorp mapping object. Create one of these for each 97 | // database schema you wish to map. Each DbMap contains a list of 98 | // mapped tables. 99 | // 100 | // Example: 101 | // 102 | // dialect := gorp.MySQLDialect{"InnoDB", "UTF8"} 103 | // dbmap := &gorp.DbMap{Db: db, Dialect: dialect} 104 | // 105 | type DbMap struct { 106 | // Db handle to use with this map 107 | Db *sql.DB 108 | 109 | // Dialect implementation to use with this map 110 | Dialect Dialect 111 | 112 | TypeConverter TypeConverter 113 | 114 | tables []*TableMap 115 | logger GorpLogger 116 | logPrefix string 117 | } 118 | 119 | // TableMap represents a mapping between a Go struct and a database table 120 | // Use dbmap.AddTable() or dbmap.AddTableWithName() to create these 121 | type TableMap struct { 122 | // Name of database table. 123 | TableName string 124 | SchemaName string 125 | gotype reflect.Type 126 | columns []*ColumnMap 127 | keys []*ColumnMap 128 | uniqueTogether [][]string 129 | version *ColumnMap 130 | insertPlan bindPlan 131 | updatePlan bindPlan 132 | deletePlan bindPlan 133 | getPlan bindPlan 134 | dbmap *DbMap 135 | } 136 | 137 | // ResetSql removes cached insert/update/select/delete SQL strings 138 | // associated with this TableMap. Call this if you've modified 139 | // any column names or the table name itself. 140 | func (t *TableMap) ResetSql() { 141 | t.insertPlan = bindPlan{} 142 | t.updatePlan = bindPlan{} 143 | t.deletePlan = bindPlan{} 144 | t.getPlan = bindPlan{} 145 | } 146 | 147 | // SetKeys lets you specify the fields on a struct that map to primary 148 | // key columns on the table. If isAutoIncr is set, result.LastInsertId() 149 | // will be used after INSERT to bind the generated id to the Go struct. 150 | // 151 | // Automatically calls ResetSql() to ensure SQL statements are regenerated. 152 | // 153 | // Panics if isAutoIncr is true, and fieldNames length != 1 154 | // 155 | func (t *TableMap) SetKeys(isAutoIncr bool, fieldNames ...string) *TableMap { 156 | if isAutoIncr && len(fieldNames) != 1 { 157 | panic(fmt.Sprintf( 158 | "gorp: SetKeys: fieldNames length must be 1 if key is auto-increment. (Saw %v fieldNames)", 159 | len(fieldNames))) 160 | } 161 | t.keys = make([]*ColumnMap, 0) 162 | for _, name := range fieldNames { 163 | colmap := t.ColMap(name) 164 | colmap.isPK = true 165 | colmap.isAutoIncr = isAutoIncr 166 | t.keys = append(t.keys, colmap) 167 | } 168 | t.ResetSql() 169 | 170 | return t 171 | } 172 | 173 | // SetUniqueTogether lets you specify uniqueness constraints across multiple 174 | // columns on the table. Each call adds an additional constraint for the 175 | // specified columns. 176 | // 177 | // Automatically calls ResetSql() to ensure SQL statements are regenerated. 178 | // 179 | // Panics if fieldNames length < 2. 180 | // 181 | func (t *TableMap) SetUniqueTogether(fieldNames ...string) *TableMap { 182 | if len(fieldNames) < 2 { 183 | panic(fmt.Sprintf( 184 | "gorp: SetUniqueTogether: must provide at least two fieldNames to set uniqueness constraint.")) 185 | } 186 | 187 | columns := make([]string, 0) 188 | for _, name := range fieldNames { 189 | columns = append(columns, name) 190 | } 191 | t.uniqueTogether = append(t.uniqueTogether, columns) 192 | t.ResetSql() 193 | 194 | return t 195 | } 196 | 197 | // ColMap returns the ColumnMap pointer matching the given struct field 198 | // name. It panics if the struct does not contain a field matching this 199 | // name. 200 | func (t *TableMap) ColMap(field string) *ColumnMap { 201 | col := colMapOrNil(t, field) 202 | if col == nil { 203 | e := fmt.Sprintf("No ColumnMap in table %s type %s with field %s", 204 | t.TableName, t.gotype.Name(), field) 205 | 206 | panic(e) 207 | } 208 | return col 209 | } 210 | 211 | func colMapOrNil(t *TableMap, field string) *ColumnMap { 212 | for _, col := range t.columns { 213 | if col.fieldName == field || col.ColumnName == field { 214 | return col 215 | } 216 | } 217 | return nil 218 | } 219 | 220 | // SetVersionCol sets the column to use as the Version field. By default 221 | // the "Version" field is used. Returns the column found, or panics 222 | // if the struct does not contain a field matching this name. 223 | // 224 | // Automatically calls ResetSql() to ensure SQL statements are regenerated. 225 | func (t *TableMap) SetVersionCol(field string) *ColumnMap { 226 | c := t.ColMap(field) 227 | t.version = c 228 | t.ResetSql() 229 | return c 230 | } 231 | 232 | type bindPlan struct { 233 | query string 234 | argFields []string 235 | keyFields []string 236 | versField string 237 | autoIncrIdx int 238 | autoIncrFieldName string 239 | } 240 | 241 | func (plan bindPlan) createBindInstance(elem reflect.Value, conv TypeConverter) (bindInstance, error) { 242 | bi := bindInstance{query: plan.query, autoIncrIdx: plan.autoIncrIdx, autoIncrFieldName: plan.autoIncrFieldName, versField: plan.versField} 243 | if plan.versField != "" { 244 | bi.existingVersion = elem.FieldByName(plan.versField).Int() 245 | } 246 | 247 | var err error 248 | 249 | for i := 0; i < len(plan.argFields); i++ { 250 | k := plan.argFields[i] 251 | if k == versFieldConst { 252 | newVer := bi.existingVersion + 1 253 | bi.args = append(bi.args, newVer) 254 | if bi.existingVersion == 0 { 255 | elem.FieldByName(plan.versField).SetInt(int64(newVer)) 256 | } 257 | } else { 258 | val := elem.FieldByName(k).Interface() 259 | if conv != nil { 260 | val, err = conv.ToDb(val) 261 | if err != nil { 262 | return bindInstance{}, err 263 | } 264 | } 265 | bi.args = append(bi.args, val) 266 | } 267 | } 268 | 269 | for i := 0; i < len(plan.keyFields); i++ { 270 | k := plan.keyFields[i] 271 | val := elem.FieldByName(k).Interface() 272 | if conv != nil { 273 | val, err = conv.ToDb(val) 274 | if err != nil { 275 | return bindInstance{}, err 276 | } 277 | } 278 | bi.keys = append(bi.keys, val) 279 | } 280 | 281 | return bi, nil 282 | } 283 | 284 | type bindInstance struct { 285 | query string 286 | args []interface{} 287 | keys []interface{} 288 | existingVersion int64 289 | versField string 290 | autoIncrIdx int 291 | autoIncrFieldName string 292 | } 293 | 294 | func (t *TableMap) bindInsert(elem reflect.Value) (bindInstance, error) { 295 | plan := t.insertPlan 296 | if plan.query == "" { 297 | plan.autoIncrIdx = -1 298 | 299 | s := bytes.Buffer{} 300 | s2 := bytes.Buffer{} 301 | s.WriteString(fmt.Sprintf("insert into %s (", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName))) 302 | 303 | x := 0 304 | first := true 305 | for y := range t.columns { 306 | col := t.columns[y] 307 | 308 | if !col.Transient { 309 | if !first { 310 | s.WriteString(",") 311 | s2.WriteString(",") 312 | } 313 | s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) 314 | 315 | if col.isAutoIncr { 316 | s2.WriteString(t.dbmap.Dialect.AutoIncrBindValue()) 317 | plan.autoIncrIdx = y 318 | plan.autoIncrFieldName = col.fieldName 319 | } else { 320 | s2.WriteString(t.dbmap.Dialect.BindVar(x)) 321 | if col == t.version { 322 | plan.versField = col.fieldName 323 | plan.argFields = append(plan.argFields, versFieldConst) 324 | } else { 325 | plan.argFields = append(plan.argFields, col.fieldName) 326 | } 327 | 328 | x++ 329 | } 330 | 331 | first = false 332 | } 333 | } 334 | s.WriteString(") values (") 335 | s.WriteString(s2.String()) 336 | s.WriteString(")") 337 | if plan.autoIncrIdx > -1 { 338 | s.WriteString(t.dbmap.Dialect.AutoIncrInsertSuffix(t.columns[plan.autoIncrIdx])) 339 | } 340 | s.WriteString(";") 341 | 342 | plan.query = s.String() 343 | t.insertPlan = plan 344 | } 345 | 346 | return plan.createBindInstance(elem, t.dbmap.TypeConverter) 347 | } 348 | 349 | func (t *TableMap) bindUpdate(elem reflect.Value) (bindInstance, error) { 350 | plan := t.updatePlan 351 | if plan.query == "" { 352 | 353 | s := bytes.Buffer{} 354 | s.WriteString(fmt.Sprintf("update %s set ", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName))) 355 | x := 0 356 | 357 | for y := range t.columns { 358 | col := t.columns[y] 359 | if !col.isPK && !col.Transient { 360 | if x > 0 { 361 | s.WriteString(", ") 362 | } 363 | s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) 364 | s.WriteString("=") 365 | s.WriteString(t.dbmap.Dialect.BindVar(x)) 366 | 367 | if col == t.version { 368 | plan.versField = col.fieldName 369 | plan.argFields = append(plan.argFields, versFieldConst) 370 | } else { 371 | plan.argFields = append(plan.argFields, col.fieldName) 372 | } 373 | x++ 374 | } 375 | } 376 | 377 | s.WriteString(" where ") 378 | for y := range t.keys { 379 | col := t.keys[y] 380 | if y > 0 { 381 | s.WriteString(" and ") 382 | } 383 | s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) 384 | s.WriteString("=") 385 | s.WriteString(t.dbmap.Dialect.BindVar(x)) 386 | 387 | plan.argFields = append(plan.argFields, col.fieldName) 388 | plan.keyFields = append(plan.keyFields, col.fieldName) 389 | x++ 390 | } 391 | if plan.versField != "" { 392 | s.WriteString(" and ") 393 | s.WriteString(t.dbmap.Dialect.QuoteField(t.version.ColumnName)) 394 | s.WriteString("=") 395 | s.WriteString(t.dbmap.Dialect.BindVar(x)) 396 | plan.argFields = append(plan.argFields, plan.versField) 397 | } 398 | s.WriteString(";") 399 | 400 | plan.query = s.String() 401 | t.updatePlan = plan 402 | } 403 | 404 | return plan.createBindInstance(elem, t.dbmap.TypeConverter) 405 | } 406 | 407 | func (t *TableMap) bindDelete(elem reflect.Value) (bindInstance, error) { 408 | plan := t.deletePlan 409 | if plan.query == "" { 410 | 411 | s := bytes.Buffer{} 412 | s.WriteString(fmt.Sprintf("delete from %s", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName))) 413 | 414 | for y := range t.columns { 415 | col := t.columns[y] 416 | if !col.Transient { 417 | if col == t.version { 418 | plan.versField = col.fieldName 419 | } 420 | } 421 | } 422 | 423 | s.WriteString(" where ") 424 | for x := range t.keys { 425 | k := t.keys[x] 426 | if x > 0 { 427 | s.WriteString(" and ") 428 | } 429 | s.WriteString(t.dbmap.Dialect.QuoteField(k.ColumnName)) 430 | s.WriteString("=") 431 | s.WriteString(t.dbmap.Dialect.BindVar(x)) 432 | 433 | plan.keyFields = append(plan.keyFields, k.fieldName) 434 | plan.argFields = append(plan.argFields, k.fieldName) 435 | } 436 | if plan.versField != "" { 437 | s.WriteString(" and ") 438 | s.WriteString(t.dbmap.Dialect.QuoteField(t.version.ColumnName)) 439 | s.WriteString("=") 440 | s.WriteString(t.dbmap.Dialect.BindVar(len(plan.argFields))) 441 | 442 | plan.argFields = append(plan.argFields, plan.versField) 443 | } 444 | s.WriteString(";") 445 | 446 | plan.query = s.String() 447 | t.deletePlan = plan 448 | } 449 | 450 | return plan.createBindInstance(elem, t.dbmap.TypeConverter) 451 | } 452 | 453 | func (t *TableMap) bindGet() bindPlan { 454 | plan := t.getPlan 455 | if plan.query == "" { 456 | 457 | s := bytes.Buffer{} 458 | s.WriteString("select ") 459 | 460 | x := 0 461 | for _, col := range t.columns { 462 | if !col.Transient { 463 | if x > 0 { 464 | s.WriteString(",") 465 | } 466 | s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) 467 | plan.argFields = append(plan.argFields, col.fieldName) 468 | x++ 469 | } 470 | } 471 | s.WriteString(" from ") 472 | s.WriteString(t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)) 473 | s.WriteString(" where ") 474 | for x := range t.keys { 475 | col := t.keys[x] 476 | if x > 0 { 477 | s.WriteString(" and ") 478 | } 479 | s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName)) 480 | s.WriteString("=") 481 | s.WriteString(t.dbmap.Dialect.BindVar(x)) 482 | 483 | plan.keyFields = append(plan.keyFields, col.fieldName) 484 | } 485 | s.WriteString(";") 486 | 487 | plan.query = s.String() 488 | t.getPlan = plan 489 | } 490 | 491 | return plan 492 | } 493 | 494 | // ColumnMap represents a mapping between a Go struct field and a single 495 | // column in a table. 496 | // Unique and MaxSize only inform the 497 | // CreateTables() function and are not used by Insert/Update/Delete/Get. 498 | type ColumnMap struct { 499 | // Column name in db table 500 | ColumnName string 501 | 502 | // If true, this column is skipped in generated SQL statements 503 | Transient bool 504 | 505 | // If true, " unique" is added to create table statements. 506 | // Not used elsewhere 507 | Unique bool 508 | 509 | // Passed to Dialect.ToSqlType() to assist in informing the 510 | // correct column type to map to in CreateTables() 511 | // Not used elsewhere 512 | MaxSize int 513 | 514 | fieldName string 515 | gotype reflect.Type 516 | isPK bool 517 | isAutoIncr bool 518 | isNotNull bool 519 | } 520 | 521 | // Rename allows you to specify the column name in the table 522 | // 523 | // Example: table.ColMap("Updated").Rename("date_updated") 524 | // 525 | func (c *ColumnMap) Rename(colname string) *ColumnMap { 526 | c.ColumnName = colname 527 | return c 528 | } 529 | 530 | // SetTransient allows you to mark the column as transient. If true 531 | // this column will be skipped when SQL statements are generated 532 | func (c *ColumnMap) SetTransient(b bool) *ColumnMap { 533 | c.Transient = b 534 | return c 535 | } 536 | 537 | // SetUnique adds "unique" to the create table statements for this 538 | // column, if b is true. 539 | func (c *ColumnMap) SetUnique(b bool) *ColumnMap { 540 | c.Unique = b 541 | return c 542 | } 543 | 544 | // SetNotNull adds "not null" to the create table statements for this 545 | // column, if nn is true. 546 | func (c *ColumnMap) SetNotNull(nn bool) *ColumnMap { 547 | c.isNotNull = nn 548 | return c 549 | } 550 | 551 | // SetMaxSize specifies the max length of values of this column. This is 552 | // passed to the dialect.ToSqlType() function, which can use the value 553 | // to alter the generated type for "create table" statements 554 | func (c *ColumnMap) SetMaxSize(size int) *ColumnMap { 555 | c.MaxSize = size 556 | return c 557 | } 558 | 559 | // Transaction represents a database transaction. 560 | // Insert/Update/Delete/Get/Exec operations will be run in the context 561 | // of that transaction. Transactions should be terminated with 562 | // a call to Commit() or Rollback() 563 | type Transaction struct { 564 | dbmap *DbMap 565 | tx *sql.Tx 566 | closed bool 567 | } 568 | 569 | // SqlExecutor exposes gorp operations that can be run from Pre/Post 570 | // hooks. This hides whether the current operation that triggered the 571 | // hook is in a transaction. 572 | // 573 | // See the DbMap function docs for each of the functions below for more 574 | // information. 575 | type SqlExecutor interface { 576 | Get(i interface{}, keys ...interface{}) (interface{}, error) 577 | Insert(list ...interface{}) error 578 | Update(list ...interface{}) (int64, error) 579 | Delete(list ...interface{}) (int64, error) 580 | Exec(query string, args ...interface{}) (sql.Result, error) 581 | Select(i interface{}, query string, 582 | args ...interface{}) ([]interface{}, error) 583 | SelectInt(query string, args ...interface{}) (int64, error) 584 | SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) 585 | SelectFloat(query string, args ...interface{}) (float64, error) 586 | SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) 587 | SelectStr(query string, args ...interface{}) (string, error) 588 | SelectNullStr(query string, args ...interface{}) (sql.NullString, error) 589 | SelectOne(holder interface{}, query string, args ...interface{}) error 590 | query(query string, args ...interface{}) (*sql.Rows, error) 591 | queryRow(query string, args ...interface{}) *sql.Row 592 | } 593 | 594 | // Compile-time check that DbMap and Transaction implement the SqlExecutor 595 | // interface. 596 | var _, _ SqlExecutor = &DbMap{}, &Transaction{} 597 | 598 | type GorpLogger interface { 599 | Printf(format string, v ...interface{}) 600 | } 601 | 602 | // TraceOn turns on SQL statement logging for this DbMap. After this is 603 | // called, all SQL statements will be sent to the logger. If prefix is 604 | // a non-empty string, it will be written to the front of all logged 605 | // strings, which can aid in filtering log lines. 606 | // 607 | // Use TraceOn if you want to spy on the SQL statements that gorp 608 | // generates. 609 | // 610 | // Note that the base log.Logger type satisfies GorpLogger, but adapters can 611 | // easily be written for other logging packages (e.g., the golang-sanctioned 612 | // glog framework). 613 | func (m *DbMap) TraceOn(prefix string, logger GorpLogger) { 614 | m.logger = logger 615 | if prefix == "" { 616 | m.logPrefix = prefix 617 | } else { 618 | m.logPrefix = fmt.Sprintf("%s ", prefix) 619 | } 620 | } 621 | 622 | // TraceOff turns off tracing. It is idempotent. 623 | func (m *DbMap) TraceOff() { 624 | m.logger = nil 625 | m.logPrefix = "" 626 | } 627 | 628 | // AddTable registers the given interface type with gorp. The table name 629 | // will be given the name of the TypeOf(i). You must call this function, 630 | // or AddTableWithName, for any struct type you wish to persist with 631 | // the given DbMap. 632 | // 633 | // This operation is idempotent. If i's type is already mapped, the 634 | // existing *TableMap is returned 635 | func (m *DbMap) AddTable(i interface{}) *TableMap { 636 | return m.AddTableWithName(i, "") 637 | } 638 | 639 | // AddTableWithName has the same behavior as AddTable, but sets 640 | // table.TableName to name. 641 | func (m *DbMap) AddTableWithName(i interface{}, name string) *TableMap { 642 | return m.AddTableWithNameAndSchema(i, "", name) 643 | } 644 | 645 | // AddTableWithNameAndSchema has the same behavior as AddTable, but sets 646 | // table.TableName to name. 647 | func (m *DbMap) AddTableWithNameAndSchema(i interface{}, schema string, name string) *TableMap { 648 | t := reflect.TypeOf(i) 649 | if name == "" { 650 | name = t.Name() 651 | } 652 | 653 | // check if we have a table for this type already 654 | // if so, update the name and return the existing pointer 655 | for i := range m.tables { 656 | table := m.tables[i] 657 | if table.gotype == t { 658 | table.TableName = name 659 | return table 660 | } 661 | } 662 | 663 | tmap := &TableMap{gotype: t, TableName: name, SchemaName: schema, dbmap: m} 664 | tmap.columns, tmap.version = readStructColumns(t) 665 | m.tables = append(m.tables, tmap) 666 | 667 | return tmap 668 | } 669 | 670 | func readStructColumns(t reflect.Type) (cols []*ColumnMap, version *ColumnMap) { 671 | n := t.NumField() 672 | for i := 0; i < n; i++ { 673 | f := t.Field(i) 674 | if f.Anonymous && f.Type.Kind() == reflect.Struct { 675 | // Recursively add nested fields in embedded structs. 676 | subcols, subversion := readStructColumns(f.Type) 677 | // Don't append nested fields that have the same field 678 | // name as an already-mapped field. 679 | for _, subcol := range subcols { 680 | shouldAppend := true 681 | for _, col := range cols { 682 | if !subcol.Transient && subcol.fieldName == col.fieldName { 683 | shouldAppend = false 684 | break 685 | } 686 | } 687 | if shouldAppend { 688 | cols = append(cols, subcol) 689 | } 690 | } 691 | if subversion != nil { 692 | version = subversion 693 | } 694 | } else { 695 | columnName := f.Tag.Get("db") 696 | if columnName == "" { 697 | columnName = f.Name 698 | } 699 | cm := &ColumnMap{ 700 | ColumnName: columnName, 701 | Transient: columnName == "-", 702 | fieldName: f.Name, 703 | gotype: f.Type, 704 | } 705 | // Check for nested fields of the same field name and 706 | // override them. 707 | shouldAppend := true 708 | for index, col := range cols { 709 | if !col.Transient && col.fieldName == cm.fieldName { 710 | cols[index] = cm 711 | shouldAppend = false 712 | break 713 | } 714 | } 715 | if shouldAppend { 716 | cols = append(cols, cm) 717 | } 718 | if cm.fieldName == "Version" { 719 | version = cm 720 | } 721 | } 722 | } 723 | return 724 | } 725 | 726 | // CreateTables iterates through TableMaps registered to this DbMap and 727 | // executes "create table" statements against the database for each. 728 | // 729 | // This is particularly useful in unit tests where you want to create 730 | // and destroy the schema automatically. 731 | func (m *DbMap) CreateTables() error { 732 | return m.createTables(false) 733 | } 734 | 735 | // CreateTablesIfNotExists is similar to CreateTables, but starts 736 | // each statement with "create table if not exists" so that existing 737 | // tables do not raise errors 738 | func (m *DbMap) CreateTablesIfNotExists() error { 739 | return m.createTables(true) 740 | } 741 | 742 | func (m *DbMap) createTables(ifNotExists bool) error { 743 | var err error 744 | for i := range m.tables { 745 | table := m.tables[i] 746 | 747 | s := bytes.Buffer{} 748 | 749 | if strings.TrimSpace(table.SchemaName) != "" { 750 | schemaCreate := "create schema" 751 | if ifNotExists { 752 | schemaCreate += " if not exists" 753 | } 754 | 755 | s.WriteString(fmt.Sprintf("%s %s;", schemaCreate, table.SchemaName)) 756 | } 757 | 758 | create := "create table" 759 | if ifNotExists { 760 | create += " if not exists" 761 | } 762 | 763 | s.WriteString(fmt.Sprintf("%s %s (", create, m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName))) 764 | x := 0 765 | for _, col := range table.columns { 766 | if !col.Transient { 767 | if x > 0 { 768 | s.WriteString(", ") 769 | } 770 | stype := m.Dialect.ToSqlType(col.gotype, col.MaxSize, col.isAutoIncr) 771 | s.WriteString(fmt.Sprintf("%s %s", m.Dialect.QuoteField(col.ColumnName), stype)) 772 | 773 | if col.isPK || col.isNotNull { 774 | s.WriteString(" not null") 775 | } 776 | if col.isPK && len(table.keys) == 1 { 777 | s.WriteString(" primary key") 778 | } 779 | if col.Unique { 780 | s.WriteString(" unique") 781 | } 782 | if col.isAutoIncr { 783 | s.WriteString(fmt.Sprintf(" %s", m.Dialect.AutoIncrStr())) 784 | } 785 | 786 | x++ 787 | } 788 | } 789 | if len(table.keys) > 1 { 790 | s.WriteString(", primary key (") 791 | for x := range table.keys { 792 | if x > 0 { 793 | s.WriteString(", ") 794 | } 795 | s.WriteString(m.Dialect.QuoteField(table.keys[x].ColumnName)) 796 | } 797 | s.WriteString(")") 798 | } 799 | if len(table.uniqueTogether) > 0 { 800 | for _, columns := range table.uniqueTogether { 801 | s.WriteString(", unique (") 802 | for i, column := range columns { 803 | if i > 0 { 804 | s.WriteString(", ") 805 | } 806 | s.WriteString(m.Dialect.QuoteField(column)) 807 | } 808 | s.WriteString(")") 809 | } 810 | } 811 | s.WriteString(") ") 812 | s.WriteString(m.Dialect.CreateTableSuffix()) 813 | s.WriteString(";") 814 | _, err = m.Exec(s.String()) 815 | if err != nil { 816 | break 817 | } 818 | } 819 | return err 820 | } 821 | 822 | // DropTable drops an individual table. Will throw an error 823 | // if the table does not exist. 824 | func (m *DbMap) DropTable(table interface{}) error { 825 | t := reflect.TypeOf(table) 826 | return m.dropTable(t, false) 827 | } 828 | 829 | // DropTable drops an individual table. Will NOT throw an error 830 | // if the table does not exist. 831 | func (m *DbMap) DropTableIfExists(table interface{}) error { 832 | t := reflect.TypeOf(table) 833 | return m.dropTable(t, true) 834 | } 835 | 836 | // DropTables iterates through TableMaps registered to this DbMap and 837 | // executes "drop table" statements against the database for each. 838 | func (m *DbMap) DropTables() error { 839 | return m.dropTables(false) 840 | } 841 | 842 | // DropTablesIfExists is the same as DropTables, but uses the "if exists" clause to 843 | // avoid errors for tables that do not exist. 844 | func (m *DbMap) DropTablesIfExists() error { 845 | return m.dropTables(true) 846 | } 847 | 848 | // Goes through all the registered tables, dropping them one by one. 849 | // If an error is encountered, then it is returned and the rest of 850 | // the tables are not dropped. 851 | func (m *DbMap) dropTables(addIfExists bool) (err error) { 852 | for _, table := range m.tables { 853 | err = m.dropTableImpl(table, addIfExists) 854 | if err != nil { 855 | return 856 | } 857 | } 858 | return err 859 | } 860 | 861 | // Implementation of dropping a single table. 862 | func (m *DbMap) dropTable(t reflect.Type, addIfExists bool) error { 863 | table := tableOrNil(m, t) 864 | if table == nil { 865 | return errors.New(fmt.Sprintf("table %s was not registered!", table.TableName)) 866 | } 867 | 868 | return m.dropTableImpl(table, addIfExists) 869 | } 870 | 871 | func (m *DbMap) dropTableImpl(table *TableMap, addIfExists bool) (err error) { 872 | ifExists := "" 873 | if addIfExists { 874 | ifExists = " if exists" 875 | } 876 | _, err = m.Exec(fmt.Sprintf("drop table%s %s;", ifExists, m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName))) 877 | return err 878 | } 879 | 880 | // TruncateTables iterates through TableMaps registered to this DbMap and 881 | // executes "truncate table" statements against the database for each, or in the case of 882 | // sqlite, a "delete from" with no "where" clause, which uses the truncate optimization 883 | // (http://www.sqlite.org/lang_delete.html) 884 | func (m *DbMap) TruncateTables() error { 885 | var err error 886 | for i := range m.tables { 887 | table := m.tables[i] 888 | _, e := m.Exec(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName))) 889 | if e != nil { 890 | err = e 891 | } 892 | } 893 | return err 894 | } 895 | 896 | // Insert runs a SQL INSERT statement for each element in list. List 897 | // items must be pointers. 898 | // 899 | // Any interface whose TableMap has an auto-increment primary key will 900 | // have its last insert id bound to the PK field on the struct. 901 | // 902 | // The hook functions PreInsert() and/or PostInsert() will be executed 903 | // before/after the INSERT statement if the interface defines them. 904 | // 905 | // Panics if any interface in the list has not been registered with AddTable 906 | func (m *DbMap) Insert(list ...interface{}) error { 907 | return insert(m, m, list...) 908 | } 909 | 910 | // Update runs a SQL UPDATE statement for each element in list. List 911 | // items must be pointers. 912 | // 913 | // The hook functions PreUpdate() and/or PostUpdate() will be executed 914 | // before/after the UPDATE statement if the interface defines them. 915 | // 916 | // Returns the number of rows updated. 917 | // 918 | // Returns an error if SetKeys has not been called on the TableMap 919 | // Panics if any interface in the list has not been registered with AddTable 920 | func (m *DbMap) Update(list ...interface{}) (int64, error) { 921 | return update(m, m, list...) 922 | } 923 | 924 | // Delete runs a SQL DELETE statement for each element in list. List 925 | // items must be pointers. 926 | // 927 | // The hook functions PreDelete() and/or PostDelete() will be executed 928 | // before/after the DELETE statement if the interface defines them. 929 | // 930 | // Returns the number of rows deleted. 931 | // 932 | // Returns an error if SetKeys has not been called on the TableMap 933 | // Panics if any interface in the list has not been registered with AddTable 934 | func (m *DbMap) Delete(list ...interface{}) (int64, error) { 935 | return delete(m, m, list...) 936 | } 937 | 938 | // Get runs a SQL SELECT to fetch a single row from the table based on the 939 | // primary key(s) 940 | // 941 | // i should be an empty value for the struct to load. keys should be 942 | // the primary key value(s) for the row to load. If multiple keys 943 | // exist on the table, the order should match the column order 944 | // specified in SetKeys() when the table mapping was defined. 945 | // 946 | // The hook function PostGet() will be executed after the SELECT 947 | // statement if the interface defines them. 948 | // 949 | // Returns a pointer to a struct that matches or nil if no row is found. 950 | // 951 | // Returns an error if SetKeys has not been called on the TableMap 952 | // Panics if any interface in the list has not been registered with AddTable 953 | func (m *DbMap) Get(i interface{}, keys ...interface{}) (interface{}, error) { 954 | return get(m, m, i, keys...) 955 | } 956 | 957 | // Select runs an arbitrary SQL query, binding the columns in the result 958 | // to fields on the struct specified by i. args represent the bind 959 | // parameters for the SQL statement. 960 | // 961 | // Column names on the SELECT statement should be aliased to the field names 962 | // on the struct i. Returns an error if one or more columns in the result 963 | // do not match. It is OK if fields on i are not part of the SQL 964 | // statement. 965 | // 966 | // The hook function PostGet() will be executed after the SELECT 967 | // statement if the interface defines them. 968 | // 969 | // Values are returned in one of two ways: 970 | // 1. If i is a struct or a pointer to a struct, returns a slice of pointers to 971 | // matching rows of type i. 972 | // 2. If i is a pointer to a slice, the results will be appended to that slice 973 | // and nil returned. 974 | // 975 | // i does NOT need to be registered with AddTable() 976 | func (m *DbMap) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) { 977 | return hookedselect(m, m, i, query, args...) 978 | } 979 | 980 | // Exec runs an arbitrary SQL statement. args represent the bind parameters. 981 | // This is equivalent to running: Exec() using database/sql 982 | func (m *DbMap) Exec(query string, args ...interface{}) (sql.Result, error) { 983 | m.trace(query, args...) 984 | return m.Db.Exec(query, args...) 985 | } 986 | 987 | // SelectInt is a convenience wrapper around the gorp.SelectInt function 988 | func (m *DbMap) SelectInt(query string, args ...interface{}) (int64, error) { 989 | return SelectInt(m, query, args...) 990 | } 991 | 992 | // SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function 993 | func (m *DbMap) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) { 994 | return SelectNullInt(m, query, args...) 995 | } 996 | 997 | // SelectFloat is a convenience wrapper around the gorp.SelectFlot function 998 | func (m *DbMap) SelectFloat(query string, args ...interface{}) (float64, error) { 999 | return SelectFloat(m, query, args...) 1000 | } 1001 | 1002 | // SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function 1003 | func (m *DbMap) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) { 1004 | return SelectNullFloat(m, query, args...) 1005 | } 1006 | 1007 | // SelectStr is a convenience wrapper around the gorp.SelectStr function 1008 | func (m *DbMap) SelectStr(query string, args ...interface{}) (string, error) { 1009 | return SelectStr(m, query, args...) 1010 | } 1011 | 1012 | // SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function 1013 | func (m *DbMap) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) { 1014 | return SelectNullStr(m, query, args...) 1015 | } 1016 | 1017 | // SelectOne is a convenience wrapper around the gorp.SelectOne function 1018 | func (m *DbMap) SelectOne(holder interface{}, query string, args ...interface{}) error { 1019 | return SelectOne(m, m, holder, query, args...) 1020 | } 1021 | 1022 | // Begin starts a gorp Transaction 1023 | func (m *DbMap) Begin() (*Transaction, error) { 1024 | m.trace("begin;") 1025 | tx, err := m.Db.Begin() 1026 | if err != nil { 1027 | return nil, err 1028 | } 1029 | return &Transaction{m, tx, false}, nil 1030 | } 1031 | 1032 | func (m *DbMap) tableFor(t reflect.Type, checkPK bool) (*TableMap, error) { 1033 | table := tableOrNil(m, t) 1034 | if table == nil { 1035 | return nil, errors.New(fmt.Sprintf("No table found for type: %v", t.Name())) 1036 | } 1037 | 1038 | if checkPK && len(table.keys) < 1 { 1039 | e := fmt.Sprintf("gorp: No keys defined for table: %s", 1040 | table.TableName) 1041 | return nil, errors.New(e) 1042 | } 1043 | 1044 | return table, nil 1045 | } 1046 | 1047 | func tableOrNil(m *DbMap, t reflect.Type) *TableMap { 1048 | for i := range m.tables { 1049 | table := m.tables[i] 1050 | if table.gotype == t { 1051 | return table 1052 | } 1053 | } 1054 | return nil 1055 | } 1056 | 1057 | func (m *DbMap) tableForPointer(ptr interface{}, checkPK bool) (*TableMap, reflect.Value, error) { 1058 | ptrv := reflect.ValueOf(ptr) 1059 | if ptrv.Kind() != reflect.Ptr { 1060 | e := fmt.Sprintf("gorp: passed non-pointer: %v (kind=%v)", ptr, 1061 | ptrv.Kind()) 1062 | return nil, reflect.Value{}, errors.New(e) 1063 | } 1064 | elem := ptrv.Elem() 1065 | etype := reflect.TypeOf(elem.Interface()) 1066 | t, err := m.tableFor(etype, checkPK) 1067 | if err != nil { 1068 | return nil, reflect.Value{}, err 1069 | } 1070 | 1071 | return t, elem, nil 1072 | } 1073 | 1074 | func (m *DbMap) queryRow(query string, args ...interface{}) *sql.Row { 1075 | m.trace(query, args...) 1076 | return m.Db.QueryRow(query, args...) 1077 | } 1078 | 1079 | func (m *DbMap) query(query string, args ...interface{}) (*sql.Rows, error) { 1080 | m.trace(query, args...) 1081 | return m.Db.Query(query, args...) 1082 | } 1083 | 1084 | func (m *DbMap) trace(query string, args ...interface{}) { 1085 | if m.logger != nil { 1086 | m.logger.Printf("%s%s %v", m.logPrefix, query, args) 1087 | } 1088 | } 1089 | 1090 | /////////////// 1091 | 1092 | // Insert has the same behavior as DbMap.Insert(), but runs in a transaction. 1093 | func (t *Transaction) Insert(list ...interface{}) error { 1094 | return insert(t.dbmap, t, list...) 1095 | } 1096 | 1097 | // Update had the same behavior as DbMap.Update(), but runs in a transaction. 1098 | func (t *Transaction) Update(list ...interface{}) (int64, error) { 1099 | return update(t.dbmap, t, list...) 1100 | } 1101 | 1102 | // Delete has the same behavior as DbMap.Delete(), but runs in a transaction. 1103 | func (t *Transaction) Delete(list ...interface{}) (int64, error) { 1104 | return delete(t.dbmap, t, list...) 1105 | } 1106 | 1107 | // Get has the same behavior as DbMap.Get(), but runs in a transaction. 1108 | func (t *Transaction) Get(i interface{}, keys ...interface{}) (interface{}, error) { 1109 | return get(t.dbmap, t, i, keys...) 1110 | } 1111 | 1112 | // Select has the same behavior as DbMap.Select(), but runs in a transaction. 1113 | func (t *Transaction) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) { 1114 | return hookedselect(t.dbmap, t, i, query, args...) 1115 | } 1116 | 1117 | // Exec has the same behavior as DbMap.Exec(), but runs in a transaction. 1118 | func (t *Transaction) Exec(query string, args ...interface{}) (sql.Result, error) { 1119 | t.dbmap.trace(query, args...) 1120 | return t.tx.Exec(query, args...) 1121 | } 1122 | 1123 | // SelectInt is a convenience wrapper around the gorp.SelectInt function. 1124 | func (t *Transaction) SelectInt(query string, args ...interface{}) (int64, error) { 1125 | return SelectInt(t, query, args...) 1126 | } 1127 | 1128 | // SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function. 1129 | func (t *Transaction) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) { 1130 | return SelectNullInt(t, query, args...) 1131 | } 1132 | 1133 | // SelectFloat is a convenience wrapper around the gorp.SelectFloat function. 1134 | func (t *Transaction) SelectFloat(query string, args ...interface{}) (float64, error) { 1135 | return SelectFloat(t, query, args...) 1136 | } 1137 | 1138 | // SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function. 1139 | func (t *Transaction) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) { 1140 | return SelectNullFloat(t, query, args...) 1141 | } 1142 | 1143 | // SelectStr is a convenience wrapper around the gorp.SelectStr function. 1144 | func (t *Transaction) SelectStr(query string, args ...interface{}) (string, error) { 1145 | return SelectStr(t, query, args...) 1146 | } 1147 | 1148 | // SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function. 1149 | func (t *Transaction) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) { 1150 | return SelectNullStr(t, query, args...) 1151 | } 1152 | 1153 | // SelectOne is a convenience wrapper around the gorp.SelectOne function. 1154 | func (t *Transaction) SelectOne(holder interface{}, query string, args ...interface{}) error { 1155 | return SelectOne(t.dbmap, t, holder, query, args...) 1156 | } 1157 | 1158 | // Commit commits the underlying database transaction. 1159 | func (t *Transaction) Commit() error { 1160 | if !t.closed { 1161 | t.closed = true 1162 | t.dbmap.trace("commit;") 1163 | return t.tx.Commit() 1164 | } 1165 | 1166 | return sql.ErrTxDone 1167 | } 1168 | 1169 | // Rollback rolls back the underlying database transaction. 1170 | func (t *Transaction) Rollback() error { 1171 | if !t.closed { 1172 | t.closed = true 1173 | t.dbmap.trace("rollback;") 1174 | return t.tx.Rollback() 1175 | } 1176 | 1177 | return sql.ErrTxDone 1178 | } 1179 | 1180 | // Savepoint creates a savepoint with the given name. The name is interpolated 1181 | // directly into the SQL SAVEPOINT statement, so you must sanitize it if it is 1182 | // derived from user input. 1183 | func (t *Transaction) Savepoint(name string) error { 1184 | query := "savepoint " + t.dbmap.Dialect.QuoteField(name) 1185 | t.dbmap.trace(query, nil) 1186 | _, err := t.tx.Exec(query) 1187 | return err 1188 | } 1189 | 1190 | // RollbackToSavepoint rolls back to the savepoint with the given name. The 1191 | // name is interpolated directly into the SQL SAVEPOINT statement, so you must 1192 | // sanitize it if it is derived from user input. 1193 | func (t *Transaction) RollbackToSavepoint(savepoint string) error { 1194 | query := "rollback to savepoint " + t.dbmap.Dialect.QuoteField(savepoint) 1195 | t.dbmap.trace(query, nil) 1196 | _, err := t.tx.Exec(query) 1197 | return err 1198 | } 1199 | 1200 | // ReleaseSavepint releases the savepoint with the given name. The name is 1201 | // interpolated directly into the SQL SAVEPOINT statement, so you must sanitize 1202 | // it if it is derived from user input. 1203 | func (t *Transaction) ReleaseSavepoint(savepoint string) error { 1204 | query := "release savepoint " + t.dbmap.Dialect.QuoteField(savepoint) 1205 | t.dbmap.trace(query, nil) 1206 | _, err := t.tx.Exec(query) 1207 | return err 1208 | } 1209 | 1210 | func (t *Transaction) queryRow(query string, args ...interface{}) *sql.Row { 1211 | t.dbmap.trace(query, args...) 1212 | return t.tx.QueryRow(query, args...) 1213 | } 1214 | 1215 | func (t *Transaction) query(query string, args ...interface{}) (*sql.Rows, error) { 1216 | t.dbmap.trace(query, args...) 1217 | return t.tx.Query(query, args...) 1218 | } 1219 | 1220 | /////////////// 1221 | 1222 | // SelectInt executes the given query, which should be a SELECT statement for a single 1223 | // integer column, and returns the value of the first row returned. If no rows are 1224 | // found, zero is returned. 1225 | func SelectInt(e SqlExecutor, query string, args ...interface{}) (int64, error) { 1226 | var h int64 1227 | err := selectVal(e, &h, query, args...) 1228 | if err != nil { 1229 | return 0, err 1230 | } 1231 | return h, nil 1232 | } 1233 | 1234 | // SelectNullInt executes the given query, which should be a SELECT statement for a single 1235 | // integer column, and returns the value of the first row returned. If no rows are 1236 | // found, the empty sql.NullInt64 value is returned. 1237 | func SelectNullInt(e SqlExecutor, query string, args ...interface{}) (sql.NullInt64, error) { 1238 | var h sql.NullInt64 1239 | err := selectVal(e, &h, query, args...) 1240 | if err != nil { 1241 | return h, err 1242 | } 1243 | return h, nil 1244 | } 1245 | 1246 | // SelectFloat executes the given query, which should be a SELECT statement for a single 1247 | // float column, and returns the value of the first row returned. If no rows are 1248 | // found, zero is returned. 1249 | func SelectFloat(e SqlExecutor, query string, args ...interface{}) (float64, error) { 1250 | var h float64 1251 | err := selectVal(e, &h, query, args...) 1252 | if err != nil { 1253 | return 0, err 1254 | } 1255 | return h, nil 1256 | } 1257 | 1258 | // SelectNullFloat executes the given query, which should be a SELECT statement for a single 1259 | // float column, and returns the value of the first row returned. If no rows are 1260 | // found, the empty sql.NullInt64 value is returned. 1261 | func SelectNullFloat(e SqlExecutor, query string, args ...interface{}) (sql.NullFloat64, error) { 1262 | var h sql.NullFloat64 1263 | err := selectVal(e, &h, query, args...) 1264 | if err != nil { 1265 | return h, err 1266 | } 1267 | return h, nil 1268 | } 1269 | 1270 | // SelectStr executes the given query, which should be a SELECT statement for a single 1271 | // char/varchar column, and returns the value of the first row returned. If no rows are 1272 | // found, an empty string is returned. 1273 | func SelectStr(e SqlExecutor, query string, args ...interface{}) (string, error) { 1274 | var h string 1275 | err := selectVal(e, &h, query, args...) 1276 | if err != nil { 1277 | return "", err 1278 | } 1279 | return h, nil 1280 | } 1281 | 1282 | // SelectNullStr executes the given query, which should be a SELECT 1283 | // statement for a single char/varchar column, and returns the value 1284 | // of the first row returned. If no rows are found, the empty 1285 | // sql.NullString is returned. 1286 | func SelectNullStr(e SqlExecutor, query string, args ...interface{}) (sql.NullString, error) { 1287 | var h sql.NullString 1288 | err := selectVal(e, &h, query, args...) 1289 | if err != nil { 1290 | return h, err 1291 | } 1292 | return h, nil 1293 | } 1294 | 1295 | // SelectOne executes the given query (which should be a SELECT statement) 1296 | // and binds the result to holder, which must be a pointer. 1297 | // 1298 | // If no row is found, an an error (sql.ErrNoRows specifically) will be returned 1299 | // 1300 | // If more than one row is found, an error will be returned. 1301 | // 1302 | func SelectOne(m *DbMap, e SqlExecutor, holder interface{}, query string, args ...interface{}) error { 1303 | t := reflect.TypeOf(holder) 1304 | if t.Kind() == reflect.Ptr { 1305 | t = t.Elem() 1306 | } else { 1307 | return fmt.Errorf("gorp: SelectOne holder must be a pointer, but got: %t", holder) 1308 | } 1309 | 1310 | if t.Kind() == reflect.Struct { 1311 | list, err := hookedselect(m, e, holder, query, args...) 1312 | if err != nil { 1313 | return err 1314 | } 1315 | 1316 | dest := reflect.ValueOf(holder) 1317 | 1318 | if list != nil && len(list) > 0 { 1319 | // check for multiple rows 1320 | if len(list) > 1 { 1321 | return fmt.Errorf("gorp: multiple rows returned for: %s - %v", query, args) 1322 | } 1323 | 1324 | // only one row found 1325 | src := reflect.ValueOf(list[0]) 1326 | dest.Elem().Set(src.Elem()) 1327 | } else { 1328 | // No rows found, return a proper error. 1329 | return sql.ErrNoRows 1330 | } 1331 | 1332 | return nil 1333 | } 1334 | 1335 | return selectVal(e, holder, query, args...) 1336 | } 1337 | 1338 | func selectVal(e SqlExecutor, holder interface{}, query string, args ...interface{}) error { 1339 | if len(args) == 1 { 1340 | switch m := e.(type) { 1341 | case *DbMap: 1342 | query, args = maybeExpandNamedQuery(m, query, args) 1343 | case *Transaction: 1344 | query, args = maybeExpandNamedQuery(m.dbmap, query, args) 1345 | } 1346 | } 1347 | rows, err := e.query(query, args...) 1348 | if err != nil { 1349 | return err 1350 | } 1351 | defer rows.Close() 1352 | 1353 | if rows.Next() { 1354 | err = rows.Scan(holder) 1355 | if err != nil { 1356 | return err 1357 | } 1358 | } 1359 | 1360 | return nil 1361 | } 1362 | 1363 | /////////////// 1364 | 1365 | func hookedselect(m *DbMap, exec SqlExecutor, i interface{}, query string, 1366 | args ...interface{}) ([]interface{}, error) { 1367 | 1368 | list, err := rawselect(m, exec, i, query, args...) 1369 | if err != nil { 1370 | return nil, err 1371 | } 1372 | 1373 | // Determine where the results are: written to i, or returned in list 1374 | if t, _ := toSliceType(i); t == nil { 1375 | for _, v := range list { 1376 | err = runHook("PostGet", reflect.ValueOf(v), hookArg(exec)) 1377 | if err != nil { 1378 | return nil, err 1379 | } 1380 | } 1381 | } else { 1382 | resultsValue := reflect.Indirect(reflect.ValueOf(i)) 1383 | for i := 0; i < resultsValue.Len(); i++ { 1384 | err = runHook("PostGet", resultsValue.Index(i), hookArg(exec)) 1385 | if err != nil { 1386 | return nil, err 1387 | } 1388 | } 1389 | } 1390 | return list, nil 1391 | } 1392 | 1393 | func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string, 1394 | args ...interface{}) ([]interface{}, error) { 1395 | var ( 1396 | appendToSlice = false // Write results to i directly? 1397 | intoStruct = true // Selecting into a struct? 1398 | pointerElements = true // Are the slice elements pointers (vs values)? 1399 | ) 1400 | 1401 | // get type for i, verifying it's a supported destination 1402 | t, err := toType(i) 1403 | if err != nil { 1404 | var err2 error 1405 | if t, err2 = toSliceType(i); t == nil { 1406 | if err2 != nil { 1407 | return nil, err2 1408 | } 1409 | return nil, err 1410 | } 1411 | pointerElements = t.Kind() == reflect.Ptr 1412 | if pointerElements { 1413 | t = t.Elem() 1414 | } 1415 | appendToSlice = true 1416 | intoStruct = t.Kind() == reflect.Struct 1417 | } 1418 | 1419 | // If the caller supplied a single struct/map argument, assume a "named 1420 | // parameter" query. Extract the named arguments from the struct/map, create 1421 | // the flat arg slice, and rewrite the query to use the dialect's placeholder. 1422 | if len(args) == 1 { 1423 | query, args = maybeExpandNamedQuery(m, query, args) 1424 | } 1425 | 1426 | // Run the query 1427 | rows, err := exec.query(query, args...) 1428 | if err != nil { 1429 | return nil, err 1430 | } 1431 | defer rows.Close() 1432 | 1433 | // Fetch the column names as returned from db 1434 | cols, err := rows.Columns() 1435 | if err != nil { 1436 | return nil, err 1437 | } 1438 | 1439 | if !intoStruct && len(cols) > 1 { 1440 | return nil, fmt.Errorf("gorp: select into non-struct slice requires 1 column, got %d", len(cols)) 1441 | } 1442 | 1443 | var colToFieldIndex [][]int 1444 | if intoStruct { 1445 | if colToFieldIndex, err = columnToFieldIndex(m, t, cols); err != nil { 1446 | return nil, err 1447 | } 1448 | } 1449 | 1450 | conv := m.TypeConverter 1451 | 1452 | // Add results to one of these two slices. 1453 | var ( 1454 | list = make([]interface{}, 0) 1455 | sliceValue = reflect.Indirect(reflect.ValueOf(i)) 1456 | ) 1457 | 1458 | for { 1459 | if !rows.Next() { 1460 | // if error occured return rawselect 1461 | if rows.Err() != nil { 1462 | return nil, rows.Err() 1463 | } 1464 | // time to exit from outer "for" loop 1465 | break 1466 | } 1467 | v := reflect.New(t) 1468 | dest := make([]interface{}, len(cols)) 1469 | 1470 | custScan := make([]CustomScanner, 0) 1471 | 1472 | for x := range cols { 1473 | f := v.Elem() 1474 | if intoStruct { 1475 | f = f.FieldByIndex(colToFieldIndex[x]) 1476 | } 1477 | target := f.Addr().Interface() 1478 | if conv != nil { 1479 | scanner, ok := conv.FromDb(target) 1480 | if ok { 1481 | target = scanner.Holder 1482 | custScan = append(custScan, scanner) 1483 | } 1484 | } 1485 | dest[x] = target 1486 | } 1487 | 1488 | err = rows.Scan(dest...) 1489 | if err != nil { 1490 | return nil, err 1491 | } 1492 | 1493 | for _, c := range custScan { 1494 | err = c.Bind() 1495 | if err != nil { 1496 | return nil, err 1497 | } 1498 | } 1499 | 1500 | if appendToSlice { 1501 | if !pointerElements { 1502 | v = v.Elem() 1503 | } 1504 | sliceValue.Set(reflect.Append(sliceValue, v)) 1505 | } else { 1506 | list = append(list, v.Interface()) 1507 | } 1508 | } 1509 | 1510 | if appendToSlice && sliceValue.IsNil() { 1511 | sliceValue.Set(reflect.MakeSlice(sliceValue.Type(), 0, 0)) 1512 | } 1513 | 1514 | return list, nil 1515 | } 1516 | 1517 | // maybeExpandNamedQuery checks the given arg to see if it's eligible to be used 1518 | // as input to a named query. If so, it rewrites the query to use 1519 | // dialect-dependent bindvars and instantiates the corresponding slice of 1520 | // parameters by extracting data from the map / struct. 1521 | // If not, returns the input values unchanged. 1522 | func maybeExpandNamedQuery(m *DbMap, query string, args []interface{}) (string, []interface{}) { 1523 | arg := reflect.ValueOf(args[0]) 1524 | for arg.Kind() == reflect.Ptr { 1525 | arg = arg.Elem() 1526 | } 1527 | switch { 1528 | case arg.Kind() == reflect.Map && arg.Type().Key().Kind() == reflect.String: 1529 | return expandNamedQuery(m, query, func(key string) reflect.Value { 1530 | return arg.MapIndex(reflect.ValueOf(key)) 1531 | }) 1532 | // #84 - ignore time.Time structs here - there may be a cleaner way to do this 1533 | case arg.Kind() == reflect.Struct && !(arg.Type().PkgPath() == "time" && arg.Type().Name() == "Time"): 1534 | return expandNamedQuery(m, query, arg.FieldByName) 1535 | } 1536 | return query, args 1537 | } 1538 | 1539 | var keyRegexp = regexp.MustCompile(`:[[:word:]]+`) 1540 | 1541 | // expandNamedQuery accepts a query with placeholders of the form ":key", and a 1542 | // single arg of Kind Struct or Map[string]. It returns the query with the 1543 | // dialect's placeholders, and a slice of args ready for positional insertion 1544 | // into the query. 1545 | func expandNamedQuery(m *DbMap, query string, keyGetter func(key string) reflect.Value) (string, []interface{}) { 1546 | var ( 1547 | n int 1548 | args []interface{} 1549 | ) 1550 | return keyRegexp.ReplaceAllStringFunc(query, func(key string) string { 1551 | val := keyGetter(key[1:]) 1552 | if !val.IsValid() { 1553 | return key 1554 | } 1555 | args = append(args, val.Interface()) 1556 | newVar := m.Dialect.BindVar(n) 1557 | n++ 1558 | return newVar 1559 | }), args 1560 | } 1561 | 1562 | func columnToFieldIndex(m *DbMap, t reflect.Type, cols []string) ([][]int, error) { 1563 | colToFieldIndex := make([][]int, len(cols)) 1564 | 1565 | // check if type t is a mapped table - if so we'll 1566 | // check the table for column aliasing below 1567 | tableMapped := false 1568 | table := tableOrNil(m, t) 1569 | if table != nil { 1570 | tableMapped = true 1571 | } 1572 | 1573 | // Loop over column names and find field in i to bind to 1574 | // based on column name. all returned columns must match 1575 | // a field in the i struct 1576 | for x := range cols { 1577 | colName := strings.ToLower(cols[x]) 1578 | 1579 | field, found := t.FieldByNameFunc(func(fieldName string) bool { 1580 | field, _ := t.FieldByName(fieldName) 1581 | fieldName = field.Tag.Get("db") 1582 | 1583 | if fieldName == "-" { 1584 | return false 1585 | } else if fieldName == "" { 1586 | fieldName = field.Name 1587 | } 1588 | if tableMapped { 1589 | colMap := colMapOrNil(table, fieldName) 1590 | if colMap != nil { 1591 | fieldName = colMap.ColumnName 1592 | } 1593 | } 1594 | 1595 | return colName == strings.ToLower(fieldName) 1596 | }) 1597 | if found { 1598 | colToFieldIndex[x] = field.Index 1599 | } 1600 | if colToFieldIndex[x] == nil { 1601 | return nil, fmt.Errorf("gorp: No field %s in type %s", colName, t.Name()) 1602 | } 1603 | } 1604 | return colToFieldIndex, nil 1605 | } 1606 | 1607 | func fieldByName(val reflect.Value, fieldName string) *reflect.Value { 1608 | // try to find field by exact match 1609 | f := val.FieldByName(fieldName) 1610 | 1611 | if f != zeroVal { 1612 | return &f 1613 | } 1614 | 1615 | // try to find by case insensitive match - only the Postgres driver 1616 | // seems to require this - in the case where columns are aliased in the sql 1617 | fieldNameL := strings.ToLower(fieldName) 1618 | fieldCount := val.NumField() 1619 | t := val.Type() 1620 | for i := 0; i < fieldCount; i++ { 1621 | sf := t.Field(i) 1622 | if strings.ToLower(sf.Name) == fieldNameL { 1623 | f := val.Field(i) 1624 | return &f 1625 | } 1626 | } 1627 | 1628 | return nil 1629 | } 1630 | 1631 | // toSliceType returns the element type of the given object, if the object is a 1632 | // "*[]*Element" or "*[]Element". If not, returns nil. 1633 | // err is returned if the user was trying to pass a pointer-to-slice but failed. 1634 | func toSliceType(i interface{}) (reflect.Type, error) { 1635 | t := reflect.TypeOf(i) 1636 | if t.Kind() != reflect.Ptr { 1637 | // If it's a slice, return a more helpful error message 1638 | if t.Kind() == reflect.Slice { 1639 | return nil, fmt.Errorf("gorp: Cannot SELECT into a non-pointer slice: %v", t) 1640 | } 1641 | return nil, nil 1642 | } 1643 | if t = t.Elem(); t.Kind() != reflect.Slice { 1644 | return nil, nil 1645 | } 1646 | return t.Elem(), nil 1647 | } 1648 | 1649 | func toType(i interface{}) (reflect.Type, error) { 1650 | t := reflect.TypeOf(i) 1651 | 1652 | // If a Pointer to a type, follow 1653 | for t.Kind() == reflect.Ptr { 1654 | t = t.Elem() 1655 | } 1656 | 1657 | if t.Kind() != reflect.Struct { 1658 | return nil, fmt.Errorf("gorp: Cannot SELECT into this type: %v", reflect.TypeOf(i)) 1659 | } 1660 | return t, nil 1661 | } 1662 | 1663 | func get(m *DbMap, exec SqlExecutor, i interface{}, 1664 | keys ...interface{}) (interface{}, error) { 1665 | 1666 | t, err := toType(i) 1667 | if err != nil { 1668 | return nil, err 1669 | } 1670 | 1671 | table, err := m.tableFor(t, true) 1672 | if err != nil { 1673 | return nil, err 1674 | } 1675 | 1676 | plan := table.bindGet() 1677 | 1678 | v := reflect.New(t) 1679 | dest := make([]interface{}, len(plan.argFields)) 1680 | 1681 | conv := m.TypeConverter 1682 | custScan := make([]CustomScanner, 0) 1683 | 1684 | for x, fieldName := range plan.argFields { 1685 | f := v.Elem().FieldByName(fieldName) 1686 | target := f.Addr().Interface() 1687 | if conv != nil { 1688 | scanner, ok := conv.FromDb(target) 1689 | if ok { 1690 | target = scanner.Holder 1691 | custScan = append(custScan, scanner) 1692 | } 1693 | } 1694 | dest[x] = target 1695 | } 1696 | 1697 | row := exec.queryRow(plan.query, keys...) 1698 | err = row.Scan(dest...) 1699 | if err != nil { 1700 | if err == sql.ErrNoRows { 1701 | err = nil 1702 | } 1703 | return nil, err 1704 | } 1705 | 1706 | for _, c := range custScan { 1707 | err = c.Bind() 1708 | if err != nil { 1709 | return nil, err 1710 | } 1711 | } 1712 | 1713 | err = runHook("PostGet", v, hookArg(exec)) 1714 | if err != nil { 1715 | return nil, err 1716 | } 1717 | 1718 | return v.Interface(), nil 1719 | } 1720 | 1721 | func delete(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) { 1722 | hookarg := hookArg(exec) 1723 | count := int64(0) 1724 | for _, ptr := range list { 1725 | table, elem, err := m.tableForPointer(ptr, true) 1726 | if err != nil { 1727 | return -1, err 1728 | } 1729 | 1730 | eptr := elem.Addr() 1731 | err = runHook("PreDelete", eptr, hookarg) 1732 | if err != nil { 1733 | return -1, err 1734 | } 1735 | 1736 | bi, err := table.bindDelete(elem) 1737 | if err != nil { 1738 | return -1, err 1739 | } 1740 | 1741 | res, err := exec.Exec(bi.query, bi.args...) 1742 | if err != nil { 1743 | return -1, err 1744 | } 1745 | rows, err := res.RowsAffected() 1746 | if err != nil { 1747 | return -1, err 1748 | } 1749 | 1750 | if rows == 0 && bi.existingVersion > 0 { 1751 | return lockError(m, exec, table.TableName, 1752 | bi.existingVersion, elem, bi.keys...) 1753 | } 1754 | 1755 | count += rows 1756 | 1757 | err = runHook("PostDelete", eptr, hookarg) 1758 | if err != nil { 1759 | return -1, err 1760 | } 1761 | } 1762 | 1763 | return count, nil 1764 | } 1765 | 1766 | func update(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) { 1767 | hookarg := hookArg(exec) 1768 | count := int64(0) 1769 | for _, ptr := range list { 1770 | table, elem, err := m.tableForPointer(ptr, true) 1771 | if err != nil { 1772 | return -1, err 1773 | } 1774 | 1775 | eptr := elem.Addr() 1776 | err = runHook("PreUpdate", eptr, hookarg) 1777 | if err != nil { 1778 | return -1, err 1779 | } 1780 | 1781 | bi, err := table.bindUpdate(elem) 1782 | if err != nil { 1783 | return -1, err 1784 | } 1785 | 1786 | res, err := exec.Exec(bi.query, bi.args...) 1787 | if err != nil { 1788 | return -1, err 1789 | } 1790 | 1791 | rows, err := res.RowsAffected() 1792 | if err != nil { 1793 | return -1, err 1794 | } 1795 | 1796 | if rows == 0 && bi.existingVersion > 0 { 1797 | return lockError(m, exec, table.TableName, 1798 | bi.existingVersion, elem, bi.keys...) 1799 | } 1800 | 1801 | if bi.versField != "" { 1802 | elem.FieldByName(bi.versField).SetInt(bi.existingVersion + 1) 1803 | } 1804 | 1805 | count += rows 1806 | 1807 | err = runHook("PostUpdate", eptr, hookarg) 1808 | if err != nil { 1809 | return -1, err 1810 | } 1811 | } 1812 | return count, nil 1813 | } 1814 | 1815 | func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error { 1816 | hookarg := hookArg(exec) 1817 | for _, ptr := range list { 1818 | table, elem, err := m.tableForPointer(ptr, false) 1819 | if err != nil { 1820 | return err 1821 | } 1822 | 1823 | eptr := elem.Addr() 1824 | err = runHook("PreInsert", eptr, hookarg) 1825 | if err != nil { 1826 | return err 1827 | } 1828 | 1829 | bi, err := table.bindInsert(elem) 1830 | if err != nil { 1831 | return err 1832 | } 1833 | 1834 | if bi.autoIncrIdx > -1 { 1835 | id, err := m.Dialect.InsertAutoIncr(exec, bi.query, bi.args...) 1836 | if err != nil { 1837 | return err 1838 | } 1839 | f := elem.FieldByName(bi.autoIncrFieldName) 1840 | k := f.Kind() 1841 | if (k == reflect.Int) || (k == reflect.Int16) || (k == reflect.Int32) || (k == reflect.Int64) { 1842 | f.SetInt(id) 1843 | } else if (k == reflect.Uint16) || (k == reflect.Uint32) || (k == reflect.Uint64) { 1844 | f.SetUint(uint64(id)) 1845 | } else { 1846 | return fmt.Errorf("gorp: Cannot set autoincrement value on non-Int field. SQL=%s autoIncrIdx=%d autoIncrFieldName=%s", bi.query, bi.autoIncrIdx, bi.autoIncrFieldName) 1847 | } 1848 | } else { 1849 | _, err := exec.Exec(bi.query, bi.args...) 1850 | if err != nil { 1851 | return err 1852 | } 1853 | } 1854 | 1855 | err = runHook("PostInsert", eptr, hookarg) 1856 | if err != nil { 1857 | return err 1858 | } 1859 | } 1860 | return nil 1861 | } 1862 | 1863 | func hookArg(exec SqlExecutor) []reflect.Value { 1864 | execval := reflect.ValueOf(exec) 1865 | return []reflect.Value{execval} 1866 | } 1867 | 1868 | func runHook(name string, eptr reflect.Value, arg []reflect.Value) error { 1869 | hook := eptr.MethodByName(name) 1870 | if hook != zeroVal { 1871 | ret := hook.Call(arg) 1872 | if len(ret) > 0 && !ret[0].IsNil() { 1873 | return ret[0].Interface().(error) 1874 | } 1875 | } 1876 | return nil 1877 | } 1878 | 1879 | func lockError(m *DbMap, exec SqlExecutor, tableName string, 1880 | existingVer int64, elem reflect.Value, 1881 | keys ...interface{}) (int64, error) { 1882 | 1883 | existing, err := get(m, exec, elem.Interface(), keys...) 1884 | if err != nil { 1885 | return -1, err 1886 | } 1887 | 1888 | ole := OptimisticLockError{tableName, keys, true, existingVer} 1889 | if existing == nil { 1890 | ole.RowExists = false 1891 | } 1892 | return -1, ole 1893 | } 1894 | --------------------------------------------------------------------------------