add AutoTimer interface

This commit is contained in:
venjiang 2020-05-14 11:40:54 +08:00
parent 55594d1dbe
commit 71bd7f9b31
7 changed files with 216 additions and 0 deletions

7
convert/autotimer.go Normal file
View File

@ -0,0 +1,7 @@
package convert
import "time"
type AutoTimer interface {
AutoTime(t time.Time) (interface{}, error)
}

View File

@ -0,0 +1,128 @@
// 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 integrations
import (
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
type Timestamp int64
func FromTimeToTimestamp(t time.Time) Timestamp {
return Timestamp(int64(t.UnixNano()) / 1e6)
}
func (t Timestamp) String() string {
return strconv.FormatInt(int64(t), 10)
}
func (t *Timestamp) Time() time.Time {
return time.Unix(int64(*t)/1e3, (int64(*t)%1e3)*1e6)
}
func (t *Timestamp) FromDB(b []byte) error {
var err error
var value int64
value, err = strconv.ParseInt(string(b), 10, 64)
if err != nil {
return nil
}
*t = Timestamp(value)
return nil
}
func (t *Timestamp) ToDB() ([]byte, error) {
if t == nil {
return nil, nil
}
data := strconv.FormatInt(int64(*t), 10)
if len(data) == 0 {
return []byte("0"), nil
}
return []byte(data), nil
}
func (t *Timestamp) AutoTime(now time.Time) (interface{}, error) {
data := int64(now.UnixNano()) / 1e6
return data, nil
}
type AutoTimerStruct struct {
ID string `xorm:"varchar(20) pk unique 'id'" json:"id"`
Name string `xorm:"varchar(100) notnull" json:"username"`
Description string `xorm:"text" json:"description"`
CreatedAt Timestamp `xorm:"created bigint notnull" json:"created_at"`
UpdatedAt Timestamp `xorm:"updated bigint notnull" json:"updated_at"`
DeletedAt *Timestamp `xorm:"deleted bigint null" json:"deleted_at"`
}
func TestAutoTimerStructInsert(t *testing.T) {
assert.NoError(t, PrepareEngine())
assertSync(t, new(AutoTimerStruct))
// insert
id := strconv.FormatInt(time.Now().UnixNano(), 10)
item := AutoTimerStruct{
ID: id,
Name: "AutoTimer Test:Insert",
}
_, err := testEngine.Insert(&item)
assert.NoError(t, err)
assert.EqualValues(t, id, item.ID)
// get
var result AutoTimerStruct
has, err := testEngine.ID(id).Get(&result)
assert.NoError(t, err)
assert.True(t, has)
assert.NotEmpty(t, result.CreatedAt)
assert.NotEmpty(t, result.UpdatedAt)
assert.Nil(t, item.DeletedAt)
}
func TestAutoTimerStructUpdate(t *testing.T) {
assert.NoError(t, PrepareEngine())
assertSync(t, new(AutoTimerStruct))
// insert
id := strconv.FormatInt(time.Now().UnixNano(), 10)
item := AutoTimerStruct{
ID: id,
Name: "AutoTimer Test:Update",
}
_, err := testEngine.Insert(&item)
assert.NoError(t, err)
// update
item.Description = "updated"
time.Sleep(50 * time.Millisecond)
_, err = testEngine.ID(id).Update(&item)
assert.NoError(t, err)
assert.Greater(t, item.UpdatedAt, item.CreatedAt)
assert.Nil(t, item.DeletedAt)
}
func TestAutoTimerStructDelete(t *testing.T) {
assert.NoError(t, PrepareEngine())
assertSync(t, new(AutoTimerStruct))
// insert
id := strconv.FormatInt(time.Now().UnixNano(), 10)
item := AutoTimerStruct{
ID: id,
Name: "AutoTimer Test:Delete",
}
_, err := testEngine.Insert(&item)
assert.NoError(t, err)
// delete
_, err = testEngine.ID(id).Delete(&item)
assert.NoError(t, err)
// get
var result AutoTimerStruct
has, err := testEngine.ID(id).Get(&result)
assert.NoError(t, err)
assert.True(t, has)
assert.NotEmpty(t, result.CreatedAt)
assert.NotEmpty(t, result.UpdatedAt)
assert.NotEmpty(t, result.DeletedAt)
}

View File

@ -152,3 +152,44 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl
return fieldValue.Interface(), nil return fieldValue.Interface(), nil
} }
} }
// IsAutoTimer a field value of a struct implements AutoTimer interface
func (statement *Statement) IsAutoTimer(fieldValue reflect.Value, t time.Time) (interface{}, bool) {
fieldType := fieldValue.Type()
if fieldValue.CanAddr() {
if fieldType.Kind() == reflect.Ptr {
// log.Println("[IsAutoTimer] is ptr")
if !fieldValue.IsZero() {
fieldValue = fieldValue.Elem()
fieldType = fieldValue.Type()
} else {
fieldValue = reflect.Zero(fieldType)
}
}
// log.Printf("[IsAutoTimer].1 type=%v,kind=%v,addr=%v\n", fieldValue.Type(), fieldValue.Type().Kind(), fieldValue.CanAddr())
if fieldValue.CanAddr() {
if fieldConvert, ok := fieldValue.Addr().Interface().(convert.AutoTimer); ok {
val, err := fieldConvert.AutoTime(t)
if err != nil {
return nil, false
}
// log.Println("[IsAutoTimer] Addr.OK")
return val, true
}
// log.Println("[IsAutoTimer] Addr.Fail")
return nil, false
}
// log.Printf("[IsAutoTimer].2 type=%v,addr=%v\n", fieldValue.Type(), fieldValue.CanAddr())
if fieldConvert, ok := fieldValue.Interface().(convert.AutoTimer); ok {
val, err := fieldConvert.AutoTime(t)
if err != nil {
return nil, false
}
// log.Println("[IsAutoTimer] OK")
return val, true
}
}
// log.Println("[IsAutoTimer] Fail")
return nil, false
}

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"time" "time"
"xorm.io/xorm/convert"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
) )
@ -32,6 +33,15 @@ func setColumnTime(bean interface{}, col *schemas.Column, t time.Time) {
if err != nil { if err != nil {
return return
} }
if v.CanSet() {
if fieldConvert, ok := v.Addr().Interface().(convert.AutoTimer); ok {
_, err := fieldConvert.AutoTime(t)
if err != nil {
return
}
return
}
}
if v.CanSet() { if v.CanSet() {
switch v.Type().Kind() { switch v.Type().Kind() {
case reflect.Struct: case reflect.Struct:

View File

@ -201,6 +201,14 @@ func (session *Session) Delete(bean interface{}) (int64, error) {
copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1]) copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1])
val, t := session.engine.nowTime(deletedColumn) val, t := session.engine.nowTime(deletedColumn)
fieldValuePtr, err := deletedColumn.ValueOf(bean)
if err != nil {
return 0, err
}
fieldValue := *fieldValuePtr
if at, ok := session.statement.IsAutoTimer(fieldValue, t); ok {
val = at
}
condArgs[0] = val condArgs[0] = val
var colName = deletedColumn.Name var colName = deletedColumn.Name

View File

@ -166,6 +166,9 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error
} }
if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime { if (col.IsCreated || col.IsUpdated) && session.statement.UseAutoTime {
val, t := session.engine.nowTime(col) val, t := session.engine.nowTime(col)
if at, ok := session.statement.IsAutoTimer(fieldValue, t); ok {
val = at
}
args = append(args, val) args = append(args, val)
var colName = col.Name var colName = col.Name
@ -539,6 +542,9 @@ 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 := session.engine.nowTime(col)
if at, ok := session.statement.IsAutoTimer(fieldValue, t); ok {
val = at
}
args = append(args, val) args = append(args, val)
var colName = col.Name var colName = col.Name

View File

@ -206,6 +206,14 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6
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 := session.engine.nowTime(col)
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
return 0, err
}
fieldValue := *fieldValuePtr
if at, ok := session.statement.IsAutoTimer(fieldValue, t); ok {
val = at
}
args = append(args, val) args = append(args, val)
var colName = col.Name var colName = col.Name
@ -505,6 +513,14 @@ 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 := session.engine.nowTime(col)
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
return nil, nil, err
}
fieldValue := *fieldValuePtr
if at, ok := session.statement.IsAutoTimer(fieldValue, t); ok {
val = at
}
args = append(args, val) args = append(args, val)
var colName = col.Name var colName = col.Name