Merge branch 'master' into master
This commit is contained in:
commit
02414bac40
|
@ -4,9 +4,7 @@
|
||||||
|
|
||||||
Xorm is a simple and powerful ORM for Go.
|
Xorm is a simple and powerful ORM for Go.
|
||||||
|
|
||||||
[](https://drone.gitea.com/xorm/xorm) [](https://gocover.io/xorm.io/xorm)
|
[](https://drone.gitea.com/xorm/xorm) [](https://gocover.io/xorm.io/xorm) [](https://goreportcard.com/report/xorm.io/xorm) [](https://discord.gg/HuR2CF3)
|
||||||
[](https://goreportcard.com/report/xorm.io/xorm)
|
|
||||||
[](https://discord.gg/HuR2CF3)
|
|
||||||
|
|
||||||
## Notice
|
## Notice
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
|
|
||||||
xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。
|
xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。
|
||||||
|
|
||||||
[](https://drone.gitea.com/xorm/xorm) [](https://gocover.io/xorm.io/xorm)
|
[](https://drone.gitea.com/xorm/xorm) [](https://gocover.io/xorm.io/xorm) [](https://goreportcard.com/report/xorm.io/xorm) [](https://discord.gg/HuR2CF3)
|
||||||
[](https://goreportcard.com/report/xorm.io/xorm)
|
|
||||||
[](https://discord.gg/HuR2CF3)
|
|
||||||
|
|
||||||
## Notice
|
## Notice
|
||||||
|
|
||||||
|
|
70
engine.go
70
engine.go
|
@ -816,81 +816,11 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) {
|
||||||
return session.IsTableExist(beanOrTableName)
|
return session.IsTableExist(beanOrTableName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDOf get id from one struct
|
|
||||||
func (engine *Engine) IDOf(bean interface{}) (schemas.PK, error) {
|
|
||||||
return engine.IDOfV(reflect.ValueOf(bean))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName returns table name with schema prefix if has
|
// TableName returns table name with schema prefix if has
|
||||||
func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string {
|
func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string {
|
||||||
return dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, includeSchema...)
|
return dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean, includeSchema...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDOfV get id from one value of struct
|
|
||||||
func (engine *Engine) IDOfV(rv reflect.Value) (schemas.PK, error) {
|
|
||||||
return engine.idOfV(rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) {
|
|
||||||
v := reflect.Indirect(rv)
|
|
||||||
table, err := engine.tagParser.ParseWithCache(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pk := make([]interface{}, len(table.PrimaryKeys))
|
|
||||||
for i, col := range table.PKColumns() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
fieldName := col.FieldName
|
|
||||||
for {
|
|
||||||
parts := strings.SplitN(fieldName, ".", 2)
|
|
||||||
if len(parts) == 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
v = v.FieldByName(parts[0])
|
|
||||||
if v.Kind() == reflect.Ptr {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
if v.Kind() != reflect.Struct {
|
|
||||||
return nil, ErrUnSupportedType
|
|
||||||
}
|
|
||||||
fieldName = parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
pkField := v.FieldByName(fieldName)
|
|
||||||
switch pkField.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
pk[i], err = engine.idTypeAssertion(col, pkField.String())
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
pk[i], err = engine.idTypeAssertion(col, strconv.FormatInt(pkField.Int(), 10))
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
// id of uint will be converted to int64
|
|
||||||
pk[i], err = engine.idTypeAssertion(col, strconv.FormatUint(pkField.Uint(), 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return schemas.PK(pk), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (engine *Engine) idTypeAssertion(col *schemas.Column, sid string) (interface{}, error) {
|
|
||||||
if col.SQLType.IsNumeric() {
|
|
||||||
n, err := strconv.ParseInt(sid, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
} else if col.SQLType.IsText() {
|
|
||||||
return sid, nil
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("not supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIndexes create indexes
|
// CreateIndexes create indexes
|
||||||
func (engine *Engine) CreateIndexes(bean interface{}) error {
|
func (engine *Engine) CreateIndexes(bean interface{}) error {
|
||||||
session := engine.NewSession()
|
session := engine.NewSession()
|
||||||
|
|
|
@ -502,10 +502,48 @@ func TestFindAndCountOneFunc(t *testing.T) {
|
||||||
assert.EqualValues(t, 1, cnt)
|
assert.EqualValues(t, 1, cnt)
|
||||||
|
|
||||||
results = make([]FindAndCountStruct, 0, 1)
|
results = make([]FindAndCountStruct, 0, 1)
|
||||||
cnt, err = testEngine.Where("msg = ?", true).Limit(1).FindAndCount(&results)
|
cnt, err = testEngine.Where("1=1").Limit(1).FindAndCount(&results)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 1, len(results))
|
assert.EqualValues(t, 1, len(results))
|
||||||
assert.EqualValues(t, 1, cnt)
|
assert.EqualValues(t, 2, cnt)
|
||||||
|
assert.EqualValues(t, FindAndCountStruct{
|
||||||
|
Id: 1,
|
||||||
|
Content: "111",
|
||||||
|
Msg: false,
|
||||||
|
}, results[0])
|
||||||
|
|
||||||
|
results = make([]FindAndCountStruct, 0, 1)
|
||||||
|
cnt, err = testEngine.Where("1=1").Limit(1).FindAndCount(&results)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, len(results))
|
||||||
|
assert.EqualValues(t, 2, cnt)
|
||||||
|
assert.EqualValues(t, FindAndCountStruct{
|
||||||
|
Id: 1,
|
||||||
|
Content: "111",
|
||||||
|
Msg: false,
|
||||||
|
}, results[0])
|
||||||
|
|
||||||
|
results = make([]FindAndCountStruct, 0, 1)
|
||||||
|
cnt, err = testEngine.Where("1=1").Limit(1, 1).FindAndCount(&results)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, len(results))
|
||||||
|
assert.EqualValues(t, 2, cnt)
|
||||||
|
assert.EqualValues(t, FindAndCountStruct{
|
||||||
|
Id: 2,
|
||||||
|
Content: "222",
|
||||||
|
Msg: true,
|
||||||
|
}, results[0])
|
||||||
|
|
||||||
|
results = make([]FindAndCountStruct, 0, 1)
|
||||||
|
cnt, err = testEngine.Where("1=1").Limit(1, 1).FindAndCount(&results)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 1, len(results))
|
||||||
|
assert.EqualValues(t, 2, cnt)
|
||||||
|
assert.EqualValues(t, FindAndCountStruct{
|
||||||
|
Id: 2,
|
||||||
|
Content: "222",
|
||||||
|
Msg: true,
|
||||||
|
}, results[0])
|
||||||
|
|
||||||
results = make([]FindAndCountStruct, 0, 1)
|
results = make([]FindAndCountStruct, 0, 1)
|
||||||
cnt, err = testEngine.Where("msg = ?", true).Select("id, content, msg").
|
cnt, err = testEngine.Where("msg = ?", true).Select("id, content, msg").
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
"xorm.io/xorm/internal/statements"
|
||||||
"xorm.io/xorm/internal/utils"
|
"xorm.io/xorm/internal/utils"
|
||||||
"xorm.io/xorm/names"
|
"xorm.io/xorm/names"
|
||||||
)
|
)
|
||||||
|
@ -39,6 +40,14 @@ func TestUpdateMap(t *testing.T) {
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 1, cnt)
|
assert.EqualValues(t, 1, cnt)
|
||||||
|
|
||||||
|
cnt, err = testEngine.Table("update_table").ID(tb.Id).Update(map[string]interface{}{
|
||||||
|
"name": "test2",
|
||||||
|
"age": 36,
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.True(t, statements.IsIDConditionWithNoTableErr(err))
|
||||||
|
assert.EqualValues(t, 0, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateLimit(t *testing.T) {
|
func TestUpdateLimit(t *testing.T) {
|
||||||
|
@ -988,7 +997,7 @@ func TestUpdateMapContent(t *testing.T) {
|
||||||
assert.EqualValues(t, false, c2.IsMan)
|
assert.EqualValues(t, false, c2.IsMan)
|
||||||
assert.EqualValues(t, 2, c2.Gender)
|
assert.EqualValues(t, 2, c2.Gender)
|
||||||
|
|
||||||
cnt, err = testEngine.Table(testEngine.TableName(new(UpdateMapContent))).ID(c.Id).Update(map[string]interface{}{
|
cnt, err = testEngine.Table(new(UpdateMapContent)).ID(c.Id).Update(map[string]interface{}{
|
||||||
"age": 15,
|
"age": 15,
|
||||||
"is_man": true,
|
"is_man": true,
|
||||||
"gender": 1,
|
"gender": 1,
|
||||||
|
|
|
@ -20,6 +20,21 @@ var (
|
||||||
uintType = reflect.TypeOf(uint64(0))
|
uintType = reflect.TypeOf(uint64(0))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrIDConditionWithNoTable represents an error there is no reference table with an ID condition
|
||||||
|
type ErrIDConditionWithNoTable struct {
|
||||||
|
ID schemas.PK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrIDConditionWithNoTable) Error() string {
|
||||||
|
return fmt.Sprintf("ID condition %#v need reference table", err.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIDConditionWithNoTableErr return true if the err is ErrIDConditionWithNoTable
|
||||||
|
func IsIDConditionWithNoTableErr(err error) bool {
|
||||||
|
_, ok := err.(ErrIDConditionWithNoTable)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?"
|
// ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?"
|
||||||
func (statement *Statement) ID(id interface{}) *Statement {
|
func (statement *Statement) ID(id interface{}) *Statement {
|
||||||
switch t := id.(type) {
|
switch t := id.(type) {
|
||||||
|
@ -58,11 +73,16 @@ func (statement *Statement) ID(id interface{}) *Statement {
|
||||||
return statement
|
return statement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessIDParam handles the process of id condition
|
||||||
func (statement *Statement) ProcessIDParam() error {
|
func (statement *Statement) ProcessIDParam() error {
|
||||||
if statement.idParam == nil || statement.RefTable == nil {
|
if statement.idParam == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if statement.RefTable == nil {
|
||||||
|
return ErrIDConditionWithNoTable{statement.idParam}
|
||||||
|
}
|
||||||
|
|
||||||
if len(statement.RefTable.PrimaryKeys) != len(statement.idParam) {
|
if len(statement.RefTable.PrimaryKeys) != len(statement.idParam) {
|
||||||
return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d",
|
return fmt.Errorf("ID condition is error, expect %d primarykeys, there are %d",
|
||||||
len(statement.RefTable.PrimaryKeys),
|
len(statement.RefTable.PrimaryKeys),
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
package schemas
|
package schemas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -115,3 +117,17 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) {
|
||||||
|
|
||||||
return &fieldValue, nil
|
return &fieldValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConvertID converts id content to suitable type according column type
|
||||||
|
func (col *Column) ConvertID(sid string) (interface{}, error) {
|
||||||
|
if col.SQLType.IsNumeric() {
|
||||||
|
n, err := strconv.ParseInt(sid, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
} else if col.SQLType.IsText() {
|
||||||
|
return sid, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not supported")
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
package schemas
|
package schemas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,6 +30,7 @@ type Table struct {
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewEmptyTable creates an empty table
|
||||||
func NewEmptyTable() *Table {
|
func NewEmptyTable() *Table {
|
||||||
return NewTable("", nil)
|
return NewTable("", nil)
|
||||||
}
|
}
|
||||||
|
@ -44,10 +47,12 @@ func NewTable(name string, t reflect.Type) *Table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Columns returns table's columns
|
||||||
func (table *Table) Columns() []*Column {
|
func (table *Table) Columns() []*Column {
|
||||||
return table.columns
|
return table.columns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ColumnsSeq returns table's column names according sequence
|
||||||
func (table *Table) ColumnsSeq() []string {
|
func (table *Table) ColumnsSeq() []string {
|
||||||
return table.columnsSeq
|
return table.columnsSeq
|
||||||
}
|
}
|
||||||
|
@ -61,6 +66,7 @@ func (table *Table) columnsByName(name string) []*Column {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetColumn returns column according column name, if column not found, return nil
|
||||||
func (table *Table) GetColumn(name string) *Column {
|
func (table *Table) GetColumn(name string) *Column {
|
||||||
cols := table.columnsByName(name)
|
cols := table.columnsByName(name)
|
||||||
if cols != nil {
|
if cols != nil {
|
||||||
|
@ -70,6 +76,7 @@ func (table *Table) GetColumn(name string) *Column {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetColumnIdx returns column according name and idx
|
||||||
func (table *Table) GetColumnIdx(name string, idx int) *Column {
|
func (table *Table) GetColumnIdx(name string, idx int) *Column {
|
||||||
cols := table.columnsByName(name)
|
cols := table.columnsByName(name)
|
||||||
if cols != nil && idx < len(cols) {
|
if cols != nil && idx < len(cols) {
|
||||||
|
@ -144,3 +151,45 @@ func (table *Table) AddColumn(col *Column) {
|
||||||
func (table *Table) AddIndex(index *Index) {
|
func (table *Table) AddIndex(index *Index) {
|
||||||
table.Indexes[index.Name] = index
|
table.Indexes[index.Name] = index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IDOfV get id from one value of struct
|
||||||
|
func (table *Table) IDOfV(rv reflect.Value) (PK, error) {
|
||||||
|
v := reflect.Indirect(rv)
|
||||||
|
pk := make([]interface{}, len(table.PrimaryKeys))
|
||||||
|
for i, col := range table.PKColumns() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
fieldName := col.FieldName
|
||||||
|
for {
|
||||||
|
parts := strings.SplitN(fieldName, ".", 2)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
v = v.FieldByName(parts[0])
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return nil, fmt.Errorf("Unsupported read value of column %s from field %s", col.Name, col.FieldName)
|
||||||
|
}
|
||||||
|
fieldName = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
pkField := v.FieldByName(fieldName)
|
||||||
|
switch pkField.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
pk[i], err = col.ConvertID(pkField.String())
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
pk[i], err = col.ConvertID(strconv.FormatInt(pkField.Int(), 10))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
// id of uint will be converted to int64
|
||||||
|
pk[i], err = col.ConvertID(strconv.FormatUint(pkField.Uint(), 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PK(pk), nil
|
||||||
|
}
|
||||||
|
|
|
@ -60,6 +60,12 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte
|
||||||
if session.statement.OrderStr != "" {
|
if session.statement.OrderStr != "" {
|
||||||
session.statement.OrderStr = ""
|
session.statement.OrderStr = ""
|
||||||
}
|
}
|
||||||
|
if session.statement.LimitN != nil {
|
||||||
|
session.statement.LimitN = nil
|
||||||
|
}
|
||||||
|
if session.statement.Start > 0 {
|
||||||
|
session.statement.Start = 0
|
||||||
|
}
|
||||||
|
|
||||||
// session has stored the conditions so we use `unscoped` to avoid duplicated condition.
|
// session has stored the conditions so we use `unscoped` to avoid duplicated condition.
|
||||||
return session.Unscoped().Count(reflect.New(sliceElementType).Interface())
|
return session.Unscoped().Count(reflect.New(sliceElementType).Interface())
|
||||||
|
@ -320,7 +326,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
|
||||||
}
|
}
|
||||||
var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys))
|
var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys))
|
||||||
for i, col := range table.PKColumns() {
|
for i, col := range table.PKColumns() {
|
||||||
pk[i], err = session.engine.idTypeAssertion(col, res[i])
|
pk[i], err = col.ConvertID(res[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -370,7 +376,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
|
||||||
} else {
|
} else {
|
||||||
session.engine.logger.Debugf("[cache] cache hit bean: %v, %v, %v", tableName, id, bean)
|
session.engine.logger.Debugf("[cache] cache hit bean: %v, %v, %v", tableName, id, bean)
|
||||||
|
|
||||||
pk, err := session.engine.IDOf(bean)
|
pk, err := table.IDOfV(reflect.ValueOf(bean))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -419,7 +425,6 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
session.statement = statement
|
session.statement = statement
|
||||||
|
|
||||||
vs := reflect.Indirect(reflect.ValueOf(beans))
|
vs := reflect.Indirect(reflect.ValueOf(beans))
|
||||||
|
@ -428,7 +433,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
|
||||||
if rv.Kind() != reflect.Ptr {
|
if rv.Kind() != reflect.Ptr {
|
||||||
rv = rv.Addr()
|
rv = rv.Addr()
|
||||||
}
|
}
|
||||||
id, err := session.engine.idOfV(rv)
|
id, err := table.IDOfV(rv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue