Qbs stands for Query By Struct. A Go ORM. 中文版 README
{table name}_{column name}.QueryStruct method to do raw SQL query and fill the result into struct or slice of struct.
Added support for define custom table/struct and column/filed name convertion function, so you are not forced to use snake case in database.Iterate method for processing large data without loading all rows into memory.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.
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.
See Gowalker for complete documentation.
qbs.Register function has two more arguments than sql.Open, they are database name and dilect instance.func RegisterDb(){
qbs.Register("mysql","qbs_test@/qbs_test?charset=utf8&parseTime=true&loc=Local", "qbs_test", qbs.NewMysql())
}
UserId 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.Name field qbs:"size:32,index" is used to define the column attributes when create the table, attributes are comma seperated, inside double quotes.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 columnSome 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")
}
qbs.GetMigration function to get a Migration instance, and then use it to create a table.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))
}
*qbs.Qbs instance:qbs.GetQbs() to get a instance.defer q.Close() after get it.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)
}
Save method to insert or update the row,if the primary key field Id has not been set, Save would execute insert stamtment.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
}
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.
Where/WhereEqual counts, so it is only applicable to define simple condition.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
}
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 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.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 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)
}
Post has a AuthorId int64 field, and has a Author field of type *User.{xxx}Id int64, {xxx} *{yyy}.Author field is pointer type, it will be ignored when creating table.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.qbs:"join:Author" to it for arbitrary field Name. here Author is the struct pointer field of the parent table model.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.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
}
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
}
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.