implement Struct and add more docs

This commit is contained in:
Huan Du
2017-12-30 04:36:25 +08:00
parent 8751efc7f5
commit ae0151fccb
4 changed files with 626 additions and 6 deletions
+53 -6
View File
@@ -35,12 +35,59 @@ fmt.Println(args)
Following builders are implemented right now. API document and examples are provided in the `godoc` document.
* [SelectBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#SelectBuilder)
* [InsertBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#InsertBuilder)
* [UpdateBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#UpdateBuilder)
* [DeleteBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#DeleteBuilder)
* [Build](https://godoc.org/github.com/huandu/go-sqlbuilder#Build)
* [Buildf](https://godoc.org/github.com/huandu/go-sqlbuilder#Buildf)
* [Struct](https://godoc.org/github.com/huandu/go-sqlbuilder#Struct): Builder factory for a struct.
* [SelectBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#SelectBuilder): Builder for SELECT.
* [InsertBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#InsertBuilder): Builder for INSERT.
* [UpdateBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#UpdateBuilder): Builder for UPDATE.
* [DeleteBuilder](https://godoc.org/github.com/huandu/go-sqlbuilder#DeleteBuilder): Builder for DELETE.
* [Build](https://godoc.org/github.com/huandu/go-sqlbuilder#Build): Freestyle builder using `fmt.Sprintf`-like syntax.
* [Buildf](https://godoc.org/github.com/huandu/go-sqlbuilder#Buildf): Advanced freestyle builder using special syntax defined in [Args#Compile](https://godoc.org/github.com/huandu/go-sqlbuilder#Args.Compile).
### Using `Struct` to build query for a struct and scan value from results ###
`Struct` stores information of type and struct fields of a struct. It works like a factory of builders. We can use `Struct` methods to create initialized SELECT/INSERT/UPDATE/DELETE builders. It can help us to save time and avoid human-error when writing column names in query for a table.
Read [examples](https://godoc.org/github.com/huandu/go-sqlbuilder#Struct) for `Struct` to learn how to use it.
What's cool, we can use `Struct` as a kind of ORM. It's quite light weight and clean without any magic comparing with other ORM package.
```go
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Status int `db:"status"`
}
var userStruct = NewStruct(new(User))
func ExampleStruct() {
// Prepare SELECT query.
sb := userStruct.Select("user")
sb.Where(sb.E("id", 1234))
sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)
// Execute the query.
rows, _ := db.Query(sql, args...)
defer rows.Close()
// Scan row data to user.
// Suppose we get following data.
//
// | id | name | status |
// |------|--------|--------|
// | 1234 | huandu | 1 |
var user User
rows.Scan(userStruct.Addr(&user)...)
fmt.Printf("%#v", user)
// Output:
// SELECT id, name, status FROM user WHERE id = ? LIMIT 1
// [1234]
// sqlbuilder.User{ID:1234, Name:"huandu", Status:1}
}
```
### Nested SQL ###
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"fmt"
"testing"
)
func TestArgs(t *testing.T) {
cases := map[string][]interface{}{
"abc ? def\n[123]": []interface{}{"abc $? def", 123},
"abc ? def\n[456]": []interface{}{"abc $0 def", 456},
"abc def\n[]": []interface{}{"abc $1 def", 123},
"abc ? def\n[789]": []interface{}{"abc ${s} def", Named("s", 789)},
"abc def \n[]": []interface{}{"abc ${unknown} def ", 123},
"abc $ def\n[]": []interface{}{"abc $$ def", 123},
"abcdef\n[]": []interface{}{"abcdef$", 123},
"abc ? ? ? ? def\n[123 456 123 456]": []interface{}{"abc $? $? $0 $? def", 123, 456, 789},
"abc ? raw ? raw def\n[123 123]": []interface{}{"abc $? $? $0 $? def", 123, Raw("raw"), 789},
}
for expected, c := range cases {
args := new(Args)
for i := 1; i < len(c); i++ {
args.Add(c[i])
}
sql, values := args.Compile(c[0].(string))
actual := fmt.Sprintf("%v\n%v", sql, values)
if actual != expected {
t.Fatalf("invalid compile result. [expected:%v] [actual:%v]", expected, actual)
}
}
}
+293
View File
@@ -0,0 +1,293 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"reflect"
"strings"
)
var (
// DBTag is the struct tag to describe the name for a field in struct.
DBTag = "db"
// FieldTag is the struct tag to describe the tag name for a field in struct.
// Use "," to separate different tags.
FieldTag = "fieldtag"
)
// Struct represents a struct type.
//
// All methods in Struct are thread-safe.
// We can define a global variable to hold a Struct and use it in any goroutine.
type Struct struct {
structType reflect.Type
fieldAlias map[string]string
taggedFields map[string][]string
}
// NewStruct analyzes type information in structValue
// and creates a new Struct with all structValue fields.
// If structValue is not a struct, NewStruct returns a dummy Sturct.
func NewStruct(structValue interface{}) *Struct {
t := reflect.TypeOf(structValue)
t = deferencedType(t)
s := new(Struct)
if t.Kind() != reflect.Struct {
return s
}
s.structType = t
s.fieldAlias = map[string]string{}
s.taggedFields = map[string][]string{}
s.parse(t)
return s
}
func (s *Struct) parse(t reflect.Type) {
l := t.NumField()
for i := 0; i < l; i++ {
field := t.Field(i)
if field.Anonymous {
ft := deferencedType(field.Type)
s.parse(ft)
continue
}
// Parse DBTag.
dbtag := field.Tag.Get(DBTag)
aliasIdx := strings.Index(dbtag, ",")
alias := field.Name
if aliasIdx == 0 || dbtag == "" {
s.fieldAlias[field.Name] = field.Name
} else {
if aliasIdx > 0 {
alias = dbtag[:aliasIdx]
} else {
alias = dbtag
}
// Skip the field if DBTag is "-".
if alias == "-" {
continue
}
s.fieldAlias[alias] = field.Name
}
// Parse FieldTag.
fieldtag := field.Tag.Get(FieldTag)
tags := strings.Split(fieldtag, ",")
for _, t := range tags {
if t != "" {
s.taggedFields[t] = append(s.taggedFields[t], alias)
}
}
s.taggedFields[""] = append(s.taggedFields[""], alias)
}
}
// Select creates a new `SelectBuilder` with table name.
// By default, all exported fields of the s are listed as columns in SELECT
// and LIMIT is set to 1.
//
// Caller is responsible to set WHERE condition to find right record.
func (s *Struct) Select(table string) *SelectBuilder {
return s.SelectForTag(table, "")
}
// SelectForTag creates a new `SelectBuilder` with table name for a specified tag.
// By default, all fields of the s tagged with tag are listed as columns in SELECT
// and LIMIT is set to 1.
//
// Caller is responsible to set WHERE condition to find right record.
func (s *Struct) SelectForTag(table string, tag string) *SelectBuilder {
sb := NewSelectBuilder()
sb.From(table)
sb.Limit(1)
if s.taggedFields == nil {
return sb
}
fields, ok := s.taggedFields[tag]
if ok {
sb.Select(EscapeAll(fields...)...)
} else {
sb.Select("*")
}
return sb
}
// Update creates a new `UpdateBuilder` with table name.
// By default, all exported fields of the s is assigned in UPDATE with the field values from value.
// If value's type is not the same as that of s, Update returns a dummy `UpdateBuilder` with table name.
//
// Caller is responsible to set WHERE condition to match right record.
func (s *Struct) Update(table string, value interface{}) *UpdateBuilder {
return s.UpdateForTag(table, "", value)
}
// UpdateForTag creates a new `UpdateBuilder` with table name.
// By default, all fields of the s tagged with tag is assigned in UPDATE with the field values from value.
// If value's type is not the same as that of s, Update returns a dummy `UpdateBuilder` with table name.
//
// Caller is responsible to set WHERE condition to match right record.
func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *UpdateBuilder {
ub := NewUpdateBuilder()
ub.Update(table)
if s.taggedFields == nil {
return ub
}
fields, ok := s.taggedFields[tag]
if !ok {
return ub
}
v := dereferencedValue(value)
if v.Type() != s.structType {
return ub
}
assignments := make([]string, 0, len(fields))
for _, f := range fields {
name := s.fieldAlias[f]
data := v.FieldByName(name).Interface()
assignments = append(assignments, ub.Assign(f, data))
}
ub.Set(assignments...)
return ub
}
// InsertInto creates a new `InsertBuilder` with table name.
// By default, all exported fields of the s is inserted in INSERT with the field values from value.
// If value's type is not the same as that of s, Update returns a dummy `InsertBuilder` with table name.
func (s *Struct) InsertInto(table string, value interface{}) *InsertBuilder {
return s.InsertIntoForTag(table, "", value)
}
// InsertIntoForTag creates a new `InsertBuilder` with table name.
// By default, all fields of the s tagged with tag is inserted in INSERT with the field values from value.
// If value's type is not the same as that of s, Update returns a dummy `InsertBuilder` with table name.
func (s *Struct) InsertIntoForTag(table string, tag string, value interface{}) *InsertBuilder {
ib := NewInsertBuilder()
ib.InsertInto(table)
if s.taggedFields == nil {
return ib
}
fields, ok := s.taggedFields[tag]
if !ok {
return ib
}
v := dereferencedValue(value)
if v.Type() != s.structType {
return ib
}
cols := make([]string, 0, len(fields))
values := make([]interface{}, 0, len(fields))
for _, f := range fields {
name := s.fieldAlias[f]
data := v.FieldByName(name).Interface()
cols = append(cols, f)
values = append(values, data)
}
ib.Cols(cols...)
ib.Values(values...)
return ib
}
// DeleteFrom creates a new `DeleteBuilder` with table name.
//
// Caller is responsible to set WHERE condition to match right record.
func (s *Struct) DeleteFrom(table string) *DeleteBuilder {
db := NewDeleteBuilder()
db.DeleteFrom(table)
return db
}
// Addr take address of all exported fields of the s from the value.
// The returned result can be used in `Row#Scan` directly.
func (s *Struct) Addr(value interface{}) []interface{} {
return s.AddrForTag("", value)
}
// AddrForTag take address of all fields of the s tagged with tag from the value.
// The returned result can be used in `Row#Scan` directly.
//
// If tag is not defined in s in advance,
func (s *Struct) AddrForTag(tag string, value interface{}) []interface{} {
fields, ok := s.taggedFields[tag]
if !ok {
return nil
}
return s.AddrWithCols(fields, value)
}
// AddrWithCols take address of all columns defined in cols from the value.
// The returned result can be used in `Row#Scan` directly.
func (s *Struct) AddrWithCols(cols []string, value interface{}) []interface{} {
v := dereferencedValue(value)
if v.Type() != s.structType {
return nil
}
for _, c := range cols {
if _, ok := s.fieldAlias[c]; !ok {
return nil
}
}
addrs := make([]interface{}, 0, len(cols))
for _, c := range cols {
name := s.fieldAlias[c]
data := v.FieldByName(name).Addr().Interface()
addrs = append(addrs, data)
}
return addrs
}
func deferencedType(t reflect.Type) reflect.Type {
for k := t.Kind(); k == reflect.Ptr || k == reflect.Interface; k = t.Kind() {
t = t.Elem()
}
return t
}
func dereferencedValue(value interface{}) reflect.Value {
v := reflect.ValueOf(value)
for k := v.Kind(); k == reflect.Ptr || k == reflect.Interface; k = v.Kind() {
v = v.Elem()
}
return v
}
+242
View File
@@ -0,0 +1,242 @@
// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"fmt"
"reflect"
"testing"
)
type structUserForTest struct {
ID int `db:"id" fieldtag:"important"`
Name string `fieldtag:"important"`
Status int `db:"status" fieldtag:"important"`
CreatedAt int `db:"created_at"`
}
var userForTest = NewStruct(new(structUserForTest))
func TestStructSelect(t *testing.T) {
sb := userForTest.Select("user")
sql, args := sb.Build()
if expected := "SELECT id, Name, status, created_at FROM user LIMIT 1"; expected != sql {
t.Fatalf("invalid SQL. [expected:%v] [actual:%v]", expected, sql)
}
if len(args) != 0 {
t.Fatalf("args must be empty. [args:%v]", args)
}
}
func TestStructSelectForTag(t *testing.T) {
sb := userForTest.SelectForTag("user", "important")
sql, args := sb.Build()
if expected := "SELECT id, Name, status FROM user LIMIT 1"; expected != sql {
t.Fatalf("invalid SQL. [expected:%v] [actual:%v]", expected, sql)
}
if len(args) != 0 {
t.Fatalf("args must be empty. [args:%v]", args)
}
}
func TestStructUpdate(t *testing.T) {
user := &structUserForTest{
ID: 123,
Name: "Huan Du",
Status: 2,
CreatedAt: 1234567890,
}
ub := userForTest.Update("user", user)
sql, args := ub.Build()
if expected := "UPDATE user SET id = ?, Name = ?, status = ?, created_at = ?"; expected != sql {
t.Fatalf("invalid SQL. [expected:%v] [actual:%v]", expected, sql)
}
if expected := []interface{}{123, "Huan Du", 2, 1234567890}; !reflect.DeepEqual(expected, args) {
t.Fatalf("invalid args. [expected:%v] [actual:%v]", expected, args)
}
}
func TestStructUpdateForTag(t *testing.T) {
user := &structUserForTest{
ID: 123,
Name: "Huan Du",
Status: 2,
CreatedAt: 1234567890,
}
ub := userForTest.UpdateForTag("user", "important", user)
sql, args := ub.Build()
if expected := "UPDATE user SET id = ?, Name = ?, status = ?"; expected != sql {
t.Fatalf("invalid SQL. [expected:%v] [actual:%v]", expected, sql)
}
if expected := []interface{}{123, "Huan Du", 2}; !reflect.DeepEqual(expected, args) {
t.Fatalf("invalid args. [expected:%v] [actual:%v]", expected, args)
}
}
func TestStructInsertInto(t *testing.T) {
user := &structUserForTest{
ID: 123,
Name: "Huan Du",
Status: 2,
CreatedAt: 1234567890,
}
ib := userForTest.InsertInto("user", user)
sql, args := ib.Build()
if expected := "INSERT INTO user (id, Name, status, created_at) VALUES (?, ?, ?, ?)"; expected != sql {
t.Fatalf("invalid SQL. [expected:%v] [actual:%v]", expected, sql)
}
if expected := []interface{}{123, "Huan Du", 2, 1234567890}; !reflect.DeepEqual(expected, args) {
t.Fatalf("invalid args. [expected:%v] [actual:%v]", expected, args)
}
}
func TestStructInsertIntoForTag(t *testing.T) {
user := &structUserForTest{
ID: 123,
Name: "Huan Du",
Status: 2,
CreatedAt: 1234567890,
}
ib := userForTest.InsertIntoForTag("user", "important", user)
sql, args := ib.Build()
if expected := "INSERT INTO user (id, Name, status) VALUES (?, ?, ?)"; expected != sql {
t.Fatalf("invalid SQL. [expected:%v] [actual:%v]", expected, sql)
}
if expected := []interface{}{123, "Huan Du", 2}; !reflect.DeepEqual(expected, args) {
t.Fatalf("invalid args. [expected:%v] [actual:%v]", expected, args)
}
}
func TestStructDeleteFrom(t *testing.T) {
db := userForTest.DeleteFrom("user")
sql, args := db.Build()
if expected := "DELETE FROM user"; expected != sql {
t.Fatalf("invalid SQL. [expected:%v] [actual:%v]", expected, sql)
}
if len(args) != 0 {
t.Fatalf("args must be empty. [args:%v]", args)
}
}
func TestStructAddr(t *testing.T) {
user := new(structUserForTest)
expected := &structUserForTest{
ID: 123,
Name: "huandu",
Status: 2,
CreatedAt: 1234567890,
}
str := fmt.Sprintf("%v %v %v %v", expected.ID, expected.Name, expected.Status, expected.CreatedAt)
fmt.Sscanf(str, "%d%s%d%d", userForTest.Addr(user)...)
if !reflect.DeepEqual(expected, user) {
t.Fatalf("invalid user. [expected:%v] [actual:%v]", expected, user)
}
}
func TestStructAddrForTag(t *testing.T) {
user := new(structUserForTest)
expected := &structUserForTest{
ID: 123,
Name: "huandu",
Status: 2,
CreatedAt: 1234567890,
}
user.CreatedAt = 9876543210
str := fmt.Sprintf("%v %v %v %v", expected.ID, expected.Name, expected.Status, expected.CreatedAt)
fmt.Sscanf(str, "%d%s%d%d", userForTest.AddrForTag("important", user)...)
expected.CreatedAt = 9876543210
if !reflect.DeepEqual(expected, user) {
t.Fatalf("invalid user. [expected:%v] [actual:%v]", expected, user)
}
}
func TestStructAddrWithCols(t *testing.T) {
user := new(structUserForTest)
expected := &structUserForTest{
ID: 123,
Name: "huandu",
Status: 2,
CreatedAt: 1234567890,
}
str := fmt.Sprintf("%v %v %v %v", expected.Name, expected.ID, expected.CreatedAt, expected.Status)
fmt.Sscanf(str, "%s%d%d%d", userForTest.AddrWithCols([]string{"Name", "id", "created_at", "status"}, user)...)
if !reflect.DeepEqual(expected, user) {
t.Fatalf("invalid user. [expected:%v] [actual:%v]", expected, user)
}
}
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Status int `db:"status"`
}
type testDB int
type testRows int
func (db testDB) Query(query string, args ...interface{}) (testRows, error) {
return 0, nil
}
func (rows testRows) Close() error {
return nil
}
func (rows testRows) Scan(dest ...interface{}) error {
fmt.Sscan("1234 huandu 1", dest...)
return nil
}
var userStruct = NewStruct(new(User))
var db testDB
func ExampleStruct() {
// Suppose we defined following type and global variable.
//
// type User struct {
// ID int64 `db:"id"`
// Name string `db:"name"`
// Status int `db:"status"`
// }
//
// var userStruct = NewStruct(new(User))
// Prepare SELECT query.
sb := userStruct.Select("user")
sb.Where(sb.E("id", 1234))
sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)
// Execute the query.
rows, _ := db.Query(sql, args...)
defer rows.Close()
// Scan row data to user.
var user User
rows.Scan(userStruct.Addr(&user)...)
fmt.Printf("%#v", user)
// Output:
// SELECT id, name, status FROM user WHERE id = ? LIMIT 1
// [1234]
// sqlbuilder.User{ID:1234, Name:"huandu", Status:1}
}