hood

package module
v0.0.0-...-89981c9 Latest Latest
Warning

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

Go to latest
Published: Jan 8, 2013 License: MIT Imports: 10 Imported by: 0

README

Index

Overview

Hood is a database agnostic ORM for Go developed by @eaignr. It was written with following points in mind:

  • Chainable API
  • Transaction support
  • Model validations
  • Model event hooks
  • Database dialect interface
  • No implicit fields
  • Clean and testable codebase

Dialects currently supported

** not registered by default, requires some packages installed on the system

Adding a dialect is simple. Just create a new file named <dialect_name>.go and the corresponding struct type, and mixin the Base dialect. Then implement the methods that are specific to the new dialect (for an example see postgres.go).

Documentation

You can find the documentation over at GoDoc

Opening a Database

If the dialect is registered, you can open the database directly using

hd, err := hood.Open("postgres", "user=<username> dbname=<database>")

or you can pass an existing database and dialect to hood.New(*sql.DB, hood.Dialect)

hd := hood.New(db, NewPostgres())

Schemas

Schemas can be declared using the following syntax (only for demonstration purposes, would not produce valid SQL since it has 2 primary keys)

type Person struct {
	// Auto-incrementing int field 'id'
	Id hood.Id

	// Custom primary key field 'first_name', with presence validation
	FirstName string `sql:"pk" validate:"presence"`

	// Varchar field 'last_name' with size 128, NOT NULL
	LastName hood.VarChar `sql:"size(128),notnull"`

	// Varchar field 'tag' with size 255, default value 'customer'
	Tag hood.VarChar `sql:"default('customer')"`

	// You can also combine tags, default value 'orange'
	CombinedTags hood.VarChar `sql:"size(128),default('orange')"`
	Updated      time.Time    // timestamp field 'updated'
	Data         []byte       // data field 'data'
	IsAdmin      bool         // boolean field 'is_admin'
	Notes        string       // text field 'notes'

	// Validates number range
	Balance int `validate:"range(10:20)"`

	// ... and other built in types (int, uint, float...)
}

Schema creation is completely optional, you can use any other tool you like.

The following built in field properties are defined (via sql: tag):

  • pk the field is a primary key
  • notnull the field must be NOT NULL
  • size(x) the field must have the specified size, e.g. for varchar size(128)
  • default(x) the field has the specified default value, e.g. default(5) or default('orange')

Migrations

To use migrations, you first have to install the hood tool. To do that run the following:

go get github.com/eaigner/hood
cd $GOPATH/src/github.com/eaigner/hood
./install.sh

Assuming you have your $GOPATH/bin directory in your PATH, you can now invoke the hood tool with hood. Before we can use migrations we have to create a database configuration file first. To do this type

hood create:config

This command will create a db/config.json file relative to your current directory. It will look something like this:

{
  "development": {
    "driver": "",
    "source": ""
  },
  "production": {
    "driver": "",
    "source": ""
  },
  "test": {
    "driver": "",
    "source": ""
  }
}

Populate it with your database credentials. The driver and source fields are the strings you would pass to the sql.Open(2) function. Now hood knows about our database, so let's create our first migration with

hood create:migration CreateUserTable

This command creates a new migration in db/migrations/<timestamp>_CreateUserTable.go. Next we have to populate the generated migrations Up and Down methods like so:

func (m *M) CreateUserTable_1357605106_Up(hood *hood.Hood) {
	// users is the schema struct for the table, see the schema
	// section for more info on this topic
	hood.CreateTable(&users{})
}

The passed in hood instance is a transaction that will be committed after this method.

Now we can run migrations with

hood db:migrate

and roll back with

hood db:rollback

If you want to run a environment configuration other than development, you have to set an environment variable first like this:

export HOOD_ENV=production

Validation

Besides the sql: struct tag, you can specify a validate: tag for model validation:

  • presence validates that a field is set
  • len(min:max) validates that a string or VarChar field’s length lies within the specified range
    • len(min:) validates that it has the specified min length,
    • len(:max) or max length
  • range(min:max) validates that an int value lies in the specific range
    • range(min:) validates that it has the specified min value,
    • range(:max) or max value

You can also define multiple validations on one field, e.g. validate:"len(:12),presence"

For more complex validations you can use custom validation methods. The methods are added to the schema and must start with Validate and return an error.

For example:

func (u *User) ValidateUsername() error {
	rx := regexp.MustCompile(`[a-z0-9]+`)
	if !rx.MatchString(u.Name) {
		return NewValidationError(1, "username contains invalid characters")
	}
	return nil
}

Hooks

You can add hooks to a model to run on a specific action like so:

func (u *User) BeforeUpdate() error {
	u.Updated = time.Now()
	return nil
}

If the hook returns an error on a Before- action it is not performed!

The following hooks are defined:

  • Before/AfterSave
  • Before/AfterInsert
  • Before/AfterUpdate
  • Before/AfterDelete

Basic Example


package main

import (
	"hood"
)

func main() {
	// Open a DB connection, use New() alternatively for unregistered dialects
	hd, err := hood.Open("postgres", "user=hood dbname=hood_test sslmode=disable")
	if err != nil {
		panic(err)
	}

	// Create a table
	type Fruit struct {
		Id    hood.Id
		Name  string `validate:"presence"`
		Color string
	}

	err = hd.CreateTable(&Fruit{})
	if err != nil {
		panic(err)
	}

	fruits := []Fruit{
		Fruit{Name: "banana", Color: "yellow"},
		Fruit{Name: "apple", Color: "red"},
		Fruit{Name: "grapefruit", Color: "yellow"},
		Fruit{Name: "grape", Color: "green"},
		Fruit{Name: "pear", Color: "yellow"},
	}

	// Start a transaction
	tx := hd.Begin()

	ids, err := tx.SaveAll(&fruits)
	if err != nil {
		panic(err)
	}

	fmt.Println("inserted ids:", ids) // [1 2 3 4 5]

	// Commit changes
	err = tx.Commit()
	if err != nil {
		panic(err)
	}

	// Ids are automatically updated
	if fruits[0].Id != 1 || fruits[1].Id != 2 || fruits[2].Id != 3 {
		panic("id not set")
	}

	// If an id is already set, a call to save will result in an update
	fruits[0].Color = "green"

	ids, err = hd.SaveAll(&fruits)
	if err != nil {
		panic(err)
	}

	fmt.Println("updated ids:", ids) // [1 2 3 4 5]

	if fruits[0].Id != 1 || fruits[1].Id != 2 || fruits[2].Id != 3 {
		panic("id not set")
	}

	// Let's try to save a row that does not satisfy the required validations
	_, err = hd.Save(&Fruit{})
	if err == nil || err.Error() != "value not set" {
		panic("does not satisfy validations, should not save")
	}

	// Find
	//
	// The markers are db agnostic, so you can always use '?'
	// e.g. in Postgres they are replaced with $1, $2, ...
	var results []Fruit
	err = hd.Where("color = ?", "green").OrderBy("name").Limit(1).Find(&results)
	if err != nil {
		panic(err)
	}

	fmt.Println("results:", results) // [{1 banana green}]

	// Delete
	ids, err = hd.DeleteAll(&results)
	if err != nil {
		panic(err)
	}

	fmt.Println("deleted ids:", ids) // [1]

	results = nil
	err = hd.Find(&results)
	if err != nil {
		panic(err)
	}

	fmt.Println("results:", results) // [{2 apple red} {3 grapefruit yellow} {4 grape green} {5 pear yellow}]

	// Drop
	hd.DropTable(&Fruit{})
}

Contributors

Documentation

Overview

Package hood provides a database agnostic, transactional ORM for the sql package

Index

Constants

View Source
const (
	ValidationErrorValueNotSet = (1<<16 + iota)
	ValidationErrorValueTooSmall
	ValidationErrorValueTooBig
	ValidationErrorValueTooShort
	ValidationErrorValueTooLong
)

Variables

This section is empty.

Functions

func NewValidationError

func NewValidationError(id int, text string) error

NewValidationError returns a new validation error with the specified id and text. The id's purpose is to distinguish different validation error types. Built-in validation error ids start at 65536, so you should keep your custom ids under that value.

func RegisterDialect

func RegisterDialect(name string, dialect Dialect)

RegisterDialect registers a new dialect using the specified name and dialect.

Types

type Base

type Base struct {
	Dialect Dialect
}

func (*Base) AddColumn

func (d *Base) AddColumn(hood *Hood, table, column string, typ interface{}, size int) error

func (*Base) AddColumnSql

func (d *Base) AddColumnSql(table, column string, typ interface{}, size int) string

func (*Base) ChangeColumn

func (d *Base) ChangeColumn(hood *Hood, table, column string, typ interface{}, size int) error

func (*Base) ChangeColumnSql

func (d *Base) ChangeColumnSql(table, column string, typ interface{}, size int) string

func (*Base) CreateIndex

func (d *Base) CreateIndex(hood *Hood, table, column string, unique bool) error

func (*Base) CreateIndexSql

func (d *Base) CreateIndexSql(table, column string, unique bool) string

func (*Base) CreateTable

func (d *Base) CreateTable(hood *Hood, model *Model) error

func (*Base) CreateTableIfNotExists

func (d *Base) CreateTableIfNotExists(hood *Hood, model *Model) error

func (*Base) CreateTableSql

func (d *Base) CreateTableSql(model *Model, ifNotExists bool) string

func (*Base) Delete

func (d *Base) Delete(hood *Hood, model *Model) (Id, error)

func (*Base) DeleteSql

func (d *Base) DeleteSql(model *Model) (string, []interface{})

func (*Base) DropColumn

func (d *Base) DropColumn(hood *Hood, table, column string) error

func (*Base) DropColumnSql

func (d *Base) DropColumnSql(table, column string) string

func (*Base) DropIndex

func (d *Base) DropIndex(hood *Hood, column string) error

func (*Base) DropIndexSql

func (d *Base) DropIndexSql(column string) string

func (*Base) DropTable

func (d *Base) DropTable(hood *Hood, table string) error

func (*Base) DropTableIfExists

func (d *Base) DropTableIfExists(hood *Hood, table string) error

func (*Base) DropTableSql

func (d *Base) DropTableSql(table string, ifExists bool) string

func (*Base) Insert

func (d *Base) Insert(hood *Hood, model *Model) (Id, error)

func (*Base) InsertSql

func (d *Base) InsertSql(model *Model) (string, []interface{})

func (*Base) KeywordAutoIncrement

func (d *Base) KeywordAutoIncrement() string

func (*Base) KeywordDefault

func (d *Base) KeywordDefault(s string) string

func (*Base) KeywordNotNull

func (d *Base) KeywordNotNull() string

func (*Base) KeywordPrimaryKey

func (d *Base) KeywordPrimaryKey() string

func (*Base) NextMarker

func (d *Base) NextMarker(pos *int) string

func (*Base) QuerySql

func (d *Base) QuerySql(hood *Hood) (string, []interface{})

func (*Base) RenameColumn

func (d *Base) RenameColumn(hood *Hood, table, from, to string) error

func (*Base) RenameColumnSql

func (d *Base) RenameColumnSql(table, from, to string) string

func (*Base) RenameTable

func (d *Base) RenameTable(hood *Hood, from, to string) error

func (*Base) RenameTableSql

func (d *Base) RenameTableSql(from, to string) string

func (*Base) SqlType

func (d *Base) SqlType(f interface{}, size int) string

func (*Base) Update

func (d *Base) Update(hood *Hood, model *Model) (Id, error)

func (*Base) UpdateSql

func (d *Base) UpdateSql(model *Model) (string, []interface{})

func (*Base) ValueToField

func (d *Base) ValueToField(value reflect.Value, field reflect.Value) error

type Dialect

type Dialect interface {
	// Marker returns the dialect specific markers for prepared statements,
	// for instance $1, $2, ... and increments the position by one.
	// The position starts at 0.
	NextMarker(pos *int) string

	// SqlType returns the SQL type for the provided interface type. The size
	// parameter delcares the data size for the column (e.g. for VARCHARs).
	SqlType(f interface{}, size int) string

	// ValueToField converts from an SQL Value to the coresponding interface Value.
	// It is the opposite of SqlType, in a sense.
	// For example: time.Time objects needs to be marshalled back and forth
	// as Strings for databases that don't have a native "time" type.
	ValueToField(value reflect.Value, field reflect.Value) error

	// QuerySql returns the resulting query sql and attributes.
	QuerySql(hood *Hood) (sql string, args []interface{})

	// Insert inserts the values in model and returns the inserted rows Id.
	Insert(hood *Hood, model *Model) (Id, error)

	// InsertSql returns the sql for inserting the passed model.
	InsertSql(model *Model) (sql string, args []interface{})

	// Update updates the values in the specified model and returns the
	// updated rows Id.
	Update(hood *Hood, model *Model) (Id, error)

	// UpdateSql returns the sql for updating the specified model.
	UpdateSql(model *Model) (string, []interface{})

	// Delete drops the row matching the primary key of model and returns the affected Id.
	Delete(hood *Hood, model *Model) (Id, error)

	// DeleteSql returns the sql for deleting the row matching model's primary key.
	DeleteSql(model *Model) (string, []interface{})

	// CreateTable creates the table specified in model.
	CreateTable(hood *Hood, model *Model) error

	// CreateTableIfNotExists creates the table specified in model if it does not exist.
	CreateTableIfNotExists(hood *Hood, model *Model) error

	// CreateTableSql returns the sql for creating a table.
	CreateTableSql(model *Model, ifNotExists bool) string

	// DropTable drops the specified table.
	DropTable(hood *Hood, table string) error

	// DropTableIfExists drops the table if it exists.
	DropTableIfExists(hood *Hood, table string) error

	// DropTableSql returns the sql for dropping the specified table.
	DropTableSql(table string, ifExists bool) string

	// RenameTable renames the specified table.
	RenameTable(hood *Hood, from, to string) error

	// RenameTableSql returns the sql for renaming the specified table.
	RenameTableSql(from, to string) string

	// AddColumn adds the columns to the corresponding table.
	AddColumn(hood *Hood, table, column string, typ interface{}, size int) error

	// AddColumnSql returns the sql for adding the specified column in table.
	AddColumnSql(table, column string, typ interface{}, size int) string

	// RenameColumn renames a table column in the specified table.
	RenameColumn(hood *Hood, table, from, to string) error

	// RenameColumnSql returns the sql for renaming the specified column in table.
	RenameColumnSql(table, from, to string) string

	// ChangeColumn changes the data type of the specified column.
	ChangeColumn(hood *Hood, table, column string, typ interface{}, size int) error

	// ChangeColumnSql returns the sql for changing the column data type.
	ChangeColumnSql(table, column string, typ interface{}, size int) string

	// DropColumn removes the specified column.
	DropColumn(hood *Hood, table, column string) error

	// DropColumnSql returns the sql for removing the column.
	DropColumnSql(table, column string) string

	// CreateIndex creates an index on the specified column.
	CreateIndex(hood *Hood, table, column string, unique bool) error

	// CreateIndexSql returns the sql for creating an index on the specified column.
	CreateIndexSql(table, column string, unique bool) string

	// DropIndex drops the index for the specified column.
	DropIndex(hood *Hood, column string) error

	// DropIndexSql returns the sql for dropping the index on the specified column.
	DropIndexSql(column string) string

	// KeywordNotNull returns the dialect specific keyword for 'NOT NULL'.
	KeywordNotNull() string

	// KeywordDefault returns the dialect specific keyword for 'DEFAULT'.
	KeywordDefault(s string) string

	// KeywordPrimaryKey returns the dialect specific keyword for 'PRIMARY KEY'.
	KeywordPrimaryKey() string

	// KeywordAutoIncrement returns the dialect specific keyword for 'AUTO_INCREMENT'.
	KeywordAutoIncrement() string
}

func NewPostgres

func NewPostgres() Dialect

func NewSqlite3

func NewSqlite3() Dialect

type Field

type Field struct {
	Name         string            // Column name
	Value        interface{}       // Value
	SqlTags      map[string]string // The sql struct tags for this field
	ValidateTags map[string]string // The validate struct tags for this field
}

Field represents a schema field.

func (*Field) Default

func (field *Field) Default() string

Default returns the default value for the field

func (*Field) Int

func (field *Field) Int() (int64, bool)

Int returns the field int value and a bool flag indication if the conversion was successful

func (*Field) NotNull

func (field *Field) NotNull() bool

NotNull tests if the field is declared as NOT NULL

func (*Field) PrimaryKey

func (field *Field) PrimaryKey() bool

PrimaryKey tests if the field is declared using the sql tag "pk" or is of type Id

func (*Field) Size

func (field *Field) Size() int

Size returns the field size, e.g. for varchars

func (*Field) String

func (field *Field) String() (string, bool)

String returns the field string value and a bool flag indicating if the conversion was successful

func (*Field) Validate

func (field *Field) Validate() error

Validate tests if the field conforms to it's validation constraints specified int the "validate" struct tag

func (*Field) Zero

func (field *Field) Zero() bool

Zero tests wether or not the field is set

type Hood

type Hood struct {
	Db      *sql.DB
	Dialect Dialect
	Log     bool
	// contains filtered or unexported fields
}

Hood is an ORM handle.

func New

func New(database *sql.DB, dialect Dialect) *Hood

New creates a new Hood using the specified DB and dialect.

func Open

func Open(driverName, dataSourceName string) (*Hood, error)

Open opens a new database connection using the specified driver and data source name. It matches the sql.Open method signature.

func (*Hood) AddColumns

func (hood *Hood) AddColumns(schema interface{}) error

AddColumns adds the columns in the specified schema to the table.

func (*Hood) Begin

func (hood *Hood) Begin() *Hood

Begin starts a new transaction and returns a copy of Hood. You have to call subsequent methods on the newly returned object.

func (*Hood) ChangeColumn

func (hood *Hood) ChangeColumn(table, column interface{}) error

ChangeColumn changes the data type of the specified column.

func (*Hood) Commit

func (hood *Hood) Commit() error

Commit commits a started transaction.

func (*Hood) CreateIndex

func (hood *Hood) CreateIndex(table interface{}, column string, unique bool) error

func (*Hood) CreateTable

func (hood *Hood) CreateTable(table interface{}) error

CreateTable creates a new table based on the provided schema.

func (*Hood) CreateTableIfNotExists

func (hood *Hood) CreateTableIfNotExists(table interface{}) error

CreateTableIfNotExists creates a new table based on the provided schema if it does not exist yet.

func (*Hood) Delete

func (hood *Hood) Delete(f interface{}) (Id, error)

Delete deletes the row matching the specified structs Id.

func (*Hood) DeleteAll

func (hood *Hood) DeleteAll(f interface{}) ([]Id, error)

DeleteAll deletes the rows matching the specified struct slice Ids.

func (*Hood) DropIndex

func (hood *Hood) DropIndex(column string) error

func (*Hood) DropTable

func (hood *Hood) DropTable(table interface{}) error

DropTable drops the table matching the provided table name.

func (*Hood) DropTableIfExists

func (hood *Hood) DropTableIfExists(table interface{}) error

DropTableIfExists drops the table matching the provided table name if it exists.

func (*Hood) Exec

func (hood *Hood) Exec(query string, args ...interface{}) (sql.Result, error)

Exec executes a raw sql query.

func (*Hood) Find

func (hood *Hood) Find(out interface{}) error

Find performs a find using the previously specified query. If no explicit SELECT clause was specified earlier, the select is inferred from the passed interface type.

func (*Hood) GroupBy

func (hood *Hood) GroupBy(key string) *Hood

GroupBy adds a GROUP BY clause to the query.

func (*Hood) Having

func (hood *Hood) Having(condition string, args ...interface{}) *Hood

Having adds a HAVING clause to the query.

func (*Hood) Join

func (hood *Hood) Join(op, table, condition string) *Hood

Join performs a JOIN on tables, for example

Join("INNER JOIN", "users", "orders.user_id == users.id")

func (*Hood) Limit

func (hood *Hood) Limit(limit int) *Hood

Limit adds a LIMIT clause to the query.

func (*Hood) Offset

func (hood *Hood) Offset(offset int) *Hood

Offset adds an OFFSET clause to the query.

func (*Hood) OrderBy

func (hood *Hood) OrderBy(key string) *Hood

OrderBy adds an ORDER BY clause to the query.

func (*Hood) QueryRow

func (hood *Hood) QueryRow(query string, args ...interface{}) *sql.Row

QueryRow executes a query that is expected to return at most one row. QueryRow always return a non-nil value. Errors are deferred until Row's Scan method is called.

func (*Hood) RemoveColumn

func (hood *Hood) RemoveColumn(table, column interface{}) error

func (*Hood) RenameColumn

func (hood *Hood) RenameColumn(table interface{}, from, to string) error

RenameColumn renames the column in the specified table.

func (*Hood) RenameTable

func (hood *Hood) RenameTable(from, to interface{}) error

RenameTable renames a table. The arguments can either be a schema definition or plain strings.

func (*Hood) Reset

func (hood *Hood) Reset()

Reset resets the internal state.

func (*Hood) Rollback

func (hood *Hood) Rollback() error

Rollback rolls back a started transaction.

func (*Hood) Save

func (hood *Hood) Save(f interface{}) (Id, error)

Save performs an INSERT, or UPDATE if the passed structs Id is set.

func (*Hood) SaveAll

func (hood *Hood) SaveAll(f interface{}) ([]Id, error)

SaveAll performs an INSERT or UPDATE on a slice of structs.

func (*Hood) Select

func (hood *Hood) Select(selector string, table interface{}) *Hood

Select adds a SELECT clause to the query with the specified columsn and table. The table can either be a string or it's name can be inferred from the passed interface{} type.

func (*Hood) Validate

func (hood *Hood) Validate(f interface{}) error

Validate validates the provided struct

func (*Hood) Where

func (hood *Hood) Where(query string, args ...interface{}) *Hood

Where adds a WHERE clause to the query. The markers are database agnostic, so you can always use ? and it will get replaced with the dialect specific version, for example

Where("id = ?", 3)

type Id

type Id int64

Id represents a auto-incrementing integer primary key type.

type Model

type Model struct {
	Pk     *Field
	Table  string
	Fields []*Field
}

Model represents a parsed schema interface{}.

func (*Model) Validate

func (model *Model) Validate() error

type Postgres

type Postgres struct {
	Base
}

func (*Postgres) Insert

func (d *Postgres) Insert(hood *Hood, model *Model) (Id, error)

func (*Postgres) InsertSql

func (d *Postgres) InsertSql(model *Model) (string, []interface{})

func (*Postgres) KeywordAutoIncrement

func (d *Postgres) KeywordAutoIncrement() string

type Sqlite3

type Sqlite3 struct {
	Base
}

func (*Sqlite3) ChangeColumn

func (d *Sqlite3) ChangeColumn(hood *Hood, table, column string, typ interface{}, size int) error

func (*Sqlite3) ChangeColumnSql

func (d *Sqlite3) ChangeColumnSql(table, column string, typ interface{}, size int) string

func (*Sqlite3) DropColumn

func (d *Sqlite3) DropColumn(hood *Hood, table, column string) error

func (*Sqlite3) DropColumnSql

func (d *Sqlite3) DropColumnSql(table, column string) string

func (*Sqlite3) RenameColumn

func (d *Sqlite3) RenameColumn(hood *Hood, table, from, to string) error

func (*Sqlite3) RenameColumnSql

func (d *Sqlite3) RenameColumnSql(table, from, to string) string

func (*Sqlite3) SqlType

func (d *Sqlite3) SqlType(f interface{}, size int) string

func (*Sqlite3) ValueToField

func (d *Sqlite3) ValueToField(value reflect.Value, field reflect.Value) error

type ValidationError

type ValidationError struct {
	// contains filtered or unexported fields
}

Validation error type

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Id

func (e *ValidationError) Id() int

type VarChar

type VarChar string

Varchar represents a VARCHAR type.

Directories

Path Synopsis
cmd
gen

Jump to

Keyboard shortcuts

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