diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
new file mode 100644
index 0000000..83bbd47
--- /dev/null
+++ b/Godeps/Godeps.json
@@ -0,0 +1,58 @@
+{
+ "ImportPath": "github.com/thermokarst/bactdb",
+ "GoVersion": "go1.4.1",
+ "Deps": [
+ {
+ "ImportPath": "github.com/DavidHuie/gomigrate",
+ "Rev": "7883dd76040debc099811feadd58425b1495103d"
+ },
+ {
+ "ImportPath": "github.com/codegangsta/cli",
+ "Comment": "1.2.0-62-gbf4a526",
+ "Rev": "bf4a526f48af7badd25d2cb02d587e1b01be3b50"
+ },
+ {
+ "ImportPath": "github.com/dgrijalva/jwt-go",
+ "Comment": "v2.2.0-11-g7c18dce",
+ "Rev": "7c18dce7b8ff32db34d4b7054d488db656d75cc3"
+ },
+ {
+ "ImportPath": "github.com/google/go-querystring/query",
+ "Rev": "d8840cbb2baa915f4836edda4750050a2c0b7aea"
+ },
+ {
+ "ImportPath": "github.com/gorilla/context",
+ "Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
+ },
+ {
+ "ImportPath": "github.com/gorilla/mux",
+ "Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf"
+ },
+ {
+ "ImportPath": "github.com/gorilla/schema",
+ "Rev": "ad8849d14e5cdf1f4423b1f0fc53827ebbdd7477"
+ },
+ {
+ "ImportPath": "github.com/jmoiron/modl",
+ "Rev": "b0cda508e4027c836cf78bd482a3edd0458b6d2f"
+ },
+ {
+ "ImportPath": "github.com/jmoiron/sqlx",
+ "Comment": "sqlx-v1.0-67-g69738bd",
+ "Rev": "69738bd209812c5a381786011aff17944a384554"
+ },
+ {
+ "ImportPath": "github.com/lib/pq",
+ "Comment": "go1.0-cutoff-13-g19eeca3",
+ "Rev": "19eeca3e30d2577b1761db471ec130810e67f532"
+ },
+ {
+ "ImportPath": "golang.org/x/crypto/bcrypt",
+ "Rev": "632d287f9f3f54b09809eebbd3cacbcb00b9f2fc"
+ },
+ {
+ "ImportPath": "golang.org/x/crypto/blowfish",
+ "Rev": "632d287f9f3f54b09809eebbd3cacbcb00b9f2fc"
+ }
+ ]
+}
diff --git a/Godeps/Readme b/Godeps/Readme
new file mode 100644
index 0000000..4cdaa53
--- /dev/null
+++ b/Godeps/Readme
@@ -0,0 +1,5 @@
+This directory tree is generated automatically by godep.
+
+Please do not edit.
+
+See https://github.com/tools/godep for more information.
diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore
new file mode 100644
index 0000000..f037d68
--- /dev/null
+++ b/Godeps/_workspace/.gitignore
@@ -0,0 +1,2 @@
+/pkg
+/bin
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/.gitignore b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/.gitignore
new file mode 100644
index 0000000..c56069f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/.gitignore
@@ -0,0 +1 @@
+*.test
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/.travis.yml b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/.travis.yml
new file mode 100644
index 0000000..2745f77
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+go: 1.3
+addons:
+ postgresql: "9.3"
+before_script:
+ - psql -c 'create database gomigrate;' -U postgres
+ - mysql -uroot -e "CREATE USER 'gomigrate'@'localhost' IDENTIFIED BY 'password';"
+ - mysql -uroot -e "GRANT ALL PRIVILEGES ON * . * TO 'gomigrate'@'localhost';"
+ - mysql -uroot -e "CREATE DATABASE gomigrate;"
+ - go get github.com/lib/pq
+ - go get github.com/go-sql-driver/mysql
+script:
+ - DB=pg go test
+ - DB=mysql go test
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/LICENSE.txt b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/LICENSE.txt
new file mode 100644
index 0000000..5412e76
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2014 David Huie
+
+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/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/README.md b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/README.md
new file mode 100644
index 0000000..e76d305
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/README.md
@@ -0,0 +1,72 @@
+# gomigrate
+
+[](https://travis-ci.org/DavidHuie/gomigrate)
+
+A SQL database migration toolkit in Golang.
+
+## Supported databases
+
+- PostgreSQL
+- MariaDB
+- MySQL
+
+## Usage
+
+First import the package:
+
+```go
+import "github.com/DavidHuie/gomigrate"
+```
+
+Given a `database/sql` database connection to a PostgreSQL database, `db`,
+and a directory to migration files, create a migrator:
+
+```go
+migrator := gomigrate.NewMigrator(db, gomigrate.Postgres{}, "./migrations")
+```
+
+To migrate the database, run:
+
+```go
+err := migrator.Migrate()
+```
+
+To rollback the last migration, run:
+
+```go
+err := migrator.Rollback()
+```
+
+## Migration files
+
+Migration files need to follow a standard format and must be present
+in the same directory. Given "up" and "down" steps for a migration,
+create a file for each by following this template:
+
+```
+{{ id }}_{{ name }}_{{ "up" or "down" }}.sql
+```
+
+For a given migration, the `id` and `name` fields must be the same.
+The id field is an integer that corresponds to the order in which
+the migration should run relative to the other migrations.
+
+### Example
+
+If I'm trying to add a "users" table to the database, I would create
+the following two files:
+
+#### 1_add_users_table_up.sql
+
+```
+CREATE TABLE users();
+```
+
+#### 1_add_users_table_down.sql
+```
+DROP TABLE users;
+```
+
+## Copyright
+
+Copyright (c) 2014 David Huie. See LICENSE.txt for further details.
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/db.go b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/db.go
new file mode 100644
index 0000000..11f5605
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/db.go
@@ -0,0 +1,70 @@
+package gomigrate
+
+type Migratable interface {
+ SelectMigrationTableSql() string
+ CreateMigrationTableSql() string
+ GetMigrationSql() string
+ MigrationLogInsertSql() string
+ MigrationLogDeleteSql() string
+}
+
+// POSTGRES
+
+type Postgres struct{}
+
+func (p Postgres) SelectMigrationTableSql() string {
+ return "SELECT tablename FROM pg_catalog.pg_tables WHERE tablename = $1"
+}
+
+func (p Postgres) CreateMigrationTableSql() string {
+ return `CREATE TABLE gomigrate (
+ id SERIAL PRIMARY KEY,
+ migration_id BIGINT UNIQUE NOT NULL
+ )`
+}
+
+func (p Postgres) GetMigrationSql() string {
+ return `SELECT migration_id FROM gomigrate WHERE migration_id = $1`
+}
+
+func (p Postgres) MigrationLogInsertSql() string {
+ return "INSERT INTO gomigrate (migration_id) values ($1)"
+}
+
+func (p Postgres) MigrationLogDeleteSql() string {
+ return "DELETE FROM gomigrate WHERE migration_id = $1"
+}
+
+// MYSQL
+
+type Mysql struct{}
+
+func (m Mysql) SelectMigrationTableSql() string {
+ return "SELECT table_name FROM information_schema.tables WHERE table_name = ?"
+}
+
+func (m Mysql) CreateMigrationTableSql() string {
+ return `CREATE TABLE gomigrate (
+ id INT NOT NULL AUTO_INCREMENT,
+ migration_id BIGINT NOT NULL UNIQUE,
+ PRIMARY KEY (id)
+ ) ENGINE=MyISAM`
+}
+
+func (m Mysql) GetMigrationSql() string {
+ return `SELECT migration_id FROM gomigrate WHERE migration_id = ?`
+}
+
+func (m Mysql) MigrationLogInsertSql() string {
+ return "INSERT INTO gomigrate (migration_id) values (?)"
+}
+
+func (m Mysql) MigrationLogDeleteSql() string {
+ return "DELETE FROM gomigrate WHERE migration_id = ?"
+}
+
+// MARIADB
+
+type Mariadb struct {
+ Mysql
+}
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/gomigrate.go b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/gomigrate.go
new file mode 100644
index 0000000..e224fd7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/gomigrate.go
@@ -0,0 +1,327 @@
+// A simple database migrator for PostgreSQL.
+
+package gomigrate
+
+import (
+ "bytes"
+ "database/sql"
+ "errors"
+ "io/ioutil"
+ "log"
+ "path/filepath"
+ "sort"
+)
+
+type migrationType string
+
+const (
+ migrationTableName = "gomigrate"
+ upMigration = migrationType("up")
+ downMigration = migrationType("down")
+)
+
+var (
+ InvalidMigrationFile = errors.New("Invalid migration file")
+ InvalidMigrationPair = errors.New("Invalid pair of migration files")
+ InvalidMigrationsPath = errors.New("Invalid migrations path")
+ InvalidMigrationType = errors.New("Invalid migration type")
+ NoActiveMigrations = errors.New("No active migrations to rollback")
+)
+
+type Migrator struct {
+ DB *sql.DB
+ MigrationsPath string
+ dbAdapter Migratable
+ migrations map[uint64]*Migration
+}
+
+// Returns true if the migration table already exists.
+func (m *Migrator) MigrationTableExists() (bool, error) {
+ row := m.DB.QueryRow(m.dbAdapter.SelectMigrationTableSql(), migrationTableName)
+ var tableName string
+ err := row.Scan(&tableName)
+ if err == sql.ErrNoRows {
+ log.Print("Migrations table not found")
+ return false, nil
+ }
+ if err != nil {
+ log.Printf("Error checking for migration table: %v", err)
+ return false, err
+ }
+ log.Print("Migrations table found")
+ return true, nil
+}
+
+// Creates the migrations table if it doesn't exist.
+func (m *Migrator) CreateMigrationsTable() error {
+ _, err := m.DB.Query(m.dbAdapter.CreateMigrationTableSql())
+ if err != nil {
+ log.Fatalf("Error creating migrations table: %v", err)
+ }
+
+ log.Printf("Created migrations table: %s", migrationTableName)
+
+ return nil
+}
+
+// Returns a new migrator.
+func NewMigrator(db *sql.DB, adapter Migratable, migrationsPath string) (*Migrator, error) {
+ // Normalize the migrations path.
+ path := []byte(migrationsPath)
+ pathLength := len(path)
+ if path[pathLength-1] != '/' {
+ path = append(path, '/')
+ }
+
+ log.Printf("Migrations path: %s", path)
+
+ migrator := Migrator{
+ db,
+ string(path),
+ adapter,
+ make(map[uint64]*Migration),
+ }
+
+ // Create the migrations table if it doesn't exist.
+ tableExists, err := migrator.MigrationTableExists()
+ if err != nil {
+ return nil, err
+ }
+ if !tableExists {
+ if err := migrator.CreateMigrationsTable(); err != nil {
+ return nil, err
+ }
+ }
+
+ // Get all metadata from the database.
+ if err := migrator.fetchMigrations(); err != nil {
+ return nil, err
+ }
+ if err := migrator.getMigrationStatuses(); err != nil {
+ return nil, err
+ }
+
+ return &migrator, nil
+}
+
+// Populates a migrator with a sorted list of migrations from the file system.
+func (m *Migrator) fetchMigrations() error {
+ pathGlob := append([]byte(m.MigrationsPath), []byte("*")...)
+
+ matches, err := filepath.Glob(string(pathGlob))
+ if err != nil {
+ log.Fatalf("Error while globbing migrations: %v", err)
+ }
+
+ for _, match := range matches {
+ num, migrationType, name, err := parseMigrationPath(match)
+ if err != nil {
+ log.Printf("Invalid migration file found: %s", match)
+ continue
+ }
+
+ log.Printf("Migration file found: %s", match)
+
+ migration, ok := m.migrations[num]
+ if !ok {
+ migration = &Migration{Id: num, Name: name, Status: Inactive}
+ m.migrations[num] = migration
+ }
+ if migrationType == upMigration {
+ migration.UpPath = match
+ } else {
+ migration.DownPath = match
+ }
+ }
+
+ // Validate each migration.
+ for _, migration := range m.migrations {
+ if !migration.valid() {
+ path := migration.UpPath
+ if path == "" {
+ path = migration.DownPath
+ }
+ log.Printf("Invalid migration pair for path: %s", path)
+ return InvalidMigrationPair
+ }
+ }
+
+ log.Printf("Migrations file pairs found: %v", len(m.migrations))
+
+ return nil
+}
+
+// Queries the migration table to determine the status of each
+// migration.
+func (m *Migrator) getMigrationStatuses() error {
+ for _, migration := range m.migrations {
+ row := m.DB.QueryRow(m.dbAdapter.GetMigrationSql(), migration.Id)
+ var mid uint64
+ err := row.Scan(&mid)
+ if err == sql.ErrNoRows {
+ continue
+ }
+ if err != nil {
+ log.Printf(
+ "Error getting migration status for %s: %v",
+ migration.Name,
+ err,
+ )
+ return err
+ }
+ migration.Status = Active
+ }
+ return nil
+}
+
+// Returns a sorted list of migration ids for a given status. -1 returns
+// all migrations.
+func (m *Migrator) Migrations(status int) []*Migration {
+ // Sort all migration ids.
+ ids := make([]uint64, 0)
+ for id, _ := range m.migrations {
+ ids = append(ids, id)
+ }
+ sort.Sort(uint64slice(ids))
+
+ // Find ids for the given status.
+ migrations := make([]*Migration, 0)
+ for _, id := range ids {
+ migration := m.migrations[id]
+ if status == -1 || migration.Status == status {
+ migrations = append(migrations, migration)
+ }
+ }
+ return migrations
+}
+
+// Applies a single migration.
+func (m *Migrator) ApplyMigration(migration *Migration, mType migrationType) error {
+ var path string
+ if mType == upMigration {
+ path = migration.UpPath
+ } else if mType == downMigration {
+ path = migration.DownPath
+ } else {
+ return InvalidMigrationType
+ }
+
+ log.Printf("Applying migration: %s", path)
+
+ sql, err := ioutil.ReadFile(path)
+ if err != nil {
+ log.Printf("Error reading migration: %s", path)
+ return err
+ }
+ transaction, err := m.DB.Begin()
+ if err != nil {
+ log.Printf("Error opening transaction: %v", err)
+ return err
+ }
+
+ for n, subMigration := range splitMigrationString(string(sql)) {
+ if allWhitespace.Match([]byte(subMigration)) {
+ continue
+ }
+
+ log.Printf("Applying submigration: %v", n+1)
+
+ for _, line := range bytes.Split([]byte(subMigration), []byte("\n")) {
+ log.Printf("MIGRATION: %s", line)
+ }
+
+ // Perform the migration.
+ result, err := transaction.Exec(string(subMigration))
+ if err != nil {
+ log.Printf("Error executing migration: %v", err)
+ if rollbackErr := transaction.Rollback(); rollbackErr != nil {
+ log.Printf("Error rolling back transaction: %v", rollbackErr)
+ return rollbackErr
+ }
+ return err
+ }
+ if rowsAffected, err := result.RowsAffected(); err != nil {
+ log.Printf("Error getting rows affected: %v", err)
+ if rollbackErr := transaction.Rollback(); rollbackErr != nil {
+ log.Printf("Error rolling back transaction: %v", rollbackErr)
+ return rollbackErr
+ }
+ return err
+ } else {
+ log.Printf("Rows affected: %v", rowsAffected)
+ }
+ }
+
+ // Log the event.
+ if mType == upMigration {
+ _, err = transaction.Exec(
+ m.dbAdapter.MigrationLogInsertSql(),
+ migration.Id,
+ )
+ } else {
+ _, err = transaction.Exec(
+ m.dbAdapter.MigrationLogDeleteSql(),
+ migration.Id,
+ )
+ }
+ if err != nil {
+ log.Printf("Error logging migration: %v", err)
+ if rollbackErr := transaction.Rollback(); rollbackErr != nil {
+ log.Printf("Error rolling back transaction: %v", rollbackErr)
+ return rollbackErr
+ }
+ return err
+ }
+
+ // Commit and update the struct status.
+ if err := transaction.Commit(); err != nil {
+ log.Printf("Error commiting transaction: %v", err)
+ return err
+ }
+ if mType == upMigration {
+ migration.Status = Active
+ } else {
+ migration.Status = Inactive
+ }
+
+ return nil
+}
+
+// Applies all inactive migrations.
+func (m *Migrator) Migrate() error {
+ for _, migration := range m.Migrations(Inactive) {
+ if err := m.ApplyMigration(migration, upMigration); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Rolls back the last migration.
+func (m *Migrator) Rollback() error {
+ return m.RollbackN(1)
+}
+
+// Rolls back N migrations.
+func (m *Migrator) RollbackN(n int) error {
+ migrations := m.Migrations(Active)
+ if len(migrations) == 0 {
+ return NoActiveMigrations
+ }
+
+ last_migration := len(migrations) - 1 - n
+
+ for i := len(migrations) - 1; i != last_migration; i-- {
+ if err := m.ApplyMigration(migrations[i], downMigration); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Rolls back all migrations.
+func (m *Migrator) RollbackAll() error {
+ migrations := m.Migrations(Active)
+ return m.RollbackN(len(migrations))
+}
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/gomigrate_test.go b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/gomigrate_test.go
new file mode 100644
index 0000000..2c1fa23
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/gomigrate_test.go
@@ -0,0 +1,163 @@
+package gomigrate
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "os"
+ "testing"
+
+ _ "github.com/go-sql-driver/mysql"
+ _ "github.com/lib/pq"
+)
+
+var (
+ db *sql.DB
+ adapter Migratable
+)
+
+func GetMigrator(test string) *Migrator {
+ var suffix string
+ if os.Getenv("DB") == "pg" {
+ suffix = "pg"
+ } else {
+ suffix = "mysql"
+ }
+ path := fmt.Sprintf("test_migrations/%s_%s", test, suffix)
+ m, err := NewMigrator(db, adapter, path)
+ if err != nil {
+ panic(err)
+ }
+ return m
+}
+
+func TestNewMigrator(t *testing.T) {
+ m := GetMigrator("test1")
+ if len(m.migrations) != 1 {
+ t.Errorf("Invalid number of migrations detected")
+ }
+
+ migration := m.migrations[1]
+
+ if migration.Name != "test" {
+ t.Errorf("Invalid migration name detected: %s", migration.Name)
+ }
+ if migration.Id != 1 {
+ t.Errorf("Invalid migration num detected: %s", migration.Id)
+ }
+ if migration.Status != Inactive {
+ t.Errorf("Invalid migration num detected: %s", migration.Status)
+ }
+
+ cleanup()
+}
+
+func TestCreatingMigratorWhenTableExists(t *testing.T) {
+ // Create the table and populate it with a row.
+ _, err := db.Exec(adapter.CreateMigrationTableSql())
+ if err != nil {
+ t.Error(err)
+ }
+ _, err = db.Exec(adapter.MigrationLogInsertSql(), 123)
+ if err != nil {
+ t.Error(err)
+ }
+
+ GetMigrator("test1")
+
+ // Check that our row is still present.
+ row := db.QueryRow("select migration_id from gomigrate")
+ var id uint64
+ err = row.Scan(&id)
+ if err != nil {
+ t.Error(err)
+ }
+ if id != 123 {
+ t.Error("Invalid id found in database")
+ }
+ cleanup()
+}
+
+func TestMigrationAndRollback(t *testing.T) {
+ m := GetMigrator("test1")
+
+ if err := m.Migrate(); err != nil {
+ t.Error(err)
+ }
+
+ // Ensure that the migration ran.
+ row := db.QueryRow(
+ adapter.SelectMigrationTableSql(),
+ "test",
+ )
+ var tableName string
+ if err := row.Scan(&tableName); err != nil {
+ t.Error(err)
+ }
+ if tableName != "test" {
+ t.Errorf("Migration table not created")
+ }
+ // Ensure that the migrate status is correct.
+ row = db.QueryRow(
+ adapter.GetMigrationSql(),
+ 1,
+ )
+ var status int
+ if err := row.Scan(&status); err != nil {
+ t.Error(err)
+ }
+ if status != Active || m.migrations[1].Status != Active {
+ t.Error("Invalid status for migration")
+ }
+
+ if err := m.Rollback(); err != nil {
+ t.Error(err)
+ }
+
+ // Ensure that the down migration ran.
+ row = db.QueryRow(
+ adapter.SelectMigrationTableSql(),
+ "test",
+ )
+ err := row.Scan(&tableName)
+ if err != sql.ErrNoRows {
+ t.Errorf("Migration table should be deleted")
+ }
+
+ // Ensure that the migration log is missing.
+ row = db.QueryRow(
+ adapter.GetMigrationSql(),
+ 1,
+ )
+ if err := row.Scan(&status); err != sql.ErrNoRows {
+ t.Error(err)
+ }
+ if m.migrations[1].Status != Inactive {
+ t.Errorf("Invalid status for migration, %v", m.migrations[1].Status)
+ }
+
+ cleanup()
+}
+
+func cleanup() {
+ _, err := db.Exec("drop table gomigrate")
+ if err != nil {
+ panic(err)
+ }
+}
+
+func init() {
+ var err error
+ if os.Getenv("DB") == "pg" {
+ log.Print("Using postgres")
+ adapter = Postgres{}
+ db, err = sql.Open("postgres", "host=localhost dbname=gomigrate sslmode=disable")
+ if err != nil {
+ panic(err)
+ }
+ } else {
+ log.Print("Using mysql")
+ adapter = Mariadb{}
+ db, err = sql.Open("mysql", "gomigrate:password@/gomigrate")
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/migration.go b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/migration.go
new file mode 100644
index 0000000..90526c6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/migration.go
@@ -0,0 +1,26 @@
+// Holds metadata about a migration.
+
+package gomigrate
+
+// Migration statuses.
+const (
+ Inactive = iota
+ Active
+)
+
+// Holds configuration information for a given migration.
+type Migration struct {
+ DownPath string
+ Id uint64
+ Name string
+ Status int
+ UpPath string
+}
+
+// Performs a basic validation of a migration.
+func (m *Migration) valid() bool {
+ if m.Id != 0 && m.Name != "" && m.UpPath != "" && m.DownPath != "" {
+ return true
+ }
+ return false
+}
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_mysql/1_test_down.sql b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_mysql/1_test_down.sql
new file mode 100644
index 0000000..0b014e9
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_mysql/1_test_down.sql
@@ -0,0 +1 @@
+DROP TABLE test;
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_mysql/1_test_up.sql b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_mysql/1_test_up.sql
new file mode 100644
index 0000000..fe16ac4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_mysql/1_test_up.sql
@@ -0,0 +1,4 @@
+CREATE TABLE test (
+ id INT NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (id)
+) ENGINE=MyISAM;
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_pg/1_test_down.sql b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_pg/1_test_down.sql
new file mode 100644
index 0000000..a1807be
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_pg/1_test_down.sql
@@ -0,0 +1 @@
+drop table test;
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_pg/1_test_up.sql b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_pg/1_test_up.sql
new file mode 100644
index 0000000..24bb66f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/test_migrations/test1_pg/1_test_up.sql
@@ -0,0 +1 @@
+create table test();
diff --git a/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/utils.go b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/utils.go
new file mode 100644
index 0000000..c5267bc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/DavidHuie/gomigrate/utils.go
@@ -0,0 +1,63 @@
+package gomigrate
+
+import (
+ "path/filepath"
+ "regexp"
+ "strconv"
+)
+
+var (
+ upMigrationFile = regexp.MustCompile(`(\d+)_(\w+)_up\.sql`)
+ downMigrationFile = regexp.MustCompile(`(\d+)_(\w+)_down\.sql`)
+ subMigrationSplit = regexp.MustCompile(`;\s*`)
+ allWhitespace = regexp.MustCompile(`^\s*$`)
+)
+
+// Returns the migration number, type and base name, so 1, "up", "migration" from "01_migration_up.sql"
+func parseMigrationPath(path string) (uint64, migrationType, string, error) {
+ filebase := filepath.Base(path)
+
+ matches := upMigrationFile.FindAllSubmatch([]byte(filebase), -1)
+ if matches != nil {
+ return parseMatches(matches, upMigration)
+ }
+ matches = downMigrationFile.FindAllSubmatch([]byte(filebase), -1)
+ if matches != nil {
+ return parseMatches(matches, downMigration)
+ }
+
+ return 0, "", "", InvalidMigrationFile
+}
+
+// Parses matches given by a migration file regex.
+func parseMatches(matches [][][]byte, mType migrationType) (uint64, migrationType, string, error) {
+ num := matches[0][1]
+ name := matches[0][2]
+ parsedNum, err := strconv.ParseUint(string(num), 10, 64)
+ if err != nil {
+ return 0, "", "", err
+ }
+ return parsedNum, mType, string(name), nil
+}
+
+// Splits migration sql into different strings separated by a semi-colon.
+func splitMigrationString(sql string) []string {
+ return subMigrationSplit.Split(sql, -1)
+}
+
+// This type is used to sort migration ids.
+type uint64slice []uint64
+
+func (u uint64slice) Len() int {
+ return len(u)
+}
+
+func (u uint64slice) Less(a, b int) bool {
+ return u[a] < u[b]
+}
+
+func (u uint64slice) Swap(a, b int) {
+ tempA := u[a]
+ u[a] = u[b]
+ u[b] = tempA
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml b/Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml
new file mode 100644
index 0000000..baf46ab
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml
@@ -0,0 +1,6 @@
+language: go
+go: 1.1
+
+script:
+- go vet ./...
+- go test -v ./...
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE b/Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE
new file mode 100644
index 0000000..5515ccf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE
@@ -0,0 +1,21 @@
+Copyright (C) 2013 Jeremy Saenz
+All Rights Reserved.
+
+MIT LICENSE
+
+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/Godeps/_workspace/src/github.com/codegangsta/cli/README.md b/Godeps/_workspace/src/github.com/codegangsta/cli/README.md
new file mode 100644
index 0000000..0e8327b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/README.md
@@ -0,0 +1,298 @@
+[](https://travis-ci.org/codegangsta/cli)
+
+# cli.go
+cli.go is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
+
+You can view the API docs here:
+http://godoc.org/github.com/codegangsta/cli
+
+## Overview
+Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
+
+**This is where cli.go comes into play.** cli.go makes command line programming fun, organized, and expressive!
+
+## Installation
+Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html).
+
+To install `cli.go`, simply run:
+```
+$ go get github.com/codegangsta/cli
+```
+
+Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
+```
+export PATH=$PATH:$GOPATH/bin
+```
+
+## Getting Started
+One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`.
+
+``` go
+package main
+
+import (
+ "os"
+ "github.com/codegangsta/cli"
+)
+
+func main() {
+ cli.NewApp().Run(os.Args)
+}
+```
+
+This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
+
+``` go
+package main
+
+import (
+ "os"
+ "github.com/codegangsta/cli"
+)
+
+func main() {
+ app := cli.NewApp()
+ app.Name = "boom"
+ app.Usage = "make an explosive entrance"
+ app.Action = func(c *cli.Context) {
+ println("boom! I say!")
+ }
+
+ app.Run(os.Args)
+}
+```
+
+Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
+
+## Example
+
+Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
+
+Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
+
+``` go
+package main
+
+import (
+ "os"
+ "github.com/codegangsta/cli"
+)
+
+func main() {
+ app := cli.NewApp()
+ app.Name = "greet"
+ app.Usage = "fight the loneliness!"
+ app.Action = func(c *cli.Context) {
+ println("Hello friend!")
+ }
+
+ app.Run(os.Args)
+}
+```
+
+Install our command to the `$GOPATH/bin` directory:
+
+```
+$ go install
+```
+
+Finally run our new command:
+
+```
+$ greet
+Hello friend!
+```
+
+cli.go also generates some bitchass help text:
+```
+$ greet help
+NAME:
+ greet - fight the loneliness!
+
+USAGE:
+ greet [global options] command [command options] [arguments...]
+
+VERSION:
+ 0.0.0
+
+COMMANDS:
+ help, h Shows a list of commands or help for one command
+
+GLOBAL OPTIONS
+ --version Shows version information
+```
+
+### Arguments
+You can lookup arguments by calling the `Args` function on `cli.Context`.
+
+``` go
+...
+app.Action = func(c *cli.Context) {
+ println("Hello", c.Args()[0])
+}
+...
+```
+
+### Flags
+Setting and querying flags is simple.
+``` go
+...
+app.Flags = []cli.Flag {
+ cli.StringFlag{
+ Name: "lang",
+ Value: "english",
+ Usage: "language for the greeting",
+ },
+}
+app.Action = func(c *cli.Context) {
+ name := "someone"
+ if len(c.Args()) > 0 {
+ name = c.Args()[0]
+ }
+ if c.String("lang") == "spanish" {
+ println("Hola", name)
+ } else {
+ println("Hello", name)
+ }
+}
+...
+```
+
+#### Alternate Names
+
+You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
+
+``` go
+app.Flags = []cli.Flag {
+ cli.StringFlag{
+ Name: "lang, l",
+ Value: "english",
+ Usage: "language for the greeting",
+ },
+}
+```
+
+That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
+
+#### Values from the Environment
+
+You can also have the default value set from the environment via `EnvVar`. e.g.
+
+``` go
+app.Flags = []cli.Flag {
+ cli.StringFlag{
+ Name: "lang, l",
+ Value: "english",
+ Usage: "language for the greeting",
+ EnvVar: "APP_LANG",
+ },
+}
+```
+
+The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
+
+``` go
+app.Flags = []cli.Flag {
+ cli.StringFlag{
+ Name: "lang, l",
+ Value: "english",
+ Usage: "language for the greeting",
+ EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
+ },
+}
+```
+
+### Subcommands
+
+Subcommands can be defined for a more git-like command line app.
+```go
+...
+app.Commands = []cli.Command{
+ {
+ Name: "add",
+ ShortName: "a",
+ Usage: "add a task to the list",
+ Action: func(c *cli.Context) {
+ println("added task: ", c.Args().First())
+ },
+ },
+ {
+ Name: "complete",
+ ShortName: "c",
+ Usage: "complete a task on the list",
+ Action: func(c *cli.Context) {
+ println("completed task: ", c.Args().First())
+ },
+ },
+ {
+ Name: "template",
+ ShortName: "r",
+ Usage: "options for task templates",
+ Subcommands: []cli.Command{
+ {
+ Name: "add",
+ Usage: "add a new template",
+ Action: func(c *cli.Context) {
+ println("new task template: ", c.Args().First())
+ },
+ },
+ {
+ Name: "remove",
+ Usage: "remove an existing template",
+ Action: func(c *cli.Context) {
+ println("removed task template: ", c.Args().First())
+ },
+ },
+ },
+ },
+}
+...
+```
+
+### Bash Completion
+
+You can enable completion commands by setting the `EnableBashCompletion`
+flag on the `App` object. By default, this setting will only auto-complete to
+show an app's subcommands, but you can write your own completion methods for
+the App or its subcommands.
+```go
+...
+var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
+app := cli.NewApp()
+app.EnableBashCompletion = true
+app.Commands = []cli.Command{
+ {
+ Name: "complete",
+ ShortName: "c",
+ Usage: "complete a task on the list",
+ Action: func(c *cli.Context) {
+ println("completed task: ", c.Args().First())
+ },
+ BashComplete: func(c *cli.Context) {
+ // This will complete if no args are passed
+ if len(c.Args()) > 0 {
+ return
+ }
+ for _, t := range tasks {
+ fmt.Println(t)
+ }
+ },
+ }
+}
+...
+```
+
+#### To Enable
+
+Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
+setting the `PROG` variable to the name of your program:
+
+`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
+
+
+## Contribution Guidelines
+Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
+
+If you are have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
+
+If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/app.go b/Godeps/_workspace/src/github.com/codegangsta/cli/app.go
new file mode 100644
index 0000000..deb8e7f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/app.go
@@ -0,0 +1,279 @@
+package cli
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "text/tabwriter"
+ "text/template"
+ "time"
+)
+
+// App is the main structure of a cli application. It is recomended that
+// and app be created with the cli.NewApp() function
+type App struct {
+ // The name of the program. Defaults to os.Args[0]
+ Name string
+ // Description of the program.
+ Usage string
+ // Version of the program
+ Version string
+ // List of commands to execute
+ Commands []Command
+ // List of flags to parse
+ Flags []Flag
+ // Boolean to enable bash completion commands
+ EnableBashCompletion bool
+ // Boolean to hide built-in help command
+ HideHelp bool
+ // Boolean to hide built-in version flag
+ HideVersion bool
+ // An action to execute when the bash-completion flag is set
+ BashComplete func(context *Context)
+ // An action to execute before any subcommands are run, but after the context is ready
+ // If a non-nil error is returned, no subcommands are run
+ Before func(context *Context) error
+ // The action to execute when no subcommands are specified
+ Action func(context *Context)
+ // Execute this function if the proper command cannot be found
+ CommandNotFound func(context *Context, command string)
+ // Compilation date
+ Compiled time.Time
+ // Author
+ Author string
+ // Author e-mail
+ Email string
+ // Writer writer to write output to
+ Writer io.Writer
+}
+
+// Tries to find out when this binary was compiled.
+// Returns the current time if it fails to find it.
+func compileTime() time.Time {
+ info, err := os.Stat(os.Args[0])
+ if err != nil {
+ return time.Now()
+ }
+ return info.ModTime()
+}
+
+// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
+func NewApp() *App {
+ return &App{
+ Name: os.Args[0],
+ Usage: "A new cli application",
+ Version: "0.0.0",
+ BashComplete: DefaultAppComplete,
+ Action: helpCommand.Action,
+ Compiled: compileTime(),
+ Author: "Author",
+ Email: "unknown@email",
+ Writer: os.Stdout,
+ }
+}
+
+// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
+func (a *App) Run(arguments []string) error {
+ if HelpPrinter == nil {
+ defer func() {
+ HelpPrinter = nil
+ }()
+
+ HelpPrinter = func(templ string, data interface{}) {
+ w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0)
+ t := template.Must(template.New("help").Parse(templ))
+ err := t.Execute(w, data)
+ if err != nil {
+ panic(err)
+ }
+ w.Flush()
+ }
+ }
+
+ // append help to commands
+ if a.Command(helpCommand.Name) == nil && !a.HideHelp {
+ a.Commands = append(a.Commands, helpCommand)
+ if (HelpFlag != BoolFlag{}) {
+ a.appendFlag(HelpFlag)
+ }
+ }
+
+ //append version/help flags
+ if a.EnableBashCompletion {
+ a.appendFlag(BashCompletionFlag)
+ }
+
+ if !a.HideVersion {
+ a.appendFlag(VersionFlag)
+ }
+
+ // parse flags
+ set := flagSet(a.Name, a.Flags)
+ set.SetOutput(ioutil.Discard)
+ err := set.Parse(arguments[1:])
+ nerr := normalizeFlags(a.Flags, set)
+ if nerr != nil {
+ fmt.Fprintln(a.Writer, nerr)
+ context := NewContext(a, set, set)
+ ShowAppHelp(context)
+ fmt.Fprintln(a.Writer)
+ return nerr
+ }
+ context := NewContext(a, set, set)
+
+ if err != nil {
+ fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
+ ShowAppHelp(context)
+ fmt.Fprintln(a.Writer)
+ return err
+ }
+
+ if checkCompletions(context) {
+ return nil
+ }
+
+ if checkHelp(context) {
+ return nil
+ }
+
+ if checkVersion(context) {
+ return nil
+ }
+
+ if a.Before != nil {
+ err := a.Before(context)
+ if err != nil {
+ return err
+ }
+ }
+
+ args := context.Args()
+ if args.Present() {
+ name := args.First()
+ c := a.Command(name)
+ if c != nil {
+ return c.Run(context)
+ }
+ }
+
+ // Run default Action
+ a.Action(context)
+ return nil
+}
+
+// Another entry point to the cli app, takes care of passing arguments and error handling
+func (a *App) RunAndExitOnError() {
+ if err := a.Run(os.Args); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+
+// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
+func (a *App) RunAsSubcommand(ctx *Context) error {
+ // append help to commands
+ if len(a.Commands) > 0 {
+ if a.Command(helpCommand.Name) == nil && !a.HideHelp {
+ a.Commands = append(a.Commands, helpCommand)
+ if (HelpFlag != BoolFlag{}) {
+ a.appendFlag(HelpFlag)
+ }
+ }
+ }
+
+ // append flags
+ if a.EnableBashCompletion {
+ a.appendFlag(BashCompletionFlag)
+ }
+
+ // parse flags
+ set := flagSet(a.Name, a.Flags)
+ set.SetOutput(ioutil.Discard)
+ err := set.Parse(ctx.Args().Tail())
+ nerr := normalizeFlags(a.Flags, set)
+ context := NewContext(a, set, ctx.globalSet)
+
+ if nerr != nil {
+ fmt.Fprintln(a.Writer, nerr)
+ if len(a.Commands) > 0 {
+ ShowSubcommandHelp(context)
+ } else {
+ ShowCommandHelp(ctx, context.Args().First())
+ }
+ fmt.Fprintln(a.Writer)
+ return nerr
+ }
+
+ if err != nil {
+ fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
+ ShowSubcommandHelp(context)
+ return err
+ }
+
+ if checkCompletions(context) {
+ return nil
+ }
+
+ if len(a.Commands) > 0 {
+ if checkSubcommandHelp(context) {
+ return nil
+ }
+ } else {
+ if checkCommandHelp(ctx, context.Args().First()) {
+ return nil
+ }
+ }
+
+ if a.Before != nil {
+ err := a.Before(context)
+ if err != nil {
+ return err
+ }
+ }
+
+ args := context.Args()
+ if args.Present() {
+ name := args.First()
+ c := a.Command(name)
+ if c != nil {
+ return c.Run(context)
+ }
+ }
+
+ // Run default Action
+ if len(a.Commands) > 0 {
+ a.Action(context)
+ } else {
+ a.Action(ctx)
+ }
+
+ return nil
+}
+
+// Returns the named command on App. Returns nil if the command does not exist
+func (a *App) Command(name string) *Command {
+ for _, c := range a.Commands {
+ if c.HasName(name) {
+ return &c
+ }
+ }
+
+ return nil
+}
+
+func (a *App) hasFlag(flag Flag) bool {
+ for _, f := range a.Flags {
+ if flag == f {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (a *App) appendFlag(flag Flag) {
+ if !a.hasFlag(flag) {
+ a.Flags = append(a.Flags, flag)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go
new file mode 100644
index 0000000..78f0c10
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go
@@ -0,0 +1,528 @@
+package cli_test
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "testing"
+
+ "github.com/codegangsta/cli"
+)
+
+func ExampleApp() {
+ // set args for examples sake
+ os.Args = []string{"greet", "--name", "Jeremy"}
+
+ app := cli.NewApp()
+ app.Name = "greet"
+ app.Flags = []cli.Flag{
+ cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
+ }
+ app.Action = func(c *cli.Context) {
+ fmt.Printf("Hello %v\n", c.String("name"))
+ }
+ app.Run(os.Args)
+ // Output:
+ // Hello Jeremy
+}
+
+func ExampleAppSubcommand() {
+ // set args for examples sake
+ os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
+ app := cli.NewApp()
+ app.Name = "say"
+ app.Commands = []cli.Command{
+ {
+ Name: "hello",
+ ShortName: "hi",
+ Usage: "use it to see a description",
+ Description: "This is how we describe hello the function",
+ Subcommands: []cli.Command{
+ {
+ Name: "english",
+ ShortName: "en",
+ Usage: "sends a greeting in english",
+ Description: "greets someone in english",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "name",
+ Value: "Bob",
+ Usage: "Name of the person to greet",
+ },
+ },
+ Action: func(c *cli.Context) {
+ fmt.Println("Hello,", c.String("name"))
+ },
+ },
+ },
+ },
+ }
+
+ app.Run(os.Args)
+ // Output:
+ // Hello, Jeremy
+}
+
+func ExampleAppHelp() {
+ // set args for examples sake
+ os.Args = []string{"greet", "h", "describeit"}
+
+ app := cli.NewApp()
+ app.Name = "greet"
+ app.Flags = []cli.Flag{
+ cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
+ }
+ app.Commands = []cli.Command{
+ {
+ Name: "describeit",
+ ShortName: "d",
+ Usage: "use it to see a description",
+ Description: "This is how we describe describeit the function",
+ Action: func(c *cli.Context) {
+ fmt.Printf("i like to describe things")
+ },
+ },
+ }
+ app.Run(os.Args)
+ // Output:
+ // NAME:
+ // describeit - use it to see a description
+ //
+ // USAGE:
+ // command describeit [arguments...]
+ //
+ // DESCRIPTION:
+ // This is how we describe describeit the function
+}
+
+func ExampleAppBashComplete() {
+ // set args for examples sake
+ os.Args = []string{"greet", "--generate-bash-completion"}
+
+ app := cli.NewApp()
+ app.Name = "greet"
+ app.EnableBashCompletion = true
+ app.Commands = []cli.Command{
+ {
+ Name: "describeit",
+ ShortName: "d",
+ Usage: "use it to see a description",
+ Description: "This is how we describe describeit the function",
+ Action: func(c *cli.Context) {
+ fmt.Printf("i like to describe things")
+ },
+ }, {
+ Name: "next",
+ Usage: "next example",
+ Description: "more stuff to see when generating bash completion",
+ Action: func(c *cli.Context) {
+ fmt.Printf("the next example")
+ },
+ },
+ }
+
+ app.Run(os.Args)
+ // Output:
+ // describeit
+ // d
+ // next
+ // help
+ // h
+}
+
+func TestApp_Run(t *testing.T) {
+ s := ""
+
+ app := cli.NewApp()
+ app.Action = func(c *cli.Context) {
+ s = s + c.Args().First()
+ }
+
+ err := app.Run([]string{"command", "foo"})
+ expect(t, err, nil)
+ err = app.Run([]string{"command", "bar"})
+ expect(t, err, nil)
+ expect(t, s, "foobar")
+}
+
+var commandAppTests = []struct {
+ name string
+ expected bool
+}{
+ {"foobar", true},
+ {"batbaz", true},
+ {"b", true},
+ {"f", true},
+ {"bat", false},
+ {"nothing", false},
+}
+
+func TestApp_Command(t *testing.T) {
+ app := cli.NewApp()
+ fooCommand := cli.Command{Name: "foobar", ShortName: "f"}
+ batCommand := cli.Command{Name: "batbaz", ShortName: "b"}
+ app.Commands = []cli.Command{
+ fooCommand,
+ batCommand,
+ }
+
+ for _, test := range commandAppTests {
+ expect(t, app.Command(test.name) != nil, test.expected)
+ }
+}
+
+func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
+ var parsedOption, firstArg string
+
+ app := cli.NewApp()
+ command := cli.Command{
+ Name: "cmd",
+ Flags: []cli.Flag{
+ cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
+ },
+ Action: func(c *cli.Context) {
+ parsedOption = c.String("option")
+ firstArg = c.Args().First()
+ },
+ }
+ app.Commands = []cli.Command{command}
+
+ app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
+
+ expect(t, parsedOption, "my-option")
+ expect(t, firstArg, "my-arg")
+}
+
+func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
+ var parsedOption string
+ var args []string
+
+ app := cli.NewApp()
+ command := cli.Command{
+ Name: "cmd",
+ Flags: []cli.Flag{
+ cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
+ },
+ Action: func(c *cli.Context) {
+ parsedOption = c.String("option")
+ args = c.Args()
+ },
+ }
+ app.Commands = []cli.Command{command}
+
+ app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})
+
+ expect(t, parsedOption, "my-option")
+ expect(t, args[0], "my-arg")
+ expect(t, args[1], "--")
+ expect(t, args[2], "--notARealFlag")
+}
+
+func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
+ var args []string
+
+ app := cli.NewApp()
+ command := cli.Command{
+ Name: "cmd",
+ Action: func(c *cli.Context) {
+ args = c.Args()
+ },
+ }
+ app.Commands = []cli.Command{command}
+
+ app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
+
+ expect(t, args[0], "my-arg")
+ expect(t, args[1], "--")
+ expect(t, args[2], "notAFlagAtAll")
+}
+
+func TestApp_Float64Flag(t *testing.T) {
+ var meters float64
+
+ app := cli.NewApp()
+ app.Flags = []cli.Flag{
+ cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
+ }
+ app.Action = func(c *cli.Context) {
+ meters = c.Float64("height")
+ }
+
+ app.Run([]string{"", "--height", "1.93"})
+ expect(t, meters, 1.93)
+}
+
+func TestApp_ParseSliceFlags(t *testing.T) {
+ var parsedOption, firstArg string
+ var parsedIntSlice []int
+ var parsedStringSlice []string
+
+ app := cli.NewApp()
+ command := cli.Command{
+ Name: "cmd",
+ Flags: []cli.Flag{
+ cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"},
+ cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"},
+ },
+ Action: func(c *cli.Context) {
+ parsedIntSlice = c.IntSlice("p")
+ parsedStringSlice = c.StringSlice("ip")
+ parsedOption = c.String("option")
+ firstArg = c.Args().First()
+ },
+ }
+ app.Commands = []cli.Command{command}
+
+ app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})
+
+ IntsEquals := func(a, b []int) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, v := range a {
+ if v != b[i] {
+ return false
+ }
+ }
+ return true
+ }
+
+ StrsEquals := func(a, b []string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, v := range a {
+ if v != b[i] {
+ return false
+ }
+ }
+ return true
+ }
+ var expectedIntSlice = []int{22, 80}
+ var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
+
+ if !IntsEquals(parsedIntSlice, expectedIntSlice) {
+ t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
+ }
+
+ if !StrsEquals(parsedStringSlice, expectedStringSlice) {
+ t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
+ }
+}
+
+func TestApp_DefaultStdout(t *testing.T) {
+ app := cli.NewApp()
+
+ if app.Writer != os.Stdout {
+ t.Error("Default output writer not set.")
+ }
+}
+
+type mockWriter struct {
+ written []byte
+}
+
+func (fw *mockWriter) Write(p []byte) (n int, err error) {
+ if fw.written == nil {
+ fw.written = p
+ } else {
+ fw.written = append(fw.written, p...)
+ }
+
+ return len(p), nil
+}
+
+func (fw *mockWriter) GetWritten() (b []byte) {
+ return fw.written
+}
+
+func TestApp_SetStdout(t *testing.T) {
+ w := &mockWriter{}
+
+ app := cli.NewApp()
+ app.Name = "test"
+ app.Writer = w
+
+ err := app.Run([]string{"help"})
+
+ if err != nil {
+ t.Fatalf("Run error: %s", err)
+ }
+
+ if len(w.written) == 0 {
+ t.Error("App did not write output to desired writer.")
+ }
+}
+
+func TestApp_BeforeFunc(t *testing.T) {
+ beforeRun, subcommandRun := false, false
+ beforeError := fmt.Errorf("fail")
+ var err error
+
+ app := cli.NewApp()
+
+ app.Before = func(c *cli.Context) error {
+ beforeRun = true
+ s := c.String("opt")
+ if s == "fail" {
+ return beforeError
+ }
+
+ return nil
+ }
+
+ app.Commands = []cli.Command{
+ cli.Command{
+ Name: "sub",
+ Action: func(c *cli.Context) {
+ subcommandRun = true
+ },
+ },
+ }
+
+ app.Flags = []cli.Flag{
+ cli.StringFlag{Name: "opt"},
+ }
+
+ // run with the Before() func succeeding
+ err = app.Run([]string{"command", "--opt", "succeed", "sub"})
+
+ if err != nil {
+ t.Fatalf("Run error: %s", err)
+ }
+
+ if beforeRun == false {
+ t.Errorf("Before() not executed when expected")
+ }
+
+ if subcommandRun == false {
+ t.Errorf("Subcommand not executed when expected")
+ }
+
+ // reset
+ beforeRun, subcommandRun = false, false
+
+ // run with the Before() func failing
+ err = app.Run([]string{"command", "--opt", "fail", "sub"})
+
+ // should be the same error produced by the Before func
+ if err != beforeError {
+ t.Errorf("Run error expected, but not received")
+ }
+
+ if beforeRun == false {
+ t.Errorf("Before() not executed when expected")
+ }
+
+ if subcommandRun == true {
+ t.Errorf("Subcommand executed when NOT expected")
+ }
+
+}
+
+func TestAppNoHelpFlag(t *testing.T) {
+ oldFlag := cli.HelpFlag
+ defer func() {
+ cli.HelpFlag = oldFlag
+ }()
+
+ cli.HelpFlag = cli.BoolFlag{}
+
+ app := cli.NewApp()
+ err := app.Run([]string{"test", "-h"})
+
+ if err != flag.ErrHelp {
+ t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
+ }
+}
+
+func TestAppHelpPrinter(t *testing.T) {
+ oldPrinter := cli.HelpPrinter
+ defer func() {
+ cli.HelpPrinter = oldPrinter
+ }()
+
+ var wasCalled = false
+ cli.HelpPrinter = func(template string, data interface{}) {
+ wasCalled = true
+ }
+
+ app := cli.NewApp()
+ app.Run([]string{"-h"})
+
+ if wasCalled == false {
+ t.Errorf("Help printer expected to be called, but was not")
+ }
+}
+
+func TestAppVersionPrinter(t *testing.T) {
+ oldPrinter := cli.VersionPrinter
+ defer func() {
+ cli.VersionPrinter = oldPrinter
+ }()
+
+ var wasCalled = false
+ cli.VersionPrinter = func(c *cli.Context) {
+ wasCalled = true
+ }
+
+ app := cli.NewApp()
+ ctx := cli.NewContext(app, nil, nil)
+ cli.ShowVersion(ctx)
+
+ if wasCalled == false {
+ t.Errorf("Version printer expected to be called, but was not")
+ }
+}
+
+func TestAppCommandNotFound(t *testing.T) {
+ beforeRun, subcommandRun := false, false
+ app := cli.NewApp()
+
+ app.CommandNotFound = func(c *cli.Context, command string) {
+ beforeRun = true
+ }
+
+ app.Commands = []cli.Command{
+ cli.Command{
+ Name: "bar",
+ Action: func(c *cli.Context) {
+ subcommandRun = true
+ },
+ },
+ }
+
+ app.Run([]string{"command", "foo"})
+
+ expect(t, beforeRun, true)
+ expect(t, subcommandRun, false)
+}
+
+func TestGlobalFlagsInSubcommands(t *testing.T) {
+ subcommandRun := false
+ app := cli.NewApp()
+
+ app.Flags = []cli.Flag{
+ cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
+ }
+
+ app.Commands = []cli.Command{
+ cli.Command{
+ Name: "foo",
+ Subcommands: []cli.Command{
+ {
+ Name: "bar",
+ Action: func(c *cli.Context) {
+ if c.GlobalBool("debug") {
+ subcommandRun = true
+ }
+ },
+ },
+ },
+ },
+ }
+
+ app.Run([]string{"command", "-d", "foo", "bar"})
+
+ expect(t, subcommandRun, true)
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete b/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
new file mode 100644
index 0000000..9b55dd9
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
@@ -0,0 +1,13 @@
+#! /bin/bash
+
+_cli_bash_autocomplete() {
+ local cur prev opts base
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ }
+
+ complete -F _cli_bash_autocomplete $PROG
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete b/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
new file mode 100644
index 0000000..5430a18
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
@@ -0,0 +1,5 @@
+autoload -U compinit && compinit
+autoload -U bashcompinit && bashcompinit
+
+script_dir=$(dirname $0)
+source ${script_dir}/bash_autocomplete
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/cli.go b/Godeps/_workspace/src/github.com/codegangsta/cli/cli.go
new file mode 100644
index 0000000..b742545
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/cli.go
@@ -0,0 +1,19 @@
+// Package cli provides a minimal framework for creating and organizing command line
+// Go applications. cli is designed to be easy to understand and write, the most simple
+// cli application can be written as follows:
+// func main() {
+// cli.NewApp().Run(os.Args)
+// }
+//
+// Of course this application does not do much, so let's make this an actual application:
+// func main() {
+// app := cli.NewApp()
+// app.Name = "greet"
+// app.Usage = "say a greeting"
+// app.Action = func(c *cli.Context) {
+// println("Greetings")
+// }
+//
+// app.Run(os.Args)
+// }
+package cli
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go
new file mode 100644
index 0000000..879a793
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go
@@ -0,0 +1,100 @@
+package cli_test
+
+import (
+ "os"
+
+ "github.com/codegangsta/cli"
+)
+
+func Example() {
+ app := cli.NewApp()
+ app.Name = "todo"
+ app.Usage = "task list on the command line"
+ app.Commands = []cli.Command{
+ {
+ Name: "add",
+ ShortName: "a",
+ Usage: "add a task to the list",
+ Action: func(c *cli.Context) {
+ println("added task: ", c.Args().First())
+ },
+ },
+ {
+ Name: "complete",
+ ShortName: "c",
+ Usage: "complete a task on the list",
+ Action: func(c *cli.Context) {
+ println("completed task: ", c.Args().First())
+ },
+ },
+ }
+
+ app.Run(os.Args)
+}
+
+func ExampleSubcommand() {
+ app := cli.NewApp()
+ app.Name = "say"
+ app.Commands = []cli.Command{
+ {
+ Name: "hello",
+ ShortName: "hi",
+ Usage: "use it to see a description",
+ Description: "This is how we describe hello the function",
+ Subcommands: []cli.Command{
+ {
+ Name: "english",
+ ShortName: "en",
+ Usage: "sends a greeting in english",
+ Description: "greets someone in english",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "name",
+ Value: "Bob",
+ Usage: "Name of the person to greet",
+ },
+ },
+ Action: func(c *cli.Context) {
+ println("Hello, ", c.String("name"))
+ },
+ }, {
+ Name: "spanish",
+ ShortName: "sp",
+ Usage: "sends a greeting in spanish",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "surname",
+ Value: "Jones",
+ Usage: "Surname of the person to greet",
+ },
+ },
+ Action: func(c *cli.Context) {
+ println("Hola, ", c.String("surname"))
+ },
+ }, {
+ Name: "french",
+ ShortName: "fr",
+ Usage: "sends a greeting in french",
+ Flags: []cli.Flag{
+ cli.StringFlag{
+ Name: "nickname",
+ Value: "Stevie",
+ Usage: "Nickname of the person to greet",
+ },
+ },
+ Action: func(c *cli.Context) {
+ println("Bonjour, ", c.String("nickname"))
+ },
+ },
+ },
+ }, {
+ Name: "bye",
+ Usage: "says goodbye",
+ Action: func(c *cli.Context) {
+ println("bye")
+ },
+ },
+ }
+
+ app.Run(os.Args)
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/command.go b/Godeps/_workspace/src/github.com/codegangsta/cli/command.go
new file mode 100644
index 0000000..ffd3ef8
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/command.go
@@ -0,0 +1,156 @@
+package cli
+
+import (
+ "fmt"
+ "io/ioutil"
+ "strings"
+)
+
+// Command is a subcommand for a cli.App.
+type Command struct {
+ // The name of the command
+ Name string
+ // short name of the command. Typically one character
+ ShortName string
+ // A short description of the usage of this command
+ Usage string
+ // A longer explanation of how the command works
+ Description string
+ // The function to call when checking for bash command completions
+ BashComplete func(context *Context)
+ // An action to execute before any sub-subcommands are run, but after the context is ready
+ // If a non-nil error is returned, no sub-subcommands are run
+ Before func(context *Context) error
+ // The function to call when this command is invoked
+ Action func(context *Context)
+ // List of child commands
+ Subcommands []Command
+ // List of flags to parse
+ Flags []Flag
+ // Treat all flags as normal arguments if true
+ SkipFlagParsing bool
+ // Boolean to hide built-in help command
+ HideHelp bool
+}
+
+// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
+func (c Command) Run(ctx *Context) error {
+
+ if len(c.Subcommands) > 0 || c.Before != nil {
+ return c.startApp(ctx)
+ }
+
+ if !c.HideHelp && (HelpFlag != BoolFlag{}) {
+ // append help to flags
+ c.Flags = append(
+ c.Flags,
+ HelpFlag,
+ )
+ }
+
+ if ctx.App.EnableBashCompletion {
+ c.Flags = append(c.Flags, BashCompletionFlag)
+ }
+
+ set := flagSet(c.Name, c.Flags)
+ set.SetOutput(ioutil.Discard)
+
+ firstFlagIndex := -1
+ terminatorIndex := -1
+ for index, arg := range ctx.Args() {
+ if arg == "--" {
+ terminatorIndex = index
+ break
+ } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
+ firstFlagIndex = index
+ }
+ }
+
+ var err error
+ if firstFlagIndex > -1 && !c.SkipFlagParsing {
+ args := ctx.Args()
+ regularArgs := make([]string, len(args[1:firstFlagIndex]))
+ copy(regularArgs, args[1:firstFlagIndex])
+
+ var flagArgs []string
+ if terminatorIndex > -1 {
+ flagArgs = args[firstFlagIndex:terminatorIndex]
+ regularArgs = append(regularArgs, args[terminatorIndex:]...)
+ } else {
+ flagArgs = args[firstFlagIndex:]
+ }
+
+ err = set.Parse(append(flagArgs, regularArgs...))
+ } else {
+ err = set.Parse(ctx.Args().Tail())
+ }
+
+ if err != nil {
+ fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n")
+ ShowCommandHelp(ctx, c.Name)
+ fmt.Fprintln(ctx.App.Writer)
+ return err
+ }
+
+ nerr := normalizeFlags(c.Flags, set)
+ if nerr != nil {
+ fmt.Fprintln(ctx.App.Writer, nerr)
+ fmt.Fprintln(ctx.App.Writer)
+ ShowCommandHelp(ctx, c.Name)
+ fmt.Fprintln(ctx.App.Writer)
+ return nerr
+ }
+ context := NewContext(ctx.App, set, ctx.globalSet)
+
+ if checkCommandCompletions(context, c.Name) {
+ return nil
+ }
+
+ if checkCommandHelp(context, c.Name) {
+ return nil
+ }
+ context.Command = c
+ c.Action(context)
+ return nil
+}
+
+// Returns true if Command.Name or Command.ShortName matches given name
+func (c Command) HasName(name string) bool {
+ return c.Name == name || c.ShortName == name
+}
+
+func (c Command) startApp(ctx *Context) error {
+ app := NewApp()
+
+ // set the name and usage
+ app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
+ if c.Description != "" {
+ app.Usage = c.Description
+ } else {
+ app.Usage = c.Usage
+ }
+
+ // set CommandNotFound
+ app.CommandNotFound = ctx.App.CommandNotFound
+
+ // set the flags and commands
+ app.Commands = c.Subcommands
+ app.Flags = c.Flags
+ app.HideHelp = c.HideHelp
+
+ // bash completion
+ app.EnableBashCompletion = ctx.App.EnableBashCompletion
+ if c.BashComplete != nil {
+ app.BashComplete = c.BashComplete
+ }
+
+ // set the actions
+ app.Before = c.Before
+ if c.Action != nil {
+ app.Action = c.Action
+ } else {
+ app.Action = helpSubcommand.Action
+ }
+
+ return app.RunAsSubcommand(ctx)
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go
new file mode 100644
index 0000000..c0f556a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go
@@ -0,0 +1,49 @@
+package cli_test
+
+import (
+ "flag"
+ "testing"
+
+ "github.com/codegangsta/cli"
+)
+
+func TestCommandDoNotIgnoreFlags(t *testing.T) {
+ app := cli.NewApp()
+ set := flag.NewFlagSet("test", 0)
+ test := []string{"blah", "blah", "-break"}
+ set.Parse(test)
+
+ c := cli.NewContext(app, set, set)
+
+ command := cli.Command{
+ Name: "test-cmd",
+ ShortName: "tc",
+ Usage: "this is for testing",
+ Description: "testing",
+ Action: func(_ *cli.Context) {},
+ }
+ err := command.Run(c)
+
+ expect(t, err.Error(), "flag provided but not defined: -break")
+}
+
+func TestCommandIgnoreFlags(t *testing.T) {
+ app := cli.NewApp()
+ set := flag.NewFlagSet("test", 0)
+ test := []string{"blah", "blah"}
+ set.Parse(test)
+
+ c := cli.NewContext(app, set, set)
+
+ command := cli.Command{
+ Name: "test-cmd",
+ ShortName: "tc",
+ Usage: "this is for testing",
+ Description: "testing",
+ Action: func(_ *cli.Context) {},
+ SkipFlagParsing: true,
+ }
+ err := command.Run(c)
+
+ expect(t, err, nil)
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/context.go b/Godeps/_workspace/src/github.com/codegangsta/cli/context.go
new file mode 100644
index 0000000..c9f645b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/context.go
@@ -0,0 +1,339 @@
+package cli
+
+import (
+ "errors"
+ "flag"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Context is a type that is passed through to
+// each Handler action in a cli application. Context
+// can be used to retrieve context-specific Args and
+// parsed command-line options.
+type Context struct {
+ App *App
+ Command Command
+ flagSet *flag.FlagSet
+ globalSet *flag.FlagSet
+ setFlags map[string]bool
+ globalSetFlags map[string]bool
+}
+
+// Creates a new context. For use in when invoking an App or Command action.
+func NewContext(app *App, set *flag.FlagSet, globalSet *flag.FlagSet) *Context {
+ return &Context{App: app, flagSet: set, globalSet: globalSet}
+}
+
+// Looks up the value of a local int flag, returns 0 if no int flag exists
+func (c *Context) Int(name string) int {
+ return lookupInt(name, c.flagSet)
+}
+
+// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
+func (c *Context) Duration(name string) time.Duration {
+ return lookupDuration(name, c.flagSet)
+}
+
+// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
+func (c *Context) Float64(name string) float64 {
+ return lookupFloat64(name, c.flagSet)
+}
+
+// Looks up the value of a local bool flag, returns false if no bool flag exists
+func (c *Context) Bool(name string) bool {
+ return lookupBool(name, c.flagSet)
+}
+
+// Looks up the value of a local boolT flag, returns false if no bool flag exists
+func (c *Context) BoolT(name string) bool {
+ return lookupBoolT(name, c.flagSet)
+}
+
+// Looks up the value of a local string flag, returns "" if no string flag exists
+func (c *Context) String(name string) string {
+ return lookupString(name, c.flagSet)
+}
+
+// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
+func (c *Context) StringSlice(name string) []string {
+ return lookupStringSlice(name, c.flagSet)
+}
+
+// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
+func (c *Context) IntSlice(name string) []int {
+ return lookupIntSlice(name, c.flagSet)
+}
+
+// Looks up the value of a local generic flag, returns nil if no generic flag exists
+func (c *Context) Generic(name string) interface{} {
+ return lookupGeneric(name, c.flagSet)
+}
+
+// Looks up the value of a global int flag, returns 0 if no int flag exists
+func (c *Context) GlobalInt(name string) int {
+ return lookupInt(name, c.globalSet)
+}
+
+// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
+func (c *Context) GlobalDuration(name string) time.Duration {
+ return lookupDuration(name, c.globalSet)
+}
+
+// Looks up the value of a global bool flag, returns false if no bool flag exists
+func (c *Context) GlobalBool(name string) bool {
+ return lookupBool(name, c.globalSet)
+}
+
+// Looks up the value of a global string flag, returns "" if no string flag exists
+func (c *Context) GlobalString(name string) string {
+ return lookupString(name, c.globalSet)
+}
+
+// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
+func (c *Context) GlobalStringSlice(name string) []string {
+ return lookupStringSlice(name, c.globalSet)
+}
+
+// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
+func (c *Context) GlobalIntSlice(name string) []int {
+ return lookupIntSlice(name, c.globalSet)
+}
+
+// Looks up the value of a global generic flag, returns nil if no generic flag exists
+func (c *Context) GlobalGeneric(name string) interface{} {
+ return lookupGeneric(name, c.globalSet)
+}
+
+// Determines if the flag was actually set
+func (c *Context) IsSet(name string) bool {
+ if c.setFlags == nil {
+ c.setFlags = make(map[string]bool)
+ c.flagSet.Visit(func(f *flag.Flag) {
+ c.setFlags[f.Name] = true
+ })
+ }
+ return c.setFlags[name] == true
+}
+
+// Determines if the global flag was actually set
+func (c *Context) GlobalIsSet(name string) bool {
+ if c.globalSetFlags == nil {
+ c.globalSetFlags = make(map[string]bool)
+ c.globalSet.Visit(func(f *flag.Flag) {
+ c.globalSetFlags[f.Name] = true
+ })
+ }
+ return c.globalSetFlags[name] == true
+}
+
+// Returns a slice of flag names used in this context.
+func (c *Context) FlagNames() (names []string) {
+ for _, flag := range c.Command.Flags {
+ name := strings.Split(flag.getName(), ",")[0]
+ if name == "help" {
+ continue
+ }
+ names = append(names, name)
+ }
+ return
+}
+
+// Returns a slice of global flag names used by the app.
+func (c *Context) GlobalFlagNames() (names []string) {
+ for _, flag := range c.App.Flags {
+ name := strings.Split(flag.getName(), ",")[0]
+ if name == "help" || name == "version" {
+ continue
+ }
+ names = append(names, name)
+ }
+ return
+}
+
+type Args []string
+
+// Returns the command line arguments associated with the context.
+func (c *Context) Args() Args {
+ args := Args(c.flagSet.Args())
+ return args
+}
+
+// Returns the nth argument, or else a blank string
+func (a Args) Get(n int) string {
+ if len(a) > n {
+ return a[n]
+ }
+ return ""
+}
+
+// Returns the first argument, or else a blank string
+func (a Args) First() string {
+ return a.Get(0)
+}
+
+// Return the rest of the arguments (not the first one)
+// or else an empty string slice
+func (a Args) Tail() []string {
+ if len(a) >= 2 {
+ return []string(a)[1:]
+ }
+ return []string{}
+}
+
+// Checks if there are any arguments present
+func (a Args) Present() bool {
+ return len(a) != 0
+}
+
+// Swaps arguments at the given indexes
+func (a Args) Swap(from, to int) error {
+ if from >= len(a) || to >= len(a) {
+ return errors.New("index out of range")
+ }
+ a[from], a[to] = a[to], a[from]
+ return nil
+}
+
+func lookupInt(name string, set *flag.FlagSet) int {
+ f := set.Lookup(name)
+ if f != nil {
+ val, err := strconv.Atoi(f.Value.String())
+ if err != nil {
+ return 0
+ }
+ return val
+ }
+
+ return 0
+}
+
+func lookupDuration(name string, set *flag.FlagSet) time.Duration {
+ f := set.Lookup(name)
+ if f != nil {
+ val, err := time.ParseDuration(f.Value.String())
+ if err == nil {
+ return val
+ }
+ }
+
+ return 0
+}
+
+func lookupFloat64(name string, set *flag.FlagSet) float64 {
+ f := set.Lookup(name)
+ if f != nil {
+ val, err := strconv.ParseFloat(f.Value.String(), 64)
+ if err != nil {
+ return 0
+ }
+ return val
+ }
+
+ return 0
+}
+
+func lookupString(name string, set *flag.FlagSet) string {
+ f := set.Lookup(name)
+ if f != nil {
+ return f.Value.String()
+ }
+
+ return ""
+}
+
+func lookupStringSlice(name string, set *flag.FlagSet) []string {
+ f := set.Lookup(name)
+ if f != nil {
+ return (f.Value.(*StringSlice)).Value()
+
+ }
+
+ return nil
+}
+
+func lookupIntSlice(name string, set *flag.FlagSet) []int {
+ f := set.Lookup(name)
+ if f != nil {
+ return (f.Value.(*IntSlice)).Value()
+
+ }
+
+ return nil
+}
+
+func lookupGeneric(name string, set *flag.FlagSet) interface{} {
+ f := set.Lookup(name)
+ if f != nil {
+ return f.Value
+ }
+ return nil
+}
+
+func lookupBool(name string, set *flag.FlagSet) bool {
+ f := set.Lookup(name)
+ if f != nil {
+ val, err := strconv.ParseBool(f.Value.String())
+ if err != nil {
+ return false
+ }
+ return val
+ }
+
+ return false
+}
+
+func lookupBoolT(name string, set *flag.FlagSet) bool {
+ f := set.Lookup(name)
+ if f != nil {
+ val, err := strconv.ParseBool(f.Value.String())
+ if err != nil {
+ return true
+ }
+ return val
+ }
+
+ return false
+}
+
+func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
+ switch ff.Value.(type) {
+ case *StringSlice:
+ default:
+ set.Set(name, ff.Value.String())
+ }
+}
+
+func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
+ visited := make(map[string]bool)
+ set.Visit(func(f *flag.Flag) {
+ visited[f.Name] = true
+ })
+ for _, f := range flags {
+ parts := strings.Split(f.getName(), ",")
+ if len(parts) == 1 {
+ continue
+ }
+ var ff *flag.Flag
+ for _, name := range parts {
+ name = strings.Trim(name, " ")
+ if visited[name] {
+ if ff != nil {
+ return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
+ }
+ ff = set.Lookup(name)
+ }
+ }
+ if ff == nil {
+ continue
+ }
+ for _, name := range parts {
+ name = strings.Trim(name, " ")
+ if !visited[name] {
+ copyFlag(name, ff, set)
+ }
+ }
+ }
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go
new file mode 100644
index 0000000..7c9a443
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go
@@ -0,0 +1,99 @@
+package cli_test
+
+import (
+ "flag"
+ "testing"
+ "time"
+
+ "github.com/codegangsta/cli"
+)
+
+func TestNewContext(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.Int("myflag", 12, "doc")
+ globalSet := flag.NewFlagSet("test", 0)
+ globalSet.Int("myflag", 42, "doc")
+ command := cli.Command{Name: "mycommand"}
+ c := cli.NewContext(nil, set, globalSet)
+ c.Command = command
+ expect(t, c.Int("myflag"), 12)
+ expect(t, c.GlobalInt("myflag"), 42)
+ expect(t, c.Command.Name, "mycommand")
+}
+
+func TestContext_Int(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.Int("myflag", 12, "doc")
+ c := cli.NewContext(nil, set, set)
+ expect(t, c.Int("myflag"), 12)
+}
+
+func TestContext_Duration(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.Duration("myflag", time.Duration(12*time.Second), "doc")
+ c := cli.NewContext(nil, set, set)
+ expect(t, c.Duration("myflag"), time.Duration(12*time.Second))
+}
+
+func TestContext_String(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.String("myflag", "hello world", "doc")
+ c := cli.NewContext(nil, set, set)
+ expect(t, c.String("myflag"), "hello world")
+}
+
+func TestContext_Bool(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("myflag", false, "doc")
+ c := cli.NewContext(nil, set, set)
+ expect(t, c.Bool("myflag"), false)
+}
+
+func TestContext_BoolT(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("myflag", true, "doc")
+ c := cli.NewContext(nil, set, set)
+ expect(t, c.BoolT("myflag"), true)
+}
+
+func TestContext_Args(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("myflag", false, "doc")
+ c := cli.NewContext(nil, set, set)
+ set.Parse([]string{"--myflag", "bat", "baz"})
+ expect(t, len(c.Args()), 2)
+ expect(t, c.Bool("myflag"), true)
+}
+
+func TestContext_IsSet(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("myflag", false, "doc")
+ set.String("otherflag", "hello world", "doc")
+ globalSet := flag.NewFlagSet("test", 0)
+ globalSet.Bool("myflagGlobal", true, "doc")
+ c := cli.NewContext(nil, set, globalSet)
+ set.Parse([]string{"--myflag", "bat", "baz"})
+ globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
+ expect(t, c.IsSet("myflag"), true)
+ expect(t, c.IsSet("otherflag"), false)
+ expect(t, c.IsSet("bogusflag"), false)
+ expect(t, c.IsSet("myflagGlobal"), false)
+}
+
+func TestContext_GlobalIsSet(t *testing.T) {
+ set := flag.NewFlagSet("test", 0)
+ set.Bool("myflag", false, "doc")
+ set.String("otherflag", "hello world", "doc")
+ globalSet := flag.NewFlagSet("test", 0)
+ globalSet.Bool("myflagGlobal", true, "doc")
+ globalSet.Bool("myflagGlobalUnset", true, "doc")
+ c := cli.NewContext(nil, set, globalSet)
+ set.Parse([]string{"--myflag", "bat", "baz"})
+ globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
+ expect(t, c.GlobalIsSet("myflag"), false)
+ expect(t, c.GlobalIsSet("otherflag"), false)
+ expect(t, c.GlobalIsSet("bogusflag"), false)
+ expect(t, c.GlobalIsSet("myflagGlobal"), true)
+ expect(t, c.GlobalIsSet("myflagGlobalUnset"), false)
+ expect(t, c.GlobalIsSet("bogusGlobal"), false)
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/flag.go b/Godeps/_workspace/src/github.com/codegangsta/cli/flag.go
new file mode 100644
index 0000000..2511586
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/flag.go
@@ -0,0 +1,454 @@
+package cli
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// This flag enables bash-completion for all commands and subcommands
+var BashCompletionFlag = BoolFlag{
+ Name: "generate-bash-completion",
+}
+
+// This flag prints the version for the application
+var VersionFlag = BoolFlag{
+ Name: "version, v",
+ Usage: "print the version",
+}
+
+// This flag prints the help for all commands and subcommands
+// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
+// unless HideHelp is set to true)
+var HelpFlag = BoolFlag{
+ Name: "help, h",
+ Usage: "show help",
+}
+
+// Flag is a common interface related to parsing flags in cli.
+// For more advanced flag parsing techniques, it is recomended that
+// this interface be implemented.
+type Flag interface {
+ fmt.Stringer
+ // Apply Flag settings to the given flag set
+ Apply(*flag.FlagSet)
+ getName() string
+}
+
+func flagSet(name string, flags []Flag) *flag.FlagSet {
+ set := flag.NewFlagSet(name, flag.ContinueOnError)
+
+ for _, f := range flags {
+ f.Apply(set)
+ }
+ return set
+}
+
+func eachName(longName string, fn func(string)) {
+ parts := strings.Split(longName, ",")
+ for _, name := range parts {
+ name = strings.Trim(name, " ")
+ fn(name)
+ }
+}
+
+// Generic is a generic parseable type identified by a specific flag
+type Generic interface {
+ Set(value string) error
+ String() string
+}
+
+// GenericFlag is the flag type for types implementing Generic
+type GenericFlag struct {
+ Name string
+ Value Generic
+ Usage string
+ EnvVar string
+}
+
+// String returns the string representation of the generic flag to display the
+// help text to the user (uses the String() method of the generic flag to show
+// the value)
+func (f GenericFlag) String() string {
+ return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
+}
+
+// Apply takes the flagset and calls Set on the generic flag with the value
+// provided by the user for parsing by the flag
+func (f GenericFlag) Apply(set *flag.FlagSet) {
+ val := f.Value
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ val.Set(envVal)
+ break
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.Var(f.Value, name, f.Usage)
+ })
+}
+
+func (f GenericFlag) getName() string {
+ return f.Name
+}
+
+type StringSlice []string
+
+func (f *StringSlice) Set(value string) error {
+ *f = append(*f, value)
+ return nil
+}
+
+func (f *StringSlice) String() string {
+ return fmt.Sprintf("%s", *f)
+}
+
+func (f *StringSlice) Value() []string {
+ return *f
+}
+
+type StringSliceFlag struct {
+ Name string
+ Value *StringSlice
+ Usage string
+ EnvVar string
+}
+
+func (f StringSliceFlag) String() string {
+ firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
+ pref := prefixFor(firstName)
+ return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
+}
+
+func (f StringSliceFlag) Apply(set *flag.FlagSet) {
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ newVal := &StringSlice{}
+ for _, s := range strings.Split(envVal, ",") {
+ s = strings.TrimSpace(s)
+ newVal.Set(s)
+ }
+ f.Value = newVal
+ break
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.Var(f.Value, name, f.Usage)
+ })
+}
+
+func (f StringSliceFlag) getName() string {
+ return f.Name
+}
+
+type IntSlice []int
+
+func (f *IntSlice) Set(value string) error {
+
+ tmp, err := strconv.Atoi(value)
+ if err != nil {
+ return err
+ } else {
+ *f = append(*f, tmp)
+ }
+ return nil
+}
+
+func (f *IntSlice) String() string {
+ return fmt.Sprintf("%d", *f)
+}
+
+func (f *IntSlice) Value() []int {
+ return *f
+}
+
+type IntSliceFlag struct {
+ Name string
+ Value *IntSlice
+ Usage string
+ EnvVar string
+}
+
+func (f IntSliceFlag) String() string {
+ firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
+ pref := prefixFor(firstName)
+ return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
+}
+
+func (f IntSliceFlag) Apply(set *flag.FlagSet) {
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ newVal := &IntSlice{}
+ for _, s := range strings.Split(envVal, ",") {
+ s = strings.TrimSpace(s)
+ err := newVal.Set(s)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, err.Error())
+ }
+ }
+ f.Value = newVal
+ break
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.Var(f.Value, name, f.Usage)
+ })
+}
+
+func (f IntSliceFlag) getName() string {
+ return f.Name
+}
+
+type BoolFlag struct {
+ Name string
+ Usage string
+ EnvVar string
+}
+
+func (f BoolFlag) String() string {
+ return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
+}
+
+func (f BoolFlag) Apply(set *flag.FlagSet) {
+ val := false
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ envValBool, err := strconv.ParseBool(envVal)
+ if err == nil {
+ val = envValBool
+ }
+ break
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.Bool(name, val, f.Usage)
+ })
+}
+
+func (f BoolFlag) getName() string {
+ return f.Name
+}
+
+type BoolTFlag struct {
+ Name string
+ Usage string
+ EnvVar string
+}
+
+func (f BoolTFlag) String() string {
+ return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
+}
+
+func (f BoolTFlag) Apply(set *flag.FlagSet) {
+ val := true
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ envValBool, err := strconv.ParseBool(envVal)
+ if err == nil {
+ val = envValBool
+ break
+ }
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.Bool(name, val, f.Usage)
+ })
+}
+
+func (f BoolTFlag) getName() string {
+ return f.Name
+}
+
+type StringFlag struct {
+ Name string
+ Value string
+ Usage string
+ EnvVar string
+}
+
+func (f StringFlag) String() string {
+ var fmtString string
+ fmtString = "%s %v\t%v"
+
+ if len(f.Value) > 0 {
+ fmtString = "%s \"%v\"\t%v"
+ } else {
+ fmtString = "%s %v\t%v"
+ }
+
+ return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
+}
+
+func (f StringFlag) Apply(set *flag.FlagSet) {
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ f.Value = envVal
+ break
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.String(name, f.Value, f.Usage)
+ })
+}
+
+func (f StringFlag) getName() string {
+ return f.Name
+}
+
+type IntFlag struct {
+ Name string
+ Value int
+ Usage string
+ EnvVar string
+}
+
+func (f IntFlag) String() string {
+ return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
+}
+
+func (f IntFlag) Apply(set *flag.FlagSet) {
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ envValInt, err := strconv.ParseInt(envVal, 0, 64)
+ if err == nil {
+ f.Value = int(envValInt)
+ break
+ }
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.Int(name, f.Value, f.Usage)
+ })
+}
+
+func (f IntFlag) getName() string {
+ return f.Name
+}
+
+type DurationFlag struct {
+ Name string
+ Value time.Duration
+ Usage string
+ EnvVar string
+}
+
+func (f DurationFlag) String() string {
+ return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
+}
+
+func (f DurationFlag) Apply(set *flag.FlagSet) {
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ envValDuration, err := time.ParseDuration(envVal)
+ if err == nil {
+ f.Value = envValDuration
+ break
+ }
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.Duration(name, f.Value, f.Usage)
+ })
+}
+
+func (f DurationFlag) getName() string {
+ return f.Name
+}
+
+type Float64Flag struct {
+ Name string
+ Value float64
+ Usage string
+ EnvVar string
+}
+
+func (f Float64Flag) String() string {
+ return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
+}
+
+func (f Float64Flag) Apply(set *flag.FlagSet) {
+ if f.EnvVar != "" {
+ for _, envVar := range strings.Split(f.EnvVar, ",") {
+ envVar = strings.TrimSpace(envVar)
+ if envVal := os.Getenv(envVar); envVal != "" {
+ envValFloat, err := strconv.ParseFloat(envVal, 10)
+ if err == nil {
+ f.Value = float64(envValFloat)
+ }
+ }
+ }
+ }
+
+ eachName(f.Name, func(name string) {
+ set.Float64(name, f.Value, f.Usage)
+ })
+}
+
+func (f Float64Flag) getName() string {
+ return f.Name
+}
+
+func prefixFor(name string) (prefix string) {
+ if len(name) == 1 {
+ prefix = "-"
+ } else {
+ prefix = "--"
+ }
+
+ return
+}
+
+func prefixedNames(fullName string) (prefixed string) {
+ parts := strings.Split(fullName, ",")
+ for i, name := range parts {
+ name = strings.Trim(name, " ")
+ prefixed += prefixFor(name) + name
+ if i < len(parts)-1 {
+ prefixed += ", "
+ }
+ }
+ return
+}
+
+func withEnvHint(envVar, str string) string {
+ envText := ""
+ if envVar != "" {
+ envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
+ }
+ return str + envText
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go
new file mode 100644
index 0000000..f0f096a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go
@@ -0,0 +1,742 @@
+package cli_test
+
+import (
+ "fmt"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/codegangsta/cli"
+)
+
+var boolFlagTests = []struct {
+ name string
+ expected string
+}{
+ {"help", "--help\t"},
+ {"h", "-h\t"},
+}
+
+func TestBoolFlagHelpOutput(t *testing.T) {
+
+ for _, test := range boolFlagTests {
+ flag := cli.BoolFlag{Name: test.name}
+ output := flag.String()
+
+ if output != test.expected {
+ t.Errorf("%s does not match %s", output, test.expected)
+ }
+ }
+}
+
+var stringFlagTests = []struct {
+ name string
+ value string
+ expected string
+}{
+ {"help", "", "--help \t"},
+ {"h", "", "-h \t"},
+ {"h", "", "-h \t"},
+ {"test", "Something", "--test \"Something\"\t"},
+}
+
+func TestStringFlagHelpOutput(t *testing.T) {
+
+ for _, test := range stringFlagTests {
+ flag := cli.StringFlag{Name: test.name, Value: test.value}
+ output := flag.String()
+
+ if output != test.expected {
+ t.Errorf("%s does not match %s", output, test.expected)
+ }
+ }
+}
+
+func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_FOO", "derp")
+ for _, test := range stringFlagTests {
+ flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
+ output := flag.String()
+
+ if !strings.HasSuffix(output, " [$APP_FOO]") {
+ t.Errorf("%s does not end with [$APP_FOO]", output)
+ }
+ }
+}
+
+var stringSliceFlagTests = []struct {
+ name string
+ value *cli.StringSlice
+ expected string
+}{
+ {"help", func() *cli.StringSlice {
+ s := &cli.StringSlice{}
+ s.Set("")
+ return s
+ }(), "--help [--help option --help option]\t"},
+ {"h", func() *cli.StringSlice {
+ s := &cli.StringSlice{}
+ s.Set("")
+ return s
+ }(), "-h [-h option -h option]\t"},
+ {"h", func() *cli.StringSlice {
+ s := &cli.StringSlice{}
+ s.Set("")
+ return s
+ }(), "-h [-h option -h option]\t"},
+ {"test", func() *cli.StringSlice {
+ s := &cli.StringSlice{}
+ s.Set("Something")
+ return s
+ }(), "--test [--test option --test option]\t"},
+}
+
+func TestStringSliceFlagHelpOutput(t *testing.T) {
+
+ for _, test := range stringSliceFlagTests {
+ flag := cli.StringSliceFlag{Name: test.name, Value: test.value}
+ output := flag.String()
+
+ if output != test.expected {
+ t.Errorf("%q does not match %q", output, test.expected)
+ }
+ }
+}
+
+func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_QWWX", "11,4")
+ for _, test := range stringSliceFlagTests {
+ flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
+ output := flag.String()
+
+ if !strings.HasSuffix(output, " [$APP_QWWX]") {
+ t.Errorf("%q does not end with [$APP_QWWX]", output)
+ }
+ }
+}
+
+var intFlagTests = []struct {
+ name string
+ expected string
+}{
+ {"help", "--help \"0\"\t"},
+ {"h", "-h \"0\"\t"},
+}
+
+func TestIntFlagHelpOutput(t *testing.T) {
+
+ for _, test := range intFlagTests {
+ flag := cli.IntFlag{Name: test.name}
+ output := flag.String()
+
+ if output != test.expected {
+ t.Errorf("%s does not match %s", output, test.expected)
+ }
+ }
+}
+
+func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_BAR", "2")
+ for _, test := range intFlagTests {
+ flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"}
+ output := flag.String()
+
+ if !strings.HasSuffix(output, " [$APP_BAR]") {
+ t.Errorf("%s does not end with [$APP_BAR]", output)
+ }
+ }
+}
+
+var durationFlagTests = []struct {
+ name string
+ expected string
+}{
+ {"help", "--help \"0\"\t"},
+ {"h", "-h \"0\"\t"},
+}
+
+func TestDurationFlagHelpOutput(t *testing.T) {
+
+ for _, test := range durationFlagTests {
+ flag := cli.DurationFlag{Name: test.name}
+ output := flag.String()
+
+ if output != test.expected {
+ t.Errorf("%s does not match %s", output, test.expected)
+ }
+ }
+}
+
+func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_BAR", "2h3m6s")
+ for _, test := range durationFlagTests {
+ flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
+ output := flag.String()
+
+ if !strings.HasSuffix(output, " [$APP_BAR]") {
+ t.Errorf("%s does not end with [$APP_BAR]", output)
+ }
+ }
+}
+
+var intSliceFlagTests = []struct {
+ name string
+ value *cli.IntSlice
+ expected string
+}{
+ {"help", &cli.IntSlice{}, "--help [--help option --help option]\t"},
+ {"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
+ {"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
+ {"test", func() *cli.IntSlice {
+ i := &cli.IntSlice{}
+ i.Set("9")
+ return i
+ }(), "--test [--test option --test option]\t"},
+}
+
+func TestIntSliceFlagHelpOutput(t *testing.T) {
+
+ for _, test := range intSliceFlagTests {
+ flag := cli.IntSliceFlag{Name: test.name, Value: test.value}
+ output := flag.String()
+
+ if output != test.expected {
+ t.Errorf("%q does not match %q", output, test.expected)
+ }
+ }
+}
+
+func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_SMURF", "42,3")
+ for _, test := range intSliceFlagTests {
+ flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
+ output := flag.String()
+
+ if !strings.HasSuffix(output, " [$APP_SMURF]") {
+ t.Errorf("%q does not end with [$APP_SMURF]", output)
+ }
+ }
+}
+
+var float64FlagTests = []struct {
+ name string
+ expected string
+}{
+ {"help", "--help \"0\"\t"},
+ {"h", "-h \"0\"\t"},
+}
+
+func TestFloat64FlagHelpOutput(t *testing.T) {
+
+ for _, test := range float64FlagTests {
+ flag := cli.Float64Flag{Name: test.name}
+ output := flag.String()
+
+ if output != test.expected {
+ t.Errorf("%s does not match %s", output, test.expected)
+ }
+ }
+}
+
+func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_BAZ", "99.4")
+ for _, test := range float64FlagTests {
+ flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
+ output := flag.String()
+
+ if !strings.HasSuffix(output, " [$APP_BAZ]") {
+ t.Errorf("%s does not end with [$APP_BAZ]", output)
+ }
+ }
+}
+
+var genericFlagTests = []struct {
+ name string
+ value cli.Generic
+ expected string
+}{
+ {"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"},
+ {"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"},
+}
+
+func TestGenericFlagHelpOutput(t *testing.T) {
+
+ for _, test := range genericFlagTests {
+ flag := cli.GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
+ output := flag.String()
+
+ if output != test.expected {
+ t.Errorf("%q does not match %q", output, test.expected)
+ }
+ }
+}
+
+func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_ZAP", "3")
+ for _, test := range genericFlagTests {
+ flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
+ output := flag.String()
+
+ if !strings.HasSuffix(output, " [$APP_ZAP]") {
+ t.Errorf("%s does not end with [$APP_ZAP]", output)
+ }
+ }
+}
+
+func TestParseMultiString(t *testing.T) {
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.StringFlag{Name: "serve, s"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.String("serve") != "10" {
+ t.Errorf("main name not set")
+ }
+ if ctx.String("s") != "10" {
+ t.Errorf("short name not set")
+ }
+ },
+ }).Run([]string{"run", "-s", "10"})
+}
+
+func TestParseMultiStringFromEnv(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_COUNT", "20")
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.String("count") != "20" {
+ t.Errorf("main name not set")
+ }
+ if ctx.String("c") != "20" {
+ t.Errorf("short name not set")
+ }
+ },
+ }).Run([]string{"run"})
+}
+
+func TestParseMultiStringFromEnvCascade(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_COUNT", "20")
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.String("count") != "20" {
+ t.Errorf("main name not set")
+ }
+ if ctx.String("c") != "20" {
+ t.Errorf("short name not set")
+ }
+ },
+ }).Run([]string{"run"})
+}
+
+func TestParseMultiStringSlice(t *testing.T) {
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) {
+ t.Errorf("main name not set")
+ }
+ if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) {
+ t.Errorf("short name not set")
+ }
+ },
+ }).Run([]string{"run", "-s", "10", "-s", "20"})
+}
+
+func TestParseMultiStringSliceFromEnv(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_INTERVALS", "20,30,40")
+
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
+ t.Errorf("main name not set from env")
+ }
+ if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }).Run([]string{"run"})
+}
+
+func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_INTERVALS", "20,30,40")
+
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
+ t.Errorf("main name not set from env")
+ }
+ if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }).Run([]string{"run"})
+}
+
+func TestParseMultiInt(t *testing.T) {
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.IntFlag{Name: "serve, s"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Int("serve") != 10 {
+ t.Errorf("main name not set")
+ }
+ if ctx.Int("s") != 10 {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run", "-s", "10"})
+}
+
+func TestParseMultiIntFromEnv(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_TIMEOUT_SECONDS", "10")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Int("timeout") != 10 {
+ t.Errorf("main name not set")
+ }
+ if ctx.Int("t") != 10 {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+func TestParseMultiIntFromEnvCascade(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_TIMEOUT_SECONDS", "10")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Int("timeout") != 10 {
+ t.Errorf("main name not set")
+ }
+ if ctx.Int("t") != 10 {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+func TestParseMultiIntSlice(t *testing.T) {
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
+ t.Errorf("main name not set")
+ }
+ if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) {
+ t.Errorf("short name not set")
+ }
+ },
+ }).Run([]string{"run", "-s", "10", "-s", "20"})
+}
+
+func TestParseMultiIntSliceFromEnv(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_INTERVALS", "20,30,40")
+
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
+ t.Errorf("main name not set from env")
+ }
+ if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }).Run([]string{"run"})
+}
+
+func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_INTERVALS", "20,30,40")
+
+ (&cli.App{
+ Flags: []cli.Flag{
+ cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
+ t.Errorf("main name not set from env")
+ }
+ if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }).Run([]string{"run"})
+}
+
+func TestParseMultiFloat64(t *testing.T) {
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.Float64Flag{Name: "serve, s"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Float64("serve") != 10.2 {
+ t.Errorf("main name not set")
+ }
+ if ctx.Float64("s") != 10.2 {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run", "-s", "10.2"})
+}
+
+func TestParseMultiFloat64FromEnv(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Float64("timeout") != 15.5 {
+ t.Errorf("main name not set")
+ }
+ if ctx.Float64("t") != 15.5 {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Float64("timeout") != 15.5 {
+ t.Errorf("main name not set")
+ }
+ if ctx.Float64("t") != 15.5 {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+func TestParseMultiBool(t *testing.T) {
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.BoolFlag{Name: "serve, s"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Bool("serve") != true {
+ t.Errorf("main name not set")
+ }
+ if ctx.Bool("s") != true {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run", "--serve"})
+}
+
+func TestParseMultiBoolFromEnv(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_DEBUG", "1")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Bool("debug") != true {
+ t.Errorf("main name not set from env")
+ }
+ if ctx.Bool("d") != true {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+func TestParseMultiBoolFromEnvCascade(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_DEBUG", "1")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.Bool("debug") != true {
+ t.Errorf("main name not set from env")
+ }
+ if ctx.Bool("d") != true {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+func TestParseMultiBoolT(t *testing.T) {
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.BoolTFlag{Name: "serve, s"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.BoolT("serve") != true {
+ t.Errorf("main name not set")
+ }
+ if ctx.BoolT("s") != true {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run", "--serve"})
+}
+
+func TestParseMultiBoolTFromEnv(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_DEBUG", "0")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.BoolT("debug") != false {
+ t.Errorf("main name not set from env")
+ }
+ if ctx.BoolT("d") != false {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+func TestParseMultiBoolTFromEnvCascade(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_DEBUG", "0")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
+ },
+ Action: func(ctx *cli.Context) {
+ if ctx.BoolT("debug") != false {
+ t.Errorf("main name not set from env")
+ }
+ if ctx.BoolT("d") != false {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+type Parser [2]string
+
+func (p *Parser) Set(value string) error {
+ parts := strings.Split(value, ",")
+ if len(parts) != 2 {
+ return fmt.Errorf("invalid format")
+ }
+
+ (*p)[0] = parts[0]
+ (*p)[1] = parts[1]
+
+ return nil
+}
+
+func (p *Parser) String() string {
+ return fmt.Sprintf("%s,%s", p[0], p[1])
+}
+
+func TestParseGeneric(t *testing.T) {
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.GenericFlag{Name: "serve, s", Value: &Parser{}},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) {
+ t.Errorf("main name not set")
+ }
+ if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) {
+ t.Errorf("short name not set")
+ }
+ },
+ }
+ a.Run([]string{"run", "-s", "10,20"})
+}
+
+func TestParseGenericFromEnv(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_SERVE", "20,30")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
+ t.Errorf("main name not set from env")
+ }
+ if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) {
+ t.Errorf("short name not set from env")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
+
+func TestParseGenericFromEnvCascade(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("APP_FOO", "99,2000")
+ a := cli.App{
+ Flags: []cli.Flag{
+ cli.GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"},
+ },
+ Action: func(ctx *cli.Context) {
+ if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {
+ t.Errorf("value not set from env")
+ }
+ },
+ }
+ a.Run([]string{"run"})
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/help.go b/Godeps/_workspace/src/github.com/codegangsta/cli/help.go
new file mode 100644
index 0000000..bfb2788
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/help.go
@@ -0,0 +1,211 @@
+package cli
+
+import "fmt"
+
+// The text template for the Default help topic.
+// cli.go uses text/template to render templates. You can
+// render custom help text by setting this variable.
+var AppHelpTemplate = `NAME:
+ {{.Name}} - {{.Usage}}
+
+USAGE:
+ {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
+
+VERSION:
+ {{.Version}}{{if or .Author .Email}}
+
+AUTHOR:{{if .Author}}
+ {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
+ {{.Email}}{{end}}{{end}}
+
+COMMANDS:
+ {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
+ {{end}}{{if .Flags}}
+GLOBAL OPTIONS:
+ {{range .Flags}}{{.}}
+ {{end}}{{end}}
+`
+
+// The text template for the command help topic.
+// cli.go uses text/template to render templates. You can
+// render custom help text by setting this variable.
+var CommandHelpTemplate = `NAME:
+ {{.Name}} - {{.Usage}}
+
+USAGE:
+ command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}}
+
+DESCRIPTION:
+ {{.Description}}{{end}}{{if .Flags}}
+
+OPTIONS:
+ {{range .Flags}}{{.}}
+ {{end}}{{ end }}
+`
+
+// The text template for the subcommand help topic.
+// cli.go uses text/template to render templates. You can
+// render custom help text by setting this variable.
+var SubcommandHelpTemplate = `NAME:
+ {{.Name}} - {{.Usage}}
+
+USAGE:
+ {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
+
+COMMANDS:
+ {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
+ {{end}}{{if .Flags}}
+OPTIONS:
+ {{range .Flags}}{{.}}
+ {{end}}{{end}}
+`
+
+var helpCommand = Command{
+ Name: "help",
+ ShortName: "h",
+ Usage: "Shows a list of commands or help for one command",
+ Action: func(c *Context) {
+ args := c.Args()
+ if args.Present() {
+ ShowCommandHelp(c, args.First())
+ } else {
+ ShowAppHelp(c)
+ }
+ },
+}
+
+var helpSubcommand = Command{
+ Name: "help",
+ ShortName: "h",
+ Usage: "Shows a list of commands or help for one command",
+ Action: func(c *Context) {
+ args := c.Args()
+ if args.Present() {
+ ShowCommandHelp(c, args.First())
+ } else {
+ ShowSubcommandHelp(c)
+ }
+ },
+}
+
+// Prints help for the App
+type helpPrinter func(templ string, data interface{})
+
+var HelpPrinter helpPrinter = nil
+
+// Prints version for the App
+var VersionPrinter = printVersion
+
+func ShowAppHelp(c *Context) {
+ HelpPrinter(AppHelpTemplate, c.App)
+}
+
+// Prints the list of subcommands as the default app completion method
+func DefaultAppComplete(c *Context) {
+ for _, command := range c.App.Commands {
+ fmt.Fprintln(c.App.Writer, command.Name)
+ if command.ShortName != "" {
+ fmt.Fprintln(c.App.Writer, command.ShortName)
+ }
+ }
+}
+
+// Prints help for the given command
+func ShowCommandHelp(c *Context, command string) {
+ for _, c := range c.App.Commands {
+ if c.HasName(command) {
+ HelpPrinter(CommandHelpTemplate, c)
+ return
+ }
+ }
+
+ if c.App.CommandNotFound != nil {
+ c.App.CommandNotFound(c, command)
+ } else {
+ fmt.Fprintf(c.App.Writer, "No help topic for '%v'\n", command)
+ }
+}
+
+// Prints help for the given subcommand
+func ShowSubcommandHelp(c *Context) {
+ ShowCommandHelp(c, c.Command.Name)
+}
+
+// Prints the version number of the App
+func ShowVersion(c *Context) {
+ VersionPrinter(c)
+}
+
+func printVersion(c *Context) {
+ fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
+}
+
+// Prints the lists of commands within a given context
+func ShowCompletions(c *Context) {
+ a := c.App
+ if a != nil && a.BashComplete != nil {
+ a.BashComplete(c)
+ }
+}
+
+// Prints the custom completions for a given command
+func ShowCommandCompletions(ctx *Context, command string) {
+ c := ctx.App.Command(command)
+ if c != nil && c.BashComplete != nil {
+ c.BashComplete(ctx)
+ }
+}
+
+func checkVersion(c *Context) bool {
+ if c.GlobalBool("version") {
+ ShowVersion(c)
+ return true
+ }
+
+ return false
+}
+
+func checkHelp(c *Context) bool {
+ if c.GlobalBool("h") || c.GlobalBool("help") {
+ ShowAppHelp(c)
+ return true
+ }
+
+ return false
+}
+
+func checkCommandHelp(c *Context, name string) bool {
+ if c.Bool("h") || c.Bool("help") {
+ ShowCommandHelp(c, name)
+ return true
+ }
+
+ return false
+}
+
+func checkSubcommandHelp(c *Context) bool {
+ if c.GlobalBool("h") || c.GlobalBool("help") {
+ ShowSubcommandHelp(c)
+ return true
+ }
+
+ return false
+}
+
+func checkCompletions(c *Context) bool {
+ if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
+ ShowCompletions(c)
+ return true
+ }
+
+ return false
+}
+
+func checkCommandCompletions(c *Context, name string) bool {
+ if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
+ ShowCommandCompletions(c, name)
+ return true
+ }
+
+ return false
+}
diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go
new file mode 100644
index 0000000..cdc4feb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go
@@ -0,0 +1,19 @@
+package cli_test
+
+import (
+ "reflect"
+ "testing"
+)
+
+/* Test Helpers */
+func expect(t *testing.T, a interface{}, b interface{}) {
+ if a != b {
+ t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
+ }
+}
+
+func refute(t *testing.T, a interface{}, b interface{}) {
+ if a == b {
+ t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/.gitignore b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/.gitignore
new file mode 100644
index 0000000..80bed65
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+bin
+
+
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/LICENSE b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/LICENSE
new file mode 100644
index 0000000..df83a9c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/LICENSE
@@ -0,0 +1,8 @@
+Copyright (c) 2012 Dave Grijalva
+
+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/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/README.md b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/README.md
new file mode 100644
index 0000000..f0a8f9d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/README.md
@@ -0,0 +1,55 @@
+A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-jones-json-web-token.html)
+
+**NOTICE:** We recently introduced a breaking change in the API. Please refer to [VERSION_HISTORY.md](VERSION_HISTORY.md) for details.
+
+## What the heck is a JWT?
+
+In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way.
+
+The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used.
+
+The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own.
+
+## What's in the box?
+
+This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are RSA256 and HMAC SHA256, though hooks are present for adding your own.
+
+## Parse and Verify
+
+Parsing and verifying tokens is pretty straight forward. You pass in the token and a function for looking up the key. This is done as a callback since you may need to parse the token to find out what signing method and key was used.
+
+```go
+ token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) {
+ return myLookupKey(token.Header["kid"])
+ })
+
+ if err == nil && token.Valid {
+ deliverGoodness("!")
+ } else {
+ deliverUtterRejection(":(")
+ }
+```
+
+## Create a token
+
+```go
+ // Create the token
+ token := jwt.New(SigningMethodHS256)
+ // Set some claims
+ token.Claims["foo"] = "bar"
+ token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
+ // Sign and get the complete encoded token as a string
+ tokenString, err := token.SignedString(mySigningKey)
+```
+
+## Project Status & Versioning
+
+This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
+
+This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases).
+
+## More
+
+Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).
+
+The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. For a more http centric example, see [this gist](https://gist.github.com/cryptix/45c33ecf0ae54828e63b).
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md
new file mode 100644
index 0000000..972722e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md
@@ -0,0 +1,54 @@
+## `jwt-go` Version History
+
+#### 2.2.0
+
+* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic.
+
+#### 2.1.0
+
+Backwards compatible API change that was missed in 2.0.0.
+
+* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte`
+
+#### 2.0.0
+
+There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
+
+The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
+
+It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
+
+* **Compatibility Breaking Changes**
+ * `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct`
+ * `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct`
+ * `KeyFunc` now returns `interface{}` instead of `[]byte`
+ * `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key
+ * `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key
+* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type.
+ * Added public package global `SigningMethodHS256`
+ * Added public package global `SigningMethodHS384`
+ * Added public package global `SigningMethodHS512`
+* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type.
+ * Added public package global `SigningMethodRS256`
+ * Added public package global `SigningMethodRS384`
+ * Added public package global `SigningMethodRS512`
+* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged.
+* Refactored the RSA implementation to be easier to read
+* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM`
+
+#### 1.0.2
+
+* Fixed bug in parsing public keys from certificates
+* Added more tests around the parsing of keys for RS256
+* Code refactoring in RS256 implementation. No functional changes
+
+#### 1.0.1
+
+* Fixed panic if RS256 signing method was passed an invalid key
+
+#### 1.0.0
+
+* First versioned release
+* API stabilized
+* Supports creating, signing, parsing, and validating JWT tokens
+* Supports RS256 and HS256 signing methods
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/cmd/jwt/app.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/cmd/jwt/app.go
new file mode 100644
index 0000000..62cb9a4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/cmd/jwt/app.go
@@ -0,0 +1,186 @@
+// A useful example app. You can use this to debug your tokens on the command line.
+// This is also a great place to look at how you might use this library.
+//
+// Example usage:
+// The following will create and sign a token, then verify it and output the original claims.
+// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "regexp"
+
+ "github.com/dgrijalva/jwt-go"
+)
+
+var (
+ // Options
+ flagAlg = flag.String("alg", "", "signing algorithm identifier")
+ flagKey = flag.String("key", "", "path to key file or '-' to read from stdin")
+ flagCompact = flag.Bool("compact", false, "output compact JSON")
+ flagDebug = flag.Bool("debug", false, "print out all kinds of debug data")
+
+ // Modes - exactly one of these is required
+ flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin")
+ flagVerify = flag.String("verify", "", "path to JWT token to verify or '-' to read from stdin")
+)
+
+func main() {
+ // Usage message if you ask for -help or if you mess up inputs.
+ flag.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(os.Stderr, " One of the following flags is required: sign, verify\n")
+ flag.PrintDefaults()
+ }
+
+ // Parse command line options
+ flag.Parse()
+
+ // Do the thing. If something goes wrong, print error to stderr
+ // and exit with a non-zero status code
+ if err := start(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+}
+
+// Figure out which thing to do and then do that
+func start() error {
+ if *flagSign != "" {
+ return signToken()
+ } else if *flagVerify != "" {
+ return verifyToken()
+ } else {
+ flag.Usage()
+ return fmt.Errorf("None of the required flags are present. What do you want me to do?")
+ }
+}
+
+// Helper func: Read input from specified file or stdin
+func loadData(p string) ([]byte, error) {
+ if p == "" {
+ return nil, fmt.Errorf("No path specified")
+ }
+
+ var rdr io.Reader
+ if p == "-" {
+ rdr = os.Stdin
+ } else {
+ if f, err := os.Open(p); err == nil {
+ rdr = f
+ defer f.Close()
+ } else {
+ return nil, err
+ }
+ }
+ return ioutil.ReadAll(rdr)
+}
+
+// Print a json object in accordance with the prophecy (or the command line options)
+func printJSON(j interface{}) error {
+ var out []byte
+ var err error
+
+ if *flagCompact == false {
+ out, err = json.MarshalIndent(j, "", " ")
+ } else {
+ out, err = json.Marshal(j)
+ }
+
+ if err == nil {
+ fmt.Println(string(out))
+ }
+
+ return err
+}
+
+// Verify a token and output the claims. This is a great example
+// of how to verify and view a token.
+func verifyToken() error {
+ // get the token
+ tokData, err := loadData(*flagVerify)
+ if err != nil {
+ return fmt.Errorf("Couldn't read token: %v", err)
+ }
+
+ // trim possible whitespace from token
+ tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{})
+ if *flagDebug {
+ fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData))
+ }
+
+ // Parse the token. Load the key from command line option
+ token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) {
+ return loadData(*flagKey)
+ })
+
+ // Print some debug data
+ if *flagDebug && token != nil {
+ fmt.Fprintf(os.Stderr, "Header:\n%v\n", token.Header)
+ fmt.Fprintf(os.Stderr, "Claims:\n%v\n", token.Claims)
+ }
+
+ // Print an error if we can't parse for some reason
+ if err != nil {
+ return fmt.Errorf("Couldn't parse token: %v", err)
+ }
+
+ // Is token invalid?
+ if !token.Valid {
+ return fmt.Errorf("Token is invalid")
+ }
+
+ // Print the token details
+ if err := printJSON(token.Claims); err != nil {
+ return fmt.Errorf("Failed to output claims: %v", err)
+ }
+
+ return nil
+}
+
+// Create, sign, and output a token. This is a great, simple example of
+// how to use this library to create and sign a token.
+func signToken() error {
+ // get the token data from command line arguments
+ tokData, err := loadData(*flagSign)
+ if err != nil {
+ return fmt.Errorf("Couldn't read token: %v", err)
+ } else if *flagDebug {
+ fmt.Fprintf(os.Stderr, "Token: %v bytes", len(tokData))
+ }
+
+ // parse the JSON of the claims
+ var claims map[string]interface{}
+ if err := json.Unmarshal(tokData, &claims); err != nil {
+ return fmt.Errorf("Couldn't parse claims JSON: %v", err)
+ }
+
+ // get the key
+ keyData, err := loadData(*flagKey)
+ if err != nil {
+ return fmt.Errorf("Couldn't read key: %v", err)
+ }
+
+ // get the signing alg
+ alg := jwt.GetSigningMethod(*flagAlg)
+ if alg == nil {
+ return fmt.Errorf("Couldn't find signing method: %v", *flagAlg)
+ }
+
+ // create a new token
+ token := jwt.New(alg)
+ token.Claims = claims
+
+ if out, err := token.SignedString(keyData); err == nil {
+ fmt.Println(out)
+ } else {
+ return fmt.Errorf("Error signing token: %v", err)
+ }
+
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/doc.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/doc.go
new file mode 100644
index 0000000..a86dc1a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/doc.go
@@ -0,0 +1,4 @@
+// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
+//
+// See README.md for more info.
+package jwt
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/example_test.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/example_test.go
new file mode 100644
index 0000000..bfd7271
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/example_test.go
@@ -0,0 +1,52 @@
+package jwt_test
+
+import (
+ "fmt"
+ "github.com/dgrijalva/jwt-go"
+ "time"
+)
+
+func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, error)) {
+ token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) {
+ return myLookupKey(token.Header["kid"])
+ })
+
+ if err == nil && token.Valid {
+ fmt.Println("Your token is valid. I like your style.")
+ } else {
+ fmt.Println("This token is terrible! I cannot accept this.")
+ }
+}
+
+func ExampleNew(mySigningKey string) (string, error) {
+ // Create the token
+ token := jwt.New(jwt.SigningMethodHS256)
+ // Set some claims
+ token.Claims["foo"] = "bar"
+ token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
+ // Sign and get the complete encoded token as a string
+ tokenString, err := token.SignedString(mySigningKey)
+ return tokenString, err
+}
+
+func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (interface{}, error)) {
+ token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) {
+ return myLookupKey(token.Header["kid"])
+ })
+
+ if token.Valid {
+ fmt.Println("You look nice today")
+ } else if ve, ok := err.(*jwt.ValidationError); ok {
+ if ve.Errors&jwt.ValidationErrorMalformed != 0 {
+ fmt.Println("That's not even a token")
+ } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
+ // Token is either expired or not active yet
+ fmt.Println("Timing is everything")
+ } else {
+ fmt.Println("Couldn't handle this token:", err)
+ }
+ } else {
+ fmt.Println("Couldn't handle this token:", err)
+ }
+
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/hmac.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/hmac.go
new file mode 100644
index 0000000..2d7f1bf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/hmac.go
@@ -0,0 +1,82 @@
+package jwt
+
+import (
+ "crypto"
+ "crypto/hmac"
+ "errors"
+)
+
+// Implements the HMAC-SHA family of signing methods signing methods
+type SigningMethodHMAC struct {
+ Name string
+ Hash crypto.Hash
+}
+
+// Specific instances for HS256 and company
+var (
+ SigningMethodHS256 *SigningMethodHMAC
+ SigningMethodHS384 *SigningMethodHMAC
+ SigningMethodHS512 *SigningMethodHMAC
+ ErrSignatureInvalid = errors.New("signature is invalid")
+)
+
+func init() {
+ // HS256
+ SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256}
+ RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod {
+ return SigningMethodHS256
+ })
+
+ // HS384
+ SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384}
+ RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod {
+ return SigningMethodHS384
+ })
+
+ // HS512
+ SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512}
+ RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod {
+ return SigningMethodHS512
+ })
+}
+
+func (m *SigningMethodHMAC) Alg() string {
+ return m.Name
+}
+
+func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
+ if keyBytes, ok := key.([]byte); ok {
+ var sig []byte
+ var err error
+ if sig, err = DecodeSegment(signature); err == nil {
+ if !m.Hash.Available() {
+ return ErrHashUnavailable
+ }
+
+ hasher := hmac.New(m.Hash.New, keyBytes)
+ hasher.Write([]byte(signingString))
+
+ if !hmac.Equal(sig, hasher.Sum(nil)) {
+ err = ErrSignatureInvalid
+ }
+ }
+ return err
+ }
+
+ return ErrInvalidKey
+}
+
+func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
+ if keyBytes, ok := key.([]byte); ok {
+ if !m.Hash.Available() {
+ return "", ErrHashUnavailable
+ }
+
+ hasher := hmac.New(m.Hash.New, keyBytes)
+ hasher.Write([]byte(signingString))
+
+ return EncodeSegment(hasher.Sum(nil)), nil
+ }
+
+ return "", ErrInvalidKey
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/hmac_test.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/hmac_test.go
new file mode 100644
index 0000000..3cd4203
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/hmac_test.go
@@ -0,0 +1,79 @@
+package jwt_test
+
+import (
+ "github.com/dgrijalva/jwt-go"
+ "io/ioutil"
+ "strings"
+ "testing"
+)
+
+var hmacTestData = []struct {
+ name string
+ tokenString string
+ alg string
+ claims map[string]interface{}
+ valid bool
+}{
+ {
+ "web sample",
+ "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
+ "HS256",
+ map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
+ true,
+ },
+ {
+ "HS384",
+ "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy",
+ "HS384",
+ map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
+ true,
+ },
+ {
+ "HS512",
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw",
+ "HS512",
+ map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
+ true,
+ },
+ {
+ "web sample: invalid",
+ "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo",
+ "HS256",
+ map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
+ false,
+ },
+}
+
+// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
+var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey")
+
+func TestHMACVerify(t *testing.T) {
+ for _, data := range hmacTestData {
+ parts := strings.Split(data.tokenString, ".")
+
+ method := jwt.GetSigningMethod(data.alg)
+ err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey)
+ if data.valid && err != nil {
+ t.Errorf("[%v] Error while verifying key: %v", data.name, err)
+ }
+ if !data.valid && err == nil {
+ t.Errorf("[%v] Invalid key passed validation", data.name)
+ }
+ }
+}
+
+func TestHMACSign(t *testing.T) {
+ for _, data := range hmacTestData {
+ if data.valid {
+ parts := strings.Split(data.tokenString, ".")
+ method := jwt.GetSigningMethod(data.alg)
+ sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey)
+ if err != nil {
+ t.Errorf("[%v] Error signing token: %v", data.name, err)
+ }
+ if sig != parts[2] {
+ t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
+ }
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/jwt.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/jwt.go
new file mode 100644
index 0000000..f7b4322
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/jwt.go
@@ -0,0 +1,237 @@
+package jwt
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "net/http"
+ "strings"
+ "time"
+)
+
+// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
+// You can override it to use another time value. This is useful for testing or if your
+// server uses a different time zone than your tokens.
+var TimeFunc = time.Now
+
+// Parse methods use this callback function to supply
+// the key for verification. The function receives the parsed,
+// but unverified Token. This allows you to use propries in the
+// Header of the token (such as `kid`) to identify which key to use.
+type Keyfunc func(*Token) (interface{}, error)
+
+// Error constants
+var (
+ ErrInvalidKey = errors.New("key is invalid or of invalid type")
+ ErrHashUnavailable = errors.New("the requested hash function is unavailable")
+ ErrNoTokenInRequest = errors.New("no token present in request")
+)
+
+// A JWT Token. Different fields will be used depending on whether you're
+// creating or parsing/verifying a token.
+type Token struct {
+ Raw string // The raw token. Populated when you Parse a token
+ Method SigningMethod // The signing method used or to be used
+ Header map[string]interface{} // The first segment of the token
+ Claims map[string]interface{} // The second segment of the token
+ Signature string // The third segment of the token. Populated when you Parse a token
+ Valid bool // Is the token valid? Populated when you Parse/Verify a token
+}
+
+// Create a new Token. Takes a signing method
+func New(method SigningMethod) *Token {
+ return &Token{
+ Header: map[string]interface{}{
+ "typ": "JWT",
+ "alg": method.Alg(),
+ },
+ Claims: make(map[string]interface{}),
+ Method: method,
+ }
+}
+
+// Get the complete, signed token
+func (t *Token) SignedString(key interface{}) (string, error) {
+ var sig, sstr string
+ var err error
+ if sstr, err = t.SigningString(); err != nil {
+ return "", err
+ }
+ if sig, err = t.Method.Sign(sstr, key); err != nil {
+ return "", err
+ }
+ return strings.Join([]string{sstr, sig}, "."), nil
+}
+
+// Generate the signing string. This is the
+// most expensive part of the whole deal. Unless you
+// need this for something special, just go straight for
+// the SignedString.
+func (t *Token) SigningString() (string, error) {
+ var err error
+ parts := make([]string, 2)
+ for i, _ := range parts {
+ var source map[string]interface{}
+ if i == 0 {
+ source = t.Header
+ } else {
+ source = t.Claims
+ }
+
+ var jsonValue []byte
+ if jsonValue, err = json.Marshal(source); err != nil {
+ return "", err
+ }
+
+ parts[i] = EncodeSegment(jsonValue)
+ }
+ return strings.Join(parts, "."), nil
+}
+
+// Parse, validate, and return a token.
+// keyFunc will receive the parsed token and should return the key for validating.
+// If everything is kosher, err will be nil
+func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
+ parts := strings.Split(tokenString, ".")
+ if len(parts) != 3 {
+ return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed}
+ }
+
+ var err error
+ token := &Token{Raw: tokenString}
+ // parse Header
+ var headerBytes []byte
+ if headerBytes, err = DecodeSegment(parts[0]); err != nil {
+ return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
+ }
+ if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
+ return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
+ }
+
+ // parse Claims
+ var claimBytes []byte
+ if claimBytes, err = DecodeSegment(parts[1]); err != nil {
+ return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
+ }
+ if err = json.Unmarshal(claimBytes, &token.Claims); err != nil {
+ return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
+ }
+
+ // Lookup signature method
+ if method, ok := token.Header["alg"].(string); ok {
+ if token.Method = GetSigningMethod(method); token.Method == nil {
+ return token, &ValidationError{err: "signing method (alg) is unavailable.", Errors: ValidationErrorUnverifiable}
+ }
+ } else {
+ return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable}
+ }
+
+ // Lookup key
+ var key interface{}
+ if keyFunc == nil {
+ // keyFunc was not provided. short circuiting validation
+ return token, &ValidationError{err: "no Keyfunc was provided.", Errors: ValidationErrorUnverifiable}
+ }
+ if key, err = keyFunc(token); err != nil {
+ // keyFunc returned an error
+ return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable}
+ }
+
+ // Check expiration times
+ vErr := &ValidationError{}
+ now := TimeFunc().Unix()
+ if exp, ok := token.Claims["exp"].(float64); ok {
+ if now > int64(exp) {
+ vErr.err = "token is expired"
+ vErr.Errors |= ValidationErrorExpired
+ }
+ }
+ if nbf, ok := token.Claims["nbf"].(float64); ok {
+ if now < int64(nbf) {
+ vErr.err = "token is not valid yet"
+ vErr.Errors |= ValidationErrorNotValidYet
+ }
+ }
+
+ // Perform validation
+ if err = token.Method.Verify(strings.Join(parts[0:2], "."), parts[2], key); err != nil {
+ vErr.err = err.Error()
+ vErr.Errors |= ValidationErrorSignatureInvalid
+ }
+
+ if vErr.valid() {
+ token.Valid = true
+ return token, nil
+ }
+
+ return token, vErr
+}
+
+// The errors that might occur when parsing and validating a token
+const (
+ ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
+ ValidationErrorUnverifiable // Token could not be verified because of signing problems
+ ValidationErrorSignatureInvalid // Signature validation failed
+ ValidationErrorExpired // Exp validation failed
+ ValidationErrorNotValidYet // NBF validation failed
+)
+
+// The error from Parse if token is not valid
+type ValidationError struct {
+ err string
+ Errors uint32 // bitfield. see ValidationError... constants
+}
+
+// Validation error is an error type
+func (e ValidationError) Error() string {
+ if e.err == "" {
+ return "token is invalid"
+ }
+ return e.err
+}
+
+// No errors
+func (e *ValidationError) valid() bool {
+ if e.Errors > 0 {
+ return false
+ }
+ return true
+}
+
+// Try to find the token in an http.Request.
+// This method will call ParseMultipartForm if there's no token in the header.
+// Currently, it looks in the Authorization header as well as
+// looking for an 'access_token' request parameter in req.Form.
+func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err error) {
+
+ // Look for an Authorization header
+ if ah := req.Header.Get("Authorization"); ah != "" {
+ // Should be a bearer token
+ if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" {
+ return Parse(ah[7:], keyFunc)
+ }
+ }
+
+ // Look for "access_token" parameter
+ req.ParseMultipartForm(10e6)
+ if tokStr := req.Form.Get("access_token"); tokStr != "" {
+ return Parse(tokStr, keyFunc)
+ }
+
+ return nil, ErrNoTokenInRequest
+
+}
+
+// Encode JWT specific base64url encoding with padding stripped
+func EncodeSegment(seg []byte) string {
+ return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
+}
+
+// Decode JWT specific base64url encoding with padding stripped
+func DecodeSegment(seg string) ([]byte, error) {
+ if l := len(seg) % 4; l > 0 {
+ seg += strings.Repeat("=", 4-l)
+ }
+
+ return base64.URLEncoding.DecodeString(seg)
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/jwt_test.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/jwt_test.go
new file mode 100644
index 0000000..0eeee27
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/jwt_test.go
@@ -0,0 +1,174 @@
+package jwt_test
+
+import (
+ "fmt"
+ "github.com/dgrijalva/jwt-go"
+ "io/ioutil"
+ "net/http"
+ "reflect"
+ "testing"
+ "time"
+)
+
+var (
+ jwtTestDefaultKey []byte
+ defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil }
+ emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil }
+ errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, fmt.Errorf("error loading key") }
+ nilKeyFunc jwt.Keyfunc = nil
+)
+
+var jwtTestData = []struct {
+ name string
+ tokenString string
+ keyfunc jwt.Keyfunc
+ claims map[string]interface{}
+ valid bool
+ errors uint32
+}{
+ {
+ "basic",
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+ defaultKeyFunc,
+ map[string]interface{}{"foo": "bar"},
+ true,
+ 0,
+ },
+ {
+ "basic expired",
+ "", // autogen
+ defaultKeyFunc,
+ map[string]interface{}{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
+ false,
+ jwt.ValidationErrorExpired,
+ },
+ {
+ "basic nbf",
+ "", // autogen
+ defaultKeyFunc,
+ map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
+ false,
+ jwt.ValidationErrorNotValidYet,
+ },
+ {
+ "expired and nbf",
+ "", // autogen
+ defaultKeyFunc,
+ map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)},
+ false,
+ jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
+ },
+ {
+ "basic invalid",
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+ defaultKeyFunc,
+ map[string]interface{}{"foo": "bar"},
+ false,
+ jwt.ValidationErrorSignatureInvalid,
+ },
+ {
+ "basic nokeyfunc",
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+ nilKeyFunc,
+ map[string]interface{}{"foo": "bar"},
+ false,
+ jwt.ValidationErrorUnverifiable,
+ },
+ {
+ "basic nokey",
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+ emptyKeyFunc,
+ map[string]interface{}{"foo": "bar"},
+ false,
+ jwt.ValidationErrorSignatureInvalid,
+ },
+ {
+ "basic errorkey",
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+ errorKeyFunc,
+ map[string]interface{}{"foo": "bar"},
+ false,
+ jwt.ValidationErrorUnverifiable,
+ },
+}
+
+func init() {
+ var e error
+ if jwtTestDefaultKey, e = ioutil.ReadFile("test/sample_key.pub"); e != nil {
+ panic(e)
+ }
+}
+
+func makeSample(c map[string]interface{}) string {
+ key, e := ioutil.ReadFile("test/sample_key")
+ if e != nil {
+ panic(e.Error())
+ }
+
+ token := jwt.New(jwt.SigningMethodRS256)
+ token.Claims = c
+ s, e := token.SignedString(key)
+
+ if e != nil {
+ panic(e.Error())
+ }
+
+ return s
+}
+
+func TestJWT(t *testing.T) {
+ for _, data := range jwtTestData {
+ if data.tokenString == "" {
+ data.tokenString = makeSample(data.claims)
+ }
+ token, err := jwt.Parse(data.tokenString, data.keyfunc)
+
+ if !reflect.DeepEqual(data.claims, token.Claims) {
+ t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
+ }
+ if data.valid && err != nil {
+ t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err)
+ }
+ if !data.valid && err == nil {
+ t.Errorf("[%v] Invalid token passed validation", data.name)
+ }
+ if data.errors != 0 {
+ if err == nil {
+ t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
+ } else {
+ // compare the bitfield part of the error
+ if err.(*jwt.ValidationError).Errors != data.errors {
+ t.Errorf("[%v] Errors don't match expectation", data.name)
+ }
+
+ }
+ }
+ }
+}
+
+func TestParseRequest(t *testing.T) {
+ // Bearer token request
+ for _, data := range jwtTestData {
+ if data.tokenString == "" {
+ data.tokenString = makeSample(data.claims)
+ }
+
+ r, _ := http.NewRequest("GET", "/", nil)
+ r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", data.tokenString))
+ token, err := jwt.ParseFromRequest(r, data.keyfunc)
+
+ if token == nil {
+ t.Errorf("[%v] Token was not found: %v", data.name, err)
+ continue
+ }
+ if !reflect.DeepEqual(data.claims, token.Claims) {
+ t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
+ }
+ if data.valid && err != nil {
+ t.Errorf("[%v] Error while verifying token: %v", data.name, err)
+ }
+ if !data.valid && err == nil {
+ t.Errorf("[%v] Invalid token passed validation", data.name)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa.go
new file mode 100644
index 0000000..cddffce
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa.go
@@ -0,0 +1,114 @@
+package jwt
+
+import (
+ "crypto"
+ "crypto/rand"
+ "crypto/rsa"
+)
+
+// Implements the RSA family of signing methods signing methods
+type SigningMethodRSA struct {
+ Name string
+ Hash crypto.Hash
+}
+
+// Specific instances for RS256 and company
+var (
+ SigningMethodRS256 *SigningMethodRSA
+ SigningMethodRS384 *SigningMethodRSA
+ SigningMethodRS512 *SigningMethodRSA
+)
+
+func init() {
+ // RS256
+ SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256}
+ RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod {
+ return SigningMethodRS256
+ })
+
+ // RS384
+ SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384}
+ RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod {
+ return SigningMethodRS384
+ })
+
+ // RS512
+ SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512}
+ RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod {
+ return SigningMethodRS512
+ })
+}
+
+func (m *SigningMethodRSA) Alg() string {
+ return m.Name
+}
+
+// Implements the Verify method from SigningMethod
+// For this signing method, must be either a PEM encoded PKCS1 or PKCS8 RSA public key as
+// []byte, or an rsa.PublicKey structure.
+func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
+ var err error
+
+ // Decode the signature
+ var sig []byte
+ if sig, err = DecodeSegment(signature); err != nil {
+ return err
+ }
+
+ var rsaKey *rsa.PublicKey
+
+ switch k := key.(type) {
+ case []byte:
+ if rsaKey, err = ParseRSAPublicKeyFromPEM(k); err != nil {
+ return err
+ }
+ case *rsa.PublicKey:
+ rsaKey = k
+ default:
+ return ErrInvalidKey
+ }
+
+ // Create hasher
+ if !m.Hash.Available() {
+ return ErrHashUnavailable
+ }
+ hasher := m.Hash.New()
+ hasher.Write([]byte(signingString))
+
+ // Verify the signature
+ return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
+}
+
+// Implements the Sign method from SigningMethod
+// For this signing method, must be either a PEM encoded PKCS1 or PKCS8 RSA private key as
+// []byte, or an rsa.PrivateKey structure.
+func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
+ var err error
+ var rsaKey *rsa.PrivateKey
+
+ switch k := key.(type) {
+ case []byte:
+ if rsaKey, err = ParseRSAPrivateKeyFromPEM(k); err != nil {
+ return "", err
+ }
+ case *rsa.PrivateKey:
+ rsaKey = k
+ default:
+ return "", ErrInvalidKey
+ }
+
+ // Create the hasher
+ if !m.Hash.Available() {
+ return "", ErrHashUnavailable
+ }
+
+ hasher := m.Hash.New()
+ hasher.Write([]byte(signingString))
+
+ // Sign the string and return the encoded bytes
+ if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
+ return EncodeSegment(sigBytes), nil
+ } else {
+ return "", err
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa_test.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa_test.go
new file mode 100644
index 0000000..b17e9f2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa_test.go
@@ -0,0 +1,144 @@
+package jwt_test
+
+import (
+ "github.com/dgrijalva/jwt-go"
+ "io/ioutil"
+ "strings"
+ "testing"
+)
+
+var rsaTestData = []struct {
+ name string
+ tokenString string
+ alg string
+ claims map[string]interface{}
+ valid bool
+}{
+ {
+ "Basic RS256",
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+ "RS256",
+ map[string]interface{}{"foo": "bar"},
+ true,
+ },
+ {
+ "Basic RS384",
+ "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
+ "RS384",
+ map[string]interface{}{"foo": "bar"},
+ true,
+ },
+ {
+ "Basic RS512",
+ "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ",
+ "RS512",
+ map[string]interface{}{"foo": "bar"},
+ true,
+ },
+ {
+ "basic invalid: foo => bar",
+ "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
+ "RS256",
+ map[string]interface{}{"foo": "bar"},
+ false,
+ },
+}
+
+func TestRSAVerify(t *testing.T) {
+ key, _ := ioutil.ReadFile("test/sample_key.pub")
+
+ for _, data := range rsaTestData {
+ parts := strings.Split(data.tokenString, ".")
+
+ method := jwt.GetSigningMethod(data.alg)
+ err := method.Verify(strings.Join(parts[0:2], "."), parts[2], key)
+ if data.valid && err != nil {
+ t.Errorf("[%v] Error while verifying key: %v", data.name, err)
+ }
+ if !data.valid && err == nil {
+ t.Errorf("[%v] Invalid key passed validation", data.name)
+ }
+ }
+}
+
+func TestRSASign(t *testing.T) {
+ key, _ := ioutil.ReadFile("test/sample_key")
+
+ for _, data := range rsaTestData {
+ if data.valid {
+ parts := strings.Split(data.tokenString, ".")
+ method := jwt.GetSigningMethod(data.alg)
+ sig, err := method.Sign(strings.Join(parts[0:2], "."), key)
+ if err != nil {
+ t.Errorf("[%v] Error signing token: %v", data.name, err)
+ }
+ if sig != parts[2] {
+ t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
+ }
+ }
+ }
+}
+
+func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) {
+ key, _ := ioutil.ReadFile("test/sample_key.pub")
+ parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ testData := rsaTestData[0]
+ parts := strings.Split(testData.tokenString, ".")
+ err = jwt.SigningMethodRS256.Verify(strings.Join(parts[0:2], "."), parts[2], parsedKey)
+ if err != nil {
+ t.Errorf("[%v] Error while verifying key: %v", testData.name, err)
+ }
+}
+
+func TestRSAWithPreParsedPrivateKey(t *testing.T) {
+ key, _ := ioutil.ReadFile("test/sample_key")
+ parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key)
+ if err != nil {
+ t.Fatal(err)
+ }
+ testData := rsaTestData[0]
+ parts := strings.Split(testData.tokenString, ".")
+ sig, err := jwt.SigningMethodRS256.Sign(strings.Join(parts[0:2], "."), parsedKey)
+ if err != nil {
+ t.Errorf("[%v] Error signing token: %v", testData.name, err)
+ }
+ if sig != parts[2] {
+ t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", testData.name, sig, parts[2])
+ }
+}
+
+func TestRSAKeyParsing(t *testing.T) {
+ key, _ := ioutil.ReadFile("test/sample_key")
+ pubKey, _ := ioutil.ReadFile("test/sample_key.pub")
+ badKey := []byte("All your base are belong to key")
+
+ // Test parsePrivateKey
+ if _, e := jwt.ParseRSAPrivateKeyFromPEM(key); e != nil {
+ t.Errorf("Failed to parse valid private key: %v", e)
+ }
+
+ if k, e := jwt.ParseRSAPrivateKeyFromPEM(pubKey); e == nil {
+ t.Errorf("Parsed public key as valid private key: %v", k)
+ }
+
+ if k, e := jwt.ParseRSAPrivateKeyFromPEM(badKey); e == nil {
+ t.Errorf("Parsed invalid key as valid private key: %v", k)
+ }
+
+ // Test parsePublicKey
+ if _, e := jwt.ParseRSAPublicKeyFromPEM(pubKey); e != nil {
+ t.Errorf("Failed to parse valid public key: %v", e)
+ }
+
+ if k, e := jwt.ParseRSAPublicKeyFromPEM(key); e == nil {
+ t.Errorf("Parsed private key as valid public key: %v", k)
+ }
+
+ if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil {
+ t.Errorf("Parsed invalid key as valid private key: %v", k)
+ }
+
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa_utils.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa_utils.go
new file mode 100644
index 0000000..6f3b6ff
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/rsa_utils.go
@@ -0,0 +1,68 @@
+package jwt
+
+import (
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/pem"
+ "errors"
+)
+
+var (
+ ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
+ ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key")
+)
+
+// Parse PEM encoded PKCS1 or PKCS8 private key
+func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
+ var err error
+
+ // Parse PEM block
+ var block *pem.Block
+ if block, _ = pem.Decode(key); block == nil {
+ return nil, ErrKeyMustBePEMEncoded
+ }
+
+ var parsedKey interface{}
+ if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
+ if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
+ return nil, err
+ }
+ }
+
+ var pkey *rsa.PrivateKey
+ var ok bool
+ if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
+ return nil, ErrNotRSAPrivateKey
+ }
+
+ return pkey, nil
+}
+
+// Parse PEM encoded PKCS1 or PKCS8 public key
+func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
+ var err error
+
+ // Parse PEM block
+ var block *pem.Block
+ if block, _ = pem.Decode(key); block == nil {
+ return nil, ErrKeyMustBePEMEncoded
+ }
+
+ // Parse the key
+ var parsedKey interface{}
+ if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
+ if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
+ parsedKey = cert.PublicKey
+ } else {
+ return nil, err
+ }
+ }
+
+ var pkey *rsa.PublicKey
+ var ok bool
+ if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
+ return nil, ErrNotRSAPrivateKey
+ }
+
+ return pkey, nil
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/signing_method.go b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/signing_method.go
new file mode 100644
index 0000000..109dd0f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/signing_method.go
@@ -0,0 +1,24 @@
+package jwt
+
+var signingMethods = map[string]func() SigningMethod{}
+
+// Signing method
+type SigningMethod interface {
+ Verify(signingString, signature string, key interface{}) error
+ Sign(signingString string, key interface{}) (string, error)
+ Alg() string
+}
+
+// Register the "alg" name and a factory function for signing method.
+// This is typically done during init() in the method's implementation
+func RegisterSigningMethod(alg string, f func() SigningMethod) {
+ signingMethods[alg] = f
+}
+
+// Get a signing method from an "alg" string
+func GetSigningMethod(alg string) (method SigningMethod) {
+ if methodF, ok := signingMethods[alg]; ok {
+ method = methodF()
+ }
+ return
+}
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/hmacTestKey b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/hmacTestKey
new file mode 100644
index 0000000..435b8dd
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/hmacTestKey
@@ -0,0 +1 @@
+#5K+¥¼ƒ~ew{¦Z³(æðTÉ(©„²ÒP.¿ÓûZ’ÒGï–Š´Ãwb="=.!r.OÀÍšõgЀ£
\ No newline at end of file
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/sample_key b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/sample_key
new file mode 100644
index 0000000..abdbade
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/sample_key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA4f5wg5l2hKsTeNem/V41fGnJm6gOdrj8ym3rFkEU/wT8RDtn
+SgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0i
+cqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhC
+PUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR+1DcKJzQBSTAGnpYVaqpsAR
+ap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKA
+Rdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7wIDAQABAoIBAQCwia1k7+2oZ2d3
+n6agCAbqIE1QXfCmh41ZqJHbOY3oRQG3X1wpcGH4Gk+O+zDVTV2JszdcOt7E5dAy
+MaomETAhRxB7hlIOnEN7WKm+dGNrKRvV0wDU5ReFMRHg31/Lnu8c+5BvGjZX+ky9
+POIhFFYJqwCRlopGSUIxmVj5rSgtzk3iWOQXr+ah1bjEXvlxDOWkHN6YfpV5ThdE
+KdBIPGEVqa63r9n2h+qazKrtiRqJqGnOrHzOECYbRFYhexsNFz7YT02xdfSHn7gM
+IvabDDP/Qp0PjE1jdouiMaFHYnLBbgvlnZW9yuVf/rpXTUq/njxIXMmvmEyyvSDn
+FcFikB8pAoGBAPF77hK4m3/rdGT7X8a/gwvZ2R121aBcdPwEaUhvj/36dx596zvY
+mEOjrWfZhF083/nYWE2kVquj2wjs+otCLfifEEgXcVPTnEOPO9Zg3uNSL0nNQghj
+FuD3iGLTUBCtM66oTe0jLSslHe8gLGEQqyMzHOzYxNqibxcOZIe8Qt0NAoGBAO+U
+I5+XWjWEgDmvyC3TrOSf/KCGjtu0TSv30ipv27bDLMrpvPmD/5lpptTFwcxvVhCs
+2b+chCjlghFSWFbBULBrfci2FtliClOVMYrlNBdUSJhf3aYSG2Doe6Bgt1n2CpNn
+/iu37Y3NfemZBJA7hNl4dYe+f+uzM87cdQ214+jrAoGAXA0XxX8ll2+ToOLJsaNT
+OvNB9h9Uc5qK5X5w+7G7O998BN2PC/MWp8H+2fVqpXgNENpNXttkRm1hk1dych86
+EunfdPuqsX+as44oCyJGFHVBnWpm33eWQw9YqANRI+pCJzP08I5WK3osnPiwshd+
+hR54yjgfYhBFNI7B95PmEQkCgYBzFSz7h1+s34Ycr8SvxsOBWxymG5zaCsUbPsL0
+4aCgLScCHb9J+E86aVbbVFdglYa5Id7DPTL61ixhl7WZjujspeXZGSbmq0Kcnckb
+mDgqkLECiOJW2NHP/j0McAkDLL4tysF8TLDO8gvuvzNC+WQ6drO2ThrypLVZQ+ry
+eBIPmwKBgEZxhqa0gVvHQG/7Od69KWj4eJP28kq13RhKay8JOoN0vPmspXJo1HY3
+CKuHRG+AP579dncdUnOMvfXOtkdM4vk0+hWASBQzM9xzVcztCa+koAugjVaLS9A+
+9uQoqEeVNTckxx0S2bYevRy7hGQmUJTyQm3j1zEUR5jpdbL83Fbq
+-----END RSA PRIVATE KEY-----
diff --git a/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/sample_key.pub b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/sample_key.pub
new file mode 100644
index 0000000..03dc982
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/dgrijalva/jwt-go/test/sample_key.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4f5wg5l2hKsTeNem/V41
+fGnJm6gOdrj8ym3rFkEU/wT8RDtnSgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7
+mCpz9Er5qLaMXJwZxzHzAahlfA0icqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBp
+HssPnpYGIn20ZZuNlX2BrClciHhCPUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2
+XrHhR+1DcKJzQBSTAGnpYVaqpsARap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3b
+ODIRe1AuTyHceAbewn8b462yEWKARdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy
+7wIDAQAB
+-----END PUBLIC KEY-----
diff --git a/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go
new file mode 100644
index 0000000..31d36e7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go
@@ -0,0 +1,274 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package query implements encoding of structs into URL query parameters.
+//
+// As a simple example:
+//
+// type Options struct {
+// Query string `url:"q"`
+// ShowAll bool `url:"all"`
+// Page int `url:"page"`
+// }
+//
+// opt := Options{ "foo", true, 2 }
+// v, _ := query.Values(opt)
+// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
+//
+// The exact mapping between Go values and url.Values is described in the
+// documentation for the Values() function.
+package query
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "net/url"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var timeType = reflect.TypeOf(time.Time{})
+
+var encoderType = reflect.TypeOf(new(Encoder)).Elem()
+
+// Encoder is an interface implemented by any type that wishes to encode
+// itself into URL values in a non-standard way.
+type Encoder interface {
+ EncodeValues(key string, v *url.Values) error
+}
+
+// Values returns the url.Values encoding of v.
+//
+// Values expects to be passed a struct, and traverses it recursively using the
+// following encoding rules.
+//
+// Each exported struct field is encoded as a URL parameter unless
+//
+// - the field's tag is "-", or
+// - the field is empty and its tag specifies the "omitempty" option
+//
+// The empty values are false, 0, any nil pointer or interface value, any array
+// slice, map, or string of length zero, and any time.Time that returns true
+// for IsZero().
+//
+// The URL parameter name defaults to the struct field name but can be
+// specified in the struct field's tag value. The "url" key in the struct
+// field's tag value is the key name, followed by an optional comma and
+// options. For example:
+//
+// // Field is ignored by this package.
+// Field int `url:"-"`
+//
+// // Field appears as URL parameter "myName".
+// Field int `url:"myName"`
+//
+// // Field appears as URL parameter "myName" and the field is omitted if
+// // its value is empty
+// Field int `url:"myName,omitempty"`
+//
+// // Field appears as URL parameter "Field" (the default), but the field
+// // is skipped if empty. Note the leading comma.
+// Field int `url:",omitempty"`
+//
+// For encoding individual field values, the following type-dependent rules
+// apply:
+//
+// Boolean values default to encoding as the strings "true" or "false".
+// Including the "int" option signals that the field should be encoded as the
+// strings "1" or "0".
+//
+// time.Time values default to encoding as RFC3339 timestamps. Including the
+// "unix" option signals that the field should be encoded as a Unix time (see
+// time.Unix())
+//
+// Slice and Array values default to encoding as multiple URL values of the
+// same name. Including the "comma" option signals that the field should be
+// encoded as a single comma-delimited value. Including the "space" option
+// similarly encodes the value as a single space-delimited string.
+//
+// Anonymous struct fields are usually encoded as if their inner exported
+// fields were fields in the outer struct, subject to the standard Go
+// visibility rules. An anonymous struct field with a name given in its URL
+// tag is treated as having that name, rather than being anonymous.
+//
+// Non-nil pointer values are encoded as the value pointed to.
+//
+// All other values are encoded using their default string representation.
+//
+// Multiple fields that encode to the same URL parameter name will be included
+// as multiple URL values of the same name.
+func Values(v interface{}) (url.Values, error) {
+ val := reflect.ValueOf(v)
+ for val.Kind() == reflect.Ptr {
+ if val.IsNil() {
+ return nil, errors.New("query: Values() expects non-nil value")
+ }
+ val = val.Elem()
+ }
+
+ if val.Kind() != reflect.Struct {
+ return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
+ }
+
+ values := make(url.Values)
+ err := reflectValue(values, val)
+ return values, err
+}
+
+// reflectValue populates the values parameter from the struct fields in val.
+// Embedded structs are followed recursively (using the rules defined in the
+// Values function documentation) breadth-first.
+func reflectValue(values url.Values, val reflect.Value) error {
+ var embedded []reflect.Value
+
+ typ := val.Type()
+ for i := 0; i < typ.NumField(); i++ {
+ sf := typ.Field(i)
+ if sf.PkgPath != "" { // unexported
+ continue
+ }
+
+ sv := val.Field(i)
+ tag := sf.Tag.Get("url")
+ if tag == "-" {
+ continue
+ }
+ name, opts := parseTag(tag)
+ if name == "" {
+ if sf.Anonymous && sv.Kind() == reflect.Struct {
+ // save embedded struct for later processing
+ embedded = append(embedded, sv)
+ continue
+ }
+
+ name = sf.Name
+ }
+
+ if opts.Contains("omitempty") && isEmptyValue(sv) {
+ continue
+ }
+
+ if sv.Type().Implements(encoderType) {
+ m := sv.Interface().(Encoder)
+ if err := m.EncodeValues(name, &values); err != nil {
+ return err
+ }
+ continue
+ }
+
+ if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
+ var del byte
+ if opts.Contains("comma") {
+ del = ','
+ } else if opts.Contains("space") {
+ del = ' '
+ }
+
+ if del != 0 {
+ s := new(bytes.Buffer)
+ first := true
+ for i := 0; i < sv.Len(); i++ {
+ if first {
+ first = false
+ } else {
+ s.WriteByte(del)
+ }
+ s.WriteString(valueString(sv.Index(i), opts))
+ }
+ values.Add(name, s.String())
+ } else {
+ for i := 0; i < sv.Len(); i++ {
+ values.Add(name, valueString(sv.Index(i), opts))
+ }
+ }
+ continue
+ }
+
+ values.Add(name, valueString(sv, opts))
+ }
+
+ for _, f := range embedded {
+ if err := reflectValue(values, f); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// valueString returns the string representation of a value.
+func valueString(v reflect.Value, opts tagOptions) string {
+ for v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ return ""
+ }
+ v = v.Elem()
+ }
+
+ if v.Kind() == reflect.Bool && opts.Contains("int") {
+ if v.Bool() {
+ return "1"
+ }
+ return "0"
+ }
+
+ if v.Type() == timeType {
+ t := v.Interface().(time.Time)
+ if opts.Contains("unix") {
+ return strconv.FormatInt(t.Unix(), 10)
+ }
+ return t.Format(time.RFC3339)
+ }
+
+ return fmt.Sprint(v.Interface())
+}
+
+// isEmptyValue checks if a value should be considered empty for the purposes
+// of omitting fields with the "omitempty" option.
+func isEmptyValue(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len() == 0
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ return v.IsNil()
+ }
+
+ if v.Type() == timeType {
+ return v.Interface().(time.Time).IsZero()
+ }
+
+ return false
+}
+
+// tagOptions is the string following a comma in a struct field's "url" tag, or
+// the empty string. It does not include the leading comma.
+type tagOptions []string
+
+// parseTag splits a struct field's url tag into its name and comma-separated
+// options.
+func parseTag(tag string) (string, tagOptions) {
+ s := strings.Split(tag, ",")
+ return s[0], s[1:]
+}
+
+// Contains checks whether the tagOptions contains the specified option.
+func (o tagOptions) Contains(option string) bool {
+ for _, s := range o {
+ if s == option {
+ return true
+ }
+ }
+ return false
+}
diff --git a/Godeps/_workspace/src/github.com/google/go-querystring/query/encode_test.go b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode_test.go
new file mode 100644
index 0000000..8afbd0b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode_test.go
@@ -0,0 +1,238 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package query
+
+import (
+ "fmt"
+ "net/url"
+ "reflect"
+ "testing"
+ "time"
+)
+
+func TestValues_types(t *testing.T) {
+ str := "string"
+ strPtr := &str
+
+ tests := []struct {
+ in interface{}
+ want url.Values
+ }{
+ {
+ // basic primitives
+ struct {
+ A string
+ B int
+ C uint
+ D float32
+ E bool
+ }{},
+ url.Values{
+ "A": {""},
+ "B": {"0"},
+ "C": {"0"},
+ "D": {"0"},
+ "E": {"false"},
+ },
+ },
+ {
+ // pointers
+ struct {
+ A *string
+ B *int
+ C **string
+ }{A: strPtr, C: &strPtr},
+ url.Values{
+ "A": {str},
+ "B": {""},
+ "C": {str},
+ },
+ },
+ {
+ // slices and arrays
+ struct {
+ A []string
+ B []string `url:",comma"`
+ C []string `url:",space"`
+ D [2]string
+ E [2]string `url:",comma"`
+ F [2]string `url:",space"`
+ G []*string `url:",space"`
+ H []bool `url:",int,space"`
+ }{
+ A: []string{"a", "b"},
+ B: []string{"a", "b"},
+ C: []string{"a", "b"},
+ D: [2]string{"a", "b"},
+ E: [2]string{"a", "b"},
+ F: [2]string{"a", "b"},
+ G: []*string{&str, &str},
+ H: []bool{true, false},
+ },
+ url.Values{
+ "A": {"a", "b"},
+ "B": {"a,b"},
+ "C": {"a b"},
+ "D": {"a", "b"},
+ "E": {"a,b"},
+ "F": {"a b"},
+ "G": {"string string"},
+ "H": {"1 0"},
+ },
+ },
+ {
+ // other types
+ struct {
+ A time.Time
+ B time.Time `url:",unix"`
+ C bool `url:",int"`
+ D bool `url:",int"`
+ }{
+ A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
+ B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
+ C: true,
+ D: false,
+ },
+ url.Values{
+ "A": {"2000-01-01T12:34:56Z"},
+ "B": {"946730096"},
+ "C": {"1"},
+ "D": {"0"},
+ },
+ },
+ }
+
+ for i, tt := range tests {
+ v, err := Values(tt.in)
+ if err != nil {
+ t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
+ }
+
+ if !reflect.DeepEqual(tt.want, v) {
+ t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
+ }
+ }
+}
+
+func TestValues_omitEmpty(t *testing.T) {
+ str := ""
+ s := struct {
+ a string
+ A string
+ B string `url:",omitempty"`
+ C string `url:"-"`
+ D string `url:"omitempty"` // actually named omitempty, not an option
+ E *string `url:",omitempty"`
+ }{E: &str}
+
+ v, err := Values(s)
+ if err != nil {
+ t.Errorf("Values(%q) returned error: %v", s, err)
+ }
+
+ want := url.Values{
+ "A": {""},
+ "omitempty": {""},
+ "E": {""}, // E is included because the pointer is not empty, even though the string being pointed to is
+ }
+ if !reflect.DeepEqual(want, v) {
+ t.Errorf("Values(%q) returned %v, want %v", s, v, want)
+ }
+}
+
+type A struct {
+ B
+}
+
+type B struct {
+ C string
+}
+
+type D struct {
+ B
+ C string
+}
+
+func TestValues_embeddedStructs(t *testing.T) {
+ tests := []struct {
+ in interface{}
+ want url.Values
+ }{
+ {
+ A{B{C: "foo"}},
+ url.Values{"C": {"foo"}},
+ },
+ {
+ D{B: B{C: "bar"}, C: "foo"},
+ url.Values{"C": {"foo", "bar"}},
+ },
+ }
+
+ for i, tt := range tests {
+ v, err := Values(tt.in)
+ if err != nil {
+ t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
+ }
+
+ if !reflect.DeepEqual(tt.want, v) {
+ t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
+ }
+ }
+}
+
+func TestValues_invalidInput(t *testing.T) {
+ _, err := Values("")
+ if err == nil {
+ t.Errorf("expected Values() to return an error on invalid input")
+ }
+}
+
+type EncodedArgs []string
+
+func (m EncodedArgs) EncodeValues(key string, v *url.Values) error {
+ for i, arg := range m {
+ v.Set(fmt.Sprintf("%s.%d", key, i), arg)
+ }
+ return nil
+}
+
+func TestValues_Marshaler(t *testing.T) {
+ s := struct {
+ Args EncodedArgs `url:"arg"`
+ }{[]string{"a", "b", "c"}}
+ v, err := Values(s)
+ if err != nil {
+ t.Errorf("Values(%q) returned error: %v", s, err)
+ }
+
+ want := url.Values{
+ "arg.0": {"a"},
+ "arg.1": {"b"},
+ "arg.2": {"c"},
+ }
+ if !reflect.DeepEqual(want, v) {
+ t.Errorf("Values(%q) returned %v, want %v", s, v, want)
+ }
+}
+
+func TestTagParsing(t *testing.T) {
+ name, opts := parseTag("field,foobar,foo")
+ if name != "field" {
+ t.Fatalf("name = %q, want field", name)
+ }
+ for _, tt := range []struct {
+ opt string
+ want bool
+ }{
+ {"foobar", true},
+ {"foo", true},
+ {"bar", false},
+ {"field", false},
+ } {
+ if opts.Contains(tt.opt) != tt.want {
+ t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml b/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml
new file mode 100644
index 0000000..6796581
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+
+go:
+ - 1.0
+ - 1.1
+ - 1.2
+ - 1.3
+ - 1.4
+ - tip
diff --git a/Godeps/_workspace/src/github.com/gorilla/context/LICENSE b/Godeps/_workspace/src/github.com/gorilla/context/LICENSE
new file mode 100644
index 0000000..0e5fb87
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/context/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Godeps/_workspace/src/github.com/gorilla/context/README.md b/Godeps/_workspace/src/github.com/gorilla/context/README.md
new file mode 100644
index 0000000..c60a31b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/context/README.md
@@ -0,0 +1,7 @@
+context
+=======
+[](https://travis-ci.org/gorilla/context)
+
+gorilla/context is a general purpose registry for global request variables.
+
+Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
diff --git a/Godeps/_workspace/src/github.com/gorilla/context/context.go b/Godeps/_workspace/src/github.com/gorilla/context/context.go
new file mode 100644
index 0000000..81cb128
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/context/context.go
@@ -0,0 +1,143 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+ "net/http"
+ "sync"
+ "time"
+)
+
+var (
+ mutex sync.RWMutex
+ data = make(map[*http.Request]map[interface{}]interface{})
+ datat = make(map[*http.Request]int64)
+)
+
+// Set stores a value for a given key in a given request.
+func Set(r *http.Request, key, val interface{}) {
+ mutex.Lock()
+ if data[r] == nil {
+ data[r] = make(map[interface{}]interface{})
+ datat[r] = time.Now().Unix()
+ }
+ data[r][key] = val
+ mutex.Unlock()
+}
+
+// Get returns a value stored for a given key in a given request.
+func Get(r *http.Request, key interface{}) interface{} {
+ mutex.RLock()
+ if ctx := data[r]; ctx != nil {
+ value := ctx[key]
+ mutex.RUnlock()
+ return value
+ }
+ mutex.RUnlock()
+ return nil
+}
+
+// GetOk returns stored value and presence state like multi-value return of map access.
+func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
+ mutex.RLock()
+ if _, ok := data[r]; ok {
+ value, ok := data[r][key]
+ mutex.RUnlock()
+ return value, ok
+ }
+ mutex.RUnlock()
+ return nil, false
+}
+
+// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
+func GetAll(r *http.Request) map[interface{}]interface{} {
+ mutex.RLock()
+ if context, ok := data[r]; ok {
+ result := make(map[interface{}]interface{}, len(context))
+ for k, v := range context {
+ result[k] = v
+ }
+ mutex.RUnlock()
+ return result
+ }
+ mutex.RUnlock()
+ return nil
+}
+
+// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
+// the request was registered.
+func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
+ mutex.RLock()
+ context, ok := data[r]
+ result := make(map[interface{}]interface{}, len(context))
+ for k, v := range context {
+ result[k] = v
+ }
+ mutex.RUnlock()
+ return result, ok
+}
+
+// Delete removes a value stored for a given key in a given request.
+func Delete(r *http.Request, key interface{}) {
+ mutex.Lock()
+ if data[r] != nil {
+ delete(data[r], key)
+ }
+ mutex.Unlock()
+}
+
+// Clear removes all values stored for a given request.
+//
+// This is usually called by a handler wrapper to clean up request
+// variables at the end of a request lifetime. See ClearHandler().
+func Clear(r *http.Request) {
+ mutex.Lock()
+ clear(r)
+ mutex.Unlock()
+}
+
+// clear is Clear without the lock.
+func clear(r *http.Request) {
+ delete(data, r)
+ delete(datat, r)
+}
+
+// Purge removes request data stored for longer than maxAge, in seconds.
+// It returns the amount of requests removed.
+//
+// If maxAge <= 0, all request data is removed.
+//
+// This is only used for sanity check: in case context cleaning was not
+// properly set some request data can be kept forever, consuming an increasing
+// amount of memory. In case this is detected, Purge() must be called
+// periodically until the problem is fixed.
+func Purge(maxAge int) int {
+ mutex.Lock()
+ count := 0
+ if maxAge <= 0 {
+ count = len(data)
+ data = make(map[*http.Request]map[interface{}]interface{})
+ datat = make(map[*http.Request]int64)
+ } else {
+ min := time.Now().Unix() - int64(maxAge)
+ for r := range data {
+ if datat[r] < min {
+ clear(r)
+ count++
+ }
+ }
+ }
+ mutex.Unlock()
+ return count
+}
+
+// ClearHandler wraps an http.Handler and clears request values at the end
+// of a request lifetime.
+func ClearHandler(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ defer Clear(r)
+ h.ServeHTTP(w, r)
+ })
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/context/context_test.go b/Godeps/_workspace/src/github.com/gorilla/context/context_test.go
new file mode 100644
index 0000000..9814c50
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/context/context_test.go
@@ -0,0 +1,161 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+ "net/http"
+ "testing"
+)
+
+type keyType int
+
+const (
+ key1 keyType = iota
+ key2
+)
+
+func TestContext(t *testing.T) {
+ assertEqual := func(val interface{}, exp interface{}) {
+ if val != exp {
+ t.Errorf("Expected %v, got %v.", exp, val)
+ }
+ }
+
+ r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+ emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+
+ // Get()
+ assertEqual(Get(r, key1), nil)
+
+ // Set()
+ Set(r, key1, "1")
+ assertEqual(Get(r, key1), "1")
+ assertEqual(len(data[r]), 1)
+
+ Set(r, key2, "2")
+ assertEqual(Get(r, key2), "2")
+ assertEqual(len(data[r]), 2)
+
+ //GetOk
+ value, ok := GetOk(r, key1)
+ assertEqual(value, "1")
+ assertEqual(ok, true)
+
+ value, ok = GetOk(r, "not exists")
+ assertEqual(value, nil)
+ assertEqual(ok, false)
+
+ Set(r, "nil value", nil)
+ value, ok = GetOk(r, "nil value")
+ assertEqual(value, nil)
+ assertEqual(ok, true)
+
+ // GetAll()
+ values := GetAll(r)
+ assertEqual(len(values), 3)
+
+ // GetAll() for empty request
+ values = GetAll(emptyR)
+ if values != nil {
+ t.Error("GetAll didn't return nil value for invalid request")
+ }
+
+ // GetAllOk()
+ values, ok = GetAllOk(r)
+ assertEqual(len(values), 3)
+ assertEqual(ok, true)
+
+ // GetAllOk() for empty request
+ values, ok = GetAllOk(emptyR)
+ assertEqual(value, nil)
+ assertEqual(ok, false)
+
+ // Delete()
+ Delete(r, key1)
+ assertEqual(Get(r, key1), nil)
+ assertEqual(len(data[r]), 2)
+
+ Delete(r, key2)
+ assertEqual(Get(r, key2), nil)
+ assertEqual(len(data[r]), 1)
+
+ // Clear()
+ Clear(r)
+ assertEqual(len(data), 0)
+}
+
+func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
+ <-wait
+ for i := 0; i < iterations; i++ {
+ Get(r, key)
+ }
+ done <- struct{}{}
+
+}
+
+func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
+ <-wait
+ for i := 0; i < iterations; i++ {
+ Set(r, key, value)
+ }
+ done <- struct{}{}
+
+}
+
+func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
+
+ b.StopTimer()
+ r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+ done := make(chan struct{})
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ wait := make(chan struct{})
+
+ for i := 0; i < numReaders; i++ {
+ go parallelReader(r, "test", iterations, wait, done)
+ }
+
+ for i := 0; i < numWriters; i++ {
+ go parallelWriter(r, "test", "123", iterations, wait, done)
+ }
+
+ close(wait)
+
+ for i := 0; i < numReaders+numWriters; i++ {
+ <-done
+ }
+
+ }
+
+}
+
+func BenchmarkMutexSameReadWrite1(b *testing.B) {
+ benchmarkMutex(b, 1, 1, 32)
+}
+func BenchmarkMutexSameReadWrite2(b *testing.B) {
+ benchmarkMutex(b, 2, 2, 32)
+}
+func BenchmarkMutexSameReadWrite4(b *testing.B) {
+ benchmarkMutex(b, 4, 4, 32)
+}
+func BenchmarkMutex1(b *testing.B) {
+ benchmarkMutex(b, 2, 8, 32)
+}
+func BenchmarkMutex2(b *testing.B) {
+ benchmarkMutex(b, 16, 4, 64)
+}
+func BenchmarkMutex3(b *testing.B) {
+ benchmarkMutex(b, 1, 2, 128)
+}
+func BenchmarkMutex4(b *testing.B) {
+ benchmarkMutex(b, 128, 32, 256)
+}
+func BenchmarkMutex5(b *testing.B) {
+ benchmarkMutex(b, 1024, 2048, 64)
+}
+func BenchmarkMutex6(b *testing.B) {
+ benchmarkMutex(b, 2048, 1024, 512)
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/context/doc.go b/Godeps/_workspace/src/github.com/gorilla/context/doc.go
new file mode 100644
index 0000000..73c7400
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/context/doc.go
@@ -0,0 +1,82 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package context stores values shared during a request lifetime.
+
+For example, a router can set variables extracted from the URL and later
+application handlers can access those values, or it can be used to store
+sessions values to be saved at the end of a request. There are several
+others common uses.
+
+The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
+
+ http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
+
+Here's the basic usage: first define the keys that you will need. The key
+type is interface{} so a key can be of any type that supports equality.
+Here we define a key using a custom int type to avoid name collisions:
+
+ package foo
+
+ import (
+ "github.com/gorilla/context"
+ )
+
+ type key int
+
+ const MyKey key = 0
+
+Then set a variable. Variables are bound to an http.Request object, so you
+need a request instance to set a value:
+
+ context.Set(r, MyKey, "bar")
+
+The application can later access the variable using the same key you provided:
+
+ func MyHandler(w http.ResponseWriter, r *http.Request) {
+ // val is "bar".
+ val := context.Get(r, foo.MyKey)
+
+ // returns ("bar", true)
+ val, ok := context.GetOk(r, foo.MyKey)
+ // ...
+ }
+
+And that's all about the basic usage. We discuss some other ideas below.
+
+Any type can be stored in the context. To enforce a given type, make the key
+private and wrap Get() and Set() to accept and return values of a specific
+type:
+
+ type key int
+
+ const mykey key = 0
+
+ // GetMyKey returns a value for this package from the request values.
+ func GetMyKey(r *http.Request) SomeType {
+ if rv := context.Get(r, mykey); rv != nil {
+ return rv.(SomeType)
+ }
+ return nil
+ }
+
+ // SetMyKey sets a value for this package in the request values.
+ func SetMyKey(r *http.Request, val SomeType) {
+ context.Set(r, mykey, val)
+ }
+
+Variables must be cleared at the end of a request, to remove all values
+that were stored. This can be done in an http.Handler, after a request was
+served. Just call Clear() passing the request:
+
+ context.Clear(r)
+
+...or use ClearHandler(), which conveniently wraps an http.Handler to clear
+variables at the end of a request lifetime.
+
+The Routers from the packages gorilla/mux and gorilla/pat call Clear()
+so if you are using either of them you don't need to clear the context manually.
+*/
+package context
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml b/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml
new file mode 100644
index 0000000..d87d465
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml
@@ -0,0 +1,7 @@
+language: go
+
+go:
+ - 1.0
+ - 1.1
+ - 1.2
+ - tip
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE b/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE
new file mode 100644
index 0000000..0e5fb87
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/README.md b/Godeps/_workspace/src/github.com/gorilla/mux/README.md
new file mode 100644
index 0000000..e60301b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/README.md
@@ -0,0 +1,7 @@
+mux
+===
+[](https://travis-ci.org/gorilla/mux)
+
+gorilla/mux is a powerful URL router and dispatcher.
+
+Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go b/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go
new file mode 100644
index 0000000..c5f97b2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go
@@ -0,0 +1,21 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "net/http"
+ "testing"
+)
+
+func BenchmarkMux(b *testing.B) {
+ router := new(Router)
+ handler := func(w http.ResponseWriter, r *http.Request) {}
+ router.HandleFunc("/v1/{v1}", handler)
+
+ request, _ := http.NewRequest("GET", "/v1/anything", nil)
+ for i := 0; i < b.N; i++ {
+ router.ServeHTTP(nil, request)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/doc.go b/Godeps/_workspace/src/github.com/gorilla/mux/doc.go
new file mode 100644
index 0000000..b2deed3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/doc.go
@@ -0,0 +1,199 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package gorilla/mux implements a request router and dispatcher.
+
+The name mux stands for "HTTP request multiplexer". Like the standard
+http.ServeMux, mux.Router matches incoming requests against a list of
+registered routes and calls a handler for the route that matches the URL
+or other conditions. The main features are:
+
+ * Requests can be matched based on URL host, path, path prefix, schemes,
+ header and query values, HTTP methods or using custom matchers.
+ * URL hosts and paths can have variables with an optional regular
+ expression.
+ * Registered URLs can be built, or "reversed", which helps maintaining
+ references to resources.
+ * Routes can be used as subrouters: nested routes are only tested if the
+ parent route matches. This is useful to define groups of routes that
+ share common conditions like a host, a path prefix or other repeated
+ attributes. As a bonus, this optimizes request matching.
+ * It implements the http.Handler interface so it is compatible with the
+ standard http.ServeMux.
+
+Let's start registering a couple of URL paths and handlers:
+
+ func main() {
+ r := mux.NewRouter()
+ r.HandleFunc("/", HomeHandler)
+ r.HandleFunc("/products", ProductsHandler)
+ r.HandleFunc("/articles", ArticlesHandler)
+ http.Handle("/", r)
+ }
+
+Here we register three routes mapping URL paths to handlers. This is
+equivalent to how http.HandleFunc() works: if an incoming request URL matches
+one of the paths, the corresponding handler is called passing
+(http.ResponseWriter, *http.Request) as parameters.
+
+Paths can have variables. They are defined using the format {name} or
+{name:pattern}. If a regular expression pattern is not defined, the matched
+variable will be anything until the next slash. For example:
+
+ r := mux.NewRouter()
+ r.HandleFunc("/products/{key}", ProductHandler)
+ r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
+ r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
+
+The names are used to create a map of route variables which can be retrieved
+calling mux.Vars():
+
+ vars := mux.Vars(request)
+ category := vars["category"]
+
+And this is all you need to know about the basic usage. More advanced options
+are explained below.
+
+Routes can also be restricted to a domain or subdomain. Just define a host
+pattern to be matched. They can also have variables:
+
+ r := mux.NewRouter()
+ // Only matches if domain is "www.domain.com".
+ r.Host("www.domain.com")
+ // Matches a dynamic subdomain.
+ r.Host("{subdomain:[a-z]+}.domain.com")
+
+There are several other matchers that can be added. To match path prefixes:
+
+ r.PathPrefix("/products/")
+
+...or HTTP methods:
+
+ r.Methods("GET", "POST")
+
+...or URL schemes:
+
+ r.Schemes("https")
+
+...or header values:
+
+ r.Headers("X-Requested-With", "XMLHttpRequest")
+
+...or query values:
+
+ r.Queries("key", "value")
+
+...or to use a custom matcher function:
+
+ r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
+ return r.ProtoMajor == 0
+ })
+
+...and finally, it is possible to combine several matchers in a single route:
+
+ r.HandleFunc("/products", ProductsHandler).
+ Host("www.domain.com").
+ Methods("GET").
+ Schemes("http")
+
+Setting the same matching conditions again and again can be boring, so we have
+a way to group several routes that share the same requirements.
+We call it "subrouting".
+
+For example, let's say we have several URLs that should only match when the
+host is "www.domain.com". Create a route for that host and get a "subrouter"
+from it:
+
+ r := mux.NewRouter()
+ s := r.Host("www.domain.com").Subrouter()
+
+Then register routes in the subrouter:
+
+ s.HandleFunc("/products/", ProductsHandler)
+ s.HandleFunc("/products/{key}", ProductHandler)
+ s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
+
+The three URL paths we registered above will only be tested if the domain is
+"www.domain.com", because the subrouter is tested first. This is not
+only convenient, but also optimizes request matching. You can create
+subrouters combining any attribute matchers accepted by a route.
+
+Subrouters can be used to create domain or path "namespaces": you define
+subrouters in a central place and then parts of the app can register its
+paths relatively to a given subrouter.
+
+There's one more thing about subroutes. When a subrouter has a path prefix,
+the inner routes use it as base for their paths:
+
+ r := mux.NewRouter()
+ s := r.PathPrefix("/products").Subrouter()
+ // "/products/"
+ s.HandleFunc("/", ProductsHandler)
+ // "/products/{key}/"
+ s.HandleFunc("/{key}/", ProductHandler)
+ // "/products/{key}/details"
+ s.HandleFunc("/{key}/details", ProductDetailsHandler)
+
+Now let's see how to build registered URLs.
+
+Routes can be named. All routes that define a name can have their URLs built,
+or "reversed". We define a name calling Name() on a route. For example:
+
+ r := mux.NewRouter()
+ r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+ Name("article")
+
+To build a URL, get the route and call the URL() method, passing a sequence of
+key/value pairs for the route variables. For the previous route, we would do:
+
+ url, err := r.Get("article").URL("category", "technology", "id", "42")
+
+...and the result will be a url.URL with the following path:
+
+ "/articles/technology/42"
+
+This also works for host variables:
+
+ r := mux.NewRouter()
+ r.Host("{subdomain}.domain.com").
+ Path("/articles/{category}/{id:[0-9]+}").
+ HandlerFunc(ArticleHandler).
+ Name("article")
+
+ // url.String() will be "http://news.domain.com/articles/technology/42"
+ url, err := r.Get("article").URL("subdomain", "news",
+ "category", "technology",
+ "id", "42")
+
+All variables defined in the route are required, and their values must
+conform to the corresponding patterns. These requirements guarantee that a
+generated URL will always match a registered route -- the only exception is
+for explicitly defined "build-only" routes which never match.
+
+There's also a way to build only the URL host or path for a route:
+use the methods URLHost() or URLPath() instead. For the previous route,
+we would do:
+
+ // "http://news.domain.com/"
+ host, err := r.Get("article").URLHost("subdomain", "news")
+
+ // "/articles/technology/42"
+ path, err := r.Get("article").URLPath("category", "technology", "id", "42")
+
+And if you use subrouters, host and path defined separately can be built
+as well:
+
+ r := mux.NewRouter()
+ s := r.Host("{subdomain}.domain.com").Subrouter()
+ s.Path("/articles/{category}/{id:[0-9]+}").
+ HandlerFunc(ArticleHandler).
+ Name("article")
+
+ // "http://news.domain.com/articles/technology/42"
+ url, err := r.Get("article").URL("subdomain", "news",
+ "category", "technology",
+ "id", "42")
+*/
+package mux
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/mux.go b/Godeps/_workspace/src/github.com/gorilla/mux/mux.go
new file mode 100644
index 0000000..5b5f8e7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/mux.go
@@ -0,0 +1,353 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "fmt"
+ "net/http"
+ "path"
+
+ "github.com/gorilla/context"
+)
+
+// NewRouter returns a new router instance.
+func NewRouter() *Router {
+ return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
+}
+
+// Router registers routes to be matched and dispatches a handler.
+//
+// It implements the http.Handler interface, so it can be registered to serve
+// requests:
+//
+// var router = mux.NewRouter()
+//
+// func main() {
+// http.Handle("/", router)
+// }
+//
+// Or, for Google App Engine, register it in a init() function:
+//
+// func init() {
+// http.Handle("/", router)
+// }
+//
+// This will send all incoming requests to the router.
+type Router struct {
+ // Configurable Handler to be used when no route matches.
+ NotFoundHandler http.Handler
+ // Parent route, if this is a subrouter.
+ parent parentRoute
+ // Routes to be matched, in order.
+ routes []*Route
+ // Routes by name for URL building.
+ namedRoutes map[string]*Route
+ // See Router.StrictSlash(). This defines the flag for new routes.
+ strictSlash bool
+ // If true, do not clear the request context after handling the request
+ KeepContext bool
+}
+
+// Match matches registered routes against the request.
+func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
+ for _, route := range r.routes {
+ if route.Match(req, match) {
+ return true
+ }
+ }
+ return false
+}
+
+// ServeHTTP dispatches the handler registered in the matched route.
+//
+// When there is a match, the route variables can be retrieved calling
+// mux.Vars(request).
+func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ // Clean path to canonical form and redirect.
+ if p := cleanPath(req.URL.Path); p != req.URL.Path {
+
+ // Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query.
+ // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
+ // http://code.google.com/p/go/issues/detail?id=5252
+ url := *req.URL
+ url.Path = p
+ p = url.String()
+
+ w.Header().Set("Location", p)
+ w.WriteHeader(http.StatusMovedPermanently)
+ return
+ }
+ var match RouteMatch
+ var handler http.Handler
+ if r.Match(req, &match) {
+ handler = match.Handler
+ setVars(req, match.Vars)
+ setCurrentRoute(req, match.Route)
+ }
+ if handler == nil {
+ handler = r.NotFoundHandler
+ if handler == nil {
+ handler = http.NotFoundHandler()
+ }
+ }
+ if !r.KeepContext {
+ defer context.Clear(req)
+ }
+ handler.ServeHTTP(w, req)
+}
+
+// Get returns a route registered with the given name.
+func (r *Router) Get(name string) *Route {
+ return r.getNamedRoutes()[name]
+}
+
+// GetRoute returns a route registered with the given name. This method
+// was renamed to Get() and remains here for backwards compatibility.
+func (r *Router) GetRoute(name string) *Route {
+ return r.getNamedRoutes()[name]
+}
+
+// StrictSlash defines the trailing slash behavior for new routes. The initial
+// value is false.
+//
+// When true, if the route path is "/path/", accessing "/path" will redirect
+// to the former and vice versa. In other words, your application will always
+// see the path as specified in the route.
+//
+// When false, if the route path is "/path", accessing "/path/" will not match
+// this route and vice versa.
+//
+// Special case: when a route sets a path prefix using the PathPrefix() method,
+// strict slash is ignored for that route because the redirect behavior can't
+// be determined from a prefix alone. However, any subrouters created from that
+// route inherit the original StrictSlash setting.
+func (r *Router) StrictSlash(value bool) *Router {
+ r.strictSlash = value
+ return r
+}
+
+// ----------------------------------------------------------------------------
+// parentRoute
+// ----------------------------------------------------------------------------
+
+// getNamedRoutes returns the map where named routes are registered.
+func (r *Router) getNamedRoutes() map[string]*Route {
+ if r.namedRoutes == nil {
+ if r.parent != nil {
+ r.namedRoutes = r.parent.getNamedRoutes()
+ } else {
+ r.namedRoutes = make(map[string]*Route)
+ }
+ }
+ return r.namedRoutes
+}
+
+// getRegexpGroup returns regexp definitions from the parent route, if any.
+func (r *Router) getRegexpGroup() *routeRegexpGroup {
+ if r.parent != nil {
+ return r.parent.getRegexpGroup()
+ }
+ return nil
+}
+
+// ----------------------------------------------------------------------------
+// Route factories
+// ----------------------------------------------------------------------------
+
+// NewRoute registers an empty route.
+func (r *Router) NewRoute() *Route {
+ route := &Route{parent: r, strictSlash: r.strictSlash}
+ r.routes = append(r.routes, route)
+ return route
+}
+
+// Handle registers a new route with a matcher for the URL path.
+// See Route.Path() and Route.Handler().
+func (r *Router) Handle(path string, handler http.Handler) *Route {
+ return r.NewRoute().Path(path).Handler(handler)
+}
+
+// HandleFunc registers a new route with a matcher for the URL path.
+// See Route.Path() and Route.HandlerFunc().
+func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
+ *http.Request)) *Route {
+ return r.NewRoute().Path(path).HandlerFunc(f)
+}
+
+// Headers registers a new route with a matcher for request header values.
+// See Route.Headers().
+func (r *Router) Headers(pairs ...string) *Route {
+ return r.NewRoute().Headers(pairs...)
+}
+
+// Host registers a new route with a matcher for the URL host.
+// See Route.Host().
+func (r *Router) Host(tpl string) *Route {
+ return r.NewRoute().Host(tpl)
+}
+
+// MatcherFunc registers a new route with a custom matcher function.
+// See Route.MatcherFunc().
+func (r *Router) MatcherFunc(f MatcherFunc) *Route {
+ return r.NewRoute().MatcherFunc(f)
+}
+
+// Methods registers a new route with a matcher for HTTP methods.
+// See Route.Methods().
+func (r *Router) Methods(methods ...string) *Route {
+ return r.NewRoute().Methods(methods...)
+}
+
+// Path registers a new route with a matcher for the URL path.
+// See Route.Path().
+func (r *Router) Path(tpl string) *Route {
+ return r.NewRoute().Path(tpl)
+}
+
+// PathPrefix registers a new route with a matcher for the URL path prefix.
+// See Route.PathPrefix().
+func (r *Router) PathPrefix(tpl string) *Route {
+ return r.NewRoute().PathPrefix(tpl)
+}
+
+// Queries registers a new route with a matcher for URL query values.
+// See Route.Queries().
+func (r *Router) Queries(pairs ...string) *Route {
+ return r.NewRoute().Queries(pairs...)
+}
+
+// Schemes registers a new route with a matcher for URL schemes.
+// See Route.Schemes().
+func (r *Router) Schemes(schemes ...string) *Route {
+ return r.NewRoute().Schemes(schemes...)
+}
+
+// ----------------------------------------------------------------------------
+// Context
+// ----------------------------------------------------------------------------
+
+// RouteMatch stores information about a matched route.
+type RouteMatch struct {
+ Route *Route
+ Handler http.Handler
+ Vars map[string]string
+}
+
+type contextKey int
+
+const (
+ varsKey contextKey = iota
+ routeKey
+)
+
+// Vars returns the route variables for the current request, if any.
+func Vars(r *http.Request) map[string]string {
+ if rv := context.Get(r, varsKey); rv != nil {
+ return rv.(map[string]string)
+ }
+ return nil
+}
+
+// CurrentRoute returns the matched route for the current request, if any.
+func CurrentRoute(r *http.Request) *Route {
+ if rv := context.Get(r, routeKey); rv != nil {
+ return rv.(*Route)
+ }
+ return nil
+}
+
+func setVars(r *http.Request, val interface{}) {
+ context.Set(r, varsKey, val)
+}
+
+func setCurrentRoute(r *http.Request, val interface{}) {
+ context.Set(r, routeKey, val)
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+// cleanPath returns the canonical path for p, eliminating . and .. elements.
+// Borrowed from the net/http package.
+func cleanPath(p string) string {
+ if p == "" {
+ return "/"
+ }
+ if p[0] != '/' {
+ p = "/" + p
+ }
+ np := path.Clean(p)
+ // path.Clean removes trailing slash except for root;
+ // put the trailing slash back if necessary.
+ if p[len(p)-1] == '/' && np != "/" {
+ np += "/"
+ }
+ return np
+}
+
+// uniqueVars returns an error if two slices contain duplicated strings.
+func uniqueVars(s1, s2 []string) error {
+ for _, v1 := range s1 {
+ for _, v2 := range s2 {
+ if v1 == v2 {
+ return fmt.Errorf("mux: duplicated route variable %q", v2)
+ }
+ }
+ }
+ return nil
+}
+
+// mapFromPairs converts variadic string parameters to a string map.
+func mapFromPairs(pairs ...string) (map[string]string, error) {
+ length := len(pairs)
+ if length%2 != 0 {
+ return nil, fmt.Errorf(
+ "mux: number of parameters must be multiple of 2, got %v", pairs)
+ }
+ m := make(map[string]string, length/2)
+ for i := 0; i < length; i += 2 {
+ m[pairs[i]] = pairs[i+1]
+ }
+ return m, nil
+}
+
+// matchInArray returns true if the given string value is in the array.
+func matchInArray(arr []string, value string) bool {
+ for _, v := range arr {
+ if v == value {
+ return true
+ }
+ }
+ return false
+}
+
+// matchMap returns true if the given key/value pairs exist in a given map.
+func matchMap(toCheck map[string]string, toMatch map[string][]string,
+ canonicalKey bool) bool {
+ for k, v := range toCheck {
+ // Check if key exists.
+ if canonicalKey {
+ k = http.CanonicalHeaderKey(k)
+ }
+ if values := toMatch[k]; values == nil {
+ return false
+ } else if v != "" {
+ // If value was defined as an empty string we only check that the
+ // key exists. Otherwise we also check for equality.
+ valueExists := false
+ for _, value := range values {
+ if v == value {
+ valueExists = true
+ break
+ }
+ }
+ if !valueExists {
+ return false
+ }
+ }
+ }
+ return true
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go b/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go
new file mode 100644
index 0000000..e455bce
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/mux_test.go
@@ -0,0 +1,943 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/gorilla/context"
+)
+
+type routeTest struct {
+ title string // title of the test
+ route *Route // the route being tested
+ request *http.Request // a request to test the route
+ vars map[string]string // the expected vars of the match
+ host string // the expected host of the match
+ path string // the expected path of the match
+ shouldMatch bool // whether the request is expected to match the route at all
+ shouldRedirect bool // whether the request should result in a redirect
+}
+
+func TestHost(t *testing.T) {
+ // newRequestHost a new request with a method, url, and host header
+ newRequestHost := func(method, url, host string) *http.Request {
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ panic(err)
+ }
+ req.Host = host
+ return req
+ }
+
+ tests := []routeTest{
+ {
+ title: "Host route match",
+ route: new(Route).Host("aaa.bbb.ccc"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host route, wrong host in request URL",
+ route: new(Route).Host("aaa.bbb.ccc"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Host route with port, match",
+ route: new(Route).Host("aaa.bbb.ccc:1234"),
+ request: newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc:1234",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host route with port, wrong port in request URL",
+ route: new(Route).Host("aaa.bbb.ccc:1234"),
+ request: newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc:1234",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Host route, match with host in request header",
+ route: new(Route).Host("aaa.bbb.ccc"),
+ request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host route, wrong host in request header",
+ route: new(Route).Host("aaa.bbb.ccc"),
+ request: newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: false,
+ },
+ // BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true},
+ {
+ title: "Host route with port, wrong host in request header",
+ route: new(Route).Host("aaa.bbb.ccc:1234"),
+ request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
+ vars: map[string]string{},
+ host: "aaa.bbb.ccc:1234",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Host route with pattern, match",
+ route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host route with pattern, wrong host in request URL",
+ route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Host route with multiple patterns, match",
+ route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host route with multiple patterns, wrong host in request URL",
+ route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
+ host: "aaa.bbb.ccc",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestPath(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "Path route, match",
+ route: new(Route).Path("/111/222/333"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/222/333",
+ shouldMatch: true,
+ },
+ {
+ title: "Path route, match with trailing slash in request and path",
+ route: new(Route).Path("/111/"),
+ request: newRequest("GET", "http://localhost/111/"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/",
+ shouldMatch: true,
+ },
+ {
+ title: "Path route, do not match with trailing slash in path",
+ route: new(Route).Path("/111/"),
+ request: newRequest("GET", "http://localhost/111"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ shouldMatch: false,
+ },
+ {
+ title: "Path route, do not match with trailing slash in request",
+ route: new(Route).Path("/111"),
+ request: newRequest("GET", "http://localhost/111/"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/",
+ shouldMatch: false,
+ },
+ {
+ title: "Path route, wrong path in request in request URL",
+ route: new(Route).Path("/111/222/333"),
+ request: newRequest("GET", "http://localhost/1/2/3"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/222/333",
+ shouldMatch: false,
+ },
+ {
+ title: "Path route with pattern, match",
+ route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v1": "222"},
+ host: "",
+ path: "/111/222/333",
+ shouldMatch: true,
+ },
+ {
+ title: "Path route with pattern, URL in request does not match",
+ route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://localhost/111/aaa/333"),
+ vars: map[string]string{"v1": "222"},
+ host: "",
+ path: "/111/222/333",
+ shouldMatch: false,
+ },
+ {
+ title: "Path route with multiple patterns, match",
+ route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+ host: "",
+ path: "/111/222/333",
+ shouldMatch: true,
+ },
+ {
+ title: "Path route with multiple patterns, URL in request does not match",
+ route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/aaa/333"),
+ vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
+ host: "",
+ path: "/111/222/333",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestPathPrefix(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "PathPrefix route, match",
+ route: new(Route).PathPrefix("/111"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ shouldMatch: true,
+ },
+ {
+ title: "PathPrefix route, match substring",
+ route: new(Route).PathPrefix("/1"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "/1",
+ shouldMatch: true,
+ },
+ {
+ title: "PathPrefix route, URL prefix in request does not match",
+ route: new(Route).PathPrefix("/111"),
+ request: newRequest("GET", "http://localhost/1/2/3"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ shouldMatch: false,
+ },
+ {
+ title: "PathPrefix route with pattern, match",
+ route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v1": "222"},
+ host: "",
+ path: "/111/222",
+ shouldMatch: true,
+ },
+ {
+ title: "PathPrefix route with pattern, URL prefix in request does not match",
+ route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/aaa/333"),
+ vars: map[string]string{"v1": "222"},
+ host: "",
+ path: "/111/222",
+ shouldMatch: false,
+ },
+ {
+ title: "PathPrefix route with multiple patterns, match",
+ route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/222/333"),
+ vars: map[string]string{"v1": "111", "v2": "222"},
+ host: "",
+ path: "/111/222",
+ shouldMatch: true,
+ },
+ {
+ title: "PathPrefix route with multiple patterns, URL prefix in request does not match",
+ route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
+ request: newRequest("GET", "http://localhost/111/aaa/333"),
+ vars: map[string]string{"v1": "111", "v2": "222"},
+ host: "",
+ path: "/111/222",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestHostPath(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "Host and Path route, match",
+ route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Host and Path route, wrong host in request URL",
+ route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Host and Path route with pattern, match",
+ route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb", "v2": "222"},
+ host: "aaa.bbb.ccc",
+ path: "/111/222/333",
+ shouldMatch: true,
+ },
+ {
+ title: "Host and Path route with pattern, URL in request does not match",
+ route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{"v1": "bbb", "v2": "222"},
+ host: "aaa.bbb.ccc",
+ path: "/111/222/333",
+ shouldMatch: false,
+ },
+ {
+ title: "Host and Path route with multiple patterns, match",
+ route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+ request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+ host: "aaa.bbb.ccc",
+ path: "/111/222/333",
+ shouldMatch: true,
+ },
+ {
+ title: "Host and Path route with multiple patterns, URL in request does not match",
+ route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
+ request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
+ host: "aaa.bbb.ccc",
+ path: "/111/222/333",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestHeaders(t *testing.T) {
+ // newRequestHeaders creates a new request with a method, url, and headers
+ newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ panic(err)
+ }
+ for k, v := range headers {
+ req.Header.Add(k, v)
+ }
+ return req
+ }
+
+ tests := []routeTest{
+ {
+ title: "Headers route, match",
+ route: new(Route).Headers("foo", "bar", "baz", "ding"),
+ request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Headers route, bad header values",
+ route: new(Route).Headers("foo", "bar", "baz", "ding"),
+ request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+
+}
+
+func TestMethods(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "Methods route, match GET",
+ route: new(Route).Methods("GET", "POST"),
+ request: newRequest("GET", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Methods route, match POST",
+ route: new(Route).Methods("GET", "POST"),
+ request: newRequest("POST", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Methods route, bad method",
+ route: new(Route).Methods("GET", "POST"),
+ request: newRequest("PUT", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestQueries(t *testing.T) {
+ tests := []routeTest{
+ {
+ title: "Queries route, match",
+ route: new(Route).Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route, match with a query string",
+ route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route, match with a query string out of order",
+ route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route, bad query",
+ route: new(Route).Queries("foo", "bar", "baz", "ding"),
+ request: newRequest("GET", "http://localhost?foo=bar&baz=dong"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ {
+ title: "Queries route with pattern, match",
+ route: new(Route).Queries("foo", "{v1}"),
+ request: newRequest("GET", "http://localhost?foo=bar"),
+ vars: map[string]string{"v1": "bar"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with multiple patterns, match",
+ route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"),
+ request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
+ vars: map[string]string{"v1": "bar", "v2": "ding"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with regexp pattern, match",
+ route: new(Route).Queries("foo", "{v1:[0-9]+}"),
+ request: newRequest("GET", "http://localhost?foo=10"),
+ vars: map[string]string{"v1": "10"},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Queries route with regexp pattern, regexp does not match",
+ route: new(Route).Queries("foo", "{v1:[0-9]+}"),
+ request: newRequest("GET", "http://localhost?foo=a"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestSchemes(t *testing.T) {
+ tests := []routeTest{
+ // Schemes
+ {
+ title: "Schemes route, match https",
+ route: new(Route).Schemes("https", "ftp"),
+ request: newRequest("GET", "https://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Schemes route, match ftp",
+ route: new(Route).Schemes("https", "ftp"),
+ request: newRequest("GET", "ftp://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "Schemes route, bad scheme",
+ route: new(Route).Schemes("https", "ftp"),
+ request: newRequest("GET", "http://localhost"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestMatcherFunc(t *testing.T) {
+ m := func(r *http.Request, m *RouteMatch) bool {
+ if r.URL.Host == "aaa.bbb.ccc" {
+ return true
+ }
+ return false
+ }
+
+ tests := []routeTest{
+ {
+ title: "MatchFunc route, match",
+ route: new(Route).MatcherFunc(m),
+ request: newRequest("GET", "http://aaa.bbb.ccc"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: true,
+ },
+ {
+ title: "MatchFunc route, non-match",
+ route: new(Route).MatcherFunc(m),
+ request: newRequest("GET", "http://aaa.222.ccc"),
+ vars: map[string]string{},
+ host: "",
+ path: "",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestSubRouter(t *testing.T) {
+ subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
+ subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
+
+ tests := []routeTest{
+ {
+ route: subrouter1.Path("/{v2:[a-z]+}"),
+ request: newRequest("GET", "http://aaa.google.com/bbb"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb"},
+ host: "aaa.google.com",
+ path: "/bbb",
+ shouldMatch: true,
+ },
+ {
+ route: subrouter1.Path("/{v2:[a-z]+}"),
+ request: newRequest("GET", "http://111.google.com/111"),
+ vars: map[string]string{"v1": "aaa", "v2": "bbb"},
+ host: "aaa.google.com",
+ path: "/bbb",
+ shouldMatch: false,
+ },
+ {
+ route: subrouter2.Path("/baz/{v2}"),
+ request: newRequest("GET", "http://localhost/foo/bar/baz/ding"),
+ vars: map[string]string{"v1": "bar", "v2": "ding"},
+ host: "",
+ path: "/foo/bar/baz/ding",
+ shouldMatch: true,
+ },
+ {
+ route: subrouter2.Path("/baz/{v2}"),
+ request: newRequest("GET", "http://localhost/foo/bar"),
+ vars: map[string]string{"v1": "bar", "v2": "ding"},
+ host: "",
+ path: "/foo/bar/baz/ding",
+ shouldMatch: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+func TestNamedRoutes(t *testing.T) {
+ r1 := NewRouter()
+ r1.NewRoute().Name("a")
+ r1.NewRoute().Name("b")
+ r1.NewRoute().Name("c")
+
+ r2 := r1.NewRoute().Subrouter()
+ r2.NewRoute().Name("d")
+ r2.NewRoute().Name("e")
+ r2.NewRoute().Name("f")
+
+ r3 := r2.NewRoute().Subrouter()
+ r3.NewRoute().Name("g")
+ r3.NewRoute().Name("h")
+ r3.NewRoute().Name("i")
+
+ if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 {
+ t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes)
+ } else if r1.Get("i") == nil {
+ t.Errorf("Subroute name not registered")
+ }
+}
+
+func TestStrictSlash(t *testing.T) {
+ r := NewRouter()
+ r.StrictSlash(true)
+
+ tests := []routeTest{
+ {
+ title: "Redirect path without slash",
+ route: r.NewRoute().Path("/111/"),
+ request: newRequest("GET", "http://localhost/111"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/",
+ shouldMatch: true,
+ shouldRedirect: true,
+ },
+ {
+ title: "Do not redirect path with slash",
+ route: r.NewRoute().Path("/111/"),
+ request: newRequest("GET", "http://localhost/111/"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111/",
+ shouldMatch: true,
+ shouldRedirect: false,
+ },
+ {
+ title: "Redirect path with slash",
+ route: r.NewRoute().Path("/111"),
+ request: newRequest("GET", "http://localhost/111/"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ shouldMatch: true,
+ shouldRedirect: true,
+ },
+ {
+ title: "Do not redirect path without slash",
+ route: r.NewRoute().Path("/111"),
+ request: newRequest("GET", "http://localhost/111"),
+ vars: map[string]string{},
+ host: "",
+ path: "/111",
+ shouldMatch: true,
+ shouldRedirect: false,
+ },
+ {
+ title: "Propagate StrictSlash to subrouters",
+ route: r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
+ request: newRequest("GET", "http://localhost/static/images"),
+ vars: map[string]string{},
+ host: "",
+ path: "/static/images/",
+ shouldMatch: true,
+ shouldRedirect: true,
+ },
+ {
+ title: "Ignore StrictSlash for path prefix",
+ route: r.NewRoute().PathPrefix("/static/"),
+ request: newRequest("GET", "http://localhost/static/logo.png"),
+ vars: map[string]string{},
+ host: "",
+ path: "/static/",
+ shouldMatch: true,
+ shouldRedirect: false,
+ },
+ }
+
+ for _, test := range tests {
+ testRoute(t, test)
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+func getRouteTemplate(route *Route) string {
+ host, path := "none", "none"
+ if route.regexp != nil {
+ if route.regexp.host != nil {
+ host = route.regexp.host.template
+ }
+ if route.regexp.path != nil {
+ path = route.regexp.path.template
+ }
+ }
+ return fmt.Sprintf("Host: %v, Path: %v", host, path)
+}
+
+func testRoute(t *testing.T, test routeTest) {
+ request := test.request
+ route := test.route
+ vars := test.vars
+ shouldMatch := test.shouldMatch
+ host := test.host
+ path := test.path
+ url := test.host + test.path
+ shouldRedirect := test.shouldRedirect
+
+ var match RouteMatch
+ ok := route.Match(request, &match)
+ if ok != shouldMatch {
+ msg := "Should match"
+ if !shouldMatch {
+ msg = "Should not match"
+ }
+ t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
+ return
+ }
+ if shouldMatch {
+ if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
+ t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
+ return
+ }
+ if host != "" {
+ u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
+ if host != u.Host {
+ t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
+ return
+ }
+ }
+ if path != "" {
+ u, _ := route.URLPath(mapToPairs(match.Vars)...)
+ if path != u.Path {
+ t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
+ return
+ }
+ }
+ if url != "" {
+ u, _ := route.URL(mapToPairs(match.Vars)...)
+ if url != u.Host+u.Path {
+ t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
+ return
+ }
+ }
+ if shouldRedirect && match.Handler == nil {
+ t.Errorf("(%v) Did not redirect", test.title)
+ return
+ }
+ if !shouldRedirect && match.Handler != nil {
+ t.Errorf("(%v) Unexpected redirect", test.title)
+ return
+ }
+ }
+}
+
+// Tests that the context is cleared or not cleared properly depending on
+// the configuration of the router
+func TestKeepContext(t *testing.T) {
+ func1 := func(w http.ResponseWriter, r *http.Request) {}
+
+ r := NewRouter()
+ r.HandleFunc("/", func1).Name("func1")
+
+ req, _ := http.NewRequest("GET", "http://localhost/", nil)
+ context.Set(req, "t", 1)
+
+ res := new(http.ResponseWriter)
+ r.ServeHTTP(*res, req)
+
+ if _, ok := context.GetOk(req, "t"); ok {
+ t.Error("Context should have been cleared at end of request")
+ }
+
+ r.KeepContext = true
+
+ req, _ = http.NewRequest("GET", "http://localhost/", nil)
+ context.Set(req, "t", 1)
+
+ r.ServeHTTP(*res, req)
+ if _, ok := context.GetOk(req, "t"); !ok {
+ t.Error("Context should NOT have been cleared at end of request")
+ }
+
+}
+
+type TestA301ResponseWriter struct {
+ hh http.Header
+ status int
+}
+
+func (ho TestA301ResponseWriter) Header() http.Header {
+ return http.Header(ho.hh)
+}
+
+func (ho TestA301ResponseWriter) Write(b []byte) (int, error) {
+ return 0, nil
+}
+
+func (ho TestA301ResponseWriter) WriteHeader(code int) {
+ ho.status = code
+}
+
+func Test301Redirect(t *testing.T) {
+ m := make(http.Header)
+
+ func1 := func(w http.ResponseWriter, r *http.Request) {}
+ func2 := func(w http.ResponseWriter, r *http.Request) {}
+
+ r := NewRouter()
+ r.HandleFunc("/api/", func2).Name("func2")
+ r.HandleFunc("/", func1).Name("func1")
+
+ req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
+
+ res := TestA301ResponseWriter{
+ hh: m,
+ status: 0,
+ }
+ r.ServeHTTP(&res, req)
+
+ if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
+ t.Errorf("Should have complete URL with query string")
+ }
+}
+
+// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
+func TestSubrouterHeader(t *testing.T) {
+ expected := "func1 response"
+ func1 := func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprint(w, expected)
+ }
+ func2 := func(http.ResponseWriter, *http.Request) {}
+
+ r := NewRouter()
+ s := r.Headers("SomeSpecialHeader", "").Subrouter()
+ s.HandleFunc("/", func1).Name("func1")
+ r.HandleFunc("/", func2).Name("func2")
+
+ req, _ := http.NewRequest("GET", "http://localhost/", nil)
+ req.Header.Add("SomeSpecialHeader", "foo")
+ match := new(RouteMatch)
+ matched := r.Match(req, match)
+ if !matched {
+ t.Errorf("Should match request")
+ }
+ if match.Route.GetName() != "func1" {
+ t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
+ }
+ resp := NewRecorder()
+ match.Handler.ServeHTTP(resp, req)
+ if resp.Body.String() != expected {
+ t.Errorf("Expecting %q", expected)
+ }
+}
+
+// mapToPairs converts a string map to a slice of string pairs
+func mapToPairs(m map[string]string) []string {
+ var i int
+ p := make([]string, len(m)*2)
+ for k, v := range m {
+ p[i] = k
+ p[i+1] = v
+ i += 2
+ }
+ return p
+}
+
+// stringMapEqual checks the equality of two string maps
+func stringMapEqual(m1, m2 map[string]string) bool {
+ nil1 := m1 == nil
+ nil2 := m2 == nil
+ if nil1 != nil2 || len(m1) != len(m2) {
+ return false
+ }
+ for k, v := range m1 {
+ if v != m2[k] {
+ return false
+ }
+ }
+ return true
+}
+
+// newRequest is a helper function to create a new request with a method and url
+func newRequest(method, url string) *http.Request {
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ panic(err)
+ }
+ return req
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go b/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go
new file mode 100644
index 0000000..1f7c190
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/old_test.go
@@ -0,0 +1,714 @@
+// Old tests ported to Go1. This is a mess. Want to drop it one day.
+
+// Copyright 2011 Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "bytes"
+ "net/http"
+ "testing"
+)
+
+// ----------------------------------------------------------------------------
+// ResponseRecorder
+// ----------------------------------------------------------------------------
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// ResponseRecorder is an implementation of http.ResponseWriter that
+// records its mutations for later inspection in tests.
+type ResponseRecorder struct {
+ Code int // the HTTP response code from WriteHeader
+ HeaderMap http.Header // the HTTP response headers
+ Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
+ Flushed bool
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewRecorder() *ResponseRecorder {
+ return &ResponseRecorder{
+ HeaderMap: make(http.Header),
+ Body: new(bytes.Buffer),
+ }
+}
+
+// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
+// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
+const DefaultRemoteAddr = "1.2.3.4"
+
+// Header returns the response headers.
+func (rw *ResponseRecorder) Header() http.Header {
+ return rw.HeaderMap
+}
+
+// Write always succeeds and writes to rw.Body, if not nil.
+func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
+ if rw.Body != nil {
+ rw.Body.Write(buf)
+ }
+ if rw.Code == 0 {
+ rw.Code = http.StatusOK
+ }
+ return len(buf), nil
+}
+
+// WriteHeader sets rw.Code.
+func (rw *ResponseRecorder) WriteHeader(code int) {
+ rw.Code = code
+}
+
+// Flush sets rw.Flushed to true.
+func (rw *ResponseRecorder) Flush() {
+ rw.Flushed = true
+}
+
+// ----------------------------------------------------------------------------
+
+func TestRouteMatchers(t *testing.T) {
+ var scheme, host, path, query, method string
+ var headers map[string]string
+ var resultVars map[bool]map[string]string
+
+ router := NewRouter()
+ router.NewRoute().Host("{var1}.google.com").
+ Path("/{var2:[a-z]+}/{var3:[0-9]+}").
+ Queries("foo", "bar").
+ Methods("GET").
+ Schemes("https").
+ Headers("x-requested-with", "XMLHttpRequest")
+ router.NewRoute().Host("www.{var4}.com").
+ PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
+ Queries("baz", "ding").
+ Methods("POST").
+ Schemes("http").
+ Headers("Content-Type", "application/json")
+
+ reset := func() {
+ // Everything match.
+ scheme = "https"
+ host = "www.google.com"
+ path = "/product/42"
+ query = "?foo=bar"
+ method = "GET"
+ headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
+ resultVars = map[bool]map[string]string{
+ true: {"var1": "www", "var2": "product", "var3": "42"},
+ false: {},
+ }
+ }
+
+ reset2 := func() {
+ // Everything match.
+ scheme = "http"
+ host = "www.google.com"
+ path = "/foo/product/42/path/that/is/ignored"
+ query = "?baz=ding"
+ method = "POST"
+ headers = map[string]string{"Content-Type": "application/json"}
+ resultVars = map[bool]map[string]string{
+ true: {"var4": "google", "var5": "product", "var6": "42"},
+ false: {},
+ }
+ }
+
+ match := func(shouldMatch bool) {
+ url := scheme + "://" + host + path + query
+ request, _ := http.NewRequest(method, url, nil)
+ for key, value := range headers {
+ request.Header.Add(key, value)
+ }
+
+ var routeMatch RouteMatch
+ matched := router.Match(request, &routeMatch)
+ if matched != shouldMatch {
+ // Need better messages. :)
+ if matched {
+ t.Errorf("Should match.")
+ } else {
+ t.Errorf("Should not match.")
+ }
+ }
+
+ if matched {
+ currentRoute := routeMatch.Route
+ if currentRoute == nil {
+ t.Errorf("Expected a current route.")
+ }
+ vars := routeMatch.Vars
+ expectedVars := resultVars[shouldMatch]
+ if len(vars) != len(expectedVars) {
+ t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+ }
+ for name, value := range vars {
+ if expectedVars[name] != value {
+ t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
+ }
+ }
+ }
+ }
+
+ // 1st route --------------------------------------------------------------
+
+ // Everything match.
+ reset()
+ match(true)
+
+ // Scheme doesn't match.
+ reset()
+ scheme = "http"
+ match(false)
+
+ // Host doesn't match.
+ reset()
+ host = "www.mygoogle.com"
+ match(false)
+
+ // Path doesn't match.
+ reset()
+ path = "/product/notdigits"
+ match(false)
+
+ // Query doesn't match.
+ reset()
+ query = "?foo=baz"
+ match(false)
+
+ // Method doesn't match.
+ reset()
+ method = "POST"
+ match(false)
+
+ // Header doesn't match.
+ reset()
+ headers = map[string]string{}
+ match(false)
+
+ // Everything match, again.
+ reset()
+ match(true)
+
+ // 2nd route --------------------------------------------------------------
+
+ // Everything match.
+ reset2()
+ match(true)
+
+ // Scheme doesn't match.
+ reset2()
+ scheme = "https"
+ match(false)
+
+ // Host doesn't match.
+ reset2()
+ host = "sub.google.com"
+ match(false)
+
+ // Path doesn't match.
+ reset2()
+ path = "/bar/product/42"
+ match(false)
+
+ // Query doesn't match.
+ reset2()
+ query = "?foo=baz"
+ match(false)
+
+ // Method doesn't match.
+ reset2()
+ method = "GET"
+ match(false)
+
+ // Header doesn't match.
+ reset2()
+ headers = map[string]string{}
+ match(false)
+
+ // Everything match, again.
+ reset2()
+ match(true)
+}
+
+type headerMatcherTest struct {
+ matcher headerMatcher
+ headers map[string]string
+ result bool
+}
+
+var headerMatcherTests = []headerMatcherTest{
+ {
+ matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+ headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
+ result: true,
+ },
+ {
+ matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
+ headers: map[string]string{"X-Requested-With": "anything"},
+ result: true,
+ },
+ {
+ matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
+ headers: map[string]string{},
+ result: false,
+ },
+}
+
+type hostMatcherTest struct {
+ matcher *Route
+ url string
+ vars map[string]string
+ result bool
+}
+
+var hostMatcherTests = []hostMatcherTest{
+ {
+ matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+ url: "http://abc.def.ghi/",
+ vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+ result: true,
+ },
+ {
+ matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
+ url: "http://a.b.c/",
+ vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
+ result: false,
+ },
+}
+
+type methodMatcherTest struct {
+ matcher methodMatcher
+ method string
+ result bool
+}
+
+var methodMatcherTests = []methodMatcherTest{
+ {
+ matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+ method: "GET",
+ result: true,
+ },
+ {
+ matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+ method: "POST",
+ result: true,
+ },
+ {
+ matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+ method: "PUT",
+ result: true,
+ },
+ {
+ matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
+ method: "DELETE",
+ result: false,
+ },
+}
+
+type pathMatcherTest struct {
+ matcher *Route
+ url string
+ vars map[string]string
+ result bool
+}
+
+var pathMatcherTests = []pathMatcherTest{
+ {
+ matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+ url: "http://localhost:8080/123/456/789",
+ vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+ result: true,
+ },
+ {
+ matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
+ url: "http://localhost:8080/1/2/3",
+ vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
+ result: false,
+ },
+}
+
+type schemeMatcherTest struct {
+ matcher schemeMatcher
+ url string
+ result bool
+}
+
+var schemeMatcherTests = []schemeMatcherTest{
+ {
+ matcher: schemeMatcher([]string{"http", "https"}),
+ url: "http://localhost:8080/",
+ result: true,
+ },
+ {
+ matcher: schemeMatcher([]string{"http", "https"}),
+ url: "https://localhost:8080/",
+ result: true,
+ },
+ {
+ matcher: schemeMatcher([]string{"https"}),
+ url: "http://localhost:8080/",
+ result: false,
+ },
+ {
+ matcher: schemeMatcher([]string{"http"}),
+ url: "https://localhost:8080/",
+ result: false,
+ },
+}
+
+type urlBuildingTest struct {
+ route *Route
+ vars []string
+ url string
+}
+
+var urlBuildingTests = []urlBuildingTest{
+ {
+ route: new(Route).Host("foo.domain.com"),
+ vars: []string{},
+ url: "http://foo.domain.com",
+ },
+ {
+ route: new(Route).Host("{subdomain}.domain.com"),
+ vars: []string{"subdomain", "bar"},
+ url: "http://bar.domain.com",
+ },
+ {
+ route: new(Route).Host("foo.domain.com").Path("/articles"),
+ vars: []string{},
+ url: "http://foo.domain.com/articles",
+ },
+ {
+ route: new(Route).Path("/articles"),
+ vars: []string{},
+ url: "/articles",
+ },
+ {
+ route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
+ vars: []string{"category", "technology", "id", "42"},
+ url: "/articles/technology/42",
+ },
+ {
+ route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
+ vars: []string{"subdomain", "foo", "category", "technology", "id", "42"},
+ url: "http://foo.domain.com/articles/technology/42",
+ },
+}
+
+func TestHeaderMatcher(t *testing.T) {
+ for _, v := range headerMatcherTests {
+ request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
+ for key, value := range v.headers {
+ request.Header.Add(key, value)
+ }
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, request.Header)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
+ }
+ }
+ }
+}
+
+func TestHostMatcher(t *testing.T) {
+ for _, v := range hostMatcherTests {
+ request, _ := http.NewRequest("GET", v.url, nil)
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ vars := routeMatch.Vars
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, v.url)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+ }
+ }
+ if result {
+ if len(vars) != len(v.vars) {
+ t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+ }
+ for name, value := range vars {
+ if v.vars[name] != value {
+ t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+ }
+ }
+ } else {
+ if len(vars) != 0 {
+ t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+ }
+ }
+ }
+}
+
+func TestMethodMatcher(t *testing.T) {
+ for _, v := range methodMatcherTests {
+ request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, v.method)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, v.method)
+ }
+ }
+ }
+}
+
+func TestPathMatcher(t *testing.T) {
+ for _, v := range pathMatcherTests {
+ request, _ := http.NewRequest("GET", v.url, nil)
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ vars := routeMatch.Vars
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, v.url)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+ }
+ }
+ if result {
+ if len(vars) != len(v.vars) {
+ t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
+ }
+ for name, value := range vars {
+ if v.vars[name] != value {
+ t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
+ }
+ }
+ } else {
+ if len(vars) != 0 {
+ t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
+ }
+ }
+ }
+}
+
+func TestSchemeMatcher(t *testing.T) {
+ for _, v := range schemeMatcherTests {
+ request, _ := http.NewRequest("GET", v.url, nil)
+ var routeMatch RouteMatch
+ result := v.matcher.Match(request, &routeMatch)
+ if result != v.result {
+ if v.result {
+ t.Errorf("%#v: should match %v.", v.matcher, v.url)
+ } else {
+ t.Errorf("%#v: should not match %v.", v.matcher, v.url)
+ }
+ }
+ }
+}
+
+func TestUrlBuilding(t *testing.T) {
+
+ for _, v := range urlBuildingTests {
+ u, _ := v.route.URL(v.vars...)
+ url := u.String()
+ if url != v.url {
+ t.Errorf("expected %v, got %v", v.url, url)
+ /*
+ reversePath := ""
+ reverseHost := ""
+ if v.route.pathTemplate != nil {
+ reversePath = v.route.pathTemplate.Reverse
+ }
+ if v.route.hostTemplate != nil {
+ reverseHost = v.route.hostTemplate.Reverse
+ }
+
+ t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
+ */
+ }
+ }
+
+ ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
+ }
+
+ router := NewRouter()
+ router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
+
+ url, _ := router.Get("article").URL("category", "technology", "id", "42")
+ expected := "/articles/technology/42"
+ if url.String() != expected {
+ t.Errorf("Expected %v, got %v", expected, url.String())
+ }
+}
+
+func TestMatchedRouteName(t *testing.T) {
+ routeName := "stock"
+ router := NewRouter()
+ route := router.NewRoute().Path("/products/").Name(routeName)
+
+ url := "http://www.domain.com/products/"
+ request, _ := http.NewRequest("GET", url, nil)
+ var rv RouteMatch
+ ok := router.Match(request, &rv)
+
+ if !ok || rv.Route != route {
+ t.Errorf("Expected same route, got %+v.", rv.Route)
+ }
+
+ retName := rv.Route.GetName()
+ if retName != routeName {
+ t.Errorf("Expected %q, got %q.", routeName, retName)
+ }
+}
+
+func TestSubRouting(t *testing.T) {
+ // Example from docs.
+ router := NewRouter()
+ subrouter := router.NewRoute().Host("www.domain.com").Subrouter()
+ route := subrouter.NewRoute().Path("/products/").Name("products")
+
+ url := "http://www.domain.com/products/"
+ request, _ := http.NewRequest("GET", url, nil)
+ var rv RouteMatch
+ ok := router.Match(request, &rv)
+
+ if !ok || rv.Route != route {
+ t.Errorf("Expected same route, got %+v.", rv.Route)
+ }
+
+ u, _ := router.Get("products").URL()
+ builtUrl := u.String()
+ // Yay, subroute aware of the domain when building!
+ if builtUrl != url {
+ t.Errorf("Expected %q, got %q.", url, builtUrl)
+ }
+}
+
+func TestVariableNames(t *testing.T) {
+ route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
+ if route.err == nil {
+ t.Errorf("Expected error for duplicated variable names")
+ }
+}
+
+func TestRedirectSlash(t *testing.T) {
+ var route *Route
+ var routeMatch RouteMatch
+ r := NewRouter()
+
+ r.StrictSlash(false)
+ route = r.NewRoute()
+ if route.strictSlash != false {
+ t.Errorf("Expected false redirectSlash.")
+ }
+
+ r.StrictSlash(true)
+ route = r.NewRoute()
+ if route.strictSlash != true {
+ t.Errorf("Expected true redirectSlash.")
+ }
+
+ route = new(Route)
+ route.strictSlash = true
+ route.Path("/{arg1}/{arg2:[0-9]+}/")
+ request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
+ routeMatch = RouteMatch{}
+ _ = route.Match(request, &routeMatch)
+ vars := routeMatch.Vars
+ if vars["arg1"] != "foo" {
+ t.Errorf("Expected foo.")
+ }
+ if vars["arg2"] != "123" {
+ t.Errorf("Expected 123.")
+ }
+ rsp := NewRecorder()
+ routeMatch.Handler.ServeHTTP(rsp, request)
+ if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
+ t.Errorf("Expected redirect header.")
+ }
+
+ route = new(Route)
+ route.strictSlash = true
+ route.Path("/{arg1}/{arg2:[0-9]+}")
+ request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
+ routeMatch = RouteMatch{}
+ _ = route.Match(request, &routeMatch)
+ vars = routeMatch.Vars
+ if vars["arg1"] != "foo" {
+ t.Errorf("Expected foo.")
+ }
+ if vars["arg2"] != "123" {
+ t.Errorf("Expected 123.")
+ }
+ rsp = NewRecorder()
+ routeMatch.Handler.ServeHTTP(rsp, request)
+ if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
+ t.Errorf("Expected redirect header.")
+ }
+}
+
+// Test for the new regexp library, still not available in stable Go.
+func TestNewRegexp(t *testing.T) {
+ var p *routeRegexp
+ var matches []string
+
+ tests := map[string]map[string][]string{
+ "/{foo:a{2}}": {
+ "/a": nil,
+ "/aa": {"aa"},
+ "/aaa": nil,
+ "/aaaa": nil,
+ },
+ "/{foo:a{2,}}": {
+ "/a": nil,
+ "/aa": {"aa"},
+ "/aaa": {"aaa"},
+ "/aaaa": {"aaaa"},
+ },
+ "/{foo:a{2,3}}": {
+ "/a": nil,
+ "/aa": {"aa"},
+ "/aaa": {"aaa"},
+ "/aaaa": nil,
+ },
+ "/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
+ "/a": nil,
+ "/ab": nil,
+ "/abc": nil,
+ "/abcd": nil,
+ "/abc/ab": {"abc", "ab"},
+ "/abc/abc": nil,
+ "/abcd/ab": nil,
+ },
+ `/{foo:\w{3,}}/{bar:\d{2,}}`: {
+ "/a": nil,
+ "/ab": nil,
+ "/abc": nil,
+ "/abc/1": nil,
+ "/abc/12": {"abc", "12"},
+ "/abcd/12": {"abcd", "12"},
+ "/abcd/123": {"abcd", "123"},
+ },
+ }
+
+ for pattern, paths := range tests {
+ p, _ = newRouteRegexp(pattern, false, false, false, false)
+ for path, result := range paths {
+ matches = p.regexp.FindStringSubmatch(path)
+ if result == nil {
+ if matches != nil {
+ t.Errorf("%v should not match %v.", pattern, path)
+ }
+ } else {
+ if len(matches) != len(result)+1 {
+ t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
+ } else {
+ for k, v := range result {
+ if matches[k+1] != v {
+ t.Errorf("Expected %v, got %v.", v, matches[k+1])
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go b/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go
new file mode 100644
index 0000000..a630548
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/regexp.go
@@ -0,0 +1,276 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strings"
+)
+
+// newRouteRegexp parses a route template and returns a routeRegexp,
+// used to match a host, a path or a query string.
+//
+// It will extract named variables, assemble a regexp to be matched, create
+// a "reverse" template to build URLs and compile regexps to validate variable
+// values used in URL building.
+//
+// Previously we accepted only Python-like identifiers for variable
+// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
+// name and pattern can't be empty, and names can't contain a colon.
+func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
+ // Check if it is well-formed.
+ idxs, errBraces := braceIndices(tpl)
+ if errBraces != nil {
+ return nil, errBraces
+ }
+ // Backup the original.
+ template := tpl
+ // Now let's parse it.
+ defaultPattern := "[^/]+"
+ if matchQuery {
+ defaultPattern = "[^?&]+"
+ matchPrefix = true
+ } else if matchHost {
+ defaultPattern = "[^.]+"
+ matchPrefix = false
+ }
+ // Only match strict slash if not matching
+ if matchPrefix || matchHost || matchQuery {
+ strictSlash = false
+ }
+ // Set a flag for strictSlash.
+ endSlash := false
+ if strictSlash && strings.HasSuffix(tpl, "/") {
+ tpl = tpl[:len(tpl)-1]
+ endSlash = true
+ }
+ varsN := make([]string, len(idxs)/2)
+ varsR := make([]*regexp.Regexp, len(idxs)/2)
+ pattern := bytes.NewBufferString("")
+ if !matchQuery {
+ pattern.WriteByte('^')
+ }
+ reverse := bytes.NewBufferString("")
+ var end int
+ var err error
+ for i := 0; i < len(idxs); i += 2 {
+ // Set all values we are interested in.
+ raw := tpl[end:idxs[i]]
+ end = idxs[i+1]
+ parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
+ name := parts[0]
+ patt := defaultPattern
+ if len(parts) == 2 {
+ patt = parts[1]
+ }
+ // Name or pattern can't be empty.
+ if name == "" || patt == "" {
+ return nil, fmt.Errorf("mux: missing name or pattern in %q",
+ tpl[idxs[i]:end])
+ }
+ // Build the regexp pattern.
+ fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt)
+ // Build the reverse template.
+ fmt.Fprintf(reverse, "%s%%s", raw)
+ // Append variable name and compiled pattern.
+ varsN[i/2] = name
+ varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
+ if err != nil {
+ return nil, err
+ }
+ }
+ // Add the remaining.
+ raw := tpl[end:]
+ pattern.WriteString(regexp.QuoteMeta(raw))
+ if strictSlash {
+ pattern.WriteString("[/]?")
+ }
+ if !matchPrefix {
+ pattern.WriteByte('$')
+ }
+ reverse.WriteString(raw)
+ if endSlash {
+ reverse.WriteByte('/')
+ }
+ // Compile full regexp.
+ reg, errCompile := regexp.Compile(pattern.String())
+ if errCompile != nil {
+ return nil, errCompile
+ }
+ // Done!
+ return &routeRegexp{
+ template: template,
+ matchHost: matchHost,
+ matchQuery: matchQuery,
+ strictSlash: strictSlash,
+ regexp: reg,
+ reverse: reverse.String(),
+ varsN: varsN,
+ varsR: varsR,
+ }, nil
+}
+
+// routeRegexp stores a regexp to match a host or path and information to
+// collect and validate route variables.
+type routeRegexp struct {
+ // The unmodified template.
+ template string
+ // True for host match, false for path or query string match.
+ matchHost bool
+ // True for query string match, false for path and host match.
+ matchQuery bool
+ // The strictSlash value defined on the route, but disabled if PathPrefix was used.
+ strictSlash bool
+ // Expanded regexp.
+ regexp *regexp.Regexp
+ // Reverse template.
+ reverse string
+ // Variable names.
+ varsN []string
+ // Variable regexps (validators).
+ varsR []*regexp.Regexp
+}
+
+// Match matches the regexp against the URL host or path.
+func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
+ if !r.matchHost {
+ if r.matchQuery {
+ return r.regexp.MatchString(req.URL.RawQuery)
+ } else {
+ return r.regexp.MatchString(req.URL.Path)
+ }
+ }
+ return r.regexp.MatchString(getHost(req))
+}
+
+// url builds a URL part using the given values.
+func (r *routeRegexp) url(pairs ...string) (string, error) {
+ values, err := mapFromPairs(pairs...)
+ if err != nil {
+ return "", err
+ }
+ urlValues := make([]interface{}, len(r.varsN))
+ for k, v := range r.varsN {
+ value, ok := values[v]
+ if !ok {
+ return "", fmt.Errorf("mux: missing route variable %q", v)
+ }
+ urlValues[k] = value
+ }
+ rv := fmt.Sprintf(r.reverse, urlValues...)
+ if !r.regexp.MatchString(rv) {
+ // The URL is checked against the full regexp, instead of checking
+ // individual variables. This is faster but to provide a good error
+ // message, we check individual regexps if the URL doesn't match.
+ for k, v := range r.varsN {
+ if !r.varsR[k].MatchString(values[v]) {
+ return "", fmt.Errorf(
+ "mux: variable %q doesn't match, expected %q", values[v],
+ r.varsR[k].String())
+ }
+ }
+ }
+ return rv, nil
+}
+
+// braceIndices returns the first level curly brace indices from a string.
+// It returns an error in case of unbalanced braces.
+func braceIndices(s string) ([]int, error) {
+ var level, idx int
+ idxs := make([]int, 0)
+ for i := 0; i < len(s); i++ {
+ switch s[i] {
+ case '{':
+ if level++; level == 1 {
+ idx = i
+ }
+ case '}':
+ if level--; level == 0 {
+ idxs = append(idxs, idx, i+1)
+ } else if level < 0 {
+ return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
+ }
+ }
+ }
+ if level != 0 {
+ return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
+ }
+ return idxs, nil
+}
+
+// ----------------------------------------------------------------------------
+// routeRegexpGroup
+// ----------------------------------------------------------------------------
+
+// routeRegexpGroup groups the route matchers that carry variables.
+type routeRegexpGroup struct {
+ host *routeRegexp
+ path *routeRegexp
+ queries []*routeRegexp
+}
+
+// setMatch extracts the variables from the URL once a route matches.
+func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
+ // Store host variables.
+ if v.host != nil {
+ hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
+ if hostVars != nil {
+ for k, v := range v.host.varsN {
+ m.Vars[v] = hostVars[k+1]
+ }
+ }
+ }
+ // Store path variables.
+ if v.path != nil {
+ pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
+ if pathVars != nil {
+ for k, v := range v.path.varsN {
+ m.Vars[v] = pathVars[k+1]
+ }
+ // Check if we should redirect.
+ if v.path.strictSlash {
+ p1 := strings.HasSuffix(req.URL.Path, "/")
+ p2 := strings.HasSuffix(v.path.template, "/")
+ if p1 != p2 {
+ u, _ := url.Parse(req.URL.String())
+ if p1 {
+ u.Path = u.Path[:len(u.Path)-1]
+ } else {
+ u.Path += "/"
+ }
+ m.Handler = http.RedirectHandler(u.String(), 301)
+ }
+ }
+ }
+ }
+ // Store query string variables.
+ rawQuery := req.URL.RawQuery
+ for _, q := range v.queries {
+ queryVars := q.regexp.FindStringSubmatch(rawQuery)
+ if queryVars != nil {
+ for k, v := range q.varsN {
+ m.Vars[v] = queryVars[k+1]
+ }
+ }
+ }
+}
+
+// getHost tries its best to return the request host.
+func getHost(r *http.Request) string {
+ if r.URL.IsAbs() {
+ return r.URL.Host
+ }
+ host := r.Host
+ // Slice off any port information.
+ if i := strings.Index(host, ":"); i != -1 {
+ host = host[:i]
+ }
+ return host
+
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/mux/route.go b/Godeps/_workspace/src/github.com/gorilla/mux/route.go
new file mode 100644
index 0000000..c310e66
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/mux/route.go
@@ -0,0 +1,524 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mux
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+)
+
+// Route stores information to match a request and build URLs.
+type Route struct {
+ // Parent where the route was registered (a Router).
+ parent parentRoute
+ // Request handler for the route.
+ handler http.Handler
+ // List of matchers.
+ matchers []matcher
+ // Manager for the variables from host and path.
+ regexp *routeRegexpGroup
+ // If true, when the path pattern is "/path/", accessing "/path" will
+ // redirect to the former and vice versa.
+ strictSlash bool
+ // If true, this route never matches: it is only used to build URLs.
+ buildOnly bool
+ // The name used to build URLs.
+ name string
+ // Error resulted from building a route.
+ err error
+}
+
+// Match matches the route against the request.
+func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
+ if r.buildOnly || r.err != nil {
+ return false
+ }
+ // Match everything.
+ for _, m := range r.matchers {
+ if matched := m.Match(req, match); !matched {
+ return false
+ }
+ }
+ // Yay, we have a match. Let's collect some info about it.
+ if match.Route == nil {
+ match.Route = r
+ }
+ if match.Handler == nil {
+ match.Handler = r.handler
+ }
+ if match.Vars == nil {
+ match.Vars = make(map[string]string)
+ }
+ // Set variables.
+ if r.regexp != nil {
+ r.regexp.setMatch(req, match, r)
+ }
+ return true
+}
+
+// ----------------------------------------------------------------------------
+// Route attributes
+// ----------------------------------------------------------------------------
+
+// GetError returns an error resulted from building the route, if any.
+func (r *Route) GetError() error {
+ return r.err
+}
+
+// BuildOnly sets the route to never match: it is only used to build URLs.
+func (r *Route) BuildOnly() *Route {
+ r.buildOnly = true
+ return r
+}
+
+// Handler --------------------------------------------------------------------
+
+// Handler sets a handler for the route.
+func (r *Route) Handler(handler http.Handler) *Route {
+ if r.err == nil {
+ r.handler = handler
+ }
+ return r
+}
+
+// HandlerFunc sets a handler function for the route.
+func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
+ return r.Handler(http.HandlerFunc(f))
+}
+
+// GetHandler returns the handler for the route, if any.
+func (r *Route) GetHandler() http.Handler {
+ return r.handler
+}
+
+// Name -----------------------------------------------------------------------
+
+// Name sets the name for the route, used to build URLs.
+// If the name was registered already it will be overwritten.
+func (r *Route) Name(name string) *Route {
+ if r.name != "" {
+ r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
+ r.name, name)
+ }
+ if r.err == nil {
+ r.name = name
+ r.getNamedRoutes()[name] = r
+ }
+ return r
+}
+
+// GetName returns the name for the route, if any.
+func (r *Route) GetName() string {
+ return r.name
+}
+
+// ----------------------------------------------------------------------------
+// Matchers
+// ----------------------------------------------------------------------------
+
+// matcher types try to match a request.
+type matcher interface {
+ Match(*http.Request, *RouteMatch) bool
+}
+
+// addMatcher adds a matcher to the route.
+func (r *Route) addMatcher(m matcher) *Route {
+ if r.err == nil {
+ r.matchers = append(r.matchers, m)
+ }
+ return r
+}
+
+// addRegexpMatcher adds a host or path matcher and builder to a route.
+func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
+ if r.err != nil {
+ return r.err
+ }
+ r.regexp = r.getRegexpGroup()
+ if !matchHost && !matchQuery {
+ if len(tpl) == 0 || tpl[0] != '/' {
+ return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
+ }
+ if r.regexp.path != nil {
+ tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
+ }
+ }
+ rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
+ if err != nil {
+ return err
+ }
+ for _, q := range r.regexp.queries {
+ if err = uniqueVars(rr.varsN, q.varsN); err != nil {
+ return err
+ }
+ }
+ if matchHost {
+ if r.regexp.path != nil {
+ if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
+ return err
+ }
+ }
+ r.regexp.host = rr
+ } else {
+ if r.regexp.host != nil {
+ if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
+ return err
+ }
+ }
+ if matchQuery {
+ r.regexp.queries = append(r.regexp.queries, rr)
+ } else {
+ r.regexp.path = rr
+ }
+ }
+ r.addMatcher(rr)
+ return nil
+}
+
+// Headers --------------------------------------------------------------------
+
+// headerMatcher matches the request against header values.
+type headerMatcher map[string]string
+
+func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
+ return matchMap(m, r.Header, true)
+}
+
+// Headers adds a matcher for request header values.
+// It accepts a sequence of key/value pairs to be matched. For example:
+//
+// r := mux.NewRouter()
+// r.Headers("Content-Type", "application/json",
+// "X-Requested-With", "XMLHttpRequest")
+//
+// The above route will only match if both request header values match.
+//
+// It the value is an empty string, it will match any value if the key is set.
+func (r *Route) Headers(pairs ...string) *Route {
+ if r.err == nil {
+ var headers map[string]string
+ headers, r.err = mapFromPairs(pairs...)
+ return r.addMatcher(headerMatcher(headers))
+ }
+ return r
+}
+
+// Host -----------------------------------------------------------------------
+
+// Host adds a matcher for the URL host.
+// It accepts a template with zero or more URL variables enclosed by {}.
+// Variables can define an optional regexp pattern to me matched:
+//
+// - {name} matches anything until the next dot.
+//
+// - {name:pattern} matches the given regexp pattern.
+//
+// For example:
+//
+// r := mux.NewRouter()
+// r.Host("www.domain.com")
+// r.Host("{subdomain}.domain.com")
+// r.Host("{subdomain:[a-z]+}.domain.com")
+//
+// Variable names must be unique in a given route. They can be retrieved
+// calling mux.Vars(request).
+func (r *Route) Host(tpl string) *Route {
+ r.err = r.addRegexpMatcher(tpl, true, false, false)
+ return r
+}
+
+// MatcherFunc ----------------------------------------------------------------
+
+// MatcherFunc is the function signature used by custom matchers.
+type MatcherFunc func(*http.Request, *RouteMatch) bool
+
+func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
+ return m(r, match)
+}
+
+// MatcherFunc adds a custom function to be used as request matcher.
+func (r *Route) MatcherFunc(f MatcherFunc) *Route {
+ return r.addMatcher(f)
+}
+
+// Methods --------------------------------------------------------------------
+
+// methodMatcher matches the request against HTTP methods.
+type methodMatcher []string
+
+func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
+ return matchInArray(m, r.Method)
+}
+
+// Methods adds a matcher for HTTP methods.
+// It accepts a sequence of one or more methods to be matched, e.g.:
+// "GET", "POST", "PUT".
+func (r *Route) Methods(methods ...string) *Route {
+ for k, v := range methods {
+ methods[k] = strings.ToUpper(v)
+ }
+ return r.addMatcher(methodMatcher(methods))
+}
+
+// Path -----------------------------------------------------------------------
+
+// Path adds a matcher for the URL path.
+// It accepts a template with zero or more URL variables enclosed by {}. The
+// template must start with a "/".
+// Variables can define an optional regexp pattern to me matched:
+//
+// - {name} matches anything until the next slash.
+//
+// - {name:pattern} matches the given regexp pattern.
+//
+// For example:
+//
+// r := mux.NewRouter()
+// r.Path("/products/").Handler(ProductsHandler)
+// r.Path("/products/{key}").Handler(ProductsHandler)
+// r.Path("/articles/{category}/{id:[0-9]+}").
+// Handler(ArticleHandler)
+//
+// Variable names must be unique in a given route. They can be retrieved
+// calling mux.Vars(request).
+func (r *Route) Path(tpl string) *Route {
+ r.err = r.addRegexpMatcher(tpl, false, false, false)
+ return r
+}
+
+// PathPrefix -----------------------------------------------------------------
+
+// PathPrefix adds a matcher for the URL path prefix. This matches if the given
+// template is a prefix of the full URL path. See Route.Path() for details on
+// the tpl argument.
+//
+// Note that it does not treat slashes specially ("/foobar/" will be matched by
+// the prefix "/foo") so you may want to use a trailing slash here.
+//
+// Also note that the setting of Router.StrictSlash() has no effect on routes
+// with a PathPrefix matcher.
+func (r *Route) PathPrefix(tpl string) *Route {
+ r.err = r.addRegexpMatcher(tpl, false, true, false)
+ return r
+}
+
+// Query ----------------------------------------------------------------------
+
+// Queries adds a matcher for URL query values.
+// It accepts a sequence of key/value pairs. Values may define variables.
+// For example:
+//
+// r := mux.NewRouter()
+// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
+//
+// The above route will only match if the URL contains the defined queries
+// values, e.g.: ?foo=bar&id=42.
+//
+// It the value is an empty string, it will match any value if the key is set.
+//
+// Variables can define an optional regexp pattern to me matched:
+//
+// - {name} matches anything until the next slash.
+//
+// - {name:pattern} matches the given regexp pattern.
+func (r *Route) Queries(pairs ...string) *Route {
+ length := len(pairs)
+ if length%2 != 0 {
+ r.err = fmt.Errorf(
+ "mux: number of parameters must be multiple of 2, got %v", pairs)
+ return nil
+ }
+ for i := 0; i < length; i += 2 {
+ if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil {
+ return r
+ }
+ }
+
+ return r
+}
+
+// Schemes --------------------------------------------------------------------
+
+// schemeMatcher matches the request against URL schemes.
+type schemeMatcher []string
+
+func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
+ return matchInArray(m, r.URL.Scheme)
+}
+
+// Schemes adds a matcher for URL schemes.
+// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
+func (r *Route) Schemes(schemes ...string) *Route {
+ for k, v := range schemes {
+ schemes[k] = strings.ToLower(v)
+ }
+ return r.addMatcher(schemeMatcher(schemes))
+}
+
+// Subrouter ------------------------------------------------------------------
+
+// Subrouter creates a subrouter for the route.
+//
+// It will test the inner routes only if the parent route matched. For example:
+//
+// r := mux.NewRouter()
+// s := r.Host("www.domain.com").Subrouter()
+// s.HandleFunc("/products/", ProductsHandler)
+// s.HandleFunc("/products/{key}", ProductHandler)
+// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
+//
+// Here, the routes registered in the subrouter won't be tested if the host
+// doesn't match.
+func (r *Route) Subrouter() *Router {
+ router := &Router{parent: r, strictSlash: r.strictSlash}
+ r.addMatcher(router)
+ return router
+}
+
+// ----------------------------------------------------------------------------
+// URL building
+// ----------------------------------------------------------------------------
+
+// URL builds a URL for the route.
+//
+// It accepts a sequence of key/value pairs for the route variables. For
+// example, given this route:
+//
+// r := mux.NewRouter()
+// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+// Name("article")
+//
+// ...a URL for it can be built using:
+//
+// url, err := r.Get("article").URL("category", "technology", "id", "42")
+//
+// ...which will return an url.URL with the following path:
+//
+// "/articles/technology/42"
+//
+// This also works for host variables:
+//
+// r := mux.NewRouter()
+// r.Host("{subdomain}.domain.com").
+// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
+// Name("article")
+//
+// // url.String() will be "http://news.domain.com/articles/technology/42"
+// url, err := r.Get("article").URL("subdomain", "news",
+// "category", "technology",
+// "id", "42")
+//
+// All variables defined in the route are required, and their values must
+// conform to the corresponding patterns.
+func (r *Route) URL(pairs ...string) (*url.URL, error) {
+ if r.err != nil {
+ return nil, r.err
+ }
+ if r.regexp == nil {
+ return nil, errors.New("mux: route doesn't have a host or path")
+ }
+ var scheme, host, path string
+ var err error
+ if r.regexp.host != nil {
+ // Set a default scheme.
+ scheme = "http"
+ if host, err = r.regexp.host.url(pairs...); err != nil {
+ return nil, err
+ }
+ }
+ if r.regexp.path != nil {
+ if path, err = r.regexp.path.url(pairs...); err != nil {
+ return nil, err
+ }
+ }
+ return &url.URL{
+ Scheme: scheme,
+ Host: host,
+ Path: path,
+ }, nil
+}
+
+// URLHost builds the host part of the URL for a route. See Route.URL().
+//
+// The route must have a host defined.
+func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
+ if r.err != nil {
+ return nil, r.err
+ }
+ if r.regexp == nil || r.regexp.host == nil {
+ return nil, errors.New("mux: route doesn't have a host")
+ }
+ host, err := r.regexp.host.url(pairs...)
+ if err != nil {
+ return nil, err
+ }
+ return &url.URL{
+ Scheme: "http",
+ Host: host,
+ }, nil
+}
+
+// URLPath builds the path part of the URL for a route. See Route.URL().
+//
+// The route must have a path defined.
+func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
+ if r.err != nil {
+ return nil, r.err
+ }
+ if r.regexp == nil || r.regexp.path == nil {
+ return nil, errors.New("mux: route doesn't have a path")
+ }
+ path, err := r.regexp.path.url(pairs...)
+ if err != nil {
+ return nil, err
+ }
+ return &url.URL{
+ Path: path,
+ }, nil
+}
+
+// ----------------------------------------------------------------------------
+// parentRoute
+// ----------------------------------------------------------------------------
+
+// parentRoute allows routes to know about parent host and path definitions.
+type parentRoute interface {
+ getNamedRoutes() map[string]*Route
+ getRegexpGroup() *routeRegexpGroup
+}
+
+// getNamedRoutes returns the map where named routes are registered.
+func (r *Route) getNamedRoutes() map[string]*Route {
+ if r.parent == nil {
+ // During tests router is not always set.
+ r.parent = NewRouter()
+ }
+ return r.parent.getNamedRoutes()
+}
+
+// getRegexpGroup returns regexp definitions from this route.
+func (r *Route) getRegexpGroup() *routeRegexpGroup {
+ if r.regexp == nil {
+ if r.parent == nil {
+ // During tests router is not always set.
+ r.parent = NewRouter()
+ }
+ regexp := r.parent.getRegexpGroup()
+ if regexp == nil {
+ r.regexp = new(routeRegexpGroup)
+ } else {
+ // Copy.
+ r.regexp = &routeRegexpGroup{
+ host: regexp.host,
+ path: regexp.path,
+ queries: regexp.queries,
+ }
+ }
+ }
+ return r.regexp
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/.travis.yml b/Godeps/_workspace/src/github.com/gorilla/schema/.travis.yml
new file mode 100644
index 0000000..d87d465
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/schema/.travis.yml
@@ -0,0 +1,7 @@
+language: go
+
+go:
+ - 1.0
+ - 1.1
+ - 1.2
+ - tip
diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/LICENSE b/Godeps/_workspace/src/github.com/gorilla/schema/LICENSE
new file mode 100644
index 0000000..0e5fb87
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/schema/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/README.md b/Godeps/_workspace/src/github.com/gorilla/schema/README.md
new file mode 100644
index 0000000..f494e42
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/schema/README.md
@@ -0,0 +1,3 @@
+schema
+======
+[](https://travis-ci.org/gorilla/schema)
diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/cache.go b/Godeps/_workspace/src/github.com/gorilla/schema/cache.go
new file mode 100644
index 0000000..768d99e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/schema/cache.go
@@ -0,0 +1,221 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package schema
+
+import (
+ "errors"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+var invalidPath = errors.New("schema: invalid path")
+
+// newCache returns a new cache.
+func newCache() *cache {
+ c := cache{
+ m: make(map[reflect.Type]*structInfo),
+ conv: make(map[reflect.Type]Converter),
+ tag: "schema",
+ }
+ for k, v := range converters {
+ c.conv[k] = v
+ }
+ return &c
+}
+
+// cache caches meta-data about a struct.
+type cache struct {
+ l sync.RWMutex
+ m map[reflect.Type]*structInfo
+ conv map[reflect.Type]Converter
+ tag string
+}
+
+// parsePath parses a path in dotted notation verifying that it is a valid
+// path to a struct field.
+//
+// It returns "path parts" which contain indices to fields to be used by
+// reflect.Value.FieldByString(). Multiple parts are required for slices of
+// structs.
+func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
+ var struc *structInfo
+ var field *fieldInfo
+ var index64 int64
+ var err error
+ parts := make([]pathPart, 0)
+ path := make([]string, 0)
+ keys := strings.Split(p, ".")
+ for i := 0; i < len(keys); i++ {
+ if struc = c.get(t); struc == nil {
+ return nil, invalidPath
+ }
+ if field = struc.get(keys[i]); field == nil {
+ return nil, invalidPath
+ }
+ // Valid field. Append index.
+ path = append(path, field.name)
+ if field.ss {
+ // Parse a special case: slices of structs.
+ // i+1 must be the slice index, and i+2 must exist.
+ i++
+ if i+1 >= len(keys) {
+ return nil, invalidPath
+ }
+ if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
+ return nil, invalidPath
+ }
+ parts = append(parts, pathPart{
+ path: path,
+ field: field,
+ index: int(index64),
+ })
+ path = make([]string, 0)
+
+ // Get the next struct type, dropping ptrs.
+ if field.typ.Kind() == reflect.Ptr {
+ t = field.typ.Elem()
+ } else {
+ t = field.typ
+ }
+ if t.Kind() == reflect.Slice {
+ t = t.Elem()
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ }
+ } else if field.typ.Kind() == reflect.Struct {
+ t = field.typ
+ } else if field.typ.Kind() == reflect.Ptr && field.typ.Elem().Kind() == reflect.Struct {
+ t = field.typ.Elem()
+ }
+ }
+ // Add the remaining.
+ parts = append(parts, pathPart{
+ path: path,
+ field: field,
+ index: -1,
+ })
+ return parts, nil
+}
+
+// get returns a cached structInfo, creating it if necessary.
+func (c *cache) get(t reflect.Type) *structInfo {
+ c.l.RLock()
+ info := c.m[t]
+ c.l.RUnlock()
+ if info == nil {
+ info = c.create(t, nil)
+ c.l.Lock()
+ c.m[t] = info
+ c.l.Unlock()
+ }
+ return info
+}
+
+// creat creates a structInfo with meta-data about a struct.
+func (c *cache) create(t reflect.Type, info *structInfo) *structInfo {
+ if info == nil {
+ info = &structInfo{fields: []*fieldInfo{}}
+ }
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ if field.Anonymous {
+ ft := field.Type
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ if ft.Kind() == reflect.Struct {
+ c.create(ft, info)
+ }
+ }
+ c.createField(field, info)
+ }
+ return info
+}
+
+// createField creates a fieldInfo for the given field.
+func (c *cache) createField(field reflect.StructField, info *structInfo) {
+ alias := fieldAlias(field, c.tag)
+ if alias == "-" {
+ // Ignore this field.
+ return
+ }
+ // Check if the type is supported and don't cache it if not.
+ // First let's get the basic type.
+ isSlice, isStruct := false, false
+ ft := field.Type
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ if isSlice = ft.Kind() == reflect.Slice; isSlice {
+ ft = ft.Elem()
+ if ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+ }
+ if isStruct = ft.Kind() == reflect.Struct; !isStruct {
+ if conv := c.conv[ft]; conv == nil {
+ // Type is not supported.
+ return
+ }
+ }
+
+ info.fields = append(info.fields, &fieldInfo{
+ typ: field.Type,
+ name: field.Name,
+ ss: isSlice && isStruct,
+ alias: alias,
+ })
+}
+
+// ----------------------------------------------------------------------------
+
+type structInfo struct {
+ fields []*fieldInfo
+}
+
+func (i *structInfo) get(alias string) *fieldInfo {
+ for _, field := range i.fields {
+ if strings.EqualFold(field.alias, alias) {
+ return field
+ }
+ }
+ return nil
+}
+
+type fieldInfo struct {
+ typ reflect.Type
+ name string // field name in the struct.
+ ss bool // true if this is a slice of structs.
+ alias string
+}
+
+type pathPart struct {
+ field *fieldInfo
+ path []string // path to the field: walks structs using field names.
+ index int // struct index in slices of structs.
+}
+
+// ----------------------------------------------------------------------------
+
+// fieldAlias parses a field tag to get a field alias.
+func fieldAlias(field reflect.StructField, tagName string) string {
+ var alias string
+ if tag := field.Tag.Get(tagName); tag != "" {
+ // For now tags only support the name but let's folow the
+ // comma convention from encoding/json and others.
+ if idx := strings.Index(tag, ","); idx == -1 {
+ alias = tag
+ } else {
+ alias = tag[:idx]
+ }
+ }
+ if alias == "" {
+ alias = field.Name
+ }
+ return alias
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/converter.go b/Godeps/_workspace/src/github.com/gorilla/schema/converter.go
new file mode 100644
index 0000000..6dccc78
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/schema/converter.go
@@ -0,0 +1,145 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package schema
+
+import (
+ "reflect"
+ "strconv"
+)
+
+type Converter func(string) reflect.Value
+
+var (
+ invalidValue = reflect.Value{}
+ boolType = reflect.TypeOf(false)
+ float32Type = reflect.TypeOf(float32(0))
+ float64Type = reflect.TypeOf(float64(0))
+ intType = reflect.TypeOf(int(0))
+ int8Type = reflect.TypeOf(int8(0))
+ int16Type = reflect.TypeOf(int16(0))
+ int32Type = reflect.TypeOf(int32(0))
+ int64Type = reflect.TypeOf(int64(0))
+ stringType = reflect.TypeOf("")
+ uintType = reflect.TypeOf(uint(0))
+ uint8Type = reflect.TypeOf(uint8(0))
+ uint16Type = reflect.TypeOf(uint16(0))
+ uint32Type = reflect.TypeOf(uint32(0))
+ uint64Type = reflect.TypeOf(uint64(0))
+)
+
+// Default converters for basic types.
+var converters = map[reflect.Type]Converter{
+ boolType: convertBool,
+ float32Type: convertFloat32,
+ float64Type: convertFloat64,
+ intType: convertInt,
+ int8Type: convertInt8,
+ int16Type: convertInt16,
+ int32Type: convertInt32,
+ int64Type: convertInt64,
+ stringType: convertString,
+ uintType: convertUint,
+ uint8Type: convertUint8,
+ uint16Type: convertUint16,
+ uint32Type: convertUint32,
+ uint64Type: convertUint64,
+}
+
+func convertBool(value string) reflect.Value {
+ if value == "on" {
+ return reflect.ValueOf(true)
+ } else if v, err := strconv.ParseBool(value); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertFloat32(value string) reflect.Value {
+ if v, err := strconv.ParseFloat(value, 32); err == nil {
+ return reflect.ValueOf(float32(v))
+ }
+ return invalidValue
+}
+
+func convertFloat64(value string) reflect.Value {
+ if v, err := strconv.ParseFloat(value, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertInt(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 0); err == nil {
+ return reflect.ValueOf(int(v))
+ }
+ return invalidValue
+}
+
+func convertInt8(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 8); err == nil {
+ return reflect.ValueOf(int8(v))
+ }
+ return invalidValue
+}
+
+func convertInt16(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 16); err == nil {
+ return reflect.ValueOf(int16(v))
+ }
+ return invalidValue
+}
+
+func convertInt32(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 32); err == nil {
+ return reflect.ValueOf(int32(v))
+ }
+ return invalidValue
+}
+
+func convertInt64(value string) reflect.Value {
+ if v, err := strconv.ParseInt(value, 10, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
+
+func convertString(value string) reflect.Value {
+ return reflect.ValueOf(value)
+}
+
+func convertUint(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 0); err == nil {
+ return reflect.ValueOf(uint(v))
+ }
+ return invalidValue
+}
+
+func convertUint8(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 8); err == nil {
+ return reflect.ValueOf(uint8(v))
+ }
+ return invalidValue
+}
+
+func convertUint16(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 16); err == nil {
+ return reflect.ValueOf(uint16(v))
+ }
+ return invalidValue
+}
+
+func convertUint32(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 32); err == nil {
+ return reflect.ValueOf(uint32(v))
+ }
+ return invalidValue
+}
+
+func convertUint64(value string) reflect.Value {
+ if v, err := strconv.ParseUint(value, 10, 64); err == nil {
+ return reflect.ValueOf(v)
+ }
+ return invalidValue
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/decoder.go b/Godeps/_workspace/src/github.com/gorilla/schema/decoder.go
new file mode 100644
index 0000000..4fdce69
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/schema/decoder.go
@@ -0,0 +1,226 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package schema
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+)
+
+// NewDecoder returns a new Decoder.
+func NewDecoder() *Decoder {
+ return &Decoder{cache: newCache()}
+}
+
+// Decoder decodes values from a map[string][]string to a struct.
+type Decoder struct {
+ cache *cache
+ zeroEmpty bool
+ ignoreUnknownKeys bool
+}
+
+// SetAliasTag changes the tag used to locate custome field aliases.
+// The default tag is "schema".
+func (d *Decoder) SetAliasTag(tag string) {
+ d.cache.tag = tag
+}
+
+// ZeroEmpty controls the behaviour when the decoder encounters empty values
+// in a map.
+// If z is true and a key in the map has the empty string as a value
+// then the corresponding struct field is set to the zero value.
+// If z is false then empty strings are ignored.
+//
+// The default value is false, that is empty values do not change
+// the value of the struct field.
+func (d *Decoder) ZeroEmpty(z bool) {
+ d.zeroEmpty = z
+}
+
+// IgnoreUnknownKeys controls the behaviour when the decoder encounters unknown
+// keys in the map.
+// If i is true and an unknown field is encountered, it is ignored. This is
+// similar to how unknown keys are handled by encoding/json.
+// If i is false then Decode will return an error. Note that any valid keys
+// will still be decoded in to the target struct.
+//
+// To preserve backwards compatibility, the default value is false.
+func (d *Decoder) IgnoreUnknownKeys(i bool) {
+ d.ignoreUnknownKeys = i
+}
+
+// RegisterConverter registers a converter function for a custom type.
+func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
+ d.cache.conv[reflect.TypeOf(value)] = converterFunc
+}
+
+// Decode decodes a map[string][]string to a struct.
+//
+// The first parameter must be a pointer to a struct.
+//
+// The second parameter is a map, typically url.Values from an HTTP request.
+// Keys are "paths" in dotted notation to the struct fields and nested structs.
+//
+// See the package documentation for a full explanation of the mechanics.
+func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
+ v := reflect.ValueOf(dst)
+ if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
+ return errors.New("schema: interface must be a pointer to struct")
+ }
+ v = v.Elem()
+ t := v.Type()
+ errors := MultiError{}
+ for path, values := range src {
+ if parts, err := d.cache.parsePath(path, t); err == nil {
+ if err = d.decode(v, path, parts, values); err != nil {
+ errors[path] = err
+ }
+ } else if !d.ignoreUnknownKeys {
+ errors[path] = fmt.Errorf("schema: invalid path %q", path)
+ }
+ }
+ if len(errors) > 0 {
+ return errors
+ }
+ return nil
+}
+
+// decode fills a struct field using a parsed path.
+func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart,
+ values []string) error {
+ // Get the field walking the struct fields by index.
+ for _, name := range parts[0].path {
+ if v.Type().Kind() == reflect.Ptr {
+ if v.IsNil() {
+ v.Set(reflect.New(v.Type().Elem()))
+ }
+ v = v.Elem()
+ }
+ v = v.FieldByName(name)
+ }
+
+ // Don't even bother for unexported fields.
+ if !v.CanSet() {
+ return nil
+ }
+
+ // Dereference if needed.
+ t := v.Type()
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ if v.IsNil() {
+ v.Set(reflect.New(t))
+ }
+ v = v.Elem()
+ }
+
+ // Slice of structs. Let's go recursive.
+ if len(parts) > 1 {
+ idx := parts[0].index
+ if v.IsNil() || v.Len() < idx+1 {
+ value := reflect.MakeSlice(t, idx+1, idx+1)
+ if v.Len() < idx+1 {
+ // Resize it.
+ reflect.Copy(value, v)
+ }
+ v.Set(value)
+ }
+ return d.decode(v.Index(idx), path, parts[1:], values)
+ }
+
+ // Simple case.
+ if t.Kind() == reflect.Slice {
+ var items []reflect.Value
+ elemT := t.Elem()
+ isPtrElem := elemT.Kind() == reflect.Ptr
+ if isPtrElem {
+ elemT = elemT.Elem()
+ }
+ conv := d.cache.conv[elemT]
+ if conv == nil {
+ return fmt.Errorf("schema: converter not found for %v", elemT)
+ }
+ for key, value := range values {
+ if value == "" {
+ if d.zeroEmpty {
+ items = append(items, reflect.Zero(elemT))
+ }
+ } else if item := conv(value); item.IsValid() {
+ if isPtrElem {
+ ptr := reflect.New(elemT)
+ ptr.Elem().Set(item)
+ item = ptr
+ }
+ items = append(items, item)
+ } else {
+ // If a single value is invalid should we give up
+ // or set a zero value?
+ return ConversionError{path, key}
+ }
+ }
+ value := reflect.Append(reflect.MakeSlice(t, 0, 0), items...)
+ v.Set(value)
+ } else {
+ val := ""
+ // Use the last value provided if any values were provided
+ if len(values) > 0 {
+ val = values[len(values)-1]
+ }
+
+ if val == "" {
+ if d.zeroEmpty {
+ v.Set(reflect.Zero(t))
+ }
+ } else if conv := d.cache.conv[t]; conv != nil {
+ if value := conv(val); value.IsValid() {
+ v.Set(value)
+ } else {
+ return ConversionError{path, -1}
+ }
+ } else {
+ return fmt.Errorf("schema: converter not found for %v", t)
+ }
+ }
+ return nil
+}
+
+// Errors ---------------------------------------------------------------------
+
+// ConversionError stores information about a failed conversion.
+type ConversionError struct {
+ Key string // key from the source map.
+ Index int // index for multi-value fields; -1 for single-value fields.
+}
+
+func (e ConversionError) Error() string {
+ if e.Index < 0 {
+ return fmt.Sprintf("schema: error converting value for %q", e.Key)
+ }
+ return fmt.Sprintf("schema: error converting value for index %d of %q",
+ e.Index, e.Key)
+}
+
+// MultiError stores multiple decoding errors.
+//
+// Borrowed from the App Engine SDK.
+type MultiError map[string]error
+
+func (e MultiError) Error() string {
+ s := ""
+ for _, err := range e {
+ s = err.Error()
+ break
+ }
+ switch len(e) {
+ case 0:
+ return "(0 errors)"
+ case 1:
+ return s
+ case 2:
+ return s + " (and 1 other error)"
+ }
+ return fmt.Sprintf("%s (and %d other errors)", s, len(e)-1)
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/decoder_test.go b/Godeps/_workspace/src/github.com/gorilla/schema/decoder_test.go
new file mode 100644
index 0000000..44b0600
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/schema/decoder_test.go
@@ -0,0 +1,1038 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package schema
+
+import (
+ //"reflect"
+ "testing"
+)
+
+// All cases we want to cover, in a nutshell.
+type S1 struct {
+ F01 int `schema:"f1"`
+ F02 *int `schema:"f2"`
+ F03 []int `schema:"f3"`
+ F04 []*int `schema:"f4"`
+ F05 *[]int `schema:"f5"`
+ F06 *[]*int `schema:"f6"`
+ F07 S2 `schema:"f7"`
+ F08 *S1 `schema:"f8"`
+ F09 int `schema:"-"`
+ F10 []S1 `schema:"f10"`
+ F11 []*S1 `schema:"f11"`
+ F12 *[]S1 `schema:"f12"`
+ F13 *[]*S1 `schema:"f13"`
+ F14 int `schema:"f14"`
+}
+
+type S2 struct {
+ F01 *[]*int `schema:"f1"`
+}
+
+func TestAll(t *testing.T) {
+ v := map[string][]string{
+ "f1": {"1"},
+ "f2": {"2"},
+ "f3": {"31", "32"},
+ "f4": {"41", "42"},
+ "f5": {"51", "52"},
+ "f6": {"61", "62"},
+ "f7.f1": {"71", "72"},
+ "f8.f8.f7.f1": {"81", "82"},
+ "f9": {"9"},
+ "f10.0.f10.0.f6": {"101", "102"},
+ "f10.0.f10.1.f6": {"103", "104"},
+ "f11.0.f11.0.f6": {"111", "112"},
+ "f11.0.f11.1.f6": {"113", "114"},
+ "f12.0.f12.0.f6": {"121", "122"},
+ "f12.0.f12.1.f6": {"123", "124"},
+ "f13.0.f13.0.f6": {"131", "132"},
+ "f13.0.f13.1.f6": {"133", "134"},
+ "f14": {},
+ }
+ f2 := 2
+ f41, f42 := 41, 42
+ f61, f62 := 61, 62
+ f71, f72 := 71, 72
+ f81, f82 := 81, 82
+ f101, f102, f103, f104 := 101, 102, 103, 104
+ f111, f112, f113, f114 := 111, 112, 113, 114
+ f121, f122, f123, f124 := 121, 122, 123, 124
+ f131, f132, f133, f134 := 131, 132, 133, 134
+ e := S1{
+ F01: 1,
+ F02: &f2,
+ F03: []int{31, 32},
+ F04: []*int{&f41, &f42},
+ F05: &[]int{51, 52},
+ F06: &[]*int{&f61, &f62},
+ F07: S2{
+ F01: &[]*int{&f71, &f72},
+ },
+ F08: &S1{
+ F08: &S1{
+ F07: S2{
+ F01: &[]*int{&f81, &f82},
+ },
+ },
+ },
+ F09: 0,
+ F10: []S1{
+ S1{
+ F10: []S1{
+ S1{F06: &[]*int{&f101, &f102}},
+ S1{F06: &[]*int{&f103, &f104}},
+ },
+ },
+ },
+ F11: []*S1{
+ &S1{
+ F11: []*S1{
+ &S1{F06: &[]*int{&f111, &f112}},
+ &S1{F06: &[]*int{&f113, &f114}},
+ },
+ },
+ },
+ F12: &[]S1{
+ S1{
+ F12: &[]S1{
+ S1{F06: &[]*int{&f121, &f122}},
+ S1{F06: &[]*int{&f123, &f124}},
+ },
+ },
+ },
+ F13: &[]*S1{
+ &S1{
+ F13: &[]*S1{
+ &S1{F06: &[]*int{&f131, &f132}},
+ &S1{F06: &[]*int{&f133, &f134}},
+ },
+ },
+ },
+ F14: 0,
+ }
+
+ s := &S1{}
+ _ = NewDecoder().Decode(s, v)
+
+ vals := func(values []*int) []int {
+ r := make([]int, len(values))
+ for k, v := range values {
+ r[k] = *v
+ }
+ return r
+ }
+
+ if s.F01 != e.F01 {
+ t.Errorf("f1: expected %v, got %v", e.F01, s.F01)
+ }
+ if s.F02 == nil {
+ t.Errorf("f2: expected %v, got nil", *e.F02)
+ } else if *s.F02 != *e.F02 {
+ t.Errorf("f2: expected %v, got %v", *e.F02, *s.F02)
+ }
+ if s.F03 == nil {
+ t.Errorf("f3: expected %v, got nil", e.F03)
+ } else if len(s.F03) != 2 || s.F03[0] != e.F03[0] || s.F03[1] != e.F03[1] {
+ t.Errorf("f3: expected %v, got %v", e.F03, s.F03)
+ }
+ if s.F04 == nil {
+ t.Errorf("f4: expected %v, got nil", e.F04)
+ } else {
+ if len(s.F04) != 2 || *(s.F04)[0] != *(e.F04)[0] || *(s.F04)[1] != *(e.F04)[1] {
+ t.Errorf("f4: expected %v, got %v", vals(e.F04), vals(s.F04))
+ }
+ }
+ if s.F05 == nil {
+ t.Errorf("f5: expected %v, got nil", e.F05)
+ } else {
+ sF05, eF05 := *s.F05, *e.F05
+ if len(sF05) != 2 || sF05[0] != eF05[0] || sF05[1] != eF05[1] {
+ t.Errorf("f5: expected %v, got %v", eF05, sF05)
+ }
+ }
+ if s.F06 == nil {
+ t.Errorf("f6: expected %v, got nil", vals(*e.F06))
+ } else {
+ sF06, eF06 := *s.F06, *e.F06
+ if len(sF06) != 2 || *(sF06)[0] != *(eF06)[0] || *(sF06)[1] != *(eF06)[1] {
+ t.Errorf("f6: expected %v, got %v", vals(eF06), vals(sF06))
+ }
+ }
+ if s.F07.F01 == nil {
+ t.Errorf("f7.f1: expected %v, got nil", vals(*e.F07.F01))
+ } else {
+ sF07, eF07 := *s.F07.F01, *e.F07.F01
+ if len(sF07) != 2 || *(sF07)[0] != *(eF07)[0] || *(sF07)[1] != *(eF07)[1] {
+ t.Errorf("f7.f1: expected %v, got %v", vals(eF07), vals(sF07))
+ }
+ }
+ if s.F08 == nil {
+ t.Errorf("f8: got nil")
+ } else if s.F08.F08 == nil {
+ t.Errorf("f8.f8: got nil")
+ } else if s.F08.F08.F07.F01 == nil {
+ t.Errorf("f8.f8.f7.f1: expected %v, got nil", vals(*e.F08.F08.F07.F01))
+ } else {
+ sF08, eF08 := *s.F08.F08.F07.F01, *e.F08.F08.F07.F01
+ if len(sF08) != 2 || *(sF08)[0] != *(eF08)[0] || *(sF08)[1] != *(eF08)[1] {
+ t.Errorf("f8.f8.f7.f1: expected %v, got %v", vals(eF08), vals(sF08))
+ }
+ }
+ if s.F09 != e.F09 {
+ t.Errorf("f9: expected %v, got %v", e.F09, s.F09)
+ }
+ if s.F10 == nil {
+ t.Errorf("f10: got nil")
+ } else if len(s.F10) != 1 {
+ t.Errorf("f10: expected 1 element, got %v", s.F10)
+ } else {
+ if len(s.F10[0].F10) != 2 {
+ t.Errorf("f10.0.f10: expected 1 element, got %v", s.F10[0].F10)
+ } else {
+ sF10, eF10 := *s.F10[0].F10[0].F06, *e.F10[0].F10[0].F06
+ if sF10 == nil {
+ t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10))
+ } else {
+ if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] {
+ t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10))
+ }
+ }
+ sF10, eF10 = *s.F10[0].F10[1].F06, *e.F10[0].F10[1].F06
+ if sF10 == nil {
+ t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10))
+ } else {
+ if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] {
+ t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10))
+ }
+ }
+ }
+ }
+ if s.F11 == nil {
+ t.Errorf("f11: got nil")
+ } else if len(s.F11) != 1 {
+ t.Errorf("f11: expected 1 element, got %v", s.F11)
+ } else {
+ if len(s.F11[0].F11) != 2 {
+ t.Errorf("f11.0.f11: expected 1 element, got %v", s.F11[0].F11)
+ } else {
+ sF11, eF11 := *s.F11[0].F11[0].F06, *e.F11[0].F11[0].F06
+ if sF11 == nil {
+ t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11))
+ } else {
+ if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] {
+ t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11))
+ }
+ }
+ sF11, eF11 = *s.F11[0].F11[1].F06, *e.F11[0].F11[1].F06
+ if sF11 == nil {
+ t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11))
+ } else {
+ if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] {
+ t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11))
+ }
+ }
+ }
+ }
+ if s.F12 == nil {
+ t.Errorf("f12: got nil")
+ } else if len(*s.F12) != 1 {
+ t.Errorf("f12: expected 1 element, got %v", *s.F12)
+ } else {
+ sF12, eF12 := *(s.F12), *(e.F12)
+ if len(*sF12[0].F12) != 2 {
+ t.Errorf("f12.0.f12: expected 1 element, got %v", *sF12[0].F12)
+ } else {
+ sF122, eF122 := *(*sF12[0].F12)[0].F06, *(*eF12[0].F12)[0].F06
+ if sF122 == nil {
+ t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122))
+ } else {
+ if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] {
+ t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122))
+ }
+ }
+ sF122, eF122 = *(*sF12[0].F12)[1].F06, *(*eF12[0].F12)[1].F06
+ if sF122 == nil {
+ t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122))
+ } else {
+ if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] {
+ t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122))
+ }
+ }
+ }
+ }
+ if s.F13 == nil {
+ t.Errorf("f13: got nil")
+ } else if len(*s.F13) != 1 {
+ t.Errorf("f13: expected 1 element, got %v", *s.F13)
+ } else {
+ sF13, eF13 := *(s.F13), *(e.F13)
+ if len(*sF13[0].F13) != 2 {
+ t.Errorf("f13.0.f13: expected 1 element, got %v", *sF13[0].F13)
+ } else {
+ sF132, eF132 := *(*sF13[0].F13)[0].F06, *(*eF13[0].F13)[0].F06
+ if sF132 == nil {
+ t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132))
+ } else {
+ if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] {
+ t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132))
+ }
+ }
+ sF132, eF132 = *(*sF13[0].F13)[1].F06, *(*eF13[0].F13)[1].F06
+ if sF132 == nil {
+ t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132))
+ } else {
+ if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] {
+ t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132))
+ }
+ }
+ }
+ }
+ if s.F14 != e.F14 {
+ t.Errorf("f14: expected %v, got %v", e.F14, s.F14)
+ }
+}
+
+func BenchmarkAll(b *testing.B) {
+ v := map[string][]string{
+ "f1": {"1"},
+ "f2": {"2"},
+ "f3": {"31", "32"},
+ "f4": {"41", "42"},
+ "f5": {"51", "52"},
+ "f6": {"61", "62"},
+ "f7.f1": {"71", "72"},
+ "f8.f8.f7.f1": {"81", "82"},
+ "f9": {"9"},
+ "f10.0.f10.0.f6": {"101", "102"},
+ "f10.0.f10.1.f6": {"103", "104"},
+ "f11.0.f11.0.f6": {"111", "112"},
+ "f11.0.f11.1.f6": {"113", "114"},
+ "f12.0.f12.0.f6": {"121", "122"},
+ "f12.0.f12.1.f6": {"123", "124"},
+ "f13.0.f13.0.f6": {"131", "132"},
+ "f13.0.f13.1.f6": {"133", "134"},
+ }
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ s := &S1{}
+ _ = NewDecoder().Decode(s, v)
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+type S3 struct {
+ F01 bool
+ F02 float32
+ F03 float64
+ F04 int
+ F05 int8
+ F06 int16
+ F07 int32
+ F08 int64
+ F09 string
+ F10 uint
+ F11 uint8
+ F12 uint16
+ F13 uint32
+ F14 uint64
+}
+
+func TestDefaultConverters(t *testing.T) {
+ v := map[string][]string{
+ "F01": {"true"},
+ "F02": {"4.2"},
+ "F03": {"4.3"},
+ "F04": {"-42"},
+ "F05": {"-43"},
+ "F06": {"-44"},
+ "F07": {"-45"},
+ "F08": {"-46"},
+ "F09": {"foo"},
+ "F10": {"42"},
+ "F11": {"43"},
+ "F12": {"44"},
+ "F13": {"45"},
+ "F14": {"46"},
+ }
+ e := S3{
+ F01: true,
+ F02: 4.2,
+ F03: 4.3,
+ F04: -42,
+ F05: -43,
+ F06: -44,
+ F07: -45,
+ F08: -46,
+ F09: "foo",
+ F10: 42,
+ F11: 43,
+ F12: 44,
+ F13: 45,
+ F14: 46,
+ }
+ s := &S3{}
+ _ = NewDecoder().Decode(s, v)
+ if s.F01 != e.F01 {
+ t.Errorf("F01: expected %v, got %v", e.F01, s.F01)
+ }
+ if s.F02 != e.F02 {
+ t.Errorf("F02: expected %v, got %v", e.F02, s.F02)
+ }
+ if s.F03 != e.F03 {
+ t.Errorf("F03: expected %v, got %v", e.F03, s.F03)
+ }
+ if s.F04 != e.F04 {
+ t.Errorf("F04: expected %v, got %v", e.F04, s.F04)
+ }
+ if s.F05 != e.F05 {
+ t.Errorf("F05: expected %v, got %v", e.F05, s.F05)
+ }
+ if s.F06 != e.F06 {
+ t.Errorf("F06: expected %v, got %v", e.F06, s.F06)
+ }
+ if s.F07 != e.F07 {
+ t.Errorf("F07: expected %v, got %v", e.F07, s.F07)
+ }
+ if s.F08 != e.F08 {
+ t.Errorf("F08: expected %v, got %v", e.F08, s.F08)
+ }
+ if s.F09 != e.F09 {
+ t.Errorf("F09: expected %v, got %v", e.F09, s.F09)
+ }
+ if s.F10 != e.F10 {
+ t.Errorf("F10: expected %v, got %v", e.F10, s.F10)
+ }
+ if s.F11 != e.F11 {
+ t.Errorf("F11: expected %v, got %v", e.F11, s.F11)
+ }
+ if s.F12 != e.F12 {
+ t.Errorf("F12: expected %v, got %v", e.F12, s.F12)
+ }
+ if s.F13 != e.F13 {
+ t.Errorf("F13: expected %v, got %v", e.F13, s.F13)
+ }
+ if s.F14 != e.F14 {
+ t.Errorf("F14: expected %v, got %v", e.F14, s.F14)
+ }
+}
+
+func TestOn(t *testing.T) {
+ v := map[string][]string{
+ "F01": {"on"},
+ }
+ s := S3{}
+ err := NewDecoder().Decode(&s, v)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !s.F01 {
+ t.Fatal("Value was not set to true")
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+func TestInlineStruct(t *testing.T) {
+ s1 := &struct {
+ F01 bool
+ }{}
+ s2 := &struct {
+ F01 int
+ }{}
+ v1 := map[string][]string{
+ "F01": {"true"},
+ }
+ v2 := map[string][]string{
+ "F01": {"42"},
+ }
+ decoder := NewDecoder()
+ _ = decoder.Decode(s1, v1)
+ if s1.F01 != true {
+ t.Errorf("s1: expected %v, got %v", true, s1.F01)
+ }
+ _ = decoder.Decode(s2, v2)
+ if s2.F01 != 42 {
+ t.Errorf("s2: expected %v, got %v", 42, s2.F01)
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+type Foo struct {
+ F01 int
+ F02 Bar
+ Bif []Baz
+}
+
+type Bar struct {
+ F01 string
+ F02 string
+ F03 string
+ F14 string
+ S05 string
+ Str string
+}
+
+type Baz struct {
+ F99 []string
+}
+
+func TestSimpleExample(t *testing.T) {
+ data := map[string][]string{
+ "F01": {"1"},
+ "F02.F01": {"S1"},
+ "F02.F02": {"S2"},
+ "F02.F03": {"S3"},
+ "F02.F14": {"S4"},
+ "F02.S05": {"S5"},
+ "F02.Str": {"Str"},
+ "Bif.0.F99": {"A", "B", "C"},
+ }
+
+ e := &Foo{
+ F01: 1,
+ F02: Bar{
+ F01: "S1",
+ F02: "S2",
+ F03: "S3",
+ F14: "S4",
+ S05: "S5",
+ Str: "Str",
+ },
+ Bif: []Baz{{
+ F99: []string{"A", "B", "C"}},
+ },
+ }
+
+ s := &Foo{}
+ _ = NewDecoder().Decode(s, data)
+
+ if s.F01 != e.F01 {
+ t.Errorf("F01: expected %v, got %v", e.F01, s.F01)
+ }
+ if s.F02.F01 != e.F02.F01 {
+ t.Errorf("F02.F01: expected %v, got %v", e.F02.F01, s.F02.F01)
+ }
+ if s.F02.F02 != e.F02.F02 {
+ t.Errorf("F02.F02: expected %v, got %v", e.F02.F02, s.F02.F02)
+ }
+ if s.F02.F03 != e.F02.F03 {
+ t.Errorf("F02.F03: expected %v, got %v", e.F02.F03, s.F02.F03)
+ }
+ if s.F02.F14 != e.F02.F14 {
+ t.Errorf("F02.F14: expected %v, got %v", e.F02.F14, s.F02.F14)
+ }
+ if s.F02.S05 != e.F02.S05 {
+ t.Errorf("F02.S05: expected %v, got %v", e.F02.S05, s.F02.S05)
+ }
+ if s.F02.Str != e.F02.Str {
+ t.Errorf("F02.Str: expected %v, got %v", e.F02.Str, s.F02.Str)
+ }
+ if len(s.Bif) != len(e.Bif) {
+ t.Errorf("Bif len: expected %d, got %d", len(e.Bif), len(s.Bif))
+ } else {
+ if len(s.Bif[0].F99) != len(e.Bif[0].F99) {
+ t.Errorf("Bif[0].F99 len: expected %d, got %d", len(e.Bif[0].F99), len(s.Bif[0].F99))
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+type S4 struct {
+ F01 int64
+ F02 float64
+ F03 bool
+}
+
+func TestConversionError(t *testing.T) {
+ data := map[string][]string{
+ "F01": {"foo"},
+ "F02": {"bar"},
+ "F03": {"baz"},
+ }
+ s := &S4{}
+ e := NewDecoder().Decode(s, data)
+
+ m := e.(MultiError)
+ if len(m) != 3 {
+ t.Errorf("Expected 3 errors, got %v", m)
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+type S5 struct {
+ F01 []string
+}
+
+func TestEmptyValue(t *testing.T) {
+ data := map[string][]string{
+ "F01": {"", "foo"},
+ }
+ s := &S5{}
+ NewDecoder().Decode(s, data)
+ if len(s.F01) != 1 {
+ t.Errorf("Expected 1 values in F01")
+ }
+}
+
+func TestEmptyValueZeroEmpty(t *testing.T) {
+ data := map[string][]string{
+ "F01": {"", "foo"},
+ }
+ s := S5{}
+ d := NewDecoder()
+ d.ZeroEmpty(true)
+ err := d.Decode(&s, data)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(s.F01) != 2 {
+ t.Errorf("Expected 1 values in F01")
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+type S6 struct {
+ id string
+}
+
+func TestUnexportedField(t *testing.T) {
+ data := map[string][]string{
+ "id": {"identifier"},
+ }
+ s := &S6{}
+ NewDecoder().Decode(s, data)
+ if s.id != "" {
+ t.Errorf("Unexported field expected to be ignored")
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+type S7 struct {
+ ID string
+}
+
+func TestMultipleValues(t *testing.T) {
+ data := map[string][]string{
+ "ID": {"0", "1"},
+ }
+
+ s := S7{}
+ NewDecoder().Decode(&s, data)
+ if s.ID != "1" {
+ t.Errorf("Last defined value must be used when multiple values for same field are provided")
+ }
+}
+
+type S8 struct {
+ ID string `json:"id"`
+}
+
+func TestSetAliasTag(t *testing.T) {
+ data := map[string][]string{
+ "id": {"foo"},
+ }
+
+ s := S8{}
+ dec := NewDecoder()
+ dec.SetAliasTag("json")
+ dec.Decode(&s, data)
+ if s.ID != "foo" {
+ t.Fatalf("Bad value: got %q, want %q", s.ID, "foo")
+ }
+}
+
+func TestZeroEmpty(t *testing.T) {
+ data := map[string][]string{
+ "F01": {""},
+ "F03": {"true"},
+ }
+ s := S4{1, 1, false}
+ d := NewDecoder()
+ d.ZeroEmpty(true)
+
+ err := d.Decode(&s, data)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if s.F01 != 0 {
+ t.Errorf("F01: got %v, want %v", s.F01, 0)
+ }
+ if s.F02 != 1 {
+ t.Errorf("F02: got %v, want %v", s.F02, 1)
+ }
+ if s.F03 != true {
+ t.Errorf("F03: got %v, want %v", s.F03, true)
+ }
+}
+
+func TestNoZeroEmpty(t *testing.T) {
+ data := map[string][]string{
+ "F01": {""},
+ "F03": {"true"},
+ }
+ s := S4{1, 1, false}
+ d := NewDecoder()
+ d.ZeroEmpty(false)
+ err := d.Decode(&s, data)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if s.F01 != 1 {
+ t.Errorf("F01: got %v, want %v", s.F01, 1)
+ }
+ if s.F02 != 1 {
+ t.Errorf("F02: got %v, want %v", s.F02, 1)
+ }
+ if s.F03 != true {
+ t.Errorf("F03: got %v, want %v", s.F03, true)
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+type S9 struct {
+ Id string
+}
+
+type S10 struct {
+ S9
+}
+
+func TestEmbeddedField(t *testing.T) {
+ data := map[string][]string{
+ "Id": {"identifier"},
+ }
+ s := &S10{}
+ NewDecoder().Decode(s, data)
+ if s.Id != "identifier" {
+ t.Errorf("Missing support for embedded fields")
+ }
+}
+
+type S11 struct {
+ S10
+}
+
+func TestMultipleLevelEmbeddedField(t *testing.T) {
+ data := map[string][]string{
+ "Id": {"identifier"},
+ }
+ s := &S11{}
+ err := NewDecoder().Decode(s, data)
+ if s.Id != "identifier" {
+ t.Errorf("Missing support for multiple-level embedded fields (%v)", err)
+ }
+}
+
+func TestInvalidPath(t *testing.T) {
+ data := map[string][]string{
+ "Foo.Bar": {"baz"},
+ }
+ s := S9{}
+ err := NewDecoder().Decode(&s, data)
+ expectedErr := `schema: invalid path "Foo.Bar"`
+ if err.Error() != expectedErr {
+ t.Fatalf("got %q, want %q", err, expectedErr)
+ }
+}
+
+func TestInvalidPathIgnoreUnknownKeys(t *testing.T) {
+ data := map[string][]string{
+ "Foo.Bar": {"baz"},
+ }
+ s := S9{}
+ dec := NewDecoder()
+ dec.IgnoreUnknownKeys(true)
+ err := dec.Decode(&s, data)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+type S1NT struct {
+ F1 int
+ F2 *int
+ F3 []int
+ F4 []*int
+ F5 *[]int
+ F6 *[]*int
+ F7 S2
+ F8 *S1
+ F9 int `schema:"-"`
+ F10 []S1
+ F11 []*S1
+ F12 *[]S1
+ F13 *[]*S1
+}
+
+func TestAllNT(t *testing.T) {
+ v := map[string][]string{
+ "f1": {"1"},
+ "f2": {"2"},
+ "f3": {"31", "32"},
+ "f4": {"41", "42"},
+ "f5": {"51", "52"},
+ "f6": {"61", "62"},
+ "f7.f1": {"71", "72"},
+ "f8.f8.f7.f1": {"81", "82"},
+ "f9": {"9"},
+ "f10.0.f10.0.f6": {"101", "102"},
+ "f10.0.f10.1.f6": {"103", "104"},
+ "f11.0.f11.0.f6": {"111", "112"},
+ "f11.0.f11.1.f6": {"113", "114"},
+ "f12.0.f12.0.f6": {"121", "122"},
+ "f12.0.f12.1.f6": {"123", "124"},
+ "f13.0.f13.0.f6": {"131", "132"},
+ "f13.0.f13.1.f6": {"133", "134"},
+ }
+ f2 := 2
+ f41, f42 := 41, 42
+ f61, f62 := 61, 62
+ f71, f72 := 71, 72
+ f81, f82 := 81, 82
+ f101, f102, f103, f104 := 101, 102, 103, 104
+ f111, f112, f113, f114 := 111, 112, 113, 114
+ f121, f122, f123, f124 := 121, 122, 123, 124
+ f131, f132, f133, f134 := 131, 132, 133, 134
+ e := S1NT{
+ F1: 1,
+ F2: &f2,
+ F3: []int{31, 32},
+ F4: []*int{&f41, &f42},
+ F5: &[]int{51, 52},
+ F6: &[]*int{&f61, &f62},
+ F7: S2{
+ F01: &[]*int{&f71, &f72},
+ },
+ F8: &S1{
+ F08: &S1{
+ F07: S2{
+ F01: &[]*int{&f81, &f82},
+ },
+ },
+ },
+ F9: 0,
+ F10: []S1{
+ S1{
+ F10: []S1{
+ S1{F06: &[]*int{&f101, &f102}},
+ S1{F06: &[]*int{&f103, &f104}},
+ },
+ },
+ },
+ F11: []*S1{
+ &S1{
+ F11: []*S1{
+ &S1{F06: &[]*int{&f111, &f112}},
+ &S1{F06: &[]*int{&f113, &f114}},
+ },
+ },
+ },
+ F12: &[]S1{
+ S1{
+ F12: &[]S1{
+ S1{F06: &[]*int{&f121, &f122}},
+ S1{F06: &[]*int{&f123, &f124}},
+ },
+ },
+ },
+ F13: &[]*S1{
+ &S1{
+ F13: &[]*S1{
+ &S1{F06: &[]*int{&f131, &f132}},
+ &S1{F06: &[]*int{&f133, &f134}},
+ },
+ },
+ },
+ }
+
+ s := &S1NT{}
+ _ = NewDecoder().Decode(s, v)
+
+ vals := func(values []*int) []int {
+ r := make([]int, len(values))
+ for k, v := range values {
+ r[k] = *v
+ }
+ return r
+ }
+
+ if s.F1 != e.F1 {
+ t.Errorf("f1: expected %v, got %v", e.F1, s.F1)
+ }
+ if s.F2 == nil {
+ t.Errorf("f2: expected %v, got nil", *e.F2)
+ } else if *s.F2 != *e.F2 {
+ t.Errorf("f2: expected %v, got %v", *e.F2, *s.F2)
+ }
+ if s.F3 == nil {
+ t.Errorf("f3: expected %v, got nil", e.F3)
+ } else if len(s.F3) != 2 || s.F3[0] != e.F3[0] || s.F3[1] != e.F3[1] {
+ t.Errorf("f3: expected %v, got %v", e.F3, s.F3)
+ }
+ if s.F4 == nil {
+ t.Errorf("f4: expected %v, got nil", e.F4)
+ } else {
+ if len(s.F4) != 2 || *(s.F4)[0] != *(e.F4)[0] || *(s.F4)[1] != *(e.F4)[1] {
+ t.Errorf("f4: expected %v, got %v", vals(e.F4), vals(s.F4))
+ }
+ }
+ if s.F5 == nil {
+ t.Errorf("f5: expected %v, got nil", e.F5)
+ } else {
+ sF5, eF5 := *s.F5, *e.F5
+ if len(sF5) != 2 || sF5[0] != eF5[0] || sF5[1] != eF5[1] {
+ t.Errorf("f5: expected %v, got %v", eF5, sF5)
+ }
+ }
+ if s.F6 == nil {
+ t.Errorf("f6: expected %v, got nil", vals(*e.F6))
+ } else {
+ sF6, eF6 := *s.F6, *e.F6
+ if len(sF6) != 2 || *(sF6)[0] != *(eF6)[0] || *(sF6)[1] != *(eF6)[1] {
+ t.Errorf("f6: expected %v, got %v", vals(eF6), vals(sF6))
+ }
+ }
+ if s.F7.F01 == nil {
+ t.Errorf("f7.f1: expected %v, got nil", vals(*e.F7.F01))
+ } else {
+ sF7, eF7 := *s.F7.F01, *e.F7.F01
+ if len(sF7) != 2 || *(sF7)[0] != *(eF7)[0] || *(sF7)[1] != *(eF7)[1] {
+ t.Errorf("f7.f1: expected %v, got %v", vals(eF7), vals(sF7))
+ }
+ }
+ if s.F8 == nil {
+ t.Errorf("f8: got nil")
+ } else if s.F8.F08 == nil {
+ t.Errorf("f8.f8: got nil")
+ } else if s.F8.F08.F07.F01 == nil {
+ t.Errorf("f8.f8.f7.f1: expected %v, got nil", vals(*e.F8.F08.F07.F01))
+ } else {
+ sF8, eF8 := *s.F8.F08.F07.F01, *e.F8.F08.F07.F01
+ if len(sF8) != 2 || *(sF8)[0] != *(eF8)[0] || *(sF8)[1] != *(eF8)[1] {
+ t.Errorf("f8.f8.f7.f1: expected %v, got %v", vals(eF8), vals(sF8))
+ }
+ }
+ if s.F9 != e.F9 {
+ t.Errorf("f9: expected %v, got %v", e.F9, s.F9)
+ }
+ if s.F10 == nil {
+ t.Errorf("f10: got nil")
+ } else if len(s.F10) != 1 {
+ t.Errorf("f10: expected 1 element, got %v", s.F10)
+ } else {
+ if len(s.F10[0].F10) != 2 {
+ t.Errorf("f10.0.f10: expected 1 element, got %v", s.F10[0].F10)
+ } else {
+ sF10, eF10 := *s.F10[0].F10[0].F06, *e.F10[0].F10[0].F06
+ if sF10 == nil {
+ t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10))
+ } else {
+ if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] {
+ t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10))
+ }
+ }
+ sF10, eF10 = *s.F10[0].F10[1].F06, *e.F10[0].F10[1].F06
+ if sF10 == nil {
+ t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10))
+ } else {
+ if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] {
+ t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10))
+ }
+ }
+ }
+ }
+ if s.F11 == nil {
+ t.Errorf("f11: got nil")
+ } else if len(s.F11) != 1 {
+ t.Errorf("f11: expected 1 element, got %v", s.F11)
+ } else {
+ if len(s.F11[0].F11) != 2 {
+ t.Errorf("f11.0.f11: expected 1 element, got %v", s.F11[0].F11)
+ } else {
+ sF11, eF11 := *s.F11[0].F11[0].F06, *e.F11[0].F11[0].F06
+ if sF11 == nil {
+ t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11))
+ } else {
+ if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] {
+ t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11))
+ }
+ }
+ sF11, eF11 = *s.F11[0].F11[1].F06, *e.F11[0].F11[1].F06
+ if sF11 == nil {
+ t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11))
+ } else {
+ if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] {
+ t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11))
+ }
+ }
+ }
+ }
+ if s.F12 == nil {
+ t.Errorf("f12: got nil")
+ } else if len(*s.F12) != 1 {
+ t.Errorf("f12: expected 1 element, got %v", *s.F12)
+ } else {
+ sF12, eF12 := *(s.F12), *(e.F12)
+ if len(*sF12[0].F12) != 2 {
+ t.Errorf("f12.0.f12: expected 1 element, got %v", *sF12[0].F12)
+ } else {
+ sF122, eF122 := *(*sF12[0].F12)[0].F06, *(*eF12[0].F12)[0].F06
+ if sF122 == nil {
+ t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122))
+ } else {
+ if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] {
+ t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122))
+ }
+ }
+ sF122, eF122 = *(*sF12[0].F12)[1].F06, *(*eF12[0].F12)[1].F06
+ if sF122 == nil {
+ t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122))
+ } else {
+ if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] {
+ t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122))
+ }
+ }
+ }
+ }
+ if s.F13 == nil {
+ t.Errorf("f13: got nil")
+ } else if len(*s.F13) != 1 {
+ t.Errorf("f13: expected 1 element, got %v", *s.F13)
+ } else {
+ sF13, eF13 := *(s.F13), *(e.F13)
+ if len(*sF13[0].F13) != 2 {
+ t.Errorf("f13.0.f13: expected 1 element, got %v", *sF13[0].F13)
+ } else {
+ sF132, eF132 := *(*sF13[0].F13)[0].F06, *(*eF13[0].F13)[0].F06
+ if sF132 == nil {
+ t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132))
+ } else {
+ if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] {
+ t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132))
+ }
+ }
+ sF132, eF132 = *(*sF13[0].F13)[1].F06, *(*eF13[0].F13)[1].F06
+ if sF132 == nil {
+ t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132))
+ } else {
+ if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] {
+ t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132))
+ }
+ }
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/doc.go b/Godeps/_workspace/src/github.com/gorilla/schema/doc.go
new file mode 100644
index 0000000..a8df5a1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/gorilla/schema/doc.go
@@ -0,0 +1,123 @@
+// Copyright 2012 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package gorilla/schema fills a struct with form values.
+
+The basic usage is really simple. Given this struct:
+
+ type Person struct {
+ Name string
+ Phone string
+ }
+
+...we can fill it passing a map to the Load() function:
+
+ values := map[string][]string{
+ "Name": {"John"},
+ "Phone": {"999-999-999"},
+ }
+ person := new(Person)
+ decoder := schema.NewDecoder()
+ decoder.Decode(person, values)
+
+This is just a simple example and it doesn't make a lot of sense to create
+the map manually. Typically it will come from a http.Request object and
+will be of type url.Values: http.Request.Form or http.Request.MultipartForm:
+
+ func MyHandler(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+
+ if err != nil {
+ // Handle error
+ }
+
+ decoder := schema.NewDecoder()
+ // r.PostForm is a map of our POST form values
+ err := decoder.Decode(person, r.PostForm)
+
+ if err != nil {
+ // Handle error
+ }
+
+ // Do something with person.Name or person.Phone
+ }
+
+Note: it is a good idea to set a Decoder instance as a package global,
+because it caches meta-data about structs, and a instance can be shared safely:
+
+ var decoder = schema.NewDecoder()
+
+To define custom names for fields, use a struct tag "schema". To not populate
+certain fields, use a dash for the name and it will be ignored:
+
+ type Person struct {
+ Name string `schema:"name"` // custom name
+ Phone string `schema:"phone"` // custom name
+ Admin bool `schema:"-"` // this field is never set
+ }
+
+The supported field types in the destination struct are:
+
+ * bool
+ * float variants (float32, float64)
+ * int variants (int, int8, int16, int32, int64)
+ * string
+ * uint variants (uint, uint8, uint16, uint32, uint64)
+ * struct
+ * a pointer to one of the above types
+ * a slice or a pointer to a slice of one of the above types
+
+Non-supported types are simply ignored, however custom types can be registered
+to be converted.
+
+To fill nested structs, keys must use a dotted notation as the "path" for the
+field. So for example, to fill the struct Person below:
+
+ type Phone struct {
+ Label string
+ Number string
+ }
+
+ type Person struct {
+ Name string
+ Phone Phone
+ }
+
+...the source map must have the keys "Name", "Phone.Label" and "Phone.Number".
+This means that an HTML form to fill a Person struct must look like this:
+
+
+
+Single values are filled using the first value for a key from the source map.
+Slices are filled using all values for a key from the source map. So to fill
+a Person with multiple Phone values, like:
+
+ type Person struct {
+ Name string
+ Phones []Phone
+ }
+
+...an HTML form that accepts three Phone values would look like this:
+
+
+
+Notice that only for slices of structs the slice index is required.
+This is needed for disambiguation: if the nested struct also had a slice
+field, we could not translate multiple values to it if we did not use an
+index for the parent struct.
+*/
+package schema
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/.gitignore b/Godeps/_workspace/src/github.com/jmoiron/modl/.gitignore
new file mode 100644
index 0000000..43337f2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/.gitignore
@@ -0,0 +1,7 @@
+_test
+_testmain.go
+_obj
+*~
+*.6
+6.out
+environ
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/LICENSE b/Godeps/_workspace/src/github.com/jmoiron/modl/LICENSE
new file mode 100644
index 0000000..b661111
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2012 James Cooper
+
+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/Godeps/_workspace/src/github.com/jmoiron/modl/README.md b/Godeps/_workspace/src/github.com/jmoiron/modl/README.md
new file mode 100644
index 0000000..6a4c931
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/README.md
@@ -0,0 +1,112 @@
+# Modl: Go database model & mapping
+
+[](https://drone.io/github.com/jmoiron/modl/latest)
+[](https://godoc.org/github.com/jmoiron/modl)
+
+Modl is a library which provides database modelling and mapping. It is a fork of
+James Cooper's wonderful [gorp](http://github.com/coopernurse/gorp).
+
+**Note**. Modl's public facing interface is considered unfinished and open to
+change. The current API will not be broken lightly, but additions are likely.
+As Gorp's behavior moves on, Modl may adopt some of it or may not.
+
+## Goals
+
+Modl's goal is to clean up bits of gorp's API, add some additional features
+like query building helpers and additional control over SQL generation, and
+to reuse lower level abstractions provided in [sqlx](http://github.com/jmoiron/sqlx).
+The driving philosophies behind modl are:
+
+* If something can be done in `database/sql`, do it that way
+* Go is not a good declarative language, do not abuse struct tags
+* Expose as much as possible to facilitate extension by other libraries
+* Avoid abstractions which provide initial convenience but must eventually be abandoned
+* Avoid reflect and cache its results where possible, use where necessary
+
+### Features
+
+* Bind struct models to tables
+* CRUD helpers for bound structs
+* Create schema from database model (great for testing)
+* Pre/post insert/update/delete hooks
+* Automatic binding of auto increment PKs after insert
+* Delete & Fetch by primary keys (w/ multi-key support)
+* Sql trace logging
+* Bind arbitrary SQL queries to a struct
+* Optional optimistic locking using a version column (for update/deletes)
+
+### Differences from Gorp
+
+Since modl is a gorp fork, here are some of its major behavioral differences:
+
+* Field names are mapped to all-lowercase sql equivalents. Capitalization is
+ an artifact of visibility, and this was required for compatibility with `sqlx`.
+* No more struct/slice returns, pass pointers to methods instead
+* Many panics in gorp are errors in modl
+* TypeConverters are removed in favor of implementing `sql.Scanner` & `driver.Valuer`
+ for custom types.
+
+## Testing
+
+To use the `test-all` script, set the following environment variables:
+
+```sh
+# mysql DSN, note parseTime arg for time scanning support:
+MODL_MYSQL_DSN="username:password@/dbname?parseTime=true"
+
+# postgres DSN, like:
+MODL_POSTGRES_DSN="user=username password=pw dbname=dbname sslmode=disable"
+
+# sqlite DSN, which is a path
+MODL_SQLITE_DSN="/dev/shm/modltest.db"
+
+# optional, will fail the test if any DBs are skipped (for CI, mostly)
+MODL_FAIL_ON_SKIP=true
+```
+
+In addition to this, you can create an `environ` file in this directory which
+will be sourced and ignored by git. You can continue to use the `MODL_TEST_DSN`
+and `MODL_TEST_DIALECT` variables if you want to manually run `go test` or if
+you want to run the benchmarks, as described below.
+
+The original README.md follows:
+
+## API Stability
+
+The API of Modl has been quite stable since its conception. Changes to the API
+are avoided as much as possible but there is currently no promise of forward or
+backward compatibility.
+
+## Supported Databases
+
+Modl relies heavily upon the `database/sql` package, and has a
+[Dialect](dialect.go) interface which can be used to smooth over
+differences between databases. There is a [list of sql drivers](http://code.google.com/p/go-wiki/wiki/SQLDrivers)
+on the Go wiki, most of which Modl should be compatible with. Dialects
+are provided for:
+
+* MySQL
+* PostgreSQL
+* sqlite3
+
+The test suite is continuously run against all of these databases.
+
+## Documentation
+
+API Documentation is available on [godoc](https://godoc.org/github.com/jmoiron/modl).
+
+## Performance ##
+
+Modl performs similar to Gorp. There are benchmarks in `modl_test.go` which
+will benchmark native querying w/ `database/sql` and manual Scanning with what
+modl does. Modl should perform between 2-3% slower than hand-done SQL.
+
+## Contributors
+
+The original contributors to gorp are:
+
+* [@coopernurse](http://github.com/coopernurse) - James Cooper
+* [@robfig](http://github.com/robfig) - Rob Figueiredo
+* [@sqs](http://github.com/sqs) - Quinn Slack
+* matthias-margush - column aliasing via tags
+
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/benchmarks.md b/Godeps/_workspace/src/github.com/jmoiron/modl/benchmarks.md
new file mode 100644
index 0000000..2a9db6a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/benchmarks.md
@@ -0,0 +1,17 @@
+Current benchmarks:
+
+ PASS
+ BenchmarkNativeCrud 100 22605756 ns/op 5402 B/op 107 allocs/op
+ BenchmarkModlCrud 100 21107020 ns/op 7329 B/op 153 allocs/op
+ ok _/mnt/omocha/jmoiron/dev/go/modl 5.975s
+ PASS
+ BenchmarkNativeCrud 100 13153419 ns/op 8822 B/op 334 allocs/op
+ BenchmarkModlCrud 100 11676837 ns/op 10577 B/op 381 allocs/op
+ ok _/mnt/omocha/jmoiron/dev/go/modl 3.813s
+ PASS
+ BenchmarkNativeCrud 5000 281356 ns/op 1455 B/op 51 allocs/op
+ BenchmarkModlCrud 5000 313549 ns/op 2713 B/op 90 allocs/op
+ ok _/mnt/omocha/jmoiron/dev/go/modl 3.136s
+
+(In order: MySQL, PostgreSQL, SQLite)
+
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/dbmap.go b/Godeps/_workspace/src/github.com/jmoiron/modl/dbmap.go
new file mode 100644
index 0000000..438a35d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/dbmap.go
@@ -0,0 +1,467 @@
+// Package modl provides a non-declarative database modelling layer to ease
+// the use of frequently repeated patterns in database-backed applications
+// and centralize database use to ease profiling and reporting.
+//
+// It is a fork of the wonderful github.com/coopernurse/gorp package, but is
+// rewritten to use github.com/jmoiron/sqlx as a base.
+//
+// Use of this source code is governed by a MIT-style license that can be
+// found in the LICENSE file.
+//
+package modl
+
+import (
+ "bytes"
+ "database/sql"
+ "fmt"
+ "log"
+ "reflect"
+ "strings"
+
+ "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx/reflectx"
+)
+
+// TableNameMapper is the function used by AddTable to map struct names to database table names, in analogy
+// to sqlx.NameMapper which does the same for struct field name to database column names.
+var TableNameMapper = strings.ToLower
+
+// DbMap is the root modl mapping object. Create one of these for each
+// database schema you wish to map. Each DbMap contains a list of
+// mapped tables.
+//
+// Example:
+//
+// dialect := modl.MySQLDialect{"InnoDB", "UTF8"}
+// dbmap := &modl.DbMap{Db: db, Dialect: dialect}
+//
+type DbMap struct {
+ // Db handle to use with this map
+ Db *sql.DB
+ Dbx *sqlx.DB
+
+ // Dialect implementation to use with this map
+ Dialect Dialect
+
+ tables []*TableMap
+ logger *log.Logger
+ logPrefix string
+ mapper *reflectx.Mapper
+}
+
+// NewDbMap returns a new DbMap using the db connection and dialect.
+func NewDbMap(db *sql.DB, dialect Dialect) *DbMap {
+ return &DbMap{
+ Db: db,
+ Dialect: dialect,
+ Dbx: sqlx.NewDb(db, dialect.DriverName()),
+ mapper: reflectx.NewMapperFunc("db", sqlx.NameMapper),
+ }
+}
+
+// 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 modl
+// generates.
+func (m *DbMap) TraceOn(prefix string, logger *log.Logger) {
+ m.logger = logger
+ if len(prefix) == 0 {
+ m.logPrefix = prefix
+ } else {
+ m.logPrefix = prefix + " "
+ }
+}
+
+// TraceOff turns off tracing. It is idempotent.
+func (m *DbMap) TraceOff() {
+ m.logger = nil
+ m.logPrefix = ""
+}
+
+// AddTable registers the given interface type with modl. The table name
+// will be given the name of the TypeOf(i), lowercased.
+//
+// This operation is idempotent. If i's type is already mapped, the
+// existing *TableMap is returned.
+func (m *DbMap) AddTable(i interface{}, name ...string) *TableMap {
+ Name := ""
+ if len(name) > 0 {
+ Name = name[0]
+ }
+
+ t := reflect.TypeOf(i)
+ // Use sqlx's NameMapper function if no name is supplied
+ if len(Name) == 0 {
+ Name = TableNameMapper(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, dbmap: m, mapper: m.mapper}
+ tmap.setupHooks(i)
+
+ n := t.NumField()
+ tmap.Columns = make([]*ColumnMap, 0, n)
+ for i := 0; i < n; i++ {
+ f := t.Field(i)
+ columnName := f.Tag.Get("db")
+ if columnName == "" {
+ columnName = sqlx.NameMapper(f.Name)
+ }
+
+ cm := &ColumnMap{
+ ColumnName: columnName,
+ Transient: columnName == "-",
+ fieldName: f.Name,
+ gotype: f.Type,
+ table: tmap,
+ }
+ tmap.Columns = append(tmap.Columns, cm)
+ if cm.fieldName == "Version" {
+ tmap.version = tmap.Columns[len(tmap.Columns)-1]
+ }
+ }
+ m.tables = append(m.tables, tmap)
+
+ return tmap
+
+}
+
+// AddTableWithName adds a new mapping of the interface to a table name.
+func (m *DbMap) AddTableWithName(i interface{}, name string) *TableMap {
+ return m.AddTable(i, name)
+}
+
+// CreateTablesSql returns create table SQL as a map of table names to
+// their associated CREATE TABLE statements.
+func (m *DbMap) CreateTablesSql() (map[string]string, error) {
+ return m.createTables(false, false)
+}
+
+// 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 {
+ _, err := m.createTables(false, true)
+ return err
+}
+
+// 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 {
+ _, err := m.createTables(true, true)
+ return err
+}
+
+func writeColumnSql(sql *bytes.Buffer, col *ColumnMap) {
+ if len(col.createSql) > 0 {
+ sql.WriteString(col.createSql)
+ return
+ }
+ sqltype := col.sqltype
+ if len(sqltype) == 0 {
+ sqltype = col.table.dbmap.Dialect.ToSqlType(col)
+ }
+ sql.WriteString(fmt.Sprintf("%s %s", col.table.dbmap.Dialect.QuoteField(col.ColumnName), sqltype))
+ if col.isPK {
+ sql.WriteString(" not null")
+ if len(col.table.Keys) == 1 {
+ sql.WriteString(" primary key")
+ }
+ }
+ if col.Unique {
+ sql.WriteString(" unique")
+ }
+ if col.isAutoIncr {
+ sql.WriteString(" " + col.table.dbmap.Dialect.AutoIncrStr())
+ }
+}
+
+func (m *DbMap) createTables(ifNotExists, exec bool) (map[string]string, error) {
+ var err error
+ ret := map[string]string{}
+
+ sep := ", "
+ prefix := ""
+ if !exec {
+ sep = ",\n"
+ prefix = " "
+ }
+
+ for i := range m.tables {
+ table := m.tables[i]
+
+ s := bytes.Buffer{}
+ s.WriteString("create table ")
+ if ifNotExists {
+ s.WriteString("if not exists ")
+ }
+ s.WriteString(m.Dialect.QuoteField(table.TableName))
+ s.WriteString(" (")
+ if !exec {
+ s.WriteString("\n")
+ }
+ x := 0
+ for _, col := range table.Columns {
+ if !col.Transient {
+ if x > 0 {
+ s.WriteString(sep)
+ }
+ s.WriteString(prefix)
+ writeColumnSql(&s, col)
+ 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(")")
+ }
+ s.WriteString(fmt.Sprintf(")%s;", m.Dialect.CreateTableSuffix()))
+ if exec {
+ _, err = m.Exec(s.String())
+ if err != nil {
+ break
+ }
+ } else {
+ ret[table.TableName] = s.String()
+ }
+ }
+ return ret, err
+}
+
+// DropTables iterates through TableMaps registered to this DbMap and
+// executes "drop table" statements against the database for each.
+func (m *DbMap) DropTables() error {
+ var err error
+ for i := range m.tables {
+ table := m.tables[i]
+ _, e := m.Exec(fmt.Sprintf("drop table %s;", m.Dialect.QuoteField(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, because any interface whose TableMap has an
+// auto-increment PK will have its insert Id bound to the PK struct field.
+//
+// Hook functions PreInsert() and/or PostInsert() will be executed
+// before/after the INSERT statement if the interface defines them.
+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.
+//
+// Hook functions PreUpdate() and/or PostUpdate() will be executed
+// before/after the UPDATE statement if the interface defines them.
+//
+// Returns number of rows updated.
+//
+// Returns an error if SetKeys has not been called on the TableMap or 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.
+//
+// Hook functions PreDelete() and/or PostDelete() will be executed
+// before/after the DELETE statement if the interface defines them.
+//
+// Returns number of rows deleted.
+//
+// Returns an error if SetKeys has not been called on the TableMap or if
+// any interface in the list has not been registered with AddTable.
+func (m *DbMap) Delete(list ...interface{}) (int64, error) {
+ return deletes(m, m, list...)
+}
+
+// Get runs a SQL SELECT to fetch a single row from the table based on the
+// primary key(s)
+//
+// dest 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.
+//
+// Hook function PostGet() will be executed
+// after the SELECT statement if the interface defines it.
+//
+// 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 or
+// if any interface in the list has not been registered with AddTable.
+func (m *DbMap) Get(dest interface{}, keys ...interface{}) error {
+ return get(m, m, dest, keys...)
+}
+
+// Select runs an arbitrary SQL query, binding the columns in the result
+// to fields on the struct specified by dest. 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 dest. 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.
+//
+// Hook function PostGet() will be executed
+// after the SELECT statement if the interface defines it.
+//
+// Values are returned in one of two ways:
+//
+// 1. If dest is a struct or a pointer to a struct, returns a slice of pointers to
+// matching rows of type dest.
+//
+// 2. If dest is a pointer to a slice, the results will be appended to that slice
+// and nil returned.
+//
+// dest does NOT need to be registered with AddTable().
+func (m *DbMap) Select(dest interface{}, query string, args ...interface{}) error {
+ return hookedselect(m, m, dest, query, args...)
+}
+
+// SelectOne runs an arbitrary SQL Query, binding the columns in the result to
+// fields on the struct specified by dest.
+func (m *DbMap) SelectOne(dest interface{}, query string, args ...interface{}) error {
+ return hookedget(m, m, dest, 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)
+ //stmt, err := m.Db.Prepare(query)
+ //if err != nil {
+ // return nil, err
+ //}
+ //fmt.Println("Exec", query, args)
+ return m.Db.Exec(query, args...)
+}
+
+// Begin starts a modl Transaction.
+func (m *DbMap) Begin() (*Transaction, error) {
+ m.trace("begin;")
+ tx, err := m.Dbx.Beginx()
+ if err != nil {
+ return nil, err
+ }
+ return &Transaction{m, tx}, nil
+}
+
+// FIXME: This is a poor interface. Checking for nils is un-go-like, and this
+// function should be TableFor(i interface{}) (*TableMap, error)
+// FIXME: rewrite this in terms of sqlx's reflect helpers
+
+// TableFor returns any matching tables for the interface i or nil if not found.
+// If i is a slice, then the table is given for the base slice type.
+func (m *DbMap) TableFor(i interface{}) *TableMap {
+ var t reflect.Type
+ v := reflect.ValueOf(i)
+start:
+ switch v.Kind() {
+ case reflect.Ptr:
+ // dereference pointer and try again; we never want to store pointer
+ // types anywhere, that way we always know how to do lookups
+ v = v.Elem()
+ goto start
+ case reflect.Slice:
+ // if this is a slice of X's, we're interested in the type of X
+ t = v.Type().Elem()
+ default:
+ t = v.Type()
+ }
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ return m.TableForType(t)
+}
+
+// FIXME: returning a nil pointer is not go-like; return (*TableMap, err) instead.
+
+// TableForType returns any matching tables for the type t or nil if not found.
+func (m *DbMap) TableForType(t reflect.Type) *TableMap {
+ for _, table := range m.tables {
+ if table.gotype == t {
+ return table
+ }
+ }
+ return nil
+}
+
+// TruncateTables truncates all tables in the DbMap.
+func (m *DbMap) TruncateTables() error {
+ return m.truncateTables(false)
+}
+
+// TruncateTablesIdentityRestart truncates all tables in the DbMap and
+// resets the identity counter.
+func (m *DbMap) TruncateTablesIdentityRestart() error {
+ return m.truncateTables(true)
+}
+
+func (m *DbMap) truncateTables(restartIdentity bool) error {
+ var err error
+ var restartClause string
+ for i := range m.tables {
+ table := m.tables[i]
+ if restartIdentity {
+ restartClause = m.Dialect.RestartIdentityClause(table.TableName)
+ }
+
+ // if the restart clause exists and starts with ';', then assume it's an
+ // additional query to run after we truncate. This is true with MySQL and
+ // SQLite, which do not have extra clauses for this during table truncation.
+ if len(restartClause) > 0 && restartClause[0] == ';' {
+ _, err = m.Exec(fmt.Sprintf("%s %s;", m.Dialect.TruncateClause(),
+ m.Dialect.QuoteField(table.TableName)))
+ if err != nil {
+ return err
+ }
+ _, err = m.Exec(restartClause[1:])
+ if err != nil {
+ return err
+ }
+ } else {
+ _, err := m.Exec(fmt.Sprintf("%s %s %s;", m.Dialect.TruncateClause(), m.Dialect.QuoteField(table.TableName), restartClause))
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (m *DbMap) handle() handle {
+ return &tracingHandle{h: m.Dbx, d: m}
+}
+
+func (m *DbMap) trace(query string, args ...interface{}) {
+ if m.logger != nil {
+ m.logger.Printf("%s%s %v", m.logPrefix, query, args)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/dialect.go b/Godeps/_workspace/src/github.com/jmoiron/modl/dialect.go
new file mode 100644
index 0000000..579ae9f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/dialect.go
@@ -0,0 +1,420 @@
+package modl
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/jmoiron/sqlx"
+)
+
+// Dialect is an interface that encapsulates behaviors that differ across
+// SQL databases.
+type Dialect interface {
+
+ // 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(col *ColumnMap) string
+
+ // AutoIncrStr returns a string to append to primary key column definitions
+ AutoIncrStr() string
+
+ //AutoIncrBindValue returns the default value for an auto increment row in an insert.
+ AutoIncrBindValue() string
+
+ // AutoIncrInsertSuffix returns a string to append to an insert statement which
+ // will make it return the auto-increment key on insert.
+ AutoIncrInsertSuffix(col *ColumnMap) string
+
+ // CreateTableSuffix returns a string to append to "create table" statement for
+ // vendor specific table attributes, eg. MySQL engine
+ CreateTableSuffix() string
+
+ // InsertAutoIncr
+ InsertAutoIncr(e SqlExecutor, insertSql string, params ...interface{}) (int64, error)
+ // InsertAutIncrAny takes a destination for non-integer auto-incr, like
+ // uuids which scan to strings, hashes, etc.
+ InsertAutoIncrAny(e SqlExecutor, insertSql string, dest interface{}, params ...interface{}) error
+
+ // BindVar returns the variable string to use when forming SQL statements
+ // in many dbs it is "?", but Postgres requires '$#'
+ //
+ // The supplied int is a zero based index of the bindvar in the statement
+ BindVar(i int) string
+
+ // QuoteField returns a quoted version of the field name.
+ QuoteField(field string) string
+
+ // TruncateClause is a string used to truncate tables.
+ TruncateClause() string
+
+ // RestartIdentityClause returns a string used to reset the identity counter
+ // when truncating tables. If the string starts with a ';', it is assumed to
+ // be a separate query and is executed separately.
+ RestartIdentityClause(table string) string
+
+ // DriverName returns the driver name for a dialect.
+ DriverName() string
+}
+
+func standardInsertAutoIncr(e SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ res, err := e.handle().Exec(insertSql, params...)
+ if err != nil {
+ return 0, err
+ }
+ return res.LastInsertId()
+}
+
+func standardAutoIncrAny(e SqlExecutor, insertSql string, dest interface{}, params ...interface{}) error {
+ rows, err := e.handle().Queryx(insertSql, params...)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+ if rows.Next() {
+ return rows.Scan(dest)
+ }
+ return fmt.Errorf("No auto-incr value returned for insert: `%s` error: %s", insertSql, rows.Err())
+}
+
+// -- sqlite3
+
+// SqliteDialect implements the Dialect interface for Sqlite3.
+type SqliteDialect struct {
+ suffix string
+}
+
+// DriverName returns the driverName for sqlite.
+func (d SqliteDialect) DriverName() string {
+ return "sqlite"
+}
+
+// ToSqlType maps go types to sqlite types.
+func (d SqliteDialect) ToSqlType(col *ColumnMap) string {
+ switch col.gotype.Kind() {
+ case reflect.Bool:
+ return "integer"
+ case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return "integer"
+ case reflect.Float64, reflect.Float32:
+ return "real"
+ case reflect.Slice:
+ if col.gotype.Elem().Kind() == reflect.Uint8 {
+ return "blob"
+ }
+ }
+
+ switch col.gotype.Name() {
+ case "NullableInt64":
+ return "integer"
+ case "NullableFloat64":
+ return "real"
+ case "NullableBool":
+ return "integer"
+ case "NullableBytes":
+ return "blob"
+ case "Time", "NullTime":
+ return "datetime"
+ }
+
+ // sqlite ignores maxsize, so we will do that here too
+ return fmt.Sprintf("text")
+}
+
+// AutoIncrStr returns autoincrement clause for sqlite.
+func (d SqliteDialect) AutoIncrStr() string {
+ return "autoincrement"
+}
+
+// AutoIncrBindValue returns the bind value for auto incr fields in sqlite.
+func (d SqliteDialect) AutoIncrBindValue() string {
+ return "null"
+}
+
+// AutoIncrInsertSuffix returns the auto incr insert suffix for sqlite.
+func (d SqliteDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
+ return ""
+}
+
+// CreateTableSuffix returns d.suffix
+func (d SqliteDialect) CreateTableSuffix() string {
+ return d.suffix
+}
+
+// BindVar returns "?", the simpler of the sqlite bindvars.
+func (d SqliteDialect) BindVar(i int) string {
+ return "?"
+}
+
+// InsertAutoIncr runs the standard
+func (d SqliteDialect) InsertAutoIncr(e SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ return standardInsertAutoIncr(e, insertSql, params...)
+}
+
+func (d SqliteDialect) InsertAutoIncrAny(e SqlExecutor, insertSql string, dest interface{}, params ...interface{}) error {
+ return standardAutoIncrAny(e, insertSql, dest, params...)
+}
+
+// QuoteField quotes f with "" for sqlite
+func (d SqliteDialect) QuoteField(f string) string {
+ return `"` + f + `"`
+}
+
+// TruncateClause returns the truncate clause for sqlite. There is no TRUNCATE
+// statement in sqlite3, but DELETE FROM uses a truncate optimization:
+// http://www.sqlite.org/lang_delete.html
+func (d SqliteDialect) TruncateClause() string {
+ return "delete from"
+}
+
+// RestartIdentityClause restarts the sqlite_sequence for the provided table.
+// It is executed by TruncateTable as a separate query.
+func (d SqliteDialect) RestartIdentityClause(table string) string {
+ return "; DELETE FROM sqlite_sequence WHERE name='" + table + "'"
+}
+
+// -- PostgreSQL
+
+// PostgresDialect implements the Dialect interface for PostgreSQL.
+type PostgresDialect struct {
+ suffix string
+}
+
+// DriverName returns the driverName for postgres.
+func (d PostgresDialect) DriverName() string {
+ return "postgres"
+}
+
+// ToSqlType maps go types to postgres types.
+func (d PostgresDialect) ToSqlType(col *ColumnMap) string {
+
+ switch col.gotype.Kind() {
+ case reflect.Bool:
+ return "boolean"
+ case reflect.Int, reflect.Int16, reflect.Int32, reflect.Uint16, reflect.Uint32:
+ if col.isAutoIncr {
+ return "serial"
+ }
+ return "integer"
+ case reflect.Int64, reflect.Uint64:
+ if col.isAutoIncr {
+ return "bigserial"
+ }
+ return "bigint"
+ case reflect.Float64, reflect.Float32:
+ return "real"
+ case reflect.Slice:
+ if col.gotype.Elem().Kind() == reflect.Uint8 {
+ return "bytea"
+ }
+ }
+
+ switch col.gotype.Name() {
+ case "NullableInt64":
+ return "bigint"
+ case "NullableFloat64":
+ return "double"
+ case "NullableBool":
+ return "smallint"
+ case "NullableBytes":
+ return "bytea"
+ case "Time", "Nulltime":
+ return "timestamp with time zone"
+ }
+
+ maxsize := col.MaxSize
+ if col.MaxSize < 1 {
+ maxsize = 255
+ }
+ return fmt.Sprintf("varchar(%d)", maxsize)
+}
+
+// AutoIncrStr returns empty string, as it's not used in postgres.
+func (d PostgresDialect) AutoIncrStr() string {
+ return ""
+}
+
+// AutoIncrBindValue returns 'default' for default auto incr bind values.
+func (d PostgresDialect) AutoIncrBindValue() string {
+ return "default"
+}
+
+// AutoIncrInsertSuffix appnds 'returning colname' to a query so that the
+// new autoincrement value for the column name is returned by Insert.
+func (d PostgresDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
+ return " returning " + col.ColumnName
+}
+
+// CreateTableSuffix returns the configured suffix.
+func (d PostgresDialect) CreateTableSuffix() string {
+ return d.suffix
+}
+
+// BindVar returns "$(i+1)"
+func (d PostgresDialect) BindVar(i int) string {
+ return fmt.Sprintf("$%d", i+1)
+}
+
+// InsertAutoIncr inserts via a query and reads the resultant rows for the new
+// auto increment ID, as it's not returned with the result in PostgreSQL.
+func (d PostgresDialect) InsertAutoIncr(e SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ rows, err := e.handle().Queryx(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 + ", error: " + rows.Err().Error())
+}
+
+func (d PostgresDialect) InsertAutoIncrAny(e SqlExecutor, insertSql string, dest interface{}, params ...interface{}) error {
+ return standardAutoIncrAny(e, insertSql, dest, params...)
+}
+
+// QuoteField quotes f with ""
+func (d PostgresDialect) QuoteField(f string) string {
+ return `"` + sqlx.NameMapper(f) + `"`
+}
+
+// TruncateClause returns 'truncate'
+func (d PostgresDialect) TruncateClause() string {
+ return "truncate"
+}
+
+// RestartIdentityClause returns 'restart identity', which will restart serial
+// sequences for this table at the same time as a truncation is performed.
+func (d PostgresDialect) RestartIdentityClause(table string) string {
+ return "restart identity"
+}
+
+// -- MySQL
+
+// MySQLDialect is an 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
+}
+
+// DriverName returns "mysql", used by the ziutek and the go-sql-driver
+// versions of the MySQL driver.
+func (d MySQLDialect) DriverName() string {
+ return "mysql"
+}
+
+// ToSqlType maps go types to MySQL types.
+func (d MySQLDialect) ToSqlType(col *ColumnMap) string {
+ switch col.gotype.Kind() {
+ case reflect.Bool:
+ return "boolean"
+ case reflect.Int, reflect.Int16, reflect.Int32, reflect.Uint16, reflect.Uint32:
+ return "int"
+ case reflect.Int64, reflect.Uint64:
+ return "bigint"
+ case reflect.Float64, reflect.Float32:
+ return "double"
+ case reflect.Slice:
+ if col.gotype.Elem().Kind() == reflect.Uint8 {
+ return "mediumblob"
+ }
+ }
+
+ switch col.gotype.Name() {
+ case "NullableInt64":
+ return "bigint"
+ case "NullableFloat64":
+ return "double"
+ case "NullableBool":
+ return "tinyint"
+ case "NullableBytes":
+ return "mediumblob"
+ case "Time", "NullTime":
+ return "datetime"
+ }
+
+ maxsize := col.MaxSize
+ if col.MaxSize < 1 {
+ maxsize = 255
+ }
+ return fmt.Sprintf("varchar(%d)", maxsize)
+}
+
+// AutoIncrStr returns "auto_increment".
+func (d MySQLDialect) AutoIncrStr() string {
+ return "auto_increment"
+}
+
+// AutoIncrBindValue returns "null", default for auto increment fields in MySQL.
+func (d MySQLDialect) AutoIncrBindValue() string {
+ return "null"
+}
+
+// AutoIncrInsertSuffix returns "".
+func (d MySQLDialect) AutoIncrInsertSuffix(col *ColumnMap) string {
+ return ""
+}
+
+// CreateTableSuffix returns engine=%s charset=%s based on values stored on struct
+func (d MySQLDialect) CreateTableSuffix() string {
+ return fmt.Sprintf(" engine=%s charset=%s", d.Engine, d.Encoding)
+}
+
+// BindVar returns "?"
+func (d MySQLDialect) BindVar(i int) string {
+ return "?"
+}
+
+// InsertAutoIncr runs the standard Insert Exec, which uses LastInsertId to get
+// the value of the auto increment column.
+func (d MySQLDialect) InsertAutoIncr(e SqlExecutor, insertSql string, params ...interface{}) (int64, error) {
+ return standardInsertAutoIncr(e, insertSql, params...)
+}
+
+func (d MySQLDialect) InsertAutoIncrAny(e SqlExecutor, insertSql string, dest interface{}, params ...interface{}) error {
+ return standardAutoIncrAny(e, insertSql, dest, params...)
+}
+
+// QuoteField quotes f using ``.
+func (d MySQLDialect) QuoteField(f string) string {
+ return "`" + f + "`"
+}
+
+// FIXME: use sqlx's rebind, which was written after it had been created for modl
+
+// ReBind formats the bindvars in the query string (these are '?') for the dialect.
+func ReBind(query string, dialect Dialect) string {
+
+ binder := dialect.BindVar(0)
+ if binder == "?" {
+ return query
+ }
+
+ for i, j := 0, strings.Index(query, "?"); j >= 0; i++ {
+ query = strings.Replace(query, "?", dialect.BindVar(i), 1)
+ j = strings.Index(query, "?")
+ }
+ return query
+}
+
+// TruncateClause returns 'truncate'.
+func (d MySQLDialect) TruncateClause() string {
+ return "truncate"
+}
+
+// RestartIdentityClause alters the table's AUTO_INCREMENT value after truncation,
+// as MySQL doesn't have an identity clause for the truncate statement.
+func (d MySQLDialect) RestartIdentityClause(table string) string {
+ return "; alter table " + table + " AUTO_INCREMENT = 1"
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/handle.go b/Godeps/_workspace/src/github.com/jmoiron/modl/handle.go
new file mode 100644
index 0000000..febb74b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/handle.go
@@ -0,0 +1,51 @@
+package modl
+
+import (
+ "database/sql"
+
+ "github.com/jmoiron/sqlx"
+)
+
+// a cursor is either an sqlx.Db or an sqlx.Tx
+type handle interface {
+ Select(dest interface{}, query string, args ...interface{}) error
+ Get(dest interface{}, query string, args ...interface{}) error
+ Queryx(query string, args ...interface{}) (*sqlx.Rows, error)
+ QueryRowx(query string, args ...interface{}) *sqlx.Row
+ Exec(query string, args ...interface{}) (sql.Result, error)
+ /*
+ Query(query string, args ...interface{}) (*sql.Rows, error)
+ QueryRow(query string, args ...interface{}) *sql.Row
+ */
+}
+
+// an implmentation of handle which traces using dbmap
+type tracingHandle struct {
+ d *DbMap
+ h handle
+}
+
+func (t *tracingHandle) Select(dest interface{}, query string, args ...interface{}) error {
+ t.d.trace(query, args...)
+ return t.h.Select(dest, query, args...)
+}
+
+func (t *tracingHandle) Get(dest interface{}, query string, args ...interface{}) error {
+ t.d.trace(query, args...)
+ return t.h.Get(dest, query, args...)
+}
+
+func (t *tracingHandle) Queryx(query string, args ...interface{}) (*sqlx.Rows, error) {
+ t.d.trace(query, args...)
+ return t.h.Queryx(query, args...)
+}
+
+func (t *tracingHandle) QueryRowx(query string, args ...interface{}) *sqlx.Row {
+ t.d.trace(query, args...)
+ return t.h.QueryRowx(query, args...)
+}
+
+func (t *tracingHandle) Exec(query string, args ...interface{}) (sql.Result, error) {
+ t.d.trace(query, args...)
+ return t.h.Exec(query, args...)
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/hooks.go b/Godeps/_workspace/src/github.com/jmoiron/modl/hooks.go
new file mode 100644
index 0000000..91e0b41
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/hooks.go
@@ -0,0 +1,66 @@
+package modl
+
+import (
+ "reflect"
+)
+
+// PreInserter is an interface used to determine if a table type implements
+// a PreInsert hook
+type PreInserter interface {
+ PreInsert(SqlExecutor) error
+}
+
+// PostInserter is an interface used to determine if a table type implements
+// a PostInsert hook
+type PostInserter interface {
+ PostInsert(SqlExecutor) error
+}
+
+// PostGetter is an interface used to determine if a table type implements
+// a PostGet hook
+type PostGetter interface {
+ PostGet(SqlExecutor) error
+}
+
+// PreUpdater is an interface used to determine if a table type implements
+// a PreUpdate hook
+type PreUpdater interface {
+ PreUpdate(SqlExecutor) error
+}
+
+// PostUpdater is an interface used to determine if a table type implements
+// a PostUpdate hook
+type PostUpdater interface {
+ PostUpdate(SqlExecutor) error
+}
+
+// PreDeleter is an interface used to determine if a table type implements
+// a PreDelete hook
+type PreDeleter interface {
+ PreDelete(SqlExecutor) error
+}
+
+// PostDeleter is an interface used to determine if a table type implements
+// a PostDelete hook
+type PostDeleter interface {
+ PostDelete(SqlExecutor) error
+}
+
+// Determine which hooks are supported by the mapper struct i
+func (t *TableMap) setupHooks(i interface{}) {
+ // These hooks must be implemented on a pointer, so if a value is passed in
+ // we have to get a pointer for a new value of that type in order for the
+ // type assertions to pass.
+ ptr := i
+ if reflect.ValueOf(i).Kind() == reflect.Struct {
+ ptr = reflect.New(reflect.ValueOf(i).Type()).Interface()
+ }
+
+ _, t.CanPreInsert = ptr.(PreInserter)
+ _, t.CanPostInsert = ptr.(PostInserter)
+ _, t.CanPostGet = ptr.(PostGetter)
+ _, t.CanPreUpdate = ptr.(PreUpdater)
+ _, t.CanPostUpdate = ptr.(PostUpdater)
+ _, t.CanPreDelete = ptr.(PreDeleter)
+ _, t.CanPostDelete = ptr.(PostDeleter)
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/modl.go b/Godeps/_workspace/src/github.com/jmoiron/modl/modl.go
new file mode 100644
index 0000000..a05c4a5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/modl.go
@@ -0,0 +1,375 @@
+package modl
+
+// Changes Copyright 2013 Jason Moiron. Original Gorp code
+// 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.
+//
+// Source code and project home:
+// https://github.com/jmoiron/modl
+
+import (
+ "database/sql"
+ "fmt"
+ "reflect"
+)
+
+// NoKeysErr is a special error type returned when modl's CRUD helpers are
+// used on tables which have not been set up with a primary key.
+type NoKeysErr struct {
+ Table *TableMap
+}
+
+// Error returns the string representation of a NoKeysError.
+func (n NoKeysErr) Error() string {
+ return fmt.Sprintf("Could not find keys for table %v", n.Table)
+}
+
+const versFieldConst = "[modl_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("OptimisticLockError table=%s keys=%v out of date version=%d", e.TableName, e.Keys, e.LocalVersion)
+ }
+
+ return fmt.Sprintf("OptimisticLockError no row found for table=%s keys=%v", e.TableName, e.Keys)
+}
+
+// A bindPlan saves a query type (insert, get, updated, delete) so it doesn't
+// have to be re-created every time it's executed.
+type bindPlan struct {
+ query string
+ argFields []string
+ keyFields []string
+ versField string
+ autoIncrIdx int
+}
+
+func (plan bindPlan) createBindInstance(elem reflect.Value) bindInstance {
+ bi := bindInstance{query: plan.query, autoIncrIdx: plan.autoIncrIdx, versField: plan.versField}
+ if plan.versField != "" {
+ bi.existingVersion = elem.FieldByName(plan.versField).Int()
+ }
+
+ 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()
+ bi.args = append(bi.args, val)
+ }
+ }
+
+ for i := 0; i < len(plan.keyFields); i++ {
+ k := plan.keyFields[i]
+ val := elem.FieldByName(k).Interface()
+ bi.keys = append(bi.keys, val)
+ }
+
+ return bi
+}
+
+type bindInstance struct {
+ query string
+ args []interface{}
+ keys []interface{}
+ existingVersion int64
+ versField string
+ autoIncrIdx int
+}
+
+// SqlExecutor exposes modl 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(dest interface{}, keys ...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(dest interface{}, query string, args ...interface{}) error
+ SelectOne(dest interface{}, query string, args ...interface{}) error
+ handle() handle
+}
+
+// Compile-time check that DbMap and Transaction implement the SqlExecutor
+// interface.
+var (
+ _ SqlExecutor = &DbMap{}
+ _ SqlExecutor = &Transaction{}
+)
+
+///////////////
+
+func hookedget(m *DbMap, e SqlExecutor, dest interface{}, query string, args ...interface{}) error {
+ err := e.handle().Get(dest, query, args...)
+ if err != nil {
+ return err
+ }
+
+ table := m.TableFor(dest)
+
+ if table != nil && table.CanPostGet {
+ err = dest.(PostGetter).PostGet(e)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func hookedselect(m *DbMap, e SqlExecutor, dest interface{}, query string, args ...interface{}) error {
+
+ err := e.handle().Select(dest, query, args...)
+ if err != nil {
+ return err
+ }
+
+ // select can use arbitrary structs for join queries, so we needn't find a table
+ table := m.TableFor(dest)
+
+ if table != nil && table.CanPostGet {
+ var x interface{}
+ v := reflect.ValueOf(dest)
+ if v.Kind() == reflect.Ptr {
+ v = reflect.Indirect(v)
+ }
+ l := v.Len()
+ for i := 0; i < l; i++ {
+ x = v.Index(i).Interface()
+ err = x.(PostGetter).PostGet(e)
+ if err != nil {
+ return err
+ }
+
+ }
+ }
+ return nil
+}
+
+func get(m *DbMap, e SqlExecutor, dest interface{}, keys ...interface{}) error {
+
+ table := m.TableFor(dest)
+
+ if table == nil {
+ return fmt.Errorf("could not find table for %v", dest)
+ }
+ if len(table.Keys) < 1 {
+ return &NoKeysErr{table}
+ }
+
+ plan := table.bindGet()
+ err := e.handle().Get(dest, plan.query, keys...)
+
+ if err != nil {
+ return err
+ }
+
+ if table.CanPostGet {
+ err = dest.(PostGetter).PostGet(e)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func deletes(m *DbMap, e SqlExecutor, list ...interface{}) (int64, error) {
+ var err error
+ var table *TableMap
+ var elem reflect.Value
+ var count int64
+
+ for _, ptr := range list {
+ table, elem, err = tableForPointer(m, ptr, true)
+ if err != nil {
+ return -1, err
+ }
+
+ if table.CanPreDelete {
+ err = ptr.(PreDeleter).PreDelete(e)
+ if err != nil {
+ return -1, err
+ }
+ }
+
+ bi := table.bindDelete(elem)
+
+ res, err := e.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, e, table.TableName, bi.existingVersion, elem, bi.keys...)
+ }
+
+ count += rows
+
+ if table.CanPostDelete {
+ err = ptr.(PostDeleter).PostDelete(e)
+ if err != nil {
+ return -1, err
+ }
+ }
+ }
+
+ return count, nil
+}
+
+func update(m *DbMap, e SqlExecutor, list ...interface{}) (int64, error) {
+ var err error
+ var table *TableMap
+ var elem reflect.Value
+ var count int64
+
+ for _, ptr := range list {
+ table, elem, err = tableForPointer(m, ptr, true)
+ if err != nil {
+ return -1, err
+ }
+
+ if table.CanPreUpdate {
+ err = ptr.(PreUpdater).PreUpdate(e)
+ if err != nil {
+ return -1, err
+ }
+ }
+
+ bi := table.bindUpdate(elem)
+ if err != nil {
+ return -1, err
+ }
+
+ res, err := e.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, e, table.TableName,
+ bi.existingVersion, elem, bi.keys...)
+ }
+
+ if bi.versField != "" {
+ elem.FieldByName(bi.versField).SetInt(bi.existingVersion + 1)
+ }
+
+ count += rows
+
+ if table.CanPostUpdate {
+ err = ptr.(PostUpdater).PostUpdate(e)
+
+ if err != nil {
+ return -1, err
+ }
+ }
+ }
+ return count, nil
+}
+
+func insert(m *DbMap, e SqlExecutor, list ...interface{}) error {
+ var err error
+ var table *TableMap
+ var elem reflect.Value
+
+ for _, ptr := range list {
+ table, elem, err = tableForPointer(m, ptr, false)
+ if err != nil {
+ return err
+ }
+
+ if table.CanPreInsert {
+ err = ptr.(PreInserter).PreInsert(e)
+ if err != nil {
+ return err
+ }
+ }
+
+ bi := table.bindInsert(elem)
+
+ if bi.autoIncrIdx > -1 {
+ id, err := m.Dialect.InsertAutoIncr(e, bi.query, bi.args...)
+ if err != nil {
+ return err
+ }
+ f := elem.Field(bi.autoIncrIdx)
+ k := f.Kind()
+ if (k == reflect.Int) || (k == reflect.Int16) || (k == reflect.Int32) || (k == reflect.Int64) {
+ f.SetInt(id)
+ } else {
+ return fmt.Errorf("modl: Cannot set autoincrement value on non-Int field. SQL=%s autoIncrIdx=%d", bi.query, bi.autoIncrIdx)
+ }
+ } else {
+ _, err := e.Exec(bi.query, bi.args...)
+ if err != nil {
+ return err
+ }
+ }
+
+ if table.CanPostInsert {
+ err = ptr.(PostInserter).PostInsert(e)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func lockError(m *DbMap, e SqlExecutor, tableName string, existingVer int64, elem reflect.Value, keys ...interface{}) (int64, error) {
+
+ dest := reflect.New(elem.Type()).Interface()
+ err := get(m, e, dest, keys...)
+ if err != nil {
+ return -1, err
+ }
+
+ ole := OptimisticLockError{tableName, keys, true, existingVer}
+ if dest == nil {
+ ole.RowExists = false
+ }
+ return -1, ole
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/modl_test.go b/Godeps/_workspace/src/github.com/jmoiron/modl/modl_test.go
new file mode 100644
index 0000000..e533c42
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/modl_test.go
@@ -0,0 +1,1003 @@
+package modl
+
+import (
+ "bytes"
+ "database/sql"
+ "fmt"
+ "log"
+ "os"
+ "reflect"
+ "testing"
+ "time"
+
+ _ "github.com/go-sql-driver/mysql"
+ _ "github.com/lib/pq"
+ _ "github.com/mattn/go-sqlite3"
+)
+
+var _ = log.Fatal
+
+type Invoice struct {
+ ID int64
+ Created int64 `db:"date_created"`
+ Updated int64
+ Memo string
+ PersonID int64
+ IsPaid bool
+}
+
+type Person struct {
+ ID int64
+ Created int64
+ Updated int64
+ FName string
+ LName string
+ Version int64
+}
+
+type InvoicePersonView struct {
+ InvoiceID int64
+ PersonID int64
+ Memo string
+ FName string
+ LegacyVersion int64
+}
+
+type TableWithNull struct {
+ ID int64
+ Str sql.NullString
+ Int64 sql.NullInt64
+ Float64 sql.NullFloat64
+ Bool sql.NullBool
+ Bytes []byte
+}
+
+type WithIgnoredColumn struct {
+ internal int64 `db:"-"`
+ ID int64
+ Created int64
+}
+
+type WithStringPk struct {
+ ID string
+ Name string
+}
+
+type CustomStringType string
+
+func (p *Person) PreInsert(s SqlExecutor) error {
+ p.Created = time.Now().UnixNano()
+ p.Updated = p.Created
+ if p.FName == "badname" {
+ return fmt.Errorf("invalid name: %s", p.FName)
+ }
+ return nil
+}
+
+func (p *Person) PostInsert(s SqlExecutor) error {
+ p.LName = "postinsert"
+ return nil
+}
+
+func (p *Person) PreUpdate(s SqlExecutor) error {
+ p.FName = "preupdate"
+ return nil
+}
+
+func (p *Person) PostUpdate(s SqlExecutor) error {
+ p.LName = "postupdate"
+ return nil
+}
+
+func (p *Person) PreDelete(s SqlExecutor) error {
+ p.FName = "predelete"
+ return nil
+}
+
+func (p *Person) PostDelete(s SqlExecutor) error {
+ p.LName = "postdelete"
+ return nil
+}
+
+func (p *Person) PostGet(s SqlExecutor) error {
+ p.LName = "postget"
+ return nil
+}
+
+type PersistentUser struct {
+ Key int32 `db:"mykey"`
+ ID string
+ PassedTraining bool
+}
+
+func TestCreateTablesIfNotExists(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ err := dbmap.CreateTablesIfNotExists()
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestPersistentUser(t *testing.T) {
+ dbmap := newDbMap()
+ dbmap.Exec("drop table if exists persistentuser")
+ if len(os.Getenv("MODL_TEST_TRACE")) > 0 {
+ dbmap.TraceOn("test", log.New(os.Stdout, "modltest: ", log.Lmicroseconds))
+ }
+ dbmap.AddTable(PersistentUser{}).SetKeys(false, "mykey")
+ err := dbmap.CreateTablesIfNotExists()
+ if err != nil {
+ panic(err)
+ }
+ defer dbmap.Cleanup()
+ pu := &PersistentUser{43, "33r", false}
+ err = dbmap.Insert(pu)
+ if err != nil {
+ panic(err)
+ }
+
+ // prove we can pass a pointer into Get
+ pu2 := &PersistentUser{}
+ err = dbmap.Get(pu2, pu.Key)
+ if err != nil {
+ panic(err)
+ }
+ if !reflect.DeepEqual(pu, pu2) {
+ t.Errorf("%v!=%v", pu, pu2)
+ }
+
+ arr := []*PersistentUser{}
+ err = dbmap.Select(&arr, "select * from persistentuser")
+ if err != nil {
+ t.Error(err)
+ }
+ if !reflect.DeepEqual(pu, arr[0]) {
+ t.Errorf("%v!=%v", pu, arr[0])
+ }
+
+ // prove we can get the results back in a slice
+ puArr := []PersistentUser{}
+ err = dbmap.Select(&puArr, "select * from persistentuser")
+ if err != nil {
+ t.Error(err)
+ }
+ if len(puArr) != 1 {
+ t.Errorf("Expected one persistentuser, found none")
+ }
+ if !reflect.DeepEqual(pu, &puArr[0]) {
+ t.Errorf("%v!=%v", pu, puArr[0])
+ }
+}
+
+func TestOverrideVersionCol(t *testing.T) {
+ dbmap := initDbMap()
+ dbmap.DropTables()
+
+ t1 := dbmap.AddTable(InvoicePersonView{}).SetKeys(false, "invoiceid", "personid")
+ err := dbmap.CreateTables()
+
+ if err != nil {
+ panic(err)
+ }
+ defer dbmap.Cleanup()
+ c1 := t1.SetVersionCol("legacyversion")
+ if c1.ColumnName != "legacyversion" {
+ t.Errorf("Wrong col returned: %v", c1)
+ }
+
+ ipv := &InvoicePersonView{1, 2, "memo", "fname", 0}
+ _update(dbmap, ipv)
+ if ipv.LegacyVersion != 1 {
+ t.Errorf("LegacyVersion not updated: %d", ipv.LegacyVersion)
+ }
+}
+
+func TestDontPanicOnInsert(t *testing.T) {
+ var err error
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ err = dbmap.Insert(&TableWithNull{ID: 10})
+ if err == nil {
+ t.Errorf("Should have received an error for inserting without a known table.")
+ }
+}
+
+func TestOptimisticLocking(t *testing.T) {
+ var err error
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ p1 := &Person{0, 0, 0, "Bob", "Smith", 0}
+ dbmap.Insert(p1) // Version is now 1
+ if p1.Version != 1 {
+ t.Errorf("Insert didn't incr Version: %d != %d", 1, p1.Version)
+ return
+ }
+ if p1.ID == 0 {
+ t.Errorf("Insert didn't return a generated PK")
+ return
+ }
+
+ p2 := &Person{}
+ err = dbmap.Get(p2, p1.ID)
+ if err != nil {
+ panic(err)
+ }
+ p2.LName = "Edwards"
+ _, err = dbmap.Update(p2) // Version is now 2
+
+ if err != nil {
+ panic(err)
+ }
+
+ if p2.Version != 2 {
+ t.Errorf("Update didn't incr Version: %d != %d", 2, p2.Version)
+ }
+
+ p1.LName = "Howard"
+ count, err := dbmap.Update(p1)
+ if _, ok := err.(OptimisticLockError); !ok {
+ t.Errorf("update - Expected OptimisticLockError, got: %v", err)
+ }
+ if count != -1 {
+ t.Errorf("update - Expected -1 count, got: %d", count)
+ }
+
+ count, err = dbmap.Delete(p1)
+ if _, ok := err.(OptimisticLockError); !ok {
+ t.Errorf("delete - Expected OptimisticLockError, got: %v", err)
+ }
+ if count != -1 {
+ t.Errorf("delete - Expected -1 count, got: %d", count)
+ }
+}
+
+// what happens if a legacy table has a null value?
+func TestDoubleAddTable(t *testing.T) {
+ dbmap := newDbMap()
+ t1 := dbmap.AddTable(TableWithNull{}).SetKeys(false, "ID")
+ t2 := dbmap.AddTable(TableWithNull{})
+ if t1 != t2 {
+ t.Errorf("%v != %v", t1, t2)
+ }
+}
+
+// test overriding the create sql
+func TestColMapCreateSql(t *testing.T) {
+ dbmap := newDbMap()
+ t1 := dbmap.AddTable(TableWithNull{})
+ b := t1.ColMap("Bytes")
+ custom := "bytes text NOT NULL"
+ b.SetSqlCreate(custom)
+ var buf bytes.Buffer
+ writeColumnSql(&buf, b)
+ s := buf.String()
+ if s != custom {
+ t.Errorf("Expected custom sql `%s`, got %s", custom, s)
+ }
+ err := dbmap.CreateTables()
+ defer dbmap.Cleanup()
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+// what happens if a legacy table has a null value?
+func TestNullValues(t *testing.T) {
+ dbmap := initDbMapNulls()
+ defer dbmap.Cleanup()
+
+ // insert a row directly
+ _, err := dbmap.Exec(`insert into tablewithnull values (10, null, null, null, null, null)`)
+ if err != nil {
+ panic(err)
+ }
+
+ // try to load it
+ expected := &TableWithNull{ID: 10}
+ t1 := &TableWithNull{}
+ MustGet(dbmap, t1, 10)
+ if !reflect.DeepEqual(expected, t1) {
+ t.Errorf("%v != %v", expected, t1)
+ }
+
+ // update it
+ t1.Str = sql.NullString{"hi", true}
+ expected.Str = t1.Str
+ t1.Int64 = sql.NullInt64{999, true}
+ expected.Int64 = t1.Int64
+ t1.Float64 = sql.NullFloat64{53.33, true}
+ expected.Float64 = t1.Float64
+ t1.Bool = sql.NullBool{true, true}
+ expected.Bool = t1.Bool
+ t1.Bytes = []byte{1, 30, 31, 33}
+ expected.Bytes = t1.Bytes
+ _update(dbmap, t1)
+
+ MustGet(dbmap, t1, 10)
+ if t1.Str.String != "hi" {
+ t.Errorf("%s != hi", t1.Str.String)
+ }
+ if !reflect.DeepEqual(expected, t1) {
+ t.Errorf("%v != %v", expected, t1)
+ }
+}
+
+func TestColumnProps(t *testing.T) {
+ dbmap := newDbMap()
+ //dbmap.TraceOn("", log.New(os.Stdout, "modltest: ", log.Lmicroseconds))
+ t1 := dbmap.AddTable(Invoice{}).SetKeys(true, "ID")
+ //t1.ColMap("Created").Rename("date_created")
+ t1.ColMap("Updated").SetTransient(true)
+ t1.ColMap("Memo").SetMaxSize(10)
+ t1.ColMap("PersonID").SetUnique(true)
+
+ err := dbmap.CreateTables()
+ if err != nil {
+ panic(err)
+ }
+ defer dbmap.Cleanup()
+
+ // test transient
+ inv := &Invoice{0, 0, 1, "my invoice", 0, true}
+ _insert(dbmap, inv)
+ inv2 := Invoice{}
+ MustGet(dbmap, &inv2, inv.ID)
+ if inv2.Updated != 0 {
+ t.Errorf("Saved transient column 'Updated'")
+ }
+
+ // test max size
+ inv2.Memo = "this memo is too long"
+ err = dbmap.Insert(inv2)
+ if err == nil {
+ t.Errorf("max size exceeded, but Insert did not fail.")
+ }
+
+ // test unique - same person id
+ inv = &Invoice{0, 0, 1, "my invoice2", 0, false}
+ err = dbmap.Insert(inv)
+ if err == nil {
+ t.Errorf("same PersonID inserted, but Insert did not fail.")
+ }
+}
+
+func TestRawSelect(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ p1 := &Person{0, 0, 0, "bob", "smith", 0}
+ _insert(dbmap, p1)
+
+ inv1 := &Invoice{0, 0, 0, "xmas order", p1.ID, true}
+ _insert(dbmap, inv1)
+
+ expected := &InvoicePersonView{inv1.ID, p1.ID, inv1.Memo, p1.FName, 0}
+
+ query := "select i.id invoiceid, p.id personid, i.memo, p.fname " +
+ "from invoice_test i, person_test p " +
+ "where i.personid = p.id"
+ list := []InvoicePersonView{}
+ MustSelect(dbmap, &list, query)
+ if len(list) != 1 {
+ t.Errorf("len(list) != 1: %d", len(list))
+ } else if !reflect.DeepEqual(expected, &list[0]) {
+ t.Errorf("%v != %v", expected, list[0])
+ }
+}
+
+func TestHooks(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ p1 := &Person{0, 0, 0, "bob", "smith", 0}
+ _insert(dbmap, p1)
+ if p1.Created == 0 || p1.Updated == 0 {
+ t.Errorf("p1.PreInsert() didn't run: %v", p1)
+ } else if p1.LName != "postinsert" {
+ t.Errorf("p1.PostInsert() didn't run: %v", p1)
+ }
+
+ MustGet(dbmap, p1, p1.ID)
+ if p1.LName != "postget" {
+ t.Errorf("p1.PostGet() didn't run: %v", p1)
+ }
+
+ p1.LName = "smith"
+ _update(dbmap, p1)
+ if p1.FName != "preupdate" {
+ t.Errorf("p1.PreUpdate() didn't run: %v", p1)
+ } else if p1.LName != "postupdate" {
+ t.Errorf("p1.PostUpdate() didn't run: %v", p1)
+ }
+
+ var persons []*Person
+ bindVar := dbmap.Dialect.BindVar(0)
+ MustSelect(dbmap, &persons, "select * from person_test where id = "+bindVar, p1.ID)
+ if persons[0].LName != "postget" {
+ t.Errorf("p1.PostGet() didn't run after select: %v", p1)
+ }
+
+ _del(dbmap, p1)
+ if p1.FName != "predelete" {
+ t.Errorf("p1.PreDelete() didn't run: %v", p1)
+ } else if p1.LName != "postdelete" {
+ t.Errorf("p1.PostDelete() didn't run: %v", p1)
+ }
+
+ // Test error case
+ p2 := &Person{0, 0, 0, "badname", "", 0}
+ err := dbmap.Insert(p2)
+ if err == nil {
+ t.Errorf("p2.PreInsert() didn't return an error")
+ }
+}
+
+func TestTransaction(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ inv1 := &Invoice{0, 100, 200, "t1", 0, true}
+ inv2 := &Invoice{0, 100, 200, "t2", 0, false}
+
+ trans, err := dbmap.Begin()
+ if err != nil {
+ panic(err)
+ }
+ trans.Insert(inv1, inv2)
+ err = trans.Commit()
+ if err != nil {
+ panic(err)
+ }
+
+ obj := &Invoice{}
+ err = dbmap.Get(obj, inv1.ID)
+ if err != nil {
+ panic(err)
+ }
+ if !reflect.DeepEqual(inv1, obj) {
+ t.Errorf("%v != %v", inv1, obj)
+ }
+ err = dbmap.Get(obj, inv2.ID)
+ if err != nil {
+ panic(err)
+ }
+ if !reflect.DeepEqual(inv2, obj) {
+ t.Errorf("%v != %v", inv2, obj)
+ }
+}
+
+func TestMultiple(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ inv1 := &Invoice{0, 100, 200, "a", 0, false}
+ inv2 := &Invoice{0, 100, 200, "b", 0, true}
+ _insert(dbmap, inv1, inv2)
+
+ inv1.Memo = "c"
+ inv2.Memo = "d"
+ _update(dbmap, inv1, inv2)
+
+ count := _del(dbmap, inv1, inv2)
+ if count != 2 {
+ t.Errorf("%d != 2", count)
+ }
+}
+
+func TestCrud(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ inv := &Invoice{0, 100, 200, "first order", 0, true}
+
+ // INSERT row
+ _insert(dbmap, inv)
+ if inv.ID == 0 {
+ t.Errorf("inv.ID was not set on INSERT")
+ return
+ }
+
+ // SELECT row
+ inv2 := &Invoice{}
+ MustGet(dbmap, inv2, inv.ID)
+ if !reflect.DeepEqual(inv, inv2) {
+ t.Errorf("%v != %v", inv, inv2)
+ }
+
+ // UPDATE row and SELECT
+ inv.Memo = "second order"
+ inv.Created = 999
+ inv.Updated = 11111
+ count := _update(dbmap, inv)
+ if count != 1 {
+ t.Errorf("update 1 != %d", count)
+ }
+
+ MustGet(dbmap, inv2, inv.ID)
+ if !reflect.DeepEqual(inv, inv2) {
+ t.Errorf("%v != %v", inv, inv2)
+ }
+
+ // DELETE row
+ deleted := _del(dbmap, inv)
+ if deleted != 1 {
+ t.Errorf("Did not delete row with ID: %d", inv.ID)
+ return
+ }
+
+ // VERIFY deleted
+ err := dbmap.Get(inv2, inv.ID)
+ if err != sql.ErrNoRows {
+ t.Errorf("Found invoice with id: %d after Delete()", inv.ID)
+ }
+}
+
+func TestWithIgnoredColumn(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ ic := &WithIgnoredColumn{-1, 0, 1}
+ _insert(dbmap, ic)
+ expected := &WithIgnoredColumn{0, 1, 1}
+
+ ic2 := &WithIgnoredColumn{}
+ MustGet(dbmap, ic2, ic.ID)
+
+ if !reflect.DeepEqual(expected, ic2) {
+ t.Errorf("%v != %v", expected, ic2)
+ }
+
+ if _del(dbmap, ic) != 1 {
+ t.Errorf("Did not delete row with ID: %d", ic.ID)
+ return
+ }
+
+ err := dbmap.Get(ic2, ic.ID)
+ if err != sql.ErrNoRows {
+ t.Errorf("Found id: %d after Delete() (%#v)", ic.ID, ic2)
+ }
+}
+
+func TestVersionMultipleRows(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ persons := []*Person{
+ &Person{0, 0, 0, "Bob", "Smith", 0},
+ &Person{0, 0, 0, "Jane", "Smith", 0},
+ &Person{0, 0, 0, "Mike", "Smith", 0},
+ }
+
+ _insert(dbmap, persons[0], persons[1], persons[2])
+
+ for x, p := range persons {
+ if p.Version != 1 {
+ t.Errorf("person[%d].Version != 1: %d", x, p.Version)
+ }
+ }
+}
+
+func TestWithStringPk(t *testing.T) {
+ dbmap := newDbMap()
+ //dbmap.TraceOn("", log.New(os.Stdout, "modltest: ", log.Lmicroseconds))
+ dbmap.AddTableWithName(WithStringPk{}, "string_pk_test").SetKeys(true, "ID")
+ _, err := dbmap.Exec("create table string_pk_test (ID varchar(255), Name varchar(255));")
+ if err != nil {
+ t.Errorf("couldn't create string_pk_test: %v", err)
+ }
+ defer dbmap.Cleanup()
+
+ row := &WithStringPk{"1", "foo"}
+ err = dbmap.Insert(row)
+ if err == nil {
+ t.Errorf("Expected error when inserting into table w/non Int PK and autoincr set true")
+ }
+}
+
+func BenchmarkNativeCrud(b *testing.B) {
+ var err error
+
+ b.StopTimer()
+ dbmap := initDbMapBench()
+ defer dbmap.Cleanup()
+ b.StartTimer()
+
+ insert := "insert into invoice_test (date_created, updated, memo, personid) values (?, ?, ?, ?)"
+ sel := "select id, date_created, updated, memo, personid from invoice_test where id=?"
+ update := "update invoice_test set date_created=?, updated=?, memo=?, personid=? where id=?"
+ delete := "delete from invoice_test where id=?"
+
+ suffix := dbmap.Dialect.AutoIncrInsertSuffix(&ColumnMap{ColumnName: "id"})
+
+ insert = ReBind(insert, dbmap.Dialect) + suffix
+ sel = ReBind(sel, dbmap.Dialect)
+ update = ReBind(update, dbmap.Dialect)
+ delete = ReBind(delete, dbmap.Dialect)
+
+ inv := &Invoice{0, 100, 200, "my memo", 0, false}
+
+ for i := 0; i < b.N; i++ {
+ if len(suffix) == 0 {
+ res, err := dbmap.Db.Exec(insert, inv.Created, inv.Updated, inv.Memo, inv.PersonID)
+ if err != nil {
+ panic(err)
+ }
+
+ newid, err := res.LastInsertId()
+ if err != nil {
+ panic(err)
+ }
+ inv.ID = newid
+ } else {
+ rows, err := dbmap.Db.Query(insert, inv.Created, inv.Updated, inv.Memo, inv.PersonID)
+ if err != nil {
+ panic(err)
+ }
+
+ if rows.Next() {
+ err = rows.Scan(&inv.ID)
+ if err != nil {
+ panic(err)
+ }
+ }
+ rows.Close()
+
+ }
+
+ row := dbmap.Db.QueryRow(sel, inv.ID)
+ err = row.Scan(&inv.ID, &inv.Created, &inv.Updated, &inv.Memo, &inv.PersonID)
+ if err != nil {
+ panic(err)
+ }
+
+ inv.Created = 1000
+ inv.Updated = 2000
+ inv.Memo = "my memo 2"
+ inv.PersonID = 3000
+
+ _, err = dbmap.Db.Exec(update, inv.Created, inv.Updated, inv.Memo,
+ inv.PersonID, inv.ID)
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = dbmap.Db.Exec(delete, inv.ID)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+}
+
+func BenchmarkModlCrud(b *testing.B) {
+ b.StopTimer()
+ dbmap := initDbMapBench()
+ defer dbmap.Cleanup()
+ //dbmap.TraceOn("", log.New(os.Stdout, "modltest: ", log.Lmicroseconds))
+ b.StartTimer()
+
+ inv := &Invoice{0, 100, 200, "my memo", 0, true}
+ for i := 0; i < b.N; i++ {
+ err := dbmap.Insert(inv)
+ if err != nil {
+ panic(err)
+ }
+
+ inv2 := Invoice{}
+ err = dbmap.Get(&inv2, inv.ID)
+ if err != nil {
+ panic(err)
+ }
+
+ inv2.Created = 1000
+ inv2.Updated = 2000
+ inv2.Memo = "my memo 2"
+ inv2.PersonID = 3000
+ _, err = dbmap.Update(&inv2)
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = dbmap.Delete(&inv2)
+ if err != nil {
+ panic(err)
+ }
+
+ }
+}
+
+func initDbMapBench() *DbMap {
+ dbmap := newDbMap()
+ dbmap.Db.Exec("drop table if exists invoice_test")
+ dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "id")
+ err := dbmap.CreateTables()
+ if err != nil {
+ panic(err)
+ }
+ return dbmap
+}
+
+func (d *DbMap) Cleanup() {
+ err := d.DropTables()
+ if err != nil {
+ panic(err)
+ }
+ err = d.Dbx.Close()
+ if err != nil {
+ panic(err)
+ }
+}
+
+func initDbMap() *DbMap {
+ dbmap := newDbMap()
+ //dbmap.TraceOn("", log.New(os.Stdout, "modltest: ", log.Lmicroseconds))
+ dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "id")
+ dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "id")
+ dbmap.AddTableWithName(WithIgnoredColumn{}, "ignored_column_test").SetKeys(true, "id")
+ dbmap.AddTableWithName(WithTime{}, "time_test").SetKeys(true, "ID")
+ err := dbmap.CreateTables()
+ if err != nil {
+ panic(err)
+ }
+
+ return dbmap
+}
+
+func TestTruncateTables(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+ err := dbmap.CreateTablesIfNotExists()
+ if err != nil {
+ t.Error(err)
+ }
+
+ // Insert some data
+ p1 := &Person{0, 0, 0, "Bob", "Smith", 0}
+ dbmap.Insert(p1)
+ inv := &Invoice{0, 0, 1, "my invoice", 0, true}
+ dbmap.Insert(inv)
+
+ err = dbmap.TruncateTables()
+ if err != nil {
+ t.Error(err)
+ }
+
+ // Make sure all rows are deleted
+ people := []Person{}
+ invoices := []Invoice{}
+ dbmap.Select(&people, "SELECT * FROM person_test")
+ if len(people) != 0 {
+ t.Errorf("Expected 0 person rows, got %d", len(people))
+ }
+ dbmap.Select(&invoices, "SELECT * FROM invoice_test")
+ if len(invoices) != 0 {
+ t.Errorf("Expected 0 invoice rows, got %d", len(invoices))
+ }
+}
+
+func TestTruncateTablesIdentityRestart(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+ err := dbmap.CreateTablesIfNotExists()
+ if err != nil {
+ t.Error(err)
+ }
+
+ // Insert some data
+ p1 := &Person{0, 0, 0, "Bob", "Smith", 0}
+ dbmap.Insert(p1)
+ inv := &Invoice{0, 0, 1, "my invoice", 0, true}
+ dbmap.Insert(inv)
+
+ err = dbmap.TruncateTablesIdentityRestart()
+ if err != nil {
+ t.Error(err)
+ }
+
+ // Make sure all rows are deleted
+ people := []Person{}
+ invoices := []Invoice{}
+ dbmap.Select(&people, "SELECT * FROM person_test")
+ if len(people) != 0 {
+ t.Errorf("Expected 0 person rows, got %d", len(people))
+ }
+ dbmap.Select(&invoices, "SELECT * FROM invoice_test")
+ if len(invoices) != 0 {
+ t.Errorf("Expected 0 invoice rows, got %d", len(invoices))
+ }
+
+ p2 := &Person{0, 0, 0, "Other", "Person", 0}
+ dbmap.Insert(p2)
+ if p2.ID != int64(1) {
+ t.Errorf("Expected new person ID to be equal to 1, was %d", p2.ID)
+ }
+}
+
+func TestSelectBehavior(t *testing.T) {
+ db := initDbMap()
+ defer db.Cleanup()
+
+ p := Person{}
+
+ // check that SelectOne with no rows returns ErrNoRows
+ err := db.SelectOne(&p, "select * from person_test")
+ if err == nil || err != sql.ErrNoRows {
+ t.Fatal(err)
+ }
+
+ // insert and ensure SelectOne works properly
+ bob := Person{0, 0, 0, "Bob", "Smith", 0}
+ db.Insert(&bob)
+
+ err = db.SelectOne(&p, "select * from person_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.FName != "Bob" {
+ t.Errorf("Wrong FName: %s", p.FName)
+ }
+ // there's a post hook on this that sets it to postget, ensure it ran
+ if p.LName != "postget" {
+ t.Errorf("Wrong LName: %s", p.LName)
+ }
+
+ // insert again and ensure SelectOne *does not* error in rows > 1
+ ben := Person{0, 0, 0, "Ben", "Smith", 0}
+ db.Insert(&ben)
+
+ err = db.SelectOne(&p, "select * from person_test ORDER BY fname ASC")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if p.FName != "Ben" {
+ t.Errorf("Wrong FName: %s", p.FName)
+ }
+}
+
+func TestQuoteTableNames(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ quotedTableName := dbmap.Dialect.QuoteField("person_test")
+
+ // Use a buffer to hold the log to check generated queries
+ var logBuffer bytes.Buffer
+ dbmap.TraceOn("", log.New(&logBuffer, "modltest:", log.Lmicroseconds))
+
+ // Create some rows
+ p1 := &Person{0, 0, 0, "bob", "smith", 0}
+ errorTemplate := "Expected quoted table name %v in query but didn't find it"
+
+ // Check if Insert quotes the table name
+ id := dbmap.Insert(p1)
+ if !bytes.Contains(logBuffer.Bytes(), []byte(quotedTableName)) {
+ t.Log("log:", logBuffer.String())
+ t.Errorf(errorTemplate, quotedTableName)
+ }
+ logBuffer.Reset()
+
+ // Check if Get quotes the table name
+ dbmap.Get(Person{}, id)
+ if !bytes.Contains(logBuffer.Bytes(), []byte(quotedTableName)) {
+ t.Errorf(errorTemplate, quotedTableName)
+ }
+ logBuffer.Reset()
+}
+
+type WithTime struct {
+ ID int64
+ Time time.Time
+}
+
+func TestWithTime(t *testing.T) {
+ dbmap := initDbMap()
+ defer dbmap.Cleanup()
+
+ // FIXME: there seems to be a bug with go-sql-driver and timezones?
+ // MySQL doesn't have any timestamp support, but since it is not
+ // sending any, the scan assumes UTC, so the scanner should
+ // probably convert to UTC before storing. Also, note that time.Time
+ // support requires a special bit to be added to the DSN
+ t1, err := time.Parse("2006-01-02 15:04:05 -0700 MST",
+ "2013-08-09 21:30:43 +0000 UTC")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ w1 := WithTime{1, t1}
+ dbmap.Insert(&w1)
+
+ w2 := WithTime{}
+ dbmap.Get(&w2, w1.ID)
+
+ if w1.Time.UnixNano() != w2.Time.UnixNano() {
+ t.Errorf("%v != %v", w1, w2)
+ }
+}
+
+func initDbMapNulls() *DbMap {
+ dbmap := newDbMap()
+ //dbmap.TraceOn("", log.New(os.Stdout, "modltest: ", log.Lmicroseconds))
+ dbmap.AddTable(TableWithNull{}).SetKeys(false, "id")
+ err := dbmap.CreateTables()
+ if err != nil {
+ panic(err)
+ }
+ return dbmap
+}
+
+func newDbMap() *DbMap {
+ dialect, driver := dialectAndDriver()
+ return NewDbMap(connect(driver), dialect)
+}
+
+func connect(driver string) *sql.DB {
+ dsn := os.Getenv("MODL_TEST_DSN")
+ if dsn == "" {
+ panic("MODL_TEST_DSN env variable is not set. Please see README.md")
+ }
+
+ db, err := sql.Open(driver, dsn)
+ if err != nil {
+ panic("Error connecting to db: " + err.Error())
+ }
+ err = db.Ping()
+ if err != nil {
+ panic("Error connecting to db: " + err.Error())
+ }
+ return db
+}
+
+func dialectAndDriver() (Dialect, string) {
+ switch os.Getenv("MODL_TEST_DIALECT") {
+ case "mysql":
+ return MySQLDialect{"InnoDB", "UTF8"}, "mysql"
+ case "postgres":
+ return PostgresDialect{}, "postgres"
+ case "sqlite":
+ return SqliteDialect{}, "sqlite3"
+ }
+ panic("MODL_TEST_DIALECT env variable is not set or is invalid. Please see README.md")
+}
+
+func _insert(dbmap *DbMap, list ...interface{}) {
+ err := dbmap.Insert(list...)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func _update(dbmap *DbMap, list ...interface{}) int64 {
+ count, err := dbmap.Update(list...)
+ if err != nil {
+ panic(err)
+ }
+ return count
+}
+
+func _del(dbmap *DbMap, list ...interface{}) int64 {
+ count, err := dbmap.Delete(list...)
+ if err != nil {
+ panic(err)
+ }
+
+ return count
+}
+
+func MustGet(dbmap *DbMap, i interface{}, keys ...interface{}) {
+ err := dbmap.Get(i, keys...)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func MustSelect(dbmap *DbMap, dest interface{}, query string, args ...interface{}) {
+ err := dbmap.Select(dest, query, args...)
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/tablemap.go b/Godeps/_workspace/src/github.com/jmoiron/modl/tablemap.go
new file mode 100644
index 0000000..98b32f3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/tablemap.go
@@ -0,0 +1,375 @@
+package modl
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+
+ "github.com/jmoiron/sqlx"
+ "github.com/jmoiron/sqlx/reflectx"
+)
+
+// 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
+ Keys []*ColumnMap
+ Columns []*ColumnMap
+ gotype reflect.Type
+ version *ColumnMap
+ insertPlan bindPlan
+ updatePlan bindPlan
+ deletePlan bindPlan
+ getPlan bindPlan
+ dbmap *DbMap
+ mapper *reflectx.Mapper
+ // Cached capabilities for the struct mapped to this table
+ CanPreInsert bool
+ CanPostInsert bool
+ CanPostGet bool
+ CanPreUpdate bool
+ CanPostUpdate bool
+ CanPreDelete bool
+ CanPostDelete bool
+}
+
+// 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.
+func (t *TableMap) SetKeys(isAutoIncr bool, fieldNames ...string) *TableMap {
+ t.Keys = make([]*ColumnMap, 0)
+ for _, name := range fieldNames {
+ // FIXME: sqlx.NameMapper is a deprecated API. modl should have its
+ // own API which sets sqlx's mapping funcs as necessary
+ colmap := t.ColMap(sqlx.NameMapper(name))
+ colmap.isPK = true
+ colmap.isAutoIncr = isAutoIncr
+ t.Keys = append(t.Keys, colmap)
+ }
+ 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 {
+ for _, col := range t.Columns {
+ if col.fieldName == field || col.ColumnName == field {
+ return col
+ }
+ }
+ panic(fmt.Sprintf("No ColumnMap in table %s type %s with field %s",
+ t.TableName, t.gotype.Name(), field))
+}
+
+// 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
+}
+
+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.QuoteField(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(";")
+
+ plan.query = s.String()
+ t.getPlan = plan
+ }
+
+ return plan
+}
+
+func (t *TableMap) bindDelete(elem reflect.Value) bindInstance {
+ plan := t.deletePlan
+ if plan.query == "" {
+
+ s := bytes.Buffer{}
+ s.WriteString(fmt.Sprintf("delete from %s", t.dbmap.Dialect.QuoteField(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(";")
+
+ plan.query = s.String()
+ t.deletePlan = plan
+ }
+
+ return plan.createBindInstance(elem)
+}
+
+func (t *TableMap) bindUpdate(elem reflect.Value) bindInstance {
+ plan := t.updatePlan
+ if plan.query == "" {
+
+ s := bytes.Buffer{}
+ s.WriteString(fmt.Sprintf("update %s set ", t.dbmap.Dialect.QuoteField(t.TableName)))
+ x := 0
+
+ for y := range t.Columns {
+ col := t.Columns[y]
+ if !col.isPK && !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(";")
+
+ plan.query = s.String()
+ t.updatePlan = plan
+ }
+
+ return plan.createBindInstance(elem)
+}
+
+func (t *TableMap) bindInsert(elem reflect.Value) bindInstance {
+ 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.QuoteField(t.TableName)))
+
+ x := 0
+ first := true
+ for y := range t.Columns {
+ col := t.Columns[y]
+
+ 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
+ } 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
+ }
+ }
+ 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(";")
+
+ plan.query = s.String()
+ t.insertPlan = plan
+ }
+
+ return plan.createBindInstance(elem)
+}
+
+// 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 for validation 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.
+ Unique bool
+
+ // Passed to Dialect.ToSqlType() to assist in informing the
+ // correct column type to map to in CreateTables()
+ MaxSize int
+
+ // the table this column belongs to
+ table *TableMap
+
+ fieldName string
+ gotype reflect.Type
+ sqltype string
+ createSql string
+ isPK bool
+ isAutoIncr bool
+}
+
+// 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 sets the unqiue clause for this column. If true, a unique clause
+// will be added to create table statements for this column.
+func (c *ColumnMap) SetUnique(b bool) *ColumnMap {
+ c.Unique = b
+ return c
+}
+
+// SetSqlCreate overrides the default create statement used when this column
+// is created by CreateTable. This will override all other options (like
+// SetMaxSize, SetSqlType, etc). To unset, call with the empty string.
+func (c *ColumnMap) SetSqlCreate(s string) *ColumnMap {
+ c.createSql = s
+ return c
+}
+
+// SetSqlType sets an override for the column's sql type. This is a string,
+// such as 'varchar(32)' or 'text', which will be used by CreateTable and
+// nothing else. It is the caller's responsibility to ensure this will map
+// cleanly to the underlying struct field via rows.Scan
+func (c *ColumnMap) SetSqlType(t string) *ColumnMap {
+ c.sqltype = t
+ 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
+}
+
+// Return a table for a pointer; error if i is not a pointer or if the
+// table is not found
+func tableForPointer(m *DbMap, i interface{}, checkPk bool) (*TableMap, reflect.Value, error) {
+ v := reflect.ValueOf(i)
+ if v.Kind() != reflect.Ptr {
+ return nil, v, fmt.Errorf("value %v not a pointer", v)
+ }
+ v = v.Elem()
+ t := m.TableForType(v.Type())
+ if t == nil {
+ return nil, v, fmt.Errorf("could not find table for %v", t)
+ }
+ if checkPk && len(t.Keys) < 1 {
+ return t, v, &NoKeysErr{t}
+ }
+ return t, v, nil
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/test-all b/Godeps/_workspace/src/github.com/jmoiron/modl/test-all
new file mode 100644
index 0000000..f8c2a5c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/test-all
@@ -0,0 +1,69 @@
+#!/bin/bash
+#
+# To use this script, set the following environment variables:
+#
+# MODL_MYSQL_DSN - mysql connect DSN, like "modltest/modltest/modltest"
+# MODL_POSTGRES_DSN - postgres connect DSN, eg:
+# "username=modltest password=modltest dbname=modltest ssl-mode=disable"
+# MODL_SQLITE_DSN - sqlite connect DSN, which is a path to a sqlite file.
+# MODL_FAIL_ON_SKIP - optional, will fail if any DBs are skipped (for CI, mostly)
+#
+# In addition to this, you can create an `environ` file in this directory which
+# will be sourced and ignored by git.
+#
+# Additional arguments to test-all will be used after go-test, so to run
+# the benchmark suite on all dbs you can do:
+# ./test-all -bench=. -benchmem
+#
+
+if [ -f "./environ" ]; then
+ . ./environ
+fi
+
+function exit_on_error {
+ if [ $1 != 0 ]; then
+ exit $1
+ fi
+}
+
+# set -e
+
+if [ -n "$MODL_MYSQL_DSN" ]; then
+ export MODL_TEST_DSN="$MODL_MYSQL_DSN"
+ export MODL_TEST_DIALECT="mysql"
+ echo "Testing MySQL"
+ go test $@
+ exit_on_error $?
+else
+ echo "Skipping MySQL, \$MODL_MYSQL_DSN=$MODL_MYSQL_DSN"
+ if [ -n "$MODL_FAIL_ON_SKIP" ]; then
+ exit -1
+ fi
+fi
+
+if [ -n "$MODL_POSTGRES_DSN" ]; then
+ export MODL_TEST_DSN="$MODL_POSTGRES_DSN"
+ export MODL_TEST_DIALECT="postgres"
+ echo "Testing PostgreSQL"
+ go test $@
+ exit_on_error $?
+else
+ echo "Skipping PostgreSQL, \$MODL_POSTGRES_DSN=$MODL_POSTGRES_DSN"
+ if [ -n "$MODL_FAIL_ON_SKIP" ]; then
+ exit -1
+ fi
+fi
+
+if [ -n "$MODL_SQLITE_DSN" ]; then
+ export MODL_TEST_DSN="$MODL_SQLITE_DSN"
+ export MODL_TEST_DIALECT="sqlite"
+ echo "Testing SQLite"
+ go test $@
+ exit_on_error $?
+else
+ echo "Skipping SQLite, \$MODL_SQLITE_DSN=$MODL_SQLITE_DSN"
+ if [ -n "$MODL_FAIL_ON_SKIP" ]; then
+ exit -1
+ fi
+fi
+
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/todo.md b/Godeps/_workspace/src/github.com/jmoiron/modl/todo.md
new file mode 100644
index 0000000..219c9dc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/todo.md
@@ -0,0 +1,25 @@
+Future:
+
+- some way to designate a column as a foreign key
+
+Todo:
+
+- benchmarks that can compare mainline gorp to this fork
+- cache/store as much reflect stuff as possible
+- add query builder
+- update docs with new examples
+- add better interfaces to control underlying types to TableMap
+
+In Progress:
+
+(Both of these need tests)
+- alter schema creation to take advantage of ColMap.sqltype
+- alter schema creation to be able to return output (so people can look at or inspect it)
+
+Done:
+
+- remove list & new struct support form in favor of filling pointers and slices
+- replace reflect struct filling with structscan from sqlx
+- use strings.ToLower on table & field names by default, aligning behavior w/ sqlx
+- replace hook calling process with one that uses interfaces
+
diff --git a/Godeps/_workspace/src/github.com/jmoiron/modl/transaction.go b/Godeps/_workspace/src/github.com/jmoiron/modl/transaction.go
new file mode 100644
index 0000000..000d06d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/modl/transaction.go
@@ -0,0 +1,67 @@
+package modl
+
+import (
+ "database/sql"
+
+ "github.com/jmoiron/sqlx"
+)
+
+// 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 *sqlx.Tx
+}
+
+// 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 has 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 deletes(t.dbmap, t, list...)
+}
+
+// Get has the Same behavior as DbMap.Get(), but runs in a transaction.
+func (t *Transaction) Get(dest interface{}, keys ...interface{}) error {
+ return get(t.dbmap, t, dest, keys...)
+}
+
+// Select has the Same behavior as DbMap.Select(), but runs in a transaction.
+func (t *Transaction) Select(dest interface{}, query string, args ...interface{}) error {
+ return hookedselect(t.dbmap, t, dest, query, args...)
+}
+
+func (t *Transaction) SelectOne(dest interface{}, query string, args ...interface{}) error {
+ return hookedget(t.dbmap, t, dest, 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...)
+}
+
+// Commit commits the underlying database transaction.
+func (t *Transaction) Commit() error {
+ t.dbmap.trace("commit;")
+ return t.Tx.Commit()
+}
+
+// Rollback rolls back the underlying database transaction.
+func (t *Transaction) Rollback() error {
+ t.dbmap.trace("rollback;")
+ return t.Tx.Rollback()
+}
+
+func (t *Transaction) handle() handle {
+ return &tracingHandle{h: t.Tx, d: t.dbmap}
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/.gitignore b/Godeps/_workspace/src/github.com/jmoiron/sqlx/.gitignore
new file mode 100644
index 0000000..529841c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+tags
+environ
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/LICENSE b/Godeps/_workspace/src/github.com/jmoiron/sqlx/LICENSE
new file mode 100644
index 0000000..0d31edf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/LICENSE
@@ -0,0 +1,23 @@
+ Copyright (c) 2013, Jason Moiron
+
+ 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/Godeps/_workspace/src/github.com/jmoiron/sqlx/README.md b/Godeps/_workspace/src/github.com/jmoiron/sqlx/README.md
new file mode 100644
index 0000000..4e3eb6d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/README.md
@@ -0,0 +1,258 @@
+#sqlx
+
+[](https://drone.io/github.com/jmoiron/sqlx/latest) [](https://godoc.org/github.com/jmoiron/sqlx) [](https://raw.githubusercontent.com/jmoiron/sqlx/master/LICENSE)
+
+sqlx is a library which provides a set of extensions on go's standard
+`database/sql` library. The sqlx versions of `sql.DB`, `sql.TX`, `sql.Stmt`,
+et al. all leave the underlying interfaces untouched, so that their interfaces
+are a superset on the standard ones. This makes it relatively painless to
+integrate existing codebases using database/sql with sqlx.
+
+Major additional concepts are:
+
+* Marshal rows into structs (with embedded struct support), maps, and slices
+* Named parameter support including prepared statements
+* `Get` and `Select` to go quickly from query to struct/slice
+* `LoadFile` for executing statements from a file
+
+There is now some [fairly comprehensive documentation](http://jmoiron.github.io/sqlx/) for sqlx.
+You can also read the usage below for a quick sample on how sqlx works, or check out the [API
+documentation on godoc](http://godoc.org/github.com/jmoiron/sqlx).
+
+## Recent Changes
+
+The ability to use basic types as Select and Get destinations was added. This
+is only valid when there is one column in the result set, and both functions
+return an error if this isn't the case. This allows for much simpler patterns
+of access for single column results:
+
+```go
+var count int
+err := db.Get(&count, "SELECT count(*) FROM person;")
+
+var names []string
+err := db.Select(&names, "SELECT name FROM person;")
+```
+
+See the note on Scannability at the bottom of this README for some more info.
+
+### Backwards Compatibility
+
+There is no Go1-like promise of absolute stability, but I take the issue
+seriously and will maintain the library in a compatible state unless vital
+bugs prevent me from doing so. Since [#59](https://github.com/jmoiron/sqlx/issues/59) and [#60](https://github.com/jmoiron/sqlx/issues/60) necessitated
+breaking behavior, a wider API cleanup was done at the time of fixing.
+
+## install
+
+ go get github.com/jmoiron/sqlx
+
+## issues
+
+Row headers can be ambiguous (`SELECT 1 AS a, 2 AS a`), and the result of
+`Columns()` can have duplicate names on queries like:
+
+```sql
+SELECT a.id, a.name, b.id, b.name FROM foos AS a JOIN foos AS b ON a.parent = b.id;
+```
+
+making a struct or map destination ambiguous. Use `AS` in your queries
+to give rows distinct names, `rows.Scan` to scan them manually, or
+`SliceScan` to get a slice of results.
+
+## usage
+
+Below is an example which shows some common use cases for sqlx. Check
+[sqlx_test.go](https://github.com/jmoiron/sqlx/blob/master/sqlx_test.go) for more
+usage.
+
+
+```go
+package main
+
+import (
+ _ "github.com/lib/pq"
+ "database/sql"
+ "github.com/jmoiron/sqlx"
+ "log"
+)
+
+var schema = `
+CREATE TABLE person (
+ first_name text,
+ last_name text,
+ email text
+);
+
+CREATE TABLE place (
+ country text,
+ city text NULL,
+ telcode integer
+)`
+
+type Person struct {
+ FirstName string `db:"first_name"`
+ LastName string `db:"last_name"`
+ Email string
+}
+
+type Place struct {
+ Country string
+ City sql.NullString
+ TelCode int
+}
+
+func main() {
+ // this connects & tries a simple 'SELECT 1', panics on error
+ // use sqlx.Open() for sql.Open() semantics
+ db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // exec the schema or fail; multi-statement Exec behavior varies between
+ // database drivers; pq will exec them all, sqlite3 won't, ymmv
+ db.MustExec(schema)
+
+ tx := db.MustBegin()
+ tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net")
+ tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net")
+ tx.MustExec("INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)", "United States", "New York", "1")
+ tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Hong Kong", "852")
+ tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Singapore", "65")
+ // Named queries can use structs, so if you have an existing struct (i.e. person := &Person{}) that you have populated, you can pass it in as &person
+ tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &Person{"Jane", "Citizen", "jane.citzen@example.com"})
+ tx.Commit()
+
+ // Query the database, storing results in a []Person (wrapped in []interface{})
+ people := []Person{}
+ db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
+ jason, john := people[0], people[1]
+
+ fmt.Printf("%#v\n%#v", jason, john)
+ // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
+ // Person{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"}
+
+ // You can also get a single result, a la QueryRow
+ jason = Person{}
+ err = db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason")
+ fmt.Printf("%#v\n", jason)
+ // Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
+
+ // if you have null fields and use SELECT *, you must use sql.Null* in your struct
+ places := []Place{}
+ err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ usa, singsing, honkers := places[0], places[1], places[2]
+
+ fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers)
+ // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
+ // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
+ // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
+
+ // Loop through rows using only one struct
+ place := Place{}
+ rows, err := db.Queryx("SELECT * FROM place")
+ for rows.Next() {
+ err := rows.StructScan(&place)
+ if err != nil {
+ log.Fatalln(err)
+ }
+ fmt.Printf("%#v\n", place)
+ }
+ // Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
+ // Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
+ // Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
+
+ // Named queries, using `:name` as the bindvar. Automatic bindvar support
+ // which takes into account the dbtype based on the driverName on sqlx.Open/Connect
+ _, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`,
+ map[string]interface{}{
+ "first": "Bin",
+ "last": "Smuth",
+ "email": "bensmith@allblacks.nz",
+ })
+
+ // Selects Mr. Smith from the database
+ rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"})
+
+ // Named queries can also use structs. Their bind names follow the same rules
+ // as the name -> db mapping, so struct fields are lowercased and the `db` tag
+ // is taken into consideration.
+ rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
+}
+```
+
+## Scannability
+
+Get and Select are able to take base types, so the following is now possible:
+
+```go
+var name string
+db.Get(&name, "SELECT first_name FROM person WHERE id=$1", 10)
+
+var ids []int64
+db.Select(&ids, "SELECT id FROM person LIMIT 20;")
+```
+
+This can get complicated with destination types which are structs, like `sql.NullString`. Because of this, straightforward rules for *scannability* had to be developed. Iff something is "Scannable", then it is used directly in `rows.Scan`; if it's not, then the standard sqlx struct rules apply.
+
+Something is scannable if any of the following are true:
+
+* It is not a struct, ie. `reflect.ValueOf(v).Kind() != reflect.Struct`
+* It implements the `sql.Scanner` interface
+* It has no exported fields (eg. `time.Time`)
+
+## embedded structs
+
+Scan targets obey Go attribute rules directly, including nested embedded structs. Older versions of sqlx would attempt to also descend into non-embedded structs, but this is no longer supported.
+
+Go makes *accessing* '[ambiguous selectors](http://play.golang.org/p/MGRxdjLaUc)' a compile time error, defining structs with ambiguous selectors is legal. Sqlx will decide which field to use on a struct based on a breadth first search of the struct and any structs it embeds, as specified by the order of the fields as accessible by `reflect`, which generally means in source-order. This means that sqlx chooses the outer-most, top-most matching name for targets, even when the selector might technically be ambiguous.
+
+## scan safety
+
+By default, scanning into structs requires the structs to have fields for all of the
+columns in the query. This was done for a few reasons:
+
+* A mistake in naming during development could lead you to believe that data is
+ being written to a field when actually it can't be found and it is being dropped
+* This behavior mirrors the behavior of the Go compiler with respect to unused
+ variables
+* Selecting more data than you need is wasteful (more data on the wire, more time
+ marshalling, etc)
+
+Unlike Marshallers in the stdlib, the programmer scanning an sql result into a struct
+will generally have a full understanding of what the underlying data model is *and*
+full control over the SQL statement.
+
+Despite this, there are use cases where it's convenient to be able to ignore unknown
+columns. In most of these cases, you might be better off with `ScanSlice`, but where
+you want to still use structs, there is now the `Unsafe` method. Its usage is most
+simply shown in an example:
+
+```go
+ db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ type Person {
+ Name string
+ }
+ var p Person
+
+ // This fails, because there is no destination for location in Person
+ err = db.Get(&p, "SELECT name, location FROM person LIMIT 1")
+
+ udb := db.Unsafe()
+
+ // This succeeds and just sets `Name` in the p struct
+ err = udb.Get(&p, "SELECT name, location FROM person LIMIT 1")
+```
+
+The `Unsafe` method is implemented on `Tx`, `DB`, and `Stmt`. When you use an unsafe
+`Tx` or `DB` to create a new `Tx` or `Stmt`, those inherit its lack of safety.
+
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/bind.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/bind.go
new file mode 100644
index 0000000..2f1ec2b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/bind.go
@@ -0,0 +1,84 @@
+package sqlx
+
+import (
+ "bytes"
+ "strconv"
+)
+
+// Bindvar types supported by Rebind, BindMap and BindStruct.
+const (
+ UNKNOWN = iota
+ QUESTION
+ DOLLAR
+ NAMED
+)
+
+// BindType returns the bindtype for a given database given a drivername.
+func BindType(driverName string) int {
+ switch driverName {
+ case "postgres", "pgx":
+ return DOLLAR
+ case "mysql":
+ return QUESTION
+ case "sqlite3":
+ return QUESTION
+ case "oci8":
+ return NAMED
+ }
+ return UNKNOWN
+}
+
+// FIXME: this should be able to be tolerant of escaped ?'s in queries without
+// losing much speed, and should be to avoid confusion.
+
+// FIXME: this is now produces the wrong results for oracle's NAMED bindtype
+
+// Rebind a query from the default bindtype (QUESTION) to the target bindtype.
+func Rebind(bindType int, query string) string {
+ if bindType != DOLLAR {
+ return query
+ }
+
+ qb := []byte(query)
+ // Add space enough for 10 params before we have to allocate
+ rqb := make([]byte, 0, len(qb)+10)
+ j := 1
+ for _, b := range qb {
+ if b == '?' {
+ rqb = append(rqb, '$')
+ for _, b := range strconv.Itoa(j) {
+ rqb = append(rqb, byte(b))
+ }
+ j++
+ } else {
+ rqb = append(rqb, b)
+ }
+ }
+ return string(rqb)
+}
+
+// Experimental implementation of Rebind which uses a bytes.Buffer. The code is
+// much simpler and should be more resistant to odd unicode, but it is twice as
+// slow. Kept here for benchmarking purposes and to possibly replace Rebind if
+// problems arise with its somewhat naive handling of unicode.
+
+func rebindBuff(bindType int, query string) string {
+ if bindType != DOLLAR {
+ return query
+ }
+
+ b := make([]byte, 0, len(query))
+ rqb := bytes.NewBuffer(b)
+ j := 1
+ for _, r := range query {
+ if r == '?' {
+ rqb.WriteRune('$')
+ rqb.WriteString(strconv.Itoa(j))
+ j++
+ } else {
+ rqb.WriteRune(r)
+ }
+ }
+
+ return rqb.String()
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/doc.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/doc.go
new file mode 100644
index 0000000..e2b4e60
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/doc.go
@@ -0,0 +1,12 @@
+// Package sqlx provides general purpose extensions to database/sql.
+//
+// It is intended to seamlessly wrap database/sql and provide convenience
+// methods which are useful in the development of database driven applications.
+// None of the underlying database/sql methods are changed. Instead all extended
+// behavior is implemented through new methods defined on wrapper types.
+//
+// Additions include scanning into structs, named query support, rebinding
+// queries for different drivers, convenient shorthands for common error handling
+// and more.
+//
+package sqlx
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/named.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/named.go
new file mode 100644
index 0000000..d753518
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/named.go
@@ -0,0 +1,321 @@
+package sqlx
+
+// Named Query Support
+//
+// * BindMap - bind query bindvars to map/struct args
+// * NamedExec, NamedQuery - named query w/ struct or map
+// * NamedStmt - a pre-compiled named query which is a prepared statement
+//
+// Internal Interfaces:
+//
+// * compileNamedQuery - rebind a named query, returning a query and list of names
+// * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist
+//
+import (
+ "database/sql"
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+ "unicode"
+
+ "github.com/jmoiron/sqlx/reflectx"
+)
+
+// NamedStmt is a prepared statement that executes named queries. Prepare it
+// how you would execute a NamedQuery, but pass in a struct or map when executing.
+type NamedStmt struct {
+ Params []string
+ QueryString string
+ Stmt *Stmt
+}
+
+// Close closes the named statement.
+func (n *NamedStmt) Close() error {
+ return n.Stmt.Close()
+}
+
+// Exec executes a named statement using the struct passed.
+func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
+ args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
+ if err != nil {
+ return *new(sql.Result), err
+ }
+ return n.Stmt.Exec(args...)
+}
+
+// Query executes a named statement using the struct argument, returning rows.
+func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
+ args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
+ if err != nil {
+ return nil, err
+ }
+ return n.Stmt.Query(args...)
+}
+
+// QueryRow executes a named statement against the database. Because sqlx cannot
+// create a *sql.Row with an error condition pre-set for binding errors, sqlx
+// returns a *sqlx.Row instead.
+func (n *NamedStmt) QueryRow(arg interface{}) *Row {
+ args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
+ if err != nil {
+ return &Row{err: err}
+ }
+ return n.Stmt.QueryRowx(args...)
+}
+
+// MustExec execs a NamedStmt, panicing on error
+func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
+ res, err := n.Exec(arg)
+ if err != nil {
+ panic(err)
+ }
+ return res
+}
+
+// Queryx using this NamedStmt
+func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
+ r, err := n.Query(arg)
+ if err != nil {
+ return nil, err
+ }
+ return &Rows{Rows: r, Mapper: n.Stmt.Mapper}, err
+}
+
+// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is
+// an alias for QueryRow.
+func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
+ return n.QueryRow(arg)
+}
+
+// Select using this NamedStmt
+func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
+ rows, err := n.Query(arg)
+ if err != nil {
+ return err
+ }
+ // if something happens here, we want to make sure the rows are Closed
+ defer rows.Close()
+ return scanAll(rows, dest, false)
+}
+
+// Get using this NamedStmt
+func (n *NamedStmt) Get(dest interface{}, arg interface{}) error {
+ r := n.QueryRowx(arg)
+ return r.scanAny(dest, false)
+}
+
+// A union interface of preparer and binder, required to be able to prepare
+// named statements (as the bindtype must be determined).
+type namedPreparer interface {
+ Preparer
+ binder
+}
+
+func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) {
+ bindType := BindType(p.DriverName())
+ q, args, err := compileNamedQuery([]byte(query), bindType)
+ if err != nil {
+ return nil, err
+ }
+ stmt, err := Preparex(p, q)
+ if err != nil {
+ return nil, err
+ }
+ return &NamedStmt{
+ QueryString: q,
+ Params: args,
+ Stmt: stmt,
+ }, nil
+}
+
+func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
+ if maparg, ok := arg.(map[string]interface{}); ok {
+ return bindMapArgs(names, maparg)
+ }
+ return bindArgs(names, arg, m)
+}
+
+// private interface to generate a list of interfaces from a given struct
+// type, given a list of names to pull out of the struct. Used by public
+// BindStruct interface.
+func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
+ arglist := make([]interface{}, 0, len(names))
+
+ // grab the indirected value of arg
+ v := reflect.ValueOf(arg)
+ for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
+ v = v.Elem()
+ }
+
+ fields := m.TraversalsByName(v.Type(), names)
+ for i, t := range fields {
+ if len(t) == 0 {
+ return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg)
+ }
+ val := reflectx.FieldByIndexesReadOnly(v, t)
+ arglist = append(arglist, val.Interface())
+ }
+
+ return arglist, nil
+}
+
+// like bindArgs, but for maps.
+func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, error) {
+ arglist := make([]interface{}, 0, len(names))
+
+ for _, name := range names {
+ val, ok := arg[name]
+ if !ok {
+ return arglist, fmt.Errorf("could not find name %s in %#v", name, arg)
+ }
+ arglist = append(arglist, val)
+ }
+ return arglist, nil
+}
+
+// bindStruct binds a named parameter query with fields from a struct argument.
+// The rules for binding field names to parameter names follow the same
+// conventions as for StructScan, including obeying the `db` struct tags.
+func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
+ bound, names, err := compileNamedQuery([]byte(query), bindType)
+ if err != nil {
+ return "", []interface{}{}, err
+ }
+
+ arglist, err := bindArgs(names, arg, m)
+ if err != nil {
+ return "", []interface{}{}, err
+ }
+
+ return bound, arglist, nil
+}
+
+// bindMap binds a named parameter query with a map of arguments.
+func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) {
+ bound, names, err := compileNamedQuery([]byte(query), bindType)
+ if err != nil {
+ return "", []interface{}{}, err
+ }
+
+ arglist, err := bindMapArgs(names, args)
+ return bound, arglist, err
+}
+
+// -- Compilation of Named Queries
+
+// Allow digits and letters in bind params; additionally runes are
+// checked against underscores, meaning that bind params can have be
+// alphanumeric with underscores. Mind the difference between unicode
+// digits and numbers, where '5' is a digit but '五' is not.
+var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit}
+
+// FIXME: this function isn't safe for unicode named params, as a failing test
+// can testify. This is not a regression but a failure of the original code
+// as well. It should be modified to range over runes in a string rather than
+// bytes, even though this is less convenient and slower. Hopefully the
+// addition of the prepared NamedStmt (which will only do this once) will make
+// up for the slightly slower ad-hoc NamedExec/NamedQuery.
+
+// compile a NamedQuery into an unbound query (using the '?' bindvar) and
+// a list of names.
+func compileNamedQuery(qs []byte, bindType int) (query string, names []string, err error) {
+ names = make([]string, 0, 10)
+ rebound := make([]byte, 0, len(qs))
+
+ inName := false
+ last := len(qs) - 1
+ currentVar := 1
+ name := make([]byte, 0, 10)
+
+ for i, b := range qs {
+ // a ':' while we're in a name is an error
+ if b == ':' {
+ // if this is the second ':' in a '::' escape sequence, append a ':'
+ if inName && i > 0 && qs[i-1] == ':' {
+ rebound = append(rebound, ':')
+ inName = false
+ continue
+ } else if inName {
+ err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i))
+ return query, names, err
+ }
+ inName = true
+ name = []byte{}
+ // if we're in a name, and this is an allowed character, continue
+ } else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_') && i != last {
+ // append the byte to the name if we are in a name and not on the last byte
+ name = append(name, b)
+ // if we're in a name and it's not an allowed character, the name is done
+ } else if inName {
+ inName = false
+ // if this is the final byte of the string and it is part of the name, then
+ // make sure to add it to the name
+ if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) {
+ name = append(name, b)
+ }
+ // add the string representation to the names list
+ names = append(names, string(name))
+ // add a proper bindvar for the bindType
+ switch bindType {
+ // oracle only supports named type bind vars even for positional
+ case NAMED:
+ rebound = append(rebound, ':')
+ rebound = append(rebound, name...)
+ case QUESTION, UNKNOWN:
+ rebound = append(rebound, '?')
+ case DOLLAR:
+ rebound = append(rebound, '$')
+ for _, b := range strconv.Itoa(currentVar) {
+ rebound = append(rebound, byte(b))
+ }
+ currentVar++
+ }
+ // add this byte to string unless it was not part of the name
+ if i != last {
+ rebound = append(rebound, b)
+ } else if !unicode.IsOneOf(allowedBindRunes, rune(b)) {
+ rebound = append(rebound, b)
+ }
+ } else {
+ // this is a normal byte and should just go onto the rebound query
+ rebound = append(rebound, b)
+ }
+ }
+
+ return string(rebound), names, err
+}
+
+// Bind binds a struct or a map to a query with named parameters.
+func BindNamed(bindType int, query string, arg interface{}) (string, []interface{}, error) {
+ return bindNamedMapper(bindType, query, arg, mapper())
+}
+
+func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
+ if maparg, ok := arg.(map[string]interface{}); ok {
+ return bindMap(bindType, query, maparg)
+ }
+ return bindStruct(bindType, query, arg, m)
+}
+
+// NamedQuery binds a named query and then runs Query on the result using the
+// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
+// map[string]interface{} types.
+func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) {
+ q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
+ if err != nil {
+ return nil, err
+ }
+ return e.Queryx(q, args...)
+}
+
+// NamedExec uses BindStruct to get a query executable by the driver and
+// then runs Exec on the result. Returns an error from the binding
+// or the query excution itself.
+func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) {
+ q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
+ if err != nil {
+ return nil, err
+ }
+ return e.Exec(q, args...)
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/named_test.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/named_test.go
new file mode 100644
index 0000000..d3459a8
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/named_test.go
@@ -0,0 +1,227 @@
+package sqlx
+
+import (
+ "database/sql"
+ "testing"
+)
+
+func TestCompileQuery(t *testing.T) {
+ table := []struct {
+ Q, R, D, N string
+ V []string
+ }{
+ // basic test for named parameters, invalid char ',' terminating
+ {
+ Q: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`,
+ R: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`,
+ D: `INSERT INTO foo (a,b,c,d) VALUES ($1, $2, $3, $4)`,
+ N: `INSERT INTO foo (a,b,c,d) VALUES (:name, :age, :first, :last)`,
+ V: []string{"name", "age", "first", "last"},
+ },
+ // This query tests a named parameter ending the string as well as numbers
+ {
+ Q: `SELECT * FROM a WHERE first_name=:name1 AND last_name=:name2`,
+ R: `SELECT * FROM a WHERE first_name=? AND last_name=?`,
+ D: `SELECT * FROM a WHERE first_name=$1 AND last_name=$2`,
+ N: `SELECT * FROM a WHERE first_name=:name1 AND last_name=:name2`,
+ V: []string{"name1", "name2"},
+ },
+ {
+ Q: `SELECT "::foo" FROM a WHERE first_name=:name1 AND last_name=:name2`,
+ R: `SELECT ":foo" FROM a WHERE first_name=? AND last_name=?`,
+ D: `SELECT ":foo" FROM a WHERE first_name=$1 AND last_name=$2`,
+ N: `SELECT ":foo" FROM a WHERE first_name=:name1 AND last_name=:name2`,
+ V: []string{"name1", "name2"},
+ },
+ {
+ Q: `SELECT 'a::b::c' || first_name, '::::ABC::_::' FROM person WHERE first_name=:first_name AND last_name=:last_name`,
+ R: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=? AND last_name=?`,
+ D: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=$1 AND last_name=$2`,
+ N: `SELECT 'a:b:c' || first_name, '::ABC:_:' FROM person WHERE first_name=:first_name AND last_name=:last_name`,
+ V: []string{"first_name", "last_name"},
+ },
+ /* This unicode awareness test sadly fails, because of our byte-wise worldview.
+ * We could certainly iterate by Rune instead, though it's a great deal slower,
+ * it's probably the RightWay(tm)
+ {
+ Q: `INSERT INTO foo (a,b,c,d) VALUES (:ã‚, :b, :ã‚コ, :åå‰)`,
+ R: `INSERT INTO foo (a,b,c,d) VALUES (?, ?, ?, ?)`,
+ D: `INSERT INTO foo (a,b,c,d) VALUES ($1, $2, $3, $4)`,
+ N: []string{"name", "age", "first", "last"},
+ },
+ */
+ }
+
+ for _, test := range table {
+ qr, names, err := compileNamedQuery([]byte(test.Q), QUESTION)
+ if err != nil {
+ t.Error(err)
+ }
+ if qr != test.R {
+ t.Errorf("expected %s, got %s", test.R, qr)
+ }
+ if len(names) != len(test.V) {
+ t.Errorf("expected %#v, got %#v", test.V, names)
+ } else {
+ for i, name := range names {
+ if name != test.V[i] {
+ t.Errorf("expected %dth name to be %s, got %s", i+1, test.V[i], name)
+ }
+ }
+ }
+ qd, _, _ := compileNamedQuery([]byte(test.Q), DOLLAR)
+ if qd != test.D {
+ t.Errorf("\nexpected: `%s`\ngot: `%s`", test.D, qd)
+ }
+
+ qq, _, _ := compileNamedQuery([]byte(test.Q), NAMED)
+ if qq != test.N {
+ t.Errorf("\nexpected: `%s`\ngot: `%s`\n(len: %d vs %d)", test.N, qq, len(test.N), len(qq))
+ }
+ }
+}
+
+type Test struct {
+ t *testing.T
+}
+
+func (t Test) Error(err error, msg ...interface{}) {
+ if err != nil {
+ if len(msg) == 0 {
+ t.t.Error(err)
+ } else {
+ t.t.Error(msg...)
+ }
+ }
+}
+
+func (t Test) Errorf(err error, format string, args ...interface{}) {
+ if err != nil {
+ t.t.Errorf(format, args...)
+ }
+}
+
+func TestNamedQueries(t *testing.T) {
+ RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
+ loadDefaultFixture(db, t)
+ test := Test{t}
+ var ns *NamedStmt
+ var err error
+
+ // Check that invalid preparations fail
+ ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first:name")
+ if err == nil {
+ t.Error("Expected an error with invalid prepared statement.")
+ }
+
+ ns, err = db.PrepareNamed("invalid sql")
+ if err == nil {
+ t.Error("Expected an error with invalid prepared statement.")
+ }
+
+ // Check closing works as anticipated
+ ns, err = db.PrepareNamed("SELECT * FROM person WHERE first_name=:first_name")
+ test.Error(err)
+ err = ns.Close()
+ test.Error(err)
+
+ ns, err = db.PrepareNamed(`
+ SELECT first_name, last_name, email
+ FROM person WHERE first_name=:first_name AND email=:email`)
+ test.Error(err)
+
+ // test Queryx w/ uses Query
+ p := Person{FirstName: "Jason", LastName: "Moiron", Email: "jmoiron@jmoiron.net"}
+
+ rows, err := ns.Queryx(p)
+ test.Error(err)
+ for rows.Next() {
+ var p2 Person
+ rows.StructScan(&p2)
+ if p.FirstName != p2.FirstName {
+ t.Errorf("got %s, expected %s", p.FirstName, p2.FirstName)
+ }
+ if p.LastName != p2.LastName {
+ t.Errorf("got %s, expected %s", p.LastName, p2.LastName)
+ }
+ if p.Email != p2.Email {
+ t.Errorf("got %s, expected %s", p.Email, p2.Email)
+ }
+ }
+
+ // test Select
+ people := make([]Person, 0, 5)
+ err = ns.Select(&people, p)
+ test.Error(err)
+
+ if len(people) != 1 {
+ t.Errorf("got %d results, expected %d", len(people), 1)
+ }
+ if p.FirstName != people[0].FirstName {
+ t.Errorf("got %s, expected %s", p.FirstName, people[0].FirstName)
+ }
+ if p.LastName != people[0].LastName {
+ t.Errorf("got %s, expected %s", p.LastName, people[0].LastName)
+ }
+ if p.Email != people[0].Email {
+ t.Errorf("got %s, expected %s", p.Email, people[0].Email)
+ }
+
+ // test Exec
+ ns, err = db.PrepareNamed(`
+ INSERT INTO person (first_name, last_name, email)
+ VALUES (:first_name, :last_name, :email)`)
+ test.Error(err)
+
+ js := Person{
+ FirstName: "Julien",
+ LastName: "Savea",
+ Email: "jsavea@ab.co.nz",
+ }
+ _, err = ns.Exec(js)
+ test.Error(err)
+
+ // Make sure we can pull him out again
+ p2 := Person{}
+ db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), js.Email)
+ if p2.Email != js.Email {
+ t.Errorf("expected %s, got %s", js.Email, p2.Email)
+ }
+
+ // test Txn NamedStmts
+ tx := db.MustBegin()
+ txns := tx.NamedStmt(ns)
+
+ // We're going to add Steven in this txn
+ sl := Person{
+ FirstName: "Steven",
+ LastName: "Luatua",
+ Email: "sluatua@ab.co.nz",
+ }
+
+ _, err = txns.Exec(sl)
+ test.Error(err)
+ // then rollback...
+ tx.Rollback()
+ // looking for Steven after a rollback should fail
+ err = db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email)
+ if err != sql.ErrNoRows {
+ t.Errorf("expected no rows error, got %v", err)
+ }
+
+ // now do the same, but commit
+ tx = db.MustBegin()
+ txns = tx.NamedStmt(ns)
+ _, err = txns.Exec(sl)
+ test.Error(err)
+ tx.Commit()
+
+ // looking for Steven after a Commit should succeed
+ err = db.Get(&p2, db.Rebind("SELECT * FROM person WHERE email=?"), sl.Email)
+ test.Error(err)
+ if p2.Email != sl.Email {
+ t.Errorf("expected %s, got %s", sl.Email, p2.Email)
+ }
+
+ })
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/README.md b/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/README.md
new file mode 100644
index 0000000..76f1b5d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/README.md
@@ -0,0 +1,17 @@
+# reflectx
+
+The sqlx package has special reflect needs. In particular, it needs to:
+
+* be able to map a name to a field
+* understand embedded structs
+* understand mapping names to fields by a particular tag
+* user specified name -> field mapping functions
+
+These behaviors mimic the behaviors by the standard library marshallers and also the
+behavior of standard Go accessors.
+
+The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is
+addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct
+tags in the ways that are vital to most marshalers, and they are slow.
+
+This reflectx package extends reflect to achieve these goals.
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/reflect.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/reflect.go
new file mode 100644
index 0000000..d49b7bc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/reflect.go
@@ -0,0 +1,268 @@
+// Package reflect implements extensions to the standard reflect lib suitable
+// for implementing marshaling and unmarshaling packages. The main Mapper type
+// allows for Go-compatible named atribute access, including accessing embedded
+// struct attributes and the ability to use functions and struct tags to
+// customize field names.
+//
+package reflectx
+
+import "sync"
+
+import (
+ "reflect"
+ "runtime"
+)
+
+type fieldMap map[string][]int
+
+// Mapper is a general purpose mapper of names to struct fields. A Mapper
+// behaves like most marshallers, optionally obeying a field tag for name
+// mapping and a function to provide a basic mapping of fields to names.
+type Mapper struct {
+ cache map[reflect.Type]fieldMap
+ tagName string
+ tagMapFunc func(string) string
+ mapFunc func(string) string
+ mutex sync.Mutex
+}
+
+// NewMapper returns a new mapper which optionally obeys the field tag given
+// by tagName. If tagName is the empty string, it is ignored.
+func NewMapper(tagName string) *Mapper {
+ return &Mapper{
+ cache: make(map[reflect.Type]fieldMap),
+ tagName: tagName,
+ }
+}
+
+// NewMapperTagFunc returns a new mapper which contains a mapper for field names
+// AND a mapper for tag values. This is useful for tags like json which can
+// have values like "name,omitempty".
+func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
+ return &Mapper{
+ cache: make(map[reflect.Type]fieldMap),
+ tagName: tagName,
+ mapFunc: mapFunc,
+ tagMapFunc: tagMapFunc,
+ }
+}
+
+// NewMapperFunc returns a new mapper which optionally obeys a field tag and
+// a struct field name mapper func given by f. Tags will take precedence, but
+// for any other field, the mapped name will be f(field.Name)
+func NewMapperFunc(tagName string, f func(string) string) *Mapper {
+ return &Mapper{
+ cache: make(map[reflect.Type]fieldMap),
+ tagName: tagName,
+ mapFunc: f,
+ }
+}
+
+// TypeMap returns a mapping of field strings to int slices representing
+// the traversal down the struct to reach the field.
+func (m *Mapper) TypeMap(t reflect.Type) fieldMap {
+ m.mutex.Lock()
+ mapping, ok := m.cache[t]
+ if !ok {
+ mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
+ m.cache[t] = mapping
+ }
+ m.mutex.Unlock()
+ return mapping
+}
+
+// FieldMap returns the mapper's mapping of field names to reflect values. Panics
+// if v's Kind is not Struct, or v is not Indirectable to a struct kind.
+func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
+ v = reflect.Indirect(v)
+ mustBe(v, reflect.Struct)
+
+ r := map[string]reflect.Value{}
+ nm := m.TypeMap(v.Type())
+ for tagName, indexes := range nm {
+ r[tagName] = FieldByIndexes(v, indexes)
+ }
+ return r
+}
+
+// FieldByName returns a field by the its mapped name as a reflect.Value.
+// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
+// Returns zero Value if the name is not found.
+func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
+ v = reflect.Indirect(v)
+ mustBe(v, reflect.Struct)
+
+ nm := m.TypeMap(v.Type())
+ traversal, ok := nm[name]
+ if !ok {
+ return *new(reflect.Value)
+ }
+ return FieldByIndexes(v, traversal)
+}
+
+// FieldsByName returns a slice of values corresponding to the slice of names
+// for the value. Panics if v's Kind is not Struct or v is not Indirectable
+// to a struct Kind. Returns zero Value for each name not found.
+func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
+ v = reflect.Indirect(v)
+ mustBe(v, reflect.Struct)
+
+ nm := m.TypeMap(v.Type())
+
+ vals := make([]reflect.Value, 0, len(names))
+ for _, name := range names {
+ traversal, ok := nm[name]
+ if !ok {
+ vals = append(vals, *new(reflect.Value))
+ } else {
+ vals = append(vals, FieldByIndexes(v, traversal))
+ }
+ }
+ return vals
+}
+
+// Traversals by name returns a slice of int slices which represent the struct
+// traversals for each mapped name. Panics if t is not a struct or Indirectable
+// to a struct. Returns empty int slice for each name not found.
+func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
+ t = Deref(t)
+ mustBe(t, reflect.Struct)
+ nm := m.TypeMap(t)
+
+ r := make([][]int, 0, len(names))
+ for _, name := range names {
+ traversal, ok := nm[name]
+ if !ok {
+ r = append(r, []int{})
+ } else {
+ r = append(r, traversal)
+ }
+ }
+ return r
+}
+
+// FieldByIndexes returns a value for a particular struct traversal.
+func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
+ for _, i := range indexes {
+ v = reflect.Indirect(v).Field(i)
+ // if this is a pointer, it's possible it is nil
+ if v.Kind() == reflect.Ptr && v.IsNil() {
+ alloc := reflect.New(Deref(v.Type()))
+ v.Set(alloc)
+ }
+ if v.Kind() == reflect.Map && v.IsNil() {
+ v.Set(reflect.MakeMap(v.Type()))
+ }
+ }
+ return v
+}
+
+// FieldByIndexesReadOnly returns a value for a particular struct traversal,
+// but is not concerned with allocating nil pointers because the value is
+// going to be used for reading and not setting.
+func FieldByIndexesReadOnly(v reflect.Value, indexes []int) reflect.Value {
+ for _, i := range indexes {
+ v = reflect.Indirect(v).Field(i)
+ }
+ return v
+}
+
+// Deref is Indirect for reflect.Types
+func Deref(t reflect.Type) reflect.Type {
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ return t
+}
+
+// -- helpers & utilities --
+
+type Kinder interface {
+ Kind() reflect.Kind
+}
+
+// mustBe checks a value against a kind, panicing with a reflect.ValueError
+// if the kind isn't that which is required.
+func mustBe(v Kinder, expected reflect.Kind) {
+ k := v.Kind()
+ if k != expected {
+ panic(&reflect.ValueError{Method: methodName(), Kind: k})
+ }
+}
+
+// methodName is returns the caller of the function calling methodName
+func methodName() string {
+ pc, _, _, _ := runtime.Caller(2)
+ f := runtime.FuncForPC(pc)
+ if f == nil {
+ return "unknown method"
+ }
+ return f.Name()
+}
+
+type typeQueue struct {
+ t reflect.Type
+ p []int
+}
+
+// A copying append that creates a new slice each time.
+func apnd(is []int, i int) []int {
+ x := make([]int, len(is)+1)
+ for p, n := range is {
+ x[p] = n
+ }
+ x[len(x)-1] = i
+ return x
+}
+
+// getMapping returns a mapping for the t type, using the tagName, mapFunc and
+// tagMapFunc to determine the canonical names of fields.
+func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc func(string) string) fieldMap {
+ queue := []typeQueue{}
+ queue = append(queue, typeQueue{Deref(t), []int{}})
+ m := fieldMap{}
+ for len(queue) != 0 {
+ // pop the first item off of the queue
+ tq := queue[0]
+ queue = queue[1:]
+ // iterate through all of its fields
+ for fieldPos := 0; fieldPos < tq.t.NumField(); fieldPos++ {
+ f := tq.t.Field(fieldPos)
+
+ name := f.Tag.Get(tagName)
+ if len(name) == 0 {
+ if mapFunc != nil {
+ name = mapFunc(f.Name)
+ } else {
+ name = f.Name
+ }
+ } else if tagMapFunc != nil {
+ name = tagMapFunc(name)
+ }
+
+ // if the name is "-", disabled via a tag, skip it
+ if name == "-" {
+ continue
+ }
+
+ // skip unexported fields
+ if len(f.PkgPath) != 0 {
+ continue
+ }
+
+ // bfs search of anonymous embedded structs
+ if f.Anonymous {
+ queue = append(queue, typeQueue{Deref(f.Type), apnd(tq.p, fieldPos)})
+ continue
+ }
+
+ // if the name is shadowed by an earlier identical name in the search, skip it
+ if _, ok := m[name]; ok {
+ continue
+ }
+ // add it to the map at the current position
+ m[name] = apnd(tq.p, fieldPos)
+ }
+ }
+ return m
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/reflect_test.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/reflect_test.go
new file mode 100644
index 0000000..1fcba66
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/reflectx/reflect_test.go
@@ -0,0 +1,239 @@
+package reflectx
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func ival(v reflect.Value) int {
+ return v.Interface().(int)
+}
+
+func TestBasic(t *testing.T) {
+ type Foo struct {
+ A int
+ B int
+ C int
+ }
+
+ f := Foo{1, 2, 3}
+ fv := reflect.ValueOf(f)
+ m := NewMapper("")
+
+ v := m.FieldByName(fv, "A")
+ if ival(v) != f.A {
+ t.Errorf("Expecting %d, got %d", ival(v), f.A)
+ }
+ v = m.FieldByName(fv, "B")
+ if ival(v) != f.B {
+ t.Errorf("Expecting %d, got %d", f.B, ival(v))
+ }
+ v = m.FieldByName(fv, "C")
+ if ival(v) != f.C {
+ t.Errorf("Expecting %d, got %d", f.C, ival(v))
+ }
+}
+
+func TestEmbedded(t *testing.T) {
+ type Foo struct {
+ A int
+ }
+
+ type Bar struct {
+ Foo
+ B int
+ }
+
+ type Baz struct {
+ A int
+ Bar
+ }
+
+ m := NewMapper("")
+
+ z := Baz{}
+ z.A = 1
+ z.B = 2
+ z.Bar.Foo.A = 3
+ zv := reflect.ValueOf(z)
+
+ v := m.FieldByName(zv, "A")
+ if ival(v) != z.A {
+ t.Errorf("Expecting %d, got %d", ival(v), z.A)
+ }
+ v = m.FieldByName(zv, "B")
+ if ival(v) != z.B {
+ t.Errorf("Expecting %d, got %d", ival(v), z.B)
+ }
+}
+
+func TestTagNameMapping(t *testing.T) {
+ type Strategy struct {
+ StrategyId string `protobuf:"bytes,1,opt,name=strategy_id" json:"strategy_id,omitempty"`
+ StrategyName string
+ }
+
+ m := NewMapperTagFunc("json", strings.ToUpper, func(value string) string {
+ if strings.Contains(value, ",") {
+ return strings.Split(value, ",")[0]
+ } else {
+ return value
+ }
+ })
+ strategy := Strategy{"1", "Alpah"}
+ mapping := m.TypeMap(reflect.TypeOf(strategy))
+
+ for _, key := range []string{"strategy_id", "STRATEGYNAME"} {
+ if _, ok := mapping[key]; !ok {
+ t.Errorf("Expecting to find key %s in mapping but did not.", key)
+ }
+ }
+}
+
+func TestMapping(t *testing.T) {
+ type Person struct {
+ ID int
+ Name string
+ WearsGlasses bool `db:"wears_glasses"`
+ }
+
+ m := NewMapperFunc("db", strings.ToLower)
+ p := Person{1, "Jason", true}
+ mapping := m.TypeMap(reflect.TypeOf(p))
+
+ for _, key := range []string{"id", "name", "wears_glasses"} {
+ if _, ok := mapping[key]; !ok {
+ t.Errorf("Expecting to find key %s in mapping but did not.", key)
+ }
+ }
+
+ type SportsPerson struct {
+ Weight int
+ Age int
+ Person
+ }
+ s := SportsPerson{Weight: 100, Age: 30, Person: p}
+ mapping = m.TypeMap(reflect.TypeOf(s))
+ for _, key := range []string{"id", "name", "wears_glasses", "weight", "age"} {
+ if _, ok := mapping[key]; !ok {
+ t.Errorf("Expecting to find key %s in mapping but did not.", key)
+ }
+
+ }
+
+ type RugbyPlayer struct {
+ Position int
+ IsIntense bool `db:"is_intense"`
+ IsAllBlack bool `db:"-"`
+ SportsPerson
+ }
+ r := RugbyPlayer{12, true, false, s}
+ mapping = m.TypeMap(reflect.TypeOf(r))
+ for _, key := range []string{"id", "name", "wears_glasses", "weight", "age", "position", "is_intense"} {
+ if _, ok := mapping[key]; !ok {
+ t.Errorf("Expecting to find key %s in mapping but did not.", key)
+ }
+ }
+
+ if _, ok := mapping["isallblack"]; ok {
+ t.Errorf("Expecting to ignore `IsAllBlack` field")
+ }
+
+ type EmbeddedLiteral struct {
+ Embedded struct {
+ Person string
+ Position int
+ }
+ IsIntense bool
+ }
+
+ e := EmbeddedLiteral{}
+ mapping = m.TypeMap(reflect.TypeOf(e))
+ //fmt.Printf("Mapping: %#v\n", mapping)
+
+ //f := FieldByIndexes(reflect.ValueOf(e), mapping["isintense"])
+ //fmt.Println(f, f.Interface())
+
+ //tbn := m.TraversalsByName(reflect.TypeOf(e), []string{"isintense"})
+ //fmt.Printf("%#v\n", tbn)
+
+}
+
+type E1 struct {
+ A int
+}
+type E2 struct {
+ E1
+ B int
+}
+type E3 struct {
+ E2
+ C int
+}
+type E4 struct {
+ E3
+ D int
+}
+
+func BenchmarkFieldNameL1(b *testing.B) {
+ e4 := E4{D: 1}
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := v.FieldByName("D")
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkFieldNameL4(b *testing.B) {
+ e4 := E4{}
+ e4.A = 1
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := v.FieldByName("A")
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkFieldPosL1(b *testing.B) {
+ e4 := E4{D: 1}
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := v.Field(1)
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkFieldPosL4(b *testing.B) {
+ e4 := E4{}
+ e4.A = 1
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := v.Field(0)
+ f = f.Field(0)
+ f = f.Field(0)
+ f = f.Field(0)
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
+
+func BenchmarkFieldByIndexL4(b *testing.B) {
+ e4 := E4{}
+ e4.A = 1
+ idx := []int{0, 0, 0, 0}
+ for i := 0; i < b.N; i++ {
+ v := reflect.ValueOf(e4)
+ f := FieldByIndexes(v, idx)
+ if f.Interface().(int) != 1 {
+ b.Fatal("Wrong value.")
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/sqlx.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/sqlx.go
new file mode 100644
index 0000000..d195705
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/sqlx.go
@@ -0,0 +1,986 @@
+package sqlx
+
+import (
+ "database/sql"
+ "database/sql/driver"
+ "errors"
+ "fmt"
+
+ "io/ioutil"
+ "path/filepath"
+ "reflect"
+ "strings"
+
+ "github.com/jmoiron/sqlx/reflectx"
+)
+
+// Although the NameMapper is convenient, in practice it should not
+// be relied on except for application code. If you are writing a library
+// that uses sqlx, you should be aware that the name mappings you expect
+// can be overridded by your user's application.
+
+// NameMapper is used to map column names to struct field names. By default,
+// it uses strings.ToLower to lowercase struct field names. It can be set
+// to whatever you want, but it is encouraged to be set before sqlx is used
+// as name-to-field mappings are cached after first use on a type.
+var NameMapper = strings.ToLower
+var origMapper = reflect.ValueOf(NameMapper)
+
+// Rather than creating on init, this is created when necessary so that
+// importers have time to customize the NameMapper.
+var mpr *reflectx.Mapper
+
+// mapper returns a valid mapper using the configured NameMapper func.
+func mapper() *reflectx.Mapper {
+ if mpr == nil {
+ mpr = reflectx.NewMapperFunc("db", NameMapper)
+ } else if origMapper != reflect.ValueOf(NameMapper) {
+ // if NameMapper has changed, create a new mapper
+ mpr = reflectx.NewMapperFunc("db", NameMapper)
+ origMapper = reflect.ValueOf(NameMapper)
+ }
+ return mpr
+}
+
+// isScannable takes the reflect.Type and the actual dest value and returns
+// whether or not it's Scannable. Something is scannable if:
+// * it is not a struct
+// * it implements sql.Scanner
+// * it has no exported fields
+func isScannable(t reflect.Type) bool {
+ if reflect.PtrTo(t).Implements(_scannerInterface) {
+ return true
+ }
+ if t.Kind() != reflect.Struct {
+ return true
+ }
+
+ // it's not important that we use the right mapper for this particular object,
+ // we're only concerned on how many exported fields this struct has
+ m := mapper()
+ if len(m.TypeMap(t)) == 0 {
+ return true
+ }
+ return false
+}
+
+// ColScanner is an interface used by MapScan and SliceScan
+type ColScanner interface {
+ Columns() ([]string, error)
+ Scan(dest ...interface{}) error
+ Err() error
+}
+
+// Queryer is an interface used by Get and Select
+type Queryer interface {
+ Query(query string, args ...interface{}) (*sql.Rows, error)
+ Queryx(query string, args ...interface{}) (*Rows, error)
+ QueryRowx(query string, args ...interface{}) *Row
+}
+
+// Execer is an interface used by MustExec and LoadFile
+type Execer interface {
+ Exec(query string, args ...interface{}) (sql.Result, error)
+}
+
+// Binder is an interface for something which can bind queries (Tx, DB)
+type binder interface {
+ DriverName() string
+ Rebind(string) string
+ BindNamed(string, interface{}) (string, []interface{}, error)
+}
+
+// Ext is a union interface which can bind, query, and exec, used by
+// NamedQuery and NamedExec.
+type Ext interface {
+ binder
+ Queryer
+ Execer
+}
+
+// Preparer is an interface used by Preparex.
+type Preparer interface {
+ Prepare(query string) (*sql.Stmt, error)
+}
+
+// determine if any of our extensions are unsafe
+func isUnsafe(i interface{}) bool {
+ switch i.(type) {
+ case Row:
+ return i.(Row).unsafe
+ case *Row:
+ return i.(*Row).unsafe
+ case Rows:
+ return i.(Rows).unsafe
+ case *Rows:
+ return i.(*Rows).unsafe
+ case Stmt:
+ return i.(Stmt).unsafe
+ case qStmt:
+ return i.(qStmt).Stmt.unsafe
+ case *qStmt:
+ return i.(*qStmt).Stmt.unsafe
+ case DB:
+ return i.(DB).unsafe
+ case *DB:
+ return i.(*DB).unsafe
+ case Tx:
+ return i.(Tx).unsafe
+ case *Tx:
+ return i.(*Tx).unsafe
+ case sql.Rows, *sql.Rows:
+ return false
+ default:
+ return false
+ }
+}
+
+func mapperFor(i interface{}) *reflectx.Mapper {
+ switch i.(type) {
+ case DB:
+ return i.(DB).Mapper
+ case *DB:
+ return i.(*DB).Mapper
+ case Tx:
+ return i.(Tx).Mapper
+ case *Tx:
+ return i.(*Tx).Mapper
+ default:
+ return mapper()
+ }
+}
+
+var _scannerInterface = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
+var _valuerInterface = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
+
+// Row is a reimplementation of sql.Row in order to gain access to the underlying
+// sql.Rows.Columns() data, necessary for StructScan.
+type Row struct {
+ err error
+ unsafe bool
+ rows *sql.Rows
+ Mapper *reflectx.Mapper
+}
+
+// Scan is a fixed implementation of sql.Row.Scan, which does not discard the
+// underlying error from the internal rows object if it exists.
+func (r *Row) Scan(dest ...interface{}) error {
+ if r.err != nil {
+ return r.err
+ }
+
+ // TODO(bradfitz): for now we need to defensively clone all
+ // []byte that the driver returned (not permitting
+ // *RawBytes in Rows.Scan), since we're about to close
+ // the Rows in our defer, when we return from this function.
+ // the contract with the driver.Next(...) interface is that it
+ // can return slices into read-only temporary memory that's
+ // only valid until the next Scan/Close. But the TODO is that
+ // for a lot of drivers, this copy will be unnecessary. We
+ // should provide an optional interface for drivers to
+ // implement to say, "don't worry, the []bytes that I return
+ // from Next will not be modified again." (for instance, if
+ // they were obtained from the network anyway) But for now we
+ // don't care.
+ defer r.rows.Close()
+ for _, dp := range dest {
+ if _, ok := dp.(*sql.RawBytes); ok {
+ return errors.New("sql: RawBytes isn't allowed on Row.Scan")
+ }
+ }
+
+ if !r.rows.Next() {
+ if err := r.rows.Err(); err != nil {
+ return err
+ }
+ return sql.ErrNoRows
+ }
+ err := r.rows.Scan(dest...)
+ if err != nil {
+ return err
+ }
+ // Make sure the query can be processed to completion with no errors.
+ if err := r.rows.Close(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// Columns returns the underlying sql.Rows.Columns(), or the deferred error usually
+// returned by Row.Scan()
+func (r *Row) Columns() ([]string, error) {
+ if r.err != nil {
+ return []string{}, r.err
+ }
+ return r.rows.Columns()
+}
+
+// Err returns the error encountered while scanning.
+func (r *Row) Err() error {
+ return r.err
+}
+
+// DB is a wrapper around sql.DB which keeps track of the driverName upon Open,
+// used mostly to automatically bind named queries using the right bindvars.
+type DB struct {
+ *sql.DB
+ driverName string
+ unsafe bool
+ Mapper *reflectx.Mapper
+}
+
+// NewDb returns a new sqlx DB wrapper for a pre-existing *sql.DB. The
+// driverName of the original database is required for named query support.
+func NewDb(db *sql.DB, driverName string) *DB {
+ return &DB{DB: db, driverName: driverName, Mapper: mapper()}
+}
+
+// DriverName returns the driverName passed to the Open function for this DB.
+func (db *DB) DriverName() string {
+ return db.driverName
+}
+
+// Open is the same as sql.Open, but returns an *sqlx.DB instead.
+func Open(driverName, dataSourceName string) (*DB, error) {
+ db, err := sql.Open(driverName, dataSourceName)
+ if err != nil {
+ return nil, err
+ }
+ return &DB{DB: db, driverName: driverName, Mapper: mapper()}, err
+}
+
+// MustOpen is the same as sql.Open, but returns an *sqlx.DB instead and panics on error.
+func MustOpen(driverName, dataSourceName string) *DB {
+ db, err := Open(driverName, dataSourceName)
+ if err != nil {
+ panic(err)
+ }
+ return db
+}
+
+// MapperFunc sets a new mapper for this db using the default sqlx struct tag
+// and the provided mapper function.
+func (db *DB) MapperFunc(mf func(string) string) {
+ db.Mapper = reflectx.NewMapperFunc("db", mf)
+}
+
+// Rebind transforms a query from QUESTION to the DB driver's bindvar type.
+func (db *DB) Rebind(query string) string {
+ return Rebind(BindType(db.driverName), query)
+}
+
+// Unsafe returns a version of DB which will silently succeed to scan when
+// columns in the SQL result have no fields in the destination struct.
+// sqlx.Stmt and sqlx.Tx which are created from this DB will inherit its
+// safety behavior.
+func (db *DB) Unsafe() *DB {
+ return &DB{DB: db.DB, driverName: db.driverName, unsafe: true, Mapper: db.Mapper}
+}
+
+// BindNamed binds a query using the DB driver's bindvar type.
+func (db *DB) BindNamed(query string, arg interface{}) (string, []interface{}, error) {
+ return BindNamed(BindType(db.driverName), query, arg)
+}
+
+// NamedQuery using this DB.
+func (db *DB) NamedQuery(query string, arg interface{}) (*Rows, error) {
+ return NamedQuery(db, query, arg)
+}
+
+// NamedExec using this DB.
+func (db *DB) NamedExec(query string, arg interface{}) (sql.Result, error) {
+ return NamedExec(db, query, arg)
+}
+
+// Select using this DB.
+func (db *DB) Select(dest interface{}, query string, args ...interface{}) error {
+ return Select(db, dest, query, args...)
+}
+
+// Get using this DB.
+func (db *DB) Get(dest interface{}, query string, args ...interface{}) error {
+ return Get(db, dest, query, args...)
+}
+
+// MustBegin starts a transaction, and panics on error. Returns an *sqlx.Tx instead
+// of an *sql.Tx.
+func (db *DB) MustBegin() *Tx {
+ tx, err := db.Beginx()
+ if err != nil {
+ panic(err)
+ }
+ return tx
+}
+
+// Beginx begins a transaction and returns an *sqlx.Tx instead of an *sql.Tx.
+func (db *DB) Beginx() (*Tx, error) {
+ tx, err := db.DB.Begin()
+ if err != nil {
+ return nil, err
+ }
+ return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err
+}
+
+// Queryx queries the database and returns an *sqlx.Rows.
+func (db *DB) Queryx(query string, args ...interface{}) (*Rows, error) {
+ r, err := db.DB.Query(query, args...)
+ if err != nil {
+ return nil, err
+ }
+ return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err
+}
+
+// QueryRowx queries the database and returns an *sqlx.Row.
+func (db *DB) QueryRowx(query string, args ...interface{}) *Row {
+ rows, err := db.DB.Query(query, args...)
+ return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper}
+}
+
+// MustExec (panic) runs MustExec using this database.
+func (db *DB) MustExec(query string, args ...interface{}) sql.Result {
+ return MustExec(db, query, args...)
+}
+
+// Preparex returns an sqlx.Stmt instead of a sql.Stmt
+func (db *DB) Preparex(query string) (*Stmt, error) {
+ return Preparex(db, query)
+}
+
+// PrepareNamed returns an sqlx.NamedStmt
+func (db *DB) PrepareNamed(query string) (*NamedStmt, error) {
+ return prepareNamed(db, query)
+}
+
+// Tx is an sqlx wrapper around sql.Tx with extra functionality
+type Tx struct {
+ *sql.Tx
+ driverName string
+ unsafe bool
+ Mapper *reflectx.Mapper
+}
+
+// DriverName returns the driverName used by the DB which began this transaction.
+func (tx *Tx) DriverName() string {
+ return tx.driverName
+}
+
+// Rebind a query within a transaction's bindvar type.
+func (tx *Tx) Rebind(query string) string {
+ return Rebind(BindType(tx.driverName), query)
+}
+
+// Unsafe returns a version of Tx which will silently succeed to scan when
+// columns in the SQL result have no fields in the destination struct.
+func (tx *Tx) Unsafe() *Tx {
+ return &Tx{Tx: tx.Tx, driverName: tx.driverName, unsafe: true, Mapper: tx.Mapper}
+}
+
+// BindNamed binds a query within a transaction's bindvar type.
+func (tx *Tx) BindNamed(query string, arg interface{}) (string, []interface{}, error) {
+ return BindNamed(BindType(tx.driverName), query, arg)
+}
+
+// NamedQuery within a transaction.
+func (tx *Tx) NamedQuery(query string, arg interface{}) (*Rows, error) {
+ return NamedQuery(tx, query, arg)
+}
+
+// NamedExec a named query within a transaction.
+func (tx *Tx) NamedExec(query string, arg interface{}) (sql.Result, error) {
+ return NamedExec(tx, query, arg)
+}
+
+// Select within a transaction.
+func (tx *Tx) Select(dest interface{}, query string, args ...interface{}) error {
+ return Select(tx, dest, query, args...)
+}
+
+// Queryx within a transaction.
+func (tx *Tx) Queryx(query string, args ...interface{}) (*Rows, error) {
+ r, err := tx.Tx.Query(query, args...)
+ if err != nil {
+ return nil, err
+ }
+ return &Rows{Rows: r, unsafe: tx.unsafe, Mapper: tx.Mapper}, err
+}
+
+// QueryRowx within a transaction.
+func (tx *Tx) QueryRowx(query string, args ...interface{}) *Row {
+ rows, err := tx.Tx.Query(query, args...)
+ return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper}
+}
+
+// Get within a transaction.
+func (tx *Tx) Get(dest interface{}, query string, args ...interface{}) error {
+ return Get(tx, dest, query, args...)
+}
+
+// MustExec runs MustExec within a transaction.
+func (tx *Tx) MustExec(query string, args ...interface{}) sql.Result {
+ return MustExec(tx, query, args...)
+}
+
+// Preparex a statement within a transaction.
+func (tx *Tx) Preparex(query string) (*Stmt, error) {
+ return Preparex(tx, query)
+}
+
+// Stmtx returns a version of the prepared statement which runs within a transaction. Provided
+// stmt can be either *sql.Stmt or *sqlx.Stmt.
+func (tx *Tx) Stmtx(stmt interface{}) *Stmt {
+ var st sql.Stmt
+ var s *sql.Stmt
+ switch stmt.(type) {
+ case sql.Stmt:
+ st = stmt.(sql.Stmt)
+ s = &st
+ case Stmt:
+ s = stmt.(Stmt).Stmt
+ case *Stmt:
+ s = stmt.(*Stmt).Stmt
+ case *sql.Stmt:
+ s = stmt.(*sql.Stmt)
+ }
+ return &Stmt{Stmt: tx.Stmt(s), Mapper: tx.Mapper}
+}
+
+// NamedStmt returns a version of the prepared statement which runs within a transaction.
+func (tx *Tx) NamedStmt(stmt *NamedStmt) *NamedStmt {
+ return &NamedStmt{
+ QueryString: stmt.QueryString,
+ Params: stmt.Params,
+ Stmt: tx.Stmtx(stmt.Stmt),
+ }
+}
+
+// PrepareNamed returns an sqlx.NamedStmt
+func (tx *Tx) PrepareNamed(query string) (*NamedStmt, error) {
+ return prepareNamed(tx, query)
+}
+
+// Stmt is an sqlx wrapper around sql.Stmt with extra functionality
+type Stmt struct {
+ *sql.Stmt
+ unsafe bool
+ Mapper *reflectx.Mapper
+}
+
+// Unsafe returns a version of Stmt which will silently succeed to scan when
+// columns in the SQL result have no fields in the destination struct.
+func (s *Stmt) Unsafe() *Stmt {
+ return &Stmt{Stmt: s.Stmt, unsafe: true, Mapper: s.Mapper}
+}
+
+// Select using the prepared statement.
+func (s *Stmt) Select(dest interface{}, args ...interface{}) error {
+ return Select(&qStmt{*s}, dest, "", args...)
+}
+
+// Get using the prepared statement.
+func (s *Stmt) Get(dest interface{}, args ...interface{}) error {
+ return Get(&qStmt{*s}, dest, "", args...)
+}
+
+// MustExec (panic) using this statement. Note that the query portion of the error
+// output will be blank, as Stmt does not expose its query.
+func (s *Stmt) MustExec(args ...interface{}) sql.Result {
+ return MustExec(&qStmt{*s}, "", args...)
+}
+
+// QueryRowx using this statement.
+func (s *Stmt) QueryRowx(args ...interface{}) *Row {
+ qs := &qStmt{*s}
+ return qs.QueryRowx("", args...)
+}
+
+// Queryx using this statement.
+func (s *Stmt) Queryx(args ...interface{}) (*Rows, error) {
+ qs := &qStmt{*s}
+ return qs.Queryx("", args...)
+}
+
+// qStmt is an unexposed wrapper which lets you use a Stmt as a Queryer & Execer by
+// implementing those interfaces and ignoring the `query` argument.
+type qStmt struct{ Stmt }
+
+func (q *qStmt) Query(query string, args ...interface{}) (*sql.Rows, error) {
+ return q.Stmt.Query(args...)
+}
+
+func (q *qStmt) Queryx(query string, args ...interface{}) (*Rows, error) {
+ r, err := q.Stmt.Query(args...)
+ if err != nil {
+ return nil, err
+ }
+ return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err
+}
+
+func (q *qStmt) QueryRowx(query string, args ...interface{}) *Row {
+ rows, err := q.Stmt.Query(args...)
+ return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}
+}
+
+func (q *qStmt) Exec(query string, args ...interface{}) (sql.Result, error) {
+ return q.Stmt.Exec(args...)
+}
+
+// Rows is a wrapper around sql.Rows which caches costly reflect operations
+// during a looped StructScan
+type Rows struct {
+ *sql.Rows
+ unsafe bool
+ Mapper *reflectx.Mapper
+ // these fields cache memory use for a rows during iteration w/ structScan
+ started bool
+ fields [][]int
+ values []interface{}
+}
+
+// SliceScan using this Rows.
+func (r *Rows) SliceScan() ([]interface{}, error) {
+ return SliceScan(r)
+}
+
+// MapScan using this Rows.
+func (r *Rows) MapScan(dest map[string]interface{}) error {
+ return MapScan(r, dest)
+}
+
+// StructScan is like sql.Rows.Scan, but scans a single Row into a single Struct.
+// Use this and iterate over Rows manually when the memory load of Select() might be
+// prohibitive. *Rows.StructScan caches the reflect work of matching up column
+// positions to fields to avoid that overhead per scan, which means it is not safe
+// to run StructScan on the same Rows instance with different struct types.
+func (r *Rows) StructScan(dest interface{}) error {
+ v := reflect.ValueOf(dest)
+
+ if v.Kind() != reflect.Ptr {
+ return errors.New("must pass a pointer, not a value, to StructScan destination")
+ }
+
+ v = reflect.Indirect(v)
+
+ if !r.started {
+ columns, err := r.Columns()
+ if err != nil {
+ return err
+ }
+ m := r.Mapper
+
+ r.fields = m.TraversalsByName(v.Type(), columns)
+ // if we are not unsafe and are missing fields, return an error
+ if f, err := missingFields(r.fields); err != nil && !r.unsafe {
+ return fmt.Errorf("missing destination name %s", columns[f])
+ }
+ r.values = make([]interface{}, len(columns))
+ r.started = true
+ }
+
+ err := fieldsByTraversal(v, r.fields, r.values, true)
+ if err != nil {
+ return err
+ }
+ // scan into the struct field pointers and append to our results
+ err = r.Scan(r.values...)
+ if err != nil {
+ return err
+ }
+ return r.Err()
+}
+
+// Connect to a database and verify with a ping.
+func Connect(driverName, dataSourceName string) (*DB, error) {
+ db, err := Open(driverName, dataSourceName)
+ if err != nil {
+ return db, err
+ }
+ err = db.Ping()
+ return db, err
+}
+
+// MustConnect connects to a database and panics on error.
+func MustConnect(driverName, dataSourceName string) *DB {
+ db, err := Connect(driverName, dataSourceName)
+ if err != nil {
+ panic(err)
+ }
+ return db
+}
+
+// Preparex prepares a statement.
+func Preparex(p Preparer, query string) (*Stmt, error) {
+ s, err := p.Prepare(query)
+ if err != nil {
+ return nil, err
+ }
+ return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
+}
+
+// Select executes a query using the provided Queryer, and StructScans each row
+// into dest, which must be a slice. If the slice elements are scannable, then
+// the result set must have only one column. Otherwise, StructScan is used.
+// The *sql.Rows are closed automatically.
+func Select(q Queryer, dest interface{}, query string, args ...interface{}) error {
+ rows, err := q.Queryx(query, args...)
+ if err != nil {
+ return err
+ }
+ // if something happens here, we want to make sure the rows are Closed
+ defer rows.Close()
+ return scanAll(rows, dest, false)
+}
+
+// Get does a QueryRow using the provided Queryer, and scans the resulting row
+// to dest. If dest is scannable, the result must only have one column. Otherwise,
+// StructScan is used. Get will return sql.ErrNoRows like row.Scan would.
+func Get(q Queryer, dest interface{}, query string, args ...interface{}) error {
+ r := q.QueryRowx(query, args...)
+ return r.scanAny(dest, false)
+}
+
+// LoadFile exec's every statement in a file (as a single call to Exec).
+// LoadFile may return a nil *sql.Result if errors are encountered locating or
+// reading the file at path. LoadFile reads the entire file into memory, so it
+// is not suitable for loading large data dumps, but can be useful for initializing
+// schemas or loading indexes.
+//
+// FIXME: this does not really work with multi-statement files for mattn/go-sqlite3
+// or the go-mysql-driver/mysql drivers; pq seems to be an exception here. Detecting
+// this by requiring something with DriverName() and then attempting to split the
+// queries will be difficult to get right, and its current driver-specific behavior
+// is deemed at least not complex in its incorrectness.
+func LoadFile(e Execer, path string) (*sql.Result, error) {
+ realpath, err := filepath.Abs(path)
+ if err != nil {
+ return nil, err
+ }
+ contents, err := ioutil.ReadFile(realpath)
+ if err != nil {
+ return nil, err
+ }
+ res, err := e.Exec(string(contents))
+ return &res, err
+}
+
+// MustExec execs the query using e and panics if there was an error.
+func MustExec(e Execer, query string, args ...interface{}) sql.Result {
+ res, err := e.Exec(query, args...)
+ if err != nil {
+ panic(err)
+ }
+ return res
+}
+
+// SliceScan using this Rows.
+func (r *Row) SliceScan() ([]interface{}, error) {
+ return SliceScan(r)
+}
+
+// MapScan using this Rows.
+func (r *Row) MapScan(dest map[string]interface{}) error {
+ return MapScan(r, dest)
+}
+
+func (r *Row) scanAny(dest interface{}, structOnly bool) error {
+ if r.err != nil {
+ return r.err
+ }
+ defer r.rows.Close()
+
+ v := reflect.ValueOf(dest)
+ if v.Kind() != reflect.Ptr {
+ return errors.New("must pass a pointer, not a value, to StructScan destination")
+ }
+ if v.IsNil() {
+ return errors.New("nil pointer passed to StructScan destination")
+ }
+
+ base := reflectx.Deref(v.Type())
+ scannable := isScannable(base)
+
+ if structOnly && scannable {
+ return structOnlyError(base)
+ }
+
+ columns, err := r.Columns()
+ if err != nil {
+ return err
+ }
+
+ if scannable && len(columns) > 1 {
+ return fmt.Errorf("scannable dest type %s with >1 columns (%d) in result", base.Kind(), len(columns))
+ }
+
+ if scannable {
+ return r.Scan(dest)
+ }
+
+ m := r.Mapper
+
+ fields := m.TraversalsByName(v.Type(), columns)
+ // if we are not unsafe and are missing fields, return an error
+ if f, err := missingFields(fields); err != nil && !r.unsafe {
+ return fmt.Errorf("missing destination name %s", columns[f])
+ }
+ values := make([]interface{}, len(columns))
+
+ err = fieldsByTraversal(v, fields, values, true)
+ if err != nil {
+ return err
+ }
+ // scan into the struct field pointers and append to our results
+ return r.Scan(values...)
+}
+
+// StructScan a single Row into dest.
+func (r *Row) StructScan(dest interface{}) error {
+ return r.scanAny(dest, true)
+}
+
+// SliceScan a row, returning a []interface{} with values similar to MapScan.
+// This function is primarly intended for use where the number of columns
+// is not known. Because you can pass an []interface{} directly to Scan,
+// it's recommended that you do that as it will not have to allocate new
+// slices per row.
+func SliceScan(r ColScanner) ([]interface{}, error) {
+ // ignore r.started, since we needn't use reflect for anything.
+ columns, err := r.Columns()
+ if err != nil {
+ return []interface{}{}, err
+ }
+
+ values := make([]interface{}, len(columns))
+ for i := range values {
+ values[i] = new(interface{})
+ }
+
+ err = r.Scan(values...)
+
+ if err != nil {
+ return values, err
+ }
+
+ for i := range columns {
+ values[i] = *(values[i].(*interface{}))
+ }
+
+ return values, r.Err()
+}
+
+// MapScan scans a single Row into the dest map[string]interface{}.
+// Use this to get results for SQL that might not be under your control
+// (for instance, if you're building an interface for an SQL server that
+// executes SQL from input). Please do not use this as a primary interface!
+// This will modify the map sent to it in place, so reuse the same map with
+// care. Columns which occur more than once in the result will overwrite
+// eachother!
+func MapScan(r ColScanner, dest map[string]interface{}) error {
+ // ignore r.started, since we needn't use reflect for anything.
+ columns, err := r.Columns()
+ if err != nil {
+ return err
+ }
+
+ values := make([]interface{}, len(columns))
+ for i := range values {
+ values[i] = new(interface{})
+ }
+
+ err = r.Scan(values...)
+ if err != nil {
+ return err
+ }
+
+ for i, column := range columns {
+ dest[column] = *(values[i].(*interface{}))
+ }
+
+ return r.Err()
+}
+
+type rowsi interface {
+ Close() error
+ Columns() ([]string, error)
+ Err() error
+ Next() bool
+ Scan(...interface{}) error
+}
+
+// structOnlyError returns an error appropriate for type when a non-scannable
+// struct is expected but something else is given
+func structOnlyError(t reflect.Type) error {
+ isStruct := t.Kind() == reflect.Struct
+ isScanner := reflect.PtrTo(t).Implements(_scannerInterface)
+ if !isStruct {
+ return fmt.Errorf("expected %s but got %s", reflect.Struct, t.Kind())
+ }
+ if isScanner {
+ return fmt.Errorf("structscan expects a struct dest but the provided struct type %s implements scanner", t.Name())
+ }
+ return fmt.Errorf("expected a struct, but struct %s has no exported fields", t.Name())
+}
+
+// scanAll scans all rows into a destination, which must be a slice of any
+// type. If the destination slice type is a Struct, then StructScan will be
+// used on each row. If the destination is some other kind of base type, then
+// each row must only have one column which can scan into that type. This
+// allows you to do something like:
+//
+// rows, _ := db.Query("select id from people;")
+// var ids []int
+// scanAll(rows, &ids, false)
+//
+// and ids will be a list of the id results. I realize that this is a desirable
+// interface to expose to users, but for now it will only be exposed via changes
+// to `Get` and `Select`. The reason that this has been implemented like this is
+// this is the only way to not duplicate reflect work in the new API while
+// maintaining backwards compatibility.
+func scanAll(rows rowsi, dest interface{}, structOnly bool) error {
+ var v, vp reflect.Value
+
+ value := reflect.ValueOf(dest)
+
+ // json.Unmarshal returns errors for these
+ if value.Kind() != reflect.Ptr {
+ return errors.New("must pass a pointer, not a value, to StructScan destination")
+ }
+ if value.IsNil() {
+ return errors.New("nil pointer passed to StructScan destination")
+ }
+ direct := reflect.Indirect(value)
+
+ slice, err := baseType(value.Type(), reflect.Slice)
+ if err != nil {
+ return err
+ }
+
+ isPtr := slice.Elem().Kind() == reflect.Ptr
+ base := reflectx.Deref(slice.Elem())
+ scannable := isScannable(base)
+
+ if structOnly && scannable {
+ return structOnlyError(base)
+ }
+
+ columns, err := rows.Columns()
+ if err != nil {
+ return err
+ }
+
+ // if it's a base type make sure it only has 1 column; if not return an error
+ if scannable && len(columns) > 1 {
+ return fmt.Errorf("non-struct dest type %s with >1 columns (%d)", base.Kind(), len(columns))
+ }
+
+ if !scannable {
+ var values []interface{}
+ var m *reflectx.Mapper
+
+ switch rows.(type) {
+ case *Rows:
+ m = rows.(*Rows).Mapper
+ default:
+ m = mapper()
+ }
+
+ fields := m.TraversalsByName(base, columns)
+ // if we are not unsafe and are missing fields, return an error
+ if f, err := missingFields(fields); err != nil && !isUnsafe(rows) {
+ return fmt.Errorf("missing destination name %s", columns[f])
+ }
+ values = make([]interface{}, len(columns))
+
+ for rows.Next() {
+ // create a new struct type (which returns PtrTo) and indirect it
+ vp = reflect.New(base)
+ v = reflect.Indirect(vp)
+
+ err = fieldsByTraversal(v, fields, values, true)
+
+ // scan into the struct field pointers and append to our results
+ err = rows.Scan(values...)
+ if err != nil {
+ return err
+ }
+
+ if isPtr {
+ direct.Set(reflect.Append(direct, vp))
+ } else {
+ direct.Set(reflect.Append(direct, v))
+ }
+ }
+ } else {
+ for rows.Next() {
+ vp = reflect.New(base)
+ err = rows.Scan(vp.Interface())
+ // append
+ if isPtr {
+ direct.Set(reflect.Append(direct, vp))
+ } else {
+ direct.Set(reflect.Append(direct, reflect.Indirect(vp)))
+ }
+ }
+ }
+
+ return rows.Err()
+}
+
+// FIXME: StructScan was the very first bit of API in sqlx, and now unfortunately
+// it doesn't really feel like it's named properly. There is an incongruency
+// between this and the way that StructScan (which might better be ScanStruct
+// anyway) works on a rows object.
+
+// StructScan all rows from an sql.Rows or an sqlx.Rows into the dest slice.
+// StructScan will scan in the entire rows result, so if you need do not want to
+// allocate structs for the entire result, use Queryx and see sqlx.Rows.StructScan.
+// If rows is sqlx.Rows, it will use its mapper, otherwise it will use the default.
+func StructScan(rows rowsi, dest interface{}) error {
+ return scanAll(rows, dest, true)
+
+}
+
+// reflect helpers
+
+func baseType(t reflect.Type, expected reflect.Kind) (reflect.Type, error) {
+ t = reflectx.Deref(t)
+ if t.Kind() != expected {
+ return nil, fmt.Errorf("expected %s but got %s", expected, t.Kind())
+ }
+ return t, nil
+}
+
+// fieldsByName fills a values interface with fields from the passed value based
+// on the traversals in int. If ptrs is true, return addresses instead of values.
+// We write this instead of using FieldsByName to save allocations and map lookups
+// when iterating over many rows. Empty traversals will get an interface pointer.
+// Because of the necessity of requesting ptrs or values, it's considered a bit too
+// specialized for inclusion in reflectx itself.
+func fieldsByTraversal(v reflect.Value, traversals [][]int, values []interface{}, ptrs bool) error {
+ v = reflect.Indirect(v)
+ if v.Kind() != reflect.Struct {
+ return errors.New("argument not a struct")
+ }
+
+ for i, traversal := range traversals {
+ if len(traversal) == 0 {
+ values[i] = new(interface{})
+ continue
+ }
+ f := reflectx.FieldByIndexes(v, traversal)
+ if ptrs {
+ values[i] = f.Addr().Interface()
+ } else {
+ values[i] = f.Interface()
+ }
+ }
+ return nil
+}
+
+func missingFields(transversals [][]int) (field int, err error) {
+ for i, t := range transversals {
+ if len(t) == 0 {
+ return i, errors.New("missing field")
+ }
+ }
+ return 0, nil
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/sqlx_test.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/sqlx_test.go
new file mode 100644
index 0000000..37babf1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/sqlx_test.go
@@ -0,0 +1,1383 @@
+// The following environment variables, if set, will be used:
+//
+// * SQLX_SQLITE_DSN
+// * SQLX_POSTGRES_DSN
+// * SQLX_MYSQL_DSN
+//
+// Set any of these variables to 'skip' to skip them. Note that for MySQL,
+// the string '?parseTime=True' will be appended to the DSN if it's not there
+// already.
+//
+package sqlx
+
+import (
+ "database/sql"
+ "database/sql/driver"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ _ "github.com/go-sql-driver/mysql"
+ "github.com/jmoiron/sqlx/reflectx"
+ _ "github.com/lib/pq"
+ _ "github.com/mattn/go-sqlite3"
+)
+
+/* compile time checks that Db, Tx, Stmt (qStmt) implement expected interfaces */
+var _, _ Ext = &DB{}, &Tx{}
+var _, _ ColScanner = &Row{}, &Rows{}
+var _ Queryer = &qStmt{}
+var _ Execer = &qStmt{}
+
+var TestPostgres = true
+var TestSqlite = true
+var TestMysql = true
+
+var sldb *DB
+var pgdb *DB
+var mysqldb *DB
+var active = []*DB{}
+
+func init() {
+ ConnectAll()
+}
+
+func ConnectAll() {
+ var err error
+
+ pgdsn := os.Getenv("SQLX_POSTGRES_DSN")
+ mydsn := os.Getenv("SQLX_MYSQL_DSN")
+ sqdsn := os.Getenv("SQLX_SQLITE_DSN")
+
+ TestPostgres = pgdsn != "skip"
+ TestMysql = mydsn != "skip"
+ TestSqlite = sqdsn != "skip"
+
+ if TestPostgres {
+ pgdb, err = Connect("postgres", pgdsn)
+ if err != nil {
+ fmt.Printf("Disabling PG tests:\n %v\n", err)
+ TestPostgres = false
+ }
+ } else {
+ fmt.Println("Disabling Postgres tests.")
+ }
+
+ if TestMysql {
+ mysqldb, err = Connect("mysql", mydsn)
+ if err != nil {
+ fmt.Printf("Disabling MySQL tests:\n %v", err)
+ TestMysql = false
+ }
+ } else {
+ fmt.Println("Disabling MySQL tests.")
+ }
+
+ if TestSqlite {
+ sldb, err = Connect("sqlite3", sqdsn)
+ if err != nil {
+ fmt.Printf("Disabling SQLite:\n %v", err)
+ TestSqlite = false
+ }
+ } else {
+ fmt.Println("Disabling SQLite tests.")
+ }
+}
+
+type Schema struct {
+ create string
+ drop string
+}
+
+func (s Schema) Postgres() (string, string) {
+ return s.create, s.drop
+}
+
+func (s Schema) MySQL() (string, string) {
+ return strings.Replace(s.create, `"`, "`", -1), s.drop
+}
+
+func (s Schema) Sqlite3() (string, string) {
+ return strings.Replace(s.create, `now()`, `CURRENT_TIMESTAMP`, -1), s.drop
+}
+
+var defaultSchema = Schema{
+ create: `
+CREATE TABLE person (
+ first_name text,
+ last_name text,
+ email text,
+ added_at timestamp default now()
+);
+
+CREATE TABLE place (
+ country text,
+ city text NULL,
+ telcode integer
+);
+
+CREATE TABLE capplace (
+ "COUNTRY" text,
+ "CITY" text NULL,
+ "TELCODE" integer
+);
+
+CREATE TABLE nullperson (
+ first_name text NULL,
+ last_name text NULL,
+ email text NULL
+);
+`,
+ drop: `
+drop table person;
+drop table place;
+drop table capplace;
+drop table nullperson;
+`,
+}
+
+type Person struct {
+ FirstName string `db:"first_name"`
+ LastName string `db:"last_name"`
+ Email string
+ AddedAt time.Time `db:"added_at"`
+}
+
+type Person2 struct {
+ FirstName sql.NullString `db:"first_name"`
+ LastName sql.NullString `db:"last_name"`
+ Email sql.NullString
+}
+
+type Place struct {
+ Country string
+ City sql.NullString
+ TelCode int
+}
+
+type PlacePtr struct {
+ Country string
+ City *string
+ TelCode int
+}
+
+type PersonPlace struct {
+ Person
+ Place
+}
+
+type PersonPlacePtr struct {
+ *Person
+ *Place
+}
+
+type EmbedConflict struct {
+ FirstName string `db:"first_name"`
+ Person
+}
+
+type Loop1 struct {
+ Person
+}
+
+type Loop2 struct {
+ Loop1
+}
+
+type Loop3 struct {
+ Loop2
+}
+
+type SliceMember struct {
+ Country string
+ City sql.NullString
+ TelCode int
+ People []Person `db:"-"`
+ Addresses []Place `db:"-"`
+}
+
+// Note that because of field map caching, we need a new type here
+// if we've used Place already soemwhere in sqlx
+type CPlace Place
+
+func MultiExec(e Execer, query string) {
+ stmts := strings.Split(query, ";\n")
+ if len(strings.Trim(stmts[len(stmts)-1], " \n\t\r")) == 0 {
+ stmts = stmts[:len(stmts)-1]
+ }
+ for _, s := range stmts {
+ _, err := e.Exec(s)
+ if err != nil {
+ fmt.Println(err, s)
+ }
+ }
+}
+
+func RunWithSchema(schema Schema, t *testing.T, test func(db *DB, t *testing.T)) {
+ runner := func(db *DB, t *testing.T, create, drop string) {
+ defer func() {
+ MultiExec(db, drop)
+ }()
+
+ MultiExec(db, create)
+ test(db, t)
+ }
+
+ if TestPostgres {
+ create, drop := schema.Postgres()
+ runner(pgdb, t, create, drop)
+ }
+ if TestSqlite {
+ create, drop := schema.Sqlite3()
+ runner(sldb, t, create, drop)
+ }
+ if TestMysql {
+ create, drop := schema.MySQL()
+ runner(mysqldb, t, create, drop)
+ }
+}
+
+func loadDefaultFixture(db *DB, t *testing.T) {
+ tx := db.MustBegin()
+ tx.MustExec(tx.Rebind("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)"), "Jason", "Moiron", "jmoiron@jmoiron.net")
+ tx.MustExec(tx.Rebind("INSERT INTO person (first_name, last_name, email) VALUES (?, ?, ?)"), "John", "Doe", "johndoeDNE@gmail.net")
+ tx.MustExec(tx.Rebind("INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)"), "United States", "New York", "1")
+ tx.MustExec(tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Hong Kong", "852")
+ tx.MustExec(tx.Rebind("INSERT INTO place (country, telcode) VALUES (?, ?)"), "Singapore", "65")
+ if db.DriverName() == "mysql" {
+ tx.MustExec(tx.Rebind("INSERT INTO capplace (`COUNTRY`, `TELCODE`) VALUES (?, ?)"), "Sarf Efrica", "27")
+ } else {
+ tx.MustExec(tx.Rebind("INSERT INTO capplace (\"COUNTRY\", \"TELCODE\") VALUES (?, ?)"), "Sarf Efrica", "27")
+ }
+ tx.Commit()
+}
+
+// Test a new backwards compatible feature, that missing scan destinations
+// will silently scan into sql.RawText rather than failing/panicing
+func TestMissingNames(t *testing.T) {
+ RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
+ loadDefaultFixture(db, t)
+ type PersonPlus struct {
+ FirstName string `db:"first_name"`
+ LastName string `db:"last_name"`
+ Email string
+ //AddedAt time.Time `db:"added_at"`
+ }
+
+ // test Select first
+ pps := []PersonPlus{}
+ // pps lacks added_at destination
+ err := db.Select(&pps, "SELECT * FROM person")
+ if err == nil {
+ t.Error("Expected missing name from Select to fail, but it did not.")
+ }
+
+ // test Get
+ pp := PersonPlus{}
+ err = db.Get(&pp, "SELECT * FROM person LIMIT 1")
+ if err == nil {
+ t.Error("Expected missing name Get to fail, but it did not.")
+ }
+
+ // test naked StructScan
+ pps = []PersonPlus{}
+ rows, err := db.Query("SELECT * FROM person LIMIT 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Next()
+ err = StructScan(rows, &pps)
+ if err == nil {
+ t.Error("Expected missing name in StructScan to fail, but it did not.")
+ }
+ rows.Close()
+
+ // now try various things with unsafe set.
+ db = db.Unsafe()
+ pps = []PersonPlus{}
+ err = db.Select(&pps, "SELECT * FROM person")
+ if err != nil {
+ t.Error(err)
+ }
+
+ // test Get
+ pp = PersonPlus{}
+ err = db.Get(&pp, "SELECT * FROM person LIMIT 1")
+ if err != nil {
+ t.Error(err)
+ }
+
+ // test naked StructScan
+ pps = []PersonPlus{}
+ rowsx, err := db.Queryx("SELECT * FROM person LIMIT 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rowsx.Next()
+ err = StructScan(rowsx, &pps)
+ if err != nil {
+ t.Error(err)
+ }
+ rowsx.Close()
+
+ })
+}
+
+func TestEmbeddedStructs(t *testing.T) {
+ RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
+ loadDefaultFixture(db, t)
+ peopleAndPlaces := []PersonPlace{}
+ err := db.Select(
+ &peopleAndPlaces,
+ `SELECT person.*, place.* FROM
+ person natural join place`)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, pp := range peopleAndPlaces {
+ if len(pp.Person.FirstName) == 0 {
+ t.Errorf("Expected non zero lengthed first name.")
+ }
+ if len(pp.Place.Country) == 0 {
+ t.Errorf("Expected non zero lengthed country.")
+ }
+ }
+
+ // test embedded structs with StructScan
+ rows, err := db.Queryx(
+ `SELECT person.*, place.* FROM
+ person natural join place`)
+ if err != nil {
+ t.Error(err)
+ }
+
+ perp := PersonPlace{}
+ rows.Next()
+ err = rows.StructScan(&perp)
+ if err != nil {
+ t.Error(err)
+ }
+
+ if len(perp.Person.FirstName) == 0 {
+ t.Errorf("Expected non zero lengthed first name.")
+ }
+ if len(perp.Place.Country) == 0 {
+ t.Errorf("Expected non zero lengthed country.")
+ }
+
+ rows.Close()
+
+ // test the same for embedded pointer structs
+ peopleAndPlacesPtrs := []PersonPlacePtr{}
+ err = db.Select(
+ &peopleAndPlacesPtrs,
+ `SELECT person.*, place.* FROM
+ person natural join place`)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, pp := range peopleAndPlacesPtrs {
+ if len(pp.Person.FirstName) == 0 {
+ t.Errorf("Expected non zero lengthed first name.")
+ }
+ if len(pp.Place.Country) == 0 {
+ t.Errorf("Expected non zero lengthed country.")
+ }
+ }
+
+ // test "deep nesting"
+ l3s := []Loop3{}
+ err = db.Select(&l3s, `select * from person`)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, l3 := range l3s {
+ if len(l3.Loop2.Loop1.Person.FirstName) == 0 {
+ t.Errorf("Expected non zero lengthed first name.")
+ }
+ }
+
+ // test "embed conflicts"
+ ec := []EmbedConflict{}
+ err = db.Select(&ec, `select * from person`)
+ // I'm torn between erroring here or having some kind of working behavior
+ // in order to allow for more flexibility in destination structs
+ if err != nil {
+ t.Errorf("Was not expecting an error on embed conflicts.")
+ }
+ })
+}
+
+func TestSelectSliceMapTime(t *testing.T) {
+ RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
+ loadDefaultFixture(db, t)
+ rows, err := db.Queryx("SELECT * FROM person")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for rows.Next() {
+ _, err := rows.SliceScan()
+ if err != nil {
+ t.Error(err)
+ }
+ }
+
+ rows, err = db.Queryx("SELECT * FROM person")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for rows.Next() {
+ m := map[string]interface{}{}
+ err := rows.MapScan(m)
+ if err != nil {
+ t.Error(err)
+ }
+ }
+
+ })
+}
+
+func TestNilReceiver(t *testing.T) {
+ RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
+ loadDefaultFixture(db, t)
+ var p *Person
+ err := db.Get(p, "SELECT * FROM person LIMIT 1")
+ if err == nil {
+ t.Error("Expected error when getting into nil struct ptr.")
+ }
+ var pp *[]Person
+ err = db.Select(pp, "SELECT * FROM person")
+ if err == nil {
+ t.Error("Expected an error when selecting into nil slice ptr.")
+ }
+ })
+}
+
+func TestNamedQuery(t *testing.T) {
+ var schema = Schema{
+ create: `
+ CREATE TABLE person (
+ first_name text NULL,
+ last_name text NULL,
+ email text NULL
+ );
+ CREATE TABLE jsperson (
+ "FIRST" text NULL,
+ last_name text NULL,
+ "EMAIL" text NULL
+ );`,
+ drop: `
+ drop table person;
+ drop table jsperson;
+ `,
+ }
+
+ RunWithSchema(schema, t, func(db *DB, t *testing.T) {
+ type Person struct {
+ FirstName sql.NullString `db:"first_name"`
+ LastName sql.NullString `db:"last_name"`
+ Email sql.NullString
+ }
+
+ p := Person{
+ FirstName: sql.NullString{"ben", true},
+ LastName: sql.NullString{"doe", true},
+ Email: sql.NullString{"ben@doe.com", true},
+ }
+
+ q1 := `INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)`
+ _, err := db.NamedExec(q1, p)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ p2 := &Person{}
+ rows, err := db.NamedQuery("SELECT * FROM person WHERE first_name=:first_name", p)
+ if err != nil {
+ log.Fatal(err)
+ }
+ for rows.Next() {
+ err = rows.StructScan(p2)
+ if err != nil {
+ t.Error(err)
+ }
+ if p2.FirstName.String != "ben" {
+ t.Error("Expected first name of `ben`, got " + p2.FirstName.String)
+ }
+ if p2.LastName.String != "doe" {
+ t.Error("Expected first name of `doe`, got " + p2.LastName.String)
+ }
+ }
+
+ // these are tests for #73; they verify that named queries work if you've
+ // changed the db mapper. This code checks both NamedQuery "ad-hoc" style
+ // queries and NamedStmt queries, which use different code paths internally.
+ old := *db.Mapper
+
+ type JsonPerson struct {
+ FirstName sql.NullString `json:"FIRST"`
+ LastName sql.NullString `json:"last_name"`
+ Email sql.NullString
+ }
+
+ jp := JsonPerson{
+ FirstName: sql.NullString{"ben", true},
+ LastName: sql.NullString{"smith", true},
+ Email: sql.NullString{"ben@smith.com", true},
+ }
+
+ db.Mapper = reflectx.NewMapperFunc("json", strings.ToUpper)
+
+ // prepare queries for case sensitivity to test our ToUpper function.
+ // postgres and sqlite accept "", but mysql uses ``; since Go's multi-line
+ // strings are `` we use "" by default and swap out for MySQL
+ pdb := func(s string, db *DB) string {
+ if db.DriverName() == "mysql" {
+ return strings.Replace(s, `"`, "`", -1)
+ }
+ return s
+ }
+
+ q1 = `INSERT INTO jsperson ("FIRST", last_name, "EMAIL") VALUES (:FIRST, :last_name, :EMAIL)`
+ _, err = db.NamedExec(pdb(q1, db), jp)
+ if err != nil {
+ t.Fatal(err, db.DriverName())
+ }
+
+ // Checks that a person pulled out of the db matches the one we put in
+ check := func(t *testing.T, rows *Rows) {
+ jp = JsonPerson{}
+ for rows.Next() {
+ err = rows.StructScan(&jp)
+ if err != nil {
+ t.Error(err)
+ }
+ if jp.FirstName.String != "ben" {
+ t.Errorf("Expected first name of `ben`, got `%s` (%s) ", jp.FirstName.String, db.DriverName())
+ }
+ if jp.LastName.String != "smith" {
+ t.Errorf("Expected LastName of `smith`, got `%s` (%s)", jp.LastName.String, db.DriverName())
+ }
+ if jp.Email.String != "ben@smith.com" {
+ t.Errorf("Expected first name of `doe`, got `%s` (%s)", jp.Email.String, db.DriverName())
+ }
+ }
+ }
+
+ ns, err := db.PrepareNamed(pdb(`
+ SELECT * FROM jsperson
+ WHERE
+ "FIRST"=:FIRST AND
+ last_name=:last_name AND
+ "EMAIL"=:EMAIL
+ `, db))
+
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err = ns.Queryx(jp)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ check(t, rows)
+
+ // Check exactly the same thing, but with db.NamedQuery, which does not go
+ // through the PrepareNamed/NamedStmt path.
+ rows, err = db.NamedQuery(pdb(`
+ SELECT * FROM jsperson
+ WHERE
+ "FIRST"=:FIRST AND
+ last_name=:last_name AND
+ "EMAIL"=:EMAIL
+ `, db), jp)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ check(t, rows)
+
+ db.Mapper = &old
+
+ })
+}
+
+func TestNilInserts(t *testing.T) {
+ var schema = Schema{
+ create: `
+ CREATE TABLE tt (
+ id integer,
+ value text NULL DEFAULT NULL
+ );`,
+ drop: "drop table tt;",
+ }
+
+ RunWithSchema(schema, t, func(db *DB, t *testing.T) {
+ type TT struct {
+ Id int
+ Value *string
+ }
+ var v, v2 TT
+ r := db.Rebind
+
+ db.MustExec(r(`INSERT INTO tt (id) VALUES (1)`))
+ db.Get(&v, r(`SELECT * FROM tt`))
+ if v.Id != 1 {
+ t.Errorf("Expecting id of 1, got %v", v.Id)
+ }
+ if v.Value != nil {
+ t.Errorf("Expecting NULL to map to nil, got %s", v.Value)
+ }
+
+ v.Id = 2
+ // NOTE: this incidentally uncovered a bug which was that named queries with
+ // pointer destinations would not work if the passed value here was not addressable,
+ // as reflectx.FieldByIndexes attempts to allocate nil pointer receivers for
+ // writing. This was fixed by creating & using the reflectx.FieldByIndexesReadOnly
+ // function. This next line is important as it provides the only coverage for this.
+ db.NamedExec(`INSERT INTO tt (id, value) VALUES (:id, :value)`, v)
+
+ db.Get(&v2, r(`SELECT * FROM tt WHERE id=2`))
+ if v.Id != v2.Id {
+ t.Errorf("%v != %v", v.Id, v2.Id)
+ }
+ if v2.Value != nil {
+ t.Errorf("Expecting NULL to map to nil, got %s", v.Value)
+ }
+ })
+}
+
+func TestScanError(t *testing.T) {
+ var schema = Schema{
+ create: `
+ CREATE TABLE kv (
+ k text,
+ v integer
+ );`,
+ drop: `drop table kv;`,
+ }
+
+ RunWithSchema(schema, t, func(db *DB, t *testing.T) {
+ type WrongTypes struct {
+ K int
+ V string
+ }
+ _, err := db.Exec(db.Rebind("INSERT INTO kv (k, v) VALUES (?, ?)"), "hi", 1)
+ if err != nil {
+ t.Error(err)
+ }
+
+ rows, err := db.Queryx("SELECT * FROM kv")
+ if err != nil {
+ t.Error(err)
+ }
+ for rows.Next() {
+ var wt WrongTypes
+ err := rows.StructScan(&wt)
+ if err == nil {
+ t.Errorf("%s: Scanning wrong types into keys should have errored.", db.DriverName())
+ }
+ }
+ })
+}
+
+// FIXME: this function is kinda big but it slows things down to be constantly
+// loading and reloading the schema..
+
+func TestUsage(t *testing.T) {
+ RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T) {
+ loadDefaultFixture(db, t)
+ slicemembers := []SliceMember{}
+ err := db.Select(&slicemembers, "SELECT * FROM place ORDER BY telcode ASC")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ people := []Person{}
+
+ err = db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ jason, john := people[0], people[1]
+ if jason.FirstName != "Jason" {
+ t.Errorf("Expecting FirstName of Jason, got %s", jason.FirstName)
+ }
+ if jason.LastName != "Moiron" {
+ t.Errorf("Expecting LastName of Moiron, got %s", jason.LastName)
+ }
+ if jason.Email != "jmoiron@jmoiron.net" {
+ t.Errorf("Expecting Email of jmoiron@jmoiron.net, got %s", jason.Email)
+ }
+ if john.FirstName != "John" || john.LastName != "Doe" || john.Email != "johndoeDNE@gmail.net" {
+ t.Errorf("John Doe's person record not what expected: Got %v\n", john)
+ }
+
+ jason = Person{}
+ err = db.Get(&jason, db.Rebind("SELECT * FROM person WHERE first_name=?"), "Jason")
+
+ if err != nil {
+ t.Fatal(err)
+ }
+ if jason.FirstName != "Jason" {
+ t.Errorf("Expecting to get back Jason, but got %v\n", jason.FirstName)
+ }
+
+ err = db.Get(&jason, db.Rebind("SELECT * FROM person WHERE first_name=?"), "Foobar")
+ if err == nil {
+ t.Errorf("Expecting an error, got nil\n")
+ }
+ if err != sql.ErrNoRows {
+ t.Errorf("Expected sql.ErrNoRows, got %v\n", err)
+ }
+
+ // The following tests check statement reuse, which was actually a problem
+ // due to copying being done when creating Stmt's which was eventually removed
+ stmt1, err := db.Preparex(db.Rebind("SELECT * FROM person WHERE first_name=?"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ jason = Person{}
+
+ row := stmt1.QueryRowx("DoesNotExist")
+ row.Scan(&jason)
+ row = stmt1.QueryRowx("DoesNotExist")
+ row.Scan(&jason)
+
+ err = stmt1.Get(&jason, "DoesNotExist User")
+ if err == nil {
+ t.Error("Expected an error")
+ }
+ err = stmt1.Get(&jason, "DoesNotExist User 2")
+
+ stmt2, err := db.Preparex(db.Rebind("SELECT * FROM person WHERE first_name=?"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ jason = Person{}
+ tx, err := db.Beginx()
+ if err != nil {
+ t.Fatal(err)
+ }
+ tstmt2 := tx.Stmtx(stmt2)
+ row2 := tstmt2.QueryRowx("Jason")
+ err = row2.StructScan(&jason)
+ if err != nil {
+ t.Error(err)
+ }
+ tx.Commit()
+
+ places := []*Place{}
+ err = db.Select(&places, "SELECT telcode FROM place ORDER BY telcode ASC")
+ usa, singsing, honkers := places[0], places[1], places[2]
+
+ if usa.TelCode != 1 || honkers.TelCode != 852 || singsing.TelCode != 65 {
+ t.Errorf("Expected integer telcodes to work, got %#v", places)
+ }
+
+ placesptr := []PlacePtr{}
+ err = db.Select(&placesptr, "SELECT * FROM place ORDER BY telcode ASC")
+ if err != nil {
+ t.Error(err)
+ }
+ //fmt.Printf("%#v\n%#v\n%#v\n", placesptr[0], placesptr[1], placesptr[2])
+
+ // if you have null fields and use SELECT *, you must use sql.Null* in your struct
+ // this test also verifies that you can use either a []Struct{} or a []*Struct{}
+ places2 := []Place{}
+ err = db.Select(&places2, "SELECT * FROM place ORDER BY telcode ASC")
+ usa, singsing, honkers = &places2[0], &places2[1], &places2[2]
+
+ // this should return a type error that &p is not a pointer to a struct slice
+ p := Place{}
+ err = db.Select(&p, "SELECT * FROM place ORDER BY telcode ASC")
+ if err == nil {
+ t.Errorf("Expected an error, argument to select should be a pointer to a struct slice")
+ }
+
+ // this should be an error
+ pl := []Place{}
+ err = db.Select(pl, "SELECT * FROM place ORDER BY telcode ASC")
+ if err == nil {
+ t.Errorf("Expected an error, argument to select should be a pointer to a struct slice, not a slice.")
+ }
+
+ if usa.TelCode != 1 || honkers.TelCode != 852 || singsing.TelCode != 65 {
+ t.Errorf("Expected integer telcodes to work, got %#v", places)
+ }
+
+ stmt, err := db.Preparex(db.Rebind("SELECT country, telcode FROM place WHERE telcode > ? ORDER BY telcode ASC"))
+ if err != nil {
+ t.Error(err)
+ }
+
+ places = []*Place{}
+ err = stmt.Select(&places, 10)
+ if len(places) != 2 {
+ t.Error("Expected 2 places, got 0.")
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+ singsing, honkers = places[0], places[1]
+ if singsing.TelCode != 65 || honkers.TelCode != 852 {
+ t.Errorf("Expected the right telcodes, got %#v", places)
+ }
+
+ rows, err := db.Queryx("SELECT * FROM place")
+ if err != nil {
+ t.Fatal(err)
+ }
+ place := Place{}
+ for rows.Next() {
+ err = rows.StructScan(&place)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ rows, err = db.Queryx("SELECT * FROM place")
+ if err != nil {
+ t.Fatal(err)
+ }
+ m := map[string]interface{}{}
+ for rows.Next() {
+ err = rows.MapScan(m)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, ok := m["country"]
+ if !ok {
+ t.Errorf("Expected key `country` in map but could not find it (%#v)\n", m)
+ }
+ }
+
+ rows, err = db.Queryx("SELECT * FROM place")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for rows.Next() {
+ s, err := rows.SliceScan()
+ if err != nil {
+ t.Error(err)
+ }
+ if len(s) != 3 {
+ t.Errorf("Expected 3 columns in result, got %d\n", len(s))
+ }
+ }
+
+ // test advanced querying
+ // test that NamedExec works with a map as well as a struct
+ _, err = db.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first, :last, :email)", map[string]interface{}{
+ "first": "Bin",
+ "last": "Smuth",
+ "email": "bensmith@allblacks.nz",
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // ensure that if the named param happens right at the end it still works
+ // ensure that NamedQuery works with a map[string]interface{}
+ rows, err = db.NamedQuery("SELECT * FROM person WHERE first_name=:first", map[string]interface{}{"first": "Bin"})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ben := &Person{}
+ for rows.Next() {
+ err = rows.StructScan(ben)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ben.FirstName != "Bin" {
+ t.Fatal("Expected first name of `Bin`, got " + ben.FirstName)
+ }
+ if ben.LastName != "Smuth" {
+ t.Fatal("Expected first name of `Smuth`, got " + ben.LastName)
+ }
+ }
+
+ ben.FirstName = "Ben"
+ ben.LastName = "Smith"
+ ben.Email = "binsmuth@allblacks.nz"
+
+ // Insert via a named query using the struct
+ _, err = db.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", ben)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rows, err = db.NamedQuery("SELECT * FROM person WHERE first_name=:first_name", ben)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for rows.Next() {
+ err = rows.StructScan(ben)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ben.FirstName != "Ben" {
+ t.Fatal("Expected first name of `Ben`, got " + ben.FirstName)
+ }
+ if ben.LastName != "Smith" {
+ t.Fatal("Expected first name of `Smith`, got " + ben.LastName)
+ }
+ }
+ // ensure that Get does not panic on emppty result set
+ person := &Person{}
+ err = db.Get(person, "SELECT * FROM person WHERE first_name=$1", "does-not-exist")
+ if err == nil {
+ t.Fatal("Should have got an error for Get on non-existant row.")
+ }
+
+ // lets test prepared statements some more
+
+ stmt, err = db.Preparex(db.Rebind("SELECT * FROM person WHERE first_name=?"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err = stmt.Queryx("Ben")
+ if err != nil {
+ t.Fatal(err)
+ }
+ for rows.Next() {
+ err = rows.StructScan(ben)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ben.FirstName != "Ben" {
+ t.Fatal("Expected first name of `Ben`, got " + ben.FirstName)
+ }
+ if ben.LastName != "Smith" {
+ t.Fatal("Expected first name of `Smith`, got " + ben.LastName)
+ }
+ }
+
+ john = Person{}
+ stmt, err = db.Preparex(db.Rebind("SELECT * FROM person WHERE first_name=?"))
+ if err != nil {
+ t.Error(err)
+ }
+ err = stmt.Get(&john, "John")
+ if err != nil {
+ t.Error(err)
+ }
+
+ // test name mapping
+ // THIS USED TO WORK BUT WILL NO LONGER WORK.
+ db.MapperFunc(strings.ToUpper)
+ rsa := CPlace{}
+ err = db.Get(&rsa, "SELECT * FROM capplace;")
+ if err != nil {
+ t.Error(err, "in db:", db.DriverName())
+ }
+ db.MapperFunc(strings.ToLower)
+
+ // create a copy and change the mapper, then verify the copy behaves
+ // differently from the original.
+ dbCopy := NewDb(db.DB, db.DriverName())
+ dbCopy.MapperFunc(strings.ToUpper)
+ err = dbCopy.Get(&rsa, "SELECT * FROM capplace;")
+ if err != nil {
+ fmt.Println(db.DriverName())
+ t.Error(err)
+ }
+
+ err = db.Get(&rsa, "SELECT * FROM cappplace;")
+ if err == nil {
+ t.Error("Expected no error, got ", err)
+ }
+
+ // test base type slices
+ var sdest []string
+ rows, err = db.Queryx("SELECT email FROM person ORDER BY email ASC;")
+ if err != nil {
+ t.Error(err)
+ }
+ err = scanAll(rows, &sdest, false)
+ if err != nil {
+ t.Error(err)
+ }
+
+ // test Get with base types
+ var count int
+ err = db.Get(&count, "SELECT count(*) FROM person;")
+ if err != nil {
+ t.Error(err)
+ }
+ if count != len(sdest) {
+ t.Errorf("Expected %d == %d (count(*) vs len(SELECT ..)", count, len(sdest))
+ }
+
+ // test Get and Select with time.Time, #84
+ var addedAt time.Time
+ err = db.Get(&addedAt, "SELECT added_at FROM person LIMIT 1;")
+ if err != nil {
+ t.Error(err)
+ }
+
+ var addedAts []time.Time
+ err = db.Select(&addedAts, "SELECT added_at FROM person;")
+ if err != nil {
+ t.Error(err)
+ }
+
+ // test it on a double pointer
+ var pcount *int
+ err = db.Get(&pcount, "SELECT count(*) FROM person;")
+ if err != nil {
+ t.Error(err)
+ }
+ if *pcount != count {
+ t.Error("expected %d = %d", *pcount, count)
+ }
+
+ // test Select...
+ sdest = []string{}
+ err = db.Select(&sdest, "SELECT first_name FROM person ORDER BY first_name ASC;")
+ if err != nil {
+ t.Error(err)
+ }
+ expected := []string{"Ben", "Bin", "Jason", "John"}
+ for i, got := range sdest {
+ if got != expected[i] {
+ t.Errorf("Expected %d result to be %s, but got %s", i, expected[i], got)
+ }
+ }
+
+ var nsdest []sql.NullString
+ err = db.Select(&nsdest, "SELECT city FROM place ORDER BY city ASC")
+ if err != nil {
+ t.Error(err)
+ }
+ for _, val := range nsdest {
+ if val.Valid && val.String != "New York" {
+ t.Errorf("expected single valid result to be `New York`, but got %s", val.String)
+ }
+ }
+ })
+}
+
+type Product struct {
+ ProductID int
+}
+
+// tests that sqlx will not panic when the wrong driver is passed because
+// of an automatic nil dereference in sqlx.Open(), which was fixed.
+func TestDoNotPanicOnConnect(t *testing.T) {
+ _, err := Connect("bogus", "hehe")
+ if err == nil {
+ t.Errorf("Should return error when using bogus driverName")
+ }
+}
+func TestRebind(t *testing.T) {
+ q1 := `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
+ q2 := `INSERT INTO foo (a, b, c) VALUES (?, ?, "foo"), ("Hi", ?, ?)`
+
+ s1 := Rebind(DOLLAR, q1)
+ s2 := Rebind(DOLLAR, q2)
+
+ if s1 != `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` {
+ t.Errorf("q1 failed")
+ }
+
+ if s2 != `INSERT INTO foo (a, b, c) VALUES ($1, $2, "foo"), ("Hi", $3, $4)` {
+ t.Errorf("q2 failed")
+ }
+}
+
+func TestBindMap(t *testing.T) {
+ // Test that it works..
+ q1 := `INSERT INTO foo (a, b, c, d) VALUES (:name, :age, :first, :last)`
+ am := map[string]interface{}{
+ "name": "Jason Moiron",
+ "age": 30,
+ "first": "Jason",
+ "last": "Moiron",
+ }
+
+ bq, args, _ := bindMap(QUESTION, q1, am)
+ expect := `INSERT INTO foo (a, b, c, d) VALUES (?, ?, ?, ?)`
+ if bq != expect {
+ t.Errorf("Interpolation of query failed: got `%v`, expected `%v`\n", bq, expect)
+ }
+
+ if args[0].(string) != "Jason Moiron" {
+ t.Errorf("Expected `Jason Moiron`, got %v\n", args[0])
+ }
+
+ if args[1].(int) != 30 {
+ t.Errorf("Expected 30, got %v\n", args[1])
+ }
+
+ if args[2].(string) != "Jason" {
+ t.Errorf("Expected Jason, got %v\n", args[2])
+ }
+
+ if args[3].(string) != "Moiron" {
+ t.Errorf("Expected Moiron, got %v\n", args[3])
+ }
+}
+
+// Test for #117, embedded nil maps
+
+type Message struct {
+ Text string `db:"string"`
+ Properties PropertyMap // Stored as JSON in the database
+}
+type PropertyMap map[string]string
+
+// Implement driver.Valuer and sql.Scanner interfaces on PropertyMap
+func (p PropertyMap) Value() (driver.Value, error) {
+ if len(p) == 0 {
+ return nil, nil
+ }
+ return json.Marshal(p)
+}
+
+func (p PropertyMap) Scan(src interface{}) error {
+ v := reflect.ValueOf(src)
+ if !v.IsValid() || v.IsNil() {
+ return nil
+ }
+ if data, ok := src.([]byte); ok {
+ return json.Unmarshal(data, &p)
+ }
+ return fmt.Errorf("Could not not decode type %T -> %T", src, p)
+}
+
+func TestEmbeddedMaps(t *testing.T) {
+ var schema = Schema{
+ create: `
+ CREATE TABLE message (
+ string text,
+ properties text
+ );`,
+ drop: `drop table message;`,
+ }
+
+ RunWithSchema(schema, t, func(db *DB, t *testing.T) {
+ messages := []Message{
+ {"Hello, World", PropertyMap{"one": "1", "two": "2"}},
+ {"Thanks, Joy", PropertyMap{"pull": "request"}},
+ }
+ q1 := `INSERT INTO message (string, properties) VALUES (:string, :properties)`
+ for _, m := range messages {
+ _, err := db.NamedExec(q1, m)
+ if err != nil {
+ t.Error(err)
+ }
+ }
+ var count int
+ err := db.Get(&count, "SELECT count(*) FROM message")
+ if err != nil {
+ t.Error(err)
+ }
+ if count != len(messages) {
+ t.Errorf("Expected %d messages in DB, found %d", len(messages), count)
+ }
+
+ var m Message
+ err = db.Get(&m, "SELECT * FROM message LIMIT 1")
+ if err != nil {
+ t.Error(err)
+ }
+ if m.Properties == nil {
+ t.Error("Expected m.Properties to not be nil, but it was.")
+ }
+ })
+}
+
+func TestBindStruct(t *testing.T) {
+ var err error
+
+ q1 := `INSERT INTO foo (a, b, c, d) VALUES (:name, :age, :first, :last)`
+
+ type tt struct {
+ Name string
+ Age int
+ First string
+ Last string
+ }
+
+ type tt2 struct {
+ Field1 string `db:"field_1"`
+ Field2 string `db:"field_2"`
+ }
+
+ type tt3 struct {
+ tt2
+ Name string
+ }
+
+ am := tt{"Jason Moiron", 30, "Jason", "Moiron"}
+
+ bq, args, _ := bindStruct(QUESTION, q1, am, mapper())
+ expect := `INSERT INTO foo (a, b, c, d) VALUES (?, ?, ?, ?)`
+ if bq != expect {
+ t.Errorf("Interpolation of query failed: got `%v`, expected `%v`\n", bq, expect)
+ }
+
+ if args[0].(string) != "Jason Moiron" {
+ t.Errorf("Expected `Jason Moiron`, got %v\n", args[0])
+ }
+
+ if args[1].(int) != 30 {
+ t.Errorf("Expected 30, got %v\n", args[1])
+ }
+
+ if args[2].(string) != "Jason" {
+ t.Errorf("Expected Jason, got %v\n", args[2])
+ }
+
+ if args[3].(string) != "Moiron" {
+ t.Errorf("Expected Moiron, got %v\n", args[3])
+ }
+
+ am2 := tt2{"Hello", "World"}
+ bq, args, _ = bindStruct(QUESTION, "INSERT INTO foo (a, b) VALUES (:field_2, :field_1)", am2, mapper())
+ expect = `INSERT INTO foo (a, b) VALUES (?, ?)`
+ if bq != expect {
+ t.Errorf("Interpolation of query failed: got `%v`, expected `%v`\n", bq, expect)
+ }
+
+ if args[0].(string) != "World" {
+ t.Errorf("Expected 'World', got %s\n", args[0].(string))
+ }
+ if args[1].(string) != "Hello" {
+ t.Errorf("Expected 'Hello', got %s\n", args[1].(string))
+ }
+
+ am3 := tt3{Name: "Hello!"}
+ am3.Field1 = "Hello"
+ am3.Field2 = "World"
+
+ bq, args, err = bindStruct(QUESTION, "INSERT INTO foo (a, b, c) VALUES (:name, :field_1, :field_2)", am3, mapper())
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expect = `INSERT INTO foo (a, b, c) VALUES (?, ?, ?)`
+ if bq != expect {
+ t.Errorf("Interpolation of query failed: got `%v`, expected `%v`\n", bq, expect)
+ }
+
+ if args[0].(string) != "Hello!" {
+ t.Errorf("Expected 'Hello!', got %s\n", args[0].(string))
+ }
+ if args[1].(string) != "Hello" {
+ t.Errorf("Expected 'Hello', got %s\n", args[1].(string))
+ }
+ if args[2].(string) != "World" {
+ t.Errorf("Expected 'World', got %s\n", args[0].(string))
+ }
+}
+
+func TestEmbeddedLiterals(t *testing.T) {
+ var schema = Schema{
+ create: `
+ CREATE TABLE x (
+ k text
+ );`,
+ drop: `drop table x;`,
+ }
+
+ RunWithSchema(schema, t, func(db *DB, t *testing.T) {
+ type t1 struct {
+ K *string
+ }
+ type t2 struct {
+ Inline struct {
+ F string
+ }
+ K *string
+ }
+
+ db.MustExec(db.Rebind("INSERT INTO x (k) VALUES (?), (?), (?);"), "one", "two", "three")
+
+ target := t1{}
+ err := db.Get(&target, db.Rebind("SELECT * FROM x WHERE k=?"), "one")
+ if err != nil {
+ t.Error(err)
+ }
+ if *target.K != "one" {
+ t.Error("Expected target.K to be `one`, got ", target.K)
+ }
+
+ target2 := t2{}
+ err = db.Get(&target2, db.Rebind("SELECT * FROM x WHERE k=?"), "one")
+ if err != nil {
+ t.Error(err)
+ }
+ if *target2.K != "one" {
+ t.Errorf("Expected target2.K to be `one`, got `%v`", target2.K)
+ }
+
+ })
+}
+
+func BenchmarkBindStruct(b *testing.B) {
+ b.StopTimer()
+ q1 := `INSERT INTO foo (a, b, c, d) VALUES (:name, :age, :first, :last)`
+ type t struct {
+ Name string
+ Age int
+ First string
+ Last string
+ }
+ am := t{"Jason Moiron", 30, "Jason", "Moiron"}
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ bindStruct(DOLLAR, q1, am, mapper())
+ //bindMap(QUESTION, q1, am)
+ }
+}
+
+func BenchmarkBindMap(b *testing.B) {
+ b.StopTimer()
+ q1 := `INSERT INTO foo (a, b, c, d) VALUES (:name, :age, :first, :last)`
+ am := map[string]interface{}{
+ "name": "Jason Moiron",
+ "age": 30,
+ "first": "Jason",
+ "last": "Moiron",
+ }
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ bindMap(DOLLAR, q1, am)
+ //bindMap(QUESTION, q1, am)
+ }
+}
+
+func BenchmarkRebind(b *testing.B) {
+ b.StopTimer()
+ q1 := `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
+ q2 := `INSERT INTO foo (a, b, c) VALUES (?, ?, "foo"), ("Hi", ?, ?)`
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ Rebind(DOLLAR, q1)
+ Rebind(DOLLAR, q2)
+ }
+}
+
+func BenchmarkRebindBuffer(b *testing.B) {
+ b.StopTimer()
+ q1 := `INSERT INTO foo (a, b, c, d, e, f, g, h, i) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
+ q2 := `INSERT INTO foo (a, b, c) VALUES (?, ?, "foo"), ("Hi", ?, ?)`
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ rebindBuff(DOLLAR, q1)
+ rebindBuff(DOLLAR, q2)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/README.md b/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/README.md
new file mode 100644
index 0000000..713abe5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/README.md
@@ -0,0 +1,5 @@
+# types
+
+The types package provides some useful types which implement the `sql.Scanner`
+and `driver.Valuer` interfaces, suitable for use as scan and value targets with
+database/sql.
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/types.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/types.go
new file mode 100644
index 0000000..f1700b6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/types.go
@@ -0,0 +1,95 @@
+package types
+
+import (
+ "bytes"
+ "compress/gzip"
+ "database/sql/driver"
+ "encoding/json"
+ "errors"
+
+ "io/ioutil"
+)
+
+type GzippedText []byte
+
+func (g GzippedText) Value() (driver.Value, error) {
+ b := make([]byte, 0, len(g))
+ buf := bytes.NewBuffer(b)
+ w := gzip.NewWriter(buf)
+ w.Write(g)
+ w.Close()
+ return buf.Bytes(), nil
+
+}
+
+func (g *GzippedText) Scan(src interface{}) error {
+ var source []byte
+ switch src.(type) {
+ case string:
+ source = []byte(src.(string))
+ case []byte:
+ source = src.([]byte)
+ default:
+ return errors.New("Incompatible type for GzippedText")
+ }
+ reader, err := gzip.NewReader(bytes.NewReader(source))
+ defer reader.Close()
+ b, err := ioutil.ReadAll(reader)
+ if err != nil {
+ return err
+ }
+ *g = GzippedText(b)
+ return nil
+}
+
+// JsonText is a json.RawMessage, which is a []byte underneath.
+// Value() validates the json format in the source, and returns an error if
+// the json is not valid. Scan does no validation. JsonText additionally
+// implements `Unmarshal`, which unmarshals the json within to an interface{}
+type JsonText json.RawMessage
+
+// Returns the *j as the JSON encoding of j.
+func (j *JsonText) MarshalJSON() ([]byte, error) {
+ return *j, nil
+}
+
+// UnmarshalJSON sets *j to a copy of data
+func (j *JsonText) UnmarshalJSON(data []byte) error {
+ if j == nil {
+ return errors.New("JsonText: UnmarshalJSON on nil pointer")
+ }
+ *j = append((*j)[0:0], data...)
+ return nil
+
+}
+
+// Value returns j as a value. This does a validating unmarshal into another
+// RawMessage. If j is invalid json, it returns an error.
+func (j JsonText) Value() (driver.Value, error) {
+ var m json.RawMessage
+ var err = j.Unmarshal(&m)
+ if err != nil {
+ return []byte{}, err
+ }
+ return []byte(j), nil
+}
+
+// Scan stores the src in *j. No validation is done.
+func (j *JsonText) Scan(src interface{}) error {
+ var source []byte
+ switch src.(type) {
+ case string:
+ source = []byte(src.(string))
+ case []byte:
+ source = src.([]byte)
+ default:
+ return errors.New("Incompatible type for JsonText")
+ }
+ *j = JsonText(append((*j)[0:0], source...))
+ return nil
+}
+
+// Unmarshal unmarshal's the json in j to v, as in json.Unmarshal.
+func (j *JsonText) Unmarshal(v interface{}) error {
+ return json.Unmarshal([]byte(*j), v)
+}
diff --git a/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/types_test.go b/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/types_test.go
new file mode 100644
index 0000000..e5c9e1a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/jmoiron/sqlx/types/types_test.go
@@ -0,0 +1,42 @@
+package types
+
+import "testing"
+
+func TestGzipText(t *testing.T) {
+ g := GzippedText("Hello, world")
+ v, err := g.Value()
+ if err != nil {
+ t.Errorf("Was not expecting an error")
+ }
+ err = (&g).Scan(v)
+ if err != nil {
+ t.Errorf("Was not expecting an error")
+ }
+ if string(g) != "Hello, world" {
+ t.Errorf("Was expecting the string we sent in (Hello World), got %s", string(g))
+ }
+}
+
+func TestJsonText(t *testing.T) {
+ j := JsonText(`{"foo": 1, "bar": 2}`)
+ v, err := j.Value()
+ if err != nil {
+ t.Errorf("Was not expecting an error")
+ }
+ err = (&j).Scan(v)
+ if err != nil {
+ t.Errorf("Was not expecting an error")
+ }
+ m := map[string]interface{}{}
+ j.Unmarshal(&m)
+
+ if m["foo"].(float64) != 1 || m["bar"].(float64) != 2 {
+ t.Errorf("Expected valid json but got some garbage instead? %#v", m)
+ }
+
+ j = JsonText(`{"foo": 1, invalid, false}`)
+ v, err = j.Value()
+ if err == nil {
+ t.Errorf("Was expecting invalid json to fail!")
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/.gitignore b/Godeps/_workspace/src/github.com/lib/pq/.gitignore
new file mode 100644
index 0000000..0f1d00e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/.gitignore
@@ -0,0 +1,4 @@
+.db
+*.test
+*~
+*.swp
diff --git a/Godeps/_workspace/src/github.com/lib/pq/.travis.yml b/Godeps/_workspace/src/github.com/lib/pq/.travis.yml
new file mode 100644
index 0000000..82df2a0
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/.travis.yml
@@ -0,0 +1,61 @@
+language: go
+
+go:
+ - 1.1
+ - 1.2
+ - 1.3
+ - 1.4
+ - tip
+
+before_install:
+ - psql --version
+ - sudo /etc/init.d/postgresql stop
+ - sudo apt-get -y --purge remove postgresql libpq-dev libpq5 postgresql-client-common postgresql-common
+ - sudo rm -rf /var/lib/postgresql
+ - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
+ - sudo sh -c "echo deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main $PGVERSION >> /etc/apt/sources.list.d/postgresql.list"
+ - sudo apt-get update -qq
+ - sudo apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::="--force-confnew" install postgresql-$PGVERSION postgresql-server-dev-$PGVERSION postgresql-contrib-$PGVERSION
+ - sudo chmod 777 /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "local all postgres trust" > /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "local all all trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "hostnossl all pqgossltest 127.0.0.1/32 reject" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "hostnossl all pqgosslcert 127.0.0.1/32 reject" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "hostssl all pqgossltest 127.0.0.1/32 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "hostssl all pqgosslcert 127.0.0.1/32 cert" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "host all all 127.0.0.1/32 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "hostnossl all pqgossltest ::1/128 reject" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "hostnossl all pqgosslcert ::1/128 reject" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "hostssl all pqgossltest ::1/128 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "hostssl all pqgosslcert ::1/128 cert" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - echo "host all all ::1/128 trust" >> /etc/postgresql/$PGVERSION/main/pg_hba.conf
+ - sudo install -o postgres -g postgres -m 600 -t /var/lib/postgresql/$PGVERSION/main/ certs/server.key certs/server.crt certs/root.crt
+ - sudo bash -c "[[ '${PGVERSION}' < '9.2' ]] || (echo \"ssl_cert_file = 'server.crt'\" >> /etc/postgresql/$PGVERSION/main/postgresql.conf)"
+ - sudo bash -c "[[ '${PGVERSION}' < '9.2' ]] || (echo \"ssl_key_file = 'server.key'\" >> /etc/postgresql/$PGVERSION/main/postgresql.conf)"
+ - sudo bash -c "[[ '${PGVERSION}' < '9.2' ]] || (echo \"ssl_ca_file = 'root.crt'\" >> /etc/postgresql/$PGVERSION/main/postgresql.conf)"
+ - sudo sh -c "echo 127.0.0.1 postgres >> /etc/hosts"
+ - sudo ls -l /var/lib/postgresql/$PGVERSION/main/
+ - sudo cat /etc/postgresql/$PGVERSION/main/postgresql.conf
+ - sudo chmod 600 $PQSSLCERTTEST_PATH/postgresql.key
+ - sudo /etc/init.d/postgresql restart
+
+env:
+ global:
+ - PGUSER=postgres
+ - PQGOSSLTESTS=1
+ - PQSSLCERTTEST_PATH=$PWD/certs
+ matrix:
+ - PGVERSION=9.4
+ - PGVERSION=9.3
+ - PGVERSION=9.2
+ - PGVERSION=9.1
+ - PGVERSION=9.0
+ - PGVERSION=8.4
+
+script:
+ - go test -v ./...
+
+before_script:
+ - psql -c 'create database pqgotest' -U postgres
+ - psql -c 'create user pqgossltest' -U postgres
+ - psql -c 'create user pqgosslcert' -U postgres
diff --git a/Godeps/_workspace/src/github.com/lib/pq/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/lib/pq/CONTRIBUTING.md
new file mode 100644
index 0000000..84c937f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/CONTRIBUTING.md
@@ -0,0 +1,29 @@
+## Contributing to pq
+
+`pq` has a backlog of pull requests, but contributions are still very
+much welcome. You can help with patch review, submitting bug reports,
+or adding new functionality. There is no formal style guide, but
+please conform to the style of existing code and general Go formatting
+conventions when submitting patches.
+
+### Patch review
+
+Help review existing open pull requests by commenting on the code or
+proposed functionality.
+
+### Bug reports
+
+We appreciate any bug reports, but especially ones with self-contained
+(doesn't depend on code outside of pq), minimal (can't be simplified
+further) test cases. It's especially helpful if you can submit a pull
+request with just the failing test case (you'll probably want to
+pattern it after the tests in
+[conn_test.go](https://github.com/lib/pq/blob/master/conn_test.go).
+
+### New functionality
+
+There are a number of pending patches for new functionality, so
+additional feature patches will take a while to merge. Still, patches
+are generally reviewed based on usefulness and complexity in addition
+to time-in-queue, so if you have a knockout idea, take a shot. Feel
+free to open an issue discussion your proposed patch beforehand.
diff --git a/Godeps/_workspace/src/github.com/lib/pq/LICENSE.md b/Godeps/_workspace/src/github.com/lib/pq/LICENSE.md
new file mode 100644
index 0000000..5773904
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/LICENSE.md
@@ -0,0 +1,8 @@
+Copyright (c) 2011-2013, 'pq' Contributors
+Portions Copyright (C) 2011 Blake Mizerany
+
+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/Godeps/_workspace/src/github.com/lib/pq/README.md b/Godeps/_workspace/src/github.com/lib/pq/README.md
new file mode 100644
index 0000000..cb15d6f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/README.md
@@ -0,0 +1,98 @@
+# pq - A pure Go postgres driver for Go's database/sql package
+
+[](https://travis-ci.org/lib/pq)
+
+## Install
+
+ go get github.com/lib/pq
+
+## Docs
+
+For detailed documentation and basic usage examples, please see the package
+documentation at .
+
+## Tests
+
+`go test` is used for testing. A running PostgreSQL server is
+required, with the ability to log in. The default database to connect
+to test with is "pqgotest," but it can be overridden using environment
+variables.
+
+Example:
+
+ PGHOST=/var/run/postgresql go test github.com/lib/pq
+
+Optionally, a benchmark suite can be run as part of the tests:
+
+ PGHOST=/var/run/postgresql go test -bench .
+
+## Features
+
+* SSL
+* Handles bad connections for `database/sql`
+* Scan `time.Time` correctly (i.e. `timestamp[tz]`, `time[tz]`, `date`)
+* Scan binary blobs correctly (i.e. `bytea`)
+* Package for `hstore` support
+* COPY FROM support
+* pq.ParseURL for converting urls to connection strings for sql.Open.
+* Many libpq compatible environment variables
+* Unix socket support
+* Notifications: `LISTEN`/`NOTIFY`
+
+## Future / Things you can help with
+
+* Better COPY FROM / COPY TO (see discussion in #181)
+
+## Thank you (alphabetical)
+
+Some of these contributors are from the original library `bmizerany/pq.go` whose
+code still exists in here.
+
+* Andy Balholm (andybalholm)
+* Ben Berkert (benburkert)
+* Benjamin Heatwole (bheatwole)
+* Bill Mill (llimllib)
+* Bjørn Madsen (aeons)
+* Blake Gentry (bgentry)
+* Brad Fitzpatrick (bradfitz)
+* Charlie Melbye (cmelbye)
+* Chris Bandy (cbandy)
+* Chris Walsh (cwds)
+* Dan Sosedoff (sosedoff)
+* Daniel Farina (fdr)
+* Eric Chlebek (echlebek)
+* Everyone at The Go Team
+* Evan Shaw (edsrzf)
+* Ewan Chou (coocood)
+* Federico Romero (federomero)
+* Fumin (fumin)
+* Gary Burd (garyburd)
+* Heroku (heroku)
+* James Pozdena (jpoz)
+* Jason McVetta (jmcvetta)
+* Jeremy Jay (pbnjay)
+* Joakim Sernbrant (serbaut)
+* John Gallagher (jgallagher)
+* Jonathan Rudenberg (titanous)
+* Joël Stemmer (jstemmer)
+* Kamil Kisiel (kisielk)
+* Kelly Dunn (kellydunn)
+* Keith Rarick (kr)
+* Kir Shatrov (kirs)
+* Lann Martin (lann)
+* Maciek Sakrejda (deafbybeheading)
+* Marc Brinkmann (mbr)
+* Marko Tiikkaja (johto)
+* Matt Newberry (MattNewberry)
+* Matt Robenolt (mattrobenolt)
+* Martin Olsen (martinolsen)
+* Mike Lewis (mikelikespie)
+* Nicolas Patry (Narsil)
+* Oliver Tonnhofer (olt)
+* Patrick Hayes (phayes)
+* Paul Hammond (paulhammond)
+* Ryan Smith (ryandotsmith)
+* Samuel Stauffer (samuel)
+* Timothée Peignier (cyberdelia)
+* TruongSinh Tran-Nguyen (truongsinh)
+* notedit (notedit)
diff --git a/Godeps/_workspace/src/github.com/lib/pq/bench_test.go b/Godeps/_workspace/src/github.com/lib/pq/bench_test.go
new file mode 100644
index 0000000..f2eb1da
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/bench_test.go
@@ -0,0 +1,434 @@
+// +build go1.1
+
+package pq
+
+import (
+ "bufio"
+ "bytes"
+ "database/sql"
+ "database/sql/driver"
+ "github.com/lib/pq/oid"
+ "io"
+ "math/rand"
+ "net"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+)
+
+var (
+ selectStringQuery = "SELECT '" + strings.Repeat("0123456789", 10) + "'"
+ selectSeriesQuery = "SELECT generate_series(1, 100)"
+)
+
+func BenchmarkSelectString(b *testing.B) {
+ var result string
+ benchQuery(b, selectStringQuery, &result)
+}
+
+func BenchmarkSelectSeries(b *testing.B) {
+ var result int
+ benchQuery(b, selectSeriesQuery, &result)
+}
+
+func benchQuery(b *testing.B, query string, result interface{}) {
+ b.StopTimer()
+ db := openTestConn(b)
+ defer db.Close()
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchQueryLoop(b, db, query, result)
+ }
+}
+
+func benchQueryLoop(b *testing.B, db *sql.DB, query string, result interface{}) {
+ rows, err := db.Query(query)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err = rows.Scan(result)
+ if err != nil {
+ b.Fatal("failed to scan", err)
+ }
+ }
+}
+
+// reading from circularConn yields content[:prefixLen] once, followed by
+// content[prefixLen:] over and over again. It never returns EOF.
+type circularConn struct {
+ content string
+ prefixLen int
+ pos int
+ net.Conn // for all other net.Conn methods that will never be called
+}
+
+func (r *circularConn) Read(b []byte) (n int, err error) {
+ n = copy(b, r.content[r.pos:])
+ r.pos += n
+ if r.pos >= len(r.content) {
+ r.pos = r.prefixLen
+ }
+ return
+}
+
+func (r *circularConn) Write(b []byte) (n int, err error) { return len(b), nil }
+
+func (r *circularConn) Close() error { return nil }
+
+func fakeConn(content string, prefixLen int) *conn {
+ c := &circularConn{content: content, prefixLen: prefixLen}
+ return &conn{buf: bufio.NewReader(c), c: c}
+}
+
+// This benchmark is meant to be the same as BenchmarkSelectString, but takes
+// out some of the factors this package can't control. The numbers are less noisy,
+// but also the costs of network communication aren't accurately represented.
+func BenchmarkMockSelectString(b *testing.B) {
+ b.StopTimer()
+ // taken from a recorded run of BenchmarkSelectString
+ // See: http://www.postgresql.org/docs/current/static/protocol-message-formats.html
+ const response = "1\x00\x00\x00\x04" +
+ "t\x00\x00\x00\x06\x00\x00" +
+ "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
+ "Z\x00\x00\x00\x05I" +
+ "2\x00\x00\x00\x04" +
+ "D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
+ "C\x00\x00\x00\rSELECT 1\x00" +
+ "Z\x00\x00\x00\x05I" +
+ "3\x00\x00\x00\x04" +
+ "Z\x00\x00\x00\x05I"
+ c := fakeConn(response, 0)
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchMockQuery(b, c, selectStringQuery)
+ }
+}
+
+var seriesRowData = func() string {
+ var buf bytes.Buffer
+ for i := 1; i <= 100; i++ {
+ digits := byte(2)
+ if i >= 100 {
+ digits = 3
+ } else if i < 10 {
+ digits = 1
+ }
+ buf.WriteString("D\x00\x00\x00")
+ buf.WriteByte(10 + digits)
+ buf.WriteString("\x00\x01\x00\x00\x00")
+ buf.WriteByte(digits)
+ buf.WriteString(strconv.Itoa(i))
+ }
+ return buf.String()
+}()
+
+func BenchmarkMockSelectSeries(b *testing.B) {
+ b.StopTimer()
+ var response = "1\x00\x00\x00\x04" +
+ "t\x00\x00\x00\x06\x00\x00" +
+ "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
+ "Z\x00\x00\x00\x05I" +
+ "2\x00\x00\x00\x04" +
+ seriesRowData +
+ "C\x00\x00\x00\x0fSELECT 100\x00" +
+ "Z\x00\x00\x00\x05I" +
+ "3\x00\x00\x00\x04" +
+ "Z\x00\x00\x00\x05I"
+ c := fakeConn(response, 0)
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchMockQuery(b, c, selectSeriesQuery)
+ }
+}
+
+func benchMockQuery(b *testing.B, c *conn, query string) {
+ stmt, err := c.Prepare(query)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer stmt.Close()
+ rows, err := stmt.Query(nil)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer rows.Close()
+ var dest [1]driver.Value
+ for {
+ if err := rows.Next(dest[:]); err != nil {
+ if err == io.EOF {
+ break
+ }
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkPreparedSelectString(b *testing.B) {
+ var result string
+ benchPreparedQuery(b, selectStringQuery, &result)
+}
+
+func BenchmarkPreparedSelectSeries(b *testing.B) {
+ var result int
+ benchPreparedQuery(b, selectSeriesQuery, &result)
+}
+
+func benchPreparedQuery(b *testing.B, query string, result interface{}) {
+ b.StopTimer()
+ db := openTestConn(b)
+ defer db.Close()
+ stmt, err := db.Prepare(query)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer stmt.Close()
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchPreparedQueryLoop(b, db, stmt, result)
+ }
+}
+
+func benchPreparedQueryLoop(b *testing.B, db *sql.DB, stmt *sql.Stmt, result interface{}) {
+ rows, err := stmt.Query()
+ if err != nil {
+ b.Fatal(err)
+ }
+ if !rows.Next() {
+ rows.Close()
+ b.Fatal("no rows")
+ }
+ defer rows.Close()
+ for rows.Next() {
+ err = rows.Scan(&result)
+ if err != nil {
+ b.Fatal("failed to scan")
+ }
+ }
+}
+
+// See the comment for BenchmarkMockSelectString.
+func BenchmarkMockPreparedSelectString(b *testing.B) {
+ b.StopTimer()
+ const parseResponse = "1\x00\x00\x00\x04" +
+ "t\x00\x00\x00\x06\x00\x00" +
+ "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
+ "Z\x00\x00\x00\x05I"
+ const responses = parseResponse +
+ "2\x00\x00\x00\x04" +
+ "D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
+ "C\x00\x00\x00\rSELECT 1\x00" +
+ "Z\x00\x00\x00\x05I"
+ c := fakeConn(responses, len(parseResponse))
+
+ stmt, err := c.Prepare(selectStringQuery)
+ if err != nil {
+ b.Fatal(err)
+ }
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchPreparedMockQuery(b, c, stmt)
+ }
+}
+
+func BenchmarkMockPreparedSelectSeries(b *testing.B) {
+ b.StopTimer()
+ const parseResponse = "1\x00\x00\x00\x04" +
+ "t\x00\x00\x00\x06\x00\x00" +
+ "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
+ "Z\x00\x00\x00\x05I"
+ var responses = parseResponse +
+ "2\x00\x00\x00\x04" +
+ seriesRowData +
+ "C\x00\x00\x00\x0fSELECT 100\x00" +
+ "Z\x00\x00\x00\x05I"
+ c := fakeConn(responses, len(parseResponse))
+
+ stmt, err := c.Prepare(selectSeriesQuery)
+ if err != nil {
+ b.Fatal(err)
+ }
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ benchPreparedMockQuery(b, c, stmt)
+ }
+}
+
+func benchPreparedMockQuery(b *testing.B, c *conn, stmt driver.Stmt) {
+ rows, err := stmt.Query(nil)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer rows.Close()
+ var dest [1]driver.Value
+ for {
+ if err := rows.Next(dest[:]); err != nil {
+ if err == io.EOF {
+ break
+ }
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkEncodeInt64(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ encode(¶meterStatus{}, int64(1234), oid.T_int8)
+ }
+}
+
+func BenchmarkEncodeFloat64(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ encode(¶meterStatus{}, 3.14159, oid.T_float8)
+ }
+}
+
+var testByteString = []byte("abcdefghijklmnopqrstuvwxyz")
+
+func BenchmarkEncodeByteaHex(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ encode(¶meterStatus{serverVersion: 90000}, testByteString, oid.T_bytea)
+ }
+}
+func BenchmarkEncodeByteaEscape(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ encode(¶meterStatus{serverVersion: 84000}, testByteString, oid.T_bytea)
+ }
+}
+
+func BenchmarkEncodeBool(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ encode(¶meterStatus{}, true, oid.T_bool)
+ }
+}
+
+var testTimestamptz = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.Local)
+
+func BenchmarkEncodeTimestamptz(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ encode(¶meterStatus{}, testTimestamptz, oid.T_timestamptz)
+ }
+}
+
+var testIntBytes = []byte("1234")
+
+func BenchmarkDecodeInt64(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ decode(¶meterStatus{}, testIntBytes, oid.T_int8)
+ }
+}
+
+var testFloatBytes = []byte("3.14159")
+
+func BenchmarkDecodeFloat64(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ decode(¶meterStatus{}, testFloatBytes, oid.T_float8)
+ }
+}
+
+var testBoolBytes = []byte{'t'}
+
+func BenchmarkDecodeBool(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ decode(¶meterStatus{}, testBoolBytes, oid.T_bool)
+ }
+}
+
+func TestDecodeBool(t *testing.T) {
+ db := openTestConn(t)
+ rows, err := db.Query("select true")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Close()
+}
+
+var testTimestamptzBytes = []byte("2013-09-17 22:15:32.360754-07")
+
+func BenchmarkDecodeTimestamptz(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz)
+ }
+}
+
+func BenchmarkDecodeTimestamptzMultiThread(b *testing.B) {
+ oldProcs := runtime.GOMAXPROCS(0)
+ defer runtime.GOMAXPROCS(oldProcs)
+ runtime.GOMAXPROCS(runtime.NumCPU())
+ globalLocationCache = newLocationCache()
+
+ f := func(wg *sync.WaitGroup, loops int) {
+ defer wg.Done()
+ for i := 0; i < loops; i++ {
+ decode(¶meterStatus{}, testTimestamptzBytes, oid.T_timestamptz)
+ }
+ }
+
+ wg := &sync.WaitGroup{}
+ b.ResetTimer()
+ for j := 0; j < 10; j++ {
+ wg.Add(1)
+ go f(wg, b.N/10)
+ }
+ wg.Wait()
+}
+
+func BenchmarkLocationCache(b *testing.B) {
+ globalLocationCache = newLocationCache()
+ for i := 0; i < b.N; i++ {
+ globalLocationCache.getLocation(rand.Intn(10000))
+ }
+}
+
+func BenchmarkLocationCacheMultiThread(b *testing.B) {
+ oldProcs := runtime.GOMAXPROCS(0)
+ defer runtime.GOMAXPROCS(oldProcs)
+ runtime.GOMAXPROCS(runtime.NumCPU())
+ globalLocationCache = newLocationCache()
+
+ f := func(wg *sync.WaitGroup, loops int) {
+ defer wg.Done()
+ for i := 0; i < loops; i++ {
+ globalLocationCache.getLocation(rand.Intn(10000))
+ }
+ }
+
+ wg := &sync.WaitGroup{}
+ b.ResetTimer()
+ for j := 0; j < 10; j++ {
+ wg.Add(1)
+ go f(wg, b.N/10)
+ }
+ wg.Wait()
+}
+
+// Stress test the performance of parsing results from the wire.
+func BenchmarkResultParsing(b *testing.B) {
+ b.StopTimer()
+
+ db := openTestConn(b)
+ defer db.Close()
+ _, err := db.Exec("BEGIN")
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ res, err := db.Query("SELECT generate_series(1, 50000)")
+ if err != nil {
+ b.Fatal(err)
+ }
+ res.Close()
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/buf.go b/Godeps/_workspace/src/github.com/lib/pq/buf.go
new file mode 100644
index 0000000..9f417a1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/buf.go
@@ -0,0 +1,73 @@
+package pq
+
+import (
+ "bytes"
+ "encoding/binary"
+ "github.com/lib/pq/oid"
+)
+
+type readBuf []byte
+
+func (b *readBuf) int32() (n int) {
+ n = int(int32(binary.BigEndian.Uint32(*b)))
+ *b = (*b)[4:]
+ return
+}
+
+func (b *readBuf) oid() (n oid.Oid) {
+ n = oid.Oid(binary.BigEndian.Uint32(*b))
+ *b = (*b)[4:]
+ return
+}
+
+func (b *readBuf) int16() (n int) {
+ n = int(binary.BigEndian.Uint16(*b))
+ *b = (*b)[2:]
+ return
+}
+
+func (b *readBuf) string() string {
+ i := bytes.IndexByte(*b, 0)
+ if i < 0 {
+ errorf("invalid message format; expected string terminator")
+ }
+ s := (*b)[:i]
+ *b = (*b)[i+1:]
+ return string(s)
+}
+
+func (b *readBuf) next(n int) (v []byte) {
+ v = (*b)[:n]
+ *b = (*b)[n:]
+ return
+}
+
+func (b *readBuf) byte() byte {
+ return b.next(1)[0]
+}
+
+type writeBuf []byte
+
+func (b *writeBuf) int32(n int) {
+ x := make([]byte, 4)
+ binary.BigEndian.PutUint32(x, uint32(n))
+ *b = append(*b, x...)
+}
+
+func (b *writeBuf) int16(n int) {
+ x := make([]byte, 2)
+ binary.BigEndian.PutUint16(x, uint16(n))
+ *b = append(*b, x...)
+}
+
+func (b *writeBuf) string(s string) {
+ *b = append(*b, (s + "\000")...)
+}
+
+func (b *writeBuf) byte(c byte) {
+ *b = append(*b, c)
+}
+
+func (b *writeBuf) bytes(v []byte) {
+ *b = append(*b, v...)
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/certs/README b/Godeps/_workspace/src/github.com/lib/pq/certs/README
new file mode 100644
index 0000000..24ab7b2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/certs/README
@@ -0,0 +1,3 @@
+This directory contains certificates and private keys for testing some
+SSL-related functionality in Travis. Do NOT use these certificates for
+anything other than testing.
diff --git a/Godeps/_workspace/src/github.com/lib/pq/certs/postgresql.crt b/Godeps/_workspace/src/github.com/lib/pq/certs/postgresql.crt
new file mode 100644
index 0000000..6e6b428
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/certs/postgresql.crt
@@ -0,0 +1,69 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 2 (0x2)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, ST=Nevada, L=Las Vegas, O=github.com/lib/pq, CN=pq CA
+ Validity
+ Not Before: Oct 11 15:10:11 2014 GMT
+ Not After : Oct 8 15:10:11 2024 GMT
+ Subject: C=US, ST=Nevada, L=Las Vegas, O=github.com/lib/pq, CN=pqgosslcert
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:e3:8c:06:9a:70:54:51:d1:34:34:83:39:cd:a2:
+ 59:0f:05:ed:8d:d8:0e:34:d0:92:f4:09:4d:ee:8c:
+ 78:55:49:24:f8:3c:e0:34:58:02:b2:e7:94:58:c1:
+ e8:e5:bb:d1:af:f6:54:c1:40:b1:90:70:79:0d:35:
+ 54:9c:8f:16:e9:c2:f0:92:e6:64:49:38:c1:76:f8:
+ 47:66:c4:5b:4a:b6:a9:43:ce:c8:be:6c:4d:2b:94:
+ 97:3c:55:bc:d1:d0:6e:b7:53:ae:89:5c:4b:6b:86:
+ 40:be:c1:ae:1e:64:ce:9c:ae:87:0a:69:e5:c8:21:
+ 12:be:ae:1d:f6:45:df:16:a7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 9B:25:31:63:A2:D8:06:FF:CB:E3:E9:96:FF:0D:BA:DC:12:7D:04:CF
+ X509v3 Authority Key Identifier:
+ keyid:52:93:ED:1E:76:0A:9F:65:4F:DE:19:66:C1:D5:22:40:35:CB:A0:72
+
+ X509v3 Basic Constraints:
+ CA:FALSE
+ X509v3 Key Usage:
+ Digital Signature, Non Repudiation, Key Encipherment
+ Signature Algorithm: sha256WithRSAEncryption
+ 3e:f5:f8:0b:4e:11:bd:00:86:1f:ce:dc:97:02:98:91:11:f5:
+ 65:f6:f2:8a:b2:3e:47:92:05:69:28:c9:e9:b4:f7:cf:93:d1:
+ 2d:81:5d:00:3c:23:be:da:70:ea:59:e1:2c:d3:25:49:ae:a6:
+ 95:54:c1:10:df:23:e3:fe:d6:e4:76:c7:6b:73:ad:1b:34:7c:
+ e2:56:cc:c0:37:ae:c5:7a:11:20:6c:3d:05:0e:99:cd:22:6c:
+ cf:59:a1:da:28:d4:65:ba:7d:2f:2b:3d:69:6d:a6:c1:ae:57:
+ bf:56:64:13:79:f8:48:46:65:eb:81:67:28:0b:7b:de:47:10:
+ b3:80:3c:31:d1:58:94:01:51:4a:c7:c8:1a:01:a8:af:c4:cd:
+ bb:84:a5:d9:8b:b4:b9:a1:64:3e:95:d9:90:1d:d5:3f:67:cc:
+ 3b:ba:f5:b4:d1:33:77:ee:c2:d2:3e:7e:c5:66:6e:b7:35:4c:
+ 60:57:b0:b8:be:36:c8:f3:d3:95:8c:28:4a:c9:f7:27:a4:0d:
+ e5:96:99:eb:f5:c8:bd:f3:84:6d:ef:02:f9:8a:36:7d:6b:5f:
+ 36:68:37:41:d9:74:ae:c6:78:2e:44:86:a1:ad:43:ca:fb:b5:
+ 3e:ba:10:23:09:02:ac:62:d1:d0:83:c8:95:b9:e3:5e:30:ff:
+ 5b:2b:38:fa
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQsFADBeMQswCQYDVQQGEwJVUzEP
+MA0GA1UECBMGTmV2YWRhMRIwEAYDVQQHEwlMYXMgVmVnYXMxGjAYBgNVBAoTEWdp
+dGh1Yi5jb20vbGliL3BxMQ4wDAYDVQQDEwVwcSBDQTAeFw0xNDEwMTExNTEwMTFa
+Fw0yNDEwMDgxNTEwMTFaMGQxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGEx
+EjAQBgNVBAcTCUxhcyBWZWdhczEaMBgGA1UEChMRZ2l0aHViLmNvbS9saWIvcHEx
+FDASBgNVBAMTC3BxZ29zc2xjZXJ0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQDjjAaacFRR0TQ0gznNolkPBe2N2A400JL0CU3ujHhVSST4POA0WAKy55RYwejl
+u9Gv9lTBQLGQcHkNNVScjxbpwvCS5mRJOMF2+EdmxFtKtqlDzsi+bE0rlJc8VbzR
+0G63U66JXEtrhkC+wa4eZM6crocKaeXIIRK+rh32Rd8WpwIDAQABo1owWDAdBgNV
+HQ4EFgQUmyUxY6LYBv/L4+mW/w263BJ9BM8wHwYDVR0jBBgwFoAUUpPtHnYKn2VP
+3hlmwdUiQDXLoHIwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL
+BQADggEBAD71+AtOEb0Ahh/O3JcCmJER9WX28oqyPkeSBWkoyem098+T0S2BXQA8
+I77acOpZ4SzTJUmuppVUwRDfI+P+1uR2x2tzrRs0fOJWzMA3rsV6ESBsPQUOmc0i
+bM9Zodoo1GW6fS8rPWltpsGuV79WZBN5+EhGZeuBZygLe95HELOAPDHRWJQBUUrH
+yBoBqK/EzbuEpdmLtLmhZD6V2ZAd1T9nzDu69bTRM3fuwtI+fsVmbrc1TGBXsLi+
+Nsjz05WMKErJ9yekDeWWmev1yL3zhG3vAvmKNn1rXzZoN0HZdK7GeC5EhqGtQ8r7
+tT66ECMJAqxi0dCDyJW5414w/1srOPo=
+-----END CERTIFICATE-----
diff --git a/Godeps/_workspace/src/github.com/lib/pq/certs/postgresql.key b/Godeps/_workspace/src/github.com/lib/pq/certs/postgresql.key
new file mode 100644
index 0000000..eb8b20b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/certs/postgresql.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDjjAaacFRR0TQ0gznNolkPBe2N2A400JL0CU3ujHhVSST4POA0
+WAKy55RYwejlu9Gv9lTBQLGQcHkNNVScjxbpwvCS5mRJOMF2+EdmxFtKtqlDzsi+
+bE0rlJc8VbzR0G63U66JXEtrhkC+wa4eZM6crocKaeXIIRK+rh32Rd8WpwIDAQAB
+AoGAM5dM6/kp9P700i8qjOgRPym96Zoh5nGfz/rIE5z/r36NBkdvIg8OVZfR96nH
+b0b9TOMR5lsPp0sI9yivTWvX6qyvLJRWy2vvx17hXK9NxXUNTAm0PYZUTvCtcPeX
+RnJpzQKNZQPkFzF0uXBc4CtPK2Vz0+FGvAelrhYAxnw1dIkCQQD+9qaW5QhXjsjb
+Nl85CmXgxPmGROcgLQCO+omfrjf9UXrituU9Dz6auym5lDGEdMFnkzfr+wpasEy9
+mf5ZZOhDAkEA5HjXfVGaCtpydOt6hDon/uZsyssCK2lQ7NSuE3vP+sUsYMzIpEoy
+t3VWXqKbo+g9KNDTP4WEliqp1aiSIylzzQJANPeqzihQnlgEdD4MdD4rwhFJwVIp
+Le8Lcais1KaN7StzOwxB/XhgSibd2TbnPpw+3bSg5n5lvUdo+e62/31OHwJAU1jS
+I+F09KikQIr28u3UUWT2IzTT4cpVv1AHAQyV3sG3YsjSGT0IK20eyP9BEBZU2WL0
+7aNjrvR5aHxKc5FXsQJABsFtyGpgI5X4xufkJZVZ+Mklz2n7iXa+XPatMAHFxAtb
+EEMt60rngwMjXAzBSC6OYuYogRRAY3UCacNC5VhLYQ==
+-----END RSA PRIVATE KEY-----
diff --git a/Godeps/_workspace/src/github.com/lib/pq/certs/root.crt b/Godeps/_workspace/src/github.com/lib/pq/certs/root.crt
new file mode 100644
index 0000000..aecf8f6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/certs/root.crt
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEAzCCAuugAwIBAgIJANmheROCdW1NMA0GCSqGSIb3DQEBBQUAMF4xCzAJBgNV
+BAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGExEjAQBgNVBAcTCUxhcyBWZWdhczEaMBgG
+A1UEChMRZ2l0aHViLmNvbS9saWIvcHExDjAMBgNVBAMTBXBxIENBMB4XDTE0MTAx
+MTE1MDQyOVoXDTI0MTAwODE1MDQyOVowXjELMAkGA1UEBhMCVVMxDzANBgNVBAgT
+Bk5ldmFkYTESMBAGA1UEBxMJTGFzIFZlZ2FzMRowGAYDVQQKExFnaXRodWIuY29t
+L2xpYi9wcTEOMAwGA1UEAxMFcHEgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCV4PxP7ShzWBzUCThcKk3qZtOLtHmszQVtbqhvgTpm1kTRtKBdVMu0
+pLAHQ3JgJCnAYgH0iZxVGoMP16T3irdgsdC48+nNTFM2T0cCdkfDURGIhSFN47cb
+Pgy306BcDUD2q7ucW33+dlFSRuGVewocoh4BWM/vMtMvvWzdi4Ag/L/jhb+5wZxZ
+sWymsadOVSDePEMKOvlCa3EdVwVFV40TVyDb+iWBUivDAYsS2a3KajuJrO6MbZiE
+Sp2RCIkZS2zFmzWxVRi9ZhzIZhh7EVF9JAaNC3T52jhGUdlRq3YpBTMnd89iOh74
+6jWXG7wSuPj3haFzyNhmJ0ZUh+2Ynoh1AgMBAAGjgcMwgcAwHQYDVR0OBBYEFFKT
+7R52Cp9lT94ZZsHVIkA1y6ByMIGQBgNVHSMEgYgwgYWAFFKT7R52Cp9lT94ZZsHV
+IkA1y6ByoWKkYDBeMQswCQYDVQQGEwJVUzEPMA0GA1UECBMGTmV2YWRhMRIwEAYD
+VQQHEwlMYXMgVmVnYXMxGjAYBgNVBAoTEWdpdGh1Yi5jb20vbGliL3BxMQ4wDAYD
+VQQDEwVwcSBDQYIJANmheROCdW1NMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
+BQADggEBAAEhCLWkqJNMI8b4gkbmj5fqQ/4+oO83bZ3w2Oqf6eZ8I8BC4f2NOyE6
+tRUlq5+aU7eqC1cOAvGjO+YHN/bF/DFpwLlzvUSXt+JP/pYcUjL7v+pIvwqec9hD
+ndvM4iIbkD/H/OYQ3L+N3W+G1x7AcFIX+bGCb3PzYVQAjxreV6//wgKBosMGFbZo
+HPxT9RPMun61SViF04H5TNs0derVn1+5eiiYENeAhJzQNyZoOOUuX1X/Inx9bEPh
+C5vFBtSMgIytPgieRJVWAiMLYsfpIAStrHztRAbBs2DU01LmMgRvHdxgFEKinC/d
+UHZZQDP+6pT+zADrGhQGXe4eThaO6f0=
+-----END CERTIFICATE-----
diff --git a/Godeps/_workspace/src/github.com/lib/pq/certs/server.crt b/Godeps/_workspace/src/github.com/lib/pq/certs/server.crt
new file mode 100644
index 0000000..ddc995a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/certs/server.crt
@@ -0,0 +1,81 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, ST=Nevada, L=Las Vegas, O=github.com/lib/pq, CN=pq CA
+ Validity
+ Not Before: Oct 11 15:05:15 2014 GMT
+ Not After : Oct 8 15:05:15 2024 GMT
+ Subject: C=US, ST=Nevada, L=Las Vegas, O=github.com/lib/pq, CN=postgres
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:d7:8a:4c:85:fb:17:a5:3c:8f:e0:72:11:29:ce:
+ 3f:b0:1f:3f:7d:c6:ee:7f:a7:fc:02:2b:35:47:08:
+ a6:3d:90:df:5c:56:14:94:00:c7:6d:d1:d2:e2:61:
+ 95:77:b8:e3:a6:66:31:f9:1f:21:7d:62:e1:27:da:
+ 94:37:61:4a:ea:63:53:a0:61:b8:9c:bb:a5:e2:e7:
+ b7:a6:d8:0f:05:04:c7:29:e2:ea:49:2b:7f:de:15:
+ 00:a6:18:70:50:c7:0c:de:9a:f9:5a:96:b0:e1:94:
+ 06:c6:6d:4a:21:3b:b4:0f:a5:6d:92:86:34:b2:4e:
+ d7:0e:a7:19:c0:77:0b:7b:87:c8:92:de:42:ff:86:
+ d2:b7:9a:a4:d4:15:23:ca:ad:a5:69:21:b8:ce:7e:
+ 66:cb:85:5d:b9:ed:8b:2d:09:8d:94:e4:04:1e:72:
+ ec:ef:d0:76:90:15:5a:a4:f7:91:4b:e9:ce:4e:9d:
+ 5d:9a:70:17:9c:d8:e9:73:83:ea:3d:61:99:a6:cd:
+ ac:91:40:5a:88:77:e5:4e:2a:8e:3d:13:f3:f9:38:
+ 6f:81:6b:8a:95:ca:0e:07:ab:6f:da:b4:8c:d9:ff:
+ aa:78:03:aa:c7:c2:cf:6f:64:92:d3:d8:83:d5:af:
+ f1:23:18:a7:2e:7b:17:0b:e7:7d:f1:fa:a8:41:a3:
+ 04:57
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ EE:F0:B3:46:DC:C7:09:EB:0E:B6:2F:E5:FE:62:60:45:44:9F:59:CC
+ X509v3 Authority Key Identifier:
+ keyid:52:93:ED:1E:76:0A:9F:65:4F:DE:19:66:C1:D5:22:40:35:CB:A0:72
+
+ X509v3 Basic Constraints:
+ CA:FALSE
+ X509v3 Key Usage:
+ Digital Signature, Non Repudiation, Key Encipherment
+ Signature Algorithm: sha256WithRSAEncryption
+ 7e:5a:6e:be:bf:d2:6c:c1:d6:fa:b6:fb:3f:06:53:36:08:87:
+ 9d:95:b1:39:af:9e:f6:47:38:17:39:da:25:7c:f2:ad:0c:e3:
+ ab:74:19:ca:fb:8c:a0:50:c0:1d:19:8a:9c:21:ed:0f:3a:d1:
+ 96:54:2e:10:09:4f:b8:70:f7:2b:99:43:d2:c6:15:bc:3f:24:
+ 7d:28:39:32:3f:8d:a4:4f:40:75:7f:3e:0d:1c:d1:69:f2:4e:
+ 98:83:47:97:d2:25:ac:c9:36:86:2f:04:a6:c4:86:c7:c4:00:
+ 5f:7f:b9:ad:fc:bf:e9:f5:78:d7:82:1a:51:0d:fc:ab:9e:92:
+ 1d:5f:0c:18:d1:82:e0:14:c9:ce:91:89:71:ff:49:49:ff:35:
+ bf:7b:44:78:42:c1:d0:66:65:bb:28:2e:60:ca:9b:20:12:a9:
+ 90:61:b1:96:ec:15:46:c9:37:f7:07:90:8a:89:45:2a:3f:37:
+ ec:dc:e3:e5:8f:c3:3a:57:80:a5:54:60:0c:e1:b2:26:99:2b:
+ 40:7e:36:d1:9a:70:02:ec:63:f4:3b:72:ae:81:fb:30:20:6d:
+ cb:48:46:c6:b5:8f:39:b1:84:05:25:55:8d:f5:62:f6:1b:46:
+ 2e:da:a3:4c:26:12:44:d7:56:b6:b8:a9:ca:d3:ab:71:45:7c:
+ 9f:48:6d:1e
+-----BEGIN CERTIFICATE-----
+MIIDlDCCAnygAwIBAgIBATANBgkqhkiG9w0BAQsFADBeMQswCQYDVQQGEwJVUzEP
+MA0GA1UECBMGTmV2YWRhMRIwEAYDVQQHEwlMYXMgVmVnYXMxGjAYBgNVBAoTEWdp
+dGh1Yi5jb20vbGliL3BxMQ4wDAYDVQQDEwVwcSBDQTAeFw0xNDEwMTExNTA1MTVa
+Fw0yNDEwMDgxNTA1MTVaMGExCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGEx
+EjAQBgNVBAcTCUxhcyBWZWdhczEaMBgGA1UEChMRZ2l0aHViLmNvbS9saWIvcHEx
+ETAPBgNVBAMTCHBvc3RncmVzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA14pMhfsXpTyP4HIRKc4/sB8/fcbuf6f8Ais1RwimPZDfXFYUlADHbdHS4mGV
+d7jjpmYx+R8hfWLhJ9qUN2FK6mNToGG4nLul4ue3ptgPBQTHKeLqSSt/3hUAphhw
+UMcM3pr5Wpaw4ZQGxm1KITu0D6VtkoY0sk7XDqcZwHcLe4fIkt5C/4bSt5qk1BUj
+yq2laSG4zn5my4Vdue2LLQmNlOQEHnLs79B2kBVapPeRS+nOTp1dmnAXnNjpc4Pq
+PWGZps2skUBaiHflTiqOPRPz+ThvgWuKlcoOB6tv2rSM2f+qeAOqx8LPb2SS09iD
+1a/xIxinLnsXC+d98fqoQaMEVwIDAQABo1owWDAdBgNVHQ4EFgQU7vCzRtzHCesO
+ti/l/mJgRUSfWcwwHwYDVR0jBBgwFoAUUpPtHnYKn2VP3hlmwdUiQDXLoHIwCQYD
+VR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQELBQADggEBAH5abr6/0mzB
+1vq2+z8GUzYIh52VsTmvnvZHOBc52iV88q0M46t0Gcr7jKBQwB0Zipwh7Q860ZZU
+LhAJT7hw9yuZQ9LGFbw/JH0oOTI/jaRPQHV/Pg0c0WnyTpiDR5fSJazJNoYvBKbE
+hsfEAF9/ua38v+n1eNeCGlEN/Kuekh1fDBjRguAUyc6RiXH/SUn/Nb97RHhCwdBm
+ZbsoLmDKmyASqZBhsZbsFUbJN/cHkIqJRSo/N+zc4+WPwzpXgKVUYAzhsiaZK0B+
+NtGacALsY/Q7cq6B+zAgbctIRsa1jzmxhAUlVY31YvYbRi7ao0wmEkTXVra4qcrT
+q3FFfJ9IbR4=
+-----END CERTIFICATE-----
diff --git a/Godeps/_workspace/src/github.com/lib/pq/certs/server.key b/Godeps/_workspace/src/github.com/lib/pq/certs/server.key
new file mode 100644
index 0000000..bd7b019
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/certs/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA14pMhfsXpTyP4HIRKc4/sB8/fcbuf6f8Ais1RwimPZDfXFYU
+lADHbdHS4mGVd7jjpmYx+R8hfWLhJ9qUN2FK6mNToGG4nLul4ue3ptgPBQTHKeLq
+SSt/3hUAphhwUMcM3pr5Wpaw4ZQGxm1KITu0D6VtkoY0sk7XDqcZwHcLe4fIkt5C
+/4bSt5qk1BUjyq2laSG4zn5my4Vdue2LLQmNlOQEHnLs79B2kBVapPeRS+nOTp1d
+mnAXnNjpc4PqPWGZps2skUBaiHflTiqOPRPz+ThvgWuKlcoOB6tv2rSM2f+qeAOq
+x8LPb2SS09iD1a/xIxinLnsXC+d98fqoQaMEVwIDAQABAoIBAF3ZoihUhJ82F4+r
+Gz4QyDpv4L1reT2sb1aiabhcU8ZK5nbWJG+tRyjSS/i2dNaEcttpdCj9HR/zhgZM
+bm0OuAgG58rVwgS80CZUruq++Qs+YVojq8/gWPTiQD4SNhV2Fmx3HkwLgUk3oxuT
+SsvdqzGE3okGVrutCIcgy126eA147VPMoej1Bb3fO6npqK0pFPhZfAc0YoqJuM+k
+obRm5pAnGUipyLCFXjA9HYPKwYZw2RtfdA3CiImHeanSdqS+ctrC9y8BV40Th7gZ
+haXdKUNdjmIxV695QQ1mkGqpKLZFqhzKioGQ2/Ly2d1iaKN9fZltTusu8unepWJ2
+tlT9qMECgYEA9uHaF1t2CqE+AJvWTihHhPIIuLxoOQXYea1qvxfcH/UMtaLKzCNm
+lQ5pqCGsPvp+10f36yttO1ZehIvlVNXuJsjt0zJmPtIolNuJY76yeussfQ9jHheB
+5uPEzCFlHzxYbBUyqgWaF6W74okRGzEGJXjYSP0yHPPdU4ep2q3bGiUCgYEA34Af
+wBSuQSK7uLxArWHvQhyuvi43ZGXls6oRGl+Ysj54s8BP6XGkq9hEJ6G4yxgyV+BR
+DUOs5X8/TLT8POuIMYvKTQthQyCk0eLv2FLdESDuuKx0kBVY3s8lK3/z5HhrdOiN
+VMNZU+xDKgKc3hN9ypkk8vcZe6EtH7Y14e0rVcsCgYBTgxi8F/M5K0wG9rAqphNz
+VFBA9XKn/2M33cKjO5X5tXIEKzpAjaUQvNxexG04rJGljzG8+mar0M6ONahw5yD1
+O7i/XWgazgpuOEkkVYiYbd8RutfDgR4vFVMn3hAP3eDnRtBplRWH9Ec3HTiNIys6
+F8PKBOQjyRZQQC7jyzW3hQKBgACe5HeuFwXLSOYsb6mLmhR+6+VPT4wR1F95W27N
+USk9jyxAnngxfpmTkiziABdgS9N+pfr5cyN4BP77ia/Jn6kzkC5Cl9SN5KdIkA3z
+vPVtN/x/ThuQU5zaymmig1ThGLtMYggYOslG4LDfLPxY5YKIhle+Y+259twdr2yf
+Mf2dAoGAaGv3tWMgnIdGRk6EQL/yb9PKHo7ShN+tKNlGaK7WwzBdKs+Fe8jkgcr7
+pz4Ne887CmxejdISzOCcdT+Zm9Bx6I/uZwWOtDvWpIgIxVX9a9URj/+D1MxTE/y4
+d6H+c89yDY62I2+drMpdjCd3EtCaTlxpTbRS+s1eAHMH7aEkcCE=
+-----END RSA PRIVATE KEY-----
diff --git a/Godeps/_workspace/src/github.com/lib/pq/conn.go b/Godeps/_workspace/src/github.com/lib/pq/conn.go
new file mode 100644
index 0000000..44e4833
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/conn.go
@@ -0,0 +1,1490 @@
+package pq
+
+import (
+ "bufio"
+ "crypto/md5"
+ "crypto/tls"
+ "crypto/x509"
+ "database/sql"
+ "database/sql/driver"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "github.com/lib/pq/oid"
+ "io"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/user"
+ "path"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+ "unicode"
+)
+
+// Common error types
+var (
+ ErrNotSupported = errors.New("pq: Unsupported command")
+ ErrInFailedTransaction = errors.New("pq: Could not complete operation in a failed transaction")
+ ErrSSLNotSupported = errors.New("pq: SSL is not enabled on the server")
+ ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key file has group or world access. Permissions should be u=rw (0600) or less.")
+ ErrCouldNotDetectUsername = errors.New("pq: Could not detect default username. Please provide one explicitly.")
+)
+
+type drv struct{}
+
+func (d *drv) Open(name string) (driver.Conn, error) {
+ return Open(name)
+}
+
+func init() {
+ sql.Register("postgres", &drv{})
+}
+
+type parameterStatus struct {
+ // server version in the same format as server_version_num, or 0 if
+ // unavailable
+ serverVersion int
+
+ // the current location based on the TimeZone value of the session, if
+ // available
+ currentLocation *time.Location
+}
+
+type transactionStatus byte
+
+const (
+ txnStatusIdle transactionStatus = 'I'
+ txnStatusIdleInTransaction transactionStatus = 'T'
+ txnStatusInFailedTransaction transactionStatus = 'E'
+)
+
+func (s transactionStatus) String() string {
+ switch s {
+ case txnStatusIdle:
+ return "idle"
+ case txnStatusIdleInTransaction:
+ return "idle in transaction"
+ case txnStatusInFailedTransaction:
+ return "in a failed transaction"
+ default:
+ errorf("unknown transactionStatus %d", s)
+ }
+
+ panic("not reached")
+}
+
+type Dialer interface {
+ Dial(network, address string) (net.Conn, error)
+ DialTimeout(network, address string, timeout time.Duration) (net.Conn, error)
+}
+
+type defaultDialer struct{}
+
+func (d defaultDialer) Dial(ntw, addr string) (net.Conn, error) {
+ return net.Dial(ntw, addr)
+}
+func (d defaultDialer) DialTimeout(ntw, addr string, timeout time.Duration) (net.Conn, error) {
+ return net.DialTimeout(ntw, addr, timeout)
+}
+
+type conn struct {
+ c net.Conn
+ buf *bufio.Reader
+ namei int
+ scratch [512]byte
+ txnStatus transactionStatus
+
+ parameterStatus parameterStatus
+
+ saveMessageType byte
+ saveMessageBuffer []byte
+
+ // If true, this connection is bad and all public-facing functions should
+ // return ErrBadConn.
+ bad bool
+}
+
+func (c *conn) writeBuf(b byte) *writeBuf {
+ c.scratch[0] = b
+ w := writeBuf(c.scratch[:5])
+ return &w
+}
+
+func Open(name string) (_ driver.Conn, err error) {
+ return DialOpen(defaultDialer{}, name)
+}
+
+func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
+ defer func() {
+ // Handle any panics during connection initialization. Note that we
+ // specifically do *not* want to use errRecover(), as that would turn
+ // any connection errors into ErrBadConns, hiding the real error
+ // message from the user.
+ e := recover()
+ if e == nil {
+ // Do nothing
+ return
+ }
+ var ok bool
+ err, ok = e.(error)
+ if !ok {
+ err = fmt.Errorf("pq: unexpected error: %#v", e)
+ }
+ }()
+
+ o := make(values)
+
+ // A number of defaults are applied here, in this order:
+ //
+ // * Very low precedence defaults applied in every situation
+ // * Environment variables
+ // * Explicitly passed connection information
+ o.Set("host", "localhost")
+ o.Set("port", "5432")
+ // N.B.: Extra float digits should be set to 3, but that breaks
+ // Postgres 8.4 and older, where the max is 2.
+ o.Set("extra_float_digits", "2")
+ for k, v := range parseEnviron(os.Environ()) {
+ o.Set(k, v)
+ }
+
+ if strings.HasPrefix(name, "postgres://") {
+ name, err = ParseURL(name)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if err := parseOpts(name, o); err != nil {
+ return nil, err
+ }
+
+ // Use the "fallback" application name if necessary
+ if fallback := o.Get("fallback_application_name"); fallback != "" {
+ if !o.Isset("application_name") {
+ o.Set("application_name", fallback)
+ }
+ }
+
+ // We can't work with any client_encoding other than UTF-8 currently.
+ // However, we have historically allowed the user to set it to UTF-8
+ // explicitly, and there's no reason to break such programs, so allow that.
+ // Note that the "options" setting could also set client_encoding, but
+ // parsing its value is not worth it. Instead, we always explicitly send
+ // client_encoding as a separate run-time parameter, which should override
+ // anything set in options.
+ if enc := o.Get("client_encoding"); enc != "" && !isUTF8(enc) {
+ return nil, errors.New("client_encoding must be absent or 'UTF8'")
+ }
+ o.Set("client_encoding", "UTF8")
+ // DateStyle needs a similar treatment.
+ if datestyle := o.Get("datestyle"); datestyle != "" {
+ if datestyle != "ISO, MDY" {
+ panic(fmt.Sprintf("setting datestyle must be absent or %v; got %v",
+ "ISO, MDY", datestyle))
+ }
+ } else {
+ o.Set("datestyle", "ISO, MDY")
+ }
+
+ // If a user is not provided by any other means, the last
+ // resort is to use the current operating system provided user
+ // name.
+ if o.Get("user") == "" {
+ u, err := userCurrent()
+ if err != nil {
+ return nil, err
+ } else {
+ o.Set("user", u)
+ }
+ }
+
+ c, err := dial(d, o)
+ if err != nil {
+ return nil, err
+ }
+
+ cn := &conn{c: c}
+ cn.ssl(o)
+ cn.buf = bufio.NewReader(cn.c)
+ cn.startup(o)
+ // reset the deadline, in case one was set (see dial)
+ err = cn.c.SetDeadline(time.Time{})
+ return cn, err
+}
+
+func dial(d Dialer, o values) (net.Conn, error) {
+ ntw, addr := network(o)
+
+ timeout := o.Get("connect_timeout")
+
+ // Zero or not specified means wait indefinitely.
+ if timeout != "" && timeout != "0" {
+ seconds, err := strconv.ParseInt(timeout, 10, 0)
+ if err != nil {
+ return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err)
+ }
+ duration := time.Duration(seconds) * time.Second
+ // connect_timeout should apply to the entire connection establishment
+ // procedure, so we both use a timeout for the TCP connection
+ // establishment and set a deadline for doing the initial handshake.
+ // The deadline is then reset after startup() is done.
+ deadline := time.Now().Add(duration)
+ conn, err := d.DialTimeout(ntw, addr, duration)
+ if err != nil {
+ return nil, err
+ }
+ err = conn.SetDeadline(deadline)
+ return conn, err
+ }
+ return d.Dial(ntw, addr)
+}
+
+func network(o values) (string, string) {
+ host := o.Get("host")
+
+ if strings.HasPrefix(host, "/") {
+ sockPath := path.Join(host, ".s.PGSQL."+o.Get("port"))
+ return "unix", sockPath
+ }
+
+ return "tcp", host + ":" + o.Get("port")
+}
+
+type values map[string]string
+
+func (vs values) Set(k, v string) {
+ vs[k] = v
+}
+
+func (vs values) Get(k string) (v string) {
+ return vs[k]
+}
+
+func (vs values) Isset(k string) bool {
+ _, ok := vs[k]
+ return ok
+}
+
+// scanner implements a tokenizer for libpq-style option strings.
+type scanner struct {
+ s []rune
+ i int
+}
+
+// newScanner returns a new scanner initialized with the option string s.
+func newScanner(s string) *scanner {
+ return &scanner{[]rune(s), 0}
+}
+
+// Next returns the next rune.
+// It returns 0, false if the end of the text has been reached.
+func (s *scanner) Next() (rune, bool) {
+ if s.i >= len(s.s) {
+ return 0, false
+ }
+ r := s.s[s.i]
+ s.i++
+ return r, true
+}
+
+// SkipSpaces returns the next non-whitespace rune.
+// It returns 0, false if the end of the text has been reached.
+func (s *scanner) SkipSpaces() (rune, bool) {
+ r, ok := s.Next()
+ for unicode.IsSpace(r) && ok {
+ r, ok = s.Next()
+ }
+ return r, ok
+}
+
+// parseOpts parses the options from name and adds them to the values.
+//
+// The parsing code is based on conninfo_parse from libpq's fe-connect.c
+func parseOpts(name string, o values) error {
+ s := newScanner(name)
+
+ for {
+ var (
+ keyRunes, valRunes []rune
+ r rune
+ ok bool
+ )
+
+ if r, ok = s.SkipSpaces(); !ok {
+ break
+ }
+
+ // Scan the key
+ for !unicode.IsSpace(r) && r != '=' {
+ keyRunes = append(keyRunes, r)
+ if r, ok = s.Next(); !ok {
+ break
+ }
+ }
+
+ // Skip any whitespace if we're not at the = yet
+ if r != '=' {
+ r, ok = s.SkipSpaces()
+ }
+
+ // The current character should be =
+ if r != '=' || !ok {
+ return fmt.Errorf(`missing "=" after %q in connection info string"`, string(keyRunes))
+ }
+
+ // Skip any whitespace after the =
+ if r, ok = s.SkipSpaces(); !ok {
+ // If we reach the end here, the last value is just an empty string as per libpq.
+ o.Set(string(keyRunes), "")
+ break
+ }
+
+ if r != '\'' {
+ for !unicode.IsSpace(r) {
+ if r == '\\' {
+ if r, ok = s.Next(); !ok {
+ return fmt.Errorf(`missing character after backslash`)
+ }
+ }
+ valRunes = append(valRunes, r)
+
+ if r, ok = s.Next(); !ok {
+ break
+ }
+ }
+ } else {
+ quote:
+ for {
+ if r, ok = s.Next(); !ok {
+ return fmt.Errorf(`unterminated quoted string literal in connection string`)
+ }
+ switch r {
+ case '\'':
+ break quote
+ case '\\':
+ r, _ = s.Next()
+ fallthrough
+ default:
+ valRunes = append(valRunes, r)
+ }
+ }
+ }
+
+ o.Set(string(keyRunes), string(valRunes))
+ }
+
+ return nil
+}
+
+func (cn *conn) isInTransaction() bool {
+ return cn.txnStatus == txnStatusIdleInTransaction ||
+ cn.txnStatus == txnStatusInFailedTransaction
+}
+
+func (cn *conn) checkIsInTransaction(intxn bool) {
+ if cn.isInTransaction() != intxn {
+ cn.bad = true
+ errorf("unexpected transaction status %v", cn.txnStatus)
+ }
+}
+
+func (cn *conn) Begin() (_ driver.Tx, err error) {
+ if cn.bad {
+ return nil, driver.ErrBadConn
+ }
+ defer cn.errRecover(&err)
+
+ cn.checkIsInTransaction(false)
+ _, commandTag, err := cn.simpleExec("BEGIN")
+ if err != nil {
+ return nil, err
+ }
+ if commandTag != "BEGIN" {
+ cn.bad = true
+ return nil, fmt.Errorf("unexpected command tag %s", commandTag)
+ }
+ if cn.txnStatus != txnStatusIdleInTransaction {
+ cn.bad = true
+ return nil, fmt.Errorf("unexpected transaction status %v", cn.txnStatus)
+ }
+ return cn, nil
+}
+
+func (cn *conn) Commit() (err error) {
+ if cn.bad {
+ return driver.ErrBadConn
+ }
+ defer cn.errRecover(&err)
+
+ cn.checkIsInTransaction(true)
+ // We don't want the client to think that everything is okay if it tries
+ // to commit a failed transaction. However, no matter what we return,
+ // database/sql will release this connection back into the free connection
+ // pool so we have to abort the current transaction here. Note that you
+ // would get the same behaviour if you issued a COMMIT in a failed
+ // transaction, so it's also the least surprising thing to do here.
+ if cn.txnStatus == txnStatusInFailedTransaction {
+ if err := cn.Rollback(); err != nil {
+ return err
+ }
+ return ErrInFailedTransaction
+ }
+
+ _, commandTag, err := cn.simpleExec("COMMIT")
+ if err != nil {
+ return err
+ }
+ if commandTag != "COMMIT" {
+ cn.bad = true
+ return fmt.Errorf("unexpected command tag %s", commandTag)
+ }
+ cn.checkIsInTransaction(false)
+ return nil
+}
+
+func (cn *conn) Rollback() (err error) {
+ if cn.bad {
+ return driver.ErrBadConn
+ }
+ defer cn.errRecover(&err)
+
+ cn.checkIsInTransaction(true)
+ _, commandTag, err := cn.simpleExec("ROLLBACK")
+ if err != nil {
+ return err
+ }
+ if commandTag != "ROLLBACK" {
+ return fmt.Errorf("unexpected command tag %s", commandTag)
+ }
+ cn.checkIsInTransaction(false)
+ return nil
+}
+
+func (cn *conn) gname() string {
+ cn.namei++
+ return strconv.FormatInt(int64(cn.namei), 10)
+}
+
+func (cn *conn) simpleExec(q string) (res driver.Result, commandTag string, err error) {
+ b := cn.writeBuf('Q')
+ b.string(q)
+ cn.send(b)
+
+ for {
+ t, r := cn.recv1()
+ switch t {
+ case 'C':
+ res, commandTag = cn.parseComplete(r.string())
+ case 'Z':
+ cn.processReadyForQuery(r)
+ // done
+ return
+ case 'E':
+ err = parseError(r)
+ case 'T', 'D', 'I':
+ // ignore any results
+ default:
+ cn.bad = true
+ errorf("unknown response for simple query: %q", t)
+ }
+ }
+}
+
+func (cn *conn) simpleQuery(q string) (res driver.Rows, err error) {
+ defer cn.errRecover(&err)
+
+ st := &stmt{cn: cn, name: ""}
+
+ b := cn.writeBuf('Q')
+ b.string(q)
+ cn.send(b)
+
+ for {
+ t, r := cn.recv1()
+ switch t {
+ case 'C', 'I':
+ // We allow queries which don't return any results through Query as
+ // well as Exec. We still have to give database/sql a rows object
+ // the user can close, though, to avoid connections from being
+ // leaked. A "rows" with done=true works fine for that purpose.
+ if err != nil {
+ cn.bad = true
+ errorf("unexpected message %q in simple query execution", t)
+ }
+ res = &rows{st: st, done: true}
+ case 'Z':
+ cn.processReadyForQuery(r)
+ // done
+ return
+ case 'E':
+ res = nil
+ err = parseError(r)
+ case 'D':
+ if res == nil {
+ cn.bad = true
+ errorf("unexpected DataRow in simple query execution")
+ }
+ // the query didn't fail; kick off to Next
+ cn.saveMessage(t, r)
+ return
+ case 'T':
+ // res might be non-nil here if we received a previous
+ // CommandComplete, but that's fine; just overwrite it
+ res = &rows{st: st}
+ st.cols, st.rowTyps = parseMeta(r)
+
+ // To work around a bug in QueryRow in Go 1.2 and earlier, wait
+ // until the first DataRow has been received.
+ default:
+ cn.bad = true
+ errorf("unknown response for simple query: %q", t)
+ }
+ }
+}
+
+func (cn *conn) prepareTo(q, stmtName string) (_ *stmt, err error) {
+ st := &stmt{cn: cn, name: stmtName}
+
+ b := cn.writeBuf('P')
+ b.string(st.name)
+ b.string(q)
+ b.int16(0)
+ cn.send(b)
+
+ b = cn.writeBuf('D')
+ b.byte('S')
+ b.string(st.name)
+ cn.send(b)
+
+ cn.send(cn.writeBuf('S'))
+
+ for {
+ t, r := cn.recv1()
+ switch t {
+ case '1':
+ case 't':
+ nparams := r.int16()
+ st.paramTyps = make([]oid.Oid, nparams)
+
+ for i := range st.paramTyps {
+ st.paramTyps[i] = r.oid()
+ }
+ case 'T':
+ st.cols, st.rowTyps = parseMeta(r)
+ case 'n':
+ // no data
+ case 'Z':
+ cn.processReadyForQuery(r)
+ return st, err
+ case 'E':
+ err = parseError(r)
+ default:
+ cn.bad = true
+ errorf("unexpected describe rows response: %q", t)
+ }
+ }
+}
+
+func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) {
+ if cn.bad {
+ return nil, driver.ErrBadConn
+ }
+ defer cn.errRecover(&err)
+
+ if len(q) >= 4 && strings.EqualFold(q[:4], "COPY") {
+ return cn.prepareCopyIn(q)
+ }
+ return cn.prepareTo(q, cn.gname())
+}
+
+func (cn *conn) Close() (err error) {
+ if cn.bad {
+ return driver.ErrBadConn
+ }
+ defer cn.errRecover(&err)
+
+ // Don't go through send(); ListenerConn relies on us not scribbling on the
+ // scratch buffer of this connection.
+ err = cn.sendSimpleMessage('X')
+ if err != nil {
+ return err
+ }
+
+ return cn.c.Close()
+}
+
+// Implement the "Queryer" interface
+func (cn *conn) Query(query string, args []driver.Value) (_ driver.Rows, err error) {
+ if cn.bad {
+ return nil, driver.ErrBadConn
+ }
+ defer cn.errRecover(&err)
+
+ // Check to see if we can use the "simpleQuery" interface, which is
+ // *much* faster than going through prepare/exec
+ if len(args) == 0 {
+ return cn.simpleQuery(query)
+ }
+
+ st, err := cn.prepareTo(query, "")
+ if err != nil {
+ panic(err)
+ }
+
+ st.exec(args)
+ return &rows{st: st}, nil
+}
+
+// Implement the optional "Execer" interface for one-shot queries
+func (cn *conn) Exec(query string, args []driver.Value) (_ driver.Result, err error) {
+ if cn.bad {
+ return nil, driver.ErrBadConn
+ }
+ defer cn.errRecover(&err)
+
+ // Check to see if we can use the "simpleExec" interface, which is
+ // *much* faster than going through prepare/exec
+ if len(args) == 0 {
+ // ignore commandTag, our caller doesn't care
+ r, _, err := cn.simpleExec(query)
+ return r, err
+ }
+
+ // Use the unnamed statement to defer planning until bind
+ // time, or else value-based selectivity estimates cannot be
+ // used.
+ st, err := cn.prepareTo(query, "")
+ if err != nil {
+ panic(err)
+ }
+
+ r, err := st.Exec(args)
+ if err != nil {
+ panic(err)
+ }
+
+ return r, err
+}
+
+// Assumes len(*m) is > 5
+func (cn *conn) send(m *writeBuf) {
+ b := (*m)[1:]
+ binary.BigEndian.PutUint32(b, uint32(len(b)))
+
+ if (*m)[0] == 0 {
+ *m = b
+ }
+
+ _, err := cn.c.Write(*m)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// Send a message of type typ to the server on the other end of cn. The
+// message should have no payload. This method does not use the scratch
+// buffer.
+func (cn *conn) sendSimpleMessage(typ byte) (err error) {
+ _, err = cn.c.Write([]byte{typ, '\x00', '\x00', '\x00', '\x04'})
+ return err
+}
+
+// saveMessage memorizes a message and its buffer in the conn struct.
+// recvMessage will then return these values on the next call to it. This
+// method is useful in cases where you have to see what the next message is
+// going to be (e.g. to see whether it's an error or not) but you can't handle
+// the message yourself.
+func (cn *conn) saveMessage(typ byte, buf *readBuf) {
+ if cn.saveMessageType != 0 {
+ cn.bad = true
+ errorf("unexpected saveMessageType %d", cn.saveMessageType)
+ }
+ cn.saveMessageType = typ
+ cn.saveMessageBuffer = *buf
+}
+
+// recvMessage receives any message from the backend, or returns an error if
+// a problem occurred while reading the message.
+func (cn *conn) recvMessage(r *readBuf) (byte, error) {
+ // workaround for a QueryRow bug, see exec
+ if cn.saveMessageType != 0 {
+ t := cn.saveMessageType
+ *r = cn.saveMessageBuffer
+ cn.saveMessageType = 0
+ cn.saveMessageBuffer = nil
+ return t, nil
+ }
+
+ x := cn.scratch[:5]
+ _, err := io.ReadFull(cn.buf, x)
+ if err != nil {
+ return 0, err
+ }
+
+ // read the type and length of the message that follows
+ t := x[0]
+ n := int(binary.BigEndian.Uint32(x[1:])) - 4
+ var y []byte
+ if n <= len(cn.scratch) {
+ y = cn.scratch[:n]
+ } else {
+ y = make([]byte, n)
+ }
+ _, err = io.ReadFull(cn.buf, y)
+ if err != nil {
+ return 0, err
+ }
+ *r = y
+ return t, nil
+}
+
+// recv receives a message from the backend, but if an error happened while
+// reading the message or the received message was an ErrorResponse, it panics.
+// NoticeResponses are ignored. This function should generally be used only
+// during the startup sequence.
+func (cn *conn) recv() (t byte, r *readBuf) {
+ for {
+ var err error
+ r = &readBuf{}
+ t, err = cn.recvMessage(r)
+ if err != nil {
+ panic(err)
+ }
+
+ switch t {
+ case 'E':
+ panic(parseError(r))
+ case 'N':
+ // ignore
+ default:
+ return
+ }
+ }
+}
+
+// recv1Buf is exactly equivalent to recv1, except it uses a buffer supplied by
+// the caller to avoid an allocation.
+func (cn *conn) recv1Buf(r *readBuf) byte {
+ for {
+ t, err := cn.recvMessage(r)
+ if err != nil {
+ panic(err)
+ }
+
+ switch t {
+ case 'A', 'N':
+ // ignore
+ case 'S':
+ cn.processParameterStatus(r)
+ default:
+ return t
+ }
+ }
+}
+
+// recv1 receives a message from the backend, panicking if an error occurs
+// while attempting to read it. All asynchronous messages are ignored, with
+// the exception of ErrorResponse.
+func (cn *conn) recv1() (t byte, r *readBuf) {
+ r = &readBuf{}
+ t = cn.recv1Buf(r)
+ return t, r
+}
+
+func (cn *conn) ssl(o values) {
+ verifyCaOnly := false
+ tlsConf := tls.Config{}
+ switch mode := o.Get("sslmode"); mode {
+ case "require", "":
+ tlsConf.InsecureSkipVerify = true
+ case "verify-ca":
+ // We must skip TLS's own verification since it requires full
+ // verification since Go 1.3.
+ tlsConf.InsecureSkipVerify = true
+ verifyCaOnly = true
+ case "verify-full":
+ tlsConf.ServerName = o.Get("host")
+ case "disable":
+ return
+ default:
+ errorf(`unsupported sslmode %q; only "require" (default), "verify-full", and "disable" supported`, mode)
+ }
+
+ cn.setupSSLClientCertificates(&tlsConf, o)
+ cn.setupSSLCA(&tlsConf, o)
+
+ w := cn.writeBuf(0)
+ w.int32(80877103)
+ cn.send(w)
+
+ b := cn.scratch[:1]
+ _, err := io.ReadFull(cn.c, b)
+ if err != nil {
+ panic(err)
+ }
+
+ if b[0] != 'S' {
+ panic(ErrSSLNotSupported)
+ }
+
+ client := tls.Client(cn.c, &tlsConf)
+ if verifyCaOnly {
+ cn.verifyCA(client, &tlsConf)
+ }
+ cn.c = client
+}
+
+// verifyCA carries out a TLS handshake to the server and verifies the
+// presented certificate against the effective CA, i.e. the one specified in
+// sslrootcert or the system CA if sslrootcert was not specified.
+func (cn *conn) verifyCA(client *tls.Conn, tlsConf *tls.Config) {
+ err := client.Handshake()
+ if err != nil {
+ panic(err)
+ }
+ certs := client.ConnectionState().PeerCertificates
+ opts := x509.VerifyOptions{
+ DNSName: client.ConnectionState().ServerName,
+ Intermediates: x509.NewCertPool(),
+ Roots: tlsConf.RootCAs,
+ }
+ for i, cert := range certs {
+ if i == 0 {
+ continue
+ }
+ opts.Intermediates.AddCert(cert)
+ }
+ _, err = certs[0].Verify(opts)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// This function sets up SSL client certificates based on either the "sslkey"
+// and "sslcert" settings (possibly set via the environment variables PGSSLKEY
+// and PGSSLCERT, respectively), or if they aren't set, from the .postgresql
+// directory in the user's home directory. If the file paths are set
+// explicitly, the files must exist. The key file must also not be
+// world-readable, or this function will panic with
+// ErrSSLKeyHasWorldPermissions.
+func (cn *conn) setupSSLClientCertificates(tlsConf *tls.Config, o values) {
+ var missingOk bool
+
+ sslkey := o.Get("sslkey")
+ sslcert := o.Get("sslcert")
+ if sslkey != "" && sslcert != "" {
+ // If the user has set an sslkey and sslcert, they *must* exist.
+ missingOk = false
+ } else {
+ // Automatically load certificates from ~/.postgresql.
+ user, err := user.Current()
+ if err != nil {
+ // user.Current() might fail when cross-compiling. We have to
+ // ignore the error and continue without client certificates, since
+ // we wouldn't know where to load them from.
+ return
+ }
+
+ sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key")
+ sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt")
+ missingOk = true
+ }
+
+ // Check that both files exist, and report the error or stop, depending on
+ // which behaviour we want. Note that we don't do any more extensive
+ // checks than this (such as checking that the paths aren't directories);
+ // LoadX509KeyPair() will take care of the rest.
+ keyfinfo, err := os.Stat(sslkey)
+ if err != nil && missingOk {
+ return
+ } else if err != nil {
+ panic(err)
+ }
+ _, err = os.Stat(sslcert)
+ if err != nil && missingOk {
+ return
+ } else if err != nil {
+ panic(err)
+ }
+
+ // If we got this far, the key file must also have the correct permissions
+ kmode := keyfinfo.Mode()
+ if kmode != kmode&0600 {
+ panic(ErrSSLKeyHasWorldPermissions)
+ }
+
+ cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
+ if err != nil {
+ panic(err)
+ }
+ tlsConf.Certificates = []tls.Certificate{cert}
+}
+
+// Sets up RootCAs in the TLS configuration if sslrootcert is set.
+func (cn *conn) setupSSLCA(tlsConf *tls.Config, o values) {
+ if sslrootcert := o.Get("sslrootcert"); sslrootcert != "" {
+ tlsConf.RootCAs = x509.NewCertPool()
+
+ cert, err := ioutil.ReadFile(sslrootcert)
+ if err != nil {
+ panic(err)
+ }
+
+ ok := tlsConf.RootCAs.AppendCertsFromPEM(cert)
+ if !ok {
+ errorf("couldn't parse pem in sslrootcert")
+ }
+ }
+}
+
+// isDriverSetting returns true iff a setting is purely for configuring the
+// driver's options and should not be sent to the server in the connection
+// startup packet.
+func isDriverSetting(key string) bool {
+ switch key {
+ case "host", "port":
+ return true
+ case "password":
+ return true
+ case "sslmode", "sslcert", "sslkey", "sslrootcert":
+ return true
+ case "fallback_application_name":
+ return true
+ case "connect_timeout":
+ return true
+
+ default:
+ return false
+ }
+}
+
+func (cn *conn) startup(o values) {
+ w := cn.writeBuf(0)
+ w.int32(196608)
+ // Send the backend the name of the database we want to connect to, and the
+ // user we want to connect as. Additionally, we send over any run-time
+ // parameters potentially included in the connection string. If the server
+ // doesn't recognize any of them, it will reply with an error.
+ for k, v := range o {
+ if isDriverSetting(k) {
+ // skip options which can't be run-time parameters
+ continue
+ }
+ // The protocol requires us to supply the database name as "database"
+ // instead of "dbname".
+ if k == "dbname" {
+ k = "database"
+ }
+ w.string(k)
+ w.string(v)
+ }
+ w.string("")
+ cn.send(w)
+
+ for {
+ t, r := cn.recv()
+ switch t {
+ case 'K':
+ case 'S':
+ cn.processParameterStatus(r)
+ case 'R':
+ cn.auth(r, o)
+ case 'Z':
+ cn.processReadyForQuery(r)
+ return
+ default:
+ errorf("unknown response for startup: %q", t)
+ }
+ }
+}
+
+func (cn *conn) auth(r *readBuf, o values) {
+ switch code := r.int32(); code {
+ case 0:
+ // OK
+ case 3:
+ w := cn.writeBuf('p')
+ w.string(o.Get("password"))
+ cn.send(w)
+
+ t, r := cn.recv()
+ if t != 'R' {
+ errorf("unexpected password response: %q", t)
+ }
+
+ if r.int32() != 0 {
+ errorf("unexpected authentication response: %q", t)
+ }
+ case 5:
+ s := string(r.next(4))
+ w := cn.writeBuf('p')
+ w.string("md5" + md5s(md5s(o.Get("password")+o.Get("user"))+s))
+ cn.send(w)
+
+ t, r := cn.recv()
+ if t != 'R' {
+ errorf("unexpected password response: %q", t)
+ }
+
+ if r.int32() != 0 {
+ errorf("unexpected authentication response: %q", t)
+ }
+ default:
+ errorf("unknown authentication response: %d", code)
+ }
+}
+
+type stmt struct {
+ cn *conn
+ name string
+ cols []string
+ rowTyps []oid.Oid
+ paramTyps []oid.Oid
+ closed bool
+}
+
+func (st *stmt) Close() (err error) {
+ if st.closed {
+ return nil
+ }
+ if st.cn.bad {
+ return driver.ErrBadConn
+ }
+ defer st.cn.errRecover(&err)
+
+ w := st.cn.writeBuf('C')
+ w.byte('S')
+ w.string(st.name)
+ st.cn.send(w)
+
+ st.cn.send(st.cn.writeBuf('S'))
+
+ t, _ := st.cn.recv1()
+ if t != '3' {
+ st.cn.bad = true
+ errorf("unexpected close response: %q", t)
+ }
+ st.closed = true
+
+ t, r := st.cn.recv1()
+ if t != 'Z' {
+ st.cn.bad = true
+ errorf("expected ready for query, but got: %q", t)
+ }
+ st.cn.processReadyForQuery(r)
+
+ return nil
+}
+
+func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) {
+ if st.cn.bad {
+ return nil, driver.ErrBadConn
+ }
+ defer st.cn.errRecover(&err)
+
+ st.exec(v)
+ return &rows{st: st}, nil
+}
+
+func (st *stmt) Exec(v []driver.Value) (res driver.Result, err error) {
+ if st.cn.bad {
+ return nil, driver.ErrBadConn
+ }
+ defer st.cn.errRecover(&err)
+
+ st.exec(v)
+
+ for {
+ t, r := st.cn.recv1()
+ switch t {
+ case 'E':
+ err = parseError(r)
+ case 'C':
+ res, _ = st.cn.parseComplete(r.string())
+ case 'Z':
+ st.cn.processReadyForQuery(r)
+ // done
+ return
+ case 'T', 'D', 'I':
+ // ignore any results
+ default:
+ st.cn.bad = true
+ errorf("unknown exec response: %q", t)
+ }
+ }
+}
+
+func (st *stmt) exec(v []driver.Value) {
+ if len(v) >= 65536 {
+ errorf("got %d parameters but PostgreSQL only supports 65535 parameters", len(v))
+ }
+ if len(v) != len(st.paramTyps) {
+ errorf("got %d parameters but the statement requires %d", len(v), len(st.paramTyps))
+ }
+
+ w := st.cn.writeBuf('B')
+ w.string("")
+ w.string(st.name)
+ w.int16(0)
+ w.int16(len(v))
+ for i, x := range v {
+ if x == nil {
+ w.int32(-1)
+ } else {
+ b := encode(&st.cn.parameterStatus, x, st.paramTyps[i])
+ w.int32(len(b))
+ w.bytes(b)
+ }
+ }
+ w.int16(0)
+ st.cn.send(w)
+
+ w = st.cn.writeBuf('E')
+ w.string("")
+ w.int32(0)
+ st.cn.send(w)
+
+ st.cn.send(st.cn.writeBuf('S'))
+
+ var err error
+ for {
+ t, r := st.cn.recv1()
+ switch t {
+ case 'E':
+ err = parseError(r)
+ case '2':
+ if err != nil {
+ panic(err)
+ }
+ goto workaround
+ case 'Z':
+ st.cn.processReadyForQuery(r)
+ if err != nil {
+ panic(err)
+ }
+ return
+ default:
+ st.cn.bad = true
+ errorf("unexpected bind response: %q", t)
+ }
+ }
+
+ // Work around a bug in sql.DB.QueryRow: in Go 1.2 and earlier it ignores
+ // any errors from rows.Next, which masks errors that happened during the
+ // execution of the query. To avoid the problem in common cases, we wait
+ // here for one more message from the database. If it's not an error the
+ // query will likely succeed (or perhaps has already, if it's a
+ // CommandComplete), so we push the message into the conn struct; recv1
+ // will return it as the next message for rows.Next or rows.Close.
+ // However, if it's an error, we wait until ReadyForQuery and then return
+ // the error to our caller.
+workaround:
+ for {
+ t, r := st.cn.recv1()
+ switch t {
+ case 'E':
+ err = parseError(r)
+ case 'C', 'D', 'I':
+ // the query didn't fail, but we can't process this message
+ st.cn.saveMessage(t, r)
+ return
+ case 'Z':
+ if err == nil {
+ st.cn.bad = true
+ errorf("unexpected ReadyForQuery during extended query execution")
+ }
+ st.cn.processReadyForQuery(r)
+ panic(err)
+ default:
+ st.cn.bad = true
+ errorf("unexpected message during query execution: %q", t)
+ }
+ }
+}
+
+func (st *stmt) NumInput() int {
+ return len(st.paramTyps)
+}
+
+// parseComplete parses the "command tag" from a CommandComplete message, and
+// returns the number of rows affected (if applicable) and a string
+// identifying only the command that was executed, e.g. "ALTER TABLE". If the
+// command tag could not be parsed, parseComplete panics.
+func (cn *conn) parseComplete(commandTag string) (driver.Result, string) {
+ commandsWithAffectedRows := []string{
+ "SELECT ",
+ // INSERT is handled below
+ "UPDATE ",
+ "DELETE ",
+ "FETCH ",
+ "MOVE ",
+ "COPY ",
+ }
+
+ var affectedRows *string
+ for _, tag := range commandsWithAffectedRows {
+ if strings.HasPrefix(commandTag, tag) {
+ t := commandTag[len(tag):]
+ affectedRows = &t
+ commandTag = tag[:len(tag)-1]
+ break
+ }
+ }
+ // INSERT also includes the oid of the inserted row in its command tag.
+ // Oids in user tables are deprecated, and the oid is only returned when
+ // exactly one row is inserted, so it's unlikely to be of value to any
+ // real-world application and we can ignore it.
+ if affectedRows == nil && strings.HasPrefix(commandTag, "INSERT ") {
+ parts := strings.Split(commandTag, " ")
+ if len(parts) != 3 {
+ cn.bad = true
+ errorf("unexpected INSERT command tag %s", commandTag)
+ }
+ affectedRows = &parts[len(parts)-1]
+ commandTag = "INSERT"
+ }
+ // There should be no affected rows attached to the tag, just return it
+ if affectedRows == nil {
+ return driver.RowsAffected(0), commandTag
+ }
+ n, err := strconv.ParseInt(*affectedRows, 10, 64)
+ if err != nil {
+ cn.bad = true
+ errorf("could not parse commandTag: %s", err)
+ }
+ return driver.RowsAffected(n), commandTag
+}
+
+type rows struct {
+ st *stmt
+ done bool
+ rb readBuf
+}
+
+func (rs *rows) Close() error {
+ // no need to look at cn.bad as Next() will
+ for {
+ err := rs.Next(nil)
+ switch err {
+ case nil:
+ case io.EOF:
+ return nil
+ default:
+ return err
+ }
+ }
+}
+
+func (rs *rows) Columns() []string {
+ return rs.st.cols
+}
+
+func (rs *rows) Next(dest []driver.Value) (err error) {
+ if rs.done {
+ return io.EOF
+ }
+
+ conn := rs.st.cn
+ if conn.bad {
+ return driver.ErrBadConn
+ }
+ defer conn.errRecover(&err)
+
+ for {
+ t := conn.recv1Buf(&rs.rb)
+ switch t {
+ case 'E':
+ err = parseError(&rs.rb)
+ case 'C', 'I':
+ continue
+ case 'Z':
+ conn.processReadyForQuery(&rs.rb)
+ rs.done = true
+ if err != nil {
+ return err
+ }
+ return io.EOF
+ case 'D':
+ n := rs.rb.int16()
+ if n < len(dest) {
+ dest = dest[:n]
+ }
+ for i := range dest {
+ l := rs.rb.int32()
+ if l == -1 {
+ dest[i] = nil
+ continue
+ }
+ dest[i] = decode(&conn.parameterStatus, rs.rb.next(l), rs.st.rowTyps[i])
+ }
+ return
+ default:
+ errorf("unexpected message after execute: %q", t)
+ }
+ }
+}
+
+// QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be
+// used as part of an SQL statement. For example:
+//
+// tblname := "my_table"
+// data := "my_data"
+// err = db.Exec(fmt.Sprintf("INSERT INTO %s VALUES ($1)", pq.QuoteIdentifier(tblname)), data)
+//
+// Any double quotes in name will be escaped. The quoted identifier will be
+// case sensitive when used in a query. If the input string contains a zero
+// byte, the result will be truncated immediately before it.
+func QuoteIdentifier(name string) string {
+ end := strings.IndexRune(name, 0)
+ if end > -1 {
+ name = name[:end]
+ }
+ return `"` + strings.Replace(name, `"`, `""`, -1) + `"`
+}
+
+func md5s(s string) string {
+ h := md5.New()
+ h.Write([]byte(s))
+ return fmt.Sprintf("%x", h.Sum(nil))
+}
+
+func (c *conn) processParameterStatus(r *readBuf) {
+ var err error
+
+ param := r.string()
+ switch param {
+ case "server_version":
+ var major1 int
+ var major2 int
+ var minor int
+ _, err = fmt.Sscanf(r.string(), "%d.%d.%d", &major1, &major2, &minor)
+ if err == nil {
+ c.parameterStatus.serverVersion = major1*10000 + major2*100 + minor
+ }
+
+ case "TimeZone":
+ c.parameterStatus.currentLocation, err = time.LoadLocation(r.string())
+ if err != nil {
+ c.parameterStatus.currentLocation = nil
+ }
+
+ default:
+ // ignore
+ }
+}
+
+func (c *conn) processReadyForQuery(r *readBuf) {
+ c.txnStatus = transactionStatus(r.byte())
+}
+
+func parseMeta(r *readBuf) (cols []string, rowTyps []oid.Oid) {
+ n := r.int16()
+ cols = make([]string, n)
+ rowTyps = make([]oid.Oid, n)
+ for i := range cols {
+ cols[i] = r.string()
+ r.next(6)
+ rowTyps[i] = r.oid()
+ r.next(8)
+ }
+ return
+}
+
+// parseEnviron tries to mimic some of libpq's environment handling
+//
+// To ease testing, it does not directly reference os.Environ, but is
+// designed to accept its output.
+//
+// Environment-set connection information is intended to have a higher
+// precedence than a library default but lower than any explicitly
+// passed information (such as in the URL or connection string).
+func parseEnviron(env []string) (out map[string]string) {
+ out = make(map[string]string)
+
+ for _, v := range env {
+ parts := strings.SplitN(v, "=", 2)
+
+ accrue := func(keyname string) {
+ out[keyname] = parts[1]
+ }
+ unsupported := func() {
+ panic(fmt.Sprintf("setting %v not supported", parts[0]))
+ }
+
+ // The order of these is the same as is seen in the
+ // PostgreSQL 9.1 manual. Unsupported but well-defined
+ // keys cause a panic; these should be unset prior to
+ // execution. Options which pq expects to be set to a
+ // certain value are allowed, but must be set to that
+ // value if present (they can, of course, be absent).
+ switch parts[0] {
+ case "PGHOST":
+ accrue("host")
+ case "PGHOSTADDR":
+ unsupported()
+ case "PGPORT":
+ accrue("port")
+ case "PGDATABASE":
+ accrue("dbname")
+ case "PGUSER":
+ accrue("user")
+ case "PGPASSWORD":
+ accrue("password")
+ case "PGPASSFILE", "PGSERVICE", "PGSERVICEFILE", "PGREALM":
+ unsupported()
+ case "PGOPTIONS":
+ accrue("options")
+ case "PGAPPNAME":
+ accrue("application_name")
+ case "PGSSLMODE":
+ accrue("sslmode")
+ case "PGSSLCERT":
+ accrue("sslcert")
+ case "PGSSLKEY":
+ accrue("sslkey")
+ case "PGSSLROOTCERT":
+ accrue("sslrootcert")
+ case "PGREQUIRESSL", "PGSSLCRL":
+ unsupported()
+ case "PGREQUIREPEER":
+ unsupported()
+ case "PGKRBSRVNAME", "PGGSSLIB":
+ unsupported()
+ case "PGCONNECT_TIMEOUT":
+ accrue("connect_timeout")
+ case "PGCLIENTENCODING":
+ accrue("client_encoding")
+ case "PGDATESTYLE":
+ accrue("datestyle")
+ case "PGTZ":
+ accrue("timezone")
+ case "PGGEQO":
+ accrue("geqo")
+ case "PGSYSCONFDIR", "PGLOCALEDIR":
+ unsupported()
+ }
+ }
+
+ return out
+}
+
+// isUTF8 returns whether name is a fuzzy variation of the string "UTF-8".
+func isUTF8(name string) bool {
+ // Recognize all sorts of silly things as "UTF-8", like Postgres does
+ s := strings.Map(alnumLowerASCII, name)
+ return s == "utf8" || s == "unicode"
+}
+
+func alnumLowerASCII(ch rune) rune {
+ if 'A' <= ch && ch <= 'Z' {
+ return ch + ('a' - 'A')
+ }
+ if 'a' <= ch && ch <= 'z' || '0' <= ch && ch <= '9' {
+ return ch
+ }
+ return -1 // discard
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/conn_test.go b/Godeps/_workspace/src/github.com/lib/pq/conn_test.go
new file mode 100644
index 0000000..6c3c6b5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/conn_test.go
@@ -0,0 +1,1286 @@
+package pq
+
+import (
+ "database/sql"
+ "database/sql/driver"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "testing"
+ "time"
+)
+
+type Fatalistic interface {
+ Fatal(args ...interface{})
+}
+
+func openTestConnConninfo(conninfo string) (*sql.DB, error) {
+ datname := os.Getenv("PGDATABASE")
+ sslmode := os.Getenv("PGSSLMODE")
+ timeout := os.Getenv("PGCONNECT_TIMEOUT")
+
+ if datname == "" {
+ os.Setenv("PGDATABASE", "pqgotest")
+ }
+
+ if sslmode == "" {
+ os.Setenv("PGSSLMODE", "disable")
+ }
+
+ if timeout == "" {
+ os.Setenv("PGCONNECT_TIMEOUT", "20")
+ }
+
+ return sql.Open("postgres", conninfo)
+}
+
+func openTestConn(t Fatalistic) *sql.DB {
+ conn, err := openTestConnConninfo("")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return conn
+}
+
+func getServerVersion(t *testing.T, db *sql.DB) int {
+ var version int
+ err := db.QueryRow("SHOW server_version_num").Scan(&version)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return version
+}
+
+func TestReconnect(t *testing.T) {
+ db1 := openTestConn(t)
+ defer db1.Close()
+ tx, err := db1.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var pid1 int
+ err = tx.QueryRow("SELECT pg_backend_pid()").Scan(&pid1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ db2 := openTestConn(t)
+ defer db2.Close()
+ _, err = db2.Exec("SELECT pg_terminate_backend($1)", pid1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // The rollback will probably "fail" because we just killed
+ // its connection above
+ _ = tx.Rollback()
+
+ const expected int = 42
+ var result int
+ err = db1.QueryRow(fmt.Sprintf("SELECT %d", expected)).Scan(&result)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if result != expected {
+ t.Errorf("got %v; expected %v", result, expected)
+ }
+}
+
+func TestCommitInFailedTransaction(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err := txn.Query("SELECT error")
+ if err == nil {
+ rows.Close()
+ t.Fatal("expected failure")
+ }
+ err = txn.Commit()
+ if err != ErrInFailedTransaction {
+ t.Fatalf("expected ErrInFailedTransaction; got %#v", err)
+ }
+}
+
+func TestOpenURL(t *testing.T) {
+ db, err := openTestConnConninfo("postgres://")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+ // database/sql might not call our Open at all unless we do something with
+ // the connection
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ txn.Rollback()
+}
+
+func TestExec(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ _, err := db.Exec("CREATE TEMP TABLE temp (a int)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ r, err := db.Exec("INSERT INTO temp VALUES (1)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if n, _ := r.RowsAffected(); n != 1 {
+ t.Fatalf("expected 1 row affected, not %d", n)
+ }
+
+ r, err = db.Exec("INSERT INTO temp VALUES ($1), ($2), ($3)", 1, 2, 3)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if n, _ := r.RowsAffected(); n != 3 {
+ t.Fatalf("expected 3 rows affected, not %d", n)
+ }
+
+ // SELECT doesn't send the number of returned rows in the command tag
+ // before 9.0
+ if getServerVersion(t, db) >= 90000 {
+ r, err = db.Exec("SELECT g FROM generate_series(1, 2) g")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n, _ := r.RowsAffected(); n != 2 {
+ t.Fatalf("expected 2 rows affected, not %d", n)
+ }
+
+ r, err = db.Exec("SELECT g FROM generate_series(1, $1) g", 3)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n, _ := r.RowsAffected(); n != 3 {
+ t.Fatalf("expected 3 rows affected, not %d", n)
+ }
+ }
+}
+
+func TestStatment(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ st, err := db.Prepare("SELECT 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ st1, err := db.Prepare("SELECT 2")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ r, err := st.Query()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+
+ if !r.Next() {
+ t.Fatal("expected row")
+ }
+
+ var i int
+ err = r.Scan(&i)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if i != 1 {
+ t.Fatalf("expected 1, got %d", i)
+ }
+
+ // st1
+
+ r1, err := st1.Query()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r1.Close()
+
+ if !r1.Next() {
+ if r.Err() != nil {
+ t.Fatal(r1.Err())
+ }
+ t.Fatal("expected row")
+ }
+
+ err = r1.Scan(&i)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if i != 2 {
+ t.Fatalf("expected 2, got %d", i)
+ }
+}
+
+func TestRowsCloseBeforeDone(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ r, err := db.Query("SELECT 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = r.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if r.Next() {
+ t.Fatal("unexpected row")
+ }
+
+ if r.Err() != nil {
+ t.Fatal(r.Err())
+ }
+}
+
+func TestParameterCountMismatch(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ var notused int
+ err := db.QueryRow("SELECT false", 1).Scan(¬used)
+ if err == nil {
+ t.Fatal("expected err")
+ }
+ // make sure we clean up correctly
+ err = db.QueryRow("SELECT 1").Scan(¬used)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = db.QueryRow("SELECT $1").Scan(¬used)
+ if err == nil {
+ t.Fatal("expected err")
+ }
+ // make sure we clean up correctly
+ err = db.QueryRow("SELECT 1").Scan(¬used)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Test that EmptyQueryResponses are handled correctly.
+func TestEmptyQuery(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ _, err := db.Exec("")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err := db.Query("")
+ if err != nil {
+ t.Fatal(err)
+ }
+ cols, err := rows.Columns()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(cols) != 0 {
+ t.Fatalf("unexpected number of columns %d in response to an empty query", len(cols))
+ }
+ if rows.Next() {
+ t.Fatal("unexpected row")
+ }
+ if rows.Err() != nil {
+ t.Fatal(rows.Err())
+ }
+
+ stmt, err := db.Prepare("")
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = stmt.Exec()
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err = stmt.Query()
+ if err != nil {
+ t.Fatal(err)
+ }
+ cols, err = rows.Columns()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(cols) != 0 {
+ t.Fatalf("unexpected number of columns %d in response to an empty query", len(cols))
+ }
+ if rows.Next() {
+ t.Fatal("unexpected row")
+ }
+ if rows.Err() != nil {
+ t.Fatal(rows.Err())
+ }
+}
+
+func TestEncodeDecode(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ q := `
+ SELECT
+ E'\\000\\001\\002'::bytea,
+ 'foobar'::text,
+ NULL::integer,
+ '2000-1-1 01:02:03.04-7'::timestamptz,
+ 0::boolean,
+ 123,
+ 3.14::float8
+ WHERE
+ E'\\000\\001\\002'::bytea = $1
+ AND 'foobar'::text = $2
+ AND $3::integer is NULL
+ `
+ // AND '2000-1-1 12:00:00.000000-7'::timestamp = $3
+
+ exp1 := []byte{0, 1, 2}
+ exp2 := "foobar"
+
+ r, err := db.Query(q, exp1, exp2, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+
+ if !r.Next() {
+ if r.Err() != nil {
+ t.Fatal(r.Err())
+ }
+ t.Fatal("expected row")
+ }
+
+ var got1 []byte
+ var got2 string
+ var got3 = sql.NullInt64{Valid: true}
+ var got4 time.Time
+ var got5, got6, got7 interface{}
+
+ err = r.Scan(&got1, &got2, &got3, &got4, &got5, &got6, &got7)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(exp1, got1) {
+ t.Errorf("expected %q byte: %q", exp1, got1)
+ }
+
+ if !reflect.DeepEqual(exp2, got2) {
+ t.Errorf("expected %q byte: %q", exp2, got2)
+ }
+
+ if got3.Valid {
+ t.Fatal("expected invalid")
+ }
+
+ if got4.Year() != 2000 {
+ t.Fatal("wrong year")
+ }
+
+ if got5 != false {
+ t.Fatalf("expected false, got %q", got5)
+ }
+
+ if got6 != int64(123) {
+ t.Fatalf("expected 123, got %d", got6)
+ }
+
+ if got7 != float64(3.14) {
+ t.Fatalf("expected 3.14, got %f", got7)
+ }
+}
+
+func TestNoData(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ st, err := db.Prepare("SELECT 1 WHERE true = false")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer st.Close()
+
+ r, err := st.Query()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+
+ if r.Next() {
+ if r.Err() != nil {
+ t.Fatal(r.Err())
+ }
+ t.Fatal("unexpected row")
+ }
+
+ _, err = db.Query("SELECT * FROM nonexistenttable WHERE age=$1", 20)
+ if err == nil {
+ t.Fatal("Should have raised an error on non existent table")
+ }
+
+ _, err = db.Query("SELECT * FROM nonexistenttable")
+ if err == nil {
+ t.Fatal("Should have raised an error on non existent table")
+ }
+}
+
+func TestErrorDuringStartup(t *testing.T) {
+ // Don't use the normal connection setup, this is intended to
+ // blow up in the startup packet from a non-existent user.
+ db, err := openTestConnConninfo("user=thisuserreallydoesntexist")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+
+ _, err = db.Begin()
+ if err == nil {
+ t.Fatal("expected error")
+ }
+
+ e, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("expected Error, got %#v", err)
+ } else if e.Code.Name() != "invalid_authorization_specification" && e.Code.Name() != "invalid_password" {
+ t.Fatalf("expected invalid_authorization_specification or invalid_password, got %s (%+v)", e.Code.Name(), err)
+ }
+}
+
+func TestBadConn(t *testing.T) {
+ var err error
+
+ cn := conn{}
+ func() {
+ defer cn.errRecover(&err)
+ panic(io.EOF)
+ }()
+ if err != driver.ErrBadConn {
+ t.Fatalf("expected driver.ErrBadConn, got: %#v", err)
+ }
+ if !cn.bad {
+ t.Fatalf("expected cn.bad")
+ }
+
+ cn = conn{}
+ func() {
+ defer cn.errRecover(&err)
+ e := &Error{Severity: Efatal}
+ panic(e)
+ }()
+ if err != driver.ErrBadConn {
+ t.Fatalf("expected driver.ErrBadConn, got: %#v", err)
+ }
+ if !cn.bad {
+ t.Fatalf("expected cn.bad")
+ }
+}
+
+func TestErrorOnExec(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMPORARY TABLE foo(f1 int PRIMARY KEY)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = txn.Exec("INSERT INTO foo VALUES (0), (0)")
+ if err == nil {
+ t.Fatal("Should have raised error")
+ }
+
+ e, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("expected Error, got %#v", err)
+ } else if e.Code.Name() != "unique_violation" {
+ t.Fatalf("expected unique_violation, got %s (%+v)", e.Code.Name(), err)
+ }
+}
+
+func TestErrorOnQuery(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMPORARY TABLE foo(f1 int PRIMARY KEY)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = txn.Query("INSERT INTO foo VALUES (0), (0)")
+ if err == nil {
+ t.Fatal("Should have raised error")
+ }
+
+ e, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("expected Error, got %#v", err)
+ } else if e.Code.Name() != "unique_violation" {
+ t.Fatalf("expected unique_violation, got %s (%+v)", e.Code.Name(), err)
+ }
+}
+
+func TestErrorOnQueryRowSimpleQuery(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMPORARY TABLE foo(f1 int PRIMARY KEY)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var v int
+ err = txn.QueryRow("INSERT INTO foo VALUES (0), (0)").Scan(&v)
+ if err == nil {
+ t.Fatal("Should have raised error")
+ }
+
+ e, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("expected Error, got %#v", err)
+ } else if e.Code.Name() != "unique_violation" {
+ t.Fatalf("expected unique_violation, got %s (%+v)", e.Code.Name(), err)
+ }
+}
+
+// Test the QueryRow bug workarounds in stmt.exec() and simpleQuery()
+func TestQueryRowBugWorkaround(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ // stmt.exec()
+ _, err := db.Exec("CREATE TEMP TABLE notnulltemp (a varchar(10) not null)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var a string
+ err = db.QueryRow("INSERT INTO notnulltemp(a) values($1) RETURNING a", nil).Scan(&a)
+ if err == sql.ErrNoRows {
+ t.Fatalf("expected constraint violation error; got: %v", err)
+ }
+ pge, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("expected *Error; got: %#v", err)
+ }
+ if pge.Code.Name() != "not_null_violation" {
+ t.Fatalf("expected not_null_violation; got: %s (%+v)", pge.Code.Name(), err)
+ }
+
+ // Test workaround in simpleQuery()
+ tx, err := db.Begin()
+ if err != nil {
+ t.Fatalf("unexpected error %s in Begin", err)
+ }
+ defer tx.Rollback()
+
+ _, err = tx.Exec("SET LOCAL check_function_bodies TO FALSE")
+ if err != nil {
+ t.Fatalf("could not disable check_function_bodies: %s", err)
+ }
+ _, err = tx.Exec(`
+CREATE OR REPLACE FUNCTION bad_function()
+RETURNS integer
+-- hack to prevent the function from being inlined
+SET check_function_bodies TO TRUE
+AS $$
+ SELECT text 'bad'
+$$ LANGUAGE sql`)
+ if err != nil {
+ t.Fatalf("could not create function: %s", err)
+ }
+
+ err = tx.QueryRow("SELECT * FROM bad_function()").Scan(&a)
+ if err == nil {
+ t.Fatalf("expected error")
+ }
+ pge, ok = err.(*Error)
+ if !ok {
+ t.Fatalf("expected *Error; got: %#v", err)
+ }
+ if pge.Code.Name() != "invalid_function_definition" {
+ t.Fatalf("expected invalid_function_definition; got: %s (%+v)", pge.Code.Name(), err)
+ }
+
+ err = tx.Rollback()
+ if err != nil {
+ t.Fatalf("unexpected error %s in Rollback", err)
+ }
+
+ // Also test that simpleQuery()'s workaround works when the query fails
+ // after a row has been received.
+ rows, err := db.Query(`
+select
+ (select generate_series(1, ss.i))
+from (select gs.i
+ from generate_series(1, 2) gs(i)
+ order by gs.i limit 2) ss`)
+ if err != nil {
+ t.Fatalf("query failed: %s", err)
+ }
+ if !rows.Next() {
+ t.Fatalf("expected at least one result row; got %s", rows.Err())
+ }
+ var i int
+ err = rows.Scan(&i)
+ if err != nil {
+ t.Fatalf("rows.Scan() failed: %s", err)
+ }
+ if i != 1 {
+ t.Fatalf("unexpected value for i: %d", i)
+ }
+ if rows.Next() {
+ t.Fatalf("unexpected row")
+ }
+ pge, ok = rows.Err().(*Error)
+ if !ok {
+ t.Fatalf("expected *Error; got: %#v", err)
+ }
+ if pge.Code.Name() != "cardinality_violation" {
+ t.Fatalf("expected cardinality_violation; got: %s (%+v)", pge.Code.Name(), rows.Err())
+ }
+}
+
+func TestSimpleQuery(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ r, err := db.Query("select 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+
+ if !r.Next() {
+ t.Fatal("expected row")
+ }
+}
+
+func TestBindError(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ _, err := db.Exec("create temp table test (i integer)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Query("select * from test where i=$1", "hhh")
+ if err == nil {
+ t.Fatal("expected an error")
+ }
+
+ // Should not get error here
+ r, err := db.Query("select * from test where i=$1", 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+}
+
+func TestParseErrorInExtendedQuery(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ rows, err := db.Query("PARSE_ERROR $1", 1)
+ if err == nil {
+ t.Fatal("expected error")
+ }
+
+ rows, err = db.Query("SELECT 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Close()
+}
+
+// TestReturning tests that an INSERT query using the RETURNING clause returns a row.
+func TestReturning(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ _, err := db.Exec("CREATE TEMP TABLE distributors (did integer default 0, dname text)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rows, err := db.Query("INSERT INTO distributors (did, dname) VALUES (DEFAULT, 'XYZ Widgets') " +
+ "RETURNING did;")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !rows.Next() {
+ t.Fatal("no rows")
+ }
+ var did int
+ err = rows.Scan(&did)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if did != 0 {
+ t.Fatalf("bad value for did: got %d, want %d", did, 0)
+ }
+
+ if rows.Next() {
+ t.Fatal("unexpected next row")
+ }
+ err = rows.Err()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestIssue186(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ // Exec() a query which returns results
+ _, err := db.Exec("VALUES (1), (2), (3)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("VALUES ($1), ($2), ($3)", 1, 2, 3)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Query() a query which doesn't return any results
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ rows, err := txn.Query("CREATE TEMP TABLE foo(f1 int)")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err = rows.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ // small trick to get NoData from a parameterized query
+ _, err = txn.Exec("CREATE RULE nodata AS ON INSERT TO foo DO INSTEAD NOTHING")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err = txn.Query("INSERT INTO foo VALUES ($1)", 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err = rows.Close(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestIssue196(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ row := db.QueryRow("SELECT float4 '0.10000122' = $1, float8 '35.03554004971999' = $2",
+ float32(0.10000122), float64(35.03554004971999))
+
+ var float4match, float8match bool
+ err := row.Scan(&float4match, &float8match)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !float4match {
+ t.Errorf("Expected float4 fidelity to be maintained; got no match")
+ }
+ if !float8match {
+ t.Errorf("Expected float8 fidelity to be maintained; got no match")
+ }
+}
+
+// Test that any CommandComplete messages sent before the query results are
+// ignored.
+func TestIssue282(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ var search_path string
+ err := db.QueryRow(`
+ SET LOCAL search_path TO pg_catalog;
+ SET LOCAL search_path TO pg_catalog;
+ SHOW search_path`).Scan(&search_path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if search_path != "pg_catalog" {
+ t.Fatalf("unexpected search_path %s", search_path)
+ }
+}
+
+func TestReadFloatPrecision(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ row := db.QueryRow("SELECT float4 '0.10000122', float8 '35.03554004971999'")
+ var float4val float32
+ var float8val float64
+ err := row.Scan(&float4val, &float8val)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if float4val != float32(0.10000122) {
+ t.Errorf("Expected float4 fidelity to be maintained; got no match")
+ }
+ if float8val != float64(35.03554004971999) {
+ t.Errorf("Expected float8 fidelity to be maintained; got no match")
+ }
+}
+
+func TestXactMultiStmt(t *testing.T) {
+ // minified test case based on bug reports from
+ // pico303@gmail.com and rangelspam@gmail.com
+ t.Skip("Skipping failing test")
+ db := openTestConn(t)
+ defer db.Close()
+
+ tx, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer tx.Commit()
+
+ rows, err := tx.Query("select 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if rows.Next() {
+ var val int32
+ if err = rows.Scan(&val); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ t.Fatal("Expected at least one row in first query in xact")
+ }
+
+ rows2, err := tx.Query("select 2")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if rows2.Next() {
+ var val2 int32
+ if err := rows2.Scan(&val2); err != nil {
+ t.Fatal(err)
+ }
+ } else {
+ t.Fatal("Expected at least one row in second query in xact")
+ }
+
+ if err = rows.Err(); err != nil {
+ t.Fatal(err)
+ }
+
+ if err = rows2.Err(); err != nil {
+ t.Fatal(err)
+ }
+
+ if err = tx.Commit(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+var envParseTests = []struct {
+ Expected map[string]string
+ Env []string
+}{
+ {
+ Env: []string{"PGDATABASE=hello", "PGUSER=goodbye"},
+ Expected: map[string]string{"dbname": "hello", "user": "goodbye"},
+ },
+ {
+ Env: []string{"PGDATESTYLE=ISO, MDY"},
+ Expected: map[string]string{"datestyle": "ISO, MDY"},
+ },
+ {
+ Env: []string{"PGCONNECT_TIMEOUT=30"},
+ Expected: map[string]string{"connect_timeout": "30"},
+ },
+}
+
+func TestParseEnviron(t *testing.T) {
+ for i, tt := range envParseTests {
+ results := parseEnviron(tt.Env)
+ if !reflect.DeepEqual(tt.Expected, results) {
+ t.Errorf("%d: Expected: %#v Got: %#v", i, tt.Expected, results)
+ }
+ }
+}
+
+func TestParseComplete(t *testing.T) {
+ tpc := func(commandTag string, command string, affectedRows int64, shouldFail bool) {
+ defer func() {
+ if p := recover(); p != nil {
+ if !shouldFail {
+ t.Error(p)
+ }
+ }
+ }()
+ cn := &conn{}
+ res, c := cn.parseComplete(commandTag)
+ if c != command {
+ t.Errorf("Expected %v, got %v", command, c)
+ }
+ n, err := res.RowsAffected()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n != affectedRows {
+ t.Errorf("Expected %d, got %d", affectedRows, n)
+ }
+ }
+
+ tpc("ALTER TABLE", "ALTER TABLE", 0, false)
+ tpc("INSERT 0 1", "INSERT", 1, false)
+ tpc("UPDATE 100", "UPDATE", 100, false)
+ tpc("SELECT 100", "SELECT", 100, false)
+ tpc("FETCH 100", "FETCH", 100, false)
+ // allow COPY (and others) without row count
+ tpc("COPY", "COPY", 0, false)
+ // don't fail on command tags we don't recognize
+ tpc("UNKNOWNCOMMANDTAG", "UNKNOWNCOMMANDTAG", 0, false)
+
+ // failure cases
+ tpc("INSERT 1", "", 0, true) // missing oid
+ tpc("UPDATE 0 1", "", 0, true) // too many numbers
+ tpc("SELECT foo", "", 0, true) // invalid row count
+}
+
+func TestExecerInterface(t *testing.T) {
+ // Gin up a straw man private struct just for the type check
+ cn := &conn{c: nil}
+ var cni interface{} = cn
+
+ _, ok := cni.(driver.Execer)
+ if !ok {
+ t.Fatal("Driver doesn't implement Execer")
+ }
+}
+
+func TestNullAfterNonNull(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ r, err := db.Query("SELECT 9::integer UNION SELECT NULL::integer")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var n sql.NullInt64
+
+ if !r.Next() {
+ if r.Err() != nil {
+ t.Fatal(err)
+ }
+ t.Fatal("expected row")
+ }
+
+ if err := r.Scan(&n); err != nil {
+ t.Fatal(err)
+ }
+
+ if n.Int64 != 9 {
+ t.Fatalf("expected 2, not %d", n.Int64)
+ }
+
+ if !r.Next() {
+ if r.Err() != nil {
+ t.Fatal(err)
+ }
+ t.Fatal("expected row")
+ }
+
+ if err := r.Scan(&n); err != nil {
+ t.Fatal(err)
+ }
+
+ if n.Valid {
+ t.Fatal("expected n to be invalid")
+ }
+
+ if n.Int64 != 0 {
+ t.Fatalf("expected n to 2, not %d", n.Int64)
+ }
+}
+
+func Test64BitErrorChecking(t *testing.T) {
+ defer func() {
+ if err := recover(); err != nil {
+ t.Fatal("panic due to 0xFFFFFFFF != -1 " +
+ "when int is 64 bits")
+ }
+ }()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ r, err := db.Query(`SELECT *
+FROM (VALUES (0::integer, NULL::text), (1, 'test string')) AS t;`)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ defer r.Close()
+
+ for r.Next() {
+ }
+}
+
+func TestCommit(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ _, err := db.Exec("CREATE TEMP TABLE temp (a int)")
+ if err != nil {
+ t.Fatal(err)
+ }
+ sqlInsert := "INSERT INTO temp VALUES (1)"
+ sqlSelect := "SELECT * FROM temp"
+ tx, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = tx.Exec(sqlInsert)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = tx.Commit()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var i int
+ err = db.QueryRow(sqlSelect).Scan(&i)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if i != 1 {
+ t.Fatalf("expected 1, got %d", i)
+ }
+}
+
+func TestErrorClass(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ _, err := db.Query("SELECT int 'notint'")
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ pge, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("expected *pq.Error, got %#+v", err)
+ }
+ if pge.Code.Class() != "22" {
+ t.Fatalf("expected class 28, got %v", pge.Code.Class())
+ }
+ if pge.Code.Class().Name() != "data_exception" {
+ t.Fatalf("expected data_exception, got %v", pge.Code.Class().Name())
+ }
+}
+
+func TestParseOpts(t *testing.T) {
+ tests := []struct {
+ in string
+ expected values
+ valid bool
+ }{
+ {"dbname=hello user=goodbye", values{"dbname": "hello", "user": "goodbye"}, true},
+ {"dbname=hello user=goodbye ", values{"dbname": "hello", "user": "goodbye"}, true},
+ {"dbname = hello user=goodbye", values{"dbname": "hello", "user": "goodbye"}, true},
+ {"dbname=hello user =goodbye", values{"dbname": "hello", "user": "goodbye"}, true},
+ {"dbname=hello user= goodbye", values{"dbname": "hello", "user": "goodbye"}, true},
+ {"host=localhost password='correct horse battery staple'", values{"host": "localhost", "password": "correct horse battery staple"}, true},
+ {"dbname=データベース password=パスワード", values{"dbname": "データベース", "password": "パスワード"}, true},
+ {"dbname=hello user=''", values{"dbname": "hello", "user": ""}, true},
+ {"user='' dbname=hello", values{"dbname": "hello", "user": ""}, true},
+ // The last option value is an empty string if there's no non-whitespace after its =
+ {"dbname=hello user= ", values{"dbname": "hello", "user": ""}, true},
+
+ // The parser ignores spaces after = and interprets the next set of non-whitespace characters as the value.
+ {"user= password=foo", values{"user": "password=foo"}, true},
+
+ // Backslash escapes next char
+ {`user=a\ \'\\b`, values{"user": `a '\b`}, true},
+ {`user='a \'b'`, values{"user": `a 'b`}, true},
+
+ // Incomplete escape
+ {`user=x\`, values{}, false},
+
+ // No '=' after the key
+ {"postgre://marko@internet", values{}, false},
+ {"dbname user=goodbye", values{}, false},
+ {"user=foo blah", values{}, false},
+ {"user=foo blah ", values{}, false},
+
+ // Unterminated quoted value
+ {"dbname=hello user='unterminated", values{}, false},
+ }
+
+ for _, test := range tests {
+ o := make(values)
+ err := parseOpts(test.in, o)
+
+ switch {
+ case err != nil && test.valid:
+ t.Errorf("%q got unexpected error: %s", test.in, err)
+ case err == nil && test.valid && !reflect.DeepEqual(test.expected, o):
+ t.Errorf("%q got: %#v want: %#v", test.in, o, test.expected)
+ case err == nil && !test.valid:
+ t.Errorf("%q expected an error", test.in)
+ }
+ }
+}
+
+func TestRuntimeParameters(t *testing.T) {
+ type RuntimeTestResult int
+ const (
+ ResultUnknown RuntimeTestResult = iota
+ ResultSuccess
+ ResultError // other error
+ )
+
+ tests := []struct {
+ conninfo string
+ param string
+ expected string
+ expectedOutcome RuntimeTestResult
+ }{
+ // invalid parameter
+ {"DOESNOTEXIST=foo", "", "", ResultError},
+ // we can only work with a specific value for these two
+ {"client_encoding=SQL_ASCII", "", "", ResultError},
+ {"datestyle='ISO, YDM'", "", "", ResultError},
+ // "options" should work exactly as it does in libpq
+ {"options='-c search_path=pqgotest'", "search_path", "pqgotest", ResultSuccess},
+ // pq should override client_encoding in this case
+ {"options='-c client_encoding=SQL_ASCII'", "client_encoding", "UTF8", ResultSuccess},
+ // allow client_encoding to be set explicitly
+ {"client_encoding=UTF8", "client_encoding", "UTF8", ResultSuccess},
+ // test a runtime parameter not supported by libpq
+ {"work_mem='139kB'", "work_mem", "139kB", ResultSuccess},
+ // test fallback_application_name
+ {"application_name=foo fallback_application_name=bar", "application_name", "foo", ResultSuccess},
+ {"application_name='' fallback_application_name=bar", "application_name", "", ResultSuccess},
+ {"fallback_application_name=bar", "application_name", "bar", ResultSuccess},
+ }
+
+ for _, test := range tests {
+ db, err := openTestConnConninfo(test.conninfo)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // application_name didn't exist before 9.0
+ if test.param == "application_name" && getServerVersion(t, db) < 90000 {
+ db.Close()
+ continue
+ }
+
+ tryGetParameterValue := func() (value string, outcome RuntimeTestResult) {
+ defer db.Close()
+ row := db.QueryRow("SELECT current_setting($1)", test.param)
+ err = row.Scan(&value)
+ if err != nil {
+ return "", ResultError
+ }
+ return value, ResultSuccess
+ }
+
+ value, outcome := tryGetParameterValue()
+ if outcome != test.expectedOutcome && outcome == ResultError {
+ t.Fatalf("%v: unexpected error: %v", test.conninfo, err)
+ }
+ if outcome != test.expectedOutcome {
+ t.Fatalf("unexpected outcome %v (was expecting %v) for conninfo \"%s\"",
+ outcome, test.expectedOutcome, test.conninfo)
+ }
+ if value != test.expected {
+ t.Fatalf("bad value for %s: got %s, want %s with conninfo \"%s\"",
+ test.param, value, test.expected, test.conninfo)
+ }
+ }
+}
+
+func TestIsUTF8(t *testing.T) {
+ var cases = []struct {
+ name string
+ want bool
+ }{
+ {"unicode", true},
+ {"utf-8", true},
+ {"utf_8", true},
+ {"UTF-8", true},
+ {"UTF8", true},
+ {"utf8", true},
+ {"u n ic_ode", true},
+ {"ut_f%8", true},
+ {"ubf8", false},
+ {"punycode", false},
+ }
+
+ for _, test := range cases {
+ if g := isUTF8(test.name); g != test.want {
+ t.Errorf("isUTF8(%q) = %v want %v", test.name, g, test.want)
+ }
+ }
+}
+
+func TestQuoteIdentifier(t *testing.T) {
+ var cases = []struct {
+ input string
+ want string
+ }{
+ {`foo`, `"foo"`},
+ {`foo bar baz`, `"foo bar baz"`},
+ {`foo"bar`, `"foo""bar"`},
+ {"foo\x00bar", `"foo"`},
+ {"\x00foo", `""`},
+ }
+
+ for _, test := range cases {
+ got := QuoteIdentifier(test.input)
+ if got != test.want {
+ t.Errorf("QuoteIdentifier(%q) = %v want %v", test.input, got, test.want)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/copy.go b/Godeps/_workspace/src/github.com/lib/pq/copy.go
new file mode 100644
index 0000000..18c04e7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/copy.go
@@ -0,0 +1,266 @@
+package pq
+
+import (
+ "database/sql/driver"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "sync"
+)
+
+var (
+ errCopyInClosed = errors.New("pq: copyin statement has already been closed")
+ errBinaryCopyNotSupported = errors.New("pq: only text format supported for COPY")
+ errCopyToNotSupported = errors.New("pq: COPY TO is not supported")
+ errCopyNotSupportedOutsideTxn = errors.New("pq: COPY is only allowed inside a transaction")
+)
+
+// CopyIn creates a COPY FROM statement which can be prepared with
+// Tx.Prepare(). The target table should be visible in search_path.
+func CopyIn(table string, columns ...string) string {
+ stmt := "COPY " + QuoteIdentifier(table) + " ("
+ for i, col := range columns {
+ if i != 0 {
+ stmt += ", "
+ }
+ stmt += QuoteIdentifier(col)
+ }
+ stmt += ") FROM STDIN"
+ return stmt
+}
+
+// CopyInSchema creates a COPY FROM statement which can be prepared with
+// Tx.Prepare().
+func CopyInSchema(schema, table string, columns ...string) string {
+ stmt := "COPY " + QuoteIdentifier(schema) + "." + QuoteIdentifier(table) + " ("
+ for i, col := range columns {
+ if i != 0 {
+ stmt += ", "
+ }
+ stmt += QuoteIdentifier(col)
+ }
+ stmt += ") FROM STDIN"
+ return stmt
+}
+
+type copyin struct {
+ cn *conn
+ buffer []byte
+ rowData chan []byte
+ done chan bool
+
+ closed bool
+
+ sync.Mutex // guards err
+ err error
+}
+
+const ciBufferSize = 64 * 1024
+
+// flush buffer before the buffer is filled up and needs reallocation
+const ciBufferFlushSize = 63 * 1024
+
+func (cn *conn) prepareCopyIn(q string) (_ driver.Stmt, err error) {
+ if !cn.isInTransaction() {
+ return nil, errCopyNotSupportedOutsideTxn
+ }
+
+ ci := ©in{
+ cn: cn,
+ buffer: make([]byte, 0, ciBufferSize),
+ rowData: make(chan []byte),
+ done: make(chan bool, 1),
+ }
+ // add CopyData identifier + 4 bytes for message length
+ ci.buffer = append(ci.buffer, 'd', 0, 0, 0, 0)
+
+ b := cn.writeBuf('Q')
+ b.string(q)
+ cn.send(b)
+
+awaitCopyInResponse:
+ for {
+ t, r := cn.recv1()
+ switch t {
+ case 'G':
+ if r.byte() != 0 {
+ err = errBinaryCopyNotSupported
+ break awaitCopyInResponse
+ }
+ go ci.resploop()
+ return ci, nil
+ case 'H':
+ err = errCopyToNotSupported
+ break awaitCopyInResponse
+ case 'E':
+ err = parseError(r)
+ case 'Z':
+ if err == nil {
+ cn.bad = true
+ errorf("unexpected ReadyForQuery in response to COPY")
+ }
+ cn.processReadyForQuery(r)
+ return nil, err
+ default:
+ cn.bad = true
+ errorf("unknown response for copy query: %q", t)
+ }
+ }
+
+ // something went wrong, abort COPY before we return
+ b = cn.writeBuf('f')
+ b.string(err.Error())
+ cn.send(b)
+
+ for {
+ t, r := cn.recv1()
+ switch t {
+ case 'c', 'C', 'E':
+ case 'Z':
+ // correctly aborted, we're done
+ cn.processReadyForQuery(r)
+ return nil, err
+ default:
+ cn.bad = true
+ errorf("unknown response for CopyFail: %q", t)
+ }
+ }
+}
+
+func (ci *copyin) flush(buf []byte) {
+ // set message length (without message identifier)
+ binary.BigEndian.PutUint32(buf[1:], uint32(len(buf)-1))
+
+ _, err := ci.cn.c.Write(buf)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func (ci *copyin) resploop() {
+ for {
+ var r readBuf
+ t, err := ci.cn.recvMessage(&r)
+ if err != nil {
+ ci.cn.bad = true
+ ci.setError(err)
+ ci.done <- true
+ return
+ }
+ switch t {
+ case 'C':
+ // complete
+ case 'Z':
+ ci.cn.processReadyForQuery(&r)
+ ci.done <- true
+ return
+ case 'E':
+ err := parseError(&r)
+ ci.setError(err)
+ default:
+ ci.cn.bad = true
+ ci.setError(fmt.Errorf("unknown response during CopyIn: %q", t))
+ ci.done <- true
+ return
+ }
+ }
+}
+
+func (ci *copyin) isErrorSet() bool {
+ ci.Lock()
+ isSet := (ci.err != nil)
+ ci.Unlock()
+ return isSet
+}
+
+// setError() sets ci.err if one has not been set already. Caller must not be
+// holding ci.Mutex.
+func (ci *copyin) setError(err error) {
+ ci.Lock()
+ if ci.err == nil {
+ ci.err = err
+ }
+ ci.Unlock()
+}
+
+func (ci *copyin) NumInput() int {
+ return -1
+}
+
+func (ci *copyin) Query(v []driver.Value) (r driver.Rows, err error) {
+ return nil, ErrNotSupported
+}
+
+// Exec inserts values into the COPY stream. The insert is asynchronous
+// and Exec can return errors from previous Exec calls to the same
+// COPY stmt.
+//
+// You need to call Exec(nil) to sync the COPY stream and to get any
+// errors from pending data, since Stmt.Close() doesn't return errors
+// to the user.
+func (ci *copyin) Exec(v []driver.Value) (r driver.Result, err error) {
+ if ci.closed {
+ return nil, errCopyInClosed
+ }
+
+ if ci.cn.bad {
+ return nil, driver.ErrBadConn
+ }
+ defer ci.cn.errRecover(&err)
+
+ if ci.isErrorSet() {
+ return nil, ci.err
+ }
+
+ if len(v) == 0 {
+ err = ci.Close()
+ ci.closed = true
+ return nil, err
+ }
+
+ numValues := len(v)
+ for i, value := range v {
+ ci.buffer = appendEncodedText(&ci.cn.parameterStatus, ci.buffer, value)
+ if i < numValues-1 {
+ ci.buffer = append(ci.buffer, '\t')
+ }
+ }
+
+ ci.buffer = append(ci.buffer, '\n')
+
+ if len(ci.buffer) > ciBufferFlushSize {
+ ci.flush(ci.buffer)
+ // reset buffer, keep bytes for message identifier and length
+ ci.buffer = ci.buffer[:5]
+ }
+
+ return driver.RowsAffected(0), nil
+}
+
+func (ci *copyin) Close() (err error) {
+ if ci.closed {
+ return errCopyInClosed
+ }
+
+ if ci.cn.bad {
+ return driver.ErrBadConn
+ }
+ defer ci.cn.errRecover(&err)
+
+ if len(ci.buffer) > 0 {
+ ci.flush(ci.buffer)
+ }
+ // Avoid touching the scratch buffer as resploop could be using it.
+ err = ci.cn.sendSimpleMessage('c')
+ if err != nil {
+ return err
+ }
+
+ <-ci.done
+
+ if ci.isErrorSet() {
+ err = ci.err
+ return err
+ }
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/copy_test.go b/Godeps/_workspace/src/github.com/lib/pq/copy_test.go
new file mode 100644
index 0000000..e489f22
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/copy_test.go
@@ -0,0 +1,380 @@
+package pq
+
+import (
+ "bytes"
+ "database/sql"
+ "strings"
+ "testing"
+)
+
+func TestCopyInStmt(t *testing.T) {
+ var stmt string
+ stmt = CopyIn("table name")
+ if stmt != `COPY "table name" () FROM STDIN` {
+ t.Fatal(stmt)
+ }
+
+ stmt = CopyIn("table name", "column 1", "column 2")
+ if stmt != `COPY "table name" ("column 1", "column 2") FROM STDIN` {
+ t.Fatal(stmt)
+ }
+
+ stmt = CopyIn(`table " name """`, `co"lumn""`)
+ if stmt != `COPY "table "" name """"""" ("co""lumn""""") FROM STDIN` {
+ t.Fatal(stmt)
+ }
+}
+
+func TestCopyInSchemaStmt(t *testing.T) {
+ var stmt string
+ stmt = CopyInSchema("schema name", "table name")
+ if stmt != `COPY "schema name"."table name" () FROM STDIN` {
+ t.Fatal(stmt)
+ }
+
+ stmt = CopyInSchema("schema name", "table name", "column 1", "column 2")
+ if stmt != `COPY "schema name"."table name" ("column 1", "column 2") FROM STDIN` {
+ t.Fatal(stmt)
+ }
+
+ stmt = CopyInSchema(`schema " name """`, `table " name """`, `co"lumn""`)
+ if stmt != `COPY "schema "" name """"""".`+
+ `"table "" name """"""" ("co""lumn""""") FROM STDIN` {
+ t.Fatal(stmt)
+ }
+}
+
+func TestCopyInMultipleValues(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ longString := strings.Repeat("#", 500)
+
+ for i := 0; i < 500; i++ {
+ _, err = stmt.Exec(int64(i), longString)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ _, err = stmt.Exec()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = stmt.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var num int
+ err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if num != 500 {
+ t.Fatalf("expected 500 items, not %d", num)
+ }
+}
+
+func TestCopyInTypes(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER, text VARCHAR, blob BYTEA, nothing VARCHAR)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ stmt, err := txn.Prepare(CopyIn("temp", "num", "text", "blob", "nothing"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = stmt.Exec(int64(1234567890), "Héllö\n ☃!\r\t\\", []byte{0, 255, 9, 10, 13}, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = stmt.Exec()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = stmt.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var num int
+ var text string
+ var blob []byte
+ var nothing sql.NullString
+
+ err = txn.QueryRow("SELECT * FROM temp").Scan(&num, &text, &blob, ¬hing)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if num != 1234567890 {
+ t.Fatal("unexpected result", num)
+ }
+ if text != "Héllö\n ☃!\r\t\\" {
+ t.Fatal("unexpected result", text)
+ }
+ if bytes.Compare(blob, []byte{0, 255, 9, 10, 13}) != 0 {
+ t.Fatal("unexpected result", blob)
+ }
+ if nothing.Valid {
+ t.Fatal("unexpected result", nothing.String)
+ }
+}
+
+func TestCopyInWrongType(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ stmt, err := txn.Prepare(CopyIn("temp", "num"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer stmt.Close()
+
+ _, err = stmt.Exec("Héllö\n ☃!\r\t\\")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = stmt.Exec()
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if pge := err.(*Error); pge.Code.Name() != "invalid_text_representation" {
+ t.Fatalf("expected 'invalid input syntax for integer' error, got %s (%+v)", pge.Code.Name(), pge)
+ }
+}
+
+func TestCopyOutsideOfTxnError(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ _, err := db.Prepare(CopyIn("temp", "num"))
+ if err == nil {
+ t.Fatal("COPY outside of transaction did not return an error")
+ }
+ if err != errCopyNotSupportedOutsideTxn {
+ t.Fatalf("expected %s, got %s", err, err.Error())
+ }
+}
+
+func TestCopyInBinaryError(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = txn.Prepare("COPY temp (num) FROM STDIN WITH binary")
+ if err != errBinaryCopyNotSupported {
+ t.Fatalf("expected %s, got %+v", errBinaryCopyNotSupported, err)
+ }
+ // check that the protocol is in a valid state
+ err = txn.Rollback()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestCopyFromError(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMP TABLE temp (num INTEGER)")
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = txn.Prepare("COPY temp (num) TO STDOUT")
+ if err != errCopyToNotSupported {
+ t.Fatalf("expected %s, got %+v", errCopyToNotSupported, err)
+ }
+ // check that the protocol is in a valid state
+ err = txn.Rollback()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestCopySyntaxError(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Prepare("COPY ")
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if pge := err.(*Error); pge.Code.Name() != "syntax_error" {
+ t.Fatalf("expected syntax error, got %s (%+v)", pge.Code.Name(), pge)
+ }
+ // check that the protocol is in a valid state
+ err = txn.Rollback()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Tests for connection errors in copyin.resploop()
+func TestCopyRespLoopConnectionError(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ var pid int
+ err = txn.QueryRow("SELECT pg_backend_pid()").Scan(&pid)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = txn.Exec("CREATE TEMP TABLE temp (a int)")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ stmt, err := txn.Prepare(CopyIn("temp", "a"))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("SELECT pg_terminate_backend($1)", pid)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // We have to try and send something over, since postgres won't process
+ // SIGTERMs while it's waiting for CopyData/CopyEnd messages; see
+ // tcop/postgres.c.
+ _, err = stmt.Exec(1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = stmt.Exec()
+ if err == nil {
+ t.Fatalf("expected error")
+ }
+ pge, ok := err.(*Error)
+ if !ok {
+ t.Fatalf("expected *pq.Error, got %+#v", err)
+ } else if pge.Code.Name() != "admin_shutdown" {
+ t.Fatalf("expected admin_shutdown, got %s", pge.Code.Name())
+ }
+
+ err = stmt.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func BenchmarkCopyIn(b *testing.B) {
+ db := openTestConn(b)
+ defer db.Close()
+
+ txn, err := db.Begin()
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("CREATE TEMP TABLE temp (a int, b varchar)")
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ stmt, err := txn.Prepare(CopyIn("temp", "a", "b"))
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ for i := 0; i < b.N; i++ {
+ _, err = stmt.Exec(int64(i), "hello world!")
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+
+ _, err = stmt.Exec()
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ err = stmt.Close()
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ var num int
+ err = txn.QueryRow("SELECT COUNT(*) FROM temp").Scan(&num)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ if num != b.N {
+ b.Fatalf("expected %d items, not %d", b.N, num)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/doc.go b/Godeps/_workspace/src/github.com/lib/pq/doc.go
new file mode 100644
index 0000000..4d7a0e3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/doc.go
@@ -0,0 +1,209 @@
+/*
+Package pq is a pure Go Postgres driver for the database/sql package.
+
+In most cases clients will use the database/sql package instead of
+using this package directly. For example:
+
+ import (
+ _ "github.com/lib/pq"
+ "database/sql"
+ )
+
+ func main() {
+ db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ age := 21
+ rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
+ …
+ }
+
+You can also connect to a database using a URL. For example:
+
+ db, err := sql.Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full")
+
+
+Connection String Parameters
+
+
+Similarly to libpq, when establishing a connection using pq you are expected to
+supply a connection string containing zero or more parameters.
+A subset of the connection parameters supported by libpq are also supported by pq.
+Additionally, pq also lets you specify run-time parameters (such as search_path or work_mem)
+directly in the connection string. This is different from libpq, which does not allow
+run-time parameters in the connection string, instead requiring you to supply
+them in the options parameter.
+
+For compatibility with libpq, the following special connection parameters are
+supported:
+
+ * dbname - The name of the database to connect to
+ * user - The user to sign in as
+ * password - The user's password
+ * host - The host to connect to. Values that start with / are for unix domain sockets. (default is localhost)
+ * port - The port to bind to. (default is 5432)
+ * sslmode - Whether or not to use SSL (default is require, this is not the default for libpq)
+ * fallback_application_name - An application_name to fall back to if one isn't provided.
+ * connect_timeout - Maximum wait for connection, in seconds. Zero or not specified means wait indefinitely.
+ * sslcert - Cert file location. The file must contain PEM encoded data.
+ * sslkey - Key file location. The file must contain PEM encoded data.
+ * sslrootcert - The location of the root certificate file. The file must contain PEM encoded data.
+
+Valid values for sslmode are:
+
+ * disable - No SSL
+ * require - Always SSL (skip verification)
+ * verify-ca - Always SSL (verify that the certificate presented by the server was signed by a trusted CA)
+ * verify-full - Always SSL (verify that the certification presented by the server was signed by a trusted CA and the server host name matches the one in the certificate)
+
+See http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
+for more information about connection string parameters.
+
+Use single quotes for values that contain whitespace:
+
+ "user=pqgotest password='with spaces'"
+
+A backslash will escape the next character in values:
+
+ "user=space\ man password='it\'s valid'
+
+Note that the connection parameter client_encoding (which sets the
+text encoding for the connection) may be set but must be "UTF8",
+matching with the same rules as Postgres. It is an error to provide
+any other value.
+
+In addition to the parameters listed above, any run-time parameter that can be
+set at backend start time can be set in the connection string. For more
+information, see
+http://www.postgresql.org/docs/current/static/runtime-config.html.
+
+Most environment variables as specified at http://www.postgresql.org/docs/current/static/libpq-envars.html
+supported by libpq are also supported by pq. If any of the environment
+variables not supported by pq are set, pq will panic during connection
+establishment. Environment variables have a lower precedence than explicitly
+provided connection parameters.
+
+
+Queries
+
+database/sql does not dictate any specific format for parameter
+markers in query strings, and pq uses the Postgres-native ordinal markers,
+as shown above. The same marker can be reused for the same parameter:
+
+ rows, err := db.Query(`SELECT name FROM users WHERE favorite_fruit = $1
+ OR age BETWEEN $2 AND $2 + 3`, "orange", 64)
+
+pq does not support the LastInsertId() method of the Result type in database/sql.
+To return the identifier of an INSERT (or UPDATE or DELETE), use the Postgres
+RETURNING clause with a standard Query or QueryRow call:
+
+ var userid int
+ err := db.QueryRow(`INSERT INTO users(name, favorite_fruit, age)
+ VALUES('beatrice', 'starfruit', 93) RETURNING id`).Scan(&userid)
+
+For more details on RETURNING, see the Postgres documentation:
+
+ http://www.postgresql.org/docs/current/static/sql-insert.html
+ http://www.postgresql.org/docs/current/static/sql-update.html
+ http://www.postgresql.org/docs/current/static/sql-delete.html
+
+For additional instructions on querying see the documentation for the database/sql package.
+
+Errors
+
+pq may return errors of type *pq.Error which can be interrogated for error details:
+
+ if err, ok := err.(*pq.Error); ok {
+ fmt.Println("pq error:", err.Code.Name())
+ }
+
+See the pq.Error type for details.
+
+
+Bulk imports
+
+You can perform bulk imports by preparing a statement returned by pq.CopyIn (or
+pq.CopyInSchema) in an explicit transaction (sql.Tx). The returned statement
+handle can then be repeatedly "executed" to copy data into the target table.
+After all data has been processed you should call Exec() once with no arguments
+to flush all buffered data. Any call to Exec() might return an error which
+should be handled appropriately, but because of the internal buffering an error
+returned by Exec() might not be related to the data passed in the call that
+failed.
+
+CopyIn uses COPY FROM internally. It is not possible to COPY outside of an
+explicit transaction in pq.
+
+Usage example:
+
+ txn, err := db.Begin()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ stmt, err := txn.Prepare(pq.CopyIn("users", "name", "age"))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, user := range users {
+ _, err = stmt.Exec(user.Name, int64(user.Age))
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ _, err = stmt.Exec()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = stmt.Close()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = txn.Commit()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+
+Notifications
+
+
+PostgreSQL supports a simple publish/subscribe model over database
+connections. See http://www.postgresql.org/docs/current/static/sql-notify.html
+for more information about the general mechanism.
+
+To start listening for notifications, you first have to open a new connection
+to the database by calling NewListener. This connection can not be used for
+anything other than LISTEN / NOTIFY. Calling Listen will open a "notification
+channel"; once a notification channel is open, a notification generated on that
+channel will effect a send on the Listener.Notify channel. A notification
+channel will remain open until Unlisten is called, though connection loss might
+result in some notifications being lost. To solve this problem, Listener sends
+a nil pointer over the Notify channel any time the connection is re-established
+following a connection loss. The application can get information about the
+state of the underlying connection by setting an event callback in the call to
+NewListener.
+
+A single Listener can safely be used from concurrent goroutines, which means
+that there is often no need to create more than one Listener in your
+application. However, a Listener is always connected to a single database, so
+you will need to create a new Listener instance for every database you want to
+receive notifications in.
+
+The channel name in both Listen and Unlisten is case sensitive, and can contain
+any characters legal in an identifier (see
+http://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
+for more information). Note that the channel name will be truncated to 63
+bytes by the PostgreSQL server.
+
+You can find a complete, working example of Listener usage at
+http://godoc.org/github.com/lib/pq/listen_example.
+
+*/
+package pq
diff --git a/Godeps/_workspace/src/github.com/lib/pq/encode.go b/Godeps/_workspace/src/github.com/lib/pq/encode.go
new file mode 100644
index 0000000..3887f72
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/encode.go
@@ -0,0 +1,437 @@
+package pq
+
+import (
+ "bytes"
+ "database/sql/driver"
+ "encoding/hex"
+ "fmt"
+ "github.com/lib/pq/oid"
+ "math"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+func encode(parameterStatus *parameterStatus, x interface{}, pgtypOid oid.Oid) []byte {
+ switch v := x.(type) {
+ case int64:
+ return []byte(fmt.Sprintf("%d", v))
+ case float32:
+ return []byte(fmt.Sprintf("%.9f", v))
+ case float64:
+ return []byte(fmt.Sprintf("%.17f", v))
+ case []byte:
+ if pgtypOid == oid.T_bytea {
+ return encodeBytea(parameterStatus.serverVersion, v)
+ }
+
+ return v
+ case string:
+ if pgtypOid == oid.T_bytea {
+ return encodeBytea(parameterStatus.serverVersion, []byte(v))
+ }
+
+ return []byte(v)
+ case bool:
+ return []byte(fmt.Sprintf("%t", v))
+ case time.Time:
+ return formatTs(v)
+
+ default:
+ errorf("encode: unknown type for %T", v)
+ }
+
+ panic("not reached")
+}
+
+func decode(parameterStatus *parameterStatus, s []byte, typ oid.Oid) interface{} {
+ switch typ {
+ case oid.T_bytea:
+ return parseBytea(s)
+ case oid.T_timestamptz:
+ return parseTs(parameterStatus.currentLocation, string(s))
+ case oid.T_timestamp, oid.T_date:
+ return parseTs(nil, string(s))
+ case oid.T_time:
+ return mustParse("15:04:05", typ, s)
+ case oid.T_timetz:
+ return mustParse("15:04:05-07", typ, s)
+ case oid.T_bool:
+ return s[0] == 't'
+ case oid.T_int8, oid.T_int2, oid.T_int4:
+ i, err := strconv.ParseInt(string(s), 10, 64)
+ if err != nil {
+ errorf("%s", err)
+ }
+ return i
+ case oid.T_float4, oid.T_float8:
+ bits := 64
+ if typ == oid.T_float4 {
+ bits = 32
+ }
+ f, err := strconv.ParseFloat(string(s), bits)
+ if err != nil {
+ errorf("%s", err)
+ }
+ return f
+ }
+
+ return s
+}
+
+// appendEncodedText encodes item in text format as required by COPY
+// and appends to buf
+func appendEncodedText(parameterStatus *parameterStatus, buf []byte, x interface{}) []byte {
+ switch v := x.(type) {
+ case int64:
+ return strconv.AppendInt(buf, v, 10)
+ case float32:
+ return strconv.AppendFloat(buf, float64(v), 'f', -1, 32)
+ case float64:
+ return strconv.AppendFloat(buf, v, 'f', -1, 64)
+ case []byte:
+ encodedBytea := encodeBytea(parameterStatus.serverVersion, v)
+ return appendEscapedText(buf, string(encodedBytea))
+ case string:
+ return appendEscapedText(buf, v)
+ case bool:
+ return strconv.AppendBool(buf, v)
+ case time.Time:
+ return append(buf, formatTs(v)...)
+ case nil:
+ return append(buf, "\\N"...)
+ default:
+ errorf("encode: unknown type for %T", v)
+ }
+
+ panic("not reached")
+}
+
+func appendEscapedText(buf []byte, text string) []byte {
+ escapeNeeded := false
+ startPos := 0
+ var c byte
+
+ // check if we need to escape
+ for i := 0; i < len(text); i++ {
+ c = text[i]
+ if c == '\\' || c == '\n' || c == '\r' || c == '\t' {
+ escapeNeeded = true
+ startPos = i
+ break
+ }
+ }
+ if !escapeNeeded {
+ return append(buf, text...)
+ }
+
+ // copy till first char to escape, iterate the rest
+ result := append(buf, text[:startPos]...)
+ for i := startPos; i < len(text); i++ {
+ c = text[i]
+ switch c {
+ case '\\':
+ result = append(result, '\\', '\\')
+ case '\n':
+ result = append(result, '\\', 'n')
+ case '\r':
+ result = append(result, '\\', 'r')
+ case '\t':
+ result = append(result, '\\', 't')
+ default:
+ result = append(result, c)
+ }
+ }
+ return result
+}
+
+func mustParse(f string, typ oid.Oid, s []byte) time.Time {
+ str := string(s)
+
+ // Special case until time.Parse bug is fixed:
+ // http://code.google.com/p/go/issues/detail?id=3487
+ if str[len(str)-2] == '.' {
+ str += "0"
+ }
+
+ // check for a 30-minute-offset timezone
+ if (typ == oid.T_timestamptz || typ == oid.T_timetz) &&
+ str[len(str)-3] == ':' {
+ f += ":00"
+ }
+ t, err := time.Parse(f, str)
+ if err != nil {
+ errorf("decode: %s", err)
+ }
+ return t
+}
+
+func expect(str, char string, pos int) {
+ if c := str[pos : pos+1]; c != char {
+ errorf("expected '%v' at position %v; got '%v'", char, pos, c)
+ }
+}
+
+func mustAtoi(str string) int {
+ result, err := strconv.Atoi(str)
+ if err != nil {
+ errorf("expected number; got '%v'", str)
+ }
+ return result
+}
+
+// The location cache caches the time zones typically used by the client.
+type locationCache struct {
+ cache map[int]*time.Location
+ lock sync.Mutex
+}
+
+// All connections share the same list of timezones. Benchmarking shows that
+// about 5% speed could be gained by putting the cache in the connection and
+// losing the mutex, at the cost of a small amount of memory and a somewhat
+// significant increase in code complexity.
+var globalLocationCache *locationCache = newLocationCache()
+
+func newLocationCache() *locationCache {
+ return &locationCache{cache: make(map[int]*time.Location)}
+}
+
+// Returns the cached timezone for the specified offset, creating and caching
+// it if necessary.
+func (c *locationCache) getLocation(offset int) *time.Location {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ location, ok := c.cache[offset]
+ if !ok {
+ location = time.FixedZone("", offset)
+ c.cache[offset] = location
+ }
+
+ return location
+}
+
+// This is a time function specific to the Postgres default DateStyle
+// setting ("ISO, MDY"), the only one we currently support. This
+// accounts for the discrepancies between the parsing available with
+// time.Parse and the Postgres date formatting quirks.
+func parseTs(currentLocation *time.Location, str string) (result time.Time) {
+ monSep := strings.IndexRune(str, '-')
+ // this is Gregorian year, not ISO Year
+ // In Gregorian system, the year 1 BC is followed by AD 1
+ year := mustAtoi(str[:monSep])
+ daySep := monSep + 3
+ month := mustAtoi(str[monSep+1 : daySep])
+ expect(str, "-", daySep)
+ timeSep := daySep + 3
+ day := mustAtoi(str[daySep+1 : timeSep])
+
+ var hour, minute, second int
+ if len(str) > monSep+len("01-01")+1 {
+ expect(str, " ", timeSep)
+ minSep := timeSep + 3
+ expect(str, ":", minSep)
+ hour = mustAtoi(str[timeSep+1 : minSep])
+ secSep := minSep + 3
+ expect(str, ":", secSep)
+ minute = mustAtoi(str[minSep+1 : secSep])
+ secEnd := secSep + 3
+ second = mustAtoi(str[secSep+1 : secEnd])
+ }
+ remainderIdx := monSep + len("01-01 00:00:00") + 1
+ // Three optional (but ordered) sections follow: the
+ // fractional seconds, the time zone offset, and the BC
+ // designation. We set them up here and adjust the other
+ // offsets if the preceding sections exist.
+
+ nanoSec := 0
+ tzOff := 0
+
+ if remainderIdx < len(str) && str[remainderIdx:remainderIdx+1] == "." {
+ fracStart := remainderIdx + 1
+ fracOff := strings.IndexAny(str[fracStart:], "-+ ")
+ if fracOff < 0 {
+ fracOff = len(str) - fracStart
+ }
+ fracSec := mustAtoi(str[fracStart : fracStart+fracOff])
+ nanoSec = fracSec * (1000000000 / int(math.Pow(10, float64(fracOff))))
+
+ remainderIdx += fracOff + 1
+ }
+ if tzStart := remainderIdx; tzStart < len(str) && (str[tzStart:tzStart+1] == "-" || str[tzStart:tzStart+1] == "+") {
+ // time zone separator is always '-' or '+' (UTC is +00)
+ var tzSign int
+ if c := str[tzStart : tzStart+1]; c == "-" {
+ tzSign = -1
+ } else if c == "+" {
+ tzSign = +1
+ } else {
+ errorf("expected '-' or '+' at position %v; got %v", tzStart, c)
+ }
+ tzHours := mustAtoi(str[tzStart+1 : tzStart+3])
+ remainderIdx += 3
+ var tzMin, tzSec int
+ if tzStart+3 < len(str) && str[tzStart+3:tzStart+4] == ":" {
+ tzMin = mustAtoi(str[tzStart+4 : tzStart+6])
+ remainderIdx += 3
+ }
+ if tzStart+6 < len(str) && str[tzStart+6:tzStart+7] == ":" {
+ tzSec = mustAtoi(str[tzStart+7 : tzStart+9])
+ remainderIdx += 3
+ }
+ tzOff = tzSign * ((tzHours * 60 * 60) + (tzMin * 60) + tzSec)
+ }
+ var isoYear int
+ if remainderIdx < len(str) && str[remainderIdx:remainderIdx+3] == " BC" {
+ isoYear = 1 - year
+ remainderIdx += 3
+ } else {
+ isoYear = year
+ }
+ if remainderIdx < len(str) {
+ errorf("expected end of input, got %v", str[remainderIdx:])
+ }
+ t := time.Date(isoYear, time.Month(month), day,
+ hour, minute, second, nanoSec,
+ globalLocationCache.getLocation(tzOff))
+
+ if currentLocation != nil {
+ // Set the location of the returned Time based on the session's
+ // TimeZone value, but only if the local time zone database agrees with
+ // the remote database on the offset.
+ lt := t.In(currentLocation)
+ _, newOff := lt.Zone()
+ if newOff == tzOff {
+ t = lt
+ }
+ }
+
+ return t
+}
+
+// formatTs formats t as time.RFC3339Nano and appends time zone seconds if
+// needed.
+func formatTs(t time.Time) (b []byte) {
+ b = []byte(t.Format(time.RFC3339Nano))
+ // Need to send dates before 0001 A.D. with " BC" suffix, instead of the
+ // minus sign preferred by Go.
+ // Beware, "0000" in ISO is "1 BC", "-0001" is "2 BC" and so on
+ bc := false
+ if t.Year() <= 0 {
+ // flip year sign, and add 1, e.g: "0" will be "1", and "-10" will be "11"
+ t = t.AddDate((-t.Year())*2+1, 0, 0)
+ bc = true
+ }
+ b = []byte(t.Format(time.RFC3339Nano))
+ if bc {
+ b = append(b, " BC"...)
+ }
+
+ _, offset := t.Zone()
+ offset = offset % 60
+ if offset == 0 {
+ return b
+ }
+
+ if offset < 0 {
+ offset = -offset
+ }
+
+ b = append(b, ':')
+ if offset < 10 {
+ b = append(b, '0')
+ }
+ return strconv.AppendInt(b, int64(offset), 10)
+}
+
+// Parse a bytea value received from the server. Both "hex" and the legacy
+// "escape" format are supported.
+func parseBytea(s []byte) (result []byte) {
+ if len(s) >= 2 && bytes.Equal(s[:2], []byte("\\x")) {
+ // bytea_output = hex
+ s = s[2:] // trim off leading "\\x"
+ result = make([]byte, hex.DecodedLen(len(s)))
+ _, err := hex.Decode(result, s)
+ if err != nil {
+ errorf("%s", err)
+ }
+ } else {
+ // bytea_output = escape
+ for len(s) > 0 {
+ if s[0] == '\\' {
+ // escaped '\\'
+ if len(s) >= 2 && s[1] == '\\' {
+ result = append(result, '\\')
+ s = s[2:]
+ continue
+ }
+
+ // '\\' followed by an octal number
+ if len(s) < 4 {
+ errorf("invalid bytea sequence %v", s)
+ }
+ r, err := strconv.ParseInt(string(s[1:4]), 8, 9)
+ if err != nil {
+ errorf("could not parse bytea value: %s", err.Error())
+ }
+ result = append(result, byte(r))
+ s = s[4:]
+ } else {
+ // We hit an unescaped, raw byte. Try to read in as many as
+ // possible in one go.
+ i := bytes.IndexByte(s, '\\')
+ if i == -1 {
+ result = append(result, s...)
+ break
+ }
+ result = append(result, s[:i]...)
+ s = s[i:]
+ }
+ }
+ }
+
+ return result
+}
+
+func encodeBytea(serverVersion int, v []byte) (result []byte) {
+ if serverVersion >= 90000 {
+ // Use the hex format if we know that the server supports it
+ result = []byte(fmt.Sprintf("\\x%x", v))
+ } else {
+ // .. or resort to "escape"
+ for _, b := range v {
+ if b == '\\' {
+ result = append(result, '\\', '\\')
+ } else if b < 0x20 || b > 0x7e {
+ result = append(result, []byte(fmt.Sprintf("\\%03o", b))...)
+ } else {
+ result = append(result, b)
+ }
+ }
+ }
+
+ return result
+}
+
+// NullTime represents a time.Time that may be null. NullTime implements the
+// sql.Scanner interface so it can be used as a scan destination, similar to
+// sql.NullString.
+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
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/encode_test.go b/Godeps/_workspace/src/github.com/lib/pq/encode_test.go
new file mode 100644
index 0000000..e835f2b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/encode_test.go
@@ -0,0 +1,433 @@
+package pq
+
+import (
+ "github.com/lib/pq/oid"
+
+ "bytes"
+ "fmt"
+ "testing"
+ "time"
+)
+
+func TestScanTimestamp(t *testing.T) {
+ var nt NullTime
+ tn := time.Now()
+ nt.Scan(tn)
+ if !nt.Valid {
+ t.Errorf("Expected Valid=false")
+ }
+ if nt.Time != tn {
+ t.Errorf("Time value mismatch")
+ }
+}
+
+func TestScanNilTimestamp(t *testing.T) {
+ var nt NullTime
+ nt.Scan(nil)
+ if nt.Valid {
+ t.Errorf("Expected Valid=false")
+ }
+}
+
+var timeTests = []struct {
+ str string
+ timeval time.Time
+}{
+ {"22001-02-03", time.Date(22001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
+ {"2001-02-03", time.Date(2001, time.February, 3, 0, 0, 0, 0, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06", time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.000001", time.Date(2001, time.February, 3, 4, 5, 6, 1000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.00001", time.Date(2001, time.February, 3, 4, 5, 6, 10000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.0001", time.Date(2001, time.February, 3, 4, 5, 6, 100000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.001", time.Date(2001, time.February, 3, 4, 5, 6, 1000000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.01", time.Date(2001, time.February, 3, 4, 5, 6, 10000000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.1", time.Date(2001, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.12", time.Date(2001, time.February, 3, 4, 5, 6, 120000000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.123", time.Date(2001, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.1234", time.Date(2001, time.February, 3, 4, 5, 6, 123400000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.12345", time.Date(2001, time.February, 3, 4, 5, 6, 123450000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.123456", time.Date(2001, time.February, 3, 4, 5, 6, 123456000, time.FixedZone("", 0))},
+ {"2001-02-03 04:05:06.123-07", time.Date(2001, time.February, 3, 4, 5, 6, 123000000,
+ time.FixedZone("", -7*60*60))},
+ {"2001-02-03 04:05:06-07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
+ time.FixedZone("", -7*60*60))},
+ {"2001-02-03 04:05:06-07:42", time.Date(2001, time.February, 3, 4, 5, 6, 0,
+ time.FixedZone("", -(7*60*60+42*60)))},
+ {"2001-02-03 04:05:06-07:30:09", time.Date(2001, time.February, 3, 4, 5, 6, 0,
+ time.FixedZone("", -(7*60*60+30*60+9)))},
+ {"2001-02-03 04:05:06+07", time.Date(2001, time.February, 3, 4, 5, 6, 0,
+ time.FixedZone("", 7*60*60))},
+ {"0011-02-03 04:05:06 BC", time.Date(-10, time.February, 3, 4, 5, 6, 0, time.FixedZone("", 0))},
+ {"0011-02-03 04:05:06.123 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
+ {"0011-02-03 04:05:06.123-07 BC", time.Date(-10, time.February, 3, 4, 5, 6, 123000000,
+ time.FixedZone("", -7*60*60))},
+ {"0001-02-03 04:05:06.123", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
+ {"0001-02-03 04:05:06.123 BC", time.Date(1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
+ {"0001-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
+ {"0002-02-03 04:05:06.123 BC", time.Date(0, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0)).AddDate(-1, 0, 0)},
+ {"0002-02-03 04:05:06.123 BC", time.Date(-1, time.February, 3, 4, 5, 6, 123000000, time.FixedZone("", 0))},
+ {"12345-02-03 04:05:06.1", time.Date(12345, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
+ {"123456-02-03 04:05:06.1", time.Date(123456, time.February, 3, 4, 5, 6, 100000000, time.FixedZone("", 0))},
+}
+
+// Helper function for the two tests below
+func tryParse(str string) (t time.Time, err error) {
+ defer func() {
+ if p := recover(); p != nil {
+ err = fmt.Errorf("%v", p)
+ return
+ }
+ }()
+ t = parseTs(nil, str)
+ return
+}
+
+// Test that parsing the string results in the expected value.
+func TestParseTs(t *testing.T) {
+ for i, tt := range timeTests {
+ val, err := tryParse(tt.str)
+ if err != nil {
+ t.Errorf("%d: got error: %v", i, err)
+ } else if val.String() != tt.timeval.String() {
+ t.Errorf("%d: expected to parse %q into %q; got %q",
+ i, tt.str, tt.timeval, val)
+ }
+ }
+}
+
+// Now test that sending the value into the database and parsing it back
+// returns the same time.Time value.
+func TestEncodeAndParseTs(t *testing.T) {
+ db, err := openTestConnConninfo("timezone='Etc/UTC'")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer db.Close()
+
+ for i, tt := range timeTests {
+ var dbstr string
+ err = db.QueryRow("SELECT ($1::timestamptz)::text", tt.timeval).Scan(&dbstr)
+ if err != nil {
+ t.Errorf("%d: could not send value %q to the database: %s", i, tt.timeval, err)
+ continue
+ }
+
+ val, err := tryParse(dbstr)
+ if err != nil {
+ t.Errorf("%d: could not parse value %q: %s", i, dbstr, err)
+ continue
+ }
+ val = val.In(tt.timeval.Location())
+ if val.String() != tt.timeval.String() {
+ t.Errorf("%d: expected to parse %q into %q; got %q", i, dbstr, tt.timeval, val)
+ }
+ }
+}
+
+var formatTimeTests = []struct {
+ time time.Time
+ expected string
+}{
+ {time.Time{}, "0001-01-01T00:00:00Z"},
+ {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 0)), "2001-02-03T04:05:06.123456789Z"},
+ {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", 2*60*60)), "2001-02-03T04:05:06.123456789+02:00"},
+ {time.Date(2001, time.February, 3, 4, 5, 6, 123456789, time.FixedZone("", -6*60*60)), "2001-02-03T04:05:06.123456789-06:00"},
+ {time.Date(1, time.January, 1, 0, 0, 0, 0, time.FixedZone("", 19*60+32)), "0001-01-01T00:00:00+00:19:32"},
+ {time.Date(2001, time.February, 3, 4, 5, 6, 0, time.FixedZone("", -(7*60*60+30*60+9))), "2001-02-03T04:05:06-07:30:09"},
+}
+
+func TestFormatTs(t *testing.T) {
+ for i, tt := range formatTimeTests {
+ val := string(formatTs(tt.time))
+ if val != tt.expected {
+ t.Errorf("%d: incorrect time format %q, want %q", i, val, tt.expected)
+ }
+ }
+}
+
+func TestTimestampWithTimeZone(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ tx, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer tx.Rollback()
+
+ // try several different locations, all included in Go's zoneinfo.zip
+ for _, locName := range []string{
+ "UTC",
+ "America/Chicago",
+ "America/New_York",
+ "Australia/Darwin",
+ "Australia/Perth",
+ } {
+ loc, err := time.LoadLocation(locName)
+ if err != nil {
+ t.Logf("Could not load time zone %s - skipping", locName)
+ continue
+ }
+
+ // Postgres timestamps have a resolution of 1 microsecond, so don't
+ // use the full range of the Nanosecond argument
+ refTime := time.Date(2012, 11, 6, 10, 23, 42, 123456000, loc)
+
+ for _, pgTimeZone := range []string{"US/Eastern", "Australia/Darwin"} {
+ // Switch Postgres's timezone to test different output timestamp formats
+ _, err = tx.Exec(fmt.Sprintf("set time zone '%s'", pgTimeZone))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var gotTime time.Time
+ row := tx.QueryRow("select $1::timestamp with time zone", refTime)
+ err = row.Scan(&gotTime)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !refTime.Equal(gotTime) {
+ t.Errorf("timestamps not equal: %s != %s", refTime, gotTime)
+ }
+
+ // check that the time zone is set correctly based on TimeZone
+ pgLoc, err := time.LoadLocation(pgTimeZone)
+ if err != nil {
+ t.Logf("Could not load time zone %s - skipping", pgLoc)
+ continue
+ }
+ translated := refTime.In(pgLoc)
+ if translated.String() != gotTime.String() {
+ t.Errorf("timestamps not equal: %s != %s", translated, gotTime)
+ }
+ }
+ }
+}
+
+func TestTimestampWithOutTimezone(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ test := func(ts, pgts string) {
+ r, err := db.Query("SELECT $1::timestamp", pgts)
+ if err != nil {
+ t.Fatalf("Could not run query: %v", err)
+ }
+
+ n := r.Next()
+
+ if n != true {
+ t.Fatal("Expected at least one row")
+ }
+
+ var result time.Time
+ err = r.Scan(&result)
+ if err != nil {
+ t.Fatalf("Did not expect error scanning row: %v", err)
+ }
+
+ expected, err := time.Parse(time.RFC3339, ts)
+ if err != nil {
+ t.Fatalf("Could not parse test time literal: %v", err)
+ }
+
+ if !result.Equal(expected) {
+ t.Fatalf("Expected time to match %v: got mismatch %v",
+ expected, result)
+ }
+
+ n = r.Next()
+ if n != false {
+ t.Fatal("Expected only one row")
+ }
+ }
+
+ test("2000-01-01T00:00:00Z", "2000-01-01T00:00:00")
+
+ // Test higher precision time
+ test("2013-01-04T20:14:58.80033Z", "2013-01-04 20:14:58.80033")
+}
+
+func TestStringWithNul(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ hello0world := string("hello\x00world")
+ _, err := db.Query("SELECT $1::text", &hello0world)
+ if err == nil {
+ t.Fatal("Postgres accepts a string with nul in it; " +
+ "injection attacks may be plausible")
+ }
+}
+
+func TestByteaToText(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ b := []byte("hello world")
+ row := db.QueryRow("SELECT $1::text", b)
+
+ var result []byte
+ err := row.Scan(&result)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if string(result) != string(b) {
+ t.Fatalf("expected %v but got %v", b, result)
+ }
+}
+
+func TestTextToBytea(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ b := "hello world"
+ row := db.QueryRow("SELECT $1::bytea", b)
+
+ var result []byte
+ err := row.Scan(&result)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bytes.Equal(result, []byte(b)) {
+ t.Fatalf("expected %v but got %v", b, result)
+ }
+}
+
+func TestByteaOutputFormatEncoding(t *testing.T) {
+ input := []byte("\\x\x00\x01\x02\xFF\xFEabcdefg0123")
+ want := []byte("\\x5c78000102fffe6162636465666730313233")
+ got := encode(¶meterStatus{serverVersion: 90000}, input, oid.T_bytea)
+ if !bytes.Equal(want, got) {
+ t.Errorf("invalid hex bytea output, got %v but expected %v", got, want)
+ }
+
+ want = []byte("\\\\x\\000\\001\\002\\377\\376abcdefg0123")
+ got = encode(¶meterStatus{serverVersion: 84000}, input, oid.T_bytea)
+ if !bytes.Equal(want, got) {
+ t.Errorf("invalid escape bytea output, got %v but expected %v", got, want)
+ }
+}
+
+func TestByteaOutputFormats(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ if getServerVersion(t, db) < 90000 {
+ // skip
+ return
+ }
+
+ testByteaOutputFormat := func(f string) {
+ expectedData := []byte("\x5c\x78\x00\xff\x61\x62\x63\x01\x08")
+ sqlQuery := "SELECT decode('5c7800ff6162630108', 'hex')"
+
+ var data []byte
+
+ // use a txn to avoid relying on getting the same connection
+ txn, err := db.Begin()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer txn.Rollback()
+
+ _, err = txn.Exec("SET LOCAL bytea_output TO " + f)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // use Query; QueryRow would hide the actual error
+ rows, err := txn.Query(sqlQuery)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !rows.Next() {
+ if rows.Err() != nil {
+ t.Fatal(rows.Err())
+ }
+ t.Fatal("shouldn't happen")
+ }
+ err = rows.Scan(&data)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = rows.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(data, expectedData) {
+ t.Errorf("unexpected bytea value %v for format %s; expected %v", data, f, expectedData)
+ }
+ }
+
+ testByteaOutputFormat("hex")
+ testByteaOutputFormat("escape")
+}
+
+func TestAppendEncodedText(t *testing.T) {
+ var buf []byte
+
+ buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, int64(10))
+ buf = append(buf, '\t')
+ buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, float32(42.0000000001))
+ buf = append(buf, '\t')
+ buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, 42.0000000001)
+ buf = append(buf, '\t')
+ buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, "hello\tworld")
+ buf = append(buf, '\t')
+ buf = appendEncodedText(¶meterStatus{serverVersion: 90000}, buf, []byte{0, 128, 255})
+
+ if string(buf) != "10\t42\t42.0000000001\thello\\tworld\t\\\\x0080ff" {
+ t.Fatal(string(buf))
+ }
+}
+
+func TestAppendEscapedText(t *testing.T) {
+ if esc := appendEscapedText(nil, "hallo\tescape"); string(esc) != "hallo\\tescape" {
+ t.Fatal(string(esc))
+ }
+ if esc := appendEscapedText(nil, "hallo\\tescape\n"); string(esc) != "hallo\\\\tescape\\n" {
+ t.Fatal(string(esc))
+ }
+ if esc := appendEscapedText(nil, "\n\r\t\f"); string(esc) != "\\n\\r\\t\f" {
+ t.Fatal(string(esc))
+ }
+}
+
+func TestAppendEscapedTextExistingBuffer(t *testing.T) {
+ var buf []byte
+ buf = []byte("123\t")
+ if esc := appendEscapedText(buf, "hallo\tescape"); string(esc) != "123\thallo\\tescape" {
+ t.Fatal(string(esc))
+ }
+ buf = []byte("123\t")
+ if esc := appendEscapedText(buf, "hallo\\tescape\n"); string(esc) != "123\thallo\\\\tescape\\n" {
+ t.Fatal(string(esc))
+ }
+ buf = []byte("123\t")
+ if esc := appendEscapedText(buf, "\n\r\t\f"); string(esc) != "123\t\\n\\r\\t\f" {
+ t.Fatal(string(esc))
+ }
+}
+
+func BenchmarkAppendEscapedText(b *testing.B) {
+ longString := ""
+ for i := 0; i < 100; i++ {
+ longString += "123456789\n"
+ }
+ for i := 0; i < b.N; i++ {
+ appendEscapedText(nil, longString)
+ }
+}
+
+func BenchmarkAppendEscapedTextNoEscape(b *testing.B) {
+ longString := ""
+ for i := 0; i < 100; i++ {
+ longString += "1234567890"
+ }
+ for i := 0; i < b.N; i++ {
+ appendEscapedText(nil, longString)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/error.go b/Godeps/_workspace/src/github.com/lib/pq/error.go
new file mode 100644
index 0000000..0a49364
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/error.go
@@ -0,0 +1,495 @@
+package pq
+
+import (
+ "database/sql/driver"
+ "fmt"
+ "io"
+ "net"
+ "runtime"
+)
+
+// Error severities
+const (
+ Efatal = "FATAL"
+ Epanic = "PANIC"
+ Ewarning = "WARNING"
+ Enotice = "NOTICE"
+ Edebug = "DEBUG"
+ Einfo = "INFO"
+ Elog = "LOG"
+)
+
+// Error represents an error communicating with the server.
+//
+// See http://www.postgresql.org/docs/current/static/protocol-error-fields.html for details of the fields
+type Error struct {
+ Severity string
+ Code ErrorCode
+ Message string
+ Detail string
+ Hint string
+ Position string
+ InternalPosition string
+ InternalQuery string
+ Where string
+ Schema string
+ Table string
+ Column string
+ DataTypeName string
+ Constraint string
+ File string
+ Line string
+ Routine string
+}
+
+// ErrorCode is a five-character error code.
+type ErrorCode string
+
+// Name returns a more human friendly rendering of the error code, namely the
+// "condition name".
+//
+// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
+// details.
+func (ec ErrorCode) Name() string {
+ return errorCodeNames[ec]
+}
+
+// ErrorClass is only the class part of an error code.
+type ErrorClass string
+
+// Name returns the condition name of an error class. It is equivalent to the
+// condition name of the "standard" error code (i.e. the one having the last
+// three characters "000").
+func (ec ErrorClass) Name() string {
+ return errorCodeNames[ErrorCode(ec+"000")]
+}
+
+// Class returns the error class, e.g. "28".
+//
+// See http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html for
+// details.
+func (ec ErrorCode) Class() ErrorClass {
+ return ErrorClass(ec[0:2])
+}
+
+// errorCodeNames is a mapping between the five-character error codes and the
+// human readable "condition names". It is derived from the list at
+// http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
+var errorCodeNames = map[ErrorCode]string{
+ // Class 00 - Successful Completion
+ "00000": "successful_completion",
+ // Class 01 - Warning
+ "01000": "warning",
+ "0100C": "dynamic_result_sets_returned",
+ "01008": "implicit_zero_bit_padding",
+ "01003": "null_value_eliminated_in_set_function",
+ "01007": "privilege_not_granted",
+ "01006": "privilege_not_revoked",
+ "01004": "string_data_right_truncation",
+ "01P01": "deprecated_feature",
+ // Class 02 - No Data (this is also a warning class per the SQL standard)
+ "02000": "no_data",
+ "02001": "no_additional_dynamic_result_sets_returned",
+ // Class 03 - SQL Statement Not Yet Complete
+ "03000": "sql_statement_not_yet_complete",
+ // Class 08 - Connection Exception
+ "08000": "connection_exception",
+ "08003": "connection_does_not_exist",
+ "08006": "connection_failure",
+ "08001": "sqlclient_unable_to_establish_sqlconnection",
+ "08004": "sqlserver_rejected_establishment_of_sqlconnection",
+ "08007": "transaction_resolution_unknown",
+ "08P01": "protocol_violation",
+ // Class 09 - Triggered Action Exception
+ "09000": "triggered_action_exception",
+ // Class 0A - Feature Not Supported
+ "0A000": "feature_not_supported",
+ // Class 0B - Invalid Transaction Initiation
+ "0B000": "invalid_transaction_initiation",
+ // Class 0F - Locator Exception
+ "0F000": "locator_exception",
+ "0F001": "invalid_locator_specification",
+ // Class 0L - Invalid Grantor
+ "0L000": "invalid_grantor",
+ "0LP01": "invalid_grant_operation",
+ // Class 0P - Invalid Role Specification
+ "0P000": "invalid_role_specification",
+ // Class 0Z - Diagnostics Exception
+ "0Z000": "diagnostics_exception",
+ "0Z002": "stacked_diagnostics_accessed_without_active_handler",
+ // Class 20 - Case Not Found
+ "20000": "case_not_found",
+ // Class 21 - Cardinality Violation
+ "21000": "cardinality_violation",
+ // Class 22 - Data Exception
+ "22000": "data_exception",
+ "2202E": "array_subscript_error",
+ "22021": "character_not_in_repertoire",
+ "22008": "datetime_field_overflow",
+ "22012": "division_by_zero",
+ "22005": "error_in_assignment",
+ "2200B": "escape_character_conflict",
+ "22022": "indicator_overflow",
+ "22015": "interval_field_overflow",
+ "2201E": "invalid_argument_for_logarithm",
+ "22014": "invalid_argument_for_ntile_function",
+ "22016": "invalid_argument_for_nth_value_function",
+ "2201F": "invalid_argument_for_power_function",
+ "2201G": "invalid_argument_for_width_bucket_function",
+ "22018": "invalid_character_value_for_cast",
+ "22007": "invalid_datetime_format",
+ "22019": "invalid_escape_character",
+ "2200D": "invalid_escape_octet",
+ "22025": "invalid_escape_sequence",
+ "22P06": "nonstandard_use_of_escape_character",
+ "22010": "invalid_indicator_parameter_value",
+ "22023": "invalid_parameter_value",
+ "2201B": "invalid_regular_expression",
+ "2201W": "invalid_row_count_in_limit_clause",
+ "2201X": "invalid_row_count_in_result_offset_clause",
+ "22009": "invalid_time_zone_displacement_value",
+ "2200C": "invalid_use_of_escape_character",
+ "2200G": "most_specific_type_mismatch",
+ "22004": "null_value_not_allowed",
+ "22002": "null_value_no_indicator_parameter",
+ "22003": "numeric_value_out_of_range",
+ "22026": "string_data_length_mismatch",
+ "22001": "string_data_right_truncation",
+ "22011": "substring_error",
+ "22027": "trim_error",
+ "22024": "unterminated_c_string",
+ "2200F": "zero_length_character_string",
+ "22P01": "floating_point_exception",
+ "22P02": "invalid_text_representation",
+ "22P03": "invalid_binary_representation",
+ "22P04": "bad_copy_file_format",
+ "22P05": "untranslatable_character",
+ "2200L": "not_an_xml_document",
+ "2200M": "invalid_xml_document",
+ "2200N": "invalid_xml_content",
+ "2200S": "invalid_xml_comment",
+ "2200T": "invalid_xml_processing_instruction",
+ // Class 23 - Integrity Constraint Violation
+ "23000": "integrity_constraint_violation",
+ "23001": "restrict_violation",
+ "23502": "not_null_violation",
+ "23503": "foreign_key_violation",
+ "23505": "unique_violation",
+ "23514": "check_violation",
+ "23P01": "exclusion_violation",
+ // Class 24 - Invalid Cursor State
+ "24000": "invalid_cursor_state",
+ // Class 25 - Invalid Transaction State
+ "25000": "invalid_transaction_state",
+ "25001": "active_sql_transaction",
+ "25002": "branch_transaction_already_active",
+ "25008": "held_cursor_requires_same_isolation_level",
+ "25003": "inappropriate_access_mode_for_branch_transaction",
+ "25004": "inappropriate_isolation_level_for_branch_transaction",
+ "25005": "no_active_sql_transaction_for_branch_transaction",
+ "25006": "read_only_sql_transaction",
+ "25007": "schema_and_data_statement_mixing_not_supported",
+ "25P01": "no_active_sql_transaction",
+ "25P02": "in_failed_sql_transaction",
+ // Class 26 - Invalid SQL Statement Name
+ "26000": "invalid_sql_statement_name",
+ // Class 27 - Triggered Data Change Violation
+ "27000": "triggered_data_change_violation",
+ // Class 28 - Invalid Authorization Specification
+ "28000": "invalid_authorization_specification",
+ "28P01": "invalid_password",
+ // Class 2B - Dependent Privilege Descriptors Still Exist
+ "2B000": "dependent_privilege_descriptors_still_exist",
+ "2BP01": "dependent_objects_still_exist",
+ // Class 2D - Invalid Transaction Termination
+ "2D000": "invalid_transaction_termination",
+ // Class 2F - SQL Routine Exception
+ "2F000": "sql_routine_exception",
+ "2F005": "function_executed_no_return_statement",
+ "2F002": "modifying_sql_data_not_permitted",
+ "2F003": "prohibited_sql_statement_attempted",
+ "2F004": "reading_sql_data_not_permitted",
+ // Class 34 - Invalid Cursor Name
+ "34000": "invalid_cursor_name",
+ // Class 38 - External Routine Exception
+ "38000": "external_routine_exception",
+ "38001": "containing_sql_not_permitted",
+ "38002": "modifying_sql_data_not_permitted",
+ "38003": "prohibited_sql_statement_attempted",
+ "38004": "reading_sql_data_not_permitted",
+ // Class 39 - External Routine Invocation Exception
+ "39000": "external_routine_invocation_exception",
+ "39001": "invalid_sqlstate_returned",
+ "39004": "null_value_not_allowed",
+ "39P01": "trigger_protocol_violated",
+ "39P02": "srf_protocol_violated",
+ // Class 3B - Savepoint Exception
+ "3B000": "savepoint_exception",
+ "3B001": "invalid_savepoint_specification",
+ // Class 3D - Invalid Catalog Name
+ "3D000": "invalid_catalog_name",
+ // Class 3F - Invalid Schema Name
+ "3F000": "invalid_schema_name",
+ // Class 40 - Transaction Rollback
+ "40000": "transaction_rollback",
+ "40002": "transaction_integrity_constraint_violation",
+ "40001": "serialization_failure",
+ "40003": "statement_completion_unknown",
+ "40P01": "deadlock_detected",
+ // Class 42 - Syntax Error or Access Rule Violation
+ "42000": "syntax_error_or_access_rule_violation",
+ "42601": "syntax_error",
+ "42501": "insufficient_privilege",
+ "42846": "cannot_coerce",
+ "42803": "grouping_error",
+ "42P20": "windowing_error",
+ "42P19": "invalid_recursion",
+ "42830": "invalid_foreign_key",
+ "42602": "invalid_name",
+ "42622": "name_too_long",
+ "42939": "reserved_name",
+ "42804": "datatype_mismatch",
+ "42P18": "indeterminate_datatype",
+ "42P21": "collation_mismatch",
+ "42P22": "indeterminate_collation",
+ "42809": "wrong_object_type",
+ "42703": "undefined_column",
+ "42883": "undefined_function",
+ "42P01": "undefined_table",
+ "42P02": "undefined_parameter",
+ "42704": "undefined_object",
+ "42701": "duplicate_column",
+ "42P03": "duplicate_cursor",
+ "42P04": "duplicate_database",
+ "42723": "duplicate_function",
+ "42P05": "duplicate_prepared_statement",
+ "42P06": "duplicate_schema",
+ "42P07": "duplicate_table",
+ "42712": "duplicate_alias",
+ "42710": "duplicate_object",
+ "42702": "ambiguous_column",
+ "42725": "ambiguous_function",
+ "42P08": "ambiguous_parameter",
+ "42P09": "ambiguous_alias",
+ "42P10": "invalid_column_reference",
+ "42611": "invalid_column_definition",
+ "42P11": "invalid_cursor_definition",
+ "42P12": "invalid_database_definition",
+ "42P13": "invalid_function_definition",
+ "42P14": "invalid_prepared_statement_definition",
+ "42P15": "invalid_schema_definition",
+ "42P16": "invalid_table_definition",
+ "42P17": "invalid_object_definition",
+ // Class 44 - WITH CHECK OPTION Violation
+ "44000": "with_check_option_violation",
+ // Class 53 - Insufficient Resources
+ "53000": "insufficient_resources",
+ "53100": "disk_full",
+ "53200": "out_of_memory",
+ "53300": "too_many_connections",
+ "53400": "configuration_limit_exceeded",
+ // Class 54 - Program Limit Exceeded
+ "54000": "program_limit_exceeded",
+ "54001": "statement_too_complex",
+ "54011": "too_many_columns",
+ "54023": "too_many_arguments",
+ // Class 55 - Object Not In Prerequisite State
+ "55000": "object_not_in_prerequisite_state",
+ "55006": "object_in_use",
+ "55P02": "cant_change_runtime_param",
+ "55P03": "lock_not_available",
+ // Class 57 - Operator Intervention
+ "57000": "operator_intervention",
+ "57014": "query_canceled",
+ "57P01": "admin_shutdown",
+ "57P02": "crash_shutdown",
+ "57P03": "cannot_connect_now",
+ "57P04": "database_dropped",
+ // Class 58 - System Error (errors external to PostgreSQL itself)
+ "58000": "system_error",
+ "58030": "io_error",
+ "58P01": "undefined_file",
+ "58P02": "duplicate_file",
+ // Class F0 - Configuration File Error
+ "F0000": "config_file_error",
+ "F0001": "lock_file_exists",
+ // Class HV - Foreign Data Wrapper Error (SQL/MED)
+ "HV000": "fdw_error",
+ "HV005": "fdw_column_name_not_found",
+ "HV002": "fdw_dynamic_parameter_value_needed",
+ "HV010": "fdw_function_sequence_error",
+ "HV021": "fdw_inconsistent_descriptor_information",
+ "HV024": "fdw_invalid_attribute_value",
+ "HV007": "fdw_invalid_column_name",
+ "HV008": "fdw_invalid_column_number",
+ "HV004": "fdw_invalid_data_type",
+ "HV006": "fdw_invalid_data_type_descriptors",
+ "HV091": "fdw_invalid_descriptor_field_identifier",
+ "HV00B": "fdw_invalid_handle",
+ "HV00C": "fdw_invalid_option_index",
+ "HV00D": "fdw_invalid_option_name",
+ "HV090": "fdw_invalid_string_length_or_buffer_length",
+ "HV00A": "fdw_invalid_string_format",
+ "HV009": "fdw_invalid_use_of_null_pointer",
+ "HV014": "fdw_too_many_handles",
+ "HV001": "fdw_out_of_memory",
+ "HV00P": "fdw_no_schemas",
+ "HV00J": "fdw_option_name_not_found",
+ "HV00K": "fdw_reply_handle",
+ "HV00Q": "fdw_schema_not_found",
+ "HV00R": "fdw_table_not_found",
+ "HV00L": "fdw_unable_to_create_execution",
+ "HV00M": "fdw_unable_to_create_reply",
+ "HV00N": "fdw_unable_to_establish_connection",
+ // Class P0 - PL/pgSQL Error
+ "P0000": "plpgsql_error",
+ "P0001": "raise_exception",
+ "P0002": "no_data_found",
+ "P0003": "too_many_rows",
+ // Class XX - Internal Error
+ "XX000": "internal_error",
+ "XX001": "data_corrupted",
+ "XX002": "index_corrupted",
+}
+
+func parseError(r *readBuf) *Error {
+ err := new(Error)
+ for t := r.byte(); t != 0; t = r.byte() {
+ msg := r.string()
+ switch t {
+ case 'S':
+ err.Severity = msg
+ case 'C':
+ err.Code = ErrorCode(msg)
+ case 'M':
+ err.Message = msg
+ case 'D':
+ err.Detail = msg
+ case 'H':
+ err.Hint = msg
+ case 'P':
+ err.Position = msg
+ case 'p':
+ err.InternalPosition = msg
+ case 'q':
+ err.InternalQuery = msg
+ case 'W':
+ err.Where = msg
+ case 's':
+ err.Schema = msg
+ case 't':
+ err.Table = msg
+ case 'c':
+ err.Column = msg
+ case 'd':
+ err.DataTypeName = msg
+ case 'n':
+ err.Constraint = msg
+ case 'F':
+ err.File = msg
+ case 'L':
+ err.Line = msg
+ case 'R':
+ err.Routine = msg
+ }
+ }
+ return err
+}
+
+// Fatal returns true if the Error Severity is fatal.
+func (err *Error) Fatal() bool {
+ return err.Severity == Efatal
+}
+
+// Get implements the legacy PGError interface. New code should use the fields
+// of the Error struct directly.
+func (err *Error) Get(k byte) (v string) {
+ switch k {
+ case 'S':
+ return err.Severity
+ case 'C':
+ return string(err.Code)
+ case 'M':
+ return err.Message
+ case 'D':
+ return err.Detail
+ case 'H':
+ return err.Hint
+ case 'P':
+ return err.Position
+ case 'p':
+ return err.InternalPosition
+ case 'q':
+ return err.InternalQuery
+ case 'W':
+ return err.Where
+ case 's':
+ return err.Schema
+ case 't':
+ return err.Table
+ case 'c':
+ return err.Column
+ case 'd':
+ return err.DataTypeName
+ case 'n':
+ return err.Constraint
+ case 'F':
+ return err.File
+ case 'L':
+ return err.Line
+ case 'R':
+ return err.Routine
+ }
+ return ""
+}
+
+func (err Error) Error() string {
+ return "pq: " + err.Message
+}
+
+// PGError is an interface used by previous versions of pq. It is provided
+// only to support legacy code. New code should use the Error type.
+type PGError interface {
+ Error() string
+ Fatal() bool
+ Get(k byte) (v string)
+}
+
+func errorf(s string, args ...interface{}) {
+ panic(fmt.Errorf("pq: %s", fmt.Sprintf(s, args...)))
+}
+
+func (c *conn) errRecover(err *error) {
+ e := recover()
+ switch v := e.(type) {
+ case nil:
+ // Do nothing
+ case runtime.Error:
+ c.bad = true
+ panic(v)
+ case *Error:
+ if v.Fatal() {
+ *err = driver.ErrBadConn
+ } else {
+ *err = v
+ }
+ case *net.OpError:
+ *err = driver.ErrBadConn
+ case error:
+ if v == io.EOF || v.(error).Error() == "remote error: handshake failure" {
+ *err = driver.ErrBadConn
+ } else {
+ *err = v
+ }
+
+ default:
+ c.bad = true
+ panic(fmt.Sprintf("unknown error: %#v", e))
+ }
+
+ // Any time we return ErrBadConn, we need to remember it since *Tx doesn't
+ // mark the connection bad in database/sql.
+ if *err == driver.ErrBadConn {
+ c.bad = true
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/hstore/hstore.go b/Godeps/_workspace/src/github.com/lib/pq/hstore/hstore.go
new file mode 100644
index 0000000..72d5abf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/hstore/hstore.go
@@ -0,0 +1,118 @@
+package hstore
+
+import (
+ "database/sql"
+ "database/sql/driver"
+ "strings"
+)
+
+// A wrapper for transferring Hstore values back and forth easily.
+type Hstore struct {
+ Map map[string]sql.NullString
+}
+
+// escapes and quotes hstore keys/values
+// s should be a sql.NullString or string
+func hQuote(s interface{}) string {
+ var str string
+ switch v := s.(type) {
+ case sql.NullString:
+ if !v.Valid {
+ return "NULL"
+ }
+ str = v.String
+ case string:
+ str = v
+ default:
+ panic("not a string or sql.NullString")
+ }
+
+ str = strings.Replace(str, "\\", "\\\\", -1)
+ return `"` + strings.Replace(str, "\"", "\\\"", -1) + `"`
+}
+
+// Scan implements the Scanner interface.
+//
+// Note h.Map is reallocated before the scan to clear existing values. If the
+// hstore column's database value is NULL, then h.Map is set to nil instead.
+func (h *Hstore) Scan(value interface{}) error {
+ if value == nil {
+ h.Map = nil
+ return nil
+ }
+ h.Map = make(map[string]sql.NullString)
+ var b byte
+ pair := [][]byte{{}, {}}
+ pi := 0
+ inQuote := false
+ didQuote := false
+ sawSlash := false
+ bindex := 0
+ for bindex, b = range value.([]byte) {
+ if sawSlash {
+ pair[pi] = append(pair[pi], b)
+ sawSlash = false
+ continue
+ }
+
+ switch b {
+ case '\\':
+ sawSlash = true
+ continue
+ case '"':
+ inQuote = !inQuote
+ if !didQuote {
+ didQuote = true
+ }
+ continue
+ default:
+ if !inQuote {
+ switch b {
+ case ' ', '\t', '\n', '\r':
+ continue
+ case '=':
+ continue
+ case '>':
+ pi = 1
+ didQuote = false
+ continue
+ case ',':
+ s := string(pair[1])
+ if !didQuote && len(s) == 4 && strings.ToLower(s) == "null" {
+ h.Map[string(pair[0])] = sql.NullString{String: "", Valid: false}
+ } else {
+ h.Map[string(pair[0])] = sql.NullString{String: string(pair[1]), Valid: true}
+ }
+ pair[0] = []byte{}
+ pair[1] = []byte{}
+ pi = 0
+ continue
+ }
+ }
+ }
+ pair[pi] = append(pair[pi], b)
+ }
+ if bindex > 0 {
+ s := string(pair[1])
+ if !didQuote && len(s) == 4 && strings.ToLower(s) == "null" {
+ h.Map[string(pair[0])] = sql.NullString{String: "", Valid: false}
+ } else {
+ h.Map[string(pair[0])] = sql.NullString{String: string(pair[1]), Valid: true}
+ }
+ }
+ return nil
+}
+
+// Value implements the driver Valuer interface. Note if h.Map is nil, the
+// database column value will be set to NULL.
+func (h Hstore) Value() (driver.Value, error) {
+ if h.Map == nil {
+ return nil, nil
+ }
+ parts := []string{}
+ for key, val := range h.Map {
+ thispart := hQuote(key) + "=>" + hQuote(val)
+ parts = append(parts, thispart)
+ }
+ return []byte(strings.Join(parts, ",")), nil
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/hstore/hstore_test.go b/Godeps/_workspace/src/github.com/lib/pq/hstore/hstore_test.go
new file mode 100644
index 0000000..8e61e69
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/hstore/hstore_test.go
@@ -0,0 +1,147 @@
+package hstore
+
+import (
+ "database/sql"
+ _ "github.com/lib/pq"
+ "os"
+ "testing"
+)
+
+type Fatalistic interface {
+ Fatal(args ...interface{})
+}
+
+func openTestConn(t Fatalistic) *sql.DB {
+ datname := os.Getenv("PGDATABASE")
+ sslmode := os.Getenv("PGSSLMODE")
+
+ if datname == "" {
+ os.Setenv("PGDATABASE", "pqgotest")
+ }
+
+ if sslmode == "" {
+ os.Setenv("PGSSLMODE", "disable")
+ }
+
+ conn, err := sql.Open("postgres", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return conn
+}
+
+func TestHstore(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ // quitely create hstore if it doesn't exist
+ _, err := db.Exec("CREATE EXTENSION IF NOT EXISTS hstore")
+ if err != nil {
+ t.Skipf("Skipping hstore tests - hstore extension create failed: %s", err.Error())
+ }
+
+ hs := Hstore{}
+
+ // test for null-valued hstores
+ err = db.QueryRow("SELECT NULL::hstore").Scan(&hs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if hs.Map != nil {
+ t.Fatalf("expected null map")
+ }
+
+ err = db.QueryRow("SELECT $1::hstore", hs).Scan(&hs)
+ if err != nil {
+ t.Fatalf("re-query null map failed: %s", err.Error())
+ }
+ if hs.Map != nil {
+ t.Fatalf("expected null map")
+ }
+
+ // test for empty hstores
+ err = db.QueryRow("SELECT ''::hstore").Scan(&hs)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if hs.Map == nil {
+ t.Fatalf("expected empty map, got null map")
+ }
+ if len(hs.Map) != 0 {
+ t.Fatalf("expected empty map, got len(map)=%d", len(hs.Map))
+ }
+
+ err = db.QueryRow("SELECT $1::hstore", hs).Scan(&hs)
+ if err != nil {
+ t.Fatalf("re-query empty map failed: %s", err.Error())
+ }
+ if hs.Map == nil {
+ t.Fatalf("expected empty map, got null map")
+ }
+ if len(hs.Map) != 0 {
+ t.Fatalf("expected empty map, got len(map)=%d", len(hs.Map))
+ }
+
+ // a few example maps to test out
+ hsOnePair := Hstore{
+ Map: map[string]sql.NullString{
+ "key1": {"value1", true},
+ },
+ }
+
+ hsThreePairs := Hstore{
+ Map: map[string]sql.NullString{
+ "key1": {"value1", true},
+ "key2": {"value2", true},
+ "key3": {"value3", true},
+ },
+ }
+
+ hsSmorgasbord := Hstore{
+ Map: map[string]sql.NullString{
+ "nullstring": {"NULL", true},
+ "actuallynull": {"", false},
+ "NULL": {"NULL string key", true},
+ "withbracket": {"value>42", true},
+ "withequal": {"value=42", true},
+ `"withquotes1"`: {`this "should" be fine`, true},
+ `"withquotes"2"`: {`this "should\" also be fine`, true},
+ "embedded1": {"value1=>x1", true},
+ "embedded2": {`"value2"=>x2`, true},
+ "withnewlines": {"\n\nvalue\t=>2", true},
+ "<>": {`this, "should,\" also, => be fine`, true},
+ },
+ }
+
+ // test encoding in query params, then decoding during Scan
+ testBidirectional := func(h Hstore) {
+ err = db.QueryRow("SELECT $1::hstore", h).Scan(&hs)
+ if err != nil {
+ t.Fatalf("re-query %d-pair map failed: %s", len(h.Map), err.Error())
+ }
+ if hs.Map == nil {
+ t.Fatalf("expected %d-pair map, got null map", len(h.Map))
+ }
+ if len(hs.Map) != len(h.Map) {
+ t.Fatalf("expected %d-pair map, got len(map)=%d", len(h.Map), len(hs.Map))
+ }
+
+ for key, val := range hs.Map {
+ otherval, found := h.Map[key]
+ if !found {
+ t.Fatalf(" key '%v' not found in %d-pair map", key, len(h.Map))
+ }
+ if otherval.Valid != val.Valid {
+ t.Fatalf(" value %v <> %v in %d-pair map", otherval, val, len(h.Map))
+ }
+ if otherval.String != val.String {
+ t.Fatalf(" value '%v' <> '%v' in %d-pair map", otherval.String, val.String, len(h.Map))
+ }
+ }
+ }
+
+ testBidirectional(hsOnePair)
+ testBidirectional(hsThreePairs)
+ testBidirectional(hsSmorgasbord)
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/listen_example/doc.go b/Godeps/_workspace/src/github.com/lib/pq/listen_example/doc.go
new file mode 100644
index 0000000..34496f4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/listen_example/doc.go
@@ -0,0 +1,102 @@
+/*
+
+Below you will find a self-contained Go program which uses the LISTEN / NOTIFY
+mechanism to avoid polling the database while waiting for more work to arrive.
+
+ //
+ // You can see the program in action by defining a function similar to
+ // the following:
+ //
+ // CREATE OR REPLACE FUNCTION public.get_work()
+ // RETURNS bigint
+ // LANGUAGE sql
+ // AS $$
+ // SELECT CASE WHEN random() >= 0.2 THEN int8 '1' END
+ // $$
+ // ;
+
+ package main
+
+ import (
+ "github.com/lib/pq"
+
+ "database/sql"
+ "fmt"
+ "time"
+ )
+
+ func doWork(db *sql.DB, work int64) {
+ // work here
+ }
+
+ func getWork(db *sql.DB) {
+ for {
+ // get work from the database here
+ var work sql.NullInt64
+ err := db.QueryRow("SELECT get_work()").Scan(&work)
+ if err != nil {
+ fmt.Println("call to get_work() failed: ", err)
+ time.Sleep(10 * time.Second)
+ continue
+ }
+ if !work.Valid {
+ // no more work to do
+ fmt.Println("ran out of work")
+ return
+ }
+
+ fmt.Println("starting work on ", work.Int64)
+ go doWork(db, work.Int64)
+ }
+ }
+
+ func waitForNotification(l *pq.Listener) {
+ for {
+ select {
+ case <-l.Notify:
+ fmt.Println("received notification, new work available")
+ return
+ case <-time.After(90 * time.Second):
+ go func() {
+ l.Ping()
+ }()
+ // Check if there's more work available, just in case it takes
+ // a while for the Listener to notice connection loss and
+ // reconnect.
+ fmt.Println("received no work for 90 seconds, checking for new work")
+ return
+ }
+ }
+ }
+
+ func main() {
+ var conninfo string = ""
+
+ db, err := sql.Open("postgres", conninfo)
+ if err != nil {
+ panic(err)
+ }
+
+ reportProblem := func(ev pq.ListenerEventType, err error) {
+ if err != nil {
+ fmt.Println(err.Error())
+ }
+ }
+
+ listener := pq.NewListener(conninfo, 10 * time.Second, time.Minute, reportProblem)
+ err = listener.Listen("getwork")
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("entering main loop")
+ for {
+ // process all available work before waiting for notifications
+ getWork(db)
+ waitForNotification(listener)
+ }
+ }
+
+
+*/
+package listen_example
diff --git a/Godeps/_workspace/src/github.com/lib/pq/notify.go b/Godeps/_workspace/src/github.com/lib/pq/notify.go
new file mode 100644
index 0000000..e3b08d5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/notify.go
@@ -0,0 +1,752 @@
+package pq
+
+// Package pq is a pure Go Postgres driver for the database/sql package.
+// This module contains support for Postgres LISTEN/NOTIFY.
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+// Notification represents a single notification from the database.
+type Notification struct {
+ // Process ID (PID) of the notifying postgres backend.
+ BePid int
+ // Name of the channel the notification was sent on.
+ Channel string
+ // Payload, or the empty string if unspecified.
+ Extra string
+}
+
+func recvNotification(r *readBuf) *Notification {
+ bePid := r.int32()
+ channel := r.string()
+ extra := r.string()
+
+ return &Notification{bePid, channel, extra}
+}
+
+const (
+ connStateIdle int32 = iota
+ connStateExpectResponse
+ connStateExpectReadyForQuery
+)
+
+type message struct {
+ typ byte
+ err error
+}
+
+var errListenerConnClosed = errors.New("pq: ListenerConn has been closed")
+
+// ListenerConn is a low-level interface for waiting for notifications. You
+// should use Listener instead.
+type ListenerConn struct {
+ // guards cn and err
+ connectionLock sync.Mutex
+ cn *conn
+ err error
+
+ connState int32
+
+ // the sending goroutine will be holding this lock
+ senderLock sync.Mutex
+
+ notificationChan chan<- *Notification
+
+ replyChan chan message
+}
+
+// Creates a new ListenerConn. Use NewListener instead.
+func NewListenerConn(name string, notificationChan chan<- *Notification) (*ListenerConn, error) {
+ cn, err := Open(name)
+ if err != nil {
+ return nil, err
+ }
+
+ l := &ListenerConn{
+ cn: cn.(*conn),
+ notificationChan: notificationChan,
+ connState: connStateIdle,
+ replyChan: make(chan message, 2),
+ }
+
+ go l.listenerConnMain()
+
+ return l, nil
+}
+
+// We can only allow one goroutine at a time to be running a query on the
+// connection for various reasons, so the goroutine sending on the connection
+// must be holding senderLock.
+//
+// Returns an error if an unrecoverable error has occurred and the ListenerConn
+// should be abandoned.
+func (l *ListenerConn) acquireSenderLock() error {
+ l.connectionLock.Lock()
+ defer l.connectionLock.Unlock()
+ if l.err != nil {
+ return l.err
+ }
+ l.senderLock.Lock()
+ return nil
+}
+
+func (l *ListenerConn) releaseSenderLock() {
+ l.senderLock.Unlock()
+}
+
+// setState advances the protocol state to newState. Returns false if moving
+// to that state from the current state is not allowed.
+func (l *ListenerConn) setState(newState int32) bool {
+ var expectedState int32
+
+ switch newState {
+ case connStateIdle:
+ expectedState = connStateExpectReadyForQuery
+ case connStateExpectResponse:
+ expectedState = connStateIdle
+ case connStateExpectReadyForQuery:
+ expectedState = connStateExpectResponse
+ default:
+ panic(fmt.Sprintf("unexpected listenerConnState %d", newState))
+ }
+
+ return atomic.CompareAndSwapInt32(&l.connState, expectedState, newState)
+}
+
+// Main logic is here: receive messages from the postgres backend, forward
+// notifications and query replies and keep the internal state in sync with the
+// protocol state. Returns when the connection has been lost, is about to go
+// away or should be discarded because we couldn't agree on the state with the
+// server backend.
+func (l *ListenerConn) listenerConnLoop() (err error) {
+ defer l.cn.errRecover(&err)
+
+ r := &readBuf{}
+ for {
+ t, err := l.cn.recvMessage(r)
+ if err != nil {
+ return err
+ }
+
+ switch t {
+ case 'A':
+ // recvNotification copies all the data so we don't need to worry
+ // about the scratch buffer being overwritten.
+ l.notificationChan <- recvNotification(r)
+
+ case 'E':
+ // We might receive an ErrorResponse even when not in a query; it
+ // is expected that the server will close the connection after
+ // that, but we should make sure that the error we display is the
+ // one from the stray ErrorResponse, not io.ErrUnexpectedEOF.
+ if !l.setState(connStateExpectReadyForQuery) {
+ return parseError(r)
+ }
+ l.replyChan <- message{t, parseError(r)}
+
+ case 'C', 'I':
+ if !l.setState(connStateExpectReadyForQuery) {
+ // protocol out of sync
+ return fmt.Errorf("unexpected CommandComplete")
+ }
+ // ExecSimpleQuery doesn't need to know about this message
+
+ case 'Z':
+ if !l.setState(connStateIdle) {
+ // protocol out of sync
+ return fmt.Errorf("unexpected ReadyForQuery")
+ }
+ l.replyChan <- message{t, nil}
+
+ case 'N', 'S':
+ // ignore
+ default:
+ return fmt.Errorf("unexpected message %q from server in listenerConnLoop", t)
+ }
+ }
+}
+
+// This is the main routine for the goroutine receiving on the database
+// connection. Most of the main logic is in listenerConnLoop.
+func (l *ListenerConn) listenerConnMain() {
+ err := l.listenerConnLoop()
+
+ // listenerConnLoop terminated; we're done, but we still have to clean up.
+ // Make sure nobody tries to start any new queries by making sure the err
+ // pointer is set. It is important that we do not overwrite its value; a
+ // connection could be closed by either this goroutine or one sending on
+ // the connection -- whoever closes the connection is assumed to have the
+ // more meaningful error message (as the other one will probably get
+ // net.errClosed), so that goroutine sets the error we expose while the
+ // other error is discarded. If the connection is lost while two
+ // goroutines are operating on the socket, it probably doesn't matter which
+ // error we expose so we don't try to do anything more complex.
+ l.connectionLock.Lock()
+ if l.err == nil {
+ l.err = err
+ }
+ l.cn.Close()
+ l.connectionLock.Unlock()
+
+ // There might be a query in-flight; make sure nobody's waiting for a
+ // response to it, since there's not going to be one.
+ close(l.replyChan)
+
+ // let the listener know we're done
+ close(l.notificationChan)
+
+ // this ListenerConn is done
+}
+
+// Send a LISTEN query to the server. See ExecSimpleQuery.
+func (l *ListenerConn) Listen(channel string) (bool, error) {
+ return l.ExecSimpleQuery("LISTEN " + QuoteIdentifier(channel))
+}
+
+// Send an UNLISTEN query to the server. See ExecSimpleQuery.
+func (l *ListenerConn) Unlisten(channel string) (bool, error) {
+ return l.ExecSimpleQuery("UNLISTEN " + QuoteIdentifier(channel))
+}
+
+// Send `UNLISTEN *` to the server. See ExecSimpleQuery.
+func (l *ListenerConn) UnlistenAll() (bool, error) {
+ return l.ExecSimpleQuery("UNLISTEN *")
+}
+
+// Ping the remote server to make sure it's alive. Non-nil error means the
+// connection has failed and should be abandoned.
+func (l *ListenerConn) Ping() error {
+ sent, err := l.ExecSimpleQuery("")
+ if !sent {
+ return err
+ }
+ if err != nil {
+ // shouldn't happen
+ panic(err)
+ }
+ return nil
+}
+
+// Attempt to send a query on the connection. Returns an error if sending the
+// query failed, and the caller should initiate closure of this connection.
+// The caller must be holding senderLock (see acquireSenderLock and
+// releaseSenderLock).
+func (l *ListenerConn) sendSimpleQuery(q string) (err error) {
+ defer l.cn.errRecover(&err)
+
+ // must set connection state before sending the query
+ if !l.setState(connStateExpectResponse) {
+ panic("two queries running at the same time")
+ }
+
+ // Can't use l.cn.writeBuf here because it uses the scratch buffer which
+ // might get overwritten by listenerConnLoop.
+ data := writeBuf([]byte("Q\x00\x00\x00\x00"))
+ b := &data
+ b.string(q)
+ l.cn.send(b)
+
+ return nil
+}
+
+// Execute a "simple query" (i.e. one with no bindable parameters) on the
+// connection. The possible return values are:
+// 1) "executed" is true; the query was executed to completion on the
+// database server. If the query failed, err will be set to the error
+// returned by the database, otherwise err will be nil.
+// 2) If "executed" is false, the query could not be executed on the remote
+// server. err will be non-nil.
+//
+// After a call to ExecSimpleQuery has returned an executed=false value, the
+// connection has either been closed or will be closed shortly thereafter, and
+// all subsequently executed queries will return an error.
+func (l *ListenerConn) ExecSimpleQuery(q string) (executed bool, err error) {
+ if err = l.acquireSenderLock(); err != nil {
+ return false, err
+ }
+ defer l.releaseSenderLock()
+
+ err = l.sendSimpleQuery(q)
+ if err != nil {
+ // We can't know what state the protocol is in, so we need to abandon
+ // this connection.
+ l.connectionLock.Lock()
+ defer l.connectionLock.Unlock()
+ // Set the error pointer if it hasn't been set already; see
+ // listenerConnMain.
+ if l.err == nil {
+ l.err = err
+ }
+ l.cn.Close()
+ return false, err
+ }
+
+ // now we just wait for a reply..
+ for {
+ m, ok := <-l.replyChan
+ if !ok {
+ // We lost the connection to server, don't bother waiting for a
+ // a response.
+ return false, io.EOF
+ }
+ switch m.typ {
+ case 'Z':
+ // sanity check
+ if m.err != nil {
+ panic("m.err != nil")
+ }
+ // done; err might or might not be set
+ return true, err
+
+ case 'E':
+ // sanity check
+ if m.err == nil {
+ panic("m.err == nil")
+ }
+ // server responded with an error; ReadyForQuery to follow
+ err = m.err
+
+ default:
+ return false, fmt.Errorf("unknown response for simple query: %q", m.typ)
+ }
+ }
+}
+
+func (l *ListenerConn) Close() error {
+ l.connectionLock.Lock()
+ defer l.connectionLock.Unlock()
+ if l.err != nil {
+ return errListenerConnClosed
+ }
+ l.err = errListenerConnClosed
+ return l.cn.Close()
+}
+
+// Err() returns the reason the connection was closed. It is not safe to call
+// this function until l.Notify has been closed.
+func (l *ListenerConn) Err() error {
+ return l.err
+}
+
+var errListenerClosed = errors.New("pq: Listener has been closed")
+
+var ErrChannelAlreadyOpen = errors.New("pq: channel is already open")
+var ErrChannelNotOpen = errors.New("pq: channel is not open")
+
+type ListenerEventType int
+
+const (
+ // Emitted only when the database connection has been initially
+ // initialized. err will always be nil.
+ ListenerEventConnected ListenerEventType = iota
+
+ // Emitted after a database connection has been lost, either because of an
+ // error or because Close has been called. err will be set to the reason
+ // the database connection was lost.
+ ListenerEventDisconnected
+
+ // Emitted after a database connection has been re-established after
+ // connection loss. err will always be nil. After this event has been
+ // emitted, a nil pq.Notification is sent on the Listener.Notify channel.
+ ListenerEventReconnected
+
+ // Emitted after a connection to the database was attempted, but failed.
+ // err will be set to an error describing why the connection attempt did
+ // not succeed.
+ ListenerEventConnectionAttemptFailed
+)
+
+type EventCallbackType func(event ListenerEventType, err error)
+
+// Listener provides an interface for listening to notifications from a
+// PostgreSQL database. For general usage information, see section
+// "Notifications".
+//
+// Listener can safely be used from concurrently running goroutines.
+type Listener struct {
+ // Channel for receiving notifications from the database. In some cases a
+ // nil value will be sent. See section "Notifications" above.
+ Notify chan *Notification
+
+ name string
+ minReconnectInterval time.Duration
+ maxReconnectInterval time.Duration
+ eventCallback EventCallbackType
+
+ lock sync.Mutex
+ isClosed bool
+ reconnectCond *sync.Cond
+ cn *ListenerConn
+ connNotificationChan <-chan *Notification
+ channels map[string]struct{}
+}
+
+// NewListener creates a new database connection dedicated to LISTEN / NOTIFY.
+//
+// name should be set to a connection string to be used to establish the
+// database connection (see section "Connection String Parameters" above).
+//
+// minReconnectInterval controls the duration to wait before trying to
+// re-establish the database connection after connection loss. After each
+// consecutive failure this interval is doubled, until maxReconnectInterval is
+// reached. Successfully completing the connection establishment procedure
+// resets the interval back to minReconnectInterval.
+//
+// The last parameter eventCallback can be set to a function which will be
+// called by the Listener when the state of the underlying database connection
+// changes. This callback will be called by the goroutine which dispatches the
+// notifications over the Notify channel, so you should try to avoid doing
+// potentially time-consuming operations from the callback.
+func NewListener(name string,
+ minReconnectInterval time.Duration,
+ maxReconnectInterval time.Duration,
+ eventCallback EventCallbackType) *Listener {
+ l := &Listener{
+ name: name,
+ minReconnectInterval: minReconnectInterval,
+ maxReconnectInterval: maxReconnectInterval,
+ eventCallback: eventCallback,
+
+ channels: make(map[string]struct{}),
+
+ Notify: make(chan *Notification, 32),
+ }
+ l.reconnectCond = sync.NewCond(&l.lock)
+
+ go l.listenerMain()
+
+ return l
+}
+
+// Returns the notification channel for this listener. This is the same
+// channel as Notify, and will not be recreated during the life time of the
+// Listener.
+func (l *Listener) NotificationChannel() <-chan *Notification {
+ return l.Notify
+}
+
+// Listen starts listening for notifications on a channel. Calls to this
+// function will block until an acknowledgement has been received from the
+// server. Note that Listener automatically re-establishes the connection
+// after connection loss, so this function may block indefinitely if the
+// connection can not be re-established.
+//
+// Listen will only fail in three conditions:
+// 1) The channel is already open. The returned error will be
+// ErrChannelAlreadyOpen.
+// 2) The query was executed on the remote server, but PostgreSQL returned an
+// error message in response to the query. The returned error will be a
+// pq.Error containing the information the server supplied.
+// 3) Close is called on the Listener before the request could be completed.
+//
+// The channel name is case-sensitive.
+func (l *Listener) Listen(channel string) error {
+ l.lock.Lock()
+ defer l.lock.Unlock()
+
+ if l.isClosed {
+ return errListenerClosed
+ }
+
+ // The server allows you to issue a LISTEN on a channel which is already
+ // open, but it seems useful to be able to detect this case to spot for
+ // mistakes in application logic. If the application genuinely does't
+ // care, it can check the exported error and ignore it.
+ _, exists := l.channels[channel]
+ if exists {
+ return ErrChannelAlreadyOpen
+ }
+
+ if l.cn != nil {
+ // If gotResponse is true but error is set, the query was executed on
+ // the remote server, but resulted in an error. This should be
+ // relatively rare, so it's fine if we just pass the error to our
+ // caller. However, if gotResponse is false, we could not complete the
+ // query on the remote server and our underlying connection is about
+ // to go away, so we only add relname to l.channels, and wait for
+ // resync() to take care of the rest.
+ gotResponse, err := l.cn.Listen(channel)
+ if gotResponse && err != nil {
+ return err
+ }
+ }
+
+ l.channels[channel] = struct{}{}
+ for l.cn == nil {
+ l.reconnectCond.Wait()
+ // we let go of the mutex for a while
+ if l.isClosed {
+ return errListenerClosed
+ }
+ }
+
+ return nil
+}
+
+// Unlisten removes a channel from the Listener's channel list. Returns
+// ErrChannelNotOpen if the Listener is not listening on the specified channel.
+// Returns immediately with no error if there is no connection. Note that you
+// might still get notifications for this channel even after Unlisten has
+// returned.
+//
+// The channel name is case-sensitive.
+func (l *Listener) Unlisten(channel string) error {
+ l.lock.Lock()
+ defer l.lock.Unlock()
+
+ if l.isClosed {
+ return errListenerClosed
+ }
+
+ // Similarly to LISTEN, this is not an error in Postgres, but it seems
+ // useful to distinguish from the normal conditions.
+ _, exists := l.channels[channel]
+ if !exists {
+ return ErrChannelNotOpen
+ }
+
+ if l.cn != nil {
+ // Similarly to Listen (see comment in that function), the caller
+ // should only be bothered with an error if it came from the backend as
+ // a response to our query.
+ gotResponse, err := l.cn.Unlisten(channel)
+ if gotResponse && err != nil {
+ return err
+ }
+ }
+
+ // Don't bother waiting for resync if there's no connection.
+ delete(l.channels, channel)
+ return nil
+}
+
+// UnlistenAll removes all channels from the Listener's channel list. Returns
+// immediately with no error if there is no connection. Note that you might
+// still get notifications for any of the deleted channels even after
+// UnlistenAll has returned.
+func (l *Listener) UnlistenAll() error {
+ l.lock.Lock()
+ defer l.lock.Unlock()
+
+ if l.isClosed {
+ return errListenerClosed
+ }
+
+ if l.cn != nil {
+ // Similarly to Listen (see comment in that function), the caller
+ // should only be bothered with an error if it came from the backend as
+ // a response to our query.
+ gotResponse, err := l.cn.UnlistenAll()
+ if gotResponse && err != nil {
+ return err
+ }
+ }
+
+ // Don't bother waiting for resync if there's no connection.
+ l.channels = make(map[string]struct{})
+ return nil
+}
+
+// Ping the remote server to make sure it's alive. Non-nil return value means
+// that there is no active connection.
+func (l *Listener) Ping() error {
+ l.lock.Lock()
+ defer l.lock.Unlock()
+
+ if l.isClosed {
+ return errListenerClosed
+ }
+ if l.cn == nil {
+ return errors.New("no connection")
+ }
+
+ return l.cn.Ping()
+}
+
+// Clean up after losing the server connection. Returns l.cn.Err(), which
+// should have the reason the connection was lost.
+func (l *Listener) disconnectCleanup() error {
+ l.lock.Lock()
+ defer l.lock.Unlock()
+
+ // sanity check; can't look at Err() until the channel has been closed
+ select {
+ case _, ok := <-l.connNotificationChan:
+ if ok {
+ panic("connNotificationChan not closed")
+ }
+ default:
+ panic("connNotificationChan not closed")
+ }
+
+ err := l.cn.Err()
+ l.cn.Close()
+ l.cn = nil
+ return err
+}
+
+// Synchronize the list of channels we want to be listening on with the server
+// after the connection has been established.
+func (l *Listener) resync(cn *ListenerConn, notificationChan <-chan *Notification) error {
+ doneChan := make(chan error)
+ go func() {
+ for channel := range l.channels {
+ // If we got a response, return that error to our caller as it's
+ // going to be more descriptive than cn.Err().
+ gotResponse, err := cn.Listen(channel)
+ if gotResponse && err != nil {
+ doneChan <- err
+ return
+ }
+
+ // If we couldn't reach the server, wait for notificationChan to
+ // close and then return the error message from the connection, as
+ // per ListenerConn's interface.
+ if err != nil {
+ for _ = range notificationChan {
+ }
+ doneChan <- cn.Err()
+ return
+ }
+ }
+ doneChan <- nil
+ }()
+
+ // Ignore notifications while synchronization is going on to avoid
+ // deadlocks. We have to send a nil notification over Notify anyway as
+ // we can't possibly know which notifications (if any) were lost while
+ // the connection was down, so there's no reason to try and process
+ // these messages at all.
+ for {
+ select {
+ case _, ok := <-notificationChan:
+ if !ok {
+ notificationChan = nil
+ }
+
+ case err := <-doneChan:
+ return err
+ }
+ }
+}
+
+// caller should NOT be holding l.lock
+func (l *Listener) closed() bool {
+ l.lock.Lock()
+ defer l.lock.Unlock()
+
+ return l.isClosed
+}
+
+func (l *Listener) connect() error {
+ notificationChan := make(chan *Notification, 32)
+ cn, err := NewListenerConn(l.name, notificationChan)
+ if err != nil {
+ return err
+ }
+
+ l.lock.Lock()
+ defer l.lock.Unlock()
+
+ err = l.resync(cn, notificationChan)
+ if err != nil {
+ cn.Close()
+ return err
+ }
+
+ l.cn = cn
+ l.connNotificationChan = notificationChan
+ l.reconnectCond.Broadcast()
+
+ return nil
+}
+
+// Close disconnects the Listener from the database and shuts it down.
+// Subsequent calls to its methods will return an error. Close returns an
+// error if the connection has already been closed.
+func (l *Listener) Close() error {
+ l.lock.Lock()
+ defer l.lock.Unlock()
+
+ if l.isClosed {
+ return errListenerClosed
+ }
+
+ if l.cn != nil {
+ l.cn.Close()
+ }
+ l.isClosed = true
+
+ return nil
+}
+
+func (l *Listener) emitEvent(event ListenerEventType, err error) {
+ if l.eventCallback != nil {
+ l.eventCallback(event, err)
+ }
+}
+
+// Main logic here: maintain a connection to the server when possible, wait
+// for notifications and emit events.
+func (l *Listener) listenerConnLoop() {
+ var nextReconnect time.Time
+
+ reconnectInterval := l.minReconnectInterval
+ for {
+ for {
+ err := l.connect()
+ if err == nil {
+ break
+ }
+
+ if l.closed() {
+ return
+ }
+ l.emitEvent(ListenerEventConnectionAttemptFailed, err)
+
+ time.Sleep(reconnectInterval)
+ reconnectInterval *= 2
+ if reconnectInterval > l.maxReconnectInterval {
+ reconnectInterval = l.maxReconnectInterval
+ }
+ }
+
+ if nextReconnect.IsZero() {
+ l.emitEvent(ListenerEventConnected, nil)
+ } else {
+ l.emitEvent(ListenerEventReconnected, nil)
+ l.Notify <- nil
+ }
+
+ reconnectInterval = l.minReconnectInterval
+ nextReconnect = time.Now().Add(reconnectInterval)
+
+ for {
+ notification, ok := <-l.connNotificationChan
+ if !ok {
+ // lost connection, loop again
+ break
+ }
+ l.Notify <- notification
+ }
+
+ err := l.disconnectCleanup()
+ if l.closed() {
+ return
+ }
+ l.emitEvent(ListenerEventDisconnected, err)
+
+ time.Sleep(nextReconnect.Sub(time.Now()))
+ }
+}
+
+func (l *Listener) listenerMain() {
+ l.listenerConnLoop()
+ close(l.Notify)
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/notify_test.go b/Godeps/_workspace/src/github.com/lib/pq/notify_test.go
new file mode 100644
index 0000000..ae1208d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/notify_test.go
@@ -0,0 +1,502 @@
+package pq
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "testing"
+ "time"
+)
+
+var errNilNotification = errors.New("nil notification")
+
+func expectNotification(t *testing.T, ch <-chan *Notification, relname string, extra string) error {
+ select {
+ case n := <-ch:
+ if n == nil {
+ return errNilNotification
+ }
+ if n.Channel != relname || n.Extra != extra {
+ return fmt.Errorf("unexpected notification %v", n)
+ }
+ return nil
+ case <-time.After(1500 * time.Millisecond):
+ return fmt.Errorf("timeout")
+ }
+}
+
+func expectNoNotification(t *testing.T, ch <-chan *Notification) error {
+ select {
+ case n := <-ch:
+ return fmt.Errorf("unexpected notification %v", n)
+ case <-time.After(100 * time.Millisecond):
+ return nil
+ }
+}
+
+func expectEvent(t *testing.T, eventch <-chan ListenerEventType, et ListenerEventType) error {
+ select {
+ case e := <-eventch:
+ if e != et {
+ return fmt.Errorf("unexpected event %v", e)
+ }
+ return nil
+ case <-time.After(1500 * time.Millisecond):
+ return fmt.Errorf("timeout")
+ }
+}
+
+func expectNoEvent(t *testing.T, eventch <-chan ListenerEventType) error {
+ select {
+ case e := <-eventch:
+ return fmt.Errorf("unexpected event %v", e)
+ case <-time.After(100 * time.Millisecond):
+ return nil
+ }
+}
+
+func newTestListenerConn(t *testing.T) (*ListenerConn, <-chan *Notification) {
+ datname := os.Getenv("PGDATABASE")
+ sslmode := os.Getenv("PGSSLMODE")
+
+ if datname == "" {
+ os.Setenv("PGDATABASE", "pqgotest")
+ }
+
+ if sslmode == "" {
+ os.Setenv("PGSSLMODE", "disable")
+ }
+
+ notificationChan := make(chan *Notification)
+ l, err := NewListenerConn("", notificationChan)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return l, notificationChan
+}
+
+func TestNewListenerConn(t *testing.T) {
+ l, _ := newTestListenerConn(t)
+
+ defer l.Close()
+}
+
+func TestConnListen(t *testing.T) {
+ l, channel := newTestListenerConn(t)
+
+ defer l.Close()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ ok, err := l.Listen("notify_test")
+ if !ok || err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, channel, "notify_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestConnUnlisten(t *testing.T) {
+ l, channel := newTestListenerConn(t)
+
+ defer l.Close()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ ok, err := l.Listen("notify_test")
+ if !ok || err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_test")
+
+ err = expectNotification(t, channel, "notify_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ok, err = l.Unlisten("notify_test")
+ if !ok || err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNoNotification(t, channel)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestConnUnlistenAll(t *testing.T) {
+ l, channel := newTestListenerConn(t)
+
+ defer l.Close()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ ok, err := l.Listen("notify_test")
+ if !ok || err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_test")
+
+ err = expectNotification(t, channel, "notify_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ok, err = l.UnlistenAll()
+ if !ok || err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNoNotification(t, channel)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestConnClose(t *testing.T) {
+ l, _ := newTestListenerConn(t)
+ defer l.Close()
+
+ err := l.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = l.Close()
+ if err != errListenerConnClosed {
+ t.Fatalf("expected errListenerConnClosed; got %v", err)
+ }
+}
+
+func TestConnPing(t *testing.T) {
+ l, _ := newTestListenerConn(t)
+ defer l.Close()
+ err := l.Ping()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = l.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = l.Ping()
+ if err != errListenerConnClosed {
+ t.Fatalf("expected errListenerConnClosed; got %v", err)
+ }
+}
+
+func TestNotifyExtra(t *testing.T) {
+ db := openTestConn(t)
+ defer db.Close()
+
+ if getServerVersion(t, db) < 90000 {
+ t.Skip("skipping NOTIFY payload test since the server does not appear to support it")
+ }
+
+ l, channel := newTestListenerConn(t)
+ defer l.Close()
+
+ ok, err := l.Listen("notify_test")
+ if !ok || err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_test, 'something'")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, channel, "notify_test", "something")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// create a new test listener and also set the timeouts
+func newTestListenerTimeout(t *testing.T, min time.Duration, max time.Duration) (*Listener, <-chan ListenerEventType) {
+ datname := os.Getenv("PGDATABASE")
+ sslmode := os.Getenv("PGSSLMODE")
+
+ if datname == "" {
+ os.Setenv("PGDATABASE", "pqgotest")
+ }
+
+ if sslmode == "" {
+ os.Setenv("PGSSLMODE", "disable")
+ }
+
+ eventch := make(chan ListenerEventType, 16)
+ l := NewListener("", min, max, func(t ListenerEventType, err error) { eventch <- t })
+ err := expectEvent(t, eventch, ListenerEventConnected)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return l, eventch
+}
+
+func newTestListener(t *testing.T) (*Listener, <-chan ListenerEventType) {
+ return newTestListenerTimeout(t, time.Hour, time.Hour)
+}
+
+func TestListenerListen(t *testing.T) {
+ l, _ := newTestListener(t)
+ defer l.Close()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ err := l.Listen("notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, l.Notify, "notify_listen_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestListenerUnlisten(t *testing.T) {
+ l, _ := newTestListener(t)
+ defer l.Close()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ err := l.Listen("notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = l.Unlisten("notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, l.Notify, "notify_listen_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNoNotification(t, l.Notify)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestListenerUnlistenAll(t *testing.T) {
+ l, _ := newTestListener(t)
+ defer l.Close()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ err := l.Listen("notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = l.UnlistenAll()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, l.Notify, "notify_listen_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNoNotification(t, l.Notify)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestListenerFailedQuery(t *testing.T) {
+ l, eventch := newTestListener(t)
+ defer l.Close()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ err := l.Listen("notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, l.Notify, "notify_listen_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // shouldn't cause a disconnect
+ ok, err := l.cn.ExecSimpleQuery("SELECT error")
+ if !ok {
+ t.Fatalf("could not send query to server: %v", err)
+ }
+ _, ok = err.(PGError)
+ if !ok {
+ t.Fatalf("unexpected error %v", err)
+ }
+ err = expectNoEvent(t, eventch)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // should still work
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, l.Notify, "notify_listen_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestListenerReconnect(t *testing.T) {
+ l, eventch := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
+ defer l.Close()
+
+ db := openTestConn(t)
+ defer db.Close()
+
+ err := l.Listen("notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, l.Notify, "notify_listen_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // kill the connection and make sure it comes back up
+ ok, err := l.cn.ExecSimpleQuery("SELECT pg_terminate_backend(pg_backend_pid())")
+ if ok {
+ t.Fatalf("could not kill the connection: %v", err)
+ }
+ if err != io.EOF {
+ t.Fatalf("unexpected error %v", err)
+ }
+ err = expectEvent(t, eventch, ListenerEventDisconnected)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = expectEvent(t, eventch, ListenerEventReconnected)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // should still work
+ _, err = db.Exec("NOTIFY notify_listen_test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // should get nil after Reconnected
+ err = expectNotification(t, l.Notify, "", "")
+ if err != errNilNotification {
+ t.Fatal(err)
+ }
+
+ err = expectNotification(t, l.Notify, "notify_listen_test", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestListenerClose(t *testing.T) {
+ l, _ := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
+ defer l.Close()
+
+ err := l.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = l.Close()
+ if err != errListenerClosed {
+ t.Fatalf("expected errListenerClosed; got %v", err)
+ }
+}
+
+func TestListenerPing(t *testing.T) {
+ l, _ := newTestListenerTimeout(t, 20*time.Millisecond, time.Hour)
+ defer l.Close()
+
+ err := l.Ping()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = l.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = l.Ping()
+ if err != errListenerClosed {
+ t.Fatalf("expected errListenerClosed; got %v", err)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/oid/doc.go b/Godeps/_workspace/src/github.com/lib/pq/oid/doc.go
new file mode 100644
index 0000000..caaede2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/oid/doc.go
@@ -0,0 +1,6 @@
+// Package oid contains OID constants
+// as defined by the Postgres server.
+package oid
+
+// Oid is a Postgres Object ID.
+type Oid uint32
diff --git a/Godeps/_workspace/src/github.com/lib/pq/oid/gen.go b/Godeps/_workspace/src/github.com/lib/pq/oid/gen.go
new file mode 100644
index 0000000..f16a51c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/oid/gen.go
@@ -0,0 +1,74 @@
+// +build ignore
+
+// Generate the table of OID values
+// Run with 'go run gen.go'.
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+
+ "database/sql"
+ _ "github.com/lib/pq"
+)
+
+func main() {
+ datname := os.Getenv("PGDATABASE")
+ sslmode := os.Getenv("PGSSLMODE")
+
+ if datname == "" {
+ os.Setenv("PGDATABASE", "pqgotest")
+ }
+
+ if sslmode == "" {
+ os.Setenv("PGSSLMODE", "disable")
+ }
+
+ db, err := sql.Open("postgres", "")
+ if err != nil {
+ log.Fatal(err)
+ }
+ cmd := exec.Command("gofmt")
+ cmd.Stderr = os.Stderr
+ w, err := cmd.StdinPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ f, err := os.Create("types.go")
+ if err != nil {
+ log.Fatal(err)
+ }
+ cmd.Stdout = f
+ err = cmd.Start()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Fprintln(w, "// generated by 'go run gen.go'; do not edit")
+ fmt.Fprintln(w, "\npackage oid")
+ fmt.Fprintln(w, "const (")
+ rows, err := db.Query(`
+ SELECT typname, oid
+ FROM pg_type WHERE oid < 10000
+ ORDER BY oid;
+ `)
+ if err != nil {
+ log.Fatal(err)
+ }
+ var name string
+ var oid int
+ for rows.Next() {
+ err = rows.Scan(&name, &oid)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Fprintf(w, "T_%s Oid = %d\n", name, oid)
+ }
+ if err = rows.Err(); err != nil {
+ log.Fatal(err)
+ }
+ fmt.Fprintln(w, ")")
+ w.Close()
+ cmd.Wait()
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/oid/types.go b/Godeps/_workspace/src/github.com/lib/pq/oid/types.go
new file mode 100644
index 0000000..03df05a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/oid/types.go
@@ -0,0 +1,161 @@
+// generated by 'go run gen.go'; do not edit
+
+package oid
+
+const (
+ T_bool Oid = 16
+ T_bytea Oid = 17
+ T_char Oid = 18
+ T_name Oid = 19
+ T_int8 Oid = 20
+ T_int2 Oid = 21
+ T_int2vector Oid = 22
+ T_int4 Oid = 23
+ T_regproc Oid = 24
+ T_text Oid = 25
+ T_oid Oid = 26
+ T_tid Oid = 27
+ T_xid Oid = 28
+ T_cid Oid = 29
+ T_oidvector Oid = 30
+ T_pg_type Oid = 71
+ T_pg_attribute Oid = 75
+ T_pg_proc Oid = 81
+ T_pg_class Oid = 83
+ T_json Oid = 114
+ T_xml Oid = 142
+ T__xml Oid = 143
+ T_pg_node_tree Oid = 194
+ T__json Oid = 199
+ T_smgr Oid = 210
+ T_point Oid = 600
+ T_lseg Oid = 601
+ T_path Oid = 602
+ T_box Oid = 603
+ T_polygon Oid = 604
+ T_line Oid = 628
+ T__line Oid = 629
+ T_cidr Oid = 650
+ T__cidr Oid = 651
+ T_float4 Oid = 700
+ T_float8 Oid = 701
+ T_abstime Oid = 702
+ T_reltime Oid = 703
+ T_tinterval Oid = 704
+ T_unknown Oid = 705
+ T_circle Oid = 718
+ T__circle Oid = 719
+ T_money Oid = 790
+ T__money Oid = 791
+ T_macaddr Oid = 829
+ T_inet Oid = 869
+ T__bool Oid = 1000
+ T__bytea Oid = 1001
+ T__char Oid = 1002
+ T__name Oid = 1003
+ T__int2 Oid = 1005
+ T__int2vector Oid = 1006
+ T__int4 Oid = 1007
+ T__regproc Oid = 1008
+ T__text Oid = 1009
+ T__tid Oid = 1010
+ T__xid Oid = 1011
+ T__cid Oid = 1012
+ T__oidvector Oid = 1013
+ T__bpchar Oid = 1014
+ T__varchar Oid = 1015
+ T__int8 Oid = 1016
+ T__point Oid = 1017
+ T__lseg Oid = 1018
+ T__path Oid = 1019
+ T__box Oid = 1020
+ T__float4 Oid = 1021
+ T__float8 Oid = 1022
+ T__abstime Oid = 1023
+ T__reltime Oid = 1024
+ T__tinterval Oid = 1025
+ T__polygon Oid = 1027
+ T__oid Oid = 1028
+ T_aclitem Oid = 1033
+ T__aclitem Oid = 1034
+ T__macaddr Oid = 1040
+ T__inet Oid = 1041
+ T_bpchar Oid = 1042
+ T_varchar Oid = 1043
+ T_date Oid = 1082
+ T_time Oid = 1083
+ T_timestamp Oid = 1114
+ T__timestamp Oid = 1115
+ T__date Oid = 1182
+ T__time Oid = 1183
+ T_timestamptz Oid = 1184
+ T__timestamptz Oid = 1185
+ T_interval Oid = 1186
+ T__interval Oid = 1187
+ T__numeric Oid = 1231
+ T_pg_database Oid = 1248
+ T__cstring Oid = 1263
+ T_timetz Oid = 1266
+ T__timetz Oid = 1270
+ T_bit Oid = 1560
+ T__bit Oid = 1561
+ T_varbit Oid = 1562
+ T__varbit Oid = 1563
+ T_numeric Oid = 1700
+ T_refcursor Oid = 1790
+ T__refcursor Oid = 2201
+ T_regprocedure Oid = 2202
+ T_regoper Oid = 2203
+ T_regoperator Oid = 2204
+ T_regclass Oid = 2205
+ T_regtype Oid = 2206
+ T__regprocedure Oid = 2207
+ T__regoper Oid = 2208
+ T__regoperator Oid = 2209
+ T__regclass Oid = 2210
+ T__regtype Oid = 2211
+ T_record Oid = 2249
+ T_cstring Oid = 2275
+ T_any Oid = 2276
+ T_anyarray Oid = 2277
+ T_void Oid = 2278
+ T_trigger Oid = 2279
+ T_language_handler Oid = 2280
+ T_internal Oid = 2281
+ T_opaque Oid = 2282
+ T_anyelement Oid = 2283
+ T__record Oid = 2287
+ T_anynonarray Oid = 2776
+ T_pg_authid Oid = 2842
+ T_pg_auth_members Oid = 2843
+ T__txid_snapshot Oid = 2949
+ T_uuid Oid = 2950
+ T__uuid Oid = 2951
+ T_txid_snapshot Oid = 2970
+ T_fdw_handler Oid = 3115
+ T_anyenum Oid = 3500
+ T_tsvector Oid = 3614
+ T_tsquery Oid = 3615
+ T_gtsvector Oid = 3642
+ T__tsvector Oid = 3643
+ T__gtsvector Oid = 3644
+ T__tsquery Oid = 3645
+ T_regconfig Oid = 3734
+ T__regconfig Oid = 3735
+ T_regdictionary Oid = 3769
+ T__regdictionary Oid = 3770
+ T_anyrange Oid = 3831
+ T_event_trigger Oid = 3838
+ T_int4range Oid = 3904
+ T__int4range Oid = 3905
+ T_numrange Oid = 3906
+ T__numrange Oid = 3907
+ T_tsrange Oid = 3908
+ T__tsrange Oid = 3909
+ T_tstzrange Oid = 3910
+ T__tstzrange Oid = 3911
+ T_daterange Oid = 3912
+ T__daterange Oid = 3913
+ T_int8range Oid = 3926
+ T__int8range Oid = 3927
+)
diff --git a/Godeps/_workspace/src/github.com/lib/pq/ssl_test.go b/Godeps/_workspace/src/github.com/lib/pq/ssl_test.go
new file mode 100644
index 0000000..932b336
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/ssl_test.go
@@ -0,0 +1,226 @@
+package pq
+
+// This file contains SSL tests
+
+import (
+ _ "crypto/sha256"
+ "crypto/x509"
+ "database/sql"
+ "fmt"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func maybeSkipSSLTests(t *testing.T) {
+ // Require some special variables for testing certificates
+ if os.Getenv("PQSSLCERTTEST_PATH") == "" {
+ t.Skip("PQSSLCERTTEST_PATH not set, skipping SSL tests")
+ }
+
+ value := os.Getenv("PQGOSSLTESTS")
+ if value == "" || value == "0" {
+ t.Skip("PQGOSSLTESTS not enabled, skipping SSL tests")
+ } else if value != "1" {
+ t.Fatalf("unexpected value %q for PQGOSSLTESTS", value)
+ }
+}
+
+func openSSLConn(t *testing.T, conninfo string) (*sql.DB, error) {
+ db, err := openTestConnConninfo(conninfo)
+ if err != nil {
+ // should never fail
+ t.Fatal(err)
+ }
+ // Do something with the connection to see whether it's working or not.
+ tx, err := db.Begin()
+ if err == nil {
+ return db, tx.Rollback()
+ }
+ _ = db.Close()
+ return nil, err
+}
+
+func checkSSLSetup(t *testing.T, conninfo string) {
+ db, err := openSSLConn(t, conninfo)
+ if err == nil {
+ db.Close()
+ t.Fatalf("expected error with conninfo=%q", conninfo)
+ }
+}
+
+// Connect over SSL and run a simple query to test the basics
+func TestSSLConnection(t *testing.T) {
+ maybeSkipSSLTests(t)
+ // Environment sanity check: should fail without SSL
+ checkSSLSetup(t, "sslmode=disable user=pqgossltest")
+
+ db, err := openSSLConn(t, "sslmode=require user=pqgossltest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err := db.Query("SELECT 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Close()
+}
+
+// Test sslmode=verify-full
+func TestSSLVerifyFull(t *testing.T) {
+ maybeSkipSSLTests(t)
+ // Environment sanity check: should fail without SSL
+ checkSSLSetup(t, "sslmode=disable user=pqgossltest")
+
+ // Not OK according to the system CA
+ _, err := openSSLConn(t, "host=postgres sslmode=verify-full user=pqgossltest")
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ _, ok := err.(x509.UnknownAuthorityError)
+ if !ok {
+ t.Fatalf("expected x509.UnknownAuthorityError, got %#+v", err)
+ }
+
+ rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
+ rootCert := "sslrootcert=" + rootCertPath + " "
+ // No match on Common Name
+ _, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-full user=pqgossltest")
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ _, ok = err.(x509.HostnameError)
+ if !ok {
+ t.Fatalf("expected x509.HostnameError, got %#+v", err)
+ }
+ // OK
+ _, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-full user=pqgossltest")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Test sslmode=verify-ca
+func TestSSLVerifyCA(t *testing.T) {
+ maybeSkipSSLTests(t)
+ // Environment sanity check: should fail without SSL
+ checkSSLSetup(t, "sslmode=disable user=pqgossltest")
+
+ // Not OK according to the system CA
+ _, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest")
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ _, ok := err.(x509.UnknownAuthorityError)
+ if !ok {
+ t.Fatalf("expected x509.UnknownAuthorityError, got %#+v", err)
+ }
+
+ rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt")
+ rootCert := "sslrootcert=" + rootCertPath + " "
+ // No match on Common Name, but that's OK
+ _, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-ca user=pqgossltest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Everything OK
+ _, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-ca user=pqgossltest")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func getCertConninfo(t *testing.T, source string) string {
+ var sslkey string
+ var sslcert string
+
+ certpath := os.Getenv("PQSSLCERTTEST_PATH")
+
+ switch source {
+ case "missingkey":
+ sslkey = "/tmp/filedoesnotexist"
+ sslcert = filepath.Join(certpath, "postgresql.crt")
+ case "missingcert":
+ sslkey = filepath.Join(certpath, "postgresql.key")
+ sslcert = "/tmp/filedoesnotexist"
+ case "certtwice":
+ sslkey = filepath.Join(certpath, "postgresql.crt")
+ sslcert = filepath.Join(certpath, "postgresql.crt")
+ case "valid":
+ sslkey = filepath.Join(certpath, "postgresql.key")
+ sslcert = filepath.Join(certpath, "postgresql.crt")
+ default:
+ t.Fatalf("invalid source %q", source)
+ }
+ return fmt.Sprintf("sslmode=require user=pqgosslcert sslkey=%s sslcert=%s", sslkey, sslcert)
+}
+
+// Authenticate over SSL using client certificates
+func TestSSLClientCertificates(t *testing.T) {
+ maybeSkipSSLTests(t)
+ // Environment sanity check: should fail without SSL
+ checkSSLSetup(t, "sslmode=disable user=pqgossltest")
+
+ // Should also fail without a valid certificate
+ db, err := openSSLConn(t, "sslmode=require user=pqgosslcert")
+ if err == nil {
+ db.Close()
+ t.Fatal("expected error")
+ }
+ pge, ok := err.(*Error)
+ if !ok {
+ t.Fatal("expected pq.Error")
+ }
+ if pge.Code.Name() != "invalid_authorization_specification" {
+ t.Fatalf("unexpected error code %q", pge.Code.Name())
+ }
+
+ // Should work
+ db, err = openSSLConn(t, getCertConninfo(t, "valid"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err := db.Query("SELECT 1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows.Close()
+}
+
+// Test errors with ssl certificates
+func TestSSLClientCertificatesMissingFiles(t *testing.T) {
+ maybeSkipSSLTests(t)
+ // Environment sanity check: should fail without SSL
+ checkSSLSetup(t, "sslmode=disable user=pqgossltest")
+
+ // Key missing, should fail
+ _, err := openSSLConn(t, getCertConninfo(t, "missingkey"))
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ // should be a PathError
+ _, ok := err.(*os.PathError)
+ if !ok {
+ t.Fatalf("expected PathError, got %#+v", err)
+ }
+
+ // Cert missing, should fail
+ _, err = openSSLConn(t, getCertConninfo(t, "missingcert"))
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ // should be a PathError
+ _, ok = err.(*os.PathError)
+ if !ok {
+ t.Fatalf("expected PathError, got %#+v", err)
+ }
+
+ // Key has wrong permissions, should fail
+ _, err = openSSLConn(t, getCertConninfo(t, "certtwice"))
+ if err == nil {
+ t.Fatal("expected error")
+ }
+ if err != ErrSSLKeyHasWorldPermissions {
+ t.Fatalf("expected ErrSSLKeyHasWorldPermissions, got %#+v", err)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/url.go b/Godeps/_workspace/src/github.com/lib/pq/url.go
new file mode 100644
index 0000000..b83e806
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/url.go
@@ -0,0 +1,76 @@
+package pq
+
+import (
+ "fmt"
+ nurl "net/url"
+ "sort"
+ "strings"
+)
+
+// ParseURL no longer needs to be used by clients of this library since supplying a URL as a
+// connection string to sql.Open() is now supported:
+//
+// sql.Open("postgres", "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full")
+//
+// It remains exported here for backwards-compatibility.
+//
+// ParseURL converts a url to a connection string for driver.Open.
+// Example:
+//
+// "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full"
+//
+// converts to:
+//
+// "user=bob password=secret host=1.2.3.4 port=5432 dbname=mydb sslmode=verify-full"
+//
+// A minimal example:
+//
+// "postgres://"
+//
+// This will be blank, causing driver.Open to use all of the defaults
+func ParseURL(url string) (string, error) {
+ u, err := nurl.Parse(url)
+ if err != nil {
+ return "", err
+ }
+
+ if u.Scheme != "postgres" {
+ return "", fmt.Errorf("invalid connection protocol: %s", u.Scheme)
+ }
+
+ var kvs []string
+ escaper := strings.NewReplacer(` `, `\ `, `'`, `\'`, `\`, `\\`)
+ accrue := func(k, v string) {
+ if v != "" {
+ kvs = append(kvs, k+"="+escaper.Replace(v))
+ }
+ }
+
+ if u.User != nil {
+ v := u.User.Username()
+ accrue("user", v)
+
+ v, _ = u.User.Password()
+ accrue("password", v)
+ }
+
+ i := strings.Index(u.Host, ":")
+ if i < 0 {
+ accrue("host", u.Host)
+ } else {
+ accrue("host", u.Host[:i])
+ accrue("port", u.Host[i+1:])
+ }
+
+ if u.Path != "" {
+ accrue("dbname", u.Path[1:])
+ }
+
+ q := u.Query()
+ for k := range q {
+ accrue(k, q.Get(k))
+ }
+
+ sort.Strings(kvs) // Makes testing easier (not a performance concern)
+ return strings.Join(kvs, " "), nil
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/url_test.go b/Godeps/_workspace/src/github.com/lib/pq/url_test.go
new file mode 100644
index 0000000..29f4a7c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/url_test.go
@@ -0,0 +1,54 @@
+package pq
+
+import (
+ "testing"
+)
+
+func TestSimpleParseURL(t *testing.T) {
+ expected := "host=hostname.remote"
+ str, err := ParseURL("postgres://hostname.remote")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if str != expected {
+ t.Fatalf("unexpected result from ParseURL:\n+ %v\n- %v", str, expected)
+ }
+}
+
+func TestFullParseURL(t *testing.T) {
+ expected := `dbname=database host=hostname.remote password=top\ secret port=1234 user=username`
+ str, err := ParseURL("postgres://username:top%20secret@hostname.remote:1234/database")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if str != expected {
+ t.Fatalf("unexpected result from ParseURL:\n+ %s\n- %s", str, expected)
+ }
+}
+
+func TestInvalidProtocolParseURL(t *testing.T) {
+ _, err := ParseURL("http://hostname.remote")
+ switch err {
+ case nil:
+ t.Fatal("Expected an error from parsing invalid protocol")
+ default:
+ msg := "invalid connection protocol: http"
+ if err.Error() != msg {
+ t.Fatalf("Unexpected error message:\n+ %s\n- %s",
+ err.Error(), msg)
+ }
+ }
+}
+
+func TestMinimalURL(t *testing.T) {
+ cs, err := ParseURL("postgres://")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if cs != "" {
+ t.Fatalf("expected blank connection string, got: %q", cs)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/user_posix.go b/Godeps/_workspace/src/github.com/lib/pq/user_posix.go
new file mode 100644
index 0000000..e937d7d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/user_posix.go
@@ -0,0 +1,24 @@
+// Package pq is a pure Go Postgres driver for the database/sql package.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package pq
+
+import (
+ "os"
+ "os/user"
+)
+
+func userCurrent() (string, error) {
+ u, err := user.Current()
+ if err == nil {
+ return u.Username, nil
+ }
+
+ name := os.Getenv("USER")
+ if name != "" {
+ return name, nil
+ }
+
+ return "", ErrCouldNotDetectUsername
+}
diff --git a/Godeps/_workspace/src/github.com/lib/pq/user_windows.go b/Godeps/_workspace/src/github.com/lib/pq/user_windows.go
new file mode 100644
index 0000000..2b69126
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/lib/pq/user_windows.go
@@ -0,0 +1,27 @@
+// Package pq is a pure Go Postgres driver for the database/sql package.
+package pq
+
+import (
+ "path/filepath"
+ "syscall"
+)
+
+// Perform Windows user name lookup identically to libpq.
+//
+// The PostgreSQL code makes use of the legacy Win32 function
+// GetUserName, and that function has not been imported into stock Go.
+// GetUserNameEx is available though, the difference being that a
+// wider range of names are available. To get the output to be the
+// same as GetUserName, only the base (or last) component of the
+// result is returned.
+func userCurrent() (string, error) {
+ pw_name := make([]uint16, 128)
+ pwname_size := uint32(len(pw_name)) - 1
+ err := syscall.GetUserNameEx(syscall.NameSamCompatible, &pw_name[0], &pwname_size)
+ if err != nil {
+ return "", ErrCouldNotDetectUsername
+ }
+ s := syscall.UTF16ToString(pw_name)
+ u := filepath.Base(s)
+ return u, nil
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/base64.go b/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/base64.go
new file mode 100644
index 0000000..fc31160
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/base64.go
@@ -0,0 +1,35 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bcrypt
+
+import "encoding/base64"
+
+const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+
+var bcEncoding = base64.NewEncoding(alphabet)
+
+func base64Encode(src []byte) []byte {
+ n := bcEncoding.EncodedLen(len(src))
+ dst := make([]byte, n)
+ bcEncoding.Encode(dst, src)
+ for dst[n-1] == '=' {
+ n--
+ }
+ return dst[:n]
+}
+
+func base64Decode(src []byte) ([]byte, error) {
+ numOfEquals := 4 - (len(src) % 4)
+ for i := 0; i < numOfEquals; i++ {
+ src = append(src, '=')
+ }
+
+ dst := make([]byte, bcEncoding.DecodedLen(len(src)))
+ n, err := bcEncoding.Decode(dst, src)
+ if err != nil {
+ return nil, err
+ }
+ return dst[:n], nil
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/bcrypt.go b/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/bcrypt.go
new file mode 100644
index 0000000..b8e18d7
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/bcrypt.go
@@ -0,0 +1,294 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
+// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
+package bcrypt
+
+// The code is a port of Provos and Mazières's C implementation.
+import (
+ "crypto/rand"
+ "crypto/subtle"
+ "errors"
+ "fmt"
+ "golang.org/x/crypto/blowfish"
+ "io"
+ "strconv"
+)
+
+const (
+ MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword
+ MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
+ DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
+)
+
+// The error returned from CompareHashAndPassword when a password and hash do
+// not match.
+var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
+
+// The error returned from CompareHashAndPassword when a hash is too short to
+// be a bcrypt hash.
+var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
+
+// The error returned from CompareHashAndPassword when a hash was created with
+// a bcrypt algorithm newer than this implementation.
+type HashVersionTooNewError byte
+
+func (hv HashVersionTooNewError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
+}
+
+// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
+type InvalidHashPrefixError byte
+
+func (ih InvalidHashPrefixError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
+}
+
+type InvalidCostError int
+
+func (ic InvalidCostError) Error() string {
+ return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost))
+}
+
+const (
+ majorVersion = '2'
+ minorVersion = 'a'
+ maxSaltSize = 16
+ maxCryptedHashSize = 23
+ encodedSaltSize = 22
+ encodedHashSize = 31
+ minHashSize = 59
+)
+
+// magicCipherData is an IV for the 64 Blowfish encryption calls in
+// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
+var magicCipherData = []byte{
+ 0x4f, 0x72, 0x70, 0x68,
+ 0x65, 0x61, 0x6e, 0x42,
+ 0x65, 0x68, 0x6f, 0x6c,
+ 0x64, 0x65, 0x72, 0x53,
+ 0x63, 0x72, 0x79, 0x44,
+ 0x6f, 0x75, 0x62, 0x74,
+}
+
+type hashed struct {
+ hash []byte
+ salt []byte
+ cost int // allowed range is MinCost to MaxCost
+ major byte
+ minor byte
+}
+
+// GenerateFromPassword returns the bcrypt hash of the password at the given
+// cost. If the cost given is less than MinCost, the cost will be set to
+// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
+// to compare the returned hashed password with its cleartext version.
+func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
+ p, err := newFromPassword(password, cost)
+ if err != nil {
+ return nil, err
+ }
+ return p.Hash(), nil
+}
+
+// CompareHashAndPassword compares a bcrypt hashed password with its possible
+// plaintext equivalent. Returns nil on success, or an error on failure.
+func CompareHashAndPassword(hashedPassword, password []byte) error {
+ p, err := newFromHash(hashedPassword)
+ if err != nil {
+ return err
+ }
+
+ otherHash, err := bcrypt(password, p.cost, p.salt)
+ if err != nil {
+ return err
+ }
+
+ otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
+ if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
+ return nil
+ }
+
+ return ErrMismatchedHashAndPassword
+}
+
+// Cost returns the hashing cost used to create the given hashed
+// password. When, in the future, the hashing cost of a password system needs
+// to be increased in order to adjust for greater computational power, this
+// function allows one to establish which passwords need to be updated.
+func Cost(hashedPassword []byte) (int, error) {
+ p, err := newFromHash(hashedPassword)
+ if err != nil {
+ return 0, err
+ }
+ return p.cost, nil
+}
+
+func newFromPassword(password []byte, cost int) (*hashed, error) {
+ if cost < MinCost {
+ cost = DefaultCost
+ }
+ p := new(hashed)
+ p.major = majorVersion
+ p.minor = minorVersion
+
+ err := checkCost(cost)
+ if err != nil {
+ return nil, err
+ }
+ p.cost = cost
+
+ unencodedSalt := make([]byte, maxSaltSize)
+ _, err = io.ReadFull(rand.Reader, unencodedSalt)
+ if err != nil {
+ return nil, err
+ }
+
+ p.salt = base64Encode(unencodedSalt)
+ hash, err := bcrypt(password, p.cost, p.salt)
+ if err != nil {
+ return nil, err
+ }
+ p.hash = hash
+ return p, err
+}
+
+func newFromHash(hashedSecret []byte) (*hashed, error) {
+ if len(hashedSecret) < minHashSize {
+ return nil, ErrHashTooShort
+ }
+ p := new(hashed)
+ n, err := p.decodeVersion(hashedSecret)
+ if err != nil {
+ return nil, err
+ }
+ hashedSecret = hashedSecret[n:]
+ n, err = p.decodeCost(hashedSecret)
+ if err != nil {
+ return nil, err
+ }
+ hashedSecret = hashedSecret[n:]
+
+ // The "+2" is here because we'll have to append at most 2 '=' to the salt
+ // when base64 decoding it in expensiveBlowfishSetup().
+ p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
+ copy(p.salt, hashedSecret[:encodedSaltSize])
+
+ hashedSecret = hashedSecret[encodedSaltSize:]
+ p.hash = make([]byte, len(hashedSecret))
+ copy(p.hash, hashedSecret)
+
+ return p, nil
+}
+
+func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
+ cipherData := make([]byte, len(magicCipherData))
+ copy(cipherData, magicCipherData)
+
+ c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
+ if err != nil {
+ return nil, err
+ }
+
+ for i := 0; i < 24; i += 8 {
+ for j := 0; j < 64; j++ {
+ c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
+ }
+ }
+
+ // Bug compatibility with C bcrypt implementations. We only encode 23 of
+ // the 24 bytes encrypted.
+ hsh := base64Encode(cipherData[:maxCryptedHashSize])
+ return hsh, nil
+}
+
+func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
+
+ csalt, err := base64Decode(salt)
+ if err != nil {
+ return nil, err
+ }
+
+ // Bug compatibility with C bcrypt implementations. They use the trailing
+ // NULL in the key string during expansion.
+ ckey := append(key, 0)
+
+ c, err := blowfish.NewSaltedCipher(ckey, csalt)
+ if err != nil {
+ return nil, err
+ }
+
+ var i, rounds uint64
+ rounds = 1 << cost
+ for i = 0; i < rounds; i++ {
+ blowfish.ExpandKey(ckey, c)
+ blowfish.ExpandKey(csalt, c)
+ }
+
+ return c, nil
+}
+
+func (p *hashed) Hash() []byte {
+ arr := make([]byte, 60)
+ arr[0] = '$'
+ arr[1] = p.major
+ n := 2
+ if p.minor != 0 {
+ arr[2] = p.minor
+ n = 3
+ }
+ arr[n] = '$'
+ n += 1
+ copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
+ n += 2
+ arr[n] = '$'
+ n += 1
+ copy(arr[n:], p.salt)
+ n += encodedSaltSize
+ copy(arr[n:], p.hash)
+ n += encodedHashSize
+ return arr[:n]
+}
+
+func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
+ if sbytes[0] != '$' {
+ return -1, InvalidHashPrefixError(sbytes[0])
+ }
+ if sbytes[1] > majorVersion {
+ return -1, HashVersionTooNewError(sbytes[1])
+ }
+ p.major = sbytes[1]
+ n := 3
+ if sbytes[2] != '$' {
+ p.minor = sbytes[2]
+ n++
+ }
+ return n, nil
+}
+
+// sbytes should begin where decodeVersion left off.
+func (p *hashed) decodeCost(sbytes []byte) (int, error) {
+ cost, err := strconv.Atoi(string(sbytes[0:2]))
+ if err != nil {
+ return -1, err
+ }
+ err = checkCost(cost)
+ if err != nil {
+ return -1, err
+ }
+ p.cost = cost
+ return 3, nil
+}
+
+func (p *hashed) String() string {
+ return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
+}
+
+func checkCost(cost int) error {
+ if cost < MinCost || cost > MaxCost {
+ return InvalidCostError(cost)
+ }
+ return nil
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/bcrypt_test.go b/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/bcrypt_test.go
new file mode 100644
index 0000000..f08a6f5
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/bcrypt/bcrypt_test.go
@@ -0,0 +1,226 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bcrypt
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+)
+
+func TestBcryptingIsEasy(t *testing.T) {
+ pass := []byte("mypassword")
+ hp, err := GenerateFromPassword(pass, 0)
+ if err != nil {
+ t.Fatalf("GenerateFromPassword error: %s", err)
+ }
+
+ if CompareHashAndPassword(hp, pass) != nil {
+ t.Errorf("%v should hash %s correctly", hp, pass)
+ }
+
+ notPass := "notthepass"
+ err = CompareHashAndPassword(hp, []byte(notPass))
+ if err != ErrMismatchedHashAndPassword {
+ t.Errorf("%v and %s should be mismatched", hp, notPass)
+ }
+}
+
+func TestBcryptingIsCorrect(t *testing.T) {
+ pass := []byte("allmine")
+ salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+ expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
+
+ hash, err := bcrypt(pass, 10, salt)
+ if err != nil {
+ t.Fatalf("bcrypt blew up: %v", err)
+ }
+ if !bytes.HasSuffix(expectedHash, hash) {
+ t.Errorf("%v should be the suffix of %v", hash, expectedHash)
+ }
+
+ h, err := newFromHash(expectedHash)
+ if err != nil {
+ t.Errorf("Unable to parse %s: %v", string(expectedHash), err)
+ }
+
+ // This is not the safe way to compare these hashes. We do this only for
+ // testing clarity. Use bcrypt.CompareHashAndPassword()
+ if err == nil && !bytes.Equal(expectedHash, h.Hash()) {
+ t.Errorf("Parsed hash %v should equal %v", h.Hash(), expectedHash)
+ }
+}
+
+func TestVeryShortPasswords(t *testing.T) {
+ key := []byte("k")
+ salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+ _, err := bcrypt(key, 10, salt)
+ if err != nil {
+ t.Errorf("One byte key resulted in error: %s", err)
+ }
+}
+
+func TestTooLongPasswordsWork(t *testing.T) {
+ salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+ // One byte over the usual 56 byte limit that blowfish has
+ tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456")
+ tooLongExpected := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C")
+ hash, err := bcrypt(tooLongPass, 10, salt)
+ if err != nil {
+ t.Fatalf("bcrypt blew up on long password: %v", err)
+ }
+ if !bytes.HasSuffix(tooLongExpected, hash) {
+ t.Errorf("%v should be the suffix of %v", hash, tooLongExpected)
+ }
+}
+
+type InvalidHashTest struct {
+ err error
+ hash []byte
+}
+
+var invalidTests = []InvalidHashTest{
+ {ErrHashTooShort, []byte("$2a$10$fooo")},
+ {ErrHashTooShort, []byte("$2a")},
+ {HashVersionTooNewError('3'), []byte("$3a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+ {InvalidHashPrefixError('%'), []byte("%2a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+ {InvalidCostError(32), []byte("$2a$32$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+}
+
+func TestInvalidHashErrors(t *testing.T) {
+ check := func(name string, expected, err error) {
+ if err == nil {
+ t.Errorf("%s: Should have returned an error", name)
+ }
+ if err != nil && err != expected {
+ t.Errorf("%s gave err %v but should have given %v", name, err, expected)
+ }
+ }
+ for _, iht := range invalidTests {
+ _, err := newFromHash(iht.hash)
+ check("newFromHash", iht.err, err)
+ err = CompareHashAndPassword(iht.hash, []byte("anything"))
+ check("CompareHashAndPassword", iht.err, err)
+ }
+}
+
+func TestUnpaddedBase64Encoding(t *testing.T) {
+ original := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30}
+ encodedOriginal := []byte("XajjQvNhvvRt5GSeFk1xFe")
+
+ encoded := base64Encode(original)
+
+ if !bytes.Equal(encodedOriginal, encoded) {
+ t.Errorf("Encoded %v should have equaled %v", encoded, encodedOriginal)
+ }
+
+ decoded, err := base64Decode(encodedOriginal)
+ if err != nil {
+ t.Fatalf("base64Decode blew up: %s", err)
+ }
+
+ if !bytes.Equal(decoded, original) {
+ t.Errorf("Decoded %v should have equaled %v", decoded, original)
+ }
+}
+
+func TestCost(t *testing.T) {
+ suffix := "XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C"
+ for _, vers := range []string{"2a", "2"} {
+ for _, cost := range []int{4, 10} {
+ s := fmt.Sprintf("$%s$%02d$%s", vers, cost, suffix)
+ h := []byte(s)
+ actual, err := Cost(h)
+ if err != nil {
+ t.Errorf("Cost, error: %s", err)
+ continue
+ }
+ if actual != cost {
+ t.Errorf("Cost, expected: %d, actual: %d", cost, actual)
+ }
+ }
+ }
+ _, err := Cost([]byte("$a$a$" + suffix))
+ if err == nil {
+ t.Errorf("Cost, malformed but no error returned")
+ }
+}
+
+func TestCostValidationInHash(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+
+ pass := []byte("mypassword")
+
+ for c := 0; c < MinCost; c++ {
+ p, _ := newFromPassword(pass, c)
+ if p.cost != DefaultCost {
+ t.Errorf("newFromPassword should default costs below %d to %d, but was %d", MinCost, DefaultCost, p.cost)
+ }
+ }
+
+ p, _ := newFromPassword(pass, 14)
+ if p.cost != 14 {
+ t.Errorf("newFromPassword should default cost to 14, but was %d", p.cost)
+ }
+
+ hp, _ := newFromHash(p.Hash())
+ if p.cost != hp.cost {
+ t.Errorf("newFromHash should maintain the cost at %d, but was %d", p.cost, hp.cost)
+ }
+
+ _, err := newFromPassword(pass, 32)
+ if err == nil {
+ t.Fatalf("newFromPassword: should return a cost error")
+ }
+ if err != InvalidCostError(32) {
+ t.Errorf("newFromPassword: should return cost error, got %#v", err)
+ }
+}
+
+func TestCostReturnsWithLeadingZeroes(t *testing.T) {
+ hp, _ := newFromPassword([]byte("abcdefgh"), 7)
+ cost := hp.Hash()[4:7]
+ expected := []byte("07$")
+
+ if !bytes.Equal(expected, cost) {
+ t.Errorf("single digit costs in hash should have leading zeros: was %v instead of %v", cost, expected)
+ }
+}
+
+func TestMinorNotRequired(t *testing.T) {
+ noMinorHash := []byte("$2$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
+ h, err := newFromHash(noMinorHash)
+ if err != nil {
+ t.Fatalf("No minor hash blew up: %s", err)
+ }
+ if h.minor != 0 {
+ t.Errorf("Should leave minor version at 0, but was %d", h.minor)
+ }
+
+ if !bytes.Equal(noMinorHash, h.Hash()) {
+ t.Errorf("Should generate hash %v, but created %v", noMinorHash, h.Hash())
+ }
+}
+
+func BenchmarkEqual(b *testing.B) {
+ b.StopTimer()
+ passwd := []byte("somepasswordyoulike")
+ hash, _ := GenerateFromPassword(passwd, 10)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ CompareHashAndPassword(hash, passwd)
+ }
+}
+
+func BenchmarkGeneration(b *testing.B) {
+ b.StopTimer()
+ passwd := []byte("mylongpassword1234")
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ GenerateFromPassword(passwd, 10)
+ }
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/blowfish/block.go b/Godeps/_workspace/src/golang.org/x/crypto/blowfish/block.go
new file mode 100644
index 0000000..9d80f19
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/blowfish/block.go
@@ -0,0 +1,159 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package blowfish
+
+// getNextWord returns the next big-endian uint32 value from the byte slice
+// at the given position in a circular manner, updating the position.
+func getNextWord(b []byte, pos *int) uint32 {
+ var w uint32
+ j := *pos
+ for i := 0; i < 4; i++ {
+ w = w<<8 | uint32(b[j])
+ j++
+ if j >= len(b) {
+ j = 0
+ }
+ }
+ *pos = j
+ return w
+}
+
+// ExpandKey performs a key expansion on the given *Cipher. Specifically, it
+// performs the Blowfish algorithm's key schedule which sets up the *Cipher's
+// pi and substitution tables for calls to Encrypt. This is used, primarily,
+// by the bcrypt package to reuse the Blowfish key schedule during its
+// set up. It's unlikely that you need to use this directly.
+func ExpandKey(key []byte, c *Cipher) {
+ j := 0
+ for i := 0; i < 18; i++ {
+ // Using inlined getNextWord for performance.
+ var d uint32
+ for k := 0; k < 4; k++ {
+ d = d<<8 | uint32(key[j])
+ j++
+ if j >= len(key) {
+ j = 0
+ }
+ }
+ c.p[i] ^= d
+ }
+
+ var l, r uint32
+ for i := 0; i < 18; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.p[i], c.p[i+1] = l, r
+ }
+
+ for i := 0; i < 256; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.s0[i], c.s0[i+1] = l, r
+ }
+ for i := 0; i < 256; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.s1[i], c.s1[i+1] = l, r
+ }
+ for i := 0; i < 256; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.s2[i], c.s2[i+1] = l, r
+ }
+ for i := 0; i < 256; i += 2 {
+ l, r = encryptBlock(l, r, c)
+ c.s3[i], c.s3[i+1] = l, r
+ }
+}
+
+// This is similar to ExpandKey, but folds the salt during the key
+// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero
+// salt passed in, reusing ExpandKey turns out to be a place of inefficiency
+// and specializing it here is useful.
+func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) {
+ j := 0
+ for i := 0; i < 18; i++ {
+ c.p[i] ^= getNextWord(key, &j)
+ }
+
+ j = 0
+ var l, r uint32
+ for i := 0; i < 18; i += 2 {
+ l ^= getNextWord(salt, &j)
+ r ^= getNextWord(salt, &j)
+ l, r = encryptBlock(l, r, c)
+ c.p[i], c.p[i+1] = l, r
+ }
+
+ for i := 0; i < 256; i += 2 {
+ l ^= getNextWord(salt, &j)
+ r ^= getNextWord(salt, &j)
+ l, r = encryptBlock(l, r, c)
+ c.s0[i], c.s0[i+1] = l, r
+ }
+
+ for i := 0; i < 256; i += 2 {
+ l ^= getNextWord(salt, &j)
+ r ^= getNextWord(salt, &j)
+ l, r = encryptBlock(l, r, c)
+ c.s1[i], c.s1[i+1] = l, r
+ }
+
+ for i := 0; i < 256; i += 2 {
+ l ^= getNextWord(salt, &j)
+ r ^= getNextWord(salt, &j)
+ l, r = encryptBlock(l, r, c)
+ c.s2[i], c.s2[i+1] = l, r
+ }
+
+ for i := 0; i < 256; i += 2 {
+ l ^= getNextWord(salt, &j)
+ r ^= getNextWord(salt, &j)
+ l, r = encryptBlock(l, r, c)
+ c.s3[i], c.s3[i+1] = l, r
+ }
+}
+
+func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) {
+ xl, xr := l, r
+ xl ^= c.p[0]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16]
+ xr ^= c.p[17]
+ return xr, xl
+}
+
+func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) {
+ xl, xr := l, r
+ xl ^= c.p[17]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3]
+ xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2]
+ xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1]
+ xr ^= c.p[0]
+ return xr, xl
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/blowfish/blowfish_test.go b/Godeps/_workspace/src/golang.org/x/crypto/blowfish/blowfish_test.go
new file mode 100644
index 0000000..7afa1fd
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/blowfish/blowfish_test.go
@@ -0,0 +1,274 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package blowfish
+
+import "testing"
+
+type CryptTest struct {
+ key []byte
+ in []byte
+ out []byte
+}
+
+// Test vector values are from http://www.schneier.com/code/vectors.txt.
+var encryptTests = []CryptTest{
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}},
+ {
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x51, 0x86, 0x6F, 0xD5, 0xB8, 0x5E, 0xCB, 0x8A}},
+ {
+ []byte{0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
+ []byte{0x7D, 0x85, 0x6F, 0x9A, 0x61, 0x30, 0x63, 0xF2}},
+ {
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x24, 0x66, 0xDD, 0x87, 0x8B, 0x96, 0x3C, 0x9D}},
+
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x61, 0xF9, 0xC3, 0x80, 0x22, 0x81, 0xB0, 0x96}},
+ {
+ []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x7D, 0x0C, 0xC6, 0x30, 0xAF, 0xDA, 0x1E, 0xC7}},
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}},
+ {
+ []byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x0A, 0xCE, 0xAB, 0x0F, 0xC6, 0xA0, 0xA2, 0x8D}},
+ {
+ []byte{0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57},
+ []byte{0x01, 0xA1, 0xD6, 0xD0, 0x39, 0x77, 0x67, 0x42},
+ []byte{0x59, 0xC6, 0x82, 0x45, 0xEB, 0x05, 0x28, 0x2B}},
+ {
+ []byte{0x01, 0x31, 0xD9, 0x61, 0x9D, 0xC1, 0x37, 0x6E},
+ []byte{0x5C, 0xD5, 0x4C, 0xA8, 0x3D, 0xEF, 0x57, 0xDA},
+ []byte{0xB1, 0xB8, 0xCC, 0x0B, 0x25, 0x0F, 0x09, 0xA0}},
+ {
+ []byte{0x07, 0xA1, 0x13, 0x3E, 0x4A, 0x0B, 0x26, 0x86},
+ []byte{0x02, 0x48, 0xD4, 0x38, 0x06, 0xF6, 0x71, 0x72},
+ []byte{0x17, 0x30, 0xE5, 0x77, 0x8B, 0xEA, 0x1D, 0xA4}},
+ {
+ []byte{0x38, 0x49, 0x67, 0x4C, 0x26, 0x02, 0x31, 0x9E},
+ []byte{0x51, 0x45, 0x4B, 0x58, 0x2D, 0xDF, 0x44, 0x0A},
+ []byte{0xA2, 0x5E, 0x78, 0x56, 0xCF, 0x26, 0x51, 0xEB}},
+ {
+ []byte{0x04, 0xB9, 0x15, 0xBA, 0x43, 0xFE, 0xB5, 0xB6},
+ []byte{0x42, 0xFD, 0x44, 0x30, 0x59, 0x57, 0x7F, 0xA2},
+ []byte{0x35, 0x38, 0x82, 0xB1, 0x09, 0xCE, 0x8F, 0x1A}},
+ {
+ []byte{0x01, 0x13, 0xB9, 0x70, 0xFD, 0x34, 0xF2, 0xCE},
+ []byte{0x05, 0x9B, 0x5E, 0x08, 0x51, 0xCF, 0x14, 0x3A},
+ []byte{0x48, 0xF4, 0xD0, 0x88, 0x4C, 0x37, 0x99, 0x18}},
+ {
+ []byte{0x01, 0x70, 0xF1, 0x75, 0x46, 0x8F, 0xB5, 0xE6},
+ []byte{0x07, 0x56, 0xD8, 0xE0, 0x77, 0x47, 0x61, 0xD2},
+ []byte{0x43, 0x21, 0x93, 0xB7, 0x89, 0x51, 0xFC, 0x98}},
+ {
+ []byte{0x43, 0x29, 0x7F, 0xAD, 0x38, 0xE3, 0x73, 0xFE},
+ []byte{0x76, 0x25, 0x14, 0xB8, 0x29, 0xBF, 0x48, 0x6A},
+ []byte{0x13, 0xF0, 0x41, 0x54, 0xD6, 0x9D, 0x1A, 0xE5}},
+ {
+ []byte{0x07, 0xA7, 0x13, 0x70, 0x45, 0xDA, 0x2A, 0x16},
+ []byte{0x3B, 0xDD, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02},
+ []byte{0x2E, 0xED, 0xDA, 0x93, 0xFF, 0xD3, 0x9C, 0x79}},
+ {
+ []byte{0x04, 0x68, 0x91, 0x04, 0xC2, 0xFD, 0x3B, 0x2F},
+ []byte{0x26, 0x95, 0x5F, 0x68, 0x35, 0xAF, 0x60, 0x9A},
+ []byte{0xD8, 0x87, 0xE0, 0x39, 0x3C, 0x2D, 0xA6, 0xE3}},
+ {
+ []byte{0x37, 0xD0, 0x6B, 0xB5, 0x16, 0xCB, 0x75, 0x46},
+ []byte{0x16, 0x4D, 0x5E, 0x40, 0x4F, 0x27, 0x52, 0x32},
+ []byte{0x5F, 0x99, 0xD0, 0x4F, 0x5B, 0x16, 0x39, 0x69}},
+ {
+ []byte{0x1F, 0x08, 0x26, 0x0D, 0x1A, 0xC2, 0x46, 0x5E},
+ []byte{0x6B, 0x05, 0x6E, 0x18, 0x75, 0x9F, 0x5C, 0xCA},
+ []byte{0x4A, 0x05, 0x7A, 0x3B, 0x24, 0xD3, 0x97, 0x7B}},
+ {
+ []byte{0x58, 0x40, 0x23, 0x64, 0x1A, 0xBA, 0x61, 0x76},
+ []byte{0x00, 0x4B, 0xD6, 0xEF, 0x09, 0x17, 0x60, 0x62},
+ []byte{0x45, 0x20, 0x31, 0xC1, 0xE4, 0xFA, 0xDA, 0x8E}},
+ {
+ []byte{0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xB0, 0x07},
+ []byte{0x48, 0x0D, 0x39, 0x00, 0x6E, 0xE7, 0x62, 0xF2},
+ []byte{0x75, 0x55, 0xAE, 0x39, 0xF5, 0x9B, 0x87, 0xBD}},
+ {
+ []byte{0x49, 0x79, 0x3E, 0xBC, 0x79, 0xB3, 0x25, 0x8F},
+ []byte{0x43, 0x75, 0x40, 0xC8, 0x69, 0x8F, 0x3C, 0xFA},
+ []byte{0x53, 0xC5, 0x5F, 0x9C, 0xB4, 0x9F, 0xC0, 0x19}},
+ {
+ []byte{0x4F, 0xB0, 0x5E, 0x15, 0x15, 0xAB, 0x73, 0xA7},
+ []byte{0x07, 0x2D, 0x43, 0xA0, 0x77, 0x07, 0x52, 0x92},
+ []byte{0x7A, 0x8E, 0x7B, 0xFA, 0x93, 0x7E, 0x89, 0xA3}},
+ {
+ []byte{0x49, 0xE9, 0x5D, 0x6D, 0x4C, 0xA2, 0x29, 0xBF},
+ []byte{0x02, 0xFE, 0x55, 0x77, 0x81, 0x17, 0xF1, 0x2A},
+ []byte{0xCF, 0x9C, 0x5D, 0x7A, 0x49, 0x86, 0xAD, 0xB5}},
+ {
+ []byte{0x01, 0x83, 0x10, 0xDC, 0x40, 0x9B, 0x26, 0xD6},
+ []byte{0x1D, 0x9D, 0x5C, 0x50, 0x18, 0xF7, 0x28, 0xC2},
+ []byte{0xD1, 0xAB, 0xB2, 0x90, 0x65, 0x8B, 0xC7, 0x78}},
+ {
+ []byte{0x1C, 0x58, 0x7F, 0x1C, 0x13, 0x92, 0x4F, 0xEF},
+ []byte{0x30, 0x55, 0x32, 0x28, 0x6D, 0x6F, 0x29, 0x5A},
+ []byte{0x55, 0xCB, 0x37, 0x74, 0xD1, 0x3E, 0xF2, 0x01}},
+ {
+ []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xFA, 0x34, 0xEC, 0x48, 0x47, 0xB2, 0x68, 0xB2}},
+ {
+ []byte{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xA7, 0x90, 0x79, 0x51, 0x08, 0xEA, 0x3C, 0xAE}},
+ {
+ []byte{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE},
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0xC3, 0x9E, 0x07, 0x2D, 0x9F, 0xAC, 0x63, 0x1D}},
+ {
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x01, 0x49, 0x33, 0xE0, 0xCD, 0xAF, 0xF6, 0xE4}},
+ {
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0xF2, 0x1E, 0x9A, 0x77, 0xB7, 0x1C, 0x49, 0xBC}},
+ {
+ []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+ []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ []byte{0x24, 0x59, 0x46, 0x88, 0x57, 0x54, 0x36, 0x9A}},
+ {
+ []byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},
+ []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+ []byte{0x6B, 0x5C, 0x5A, 0x9C, 0x5D, 0x9E, 0x0A, 0x5A}},
+}
+
+func TestCipherEncrypt(t *testing.T) {
+ for i, tt := range encryptTests {
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err)
+ continue
+ }
+ ct := make([]byte, len(tt.out))
+ c.Encrypt(ct, tt.in)
+ for j, v := range ct {
+ if v != tt.out[j] {
+ t.Errorf("Cipher.Encrypt, test vector #%d: cipher-text[%d] = %#x, expected %#x", i, j, v, tt.out[j])
+ break
+ }
+ }
+ }
+}
+
+func TestCipherDecrypt(t *testing.T) {
+ for i, tt := range encryptTests {
+ c, err := NewCipher(tt.key)
+ if err != nil {
+ t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err)
+ continue
+ }
+ pt := make([]byte, len(tt.in))
+ c.Decrypt(pt, tt.out)
+ for j, v := range pt {
+ if v != tt.in[j] {
+ t.Errorf("Cipher.Decrypt, test vector #%d: plain-text[%d] = %#x, expected %#x", i, j, v, tt.in[j])
+ break
+ }
+ }
+ }
+}
+
+func TestSaltedCipherKeyLength(t *testing.T) {
+ if _, err := NewSaltedCipher(nil, []byte{'a'}); err != KeySizeError(0) {
+ t.Errorf("NewSaltedCipher with short key, gave error %#v, expected %#v", err, KeySizeError(0))
+ }
+
+ // A 57-byte key. One over the typical blowfish restriction.
+ key := []byte("012345678901234567890123456789012345678901234567890123456")
+ if _, err := NewSaltedCipher(key, []byte{'a'}); err != nil {
+ t.Errorf("NewSaltedCipher with long key, gave error %#v", err)
+ }
+}
+
+// Test vectors generated with Blowfish from OpenSSH.
+var saltedVectors = [][8]byte{
+ {0x0c, 0x82, 0x3b, 0x7b, 0x8d, 0x01, 0x4b, 0x7e},
+ {0xd1, 0xe1, 0x93, 0xf0, 0x70, 0xa6, 0xdb, 0x12},
+ {0xfc, 0x5e, 0xba, 0xde, 0xcb, 0xf8, 0x59, 0xad},
+ {0x8a, 0x0c, 0x76, 0xe7, 0xdd, 0x2c, 0xd3, 0xa8},
+ {0x2c, 0xcb, 0x7b, 0xee, 0xac, 0x7b, 0x7f, 0xf8},
+ {0xbb, 0xf6, 0x30, 0x6f, 0xe1, 0x5d, 0x62, 0xbf},
+ {0x97, 0x1e, 0xc1, 0x3d, 0x3d, 0xe0, 0x11, 0xe9},
+ {0x06, 0xd7, 0x4d, 0xb1, 0x80, 0xa3, 0xb1, 0x38},
+ {0x67, 0xa1, 0xa9, 0x75, 0x0e, 0x5b, 0xc6, 0xb4},
+ {0x51, 0x0f, 0x33, 0x0e, 0x4f, 0x67, 0xd2, 0x0c},
+ {0xf1, 0x73, 0x7e, 0xd8, 0x44, 0xea, 0xdb, 0xe5},
+ {0x14, 0x0e, 0x16, 0xce, 0x7f, 0x4a, 0x9c, 0x7b},
+ {0x4b, 0xfe, 0x43, 0xfd, 0xbf, 0x36, 0x04, 0x47},
+ {0xb1, 0xeb, 0x3e, 0x15, 0x36, 0xa7, 0xbb, 0xe2},
+ {0x6d, 0x0b, 0x41, 0xdd, 0x00, 0x98, 0x0b, 0x19},
+ {0xd3, 0xce, 0x45, 0xce, 0x1d, 0x56, 0xb7, 0xfc},
+ {0xd9, 0xf0, 0xfd, 0xda, 0xc0, 0x23, 0xb7, 0x93},
+ {0x4c, 0x6f, 0xa1, 0xe4, 0x0c, 0xa8, 0xca, 0x57},
+ {0xe6, 0x2f, 0x28, 0xa7, 0x0c, 0x94, 0x0d, 0x08},
+ {0x8f, 0xe3, 0xf0, 0xb6, 0x29, 0xe3, 0x44, 0x03},
+ {0xff, 0x98, 0xdd, 0x04, 0x45, 0xb4, 0x6d, 0x1f},
+ {0x9e, 0x45, 0x4d, 0x18, 0x40, 0x53, 0xdb, 0xef},
+ {0xb7, 0x3b, 0xef, 0x29, 0xbe, 0xa8, 0x13, 0x71},
+ {0x02, 0x54, 0x55, 0x41, 0x8e, 0x04, 0xfc, 0xad},
+ {0x6a, 0x0a, 0xee, 0x7c, 0x10, 0xd9, 0x19, 0xfe},
+ {0x0a, 0x22, 0xd9, 0x41, 0xcc, 0x23, 0x87, 0x13},
+ {0x6e, 0xff, 0x1f, 0xff, 0x36, 0x17, 0x9c, 0xbe},
+ {0x79, 0xad, 0xb7, 0x40, 0xf4, 0x9f, 0x51, 0xa6},
+ {0x97, 0x81, 0x99, 0xa4, 0xde, 0x9e, 0x9f, 0xb6},
+ {0x12, 0x19, 0x7a, 0x28, 0xd0, 0xdc, 0xcc, 0x92},
+ {0x81, 0xda, 0x60, 0x1e, 0x0e, 0xdd, 0x65, 0x56},
+ {0x7d, 0x76, 0x20, 0xb2, 0x73, 0xc9, 0x9e, 0xee},
+}
+
+func TestSaltedCipher(t *testing.T) {
+ var key, salt [32]byte
+ for i := range key {
+ key[i] = byte(i)
+ salt[i] = byte(i + 32)
+ }
+ for i, v := range saltedVectors {
+ c, err := NewSaltedCipher(key[:], salt[:i])
+ if err != nil {
+ t.Fatal(err)
+ }
+ var buf [8]byte
+ c.Encrypt(buf[:], buf[:])
+ if v != buf {
+ t.Errorf("%d: expected %x, got %x", i, v, buf)
+ }
+ }
+}
+
+func BenchmarkExpandKeyWithSalt(b *testing.B) {
+ key := make([]byte, 32)
+ salt := make([]byte, 16)
+ c, _ := NewCipher(key)
+ for i := 0; i < b.N; i++ {
+ expandKeyWithSalt(key, salt, c)
+ }
+}
+
+func BenchmarkExpandKey(b *testing.B) {
+ key := make([]byte, 32)
+ c, _ := NewCipher(key)
+ for i := 0; i < b.N; i++ {
+ ExpandKey(key, c)
+ }
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/blowfish/cipher.go b/Godeps/_workspace/src/golang.org/x/crypto/blowfish/cipher.go
new file mode 100644
index 0000000..5019658
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/blowfish/cipher.go
@@ -0,0 +1,91 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm.
+package blowfish
+
+// The code is a port of Bruce Schneier's C implementation.
+// See http://www.schneier.com/blowfish.html.
+
+import "strconv"
+
+// The Blowfish block size in bytes.
+const BlockSize = 8
+
+// A Cipher is an instance of Blowfish encryption using a particular key.
+type Cipher struct {
+ p [18]uint32
+ s0, s1, s2, s3 [256]uint32
+}
+
+type KeySizeError int
+
+func (k KeySizeError) Error() string {
+ return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k))
+}
+
+// NewCipher creates and returns a Cipher.
+// The key argument should be the Blowfish key, from 1 to 56 bytes.
+func NewCipher(key []byte) (*Cipher, error) {
+ var result Cipher
+ if k := len(key); k < 1 || k > 56 {
+ return nil, KeySizeError(k)
+ }
+ initCipher(&result)
+ ExpandKey(key, &result)
+ return &result, nil
+}
+
+// NewSaltedCipher creates a returns a Cipher that folds a salt into its key
+// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is
+// sufficient and desirable. For bcrypt compatiblity, the key can be over 56
+// bytes.
+func NewSaltedCipher(key, salt []byte) (*Cipher, error) {
+ if len(salt) == 0 {
+ return NewCipher(key)
+ }
+ var result Cipher
+ if k := len(key); k < 1 {
+ return nil, KeySizeError(k)
+ }
+ initCipher(&result)
+ expandKeyWithSalt(key, salt, &result)
+ return &result, nil
+}
+
+// BlockSize returns the Blowfish block size, 8 bytes.
+// It is necessary to satisfy the Block interface in the
+// package "crypto/cipher".
+func (c *Cipher) BlockSize() int { return BlockSize }
+
+// Encrypt encrypts the 8-byte buffer src using the key k
+// and stores the result in dst.
+// Note that for amounts of data larger than a block,
+// it is not safe to just call Encrypt on successive blocks;
+// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
+func (c *Cipher) Encrypt(dst, src []byte) {
+ l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+ r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+ l, r = encryptBlock(l, r, c)
+ dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
+ dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r)
+}
+
+// Decrypt decrypts the 8-byte buffer src using the key k
+// and stores the result in dst.
+func (c *Cipher) Decrypt(dst, src []byte) {
+ l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+ r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+ l, r = decryptBlock(l, r, c)
+ dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
+ dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r)
+}
+
+func initCipher(c *Cipher) {
+ copy(c.p[0:], p[0:])
+ copy(c.s0[0:], s0[0:])
+ copy(c.s1[0:], s1[0:])
+ copy(c.s2[0:], s2[0:])
+ copy(c.s3[0:], s3[0:])
+}
diff --git a/Godeps/_workspace/src/golang.org/x/crypto/blowfish/const.go b/Godeps/_workspace/src/golang.org/x/crypto/blowfish/const.go
new file mode 100644
index 0000000..8c5ee4c
--- /dev/null
+++ b/Godeps/_workspace/src/golang.org/x/crypto/blowfish/const.go
@@ -0,0 +1,199 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The startup permutation array and substitution boxes.
+// They are the hexadecimal digits of PI; see:
+// http://www.schneier.com/code/constants.txt.
+
+package blowfish
+
+var s0 = [256]uint32{
+ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,
+ 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+ 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,
+ 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+ 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e,
+ 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+ 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,
+ 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+ 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,
+ 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+ 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1,
+ 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+ 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,
+ 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+ 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,
+ 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+ 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706,
+ 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+ 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,
+ 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+ 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,
+ 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+ 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a,
+ 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+ 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,
+ 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+ 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,
+ 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+ 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33,
+ 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+ 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,
+ 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+ 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,
+ 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+ 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705,
+ 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+ 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,
+ 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+ 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,
+ 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+ 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f,
+ 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+ 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
+}
+
+var s1 = [256]uint32{
+ 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d,
+ 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+ 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65,
+ 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+ 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9,
+ 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+ 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,
+ 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+ 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc,
+ 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+ 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908,
+ 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+ 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124,
+ 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+ 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908,
+ 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+ 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,
+ 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+ 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa,
+ 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+ 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d,
+ 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+ 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5,
+ 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+ 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96,
+ 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+ 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,
+ 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+ 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77,
+ 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+ 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054,
+ 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+ 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea,
+ 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+ 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646,
+ 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+ 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,
+ 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+ 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e,
+ 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+ 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd,
+ 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+ 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
+}
+
+var s2 = [256]uint32{
+ 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7,
+ 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+ 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af,
+ 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+ 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4,
+ 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+ 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec,
+ 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+ 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332,
+ 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+ 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58,
+ 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+ 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22,
+ 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+ 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60,
+ 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+ 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99,
+ 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+ 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74,
+ 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+ 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3,
+ 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+ 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979,
+ 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+ 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa,
+ 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+ 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086,
+ 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+ 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24,
+ 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+ 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84,
+ 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+ 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09,
+ 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+ 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe,
+ 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+ 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0,
+ 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+ 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188,
+ 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+ 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8,
+ 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+ 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
+}
+
+var s3 = [256]uint32{
+ 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,
+ 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+ 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79,
+ 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+ 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,
+ 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+ 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,
+ 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+ 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797,
+ 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+ 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,
+ 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+ 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,
+ 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+ 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5,
+ 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+ 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,
+ 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+ 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,
+ 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+ 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb,
+ 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+ 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,
+ 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+ 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,
+ 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+ 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a,
+ 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+ 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,
+ 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+ 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,
+ 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+ 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e,
+ 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+ 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,
+ 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+ 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,
+ 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+ 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3,
+ 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+ 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,
+ 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+ 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
+}
+
+var p = [18]uint32{
+ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
+ 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+ 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b,
+}
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..f9d7bdb
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: bactdb serve
diff --git a/cmd/bactdb/bactdb.go b/bactdb.go
similarity index 100%
rename from cmd/bactdb/bactdb.go
rename to bactdb.go