Persistence is a useful ORM and query builder/executor package for the Go programming language. It aims to remain dialect agnostic, easy to use, and to not hide functionality from the user. Like many of my other software projects, it was built due to the frustration I had with other similar libraries available at the time. Absolutely no claims could be made that persistence is the best solution for this problem set at the moment, but it is definitely good enough tool. All of my projects which interact with sql databases use it though one could bet that this is probably just me eating my own dog food.
Installation
Just install it with go get for most dialects 1
go get -u gitlab.com/cosban/persistence
Selecting a Database
Given that persistence tries to be as dialect agnostic as possible, it makes no effort to choose a default dialect for you to use. You will need to import one that matches the database you plan on connecting to. Luckily, drivers for both postgres and sqlite3 come included.
// postgres
import _ "gitlab.com/cosban/persistence/postgres"
// sqlite3
// import _ "gitlab.com/cosban/persistence/sqlite3"
// some other driver
// import _ "your.git/repo"
Only one dialect may be chosen at a time because there has not yet been a use-case for requiring multiple dialects at the same time.
Connecting
Connecting to the database is as straight-forward as importing the dialect, creating a connection string for that dialect, and actually connecting. Connection strings need to be formatted in a way specified by the underlying database.
package main
import (
"gitlab.com/cosban/persistence"
_ "gitlab.com/cosban/persistence/sqlite3" // for an in memory database
)
func main() {
c, err := persistence.Open("sqlite3", "file::memory:?mode=memory")
// or "postgres" for the postgres driver
if err != nil {
panic("unable to connect to the database")
}
defer c.Close()
}
Models
Data which should be mapped to entries within a database are modeled as structs or pointers to structs. Only fields which are exported will be used as columns within the database
Here is an example of a simple model:
type Book struct {
ID uint64 `persistence:"primary,generated"` // a primary key that auto increments
Title string `persistence:"unique,not_null"` // require uniqueness and non-nullness
Published *time.Time `persistence:"generated"` // automatically insert now() as the date
Author string
Ignored int `persistence:"-"` // ignore this field
}
Model Tags
In order to make well-formed tables, it may be required that constraints, default values, and other attributes are put into the database schema. Persistence does this through struct optional tags.
The following are supported for use:
Tag | Details |
---|---|
name | Used to override the default column name |
not_null | Used to specify that a column must have a NOT NULL constraint |
unique | Used to specify that a column must have a UNIQUE constraint |
primary | Used to specify that one or more columns is part of a PRIMARY KEY |
primary_key | Used to specify that one or more columns is part of a PRIMARY KEY |
combined | Used to specify that one or more columns is part of a composite key (unique) |
combined_key | Used to specify that one or more columns is part of a composite key (unique) |
default | Used to specify the DEFAULT value of a column |
references | Used to specify that this column is a FOREIGN KEY on table |
foreign | Used to specify that this column is a FOREIGN KEY on table |
foreign_key | Used to specify that this column is a FOREIGN KEY on table |
type | Used to override the default type which the driver would otherwise choose |
join | Used to specify that a field is to be populated by a JOIN and is not modeled in this table |
many_to_many | Used to specify that a field is to be populated by a JOIN using a join table which allows for many-to-many relationships |
generated | Used to specify that a fiels should be automatically generated by the database. Each dialect driver determines how generated fields should be handled |
Basic CRUD Usage with the query builder
package main
import (
"time"
"gitlab.com/cosban/persistence"
_ "gitlab.com/cosban/persistence/sqlite3" // for an in memory database
)
type Book struct {
ID uint32 `persistence:"primary,generated"`
Title string `persistence:"not_null"`
Author string `persistence:"not_null"`
Published time.Time `persistence:"generated"`
}
func main() {
c, err := persistence.Open("sqlite3", "file::memory:?mode=memory")
if err != nil {
panic("unable to connect to the database")
}
defer c.Close()
// create or update a table
err = c.BuildStatement().CreateOrUpdateTable(Book{})
if err != nil {
panic("unable to create or update table")
}
// create/insert a new entry
err = c.BuildStatement().Insert(Book{Title: "War and Peace", Author: "Leo Tolstoy"})
if err != nil {
panic("unable to insert book")
}
// read single item with id of 1
var book Book
err = c.BuildStatement().From(Book{}).Where("id = ?", 1).Query(&book)
if err != nil {
panic("unable to read book")
}
// update the book which was read
book.title = "Война и миръ"
err = c.BuildStatement().Update(book)
if err != nil {
panic("unable to update book")
}
// delete
err = c.BuildStatement().Delete(book)
if err != nil {
panic("unable to delete book")
}
}
Basic Raw Statement Execution
Given that many advanced queries are unable to be done with the query builder, Persistence allows the user to create and execute manually rolled prepared statements with the same connection used by the query builder.
package main
import (
"time"
"gitlab.com/cosban/persistence"
_ "gitlab.com/cosban/persistence/sqlite3" // for an in memory database
)
type Book struct {
ID uint32 `persistence:"primary,generated"`
Title string `persistence:"not_null"`
Author string `persistence:"not_null"`
Published time.Time `persistence:"generated"`
}
func main() {
c, err := persistence.Open("sqlite3", "file::memory:?mode=memory")
if err != nil {
panic("unable to connect to the database")
}
defer c.Close()
- If your app relies on a sqlite3 database, you will need to ensure cgo is set up correctly before installing.