From 94882e39df20931cf6d96e7c0c5433e31db9d6ce Mon Sep 17 00:00:00 2001 From: CyJaySong Date: Mon, 7 Aug 2023 04:28:55 +0000 Subject: [PATCH] Fix deleted tag attribute zeroTime is not DatabaseTZ (#2299) Co-authored-by: CyJay Co-authored-by: Lunny Xiao Reviewed-on: https://gitea.com/xorm/xorm/pulls/2299 Reviewed-by: Lunny Xiao Co-authored-by: CyJaySong Co-committed-by: CyJaySong --- convert/time.go | 38 +++++++++++++++- dialects/mssql.go | 6 +-- internal/statements/statement.go | 5 +-- session_insert.go | 3 +- tests/session_insert_test.go | 77 ++++++++++++++++++++++++++++++++ tests/tags_test.go | 8 ++-- 6 files changed, 122 insertions(+), 15 deletions(-) diff --git a/convert/time.go b/convert/time.go index c923e955..d90dc428 100644 --- a/convert/time.go +++ b/convert/time.go @@ -28,14 +28,19 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t dt = dt.In(convertedLocation) return &dt, nil } else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' { + if strings.HasPrefix(s, "0000-00-00T00:00:00") || strings.HasPrefix(s, "0001-01-01T00:00:00") { + return &time.Time{}, nil + } dt, err := time.ParseInLocation("2006-01-02T15:04:05", s[:19], originalLocation) if err != nil { return nil, err } dt = dt.In(convertedLocation) - dt.IsZero() return &dt, nil } else if len(s) == 25 && s[10] == 'T' && s[19] == '+' && s[22] == ':' { + if strings.HasPrefix(s, "0000-00-00T00:00:00") || strings.HasPrefix(s, "0001-01-01T00:00:00") { + return &time.Time{}, nil + } dt, err := time.Parse(time.RFC3339, s) if err != nil { return nil, err @@ -43,6 +48,10 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t dt = dt.In(convertedLocation) return &dt, nil } else if len(s) >= 21 && s[10] == 'T' && s[19] == '.' { + if strings.HasPrefix(s, "0000-00-00T00:00:00."+strings.Repeat("0", len(s)-20)) || + strings.HasPrefix(s, "0001-01-01T00:00:00."+strings.Repeat("0", len(s)-20)) { + return &time.Time{}, nil + } dt, err := time.Parse(time.RFC3339Nano, s) if err != nil { return nil, err @@ -50,6 +59,10 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t dt = dt.In(convertedLocation) return &dt, nil } else if len(s) >= 21 && s[19] == '.' { + if strings.HasPrefix(s, "0000-00-00T00:00:00."+strings.Repeat("0", len(s)-20)) || + strings.HasPrefix(s, "0001-01-01T00:00:00."+strings.Repeat("0", len(s)-20)) { + return &time.Time{}, nil + } layout := "2006-01-02 15:04:05." + strings.Repeat("0", len(s)-20) dt, err := time.ParseInLocation(layout, s, originalLocation) if err != nil { @@ -68,11 +81,11 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t dt = dt.In(convertedLocation) return &dt, nil } else if len(s) == 8 && s[2] == ':' && s[5] == ':' { - currentDate := time.Now() dt, err := time.ParseInLocation("15:04:05", s, originalLocation) if err != nil { return nil, err } + currentDate := time.Now() // add current date for correct time locations dt = dt.AddDate(currentDate.Year(), int(currentDate.Month()), currentDate.Day()) dt = dt.In(convertedLocation) @@ -82,6 +95,9 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t } else { i, err := strconv.ParseInt(s, 10, 64) if err == nil { + if i == 0 { + return &time.Time{}, nil + } tm := time.Unix(i, 0).In(convertedLocation) return &tm, nil } @@ -108,6 +124,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time. if !t.Valid { return nil, nil } + if utils.IsTimeZero(t.Time) { + return &time.Time{}, nil + } z, _ := t.Time.Zone() if len(z) == 0 || t.Time.Year() == 0 || t.Time.Location().String() != dbLoc.String() { tm := time.Date(t.Time.Year(), t.Time.Month(), t.Time.Day(), t.Time.Hour(), @@ -117,6 +136,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time. tm := t.Time.In(uiLoc) return &tm, nil case *time.Time: + if utils.IsTimeZero(*t) { + return &time.Time{}, nil + } z, _ := t.Zone() if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() { tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), @@ -126,6 +148,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time. tm := t.In(uiLoc) return &tm, nil case time.Time: + if utils.IsTimeZero(t) { + return &time.Time{}, nil + } z, _ := t.Zone() if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() { tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), @@ -135,12 +160,21 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time. tm := t.In(uiLoc) return &tm, nil case int: + if t == 0 { + return &time.Time{}, nil + } tm := time.Unix(int64(t), 0).In(uiLoc) return &tm, nil case int64: + if t == 0 { + return &time.Time{}, nil + } tm := time.Unix(t, 0).In(uiLoc) return &tm, nil case *sql.NullInt64: + if t.Int64 == 0 { + return &time.Time{}, nil + } tm := time.Unix(t.Int64, 0).In(uiLoc) return &tm, nil } diff --git a/dialects/mssql.go b/dialects/mssql.go index dcac9c3f..2c64e637 100644 --- a/dialects/mssql.go +++ b/dialects/mssql.go @@ -320,11 +320,7 @@ func (db *mssql) SQLType(c *schemas.Column) string { res += "(MAX)" } case schemas.TimeStamp, schemas.DateTime: - if c.Length > 3 { - res = "DATETIME2" - } else { - return schemas.DateTime - } + return "DATETIME2" case schemas.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 diff --git a/internal/statements/statement.go b/internal/statements/statement.go index 017f40a5..7ad735f5 100644 --- a/internal/statements/statement.go +++ b/internal/statements/statement.go @@ -690,10 +690,7 @@ func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond { if col.SQLType.IsNumeric() { cond = builder.Eq{colName: 0} } else { - // FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value. - if statement.dialect.URI().DBType != schemas.MSSQL { - cond = builder.Eq{colName: utils.ZeroTime1} - } + cond = builder.Eq{colName: utils.ZeroTime1} } if col.Nullable { diff --git a/session_insert.go b/session_insert.go index 7003e0f7..7cc15241 100644 --- a/session_insert.go +++ b/session_insert.go @@ -471,7 +471,8 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac } if col.IsDeleted { - arg, err := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, time.Time{}) + zeroTime := time.Date(1, 1, 1, 0, 0, 0, 0, session.engine.DatabaseTZ) + arg, err := dialects.FormatColumnTime(session.engine.dialect, session.engine.DatabaseTZ, col, zeroTime) if err != nil { return nil, nil, err } diff --git a/tests/session_insert_test.go b/tests/session_insert_test.go index e45e6e54..dd3e8405 100644 --- a/tests/session_insert_test.go +++ b/tests/session_insert_test.go @@ -1209,3 +1209,80 @@ func TestInsertMultipleMap(t *testing.T) { Name: "xiaolunwen", }, res[1]) } + +func TestInsertNotDeleted(t *testing.T) { + assert.NoError(t, PrepareEngine()) + zeroTime := time.Date(1, 1, 1, 0, 0, 0, 0, testEngine.GetTZDatabase()) + type TestInsertNotDeletedStructNotRight struct { + ID uint64 `xorm:"'ID' pk autoincr"` + DeletedAt time.Time `xorm:"'DELETED_AT' deleted notnull"` + } + // notnull tag will be ignored + err := testEngine.Sync(new(TestInsertNotDeletedStructNotRight)) + assert.NoError(t, err) + + type TestInsertNotDeletedStruct struct { + ID uint64 `xorm:"'ID' pk autoincr"` + DeletedAt time.Time `xorm:"'DELETED_AT' deleted"` + } + + assert.NoError(t, testEngine.Sync(new(TestInsertNotDeletedStruct))) + + var v1 TestInsertNotDeletedStructNotRight + _, err = testEngine.Insert(&v1) + assert.NoError(t, err) + + var v2 TestInsertNotDeletedStructNotRight + has, err := testEngine.Get(&v2) + assert.NoError(t, err) + assert.True(t, has) + assert.Equal(t, v2.DeletedAt.In(testEngine.GetTZDatabase()).Format("2006-01-02 15:04:05"), zeroTime.Format("2006-01-02 15:04:05")) + + var v3 TestInsertNotDeletedStruct + _, err = testEngine.Insert(&v3) + assert.NoError(t, err) + + var v4 TestInsertNotDeletedStruct + has, err = testEngine.Get(&v4) + assert.NoError(t, err) + assert.True(t, has) + assert.Equal(t, v4.DeletedAt.In(testEngine.GetTZDatabase()).Format("2006-01-02 15:04:05"), zeroTime.Format("2006-01-02 15:04:05")) +} + +type MyAutoTimeFields1 struct { + Id int64 + Dt time.Time `xorm:"created DATETIME"` +} + +func (MyAutoTimeFields1) TableName() string { + return "my_auto_time_fields" +} + +type MyAutoTimeFields2 struct { + Id int64 + Dt time.Time `xorm:"created"` +} + +func (MyAutoTimeFields2) TableName() string { + return "my_auto_time_fields" +} + +func TestAutoTimeFields(t *testing.T) { + assert.NoError(t, PrepareEngine()) + + assertSync(t, new(MyAutoTimeFields1)) + + _, err := testEngine.Insert(&MyAutoTimeFields1{}) + assert.NoError(t, err) + + var res []MyAutoTimeFields2 + assert.NoError(t, testEngine.Find(&res)) + assert.EqualValues(t, 1, len(res)) + + _, err = testEngine.Insert(&MyAutoTimeFields2{}) + assert.NoError(t, err) + + res = []MyAutoTimeFields2{} + assert.NoError(t, testEngine.Find(&res)) + assert.EqualValues(t, 2, len(res)) +} diff --git a/tests/tags_test.go b/tests/tags_test.go index f8448b4a..14803462 100644 --- a/tests/tags_test.go +++ b/tests/tags_test.go @@ -7,11 +7,11 @@ package tests import ( "fmt" "sort" - "strings" "testing" "time" "github.com/stretchr/testify/assert" + "xorm.io/xorm/convert" "xorm.io/xorm/internal/utils" "xorm.io/xorm/names" "xorm.io/xorm/schemas" @@ -1201,8 +1201,10 @@ 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"), - strings.ReplaceAll(strings.ReplaceAll(tm, "T", " "), "Z", "")) + + tmTime, err := convert.String2Time(tm, time.UTC, time.UTC) + assert.NoError(t, err) + assert.EqualValues(t, s.Created.UTC().Format("2006-01-02 15:04:05"), tmTime.Format("2006-01-02 15:04:05")) } func TestTagAutoIncr(t *testing.T) {