• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

.gitignoreH A D04-Feb-201554

.travis.ymlH A D04-Feb-2015532

LICENSEH A D04-Feb-20151.1 KiB

MakefileH A D04-Feb-2015130

README.mdH A D04-Feb-201518.8 KiB

dialect.goH A D04-Feb-201517.2 KiB

errors.goH A D04-Feb-2015593

gorp.goH A D04-Feb-201557.7 KiB

gorp_test.goH A D04-Feb-201550.8 KiB

test_all.shH A D04-Feb-2015546

README.md

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