diff --git a/circle.yml b/circle.yml index 7e49116a..3063ac9d 100644 --- a/circle.yml +++ b/circle.yml @@ -21,7 +21,7 @@ database: test: override: # './...' is a relative pattern which means all subdirectories - - go test -v -race -db="sqlite3;mysql;postgres" -conn_str="./test.db;root:@/xorm_test;dbname=xorm_test sslmode=disable" -coverprofile=coverage.txt -covermode=atomic + - go test -v -race -db="sqlite3::mysql::postgres" -conn_str="./test.db::root:@/xorm_test::dbname=xorm_test sslmode=disable" -coverprofile=coverage.txt -covermode=atomic - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./sqlite3.sh - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./mysql.sh - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./postgres.sh diff --git a/dialect_mssql.go b/dialect_mssql.go index f83cfc17..6d2291dc 100644 --- a/dialect_mssql.go +++ b/dialect_mssql.go @@ -215,7 +215,7 @@ func (db *mssql) SqlType(c *core.Column) string { var res string switch t := c.SQLType.Name; t { case core.Bool: - res = core.TinyInt + res = core.Bit if strings.EqualFold(c.Default, "true") { c.Default = "1" } else { @@ -250,6 +250,9 @@ func (db *mssql) SqlType(c *core.Column) string { case core.Uuid: res = core.Varchar c.Length = 40 + case core.TinyInt: + res = core.TinyInt + c.Length = 0 default: res = t } @@ -335,9 +338,15 @@ func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) { func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { args := []interface{}{} s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable, - replace(replace(isnull(c.text,''),'(',''),')','') as vdefault - from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id - left join sys.syscomments c on a.default_object_id=c.id + replace(replace(isnull(c.text,''),'(',''),')','') as vdefault, + ISNULL(i.is_primary_key, 0) + from sys.columns a + left join sys.types b on a.user_type_id=b.user_type_id + left join sys.syscomments c on a.default_object_id=c.id + LEFT OUTER JOIN + sys.index_columns ic ON ic.object_id = a.object_id AND ic.column_id = a.column_id + LEFT OUTER JOIN + sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id where a.object_id=object_id('` + tableName + `')` db.LogSQL(s, args) @@ -352,8 +361,8 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column for rows.Next() { var name, ctype, vdefault string var maxLen, precision, scale int - var nullable bool - err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &vdefault) + var nullable, isPK bool + err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &vdefault, &isPK) if err != nil { return nil, nil, err } @@ -363,6 +372,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column col.Name = strings.Trim(name, "` ") col.Nullable = nullable col.Default = vdefault + col.IsPrimaryKey = isPK ct := strings.ToUpper(ctype) if ct == "DECIMAL" { col.Length = precision @@ -536,7 +546,6 @@ type odbcDriver struct { func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { kv := strings.Split(dataSourceName, ";") var dbName string - for _, c := range kv { vv := strings.Split(strings.TrimSpace(c), "=") if len(vv) == 2 { diff --git a/engine.go b/engine.go index 5b087b37..b2b2f97b 100644 --- a/engine.go +++ b/engine.go @@ -40,7 +40,7 @@ type Engine struct { showExecTime bool logger core.ILogger - TZLocation *time.Location + TZLocation *time.Location // The timezone of the application DatabaseTZ *time.Location // The timezone of the database disableGlobalCache bool @@ -1498,7 +1498,6 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { results = append(results, result) if err != nil { return nil, err - //lastError = err } } } @@ -1506,49 +1505,28 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { return results, lastError } -// TZTime change one time to xorm time location -func (engine *Engine) TZTime(t time.Time) time.Time { - if !t.IsZero() { // if time is not initialized it's not suitable for Time.In() - return t.In(engine.TZLocation) - } - return t -} - -// NowTime return current time -func (engine *Engine) NowTime(sqlTypeName string) interface{} { - t := time.Now() - return engine.FormatTime(sqlTypeName, t) -} - // NowTime2 return current time func (engine *Engine) NowTime2(sqlTypeName string) (interface{}, time.Time) { t := time.Now() - return engine.FormatTime(sqlTypeName, t), t -} - -// FormatTime format time -func (engine *Engine) FormatTime(sqlTypeName string, t time.Time) (v interface{}) { - return engine.formatTime(engine.TZLocation, sqlTypeName, t) + return engine.formatTime(sqlTypeName, t.In(engine.DatabaseTZ)), t.In(engine.TZLocation) } func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{}) { - if col.DisableTimeZone { - return engine.formatTime(nil, col.SQLType.Name, t) - } else if col.TimeZone != nil { - return engine.formatTime(col.TimeZone, col.SQLType.Name, t) + if t.IsZero() { + if col.Nullable { + return nil + } + return "" } - return engine.formatTime(engine.TZLocation, col.SQLType.Name, t) + + if col.TimeZone != nil { + return engine.formatTime(col.SQLType.Name, t.In(col.TimeZone)) + } + return engine.formatTime(col.SQLType.Name, t.In(engine.DatabaseTZ)) } -func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.Time) (v interface{}) { - if engine.dialect.DBType() == core.ORACLE { - return t - } - if tz != nil { - t = t.In(tz) - } else { - t = engine.TZTime(t) - } +// formatTime format time as column type +func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{}) { switch sqlTypeName { case core.Time: s := t.Format("2006-01-02 15:04:05") //time.RFC3339 @@ -1556,18 +1534,10 @@ func (engine *Engine) formatTime(tz *time.Location, sqlTypeName string, t time.T case core.Date: v = t.Format("2006-01-02") case core.DateTime, core.TimeStamp: - if engine.dialect.DBType() == "ql" { - v = t - } else if engine.dialect.DBType() == "sqlite3" { - v = t.UTC().Format("2006-01-02 15:04:05") - } else { - v = t.Format("2006-01-02 15:04:05") - } + v = t.Format("2006-01-02 15:04:05") case core.TimeStampz: if engine.dialect.DBType() == core.MSSQL { v = t.Format("2006-01-02T15:04:05.9999999Z07:00") - } else if engine.DriverName() == "mssql" { - v = t } else { v = t.Format(time.RFC3339Nano) } diff --git a/helpers.go b/helpers.go index bac1fd9c..5e54466e 100644 --- a/helpers.go +++ b/helpers.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "time" + "github.com/go-xorm/core" ) @@ -319,175 +320,6 @@ func sliceEq(left, right []string) bool { return true } -func reflect2value(rawValue *reflect.Value) (str string, err error) { - aa := reflect.TypeOf((*rawValue).Interface()) - vv := reflect.ValueOf((*rawValue).Interface()) - switch aa.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - str = strconv.FormatInt(vv.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - str = strconv.FormatUint(vv.Uint(), 10) - case reflect.Float32, reflect.Float64: - str = strconv.FormatFloat(vv.Float(), 'f', -1, 64) - case reflect.String: - str = vv.String() - case reflect.Array, reflect.Slice: - switch aa.Elem().Kind() { - case reflect.Uint8: - data := rawValue.Interface().([]byte) - str = string(data) - default: - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - // time type - case reflect.Struct: - if aa.ConvertibleTo(core.TimeType) { - str = vv.Convert(core.TimeType).Interface().(time.Time).Format(time.RFC3339Nano) - } else { - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - case reflect.Bool: - str = strconv.FormatBool(vv.Bool()) - case reflect.Complex128, reflect.Complex64: - str = fmt.Sprintf("%v", vv.Complex()) - /* TODO: unsupported types below - case reflect.Map: - case reflect.Ptr: - case reflect.Uintptr: - case reflect.UnsafePointer: - case reflect.Chan, reflect.Func, reflect.Interface: - */ - default: - err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) - } - return -} - -func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { - var str string - str, err = reflect2value(rawValue) - if err != nil { - return - } - data = []byte(str) - return -} - -func value2String(rawValue *reflect.Value) (data string, err error) { - data, err = reflect2value(rawValue) - if err != nil { - return - } - return -} - -func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - for rows.Next() { - result, err := row2mapStr(rows, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, result) - } - - return resultsSlice, nil -} - -func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { - fields, err := rows.Columns() - if err != nil { - return nil, err - } - for rows.Next() { - result, err := row2map(rows, fields) - if err != nil { - return nil, err - } - resultsSlice = append(resultsSlice, result) - } - - return resultsSlice, nil -} - -func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { - result := make(map[string][]byte) - scanResultContainers := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer - } - if err := rows.Scan(scanResultContainers...); err != nil { - return nil, err - } - - for ii, key := range fields { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) - //if row is null then ignore - if rawValue.Interface() == nil { - //fmt.Println("ignore ...", key, rawValue) - continue - } - - if data, err := value2Bytes(&rawValue); err == nil { - result[key] = data - } else { - return nil, err // !nashtsai! REVIEW, should return err or just error log? - } - } - return result, nil -} - -func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) { - result := make(map[string]string) - scanResultContainers := make([]interface{}, len(fields)) - for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer - } - if err := rows.Scan(scanResultContainers...); err != nil { - return nil, err - } - - for ii, key := range fields { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) - //if row is null then ignore - if rawValue.Interface() == nil { - //fmt.Println("ignore ...", key, rawValue) - continue - } - - if data, err := value2String(&rawValue); err == nil { - result[key] = data - } else { - return nil, err // !nashtsai! REVIEW, should return err or just error log? - } - } - return result, nil -} - -func txQuery2(tx *core.Tx, sqlStr string, params ...interface{}) ([]map[string]string, error) { - rows, err := tx.Query(sqlStr, params...) - if err != nil { - return nil, err - } - defer rows.Close() - - return rows2Strings(rows) -} - -func query2(db *core.DB, sqlStr string, params ...interface{}) ([]map[string]string, error) { - rows, err := db.Query(sqlStr, params...) - if err != nil { - return nil, err - } - defer rows.Close() - return rows2Strings(rows) -} - func setColumnInt(bean interface{}, col *core.Column, t int64) { v, err := col.ValueOf(bean) if err != nil { diff --git a/helpler_time.go b/helpler_time.go new file mode 100644 index 00000000..f4013e27 --- /dev/null +++ b/helpler_time.go @@ -0,0 +1,21 @@ +// 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 "time" + +const ( + zeroTime0 = "0000-00-00 00:00:00" + zeroTime1 = "0001-01-01 00:00:00" +) + +func formatTime(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + +func isTimeZero(t time.Time) bool { + return t.IsZero() || formatTime(t) == zeroTime0 || + formatTime(t) == zeroTime1 +} diff --git a/session.go b/session.go index 475c769f..bbe56adc 100644 --- a/session.go +++ b/session.go @@ -344,15 +344,6 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i } }() - dbTZ := session.Engine.DatabaseTZ - if dbTZ == nil { - if session.Engine.dialect.DBType() == core.SQLITE { - dbTZ = time.UTC - } else { - dbTZ = time.Local - } - } - var tempMap = make(map[string]int) var pk core.PK for ii, key := range fields { @@ -528,11 +519,9 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i } case reflect.Struct: if fieldType.ConvertibleTo(core.TimeType) { - var tz *time.Location - if col.TimeZone == nil { - tz = session.Engine.TZLocation - } else { - tz = col.TimeZone + dbTZ := session.Engine.DatabaseTZ + if col.TimeZone != nil { + dbTZ = col.TimeZone } if rawValueType == core.TimeType { @@ -548,14 +537,13 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i t.Minute(), t.Second(), t.Nanosecond(), dbTZ) } - // !nashtsai! convert to engine location - t = t.In(tz) + t = t.In(session.Engine.TZLocation) fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) } else if rawValueType == core.IntType || rawValueType == core.Int64Type || rawValueType == core.Int32Type { hasAssigned = true - t := time.Unix(vv.Int(), 0).In(tz) + 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 { diff --git a/session_cols_test.go b/session_cols_test.go index 9ee75904..33105281 100644 --- a/session_cols_test.go +++ b/session_cols_test.go @@ -7,6 +7,7 @@ package xorm import ( "testing" + "github.com/go-xorm/core" "github.com/stretchr/testify/assert" ) @@ -26,7 +27,11 @@ func TestSetExpr(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - cnt, err = testEngine.SetExpr("show", "NOT `show`").Id(1).Update(new(User)) + var not = "NOT" + if testEngine.dialect.DBType() == core.MSSQL { + not = "~" + } + cnt, err = testEngine.SetExpr("show", not+" `show`").Id(1).Update(new(User)) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) } diff --git a/session_convert.go b/session_convert.go index 7ef57b5f..df44ace7 100644 --- a/session_convert.go +++ b/session_convert.go @@ -23,6 +23,11 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti var x time.Time var err error + var parseLoc = session.Engine.DatabaseTZ + if col.TimeZone != nil { + parseLoc = col.TimeZone + } + 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 @@ -30,33 +35,26 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti 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) + x, err = time.ParseInLocation(time.RFC3339Nano, sdata, parseLoc) 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) + x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, parseLoc) 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) + x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, parseLoc) 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) + x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, parseLoc) 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) + x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc) 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, " ") { @@ -70,7 +68,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti } st := fmt.Sprintf("2006-01-02 %v", sdata) - x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation) + x, err = time.ParseInLocation("2006-01-02 15:04:05", st, parseLoc) 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) @@ -80,7 +78,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err) return } - outTime = x + outTime = x.In(session.Engine.TZLocation) return } @@ -588,12 +586,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val 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) + tf := session.Engine.formatColTime(col, t) return tf, nil } diff --git a/session_get_test.go b/session_get_test.go index a8806ced..335d2570 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/go-xorm/core" "github.com/stretchr/testify/assert" ) @@ -119,6 +120,11 @@ func TestGetStruct(t *testing.T) { assert.NoError(t, testEngine.Sync(new(UserinfoGet))) + var err error + if testEngine.dialect.DBType() == core.MSSQL { + _, err = testEngine.Exec("SET IDENTITY_INSERT userinfo_get ON") + assert.NoError(t, err) + } cnt, err := testEngine.Insert(&UserinfoGet{Uid: 2}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) diff --git a/session_insert.go b/session_insert.go index 93168c2e..c3648171 100644 --- a/session_insert.go +++ b/session_insert.go @@ -357,10 +357,10 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { session.Engine.QuoteStr(), colPlaces) } else { - if session.Engine.dialect.DBType() == core.SQLITE || session.Engine.dialect.DBType() == core.POSTGRES { - sqlStr = fmt.Sprintf("INSERT INTO %s DEFAULT VALUES", session.Engine.Quote(session.Statement.TableName())) - } else { + if session.Engine.dialect.DBType() == core.MYSQL { sqlStr = fmt.Sprintf("INSERT INTO %s VALUES ()", session.Engine.Quote(session.Statement.TableName())) + } else { + sqlStr = fmt.Sprintf("INSERT INTO %s DEFAULT VALUES", session.Engine.Quote(session.Statement.TableName())) } } diff --git a/session_raw.go b/session_raw.go index 0f5a0a43..b44b1cd5 100644 --- a/session_raw.go +++ b/session_raw.go @@ -6,6 +6,10 @@ package xorm import ( "database/sql" + "fmt" + "reflect" + "strconv" + "time" "github.com/go-xorm/core" ) @@ -59,6 +63,60 @@ func (session *Session) innerQuery(sqlStr string, params ...interface{}) (*core. return stmt, rows, nil } +func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := row2map(rows, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, result) + } + + return resultsSlice, nil +} + +func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { + var str string + str, err = reflect2value(rawValue) + if err != nil { + return + } + data = []byte(str) + return +} + +func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { + result := make(map[string][]byte) + scanResultContainers := make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var scanResultContainer interface{} + scanResultContainers[i] = &scanResultContainer + } + if err := rows.Scan(scanResultContainers...); err != nil { + return nil, err + } + + for ii, key := range fields { + rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) + //if row is null then ignore + if rawValue.Interface() == nil { + //fmt.Println("ignore ...", key, rawValue) + continue + } + + if data, err := value2Bytes(&rawValue); err == nil { + result[key] = data + } else { + return nil, err // !nashtsai! REVIEW, should return err or just error log? + } + } + return result, nil +} + func (session *Session) innerQuery2(sqlStr string, params ...interface{}) ([]map[string][]byte, error) { _, rows, err := session.innerQuery(sqlStr, params...) if rows != nil { @@ -80,6 +138,121 @@ func (session *Session) Query(sqlStr string, paramStr ...interface{}) ([]map[str return session.query(sqlStr, paramStr...) } +func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := row2mapStr(rows, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, result) + } + + return resultsSlice, nil +} + +func reflect2value(rawValue *reflect.Value) (str string, err error) { + aa := reflect.TypeOf((*rawValue).Interface()) + vv := reflect.ValueOf((*rawValue).Interface()) + switch aa.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + str = strconv.FormatInt(vv.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + str = strconv.FormatUint(vv.Uint(), 10) + case reflect.Float32, reflect.Float64: + str = strconv.FormatFloat(vv.Float(), 'f', -1, 64) + case reflect.String: + str = vv.String() + case reflect.Array, reflect.Slice: + switch aa.Elem().Kind() { + case reflect.Uint8: + data := rawValue.Interface().([]byte) + str = string(data) + default: + err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) + } + // time type + case reflect.Struct: + if aa.ConvertibleTo(core.TimeType) { + str = vv.Convert(core.TimeType).Interface().(time.Time).Format(time.RFC3339Nano) + } else { + err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) + } + case reflect.Bool: + str = strconv.FormatBool(vv.Bool()) + case reflect.Complex128, reflect.Complex64: + str = fmt.Sprintf("%v", vv.Complex()) + /* TODO: unsupported types below + case reflect.Map: + case reflect.Ptr: + case reflect.Uintptr: + case reflect.UnsafePointer: + case reflect.Chan, reflect.Func, reflect.Interface: + */ + default: + err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) + } + return +} + +func value2String(rawValue *reflect.Value) (data string, err error) { + data, err = reflect2value(rawValue) + if err != nil { + return + } + return +} + +func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) { + result := make(map[string]string) + scanResultContainers := make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var scanResultContainer interface{} + scanResultContainers[i] = &scanResultContainer + } + if err := rows.Scan(scanResultContainers...); err != nil { + return nil, err + } + + for ii, key := range fields { + rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) + //if row is null then ignore + if rawValue.Interface() == nil { + //fmt.Println("ignore ...", key, rawValue) + continue + } + + if data, err := value2String(&rawValue); err == nil { + result[key] = data + } else { + return nil, err // !nashtsai! REVIEW, should return err or just error log? + } + } + return result, nil +} + +func txQuery2(tx *core.Tx, sqlStr string, params ...interface{}) ([]map[string]string, error) { + rows, err := tx.Query(sqlStr, params...) + if err != nil { + return nil, err + } + defer rows.Close() + + return rows2Strings(rows) +} + +func query2(db *core.DB, sqlStr string, params ...interface{}) ([]map[string]string, error) { + rows, err := db.Query(sqlStr, params...) + if err != nil { + return nil, err + } + defer rows.Close() + return rows2Strings(rows) +} + // QueryString runs a raw sql and return records as []map[string]string func (session *Session) QueryString(sqlStr string, args ...interface{}) ([]map[string]string, error) { defer session.resetStatement() diff --git a/session_update.go b/session_update.go index 1d77d294..7cb38c22 100644 --- a/session_update.go +++ b/session_update.go @@ -298,7 +298,19 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 condSQL = "WHERE " + condSQL } } else if st.Engine.dialect.DBType() == core.MSSQL { - top = fmt.Sprintf("top (%d) ", st.LimitN) + if st.OrderStr != "" && st.Engine.dialect.DBType() == core.MSSQL && + table != nil && len(table.PrimaryKeys) == 1 { + cond = builder.Expr(fmt.Sprintf("%s IN (SELECT TOP (%d) %s FROM %v%v)", + table.PrimaryKeys[0], st.LimitN, table.PrimaryKeys[0], + session.Engine.Quote(session.Statement.TableName()), condSQL), condArgs...) + + condSQL, condArgs, _ = builder.ToSQL(cond) + if len(condSQL) > 0 { + condSQL = "WHERE " + condSQL + } + } else { + top = fmt.Sprintf("TOP (%d) ", st.LimitN) + } } } diff --git a/session_update_test.go b/session_update_test.go index 46df78ca..2d41aceb 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -356,7 +356,6 @@ func TestUpdate1(t *testing.T) { And("departname = ?", ""). And("detail_id = ?", 0). And("is_man = ?", 0). - And("created IS NOT NULL"). Get(&Userinfo{}) if err != nil { t.Error(err) diff --git a/statement.go b/statement.go index 3c9ac4e9..58fa616b 100644 --- a/statement.go +++ b/statement.go @@ -376,7 +376,7 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } - val = engine.FormatTime(col.SQLType.Name, t) + val = engine.formatColTime(col, t) } else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok { val, _ = nulType.Value() } else { @@ -612,7 +612,7 @@ func buildConds(engine *Engine, table *core.Table, bean interface{}, if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } - val = engine.FormatTime(col.SQLType.Name, t) + val = engine.formatColTime(col, t) } else if _, ok := reflect.New(fieldType).Interface().(core.Conversion); ok { continue } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok { diff --git a/statement_test.go b/statement_test.go index 0cfd7f3e..594aa4f3 100644 --- a/statement_test.go +++ b/statement_test.go @@ -26,7 +26,7 @@ var colStrTests = []struct { } func TestColumnsStringGeneration(t *testing.T) { - if dbType == "postgres" { + if dbType == "postgres" || dbType == "mssql" { return } diff --git a/tag_extends_test.go b/tag_extends_test.go index c497b8d4..4b94fd4b 100644 --- a/tag_extends_test.go +++ b/tag_extends_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/go-xorm/core" "github.com/stretchr/testify/assert" ) @@ -328,6 +329,11 @@ func TestExtends2(t *testing.T) { Uid: sender.Id, ToUid: receiver.Id, } + if testEngine.dialect.DBType() == core.MSSQL { + _, err = testEngine.Exec("SET IDENTITY_INSERT message ON") + assert.NoError(t, err) + } + _, err = testEngine.Insert(&msg) if err != nil { t.Error(err) @@ -395,6 +401,10 @@ func TestExtends3(t *testing.T) { Uid: sender.Id, ToUid: receiver.Id, } + if testEngine.dialect.DBType() == core.MSSQL { + _, err = testEngine.Exec("SET IDENTITY_INSERT message ON") + assert.NoError(t, err) + } _, err = testEngine.Insert(&msg) if err != nil { t.Error(err) @@ -478,6 +488,10 @@ func TestExtends4(t *testing.T) { Content: "test", Uid: sender.Id, } + if testEngine.dialect.DBType() == core.MSSQL { + _, err = testEngine.Exec("SET IDENTITY_INSERT message ON") + assert.NoError(t, err) + } _, err = testEngine.Insert(&msg) if err != nil { t.Error(err) diff --git a/tag_id_test.go b/tag_id_test.go index 7a6d2c8a..d22cc7b1 100644 --- a/tag_id_test.go +++ b/tag_id_test.go @@ -38,7 +38,7 @@ func TestGonicMapperID(t *testing.T) { for _, tb := range tables { if tb.Name == "id_gonic_mapper" { - if len(tb.PKColumns()) != 1 && !tb.PKColumns()[0].IsPrimaryKey && !tb.PKColumns()[0].IsPrimaryKey { + if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "id" { t.Fatal(tb) } return @@ -75,7 +75,7 @@ func TestSameMapperID(t *testing.T) { for _, tb := range tables { if tb.Name == "IDSameMapper" { - if len(tb.PKColumns()) != 1 && !tb.PKColumns()[0].IsPrimaryKey && !tb.PKColumns()[0].IsPrimaryKey { + if len(tb.PKColumns()) != 1 || tb.PKColumns()[0].Name != "ID" { t.Fatal(tb) } return diff --git a/test_mssql.sh b/test_mssql.sh new file mode 100755 index 00000000..6f9cf729 --- /dev/null +++ b/test_mssql.sh @@ -0,0 +1 @@ +go test -db=mssql -conn_str="server=192.168.1.58;user id=sa;password=123456;database=xorm_test" \ No newline at end of file diff --git a/time_test.go b/time_test.go new file mode 100644 index 00000000..fe5864d4 --- /dev/null +++ b/time_test.go @@ -0,0 +1,476 @@ +// 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 ( + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTimeUserTime(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type TimeUser struct { + Id string + OperTime time.Time + } + + assertSync(t, new(TimeUser)) + + var user = TimeUser{ + Id: "lunny", + OperTime: time.Now(), + } + + fmt.Println("user", user.OperTime) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 TimeUser + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.OperTime.Unix(), user2.OperTime.Unix()) + assert.EqualValues(t, formatTime(user.OperTime), formatTime(user2.OperTime)) + fmt.Println("user2", user2.OperTime) +} + +func TestTimeUserTimeDiffLoc(t *testing.T) { + assert.NoError(t, prepareEngine()) + loc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + testEngine.TZLocation = loc + dbLoc, err := time.LoadLocation("America/New_York") + assert.NoError(t, err) + testEngine.DatabaseTZ = dbLoc + + type TimeUser struct { + Id string + OperTime time.Time + } + + assertSync(t, new(TimeUser)) + + var user = TimeUser{ + Id: "lunny", + OperTime: time.Now(), + } + + fmt.Println("user", user.OperTime) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 TimeUser + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.OperTime.Unix(), user2.OperTime.Unix()) + assert.EqualValues(t, formatTime(user.OperTime.In(loc)), formatTime(user2.OperTime)) + fmt.Println("user2", user2.OperTime) +} + +func TestTimeUserCreated(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type UserCreated struct { + Id string + CreatedAt time.Time `xorm:"created"` + } + + assertSync(t, new(UserCreated)) + + var user = UserCreated{ + Id: "lunny", + } + + fmt.Println("user", user.CreatedAt) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 UserCreated + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + fmt.Println("user2", user2.CreatedAt) +} + +func TestTimeUserCreatedDiffLoc(t *testing.T) { + assert.NoError(t, prepareEngine()) + loc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + testEngine.TZLocation = loc + dbLoc, err := time.LoadLocation("America/New_York") + assert.NoError(t, err) + testEngine.DatabaseTZ = dbLoc + + type UserCreated struct { + Id string + CreatedAt time.Time `xorm:"created"` + } + + assertSync(t, new(UserCreated)) + + var user = UserCreated{ + Id: "lunny", + } + + fmt.Println("user", user.CreatedAt) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 UserCreated + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + fmt.Println("user2", user2.CreatedAt) +} + +func TestTimeUserUpdated(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type UserUpdated struct { + Id string + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + } + + assertSync(t, new(UserUpdated)) + + var user = UserUpdated{ + Id: "lunny", + } + + fmt.Println("user", user.CreatedAt, user.UpdatedAt) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 UserUpdated + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) + fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt) + + var user3 = UserUpdated{ + Id: "lunny2", + } + + cnt, err = testEngine.Update(&user3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.True(t, user.UpdatedAt.Unix() <= user3.UpdatedAt.Unix()) + + var user4 UserUpdated + has, err = testEngine.Get(&user4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user4.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user4.CreatedAt)) + assert.EqualValues(t, user3.UpdatedAt.Unix(), user4.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user3.UpdatedAt), formatTime(user4.UpdatedAt)) + fmt.Println("user3", user.CreatedAt, user4.UpdatedAt) +} + +func TestTimeUserUpdatedDiffLoc(t *testing.T) { + assert.NoError(t, prepareEngine()) + loc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + testEngine.TZLocation = loc + dbLoc, err := time.LoadLocation("America/New_York") + assert.NoError(t, err) + testEngine.DatabaseTZ = dbLoc + + type UserUpdated struct { + Id string + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + } + + assertSync(t, new(UserUpdated)) + + var user = UserUpdated{ + Id: "lunny", + } + + fmt.Println("user", user.CreatedAt, user.UpdatedAt) + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + var user2 UserUpdated + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) + fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt) + + var user3 = UserUpdated{ + Id: "lunny2", + } + + cnt, err = testEngine.Update(&user3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.True(t, user.UpdatedAt.Unix() <= user3.UpdatedAt.Unix()) + + var user4 UserUpdated + has, err = testEngine.Get(&user4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user4.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user4.CreatedAt)) + assert.EqualValues(t, user3.UpdatedAt.Unix(), user4.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user3.UpdatedAt), formatTime(user4.UpdatedAt)) + fmt.Println("user3", user.CreatedAt, user4.UpdatedAt) +} + +func TestTimeUserDeleted(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type UserDeleted struct { + Id string + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + DeletedAt time.Time `xorm:"deleted"` + } + + assertSync(t, new(UserDeleted)) + + var user = UserDeleted{ + Id: "lunny", + } + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + fmt.Println("user", user.CreatedAt, user.UpdatedAt, user.DeletedAt) + + var user2 UserDeleted + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) + assert.True(t, isTimeZero(user2.DeletedAt)) + fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) + + var user3 UserDeleted + cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.True(t, !isTimeZero(user3.DeletedAt)) + + var user4 UserDeleted + has, err = testEngine.Unscoped().Get(&user4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user3.DeletedAt.Unix(), user4.DeletedAt.Unix()) + assert.EqualValues(t, formatTime(user3.DeletedAt), formatTime(user4.DeletedAt)) + fmt.Println("user3", user3.DeletedAt, user4.DeletedAt) +} + +func TestTimeUserDeletedDiffLoc(t *testing.T) { + assert.NoError(t, prepareEngine()) + loc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + testEngine.TZLocation = loc + dbLoc, err := time.LoadLocation("America/New_York") + assert.NoError(t, err) + testEngine.DatabaseTZ = dbLoc + + type UserDeleted struct { + Id string + CreatedAt time.Time `xorm:"created"` + UpdatedAt time.Time `xorm:"updated"` + DeletedAt time.Time `xorm:"deleted"` + } + + assertSync(t, new(UserDeleted)) + + var user = UserDeleted{ + Id: "lunny", + } + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + fmt.Println("user", user.CreatedAt, user.UpdatedAt, user.DeletedAt) + + var user2 UserDeleted + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(user.CreatedAt), formatTime(user2.CreatedAt)) + assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(user.UpdatedAt), formatTime(user2.UpdatedAt)) + assert.True(t, isTimeZero(user2.DeletedAt)) + fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) + + var user3 UserDeleted + cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.True(t, !isTimeZero(user3.DeletedAt)) + + var user4 UserDeleted + has, err = testEngine.Unscoped().Get(&user4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user3.DeletedAt.Unix(), user4.DeletedAt.Unix()) + assert.EqualValues(t, formatTime(user3.DeletedAt), formatTime(user4.DeletedAt)) + fmt.Println("user3", user3.DeletedAt, user4.DeletedAt) +} + +type JsonDate time.Time + +func (j JsonDate) MarshalJSON() ([]byte, error) { + if time.Time(j).IsZero() { + return []byte(`""`), nil + } + return []byte(`"` + time.Time(j).Format("2006-01-02 15:04:05") + `"`), nil +} + +func (j *JsonDate) UnmarshalJSON(value []byte) error { + var v = strings.TrimSpace(strings.Trim(string(value), "\"")) + + t, err := time.ParseInLocation("2006-01-02 15:04:05", v, time.Local) + if err != nil { + return err + } + *j = JsonDate(t) + return nil +} + +func (j *JsonDate) Unix() int64 { + return (*time.Time)(j).Unix() +} + +func TestCustomTimeUserDeleted(t *testing.T) { + assert.NoError(t, prepareEngine()) + + type UserDeleted struct { + Id string + CreatedAt JsonDate `xorm:"created"` + UpdatedAt JsonDate `xorm:"updated"` + DeletedAt JsonDate `xorm:"deleted"` + } + + assertSync(t, new(UserDeleted)) + + var user = UserDeleted{ + Id: "lunny", + } + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + fmt.Println("user", user.CreatedAt, user.UpdatedAt, user.DeletedAt) + + var user2 UserDeleted + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(time.Time(user.CreatedAt)), formatTime(time.Time(user2.CreatedAt))) + assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(time.Time(user.UpdatedAt)), formatTime(time.Time(user2.UpdatedAt))) + assert.True(t, isTimeZero(time.Time(user2.DeletedAt))) + fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) + + var user3 UserDeleted + cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.True(t, !isTimeZero(time.Time(user3.DeletedAt))) + + var user4 UserDeleted + has, err = testEngine.Unscoped().Get(&user4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user3.DeletedAt.Unix(), user4.DeletedAt.Unix()) + assert.EqualValues(t, formatTime(time.Time(user3.DeletedAt)), formatTime(time.Time(user4.DeletedAt))) + fmt.Println("user3", user3.DeletedAt, user4.DeletedAt) +} + +func TestCustomTimeUserDeletedDiffLoc(t *testing.T) { + assert.NoError(t, prepareEngine()) + loc, err := time.LoadLocation("Asia/Shanghai") + assert.NoError(t, err) + testEngine.TZLocation = loc + dbLoc, err := time.LoadLocation("America/New_York") + assert.NoError(t, err) + testEngine.DatabaseTZ = dbLoc + + type UserDeleted struct { + Id string + CreatedAt JsonDate `xorm:"created"` + UpdatedAt JsonDate `xorm:"updated"` + DeletedAt JsonDate `xorm:"deleted"` + } + + assertSync(t, new(UserDeleted)) + + var user = UserDeleted{ + Id: "lunny", + } + + cnt, err := testEngine.Insert(&user) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + fmt.Println("user", user.CreatedAt, user.UpdatedAt, user.DeletedAt) + + var user2 UserDeleted + has, err := testEngine.Get(&user2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user.CreatedAt.Unix(), user2.CreatedAt.Unix()) + assert.EqualValues(t, formatTime(time.Time(user.CreatedAt)), formatTime(time.Time(user2.CreatedAt))) + assert.EqualValues(t, user.UpdatedAt.Unix(), user2.UpdatedAt.Unix()) + assert.EqualValues(t, formatTime(time.Time(user.UpdatedAt)), formatTime(time.Time(user2.UpdatedAt))) + assert.True(t, isTimeZero(time.Time(user2.DeletedAt))) + fmt.Println("user2", user2.CreatedAt, user2.UpdatedAt, user2.DeletedAt) + + var user3 UserDeleted + cnt, err = testEngine.Where("id = ?", "lunny").Delete(&user3) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.True(t, !isTimeZero(time.Time(user3.DeletedAt))) + + var user4 UserDeleted + has, err = testEngine.Unscoped().Get(&user4) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, user3.DeletedAt.Unix(), user4.DeletedAt.Unix()) + assert.EqualValues(t, formatTime(time.Time(user3.DeletedAt)), formatTime(time.Time(user4.DeletedAt))) + fmt.Println("user3", user3.DeletedAt, user4.DeletedAt) +} diff --git a/xorm.go b/xorm.go index 63cb15a6..3e1e2e50 100644 --- a/xorm.go +++ b/xorm.go @@ -89,6 +89,12 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { tagHandlers: defaultTagHandlers, } + if uri.DbType == core.SQLITE { + engine.DatabaseTZ = time.UTC + } else { + engine.DatabaseTZ = time.Local + } + logger := NewSimpleLogger(os.Stdout) logger.SetLevel(core.LOG_INFO) engine.SetLogger(logger) diff --git a/xorm_test.go b/xorm_test.go index 66868b65..2e722f86 100644 --- a/xorm_test.go +++ b/xorm_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + _ "github.com/denisenkom/go-mssqldb" _ "github.com/go-sql-driver/mysql" "github.com/go-xorm/core" _ "github.com/lib/pq" @@ -45,7 +46,10 @@ func createEngine(dbType, connStr string) error { for _, table := range tables { tableNames = append(tableNames, table.Name) } - return testEngine.DropTables(tableNames...) + if err = testEngine.DropTables(tableNames...); err != nil { + return err + } + return nil } func prepareEngine() error { @@ -70,8 +74,8 @@ func TestMain(m *testing.M) { connString = *ptrConnStr } - dbs := strings.Split(*db, ";") - conns := strings.Split(connString, ";") + dbs := strings.Split(*db, "::") + conns := strings.Split(connString, "::") var res int for i := 0; i < len(dbs); i++ {