qbs

package module
v0.0.0-...-8554e18 Latest Latest
Warning

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

Go to latest
Published: Apr 18, 2017 License: MIT Imports: 13 Imported by: 33

README

Qbs

Qbs stands for Query By Struct. A Go ORM. 中文版 README

Build Status

ChangeLog

  • 2013.03.14: index name has changed to {table name}_{column name}.
    • For existing application with existing database, update to this change may lead to creating redundant index, you may need to drop duplicated index manually.
  • 2013.03.14: make all internal structures unexported.
  • 2013.05.22: fixed memory leak issue.
  • 2013.05.23: Breaking change, improved performance up to 100%, removed deprecated methods, make Db and Tx field unexported.
  • 2013.05.25: Added QueryStruct method to do raw SQL query and fill the result into struct or slice of struct. Added support for defining custom table/struct and column/field name convertion function, so you are not forced to use snake case in database.
  • 2013.05.27: dropped go 1.0 support, only support go 1.1, changed to use build-in sql.DB connection pool.
  • 2013.05.29: Added Iterate method for processing large data without loading all rows into memory.
  • 2013.08.03: Fixed a bug that exceeds max_prepared_stmt_count

Features

  • Define table schema in struct type, create table if not exists.
  • Detect table columns in database and alter table add new column automatically.
  • Define selection clause in struct type, fields in the struct type become the columns to be selected.
  • Define join query in struct type by add pointer fields which point to the parent table's struct.
  • Do CRUD query by struct value.
  • After a query, all the data you need will be filled into the struct value.
  • Compose where clause by condition, which can easily handle complex precedence of "AND/OR" sub conditions.
  • If Id value in the struct is provided, it will be added to the where clause.
  • "Created" column will be set to current time when insert, "Updated" column will be set to current time when insert and update.
  • Struct type can implement Validator interface to do validation before insert or update.
  • Support MySQL, PosgreSQL and SQLite3.
  • Support connection pool.

Performance

Qbs.Find is about 60% faster on mysql, 130% faster on postgreSQL than raw Db.Query, about 20% slower than raw Stmt.Query. (benchmarked on windows). The reason why it is faster than Db.Query is because all prepared Statements are cached in map.

Install

Only support go 1.1+

Go get to get the most recent source code.

go get github.com/coocood/qbs

New version may break backwards compatibility, so for production project, it's better to download the tagged version. The most recent release is v0.2.

tags with same minor version would be backward compatible, e.g v0.1 and v0.1.1.

tags with different minor version would break compatibility, e.g v0.1.1 and v0.2.

API Documentation

See Gowalker for complete documentation.

Get Started

First you need to register your database
  • The qbs.Register function has two more arguments than sql.Open, they are database name and dilect instance.

  • You only need to call it once at the start time..

      func RegisterDb(){
          qbs.Register("mysql","qbs_test@/qbs_test?charset=utf8&parseTime=true&loc=Local", "qbs_test", qbs.NewMysql())
      }
    
Define a model User
  • If the field name is Id and field type is int64, the field will be considered as the primary key of the table. if you want define a primary key with name other than Id, you can set the tag qbs:"pk" to explictly mark the field as primary key.

  • The tag of Name field qbs:"size:32,index" is used to define the column attributes when create the table, attributes are comma seperated, inside double quotes.

  • The size:32 tag on a string field will be translated to SQL varchar(32), add index attribute to create a index on the column, add unique attribute to create a unique index on the column

  • Some DB (MySQL) can not create a index on string column without size defined.

      type User struct {
          Id   int64
          Name string `qbs:"size:32,index"`
      }
    
  • If you want to create multi column index, you should implement Indexed interface by define a Indexes method like the following.

      func (*User) Indexes(indexes *qbs.Indexes){
          //indexes.Add("column_a", "column_b") or indexes.AddUnique("column_a", "column_b")
      }
    
Create a new table
  • call qbs.GetMigration function to get a Migration instance, and then use it to create a table.

  • When you create a table, if the table already exists, it will not recreate it, but looking for newly added columns or indexes in the model, and execute add column or add index operation.

  • It is better to do create table task at the start time, because the Migration only do incremental operation, it is safe to keep the table creation code in production enviroment.

  • CreateTableIfNotExists expect a struct pointer parameter.

      func CreateUserTable() error{
          migration, err := qbs.GetMigration()
          if err != nil {
              return err
          }
          defer migration.Close()
          return migration.CreateTableIfNotExists(new(User))
      }
    
Get and use *qbs.Qbs instance:
  • Suppose we are in a handle http function. call qbs.GetQbs() to get a instance.

  • Be sure to close it by calling defer q.Close() after get it.

  • qbs has connection pool, the default size is 100, you can call qbs.ChangePoolSize() to change the size.

      func GetUser(w http.ResponseWriter, r *http.Request){
      	q, err := qbs.GetQbs()
      	if err != nil {
      		fmt.Println(err)
      		w.WriteHeader(500)
      		return
      	}
      	defer q.Close()
      	u, err := FindUserById(q, 6)
      	data, _ := json.Marshal(u)
      	w.Write(data)
      }
    
Inset a row:
  • Call Save method to insert or update the row,if the primary key field Id has not been set, Save would execute insert stamtment.

  • If Id is set to a positive integer, Save would query the count of the row to find out if the row already exists, if not then execute INSERT statement. otherwise execute UPDATE.

  • Save expects a struct pointer parameter.

      func CreateUser(q *qbs.Qbs) (*User,error){
          user := new(User)
          user.Name = "Green"
          _, err := q.Save(user)
          return user,err
      }
    
Find:
  • If you want to get a row by Id, just assign the Id value to the model instance.

      func FindUserById(q *qbs.Qbs, id int64) (*User, error) {
          user := new(User)
          user.Id = id
          err := q.Find(user)
          return user, err
      }
    
  • Call FindAll to get multiple rows, it expects a pointer of slice, and the element of the slice must be a pointer of struct.

      func FindUsers(q *qbs.Qbs) ([]*User, error) {
      	var users []*User
      	err := q.Limit(10).Offset(10).FindAll(&users)
      	return users, err
      }
    
  • If you want to add conditions other than Id, you should all Where method. WhereEqual("name", name) is equivalent to Where("name = ?", name), just a shorthand method.

  • Only the last call to Where/WhereEqual counts, so it is only applicable to define simple condition.

  • Notice that the column name passed to WhereEqual method is lower case, by default, all the camel case field name and struct name will be converted to snake case in database storage, so whenever you pass a column name or table name parameter in string, it should be in snake case.

  • You can change the convertion behavior by setting the 4 convertion function variable: FieldNameToColumnName,StructNameToTableName,ColumnNameToFieldName,TableNameToStructName to your own function.

      func FindUserByName(q *qbs.Qbs, n string) (*User, error) {
          user := new(User)
          err := q.WhereEqual("name", n).Find(user)
          return user, err
      }
    
  • If you need to define more complex condition, you should call Condition method, it expects a *Condition parameter. you can get a new condition instance by calling qbs.NewCondition, qbs.NewEqualCondition or qbs.NewInCondition function.

  • *Condition instance has And, Or ... methods, can be called sequentially to construct a complex condition.

  • Condition method of Qbs instance should only be called once as well, it will replace previous condition defined by Condition or Where methods.

      func FindUserByCondition(q *qbs.Qbs) (*User, error) {
          user := new(User)
          condition1 := qbs.NewCondition("id > ?", 100).Or("id < ?", 50).OrEqual("id", 75)
          condition2 := qbs.NewCondition("name != ?", "Red").And("name != ?", "Black")
          condition1.AndCondition(condition2)
          err := q.Condition(condition1).Find(user)
          return user, err
      }
    
Update a single row
  • To update a single row, you should call Find first, then update the model, and Save it.

      func UpdateOneUser(q *qbs.Qbs, id int64, name string) (affected int64, error){
      	user, err := FindUserById(q, id)
      	if err != nil {
      		return 0, err
      	}
      	user.Name = name
      	return q.Save(user)
      }
    
Update multiple row
  • Call Update to update multiple rows at once, but you should call this method cautiously, if the the model struct contains all the columns, it will update every column, most of the time this is not what we want.

  • The right way to do it is to define a temporary model struct in method or block, that only contains the column we want to update.

      func UpdateMultipleUsers(q *qbs.Qbs)(affected int64, error) {
      	type User struct {
      		Name string
      	}
      	user := new(User)
      	user.Name = "Blue"
      	return q.WhereEqual("name", "Green").Update(user)
      }
    
Delete
  • call Delete method to delete a row, there must be at least one condition defined, either by Id value, or by Where/Condition.

      func DeleteUser(q *qbs.Qbs, id int64)(affected int64, err error) {
      	user := new(User)
      	user.Id = id
      	return q.Delete(user)
      }
    
Define another table for join query
  • For join query to work, you should has a pair of fields to define the join relationship in the model struct.

  • Here the model Post has a AuthorId int64 field, and has a Author field of type *User.

  • The rule to define join relationship is like {xxx}Id int64, {xxx} *{yyy}.

  • As the Author field is pointer type, it will be ignored when creating table.

  • As AuthorId is a join column, a index of it will be created automatically when creating the table, so you don't have to add qbs:"index" tag on it.

  • You can also set the join column explicitly by add a tag qbs:"join:Author" to it for arbitrary field Name. here Author is the struct pointer field of the parent table model.

  • To define a foreign key constraint, you have to explicitly add a tag qbs:"fk:Author" to the foreign key column, and an index will be created as well when creating table.

  • Created time.Time field will be set to the current time when insert a row,Updated time.Time field will be set to current time when update the row.

  • You can explicitly set tag qbs:"created" or qbs:"updated" on time.Time field to get the functionality for arbitrary field name.

      type Post struct {
          Id int64
          AuthorId int64
          Author *User
          Content string
          Created time.Time
          Updated time.Time
      }
    
Omit some column
  • Sometimes we do not need to get every field of a model, especially for joined field (like Author field) or large field (like Content field).

  • Omit them will get better performance.

      func FindPostsOmitContentAndCreated(q *qbs.Qbs) ([]*Post, error) {
      	var posts []*Post
      	err := q.OmitFields("Content","Created").Find(&posts)
      	return posts, err
      }
    
  • With OmitJoin, you can omit every join fields, return only the columns in a single table, and it can be used along with OmitFields.

      func FindPostsOmitJoin(q *qbs.Qbs) ([]*Post, error) {
      	var posts []*Post
      	err := q.OmitJoin().OmitFields("Content").Find(&posts)
      	return posts, err
      }
    

Projects use Qbs:

Contributors

Erik Aigner Qbs was originally a fork from hood by Erik Aigner, but I changed more than 80% of the code, then it ended up become a totally different ORM.

NuVivo314, Jason McVetta, pix64, vadimi, Ravi Teja.

Documentation

Index

Constants

View Source
const QBS_COLTYPE_BIGINT = "bigint"
View Source
const QBS_COLTYPE_BOOL = "boolean"
View Source
const QBS_COLTYPE_DOUBLE = "double"
View Source
const QBS_COLTYPE_INT = "int"
View Source
const QBS_COLTYPE_TEXT = "text"
View Source
const QBS_COLTYPE_TIME = "timestamp"

Variables

View Source
var ColumnNameToFieldName func(string) string = snakeToUpperCamel

onvert column name to struct field name.

View Source
var ConnectionLimitError = errors.New("Connection limit reached")
View Source
var FieldNameToColumnName func(string) string = toSnake

convert struct field name to column name.

View Source
var StructNameToTableName func(string) string = toSnake

convert struct name to table name.

View Source
var TableNameToStructName func(string) string = snakeToUpperCamel

convert table name to struct name.

View Source
var ValidTags = map[string]bool{
	"pk":      true,
	"fk":      true,
	"size":    true,
	"default": true,
	"join":    true,
	"-":       true,
	"index":   true,
	"unique":  true,
	"notnull": true,
	"updated": true,
	"created": true,
	"coltype": true,
}

Functions

func ChangePoolSize

func ChangePoolSize(size int)

The default connection pool size is 100.

func IntsToInterfaces

func IntsToInterfaces(ints ...int64) []interface{}

func Register

func Register(driverName, driverSourceName, databaseName string, dialect Dialect)

Register a database, should be call at the beginning of the application.

func RegisterSqlite3

func RegisterSqlite3(dbFileName string)

func RegisterWithDataSourceName

func RegisterWithDataSourceName(dsn *DataSourceName)

func RegisterWithDb

func RegisterWithDb(driverName string, database *sql.DB, dialect Dialect)

func SetConnectionLimit

func SetConnectionLimit(maxCon int, blocking bool)

Set the connection limit, there is no limit by default. If blocking is true, GetQbs method will be blocked, otherwise returns ConnectionLimitError.

func SetLogger

func SetLogger(query *log.Logger, err *log.Logger)

func StringsToInterfaces

func StringsToInterfaces(strs ...string) []interface{}

Used for in condition.

func WithMigration

func WithMigration(task func(mg *Migration) error) error

A safe and easy way to work with Migration instance without the need to open and close it.

func WithQbs

func WithQbs(task func(*Qbs) error) error

A safe and easy way to work with *Qbs instance without the need to open and close it.

Types

type Assert

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

func NewAssert

func NewAssert(t tester) *Assert

func (*Assert) Equal

func (ast *Assert) Equal(expected, actual interface{}, logs ...interface{})

func (*Assert) MustEqual

func (ast *Assert) MustEqual(expected, actual interface{}, logs ...interface{})

func (*Assert) MustNil

func (ast *Assert) MustNil(value interface{}, logs ...interface{})

func (*Assert) MustNotEqual

func (ast *Assert) MustNotEqual(expected, actual interface{}, logs ...interface{})

func (*Assert) MustNotNil

func (ast *Assert) MustNotNil(value interface{}, logs ...interface{})

func (*Assert) MustTrue

func (ast *Assert) MustTrue(boolValue bool, logs ...interface{})

func (*Assert) Nil

func (ast *Assert) Nil(value interface{}, logs ...interface{})

func (*Assert) NotEqual

func (ast *Assert) NotEqual(expected, actual interface{}, logs ...interface{})

func (*Assert) NotNil

func (ast *Assert) NotNil(value interface{}, logs ...interface{})

func (*Assert) True

func (ast *Assert) True(boolValue bool, logs ...interface{})

type Condition

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

Conditions are structured in a way to define complex where clause easily.

func NewCondition

func NewCondition(expr string, args ...interface{}) *Condition

func NewEqualCondition

func NewEqualCondition(column string, value interface{}) *Condition

Snakecase column name

func NewInCondition

func NewInCondition(column string, values []interface{}) *Condition

func (*Condition) And

func (c *Condition) And(expr string, args ...interface{}) *Condition

func (*Condition) AndCondition

func (c *Condition) AndCondition(subCondition *Condition) *Condition

func (*Condition) AndEqual

func (c *Condition) AndEqual(column string, value interface{}) *Condition

Snakecase column name

func (*Condition) Merge

func (c *Condition) Merge() (expr string, args []interface{})

func (*Condition) Or

func (c *Condition) Or(expr string, args ...interface{}) *Condition

func (*Condition) OrCondition

func (c *Condition) OrCondition(subCondition *Condition) *Condition

func (*Condition) OrEqual

func (c *Condition) OrEqual(column string, value interface{}) *Condition

Snakecase column name

type DataSourceName

type DataSourceName struct {
	DbName     string
	Username   string
	Password   string
	UnixSocket bool
	Host       string
	Port       string
	Variables  []string
	Dialect    Dialect
}

func DefaultMysqlDataSourceName

func DefaultMysqlDataSourceName(dbName string) *DataSourceName

func DefaultPostgresDataSourceName

func DefaultPostgresDataSourceName(dbName string) *DataSourceName

func (*DataSourceName) Append

func (dsn *DataSourceName) Append(key, value string) *DataSourceName

func (*DataSourceName) String

func (dsn *DataSourceName) String() string

type Dialect

type Dialect interface {
	// contains filtered or unexported methods
}

func NewMysql

func NewMysql() Dialect

func NewOracle

func NewOracle() Dialect

func NewPostgres

func NewPostgres() Dialect

func NewSqlite3

func NewSqlite3() Dialect

type Indexed

type Indexed interface {
	Indexes(indexes *Indexes)
}

type Indexes

type Indexes []*index

Indexes represents an array of indexes.

func (*Indexes) Add

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

Add adds an index

func (*Indexes) AddUnique

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

AddUnique adds an unique index

type Migration

type Migration struct {
	Log bool
	// contains filtered or unexported fields
}

func GetMigration

func GetMigration() (mg *Migration, err error)

Get a Migration instance should get closed like Qbs instance.

func (*Migration) Close

func (mg *Migration) Close()

func (*Migration) CreateIndexIfNotExists

func (mg *Migration) CreateIndexIfNotExists(table interface{}, name string, unique bool, columns ...string) error

CreateIndex creates the specified index on table. Some databases like mysql do not support this feature directly, So dialect may need to query the database schema table to find out if an index exists. Normally you don't need to do it explicitly, it will be created automatically in CreateTableIfNotExists method.

func (*Migration) CreateTableIfNotExists

func (mg *Migration) CreateTableIfNotExists(structPtr interface{}) error

CreateTableIfNotExists creates a new table and its indexes based on the table struct type It will panic if table creation failed, and it will return error if the index creation failed.

func (*Migration) DropTable

func (mg *Migration) DropTable(strutPtr interface{})

Can only drop table on database which name has "test" suffix. Used for testing

type Qbs

type Qbs struct {
	Dialect Dialect
	Log     bool //Set to true to print out sql statement.
	// contains filtered or unexported fields
}

func GetQbs

func GetQbs() (q *Qbs, err error)

Get an Qbs instance, should call `defer q.Close()` next, like:

	q, err := qbs.GetQbs()
  	if err != nil {
		fmt.Println(err)
		return
	}
	defer q.Close()
	...

func (*Qbs) Begin

func (q *Qbs) Begin() error

Begin create a transaction object internally You can perform queries with the same Qbs object no matter it is in transaction or not. It panics if it's already in a transaction.

func (*Qbs) BulkInsert

func (q *Qbs) BulkInsert(sliceOfStructPtr interface{}) error

func (*Qbs) Close

func (q *Qbs) Close() error

If the connection pool is not full, the Db will be sent back into the pool, otherwise the Db will get closed.

func (*Qbs) Commit

func (q *Qbs) Commit() error

Commit commits a started transaction and will report the first error that occurred inside the transaction.

func (*Qbs) Condition

func (q *Qbs) Condition(condition *Condition) *Qbs

Condition defines the SQL "WHERE" clause If other condition can be inferred by the struct argument in Find method, it will be merged with AND

func (*Qbs) ContainsValue

func (q *Qbs) ContainsValue(table interface{}, column string, value interface{}) bool

This method can be used to validate unique column before trying to save The table parameter can be either a string or a struct pointer

func (*Qbs) Count

func (q *Qbs) Count(table interface{}) int64

Query the count of rows in a table the talbe parameter can be either a string or struct pointer. If condition is given, the count will be the count of rows meet that condition.

func (*Qbs) Delete

func (q *Qbs) Delete(structPtr interface{}) (affected int64, err error)

The delete condition can be inferred by the Id value of the struct If neither Id value or condition are provided, it would cause runtime panic

func (*Qbs) Exec

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

Same as sql.Db.Exec or sql.Tx.Exec depends on if transaction has began

func (*Qbs) Find

func (q *Qbs) Find(structPtr interface{}) error

Perform select query by parsing the struct's type and then fill the values into the struct All fields of supported types in the struct will be added in select clause. If Id value is provided, it will be added into the where clause If a foreign key field with its referenced struct pointer field are provided, It will perform a join query, the referenced struct pointer field will be filled in the values obtained by the query. If not found, "sql.ErrNoRows" will be returned.

func (*Qbs) FindAll

func (q *Qbs) FindAll(ptrOfSliceOfStructPtr interface{}) error

Similar to Find, except that FindAll accept pointer of slice of struct pointer, rows will be appended to the slice.

func (*Qbs) InTransaction

func (q *Qbs) InTransaction() bool

func (*Qbs) Iterate

func (q *Qbs) Iterate(structPtr interface{}, do func() error) error

Iterate the rows, the first parameter is a struct pointer, the second parameter is a fucntion which will get called on each row, the in `do` function the structPtr's value will be set to the current row's value.. if `do` function returns an error, the iteration will be stopped.

func (*Qbs) Limit

func (q *Qbs) Limit(limit int) *Qbs

func (*Qbs) Offset

func (q *Qbs) Offset(offset int) *Qbs

func (*Qbs) OmitFields

func (q *Qbs) OmitFields(fieldName ...string) *Qbs

Camel case field names

func (*Qbs) OmitJoin

func (q *Qbs) OmitJoin() *Qbs

func (*Qbs) OrderBy

func (q *Qbs) OrderBy(path string) *Qbs

func (*Qbs) OrderByDesc

func (q *Qbs) OrderByDesc(path string) *Qbs

func (*Qbs) Query

func (q *Qbs) Query(query string, args ...interface{}) (rows *sql.Rows, err error)

Same as sql.Db.Query or sql.Tx.Query depends on if transaction has began

func (*Qbs) QueryMap

func (q *Qbs) QueryMap(query string, args ...interface{}) (map[string]interface{}, error)

Query raw sql and return a map.

func (*Qbs) QueryMapSlice

func (q *Qbs) QueryMapSlice(query string, args ...interface{}) ([]map[string]interface{}, error)

Query raw sql and return a slice of map..

func (*Qbs) QueryRow

func (q *Qbs) QueryRow(query string, args ...interface{}) *sql.Row

Same as sql.Db.QueryRow or sql.Tx.QueryRow depends on if transaction has began

func (*Qbs) QueryStruct

func (q *Qbs) QueryStruct(dest interface{}, query string, args ...interface{}) error

Do a raw sql query and set the result values in dest parameter. The dest parameter can be either a struct pointer or a pointer of struct pointer.slice This method do not support pointer field in the struct.

func (*Qbs) Reset

func (q *Qbs) Reset()

Create a new criteria for subsequent query

func (*Qbs) Rollback

func (q *Qbs) Rollback() error

Rollback rolls back a started transaction.

func (*Qbs) Save

func (q *Qbs) Save(structPtr interface{}) (affected int64, err error)

If Id value is not provided, save will insert the record, and the Id value will be filled in the struct after insertion. If Id value is provided, save will do a query count first to see if the row exists, if not then insert it, otherwise update it. If struct implements Validator interface, it will be validated first

func (*Qbs) Update

func (q *Qbs) Update(structPtr interface{}) (affected int64, err error)

If the struct type implements Validator interface, values will be validated before update. In order to avoid inadvertently update the struct field to zero value, it is better to define a temporary struct in function, only define the fields that should be updated. But the temporary struct can not implement Validator interface, we have to validate values manually. The update condition can be inferred by the Id value of the struct. If neither Id value or condition are provided, it would cause runtime panic

func (*Qbs) Where

func (q *Qbs) Where(expr string, args ...interface{}) *Qbs

Where is a shortcut method to call Condtion(NewCondtition(expr, args...)).

func (*Qbs) WhereEqual

func (q *Qbs) WhereEqual(column string, value interface{}) *Qbs

Snakecase column name

func (*Qbs) WhereIn

func (q *Qbs) WhereIn(column string, values []interface{}) *Qbs

type TableNamer

type TableNamer interface {
	TableName() string
}

type Validator

type Validator interface {
	Validate(*Qbs) error
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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