Skip to content

Commit

Permalink
Merge pull request #75 from flycash/main
Browse files Browse the repository at this point in the history
eorm, valuer: 提供基于 unsafe 的 Value 实现
  • Loading branch information
flycash authored May 30, 2022
2 parents 1b5055e + 182d7eb commit 7ebda8e
Show file tree
Hide file tree
Showing 21 changed files with 841 additions and 142 deletions.
1 change: 1 addition & 0 deletions .CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- [internal/value: 抽象 Value 接口与基于反射的实现](https://github.com/gotomicro/eorm/pull/60)
- [eorm: 改为依赖 value 包来获取值](https://github.com/gotomicro/eorm/pull/62)
- [eorm: 使用eorm作为标签名字](https://github.com/gotomicro/eorm/pull/72)
- [eorm, valuer: 提供基于 unsafe 的 Value 实现](https://github.com/gotomicro/eorm/pull/75)

### 文档, 代码质量以及文档
- [Add examples and docs for Aggregate and Assign](https://github.com/gotomicro/eorm/pull/50)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

丑话说前头。

- [eorm 支持的字段类型](https://github.com/gotomicro/eorm/discussions/71):这是指在 Go 语言层面上支持的类型,eorm 本身并不关心数据库表里面定义的类型;

### Go 版本

请使用 Go 1.18 以上版本。
Expand Down
10 changes: 6 additions & 4 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/gotomicro/eorm/internal/dialect"
"github.com/gotomicro/eorm/internal/errs"
"github.com/gotomicro/eorm/internal/model"
"github.com/gotomicro/eorm/internal/valuer"
"github.com/valyala/bytebufferpool"
)

Expand All @@ -35,10 +36,11 @@ type Query struct {

type builder struct {
registry model.MetaRegistry
dialect dialect.Dialect
// Use bytebufferpool to reduce memory allocation.
// After using buffer, it must be put back in bytebufferpool.
// Call bytebufferpool.Get() to get a buffer, call bytebufferpool.Put() to put buffer back to bytebufferpool.
dialect dialect.Dialect
valCreator valuer.Creator

// 使用 bytebufferpool 以减少内存分配
// 每次调用 Get 之后不要忘记再调用 Put
buffer *bytebufferpool.ByteBuffer
meta *model.TableMeta
args []interface{}
Expand Down
13 changes: 9 additions & 4 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package eorm
import (
"github.com/gotomicro/eorm/internal/dialect"
"github.com/gotomicro/eorm/internal/model"
"github.com/gotomicro/eorm/internal/valuer"
"github.com/valyala/bytebufferpool"
)

Expand All @@ -27,6 +28,7 @@ type DBOption func(db *DB)
type DB struct {
metaRegistry model.MetaRegistry
dialect dialect.Dialect
valCreator valuer.Creator
}

// NewDB returns DB.
Expand All @@ -35,42 +37,44 @@ func NewDB(opts ...DBOption) *DB {
db := &DB{
metaRegistry: model.NewMetaRegistry(),
dialect: dialect.MySQL,
valCreator: valuer.NewUnsafeValue,
}
for _, o := range opts {
o(db)
}
return db
}

// DBWithMetaRegistry specify the MetaRegistry
// DBWithMetaRegistry 指定元数据注册中心
func DBWithMetaRegistry(registry model.MetaRegistry) DBOption {
return func(db *DB) {
db.metaRegistry = registry
}
}

// DBWithDialect specify dialect
// DBWithDialect 指定方言
func DBWithDialect(dialect dialect.Dialect) DBOption {
return func(db *DB) {
db.dialect = dialect
}
}

// Delete starts a "delete" query.
// Delete 开始构建一个 DELETE 查询
func (db *DB) Delete() *Deleter {
return &Deleter{
builder: db.builder(),
}
}

// Update 开始构建一个 UPDATE 查询
func (db *DB) Update(table interface{}) *Updater {
return &Updater{
builder: db.builder(),
table: table,
}
}

// Insert generate Inserter to builder insert query
// Insert 开始构建一个 INSERT 查询
func (db *DB) Insert() *Inserter {
return &Inserter{
builder: db.builder(),
Expand All @@ -82,5 +86,6 @@ func (db *DB) builder() builder {
registry: db.metaRegistry,
dialect: db.dialect,
buffer: bytebufferpool.Get(),
valCreator: db.valCreator,
}
}
18 changes: 12 additions & 6 deletions insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"errors"
"github.com/gotomicro/eorm/internal/errs"
"github.com/gotomicro/eorm/internal/model"
"github.com/gotomicro/eorm/internal/value"
)

// Inserter is used to construct an insert query
Expand Down Expand Up @@ -47,17 +46,24 @@ func (i *Inserter) Build() (*Query, error) {
i.writeString("(")
fields, err := i.buildColumns()
if err != nil {
return &Query{}, err
return nil, err
}
i.writeString(")")
i.writeString(" VALUES")
for index, val := range i.values {
i.writeString("(")
refVal := value.NewValue(val)
meta, err := i.registry.Get(val)
if err != nil {
return nil, err
}
if meta.Typ != i.meta.Typ {
return nil, errs.NewInsertDiffTypesError(i.meta.Typ.Elem().Name(), meta.Typ.Elem().Name())
}
refVal := i.valCreator(val, meta)
for j, v := range fields {
fdVal, err := refVal.Field(v.FieldName)
if err != nil {
return &Query{}, err
return nil, err
}
i.parameter(fdVal)
if j != len(fields)-1 {
Expand Down Expand Up @@ -117,8 +123,8 @@ func (i *Inserter) buildColumns() ([]*model.ColumnMeta, error) {
cs = append(cs, v)
}
} else {
for index, value := range i.meta.Columns {
i.quote(value.ColumnName)
for index, val := range i.meta.Columns {
i.quote(val.ColumnName)
if index != len(cs)-1 {
i.comma()
}
Expand Down
22 changes: 12 additions & 10 deletions insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,23 @@ package eorm
import (
"errors"
"fmt"
"github.com/gotomicro/eorm/internal/errs"
"github.com/stretchr/testify/assert"
"testing"
"time"
)

func TestInserter_Values(t *testing.T) {
type User struct {
Id int64
FirstName string
Ctime time.Time
Ctime uint64
}
type Order struct {
Id int64
Name string
Price int64
}

n := time.Now()
n := uint64(1000)
u := &User{
Id: 12,
FirstName: "Tom",
Expand Down Expand Up @@ -78,7 +77,7 @@ func TestInserter_Values(t *testing.T) {
{
name: "an example with invalid columns",
builder: NewDB().Insert().Columns("id", "FirstName").Values(u),
wantErr: errors.New("eorm: 非法字段 id"),
wantErr: errors.New("eorm: 未知字段 id"),
},
{
name: "no whole columns and multiple values of same type",
Expand All @@ -87,9 +86,9 @@ func TestInserter_Values(t *testing.T) {
wantArgs: []interface{}{int64(12), "Tom", int64(13), "Jerry"},
},
{
name: "multiple values of invalid column",
name: "insert diff types",
builder: NewDB().Insert().Values(u, o1),
wantErr: errors.New("eorm: 非法字段 FirstName"),
wantErr: errs.NewInsertDiffTypesError("User", "Order"),
},
}

Expand All @@ -99,6 +98,9 @@ func TestInserter_Values(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
q, err := c.builder.Build()
assert.Equal(t, c.wantErr, err)
if err != nil {
return
}
assert.Equal(t, c.wantSql, q.SQL)
assert.Equal(t, c.wantArgs, q.Args)
})
Expand All @@ -118,10 +120,10 @@ func ExampleInserter_Build() {
// Output:
// case1
// SQL: INSERT INTO `test_model`(`id`,`first_name`,`age`,`last_name`) VALUES(?,?,?,?);
// Args: []interface {}{1, "", 18, (*string)(nil)}
// Args: []interface {}{1, "", 18, (*sql.NullString)(nil)}
// case2
// SQL: INSERT INTO `test_model`(`id`,`first_name`,`age`,`last_name`) VALUES(?,?,?,?);
// Args: []interface {}{0, "", 0, (*string)(nil)}
// Args: []interface {}{0, "", 0, (*sql.NullString)(nil)}
}

func ExampleInserter_Columns() {
Expand Down Expand Up @@ -155,5 +157,5 @@ func ExampleInserter_Values() {
fmt.Println(query.string())
// Output:
// SQL: INSERT INTO `test_model`(`id`,`first_name`,`age`,`last_name`) VALUES(?,?,?,?),(?,?,?,?);
// Args: []interface {}{1, "", 18, (*string)(nil), 0, "", 0, (*string)(nil)}
// Args: []interface {}{1, "", 18, (*sql.NullString)(nil), 0, "", 0, (*sql.NullString)(nil)}
}
16 changes: 14 additions & 2 deletions internal/errs/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,30 @@ package errs
import (
"errors"
"fmt"
"reflect"
)

var (
errValueNotSet = errors.New("value unset")
errValueNotSet = errors.New("eorm: 值未设置")
)


// NewInvalidColumnError returns an error represents invalid field name
func NewInvalidColumnError(field string) error {
return fmt.Errorf("eorm: 非法字段 %s", field)
return fmt.Errorf("eorm: 未知字段 %s", field)
}

func NewValueNotSetError() error {
return errValueNotSet
}

// NewUnsupportedTypeError 不支持的字段类型
// 请参阅 https://github.com/gotomicro/eorm/discussions/71
func NewUnsupportedTypeError(typ reflect.Type) error {
return fmt.Errorf("eorm: 不支持字段类型 %s, %s", typ.PkgPath(), typ.Name())
}

// NewInsertDiffTypesError 在批量插入中,试图插入不同类型的数据
func NewInsertDiffTypesError(origin, now string) error {
return fmt.Errorf("eorm: 试图插入不同类型的数据,原来:%s,现在:%s", origin, now)
}
27 changes: 25 additions & 2 deletions internal/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@
package model

import (
"database/sql"
"database/sql/driver"
"reflect"
"strings"
"sync"
// nolint
"unicode"
)

var (
scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
driverValuerType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
)

// TableMeta represents data model, or a table
type TableMeta struct {
TableName string
Expand All @@ -36,6 +44,20 @@ type ColumnMeta struct {
Typ reflect.Type
IsPrimaryKey bool
IsAutoIncrement bool
// Offset 是字段偏移量。需要注意的是,这里的字段偏移量是相对于整个结构体的偏移量
// 例如在组合的情况下,
// type A struct {
// name string
// B
// }
// type B struct {
// age int
// }
// age 的偏移量是相对于 A 的起始地址的偏移量
Offset uintptr
// IsHolderType 用于表达是否是 Holder 的类型
// 所谓的 Holder,就是指同时实现了 sql.Scanner 和 driver.Valuer 两个接口的类型
IsHolderType bool
}

// TableMetaOption represents options of TableMeta, this options will cover default cover.
Expand Down Expand Up @@ -63,7 +85,6 @@ func NewTagMetaRegistry() MetaRegistry {
// Get the metadata for each column of the data table,
// If there is none, it will register one and return the metadata for each column
func (t *tagMetaRegistry) Get(table interface{}) (*TableMeta, error) {

if v, ok := t.metas.Load(reflect.TypeOf(table)); ok {
return v.(*TableMeta), nil
}
Expand All @@ -75,7 +96,7 @@ func (t *tagMetaRegistry) Get(table interface{}) (*TableMeta, error) {
func (t *tagMetaRegistry) Register(table interface{}, opts ...TableMetaOption) (*TableMeta, error) {
rtype := reflect.TypeOf(table)
v := rtype.Elem()
columnMetas := []*ColumnMeta{}
var columnMetas []*ColumnMeta
lens := v.NumField()
fieldMap := make(map[string]*ColumnMeta, lens)
for i := 0; i < lens; i++ {
Expand All @@ -102,6 +123,8 @@ func (t *tagMetaRegistry) Register(table interface{}, opts ...TableMetaOption) (
Typ: structField.Type,
IsAutoIncrement: isAuto,
IsPrimaryKey: isKey,
Offset: structField.Offset,
IsHolderType: structField.Type.AssignableTo(scannerType) && structField.Type.AssignableTo(driverValuerType),
}
columnMetas = append(columnMetas, columnMeta)
fieldMap[columnMeta.FieldName] = columnMeta
Expand Down
9 changes: 8 additions & 1 deletion internal/model/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ func TestTagMetaRegistry(t *testing.T) {
input interface{}
}{
{
name: "普通model",
// 普通
name: "normal model",
wantMeta: &TableMeta{
TableName: "test_model",
Columns: []*ColumnMeta{
Expand All @@ -46,16 +47,19 @@ func TestTagMetaRegistry(t *testing.T) {
ColumnName: "first_name",
FieldName: "FirstName",
Typ: reflect.TypeOf(""),
Offset: 8,
},
{
ColumnName: "age",
FieldName: "Age",
Typ: reflect.TypeOf(int8(0)),
Offset: 24,
},
{
ColumnName: "last_name",
FieldName: "LastName",
Typ: reflect.TypeOf((*string)(nil)),
Offset: 32,
},
},
FieldMap: map[string]*ColumnMeta{
Expand All @@ -70,16 +74,19 @@ func TestTagMetaRegistry(t *testing.T) {
ColumnName: "first_name",
FieldName: "FirstName",
Typ: reflect.TypeOf(""),
Offset: 8,
},
"Age": {
ColumnName: "age",
FieldName: "Age",
Typ: reflect.TypeOf(int8(0)),
Offset: 24,
},
"LastName": {
ColumnName: "last_name",
FieldName: "LastName",
Typ: reflect.TypeOf((*string)(nil)),
Offset: 32,
},
},
Typ: reflect.TypeOf(&TestModel{}),
Expand Down
Loading

0 comments on commit 7ebda8e

Please sign in to comment.