database

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2015 License: MIT Imports: 9 Imported by: 0

README

go-database

Provides a wrapper around the go postgres database driver to add migration management, connection persistence, and transaction composition.

This was extracted from graciouseloise/api.

Configuration

The library expects two environment variables to be set:

  • DATABASE_URL: used for connections created with GetDB().
  • TEST_DATABASE_URL: used for connections created with GetTestDB().

Migrations

One folder in your project should contain only raw SQL scripts.

bootstrap.sql is a special file that is loaded once onto a clean database. It sets up the meta schema for tracking migrations that have been applied.

Other files in this directory will be applied to the database in order determined by their filename. The convention is to name the file with a version prefix that is zero-padded to 3 digits long (e.g. 015_index_pizza_size.sql)

Hint

While developing a new migration, it's often easiest to guard your migration file with a BEGIN statement at the start of the file and a ROLLBACK at the end. This will let you ensure your syntax is OK and will throw the migration away at the end. Once you have it working with no errors, just remove the BEGIN and ROLLBACK lines.

You can also skip updating the migration log by just piping these scripts directly to your DB via psql (e.g. psql mydb < sql/012_drop_foo.sql)

Notes

Skipping migration numbers or defining the same version more than once are both treated as fatal errors to the migration script.

Code Status

Circle CI

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Check

func Check(err error, extraInfo ...string)

Check is shorthand for dying on errors, but see if we can cast these errors to pq.Errors for more info

Types

type DB

type DB struct {
	*sql.DB
}

DB wraps sql.DB FIXME Pointer type nesting is messy here...

func GetDB

func GetDB() *DB

GetDB returns a db connection, reusing the same connection if one already exists.

func (*DB) Begin

func (db *DB) Begin() (*Tx, error)

Begin starts a transaction

func (*DB) GetCurrentVersion

func (db *DB) GetCurrentVersion() int

GetCurrentVersion returns the current schema version as an integer, or nil with an error

func (*DB) Migrate

func (db *DB) Migrate(migrationDir string, dryrun bool)

Migrate ensures the db supports migration, loads bootstrap info, and applies as many migrations as it can in order until there are no migrations left to run, or a migration produces an error.

func (DB) Transaction

func (db DB) Transaction(f func(*Tx) error) (err error)

Transaction runs your function in a new transaction

Example
err := GetTestDB().Transaction(func(tx *Tx) error {
	q := `CREATE TABLE bogus_transaction_test (id integer)`
	_, err := tx.Exec(q)
	if err != nil {
		return err
	}

	q = `INSERT INTO bogus_transaction_test VALUES (42)`
	_, err = tx.Exec(q)
	if err != nil {
		return err
	}

	q = `SELECT id FROM bogus_transaction_test`
	r := tx.QueryRow(q)
	var i int64
	err = r.Scan(&i)
	if err != nil {
		return err
	}

	if i != 42 {
		return errors.New("i != 42")
	}

	return errors.New("OK. I've had my fun, but I don't really want that table")
})

fmt.Println(err)

err = GetTestDB().Transaction(func(tx *Tx) error {
	q := `SELECT id FROM bogus_transaction_test`
	r := tx.QueryRow(q)
	var i int64
	err = r.Scan(&i)
	return err
})
fmt.Println(err)
Output:

OK. I've had my fun, but I don't really want that table
pq: relation "bogus_transaction_test" does not exist
Example (DestructiveOperationRollback)
// Create table with one value
err := GetTestDB().Transaction(func(tx *Tx) error {
	q := `CREATE TABLE bogus_transaction_test (id integer)`
	_, err := tx.Exec(q)
	if err != nil {
		return err
	}

	q = `INSERT INTO bogus_transaction_test VALUES (42)`
	_, err = tx.Exec(q)
	if err != nil {
		return err
	}
	return nil
})

if err != nil {
	fmt.Println(err)
	return
}
fmt.Println("Table created")

// Truncate table in transaction that rolls back
err = GetTestDB().Transaction(func(tx *Tx) error {
	q := `TRUNCATE TABLE bogus_transaction_test`
	_, err = tx.Exec(q)
	if err != nil {
		fmt.Println(err)
	}
	return errors.New("Truncate rolled back")
})
fmt.Println(err)

// Verify data is still there
err = GetTestDB().Transaction(func(tx *Tx) error {
	q := `SELECT id FROM bogus_transaction_test`
	r := tx.QueryRow(q)
	var i int64
	err = r.Scan(&i)
	if err != nil {
		return err
	}

	if i != 42 {
		return errors.New("i != 42")
	}

	return nil
})

if err != nil {
	fmt.Println(err)
} else {
	fmt.Println("Data still exists in table")
}

// Clean up
err = GetTestDB().Transaction(func(tx *Tx) error {
	q := `DROP TABLE bogus_transaction_test`
	_, err = tx.Exec(q)
	if err != nil {
		fmt.Println(err)
	}
	return nil
})
if err != nil {
	fmt.Println(err)
}
Output:

Table created
Truncate rolled back
Data still exists in table

type OrderDirection

type OrderDirection int

OrderDirection ...

const (
	// OrderAscending ...
	OrderAscending OrderDirection = iota
	// OrderDescending ...
	OrderDescending
)

func (OrderDirection) String

func (o OrderDirection) String() (out string)

String ...

type Scanner added in v1.0.1

type Scanner interface {
	Scan(dest ...interface{}) error
}

Scanner is a stand-in for sql.Row or sql.Rows. It allows for writing model methods that handle the call to Scan to populate the struct.

Example
package main

import (
	"errors"
	"fmt"
)

type Model struct {
	ID   int
	Name string
}

func (m *Model) scan(s Scanner) error {
	return s.Scan(&m.ID, &m.Name)
}

func main() {
	err := GetTestDB().Transaction(func(tx *Tx) error {
		q := `CREATE TABLE models (id integer, name text)`
		_, err := tx.Exec(q)
		if err != nil {
			return err
		}

		q = `INSERT INTO models VALUES (23, 'skidoo')`
		_, err = tx.Exec(q)
		if err != nil {
			return err
		}

		q = `SELECT id, name FROM models`
		r := tx.QueryRow(q)
		var m Model
		err = m.scan(r)
		if err != nil {
			return err
		}

		if m.ID != 23 {
			return errors.New("m.ID != 23")
		}
		if m.Name != "skidoo" {
			return errors.New("m.Name != skidoo")
		}

		return errors.New("Rolling back.")
	})
	fmt.Println(err)

}
Output:

Rolling back.

type TestDB

type TestDB struct {
	*DB
}

TestDB is the same as DB, but provides clean up methods that should never be used in production.

func GetTestDB

func GetTestDB() *TestDB

GetTestDB returns a connection to the TEST_DATABASE_URL, reusing the same connection if one already exists.

func (*TestDB) TruncateTable

func (db *TestDB) TruncateTable(tableName string)

TruncateTable does horrible things which is why it's only allowed on test databases

type Transacter

type Transacter interface {
	Transaction(func(*Tx) error) error
}

Transacter interface allows us to specify that we don't care if it's a DB or a Tx

type Tx

type Tx struct {
	*sql.Tx
}

Tx wraps sql.Tx

func (Tx) Transaction

func (tx Tx) Transaction(f func(*Tx) error) error

Transaction runs your function in the current transaction allowing you to ignore the difference between a DB and a Tx

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL