Merge b8373f09d7
into 9242b921d8
This commit is contained in:
commit
af0055f8c7
12
convert.go
12
convert.go
|
@ -25,11 +25,10 @@ func strconvErr(err error) error {
|
||||||
func cloneBytes(b []byte) []byte {
|
func cloneBytes(b []byte) []byte {
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
c := make([]byte, len(b))
|
|
||||||
copy(c, b)
|
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
c := make([]byte, len(b))
|
||||||
|
copy(c, b)
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func asString(src interface{}) string {
|
func asString(src interface{}) string {
|
||||||
|
@ -274,11 +273,12 @@ func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) {
|
||||||
return vv.String(), nil
|
return vv.String(), nil
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
if tp.Elem().Kind() == reflect.Uint8 {
|
if tp.Elem().Kind() == reflect.Uint8 {
|
||||||
v, err := strconv.ParseInt(string(vv.Interface().([]byte)), 10, 64)
|
return string(vv.Interface().([]byte)), nil
|
||||||
|
/*v, err := strconv.ParseInt(string(vv.Interface().([]byte)), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return v, nil
|
return v, nil*/
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -820,7 +820,7 @@ func (db *postgres) SqlType(c *core.Column) string {
|
||||||
case core.NVarchar:
|
case core.NVarchar:
|
||||||
res = core.Varchar
|
res = core.Varchar
|
||||||
case core.Uuid:
|
case core.Uuid:
|
||||||
res = core.Uuid
|
return core.Uuid
|
||||||
case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob:
|
case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob:
|
||||||
return core.Bytea
|
return core.Bytea
|
||||||
case core.Double:
|
case core.Double:
|
||||||
|
|
111
engine.go
111
engine.go
|
@ -9,7 +9,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -21,6 +20,7 @@ import (
|
||||||
|
|
||||||
"github.com/go-xorm/builder"
|
"github.com/go-xorm/builder"
|
||||||
"github.com/go-xorm/core"
|
"github.com/go-xorm/core"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Engine is the major struct of xorm, it means a database manager.
|
// Engine is the major struct of xorm, it means a database manager.
|
||||||
|
@ -799,13 +799,18 @@ func (engine *Engine) UnMapType(t reflect.Type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) autoMapType(v reflect.Value) (*core.Table, error) {
|
func (engine *Engine) autoMapType(v reflect.Value) (*core.Table, error) {
|
||||||
t := v.Type()
|
|
||||||
engine.mutex.Lock()
|
engine.mutex.Lock()
|
||||||
defer engine.mutex.Unlock()
|
defer engine.mutex.Unlock()
|
||||||
|
return engine.autoMapTypeNoLock(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) autoMapTypeNoLock(v reflect.Value) (*core.Table, error) {
|
||||||
|
t := v.Type()
|
||||||
table, ok := engine.Tables[t]
|
table, ok := engine.Tables[t]
|
||||||
if !ok {
|
if !ok {
|
||||||
var err error
|
var err error
|
||||||
table, err = engine.mapType(v)
|
var parsingTables = make(map[reflect.Type]*core.Table)
|
||||||
|
table, err = engine.mapType(parsingTables, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -879,9 +884,17 @@ var (
|
||||||
tpTableName = reflect.TypeOf((*TableName)(nil)).Elem()
|
tpTableName = reflect.TypeOf((*TableName)(nil)).Elem()
|
||||||
)
|
)
|
||||||
|
|
||||||
func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
|
func (engine *Engine) mapType(parsingTables map[reflect.Type]*core.Table, v reflect.Value) (*core.Table, error) {
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return nil, errors.New("need a struct to map")
|
||||||
|
}
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
|
if table, ok := parsingTables[t]; ok {
|
||||||
|
return table, nil
|
||||||
|
}
|
||||||
|
|
||||||
table := engine.newTable()
|
table := engine.newTable()
|
||||||
|
parsingTables[t] = table
|
||||||
if tb, ok := v.Interface().(TableName); ok {
|
if tb, ok := v.Interface().(TableName); ok {
|
||||||
table.Name = tb.TableName()
|
table.Name = tb.TableName()
|
||||||
} else {
|
} else {
|
||||||
|
@ -908,24 +921,38 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
|
||||||
fieldValue := v.Field(i)
|
fieldValue := v.Field(i)
|
||||||
fieldType := fieldValue.Type()
|
fieldType := fieldValue.Type()
|
||||||
|
|
||||||
if ormTagStr != "" {
|
var ctx = tagContext{
|
||||||
col = &core.Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false,
|
engine: engine,
|
||||||
IsAutoIncrement: false, MapType: core.TWOSIDES, Indexes: make(map[string]int)}
|
parsingTables: parsingTables,
|
||||||
tags := splitTag(ormTagStr)
|
|
||||||
|
|
||||||
|
table: table,
|
||||||
|
hasCacheTag: false,
|
||||||
|
hasNoCacheTag: false,
|
||||||
|
|
||||||
|
fieldValue: fieldValue,
|
||||||
|
indexNames: make(map[string]int),
|
||||||
|
isIndex: false,
|
||||||
|
isUnique: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ormTagStr != "" {
|
||||||
|
col = &core.Column{
|
||||||
|
FieldName: t.Field(i).Name,
|
||||||
|
FieldType: t.Field(i).Type,
|
||||||
|
Nullable: true,
|
||||||
|
IsPrimaryKey: false,
|
||||||
|
IsAutoIncrement: false,
|
||||||
|
MapType: core.TWOSIDES,
|
||||||
|
Indexes: make(map[string]int),
|
||||||
|
}
|
||||||
|
ctx.col = col
|
||||||
|
|
||||||
|
tags := splitTag(ormTagStr)
|
||||||
if len(tags) > 0 {
|
if len(tags) > 0 {
|
||||||
if tags[0] == "-" {
|
if tags[0] == "-" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctx = tagContext{
|
|
||||||
table: table,
|
|
||||||
col: col,
|
|
||||||
fieldValue: fieldValue,
|
|
||||||
indexNames: make(map[string]int),
|
|
||||||
engine: engine,
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.ToUpper(tags[0]) == "EXTENDS" {
|
if strings.ToUpper(tags[0]) == "EXTENDS" {
|
||||||
if err := ExtendsTagHandler(&ctx); err != nil {
|
if err := ExtendsTagHandler(&ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1021,20 +1048,57 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) {
|
||||||
} else {
|
} else {
|
||||||
sqlType = core.Type2SQLType(fieldType)
|
sqlType = core.Type2SQLType(fieldType)
|
||||||
}
|
}
|
||||||
col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name),
|
|
||||||
t.Field(i).Name, sqlType, sqlType.DefaultLength,
|
col = core.NewColumn(
|
||||||
sqlType.DefaultLength2, true)
|
engine.ColumnMapper.Obj2Table(t.Field(i).Name),
|
||||||
|
t.Field(i).Name,
|
||||||
|
|
||||||
|
sqlType,
|
||||||
|
sqlType.DefaultLength,
|
||||||
|
sqlType.DefaultLength2,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
|
||||||
if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) {
|
if fieldType.Kind() == reflect.Int64 && (strings.ToUpper(col.FieldName) == "ID" || strings.HasSuffix(strings.ToUpper(col.FieldName), ".ID")) {
|
||||||
idFieldColName = col.Name
|
idFieldColName = col.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
col.FieldType = t.Field(i).Type
|
||||||
|
ctx.col = col
|
||||||
}
|
}
|
||||||
if col.IsAutoIncrement {
|
if col.IsAutoIncrement {
|
||||||
col.Nullable = false
|
col.Nullable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
table.AddColumn(col)
|
var tp = fieldType
|
||||||
|
if isPtrStruct(fieldType) {
|
||||||
|
tp = fieldType.Elem()
|
||||||
|
}
|
||||||
|
if isStruct(tp) && col.AssociateTable == nil {
|
||||||
|
var isBelongsTo = !(tp.ConvertibleTo(core.TimeType) || col.SQLType.IsJson())
|
||||||
|
if _, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
|
||||||
|
isBelongsTo = false
|
||||||
|
}
|
||||||
|
if _, ok := fieldValue.Interface().(sql.Scanner); ok {
|
||||||
|
isBelongsTo = false
|
||||||
|
}
|
||||||
|
if _, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
|
||||||
|
isBelongsTo = false
|
||||||
|
}
|
||||||
|
if _, ok := fieldValue.Interface().(core.Conversion); ok {
|
||||||
|
isBelongsTo = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isBelongsTo {
|
||||||
|
err := BelongsToTagHandler(&ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
col.AssociateType = core.AssociateNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.AddColumn(col)
|
||||||
} // end for
|
} // end for
|
||||||
|
|
||||||
if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
|
if idFieldColName != "" && len(table.PrimaryKeys) == 0 {
|
||||||
|
@ -1444,6 +1508,13 @@ func (engine *Engine) Exist(bean ...interface{}) (bool, error) {
|
||||||
return session.Exist(bean...)
|
return session.Exist(bean...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load loads bean's belongs to tag field immedicatlly
|
||||||
|
func (engine *Engine) Load(bean interface{}, cols ...string) error {
|
||||||
|
session := engine.NewSession()
|
||||||
|
defer session.Close()
|
||||||
|
return session.Load(bean, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
// Find retrieve records from table, condiBeans's non-empty fields
|
// Find retrieve records from table, condiBeans's non-empty fields
|
||||||
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
|
// are conditions. beans could be []Struct, []*Struct, map[int64]Struct
|
||||||
// map[int64]*Struct
|
// map[int64]*Struct
|
||||||
|
|
37
helpers.go
37
helpers.go
|
@ -16,6 +16,14 @@ import (
|
||||||
"github.com/go-xorm/core"
|
"github.com/go-xorm/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isStruct(t reflect.Type) bool {
|
||||||
|
return t.Kind() == reflect.Struct || isPtrStruct(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPtrStruct(t reflect.Type) bool {
|
||||||
|
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
||||||
|
}
|
||||||
|
|
||||||
// str2PK convert string value to primary key value according to tp
|
// str2PK convert string value to primary key value according to tp
|
||||||
func str2PKValue(s string, tp reflect.Type) (reflect.Value, error) {
|
func str2PKValue(s string, tp reflect.Type) (reflect.Value, error) {
|
||||||
var err error
|
var err error
|
||||||
|
@ -96,26 +104,6 @@ func str2PK(s string, tp reflect.Type) (interface{}, error) {
|
||||||
return v.Interface(), nil
|
return v.Interface(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitTag(tag string) (tags []string) {
|
|
||||||
tag = strings.TrimSpace(tag)
|
|
||||||
var hasQuote = false
|
|
||||||
var lastIdx = 0
|
|
||||||
for i, t := range tag {
|
|
||||||
if t == '\'' {
|
|
||||||
hasQuote = !hasQuote
|
|
||||||
} else if t == ' ' {
|
|
||||||
if lastIdx < i && !hasQuote {
|
|
||||||
tags = append(tags, strings.TrimSpace(tag[lastIdx:i]))
|
|
||||||
lastIdx = i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lastIdx < len(tag) {
|
|
||||||
tags = append(tags, strings.TrimSpace(tag[lastIdx:]))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type zeroable interface {
|
type zeroable interface {
|
||||||
IsZero() bool
|
IsZero() bool
|
||||||
}
|
}
|
||||||
|
@ -471,3 +459,12 @@ func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool)
|
||||||
|
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isStringInSlice(s string, slice []string) bool {
|
||||||
|
for _, e := range slice {
|
||||||
|
if s == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -3,24 +3,3 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package xorm
|
package xorm
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestSplitTag(t *testing.T) {
|
|
||||||
var cases = []struct {
|
|
||||||
tag string
|
|
||||||
tags []string
|
|
||||||
}{
|
|
||||||
{"not null default '2000-01-01 00:00:00' TIMESTAMP", []string{"not", "null", "default", "'2000-01-01 00:00:00'", "TIMESTAMP"}},
|
|
||||||
{"TEXT", []string{"TEXT"}},
|
|
||||||
{"default('2000-01-01 00:00:00')", []string{"default('2000-01-01 00:00:00')"}},
|
|
||||||
{"json binary", []string{"json", "binary"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, kase := range cases {
|
|
||||||
tags := splitTag(kase.tag)
|
|
||||||
if !sliceEq(tags, kase.tags) {
|
|
||||||
t.Fatalf("[%d]%v is not equal [%d]%v", len(tags), tags, len(kase.tags), kase.tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ type Interface interface {
|
||||||
Alias(alias string) *Session
|
Alias(alias string) *Session
|
||||||
Asc(colNames ...string) *Session
|
Asc(colNames ...string) *Session
|
||||||
BufferSize(size int) *Session
|
BufferSize(size int) *Session
|
||||||
|
Cascade(...bool) *Session
|
||||||
Cols(columns ...string) *Session
|
Cols(columns ...string) *Session
|
||||||
Count(...interface{}) (int64, error)
|
Count(...interface{}) (int64, error)
|
||||||
CreateIndexes(bean interface{}) error
|
CreateIndexes(bean interface{}) error
|
||||||
|
@ -27,6 +28,7 @@ type Interface interface {
|
||||||
Delete(interface{}) (int64, error)
|
Delete(interface{}) (int64, error)
|
||||||
Distinct(columns ...string) *Session
|
Distinct(columns ...string) *Session
|
||||||
DropIndexes(bean interface{}) error
|
DropIndexes(bean interface{}) error
|
||||||
|
Load(interface{}, ...string) error
|
||||||
Exec(string, ...interface{}) (sql.Result, error)
|
Exec(string, ...interface{}) (sql.Result, error)
|
||||||
Exist(bean ...interface{}) (bool, error)
|
Exist(bean ...interface{}) (bool, error)
|
||||||
Find(interface{}, ...interface{}) error
|
Find(interface{}, ...interface{}) error
|
||||||
|
|
392
session.go
392
session.go
|
@ -17,6 +17,13 @@ import (
|
||||||
"github.com/go-xorm/core"
|
"github.com/go-xorm/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type loadClosure struct {
|
||||||
|
Func func(core.PK, *reflect.Value) error
|
||||||
|
pk core.PK
|
||||||
|
fieldValue *reflect.Value
|
||||||
|
loaded bool
|
||||||
|
}
|
||||||
|
|
||||||
// Session keep a pointer to sql.DB and provides all execution of all
|
// Session keep a pointer to sql.DB and provides all execution of all
|
||||||
// kind of database operations.
|
// kind of database operations.
|
||||||
type Session struct {
|
type Session struct {
|
||||||
|
@ -51,6 +58,9 @@ type Session struct {
|
||||||
lastSQL string
|
lastSQL string
|
||||||
lastSQLArgs []interface{}
|
lastSQLArgs []interface{}
|
||||||
|
|
||||||
|
cascadeMode cascadeMode
|
||||||
|
cascadeLevel int // load level
|
||||||
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +92,9 @@ func (session *Session) Init() {
|
||||||
|
|
||||||
session.lastSQL = ""
|
session.lastSQL = ""
|
||||||
session.lastSQLArgs = []interface{}{}
|
session.lastSQLArgs = []interface{}{}
|
||||||
|
|
||||||
|
session.cascadeMode = cascadeCompitable
|
||||||
|
session.cascadeLevel = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close release the connection from pool
|
// Close release the connection from pool
|
||||||
|
@ -149,7 +162,7 @@ func (session *Session) Alias(alias string) *Session {
|
||||||
|
|
||||||
// NoCascade indicate that no cascade load child object
|
// NoCascade indicate that no cascade load child object
|
||||||
func (session *Session) NoCascade() *Session {
|
func (session *Session) NoCascade() *Session {
|
||||||
session.statement.UseCascade = false
|
session.cascadeMode = cascadeLazy
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,9 +217,16 @@ func (session *Session) Charset(charset string) *Session {
|
||||||
|
|
||||||
// Cascade indicates if loading sub Struct
|
// Cascade indicates if loading sub Struct
|
||||||
func (session *Session) Cascade(trueOrFalse ...bool) *Session {
|
func (session *Session) Cascade(trueOrFalse ...bool) *Session {
|
||||||
|
var mode = cascadeEager
|
||||||
if len(trueOrFalse) >= 1 {
|
if len(trueOrFalse) >= 1 {
|
||||||
session.statement.UseCascade = trueOrFalse[0]
|
if trueOrFalse[0] {
|
||||||
|
mode = cascadeEager
|
||||||
|
} else {
|
||||||
|
mode = cascadeLazy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.cascadeMode = mode
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,8 +460,8 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rawValueType := reflect.TypeOf(rawValue.Interface())
|
|
||||||
vv := reflect.ValueOf(rawValue.Interface())
|
vv := reflect.ValueOf(rawValue.Interface())
|
||||||
|
rawValueType := vv.Type()
|
||||||
col := table.GetColumnIdx(key, idx)
|
col := table.GetColumnIdx(key, idx)
|
||||||
if col.IsPrimaryKey {
|
if col.IsPrimaryKey {
|
||||||
pk = append(pk, rawValue.Interface())
|
pk = append(pk, rawValue.Interface())
|
||||||
|
@ -629,175 +649,205 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b
|
||||||
session.engine.logger.Error("sql.Sanner error:", err.Error())
|
session.engine.logger.Error("sql.Sanner error:", err.Error())
|
||||||
hasAssigned = false
|
hasAssigned = false
|
||||||
}
|
}
|
||||||
} else if col.SQLType.IsJson() {
|
} else if session.cascadeLevel > 0 && ((col.AssociateType == core.AssociateNone &&
|
||||||
if rawValueType.Kind() == reflect.String {
|
session.cascadeMode == cascadeCompitable) ||
|
||||||
hasAssigned = true
|
(col.AssociateType == core.AssociateBelongsTo &&
|
||||||
x := reflect.New(fieldType)
|
session.cascadeMode == cascadeEager)) {
|
||||||
if len([]byte(vv.String())) > 0 {
|
var pk = make(core.PK, len(col.AssociateTable.PrimaryKeys))
|
||||||
err := json.Unmarshal([]byte(vv.String()), x.Interface())
|
var err error
|
||||||
if err != nil {
|
rawValueType := col.AssociateTable.PKColumns()[0].FieldType
|
||||||
return nil, err
|
if rawValueType.Kind() == reflect.Ptr {
|
||||||
}
|
pk[0] = reflect.New(rawValueType.Elem()).Interface()
|
||||||
fieldValue.Set(x.Elem())
|
} else {
|
||||||
}
|
pk[0] = reflect.New(rawValueType).Interface()
|
||||||
} else if rawValueType.Kind() == reflect.Slice {
|
|
||||||
hasAssigned = true
|
|
||||||
x := reflect.New(fieldType)
|
|
||||||
if len(vv.Bytes()) > 0 {
|
|
||||||
err := json.Unmarshal(vv.Bytes(), x.Interface())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fieldValue.Set(x.Elem())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if session.statement.UseCascade {
|
err = convertAssign(pk[0], vv.Interface())
|
||||||
table, err := session.engine.autoMapType(*fieldValue)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pk[0] = reflect.ValueOf(pk[0]).Elem().Interface()
|
||||||
|
session.afterProcessors = append(session.afterProcessors, executedProcessor{
|
||||||
|
fun: func(session *Session, bean interface{}) error {
|
||||||
|
fieldValue := bean.(*reflect.Value)
|
||||||
|
return session.getByPK(pk, fieldValue)
|
||||||
|
},
|
||||||
|
session: session,
|
||||||
|
bean: fieldValue,
|
||||||
|
})
|
||||||
|
session.cascadeLevel--
|
||||||
hasAssigned = true
|
hasAssigned = true
|
||||||
if len(table.PrimaryKeys) != 1 {
|
} else if col.AssociateType == core.AssociateBelongsTo {
|
||||||
return nil, errors.New("unsupported non or composited primary key cascade")
|
hasAssigned = true
|
||||||
}
|
err := convertAssign(fieldValue.FieldByName(table.PKColumns()[0].FieldName).Addr().Interface(),
|
||||||
var pk = make(core.PK, len(table.PrimaryKeys))
|
vv.Interface())
|
||||||
pk[0], err = asKind(vv, rawValueType)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPKZero(pk) {
|
|
||||||
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
|
|
||||||
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
|
|
||||||
// property to be fetched lazily
|
|
||||||
structInter := reflect.New(fieldValue.Type())
|
|
||||||
has, err := session.ID(pk).NoCascade().get(structInter.Interface())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if has {
|
|
||||||
fieldValue.Set(structInter.Elem())
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("cascade obj is not exist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
// !nashtsai! TODO merge duplicated codes above
|
if fieldType != core.PtrTimeType && fieldType.Elem().Kind() == reflect.Struct {
|
||||||
switch fieldType {
|
if session.cascadeLevel > 0 && ((col.AssociateType == core.AssociateNone &&
|
||||||
// following types case matching ptr's native type, therefore assign ptr directly
|
session.cascadeMode == cascadeCompitable) ||
|
||||||
case core.PtrStringType:
|
(col.AssociateType == core.AssociateBelongsTo &&
|
||||||
if rawValueType.Kind() == reflect.String {
|
session.cascadeMode == cascadeEager)) {
|
||||||
x := vv.String()
|
var pk = make(core.PK, len(col.AssociateTable.PrimaryKeys))
|
||||||
hasAssigned = true
|
var err error
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
rawValueType := col.AssociateTable.ColumnType(col.AssociateTable.PKColumns()[0].FieldName)
|
||||||
}
|
if rawValueType.Kind() == reflect.Ptr {
|
||||||
case core.PtrBoolType:
|
pk[0] = reflect.New(rawValueType.Elem()).Interface()
|
||||||
if rawValueType.Kind() == reflect.Bool {
|
} else {
|
||||||
x := vv.Bool()
|
pk[0] = reflect.New(rawValueType).Interface()
|
||||||
hasAssigned = true
|
}
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
err = convertAssign(pk[0], vv.Interface())
|
||||||
}
|
|
||||||
case core.PtrTimeType:
|
|
||||||
if rawValueType == core.PtrTimeType {
|
|
||||||
hasAssigned = true
|
|
||||||
var x = rawValue.Interface().(time.Time)
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrFloat64Type:
|
|
||||||
if rawValueType.Kind() == reflect.Float64 {
|
|
||||||
x := vv.Float()
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrUint64Type:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = uint64(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrInt64Type:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
x := vv.Int()
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrFloat32Type:
|
|
||||||
if rawValueType.Kind() == reflect.Float64 {
|
|
||||||
var x = float32(vv.Float())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrIntType:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = int(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrInt32Type:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = int32(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrInt8Type:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = int8(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrInt16Type:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = int16(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrUintType:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = uint(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.PtrUint32Type:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = uint32(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.Uint8Type:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = uint8(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.Uint16Type:
|
|
||||||
if rawValueType.Kind() == reflect.Int64 {
|
|
||||||
var x = uint16(vv.Int())
|
|
||||||
hasAssigned = true
|
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
|
||||||
case core.Complex64Type:
|
|
||||||
var x complex64
|
|
||||||
if len([]byte(vv.String())) > 0 {
|
|
||||||
err := json.Unmarshal([]byte(vv.String()), &x)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
pk[0] = reflect.ValueOf(pk[0]).Elem().Interface()
|
||||||
hasAssigned = true
|
session.afterProcessors = append(session.afterProcessors, executedProcessor{
|
||||||
case core.Complex128Type:
|
fun: func(session *Session, bean interface{}) error {
|
||||||
var x complex128
|
fieldValue := bean.(*reflect.Value)
|
||||||
if len([]byte(vv.String())) > 0 {
|
return session.getByPK(pk, fieldValue)
|
||||||
err := json.Unmarshal([]byte(vv.String()), &x)
|
},
|
||||||
|
session: session,
|
||||||
|
bean: fieldValue,
|
||||||
|
})
|
||||||
|
|
||||||
|
session.cascadeLevel--
|
||||||
|
hasAssigned = true
|
||||||
|
} else if col.AssociateType == core.AssociateBelongsTo {
|
||||||
|
hasAssigned = true
|
||||||
|
if fieldValue.IsNil() {
|
||||||
|
// FIXME: find id column
|
||||||
|
structInter := reflect.New(fieldValue.Type().Elem())
|
||||||
|
fieldValue.Set(structInter)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := convertAssign(fieldValue.Elem().FieldByName(table.PKColumns()[0].FieldName).Addr().Interface(),
|
||||||
|
vv.Interface())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
|
||||||
}
|
}
|
||||||
hasAssigned = true
|
} else {
|
||||||
} // switch fieldType
|
// !nashtsai! TODO merge duplicated codes above
|
||||||
|
switch fieldType {
|
||||||
|
// following types case matching ptr's native type, therefore assign ptr directly
|
||||||
|
case core.PtrStringType:
|
||||||
|
if rawValueType.Kind() == reflect.String {
|
||||||
|
x := vv.String()
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrBoolType:
|
||||||
|
if rawValueType.Kind() == reflect.Bool {
|
||||||
|
x := vv.Bool()
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrTimeType:
|
||||||
|
if rawValueType == core.PtrTimeType {
|
||||||
|
hasAssigned = true
|
||||||
|
var x = rawValue.Interface().(time.Time)
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrFloat64Type:
|
||||||
|
if rawValueType.Kind() == reflect.Float64 {
|
||||||
|
x := vv.Float()
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrUint64Type:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = uint64(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrInt64Type:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
x := vv.Int()
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrFloat32Type:
|
||||||
|
if rawValueType.Kind() == reflect.Float64 {
|
||||||
|
var x = float32(vv.Float())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrIntType:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = int(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrInt32Type:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = int32(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrInt8Type:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = int8(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrInt16Type:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = int16(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrUintType:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = uint(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrUint32Type:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = uint32(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrUint8Type:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = uint8(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrUint16Type:
|
||||||
|
if rawValueType.Kind() == reflect.Int64 {
|
||||||
|
var x = uint16(vv.Int())
|
||||||
|
hasAssigned = true
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
case core.PtrComplex64Type:
|
||||||
|
var x complex64
|
||||||
|
if len([]byte(vv.String())) > 0 {
|
||||||
|
err := json.Unmarshal([]byte(vv.String()), &x)
|
||||||
|
if err != nil {
|
||||||
|
session.engine.logger.Error(err)
|
||||||
|
} else {
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasAssigned = true
|
||||||
|
case core.PtrComplex128Type:
|
||||||
|
var x complex128
|
||||||
|
if len([]byte(vv.String())) > 0 {
|
||||||
|
err := json.Unmarshal([]byte(vv.String()), &x)
|
||||||
|
if err != nil {
|
||||||
|
session.engine.logger.Error(err)
|
||||||
|
} else {
|
||||||
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasAssigned = true
|
||||||
|
} // switch fieldType
|
||||||
|
}
|
||||||
} // switch fieldType.Kind()
|
} // switch fieldType.Kind()
|
||||||
|
|
||||||
// !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value
|
// !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value
|
||||||
|
@ -816,6 +866,40 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b
|
||||||
return pk, nil
|
return pk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (session *Session) getByPK(pk core.PK, fieldValue *reflect.Value) error {
|
||||||
|
if !isPKZero(pk) {
|
||||||
|
var structInter reflect.Value
|
||||||
|
if fieldValue.Kind() == reflect.Ptr {
|
||||||
|
if fieldValue.IsNil() {
|
||||||
|
structInter = reflect.New(fieldValue.Type().Elem())
|
||||||
|
} else {
|
||||||
|
structInter = *fieldValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
structInter = fieldValue.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := session.ID(pk).NoAutoCondition().get(structInter.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if has {
|
||||||
|
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
|
||||||
|
fieldValue.Set(structInter)
|
||||||
|
fmt.Println("getByPK value ptr:", fieldValue.Interface())
|
||||||
|
} else if fieldValue.Kind() == reflect.Struct {
|
||||||
|
fieldValue.Set(structInter.Elem())
|
||||||
|
fmt.Println("getByPK value:", fieldValue.Interface())
|
||||||
|
} else {
|
||||||
|
return errors.New("set value failed")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("cascade obj is not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// saveLastSQL stores executed query information
|
// saveLastSQL stores executed query information
|
||||||
func (session *Session) saveLastSQL(sql string, args ...interface{}) {
|
func (session *Session) saveLastSQL(sql string, args ...interface{}) {
|
||||||
session.lastSQL = sql
|
session.lastSQL = sql
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
// Copyright 2017 The Xorm Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/go-xorm/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load loads associated fields from database
|
||||||
|
func (session *Session) Load(beanOrSlices interface{}, cols ...string) error {
|
||||||
|
v := reflect.ValueOf(beanOrSlices)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.Slice {
|
||||||
|
return session.loadFind(beanOrSlices, cols...)
|
||||||
|
} else if v.Kind() == reflect.Struct {
|
||||||
|
return session.loadGet(beanOrSlices, cols...)
|
||||||
|
}
|
||||||
|
return errors.New("unsupported load type, must struct or slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadFind load 's belongs to tag field immedicatlly
|
||||||
|
func (session *Session) loadFind(slices interface{}, cols ...string) error {
|
||||||
|
v := reflect.ValueOf(slices)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if v.Kind() != reflect.Slice {
|
||||||
|
return errors.New("only slice is supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Len() <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := v.Index(0)
|
||||||
|
if vv.Kind() == reflect.Ptr {
|
||||||
|
vv = vv.Elem()
|
||||||
|
}
|
||||||
|
tb, err := session.engine.autoMapType(vv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pks = make(map[*core.Column][]interface{})
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
ev := v.Index(i)
|
||||||
|
|
||||||
|
for _, col := range tb.Columns() {
|
||||||
|
if len(cols) > 0 && !isStringInSlice(col.Name, cols) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if col.AssociateTable != nil {
|
||||||
|
if col.AssociateType == core.AssociateBelongsTo {
|
||||||
|
colV, err := col.ValueOfV(&ev)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := session.engine.idOfV(*colV)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
/*var colPtr reflect.Value
|
||||||
|
if colV.Kind() == reflect.Ptr {
|
||||||
|
colPtr = *colV
|
||||||
|
} else {
|
||||||
|
colPtr = colV.Addr()
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if !isZero(pk[0]) {
|
||||||
|
pks[col] = append(pks[col], pk[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for col, pk := range pks {
|
||||||
|
slice := reflect.MakeSlice(col.FieldType, 0, len(pk))
|
||||||
|
err = session.In(col.Name, pk...).find(slice.Addr().Interface())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadGet load bean's belongs to tag field immedicatlly
|
||||||
|
func (session *Session) loadGet(bean interface{}, cols ...string) error {
|
||||||
|
if session.isAutoClose {
|
||||||
|
defer session.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
v := rValue(bean)
|
||||||
|
tb, err := session.engine.autoMapType(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, col := range tb.Columns() {
|
||||||
|
if len(cols) > 0 && !isStringInSlice(col.Name, cols) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if col.AssociateTable != nil {
|
||||||
|
if col.AssociateType == core.AssociateBelongsTo {
|
||||||
|
colV, err := col.ValueOfV(&v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := session.engine.idOfV(*colV)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var colPtr reflect.Value
|
||||||
|
if colV.Kind() == reflect.Ptr {
|
||||||
|
colPtr = *colV
|
||||||
|
} else {
|
||||||
|
colPtr = colV.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isZero(pk[0]) && session.cascadeLevel > 0 {
|
||||||
|
has, err := session.ID(pk).NoAutoCondition().get(colPtr.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return errors.New("load bean does not exist")
|
||||||
|
}
|
||||||
|
session.cascadeLevel--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
// Copyright 2017 The Xorm Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBelongsTo_Get(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
|
type Face1 struct {
|
||||||
|
Id int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nose1 struct {
|
||||||
|
Id int64
|
||||||
|
Face Face1 `xorm:"belongs_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := testEngine.Sync2(new(Nose1), new(Face1))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var face = Face1{
|
||||||
|
Name: "face1",
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&face)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfgFace Face1
|
||||||
|
has, err := testEngine.Get(&cfgFace)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, face, cfgFace)
|
||||||
|
|
||||||
|
var nose = Nose1{Face: face}
|
||||||
|
_, err = testEngine.Insert(&nose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfgNose Nose1
|
||||||
|
has, err = testEngine.Get(&cfgNose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, nose.Id, cfgNose.Id)
|
||||||
|
assert.Equal(t, nose.Face.Id, cfgNose.Face.Id)
|
||||||
|
assert.Equal(t, "", cfgNose.Face.Name)
|
||||||
|
|
||||||
|
err = testEngine.Load(&cfgNose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, nose.Id, cfgNose.Id)
|
||||||
|
assert.Equal(t, nose.Face.Id, cfgNose.Face.Id)
|
||||||
|
assert.Equal(t, "face1", cfgNose.Face.Name)
|
||||||
|
|
||||||
|
var cfgNose2 Nose1
|
||||||
|
has, err = testEngine.Cascade().Get(&cfgNose2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, nose.Id, cfgNose2.Id)
|
||||||
|
assert.Equal(t, nose.Face.Id, cfgNose2.Face.Id)
|
||||||
|
assert.Equal(t, "face1", cfgNose2.Face.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBelongsTo_GetPtr(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
|
type Face2 struct {
|
||||||
|
Id int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nose2 struct {
|
||||||
|
Id int64
|
||||||
|
Face *Face2 `xorm:"belongs_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := testEngine.Sync2(new(Nose2), new(Face2))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var face = Face2{
|
||||||
|
Name: "face1",
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&face)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfgFace Face2
|
||||||
|
has, err := testEngine.Get(&cfgFace)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, face, cfgFace)
|
||||||
|
|
||||||
|
var nose = Nose2{Face: &face}
|
||||||
|
_, err = testEngine.Insert(&nose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfgNose Nose2
|
||||||
|
has, err = testEngine.Get(&cfgNose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, nose.Id, cfgNose.Id)
|
||||||
|
assert.Equal(t, nose.Face.Id, cfgNose.Face.Id)
|
||||||
|
|
||||||
|
err = testEngine.Load(&cfgNose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, nose.Id, cfgNose.Id)
|
||||||
|
assert.Equal(t, nose.Face.Id, cfgNose.Face.Id)
|
||||||
|
assert.Equal(t, "face1", cfgNose.Face.Name)
|
||||||
|
|
||||||
|
var cfgNose2 Nose2
|
||||||
|
has, err = testEngine.Cascade().Get(&cfgNose2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, nose.Id, cfgNose2.Id)
|
||||||
|
assert.Equal(t, nose.Face.Id, cfgNose2.Face.Id)
|
||||||
|
assert.Equal(t, "face1", cfgNose2.Face.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBelongsTo_Find(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
|
type Face3 struct {
|
||||||
|
Id int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nose3 struct {
|
||||||
|
Id int64
|
||||||
|
Face Face3 `xorm:"belongs_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := testEngine.Sync2(new(Nose3), new(Face3))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var face1 = Face3{
|
||||||
|
Name: "face1",
|
||||||
|
}
|
||||||
|
var face2 = Face3{
|
||||||
|
Name: "face2",
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&face1, &face2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var noses = []Nose3{
|
||||||
|
{Face: face1},
|
||||||
|
{Face: face2},
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&noses)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var noses1 []Nose3
|
||||||
|
err = testEngine.Find(&noses1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(noses1))
|
||||||
|
assert.Equal(t, face1.Id, noses1[0].Face.Id)
|
||||||
|
assert.Equal(t, face2.Id, noses1[1].Face.Id)
|
||||||
|
assert.Equal(t, "", noses1[0].Face.Name)
|
||||||
|
assert.Equal(t, "", noses1[1].Face.Name)
|
||||||
|
|
||||||
|
var noses2 []Nose3
|
||||||
|
err = testEngine.Cascade().Find(&noses2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(noses2))
|
||||||
|
assert.Equal(t, face1.Id, noses2[0].Face.Id)
|
||||||
|
assert.Equal(t, face2.Id, noses2[1].Face.Id)
|
||||||
|
assert.Equal(t, "face1", noses2[0].Face.Name)
|
||||||
|
assert.Equal(t, "face2", noses2[1].Face.Name)
|
||||||
|
|
||||||
|
err = testEngine.Load(noses1, "face")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "face1", noses1[0].Face.Name)
|
||||||
|
assert.Equal(t, "face2", noses1[1].Face.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBelongsTo_FindPtr(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
|
type Face4 struct {
|
||||||
|
Id int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nose4 struct {
|
||||||
|
Id int64
|
||||||
|
Face *Face4 `xorm:"belongs_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := testEngine.Sync2(new(Nose4), new(Face4))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var face1 = Face4{
|
||||||
|
Name: "face1",
|
||||||
|
}
|
||||||
|
var face2 = Face4{
|
||||||
|
Name: "face2",
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&face1, &face2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var noses = []Nose4{
|
||||||
|
{Face: &face1},
|
||||||
|
{Face: &face2},
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&noses)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var noses1 []Nose4
|
||||||
|
err = testEngine.Find(&noses1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(noses1))
|
||||||
|
assert.Equal(t, face1.Id, noses1[0].Face.Id)
|
||||||
|
assert.Equal(t, face2.Id, noses1[1].Face.Id)
|
||||||
|
assert.Equal(t, "", noses1[0].Face.Name)
|
||||||
|
assert.Equal(t, "", noses1[1].Face.Name)
|
||||||
|
|
||||||
|
var noses2 []Nose4
|
||||||
|
err = testEngine.Cascade().Find(&noses2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(noses2))
|
||||||
|
assert.NotNil(t, noses2[0].Face)
|
||||||
|
assert.NotNil(t, noses2[1].Face)
|
||||||
|
assert.Equal(t, face1.Id, noses2[0].Face.Id)
|
||||||
|
assert.Equal(t, face2.Id, noses2[1].Face.Id)
|
||||||
|
assert.Equal(t, "face1", noses2[0].Face.Name)
|
||||||
|
assert.Equal(t, "face2", noses2[1].Face.Name)
|
||||||
|
|
||||||
|
err = testEngine.Load(noses2, "face")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -203,39 +202,22 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
|
||||||
}
|
}
|
||||||
v = x
|
v = x
|
||||||
fieldValue.Set(reflect.ValueOf(v).Convert(fieldType))
|
fieldValue.Set(reflect.ValueOf(v).Convert(fieldType))
|
||||||
} else if session.statement.UseCascade {
|
} else if session.cascadeLevel > 0 && ((col.AssociateType == core.AssociateNone &&
|
||||||
table, err := session.engine.autoMapType(*fieldValue)
|
session.cascadeMode == cascadeCompitable) ||
|
||||||
if err != nil {
|
(col.AssociateType == core.AssociateBelongsTo &&
|
||||||
return err
|
session.cascadeMode == cascadeEager)) {
|
||||||
}
|
var pk = make(core.PK, len(col.AssociateTable.PrimaryKeys))
|
||||||
|
// only 1 PK checked on tag parsing
|
||||||
// TODO: current only support 1 primary key
|
rawValueType := col.AssociateTable.ColumnType(col.AssociateTable.PKColumns()[0].FieldName)
|
||||||
if len(table.PrimaryKeys) > 1 {
|
var err error
|
||||||
return errors.New("unsupported composited primary key cascade")
|
|
||||||
}
|
|
||||||
|
|
||||||
var pk = make(core.PK, len(table.PrimaryKeys))
|
|
||||||
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
|
|
||||||
pk[0], err = str2PK(string(data), rawValueType)
|
pk[0], err = str2PK(string(data), rawValueType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPKZero(pk) {
|
session.cascadeLevel--
|
||||||
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
|
if err = session.getByPK(pk, fieldValue); err != nil {
|
||||||
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
|
return err
|
||||||
// property to be fetched lazily
|
|
||||||
structInter := reflect.New(fieldValue.Type())
|
|
||||||
has, err := session.ID(pk).NoCascade().get(structInter.Interface())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if has {
|
|
||||||
v = structInter.Elem().Interface()
|
|
||||||
fieldValue.Set(reflect.ValueOf(v))
|
|
||||||
} else {
|
|
||||||
return errors.New("cascade obj is not exist")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -485,41 +467,23 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
|
||||||
v = x
|
v = x
|
||||||
fieldValue.Set(reflect.ValueOf(&x))
|
fieldValue.Set(reflect.ValueOf(&x))
|
||||||
default:
|
default:
|
||||||
if session.statement.UseCascade {
|
if session.cascadeLevel > 0 && ((col.AssociateType == core.AssociateNone &&
|
||||||
structInter := reflect.New(fieldType.Elem())
|
session.cascadeMode == cascadeCompitable) ||
|
||||||
table, err := session.engine.autoMapType(structInter.Elem())
|
(col.AssociateType == core.AssociateBelongsTo &&
|
||||||
if err != nil {
|
session.cascadeMode == cascadeEager)) {
|
||||||
return err
|
var pk = make(core.PK, len(col.AssociateTable.PrimaryKeys))
|
||||||
}
|
var err error
|
||||||
|
// only 1 PK checked on tag parsing
|
||||||
if len(table.PrimaryKeys) > 1 {
|
rawValueType := col.AssociateTable.ColumnType(col.AssociateTable.PKColumns()[0].FieldName)
|
||||||
return errors.New("unsupported composited primary key cascade")
|
|
||||||
}
|
|
||||||
|
|
||||||
var pk = make(core.PK, len(table.PrimaryKeys))
|
|
||||||
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
|
|
||||||
pk[0], err = str2PK(string(data), rawValueType)
|
pk[0], err = str2PK(string(data), rawValueType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPKZero(pk) {
|
session.cascadeLevel--
|
||||||
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
|
if err = session.getByPK(pk, fieldValue); err != nil {
|
||||||
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
|
return err
|
||||||
// property to be fetched lazily
|
|
||||||
has, err := session.ID(pk).NoCascade().get(structInter.Interface())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if has {
|
|
||||||
v = structInter.Interface()
|
|
||||||
fieldValue.Set(reflect.ValueOf(v))
|
|
||||||
} else {
|
|
||||||
return errors.New("cascade obj is not exist")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -205,9 +205,8 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va
|
||||||
|
|
||||||
var newElemFunc func(fields []string) reflect.Value
|
var newElemFunc func(fields []string) reflect.Value
|
||||||
elemType := containerValue.Type().Elem()
|
elemType := containerValue.Type().Elem()
|
||||||
var isPointer bool
|
var isPointer = elemType.Kind() == reflect.Ptr
|
||||||
if elemType.Kind() == reflect.Ptr {
|
if isPointer {
|
||||||
isPointer = true
|
|
||||||
elemType = elemType.Elem()
|
elemType = elemType.Elem()
|
||||||
}
|
}
|
||||||
if elemType.Kind() == reflect.Ptr {
|
if elemType.Kind() == reflect.Ptr {
|
||||||
|
@ -237,7 +236,9 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va
|
||||||
if isPointer {
|
if isPointer {
|
||||||
containerValue.Set(reflect.Append(containerValue, newValue.Elem().Addr()))
|
containerValue.Set(reflect.Append(containerValue, newValue.Elem().Addr()))
|
||||||
} else {
|
} else {
|
||||||
|
fmt.Println("---", newValue.Elem())
|
||||||
containerValue.Set(reflect.Append(containerValue, newValue.Elem()))
|
containerValue.Set(reflect.Append(containerValue, newValue.Elem()))
|
||||||
|
fmt.Println("===", containerValue.Interface())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,7 +265,7 @@ func (session *Session) Sync2(beans ...interface{}) error {
|
||||||
|
|
||||||
for _, bean := range beans {
|
for _, bean := range beans {
|
||||||
v := rValue(bean)
|
v := rValue(bean)
|
||||||
table, err := engine.mapType(v)
|
table, err := engine.autoMapType(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
11
statement.go
11
statement.go
|
@ -33,6 +33,15 @@ type exprParam struct {
|
||||||
expr string
|
expr string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cascadeMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
cascadeCompitable cascadeMode = iota // load field beans with another SQL with no
|
||||||
|
cascadeEager // load field beans with another SQL
|
||||||
|
cascadeJoin // load field beans with join
|
||||||
|
cascadeLazy // don't load anything
|
||||||
|
)
|
||||||
|
|
||||||
// Statement save all the sql info for executing SQL
|
// Statement save all the sql info for executing SQL
|
||||||
type Statement struct {
|
type Statement struct {
|
||||||
RefTable *core.Table
|
RefTable *core.Table
|
||||||
|
@ -54,7 +63,6 @@ type Statement struct {
|
||||||
tableName string
|
tableName string
|
||||||
RawSQL string
|
RawSQL string
|
||||||
RawParams []interface{}
|
RawParams []interface{}
|
||||||
UseCascade bool
|
|
||||||
UseAutoJoin bool
|
UseAutoJoin bool
|
||||||
StoreEngine string
|
StoreEngine string
|
||||||
Charset string
|
Charset string
|
||||||
|
@ -82,7 +90,6 @@ func (statement *Statement) Init() {
|
||||||
statement.Start = 0
|
statement.Start = 0
|
||||||
statement.LimitN = 0
|
statement.LimitN = 0
|
||||||
statement.OrderStr = ""
|
statement.OrderStr = ""
|
||||||
statement.UseCascade = true
|
|
||||||
statement.JoinStr = ""
|
statement.JoinStr = ""
|
||||||
statement.joinArgs = make([]interface{}, 0)
|
statement.joinArgs = make([]interface{}, 0)
|
||||||
statement.GroupByStr = ""
|
statement.GroupByStr = ""
|
||||||
|
|
132
tag.go
132
tag.go
|
@ -5,6 +5,7 @@
|
||||||
package xorm
|
package xorm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -15,46 +16,71 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type tagContext struct {
|
type tagContext struct {
|
||||||
|
engine *Engine
|
||||||
|
parsingTables map[reflect.Type]*core.Table
|
||||||
|
|
||||||
|
table *core.Table
|
||||||
|
hasCacheTag bool
|
||||||
|
hasNoCacheTag bool
|
||||||
|
|
||||||
|
col *core.Column
|
||||||
|
fieldValue reflect.Value
|
||||||
|
isIndex bool
|
||||||
|
isUnique bool
|
||||||
|
indexNames map[string]int
|
||||||
|
|
||||||
tagName string
|
tagName string
|
||||||
params []string
|
params []string
|
||||||
preTag, nextTag string
|
preTag, nextTag string
|
||||||
table *core.Table
|
|
||||||
col *core.Column
|
|
||||||
fieldValue reflect.Value
|
|
||||||
isIndex bool
|
|
||||||
isUnique bool
|
|
||||||
indexNames map[string]int
|
|
||||||
engine *Engine
|
|
||||||
hasCacheTag bool
|
|
||||||
hasNoCacheTag bool
|
|
||||||
ignoreNext bool
|
ignoreNext bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitTag(tag string) (tags []string) {
|
||||||
|
tag = strings.TrimSpace(tag)
|
||||||
|
var hasQuote = false
|
||||||
|
var lastIdx = 0
|
||||||
|
for i, t := range tag {
|
||||||
|
if t == '\'' {
|
||||||
|
hasQuote = !hasQuote
|
||||||
|
} else if t == ' ' {
|
||||||
|
if lastIdx < i && !hasQuote {
|
||||||
|
tags = append(tags, strings.TrimSpace(tag[lastIdx:i]))
|
||||||
|
lastIdx = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lastIdx < len(tag) {
|
||||||
|
tags = append(tags, strings.TrimSpace(tag[lastIdx:]))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// tagHandler describes tag handler for XORM
|
// tagHandler describes tag handler for XORM
|
||||||
type tagHandler func(ctx *tagContext) error
|
type tagHandler func(ctx *tagContext) error
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// defaultTagHandlers enumerates all the default tag handler
|
// defaultTagHandlers enumerates all the default tag handler
|
||||||
defaultTagHandlers = map[string]tagHandler{
|
defaultTagHandlers = map[string]tagHandler{
|
||||||
"<-": OnlyFromDBTagHandler,
|
"<-": OnlyFromDBTagHandler,
|
||||||
"->": OnlyToDBTagHandler,
|
"->": OnlyToDBTagHandler,
|
||||||
"PK": PKTagHandler,
|
"PK": PKTagHandler,
|
||||||
"NULL": NULLTagHandler,
|
"NULL": NULLTagHandler,
|
||||||
"NOT": IgnoreTagHandler,
|
"NOT": IgnoreTagHandler,
|
||||||
"AUTOINCR": AutoIncrTagHandler,
|
"AUTOINCR": AutoIncrTagHandler,
|
||||||
"DEFAULT": DefaultTagHandler,
|
"DEFAULT": DefaultTagHandler,
|
||||||
"CREATED": CreatedTagHandler,
|
"CREATED": CreatedTagHandler,
|
||||||
"UPDATED": UpdatedTagHandler,
|
"UPDATED": UpdatedTagHandler,
|
||||||
"DELETED": DeletedTagHandler,
|
"DELETED": DeletedTagHandler,
|
||||||
"VERSION": VersionTagHandler,
|
"VERSION": VersionTagHandler,
|
||||||
"UTC": UTCTagHandler,
|
"UTC": UTCTagHandler,
|
||||||
"LOCAL": LocalTagHandler,
|
"LOCAL": LocalTagHandler,
|
||||||
"NOTNULL": NotNullTagHandler,
|
"NOTNULL": NotNullTagHandler,
|
||||||
"INDEX": IndexTagHandler,
|
"INDEX": IndexTagHandler,
|
||||||
"UNIQUE": UniqueTagHandler,
|
"UNIQUE": UniqueTagHandler,
|
||||||
"CACHE": CacheTagHandler,
|
"CACHE": CacheTagHandler,
|
||||||
"NOCACHE": NoCacheTagHandler,
|
"NOCACHE": NoCacheTagHandler,
|
||||||
"COMMENT": CommentTagHandler,
|
"COMMENT": CommentTagHandler,
|
||||||
|
"BELONGS_TO": BelongsToTagHandler,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -256,15 +282,16 @@ func ExtendsTagHandler(ctx *tagContext) error {
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
parentTable, err := ctx.engine.mapType(fieldValue)
|
parentTable, err := ctx.engine.mapType(ctx.parsingTables, fieldValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, col := range parentTable.Columns() {
|
for _, oriCol := range parentTable.Columns() {
|
||||||
|
col := *oriCol
|
||||||
col.FieldName = fmt.Sprintf("%v.%v", ctx.col.FieldName, col.FieldName)
|
col.FieldName = fmt.Sprintf("%v.%v", ctx.col.FieldName, col.FieldName)
|
||||||
ctx.table.AddColumn(col)
|
ctx.table.AddColumn(&col)
|
||||||
for indexName, indexType := range col.Indexes {
|
for indexName, indexType := range col.Indexes {
|
||||||
addIndex(indexName, ctx.table, col, indexType)
|
addIndex(indexName, ctx.table, &col, indexType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -288,3 +315,44 @@ func NoCacheTagHandler(ctx *tagContext) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BelongsToTagHandler describes belongs_to tag handler
|
||||||
|
func BelongsToTagHandler(ctx *tagContext) error {
|
||||||
|
if !isStruct(ctx.fieldValue.Type()) {
|
||||||
|
return errors.New("Tag belongs_to cannot be applied on non-struct field")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.col.AssociateType = core.AssociateBelongsTo
|
||||||
|
var t reflect.Value
|
||||||
|
if ctx.fieldValue.Kind() == reflect.Struct {
|
||||||
|
t = ctx.fieldValue
|
||||||
|
} else {
|
||||||
|
if ctx.fieldValue.Type().Kind() == reflect.Ptr && ctx.fieldValue.Type().Elem().Kind() == reflect.Struct {
|
||||||
|
if ctx.fieldValue.IsNil() {
|
||||||
|
t = reflect.New(ctx.fieldValue.Type().Elem()).Elem()
|
||||||
|
} else {
|
||||||
|
t = ctx.fieldValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.New("Only struct or ptr to struct field could add belongs_to flag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
belongsT, err := ctx.engine.mapType(ctx.parsingTables, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pks := belongsT.PKColumns()
|
||||||
|
if len(pks) != 1 {
|
||||||
|
panic("unsupported non or composited primary key cascade")
|
||||||
|
return errors.New("blongs_to only should be as a tag of table has one primary key")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.col.AssociateTable = belongsT
|
||||||
|
ctx.col.SQLType = pks[0].SQLType
|
||||||
|
|
||||||
|
if len(ctx.col.Name) == 0 {
|
||||||
|
ctx.col.Name = ctx.engine.ColumnMapper.Obj2Table(ctx.col.FieldName) + "_id"
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -169,22 +169,12 @@ func TestExtends(t *testing.T) {
|
||||||
|
|
||||||
tu6 := &tempUser3{&tempUser{0, "extends update"}, ""}
|
tu6 := &tempUser3{&tempUser{0, "extends update"}, ""}
|
||||||
_, err = testEngine.ID(tu5.Temp.Id).Update(tu6)
|
_, err = testEngine.ID(tu5.Temp.Id).Update(tu6)
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
t.Error(err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
users := make([]tempUser3, 0)
|
users := make([]tempUser3, 0)
|
||||||
err = testEngine.Find(&users)
|
err = testEngine.Find(&users)
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
t.Error(err)
|
assert.EqualValues(t, 1, len(users))
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if len(users) != 1 {
|
|
||||||
err = errors.New("error get data not 1")
|
|
||||||
t.Error(err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertSync(t, new(Userinfo), new(Userdetail))
|
assertSync(t, new(Userinfo), new(Userdetail))
|
||||||
|
|
||||||
|
@ -209,21 +199,9 @@ func TestExtends(t *testing.T) {
|
||||||
sql := fmt.Sprintf("select * from %s, %s where %s.%s = %s.%s",
|
sql := fmt.Sprintf("select * from %s, %s where %s.%s = %s.%s",
|
||||||
qt(ui), qt(ud), qt(ui), qt(udid), qt(ud), qt(uiid))
|
qt(ui), qt(ud), qt(ui), qt(udid), qt(ud), qt(uiid))
|
||||||
b, err := testEngine.SQL(sql).NoCascade().Get(&info)
|
b, err := testEngine.SQL(sql).NoCascade().Get(&info)
|
||||||
if err != nil {
|
assert.NoError(t, err)
|
||||||
t.Error(err)
|
assert.True(t, b)
|
||||||
panic(err)
|
assert.False(t, info.Userinfo.Uid == 0 || info.Userdetail.Id == 0)
|
||||||
}
|
|
||||||
if !b {
|
|
||||||
err = errors.New("should has lest one record")
|
|
||||||
t.Error(err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println(info)
|
|
||||||
if info.Userinfo.Uid == 0 || info.Userdetail.Id == 0 {
|
|
||||||
err = errors.New("all of the id should has value")
|
|
||||||
t.Error(err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("----join--info2")
|
fmt.Println("----join--info2")
|
||||||
var info2 UserAndDetail
|
var info2 UserAndDetail
|
||||||
|
@ -536,3 +514,55 @@ func TestExtends4(t *testing.T) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtendsTag(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
|
table := testEngine.TableInfo(new(Userdetail))
|
||||||
|
assert.NotNil(t, table)
|
||||||
|
assert.EqualValues(t, 3, len(table.ColumnsSeq()))
|
||||||
|
assert.EqualValues(t, "id", table.ColumnsSeq()[0])
|
||||||
|
assert.EqualValues(t, "intro", table.ColumnsSeq()[1])
|
||||||
|
assert.EqualValues(t, "profile", table.ColumnsSeq()[2])
|
||||||
|
|
||||||
|
table = testEngine.TableInfo(new(Userinfo))
|
||||||
|
assert.NotNil(t, table)
|
||||||
|
assert.EqualValues(t, 8, len(table.ColumnsSeq()))
|
||||||
|
assert.EqualValues(t, "id", table.ColumnsSeq()[0])
|
||||||
|
assert.EqualValues(t, "username", table.ColumnsSeq()[1])
|
||||||
|
assert.EqualValues(t, "departname", table.ColumnsSeq()[2])
|
||||||
|
assert.EqualValues(t, "created", table.ColumnsSeq()[3])
|
||||||
|
assert.EqualValues(t, "detail_id", table.ColumnsSeq()[4])
|
||||||
|
assert.EqualValues(t, "height", table.ColumnsSeq()[5])
|
||||||
|
assert.EqualValues(t, "avatar", table.ColumnsSeq()[6])
|
||||||
|
assert.EqualValues(t, "is_man", table.ColumnsSeq()[7])
|
||||||
|
|
||||||
|
table = testEngine.TableInfo(new(UserAndDetail))
|
||||||
|
assert.NotNil(t, table)
|
||||||
|
assert.EqualValues(t, 11, len(table.ColumnsSeq()))
|
||||||
|
assert.EqualValues(t, "id", table.ColumnsSeq()[0])
|
||||||
|
assert.EqualValues(t, "username", table.ColumnsSeq()[1])
|
||||||
|
assert.EqualValues(t, "departname", table.ColumnsSeq()[2])
|
||||||
|
assert.EqualValues(t, "created", table.ColumnsSeq()[3])
|
||||||
|
assert.EqualValues(t, "detail_id", table.ColumnsSeq()[4])
|
||||||
|
assert.EqualValues(t, "height", table.ColumnsSeq()[5])
|
||||||
|
assert.EqualValues(t, "avatar", table.ColumnsSeq()[6])
|
||||||
|
assert.EqualValues(t, "is_man", table.ColumnsSeq()[7])
|
||||||
|
assert.EqualValues(t, "id", table.ColumnsSeq()[8])
|
||||||
|
assert.EqualValues(t, "intro", table.ColumnsSeq()[9])
|
||||||
|
assert.EqualValues(t, "profile", table.ColumnsSeq()[10])
|
||||||
|
|
||||||
|
cols := table.Columns()
|
||||||
|
assert.EqualValues(t, 11, len(cols))
|
||||||
|
assert.EqualValues(t, "Userinfo.Uid", cols[0].FieldName)
|
||||||
|
assert.EqualValues(t, "Userinfo.Username", cols[1].FieldName)
|
||||||
|
assert.EqualValues(t, "Userinfo.Departname", cols[2].FieldName)
|
||||||
|
assert.EqualValues(t, "Userinfo.Created", cols[3].FieldName)
|
||||||
|
assert.EqualValues(t, "Userinfo.Detail", cols[4].FieldName)
|
||||||
|
assert.EqualValues(t, "Userinfo.Height", cols[5].FieldName)
|
||||||
|
assert.EqualValues(t, "Userinfo.Avatar", cols[6].FieldName)
|
||||||
|
assert.EqualValues(t, "Userinfo.IsMan", cols[7].FieldName)
|
||||||
|
assert.EqualValues(t, "Userdetail.Id", cols[8].FieldName)
|
||||||
|
assert.EqualValues(t, "Userdetail.Intro", cols[9].FieldName)
|
||||||
|
assert.EqualValues(t, "Userdetail.Profile", cols[10].FieldName)
|
||||||
|
}
|
||||||
|
|
19
tag_test.go
19
tag_test.go
|
@ -393,3 +393,22 @@ func TestTagTime(t *testing.T) {
|
||||||
assert.EqualValues(t, s.Created.UTC().Format("2006-01-02 15:04:05"),
|
assert.EqualValues(t, s.Created.UTC().Format("2006-01-02 15:04:05"),
|
||||||
strings.Replace(strings.Replace(tm, "T", " ", -1), "Z", "", -1))
|
strings.Replace(strings.Replace(tm, "T", " ", -1), "Z", "", -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSplitTag(t *testing.T) {
|
||||||
|
var cases = []struct {
|
||||||
|
tag string
|
||||||
|
tags []string
|
||||||
|
}{
|
||||||
|
{"not null default '2000-01-01 00:00:00' TIMESTAMP", []string{"not", "null", "default", "'2000-01-01 00:00:00'", "TIMESTAMP"}},
|
||||||
|
{"TEXT", []string{"TEXT"}},
|
||||||
|
{"default('2000-01-01 00:00:00')", []string{"default('2000-01-01 00:00:00')"}},
|
||||||
|
{"json binary", []string{"json", "binary"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kase := range cases {
|
||||||
|
tags := splitTag(kase.tag)
|
||||||
|
if !sliceEq(tags, kase.tags) {
|
||||||
|
t.Fatalf("[%d]%v is not equal [%d]%v", len(tags), tags, len(kase.tags), kase.tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/go-xorm/core"
|
"github.com/go-xorm/core"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
_ "github.com/ziutek/mymysql/godrv"
|
_ "github.com/ziutek/mymysql/godrv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,10 +33,8 @@ var (
|
||||||
func createEngine(dbType, connStr string) error {
|
func createEngine(dbType, connStr string) error {
|
||||||
if testEngine == nil {
|
if testEngine == nil {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !*cluster {
|
if !*cluster {
|
||||||
testEngine, err = NewEngine(dbType, connStr)
|
testEngine, err = NewEngine(dbType, connStr)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
testEngine, err = NewEngineGroup(dbType, strings.Split(connStr, *splitter))
|
testEngine, err = NewEngineGroup(dbType, strings.Split(connStr, *splitter))
|
||||||
}
|
}
|
||||||
|
@ -123,6 +122,8 @@ func TestMain(m *testing.M) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPing(t *testing.T) {
|
func TestPing(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
if err := testEngine.Ping(); err != nil {
|
if err := testEngine.Ping(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue