hood

package module
Version: v0.0.0-...-ca2c4bd Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2013 License: MIT Imports: 14 Imported by: 18

README

If you are looking for something more lightweight and flexible, have a look at jet

For questions, suggestions and general topics visit the group.

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
  • Migration and schema generation
  • Model validations
  • Model event hooks
  • Database dialect interface
  • No implicit fields
  • Clean and testable codebase

Dialects currently implemented

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. To get a sense of the API, it's best to take a quick look at the unit tests, as they are always up to date!

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"`

  // string field 'last_name' with size 128, NOT NULL
  LastName string `sql:"size(128),notnull"`

  // string field 'tag' with size 255, default value 'customer'
  Tag string `sql:"size(255),default('customer')"`

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

  // You can alternatively define a var char as a string field by setting a size
  Nick  string  `sql:"size(128)"`

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

  // These fields are auto updated on save
  Created hood.Created
  Updated hood.Updated

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

// Indexes are defined via the Indexed interface to avoid
// polluting the table fields.

func (table *Person) Indexes(indexes *hood.Indexes) {
  indexes.Add("tag_index", "tag") // params: indexName, unique, columns...
  indexes.AddUnique("name_index", "first_name", "last_name")
}

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')
  • - ignores the field

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

and another one

hood create:migration AddUserNameIndex

This command creates new migrations in db/migrations. Next we have to populate the generated migrations Up (and Down) methods like so:

func (m *M) CreateUserTable_1357605106_Up(hd *hood.Hood) {
  type Users struct {
    Id		hood.Id
    First	string
    Last	string
  }
  hd.CreateTable(&Users{})
}
func (m *M) AddUserNameIndex_1357605116_Up(hd *hood.Hood) {
  hd.CreateIndex("users", "name_index", true, "first", "last")
}

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

Now we can run migrations with

hood db:migrate

and roll back with

hood db:rollback

For a complete list of commands invoke hood -help

A schema.go file is automatically generated in the db directory. This file reflects the current state of the database! In our example, it will look like this:

package db

import (
  "github.com/eaigner/hood"
)

type Users struct {
  Id    hood.Id
  First string
  Last  string
}

func (table *Users) Indexes(indexes *hood.Indexes) {
  indexes.AddUnique("name_index", "first", "last")
}

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 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
  • <regexp>, e.g. ^[a-z]+$, validates that a string matches the regular expression
    • the expression must start with ^
    • backslash and double quote should be escaped
    • does not work with other validation methods on the same field

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{})
}

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
	ValidationErrorValueNotMatch
)
View Source
const (
	InnerJoin = Join(iota)
	LeftJoin
	RightJoin
	FullJoin
)

Variables

This section is empty.

Functions

func NewValidationError

func NewValidationError(id int, field 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 Config

type Config map[string]string

Config represents an environment entry in the config.json file

type Created

type Created struct {
	time.Time
}

Created denotes a timestamp field that is automatically set on insert.

type Dialect

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

	// Quote will quote identifiers in a SQL statement.
	Quote(s string) 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

	// If database do not support boolean type this can be used to parse int
	// value to boolean value.
	ParseBool(value reflect.Value) bool

	// SetModelValue sets a model field from a db value.
	//
	// For example: time.Time objects needs to be marshalled back and forth
	// as Strings for databases that don't have a native "time" type.
	SetModelValue(value reflect.Value, field reflect.Value) error

	// ConvertHoodType converts special types such as Created or Updated to
	// values the driver can understand.
	ConvertHoodType(f interface{}) interface{}

	// 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{})

	// DeleteFrom deletes the matching rows in the specified table
	DeleteFrom(hood *Hood, table string) error

	// DeleteFromSql returns the sql for DeleteFrom
	DeleteFromSql(hood *Hood, table string) (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, name, table string, unique bool, columns ...string) error

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

	// DropIndex drops the index.
	DropIndex(hood *Hood, name string) error

	// DropIndexSql returns the sql for dropping the index.
	DropIndexSql(name 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 NewMysql

func NewMysql() Dialect

func NewPostgres

func NewPostgres() Dialect

type Environment

type Environment map[string]Config

Environment represents a configuration map for each environment specified in the config.json file

type Hood

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

Hood is an ORM handle.

func Dry

func Dry() *Hood

Dry creates a new Hood instance for schema generation.

func Load

func Load(path, env string) (*Hood, error)

Load opens a new database from a config.json file with the specified environment, or development if none is specified.

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(table, columns interface{}) error

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

func (*Hood) And

func (hood *Hood) And(a Path, op string, b interface{}) *Hood

Where adds a AND clause to the WHERE query. You can concatenate using the And and Or methods.

func (*Hood) Asc

func (hood *Hood) Asc() *Hood

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) ChangeColumns

func (hood *Hood) ChangeColumns(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 and will report the first error that occurred inside the transaction.

func (*Hood) Copy

func (hood *Hood) Copy() *Hood

Copy copies the hood instance for safe context manipulation.

func (*Hood) CreateIndex

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

CreateIndex creates the specified index on table.

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) DeleteFrom

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

DeleteFrom deletes the rows matched by the previous Where clause. table can either be a table struct or a string.

Example:

hd.Where("amount", "=", 0).DeleteFrom("stock")

func (*Hood) Desc

func (hood *Hood) Desc() *Hood

func (*Hood) DropIndex

func (hood *Hood) DropIndex(table interface{}, name string) error

DropIndex drops the specified index from table.

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) FindSql

func (hood *Hood) FindSql(out interface{}, query string, args ...interface{}) error

FindSql performs a find using the specified custom sql query and arguments and writes the results to the specified out interface{}.

func (*Hood) GoSchema

func (hood *Hood) GoSchema() string

GoSchema returns a string of the schema file in Go syntax.

func (*Hood) GroupBy

func (hood *Hood) GroupBy(path Path) *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) IsTransaction

func (hood *Hood) IsTransaction() bool

IsTransaction returns wether the hood object represents an active transaction or not.

func (*Hood) Join

func (hood *Hood) Join(op Join, table interface{}, a Path, b Path) *Hood

Join performs a JOIN on tables, for example

Join(hood.InnerJoin, &User{}, "user.id", "order.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) Or

func (hood *Hood) Or(a Path, op string, b interface{}) *Hood

Where adds a OR clause to the WHERE query. You can concatenate using the And and Or methods.

func (*Hood) OrderBy

func (hood *Hood) OrderBy(path Path) *Hood

OrderBy adds an ORDER BY clause to the query.

func (*Hood) Query

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

Query executes a query that returns rows, typically a SELECT

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) RemoveColumns

func (hood *Hood) RemoveColumns(table, columns interface{}) error

RemoveColumns removes the specified columns from the table.

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(table interface{}, paths ...Path) *Hood

Select adds a SELECT clause to the query with the specified table and columns. 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(a Path, op string, b interface{}) *Hood

Where adds a WHERE clause to the query. You can concatenate using the And and Or methods.

type Id

type Id int64

Id represents a auto-incrementing integer primary key type.

type Index

type Index struct {
	Name    string
	Columns []string
	Unique  bool
}

Index represents a table index and is returned via the Indexed interface.

func (*Index) GoDeclaration

func (index *Index) GoDeclaration() string

type Indexed

type Indexed interface {
	Indexes(indexes *Indexes)
}

Indexed defines the indexes for a table. You can invoke Add on the passed instance.

type Indexes

type Indexes []*Index

Indexes represents an array of indexes.

func (*Indexes) Add

func (ix *Indexes) Add(name string, columns ...string)

Add adds an index

func (*Indexes) AddUnique

func (ix *Indexes) AddUnique(name string, columns ...string)

AddUnique adds an unique index

type Join

type Join int

type Model

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

Model represents a parsed schema interface{}.

func (*Model) GoDeclaration

func (model *Model) GoDeclaration() string

func (*Model) Validate

func (model *Model) Validate() error

type ModelField

type ModelField 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
	RawTag       reflect.StructTag // The raw tag
}

ModelField represents a schema field of a parsed model.

func (*ModelField) Default

func (field *ModelField) Default() string

Default returns the default value for the field

func (*ModelField) GoDeclaration

func (field *ModelField) GoDeclaration() string

func (*ModelField) Int

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

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

func (*ModelField) NotNull

func (field *ModelField) NotNull() bool

NotNull tests if the field is declared as NOT NULL

func (*ModelField) PrimaryKey

func (field *ModelField) PrimaryKey() bool

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

func (*ModelField) Size

func (field *ModelField) Size() int

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

func (*ModelField) String

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

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

func (*ModelField) Validate

func (field *ModelField) Validate() error

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

func (*ModelField) Zero

func (field *ModelField) Zero() bool

Zero tests wether or not the field is set

type Path

type Path string

Path denotes a combined sql identifier such as 'table.column'

func (Path) Quote

func (p Path) Quote(d Dialect) string

Quote quotes the path using the given dialects Quote method

type Schema

type Schema []*Model

Schema is a collection of models

func (Schema) GoDeclaration

func (schema Schema) GoDeclaration() string

type Updated

type Updated struct {
	time.Time
}

Updated denotes a timestamp field that is automatically set on update.

type ValidationError

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

Validation error type

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Field

func (e *ValidationError) Field() string

func (*ValidationError) Kind

func (e *ValidationError) Kind() int

Directories

Path Synopsis
cmd
gen

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
t or T : Toggle theme light dark auto
y or Y : Canonical URL