implement simple belongs_to tag
This commit is contained in:
parent
43222cbcaf
commit
557d5a4101
|
@ -0,0 +1,149 @@
|
||||||
|
package xorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBelongsTo_Get(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
|
type Face struct {
|
||||||
|
Id int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nose struct {
|
||||||
|
Id int64
|
||||||
|
Face Face `xorm:"belongs_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := testEngine.Sync2(new(Nose), new(Face))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var face = Face{
|
||||||
|
Name: "face1",
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&face)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfgFace Face
|
||||||
|
has, err := testEngine.Get(&cfgFace)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, cfgFace, face)
|
||||||
|
|
||||||
|
var nose = Nose{Face: face}
|
||||||
|
_, err = testEngine.Insert(&nose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfgNose Nose
|
||||||
|
has, err = testEngine.Get(&cfgNose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, cfgNose.Id, nose.Id)
|
||||||
|
// FIXME: the id should be set back to the field
|
||||||
|
//assert.Equal(t, cfgNose.Face.Id, nose.Face.Id)
|
||||||
|
assert.Equal(t, "", cfgNose.Face.Name)
|
||||||
|
|
||||||
|
var cfgNose2 Nose
|
||||||
|
has, err = testEngine.Cascade().Get(&cfgNose2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, cfgNose2.Id, nose.Id)
|
||||||
|
assert.Equal(t, cfgNose2.Face.Id, nose.Face.Id)
|
||||||
|
assert.Equal(t, "face1", cfgNose2.Face.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBelongsTo_GetPtr(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
|
type Face struct {
|
||||||
|
Id int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nose struct {
|
||||||
|
Id int64
|
||||||
|
Face *Face `xorm:"belongs_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := testEngine.Sync2(new(Nose), new(Face))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var face = Face{
|
||||||
|
Name: "face1",
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&face)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfgFace Face
|
||||||
|
has, err := testEngine.Get(&cfgFace)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, cfgFace, face)
|
||||||
|
|
||||||
|
var nose = Nose{Face: &face}
|
||||||
|
_, err = testEngine.Insert(&nose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var cfgNose Nose
|
||||||
|
has, err = testEngine.Get(&cfgNose)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, cfgNose.Id, nose.Id)
|
||||||
|
// FIXME: the id should be set back to the field
|
||||||
|
//assert.Equal(t, cfgNose.Face.Id, nose.Face.Id)
|
||||||
|
|
||||||
|
var cfgNose2 Nose
|
||||||
|
has, err = testEngine.Cascade().Get(&cfgNose2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, true, has)
|
||||||
|
assert.Equal(t, cfgNose2.Id, nose.Id)
|
||||||
|
assert.Equal(t, cfgNose2.Face.Id, nose.Face.Id)
|
||||||
|
assert.Equal(t, "face1", cfgNose2.Face.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBelongsTo_Find(t *testing.T) {
|
||||||
|
assert.NoError(t, prepareEngine())
|
||||||
|
|
||||||
|
type Face struct {
|
||||||
|
Id int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nose struct {
|
||||||
|
Id int64
|
||||||
|
Face Face `xorm:"belongs_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := testEngine.Sync2(new(Nose), new(Face))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var face1 = Face{
|
||||||
|
Name: "face1",
|
||||||
|
}
|
||||||
|
var face2 = Face{
|
||||||
|
Name: "face2",
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&face1, &face2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var noses = []Nose{
|
||||||
|
{Face: face1},
|
||||||
|
{Face: face2},
|
||||||
|
}
|
||||||
|
_, err = testEngine.Insert(&noses)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var noses1 []Nose
|
||||||
|
err = testEngine.Find(&noses1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 2, len(noses1))
|
||||||
|
// FIXME:
|
||||||
|
//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)
|
||||||
|
}
|
|
@ -811,7 +811,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:
|
||||||
|
|
101
engine.go
101
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.
|
||||||
|
@ -792,13 +792,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
|
||||||
}
|
}
|
||||||
|
@ -872,9 +877,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 {
|
||||||
|
@ -901,24 +914,37 @@ 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,
|
||||||
|
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
|
||||||
|
@ -1014,20 +1040,55 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
26
session.go
26
session.go
|
@ -149,7 +149,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.statement.cascadeMode = cascadeManuallyLoad
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,9 +204,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 = cascadeAutoLoad
|
||||||
if len(trueOrFalse) >= 1 {
|
if len(trueOrFalse) >= 1 {
|
||||||
session.statement.UseCascade = trueOrFalse[0]
|
if trueOrFalse[0] {
|
||||||
|
mode = cascadeAutoLoad
|
||||||
|
} else {
|
||||||
|
mode = cascadeManuallyLoad
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.statement.cascadeMode = mode
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,7 +636,7 @@ 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 col.SQLType.IsJson() {
|
||||||
if rawValueType.Kind() == reflect.String {
|
if rawValueType.Kind() == reflect.String {
|
||||||
hasAssigned = true
|
hasAssigned = true
|
||||||
x := reflect.New(fieldType)
|
x := reflect.New(fieldType)
|
||||||
|
@ -650,18 +657,19 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b
|
||||||
}
|
}
|
||||||
fieldValue.Set(x.Elem())
|
fieldValue.Set(x.Elem())
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
} else if session.statement.UseCascade {
|
} else if (col.AssociateType == core.AssociateNone &&
|
||||||
table, err := session.engine.autoMapType(*fieldValue)
|
session.statement.cascadeMode == cascadeCompitable) ||
|
||||||
if err != nil {
|
(col.AssociateType == core.AssociateBelongsTo &&
|
||||||
return nil, err
|
session.statement.cascadeMode == cascadeAutoLoad) {
|
||||||
}
|
table := col.AssociateTable
|
||||||
|
|
||||||
hasAssigned = true
|
hasAssigned = true
|
||||||
if len(table.PrimaryKeys) != 1 {
|
if len(table.PrimaryKeys) != 1 {
|
||||||
return nil, errors.New("unsupported non or composited primary key cascade")
|
return nil, errors.New("unsupported non or composited primary key cascade")
|
||||||
}
|
}
|
||||||
var pk = make(core.PK, len(table.PrimaryKeys))
|
var pk = make(core.PK, len(table.PrimaryKeys))
|
||||||
|
var err error
|
||||||
pk[0], err = asKind(vv, rawValueType)
|
pk[0], err = asKind(vv, rawValueType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -203,11 +203,11 @@ 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 (col.AssociateType == core.AssociateNone &&
|
||||||
table, err := session.engine.autoMapType(*fieldValue)
|
session.statement.cascadeMode == cascadeCompitable) ||
|
||||||
if err != nil {
|
(col.AssociateType == core.AssociateBelongsTo &&
|
||||||
return err
|
session.statement.cascadeMode == cascadeAutoLoad) {
|
||||||
}
|
table := col.AssociateTable
|
||||||
|
|
||||||
// TODO: current only support 1 primary key
|
// TODO: current only support 1 primary key
|
||||||
if len(table.PrimaryKeys) > 1 {
|
if len(table.PrimaryKeys) > 1 {
|
||||||
|
@ -216,6 +216,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
|
||||||
|
|
||||||
var pk = make(core.PK, len(table.PrimaryKeys))
|
var pk = make(core.PK, len(table.PrimaryKeys))
|
||||||
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
|
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
|
||||||
|
var err error
|
||||||
pk[0], err = str2PK(string(data), rawValueType)
|
pk[0], err = str2PK(string(data), rawValueType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -485,18 +486,17 @@ 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 (col.AssociateType == core.AssociateNone &&
|
||||||
structInter := reflect.New(fieldType.Elem())
|
session.statement.cascadeMode == cascadeCompitable) ||
|
||||||
table, err := session.engine.autoMapType(structInter.Elem())
|
(col.AssociateType == core.AssociateBelongsTo &&
|
||||||
if err != nil {
|
session.statement.cascadeMode == cascadeAutoLoad) {
|
||||||
return err
|
table := col.AssociateTable
|
||||||
}
|
|
||||||
|
|
||||||
if len(table.PrimaryKeys) > 1 {
|
if len(table.PrimaryKeys) > 1 {
|
||||||
return errors.New("unsupported composited primary key cascade")
|
return errors.New("unsupported composited primary key cascade")
|
||||||
}
|
}
|
||||||
|
|
||||||
var pk = make(core.PK, len(table.PrimaryKeys))
|
var pk = make(core.PK, len(table.PrimaryKeys))
|
||||||
|
var err error
|
||||||
rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
|
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 {
|
||||||
|
@ -504,6 +504,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPKZero(pk) {
|
if !isPKZero(pk) {
|
||||||
|
structInter := reflect.New(fieldType.Elem())
|
||||||
// !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch
|
// !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
|
// however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne
|
||||||
// property to be fetched lazily
|
// property to be fetched lazily
|
||||||
|
@ -518,8 +519,6 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value,
|
||||||
return errors.New("cascade obj is not exist")
|
return errors.New("cascade obj is not exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -259,7 +259,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
|
||||||
}
|
}
|
||||||
|
|
12
statement.go
12
statement.go
|
@ -33,6 +33,14 @@ type exprParam struct {
|
||||||
expr string
|
expr string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cascadeMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
cascadeCompitable cascadeMode = iota
|
||||||
|
cascadeAutoLoad
|
||||||
|
cascadeManuallyLoad
|
||||||
|
)
|
||||||
|
|
||||||
// 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 +62,7 @@ type Statement struct {
|
||||||
tableName string
|
tableName string
|
||||||
RawSQL string
|
RawSQL string
|
||||||
RawParams []interface{}
|
RawParams []interface{}
|
||||||
UseCascade bool
|
cascadeMode cascadeMode
|
||||||
UseAutoJoin bool
|
UseAutoJoin bool
|
||||||
StoreEngine string
|
StoreEngine string
|
||||||
Charset string
|
Charset string
|
||||||
|
@ -82,7 +90,7 @@ func (statement *Statement) Init() {
|
||||||
statement.Start = 0
|
statement.Start = 0
|
||||||
statement.LimitN = 0
|
statement.LimitN = 0
|
||||||
statement.OrderStr = ""
|
statement.OrderStr = ""
|
||||||
statement.UseCascade = true
|
statement.cascadeMode = cascadeCompitable
|
||||||
statement.JoinStr = ""
|
statement.JoinStr = ""
|
||||||
statement.joinArgs = make([]interface{}, 0)
|
statement.joinArgs = make([]interface{}, 0)
|
||||||
statement.GroupByStr = ""
|
statement.GroupByStr = ""
|
||||||
|
|
105
tag.go
105
tag.go
|
@ -5,6 +5,7 @@
|
||||||
package xorm
|
package xorm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -15,18 +16,22 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,25 +41,26 @@ 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,7 +262,7 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -288,3 +294,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
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -123,6 +124,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