diff --git a/convert.go b/convert.go index a2124dd3..081e799f 100644 --- a/convert.go +++ b/convert.go @@ -42,6 +42,13 @@ func asString(src interface{}) string { return string(v) case *sql.NullString: return v.String + case *sql.RawBytes: + return string(*v) + case *sql.NullBool: + if v.Valid { + return strconv.FormatBool(v.Bool) + } + return "" case *sql.NullInt32: return fmt.Sprintf("%d", v.Int32) case *sql.NullInt64: @@ -236,6 +243,7 @@ func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { // An error is returned if the copy would result in loss of information. // dest should be a pointer type. func convertAssign(dest, src interface{}, originalLocation *time.Location, convertedLocation *time.Location) error { + fmt.Printf("======= %#v ------> %#v \n", src, dest) // Common cases, without reflect. switch s := src.(type) { case string: @@ -253,6 +261,17 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve *d = []byte(s) return nil } + case int64: + switch d := dest.(type) { + case *uint64: + *d = uint64(s) + return nil + } + fmt.Println("======", src, dest) + case int, int32, int16, int8: + fmt.Println("22222222") + case uint, uint32, uint64, uint8, uint16: + fmt.Println("3333333") case []byte: switch d := dest.(type) { case *string: @@ -274,7 +293,6 @@ func convertAssign(dest, src interface{}, originalLocation *time.Location, conve *d = cloneBytes(s) return nil } - case time.Time: switch d := dest.(type) { case *string: @@ -527,6 +545,7 @@ func convertAssignV(dpv reflect.Value, src interface{}, originalLocation, conver } dv.Set(reflect.New(dv.Type().Elem())) + fmt.Println("333333") return convertAssign(dv.Interface(), src, originalLocation, convertedLocation) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: i64, err := asInt64(src) @@ -592,7 +611,6 @@ func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) { } return v, nil } - } return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv) } diff --git a/convert/string.go b/convert/string.go index e39f94a1..bf1c5ff4 100644 --- a/convert/string.go +++ b/convert/string.go @@ -62,3 +62,52 @@ func Interface2String(v interface{}) (string, error) { return "", fmt.Errorf("convert assign string unsupported type: %#v", vv) } } + +func Interface2Interface(v interface{}) (interface{}, error) { + if v == nil { + return nil, nil + } + switch vv := v.(type) { + case *int64: + return *vv, nil + case *int8: + return *vv, nil + case *sql.NullString: + if vv.Valid { + return vv.String, nil + } + return "", nil + case *sql.RawBytes: + if len([]byte(*vv)) > 0 { + return []byte(*vv), nil + } + return nil, nil + case *sql.NullInt32: + if vv.Valid { + return vv.Int32, nil + } + return 0, nil + case *sql.NullInt64: + if vv.Valid { + return vv.Int64, nil + } + return 0, nil + case *sql.NullFloat64: + if vv.Valid { + return vv.Float64, nil + } + return 0, nil + case *sql.NullBool: + if vv.Valid { + return vv.Bool, nil + } + return nil, nil + case *sql.NullTime: + if vv.Valid { + return vv.Time.Format("2006-01-02 15:04:05"), nil + } + return "", nil + default: + return "", fmt.Errorf("convert assign string unsupported type: %#v", vv) + } +} diff --git a/integrations/tags_test.go b/integrations/tags_test.go index fc7b505e..b3c638ba 100644 --- a/integrations/tags_test.go +++ b/integrations/tags_test.go @@ -1201,7 +1201,7 @@ func TestTagTime(t *testing.T) { has, err = testEngine.Table("tag_u_t_c_struct").Cols("created").Get(&tm) assert.NoError(t, err) assert.True(t, has) - assert.EqualValues(t, s.Created.UTC().Format("2006-01-02 15:04:05"), + assert.EqualValues(t, s.Created.Format("2006-01-02 15:04:05"), strings.Replace(strings.Replace(tm, "T", " ", -1), "Z", "", -1)) } diff --git a/integrations/types_test.go b/integrations/types_test.go index 539171d5..7aededbb 100644 --- a/integrations/types_test.go +++ b/integrations/types_test.go @@ -109,6 +109,7 @@ func TestGetBytes(t *testing.T) { type ConvString string func (s *ConvString) FromDB(data []byte) error { + fmt.Printf("======= %#v,,,,,, %#v\n", s, data) *s = ConvString("prefix---" + string(data)) return nil } diff --git a/processors.go b/processors.go index 8697e302..fd925ea5 100644 --- a/processors.go +++ b/processors.go @@ -4,6 +4,8 @@ package xorm +import "reflect" + // BeforeInsertProcessor executed before an object is initially persisted to the database type BeforeInsertProcessor interface { BeforeInsert() @@ -94,7 +96,8 @@ func executeBeforeClosures(session *Session, bean interface{}) { func executeBeforeSet(bean interface{}, fields []string, scanResults []interface{}) { if b, hasBeforeSet := bean.(BeforeSetProcessor); hasBeforeSet { for ii, key := range fields { - b.BeforeSet(key, Cell(scanResults[ii].(*interface{}))) + + b.BeforeSet(key, Cell(reflect.ValueOf(scanResults[ii]).Elem().Interface())) } } } @@ -102,7 +105,7 @@ func executeBeforeSet(bean interface{}, fields []string, scanResults []interface func executeAfterSet(bean interface{}, fields []string, scanResults []interface{}) { if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet { for ii, key := range fields { - b.AfterSet(key, Cell(scanResults[ii].(*interface{}))) + b.AfterSet(key, Cell(reflect.ValueOf(scanResults[ii]).Elem().Interface())) } } } diff --git a/rows.go b/rows.go index a56ea1c9..dbae9862 100644 --- a/rows.go +++ b/rows.go @@ -130,7 +130,12 @@ func (rows *Rows) Scan(bean interface{}) error { return err } - scanResults, err := rows.session.row2Slice(rows.rows, fields, bean) + types, err := rows.rows.ColumnTypes() + if err != nil { + return err + } + + scanResults, err := rows.session.row2Slice(rows.rows, types, fields, bean) if err != nil { return err } diff --git a/schemas/type.go b/schemas/type.go index fc02f015..7b3169f5 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -5,6 +5,7 @@ package schemas import ( + "database/sql" "reflect" "sort" "strings" @@ -222,24 +223,28 @@ var ( // !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison var ( - emptyString string - boolDefault bool - byteDefault byte - complex64Default complex64 - complex128Default complex128 - float32Default float32 - float64Default float64 - int64Default int64 - uint64Default uint64 - int32Default int32 - uint32Default uint32 - int16Default int16 - uint16Default uint16 - int8Default int8 - uint8Default uint8 - intDefault int - uintDefault uint - timeDefault time.Time + emptyString string + boolDefault bool + byteDefault byte + complex64Default complex64 + complex128Default complex128 + float32Default float32 + float64Default float64 + int64Default int64 + uint64Default uint64 + int32Default int32 + uint32Default uint32 + int16Default int16 + uint16Default uint16 + int8Default int8 + uint8Default uint8 + intDefault int + uintDefault uint + timeDefault time.Time + nullInt64Default sql.NullInt64 + nullFloat64Default sql.NullFloat64 + nullBoolDefault sql.NullBool + nullTimeDefault sql.NullTime ) // enumerates all types @@ -268,6 +273,11 @@ var ( BytesType = reflect.SliceOf(ByteType) TimeType = reflect.TypeOf(timeDefault) + + NullInt64Type = reflect.TypeOf(nullInt64Default) + NullFloat64Type = reflect.TypeOf(nullFloat64Default) + NullBoolType = reflect.TypeOf(nullBoolDefault) + NullTimeType = reflect.TypeOf(nullTimeDefault) ) // enumerates all types @@ -295,6 +305,9 @@ var ( PtrByteType = reflect.PtrTo(ByteType) PtrTimeType = reflect.PtrTo(TimeType) + + PtrNullInt64Type = reflect.PtrTo(NullInt64Type) + PtrNullFloat64Type = reflect.PtrTo(NullFloat64Type) ) // Type2SQLType generate SQLType acorrding Go's type @@ -327,6 +340,14 @@ func Type2SQLType(t reflect.Type) (st SQLType) { case reflect.Struct: if t.ConvertibleTo(TimeType) { st = SQLType{DateTime, 0, 0} + } else if t.ConvertibleTo(NullInt64Type) { + st = SQLType{BigInt, 0, 0} + } else if t.ConvertibleTo(NullFloat64Type) { + st = SQLType{Double, 0, 0} + } else if t.ConvertibleTo(NullBoolType) { + st = SQLType{Bool, 0, 0} + } else if t.ConvertibleTo(NullTimeType) { + st = SQLType{DateTime, 0, 0} } else { // TODO need to handle association struct st = SQLType{Text, 0, 0} diff --git a/session.go b/session.go index 6df9e20d..8a645815 100644 --- a/session.go +++ b/session.go @@ -15,6 +15,7 @@ import ( "hash/crc32" "io" "reflect" + "strconv" "strings" "time" @@ -387,9 +388,9 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *s } // Cell cell is a result of one column field -type Cell *interface{} +type Cell interface{} -func (session *Session) rows2Beans(rows *core.Rows, fields []string, +func (session *Session) rows2Beans(rows *core.Rows, types []*sql.ColumnType, fields []string, table *schemas.Table, newElemFunc func([]string) reflect.Value, sliceValueSetFunc func(*reflect.Value, schemas.PK) error) error { for rows.Next() { @@ -398,7 +399,7 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, dataStruct := newValue.Elem() // handle beforeClosures - scanResults, err := session.row2Slice(rows, fields, bean) + scanResults, err := session.row2Slice(rows, types, fields, bean) if err != nil { return err } @@ -417,17 +418,29 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, return nil } -func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interface{}) ([]interface{}, error) { +func (session *Session) genScanResultsByTypes(types []*sql.ColumnType) ([]interface{}, error) { + scanResults := make([]interface{}, len(types)) + for i := 0; i < len(types); i++ { + result, err := session.engine.driver.GenScanResult(types[i].DatabaseTypeName()) + if err != nil { + return nil, err + } + scanResults[i] = result + } + return scanResults, nil +} + +func (session *Session) row2Slice(rows *core.Rows, types []*sql.ColumnType, fields []string, bean interface{}) ([]interface{}, error) { for _, closure := range session.beforeClosures { closure(bean) } - scanResults := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var cell interface{} - scanResults[i] = &cell + scanResults, err := session.genScanResultsByTypes(types) + if err != nil { + return nil, err } - if err := rows.Scan(scanResults...); err != nil { + + if err := session.engine.scan(rows, types, scanResults...); err != nil { return nil, err } @@ -436,206 +449,370 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interfa return scanResults, nil } -func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) { - defer func() { - executeAfterSet(bean, fields, scanResults) - }() +var ( + scannerTypePlaceHolder sql.Scanner + scannerType = reflect.TypeOf(&scannerTypePlaceHolder).Elem() +) - buildAfterProcessors(session, bean) +// convertAssign converts an interface src to dst reflect.Value fieldValue +func (session *Session) convertAssign(fieldValue *reflect.Value, columnName string, src interface{}, table *schemas.Table, pk *schemas.PK, idx int) error { + if fieldValue == nil { + return nil + } + // if row is null then ignore + if src == nil { + return nil + } - var tempMap = make(map[string]int) - var pk schemas.PK - for ii, key := range fields { - var idx int - var ok bool - var lKey = strings.ToLower(key) - if idx, ok = tempMap[lKey]; !ok { - idx = 0 - } else { - idx = idx + 1 - } - tempMap[lKey] = idx + rawValue := reflect.Indirect(reflect.ValueOf(src)) + if rawValue.Interface() == nil { + return nil + } - fieldValue, err := session.getField(dataStruct, key, table, idx) - if err != nil { - if !strings.Contains(err.Error(), "is not valid") { - session.engine.logger.Warnf("%v", err) - } - continue - } - if fieldValue == nil { - continue - } - rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii])) + fmt.Printf("----- %v <------ %v \n", fieldValue.Type(), rawValue.Type()) + if fieldValue.Type() == rawValue.Type() { + fieldValue.Set(rawValue) + return nil + } - // if row is null then ignore - if rawValue.Interface() == nil { - continue + if fieldValue.CanAddr() { + if scanner, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { + fmt.Printf("%s, ===========00000000000 %#v <----- %#v \n", columnName, fieldValue.Addr().Interface(), src) + return scanner.Scan(src) } - if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { - if data, err := value2Bytes(&rawValue); err == nil { - if err := structConvert.FromDB(data); err != nil { - return nil, err - } - } else { - return nil, err + if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok { + switch t := src.(type) { + case *sql.RawBytes: + if err := structConvert.FromDB([]byte(*t)); err != nil { + return err } - continue + case *sql.NullString: + if t.Valid { + if err := structConvert.FromDB([]byte(t.String)); err != nil { + return err + } + } + default: + return fmt.Errorf("unsupported type: %#v on column %v", t, columnName) } + return nil + } + } + + if structConvert, ok := fieldValue.Interface().(convert.Conversion); ok { + if scanner, ok := fieldValue.Interface().(sql.Scanner); ok { + fmt.Println("===========111111111111") + return scanner.Scan(src) } - if _, ok := fieldValue.Interface().(convert.Conversion); ok { - if data, err := value2Bytes(&rawValue); err == nil { - if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { + switch t := src.(type) { + case *sql.RawBytes: + if fieldValue.IsNil() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + structConvert = fieldValue.Interface().(convert.Conversion) + } + if err := structConvert.FromDB([]byte(*t)); err != nil { + return err + } + case *sql.NullString: + if t.Valid { + if fieldValue.IsNil() { fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + structConvert = fieldValue.Interface().(convert.Conversion) + } + if err := structConvert.FromDB([]byte(t.String)); err != nil { + return err } - fieldValue.Interface().(convert.Conversion).FromDB(data) - } else { - return nil, err } - continue + default: + return fmt.Errorf("unsupported type: %#v", t) } + return nil + } - rawValueType := reflect.TypeOf(rawValue.Interface()) - vv := reflect.ValueOf(rawValue.Interface()) - col := table.GetColumnIdx(key, idx) - if col.IsPrimaryKey { - pk = append(pk, rawValue.Interface()) - } - fieldType := fieldValue.Type() - hasAssigned := false + rawValueType := reflect.TypeOf(rawValue.Interface()) + vv := reflect.ValueOf(rawValue.Interface()) + col := table.GetColumnIdx(columnName, idx) + if col.IsPrimaryKey { + *pk = append(*pk, rawValue.Interface()) + } + fieldType := fieldValue.Type() + hasAssigned := false - if col.IsJSON { - var bs []byte + if col.IsJSON { + var bs []byte + switch t := src.(type) { + case *sql.NullString: + if t.Valid { + bs = []byte(t.String) + } + case *sql.RawBytes: + bs = *t + default: if rawValueType.Kind() == reflect.String { bs = []byte(vv.String()) } else if rawValueType.ConvertibleTo(schemas.BytesType) { bs = vv.Bytes() } else { - return nil, fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind()) + return fmt.Errorf("unsupported database data type: %s %v ---> %#v", columnName, rawValueType.Kind(), src) } - - hasAssigned = true - - if len(bs) > 0 { - if fieldType.Kind() == reflect.String { - fieldValue.SetString(string(bs)) - continue - } - if fieldValue.CanAddr() { - err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) - if err != nil { - return nil, err - } - } else { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) - } - } - - continue } - switch fieldType.Kind() { - case reflect.Complex64, reflect.Complex128: - // TODO: reimplement this - var bs []byte - if rawValueType.Kind() == reflect.String { - bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(schemas.BytesType) { - bs = vv.Bytes() + if len(bs) > 0 { + if fieldType.Kind() == reflect.String { + fieldValue.SetString(string(bs)) + return nil } + if fieldValue.CanAddr() { + err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) + if err != nil { + return err + } + } else { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } + } + return nil + } + var kind = fieldType.Kind() + if reflect.Ptr == kind { + kind = fieldType.Elem().Kind() + } + + switch kind { + case reflect.Complex64, reflect.Complex128: + // TODO: reimplement this + var bs []byte + if rawValueType.Kind() == reflect.String { + bs = []byte(vv.String()) + } else if rawValueType.ConvertibleTo(schemas.BytesType) { + bs = vv.Bytes() + } + + if len(bs) > 0 { + if fieldValue.CanAddr() { + err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) + if err != nil { + return err + } + } else { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } + } + return nil + case reflect.Slice, reflect.Array: + switch t := src.(type) { + case *sql.NullString: hasAssigned = true - if len(bs) > 0 { - if fieldValue.CanAddr() { - err := json.DefaultJSONHandler.Unmarshal(bs, fieldValue.Addr().Interface()) - if err != nil { - return nil, err - } - } else { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) + fmt.Printf("====== %#v <-------- %#v \n", fieldValue.Interface(), t) + if t.Valid { + if fieldType.Elem().Kind() == reflect.Uint8 { + fieldValue.SetBytes([]byte(t.String)) } } + } + switch rawValueType.Kind() { case reflect.Slice, reflect.Array: - switch rawValueType.Kind() { - case reflect.Slice, reflect.Array: - switch rawValueType.Elem().Kind() { - case reflect.Uint8: - if fieldType.Elem().Kind() == reflect.Uint8 { - hasAssigned = true - if col.SQLType.IsText() { - x := reflect.New(fieldType) - err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) - if err != nil { - return nil, err + switch rawValueType.Elem().Kind() { + case reflect.Uint8: + if fieldType.Elem().Kind() == reflect.Uint8 { + hasAssigned = true + if col.SQLType.IsText() { + x := reflect.New(fieldType) + err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) + if err != nil { + return err + } + fieldValue.Set(x.Elem()) + } else { + if fieldValue.Len() > 0 { + for i := 0; i < fieldValue.Len(); i++ { + if i < vv.Len() { + fieldValue.Index(i).Set(vv.Index(i)) + } } - fieldValue.Set(x.Elem()) } else { - if fieldValue.Len() > 0 { - for i := 0; i < fieldValue.Len(); i++ { - if i < vv.Len() { - fieldValue.Index(i).Set(vv.Index(i)) - } - } - } else { - for i := 0; i < vv.Len(); i++ { - fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i))) - } + for i := 0; i < vv.Len(); i++ { + fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i))) } } } } } - case reflect.String: - if rawValueType.Kind() == reflect.String { - hasAssigned = true - fieldValue.SetString(vv.String()) + } + case reflect.String: + switch t := src.(type) { + case *sql.NullString: + if t.Valid { + fieldValue.SetString(t.String) } - case reflect.Bool: - if rawValueType.Kind() == reflect.Bool { - hasAssigned = true - fieldValue.SetBool(vv.Bool()) + return nil + case sql.NullString: + if t.Valid { + fieldValue.SetString(t.String) } + return nil + case *sql.NullTime: + if t.Valid { + fieldValue.SetString(t.Time.In(session.engine.TZLocation).Format("2006-01-02 15:04:05")) + } + return nil + } + if rawValueType.Kind() == reflect.String { + fieldValue.SetString(vv.String()) + return nil + } + case reflect.Bool: + switch t := src.(type) { + case *sql.NullBool: + if t.Valid { + fieldValue.SetBool(t.Bool) + } + return nil + case *sql.NullInt64: + if t.Valid { + fieldValue.SetBool(t.Int64 > 0) + } + return nil + } + if rawValueType.Kind() == reflect.Bool { + fieldValue.SetBool(vv.Bool()) + return nil + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch t := src.(type) { + case *sql.NullInt64: + if t.Valid { + fieldValue.SetInt(t.Int64) + } + return nil + case *sql.NullString: + if t.Valid { + tv, _ := strconv.ParseInt(t.String, 10, 64) + fieldValue.SetInt(tv) + } + return nil + } + switch rawValueType.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - switch rawValueType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - hasAssigned = true - fieldValue.SetInt(vv.Int()) + fieldValue.SetInt(vv.Int()) + return nil + default: + return fmt.Errorf("unsupported convert %v to %v", rawValueType.Kind(), fieldType.Kind()) + } + case reflect.Float32, reflect.Float64: + switch t := src.(type) { + case *sql.NullFloat64: + if t.Valid { + fieldValue.SetFloat(t.Float64) } - case reflect.Float32, reflect.Float64: - switch rawValueType.Kind() { - case reflect.Float32, reflect.Float64: - hasAssigned = true - fieldValue.SetFloat(vv.Float()) - } - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - switch rawValueType.Kind() { - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - hasAssigned = true - fieldValue.SetUint(vv.Uint()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - hasAssigned = true - fieldValue.SetUint(uint64(vv.Int())) - } - case reflect.Struct: - if fieldType.ConvertibleTo(schemas.TimeType) { - dbTZ := session.engine.DatabaseTZ - if col.TimeZone != nil { - dbTZ = col.TimeZone + return nil + case *sql.NullString: + if t.Valid { + vv, err := strconv.ParseFloat(t.String, 10) + if err != nil { + return err } + fieldValue.SetFloat(vv) + } + return nil + } + switch rawValueType.Kind() { + case reflect.Float32, reflect.Float64: + fieldValue.SetFloat(vv.Float()) + return nil + } + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + switch t := src.(type) { + case *sql.NullInt64: + if t.Valid { + fieldValue.SetUint(uint64(t.Int64)) + } + return nil + case *sql.NullString: + if t.Valid { + vv, err := strconv.ParseUint(t.String, 10, 64) + if err != nil { + return err + } + fieldValue.SetUint(vv) + } + return nil + } + switch rawValueType.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + fieldValue.SetUint(vv.Uint()) + return nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + hasAssigned = true + return nil + } + case reflect.Struct: + if fieldType.ConvertibleTo(schemas.TimeType) { + dbTZ := session.engine.DatabaseTZ + if col.TimeZone != nil { + dbTZ = col.TimeZone + } + + fmt.Printf("99999999 %#v\n", src) + + switch d := src.(type) { + case []byte: + hasAssigned = true + t, err := session.byte2Time(col, d) + if err != nil { + session.engine.logger.Errorf("byte2Time error: %v", err) + hasAssigned = false + } else { + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + } + case string: + hasAssigned = true + t, err := session.str2Time(col, d) + if err != nil { + session.engine.logger.Errorf("byte2Time error: %v", err) + hasAssigned = false + } else { + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + } + case *sql.NullInt64: + hasAssigned = true + if d.Valid { + t := time.Unix(d.Int64, 0).In(session.engine.TZLocation) + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + } + case *sql.NullString: + hasAssigned = true + if d.Valid { + t, err := session.str2Time(col, d.String) + if err != nil { + session.engine.logger.Errorf("byte2Time error: %v", err) + hasAssigned = false + } else { + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + } + } + case *sql.NullTime: + hasAssigned = true + if d.Valid { + fieldValue.Set(reflect.ValueOf(d.Time).Convert(fieldType)) + } + case nil: + hasAssigned = true + default: if rawValueType == schemas.TimeType { hasAssigned = true @@ -644,7 +821,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b z, _ := t.Zone() // set new location if database don't save timezone or give an incorrect timezone if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location - session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) + session.engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", columnName, t, z, *t.Location()) t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), dbTZ) } @@ -658,216 +835,123 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation) fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) } else { - if d, ok := vv.Interface().([]uint8); ok { - hasAssigned = true - t, err := session.byte2Time(col, d) - if err != nil { - session.engine.logger.Errorf("byte2Time error: %v", err) - hasAssigned = false - } else { - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } - } else if d, ok := vv.Interface().(string); ok { - hasAssigned = true - t, err := session.str2Time(col, d) - if err != nil { - session.engine.logger.Errorf("byte2Time error: %v", err) - hasAssigned = false - } else { - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } - } else { - return nil, fmt.Errorf("rawValueType is %v, value is %v", rawValueType, vv.Interface()) - } + return fmt.Errorf("rawValueType is %v, value is %v", rawValueType, src) } - } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { - // !! 增加支持sql.Scanner接口的结构,如sql.NullString + } + } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { + if err := nulVal.Scan(src); err != nil { + return fmt.Errorf("sql.Sanner error: %v", err) + } + return nil + } else if col.IsJSON { + if rawValueType.Kind() == reflect.String { hasAssigned = true - if err := nulVal.Scan(vv.Interface()); err != nil { - session.engine.logger.Errorf("sql.Sanner error: %v", err) - hasAssigned = false - } - } else if col.IsJSON { - if rawValueType.Kind() == reflect.String { - hasAssigned = true - x := reflect.New(fieldType) - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) + x := reflect.New(fieldType) + if len([]byte(vv.String())) > 0 { + err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), x.Interface()) + if err != nil { + return err } - } else if rawValueType.Kind() == reflect.Slice { - hasAssigned = true - x := reflect.New(fieldType) - if len(vv.Bytes()) > 0 { - err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) - if err != nil { - return nil, err - } - fieldValue.Set(x.Elem()) + fieldValue.Set(x.Elem()) + } + } else if rawValueType.Kind() == reflect.Slice { + hasAssigned = true + x := reflect.New(fieldType) + if len(vv.Bytes()) > 0 { + err := json.DefaultJSONHandler.Unmarshal(vv.Bytes(), x.Interface()) + if err != nil { + return err } + fieldValue.Set(x.Elem()) } - } else if session.statement.UseCascade { - table, err := session.engine.tagParser.ParseWithCache(*fieldValue) - if err != nil { - return nil, err - } + } + } else if session.statement.UseCascade { + fmt.Printf("5565666======= %#v \n", *fieldValue) + table, err := session.engine.tagParser.ParseWithCache(*fieldValue) + if err != nil { + return err + } - hasAssigned = true - if len(table.PrimaryKeys) != 1 { - return nil, errors.New("unsupported non or composited primary key cascade") + hasAssigned = true + if len(table.PrimaryKeys) != 1 { + return errors.New("unsupported non or composited primary key cascade") + } + var pk = make(schemas.PK, len(table.PrimaryKeys)) + switch t := src.(type) { + case int64: + pk[0] = t + case *sql.NullInt64: + if t.Valid { + pk[0] = t.Int64 } - var pk = make(schemas.PK, len(table.PrimaryKeys)) + default: pk[0], err = asKind(vv, rawValueType) if err != nil { - return nil, err - } - - if !pk.IsZero() { - // !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") - } + return err } } - case reflect.Ptr: - // !nashtsai! TODO merge duplicated codes above - switch fieldType { - // following types case matching ptr's native type, therefore assign ptr directly - case schemas.PtrStringType: - if rawValueType.Kind() == reflect.String { - x := vv.String() - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrBoolType: - if rawValueType.Kind() == reflect.Bool { - x := vv.Bool() - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrTimeType: - if rawValueType == schemas.PtrTimeType { - hasAssigned = true - var x = rawValue.Interface().(time.Time) - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrFloat64Type: - if rawValueType.Kind() == reflect.Float64 { - x := vv.Float() - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrUint64Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint64(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrInt64Type: - if rawValueType.Kind() == reflect.Int64 { - x := vv.Int() - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrFloat32Type: - if rawValueType.Kind() == reflect.Float64 { - var x = float32(vv.Float()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrIntType: - if rawValueType.Kind() == reflect.Int64 { - var x = int(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrInt32Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int32(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrInt8Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int8(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrInt16Type: - if rawValueType.Kind() == reflect.Int64 { - var x = int16(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrUintType: - if rawValueType.Kind() == reflect.Int64 { - var x = uint(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.PtrUint32Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint32(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.Uint8Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint8(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.Uint16Type: - if rawValueType.Kind() == reflect.Int64 { - var x = uint16(vv.Int()) - hasAssigned = true - fieldValue.Set(reflect.ValueOf(&x)) - } - case schemas.Complex64Type: - var x complex64 - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) - if err != nil { - return nil, err - } - fieldValue.Set(reflect.ValueOf(&x)) - } - hasAssigned = true - case schemas.Complex128Type: - var x complex128 - if len([]byte(vv.String())) > 0 { - err := json.DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) - if err != nil { - return nil, err - } - fieldValue.Set(reflect.ValueOf(&x)) - } - hasAssigned = true - } // switch fieldType - } // switch fieldType.Kind() - // !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value - if !hasAssigned { - data, err := value2Bytes(&rawValue) - if err != nil { - return nil, err + if !pk.IsZero() { + // !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 err + } + if has { + fieldValue.Set(structInter.Elem()) + } else { + return errors.New("cascade obj is not exist") + } } + } else if fieldType.ConvertibleTo(reflect.TypeOf(&sql.NullString{})) { + fmt.Println("=====3333======") + } else if fieldType.ConvertibleTo(reflect.TypeOf(&sql.NullInt64{})) { + fmt.Println("=====4444======") + } + case reflect.Ptr: + return errors.New("unsupported pointer to pointer") + } // switch fieldType.Kind() - if err = session.bytes2Value(col, fieldValue, data); err != nil { - return nil, err + if !hasAssigned { + return fmt.Errorf("unsupported convertion from %#v to %#v", src, fieldValue.Interface()) + } + + return nil +} + +func (session *Session) slice2Bean(scanResults []interface{}, columnNames []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) { + defer func() { + executeAfterSet(bean, columnNames, scanResults) + }() + + buildAfterProcessors(session, bean) + + var tempMap = make(map[string]int) + var pk schemas.PK + for i, columnName := range columnNames { + var idx int + var ok bool + var lKey = strings.ToLower(columnName) + if idx, ok = tempMap[lKey]; !ok { + idx = 0 + } else { + idx = idx + 1 + } + tempMap[lKey] = idx + + fieldValue, err := session.getField(dataStruct, columnName, table, idx) + if err != nil { + if !strings.Contains(err.Error(), "is not valid") { + session.engine.logger.Warnf("%v", err) } + continue + } + fmt.Printf("88888====== %#v \n ", scanResults[i]) + if err := session.convertAssign(fieldValue, columnName, scanResults[i], table, &pk, idx); err != nil { + return nil, err } } return pk, nil diff --git a/session_find.go b/session_find.go index 261e6b7f..3c80da57 100644 --- a/session_find.go +++ b/session_find.go @@ -172,6 +172,11 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect return err } + types, err := rows.ColumnTypes() + if err != nil { + return err + } + var newElemFunc func(fields []string) reflect.Value elemType := containerValue.Type().Elem() var isPointer bool @@ -241,7 +246,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect if err != nil { return err } - err = session.rows2Beans(rows, fields, tb, newElemFunc, containerValueSetFunc) + err = session.rows2Beans(rows, types, fields, tb, newElemFunc, containerValueSetFunc) rows.Close() if err != nil { return err @@ -274,6 +279,7 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect } func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) error { + fmt.Printf("----- %#v \n", pk) cols := table.PKColumns() if len(cols) == 1 { return convertAssign(dst, pk[0], nil, nil) diff --git a/session_get.go b/session_get.go index 473490a0..bb848c06 100644 --- a/session_get.go +++ b/session_get.go @@ -124,6 +124,31 @@ var ( conversionType = reflect.TypeOf(&conversionTypePlaceHolder).Elem() ) +func (session *Session) genScanResultsByBeanStruct(bean interface{}, fields ...string) ([]interface{}, error) { + structV := reflect.ValueOf(bean) + table, err := session.engine.tagParser.ParseWithCache(structV) + if err != nil { + return nil, err + } + structV = structV.Elem() + var dstResults = make([]interface{}, 0, len(fields)) + for _, field := range fields { + var fieldName string + for _, col := range table.Columns() { + if col.Name == field { + fieldName = col.FieldName + break + } + } + v := structV.FieldByName(fieldName) + if !v.IsValid() { + return nil, fmt.Errorf("get field named %v failed", field) + } + dstResults = append(dstResults, v.Addr()) + } + return dstResults, nil +} + func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { rows, err := session.queryRows(sqlStr, args...) if err != nil { diff --git a/session_raw.go b/session_raw.go index bf32c6ed..362eab8c 100644 --- a/session_raw.go +++ b/session_raw.go @@ -122,14 +122,6 @@ func value2String(rawValue *reflect.Value) (str string, err error) { return } -func value2Bytes(rawValue *reflect.Value) ([]byte, error) { - str, err := value2String(rawValue) - if err != nil { - return nil, err - } - return []byte(str), nil -} - func (session *Session) queryBytes(sqlStr string, args ...interface{}) ([]map[string][]byte, error) { rows, err := session.queryRows(sqlStr, args...) if err != nil { diff --git a/tags/parser.go b/tags/parser.go index b793a8f1..fc624d9f 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -271,6 +271,7 @@ func (parser *Parser) Parse(v reflect.Value) (*schemas.Table, error) { if t.Kind() == reflect.Ptr { t = t.Elem() v = v.Elem() + fmt.Println("======3333", v) } if t.Kind() != reflect.Struct { return nil, ErrUnsupportedType