Fix timesatmp #2021

Merged
lunny merged 7 commits from lunny/timestamp into master 2021-08-04 08:12:10 +00:00
11 changed files with 165 additions and 69 deletions

View File

@ -313,8 +313,12 @@ func (db *mssql) SQLType(c *schemas.Column) string {
if c.Length == 0 { if c.Length == 0 {
res += "(MAX)" res += "(MAX)"
} }
case schemas.TimeStamp: case schemas.TimeStamp, schemas.DateTime:
res = schemas.DateTime if c.Length > 3 {
res = "DATETIME2"
} else {
return schemas.DateTime
}
case schemas.TimeStampz: case schemas.TimeStampz:
res = "DATETIMEOFFSET" res = "DATETIMEOFFSET"
c.Length = 7 c.Length = 7
@ -357,7 +361,7 @@ func (db *mssql) SQLType(c *schemas.Column) string {
res = t res = t
} }
if res == schemas.Int || res == schemas.Bit || res == schemas.DateTime { if res == schemas.Int || res == schemas.Bit {
return res return res
} }
@ -498,6 +502,12 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
col.Length /= 2 col.Length /= 2
col.Length2 /= 2 col.Length2 /= 2
} }
case "DATETIME2":
col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 7, DefaultLength2: 0}
col.Length = scale
case "DATETIME":
col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 3, DefaultLength2: 0}
col.Length = scale
case "IMAGE": case "IMAGE":
col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0} col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0}
case "NCHAR": case "NCHAR":

View File

@ -5,50 +5,57 @@
package dialects package dialects
import ( import (
"strings"
"time" "time"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
) )
// FormatTime format time as column type
func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{}) {
switch sqlTypeName {
case schemas.Time:
s := t.Format("2006-01-02 15:04:05") // time.RFC3339
v = s[11:19]
case schemas.Date:
v = t.Format("2006-01-02")
case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar.
if dialect.URI().DBType == schemas.ORACLE {
v = t
} else {
v = t.Format("2006-01-02 15:04:05")
}
case schemas.TimeStampz:
if dialect.URI().DBType == schemas.MSSQL {
v = t.Format("2006-01-02T15:04:05.9999999Z07:00")
} else {
v = t.Format(time.RFC3339Nano)
}
case schemas.BigInt, schemas.Int:
v = t.Unix()
default:
v = t
}
return
}
// FormatColumnTime format column time // FormatColumnTime format column time
func FormatColumnTime(dialect Dialect, defaultTimeZone *time.Location, col *schemas.Column, t time.Time) (v interface{}) { func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.Column, t time.Time) (interface{}, error) {
if t.IsZero() { if t.IsZero() {
if col.Nullable { if col.Nullable {
return nil return nil, nil
}
if col.SQLType.IsNumeric() {
return 0, nil
} }
return ""
} }
var tmZone = dbLocation
if col.TimeZone != nil { if col.TimeZone != nil {
return FormatTime(dialect, col.SQLType.Name, t.In(col.TimeZone)) tmZone = col.TimeZone
}
t = t.In(tmZone)
switch col.SQLType.Name {
case schemas.Date:
return t.Format("2006-01-02"), nil
case schemas.Time:
var layout = "15:04:05"
if col.Length > 0 {
layout += "." + strings.Repeat("0", col.Length)
}
return t.Format(layout), nil
case schemas.DateTime, schemas.TimeStamp:
var layout = "2006-01-02 15:04:05"
if col.Length > 0 {
layout += "." + strings.Repeat("0", col.Length)
}
return t.Format(layout), nil
case schemas.Varchar:
return t.Format("2006-01-02 15:04:05"), nil
case schemas.TimeStampz:
if dialect.URI().DBType == schemas.MSSQL {
return t.Format("2006-01-02T15:04:05.9999999Z07:00"), nil
} else {
return t.Format(time.RFC3339Nano), nil
}
case schemas.BigInt, schemas.Int:
return t.Unix(), nil
default:
return t, nil
} }
return FormatTime(dialect, col.SQLType.Name, t.In(defaultTimeZone))
} }

View File

@ -1226,13 +1226,13 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) {
} }
// nowTime return current time // nowTime return current time
func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) { func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time, error) {
t := time.Now() t := time.Now()
var tz = engine.DatabaseTZ result, err := dialects.FormatColumnTime(engine.dialect, engine.DatabaseTZ, col, t)
if !col.DisableTimeZone && col.TimeZone != nil { if err != nil {
tz = col.TimeZone return nil, time.Time{}, err
} }
return dialects.FormatTime(engine.dialect, col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation) return result, t.In(engine.TZLocation), nil
} }
// GetColumnMapper returns the column name mapper // GetColumnMapper returns the column name mapper

View File

@ -15,8 +15,12 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func formatTime(t time.Time) string { func formatTime(t time.Time, scales ...int) string {
return t.Format("2006-01-02 15:04:05") var layout = "2006-01-02 15:04:05"
if len(scales) > 0 && scales[0] > 0 {
layout += "." + strings.Repeat("0", scales[0])
}
return t.Format(layout)
} }
func TestTimeUserTime(t *testing.T) { func TestTimeUserTime(t *testing.T) {
@ -565,3 +569,53 @@ func TestDeletedInt64(t *testing.T) {
assert.True(t, has) assert.True(t, has)
assert.EqualValues(t, d1, d4) assert.EqualValues(t, d1, d4)
} }
func TestTimestamp(t *testing.T) {
{
assert.NoError(t, PrepareEngine())
type TimestampStruct struct {
Id int64
InsertTime time.Time `xorm:"DATETIME(6)"`
}
assertSync(t, new(TimestampStruct))
var d1 = TimestampStruct{
InsertTime: time.Now(),
}
cnt, err := testEngine.Insert(&d1)
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
var d2 TimestampStruct
has, err := testEngine.ID(d1.Id).Get(&d2)
assert.NoError(t, err)
assert.True(t, has)
assert.EqualValues(t, formatTime(d1.InsertTime, 6), formatTime(d2.InsertTime, 6))
}
/*{
assert.NoError(t, PrepareEngine())
type TimestampzStruct struct {
Id int64
InsertTime time.Time `xorm:"TIMESTAMPZ"`
}
assertSync(t, new(TimestampzStruct))
var d3 = TimestampzStruct{
InsertTime: time.Now(),
}
cnt, err := testEngine.Insert(&d3)
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
var d4 TimestampzStruct
has, err := testEngine.ID(d3.Id).Get(&d4)
assert.NoError(t, err)
assert.True(t, has)
assert.EqualValues(t, formatTime(d3.InsertTime, 6), formatTime(d4.InsertTime, 6))
}*/
}

View File

@ -8,6 +8,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"time" "time"
"xorm.io/xorm/internal/utils" "xorm.io/xorm/internal/utils"
@ -39,6 +40,14 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
} }
dt = dt.In(convertedLocation) dt = dt.In(convertedLocation)
return &dt, nil return &dt, nil
} else if len(s) >= 21 && s[19] == '.' {
var layout = "2006-01-02 15:04:05." + strings.Repeat("0", len(s)-20)
dt, err := time.ParseInLocation(layout, s, originalLocation)
if err != nil {
return nil, err
}
dt = dt.In(convertedLocation)
return &dt, nil
} else { } else {
i, err := strconv.ParseInt(s, 10, 64) i, err := strconv.ParseInt(s, 10, 64)
if err == nil { if err == nil {

View File

@ -734,7 +734,11 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
return nil, false, nil return nil, false, nil
} }
return dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t), true, nil res, err := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
if err != nil {
return nil, false, err
}
return res, true, nil
} else if fieldType.ConvertibleTo(schemas.BigFloatType) { } else if fieldType.ConvertibleTo(schemas.BigFloatType) {
t := fieldValue.Convert(schemas.BigFloatType).Interface().(big.Float) t := fieldValue.Convert(schemas.BigFloatType).Interface().(big.Float)
v := t.String() v := t.String()

View File

@ -208,7 +208,10 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
continue continue
} }
val = dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) val, err = dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
if err != nil {
return nil, nil, err
}
} else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok { } else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok {
val, _ = nulType.Value() val, _ = nulType.Value()
if val == nil && !requiredField { if val == nil && !requiredField {

View File

@ -87,8 +87,8 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl
case reflect.Struct: case reflect.Struct:
if fieldType.ConvertibleTo(schemas.TimeType) { if fieldType.ConvertibleTo(schemas.TimeType) {
t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time)
tf := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) tf, err := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
return tf, nil return tf, err
} else if fieldType.ConvertibleTo(nullFloatType) { } else if fieldType.ConvertibleTo(nullFloatType) {
t := fieldValue.Convert(nullFloatType).Interface().(sql.NullFloat64) t := fieldValue.Convert(nullFloatType).Interface().(sql.NullFloat64)
if !t.Valid { if !t.Valid {

View File

@ -212,7 +212,10 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) {
paramsLen := len(condArgs) paramsLen := len(condArgs)
copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1]) copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1])
val, t := session.engine.nowTime(deletedColumn) val, t, err := session.engine.nowTime(deletedColumn)
if err != nil {
return 0, err
}
condArgs[0] = val condArgs[0] = val
var colName = deletedColumn.Name var colName = deletedColumn.Name

View File

@ -12,6 +12,7 @@ import (
"strings" "strings"
"time" "time"
"xorm.io/xorm/dialects"
"xorm.io/xorm/internal/convert" "xorm.io/xorm/internal/convert"
"xorm.io/xorm/internal/utils" "xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
@ -137,7 +138,10 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e
continue continue
} }
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime {
val, t := session.engine.nowTime(col) val, t, err := session.engine.nowTime(col)
if err != nil {
return 0, err
}
args = append(args, val) args = append(args, val)
var colName = col.Name var colName = col.Name
@ -427,29 +431,12 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac
if col.MapType == schemas.ONLYFROMDB { if col.MapType == schemas.ONLYFROMDB {
continue continue
} }
if col.IsDeleted {
colNames = append(colNames, col.Name)
if !col.Nullable {
if col.SQLType.IsNumeric() {
args = append(args, 0)
} else {
args = append(args, time.Time{}.Format("2006-01-02 15:04:05"))
}
} else {
args = append(args, nil)
}
continue
}
if session.statement.OmitColumnMap.Contain(col.Name) { if session.statement.OmitColumnMap.Contain(col.Name) {
continue continue
} }
if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) { if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) {
continue continue
} }
if session.statement.IncrColumns.IsColExist(col.Name) { if session.statement.IncrColumns.IsColExist(col.Name) {
continue continue
} else if session.statement.DecrColumns.IsColExist(col.Name) { } else if session.statement.DecrColumns.IsColExist(col.Name) {
@ -458,6 +445,16 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac
continue continue
} }
if col.IsDeleted {
arg, err := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, time.Time{})
if err != nil {
return nil, nil, err
}
args = append(args, arg)
colNames = append(colNames, col.Name)
continue
}
fieldValuePtr, err := col.ValueOf(bean) fieldValuePtr, err := col.ValueOf(bean)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -478,7 +475,10 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ { if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time // if time is non-empty, then set to auto time
val, t := session.engine.nowTime(col) val, t, err := session.engine.nowTime(col)
if err != nil {
return nil, nil, err
}
args = append(args, val) args = append(args, val)
var colName = col.Name var colName = col.Name

View File

@ -215,7 +215,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
!session.statement.OmitColumnMap.Contain(table.Updated) { !session.statement.OmitColumnMap.Contain(table.Updated) {
colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?") colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
col := table.UpdatedColumn() col := table.UpdatedColumn()
val, t := session.engine.nowTime(col) val, t, err := session.engine.nowTime(col)
if err != nil {
return 0, err
}
if session.engine.dialect.URI().DBType == schemas.ORACLE { if session.engine.dialect.URI().DBType == schemas.ORACLE {
args = append(args, t) args = append(args, t)
} else { } else {
@ -521,7 +524,10 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac
if col.IsUpdated && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ { if col.IsUpdated && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// if time is non-empty, then set to auto time // if time is non-empty, then set to auto time
val, t := session.engine.nowTime(col) val, t, err := session.engine.nowTime(col)
if err != nil {
return nil, nil, err
}
args = append(args, val) args = append(args, val)
var colName = col.Name var colName = col.Name