Browse Source

1.refactor rowsbean
2.move some functions from session.go to session_cols.go and session_convert.go

xormplus 8 years ago
parent
commit
e392f0bb39
6 changed files with 764 additions and 747 deletions
  1. 4 1
      rows.go
  2. 2 744
      session.go
  3. 84 0
      session_cols.go
  4. 670 0
      session_convert.go
  5. 3 1
      session_get.go
  6. 1 1
      xorm.go

+ 4 - 1
rows.go

@@ -112,7 +112,10 @@ func (rows *Rows) Scan(bean interface{}) error {
 		return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType)
 	}
 
-	_, err := rows.session.row2Bean(rows.rows, rows.fields, len(rows.fields), bean)
+	dataStruct := rValue(bean)
+	rows.session.Statement.setRefValue(dataStruct)
+	_, err := rows.session.row2Bean(rows.rows, rows.fields, len(rows.fields), bean, &dataStruct, rows.session.Statement.RefTable)
+
 	return err
 }
 

+ 2 - 744
session.go

@@ -6,7 +6,6 @@ package xorm
 
 import (
 	"database/sql"
-	"database/sql/driver"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -146,97 +145,18 @@ func (session *Session) Alias(alias string) *Session {
 	return session
 }
 
-// Incr provides a query string like "count = count + 1"
-func (session *Session) Incr(column string, arg ...interface{}) *Session {
-	session.Statement.Incr(column, arg...)
-	return session
-}
-
-// Decr provides a query string like "count = count - 1"
-func (session *Session) Decr(column string, arg ...interface{}) *Session {
-	session.Statement.Decr(column, arg...)
-	return session
-}
-
-// SetExpr provides a query string like "column = {expression}"
-func (session *Session) SetExpr(column string, expression string) *Session {
-	session.Statement.SetExpr(column, expression)
-	return session
-}
-
-// Select provides some columns to special
-func (session *Session) Select(str string) *Session {
-	session.Statement.Select(str)
-	return session
-}
-
-// Cols provides some columns to special
-func (session *Session) Cols(columns ...string) *Session {
-	session.Statement.Cols(columns...)
-	return session
-}
-
-// AllCols ask all columns
-func (session *Session) AllCols() *Session {
-	session.Statement.AllCols()
-	return session
-}
-
-// MustCols specify some columns must use even if they are empty
-func (session *Session) MustCols(columns ...string) *Session {
-	session.Statement.MustCols(columns...)
-	return session
-}
-
 // NoCascade indicate that no cascade load child object
 func (session *Session) NoCascade() *Session {
 	session.Statement.UseCascade = false
 	return session
 }
 
-// UseBool automatically retrieve condition according struct, but
-// if struct has bool field, it will ignore them. So use UseBool
-// to tell system to do not ignore them.
-// If no parameters, it will use all the bool field of struct, or
-// it will use parameters's columns
-func (session *Session) UseBool(columns ...string) *Session {
-	session.Statement.UseBool(columns...)
-	return session
-}
-
-// Distinct use for distinct columns. Caution: when you are using cache,
-// distinct will not be cached because cache system need id,
-// but distinct will not provide id
-func (session *Session) Distinct(columns ...string) *Session {
-	session.Statement.Distinct(columns...)
-	return session
-}
-
 // ForUpdate Set Read/Write locking for UPDATE
 func (session *Session) ForUpdate() *Session {
 	session.Statement.IsForUpdate = true
 	return session
 }
 
-// Omit Only not use the parameters as select or update columns
-func (session *Session) Omit(columns ...string) *Session {
-	session.Statement.Omit(columns...)
-	return session
-}
-
-// Nullable Set null when column is zero-value and nullable for update
-func (session *Session) Nullable(columns ...string) *Session {
-	session.Statement.Nullable(columns...)
-	return session
-}
-
-// NoAutoTime means do not automatically give created field and updated field
-// the current time on the current session temporarily
-func (session *Session) NoAutoTime() *Session {
-	session.Statement.UseAutoTime = false
-	return session
-}
-
 // NoAutoCondition disable generate SQL condition from beans
 func (session *Session) NoAutoCondition(no ...bool) *Session {
 	session.Statement.NoAutoCondition(no...)
@@ -386,7 +306,7 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, fieldsCount
 		var newValue = newElemFunc(fields)
 		bean := newValue.Interface()
 		dataStruct := rValue(bean)
-		pk, err := session._row2Bean(rows, fields, fieldsCount, bean, &dataStruct, table)
+		pk, err := session.row2Bean(rows, fields, fieldsCount, bean, &dataStruct, table)
 		if err != nil {
 			return err
 		}
@@ -399,18 +319,7 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, fieldsCount
 	return nil
 }
 
-func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}) (core.PK, error) {
-	dataStruct := rValue(bean)
-	if dataStruct.Kind() != reflect.Struct {
-		return nil, errors.New("Expected a pointer to a struct")
-	}
-
-	session.Statement.setRefValue(dataStruct)
-
-	return session._row2Bean(rows, fields, fieldsCount, bean, &dataStruct, session.Statement.RefTable)
-}
-
-func (session *Session) _row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}, dataStruct *reflect.Value, table *core.Table) (core.PK, error) {
+func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}, dataStruct *reflect.Value, table *core.Table) (core.PK, error) {
 	scanResults := make([]interface{}, fieldsCount)
 	for i := 0; i < len(fields); i++ {
 		var cell interface{}
@@ -896,657 +805,6 @@ func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{})
 	session.saveLastSQL(*sqlStr, paramStr...)
 }
 
-func (session *Session) str2Time(col *core.Column, data string) (outTime time.Time, outErr error) {
-	sdata := strings.TrimSpace(data)
-	var x time.Time
-	var err error
-
-	if sdata == "0000-00-00 00:00:00" ||
-		sdata == "0001-01-01 00:00:00" {
-	} else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column
-		// time stamp
-		sd, err := strconv.ParseInt(sdata, 10, 64)
-		if err == nil {
-			x = time.Unix(sd, 0)
-			// !nashtsai! HACK mymysql driver is causing Local location being change to CHAT and cause wrong time conversion
-			if col.TimeZone == nil {
-				x = x.In(session.Engine.TZLocation)
-			} else {
-				x = x.In(col.TimeZone)
-			}
-			session.Engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
-		} else {
-			session.Engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
-		}
-	} else if len(sdata) > 19 && strings.Contains(sdata, "-") {
-		x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation)
-		session.Engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
-		if err != nil {
-			x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation)
-			session.Engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
-		}
-		if err != nil {
-			x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation)
-			session.Engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
-		}
-
-	} else if len(sdata) == 19 && strings.Contains(sdata, "-") {
-		x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation)
-		session.Engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
-	} else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' {
-		x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation)
-		session.Engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
-	} else if col.SQLType.Name == core.Time {
-		if strings.Contains(sdata, " ") {
-			ssd := strings.Split(sdata, " ")
-			sdata = ssd[1]
-		}
-
-		sdata = strings.TrimSpace(sdata)
-		if session.Engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 {
-			sdata = sdata[len(sdata)-8:]
-		}
-
-		st := fmt.Sprintf("2006-01-02 %v", sdata)
-		x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation)
-		session.Engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
-	} else {
-		outErr = fmt.Errorf("unsupported time format %v", sdata)
-		return
-	}
-	if err != nil {
-		outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err)
-		return
-	}
-	outTime = x
-	return
-}
-
-func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) {
-	return session.str2Time(col, string(data))
-}
-
-// convert a db data([]byte) to a field value
-func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, data []byte) error {
-	if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
-		return structConvert.FromDB(data)
-	}
-
-	if structConvert, ok := fieldValue.Interface().(core.Conversion); ok {
-		return structConvert.FromDB(data)
-	}
-
-	var v interface{}
-	key := col.Name
-	fieldType := fieldValue.Type()
-
-	switch fieldType.Kind() {
-	case reflect.Complex64, reflect.Complex128:
-		x := reflect.New(fieldType)
-		if len(data) > 0 {
-			err := json.Unmarshal(data, x.Interface())
-			if err != nil {
-				session.Engine.logger.Error(err)
-				return err
-			}
-			fieldValue.Set(x.Elem())
-		}
-	case reflect.Slice, reflect.Array, reflect.Map:
-		v = data
-		t := fieldType.Elem()
-		k := t.Kind()
-		if col.SQLType.IsText() {
-			x := reflect.New(fieldType)
-			if len(data) > 0 {
-				err := json.Unmarshal(data, x.Interface())
-				if err != nil {
-					session.Engine.logger.Error(err)
-					return err
-				}
-				fieldValue.Set(x.Elem())
-			}
-		} else if col.SQLType.IsBlob() {
-			if k == reflect.Uint8 {
-				fieldValue.Set(reflect.ValueOf(v))
-			} else {
-				x := reflect.New(fieldType)
-				if len(data) > 0 {
-					err := json.Unmarshal(data, x.Interface())
-					if err != nil {
-						session.Engine.logger.Error(err)
-						return err
-					}
-					fieldValue.Set(x.Elem())
-				}
-			}
-		} else {
-			return ErrUnSupportedType
-		}
-	case reflect.String:
-		fieldValue.SetString(string(data))
-	case reflect.Bool:
-		d := string(data)
-		v, err := strconv.ParseBool(d)
-		if err != nil {
-			return fmt.Errorf("arg %v as bool: %s", key, err.Error())
-		}
-		fieldValue.Set(reflect.ValueOf(v))
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		sdata := string(data)
-		var x int64
-		var err error
-		// for mysql, when use bit, it returned \x01
-		if col.SQLType.Name == core.Bit &&
-			session.Engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API
-			if len(data) == 1 {
-				x = int64(data[0])
-			} else {
-				x = 0
-			}
-		} else if strings.HasPrefix(sdata, "0x") {
-			x, err = strconv.ParseInt(sdata, 16, 64)
-		} else if strings.HasPrefix(sdata, "0") {
-			x, err = strconv.ParseInt(sdata, 8, 64)
-		} else if strings.EqualFold(sdata, "true") {
-			x = 1
-		} else if strings.EqualFold(sdata, "false") {
-			x = 0
-		} else {
-			x, err = strconv.ParseInt(sdata, 10, 64)
-		}
-		if err != nil {
-			return fmt.Errorf("arg %v as int: %s", key, err.Error())
-		}
-		fieldValue.SetInt(x)
-	case reflect.Float32, reflect.Float64:
-		x, err := strconv.ParseFloat(string(data), 64)
-		if err != nil {
-			return fmt.Errorf("arg %v as float64: %s", key, err.Error())
-		}
-		fieldValue.SetFloat(x)
-	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
-		x, err := strconv.ParseUint(string(data), 10, 64)
-		if err != nil {
-			return fmt.Errorf("arg %v as int: %s", key, err.Error())
-		}
-		fieldValue.SetUint(x)
-	//Currently only support Time type
-	case reflect.Struct:
-		// !<winxxp>! 增加支持sql.Scanner接口的结构,如sql.NullString
-		if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
-			if err := nulVal.Scan(data); err != nil {
-				return fmt.Errorf("sql.Scan(%v) failed: %s ", data, err.Error())
-			}
-		} else {
-			if fieldType.ConvertibleTo(core.TimeType) {
-				x, err := session.byte2Time(col, data)
-				if err != nil {
-					return err
-				}
-				v = x
-				fieldValue.Set(reflect.ValueOf(v).Convert(fieldType))
-			} else if session.Statement.UseCascade {
-				table := session.Engine.autoMapType(*fieldValue)
-				if table != nil {
-					// TODO: current only support 1 primary key
-					if len(table.PrimaryKeys) > 1 {
-						panic("unsupported composited primary key cascade")
-					}
-					var pk = make(core.PK, len(table.PrimaryKeys))
-					rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
-					var err error
-					pk[0], err = str2PK(string(data), rawValueType)
-					if err != nil {
-						return 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())
-						newsession := session.Engine.NewSession()
-						defer newsession.Close()
-						has, err := newsession.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")
-						}
-					}
-				} else {
-					return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String())
-				}
-			}
-		}
-	case reflect.Ptr:
-		// !nashtsai! TODO merge duplicated codes above
-		//typeStr := fieldType.String()
-		switch fieldType.Elem().Kind() {
-		// case "*string":
-		case core.StringType.Kind():
-			x := string(data)
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*bool":
-		case core.BoolType.Kind():
-			d := string(data)
-			v, err := strconv.ParseBool(d)
-			if err != nil {
-				return fmt.Errorf("arg %v as bool: %s", key, err.Error())
-			}
-			fieldValue.Set(reflect.ValueOf(&v).Convert(fieldType))
-		// case "*complex64":
-		case core.Complex64Type.Kind():
-			var x complex64
-			if len(data) > 0 {
-				err := json.Unmarshal(data, &x)
-				if err != nil {
-					session.Engine.logger.Error(err)
-					return err
-				}
-				fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-			}
-		// case "*complex128":
-		case core.Complex128Type.Kind():
-			var x complex128
-			if len(data) > 0 {
-				err := json.Unmarshal(data, &x)
-				if err != nil {
-					session.Engine.logger.Error(err)
-					return err
-				}
-				fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-			}
-		// case "*float64":
-		case core.Float64Type.Kind():
-			x, err := strconv.ParseFloat(string(data), 64)
-			if err != nil {
-				return fmt.Errorf("arg %v as float64: %s", key, err.Error())
-			}
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*float32":
-		case core.Float32Type.Kind():
-			var x float32
-			x1, err := strconv.ParseFloat(string(data), 32)
-			if err != nil {
-				return fmt.Errorf("arg %v as float32: %s", key, err.Error())
-			}
-			x = float32(x1)
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*uint64":
-		case core.Uint64Type.Kind():
-			var x uint64
-			x, err := strconv.ParseUint(string(data), 10, 64)
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*uint":
-		case core.UintType.Kind():
-			var x uint
-			x1, err := strconv.ParseUint(string(data), 10, 64)
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			x = uint(x1)
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*uint32":
-		case core.Uint32Type.Kind():
-			var x uint32
-			x1, err := strconv.ParseUint(string(data), 10, 64)
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			x = uint32(x1)
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*uint8":
-		case core.Uint8Type.Kind():
-			var x uint8
-			x1, err := strconv.ParseUint(string(data), 10, 64)
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			x = uint8(x1)
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*uint16":
-		case core.Uint16Type.Kind():
-			var x uint16
-			x1, err := strconv.ParseUint(string(data), 10, 64)
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			x = uint16(x1)
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*int64":
-		case core.Int64Type.Kind():
-			sdata := string(data)
-			var x int64
-			var err error
-			// for mysql, when use bit, it returned \x01
-			if col.SQLType.Name == core.Bit &&
-				strings.Contains(session.Engine.DriverName(), "mysql") {
-				if len(data) == 1 {
-					x = int64(data[0])
-				} else {
-					x = 0
-				}
-			} else if strings.HasPrefix(sdata, "0x") {
-				x, err = strconv.ParseInt(sdata, 16, 64)
-			} else if strings.HasPrefix(sdata, "0") {
-				x, err = strconv.ParseInt(sdata, 8, 64)
-			} else {
-				x, err = strconv.ParseInt(sdata, 10, 64)
-			}
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*int":
-		case core.IntType.Kind():
-			sdata := string(data)
-			var x int
-			var x1 int64
-			var err error
-			// for mysql, when use bit, it returned \x01
-			if col.SQLType.Name == core.Bit &&
-				strings.Contains(session.Engine.DriverName(), "mysql") {
-				if len(data) == 1 {
-					x = int(data[0])
-				} else {
-					x = 0
-				}
-			} else if strings.HasPrefix(sdata, "0x") {
-				x1, err = strconv.ParseInt(sdata, 16, 64)
-				x = int(x1)
-			} else if strings.HasPrefix(sdata, "0") {
-				x1, err = strconv.ParseInt(sdata, 8, 64)
-				x = int(x1)
-			} else {
-				x1, err = strconv.ParseInt(sdata, 10, 64)
-				x = int(x1)
-			}
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*int32":
-		case core.Int32Type.Kind():
-			sdata := string(data)
-			var x int32
-			var x1 int64
-			var err error
-			// for mysql, when use bit, it returned \x01
-			if col.SQLType.Name == core.Bit &&
-				session.Engine.dialect.DBType() == core.MYSQL {
-				if len(data) == 1 {
-					x = int32(data[0])
-				} else {
-					x = 0
-				}
-			} else if strings.HasPrefix(sdata, "0x") {
-				x1, err = strconv.ParseInt(sdata, 16, 64)
-				x = int32(x1)
-			} else if strings.HasPrefix(sdata, "0") {
-				x1, err = strconv.ParseInt(sdata, 8, 64)
-				x = int32(x1)
-			} else {
-				x1, err = strconv.ParseInt(sdata, 10, 64)
-				x = int32(x1)
-			}
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*int8":
-		case core.Int8Type.Kind():
-			sdata := string(data)
-			var x int8
-			var x1 int64
-			var err error
-			// for mysql, when use bit, it returned \x01
-			if col.SQLType.Name == core.Bit &&
-				strings.Contains(session.Engine.DriverName(), "mysql") {
-				if len(data) == 1 {
-					x = int8(data[0])
-				} else {
-					x = 0
-				}
-			} else if strings.HasPrefix(sdata, "0x") {
-				x1, err = strconv.ParseInt(sdata, 16, 64)
-				x = int8(x1)
-			} else if strings.HasPrefix(sdata, "0") {
-				x1, err = strconv.ParseInt(sdata, 8, 64)
-				x = int8(x1)
-			} else {
-				x1, err = strconv.ParseInt(sdata, 10, 64)
-				x = int8(x1)
-			}
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*int16":
-		case core.Int16Type.Kind():
-			sdata := string(data)
-			var x int16
-			var x1 int64
-			var err error
-			// for mysql, when use bit, it returned \x01
-			if col.SQLType.Name == core.Bit &&
-				strings.Contains(session.Engine.DriverName(), "mysql") {
-				if len(data) == 1 {
-					x = int16(data[0])
-				} else {
-					x = 0
-				}
-			} else if strings.HasPrefix(sdata, "0x") {
-				x1, err = strconv.ParseInt(sdata, 16, 64)
-				x = int16(x1)
-			} else if strings.HasPrefix(sdata, "0") {
-				x1, err = strconv.ParseInt(sdata, 8, 64)
-				x = int16(x1)
-			} else {
-				x1, err = strconv.ParseInt(sdata, 10, 64)
-				x = int16(x1)
-			}
-			if err != nil {
-				return fmt.Errorf("arg %v as int: %s", key, err.Error())
-			}
-			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
-		// case "*SomeStruct":
-		case reflect.Struct:
-			switch fieldType {
-			// case "*.time.Time":
-			case core.PtrTimeType:
-				x, err := session.byte2Time(col, data)
-				if err != nil {
-					return err
-				}
-				v = x
-				fieldValue.Set(reflect.ValueOf(&x))
-			default:
-				if session.Statement.UseCascade {
-					structInter := reflect.New(fieldType.Elem())
-					table := session.Engine.autoMapType(structInter.Elem())
-					if table != nil {
-						if len(table.PrimaryKeys) > 1 {
-							panic("unsupported composited primary key cascade")
-						}
-						var pk = make(core.PK, len(table.PrimaryKeys))
-						var err error
-						rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
-						pk[0], err = str2PK(string(data), rawValueType)
-						if err != nil {
-							return 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
-							newsession := session.Engine.NewSession()
-							defer newsession.Close()
-							has, err := newsession.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:
-			return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String())
-		}
-	default:
-		return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String())
-	}
-
-	return nil
-}
-
-// convert a field value of a struct to interface for put into db
-func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Value) (interface{}, error) {
-	if fieldValue.CanAddr() {
-		if fieldConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
-			data, err := fieldConvert.ToDB()
-			if err != nil {
-				return 0, err
-			}
-			if col.SQLType.IsBlob() {
-				return data, nil
-			}
-			return string(data), nil
-		}
-	}
-
-	if fieldConvert, ok := fieldValue.Interface().(core.Conversion); ok {
-		data, err := fieldConvert.ToDB()
-		if err != nil {
-			return 0, err
-		}
-		if col.SQLType.IsBlob() {
-			return data, nil
-		}
-		return string(data), nil
-	}
-
-	fieldType := fieldValue.Type()
-	k := fieldType.Kind()
-	if k == reflect.Ptr {
-		if fieldValue.IsNil() {
-			return nil, nil
-		} else if !fieldValue.IsValid() {
-			session.Engine.logger.Warn("the field[", col.FieldName, "] is invalid")
-			return nil, nil
-		} else {
-			// !nashtsai! deference pointer type to instance type
-			fieldValue = fieldValue.Elem()
-			fieldType = fieldValue.Type()
-			k = fieldType.Kind()
-		}
-	}
-
-	switch k {
-	case reflect.Bool:
-		return fieldValue.Bool(), nil
-	case reflect.String:
-		return fieldValue.String(), nil
-	case reflect.Struct:
-		if fieldType.ConvertibleTo(core.TimeType) {
-			t := fieldValue.Convert(core.TimeType).Interface().(time.Time)
-			if session.Engine.dialect.DBType() == core.MSSQL {
-				if t.IsZero() {
-					return nil, nil
-				}
-			}
-			tf := session.Engine.FormatTime(col.SQLType.Name, t)
-			return tf, nil
-		}
-
-		if !col.SQLType.IsJson() {
-			// !<winxxp>! 增加支持driver.Valuer接口的结构,如sql.NullString
-			if v, ok := fieldValue.Interface().(driver.Valuer); ok {
-				return v.Value()
-			}
-
-			fieldTable := session.Engine.autoMapType(fieldValue)
-			if len(fieldTable.PrimaryKeys) == 1 {
-				pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName)
-				return pkField.Interface(), nil
-			}
-			return 0, fmt.Errorf("no primary key for col %v", col.Name)
-		}
-
-		if col.SQLType.IsText() {
-			bytes, err := json.Marshal(fieldValue.Interface())
-			if err != nil {
-				session.Engine.logger.Error(err)
-				return 0, err
-			}
-			return string(bytes), nil
-		} else if col.SQLType.IsBlob() {
-			bytes, err := json.Marshal(fieldValue.Interface())
-			if err != nil {
-				session.Engine.logger.Error(err)
-				return 0, err
-			}
-			return bytes, nil
-		}
-		return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type())
-	case reflect.Complex64, reflect.Complex128:
-		bytes, err := json.Marshal(fieldValue.Interface())
-		if err != nil {
-			session.Engine.logger.Error(err)
-			return 0, err
-		}
-		return string(bytes), nil
-	case reflect.Array, reflect.Slice, reflect.Map:
-		if !fieldValue.IsValid() {
-			return fieldValue.Interface(), nil
-		}
-
-		if col.SQLType.IsText() {
-			bytes, err := json.Marshal(fieldValue.Interface())
-			if err != nil {
-				session.Engine.logger.Error(err)
-				return 0, err
-			}
-			return string(bytes), nil
-		} else if col.SQLType.IsBlob() {
-			var bytes []byte
-			var err error
-			if (k == reflect.Array || k == reflect.Slice) &&
-				(fieldValue.Type().Elem().Kind() == reflect.Uint8) {
-				bytes = fieldValue.Bytes()
-			} else {
-				bytes, err = json.Marshal(fieldValue.Interface())
-				if err != nil {
-					session.Engine.logger.Error(err)
-					return 0, err
-				}
-			}
-			return bytes, nil
-		}
-		return nil, ErrUnSupportedType
-	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
-		return int64(fieldValue.Uint()), nil
-	default:
-		return fieldValue.Interface(), nil
-	}
-}
-
 // saveLastSQL stores executed query information
 func (session *Session) saveLastSQL(sql string, args ...interface{}) {
 	session.lastSQL = sql

+ 84 - 0
session_cols.go

@@ -0,0 +1,84 @@
+// 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
+
+// Incr provides a query string like "count = count + 1"
+func (session *Session) Incr(column string, arg ...interface{}) *Session {
+	session.Statement.Incr(column, arg...)
+	return session
+}
+
+// Decr provides a query string like "count = count - 1"
+func (session *Session) Decr(column string, arg ...interface{}) *Session {
+	session.Statement.Decr(column, arg...)
+	return session
+}
+
+// SetExpr provides a query string like "column = {expression}"
+func (session *Session) SetExpr(column string, expression string) *Session {
+	session.Statement.SetExpr(column, expression)
+	return session
+}
+
+// Select provides some columns to special
+func (session *Session) Select(str string) *Session {
+	session.Statement.Select(str)
+	return session
+}
+
+// Cols provides some columns to special
+func (session *Session) Cols(columns ...string) *Session {
+	session.Statement.Cols(columns...)
+	return session
+}
+
+// AllCols ask all columns
+func (session *Session) AllCols() *Session {
+	session.Statement.AllCols()
+	return session
+}
+
+// MustCols specify some columns must use even if they are empty
+func (session *Session) MustCols(columns ...string) *Session {
+	session.Statement.MustCols(columns...)
+	return session
+}
+
+// UseBool automatically retrieve condition according struct, but
+// if struct has bool field, it will ignore them. So use UseBool
+// to tell system to do not ignore them.
+// If no parameters, it will use all the bool field of struct, or
+// it will use parameters's columns
+func (session *Session) UseBool(columns ...string) *Session {
+	session.Statement.UseBool(columns...)
+	return session
+}
+
+// Distinct use for distinct columns. Caution: when you are using cache,
+// distinct will not be cached because cache system need id,
+// but distinct will not provide id
+func (session *Session) Distinct(columns ...string) *Session {
+	session.Statement.Distinct(columns...)
+	return session
+}
+
+// Omit Only not use the parameters as select or update columns
+func (session *Session) Omit(columns ...string) *Session {
+	session.Statement.Omit(columns...)
+	return session
+}
+
+// Nullable Set null when column is zero-value and nullable for update
+func (session *Session) Nullable(columns ...string) *Session {
+	session.Statement.Nullable(columns...)
+	return session
+}
+
+// NoAutoTime means do not automatically give created field and updated field
+// the current time on the current session temporarily
+func (session *Session) NoAutoTime() *Session {
+	session.Statement.UseAutoTime = false
+	return session
+}

+ 670 - 0
session_convert.go

@@ -0,0 +1,670 @@
+// 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 (
+	"database/sql"
+	"database/sql/driver"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/xormplus/core"
+)
+
+func (session *Session) str2Time(col *core.Column, data string) (outTime time.Time, outErr error) {
+	sdata := strings.TrimSpace(data)
+	var x time.Time
+	var err error
+
+	if sdata == "0000-00-00 00:00:00" ||
+		sdata == "0001-01-01 00:00:00" {
+	} else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column
+		// time stamp
+		sd, err := strconv.ParseInt(sdata, 10, 64)
+		if err == nil {
+			x = time.Unix(sd, 0)
+			// !nashtsai! HACK mymysql driver is causing Local location being change to CHAT and cause wrong time conversion
+			if col.TimeZone == nil {
+				x = x.In(session.Engine.TZLocation)
+			} else {
+				x = x.In(col.TimeZone)
+			}
+			session.Engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+		} else {
+			session.Engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+		}
+	} else if len(sdata) > 19 && strings.Contains(sdata, "-") {
+		x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation)
+		session.Engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+		if err != nil {
+			x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation)
+			session.Engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+		}
+		if err != nil {
+			x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation)
+			session.Engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+		}
+
+	} else if len(sdata) == 19 && strings.Contains(sdata, "-") {
+		x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation)
+		session.Engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+	} else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' {
+		x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation)
+		session.Engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+	} else if col.SQLType.Name == core.Time {
+		if strings.Contains(sdata, " ") {
+			ssd := strings.Split(sdata, " ")
+			sdata = ssd[1]
+		}
+
+		sdata = strings.TrimSpace(sdata)
+		if session.Engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 {
+			sdata = sdata[len(sdata)-8:]
+		}
+
+		st := fmt.Sprintf("2006-01-02 %v", sdata)
+		x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation)
+		session.Engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata)
+	} else {
+		outErr = fmt.Errorf("unsupported time format %v", sdata)
+		return
+	}
+	if err != nil {
+		outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err)
+		return
+	}
+	outTime = x
+	return
+}
+
+func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) {
+	return session.str2Time(col, string(data))
+}
+
+// convert a db data([]byte) to a field value
+func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, data []byte) error {
+	if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
+		return structConvert.FromDB(data)
+	}
+
+	if structConvert, ok := fieldValue.Interface().(core.Conversion); ok {
+		return structConvert.FromDB(data)
+	}
+
+	var v interface{}
+	key := col.Name
+	fieldType := fieldValue.Type()
+
+	switch fieldType.Kind() {
+	case reflect.Complex64, reflect.Complex128:
+		x := reflect.New(fieldType)
+		if len(data) > 0 {
+			err := json.Unmarshal(data, x.Interface())
+			if err != nil {
+				session.Engine.logger.Error(err)
+				return err
+			}
+			fieldValue.Set(x.Elem())
+		}
+	case reflect.Slice, reflect.Array, reflect.Map:
+		v = data
+		t := fieldType.Elem()
+		k := t.Kind()
+		if col.SQLType.IsText() {
+			x := reflect.New(fieldType)
+			if len(data) > 0 {
+				err := json.Unmarshal(data, x.Interface())
+				if err != nil {
+					session.Engine.logger.Error(err)
+					return err
+				}
+				fieldValue.Set(x.Elem())
+			}
+		} else if col.SQLType.IsBlob() {
+			if k == reflect.Uint8 {
+				fieldValue.Set(reflect.ValueOf(v))
+			} else {
+				x := reflect.New(fieldType)
+				if len(data) > 0 {
+					err := json.Unmarshal(data, x.Interface())
+					if err != nil {
+						session.Engine.logger.Error(err)
+						return err
+					}
+					fieldValue.Set(x.Elem())
+				}
+			}
+		} else {
+			return ErrUnSupportedType
+		}
+	case reflect.String:
+		fieldValue.SetString(string(data))
+	case reflect.Bool:
+		d := string(data)
+		v, err := strconv.ParseBool(d)
+		if err != nil {
+			return fmt.Errorf("arg %v as bool: %s", key, err.Error())
+		}
+		fieldValue.Set(reflect.ValueOf(v))
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		sdata := string(data)
+		var x int64
+		var err error
+		// for mysql, when use bit, it returned \x01
+		if col.SQLType.Name == core.Bit &&
+			session.Engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API
+			if len(data) == 1 {
+				x = int64(data[0])
+			} else {
+				x = 0
+			}
+		} else if strings.HasPrefix(sdata, "0x") {
+			x, err = strconv.ParseInt(sdata, 16, 64)
+		} else if strings.HasPrefix(sdata, "0") {
+			x, err = strconv.ParseInt(sdata, 8, 64)
+		} else if strings.EqualFold(sdata, "true") {
+			x = 1
+		} else if strings.EqualFold(sdata, "false") {
+			x = 0
+		} else {
+			x, err = strconv.ParseInt(sdata, 10, 64)
+		}
+		if err != nil {
+			return fmt.Errorf("arg %v as int: %s", key, err.Error())
+		}
+		fieldValue.SetInt(x)
+	case reflect.Float32, reflect.Float64:
+		x, err := strconv.ParseFloat(string(data), 64)
+		if err != nil {
+			return fmt.Errorf("arg %v as float64: %s", key, err.Error())
+		}
+		fieldValue.SetFloat(x)
+	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
+		x, err := strconv.ParseUint(string(data), 10, 64)
+		if err != nil {
+			return fmt.Errorf("arg %v as int: %s", key, err.Error())
+		}
+		fieldValue.SetUint(x)
+	//Currently only support Time type
+	case reflect.Struct:
+		// !<winxxp>! 增加支持sql.Scanner接口的结构,如sql.NullString
+		if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
+			if err := nulVal.Scan(data); err != nil {
+				return fmt.Errorf("sql.Scan(%v) failed: %s ", data, err.Error())
+			}
+		} else {
+			if fieldType.ConvertibleTo(core.TimeType) {
+				x, err := session.byte2Time(col, data)
+				if err != nil {
+					return err
+				}
+				v = x
+				fieldValue.Set(reflect.ValueOf(v).Convert(fieldType))
+			} else if session.Statement.UseCascade {
+				table := session.Engine.autoMapType(*fieldValue)
+				if table != nil {
+					// TODO: current only support 1 primary key
+					if len(table.PrimaryKeys) > 1 {
+						panic("unsupported composited primary key cascade")
+					}
+					var pk = make(core.PK, len(table.PrimaryKeys))
+					rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
+					var err error
+					pk[0], err = str2PK(string(data), rawValueType)
+					if err != nil {
+						return 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())
+						newsession := session.Engine.NewSession()
+						defer newsession.Close()
+						has, err := newsession.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")
+						}
+					}
+				} else {
+					return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String())
+				}
+			}
+		}
+	case reflect.Ptr:
+		// !nashtsai! TODO merge duplicated codes above
+		//typeStr := fieldType.String()
+		switch fieldType.Elem().Kind() {
+		// case "*string":
+		case core.StringType.Kind():
+			x := string(data)
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*bool":
+		case core.BoolType.Kind():
+			d := string(data)
+			v, err := strconv.ParseBool(d)
+			if err != nil {
+				return fmt.Errorf("arg %v as bool: %s", key, err.Error())
+			}
+			fieldValue.Set(reflect.ValueOf(&v).Convert(fieldType))
+		// case "*complex64":
+		case core.Complex64Type.Kind():
+			var x complex64
+			if len(data) > 0 {
+				err := json.Unmarshal(data, &x)
+				if err != nil {
+					session.Engine.logger.Error(err)
+					return err
+				}
+				fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+			}
+		// case "*complex128":
+		case core.Complex128Type.Kind():
+			var x complex128
+			if len(data) > 0 {
+				err := json.Unmarshal(data, &x)
+				if err != nil {
+					session.Engine.logger.Error(err)
+					return err
+				}
+				fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+			}
+		// case "*float64":
+		case core.Float64Type.Kind():
+			x, err := strconv.ParseFloat(string(data), 64)
+			if err != nil {
+				return fmt.Errorf("arg %v as float64: %s", key, err.Error())
+			}
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*float32":
+		case core.Float32Type.Kind():
+			var x float32
+			x1, err := strconv.ParseFloat(string(data), 32)
+			if err != nil {
+				return fmt.Errorf("arg %v as float32: %s", key, err.Error())
+			}
+			x = float32(x1)
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*uint64":
+		case core.Uint64Type.Kind():
+			var x uint64
+			x, err := strconv.ParseUint(string(data), 10, 64)
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*uint":
+		case core.UintType.Kind():
+			var x uint
+			x1, err := strconv.ParseUint(string(data), 10, 64)
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			x = uint(x1)
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*uint32":
+		case core.Uint32Type.Kind():
+			var x uint32
+			x1, err := strconv.ParseUint(string(data), 10, 64)
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			x = uint32(x1)
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*uint8":
+		case core.Uint8Type.Kind():
+			var x uint8
+			x1, err := strconv.ParseUint(string(data), 10, 64)
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			x = uint8(x1)
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*uint16":
+		case core.Uint16Type.Kind():
+			var x uint16
+			x1, err := strconv.ParseUint(string(data), 10, 64)
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			x = uint16(x1)
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*int64":
+		case core.Int64Type.Kind():
+			sdata := string(data)
+			var x int64
+			var err error
+			// for mysql, when use bit, it returned \x01
+			if col.SQLType.Name == core.Bit &&
+				strings.Contains(session.Engine.DriverName(), "mysql") {
+				if len(data) == 1 {
+					x = int64(data[0])
+				} else {
+					x = 0
+				}
+			} else if strings.HasPrefix(sdata, "0x") {
+				x, err = strconv.ParseInt(sdata, 16, 64)
+			} else if strings.HasPrefix(sdata, "0") {
+				x, err = strconv.ParseInt(sdata, 8, 64)
+			} else {
+				x, err = strconv.ParseInt(sdata, 10, 64)
+			}
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*int":
+		case core.IntType.Kind():
+			sdata := string(data)
+			var x int
+			var x1 int64
+			var err error
+			// for mysql, when use bit, it returned \x01
+			if col.SQLType.Name == core.Bit &&
+				strings.Contains(session.Engine.DriverName(), "mysql") {
+				if len(data) == 1 {
+					x = int(data[0])
+				} else {
+					x = 0
+				}
+			} else if strings.HasPrefix(sdata, "0x") {
+				x1, err = strconv.ParseInt(sdata, 16, 64)
+				x = int(x1)
+			} else if strings.HasPrefix(sdata, "0") {
+				x1, err = strconv.ParseInt(sdata, 8, 64)
+				x = int(x1)
+			} else {
+				x1, err = strconv.ParseInt(sdata, 10, 64)
+				x = int(x1)
+			}
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*int32":
+		case core.Int32Type.Kind():
+			sdata := string(data)
+			var x int32
+			var x1 int64
+			var err error
+			// for mysql, when use bit, it returned \x01
+			if col.SQLType.Name == core.Bit &&
+				session.Engine.dialect.DBType() == core.MYSQL {
+				if len(data) == 1 {
+					x = int32(data[0])
+				} else {
+					x = 0
+				}
+			} else if strings.HasPrefix(sdata, "0x") {
+				x1, err = strconv.ParseInt(sdata, 16, 64)
+				x = int32(x1)
+			} else if strings.HasPrefix(sdata, "0") {
+				x1, err = strconv.ParseInt(sdata, 8, 64)
+				x = int32(x1)
+			} else {
+				x1, err = strconv.ParseInt(sdata, 10, 64)
+				x = int32(x1)
+			}
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*int8":
+		case core.Int8Type.Kind():
+			sdata := string(data)
+			var x int8
+			var x1 int64
+			var err error
+			// for mysql, when use bit, it returned \x01
+			if col.SQLType.Name == core.Bit &&
+				strings.Contains(session.Engine.DriverName(), "mysql") {
+				if len(data) == 1 {
+					x = int8(data[0])
+				} else {
+					x = 0
+				}
+			} else if strings.HasPrefix(sdata, "0x") {
+				x1, err = strconv.ParseInt(sdata, 16, 64)
+				x = int8(x1)
+			} else if strings.HasPrefix(sdata, "0") {
+				x1, err = strconv.ParseInt(sdata, 8, 64)
+				x = int8(x1)
+			} else {
+				x1, err = strconv.ParseInt(sdata, 10, 64)
+				x = int8(x1)
+			}
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*int16":
+		case core.Int16Type.Kind():
+			sdata := string(data)
+			var x int16
+			var x1 int64
+			var err error
+			// for mysql, when use bit, it returned \x01
+			if col.SQLType.Name == core.Bit &&
+				strings.Contains(session.Engine.DriverName(), "mysql") {
+				if len(data) == 1 {
+					x = int16(data[0])
+				} else {
+					x = 0
+				}
+			} else if strings.HasPrefix(sdata, "0x") {
+				x1, err = strconv.ParseInt(sdata, 16, 64)
+				x = int16(x1)
+			} else if strings.HasPrefix(sdata, "0") {
+				x1, err = strconv.ParseInt(sdata, 8, 64)
+				x = int16(x1)
+			} else {
+				x1, err = strconv.ParseInt(sdata, 10, 64)
+				x = int16(x1)
+			}
+			if err != nil {
+				return fmt.Errorf("arg %v as int: %s", key, err.Error())
+			}
+			fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType))
+		// case "*SomeStruct":
+		case reflect.Struct:
+			switch fieldType {
+			// case "*.time.Time":
+			case core.PtrTimeType:
+				x, err := session.byte2Time(col, data)
+				if err != nil {
+					return err
+				}
+				v = x
+				fieldValue.Set(reflect.ValueOf(&x))
+			default:
+				if session.Statement.UseCascade {
+					structInter := reflect.New(fieldType.Elem())
+					table := session.Engine.autoMapType(structInter.Elem())
+					if table != nil {
+						if len(table.PrimaryKeys) > 1 {
+							panic("unsupported composited primary key cascade")
+						}
+						var pk = make(core.PK, len(table.PrimaryKeys))
+						var err error
+						rawValueType := table.ColumnType(table.PKColumns()[0].FieldName)
+						pk[0], err = str2PK(string(data), rawValueType)
+						if err != nil {
+							return 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
+							newsession := session.Engine.NewSession()
+							defer newsession.Close()
+							has, err := newsession.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:
+			return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String())
+		}
+	default:
+		return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String())
+	}
+
+	return nil
+}
+
+// convert a field value of a struct to interface for put into db
+func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Value) (interface{}, error) {
+	if fieldValue.CanAddr() {
+		if fieldConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok {
+			data, err := fieldConvert.ToDB()
+			if err != nil {
+				return 0, err
+			}
+			if col.SQLType.IsBlob() {
+				return data, nil
+			}
+			return string(data), nil
+		}
+	}
+
+	if fieldConvert, ok := fieldValue.Interface().(core.Conversion); ok {
+		data, err := fieldConvert.ToDB()
+		if err != nil {
+			return 0, err
+		}
+		if col.SQLType.IsBlob() {
+			return data, nil
+		}
+		return string(data), nil
+	}
+
+	fieldType := fieldValue.Type()
+	k := fieldType.Kind()
+	if k == reflect.Ptr {
+		if fieldValue.IsNil() {
+			return nil, nil
+		} else if !fieldValue.IsValid() {
+			session.Engine.logger.Warn("the field[", col.FieldName, "] is invalid")
+			return nil, nil
+		} else {
+			// !nashtsai! deference pointer type to instance type
+			fieldValue = fieldValue.Elem()
+			fieldType = fieldValue.Type()
+			k = fieldType.Kind()
+		}
+	}
+
+	switch k {
+	case reflect.Bool:
+		return fieldValue.Bool(), nil
+	case reflect.String:
+		return fieldValue.String(), nil
+	case reflect.Struct:
+		if fieldType.ConvertibleTo(core.TimeType) {
+			t := fieldValue.Convert(core.TimeType).Interface().(time.Time)
+			if session.Engine.dialect.DBType() == core.MSSQL {
+				if t.IsZero() {
+					return nil, nil
+				}
+			}
+			tf := session.Engine.FormatTime(col.SQLType.Name, t)
+			return tf, nil
+		}
+
+		if !col.SQLType.IsJson() {
+			// !<winxxp>! 增加支持driver.Valuer接口的结构,如sql.NullString
+			if v, ok := fieldValue.Interface().(driver.Valuer); ok {
+				return v.Value()
+			}
+
+			fieldTable := session.Engine.autoMapType(fieldValue)
+			if len(fieldTable.PrimaryKeys) == 1 {
+				pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName)
+				return pkField.Interface(), nil
+			}
+			return 0, fmt.Errorf("no primary key for col %v", col.Name)
+		}
+
+		if col.SQLType.IsText() {
+			bytes, err := json.Marshal(fieldValue.Interface())
+			if err != nil {
+				session.Engine.logger.Error(err)
+				return 0, err
+			}
+			return string(bytes), nil
+		} else if col.SQLType.IsBlob() {
+			bytes, err := json.Marshal(fieldValue.Interface())
+			if err != nil {
+				session.Engine.logger.Error(err)
+				return 0, err
+			}
+			return bytes, nil
+		}
+		return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type())
+	case reflect.Complex64, reflect.Complex128:
+		bytes, err := json.Marshal(fieldValue.Interface())
+		if err != nil {
+			session.Engine.logger.Error(err)
+			return 0, err
+		}
+		return string(bytes), nil
+	case reflect.Array, reflect.Slice, reflect.Map:
+		if !fieldValue.IsValid() {
+			return fieldValue.Interface(), nil
+		}
+
+		if col.SQLType.IsText() {
+			bytes, err := json.Marshal(fieldValue.Interface())
+			if err != nil {
+				session.Engine.logger.Error(err)
+				return 0, err
+			}
+			return string(bytes), nil
+		} else if col.SQLType.IsBlob() {
+			var bytes []byte
+			var err error
+			if (k == reflect.Array || k == reflect.Slice) &&
+				(fieldValue.Type().Elem().Kind() == reflect.Uint8) {
+				bytes = fieldValue.Bytes()
+			} else {
+				bytes, err = json.Marshal(fieldValue.Interface())
+				if err != nil {
+					session.Engine.logger.Error(err)
+					return 0, err
+				}
+			}
+			return bytes, nil
+		}
+		return nil, ErrUnSupportedType
+	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
+		return int64(fieldValue.Uint()), nil
+	default:
+		return fieldValue.Interface(), nil
+	}
+}

+ 3 - 1
session_get.go

@@ -85,7 +85,9 @@ func (session *Session) nocacheGet(beanKind reflect.Kind, bean interface{}, sqlS
 
 		switch beanKind {
 		case reflect.Struct:
-			_, err = session.row2Bean(rawRows, fields, len(fields), bean)
+			dataStruct := rValue(bean)
+			session.Statement.setRefValue(dataStruct)
+			_, err = session.row2Bean(rawRows, fields, len(fields), bean, &dataStruct, session.Statement.RefTable)
 		case reflect.Slice:
 			err = rawRows.ScanSlice(bean)
 		case reflect.Map:

+ 1 - 1
xorm.go

@@ -17,7 +17,7 @@ import (
 
 const (
 	// Version show the xorm's version
-	Version string = "0.6.0.1022"
+	Version string = "0.6.1.0315"
 )
 
 func regDrvsNDialects() bool {