fix: 🐛invalid TypeDatetime 0000-00-00 00:00:00.000#285
fix: 🐛invalid TypeDatetime 0000-00-00 00:00:00.000#285tiansin wants to merge 2 commits intoXiaoMi:mainfrom
Conversation
|
Hi @tiansin , thanks for your contribution! 🙌 To help us review your PR efficiently, could you please update the description to follow our template format? Specifically: Problem Summary: What is changed: The template is as follows:⬇️ What problem does this PR solve?Issue Number: None Problem Summary: What is changed and how it works?Check List
Side effects
Documentation
|
|
@tiansin hi~ What is the specific type of the field that has this problem? MySQL Timestamp should not store fractional seconds. I'm trying to reproduce this step |
感谢回复, 最后我发现这个问题原来是由gorm造成的.如果不设置'DisableDatetimePrecision'参数就会自动创建带毫秒精度的时间. |
@tiansin hi~ 我正尝试复现以获取到invalid TypeDatetime Error 但是似乎没有任何错误(例如连接断开) package main
import (
"database/sql"
"fmt"
"log"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
CreatedAt time.Time
}
func main() {
dsnRoot := "superroot:superroot@tcp(127.0.0.1:13306)/?parseTime=true"
sqlDB, err := sql.Open("mysql", dsnRoot)
if err != nil {
log.Fatal(err)
}
defer sqlDB.Close()
dbName := "gorm_test"
_, err = sqlDB.Exec("CREATE DATABASE IF NOT EXISTS " + dbName)
if err != nil {
log.Fatal("create database error:", err)
}
time.Sleep(5 * time.Second)
var datetimePrecision = 3
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "superroot:superroot@tcp(127.0.0.1:13306)/gorm_test?charset=utf8&parseTime=True&loc=Local", // data source name, refer https://github.com/go-sql-driver/mysql#dsn-data-source-name
DefaultStringSize: 256, // add default size for string fields, by default, will use db type `longtext` for fields without size, not a primary key, no index defined and don't have default values
DisableDatetimePrecision: false, // disable datetime precision support, which not supported before MySQL 5.6
DefaultDatetimePrecision: &datetimePrecision, // default datetime precision
DontSupportRenameIndex: true, // drop & create index when rename index, rename index not supported before MySQL 5.7, MariaDB
DontSupportRenameColumn: true, // use change when rename column, rename rename not supported before MySQL 8, MariaDB
SkipInitializeWithVersion: false, // smart configure based on used version
}), &gorm.Config{})
if err != nil {
fmt.Println("open database error:", err)
}
if err := db.AutoMigrate(&User{}); err != nil {
log.Fatal("auto migrate error:", err)
}
db.Create(&User{Name: "Alice"})
tableName := "users"
var result struct {
Table string
CreateTable string
}
db.Raw("SHOW CREATE TABLE " + tableName).Scan(&result)
fmt.Println("结果是", result.CreateTable)
time.Sleep(30 * time.Second)
}执行SQL成功到程序退出之前的日志如下⬇️ 出现Session Close的原因是因为程序退出,除此之外没看到执行SQL出错。 |
@gongna-au 抱歉,之前的测试代码只有为什么会产生时间精度的问题,并没有SQL出错的原因 namespace配置如下: |
|
@tiansin hi,Thank you very much for giving us advance warning about this problem. I couldn't reproduce the issue before, but we recently encountered the same problem in our production environment, and this time I successfully reproduced it. I think the following fix would be a more robust solution. case TypeDatetime, TypeTimestamp:
encoded, err := encodeDateTime(v)
if err != nil {
mysqlTypeStr := "TypeDatetime"
if fieldType == TypeTimestamp {
mysqlTypeStr = "TypeTimestamp"
}
return nil, fmt.Errorf("invalid %s %s: %v", mysqlTypeStr, v, err)
}
t = encoded// encodeDateTime 将时间字符串编码为 MySQL 协议的二进制格式
func encodeDateTime(v string) ([]byte, error) {
var t []byte
// 1. 处理所有形式的零值 (0000-00-00...)
if strings.HasPrefix(v, "0000-00-00") {
// 根据 MySQL 协议,长度为 0 的日期字节表示零值
return append(t, 0), nil
}
// 2. 匹配解析格式
var layout string
switch {
case len(v) == 10: // 2024-01-01
layout = "2006-01-02"
case len(v) == 19: // 2024-01-01 12:00:00
layout = "2006-01-02 15:04:05"
case len(v) > 19: // 2024-01-01 12:00:00.000...
layout = "2006-01-02 15:04:05.999999999"
default:
return nil, fmt.Errorf("invalid time string length: %s", v)
}
ts, err := time.Parse(layout, v)
if err != nil {
return nil, fmt.Errorf("parse time error: %v (value: %s)", err, v)
}
// 3. 构建二进制报文
// 长度 11 表示包含: year(2), month(1), day(1), hour(1), minute(1), second(1), microsecond(4)
t = append(t, 11)
t = AppendUint16(t, uint16(ts.Year()))
t = append(t, byte(int(ts.Month())), byte(ts.Day()), byte(ts.Hour()), byte(ts.Minute()), byte(ts.Second()))
// 计算微秒 (1 毫秒 = 1000 微秒)
microseconds := uint32(ts.Nanosecond() / 1000)
t = AppendUint32(t, microseconds)
return t, nil
} |
|
@gongna-au 感谢回复,我也认为你的处理方案比较完美,我把这个pr关闭掉. |

Session write response error, connId: 10007, err: invalid TypeDatetime 0000-00-00 00:00:00.000