1# Go Relational Persistence 2 3[![build status](https://secure.travis-ci.org/go-gorp/gorp.png)](http://travis-ci.org/go-gorp/gorp) 4 5I hesitate to call gorp an ORM. Go doesn't really have objects, at least 6not in the classic Smalltalk/Java sense. There goes the "O". gorp doesn't 7know anything about the relationships between your structs (at least not 8yet). So the "R" is questionable too (but I use it in the name because, 9well, it seemed more clever). 10 11The "M" is alive and well. Given some Go structs and a database, gorp 12should remove a fair amount of boilerplate busy-work from your code. 13 14I hope that gorp saves you time, minimizes the drudgery of getting data 15in and out of your database, and helps your code focus on algorithms, 16not 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 gopkg.in/gorp.v1 37 38 // use in your .go code: 39 import ( 40 "gopkg.in/gorp.v1" 41 ) 42 43## Versioning 44 45This project provides a stable release (v1.x tags) and a bleeding edge codebase (master). 46 47`gopkg.in/gorp.v1` points to the latest v1.x tag. The API's for v1 are stable and shouldn't change. Development takes place at the master branch. Althought the code in master should always compile and test successfully, it might break API's. We aim to maintain backwards compatibility, but API's and behaviour might be changed to fix a bug. Also note that API's that are new in the master branch can change until released as v2. 48 49If you want to use bleeding edge, use `github.com/go-gorp/gorp` as import path. 50 51## API Documentation 52 53Full godoc output from the latest v1 release is available here: 54 55https://godoc.org/gopkg.in/gorp.v1 56 57For the latest code in master: 58 59https://godoc.org/github.com/go-gorp/gorp 60 61## Quickstart 62 63```go 64package main 65 66import ( 67 "database/sql" 68 "gopkg.in/gorp.v1" 69 _ "github.com/mattn/go-sqlite3" 70 "log" 71 "time" 72) 73 74func main() { 75 // initialize the DbMap 76 dbmap := initDb() 77 defer dbmap.Db.Close() 78 79 // delete any existing rows 80 err := dbmap.TruncateTables() 81 checkErr(err, "TruncateTables failed") 82 83 // create two posts 84 p1 := newPost("Go 1.1 released!", "Lorem ipsum lorem ipsum") 85 p2 := newPost("Go 1.2 released!", "Lorem ipsum lorem ipsum") 86 87 // insert rows - auto increment PKs will be set properly after the insert 88 err = dbmap.Insert(&p1, &p2) 89 checkErr(err, "Insert failed") 90 91 // use convenience SelectInt 92 count, err := dbmap.SelectInt("select count(*) from posts") 93 checkErr(err, "select count(*) failed") 94 log.Println("Rows after inserting:", count) 95 96 // update a row 97 p2.Title = "Go 1.2 is better than ever" 98 count, err = dbmap.Update(&p2) 99 checkErr(err, "Update failed") 100 log.Println("Rows updated:", count) 101 102 // fetch one row - note use of "post_id" instead of "Id" since column is aliased 103 // 104 // Postgres users should use $1 instead of ? placeholders 105 // See 'Known Issues' below 106 // 107 err = dbmap.SelectOne(&p2, "select * from posts where post_id=?", p2.Id) 108 checkErr(err, "SelectOne failed") 109 log.Println("p2 row:", p2) 110 111 // fetch all rows 112 var posts []Post 113 _, err = dbmap.Select(&posts, "select * from posts order by post_id") 114 checkErr(err, "Select failed") 115 log.Println("All rows:") 116 for x, p := range posts { 117 log.Printf(" %d: %v\n", x, p) 118 } 119 120 // delete row by PK 121 count, err = dbmap.Delete(&p1) 122 checkErr(err, "Delete failed") 123 log.Println("Rows deleted:", count) 124 125 // delete row manually via Exec 126 _, err = dbmap.Exec("delete from posts where post_id=?", p2.Id) 127 checkErr(err, "Exec failed") 128 129 // confirm count is zero 130 count, err = dbmap.SelectInt("select count(*) from posts") 131 checkErr(err, "select count(*) failed") 132 log.Println("Row count - should be zero:", count) 133 134 log.Println("Done!") 135} 136 137type Post struct { 138 // db tag lets you specify the column name if it differs from the struct field 139 Id int64 `db:"post_id"` 140 Created int64 141 Title string 142 Body string 143} 144 145func newPost(title, body string) Post { 146 return Post{ 147 Created: time.Now().UnixNano(), 148 Title: title, 149 Body: body, 150 } 151} 152 153func initDb() *gorp.DbMap { 154 // connect to db using standard Go database/sql API 155 // use whatever database/sql driver you wish 156 db, err := sql.Open("sqlite3", "/tmp/post_db.bin") 157 checkErr(err, "sql.Open failed") 158 159 // construct a gorp DbMap 160 dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}} 161 162 // add a table, setting the table name to 'posts' and 163 // specifying that the Id property is an auto incrementing PK 164 dbmap.AddTableWithName(Post{}, "posts").SetKeys(true, "Id") 165 166 // create the table. in a production system you'd generally 167 // use a migration tool, or create the tables via scripts 168 err = dbmap.CreateTablesIfNotExists() 169 checkErr(err, "Create tables failed") 170 171 return dbmap 172} 173 174func checkErr(err error, msg string) { 175 if err != nil { 176 log.Fatalln(msg, err) 177 } 178} 179``` 180 181## Examples 182 183### Mapping structs to tables 184 185First define some types: 186 187```go 188type Invoice struct { 189 Id int64 190 Created int64 191 Updated int64 192 Memo string 193 PersonId int64 194} 195 196type Person struct { 197 Id int64 198 Created int64 199 Updated int64 200 FName string 201 LName string 202} 203 204// Example of using tags to alias fields to column names 205// The 'db' value is the column name 206// 207// A hyphen will cause gorp to skip this field, similar to the 208// Go json package. 209// 210// This is equivalent to using the ColMap methods: 211// 212// table := dbmap.AddTableWithName(Product{}, "product") 213// table.ColMap("Id").Rename("product_id") 214// table.ColMap("Price").Rename("unit_price") 215// table.ColMap("IgnoreMe").SetTransient(true) 216// 217type Product struct { 218 Id int64 `db:"product_id"` 219 Price int64 `db:"unit_price"` 220 IgnoreMe string `db:"-"` 221} 222``` 223 224Then create a mapper, typically you'd do this one time at app startup: 225 226```go 227// connect to db using standard Go database/sql API 228// use whatever database/sql driver you wish 229db, err := sql.Open("mymysql", "tcp:localhost:3306*mydb/myuser/mypassword") 230 231// construct a gorp DbMap 232dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}} 233 234// register the structs you wish to use with gorp 235// you can also use the shorter dbmap.AddTable() if you 236// don't want to override the table name 237// 238// SetKeys(true) means we have a auto increment primary key, which 239// will get automatically bound to your struct post-insert 240// 241t1 := dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id") 242t2 := dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id") 243t3 := dbmap.AddTableWithName(Product{}, "product_test").SetKeys(true, "Id") 244``` 245 246### Struct Embedding 247 248gorp supports embedding structs. For example: 249 250```go 251type Names struct { 252 FirstName string 253 LastName string 254} 255 256type WithEmbeddedStruct struct { 257 Id int64 258 Names 259} 260 261es := &WithEmbeddedStruct{-1, Names{FirstName: "Alice", LastName: "Smith"}} 262err := dbmap.Insert(es) 263``` 264 265See the `TestWithEmbeddedStruct` function in `gorp_test.go` for a full example. 266 267### Create/Drop Tables ### 268 269Automatically create / drop registered tables. This is useful for unit tests 270but is entirely optional. You can of course use gorp with tables created manually, 271or with a separate migration tool (like goose: https://bitbucket.org/liamstask/goose). 272 273```go 274// create all registered tables 275dbmap.CreateTables() 276 277// same as above, but uses "if not exists" clause to skip tables that are 278// already defined 279dbmap.CreateTablesIfNotExists() 280 281// drop 282dbmap.DropTables() 283``` 284 285### SQL Logging 286 287Optionally you can pass in a logger to trace all SQL statements. 288I recommend enabling this initially while you're getting the feel for what 289gorp is doing on your behalf. 290 291Gorp defines a `GorpLogger` interface that Go's built in `log.Logger` satisfies. 292However, you can write your own `GorpLogger` implementation, or use a package such 293as `glog` if you want more control over how statements are logged. 294 295```go 296// Will log all SQL statements + args as they are run 297// The first arg is a string prefix to prepend to all log messages 298dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds)) 299 300// Turn off tracing 301dbmap.TraceOff() 302``` 303 304### Insert 305 306```go 307// Must declare as pointers so optional callback hooks 308// can operate on your data, not copies 309inv1 := &Invoice{0, 100, 200, "first order", 0} 310inv2 := &Invoice{0, 100, 200, "second order", 0} 311 312// Insert your rows 313err := dbmap.Insert(inv1, inv2) 314 315// Because we called SetKeys(true) on Invoice, the Id field 316// will be populated after the Insert() automatically 317fmt.Printf("inv1.Id=%d inv2.Id=%d\n", inv1.Id, inv2.Id) 318``` 319 320### Update 321 322Continuing the above example, use the `Update` method to modify an Invoice: 323 324```go 325// count is the # of rows updated, which should be 1 in this example 326count, err := dbmap.Update(inv1) 327``` 328 329### Delete 330 331If you have primary key(s) defined for a struct, you can use the `Delete` 332method to remove rows: 333 334```go 335count, err := dbmap.Delete(inv1) 336``` 337 338### Select by Key 339 340Use the `Get` method to fetch a single row by primary key. It returns 341nil if no row is found. 342 343```go 344// fetch Invoice with Id=99 345obj, err := dbmap.Get(Invoice{}, 99) 346inv := obj.(*Invoice) 347``` 348 349### Ad Hoc SQL 350 351#### SELECT 352 353`Select()` and `SelectOne()` provide a simple way to bind arbitrary queries to a slice 354or a single struct. 355 356```go 357// Select a slice - first return value is not needed when a slice pointer is passed to Select() 358var posts []Post 359_, err := dbmap.Select(&posts, "select * from post order by id") 360 361// You can also use primitive types 362var ids []string 363_, err := dbmap.Select(&ids, "select id from post") 364 365// Select a single row. 366// Returns an error if no row found, or if more than one row is found 367var post Post 368err := dbmap.SelectOne(&post, "select * from post where id=?", id) 369``` 370 371Want to do joins? Just write the SQL and the struct. gorp will bind them: 372 373```go 374// Define a type for your join 375// It *must* contain all the columns in your SELECT statement 376// 377// The names here should match the aliased column names you specify 378// in your SQL - no additional binding work required. simple. 379// 380type InvoicePersonView struct { 381 InvoiceId int64 382 PersonId int64 383 Memo string 384 FName string 385} 386 387// Create some rows 388p1 := &Person{0, 0, 0, "bob", "smith"} 389dbmap.Insert(p1) 390 391// notice how we can wire up p1.Id to the invoice easily 392inv1 := &Invoice{0, 0, 0, "xmas order", p1.Id} 393dbmap.Insert(inv1) 394 395// Run your query 396query := "select i.Id InvoiceId, p.Id PersonId, i.Memo, p.FName " + 397 "from invoice_test i, person_test p " + 398 "where i.PersonId = p.Id" 399 400// pass a slice to Select() 401var list []InvoicePersonView 402_, err := dbmap.Select(&list, query) 403 404// this should test true 405expected := InvoicePersonView{inv1.Id, p1.Id, inv1.Memo, p1.FName} 406if reflect.DeepEqual(list[0], expected) { 407 fmt.Println("Woot! My join worked!") 408} 409``` 410 411#### SELECT string or int64 412 413gorp provides a few convenience methods for selecting a single string or int64. 414 415```go 416// select single int64 from db (use $1 instead of ? for postgresql) 417i64, err := dbmap.SelectInt("select count(*) from foo where blah=?", blahVal) 418 419// select single string from db: 420s, err := dbmap.SelectStr("select name from foo where blah=?", blahVal) 421 422``` 423 424#### Named bind parameters 425 426You may use a map or struct to bind parameters by name. This is currently 427only supported in SELECT queries. 428 429```go 430_, err := dbm.Select(&dest, "select * from Foo where name = :name and age = :age", map[string]interface{}{ 431 "name": "Rob", 432 "age": 31, 433}) 434``` 435 436#### UPDATE / DELETE 437 438You can execute raw SQL if you wish. Particularly good for batch operations. 439 440```go 441res, err := dbmap.Exec("delete from invoice_test where PersonId=?", 10) 442``` 443 444### Transactions 445 446You can batch operations into a transaction: 447 448```go 449func InsertInv(dbmap *DbMap, inv *Invoice, per *Person) error { 450 // Start a new transaction 451 trans, err := dbmap.Begin() 452 if err != nil { 453 return err 454 } 455 456 trans.Insert(per) 457 inv.PersonId = per.Id 458 trans.Insert(inv) 459 460 // if the commit is successful, a nil error is returned 461 return trans.Commit() 462} 463``` 464 465### Hooks 466 467Use hooks to update data before/after saving to the db. Good for timestamps: 468 469```go 470// implement the PreInsert and PreUpdate hooks 471func (i *Invoice) PreInsert(s gorp.SqlExecutor) error { 472 i.Created = time.Now().UnixNano() 473 i.Updated = i.Created 474 return nil 475} 476 477func (i *Invoice) PreUpdate(s gorp.SqlExecutor) error { 478 i.Updated = time.Now().UnixNano() 479 return nil 480} 481 482// You can use the SqlExecutor to cascade additional SQL 483// Take care to avoid cycles. gorp won't prevent them. 484// 485// Here's an example of a cascading delete 486// 487func (p *Person) PreDelete(s gorp.SqlExecutor) error { 488 query := "delete from invoice_test where PersonId=?" 489 err := s.Exec(query, p.Id); if err != nil { 490 return err 491 } 492 return nil 493} 494``` 495 496Full list of hooks that you can implement: 497 498 PostGet 499 PreInsert 500 PostInsert 501 PreUpdate 502 PostUpdate 503 PreDelete 504 PostDelete 505 506 All have the same signature. for example: 507 508 func (p *MyStruct) PostUpdate(s gorp.SqlExecutor) error 509 510### Optimistic Locking 511 512gorp provides a simple optimistic locking feature, similar to Java's JPA, that 513will raise an error if you try to update/delete a row whose `version` column 514has a value different than the one in memory. This provides a safe way to do 515"select then update" style operations without explicit read and write locks. 516 517```go 518// Version is an auto-incremented number, managed by gorp 519// If this property is present on your struct, update 520// operations will be constrained 521// 522// For example, say we defined Person as: 523 524type Person struct { 525 Id int64 526 Created int64 527 Updated int64 528 FName string 529 LName string 530 531 // automatically used as the Version col 532 // use table.SetVersionCol("columnName") to map a different 533 // struct field as the version field 534 Version int64 535} 536 537p1 := &Person{0, 0, 0, "Bob", "Smith", 0} 538dbmap.Insert(p1) // Version is now 1 539 540obj, err := dbmap.Get(Person{}, p1.Id) 541p2 := obj.(*Person) 542p2.LName = "Edwards" 543dbmap.Update(p2) // Version is now 2 544 545p1.LName = "Howard" 546 547// Raises error because p1.Version == 1, which is out of date 548count, err := dbmap.Update(p1) 549_, ok := err.(gorp.OptimisticLockError) 550if ok { 551 // should reach this statement 552 553 // in a real app you might reload the row and retry, or 554 // you might propegate this to the user, depending on the desired 555 // semantics 556 fmt.Printf("Tried to update row with stale data: %v\n", err) 557} else { 558 // some other db error occurred - log or return up the stack 559 fmt.Printf("Unknown db err: %v\n", err) 560} 561``` 562 563## Database Drivers 564 565gorp uses the Go 1 `database/sql` package. A full list of compliant drivers is available here: 566 567http://code.google.com/p/go-wiki/wiki/SQLDrivers 568 569Sadly, SQL databases differ on various issues. gorp provides a Dialect interface that should be 570implemented per database vendor. Dialects are provided for: 571 572* MySQL 573* PostgreSQL 574* sqlite3 575 576Each of these three databases pass the test suite. See `gorp_test.go` for example 577DSNs for these three databases. 578 579Support is also provided for: 580 581* Oracle (contributed by @klaidliadon) 582* SQL Server (contributed by @qrawl) - use driver: github.com/denisenkom/go-mssqldb 583 584Note that these databases are not covered by CI and I (@coopernurse) have no good way to 585test them locally. So please try them and send patches as needed, but expect a bit more 586unpredicability. 587 588## Known Issues 589 590### SQL placeholder portability 591 592Different databases use different strings to indicate variable placeholders in 593prepared SQL statements. Unlike some database abstraction layers (such as JDBC), 594Go's `database/sql` does not standardize this. 595 596SQL generated by gorp in the `Insert`, `Update`, `Delete`, and `Get` methods delegates 597to a Dialect implementation for each database, and will generate portable SQL. 598 599Raw SQL strings passed to `Exec`, `Select`, `SelectOne`, `SelectInt`, etc will not be 600parsed. Consequently you may have portability issues if you write a query like this: 601 602```go 603// works on MySQL and Sqlite3, but not with Postgresql 604err := dbmap.SelectOne(&val, "select * from foo where id = ?", 30) 605``` 606 607In `Select` and `SelectOne` you can use named parameters to work around this. 608The following is portable: 609 610```go 611err := dbmap.SelectOne(&val, "select * from foo where id = :id", 612 map[string]interface{} { "id": 30}) 613``` 614 615### time.Time and time zones 616 617gorp will pass `time.Time` fields through to the `database/sql` driver, but note that 618the behavior of this type varies across database drivers. 619 620MySQL users should be especially cautious. See: https://github.com/ziutek/mymysql/pull/77 621 622To avoid any potential issues with timezone/DST, consider using an integer field for time 623data and storing UNIX time. 624 625## Running the tests 626 627The included tests may be run against MySQL, Postgresql, or sqlite3. 628You must set two environment variables so the test code knows which driver to 629use, and how to connect to your database. 630 631```sh 632# MySQL example: 633export GORP_TEST_DSN=gomysql_test/gomysql_test/abc123 634export GORP_TEST_DIALECT=mysql 635 636# run the tests 637go test 638 639# run the tests and benchmarks 640go test -bench="Bench" -benchtime 10 641``` 642 643Valid `GORP_TEST_DIALECT` values are: "mysql", "postgres", "sqlite3" 644See the `test_all.sh` script for examples of all 3 databases. This is the script I run 645locally to test the library. 646 647## Performance 648 649gorp 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. 650 651## Help/Support 652 653IRC: #gorp 654Mailing list: gorp-dev@googlegroups.com 655Bugs/Enhancements: Create a github issue 656 657## Pull requests / Contributions 658 659Contributions are very welcome. Please follow these guidelines: 660 661* Fork the `master` branch and issue pull requests targeting the `master` branch 662* If you are adding an enhancement, please open an issue first with your proposed change. 663* Changes that break backwards compatibility in the public API are only accepted after we 664 discuss on a GitHub issue for a while. 665 666Thanks! 667 668## Contributors 669 670* matthias-margush - column aliasing via tags 671* Rob Figueiredo - @robfig 672* Quinn Slack - @sqs 673