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

View File

@ -5,50 +5,57 @@
package dialects
import (
"strings"
"time"
"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
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 col.Nullable {
return nil
return nil, nil
}
if col.SQLType.IsNumeric() {
return 0, nil
}
return ""
}
var tmZone = dbLocation
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
func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) {
func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time, error) {
t := time.Now()
var tz = engine.DatabaseTZ
if !col.DisableTimeZone && col.TimeZone != nil {
tz = col.TimeZone
result, err := dialects.FormatColumnTime(engine.dialect, engine.DatabaseTZ, col, t)
if err != nil {
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

View File

@ -15,8 +15,12 @@ import (
"github.com/stretchr/testify/assert"
)
func formatTime(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
func formatTime(t time.Time, scales ...int) string {
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) {
@ -565,3 +569,53 @@ func TestDeletedInt64(t *testing.T) {
assert.True(t, has)
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"
"fmt"
"strconv"
"strings"
"time"
"xorm.io/xorm/internal/utils"
@ -39,6 +40,14 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
}
dt = dt.In(convertedLocation)
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 {
i, err := strconv.ParseInt(s, 10, 64)
if err == nil {

View File

@ -734,7 +734,11 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
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) {
t := fieldValue.Convert(schemas.BigFloatType).Interface().(big.Float)
v := t.String()

View File

@ -208,7 +208,10 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
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 {
val, _ = nulType.Value()
if val == nil && !requiredField {

View File

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

View File

@ -212,7 +212,10 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) {
paramsLen := len(condArgs)
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
var colName = deletedColumn.Name

View File

@ -12,6 +12,7 @@ import (
"strings"
"time"
"xorm.io/xorm/dialects"
"xorm.io/xorm/internal/convert"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
@ -137,7 +138,10 @@ func (session *Session) insertMultipleStruct(rowsSlicePtr interface{}) (int64, e
continue
}
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)
var colName = col.Name
@ -427,29 +431,12 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac
if col.MapType == schemas.ONLYFROMDB {
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) {
continue
}
if len(session.statement.ColumnMap) > 0 && !session.statement.ColumnMap.Contain(col.Name) {
continue
}
if session.statement.IncrColumns.IsColExist(col.Name) {
continue
} else if session.statement.DecrColumns.IsColExist(col.Name) {
@ -458,6 +445,16 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac
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)
if err != nil {
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 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)
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) {
colNames = append(colNames, session.engine.Quote(table.Updated)+" = ?")
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 {
args = append(args, t)
} else {
@ -521,7 +524,10 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac
if col.IsUpdated && session.statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ {
// 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)
var colName = col.Name