hood

package module
v0.0.0-...-cd6a03e Latest Latest
Warning

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

Go to latest
Published: Dec 20, 2012 License: MIT Imports: 11 Imported by: 0

README

Hood

PLEASE NOTE THAT THIS PROJECT IS CURRENTLY IN THE PROCESS OF DEFINING IT'S API. IT CANNOT BE ASSUMED THAT THE API IS FIXED YET. IF YOU PLAN TO USE HOOD, PLEASE FORK IT!

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 as simple as copying the original postgres.go, replacing the statement and field values with the new dialect versions.

Documentation

GoDoc

Open

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, err := hood.New(db, &Postgres{})

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

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

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
)

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 Dialect

type Dialect interface {
	// Marker returns the dialect specific markers for prepared statements,
	// for instance $1, $2, ... and so on. The position starts at 0.
	Marker(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

	// Insert takes the generated query and modifies it. E.g. Postgres does not
	// return the inserted IDs after executing INSERT, unless a RETURNING
	// keyword is appended. If a dialect needs a custom INSERT, it should return
	// implemented == true.
	Insert(hood *Hood, model *Model, query string, args ...interface{}) (id Id, err error, implemented bool)

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

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

func (hood *Hood) Commit() error

Commit commits a started transaction.

func (*Hood) CreateTable

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

CreateTable creates a new table based on the provided schema.

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

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

DropTable drops the table matching the provided table name.

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

type MySQL struct{}

func (*MySQL) Insert

func (d *MySQL) Insert(hood *Hood, model *Model, query string, args ...interface{}) (Id, error, bool)

func (*MySQL) KeywordAutoIncrement

func (d *MySQL) KeywordAutoIncrement() string

func (*MySQL) KeywordDefault

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

func (*MySQL) KeywordNotNull

func (d *MySQL) KeywordNotNull() string

func (*MySQL) KeywordPrimaryKey

func (d *MySQL) KeywordPrimaryKey() string

func (*MySQL) Marker

func (d *MySQL) Marker(pos int) string

func (*MySQL) SqlType

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

func (*MySQL) ValueToField

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

type Postgres

type Postgres struct{}

func (*Postgres) Insert

func (d *Postgres) Insert(hood *Hood, model *Model, query string, args ...interface{}) (Id, error, bool)

func (*Postgres) KeywordAutoIncrement

func (d *Postgres) KeywordAutoIncrement() string

func (*Postgres) KeywordDefault

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

func (*Postgres) KeywordNotNull

func (d *Postgres) KeywordNotNull() string

func (*Postgres) KeywordPrimaryKey

func (d *Postgres) KeywordPrimaryKey() string

func (*Postgres) Marker

func (d *Postgres) Marker(pos int) string

func (*Postgres) SqlType

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

func (*Postgres) ValueToField

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

type Sqlite3

type Sqlite3 struct{}

func (*Sqlite3) Insert

func (d *Sqlite3) Insert(hood *Hood, model *Model, query string, args ...interface{}) (Id, error, bool)

func (*Sqlite3) KeywordAutoIncrement

func (d *Sqlite3) KeywordAutoIncrement() string

func (*Sqlite3) KeywordDefault

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

func (*Sqlite3) KeywordNotNull

func (d *Sqlite3) KeywordNotNull() string

func (*Sqlite3) KeywordPrimaryKey

func (d *Sqlite3) KeywordPrimaryKey() string

func (*Sqlite3) Marker

func (d *Sqlite3) Marker(pos int) 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

Jump to

Keyboard shortcuts

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