diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go index 0dd50fb901f58..9cf18c4cd1829 100644 --- a/ddl/db_integration_test.go +++ b/ddl/db_integration_test.go @@ -428,6 +428,14 @@ func (s *testIntegrationSuite5) TestMySQLErrorCode(c *C) { assertErrorCode(c, tk, sql, tmysql.ErrPrimaryCantHaveNull) sql = "create table t2 (id int auto_increment);" assertErrorCode(c, tk, sql, tmysql.ErrWrongAutoKey) + sql = "create table t2 (a datetime(2) default current_timestamp(3))" + assertErrorCode(c, tk, sql, tmysql.ErrInvalidDefault) + sql = "create table t2 (a datetime(2) default current_timestamp(2) on update current_timestamp)" + assertErrorCode(c, tk, sql, tmysql.ErrInvalidOnUpdate) + sql = "create table t2 (a datetime default current_timestamp on update current_timestamp(2))" + assertErrorCode(c, tk, sql, tmysql.ErrInvalidOnUpdate) + sql = "create table t2 (a datetime(2) default current_timestamp(2) on update current_timestamp(3))" + assertErrorCode(c, tk, sql, tmysql.ErrInvalidOnUpdate) sql = "create table t2 (id int primary key , age int);" tk.MustExec(sql) diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 627ee59eaf2c8..6f0b6f82bde95 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -523,7 +523,7 @@ func columnDefToCol(ctx sessionctx.Context, offset int, colDef *ast.ColumnDef, o case ast.ColumnOptionOnUpdate: // TODO: Support other time functions. if col.Tp == mysql.TypeTimestamp || col.Tp == mysql.TypeDatetime { - if !expression.IsCurrentTimestampExpr(v.Expr) { + if !expression.IsValidCurrentTimestampExpr(v.Expr, colDef.Tp) { return nil, nil, ErrInvalidOnUpdate.GenWithStackByArgs(col.Name) } } else { @@ -2447,7 +2447,7 @@ func processColumnOptions(ctx sessionctx.Context, col *table.Column, options []* case ast.ColumnOptionOnUpdate: // TODO: Support other time functions. if col.Tp == mysql.TypeTimestamp || col.Tp == mysql.TypeDatetime { - if !expression.IsCurrentTimestampExpr(opt.Expr) { + if !expression.IsValidCurrentTimestampExpr(opt.Expr, &col.FieldType) { return ErrInvalidOnUpdate.GenWithStackByArgs(col.Name) } } else { diff --git a/executor/show.go b/executor/show.go index f2b4cf0470978..ed0864cb22f2f 100644 --- a/executor/show.go +++ b/executor/show.go @@ -721,6 +721,7 @@ func (e *ShowExec) fetchShowCreateTable() error { } if mysql.HasOnUpdateNowFlag(col.Flag) { buf.WriteString(" ON UPDATE CURRENT_TIMESTAMP") + buf.WriteString(table.OptionalFsp(&col.FieldType)) } } if len(col.Comment) > 0 { diff --git a/executor/show_test.go b/executor/show_test.go index c7306f63cd44d..6d0eefe75e991 100644 --- a/executor/show_test.go +++ b/executor/show_test.go @@ -198,6 +198,7 @@ func (s *testSuite2) TestShow2(c *C) { c_timestamp timestamp, c_timestamp_default timestamp default current_timestamp, c_timestamp_default_3 timestamp(3) default current_timestamp(3), + c_timestamp_default_4 timestamp(3) default current_timestamp(3) on update current_timestamp(3), c_blob blob, c_tinyblob tinyblob, c_mediumblob mediumblob, @@ -233,6 +234,7 @@ func (s *testSuite2) TestShow2(c *C) { "[c_timestamp timestamp YES select,insert,update,references ]\n" + "[c_timestamp_default timestamp YES CURRENT_TIMESTAMP select,insert,update,references ]\n" + "[c_timestamp_default_3 timestamp(3) YES CURRENT_TIMESTAMP(3) select,insert,update,references ]\n" + + "[c_timestamp_default_4 timestamp(3) YES CURRENT_TIMESTAMP(3) DEFAULT_GENERATED on update CURRENT_TIMESTAMP(3) select,insert,update,references ]\n" + "[c_blob blob YES select,insert,update,references ]\n" + "[c_tinyblob tinyblob YES select,insert,update,references ]\n" + "[c_mediumblob mediumblob YES select,insert,update,references ]\n" + @@ -488,7 +490,9 @@ func (s *testSuite2) TestShowCreateTable(c *C) { "`b` timestamp(3) default current_timestamp(3),\n" + "`c` datetime default current_timestamp,\n" + "`d` datetime(4) default current_timestamp(4),\n" + - "`e` varchar(20) default 'cUrrent_tImestamp')") + "`e` varchar(20) default 'cUrrent_tImestamp',\n" + + "`f` datetime(2) default current_timestamp(2) on update current_timestamp(2),\n" + + "`g` timestamp(2) default current_timestamp(2) on update current_timestamp(2))") tk.MustQuery("show create table `t`").Check(testutil.RowsWithSep("|", ""+ "t CREATE TABLE `t` (\n"+ @@ -496,7 +500,9 @@ func (s *testSuite2) TestShowCreateTable(c *C) { " `b` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3),\n"+ " `c` datetime DEFAULT CURRENT_TIMESTAMP,\n"+ " `d` datetime(4) DEFAULT CURRENT_TIMESTAMP(4),\n"+ - " `e` varchar(20) DEFAULT 'cUrrent_tImestamp'\n"+ + " `e` varchar(20) DEFAULT 'cUrrent_tImestamp',\n"+ + " `f` datetime(2) DEFAULT CURRENT_TIMESTAMP(2) ON UPDATE CURRENT_TIMESTAMP(2),\n"+ + " `g` timestamp(2) DEFAULT CURRENT_TIMESTAMP(2) ON UPDATE CURRENT_TIMESTAMP(2)\n"+ ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin", )) tk.MustExec("drop table t") diff --git a/expression/helper.go b/expression/helper.go index cd150fcb6cc84..307c2aba9cad3 100644 --- a/expression/helper.go +++ b/expression/helper.go @@ -34,12 +34,24 @@ func boolToInt64(v bool) int64 { return 0 } -// IsCurrentTimestampExpr returns whether e is CurrentTimestamp expression. -func IsCurrentTimestampExpr(e ast.ExprNode) bool { - if fn, ok := e.(*ast.FuncCallExpr); ok && fn.FnName.L == ast.CurrentTimestamp { - return true +// IsValidCurrentTimestampExpr returns true if exprNode is a valid CurrentTimestamp expression. +// Here `valid` means it is consistent with the given fieldType's Decimal. +func IsValidCurrentTimestampExpr(exprNode ast.ExprNode, fieldType *types.FieldType) bool { + fn, isFuncCall := exprNode.(*ast.FuncCallExpr) + if !isFuncCall || fn.FnName.L != ast.CurrentTimestamp { + return false } - return false + + containsArg := len(fn.Args) > 0 + // Fsp represents fractional seconds precision. + containsFsp := fieldType != nil && fieldType.Decimal > 0 + var isConsistent bool + if containsArg { + v, ok := fn.Args[0].(*driver.ValueExpr) + isConsistent = ok && fieldType != nil && v.Datum.GetInt64() == int64(fieldType.Decimal) + } + + return (containsArg && isConsistent) || (!containsArg && !containsFsp) } // GetTimeValue gets the time value with type tp. diff --git a/expression/helper_test.go b/expression/helper_test.go index e0acecfdf817b..21db9667f1063 100644 --- a/expression/helper_test.go +++ b/expression/helper_test.go @@ -14,6 +14,7 @@ package expression import ( + driver "github.com/pingcap/tidb/types/parser_driver" "strings" "time" @@ -108,11 +109,28 @@ func (s *testExpressionSuite) TestGetTimeValue(c *C) { func (s *testExpressionSuite) TestIsCurrentTimestampExpr(c *C) { defer testleak.AfterTest(c)() - v := IsCurrentTimestampExpr(ast.NewValueExpr("abc")) - c.Assert(v, IsFalse) + buildTimestampFuncCallExpr := func(i int64) *ast.FuncCallExpr { + var args []ast.ExprNode + if i != 0 { + args = []ast.ExprNode{&driver.ValueExpr{Datum: types.NewIntDatum(i)}} + } + return &ast.FuncCallExpr{FnName: model.NewCIStr("CURRENT_TIMESTAMP"), Args: args} + } - v = IsCurrentTimestampExpr(&ast.FuncCallExpr{FnName: model.NewCIStr("CURRENT_TIMESTAMP")}) + v := IsValidCurrentTimestampExpr(ast.NewValueExpr("abc"), nil) + c.Assert(v, IsFalse) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(0), nil) + c.Assert(v, IsTrue) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(3), &types.FieldType{Decimal: 3}) c.Assert(v, IsTrue) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(1), &types.FieldType{Decimal: 3}) + c.Assert(v, IsFalse) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(0), &types.FieldType{Decimal: 3}) + c.Assert(v, IsFalse) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(2), &types.FieldType{Decimal: 0}) + c.Assert(v, IsFalse) + v = IsValidCurrentTimestampExpr(buildTimestampFuncCallExpr(2), nil) + c.Assert(v, IsFalse) } func (s *testExpressionSuite) TestCurrentTimestampTimeZone(c *C) { diff --git a/infoschema/tables_test.go b/infoschema/tables_test.go index f1c4afb57673e..8a71f96bc65e1 100644 --- a/infoschema/tables_test.go +++ b/infoschema/tables_test.go @@ -272,23 +272,27 @@ func (s *testTableSuite) TestCurrentTimestampAsDefault(c *C) { c_timestamp_default_3 timestamp(3) default current_timestamp(3), c_varchar_default varchar(20) default "current_timestamp", c_varchar_default_3 varchar(20) default "current_timestamp(3)", + c_varchar_default_on_update datetime default current_timestamp on update current_timestamp, + c_varchar_default_on_update_fsp datetime(3) default current_timestamp(3) on update current_timestamp(3), c_varchar_default_with_case varchar(20) default "cUrrent_tImestamp" );`) - tk.MustQuery(`SELECT column_name, column_default + tk.MustQuery(`SELECT column_name, column_default, extra FROM information_schema.COLUMNS WHERE table_schema = "default_time_test" AND table_name = "default_time_table" ORDER BY column_name`, ).Check(testkit.Rows( - "c_datetime ", - "c_datetime_default CURRENT_TIMESTAMP", - "c_datetime_default_2 CURRENT_TIMESTAMP(2)", - "c_timestamp ", - "c_timestamp_default CURRENT_TIMESTAMP", - "c_timestamp_default_3 CURRENT_TIMESTAMP(3)", - "c_varchar_default current_timestamp", - "c_varchar_default_3 current_timestamp(3)", - "c_varchar_default_with_case cUrrent_tImestamp", + "c_datetime ", + "c_datetime_default CURRENT_TIMESTAMP ", + "c_datetime_default_2 CURRENT_TIMESTAMP(2) ", + "c_timestamp ", + "c_timestamp_default CURRENT_TIMESTAMP ", + "c_timestamp_default_3 CURRENT_TIMESTAMP(3) ", + "c_varchar_default current_timestamp ", + "c_varchar_default_3 current_timestamp(3) ", + "c_varchar_default_on_update CURRENT_TIMESTAMP DEFAULT_GENERATED on update CURRENT_TIMESTAMP", + "c_varchar_default_on_update_fsp CURRENT_TIMESTAMP(3) DEFAULT_GENERATED on update CURRENT_TIMESTAMP(3)", + "c_varchar_default_with_case cUrrent_tImestamp ", )) tk.MustExec("DROP DATABASE default_time_test") } diff --git a/table/column.go b/table/column.go index 08f4884ad5d9c..0db5313aa21ce 100644 --- a/table/column.go +++ b/table/column.go @@ -20,6 +20,7 @@ package table import ( "context" "fmt" + "strconv" "strings" "time" "unicode/utf8" @@ -272,7 +273,7 @@ func NewColDesc(col *Column) *ColDesc { } else if mysql.HasOnUpdateNowFlag(col.Flag) { //in order to match the rules of mysql 8.0.16 version //see https://github.com/pingcap/tidb/issues/10337 - extra = "DEFAULT_GENERATED on update CURRENT_TIMESTAMP" + extra = "DEFAULT_GENERATED on update CURRENT_TIMESTAMP" + OptionalFsp(&col.FieldType) } else if col.IsGenerated() { if col.GeneratedStored { extra = "STORED GENERATED" @@ -494,3 +495,12 @@ func GetZeroValue(col *model.ColumnInfo) types.Datum { } return d } + +// OptionalFsp convert a FieldType.Decimal to string. +func OptionalFsp(fieldType *types.FieldType) string { + fsp := fieldType.Decimal + if fsp == 0 { + return "" + } + return "(" + strconv.Itoa(fsp) + ")" +}