time.Time默认值不再是 0001-01-01 00:00:00 了吗? #1066

Open
opened 2018-08-09 03:52:43 +00:00 by javasgl · 12 comments
javasgl commented 2018-08-09 03:52:43 +00:00 (Migrated from github.com)

数据库:

create table `user`(
	`name` varchar(512) NOT NULL,
	`login_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
)

注意:login_time 是NOT NULL

model:

type User struct{
	Name string `xorm:"'name'"
	LoginTime time.Time `xorm:"'login_time'"`
}

Insert代码:

user:=&User{
	Name:"string",
}
_,err:=session.InsertOne(user)

对于以前的版本(0.5以前?),以上语句不会出错,因为生成的语句是:

INSERT INTO `user` (`name`,`login_time`) VALUES (?, ?) [args] [ string  0001-01-01 00:00:00 ] - took: 776.254µs

而升级到现在的版本后(dep ensure -update):

  name = "github.com/go-xorm/xorm"
  packages = ["."]
  revision = "d85bb4911c725a53b135fc12862748a41b6c07da"
  version = "v0.7.0"

以上的插入代码会出错,因为生成的语句变成了:

INSERT INTO `user` (`name`,`login_time`) VALUES (?, ?) []interface {}{"string",interface{}(nil)} - took: 776.254µs

这样导致以前设置了NOT NULL字段的数据表,在插入的时候,会报错:

Error 1048: Column 'login_time' cannot be null

导致无法完美无缝的将xorm迁移到新版

数据库: ``` create table `user`( `name` varchar(512) NOT NULL, `login_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', ) ``` #### 注意:login_time 是NOT NULL model: ``` type User struct{ Name string `xorm:"'name'" LoginTime time.Time `xorm:"'login_time'"` } ``` Insert代码: ``` user:=&User{ Name:"string", } _,err:=session.InsertOne(user) ``` 对于以前的版本(0.5以前?),以上语句不会出错,因为生成的语句是: ``` INSERT INTO `user` (`name`,`login_time`) VALUES (?, ?) [args] [ string 0001-01-01 00:00:00 ] - took: 776.254µs ``` 而升级到现在的版本后(dep ensure -update): ``` name = "github.com/go-xorm/xorm" packages = ["."] revision = "d85bb4911c725a53b135fc12862748a41b6c07da" version = "v0.7.0" ``` 以上的插入代码会出错,因为生成的语句变成了: ``` INSERT INTO `user` (`name`,`login_time`) VALUES (?, ?) []interface {}{"string",interface{}(nil)} - took: 776.254µs ``` 这样导致以前设置了NOT NULL字段的数据表,在插入的时候,会报错: ``` Error 1048: Column 'login_time' cannot be null ``` 导致无法完美无缝的将xorm迁移到新版
javasgl commented 2018-08-09 04:03:51 +00:00 (Migrated from github.com)

@lunny 瞅瞅?

@lunny 瞅瞅?
javasgl commented 2018-08-10 02:21:54 +00:00 (Migrated from github.com)

同时,即使指定了默认值,生成的语句也不是按照默认值来的

payTime time.Time `xorm:"not null default '0001-01-01 00:00:00' pay_time"`

生成的语句依然是:

INSERT INTO `user` (`pay_time`) Values(?) []interface{}{""}

此时,指定了not null,所以 time.Time在这里又变成了 "" ,而不是之前的 interface{}(nil).

此时,如果mysql数据的sql_mode设置的是严格模式(一般情况下都是严格模式),这这种日期会被认为是非法的日期,从而导致插入数据库不成功。

同时,即使指定了默认值,生成的语句也不是按照默认值来的 ``` payTime time.Time `xorm:"not null default '0001-01-01 00:00:00' pay_time"` ``` 生成的语句依然是: ``` INSERT INTO `user` (`pay_time`) Values(?) []interface{}{""} ``` 此时,指定了`not null`,所以 time.Time在这里又变成了 "" ,而不是之前的 interface{}(nil). 此时,如果mysql数据的sql_mode设置的是严格模式(一般情况下都是严格模式),这这种日期会被认为是非法的日期,从而导致插入数据库不成功。
javasgl commented 2018-08-10 02:43:18 +00:00 (Migrated from github.com)

xorm 生成sql的表现如果这么不可控的话,如果遇上特定sql_mode的设置,更容易出问题。

xorm 生成sql的表现如果这么不可控的话,如果遇上特定sql_mode的设置,更容易出问题。
javasgl commented 2018-08-10 03:59:48 +00:00 (Migrated from github.com)

避免这么多不可控因素最好的方案就是,对于 time.Time 的默认值,首先按照 struct tag中定义的 default value来传递,如果struct tag 中没有定义default 值,那么应该采用golang的默认值: '0001-01-01 00:00:00' ,而不是一会儿是 "", 一会又是 interface{}(nil)

避免这么多不可控因素最好的方案就是,对于 time.Time 的默认值,首先按照 struct tag中定义的 default value来传递,如果struct tag 中没有定义default 值,那么应该采用golang的默认值: '0001-01-01 00:00:00' ,而不是一会儿是 "", 一会又是 interface{}(nil)
javasgl commented 2018-08-10 04:01:24 +00:00 (Migrated from github.com)

@lunny 来帮忙看看是不是这么个情况?或者可以代码中可以手动指定 session级别的sql_mode?

@lunny 来帮忙看看是不是这么个情况?或者可以代码中可以手动指定 session级别的sql_mode?
javasgl commented 2018-08-10 07:32:01 +00:00 (Migrated from github.com)

升级xorm后,sql中的time.Time默认值变为 interface{}(nil)
指定字段NOT NULL后,sql中的time.Time默认值为"",
均无法满足原有数据库字段NOT NULL DEFAULT '0000-00-00 00:00:00'的要求.(msyql5.7 严格模式下)

因此,目前一个兼容方案是 当 time.Time isZero,插入数据时省略该字段

if user.LoginTime.IsZero(){
  session = session.Omit("login_time")
}
升级xorm后,sql中的time.Time默认值变为 interface{}(nil) 指定字段NOT NULL后,sql中的time.Time默认值为"", 均无法满足原有数据库字段NOT NULL DEFAULT '0000-00-00 00:00:00'的要求.(msyql5.7 严格模式下) 因此,目前一个兼容方案是 当 time.Time isZero,插入数据时省略该字段 ``` if user.LoginTime.IsZero(){ session = session.Omit("login_time") } ```

这个地方需要XORM改进,当字段为time.Time类型且IsZero时,如果有默认值则使用默认值,如果没有默认值则为nil

这个地方需要XORM改进,当字段为time.Time类型且IsZero时,如果有默认值则使用默认值,如果没有默认值则为nil

暂时以你最后那个comment作为临时解决方案

暂时以你最后那个comment作为临时解决方案
javasgl commented 2018-08-10 08:24:53 +00:00 (Migrated from github.com)

没有默认值时,建议最好不要用nil. 原因有三:
第一, golang中 time.Time 和 nil 是不能直接相等的,time.Time 的默认值不是 nil

	t := new(time.Time)
	if *t == nil {     // panic . cannot convert nil to type time.Time
		fmt.Println("err")
	}

第二,如果采用nil为默认值,会导致更新数据库中NOT NULL字段的时候报错“不允许为null”

第三,虽然golang中 '0001-01-01 00:00:00' 和 '0000-00-00 00:00:00' 是一样的,但是在数据库为严格模式下,'0000-00-00' 这类时间会被认为是非法的时间,而'0001-01-01'则不会被认为为非法。

所以,最好的方案是最终还是采用和以前的版本一样的 '0001-01-01 00:00:00',既能保持向后兼容,又能不会受到 sql_mode 的限制。

当然最完美的方案就是,优先按照用户指定的 struct_tag 中的 default value来,用户没有指定,则采用golang的time.Time 的默认值

fmt.Println(new(time.Time)) // 0001-01-01 00:00:00 +0000 UTC
没有默认值时,建议最好不要用nil. 原因有三: 第一, golang中 time.Time 和 nil 是不能直接相等的,time.Time 的默认值不是 nil ``` t := new(time.Time) if *t == nil { // panic . cannot convert nil to type time.Time fmt.Println("err") } ``` 第二,如果采用nil为默认值,会导致更新数据库中NOT NULL字段的时候报错“不允许为null” 第三,虽然golang中 '0001-01-01 00:00:00' 和 '0000-00-00 00:00:00' 是一样的,但是在数据库为严格模式下,'0000-00-00' 这类时间会被认为是非法的时间,而'0001-01-01'则不会被认为为非法。 所以,最好的方案是最终还是采用和以前的版本一样的 '0001-01-01 00:00:00',既能保持向后兼容,又能不会受到 sql_mode 的限制。 当然最完美的方案就是,优先按照用户指定的 struct_tag 中的 default value来,用户没有指定,则采用golang的time.Time 的默认值 ``` fmt.Println(new(time.Time)) // 0001-01-01 00:00:00 +0000 UTC ```
eleztian commented 2018-11-07 02:47:22 +00:00 (Migrated from github.com)

这样可以正常进行,

for _, v := range in {
_, err := dstSession.Omit("end_time").Insert(d)
panic(err)
}

但是,这样,任然会报 Error 1048: Column 'end_time' cannot be null

_, err := dstSession.Omit("end_time").Insert(in....)

这是为啥???

这样可以正常进行, ```go for _, v := range in { _, err := dstSession.Omit("end_time").Insert(d) panic(err) } ``` 但是,这样,任然会报 Error 1048: Column 'end_time' cannot be null ```go _, err := dstSession.Omit("end_time").Insert(in....) ``` 这是为啥???

@eleztian 有可能是一个bug

@eleztian 有可能是一个bug
zxysilent commented 2019-09-03 02:39:05 +00:00 (Migrated from github.com)

现在怎么处理啊

现在怎么处理啊
Sign in to join this conversation.
No Milestone
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: xorm/xorm#1066
No description provided.