aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiall Sheridan <nsheridan@gmail.com>2018-08-05 23:00:58 +0100
committerNiall Sheridan <nsheridan@gmail.com>2018-08-07 15:43:39 +0100
commitd836a4496de7b24a9d3317e274800d35053a04f6 (patch)
treedf290dee5a247b70126a62ec367d1642c44c5113
parentb7b3e5cfad53732a379b3784ec2c3b72577ab665 (diff)
Manage db schema with rubenv/sql-migrate
It's currently hard to make changes to the database schema. Use sql-migrate to make incremental changes. Stop hard-coding the database name (the default is still "certs" for backward-compatibility) The `automigrate()` function will automatically run pending migrations. Use a different migration directory per database driver. This carries a cost of duplication, but is easier than creating migrations which will cleanly execute in both SQLite and MySQL. Migrations are shipped using the packr utility.
-rw-r--r--.travis.yml4
-rw-r--r--server/store/a_store-packr.go12
-rw-r--r--server/store/migrations/mysql/20180626224600_create_issued_certs.sql15
-rw-r--r--server/store/migrations/sqlite3/20180626224600_create_issued_certs.sql15
-rw-r--r--server/store/sqldb.go48
-rw-r--r--vendor/github.com/rubenv/sql-migrate/LICENSE21
-rw-r--r--vendor/github.com/rubenv/sql-migrate/README.md289
-rw-r--r--vendor/github.com/rubenv/sql-migrate/doc.go239
-rw-r--r--vendor/github.com/rubenv/sql-migrate/migrate.go674
-rw-r--r--vendor/github.com/rubenv/sql-migrate/sqlparse/LICENSE22
-rw-r--r--vendor/github.com/rubenv/sql-migrate/sqlparse/README.md7
-rw-r--r--vendor/github.com/rubenv/sql-migrate/sqlparse/sqlparse.go235
-rw-r--r--vendor/gopkg.in/gorp.v1/LICENSE22
-rw-r--r--vendor/gopkg.in/gorp.v1/Makefile6
-rw-r--r--vendor/gopkg.in/gorp.v1/README.md672
-rw-r--r--vendor/gopkg.in/gorp.v1/dialect.go692
-rw-r--r--vendor/gopkg.in/gorp.v1/errors.go26
-rw-r--r--vendor/gopkg.in/gorp.v1/gorp.go2085
-rwxr-xr-xvendor/gopkg.in/gorp.v1/test_all.sh22
-rw-r--r--vendor/vendor.json18
20 files changed, 5111 insertions, 13 deletions
diff --git a/.travis.yml b/.travis.yml
index d057c13..403dddc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,13 +18,11 @@ matrix:
before_install:
- make dep
+ - mysql -e 'CREATE DATABASE certs;'
install:
- go version
-before_script:
- - mysql < db/seed.sql
-
sudo: false
script:
- make test \ No newline at end of file
diff --git a/server/store/a_store-packr.go b/server/store/a_store-packr.go
new file mode 100644
index 0000000..9f06e42
--- /dev/null
+++ b/server/store/a_store-packr.go
@@ -0,0 +1,12 @@
+// Code generated by github.com/gobuffalo/packr. DO NOT EDIT
+
+package store
+
+import "github.com/gobuffalo/packr"
+
+// You can use the "packr clean" command to clean up this,
+// and any other packr generated files.
+func init() {
+ packr.PackJSONBytes("migrations", "mysql/20180626224600_create_issued_certs.sql", "\"H4sIAAAAAAAA/5SRUWuzMBSG7/MrDr2p8lWIH5Sx9crNFGSuLVZhZQwTzGELrlZi1uq/H3Htpr0b5OrkeTnP4fU8+LdXb1oYhKwmDwkLUgZpcB8ziJawWqfAnqNtugWumuYTZV6gNg0HhwDwErtcSQ5HoYt3oZ3/87nbZ1ZZHM8sUWtVFaoWH80VFbJlkMUpTF5eJz1ZaBQGZS4MBykMGrXHH2rq395Qj/oe9YHSO/v8aR/DtlYam7/GNB4PJUoORlWdqozj/yrRb0Kc8hI7DgZbYyebJHoKkh08sh04l9Nd+2MnXMk2H8o4Q7Uxdl5+hV+UZqOjXOIuCBnWFB5OFQmT9eZc07iYBfkKAAD///LIlt7TAQAA\"")
+ packr.PackJSONBytes("migrations", "sqlite3/20180626224600_create_issued_certs.sql", "\"H4sIAAAAAAAA/5SR0UrDMBSG7/MUh92swxVSYYjuqtoMirUbXQsbIk1oDhrqupLGrX17aV21TkGEXIXv//nOObYNFzv1rIVBSEpyFzE3ZhC7twEDfwHhMga28dfxGriqqjeUaYbaVBwsAsBzbFIlORyEzl6Eti5ns0mXCZMgmLZEqVWRqVK8VmeUxxZuEsQwenwadWSmURiUqTAcpDBo1A4/qbFzfUVt6tjUAUpv2ueMuxjWpdJY/Tem8bDPUXIwqmhUYSznS4l+EOKY5thwMFib9mcV+Q9utIV7tgWrH31CJvN+a37osQ1wJet0aLUMf+xuaP1r/qT3Z08/xhTOKsnwsN7+WBAvWq5Oh/1eMyfvAQAA//+OXEmHBQIAAA==\"")
+}
diff --git a/server/store/migrations/mysql/20180626224600_create_issued_certs.sql b/server/store/migrations/mysql/20180626224600_create_issued_certs.sql
new file mode 100644
index 0000000..c5b80e0
--- /dev/null
+++ b/server/store/migrations/mysql/20180626224600_create_issued_certs.sql
@@ -0,0 +1,15 @@
+-- +migrate Up
+CREATE TABLE IF NOT EXISTS `issued_certs` (
+ `key_id` varchar(255) NOT NULL,
+ `principals` varchar(255) DEFAULT "[]",
+ `created_at` datetime DEFAULT '1970-01-01 00:00:01',
+ `expires_at` datetime DEFAULT '1970-01-01 00:00:01',
+ `revoked` tinyint(1) DEFAULT 0,
+ `raw_key` text,
+ PRIMARY KEY (`key_id`)
+);
+CREATE INDEX `idx_expires_at` ON `issued_certs` (`expires_at`);
+CREATE INDEX `idx_revoked_expires_at` ON `issued_certs` (`revoked`, `expires_at`);
+
+-- +migrate Down
+DROP TABLE `issued_certs`;
diff --git a/server/store/migrations/sqlite3/20180626224600_create_issued_certs.sql b/server/store/migrations/sqlite3/20180626224600_create_issued_certs.sql
new file mode 100644
index 0000000..c5b80e0
--- /dev/null
+++ b/server/store/migrations/sqlite3/20180626224600_create_issued_certs.sql
@@ -0,0 +1,15 @@
+-- +migrate Up
+CREATE TABLE IF NOT EXISTS `issued_certs` (
+ `key_id` varchar(255) NOT NULL,
+ `principals` varchar(255) DEFAULT "[]",
+ `created_at` datetime DEFAULT '1970-01-01 00:00:01',
+ `expires_at` datetime DEFAULT '1970-01-01 00:00:01',
+ `revoked` tinyint(1) DEFAULT 0,
+ `raw_key` text,
+ PRIMARY KEY (`key_id`)
+);
+CREATE INDEX `idx_expires_at` ON `issued_certs` (`expires_at`);
+CREATE INDEX `idx_revoked_expires_at` ON `issued_certs` (`revoked`, `expires_at`);
+
+-- +migrate Down
+DROP TABLE `issued_certs`;
diff --git a/server/store/sqldb.go b/server/store/sqldb.go
index 0b91023..3526a2b 100644
--- a/server/store/sqldb.go
+++ b/server/store/sqldb.go
@@ -2,15 +2,20 @@ package store
import (
"fmt"
+ "log"
"net"
+ "path"
"time"
"golang.org/x/crypto/ssh"
"github.com/go-sql-driver/mysql"
+ "github.com/gobuffalo/packr"
+ multierror "github.com/hashicorp/go-multierror"
"github.com/jmoiron/sqlx"
"github.com/nsheridan/cashier/server/config"
"github.com/pkg/errors"
+ migrate "github.com/rubenv/sql-migrate"
)
var _ CertStorer = (*SQLStore)(nil)
@@ -42,20 +47,24 @@ func NewSQLStore(c config.Database) (*SQLStore, error) {
m.User = c["username"]
m.Passwd = c["password"]
m.Addr = address
- m.DBName = "certs"
+ m.Net = "tcp"
+ m.DBName = c["dbname"]
+ if m.DBName == "" {
+ m.DBName = "certs" // Legacy database name
+ }
m.ParseTime = true
dsn = m.FormatDSN()
case "sqlite":
driver = "sqlite3"
dsn = c["filename"]
}
- conn, err := sqlx.Open(driver, dsn)
+
+ conn, err := sqlx.Connect(driver, dsn)
if err != nil {
return nil, fmt.Errorf("SQLStore: could not get a connection: %v", err)
}
- if err := conn.Ping(); err != nil {
- conn.Close()
- return nil, fmt.Errorf("SQLStore: could not establish a good connection: %v", err)
+ if err := autoMigrate(driver, conn); err != nil {
+ return nil, fmt.Errorf("SQLStore: could not update schema: %v", err)
}
db := &SQLStore{
@@ -71,7 +80,7 @@ func NewSQLStore(c config.Database) (*SQLStore, error) {
if db.listAll, err = conn.Preparex("SELECT * FROM issued_certs"); err != nil {
return nil, fmt.Errorf("SQLStore: prepare listAll: %v", err)
}
- if db.listCurrent, err = conn.Preparex("SELECT * FROM issued_certs WHERE ? <= expires_at"); err != nil {
+ if db.listCurrent, err = conn.Preparex("SELECT * FROM issued_certs WHERE expires_at >= ?"); err != nil {
return nil, fmt.Errorf("SQLStore: prepare listCurrent: %v", err)
}
if db.revoked, err = conn.Preparex("SELECT * FROM issued_certs WHERE revoked = 1 AND ? <= expires_at"); err != nil {
@@ -80,6 +89,25 @@ func NewSQLStore(c config.Database) (*SQLStore, error) {
return db, nil
}
+func autoMigrate(driver string, conn *sqlx.DB) error {
+ log.Print("Executing any pending schema migrations")
+ var err error
+ migrate.SetTable("schema_migrations")
+ srcs := &migrate.PackrMigrationSource{
+ Box: packr.NewBox(path.Join("migrations", driver)),
+ }
+ n, err := migrate.Exec(conn.DB, driver, srcs, migrate.Up)
+ if err != nil {
+ err = multierror.Append(err)
+ return err
+ }
+ log.Printf("Executed %d migrations", n)
+ if err != nil {
+ log.Fatalf("Errors were found running migrations: %v", err)
+ }
+ return nil
+}
+
// rowScanner is implemented by sql.Row and sql.Rows
type rowScanner interface {
Scan(dest ...interface{}) error
@@ -88,7 +116,7 @@ type rowScanner interface {
// Get a single *CertRecord
func (db *SQLStore) Get(id string) (*CertRecord, error) {
if err := db.conn.Ping(); err != nil {
- return nil, err
+ return nil, errors.Wrap(err, "unable to connect to database")
}
r := &CertRecord{}
return r, db.get.Get(r, id)
@@ -102,7 +130,7 @@ func (db *SQLStore) SetCert(cert *ssh.Certificate) error {
// SetRecord records a *CertRecord
func (db *SQLStore) SetRecord(rec *CertRecord) error {
if err := db.conn.Ping(); err != nil {
- return err
+ return errors.Wrap(err, "unable to connect to database")
}
_, err := db.set.Exec(rec.KeyID, rec.Principals, rec.CreatedAt, rec.Expires, rec.Raw)
return err
@@ -112,7 +140,7 @@ func (db *SQLStore) SetRecord(rec *CertRecord) error {
// By default only active certs are returned.
func (db *SQLStore) List(includeExpired bool) ([]*CertRecord, error) {
if err := db.conn.Ping(); err != nil {
- return nil, err
+ return nil, errors.Wrap(err, "unable to connect to database")
}
recs := []*CertRecord{}
if includeExpired {
@@ -140,7 +168,7 @@ func (db *SQLStore) Revoke(ids []string) error {
// GetRevoked returns all revoked certs
func (db *SQLStore) GetRevoked() ([]*CertRecord, error) {
if err := db.conn.Ping(); err != nil {
- return nil, err
+ return nil, errors.Wrap(err, "unable to connect to database")
}
var recs []*CertRecord
if err := db.revoked.Select(&recs, time.Now().UTC()); err != nil {
diff --git a/vendor/github.com/rubenv/sql-migrate/LICENSE b/vendor/github.com/rubenv/sql-migrate/LICENSE
new file mode 100644
index 0000000..2b19587
--- /dev/null
+++ b/vendor/github.com/rubenv/sql-migrate/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (C) 2014-2017 by Ruben Vermeersch <ruben@rocketeer.be>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/rubenv/sql-migrate/README.md b/vendor/github.com/rubenv/sql-migrate/README.md
new file mode 100644
index 0000000..9ac432e
--- /dev/null
+++ b/vendor/github.com/rubenv/sql-migrate/README.md
@@ -0,0 +1,289 @@
+# sql-migrate
+
+> SQL Schema migration tool for [Go](http://golang.org/). Based on [gorp](https://github.com/go-gorp/gorp) and [goose](https://bitbucket.org/liamstask/goose).
+
+[![Build Status](https://travis-ci.org/rubenv/sql-migrate.svg?branch=master)](https://travis-ci.org/rubenv/sql-migrate) [![GoDoc](https://godoc.org/github.com/rubenv/sql-migrate?status.png)](https://godoc.org/github.com/rubenv/sql-migrate)
+
+Using [modl](https://github.com/jmoiron/modl)? Check out [modl-migrate](https://github.com/rubenv/modl-migrate).
+
+## Features
+
+* Usable as a CLI tool or as a library
+* Supports SQLite, PostgreSQL, MySQL, MSSQL and Oracle databases (through [gorp](https://github.com/go-gorp/gorp))
+* Can embed migrations into your application
+* Migrations are defined with SQL for full flexibility
+* Atomic migrations
+* Up/down migrations to allow rollback
+* Supports multiple database types in one project
+
+## Installation
+
+To install the library and command line program, use the following:
+
+```bash
+go get -v github.com/rubenv/sql-migrate/...
+```
+
+## Usage
+
+### As a standalone tool
+
+```
+$ sql-migrate --help
+usage: sql-migrate [--version] [--help] <command> [<args>]
+
+Available commands are:
+ down Undo a database migration
+ new Create a new migration
+ redo Reapply the last migration
+ status Show migration status
+ up Migrates the database to the most recent version available
+```
+
+Each command requires a configuration file (which defaults to `dbconfig.yml`, but can be specified with the `-config` flag). This config file should specify one or more environments:
+
+```yml
+development:
+ dialect: sqlite3
+ datasource: test.db
+ dir: migrations/sqlite3
+
+production:
+ dialect: postgres
+ datasource: dbname=myapp sslmode=disable
+ dir: migrations/postgres
+ table: migrations
+```
+
+The `table` setting is optional and will default to `gorp_migrations`.
+
+The environment that will be used can be specified with the `-env` flag (defaults to `development`).
+
+Use the `--help` flag in combination with any of the commands to get an overview of its usage:
+
+```
+$ sql-migrate up --help
+Usage: sql-migrate up [options] ...
+
+ Migrates the database to the most recent version available.
+
+Options:
+
+ -config=config.yml Configuration file to use.
+ -env="development" Environment.
+ -limit=0 Limit the number of migrations (0 = unlimited).
+ -dryrun Don't apply migrations, just print them.
+```
+
+The `new` command creates a new empty migration template using the following pattern `<current time>-<name>.sql`.
+
+The `up` command applies all available migrations. By contrast, `down` will only apply one migration by default. This behavior can be changed for both by using the `-limit` parameter.
+
+The `redo` command will unapply the last migration and reapply it. This is useful during development, when you're writing migrations.
+
+Use the `status` command to see the state of the applied migrations:
+
+```bash
+$ sql-migrate status
++---------------+-----------------------------------------+
+| MIGRATION | APPLIED |
++---------------+-----------------------------------------+
+| 1_initial.sql | 2014-09-13 08:19:06.788354925 +0000 UTC |
+| 2_record.sql | no |
++---------------+-----------------------------------------+
+```
+
+### MySQL Caveat
+
+If you are using MySQL, you must append `?parseTime=true` to the `datasource` configuration. For example:
+
+```yml
+production:
+ dialect: mysql
+ datasource: root@/dbname?parseTime=true
+ dir: migrations/mysql
+ table: migrations
+```
+
+See [here](https://github.com/go-sql-driver/mysql#parsetime) for more information.
+
+### As a library
+
+Import sql-migrate into your application:
+
+```go
+import "github.com/rubenv/sql-migrate"
+```
+
+Set up a source of migrations, this can be from memory, from a set of files or from bindata (more on that later):
+
+```go
+// Hardcoded strings in memory:
+migrations := &migrate.MemoryMigrationSource{
+ Migrations: []*migrate.Migration{
+ &migrate.Migration{
+ Id: "123",
+ Up: []string{"CREATE TABLE people (id int)"},
+ Down: []string{"DROP TABLE people"},
+ },
+ },
+}
+
+// OR: Read migrations from a folder:
+migrations := &migrate.FileMigrationSource{
+ Dir: "db/migrations",
+}
+
+// OR: Use migrations from a packr box
+migrations := &migrate.PackrMigrationSource{
+ Box: packr.NewBox("./migrations"),
+}
+
+// OR: Use migrations from bindata:
+migrations := &migrate.AssetMigrationSource{
+ Asset: Asset,
+ AssetDir: AssetDir,
+ Dir: "migrations",
+}
+```
+
+Then use the `Exec` function to upgrade your database:
+
+```go
+db, err := sql.Open("sqlite3", filename)
+if err != nil {
+ // Handle errors!
+}
+
+n, err := migrate.Exec(db, "sqlite3", migrations, migrate.Up)
+if err != nil {
+ // Handle errors!
+}
+fmt.Printf("Applied %d migrations!\n", n)
+```
+
+Note that `n` can be greater than `0` even if there is an error: any migration that succeeded will remain applied even if a later one fails.
+
+Check [the GoDoc reference](https://godoc.org/github.com/rubenv/sql-migrate) for the full documentation.
+
+## Writing migrations
+Migrations are defined in SQL files, which contain a set of SQL statements. Special comments are used to distinguish up and down migrations.
+
+```sql
+-- +migrate Up
+-- SQL in section 'Up' is executed when this migration is applied
+CREATE TABLE people (id int);
+
+
+-- +migrate Down
+-- SQL section 'Down' is executed when this migration is rolled back
+DROP TABLE people;
+```
+
+You can put multiple statements in each block, as long as you end them with a semicolon (`;`).
+
+You can alternatively set up a separator string that matches an entire line by setting `sqlparse.LineSeparator`. This
+can be used to imitate, for example, MS SQL Query Analyzer functionality where commands can be separated by a line with
+contents of `GO`. If `sqlparse.LineSeparator` is matched, it will not be included in the resulting migration scripts.
+
+If you have complex statements which contain semicolons, use `StatementBegin` and `StatementEnd` to indicate boundaries:
+
+```sql
+-- +migrate Up
+CREATE TABLE people (id int);
+
+-- +migrate StatementBegin
+CREATE OR REPLACE FUNCTION do_something()
+returns void AS $$
+DECLARE
+ create_query text;
+BEGIN
+ -- Do something here
+END;
+$$
+language plpgsql;
+-- +migrate StatementEnd
+
+-- +migrate Down
+DROP FUNCTION do_something();
+DROP TABLE people;
+```
+
+The order in which migrations are applied is defined through the filename: sql-migrate will sort migrations based on their name. It's recommended to use an increasing version number or a timestamp as the first part of the filename.
+
+Normally each migration is run within a transaction in order to guarantee that it is fully atomic. However some SQL commands (for example creating an index concurrently in PostgreSQL) cannot be executed inside a transaction. In order to execute such a command in a migration, the migration can be run using the `notransaction` option:
+
+```sql
+-- +migrate Up notransaction
+CREATE UNIQUE INDEX people_unique_id_idx CONCURRENTLY ON people (id);
+
+-- +migrate Down
+DROP INDEX people_unique_id_idx;
+```
+
+## Embedding migrations with [packr](https://github.com/gobuffalo/packr)
+
+If you like your Go applications self-contained (that is: a single binary): use [packr](https://github.com/gobuffalo/packr) to embed the migration files.
+
+Just write your migration files as usual, as a set of SQL files in a folder.
+
+Use the `PackrMigrationSource` in your application to find the migrations:
+
+```go
+migrations := &migrate.PackrMigrationSource{
+ Box: packr.NewBox("./migrations"),
+}
+```
+
+If you already have a box and would like to use a subdirectory:
+
+```go
+migrations := &migrate.PackrMigrationSource{
+ Box: myBox,
+ Dir: "./migrations",
+}
+```
+
+## Embedding migrations with [bindata](https://github.com/shuLhan/go-bindata)
+
+As an alternative, but slightly less maintained, you can use [bindata](https://github.com/shuLhan/go-bindata) to embed the migration files.
+
+Just write your migration files as usual, as a set of SQL files in a folder.
+
+Then use bindata to generate a `.go` file with the migrations embedded:
+
+```bash
+go-bindata -pkg myapp -o bindata.go db/migrations/
+```
+
+The resulting `bindata.go` file will contain your migrations. Remember to regenerate your `bindata.go` file whenever you add/modify a migration (`go generate` will help here, once it arrives).
+
+Use the `AssetMigrationSource` in your application to find the migrations:
+
+```go
+migrations := &migrate.AssetMigrationSource{
+ Asset: Asset,
+ AssetDir: AssetDir,
+ Dir: "db/migrations",
+}
+```
+
+Both `Asset` and `AssetDir` are functions provided by bindata.
+
+Then proceed as usual.
+
+## Extending
+
+Adding a new migration source means implementing `MigrationSource`.
+
+```go
+type MigrationSource interface {
+ FindMigrations() ([]*Migration, error)
+}
+```
+
+The resulting slice of migrations will be executed in the given order, so it should usually be sorted by the `Id` field.
+
+## License
+
+This library is distributed under the [MIT](LICENSE) license.
diff --git a/vendor/github.com/rubenv/sql-migrate/doc.go b/vendor/github.com/rubenv/sql-migrate/doc.go
new file mode 100644
index 0000000..eb4ed85
--- /dev/null
+++ b/vendor/github.com/rubenv/sql-migrate/doc.go
@@ -0,0 +1,239 @@
+/*
+
+SQL Schema migration tool for Go.
+
+Key features:
+
+ * Usable as a CLI tool or as a library
+ * Supports SQLite, PostgreSQL, MySQL, MSSQL and Oracle databases (through gorp)
+ * Can embed migrations into your application
+ * Migrations are defined with SQL for full flexibility
+ * Atomic migrations
+ * Up/down migrations to allow rollback
+ * Supports multiple database types in one project
+
+Installation
+
+To install the library and command line program, use the following:
+
+ go get -v github.com/rubenv/sql-migrate/...
+
+Command-line tool
+
+The main command is called sql-migrate.
+
+ $ sql-migrate --help
+ usage: sql-migrate [--version] [--help] <command> [<args>]
+
+ Available commands are:
+ down Undo a database migration
+ new Create a new migration
+ redo Reapply the last migration
+ status Show migration status
+ up Migrates the database to the most recent version available
+
+Each command requires a configuration file (which defaults to dbconfig.yml, but can be specified with the -config flag). This config file should specify one or more environments:
+
+ development:
+ dialect: sqlite3
+ datasource: test.db
+ dir: migrations/sqlite3
+
+ production:
+ dialect: postgres
+ datasource: dbname=myapp sslmode=disable
+ dir: migrations/postgres
+ table: migrations
+
+The `table` setting is optional and will default to `gorp_migrations`.
+
+The environment that will be used can be specified with the -env flag (defaults to development).
+
+Use the --help flag in combination with any of the commands to get an overview of its usage:
+
+ $ sql-migrate up --help
+ Usage: sql-migrate up [options] ...
+
+ Migrates the database to the most recent version available.
+
+ Options:
+
+ -config=config.yml Configuration file to use.
+ -env="development" Environment.
+ -limit=0 Limit the number of migrations (0 = unlimited).
+ -dryrun Don't apply migrations, just print them.
+
+The up command applies all available migrations. By contrast, down will only apply one migration by default. This behavior can be changed for both by using the -limit parameter.
+
+The redo command will unapply the last migration and reapply it. This is useful during development, when you're writing migrations.
+
+Use the status command to see the state of the applied migrations:
+
+ $ sql-migrate status
+ +---------------+-----------------------------------------+
+ | MIGRATION | APPLIED |
+ +---------------+-----------------------------------------+
+ | 1_initial.sql | 2014-09-13 08:19:06.788354925 +0000 UTC |
+ | 2_record.sql | no |
+ +---------------+-----------------------------------------+
+
+MySQL Caveat
+
+If you are using MySQL, you must append ?parseTime=true to the datasource configuration. For example:
+
+ production:
+ dialect: mysql
+ datasource: root@/dbname?parseTime=true
+ dir: migrations/mysql
+ table: migrations
+
+See https://github.com/go-sql-driver/mysql#parsetime for more information.
+
+Library
+
+Import sql-migrate into your application:
+
+ import "github.com/rubenv/sql-migrate"
+
+Set up a source of migrations, this can be from memory, from a set of files or from bindata (more on that later):
+
+ // Hardcoded strings in memory:
+ migrations := &migrate.MemoryMigrationSource{
+ Migrations: []*migrate.Migration{
+ &migrate.Migration{
+ Id: "123",
+ Up: []string{"CREATE TABLE people (id int)"},
+ Down: []string{"DROP TABLE people"},
+ },
+ },
+ }
+
+ // OR: Read migrations from a folder:
+ migrations := &migrate.FileMigrationSource{
+ Dir: "db/migrations",
+ }
+
+ // OR: Use migrations from bindata:
+ migrations := &migrate.AssetMigrationSource{
+ Asset: Asset,
+ AssetDir: AssetDir,
+ Dir: "migrations",
+ }
+
+Then use the Exec function to upgrade your database:
+
+ db, err := sql.Open("sqlite3", filename)
+ if err != nil {
+ // Handle errors!
+ }
+
+ n, err := migrate.Exec(db, "sqlite3", migrations, migrate.Up)
+ if err != nil {
+ // Handle errors!
+ }
+ fmt.Printf("Applied %d migrations!\n", n)
+
+Note that n can be greater than 0 even if there is an error: any migration that succeeded will remain applied even if a later one fails.
+
+The full set of capabilities can be found in the API docs below.
+
+Writing migrations
+
+Migrations are defined in SQL files, which contain a set of SQL statements. Special comments are used to distinguish up and down migrations.
+
+ -- +migrate Up
+ -- SQL in section 'Up' is executed when this migration is applied
+ CREATE TABLE people (id int);
+
+
+ -- +migrate Down
+ -- SQL section 'Down' is executed when this migration is rolled back
+ DROP TABLE people;
+
+You can put multiple statements in each block, as long as you end them with a semicolon (;).
+
+If you have complex statements which contain semicolons, use StatementBegin and StatementEnd to indicate boundaries:
+
+ -- +migrate Up
+ CREATE TABLE people (id int);
+
+ -- +migrate StatementBegin
+ CREATE OR REPLACE FUNCTION do_something()
+ returns void AS $$
+ DECLARE
+ create_query text;
+ BEGIN
+ -- Do something here
+ END;
+ $$
+ language plpgsql;
+ -- +migrate StatementEnd
+
+ -- +migrate Down
+ DROP FUNCTION do_something();
+ DROP TABLE people;
+
+The order in which migrations are applied is defined through the filename: sql-migrate will sort migrations based on their name. It's recommended to use an increasing version number or a timestamp as the first part of the filename.
+
+Normally each migration is run within a transaction in order to guarantee that it is fully atomic. However some SQL commands (for example creating an index concurrently in PostgreSQL) cannot be executed inside a transaction. In order to execute such a command in a migration, the migration can be run using the notransaction option:
+
+ -- +migrate Up notransaction
+ CREATE UNIQUE INDEX people_unique_id_idx CONCURRENTLY ON people (id);
+
+ -- +migrate Down
+ DROP INDEX people_unique_id_idx;
+
+Embedding migrations with packr
+
+If you like your Go applications self-contained (that is: a single binary): use packr (https://github.com/gobuffalo/packr) to embed the migration files.
+
+Just write your migration files as usual, as a set of SQL files in a folder.
+
+Use the PackrMigrationSource in your application to find the migrations:
+
+ migrations := &migrate.PackrMigrationSource{
+ Box: packr.NewBox("./migrations"),
+ }
+
+If you already have a box and would like to use a subdirectory:
+
+ migrations := &migrate.PackrMigrationSource{
+ Box: myBox,
+ Dir: "./migrations",
+ }
+
+Embedding migrations with bindata
+
+As an alternative, but slightly less maintained, you can use bindata (https://github.com/shuLhan/go-bindata) to embed the migration files.
+
+Just write your migration files as usual, as a set of SQL files in a folder.
+
+Then use bindata to generate a .go file with the migrations embedded:
+
+ go-bindata -pkg myapp -o bindata.go db/migrations/
+
+The resulting bindata.go file will contain your migrations. Remember to regenerate your bindata.go file whenever you add/modify a migration (go generate will help here, once it arrives).
+
+Use the AssetMigrationSource in your application to find the migrations:
+
+ migrations := &migrate.AssetMigrationSource{
+ Asset: Asset,
+ AssetDir: AssetDir,
+ Dir: "db/migrations",
+ }
+
+Both Asset and AssetDir are functions provided by bindata.
+
+Then proceed as usual.
+
+Extending
+
+Adding a new migration source means implementing MigrationSource.
+
+ type MigrationSource interface {
+ FindMigrations() ([]*Migration, error)
+ }
+
+The resulting slice of migrations will be executed in the given order, so it should usually be sorted by the Id field.
+*/
+package migrate
diff --git a/vendor/github.com/rubenv/sql-migrate/migrate.go b/vendor/github.com/rubenv/sql-migrate/migrate.go
new file mode 100644
index 0000000..02f59f7
--- /dev/null
+++ b/vendor/github.com/rubenv/sql-migrate/migrate.go
@@ -0,0 +1,674 @@
+package migrate
+
+import (
+ "bytes"
+ "database/sql"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "path"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/rubenv/sql-migrate/sqlparse"
+ "gopkg.in/gorp.v1"
+)
+
+type MigrationDirection int
+
+const (
+ Up MigrationDirection = iota
+ Down
+)
+
+var tableName = "gorp_migrations"
+var schemaName = ""
+var numberPrefixRegex = regexp.MustCompile(`^(\d+).*$`)
+
+// PlanError happens where no migration plan could be created between the sets
+// of already applied migrations and the currently found. For example, when the database
+// contains a migration which is not among the migrations list found for an operation.
+type PlanError struct {
+ Migration *Migration
+ ErrorMessag string
+}
+
+func newPlanError(migration *Migration, errorMessage string) error {
+ return &PlanError{
+ Migration: migration,
+ ErrorMessag: errorMessage,
+ }
+}
+
+func (p *PlanError) Error() string {
+ return fmt.Sprintf("Unable to create migration plan because of %s: %s",
+ p.Migration.Id, p.ErrorMessag)
+}
+
+// TxError is returned when any error is encountered during a database
+// transaction. It contains the relevant *Migration and notes it's Id in the
+// Error function output.
+type TxError struct {
+ Migration *Migration
+ Err error
+}
+
+func newTxError(migration *PlannedMigration, err error) error {
+ return &TxError{
+ Migration: migration.Migration,
+ Err: err,
+ }
+}
+
+func (e *TxError) Error() string {
+ return e.Err.Error() + " handling " + e.Migration.Id
+}
+
+// Set the name of the table used to store migration info.
+//
+// Should be called before any other call such as (Exec, ExecMax, ...).
+func SetTable(name string) {
+ if name != "" {
+ tableName = name
+ }
+}
+
+// SetSchema sets the name of a schema that the migration table be referenced.
+func SetSchema(name string) {
+ if name != "" {
+ schemaName = name
+ }
+}
+
+type Migration struct {
+ Id string
+ Up []string
+ Down []string
+
+ DisableTransactionUp bool
+ DisableTransactionDown bool
+}
+
+func (m Migration) Less(other *Migration) bool {
+ switch {
+ case m.isNumeric() && other.isNumeric() && m.VersionInt() != other.VersionInt():
+ return m.VersionInt() < other.VersionInt()
+ case m.isNumeric() && !other.isNumeric():
+ return true
+ case !m.isNumeric() && other.isNumeric():
+ return false
+ default:
+ return m.Id < other.Id
+ }
+}
+
+func (m Migration) isNumeric() bool {
+ return len(m.NumberPrefixMatches()) > 0
+}
+
+func (m Migration) NumberPrefixMatches() []string {
+ return numberPrefixRegex.FindStringSubmatch(m.Id)
+}
+
+func (m Migration) VersionInt() int64 {
+ v := m.NumberPrefixMatches()[1]
+ value, err := strconv.ParseInt(v, 10, 64)
+ if err != nil {
+ panic(fmt.Sprintf("Could not parse %q into int64: %s", v, err))
+ }
+ return value
+}
+
+type PlannedMigration struct {
+ *Migration
+
+ DisableTransaction bool
+ Queries []string
+}
+
+type byId []*Migration
+
+func (b byId) Len() int { return len(b) }
+func (b byId) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
+func (b byId) Less(i, j int) bool { return b[i].Less(b[j]) }
+
+type MigrationRecord struct {
+ Id string `db:"id"`
+ AppliedAt time.Time `db:"applied_at"`
+}
+
+var MigrationDialects = map[string]gorp.Dialect{
+ "sqlite3": gorp.SqliteDialect{},
+ "postgres": gorp.PostgresDialect{},
+ "mysql": gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"},
+ "mssql": gorp.SqlServerDialect{},
+ "oci8": gorp.OracleDialect{},
+}
+
+type MigrationSource interface {
+ // Finds the migrations.
+ //
+ // The resulting slice of migrations should be sorted by Id.
+ FindMigrations() ([]*Migration, error)
+}
+
+// A hardcoded set of migrations, in-memory.
+type MemoryMigrationSource struct {
+ Migrations []*Migration
+}
+
+var _ MigrationSource = (*MemoryMigrationSource)(nil)
+
+func (m MemoryMigrationSource) FindMigrations() ([]*Migration, error) {
+ // Make sure migrations are sorted. In order to make the MemoryMigrationSource safe for
+ // concurrent use we should not mutate it in place. So `FindMigrations` would sort a copy
+ // of the m.Migrations.
+ migrations := make([]*Migration, len(m.Migrations))
+ copy(migrations, m.Migrations)
+ sort.Sort(byId(migrations))
+ return migrations, nil
+}
+
+// A set of migrations loaded from an http.FileServer
+
+type HttpFileSystemMigrationSource struct {
+ FileSystem http.FileSystem
+}
+
+var _ MigrationSource = (*HttpFileSystemMigrationSource)(nil)
+
+func (f HttpFileSystemMigrationSource) FindMigrations() ([]*Migration, error) {
+ return findMigrations(f.FileSystem)
+}
+
+// A set of migrations loaded from a directory.
+type FileMigrationSource struct {
+ Dir string
+}
+
+var _ MigrationSource = (*FileMigrationSource)(nil)
+
+func (f FileMigrationSource) FindMigrations() ([]*Migration, error) {
+ filesystem := http.Dir(f.Dir)
+ return findMigrations(filesystem)
+}
+
+func findMigrations(dir http.FileSystem) ([]*Migration, error) {
+ migrations := make([]*Migration, 0)
+
+ file, err := dir.Open("/")
+ if err != nil {
+ return nil, err
+ }
+
+ files, err := file.Readdir(0)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, info := range files {
+ if strings.HasSuffix(info.Name(), ".sql") {
+ file, err := dir.Open(info.Name())
+ if err != nil {
+ return nil, fmt.Errorf("Error while opening %s: %s", info.Name(), err)
+ }
+
+ migration, err := ParseMigration(info.Name(), file)
+ if err != nil {
+ return nil, fmt.Errorf("Error while parsing %s: %s", info.Name(), err)
+ }
+
+ migrations = append(migrations, migration)
+ }
+ }
+
+ // Make sure migrations are sorted
+ sort.Sort(byId(migrations))
+
+ return migrations, nil
+}
+
+// Migrations from a bindata asset set.
+type AssetMigrationSource struct {
+ // Asset should return content of file in path if exists
+ Asset func(path string) ([]byte, error)
+
+ // AssetDir should return list of files in the path
+ AssetDir func(path string) ([]string, error)
+
+ // Path in the bindata to use.
+ Dir string
+}
+
+var _ MigrationSource = (*AssetMigrationSource)(nil)
+
+func (a AssetMigrationSource) FindMigrations() ([]*Migration, error) {
+ migrations := make([]*Migration, 0)
+
+ files, err := a.AssetDir(a.Dir)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, name := range files {
+ if strings.HasSuffix(name, ".sql") {
+ file, err := a.Asset(path.Join(a.Dir, name))
+ if err != nil {
+ return nil, err
+ }
+
+ migration, err := ParseMigration(name, bytes.NewReader(file))
+ if err != nil {
+ return nil, err
+ }
+
+ migrations = append(migrations, migration)
+ }
+ }
+
+ // Make sure migrations are sorted
+ sort.Sort(byId(migrations))
+
+ return migrations, nil
+}
+
+// Avoids pulling in the packr library for everyone, mimicks the bits of
+// packr.Box that we need.
+type PackrBox interface {
+ List() []string
+ Bytes(name string) []byte
+}
+
+// Migrations from a packr box.
+type PackrMigrationSource struct {
+ Box PackrBox
+
+ // Path in the box to use.
+ Dir string
+}
+
+var _ MigrationSource = (*PackrMigrationSource)(nil)
+
+func (p PackrMigrationSource) FindMigrations() ([]*Migration, error) {
+ migrations := make([]*Migration, 0)
+ items := p.Box.List()
+
+ prefix := ""
+ dir := path.Clean(p.Dir)
+ if dir != "." {
+ prefix = fmt.Sprintf("%s/", dir)
+ }
+
+ for _, item := range items {
+ if !strings.HasPrefix(item, prefix) {
+ continue
+ }
+ name := strings.TrimPrefix(item, prefix)
+ if strings.Contains(name, "/") {
+ continue
+ }
+
+ if strings.HasSuffix(name, ".sql") {
+ file := p.Box.Bytes(item)
+
+ migration, err := ParseMigration(name, bytes.NewReader(file))
+ if err != nil {
+ return nil, err
+ }
+
+ migrations = append(migrations, migration)
+ }
+ }
+
+ // Make sure migrations are sorted
+ sort.Sort(byId(migrations))
+
+ return migrations, nil
+}
+
+// Migration parsing
+func ParseMigration(id string, r io.ReadSeeker) (*Migration, error) {
+ m := &Migration{
+ Id: id,
+ }
+
+ parsed, err := sqlparse.ParseMigration(r)
+ if err != nil {
+ return nil, fmt.Errorf("Error parsing migration (%s): %s", id, err)
+ }
+
+ m.Up = parsed.UpStatements
+ m.Down = parsed.DownStatements
+
+ m.DisableTransactionUp = parsed.DisableTransactionUp
+ m.DisableTransactionDown = parsed.DisableTransactionDown
+
+ return m, nil
+}
+
+type SqlExecutor interface {
+ Exec(query string, args ...interface{}) (sql.Result, error)
+ Insert(list ...interface{}) error
+ Delete(list ...interface{}) (int64, error)
+}
+
+// Execute a set of migrations
+//
+// Returns the number of applied migrations.
+func Exec(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) {
+ return ExecMax(db, dialect, m, dir, 0)
+}
+
+// Execute a set of migrations
+//
+// Will apply at most `max` migrations. Pass 0 for no limit (or use Exec).
+//
+// Returns the number of applied migrations.
+func ExecMax(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) (int, error) {
+ migrations, dbMap, err := PlanMigration(db, dialect, m, dir, max)
+ if err != nil {
+ return 0, err
+ }
+
+ // Apply migrations
+ applied := 0
+ for _, migration := range migrations {
+ var executor SqlExecutor
+
+ if migration.DisableTransaction {
+ executor = dbMap
+ } else {
+ executor, err = dbMap.Begin()
+ if err != nil {
+ return applied, newTxError(migration, err)
+ }
+ }
+
+ for _, stmt := range migration.Queries {
+ if _, err := executor.Exec(stmt); err != nil {
+ if trans, ok := executor.(*gorp.Transaction); ok {
+ trans.Rollback()
+ }
+
+ return applied, newTxError(migration, err)
+ }
+ }
+
+ switch dir {
+ case Up:
+ err = executor.Insert(&MigrationRecord{
+ Id: migration.Id,
+ AppliedAt: time.Now(),
+ })
+ if err != nil {
+ if trans, ok := executor.(*gorp.Transaction); ok {
+ trans.Rollback()
+ }
+
+ return applied, newTxError(migration, err)
+ }
+ case Down:
+ _, err := executor.Delete(&MigrationRecord{
+ Id: migration.Id,
+ })
+ if err != nil {
+ if trans, ok := executor.(*gorp.Transaction); ok {
+ trans.Rollback()
+ }
+
+ return applied, newTxError(migration, err)
+ }
+ default:
+ panic("Not possible")
+ }
+
+ if trans, ok := executor.(*gorp.Transaction); ok {
+ if err := trans.Commit(); err != nil {
+ return applied, newTxError(migration, err)
+ }
+ }
+
+ applied++
+ }
+
+ return applied, nil
+}
+
+// Plan a migration.
+func PlanMigration(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) ([]*PlannedMigration, *gorp.DbMap, error) {
+ dbMap, err := getMigrationDbMap(db, dialect)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ migrations, err := m.FindMigrations()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var migrationRecords []MigrationRecord
+ _, err = dbMap.Select(&migrationRecords, fmt.Sprintf("SELECT * FROM %s", dbMap.Dialect.QuotedTableForQuery(schemaName, tableName)))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Sort migrations that have been run by Id.
+ var existingMigrations []*Migration
+ for _, migrationRecord := range migrationRecords {
+ existingMigrations = append(existingMigrations, &Migration{
+ Id: migrationRecord.Id,
+ })
+ }
+ sort.Sort(byId(existingMigrations))
+
+ // Make sure all migrations in the database are among the found migrations which
+ // are to be applied.
+ migrationsSearch := make(map[string]struct{})
+ for _, migration := range migrations {
+ migrationsSearch[migration.Id] = struct{}{}
+ }
+ for _, existingMigration := range existingMigrations {
+ if _, ok := migrationsSearch[existingMigration.Id]; !ok {
+ return nil, nil, newPlanError(existingMigration, "unknown migration in database")
+ }
+ }
+
+ // Get last migration that was run
+ record := &Migration{}
+ if len(existingMigrations) > 0 {
+ record = existingMigrations[len(existingMigrations)-1]
+ }
+
+ result := make([]*PlannedMigration, 0)
+
+ // Add missing migrations up to the last run migration.
+ // This can happen for example when merges happened.
+ if len(existingMigrations) > 0 {
+ result = append(result, ToCatchup(migrations, existingMigrations, record)...)
+ }
+
+ // Figure out which migrations to apply
+ toApply := ToApply(migrations, record.Id, dir)
+ toApplyCount := len(toApply)
+ if max > 0 && max < toApplyCount {
+ toApplyCount = max
+ }
+ for _, v := range toApply[0:toApplyCount] {
+
+ if dir == Up {
+ result = append(result, &PlannedMigration{
+ Migration: v,
+ Queries: v.Up,
+ DisableTransaction: v.DisableTransactionUp,
+ })
+ } else if dir == Down {
+ result = append(result, &PlannedMigration{
+ Migration: v,
+ Queries: v.Down,
+ DisableTransaction: v.DisableTransactionDown,
+ })
+ }
+ }
+
+ return result, dbMap, nil
+}
+
+// Skip a set of migrations
+//
+// Will skip at most `max` migrations. Pass 0 for no limit.
+//
+// Returns the number of skipped migrations.
+func SkipMax(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) (int, error) {
+ migrations, dbMap, err := PlanMigration(db, dialect, m, dir, max)
+ if err != nil {
+ return 0, err
+ }
+
+ // Skip migrations
+ applied := 0
+ for _, migration := range migrations {
+ var executor SqlExecutor
+
+ if migration.DisableTransaction {
+ executor = dbMap
+ } else {
+ executor, err = dbMap.Begin()
+ if err != nil {
+ return applied, newTxError(migration, err)
+ }
+ }
+
+ err = executor.Insert(&MigrationRecord{
+ Id: migration.Id,
+ AppliedAt: time.Now(),
+ })
+ if err != nil {
+ if trans, ok := executor.(*gorp.Transaction); ok {
+ trans.Rollback()
+ }
+
+ return applied, newTxError(migration, err)
+ }
+
+ if trans, ok := executor.(*gorp.Transaction); ok {
+ if err := trans.Commit(); err != nil {
+ return applied, newTxError(migration, err)
+ }
+ }
+
+ applied++
+ }
+
+ return applied, nil
+}
+
+// Filter a slice of migrations into ones that should be applied.
+func ToApply(migrations []*Migration, current string, direction MigrationDirection) []*Migration {
+ var index = -1
+ if current != "" {
+ for index < len(migrations)-1 {
+ index++
+ if migrations[index].Id == current {
+ break
+ }
+ }
+ }
+
+ if direction == Up {
+ return migrations[index+1:]
+ } else if direction == Down {
+ if index == -1 {
+ return []*Migration{}
+ }
+
+ // Add in reverse order
+ toApply := make([]*Migration, index+1)
+ for i := 0; i < index+1; i++ {
+ toApply[index-i] = migrations[i]
+ }
+ return toApply
+ }
+
+ panic("Not possible")
+}
+
+func ToCatchup(migrations, existingMigrations []*Migration, lastRun *Migration) []*PlannedMigration {
+ missing := make([]*PlannedMigration, 0)
+ for _, migration := range migrations {
+ found := false
+ for _, existing := range existingMigrations {
+ if existing.Id == migration.Id {
+ found = true
+ break
+ }
+ }
+ if !found && migration.Less(lastRun) {
+ missing = append(missing, &PlannedMigration{
+ Migration: migration,
+ Queries: migration.Up,
+ DisableTransaction: migration.DisableTransactionUp,
+ })
+ }
+ }
+ return missing
+}
+
+func GetMigrationRecords(db *sql.DB, dialect string) ([]*MigrationRecord, error) {
+ dbMap, err := getMigrationDbMap(db, dialect)
+ if err != nil {
+ return nil, err
+ }
+
+ var records []*MigrationRecord
+ query := fmt.Sprintf("SELECT * FROM %s ORDER BY id ASC", dbMap.Dialect.QuotedTableForQuery(schemaName, tableName))
+ _, err = dbMap.Select(&records, query)
+ if err != nil {
+ return nil, err
+ }
+
+ return records, nil
+}
+
+func getMigrationDbMap(db *sql.DB, dialect string) (*gorp.DbMap, error) {
+ d, ok := MigrationDialects[dialect]
+ if !ok {
+ return nil, fmt.Errorf("Unknown dialect: %s", dialect)
+ }
+
+ // When using the mysql driver, make sure that the parseTime option is
+ // configured, otherwise it won't map time columns to time.Time. See
+ // https://github.com/rubenv/sql-migrate/issues/2
+ if dialect == "mysql" {
+ var out *time.Time
+ err := db.QueryRow("SELECT NOW()").Scan(&out)
+ if err != nil {
+ if err.Error() == "sql: Scan error on column index 0: unsupported driver -> Scan pair: []uint8 -> *time.Time" ||
+ err.Error() == "sql: Scan error on column index 0: unsupported Scan, storing driver.Value type []uint8 into type *time.Time" {
+ return nil, errors.New(`Cannot parse dates.
+
+Make sure that the parseTime option is supplied to your database connection.
+Check https://github.com/go-sql-driver/mysql#parsetime for more info.`)
+ } else {
+ return nil, err
+ }
+ }
+ }
+
+ // Create migration database map
+ dbMap := &gorp.DbMap{Db: db, Dialect: d}
+ dbMap.AddTableWithNameAndSchema(MigrationRecord{}, schemaName, tableName).SetKeys(false, "Id")
+ //dbMap.TraceOn("", log.New(os.Stdout, "migrate: ", log.Lmicroseconds))
+
+ err := dbMap.CreateTablesIfNotExists()
+ if err != nil {
+ return nil, err
+ }
+
+ return dbMap, nil
+}
+
+// TODO: Run migration + record insert in transaction.
diff --git a/vendor/github.com/rubenv/sql-migrate/sqlparse/LICENSE b/vendor/github.com/rubenv/sql-migrate/sqlparse/LICENSE
new file mode 100644
index 0000000..9c12525
--- /dev/null
+++ b/vendor/github.com/rubenv/sql-migrate/sqlparse/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (C) 2014-2017 by Ruben Vermeersch <ruben@rocketeer.be>
+Copyright (C) 2012-2014 by Liam Staskawicz
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/rubenv/sql-migrate/sqlparse/README.md b/vendor/github.com/rubenv/sql-migrate/sqlparse/README.md
new file mode 100644
index 0000000..fa5341a
--- /dev/null
+++ b/vendor/github.com/rubenv/sql-migrate/sqlparse/README.md
@@ -0,0 +1,7 @@
+# SQL migration parser
+
+Based on the [goose](https://bitbucket.org/liamstask/goose) migration parser.
+
+## License
+
+This library is distributed under the [MIT](LICENSE) license.
diff --git a/vendor/github.com/rubenv/sql-migrate/sqlparse/sqlparse.go b/vendor/github.com/rubenv/sql-migrate/sqlparse/sqlparse.go
new file mode 100644
index 0000000..d336e77
--- /dev/null
+++ b/vendor/github.com/rubenv/sql-migrate/sqlparse/sqlparse.go
@@ -0,0 +1,235 @@
+package sqlparse
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+
+ "strings"
+)
+
+const (
+ sqlCmdPrefix = "-- +migrate "
+ optionNoTransaction = "notransaction"
+)
+
+type ParsedMigration struct {
+ UpStatements []string
+ DownStatements []string
+
+ DisableTransactionUp bool
+ DisableTransactionDown bool
+}
+
+var (
+ // LineSeparator can be used to split migrations by an exact line match. This line
+ // will be removed from the output. If left blank, it is not considered. It is defaulted
+ // to blank so you will have to set it manually.
+ // Use case: in MSSQL, it is convenient to separate commands by GO statements like in
+ // SQL Query Analyzer.
+ LineSeparator = ""
+)
+
+func errNoTerminator() error {
+ if len(LineSeparator) == 0 {
+ return errors.New(`ERROR: The last statement must be ended by a semicolon or '-- +migrate StatementEnd' marker.
+ See https://github.com/rubenv/sql-migrate for details.`)
+ }
+
+ return errors.New(fmt.Sprintf(`ERROR: The last statement must be ended by a semicolon, a line whose contents are %q, or '-- +migrate StatementEnd' marker.
+ See https://github.com/rubenv/sql-migrate for details.`, LineSeparator))
+}
+
+// Checks the line to see if the line has a statement-ending semicolon
+// or if the line contains a double-dash comment.
+func endsWithSemicolon(line string) bool {
+
+ prev := ""
+ scanner := bufio.NewScanner(strings.NewReader(line))
+ scanner.Split(bufio.ScanWords)
+
+ for scanner.Scan() {
+ word := scanner.Text()
+ if strings.HasPrefix(word, "--") {
+ break
+ }
+ prev = word
+ }
+
+ return strings.HasSuffix(prev, ";")
+}
+
+type migrationDirection int
+
+const (
+ directionNone migrationDirection = iota
+ directionUp
+ directionDown
+)
+
+type migrateCommand struct {
+ Command string
+ Options []string
+}
+
+func (c *migrateCommand) HasOption(opt string) bool {
+ for _, specifiedOption := range c.Options {
+ if specifiedOption == opt {
+ return true
+ }
+ }
+
+ return false
+}
+
+func parseCommand(line string) (*migrateCommand, error) {
+ cmd := &migrateCommand{}
+
+ if !strings.HasPrefix(line, sqlCmdPrefix) {
+ return nil, errors.New("ERROR: not a sql-migrate command")
+ }
+
+ fields := strings.Fields(line[len(sqlCmdPrefix):])
+ if len(fields) == 0 {
+ return nil, errors.New(`ERROR: incomplete migration command`)
+ }
+
+ cmd.Command = fields[0]
+
+ cmd.Options = fields[1:]
+
+ return cmd, nil
+}
+
+// Split the given sql script into individual statements.
+//
+// The base case is to simply split on semicolons, as these
+// naturally terminate a statement.
+//
+// However, more complex cases like pl/pgsql can have semicolons
+// within a statement. For these cases, we provide the explicit annotations
+// 'StatementBegin' and 'StatementEnd' to allow the script to
+// tell us to ignore semicolons.
+func ParseMigration(r io.ReadSeeker) (*ParsedMigration, error) {
+ p := &ParsedMigration{}
+
+ _, err := r.Seek(0, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ var buf bytes.Buffer
+ scanner := bufio.NewScanner(r)
+ scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
+
+ statementEnded := false
+ ignoreSemicolons := false
+ currentDirection := directionNone
+
+ for scanner.Scan() {
+ line := scanner.Text()
+ // ignore comment except beginning with '-- +'
+ if strings.HasPrefix(line, "-- ") && !strings.HasPrefix(line, "-- +") {
+ continue
+ }
+
+ // handle any migrate-specific commands
+ if strings.HasPrefix(line, sqlCmdPrefix) {
+ cmd, err := parseCommand(line)
+ if err != nil {
+ return nil, err
+ }
+
+ switch cmd.Command {
+ case "Up":
+ if len(strings.TrimSpace(buf.String())) > 0 {
+ return nil, errNoTerminator()
+ }
+ currentDirection = directionUp
+ if cmd.HasOption(optionNoTransaction) {
+ p.DisableTransactionUp = true
+ }
+ break
+
+ case "Down":
+ if len(strings.TrimSpace(buf.String())) > 0 {
+ return nil, errNoTerminator()
+ }
+ currentDirection = directionDown
+ if cmd.HasOption(optionNoTransaction) {
+ p.DisableTransactionDown = true
+ }
+ break
+
+ case "StatementBegin":
+ if currentDirection != directionNone {
+ ignoreSemicolons = true
+ }
+ break
+
+ case "StatementEnd":
+ if currentDirection != directionNone {
+ statementEnded = (ignoreSemicolons == true)
+ ignoreSemicolons = false
+ }
+ break
+ }
+ }
+
+ if currentDirection == directionNone {
+ continue
+ }
+
+ isLineSeparator := !ignoreSemicolons && len(LineSeparator) > 0 && line == LineSeparator
+
+ if !isLineSeparator && !strings.HasPrefix(line, "-- +") {
+ if _, err := buf.WriteString(line + "\n"); err != nil {
+ return nil, err
+ }
+ }
+
+ // Wrap up the two supported cases: 1) basic with semicolon; 2) psql statement
+ // Lines that end with semicolon that are in a statement block
+ // do not conclude statement.
+ if (!ignoreSemicolons && (endsWithSemicolon(line) || isLineSeparator)) || statementEnded {
+ statementEnded = false
+ switch currentDirection {
+ case directionUp:
+ p.UpStatements = append(p.UpStatements, buf.String())
+
+ case directionDown:
+ p.DownStatements = append(p.DownStatements, buf.String())
+
+ default:
+ panic("impossible state")
+ }
+
+ buf.Reset()
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ // diagnose likely migration script errors
+ if ignoreSemicolons {
+ return nil, errors.New("ERROR: saw '-- +migrate StatementBegin' with no matching '-- +migrate StatementEnd'")
+ }
+
+ if currentDirection == directionNone {
+ return nil, errors.New(`ERROR: no Up/Down annotations found, so no statements were executed.
+ See https://github.com/rubenv/sql-migrate for details.`)
+ }
+
+ // allow comment without sql instruction. Example:
+ // -- +migrate Down
+ // -- nothing to downgrade!
+ if len(strings.TrimSpace(buf.String())) > 0 && !strings.HasPrefix(buf.String(), "-- +") {
+ return nil, errNoTerminator()
+ }
+
+ return p, nil
+}
diff --git a/vendor/gopkg.in/gorp.v1/LICENSE b/vendor/gopkg.in/gorp.v1/LICENSE
new file mode 100644
index 0000000..b661111
--- /dev/null
+++ b/vendor/gopkg.in/gorp.v1/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2012 James Cooper <james@bitmechanic.com>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/gopkg.in/gorp.v1/Makefile b/vendor/gopkg.in/gorp.v1/Makefile
new file mode 100644
index 0000000..edf771c
--- /dev/null
+++ b/vendor/gopkg.in/gorp.v1/Makefile
@@ -0,0 +1,6 @@
+include $(GOROOT)/src/Make.inc
+
+TARG = github.com/coopernurse/gorp
+GOFILES = gorp.go dialect.go
+
+include $(GOROOT)/src/Make.pkg \ No newline at end of file
diff --git a/vendor/gopkg.in/gorp.v1/README.md b/vendor/gopkg.in/gorp.v1/README.md
new file mode 100644
index 0000000..c7cd5d8
--- /dev/null
+++ b/vendor/gopkg.in/gorp.v1/README.md
@@ -0,0 +1,672 @@
+# Go Relational Persistence
+
+[![build status](https://secure.travis-ci.org/go-gorp/gorp.png)](http://travis-ci.org/go-gorp/gorp)
+
+I hesitate to call gorp an ORM. Go doesn't really have objects, at least
+not in the classic Smalltalk/Java sense. There goes the "O". gorp doesn't
+know anything about the relationships between your structs (at least not
+yet). So the "R" is questionable too (but I use it in the name because,
+well, it seemed more clever).
+
+The "M" is alive and well. Given some Go structs and a database, gorp
+should remove a fair amount of boilerplate busy-work from your code.
+
+I hope that gorp saves you time, minimizes the drudgery of getting data
+in and out of your database, and helps your code focus on algorithms,
+not infrastructure.
+
+* Bind struct fields to table columns via API or tag
+* Support for embedded structs
+* Support for transactions
+* Forward engineer db schema from structs (great for unit tests)
+* Pre/post insert/update/delete hooks
+* Automatically generate insert/update/delete statements for a struct
+* Automatic binding of auto increment PKs back to struct after insert
+* Delete by primary key(s)
+* Select by primary key(s)
+* Optional trace sql logging
+* Bind arbitrary SQL queries to a struct
+* Bind slice to SELECT query results without type assertions
+* Use positional or named bind parameters in custom SELECT queries
+* Optional optimistic locking using a version column (for update/deletes)
+
+## Installation
+
+ # install the library:
+ go get gopkg.in/gorp.v1
+
+ // use in your .go code:
+ import (
+ "gopkg.in/gorp.v1"
+ )
+
+## Versioning
+
+This project provides a stable release (v1.x tags) and a bleeding edge codebase (master).
+
+`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.
+
+If you want to use bleeding edge, use `github.com/go-gorp/gorp` as import path.
+
+## API Documentation
+
+Full godoc output from the latest v1 release is available here:
+
+https://godoc.org/gopkg.in/gorp.v1
+
+For the latest code in master:
+
+https://godoc.org/github.com/go-gorp/gorp
+
+## Quickstart
+
+```go
+package main
+
+import (
+ "database/sql"
+ "gopkg.in/gorp.v1"
+ _ "github.com/mattn/go-sqlite3"
+ "log"
+ "time"
+)
+
+func main() {
+ // initialize the DbMap
+ dbmap := initDb()
+ defer dbmap.Db.Close()
+
+ // delete any existing rows
+ err := dbmap.TruncateTables()
+ checkErr(err, "TruncateTables failed")
+
+ // create two posts
+ p1 := newPost("Go 1.1 released!", "Lorem ipsum lorem ipsum")
+ p2 := newPost("Go 1.2 released!", "Lorem ipsum lorem ipsum")
+
+ // insert rows - auto increment PKs will be set properly after the insert
+ err = dbmap.Insert(&p1, &p2)
+ checkErr(err, "Insert failed")
+
+ // use convenience SelectInt
+ count, err := dbmap.SelectInt("select count(*) from posts")
+ checkErr(err, "select count(*) failed")
+ log.Println("Rows after inserting:", count)
+
+ // update a row
+ p2.Title = "Go 1.2 is better than ever"
+ count, err = dbmap.Update(&p2)
+ checkErr(err, "Update failed")
+ log.Println("Rows updated:", count)
+
+ // fetch one row - note use of "post_id" instead of "Id" since column is aliased
+ //
+ // Postgres users should use $1 instead of ? placeholders
+ // See 'Known Issues' below
+ //
+ err = dbmap.SelectOne(&p2, "select * from posts where post_id=?", p2.Id)
+ checkErr(err, "SelectOne failed")
+ log.Println("p2 row:", p2)
+
+ // fetch all rows
+ var posts []Post
+ _, err = dbmap.Select(&posts, "select * from posts order by post_id")
+ checkErr(err, "Select failed")
+ log.Println("All rows:")
+ for x, p := range posts {
+ log.Printf(" %d: %v\n", x, p)
+ }
+
+ // delete row by PK
+ count, err = dbmap.Delete(&p1)
+ checkErr(err, "Delete failed")
+ log.Println("Rows deleted:", count)
+
+ // delete row manually via Exec
+ _, err = dbmap.Exec("delete from posts where post_id=?", p2.Id)
+ checkErr(err, "Exec failed")
+
+ // confirm count is zero
+ count, err = dbmap.SelectInt("select count(*) from posts")
+ checkErr(err, "select count(*) failed")
+ log.Println("Row count - should be zero:", count)
+
+ log.Println("Done!")
+}
+
+type Post struct {
+ // db tag lets you specify the column name if it differs from the struct field
+ Id int64 `db:"post_id"`
+ Created int64
+ Title string
+ Body string
+}
+
+func newPost(title, body string) Post {
+ return Post{
+ Created: time.Now().UnixNano(),
+ Title: title,
+ Body: body,
+ }
+}
+
+func initDb() *gorp.DbMap {
+ // connect to db using standard Go database/sql API
+ // use whatever database/sql driver you wish
+ db, err := sql.Open("sqlite3", "/tmp/post_db.bin")
+ checkErr(err, "sql.Open failed")
+
+ // construct a gorp DbMap
+ dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
+
+ // add a table, setting the table name to 'posts' and
+ // specifying that the Id property is an auto incrementing PK
+ dbmap.AddTableWithName(Post{}, "posts").SetKeys(true, "Id")
+
+ // create the table. in a production system you'd generally
+ // use a migration tool, or create the tables via scripts
+ err = dbmap.CreateTablesIfNotExists()
+ checkErr(err, "Create tables failed")
+
+ return dbmap
+}
+
+func checkErr(err error, msg string) {
+ if err != nil {
+ log.Fatalln(msg, err)
+ }
+}
+```
+
+## Examples
+
+### Mapping structs to tables
+
+First define some types:
+
+```go
+type Invoice struct {
+ Id int64
+ Created int64
+ Updated int64
+ Memo string
+ PersonId int64
+}
+
+type Person struct {
+ Id int64
+ Created int64
+ Updated int64
+ FName string
+ LName string
+}
+
+// Example of using tags to alias fields to column names
+// The 'db' value is the column name
+//
+// A hyphen will cause gorp to skip this field, similar to the
+// Go json package.
+//
+// This is equivalent to using the ColMap methods:
+//
+// table := dbmap.AddTableWithName(Product{}, "product")
+// table.ColMap("Id").Rename("product_id")
+// table.ColMap("Price").Rename("unit_price")
+// table.ColMap("IgnoreMe").SetTransient(true)
+//
+type Product struct {
+ Id int64 `db:"product_id"`
+ Price int64 `db:"unit_price"`
+ IgnoreMe string `db:"-"`
+}
+```
+
+Then create a mapper, typically you'd do this one time at app startup:
+
+```go
+// connect to db using standard Go database/sql API
+// use whatever database/sql driver you wish
+db, err := sql.Open("mymysql", "tcp:localhost:3306*mydb/myuser/mypassword")
+
+// construct a gorp DbMap
+dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}
+
+// register the structs you wish to use with gorp
+// you can also use the shorter dbmap.AddTable() if you
+// don't want to override the table name
+//
+// SetKeys(true) means we have a auto increment primary key, which
+// will get automatically bound to your struct post-insert
+//
+t1 := dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id")
+t2 := dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id")
+t3 := dbmap.AddTableWithName(Product{}, "product_test").SetKeys(true, "Id")
+```
+
+### Struct Embedding
+
+gorp supports embedding structs. For example:
+
+```go
+type Names struct {
+ FirstName string
+ LastName string
+}
+
+type WithEmbeddedStruct struct {
+ Id int64
+ Names
+}
+
+es := &WithEmbeddedStruct{-1, Names{FirstName: "Alice", LastName: "Smith"}}
+err := dbmap.Insert(es)
+```
+
+See the `TestWithEmbeddedStruct` function in `gorp_test.go` for a full example.
+
+### Create/Drop Tables ###
+
+Automatically create / drop registered tables. This is useful for unit tests
+but is entirely optional. You can of course use gorp with tables created manually,
+or with a separate migration tool (like goose: https://bitbucket.org/liamstask/goose).
+
+```go
+// create all registered tables
+dbmap.CreateTables()
+
+// same as above, but uses "if not exists" clause to skip tables that are
+// already defined
+dbmap.CreateTablesIfNotExists()
+
+// drop
+dbmap.DropTables()
+```
+
+### SQL Logging
+
+Optionally you can pass in a logger to trace all SQL statements.
+I recommend enabling this initially while you're getting the feel for what
+gorp is doing on your behalf.
+
+Gorp defines a `GorpLogger` interface that Go's built in `log.Logger` satisfies.
+However, you can write your own `GorpLogger` implementation, or use a package such
+as `glog` if you want more control over how statements are logged.
+
+```go
+// Will log all SQL statements + args as they are run
+// The first arg is a string prefix to prepend to all log messages
+dbmap.TraceOn("[gorp]", log.New(os.Stdout, "myapp:", log.Lmicroseconds))
+
+// Turn off tracing
+dbmap.TraceOff()
+```
+
+### Insert
+
+```go
+// Must declare as pointers so optional callback hooks
+// can operate on your data, not copies
+inv1 := &Invoice{0, 100, 200, "first order", 0}
+inv2 := &Invoice{0, 100, 200, "second order", 0}
+
+// Insert your rows
+err := dbmap.Insert(inv1, inv2)
+
+// Because we called SetKeys(true) on Invoice, the Id field
+// will be populated after the Insert() automatically
+fmt.Printf("inv1.Id=%d inv2.Id=%d\n", inv1.Id, inv2.Id)
+```
+
+### Update
+
+Continuing the above example, use the `Update` method to modify an Invoice:
+
+```go
+// count is the # of rows updated, which should be 1 in this example
+count, err := dbmap.Update(inv1)
+```
+
+### Delete
+
+If you have primary key(s) defined for a struct, you can use the `Delete`
+method to remove rows:
+
+```go
+count, err := dbmap.Delete(inv1)
+```
+
+### Select by Key
+
+Use the `Get` method to fetch a single row by primary key. It returns
+nil if no row is found.
+
+```go
+// fetch Invoice with Id=99
+obj, err := dbmap.Get(Invoice{}, 99)
+inv := obj.(*Invoice)
+```
+
+### Ad Hoc SQL
+
+#### SELECT
+
+`Select()` and `SelectOne()` provide a simple way to bind arbitrary queries to a slice
+or a single struct.
+
+```go
+// Select a slice - first return value is not needed when a slice pointer is passed to Select()
+var posts []Post
+_, err := dbmap.Select(&posts, "select * from post order by id")
+
+// You can also use primitive types
+var ids []string
+_, err := dbmap.Select(&ids, "select id from post")
+
+// Select a single row.
+// Returns an error if no row found, or if more than one row is found
+var post Post
+err := dbmap.SelectOne(&post, "select * from post where id=?", id)
+```
+
+Want to do joins? Just write the SQL and the struct. gorp will bind them:
+
+```go
+// Define a type for your join
+// It *must* contain all the columns in your SELECT statement
+//
+// The names here should match the aliased column names you specify
+// in your SQL - no additional binding work required. simple.
+//
+type InvoicePersonView struct {
+ InvoiceId int64
+ PersonId int64
+ Memo string
+ FName string
+}
+
+// Create some rows
+p1 := &Person{0, 0, 0, "bob", "smith"}
+dbmap.Insert(p1)
+
+// notice how we can wire up p1.Id to the invoice easily
+inv1 := &Invoice{0, 0, 0, "xmas order", p1.Id}
+dbmap.Insert(inv1)
+
+// Run your query
+query := "select i.Id InvoiceId, p.Id PersonId, i.Memo, p.FName " +
+ "from invoice_test i, person_test p " +
+ "where i.PersonId = p.Id"
+
+// pass a slice to Select()
+var list []InvoicePersonView
+_, err := dbmap.Select(&list, query)
+
+// this should test true
+expected := InvoicePersonView{inv1.Id, p1.Id, inv1.Memo, p1.FName}
+if reflect.DeepEqual(list[0], expected) {
+ fmt.Println("Woot! My join worked!")
+}
+```
+
+#### SELECT string or int64
+
+gorp provides a few convenience methods for selecting a single string or int64.
+
+```go
+// select single int64 from db (use $1 instead of ? for postgresql)
+i64, err := dbmap.SelectInt("select count(*) from foo where blah=?", blahVal)
+
+// select single string from db:
+s, err := dbmap.SelectStr("select name from foo where blah=?", blahVal)
+
+```
+
+#### Named bind parameters
+
+You may use a map or struct to bind parameters by name. This is currently
+only supported in SELECT queries.
+
+```go
+_, err := dbm.Select(&dest, "select * from Foo where name = :name and age = :age", map[string]interface{}{
+ "name": "Rob",
+ "age": 31,
+})
+```
+
+#### UPDATE / DELETE
+
+You can execute raw SQL if you wish. Particularly good for batch operations.
+
+```go
+res, err := dbmap.Exec("delete from invoice_test where PersonId=?", 10)
+```
+
+### Transactions
+
+You can batch operations into a transaction:
+
+```go
+func InsertInv(dbmap *DbMap, inv *Invoice, per *Person) error {
+ // Start a new transaction
+ trans, err := dbmap.Begin()
+ if err != nil {
+ return err
+ }
+
+ trans.Insert(per)
+ inv.PersonId = per.Id
+ trans.Insert(inv)
+
+ // if the commit is successful, a nil error is returned
+ return trans.Commit()
+}
+```
+
+### Hooks
+
+Use hooks to update data before/after saving to the db. Good for timestamps:
+
+```go
+// implement the PreInsert and PreUpdate hooks
+func (i *Invoice) PreInsert(s gorp.SqlExecutor) error {
+ i.Created = time.Now().UnixNano()
+ i.Updated = i.Created
+ return nil
+}
+
+func (i *Invoice) PreUpdate(s gorp.SqlExecutor) error {
+ i.Updated = time.Now().UnixNano()
+ return nil
+}
+
+// You can use the SqlExecutor to cascade additional SQL
+// Take care to avoid cycles. gorp won't prevent them.
+//
+// Here's an example of a cascading delete
+//
+func (p *Person) PreDelete(s gorp.SqlExecutor) error {
+ query := "delete from invoice_test where PersonId=?"
+ err := s.Exec(query, p.Id); if err != nil {
+ return err
+ }
+ return nil
+}
+```
+
+Full list of hooks that you can implement:
+
+ PostGet
+ PreInsert
+ PostInsert
+ PreUpdate
+ PostUpdate
+ PreDelete
+ PostDelete
+
+ All have the same signature. for example:
+
+ func (p *MyStruct) PostUpdate(s gorp.SqlExecutor) error
+
+### Optimistic Locking
+
+gorp provides a simple optimistic locking feature, similar to Java's JPA, that
+will raise an error if you try to update/delete a row whose `version` column
+has a value different than the one in memory. This provides a safe way to do
+"select then update" style operations without explicit read and write locks.
+
+```go
+// Version is an auto-incremented number, managed by gorp
+// If this property is present on your struct, update
+// operations will be constrained
+//
+// For example, say we defined Person as:
+
+type Person struct {
+ Id int64
+ Created int64
+ Updated int64
+ FName string
+ LName string
+
+ // automatically used as the Version col
+ // use table.SetVersionCol("columnName") to map a different
+ // struct field as the version field
+ Version int64
+}
+
+p1 := &Person{0, 0, 0, "Bob", "Smith", 0}
+dbmap.Insert(p1) // Version is now 1
+
+obj, err := dbmap.Get(Person{}, p1.Id)
+p2 := obj.(*Person)
+p2.LName = "Edwards"
+dbmap.Update(p2) // Version is now 2
+
+p1.LName = "Howard"
+
+// Raises error because p1.Version == 1, which is out of date
+count, err := dbmap.Update(p1)
+_, ok := err.(gorp.OptimisticLockError)
+if ok {
+ // should reach this statement
+
+ // in a real app you might reload the row and retry, or
+ // you might propegate this to the user, depending on the desired
+ // semantics
+ fmt.Printf("Tried to update row with stale data: %v\n", err)
+} else {
+ // some other db error occurred - log or return up the stack
+ fmt.Printf("Unknown db err: %v\n", err)
+}
+```
+
+## Database Drivers
+
+gorp uses the Go 1 `database/sql` package. A full list of compliant drivers is available here:
+
+http://code.google.com/p/go-wiki/wiki/SQLDrivers
+
+Sadly, SQL databases differ on various issues. gorp provides a Dialect interface that should be
+implemented per database vendor. Dialects are provided for:
+
+* MySQL
+* PostgreSQL
+* sqlite3
+
+Each of these three databases pass the test suite. See `gorp_test.go` for example
+DSNs for these three databases.
+
+Support is also provided for:
+
+* Oracle (contributed by @klaidliadon)
+* SQL Server (contributed by @qrawl) - use driver: github.com/denisenkom/go-mssqldb
+
+Note that these databases are not covered by CI and I (@coopernurse) have no good way to
+test them locally. So please try them and send patches as needed, but expect a bit more
+unpredicability.
+
+## Known Issues
+
+### SQL placeholder portability
+
+Different databases use different strings to indicate variable placeholders in
+prepared SQL statements. Unlike some database abstraction layers (such as JDBC),
+Go's `database/sql` does not standardize this.
+
+SQL generated by gorp in the `Insert`, `Update`, `Delete`, and `Get` methods delegates
+to a Dialect implementation for each database, and will generate portable SQL.
+
+Raw SQL strings passed to `Exec`, `Select`, `SelectOne`, `SelectInt`, etc will not be
+parsed. Consequently you may have portability issues if you write a query like this:
+
+```go
+// works on MySQL and Sqlite3, but not with Postgresql
+err := dbmap.SelectOne(&val, "select * from foo where id = ?", 30)
+```
+
+In `Select` and `SelectOne` you can use named parameters to work around this.
+The following is portable:
+
+```go
+err := dbmap.SelectOne(&val, "select * from foo where id = :id",
+ map[string]interface{} { "id": 30})
+```
+
+### time.Time and time zones
+
+gorp will pass `time.Time` fields through to the `database/sql` driver, but note that
+the behavior of this type varies across database drivers.
+
+MySQL users should be especially cautious. See: https://github.com/ziutek/mymysql/pull/77
+
+To avoid any potential issues with timezone/DST, consider using an integer field for time
+data and storing UNIX time.
+
+## Running the tests
+
+The included tests may be run against MySQL, Postgresql, or sqlite3.
+You must set two environment variables so the test code knows which driver to
+use, and how to connect to your database.
+
+```sh
+# MySQL example:
+export GORP_TEST_DSN=gomysql_test/gomysql_test/abc123
+export GORP_TEST_DIALECT=mysql
+
+# run the tests
+go test
+
+# run the tests and benchmarks
+go test -bench="Bench" -benchtime 10
+```
+
+Valid `GORP_TEST_DIALECT` values are: "mysql", "postgres", "sqlite3"
+See the `test_all.sh` script for examples of all 3 databases. This is the script I run
+locally to test the library.
+
+## Performance
+
+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.
+
+## Help/Support
+
+IRC: #gorp
+Mailing list: gorp-dev@googlegroups.com
+Bugs/Enhancements: Create a github issue
+
+## Pull requests / Contributions
+
+Contributions are very welcome. Please follow these guidelines:
+
+* Fork the `master` branch and issue pull requests targeting the `master` branch
+* If you are adding an enhancement, please open an issue first with your proposed change.
+* Changes that break backwards compatibility in the public API are only accepted after we
+ discuss on a GitHub issue for a while.
+
+Thanks!
+
+## Contributors
+
+* matthias-margush - column aliasing via tags
+* Rob Figueiredo - @robfig
+* Quinn Slack - @sqs
diff --git a/vendor/gopkg.in/gorp.v1/dialect.go b/vendor/gopkg.in/gorp.v1/dialect.go
new file mode 100644
index 0000000..5e8fdc6
--- /dev/null
+++ b/vendor/gopkg.in/gorp.v1/dialect.go
@@ -0,0 +1,692 @@
+package gorp
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+)
+
+// The Dialect interface encapsulates behaviors that differ across
+// SQL databases. At present the Dialect is only used by CreateTables()
+// but this could change in the future
+type Dialect interface {
+
+ // adds a suffix to any query, usually ";"
+ QuerySuffix() string
+
+ // ToSqlType returns the SQL column type to use when creating a
+ // table of the given Go Type. maxsize can be used to switch based on
+ // size. For example, in MySQL []byte could map to BLOB, MEDIUMBLOB,
+ // or LONGBLOB depending on the maxsize
+ ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string
+
+ // string to append to primary key column definitions
+ AutoIncrStr() string
+
+ // string to bind autoincrement columns to. Empty string will
+ // remove reference to those columns in the INSERT statement.
+ AutoIncrBindValue() string
+
+ AutoIncrInsertSuffix(col *ColumnMap) string
+
+ // string to append to "create table" statement for vendor specific
+ // table attributes
+ CreateTableSuffix() string
+
+ // string to truncate tables
+ TruncateClause() string
+
+ // bind variable string to use when forming SQL statements
+ // in many dbs it is "?", but Postgres appears to use $1
+ //
+ // i is a zero based index of the bind variable in this statement
+ //
+ BindVar(i int) string
+
+ // Handles quoting of a field name to ensure that it doesn't raise any
+ // SQL parsing exceptions by using a reserved word as a field name.
+ QuoteField(field string) string
+
+ // Handles building up of a schema.database string that is compatible with
+ // the given dialect
+ //
+ // schema - The schema that <table> lives in
+ // table - The table name
+ QuotedTableForQuery(schema string, table string) string
+
+ // Existance clause for table creation / deletion
+ IfSchemaNotExists(command, schema string) string
+ IfTableExists(command, schema, table string) string
+ IfTableNotExists(command, schema, table string) string
+}
+
+// IntegerAutoIncrInserter is implemented by dialects that can perform
+// inserts with automatically incremented integer primary keys. If
+// the dialect can handle automatic assignment of more than just
+// integers, see TargetedAutoIncrInserter.
+type IntegerAutoIncrInserter interface {
+ InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error)
+}
+
+// TargetedAutoIncrInserter is implemented by dialects that can
+// perform automatic assignment of any primary key type (i.e. strings
+// for uuids, integers for serials, etc).
+type TargetedAutoIncrInserter interface {
+ // InsertAutoIncrToTarget runs an insert operation and assigns the
+ // automatically generated primary key directly to the passed in
+ // target. The target should be a pointer to the primary key
+ // field of the value being inserted.
+ InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error
+}
+
+func standardInsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ res, err := exec.Exec(insertSql, params...)
+ if err != nil {
+ return 0, err
+ }
+ return res.LastInsertId()
+}
+
+///////////////////////////////////////////////////////
+// sqlite3 //
+/////////////
+
+type SqliteDialect struct {
+ suffix string
+}
+
+func (d SqliteDialect) QuerySuffix() string { return ";" }
+
+func (d SqliteDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
+ switch val.Kind() {
+ case reflect.Ptr:
+ return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
+ case reflect.Bool:
+ return "integer"
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return "integer"
+ case reflect.Float64, reflect.Float32:
+ return "real"
+ case reflect.Slice:
+ if val.Elem().Kind() == reflect.Uint8 {
+ return "blob"
+ }
+ }
+
+ switch val.Name() {
+ case "NullInt64":
+ return "integer"
+ case "NullFloat64":
+ return "real"
+ case "NullBool":
+ return "integer"
+ case "Time":
+ return "datetime"
+ }
+
+ if maxsize < 1 {
+ maxsize = 255
+ }
+ return fmt.Sprintf("varchar(%d)", maxsize)
+}
+
+// Returns autoincrement
+func (d SqliteDialect) AutoIncrStr() string {
+ return "autoincrement"
+}
+
+func (d SqliteDialect) AutoIncrBindValue() string {
+ return "null"
+}
+
+func (d SqliteDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
+ return ""
+}
+
+// Returns suffix
+func (d SqliteDialect) CreateTableSuffix() string {
+ return d.suffix
+}
+
+// With sqlite, there technically isn't a TRUNCATE statement,
+// but a DELETE FROM uses a truncate optimization:
+// http://www.sqlite.org/lang_delete.html
+func (d SqliteDialect) TruncateClause() string {
+ return "delete from"
+}
+
+// Returns "?"
+func (d SqliteDialect) BindVar(i int) string {
+ return "?"
+}
+
+func (d SqliteDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ return standardInsertAutoIncr(exec, insertSql, params...)
+}
+
+func (d SqliteDialect) QuoteField(f string) string {
+ return `"` + f + `"`
+}
+
+// sqlite does not have schemas like PostgreSQL does, so just escape it like normal
+func (d SqliteDialect) QuotedTableForQuery(schema string, table string) string {
+ return d.QuoteField(table)
+}
+
+func (d SqliteDialect) IfSchemaNotExists(command, schema string) string {
+ return fmt.Sprintf("%s if not exists", command)
+}
+
+func (d SqliteDialect) IfTableExists(command, schema, table string) string {
+ return fmt.Sprintf("%s if exists", command)
+}
+
+func (d SqliteDialect) IfTableNotExists(command, schema, table string) string {
+ return fmt.Sprintf("%s if not exists", command)
+}
+
+///////////////////////////////////////////////////////
+// PostgreSQL //
+////////////////
+
+type PostgresDialect struct {
+ suffix string
+}
+
+func (d PostgresDialect) QuerySuffix() string { return ";" }
+
+func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
+ switch val.Kind() {
+ case reflect.Ptr:
+ return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
+ case reflect.Bool:
+ return "boolean"
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
+ if isAutoIncr {
+ return "serial"
+ }
+ return "integer"
+ case reflect.Int64, reflect.Uint64:
+ if isAutoIncr {
+ return "bigserial"
+ }
+ return "bigint"
+ case reflect.Float64:
+ return "double precision"
+ case reflect.Float32:
+ return "real"
+ case reflect.Slice:
+ if val.Elem().Kind() == reflect.Uint8 {
+ return "bytea"
+ }
+ }
+
+ switch val.Name() {
+ case "NullInt64":
+ return "bigint"
+ case "NullFloat64":
+ return "double precision"
+ case "NullBool":
+ return "boolean"
+ case "Time":
+ return "timestamp with time zone"
+ }
+
+ if maxsize > 0 {
+ return fmt.Sprintf("varchar(%d)", maxsize)
+ } else {
+ return "text"
+ }
+
+}
+
+// Returns empty string
+func (d PostgresDialect) AutoIncrStr() string {
+ return ""
+}
+
+func (d PostgresDialect) AutoIncrBindValue() string {
+ return "default"
+}
+
+func (d PostgresDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
+ return " returning " + col.ColumnName
+}
+
+// Returns suffix
+func (d PostgresDialect) CreateTableSuffix() string {
+ return d.suffix
+}
+
+func (d PostgresDialect) TruncateClause() string {
+ return "truncate"
+}
+
+// Returns "$(i+1)"
+func (d PostgresDialect) BindVar(i int) string {
+ return fmt.Sprintf("$%d", i+1)
+}
+
+func (d PostgresDialect) InsertAutoIncrToTarget(exec SqlExecutor, insertSql string, target interface{}, params ...interface{}) error {
+ rows, err := exec.query(insertSql, params...)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ if rows.Next() {
+ err := rows.Scan(target)
+ return err
+ }
+
+ return errors.New("No serial value returned for insert: " + insertSql + " Encountered error: " + rows.Err().Error())
+}
+
+func (d PostgresDialect) QuoteField(f string) string {
+ return `"` + strings.ToLower(f) + `"`
+}
+
+func (d PostgresDialect) QuotedTableForQuery(schema string, table string) string {
+ if strings.TrimSpace(schema) == "" {
+ return d.QuoteField(table)
+ }
+
+ return schema + "." + d.QuoteField(table)
+}
+
+func (d PostgresDialect) IfSchemaNotExists(command, schema string) string {
+ return fmt.Sprintf("%s if not exists", command)
+}
+
+func (d PostgresDialect) IfTableExists(command, schema, table string) string {
+ return fmt.Sprintf("%s if exists", command)
+}
+
+func (d PostgresDialect) IfTableNotExists(command, schema, table string) string {
+ return fmt.Sprintf("%s if not exists", command)
+}
+
+///////////////////////////////////////////////////////
+// MySQL //
+///////////
+
+// Implementation of Dialect for MySQL databases.
+type MySQLDialect struct {
+
+ // Engine is the storage engine to use "InnoDB" vs "MyISAM" for example
+ Engine string
+
+ // Encoding is the character encoding to use for created tables
+ Encoding string
+}
+
+func (d MySQLDialect) QuerySuffix() string { return ";" }
+
+func (d MySQLDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
+ switch val.Kind() {
+ case reflect.Ptr:
+ return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
+ case reflect.Bool:
+ return "boolean"
+ case reflect.Int8:
+ return "tinyint"
+ case reflect.Uint8:
+ return "tinyint unsigned"
+ case reflect.Int16:
+ return "smallint"
+ case reflect.Uint16:
+ return "smallint unsigned"
+ case reflect.Int, reflect.Int32:
+ return "int"
+ case reflect.Uint, reflect.Uint32:
+ return "int unsigned"
+ case reflect.Int64:
+ return "bigint"
+ case reflect.Uint64:
+ return "bigint unsigned"
+ case reflect.Float64, reflect.Float32:
+ return "double"
+ case reflect.Slice:
+ if val.Elem().Kind() == reflect.Uint8 {
+ return "mediumblob"
+ }
+ }
+
+ switch val.Name() {
+ case "NullInt64":
+ return "bigint"
+ case "NullFloat64":
+ return "double"
+ case "NullBool":
+ return "tinyint"
+ case "Time":
+ return "datetime"
+ }
+
+ if maxsize < 1 {
+ maxsize = 255
+ }
+ return fmt.Sprintf("varchar(%d)", maxsize)
+}
+
+// Returns auto_increment
+func (d MySQLDialect) AutoIncrStr() string {
+ return "auto_increment"
+}
+
+func (d MySQLDialect) AutoIncrBindValue() string {
+ return "null"
+}
+
+func (d MySQLDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
+ return ""
+}
+
+// Returns engine=%s charset=%s based on values stored on struct
+func (d MySQLDialect) CreateTableSuffix() string {
+ if d.Engine == "" || d.Encoding == "" {
+ msg := "gorp - undefined"
+
+ if d.Engine == "" {
+ msg += " MySQLDialect.Engine"
+ }
+ if d.Engine == "" && d.Encoding == "" {
+ msg += ","
+ }
+ if d.Encoding == "" {
+ msg += " MySQLDialect.Encoding"
+ }
+ msg += ". Check that your MySQLDialect was correctly initialized when declared."
+ panic(msg)
+ }
+
+ return fmt.Sprintf(" engine=%s charset=%s", d.Engine, d.Encoding)
+}
+
+func (d MySQLDialect) TruncateClause() string {
+ return "truncate"
+}
+
+// Returns "?"
+func (d MySQLDialect) BindVar(i int) string {
+ return "?"
+}
+
+func (d MySQLDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ return standardInsertAutoIncr(exec, insertSql, params...)
+}
+
+func (d MySQLDialect) QuoteField(f string) string {
+ return "`" + f + "`"
+}
+
+func (d MySQLDialect) QuotedTableForQuery(schema string, table string) string {
+ if strings.TrimSpace(schema) == "" {
+ return d.QuoteField(table)
+ }
+
+ return schema + "." + d.QuoteField(table)
+}
+
+func (d MySQLDialect) IfSchemaNotExists(command, schema string) string {
+ return fmt.Sprintf("%s if not exists", command)
+}
+
+func (d MySQLDialect) IfTableExists(command, schema, table string) string {
+ return fmt.Sprintf("%s if exists", command)
+}
+
+func (d MySQLDialect) IfTableNotExists(command, schema, table string) string {
+ return fmt.Sprintf("%s if not exists", command)
+}
+
+///////////////////////////////////////////////////////
+// Sql Server //
+////////////////
+
+// Implementation of Dialect for Microsoft SQL Server databases.
+// Tested on SQL Server 2008 with driver: github.com/denisenkom/go-mssqldb
+
+type SqlServerDialect struct {
+ suffix string
+}
+
+func (d SqlServerDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
+ switch val.Kind() {
+ case reflect.Ptr:
+ return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
+ case reflect.Bool:
+ return "bit"
+ case reflect.Int8:
+ return "tinyint"
+ case reflect.Uint8:
+ return "smallint"
+ case reflect.Int16:
+ return "smallint"
+ case reflect.Uint16:
+ return "int"
+ case reflect.Int, reflect.Int32:
+ return "int"
+ case reflect.Uint, reflect.Uint32:
+ return "bigint"
+ case reflect.Int64:
+ return "bigint"
+ case reflect.Uint64:
+ return "bigint"
+ case reflect.Float32:
+ return "real"
+ case reflect.Float64:
+ return "float(53)"
+ case reflect.Slice:
+ if val.Elem().Kind() == reflect.Uint8 {
+ return "varbinary"
+ }
+ }
+
+ switch val.Name() {
+ case "NullInt64":
+ return "bigint"
+ case "NullFloat64":
+ return "float(53)"
+ case "NullBool":
+ return "tinyint"
+ case "Time":
+ return "datetime"
+ }
+
+ if maxsize < 1 {
+ maxsize = 255
+ }
+ return fmt.Sprintf("varchar(%d)", maxsize)
+}
+
+// Returns auto_increment
+func (d SqlServerDialect) AutoIncrStr() string {
+ return "identity(0,1)"
+}
+
+// Empty string removes autoincrement columns from the INSERT statements.
+func (d SqlServerDialect) AutoIncrBindValue() string {
+ return ""
+}
+
+func (d SqlServerDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
+ return ""
+}
+
+// Returns suffix
+func (d SqlServerDialect) CreateTableSuffix() string {
+
+ return d.suffix
+}
+
+func (d SqlServerDialect) TruncateClause() string {
+ return "delete from"
+}
+
+// Returns "?"
+func (d SqlServerDialect) BindVar(i int) string {
+ return "?"
+}
+
+func (d SqlServerDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ return standardInsertAutoIncr(exec, insertSql, params...)
+}
+
+func (d SqlServerDialect) QuoteField(f string) string {
+ return `"` + f + `"`
+}
+
+func (d SqlServerDialect) QuotedTableForQuery(schema string, table string) string {
+ if strings.TrimSpace(schema) == "" {
+ return table
+ }
+ return schema + "." + table
+}
+
+func (d SqlServerDialect) QuerySuffix() string { return ";" }
+
+func (d SqlServerDialect) IfSchemaNotExists(command, schema string) string {
+ s := fmt.Sprintf("if not exists (select name from sys.schemas where name = '%s') %s", schema, command)
+ return s
+}
+
+func (d SqlServerDialect) IfTableExists(command, schema, table string) string {
+ var schema_clause string
+ if strings.TrimSpace(schema) != "" {
+ schema_clause = fmt.Sprintf("table_schema = '%s' and ", schema)
+ }
+ s := fmt.Sprintf("if exists (select * from information_schema.tables where %stable_name = '%s') %s", schema_clause, table, command)
+ return s
+}
+
+func (d SqlServerDialect) IfTableNotExists(command, schema, table string) string {
+ var schema_clause string
+ if strings.TrimSpace(schema) != "" {
+ schema_clause = fmt.Sprintf("table_schema = '%s' and ", schema)
+ }
+ s := fmt.Sprintf("if not exists (select * from information_schema.tables where %stable_name = '%s') %s", schema_clause, table, command)
+ return s
+}
+
+///////////////////////////////////////////////////////
+// Oracle //
+///////////
+
+// Implementation of Dialect for Oracle databases.
+type OracleDialect struct{}
+
+func (d OracleDialect) QuerySuffix() string { return "" }
+
+func (d OracleDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
+ switch val.Kind() {
+ case reflect.Ptr:
+ return d.ToSqlType(val.Elem(), maxsize, isAutoIncr)
+ case reflect.Bool:
+ return "boolean"
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32:
+ if isAutoIncr {
+ return "serial"
+ }
+ return "integer"
+ case reflect.Int64, reflect.Uint64:
+ if isAutoIncr {
+ return "bigserial"
+ }
+ return "bigint"
+ case reflect.Float64:
+ return "double precision"
+ case reflect.Float32:
+ return "real"
+ case reflect.Slice:
+ if val.Elem().Kind() == reflect.Uint8 {
+ return "bytea"
+ }
+ }
+
+ switch val.Name() {
+ case "NullInt64":
+ return "bigint"
+ case "NullFloat64":
+ return "double precision"
+ case "NullBool":
+ return "boolean"
+ case "NullTime", "Time":
+ return "timestamp with time zone"
+ }
+
+ if maxsize > 0 {
+ return fmt.Sprintf("varchar(%d)", maxsize)
+ } else {
+ return "text"
+ }
+
+}
+
+// Returns empty string
+func (d OracleDialect) AutoIncrStr() string {
+ return ""
+}
+
+func (d OracleDialect) AutoIncrBindValue() string {
+ return "default"
+}
+
+func (d OracleDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
+ return " returning " + col.ColumnName
+}
+
+// Returns suffix
+func (d OracleDialect) CreateTableSuffix() string {
+ return ""
+}
+
+func (d OracleDialect) TruncateClause() string {
+ return "truncate"
+}
+
+// Returns "$(i+1)"
+func (d OracleDialect) BindVar(i int) string {
+ return fmt.Sprintf(":%d", i+1)
+}
+
+func (d OracleDialect) InsertAutoIncr(exec SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ rows, err := exec.query(insertSql, params...)
+ if err != nil {
+ return 0, err
+ }
+ defer rows.Close()
+
+ if rows.Next() {
+ var id int64
+ err := rows.Scan(&id)
+ return id, err
+ }
+
+ return 0, errors.New("No serial value returned for insert: " + insertSql + " Encountered error: " + rows.Err().Error())
+}
+
+func (d OracleDialect) QuoteField(f string) string {
+ return `"` + strings.ToUpper(f) + `"`
+}
+
+func (d OracleDialect) QuotedTableForQuery(schema string, table string) string {
+ if strings.TrimSpace(schema) == "" {
+ return d.QuoteField(table)
+ }
+
+ return schema + "." + d.QuoteField(table)
+}
+
+func (d OracleDialect) IfSchemaNotExists(command, schema string) string {
+ return fmt.Sprintf("%s if not exists", command)
+}
+
+func (d OracleDialect) IfTableExists(command, schema, table string) string {
+ return fmt.Sprintf("%s if exists", command)
+}
+
+func (d OracleDialect) IfTableNotExists(command, schema, table string) string {
+ return fmt.Sprintf("%s if not exists", command)
+}
diff --git a/vendor/gopkg.in/gorp.v1/errors.go b/vendor/gopkg.in/gorp.v1/errors.go
new file mode 100644
index 0000000..356d684
--- /dev/null
+++ b/vendor/gopkg.in/gorp.v1/errors.go
@@ -0,0 +1,26 @@
+package gorp
+
+import (
+ "fmt"
+)
+
+// A non-fatal error, when a select query returns columns that do not exist
+// as fields in the struct it is being mapped to
+type NoFieldInTypeError struct {
+ TypeName string
+ MissingColNames []string
+}
+
+func (err *NoFieldInTypeError) Error() string {
+ return fmt.Sprintf("gorp: No fields %+v in type %s", err.MissingColNames, err.TypeName)
+}
+
+// returns true if the error is non-fatal (ie, we shouldn't immediately return)
+func NonFatalError(err error) bool {
+ switch err.(type) {
+ case *NoFieldInTypeError:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/vendor/gopkg.in/gorp.v1/gorp.go b/vendor/gopkg.in/gorp.v1/gorp.go
new file mode 100644
index 0000000..1ad6187
--- /dev/null
+++ b/vendor/gopkg.in/gorp.v1/gorp.go
@@ -0,0 +1,2085 @@
+// Copyright 2012 James Cooper. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// Package gorp provides a simple way to marshal Go structs to and from
+// SQL databases. It uses the database/sql package, and should work with any
+// compliant database/sql driver.
+//
+// Source code and project home:
+// https://github.com/coopernurse/gorp
+//
+package gorp
+
+import (
+ "bytes"
+ "database/sql"
+ "database/sql/driver"
+ "errors"
+ "fmt"
+ "reflect"
+ "regexp"
+ "strings"
+ "time"
+ "log"
+ "os"
+)
+
+// Oracle String (empty string is null)
+type OracleString struct {
+ sql.NullString
+}
+
+// Scan implements the Scanner interface.
+func (os *OracleString) Scan(value interface{}) error {
+ if value == nil {
+ os.String, os.Valid = "", false
+ return nil
+ }
+ os.Valid = true
+ return os.NullString.Scan(value)
+}
+
+// Value implements the driver Valuer interface.
+func (os OracleString) Value() (driver.Value, error) {
+ if !os.Valid || os.String == "" {
+ return nil, nil
+ }
+ return os.String, nil
+}
+
+// A nullable Time value
+type NullTime struct {
+ Time time.Time
+ Valid bool // Valid is true if Time is not NULL
+}
+
+// Scan implements the Scanner interface.
+func (nt *NullTime) Scan(value interface{}) error {
+ nt.Time, nt.Valid = value.(time.Time)
+ return nil
+}
+
+// Value implements the driver Valuer interface.
+func (nt NullTime) Value() (driver.Value, error) {
+ if !nt.Valid {
+ return nil, nil
+ }
+ return nt.Time, nil
+}
+
+var zeroVal reflect.Value
+var versFieldConst = "[gorp_ver_field]"
+
+// OptimisticLockError is returned by Update() or Delete() if the
+// struct being modified has a Version field and the value is not equal to
+// the current value in the database
+type OptimisticLockError struct {
+ // Table name where the lock error occurred
+ TableName string
+
+ // Primary key values of the row being updated/deleted
+ Keys []interface{}
+
+ // true if a row was found with those keys, indicating the
+ // LocalVersion is stale. false if no value was found with those
+ // keys, suggesting the row has been deleted since loaded, or
+ // was never inserted to begin with
+ RowExists bool
+
+ // Version value on the struct passed to Update/Delete. This value is
+ // out of sync with the database.
+ LocalVersion int64
+}
+
+// Error returns a description of the cause of the lock error
+func (e OptimisticLockError) Error() string {
+ if e.RowExists {
+ return fmt.Sprintf("gorp: OptimisticLockError table=%s keys=%v out of date version=%d", e.TableName, e.Keys, e.LocalVersion)
+ }
+
+ return fmt.Sprintf("gorp: OptimisticLockError no row found for table=%s keys=%v", e.TableName, e.Keys)
+}
+
+// The TypeConverter interface provides a way to map a value of one
+// type to another type when persisting to, or loading from, a database.
+//
+// Example use cases: Implement type converter to convert bool types to "y"/"n" strings,
+// or serialize a struct member as a JSON blob.
+type TypeConverter interface {
+ // ToDb converts val to another type. Called before INSERT/UPDATE operations
+ ToDb(val interface{}) (interface{}, error)
+
+ // FromDb returns a CustomScanner appropriate for this type. This will be used
+ // to hold values returned from SELECT queries.
+ //
+ // In particular the CustomScanner returned should implement a Binder
+ // function appropriate for the Go type you wish to convert the db value to
+ //
+ // If bool==false, then no custom scanner will be used for this field.
+ FromDb(target interface{}) (CustomScanner, bool)
+}
+
+// CustomScanner binds a database column value to a Go type
+type CustomScanner struct {
+ // After a row is scanned, Holder will contain the value from the database column.
+ // Initialize the CustomScanner with the concrete Go type you wish the database
+ // driver to scan the raw column into.
+ Holder interface{}
+ // Target typically holds a pointer to the target struct field to bind the Holder
+ // value to.
+ Target interface{}
+ // Binder is a custom function that converts the holder value to the target type
+ // and sets target accordingly. This function should return error if a problem
+ // occurs converting the holder to the target.
+ Binder func(holder interface{}, target interface{}) error
+}
+
+// Bind is called automatically by gorp after Scan()
+func (me CustomScanner) Bind() error {
+ return me.Binder(me.Holder, me.Target)
+}
+
+// DbMap is the root gorp mapping object. Create one of these for each
+// database schema you wish to map. Each DbMap contains a list of
+// mapped tables.
+//
+// Example:
+//
+// dialect := gorp.MySQLDialect{"InnoDB", "UTF8"}
+// dbmap := &gorp.DbMap{Db: db, Dialect: dialect}
+//
+type DbMap struct {
+ // Db handle to use with this map
+ Db *sql.DB
+
+ // Dialect implementation to use with this map
+ Dialect Dialect
+
+ TypeConverter TypeConverter
+
+ tables []*TableMap
+ logger GorpLogger
+ logPrefix string
+}
+
+// TableMap represents a mapping between a Go struct and a database table
+// Use dbmap.AddTable() or dbmap.AddTableWithName() to create these
+type TableMap struct {
+ // Name of database table.
+ TableName string
+ SchemaName string
+ gotype reflect.Type
+ Columns []*ColumnMap
+ keys []*ColumnMap
+ uniqueTogether [][]string
+ version *ColumnMap
+ insertPlan bindPlan
+ updatePlan bindPlan
+ deletePlan bindPlan
+ getPlan bindPlan
+ dbmap *DbMap
+}
+
+// ResetSql removes cached insert/update/select/delete SQL strings
+// associated with this TableMap. Call this if you've modified
+// any column names or the table name itself.
+func (t *TableMap) ResetSql() {
+ t.insertPlan = bindPlan{}
+ t.updatePlan = bindPlan{}
+ t.deletePlan = bindPlan{}
+ t.getPlan = bindPlan{}
+}
+
+// SetKeys lets you specify the fields on a struct that map to primary
+// key columns on the table. If isAutoIncr is set, result.LastInsertId()
+// will be used after INSERT to bind the generated id to the Go struct.
+//
+// Automatically calls ResetSql() to ensure SQL statements are regenerated.
+//
+// Panics if isAutoIncr is true, and fieldNames length != 1
+//
+func (t *TableMap) SetKeys(isAutoIncr bool, fieldNames ...string) *TableMap {
+ if isAutoIncr && len(fieldNames) != 1 {
+ panic(fmt.Sprintf(
+ "gorp: SetKeys: fieldNames length must be 1 if key is auto-increment. (Saw %v fieldNames)",
+ len(fieldNames)))
+ }
+ t.keys = make([]*ColumnMap, 0)
+ for _, name := range fieldNames {
+ colmap := t.ColMap(name)
+ colmap.isPK = true
+ colmap.isAutoIncr = isAutoIncr
+ t.keys = append(t.keys, colmap)
+ }
+ t.ResetSql()
+
+ return t
+}
+
+// SetUniqueTogether lets you specify uniqueness constraints across multiple
+// columns on the table. Each call adds an additional constraint for the
+// specified columns.
+//
+// Automatically calls ResetSql() to ensure SQL statements are regenerated.
+//
+// Panics if fieldNames length < 2.
+//
+func (t *TableMap) SetUniqueTogether(fieldNames ...string) *TableMap {
+ if len(fieldNames) < 2 {
+ panic(fmt.Sprintf(
+ "gorp: SetUniqueTogether: must provide at least two fieldNames to set uniqueness constraint."))
+ }
+
+ columns := make([]string, 0)
+ for _, name := range fieldNames {
+ columns = append(columns, name)
+ }
+ t.uniqueTogether = append(t.uniqueTogether, columns)
+ t.ResetSql()
+
+ return t
+}
+
+// ColMap returns the ColumnMap pointer matching the given struct field
+// name. It panics if the struct does not contain a field matching this
+// name.
+func (t *TableMap) ColMap(field string) *ColumnMap {
+ col := colMapOrNil(t, field)
+ if col == nil {
+ e := fmt.Sprintf("No ColumnMap in table %s type %s with field %s",
+ t.TableName, t.gotype.Name(), field)
+
+ panic(e)
+ }
+ return col
+}
+
+func colMapOrNil(t *TableMap, field string) *ColumnMap {
+ for _, col := range t.Columns {
+ if col.fieldName == field || col.ColumnName == field {
+ return col
+ }
+ }
+ return nil
+}
+
+// SetVersionCol sets the column to use as the Version field. By default
+// the "Version" field is used. Returns the column found, or panics
+// if the struct does not contain a field matching this name.
+//
+// Automatically calls ResetSql() to ensure SQL statements are regenerated.
+func (t *TableMap) SetVersionCol(field string) *ColumnMap {
+ c := t.ColMap(field)
+ t.version = c
+ t.ResetSql()
+ return c
+}
+
+type bindPlan struct {
+ query string
+ argFields []string
+ keyFields []string
+ versField string
+ autoIncrIdx int
+ autoIncrFieldName string
+}
+
+func (plan bindPlan) createBindInstance(elem reflect.Value, conv TypeConverter) (bindInstance, error) {
+ bi := bindInstance{query: plan.query, autoIncrIdx: plan.autoIncrIdx, autoIncrFieldName: plan.autoIncrFieldName, versField: plan.versField}
+ if plan.versField != "" {
+ bi.existingVersion = elem.FieldByName(plan.versField).Int()
+ }
+
+ var err error
+
+ for i := 0; i < len(plan.argFields); i++ {
+ k := plan.argFields[i]
+ if k == versFieldConst {
+ newVer := bi.existingVersion + 1
+ bi.args = append(bi.args, newVer)
+ if bi.existingVersion == 0 {
+ elem.FieldByName(plan.versField).SetInt(int64(newVer))
+ }
+ } else {
+ val := elem.FieldByName(k).Interface()
+ if conv != nil {
+ val, err = conv.ToDb(val)
+ if err != nil {
+ return bindInstance{}, err
+ }
+ }
+ bi.args = append(bi.args, val)
+ }
+ }
+
+ for i := 0; i < len(plan.keyFields); i++ {
+ k := plan.keyFields[i]
+ val := elem.FieldByName(k).Interface()
+ if conv != nil {
+ val, err = conv.ToDb(val)
+ if err != nil {
+ return bindInstance{}, err
+ }
+ }
+ bi.keys = append(bi.keys, val)
+ }
+
+ return bi, nil
+}
+
+type bindInstance struct {
+ query string
+ args []interface{}
+ keys []interface{}
+ existingVersion int64
+ versField string
+ autoIncrIdx int
+ autoIncrFieldName string
+}
+
+func (t *TableMap) bindInsert(elem reflect.Value) (bindInstance, error) {
+ plan := t.insertPlan
+ if plan.query == "" {
+ plan.autoIncrIdx = -1
+
+ s := bytes.Buffer{}
+ s2 := bytes.Buffer{}
+ s.WriteString(fmt.Sprintf("insert into %s (", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))
+
+ x := 0
+ first := true
+ for y := range t.Columns {
+ col := t.Columns[y]
+ if !(col.isAutoIncr && t.dbmap.Dialect.AutoIncrBindValue() == "") {
+ if !col.Transient {
+ if !first {
+ s.WriteString(",")
+ s2.WriteString(",")
+ }
+ s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
+
+ if col.isAutoIncr {
+ s2.WriteString(t.dbmap.Dialect.AutoIncrBindValue())
+ plan.autoIncrIdx = y
+ plan.autoIncrFieldName = col.fieldName
+ } else {
+ s2.WriteString(t.dbmap.Dialect.BindVar(x))
+ if col == t.version {
+ plan.versField = col.fieldName
+ plan.argFields = append(plan.argFields, versFieldConst)
+ } else {
+ plan.argFields = append(plan.argFields, col.fieldName)
+ }
+
+ x++
+ }
+ first = false
+ }
+ } else {
+ plan.autoIncrIdx = y
+ plan.autoIncrFieldName = col.fieldName
+ }
+ }
+ s.WriteString(") values (")
+ s.WriteString(s2.String())
+ s.WriteString(")")
+ if plan.autoIncrIdx > -1 {
+ s.WriteString(t.dbmap.Dialect.AutoIncrInsertSuffix(t.Columns[plan.autoIncrIdx]))
+ }
+ s.WriteString(t.dbmap.Dialect.QuerySuffix())
+
+ plan.query = s.String()
+ t.insertPlan = plan
+ }
+
+ return plan.createBindInstance(elem, t.dbmap.TypeConverter)
+}
+
+func (t *TableMap) bindUpdate(elem reflect.Value) (bindInstance, error) {
+ plan := t.updatePlan
+ if plan.query == "" {
+
+ s := bytes.Buffer{}
+ s.WriteString(fmt.Sprintf("update %s set ", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))
+ x := 0
+
+ for y := range t.Columns {
+ col := t.Columns[y]
+ if !col.isAutoIncr && !col.Transient {
+ if x > 0 {
+ s.WriteString(", ")
+ }
+ s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
+ s.WriteString("=")
+ s.WriteString(t.dbmap.Dialect.BindVar(x))
+
+ if col == t.version {
+ plan.versField = col.fieldName
+ plan.argFields = append(plan.argFields, versFieldConst)
+ } else {
+ plan.argFields = append(plan.argFields, col.fieldName)
+ }
+ x++
+ }
+ }
+
+ s.WriteString(" where ")
+ for y := range t.keys {
+ col := t.keys[y]
+ if y > 0 {
+ s.WriteString(" and ")
+ }
+ s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
+ s.WriteString("=")
+ s.WriteString(t.dbmap.Dialect.BindVar(x))
+
+ plan.argFields = append(plan.argFields, col.fieldName)
+ plan.keyFields = append(plan.keyFields, col.fieldName)
+ x++
+ }
+ if plan.versField != "" {
+ s.WriteString(" and ")
+ s.WriteString(t.dbmap.Dialect.QuoteField(t.version.ColumnName))
+ s.WriteString("=")
+ s.WriteString(t.dbmap.Dialect.BindVar(x))
+ plan.argFields = append(plan.argFields, plan.versField)
+ }
+ s.WriteString(t.dbmap.Dialect.QuerySuffix())
+
+ plan.query = s.String()
+ t.updatePlan = plan
+ }
+
+ return plan.createBindInstance(elem, t.dbmap.TypeConverter)
+}
+
+func (t *TableMap) bindDelete(elem reflect.Value) (bindInstance, error) {
+ plan := t.deletePlan
+ if plan.query == "" {
+
+ s := bytes.Buffer{}
+ s.WriteString(fmt.Sprintf("delete from %s", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))
+
+ for y := range t.Columns {
+ col := t.Columns[y]
+ if !col.Transient {
+ if col == t.version {
+ plan.versField = col.fieldName
+ }
+ }
+ }
+
+ s.WriteString(" where ")
+ for x := range t.keys {
+ k := t.keys[x]
+ if x > 0 {
+ s.WriteString(" and ")
+ }
+ s.WriteString(t.dbmap.Dialect.QuoteField(k.ColumnName))
+ s.WriteString("=")
+ s.WriteString(t.dbmap.Dialect.BindVar(x))
+
+ plan.keyFields = append(plan.keyFields, k.fieldName)
+ plan.argFields = append(plan.argFields, k.fieldName)
+ }
+ if plan.versField != "" {
+ s.WriteString(" and ")
+ s.WriteString(t.dbmap.Dialect.QuoteField(t.version.ColumnName))
+ s.WriteString("=")
+ s.WriteString(t.dbmap.Dialect.BindVar(len(plan.argFields)))
+
+ plan.argFields = append(plan.argFields, plan.versField)
+ }
+ s.WriteString(t.dbmap.Dialect.QuerySuffix())
+
+ plan.query = s.String()
+ t.deletePlan = plan
+ }
+
+ return plan.createBindInstance(elem, t.dbmap.TypeConverter)
+}
+
+func (t *TableMap) bindGet() bindPlan {
+ plan := t.getPlan
+ if plan.query == "" {
+
+ s := bytes.Buffer{}
+ s.WriteString("select ")
+
+ x := 0
+ for _, col := range t.Columns {
+ if !col.Transient {
+ if x > 0 {
+ s.WriteString(",")
+ }
+ s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
+ plan.argFields = append(plan.argFields, col.fieldName)
+ x++
+ }
+ }
+ s.WriteString(" from ")
+ s.WriteString(t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName))
+ s.WriteString(" where ")
+ for x := range t.keys {
+ col := t.keys[x]
+ if x > 0 {
+ s.WriteString(" and ")
+ }
+ s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
+ s.WriteString("=")
+ s.WriteString(t.dbmap.Dialect.BindVar(x))
+
+ plan.keyFields = append(plan.keyFields, col.fieldName)
+ }
+ s.WriteString(t.dbmap.Dialect.QuerySuffix())
+
+ plan.query = s.String()
+ t.getPlan = plan
+ }
+
+ return plan
+}
+
+// ColumnMap represents a mapping between a Go struct field and a single
+// column in a table.
+// Unique and MaxSize only inform the
+// CreateTables() function and are not used by Insert/Update/Delete/Get.
+type ColumnMap struct {
+ // Column name in db table
+ ColumnName string
+
+ // If true, this column is skipped in generated SQL statements
+ Transient bool
+
+ // If true, " unique" is added to create table statements.
+ // Not used elsewhere
+ Unique bool
+
+ // Passed to Dialect.ToSqlType() to assist in informing the
+ // correct column type to map to in CreateTables()
+ // Not used elsewhere
+ MaxSize int
+
+ fieldName string
+ gotype reflect.Type
+ isPK bool
+ isAutoIncr bool
+ isNotNull bool
+}
+
+// Rename allows you to specify the column name in the table
+//
+// Example: table.ColMap("Updated").Rename("date_updated")
+//
+func (c *ColumnMap) Rename(colname string) *ColumnMap {
+ c.ColumnName = colname
+ return c
+}
+
+// SetTransient allows you to mark the column as transient. If true
+// this column will be skipped when SQL statements are generated
+func (c *ColumnMap) SetTransient(b bool) *ColumnMap {
+ c.Transient = b
+ return c
+}
+
+// SetUnique adds "unique" to the create table statements for this
+// column, if b is true.
+func (c *ColumnMap) SetUnique(b bool) *ColumnMap {
+ c.Unique = b
+ return c
+}
+
+// SetNotNull adds "not null" to the create table statements for this
+// column, if nn is true.
+func (c *ColumnMap) SetNotNull(nn bool) *ColumnMap {
+ c.isNotNull = nn
+ return c
+}
+
+// SetMaxSize specifies the max length of values of this column. This is
+// passed to the dialect.ToSqlType() function, which can use the value
+// to alter the generated type for "create table" statements
+func (c *ColumnMap) SetMaxSize(size int) *ColumnMap {
+ c.MaxSize = size
+ return c
+}
+
+// Transaction represents a database transaction.
+// Insert/Update/Delete/Get/Exec operations will be run in the context
+// of that transaction. Transactions should be terminated with
+// a call to Commit() or Rollback()
+type Transaction struct {
+ dbmap *DbMap
+ tx *sql.Tx
+ closed bool
+}
+
+// SqlExecutor exposes gorp operations that can be run from Pre/Post
+// hooks. This hides whether the current operation that triggered the
+// hook is in a transaction.
+//
+// See the DbMap function docs for each of the functions below for more
+// information.
+type SqlExecutor interface {
+ Get(i interface{}, keys ...interface{}) (interface{}, error)
+ Insert(list ...interface{}) error
+ Update(list ...interface{}) (int64, error)
+ Delete(list ...interface{}) (int64, error)
+ Exec(query string, args ...interface{}) (sql.Result, error)
+ Select(i interface{}, query string,
+ args ...interface{}) ([]interface{}, error)
+ SelectInt(query string, args ...interface{}) (int64, error)
+ SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error)
+ SelectFloat(query string, args ...interface{}) (float64, error)
+ SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error)
+ SelectStr(query string, args ...interface{}) (string, error)
+ SelectNullStr(query string, args ...interface{}) (sql.NullString, error)
+ SelectOne(holder interface{}, query string, args ...interface{}) error
+ query(query string, args ...interface{}) (*sql.Rows, error)
+ queryRow(query string, args ...interface{}) *sql.Row
+}
+
+// Compile-time check that DbMap and Transaction implement the SqlExecutor
+// interface.
+var _, _ SqlExecutor = &DbMap{}, &Transaction{}
+
+type GorpLogger interface {
+ Printf(format string, v ...interface{})
+}
+
+// TraceOn turns on SQL statement logging for this DbMap. After this is
+// called, all SQL statements will be sent to the logger. If prefix is
+// a non-empty string, it will be written to the front of all logged
+// strings, which can aid in filtering log lines.
+//
+// Use TraceOn if you want to spy on the SQL statements that gorp
+// generates.
+//
+// Note that the base log.Logger type satisfies GorpLogger, but adapters can
+// easily be written for other logging packages (e.g., the golang-sanctioned
+// glog framework).
+func (m *DbMap) TraceOn(prefix string, logger GorpLogger) {
+ m.logger = logger
+ if prefix == "" {
+ m.logPrefix = prefix
+ } else {
+ m.logPrefix = fmt.Sprintf("%s ", prefix)
+ }
+}
+
+// TraceOff turns off tracing. It is idempotent.
+func (m *DbMap) TraceOff() {
+ m.logger = nil
+ m.logPrefix = ""
+}
+
+// AddTable registers the given interface type with gorp. The table name
+// will be given the name of the TypeOf(i). You must call this function,
+// or AddTableWithName, for any struct type you wish to persist with
+// the given DbMap.
+//
+// This operation is idempotent. If i's type is already mapped, the
+// existing *TableMap is returned
+func (m *DbMap) AddTable(i interface{}) *TableMap {
+ return m.AddTableWithName(i, "")
+}
+
+// AddTableWithName has the same behavior as AddTable, but sets
+// table.TableName to name.
+func (m *DbMap) AddTableWithName(i interface{}, name string) *TableMap {
+ return m.AddTableWithNameAndSchema(i, "", name)
+}
+
+// AddTableWithNameAndSchema has the same behavior as AddTable, but sets
+// table.TableName to name.
+func (m *DbMap) AddTableWithNameAndSchema(i interface{}, schema string, name string) *TableMap {
+ t := reflect.TypeOf(i)
+ if name == "" {
+ name = t.Name()
+ }
+
+ // check if we have a table for this type already
+ // if so, update the name and return the existing pointer
+ for i := range m.tables {
+ table := m.tables[i]
+ if table.gotype == t {
+ table.TableName = name
+ return table
+ }
+ }
+
+ tmap := &TableMap{gotype: t, TableName: name, SchemaName: schema, dbmap: m}
+ tmap.Columns, tmap.version = m.readStructColumns(t)
+ m.tables = append(m.tables, tmap)
+
+ return tmap
+}
+
+func (m *DbMap) readStructColumns(t reflect.Type) (cols []*ColumnMap, version *ColumnMap) {
+ n := t.NumField()
+ for i := 0; i < n; i++ {
+ f := t.Field(i)
+ if f.Anonymous && f.Type.Kind() == reflect.Struct {
+ // Recursively add nested fields in embedded structs.
+ subcols, subversion := m.readStructColumns(f.Type)
+ // Don't append nested fields that have the same field
+ // name as an already-mapped field.
+ for _, subcol := range subcols {
+ shouldAppend := true
+ for _, col := range cols {
+ if !subcol.Transient && subcol.fieldName == col.fieldName {
+ shouldAppend = false
+ break
+ }
+ }
+ if shouldAppend {
+ cols = append(cols, subcol)
+ }
+ }
+ if subversion != nil {
+ version = subversion
+ }
+ } else {
+ columnName := f.Tag.Get("db")
+ if columnName == "" {
+ columnName = f.Name
+ }
+ gotype := f.Type
+ if m.TypeConverter != nil {
+ // Make a new pointer to a value of type gotype and
+ // pass it to the TypeConverter's FromDb method to see
+ // if a different type should be used for the column
+ // type during table creation.
+ value := reflect.New(gotype).Interface()
+ scanner, useHolder := m.TypeConverter.FromDb(value)
+ if useHolder {
+ gotype = reflect.TypeOf(scanner.Holder)
+ }
+ }
+ cm := &ColumnMap{
+ ColumnName: columnName,
+ Transient: columnName == "-",
+ fieldName: f.Name,
+ gotype: gotype,
+ }
+ // Check for nested fields of the same field name and
+ // override them.
+ shouldAppend := true
+ for index, col := range cols {
+ if !col.Transient && col.fieldName == cm.fieldName {
+ cols[index] = cm
+ shouldAppend = false
+ break
+ }
+ }
+ if shouldAppend {
+ cols = append(cols, cm)
+ }
+ if cm.fieldName == "Version" {
+ log.New(os.Stderr, "", log.LstdFlags).Println("Warning: Automatic mapping of Version struct members to version columns (see optimistic locking) will be deprecated in next version (V2) See: https://github.com/go-gorp/gorp/pull/214")
+ version = cm
+ }
+ }
+ }
+ return
+}
+
+// CreateTables iterates through TableMaps registered to this DbMap and
+// executes "create table" statements against the database for each.
+//
+// This is particularly useful in unit tests where you want to create
+// and destroy the schema automatically.
+func (m *DbMap) CreateTables() error {
+ return m.createTables(false)
+}
+
+// CreateTablesIfNotExists is similar to CreateTables, but starts
+// each statement with "create table if not exists" so that existing
+// tables do not raise errors
+func (m *DbMap) CreateTablesIfNotExists() error {
+ return m.createTables(true)
+}
+
+func (m *DbMap) createTables(ifNotExists bool) error {
+ var err error
+ for i := range m.tables {
+ table := m.tables[i]
+
+ s := bytes.Buffer{}
+
+ if strings.TrimSpace(table.SchemaName) != "" {
+ schemaCreate := "create schema"
+ if ifNotExists {
+ s.WriteString(m.Dialect.IfSchemaNotExists(schemaCreate, table.SchemaName))
+ } else {
+ s.WriteString(schemaCreate)
+ }
+ s.WriteString(fmt.Sprintf(" %s;", table.SchemaName))
+ }
+
+ tableCreate := "create table"
+ if ifNotExists {
+ s.WriteString(m.Dialect.IfTableNotExists(tableCreate, table.SchemaName, table.TableName))
+ } else {
+ s.WriteString(tableCreate)
+ }
+ s.WriteString(fmt.Sprintf(" %s (", m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
+
+ x := 0
+ for _, col := range table.Columns {
+ if !col.Transient {
+ if x > 0 {
+ s.WriteString(", ")
+ }
+ stype := m.Dialect.ToSqlType(col.gotype, col.MaxSize, col.isAutoIncr)
+ s.WriteString(fmt.Sprintf("%s %s", m.Dialect.QuoteField(col.ColumnName), stype))
+
+ if col.isPK || col.isNotNull {
+ s.WriteString(" not null")
+ }
+ if col.isPK && len(table.keys) == 1 {
+ s.WriteString(" primary key")
+ }
+ if col.Unique {
+ s.WriteString(" unique")
+ }
+ if col.isAutoIncr {
+ s.WriteString(fmt.Sprintf(" %s", m.Dialect.AutoIncrStr()))
+ }
+
+ x++
+ }
+ }
+ if len(table.keys) > 1 {
+ s.WriteString(", primary key (")
+ for x := range table.keys {
+ if x > 0 {
+ s.WriteString(", ")
+ }
+ s.WriteString(m.Dialect.QuoteField(table.keys[x].ColumnName))
+ }
+ s.WriteString(")")
+ }
+ if len(table.uniqueTogether) > 0 {
+ for _, columns := range table.uniqueTogether {
+ s.WriteString(", unique (")
+ for i, column := range columns {
+ if i > 0 {
+ s.WriteString(", ")
+ }
+ s.WriteString(m.Dialect.QuoteField(column))
+ }
+ s.WriteString(")")
+ }
+ }
+ s.WriteString(") ")
+ s.WriteString(m.Dialect.CreateTableSuffix())
+ s.WriteString(m.Dialect.QuerySuffix())
+ _, err = m.Exec(s.String())
+ if err != nil {
+ break
+ }
+ }
+ return err
+}
+
+// DropTable drops an individual table. Will throw an error
+// if the table does not exist.
+func (m *DbMap) DropTable(table interface{}) error {
+ t := reflect.TypeOf(table)
+ return m.dropTable(t, false)
+}
+
+// DropTable drops an individual table. Will NOT throw an error
+// if the table does not exist.
+func (m *DbMap) DropTableIfExists(table interface{}) error {
+ t := reflect.TypeOf(table)
+ return m.dropTable(t, true)
+}
+
+// DropTables iterates through TableMaps registered to this DbMap and
+// executes "drop table" statements against the database for each.
+func (m *DbMap) DropTables() error {
+ return m.dropTables(false)
+}
+
+// DropTablesIfExists is the same as DropTables, but uses the "if exists" clause to
+// avoid errors for tables that do not exist.
+func (m *DbMap) DropTablesIfExists() error {
+ return m.dropTables(true)
+}
+
+// Goes through all the registered tables, dropping them one by one.
+// If an error is encountered, then it is returned and the rest of
+// the tables are not dropped.
+func (m *DbMap) dropTables(addIfExists bool) (err error) {
+ for _, table := range m.tables {
+ err = m.dropTableImpl(table, addIfExists)
+ if err != nil {
+ return
+ }
+ }
+ return err
+}
+
+// Implementation of dropping a single table.
+func (m *DbMap) dropTable(t reflect.Type, addIfExists bool) error {
+ table := tableOrNil(m, t)
+ if table == nil {
+ return errors.New(fmt.Sprintf("table %s was not registered!", table.TableName))
+ }
+
+ return m.dropTableImpl(table, addIfExists)
+}
+
+func (m *DbMap) dropTableImpl(table *TableMap, ifExists bool) (err error) {
+ tableDrop := "drop table"
+ if ifExists {
+ tableDrop = m.Dialect.IfTableExists(tableDrop, table.SchemaName, table.TableName)
+ }
+ _, err = m.Exec(fmt.Sprintf("%s %s;", tableDrop, m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
+ return err
+}
+
+// TruncateTables iterates through TableMaps registered to this DbMap and
+// executes "truncate table" statements against the database for each, or in the case of
+// sqlite, a "delete from" with no "where" clause, which uses the truncate optimization
+// (http://www.sqlite.org/lang_delete.html)
+func (m *DbMap) TruncateTables() error {
+ var err error
+ for i := range m.tables {
+ table := m.tables[i]
+ _, e := m.Exec(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(), m.Dialect.QuotedTableForQuery(table.SchemaName, table.TableName)))
+ if e != nil {
+ err = e
+ }
+ }
+ return err
+}
+
+// Insert runs a SQL INSERT statement for each element in list. List
+// items must be pointers.
+//
+// Any interface whose TableMap has an auto-increment primary key will
+// have its last insert id bound to the PK field on the struct.
+//
+// The hook functions PreInsert() and/or PostInsert() will be executed
+// before/after the INSERT statement if the interface defines them.
+//
+// Panics if any interface in the list has not been registered with AddTable
+func (m *DbMap) Insert(list ...interface{}) error {
+ return insert(m, m, list...)
+}
+
+// Update runs a SQL UPDATE statement for each element in list. List
+// items must be pointers.
+//
+// The hook functions PreUpdate() and/or PostUpdate() will be executed
+// before/after the UPDATE statement if the interface defines them.
+//
+// Returns the number of rows updated.
+//
+// Returns an error if SetKeys has not been called on the TableMap
+// Panics if any interface in the list has not been registered with AddTable
+func (m *DbMap) Update(list ...interface{}) (int64, error) {
+ return update(m, m, list...)
+}
+
+// Delete runs a SQL DELETE statement for each element in list. List
+// items must be pointers.
+//
+// The hook functions PreDelete() and/or PostDelete() will be executed
+// before/after the DELETE statement if the interface defines them.
+//
+// Returns the number of rows deleted.
+//
+// Returns an error if SetKeys has not been called on the TableMap
+// Panics if any interface in the list has not been registered with AddTable
+func (m *DbMap) Delete(list ...interface{}) (int64, error) {
+ return delete(m, m, list...)
+}
+
+// Get runs a SQL SELECT to fetch a single row from the table based on the
+// primary key(s)
+//
+// i should be an empty value for the struct to load. keys should be
+// the primary key value(s) for the row to load. If multiple keys
+// exist on the table, the order should match the column order
+// specified in SetKeys() when the table mapping was defined.
+//
+// The hook function PostGet() will be executed after the SELECT
+// statement if the interface defines them.
+//
+// Returns a pointer to a struct that matches or nil if no row is found.
+//
+// Returns an error if SetKeys has not been called on the TableMap
+// Panics if any interface in the list has not been registered with AddTable
+func (m *DbMap) Get(i interface{}, keys ...interface{}) (interface{}, error) {
+ return get(m, m, i, keys...)
+}
+
+// Select runs an arbitrary SQL query, binding the columns in the result
+// to fields on the struct specified by i. args represent the bind
+// parameters for the SQL statement.
+//
+// Column names on the SELECT statement should be aliased to the field names
+// on the struct i. Returns an error if one or more columns in the result
+// do not match. It is OK if fields on i are not part of the SQL
+// statement.
+//
+// The hook function PostGet() will be executed after the SELECT
+// statement if the interface defines them.
+//
+// Values are returned in one of two ways:
+// 1. If i is a struct or a pointer to a struct, returns a slice of pointers to
+// matching rows of type i.
+// 2. If i is a pointer to a slice, the results will be appended to that slice
+// and nil returned.
+//
+// i does NOT need to be registered with AddTable()
+func (m *DbMap) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) {
+ return hookedselect(m, m, i, query, args...)
+}
+
+// Exec runs an arbitrary SQL statement. args represent the bind parameters.
+// This is equivalent to running: Exec() using database/sql
+func (m *DbMap) Exec(query string, args ...interface{}) (sql.Result, error) {
+ m.trace(query, args...)
+ return m.Db.Exec(query, args...)
+}
+
+// SelectInt is a convenience wrapper around the gorp.SelectInt function
+func (m *DbMap) SelectInt(query string, args ...interface{}) (int64, error) {
+ return SelectInt(m, query, args...)
+}
+
+// SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function
+func (m *DbMap) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) {
+ return SelectNullInt(m, query, args...)
+}
+
+// SelectFloat is a convenience wrapper around the gorp.SelectFlot function
+func (m *DbMap) SelectFloat(query string, args ...interface{}) (float64, error) {
+ return SelectFloat(m, query, args...)
+}
+
+// SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function
+func (m *DbMap) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) {
+ return SelectNullFloat(m, query, args...)
+}
+
+// SelectStr is a convenience wrapper around the gorp.SelectStr function
+func (m *DbMap) SelectStr(query string, args ...interface{}) (string, error) {
+ return SelectStr(m, query, args...)
+}
+
+// SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function
+func (m *DbMap) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) {
+ return SelectNullStr(m, query, args...)
+}
+
+// SelectOne is a convenience wrapper around the gorp.SelectOne function
+func (m *DbMap) SelectOne(holder interface{}, query string, args ...interface{}) error {
+ return SelectOne(m, m, holder, query, args...)
+}
+
+// Begin starts a gorp Transaction
+func (m *DbMap) Begin() (*Transaction, error) {
+ m.trace("begin;")
+ tx, err := m.Db.Begin()
+ if err != nil {
+ return nil, err
+ }
+ return &Transaction{m, tx, false}, nil
+}
+
+// TableFor returns the *TableMap corresponding to the given Go Type
+// If no table is mapped to that type an error is returned.
+// If checkPK is true and the mapped table has no registered PKs, an error is returned.
+func (m *DbMap) TableFor(t reflect.Type, checkPK bool) (*TableMap, error) {
+ table := tableOrNil(m, t)
+ if table == nil {
+ return nil, errors.New(fmt.Sprintf("No table found for type: %v", t.Name()))
+ }
+
+ if checkPK && len(table.keys) < 1 {
+ e := fmt.Sprintf("gorp: No keys defined for table: %s",
+ table.TableName)
+ return nil, errors.New(e)
+ }
+
+ return table, nil
+}
+
+// Prepare creates a prepared statement for later queries or executions.
+// Multiple queries or executions may be run concurrently from the returned statement.
+// This is equivalent to running: Prepare() using database/sql
+func (m *DbMap) Prepare(query string) (*sql.Stmt, error) {
+ m.trace(query, nil)
+ return m.Db.Prepare(query)
+}
+
+func tableOrNil(m *DbMap, t reflect.Type) *TableMap {
+ for i := range m.tables {
+ table := m.tables[i]
+ if table.gotype == t {
+ return table
+ }
+ }
+ return nil
+}
+
+func (m *DbMap) tableForPointer(ptr interface{}, checkPK bool) (*TableMap, reflect.Value, error) {
+ ptrv := reflect.ValueOf(ptr)
+ if ptrv.Kind() != reflect.Ptr {
+ e := fmt.Sprintf("gorp: passed non-pointer: %v (kind=%v)", ptr,
+ ptrv.Kind())
+ return nil, reflect.Value{}, errors.New(e)
+ }
+ elem := ptrv.Elem()
+ etype := reflect.TypeOf(elem.Interface())
+ t, err := m.TableFor(etype, checkPK)
+ if err != nil {
+ return nil, reflect.Value{}, err
+ }
+
+ return t, elem, nil
+}
+
+func (m *DbMap) queryRow(query string, args ...interface{}) *sql.Row {
+ m.trace(query, args...)
+ return m.Db.QueryRow(query, args...)
+}
+
+func (m *DbMap) query(query string, args ...interface{}) (*sql.Rows, error) {
+ m.trace(query, args...)
+ return m.Db.Query(query, args...)
+}
+
+func (m *DbMap) trace(query string, args ...interface{}) {
+ if m.logger != nil {
+ var margs = argsString(args...)
+ m.logger.Printf("%s%s [%s]", m.logPrefix, query, margs)
+ }
+}
+
+func argsString(args ...interface{}) string {
+ var margs string
+ for i, a := range args {
+ var v interface{} = a
+ if x, ok := v.(driver.Valuer); ok {
+ y, err := x.Value()
+ if err == nil {
+ v = y
+ }
+ }
+ switch v.(type) {
+ case string:
+ v = fmt.Sprintf("%q", v)
+ default:
+ v = fmt.Sprintf("%v", v)
+ }
+ margs += fmt.Sprintf("%d:%s", i+1, v)
+ if i+1 < len(args) {
+ margs += " "
+ }
+ }
+ return margs
+}
+
+///////////////
+
+// Insert has the same behavior as DbMap.Insert(), but runs in a transaction.
+func (t *Transaction) Insert(list ...interface{}) error {
+ return insert(t.dbmap, t, list...)
+}
+
+// Update had the same behavior as DbMap.Update(), but runs in a transaction.
+func (t *Transaction) Update(list ...interface{}) (int64, error) {
+ return update(t.dbmap, t, list...)
+}
+
+// Delete has the same behavior as DbMap.Delete(), but runs in a transaction.
+func (t *Transaction) Delete(list ...interface{}) (int64, error) {
+ return delete(t.dbmap, t, list...)
+}
+
+// Get has the same behavior as DbMap.Get(), but runs in a transaction.
+func (t *Transaction) Get(i interface{}, keys ...interface{}) (interface{}, error) {
+ return get(t.dbmap, t, i, keys...)
+}
+
+// Select has the same behavior as DbMap.Select(), but runs in a transaction.
+func (t *Transaction) Select(i interface{}, query string, args ...interface{}) ([]interface{}, error) {
+ return hookedselect(t.dbmap, t, i, query, args...)
+}
+
+// Exec has the same behavior as DbMap.Exec(), but runs in a transaction.
+func (t *Transaction) Exec(query string, args ...interface{}) (sql.Result, error) {
+ t.dbmap.trace(query, args...)
+ return t.tx.Exec(query, args...)
+}
+
+// SelectInt is a convenience wrapper around the gorp.SelectInt function.
+func (t *Transaction) SelectInt(query string, args ...interface{}) (int64, error) {
+ return SelectInt(t, query, args...)
+}
+
+// SelectNullInt is a convenience wrapper around the gorp.SelectNullInt function.
+func (t *Transaction) SelectNullInt(query string, args ...interface{}) (sql.NullInt64, error) {
+ return SelectNullInt(t, query, args...)
+}
+
+// SelectFloat is a convenience wrapper around the gorp.SelectFloat function.
+func (t *Transaction) SelectFloat(query string, args ...interface{}) (float64, error) {
+ return SelectFloat(t, query, args...)
+}
+
+// SelectNullFloat is a convenience wrapper around the gorp.SelectNullFloat function.
+func (t *Transaction) SelectNullFloat(query string, args ...interface{}) (sql.NullFloat64, error) {
+ return SelectNullFloat(t, query, args...)
+}
+
+// SelectStr is a convenience wrapper around the gorp.SelectStr function.
+func (t *Transaction) SelectStr(query string, args ...interface{}) (string, error) {
+ return SelectStr(t, query, args...)
+}
+
+// SelectNullStr is a convenience wrapper around the gorp.SelectNullStr function.
+func (t *Transaction) SelectNullStr(query string, args ...interface{}) (sql.NullString, error) {
+ return SelectNullStr(t, query, args...)
+}
+
+// SelectOne is a convenience wrapper around the gorp.SelectOne function.
+func (t *Transaction) SelectOne(holder interface{}, query string, args ...interface{}) error {
+ return SelectOne(t.dbmap, t, holder, query, args...)
+}
+
+// Commit commits the underlying database transaction.
+func (t *Transaction) Commit() error {
+ if !t.closed {
+ t.closed = true
+ t.dbmap.trace("commit;")
+ return t.tx.Commit()
+ }
+
+ return sql.ErrTxDone
+}
+
+// Rollback rolls back the underlying database transaction.
+func (t *Transaction) Rollback() error {
+ if !t.closed {
+ t.closed = true
+ t.dbmap.trace("rollback;")
+ return t.tx.Rollback()
+ }
+
+ return sql.ErrTxDone
+}
+
+// Savepoint creates a savepoint with the given name. The name is interpolated
+// directly into the SQL SAVEPOINT statement, so you must sanitize it if it is
+// derived from user input.
+func (t *Transaction) Savepoint(name string) error {
+ query := "savepoint " + t.dbmap.Dialect.QuoteField(name)
+ t.dbmap.trace(query, nil)
+ _, err := t.tx.Exec(query)
+ return err
+}
+
+// RollbackToSavepoint rolls back to the savepoint with the given name. The
+// name is interpolated directly into the SQL SAVEPOINT statement, so you must
+// sanitize it if it is derived from user input.
+func (t *Transaction) RollbackToSavepoint(savepoint string) error {
+ query := "rollback to savepoint " + t.dbmap.Dialect.QuoteField(savepoint)
+ t.dbmap.trace(query, nil)
+ _, err := t.tx.Exec(query)
+ return err
+}
+
+// ReleaseSavepint releases the savepoint with the given name. The name is
+// interpolated directly into the SQL SAVEPOINT statement, so you must sanitize
+// it if it is derived from user input.
+func (t *Transaction) ReleaseSavepoint(savepoint string) error {
+ query := "release savepoint " + t.dbmap.Dialect.QuoteField(savepoint)
+ t.dbmap.trace(query, nil)
+ _, err := t.tx.Exec(query)
+ return err
+}
+
+// Prepare has the same behavior as DbMap.Prepare(), but runs in a transaction.
+func (t *Transaction) Prepare(query string) (*sql.Stmt, error) {
+ t.dbmap.trace(query, nil)
+ return t.tx.Prepare(query)
+}
+
+func (t *Transaction) queryRow(query string, args ...interface{}) *sql.Row {
+ t.dbmap.trace(query, args...)
+ return t.tx.QueryRow(query, args...)
+}
+
+func (t *Transaction) query(query string, args ...interface{}) (*sql.Rows, error) {
+ t.dbmap.trace(query, args...)
+ return t.tx.Query(query, args...)
+}
+
+///////////////
+
+// SelectInt executes the given query, which should be a SELECT statement for a single
+// integer column, and returns the value of the first row returned. If no rows are
+// found, zero is returned.
+func SelectInt(e SqlExecutor, query string, args ...interface{}) (int64, error) {
+ var h int64
+ err := selectVal(e, &h, query, args...)
+ if err != nil && err != sql.ErrNoRows {
+ return 0, err
+ }
+ return h, nil
+}
+
+// SelectNullInt executes the given query, which should be a SELECT statement for a single
+// integer column, and returns the value of the first row returned. If no rows are
+// found, the empty sql.NullInt64 value is returned.
+func SelectNullInt(e SqlExecutor, query string, args ...interface{}) (sql.NullInt64, error) {
+ var h sql.NullInt64
+ err := selectVal(e, &h, query, args...)
+ if err != nil && err != sql.ErrNoRows {
+ return h, err
+ }
+ return h, nil
+}
+
+// SelectFloat executes the given query, which should be a SELECT statement for a single
+// float column, and returns the value of the first row returned. If no rows are
+// found, zero is returned.
+func SelectFloat(e SqlExecutor, query string, args ...interface{}) (float64, error) {
+ var h float64
+ err := selectVal(e, &h, query, args...)
+ if err != nil && err != sql.ErrNoRows {
+ return 0, err
+ }
+ return h, nil
+}
+
+// SelectNullFloat executes the given query, which should be a SELECT statement for a single
+// float column, and returns the value of the first row returned. If no rows are
+// found, the empty sql.NullInt64 value is returned.
+func SelectNullFloat(e SqlExecutor, query string, args ...interface{}) (sql.NullFloat64, error) {
+ var h sql.NullFloat64
+ err := selectVal(e, &h, query, args...)
+ if err != nil && err != sql.ErrNoRows {
+ return h, err
+ }
+ return h, nil
+}
+
+// SelectStr executes the given query, which should be a SELECT statement for a single
+// char/varchar column, and returns the value of the first row returned. If no rows are
+// found, an empty string is returned.
+func SelectStr(e SqlExecutor, query string, args ...interface{}) (string, error) {
+ var h string
+ err := selectVal(e, &h, query, args...)
+ if err != nil && err != sql.ErrNoRows {
+ return "", err
+ }
+ return h, nil
+}
+
+// SelectNullStr executes the given query, which should be a SELECT
+// statement for a single char/varchar column, and returns the value
+// of the first row returned. If no rows are found, the empty
+// sql.NullString is returned.
+func SelectNullStr(e SqlExecutor, query string, args ...interface{}) (sql.NullString, error) {
+ var h sql.NullString
+ err := selectVal(e, &h, query, args...)
+ if err != nil && err != sql.ErrNoRows {
+ return h, err
+ }
+ return h, nil
+}
+
+// SelectOne executes the given query (which should be a SELECT statement)
+// and binds the result to holder, which must be a pointer.
+//
+// If no row is found, an error (sql.ErrNoRows specifically) will be returned
+//
+// If more than one row is found, an error will be returned.
+//
+func SelectOne(m *DbMap, e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
+ t := reflect.TypeOf(holder)
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ } else {
+ return fmt.Errorf("gorp: SelectOne holder must be a pointer, but got: %t", holder)
+ }
+
+ // Handle pointer to pointer
+ isptr := false
+ if t.Kind() == reflect.Ptr {
+ isptr = true
+ t = t.Elem()
+ }
+
+ if t.Kind() == reflect.Struct {
+ var nonFatalErr error
+
+ list, err := hookedselect(m, e, holder, query, args...)
+ if err != nil {
+ if !NonFatalError(err) {
+ return err
+ }
+ nonFatalErr = err
+ }
+
+ dest := reflect.ValueOf(holder)
+ if isptr {
+ dest = dest.Elem()
+ }
+
+ if list != nil && len(list) > 0 {
+ // check for multiple rows
+ if len(list) > 1 {
+ return fmt.Errorf("gorp: multiple rows returned for: %s - %v", query, args)
+ }
+
+ // Initialize if nil
+ if dest.IsNil() {
+ dest.Set(reflect.New(t))
+ }
+
+ // only one row found
+ src := reflect.ValueOf(list[0])
+ dest.Elem().Set(src.Elem())
+ } else {
+ // No rows found, return a proper error.
+ return sql.ErrNoRows
+ }
+
+ return nonFatalErr
+ }
+
+ return selectVal(e, holder, query, args...)
+}
+
+func selectVal(e SqlExecutor, holder interface{}, query string, args ...interface{}) error {
+ if len(args) == 1 {
+ switch m := e.(type) {
+ case *DbMap:
+ query, args = maybeExpandNamedQuery(m, query, args)
+ case *Transaction:
+ query, args = maybeExpandNamedQuery(m.dbmap, query, args)
+ }
+ }
+ rows, err := e.query(query, args...)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ if !rows.Next() {
+ return sql.ErrNoRows
+ }
+
+ return rows.Scan(holder)
+}
+
+///////////////
+
+func hookedselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
+ args ...interface{}) ([]interface{}, error) {
+
+ var nonFatalErr error
+
+ list, err := rawselect(m, exec, i, query, args...)
+ if err != nil {
+ if !NonFatalError(err) {
+ return nil, err
+ }
+ nonFatalErr = err
+ }
+
+ // Determine where the results are: written to i, or returned in list
+ if t, _ := toSliceType(i); t == nil {
+ for _, v := range list {
+ if v, ok := v.(HasPostGet); ok {
+ err := v.PostGet(exec)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ } else {
+ resultsValue := reflect.Indirect(reflect.ValueOf(i))
+ for i := 0; i < resultsValue.Len(); i++ {
+ if v, ok := resultsValue.Index(i).Interface().(HasPostGet); ok {
+ err := v.PostGet(exec)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+ return list, nonFatalErr
+}
+
+func rawselect(m *DbMap, exec SqlExecutor, i interface{}, query string,
+ args ...interface{}) ([]interface{}, error) {
+ var (
+ appendToSlice = false // Write results to i directly?
+ intoStruct = true // Selecting into a struct?
+ pointerElements = true // Are the slice elements pointers (vs values)?
+ )
+
+ var nonFatalErr error
+
+ // get type for i, verifying it's a supported destination
+ t, err := toType(i)
+ if err != nil {
+ var err2 error
+ if t, err2 = toSliceType(i); t == nil {
+ if err2 != nil {
+ return nil, err2
+ }
+ return nil, err
+ }
+ pointerElements = t.Kind() == reflect.Ptr
+ if pointerElements {
+ t = t.Elem()
+ }
+ appendToSlice = true
+ intoStruct = t.Kind() == reflect.Struct
+ }
+
+ // If the caller supplied a single struct/map argument, assume a "named
+ // parameter" query. Extract the named arguments from the struct/map, create
+ // the flat arg slice, and rewrite the query to use the dialect's placeholder.
+ if len(args) == 1 {
+ query, args = maybeExpandNamedQuery(m, query, args)
+ }
+
+ // Run the query
+ rows, err := exec.query(query, args...)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ // Fetch the column names as returned from db
+ cols, err := rows.Columns()
+ if err != nil {
+ return nil, err
+ }
+
+ if !intoStruct && len(cols) > 1 {
+ return nil, fmt.Errorf("gorp: select into non-struct slice requires 1 column, got %d", len(cols))
+ }
+
+ var colToFieldIndex [][]int
+ if intoStruct {
+ if colToFieldIndex, err = columnToFieldIndex(m, t, cols); err != nil {
+ if !NonFatalError(err) {
+ return nil, err
+ }
+ nonFatalErr = err
+ }
+ }
+
+ conv := m.TypeConverter
+
+ // Add results to one of these two slices.
+ var (
+ list = make([]interface{}, 0)
+ sliceValue = reflect.Indirect(reflect.ValueOf(i))
+ )
+
+ for {
+ if !rows.Next() {
+ // if error occured return rawselect
+ if rows.Err() != nil {
+ return nil, rows.Err()
+ }
+ // time to exit from outer "for" loop
+ break
+ }
+ v := reflect.New(t)
+ dest := make([]interface{}, len(cols))
+
+ custScan := make([]CustomScanner, 0)
+
+ for x := range cols {
+ f := v.Elem()
+ if intoStruct {
+ index := colToFieldIndex[x]
+ if index == nil {
+ // this field is not present in the struct, so create a dummy
+ // value for rows.Scan to scan into
+ var dummy sql.RawBytes
+ dest[x] = &dummy
+ continue
+ }
+ f = f.FieldByIndex(index)
+ }
+ target := f.Addr().Interface()
+ if conv != nil {
+ scanner, ok := conv.FromDb(target)
+ if ok {
+ target = scanner.Holder
+ custScan = append(custScan, scanner)
+ }
+ }
+ dest[x] = target
+ }
+
+ err = rows.Scan(dest...)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, c := range custScan {
+ err = c.Bind()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if appendToSlice {
+ if !pointerElements {
+ v = v.Elem()
+ }
+ sliceValue.Set(reflect.Append(sliceValue, v))
+ } else {
+ list = append(list, v.Interface())
+ }
+ }
+
+ if appendToSlice && sliceValue.IsNil() {
+ sliceValue.Set(reflect.MakeSlice(sliceValue.Type(), 0, 0))
+ }
+
+ return list, nonFatalErr
+}
+
+// maybeExpandNamedQuery checks the given arg to see if it's eligible to be used
+// as input to a named query. If so, it rewrites the query to use
+// dialect-dependent bindvars and instantiates the corresponding slice of
+// parameters by extracting data from the map / struct.
+// If not, returns the input values unchanged.
+func maybeExpandNamedQuery(m *DbMap, query string, args []interface{}) (string, []interface{}) {
+ arg := reflect.ValueOf(args[0])
+ for arg.Kind() == reflect.Ptr {
+ arg = arg.Elem()
+ }
+ switch {
+ case arg.Kind() == reflect.Map && arg.Type().Key().Kind() == reflect.String:
+ return expandNamedQuery(m, query, func(key string) reflect.Value {
+ return arg.MapIndex(reflect.ValueOf(key))
+ })
+ // #84 - ignore time.Time structs here - there may be a cleaner way to do this
+ case arg.Kind() == reflect.Struct && !(arg.Type().PkgPath() == "time" && arg.Type().Name() == "Time"):
+ return expandNamedQuery(m, query, arg.FieldByName)
+ }
+ return query, args
+}
+
+var keyRegexp = regexp.MustCompile(`:[[:word:]]+`)
+
+// expandNamedQuery accepts a query with placeholders of the form ":key", and a
+// single arg of Kind Struct or Map[string]. It returns the query with the
+// dialect's placeholders, and a slice of args ready for positional insertion
+// into the query.
+func expandNamedQuery(m *DbMap, query string, keyGetter func(key string) reflect.Value) (string, []interface{}) {
+ var (
+ n int
+ args []interface{}
+ )
+ return keyRegexp.ReplaceAllStringFunc(query, func(key string) string {
+ val := keyGetter(key[1:])
+ if !val.IsValid() {
+ return key
+ }
+ args = append(args, val.Interface())
+ newVar := m.Dialect.BindVar(n)
+ n++
+ return newVar
+ }), args
+}
+
+func columnToFieldIndex(m *DbMap, t reflect.Type, cols []string) ([][]int, error) {
+ colToFieldIndex := make([][]int, len(cols))
+
+ // check if type t is a mapped table - if so we'll
+ // check the table for column aliasing below
+ tableMapped := false
+ table := tableOrNil(m, t)
+ if table != nil {
+ tableMapped = true
+ }
+
+ // Loop over column names and find field in i to bind to
+ // based on column name. all returned columns must match
+ // a field in the i struct
+ missingColNames := []string{}
+ for x := range cols {
+ colName := strings.ToLower(cols[x])
+ field, found := t.FieldByNameFunc(func(fieldName string) bool {
+ field, _ := t.FieldByName(fieldName)
+ fieldName = field.Tag.Get("db")
+
+ if fieldName == "-" {
+ return false
+ } else if fieldName == "" {
+ fieldName = field.Name
+ }
+ if tableMapped {
+ colMap := colMapOrNil(table, fieldName)
+ if colMap != nil {
+ fieldName = colMap.ColumnName
+ }
+ }
+ return colName == strings.ToLower(fieldName)
+ })
+ if found {
+ colToFieldIndex[x] = field.Index
+ }
+ if colToFieldIndex[x] == nil {
+ missingColNames = append(missingColNames, colName)
+ }
+ }
+ if len(missingColNames) > 0 {
+ return colToFieldIndex, &NoFieldInTypeError{
+ TypeName: t.Name(),
+ MissingColNames: missingColNames,
+ }
+ }
+ return colToFieldIndex, nil
+}
+
+func fieldByName(val reflect.Value, fieldName string) *reflect.Value {
+ // try to find field by exact match
+ f := val.FieldByName(fieldName)
+
+ if f != zeroVal {
+ return &f
+ }
+
+ // try to find by case insensitive match - only the Postgres driver
+ // seems to require this - in the case where columns are aliased in the sql
+ fieldNameL := strings.ToLower(fieldName)
+ fieldCount := val.NumField()
+ t := val.Type()
+ for i := 0; i < fieldCount; i++ {
+ sf := t.Field(i)
+ if strings.ToLower(sf.Name) == fieldNameL {
+ f := val.Field(i)
+ return &f
+ }
+ }
+
+ return nil
+}
+
+// toSliceType returns the element type of the given object, if the object is a
+// "*[]*Element" or "*[]Element". If not, returns nil.
+// err is returned if the user was trying to pass a pointer-to-slice but failed.
+func toSliceType(i interface{}) (reflect.Type, error) {
+ t := reflect.TypeOf(i)
+ if t.Kind() != reflect.Ptr {
+ // If it's a slice, return a more helpful error message
+ if t.Kind() == reflect.Slice {
+ return nil, fmt.Errorf("gorp: Cannot SELECT into a non-pointer slice: %v", t)
+ }
+ return nil, nil
+ }
+ if t = t.Elem(); t.Kind() != reflect.Slice {
+ return nil, nil
+ }
+ return t.Elem(), nil
+}
+
+func toType(i interface{}) (reflect.Type, error) {
+ t := reflect.TypeOf(i)
+
+ // If a Pointer to a type, follow
+ for t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+
+ if t.Kind() != reflect.Struct {
+ return nil, fmt.Errorf("gorp: Cannot SELECT into this type: %v", reflect.TypeOf(i))
+ }
+ return t, nil
+}
+
+func get(m *DbMap, exec SqlExecutor, i interface{},
+ keys ...interface{}) (interface{}, error) {
+
+ t, err := toType(i)
+ if err != nil {
+ return nil, err
+ }
+
+ table, err := m.TableFor(t, true)
+ if err != nil {
+ return nil, err
+ }
+
+ plan := table.bindGet()
+
+ v := reflect.New(t)
+ dest := make([]interface{}, len(plan.argFields))
+
+ conv := m.TypeConverter
+ custScan := make([]CustomScanner, 0)
+
+ for x, fieldName := range plan.argFields {
+ f := v.Elem().FieldByName(fieldName)
+ target := f.Addr().Interface()
+ if conv != nil {
+ scanner, ok := conv.FromDb(target)
+ if ok {
+ target = scanner.Holder
+ custScan = append(custScan, scanner)
+ }
+ }
+ dest[x] = target
+ }
+
+ row := exec.queryRow(plan.query, keys...)
+ err = row.Scan(dest...)
+ if err != nil {
+ if err == sql.ErrNoRows {
+ err = nil
+ }
+ return nil, err
+ }
+
+ for _, c := range custScan {
+ err = c.Bind()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if v, ok := v.Interface().(HasPostGet); ok {
+ err := v.PostGet(exec)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return v.Interface(), nil
+}
+
+func delete(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
+ count := int64(0)
+ for _, ptr := range list {
+ table, elem, err := m.tableForPointer(ptr, true)
+ if err != nil {
+ return -1, err
+ }
+
+ eval := elem.Addr().Interface()
+ if v, ok := eval.(HasPreDelete); ok {
+ err = v.PreDelete(exec)
+ if err != nil {
+ return -1, err
+ }
+ }
+
+ bi, err := table.bindDelete(elem)
+ if err != nil {
+ return -1, err
+ }
+
+ res, err := exec.Exec(bi.query, bi.args...)
+ if err != nil {
+ return -1, err
+ }
+ rows, err := res.RowsAffected()
+ if err != nil {
+ return -1, err
+ }
+
+ if rows == 0 && bi.existingVersion > 0 {
+ return lockError(m, exec, table.TableName,
+ bi.existingVersion, elem, bi.keys...)
+ }
+
+ count += rows
+
+ if v, ok := eval.(HasPostDelete); ok {
+ err := v.PostDelete(exec)
+ if err != nil {
+ return -1, err
+ }
+ }
+ }
+
+ return count, nil
+}
+
+func update(m *DbMap, exec SqlExecutor, list ...interface{}) (int64, error) {
+ count := int64(0)
+ for _, ptr := range list {
+ table, elem, err := m.tableForPointer(ptr, true)
+ if err != nil {
+ return -1, err
+ }
+
+ eval := elem.Addr().Interface()
+ if v, ok := eval.(HasPreUpdate); ok {
+ err = v.PreUpdate(exec)
+ if err != nil {
+ return -1, err
+ }
+ }
+
+ bi, err := table.bindUpdate(elem)
+ if err != nil {
+ return -1, err
+ }
+
+ res, err := exec.Exec(bi.query, bi.args...)
+ if err != nil {
+ return -1, err
+ }
+
+ rows, err := res.RowsAffected()
+ if err != nil {
+ return -1, err
+ }
+
+ if rows == 0 && bi.existingVersion > 0 {
+ return lockError(m, exec, table.TableName,
+ bi.existingVersion, elem, bi.keys...)
+ }
+
+ if bi.versField != "" {
+ elem.FieldByName(bi.versField).SetInt(bi.existingVersion + 1)
+ }
+
+ count += rows
+
+ if v, ok := eval.(HasPostUpdate); ok {
+ err = v.PostUpdate(exec)
+ if err != nil {
+ return -1, err
+ }
+ }
+ }
+ return count, nil
+}
+
+func insert(m *DbMap, exec SqlExecutor, list ...interface{}) error {
+ for _, ptr := range list {
+ table, elem, err := m.tableForPointer(ptr, false)
+ if err != nil {
+ return err
+ }
+
+ eval := elem.Addr().Interface()
+ if v, ok := eval.(HasPreInsert); ok {
+ err := v.PreInsert(exec)
+ if err != nil {
+ return err
+ }
+ }
+
+ bi, err := table.bindInsert(elem)
+ if err != nil {
+ return err
+ }
+
+ if bi.autoIncrIdx > -1 {
+ f := elem.FieldByName(bi.autoIncrFieldName)
+ switch inserter := m.Dialect.(type) {
+ case IntegerAutoIncrInserter:
+ id, err := inserter.InsertAutoIncr(exec, bi.query, bi.args...)
+ if err != nil {
+ return err
+ }
+ k := f.Kind()
+ if (k == reflect.Int) || (k == reflect.Int16) || (k == reflect.Int32) || (k == reflect.Int64) {
+ f.SetInt(id)
+ } else if (k == reflect.Uint) || (k == reflect.Uint16) || (k == reflect.Uint32) || (k == reflect.Uint64) {
+ f.SetUint(uint64(id))
+ } else {
+ return fmt.Errorf("gorp: Cannot set autoincrement value on non-Int field. SQL=%s autoIncrIdx=%d autoIncrFieldName=%s", bi.query, bi.autoIncrIdx, bi.autoIncrFieldName)
+ }
+ case TargetedAutoIncrInserter:
+ err := inserter.InsertAutoIncrToTarget(exec, bi.query, f.Addr().Interface(), bi.args...)
+ if err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("gorp: Cannot use autoincrement fields on dialects that do not implement an autoincrementing interface")
+ }
+ } else {
+ _, err := exec.Exec(bi.query, bi.args...)
+ if err != nil {
+ return err
+ }
+ }
+
+ if v, ok := eval.(HasPostInsert); ok {
+ err := v.PostInsert(exec)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func lockError(m *DbMap, exec SqlExecutor, tableName string,
+ existingVer int64, elem reflect.Value,
+ keys ...interface{}) (int64, error) {
+
+ existing, err := get(m, exec, elem.Interface(), keys...)
+ if err != nil {
+ return -1, err
+ }
+
+ ole := OptimisticLockError{tableName, keys, true, existingVer}
+ if existing == nil {
+ ole.RowExists = false
+ }
+ return -1, ole
+}
+
+// PostUpdate() will be executed after the GET statement.
+type HasPostGet interface {
+ PostGet(SqlExecutor) error
+}
+
+// PostUpdate() will be executed after the DELETE statement
+type HasPostDelete interface {
+ PostDelete(SqlExecutor) error
+}
+
+// PostUpdate() will be executed after the UPDATE statement
+type HasPostUpdate interface {
+ PostUpdate(SqlExecutor) error
+}
+
+// PostInsert() will be executed after the INSERT statement
+type HasPostInsert interface {
+ PostInsert(SqlExecutor) error
+}
+
+// PreDelete() will be executed before the DELETE statement.
+type HasPreDelete interface {
+ PreDelete(SqlExecutor) error
+}
+
+// PreUpdate() will be executed before UPDATE statement.
+type HasPreUpdate interface {
+ PreUpdate(SqlExecutor) error
+}
+
+// PreInsert() will be executed before INSERT statement.
+type HasPreInsert interface {
+ PreInsert(SqlExecutor) error
+}
diff --git a/vendor/gopkg.in/gorp.v1/test_all.sh b/vendor/gopkg.in/gorp.v1/test_all.sh
new file mode 100755
index 0000000..f870b39
--- /dev/null
+++ b/vendor/gopkg.in/gorp.v1/test_all.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# on macs, you may need to:
+# export GOBUILDFLAG=-ldflags -linkmode=external
+
+set -e
+
+export GORP_TEST_DSN=gorptest/gorptest/gorptest
+export GORP_TEST_DIALECT=mysql
+go test $GOBUILDFLAG .
+
+export GORP_TEST_DSN=gorptest:gorptest@/gorptest
+export GORP_TEST_DIALECT=gomysql
+go test $GOBUILDFLAG .
+
+export GORP_TEST_DSN="user=gorptest password=gorptest dbname=gorptest sslmode=disable"
+export GORP_TEST_DIALECT=postgres
+go test $GOBUILDFLAG .
+
+export GORP_TEST_DSN=/tmp/gorptest.bin
+export GORP_TEST_DIALECT=sqlite
+go test $GOBUILDFLAG .
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 653dfd7..fffa2aa 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -655,6 +655,18 @@
"revisionTime": "2018-06-12T22:21:13Z"
},
{
+ "checksumSHA1": "N11d95+f/kzeTCb408INaeNcPCE=",
+ "path": "github.com/rubenv/sql-migrate",
+ "revision": "3f452fc0ebebbb784fdab91f7bc79a31dcacab5c",
+ "revisionTime": "2018-07-04T11:13:56Z"
+ },
+ {
+ "checksumSHA1": "rkHieYOGBsxJaDbC7vl/e4KSatw=",
+ "path": "github.com/rubenv/sql-migrate/sqlparse",
+ "revision": "3f452fc0ebebbb784fdab91f7bc79a31dcacab5c",
+ "revisionTime": "2018-07-04T11:13:56Z"
+ },
+ {
"checksumSHA1": "6JP37UqrI0H80Gpk0Y2P+KXgn5M=",
"path": "github.com/ryanuber/go-glob",
"revision": "256dc444b735e061061cf46c809487313d5b0065",
@@ -1339,6 +1351,12 @@
"revisionTime": "2018-06-20T19:19:01Z"
},
{
+ "checksumSHA1": "xunNOgG8P6Xh4SM+LPQ1zMDXa8Q=",
+ "path": "gopkg.in/gorp.v1",
+ "revision": "c87af80f3cc5036b55b83d77171e156791085e2e",
+ "revisionTime": "2015-02-04T08:55:30Z"
+ },
+ {
"checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=",
"path": "gopkg.in/yaml.v2",
"revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183",