Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions select_benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package sqrl

import "testing"

type SimpleStruct struct {
ID int
Name string
Age int
}

func Benchmark_SelectStruct(b *testing.B) {
s := SimpleStruct{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 重复调用,验证缓存效果
SelectFromStruct(&s)
}
}

func Benchmark_Select(b *testing.B) {
b.ResetTimer()
fields := []string{"ID", "Name", "Age"}
for i := 0; i < b.N; i++ {
// 手写字段,仅作对比
Select(fields...)
}
}
22 changes: 21 additions & 1 deletion select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ func TestSelectBuilderZeroOffsetLimit(t *testing.T) {
assert.Equal(t, expectedSql, sql)
}


func TestSelectBuilderFromSelect(t *testing.T) {
subQ := Select("c").From("d").Where(Eq{"i": 0})
b := Select("a", "b").FromSelect(subQ, "subq")
Expand All @@ -110,6 +109,27 @@ func TestSelectBuilderFromSelect(t *testing.T) {
assert.Equal(t, expectedArgs, args)
}

func TestSelectBuilderFromSelectWithPojo(t *testing.T) {
s1 := struct {
C string `db:"c"`
}{}

s2 := struct {
A string `db:"a"`
B string `db:"b"`
}{}
subQ := SelectFromStruct(s1).From("d").Where(Eq{"i": 0})
b := SelectFromStruct(s2).FromSelect(subQ, "subq")
sql, args, err := b.ToSql()
assert.NoError(t, err)

expectedSql := "SELECT a, b FROM (SELECT c FROM d WHERE i = ?) AS subq"
assert.Equal(t, expectedSql, sql)

expectedArgs := []interface{}{0}
assert.Equal(t, expectedArgs, args)
}

func TestSelectBuilderToSqlErr(t *testing.T) {
_, _, err := Select().From("x").ToSql()
assert.Error(t, err)
Expand Down
59 changes: 58 additions & 1 deletion statement.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package sqrl

import (
"errors"
"reflect"
)

// StatementBuilderType is the type of StatementBuilder.
type StatementBuilderType struct {
placeholderFormat PlaceholderFormat
runWith BaseRunner
columnsCacher columnsCacher
}

// Select returns a SelectBuilder for this StatementBuilder.
Expand Down Expand Up @@ -38,9 +44,18 @@ func (b StatementBuilderType) RunWith(runner BaseRunner) StatementBuilderType {
return b
}

// SelectFromStruct according to the target returns a SelectBuilder for this StatementBuilder.
func (b StatementBuilderType) SelectFromStruct(target interface{}) *SelectBuilder {
fields, err := b.columnsCacher.parseFields(target)
if err != nil {
return b.Select("*")
}
return b.Select(fields...)
}

// StatementBuilder is a basic statement builder, holds global configuration options
// like placeholder format or SQL runner
var StatementBuilder = StatementBuilderType{placeholderFormat: Question}
var StatementBuilder = StatementBuilderType{placeholderFormat: Question, columnsCacher: columnsCacher{cache: make(map[reflect.Type][]string)}}

// Select returns a new SelectBuilder, optionally setting some result columns.
//
Expand All @@ -49,6 +64,48 @@ func Select(columns ...string) *SelectBuilder {
return StatementBuilder.Select(columns...)
}

// SelectFromStruct returns a new SelectBuilder, according to the pojo object.
//
// See SelectBuilder.Columns.
func SelectFromStruct(target interface{}) *SelectBuilder {
return StatementBuilder.SelectFromStruct(target)
}

// columnsCacher for store the pojo struct to columns
type columnsCacher struct {
cache map[reflect.Type][]string
}

func (cacher *columnsCacher) parseFields(v interface{}) ([]string, error) {
if v == nil {
return nil, errors.New("must not be nil")
}
t := reflect.TypeOf(v)
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return nil, errors.New("must be a struct")
}
if fields, ok := cacher.cache[t]; ok {
return fields, nil
}
fields := make([]string, 0, t.NumField())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("db")
if field.PkgPath == "" {
if tag != "" && tag != "-" {
fields = append(fields, tag)
} else {
fields = append(fields, field.Name)
}
}
}
cacher.cache[t] = fields
return fields, nil
}

// Insert returns a new InsertBuilder with the given table name.
//
// See InsertBuilder.Into.
Expand Down
32 changes: 32 additions & 0 deletions statement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ func TestStatementBuilder(t *testing.T) {
assert.Equal(t, "SELECT test", db.LastExecSql)
}

func TestStatementWithPojo_WithTag(t *testing.T) {
db := &DBStub{}
sb := StatementBuilder.RunWith(db)

user := &struct {
Name string `db:"user_name"`
Password string `db:"password"`
otherField string
limitSelectField string `db:"-"`
}{
Name: "John Doe",
Password: "fgakfgkahfjka",
}
sb.SelectFromStruct(&user).Exec()
assert.Equal(t, "SELECT user_name, password", db.LastExecSql)
}

func TestStatementWithPojo_WithNoTag(t *testing.T) {
db := &DBStub{}
sb := StatementBuilder.RunWith(db)

user := &struct {
Name string
Password string
}{
Name: "John Doe",
Password: "fgakfgkahfjka",
}
sb.SelectFromStruct(&user).Exec()
assert.Equal(t, "SELECT Name, Password", db.LastExecSql)
}

func TestStatementBuilderPlaceholderFormat(t *testing.T) {
db := &DBStub{}
sb := StatementBuilder.RunWith(db).PlaceholderFormat(Dollar)
Expand Down