This commit is contained in:
Patrick O'brien 2016-09-01 07:01:59 +10:00
parent b655c90386
commit 7b63458055

172
README.md
View file

@ -5,7 +5,7 @@
[![CircleCI](https://circleci.com/gh/vattle/sqlboiler.svg?style=shield)](https://circleci.com/gh/vattle/sqlboiler) [![CircleCI](https://circleci.com/gh/vattle/sqlboiler.svg?style=shield)](https://circleci.com/gh/vattle/sqlboiler)
[![Go Report Card](https://goreportcard.com/badge/vattle/sqlboiler)](http://goreportcard.com/report/vattle/sqlboiler) [![Go Report Card](https://goreportcard.com/badge/vattle/sqlboiler)](http://goreportcard.com/report/vattle/sqlboiler)
SQLBoiler is a tool to generate a Go data model tailored to your database schema. SQLBoiler is a tool to generate a Go ORM tailored to your database schema.
It is a "database-first" ORM as opposed to "code-first" (like gorm/gorp). It is a "database-first" ORM as opposed to "code-first" (like gorm/gorp).
That means you must first create your database schema. Please use something That means you must first create your database schema. Please use something
@ -361,7 +361,7 @@ Note: You can set the timezone for this feature by calling `boil.SetLocation()`
We generate "Starter" methods for you. These methods are named as the plural versions of your model, We generate "Starter" methods for you. These methods are named as the plural versions of your model,
for example: `models.Jets()`. Starter methods are used to build queries using our for example: `models.Jets()`. Starter methods are used to build queries using our
[Query Mod System](#query-mod-system). They take a collection of [Query Mods](#query-mod-system) [Query Mod System](#query-mod-system). They take a slice of [Query Mods](#query-mod-system)
as parameters, and end with a call to a [Finisher](#finishers) method. as parameters, and end with a call to a [Finisher](#finishers) method.
Here are a few examples: Here are a few examples:
@ -377,6 +377,14 @@ pilots, err := models.Pilots(qm.Limit(5)).All()
err := models.Pilots(qm.Where("id=?", 1)).DeleteAll() err := models.Pilots(qm.Where("id=?", 1)).DeleteAll()
``` ```
In the event that you would like to build a query and specify the table yourself, you
can do so using `models.NewQuery()`:
```go
// Select all rows from the pilots table by using the From query mod.
err := models.NewQuery(db, From("pilots")).All()
```
As you can see, [Query Mods](#query-mods) allow you to modify your queries, and [Finishers](#finishers) As you can see, [Query Mods](#query-mods) allow you to modify your queries, and [Finishers](#finishers)
allow you to execute the final action. allow you to execute the final action.
@ -400,6 +408,9 @@ for i := 0; i < 10; i++ {
// This allows you to save on performance. // This allows you to save on performance.
``` ```
Take a look at our [Relationships Query Building](#relationships) section for some additional query
building information.
### Query Mod System ### Query Mod System
The query mod system allows you to modify queries created with [Starter](#query-building) methods The query mod system allows you to modify queries created with [Starter](#query-building) methods
@ -499,14 +510,19 @@ We provide `boil.SQL()` for executing raw queries. Generally you will want to us
this, like the following: this, like the following:
```go ```go
boil.SQL(db, "select * from pilots where id=$1", 5).Bind(&obj) err := boil.SQL(db, "select * from pilots where id=$1", 5).Bind(&obj)
``` ```
You can use your own structs or a generated struct as a parameter to Bind. Bind supports both You can use your own structs or a generated struct as a parameter to Bind. Bind supports both
a single object for single row queries and a slice of objects for multiple row queries. a single object for single row queries and a slice of objects for multiple row queries.
You also have `models.NewQuery()` at your disposal if you would still like to use [Query Build](#query-building)
but would like to build against a non-generated model.
### Binding ### Binding
For a comprehensive ruleset for `Bind()` you can refer to our [godoc](https://godoc.org/github.com/vattle/sqlboiler/boil#Bind).
The `Bind()` [Finisher](#finisher) allows the results of a query built with The `Bind()` [Finisher](#finisher) allows the results of a query built with
the [Raw SQL](#raw-query) method or the [Query Builder](#query-building) methods to be bound the [Raw SQL](#raw-query) method or the [Query Builder](#query-building) methods to be bound
to your generated struct objects, or your own custom struct objects. to your generated struct objects, or your own custom struct objects.
@ -516,52 +532,79 @@ and have no need for the rest of the object variables, or custom join struct obj
the following: the following:
```go ```go
// Custom object using two generated structs // Custom struct using two generated structs
type PilotAndJet struct { type PilotAndJet struct {
models.Pilot `boil:",bind"` models.Pilot `boil:",bind"`
models.Jet `boil:",bind"` models.Jet `boil:",bind"`
} }
var paj PilotAndJet var paj PilotAndJet
boil.SQL("select pilots.*, jets.* from pilots inner join jets on jets.pilot_id=?", 1).Bind(&paj) // Use a raw query
err := boil.SQL(`
select pilots.id as "pilots.id", pilots.name as "pilots.name",
jets.id as "jets.id", jets.pilot_id as "jets.pilot_id",
jets.age as "jets.age", jets.name as "jets.name", jets.color as "jets.color"
from pilots inner join jets on jets.pilot_id=?`, 23,
).Bind(&paj)
// Use query building
err := models.NewQuery(db,
Select("pilots.id", "pilots.name", "jets.id", "jets.pilot_id", "jets.age", "jets.name", "jets.color"),
From("pilots"),
InnerJoin("jets on jets.pilot_id = pilots.id"),
).Bind(&paj)
``` ```
```go ```go
// Custom object to select subset of data // Custom struct for selecting a subset of data
type JetInfo struct { type JetInfo struct {
AgeSum int `boil:"age_sum"` AgeSum int `boil:"age_sum"`
Count int `boil:"juicy_count"` Count int `boil:"juicy_count"`
} }
var info JetInfo var info JetInfo
boil.SQL("select sum(age) as "age_sum", count(*) as "juicy_count" from jets").Bind(&info)
// Use query building
err := models.NewQuery(db, Select("sum(age) as age_sum", "count(*) as juicy_count", From("jets"))).Bind(&info)
// Use a raw query
err := boil.SQL(`select sum(age) as "age_sum", count(*) as "juicy_count" from jets`).Bind(&info)
``` ```
We support the following struct tag modes for `Bind()` control: We support the following struct tag modes for `Bind()` control:
```go ```go
type CoolObject struct { type CoolObject struct {
// Don't specify a name, Bind will attempt snake_case conversion. // Don't specify a name, Bind will TitleCase the column
// name, and try to match against this.
Frog int Frog int
// Specify an alternative db column name, can be whatever you like. // Specify an alternative name for the column, it will
// be titlecased for matching, can be whatever you like.
Cat int `boil:"kitten"` Cat int `boil:"kitten"`
// Attempt to bind to members inside Dog if they // Ignore this struct field, do not attempt to bind it.
// cannot be found on this outer layer first. Pig int `boil:"-"`
Dog `boil:"bind"`
// Ignore this member, do not attempt to bind it. // Instead of binding to this as a regular struct field
// (like other sql-able structs eg. time.Time)
// Recursively search inside the Dog struct for field names from the query.
Dog `boil:",bind"`
// Same as the above, except specify a different table name
Mouse `boil:"rodent,bind"`
// Ignore this struct field, do not attempt to bind it.
Bird `boil:"-"` Bird `boil:"-"`
} }
``` ```
Note that structs take either `bind` or `-`, and regular members take an optional alternative column name.
### Hooks ### Hooks
We support the use of hooks for Before and After query execution. Every generated package Before and After hooks are available for most operations. If you don't need them you can
that includes hooks has the following `HookPoints` defined: shrink the size of the generated code by disabling them with the `--no-hooks` flag.
Every generated package that includes hooks has the following `HookPoints` defined:
```go ```go
const ( const (
@ -592,13 +635,13 @@ models.AddPilotHook(boil.BeforeInsertHook, myHook)
Your `ModelHook` will always be defined as `func(boil.Executor, *Model)` Your `ModelHook` will always be defined as `func(boil.Executor, *Model)`
Please be aware that if your project has no need for hooks they can be disabled on generation
using the `--no-hooks` flag. Doing so will save you some binary size on compilation.
### Transactions ### Transactions
The `boil.Executor` interface implements `sql.Tx`, as well as most other database driver The boil.Executor interface powers all of SQLBoiler. This means anything that conforms
implementations. This makes using transactions very simple: to the three `Exec/Query/QueryRow` methods can be used. `sql.DB`, `sql.Tx` as well as other
libraries (`sqlx`) conform to this interface, and therefore any of these things may be
used as an executor for any query in the system. This makes using transactions very simple:
```go ```go
tx, err := db.Begin() tx, err := db.Begin()
@ -627,7 +670,7 @@ fh, _ := os.Open("debug.txt")
boil.DebugWriter = fh boil.DebugWriter = fh
``` ```
Note: Debug output is messy at the moment. This is something we want to address. Note: Debug output is messy at the moment. This is something we would like addressed.
### Select ### Select
@ -658,8 +701,8 @@ jet, err := models.JetFind(db, 1, "name", "color")
The main thing to be aware of with `Insert` is how the `whitelist` operates. If no whitelist The main thing to be aware of with `Insert` is how the `whitelist` operates. If no whitelist
argument is provided, `Insert` will abide by the following rules: argument is provided, `Insert` will abide by the following rules:
- Insert all columns **without** a default value in the database. - Insert all columns **without** a database default value.
- Insert all columns with a non-zero value in the struct that have a default value in the database. - Insert all columns with a non-zero value that have a database default value.
On the other hand, if a whitelist is provided, we will only insert the columns specified in the whitelist. On the other hand, if a whitelist is provided, we will only insert the columns specified in the whitelist.
@ -791,10 +834,83 @@ err := pilots.ReloadAll(db)
Note: `Reload` and `ReloadAll` are not recursive, if you need your relationships reloaded Note: `Reload` and `ReloadAll` are not recursive, if you need your relationships reloaded
you will need to call the `Reload` methods on those yourself. you will need to call the `Reload` methods on those yourself.
### Exists
```go
jet, err := models.FindJet(db, 1)
// Check if the pilot assigned to this jet exists.
exists := jet.Pilot(db).Exists()
// Check if the pilot with ID 5 exists
exists := models.Pilots(db, Where("id=?", 5)).Exists()
```
### Relationships ### Relationships
relationships to one and to many
relationship set ops (to one: set, remove, tomany: add, set, remove) Helper methods will be generated for every to one and to many relationship structure
eager loading (nested and flat) you have defined in your database by using foreign keys.
We attach these helpers directly to your model struct, for example:
```go
jet, _ := models.FindJet(db, 1)
// "to one" relationship helper method.
// This will retrieve the pilot for the jet.
pilot, err := jet.Pilot(db).One()
// "to many" relationship helper method.
// This will retrieve all languages for the pilot.
languages, err := pilot.Languages(db).All()
```
If your relationship involves a join table SQLBoiler will figure it out for you transparently.
It is important to note that you should use `Eager Loading` if you plan
on loading large collections of rows, to avoid N+1 performance problems.
For example, take the following:
```go
// Avoid this loop query pattern, it is slow.
jets, _ := models.Jets(db).All()
pilots := make([]models.Pilot, len(jets))
for i := 0; i < len(jets); i++ {
pilots[i] = jets[i].Pilot(db).OneP()
}
// Instead, use Eager Loading!
jets, _ := models.Jets(db, Load("Pilot")).All()
```
Eager loading can be combined with other query mods, and it can also eager load recursively.
```go
// Example of a nested load.
// Each jet will have its pilot loaded, and each pilot will have its languages loaded.
jets, _ := models.Jets(db, Load("Pilot.Languages")).All()
// Note that each level of a nested Load call will be loaded. No need to call Load() multiple times.
// A larger, random example
users, _ := models.Users(db,
Load("Pets.Vets"),
Load("Pets.Toys"),
Load("Property"),
Where("age > ?", 23),
).All()
```
We provide the following methods for managing relationships on objects:
**To One**
- `SetX()`: Set the foreign key to point to something else: jet.SetPilot(...)
- `RemoveX()`: Null out the foreign key, effectively removing the relationship between these two objects: jet.RemovePilot(...)
**To Many**
- `AddX()`: Add more relationships to the existing set of related Xs: pilot.AddLanguages(...)
- `SetX()`: Remove all existing relationships, and replace them with the provided set: pilot.SetLanguages(...)
- `RemoveX()`: Remove all provided relationships: pilot.RemoveVideos(...)
## Benchmarks ## Benchmarks