Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ddl: support specifying expr rand() as column default value #32608

Merged
merged 32 commits into from
Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1720c3e
Support rand() as column default expr.
CbcWestwolf Mar 29, 2022
f86c1b3
Remove ColumnDefaultType
CbcWestwolf Mar 29, 2022
9b2c973
Merge branch 'master' into column_default_expr
CbcWestwolf Mar 30, 2022
0246e99
Merge branch 'master' into column_default_expr
CbcWestwolf Mar 30, 2022
328a043
Fix
CbcWestwolf Mar 31, 2022
e1787f1
Merge branch 'master' into column_default_expr
CbcWestwolf Mar 31, 2022
644bc0e
Fix
CbcWestwolf Mar 31, 2022
d9da08c
Modify grammar.
CbcWestwolf Mar 31, 2022
4940b06
Remove 'ast.Now'.
CbcWestwolf Mar 31, 2022
414a8bc
Merge branch 'master' into column_default_expr
CbcWestwolf Apr 1, 2022
7f1f986
Add more test.
CbcWestwolf Apr 1, 2022
14341dc
Merge branch 'master' into column_default_expr
CbcWestwolf Apr 1, 2022
9b35bf2
Merge branch 'master' into column_default_expr
CbcWestwolf Apr 2, 2022
c4bf8d8
Merge branch 'master' into column_default_expr
CbcWestwolf Apr 2, 2022
aee1f45
Introduce a new errno from MySQL 8.0
CbcWestwolf Apr 2, 2022
efaf2ef
Fix check_dev fail.
CbcWestwolf Apr 2, 2022
99d40fe
Reformat getDefaultValue.
CbcWestwolf Apr 2, 2022
307d733
Fix UT fail.
CbcWestwolf Apr 2, 2022
a53ce57
Merge branch 'master' into column_default_expr
CbcWestwolf Apr 2, 2022
c5b2b87
Update ddl/ddl_api.go
CbcWestwolf Apr 6, 2022
c00e526
Remove the redundant type assertion.
CbcWestwolf Apr 6, 2022
5dcddc9
Update comments.
CbcWestwolf Apr 6, 2022
eec35a3
Introduce NowSymOptionFractionParentheses.
CbcWestwolf Apr 6, 2022
1851df0
Merge branch 'master' into column_default_expr
CbcWestwolf Apr 6, 2022
f9d0aae
Extract getFuncCallDefaultValue from getDefaultValue.
CbcWestwolf Apr 6, 2022
2ccca80
Fix CI fail.
CbcWestwolf Apr 6, 2022
007fce4
getFuncCallDefaultValue.
CbcWestwolf Apr 6, 2022
e098fa4
Introduce ErrBinlogUnsafeSystemFunction.
CbcWestwolf Apr 6, 2022
388d507
Merge branch 'master' into column_default_expr
CbcWestwolf Apr 7, 2022
a289bdb
Update ddl/ddl_api.go
CbcWestwolf Apr 7, 2022
df005bc
Merge branch 'master' into column_default_expr
ti-chi-bot Apr 7, 2022
299bf2e
Merge branch 'master' into column_default_expr
ti-chi-bot Apr 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions ddl/db_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,48 @@ func TestDefaultValueIsString(t *testing.T) {
require.Equal(t, "1", tbl.Meta().Columns[0].DefaultValue)
}

func TestDefaultColumnWithRand(t *testing.T) {
// Related issue: https://github.com/pingcap/tidb/issues/10377
store, clean := testkit.CreateMockStoreWithSchemaLease(t, testLease)
defer clean()
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("drop table if exists t, t1, t2")

// create table
tk.MustExec("create table t (c int(10), c1 int default (rand()))")
tk.MustExec("create table t1 (c int, c1 double default (rand()))")
tk.MustExec("create table t2 (c int, c1 double default (rand(1)))")

// add column with default rand() for table t is forbidden in MySQL 8.0
tk.MustGetErrCode("alter table t add column c2 double default (rand(2))", errno.ErrBinlogUnsafeSystemFunction)
tk.MustGetErrCode("alter table t add column c3 int default ((rand()))", errno.ErrBinlogUnsafeSystemFunction)
tk.MustGetErrCode("alter table t add column c4 int default (((rand(3))))", errno.ErrBinlogUnsafeSystemFunction)

// insert records
tk.MustExec("insert into t(c) values (1),(2),(3)")
tk.MustExec("insert into t1(c) values (1),(2),(3)")
tk.MustExec("insert into t2(c) values (1),(2),(3)")

queryStmts := []string{
"SELECT c1 from t",
"SELECT c1 from t1",
"SELECT c1 from t2",
}
for _, queryStmt := range queryStmts {
r := tk.MustQuery(queryStmt).Rows()
for _, row := range r {
d, ok := row[0].(float64)
if ok {
require.True(t, 0.0 <= d && d < 1.0, "rand() return a random floating-point value in the range 0 <= v < 1.0.")
}
}
}

// use a non-existent function name
tk.MustGetErrCode("CREATE TABLE t3 (c int, c1 int default a_function_not_supported_yet());", errno.ErrDefValGeneratedNamedFunctionIsNotAllowed)
}

func TestChangingDBCharset(t *testing.T) {
store, clean := testkit.CreateMockStore(t)
defer clean()
Expand Down
116 changes: 73 additions & 43 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1035,27 +1035,64 @@ func columnDefToCol(ctx sessionctx.Context, offset int, colDef *ast.ColumnDef, o
return col, constraints, nil
}

// getFuncCallDefaultValue gets the default column value of function-call expression.
func getFuncCallDefaultValue(col *table.Column, option *ast.ColumnOption, expr *ast.FuncCallExpr) (interface{}, bool, error) {
switch expr.FnName.L {
case ast.Rand:
if err := expression.VerifyArgsWrapper(ast.Rand, len(expr.Args)); err != nil {
return nil, false, errors.Trace(err)
}
col.DefaultIsExpr = true
var sb strings.Builder
restoreFlags := format.RestoreStringSingleQuotes | format.RestoreKeyWordLowercase | format.RestoreNameBackQuotes |
format.RestoreSpacesAroundBinaryOperation
restoreCtx := format.NewRestoreCtx(restoreFlags, &sb)
if err := expr.Restore(restoreCtx); err != nil {
return "", false, err
}
return sb.String(), false, nil
case ast.NextVal:
// handle default next value of sequence. (keep the expr string)
str, err := getSequenceDefaultValue(option)
if err != nil {
return nil, false, errors.Trace(err)
}
return str, true, nil
case ast.CurrentTimestamp:
tp, fsp := col.FieldType.Tp, col.FieldType.Decimal
if tp == mysql.TypeTimestamp || tp == mysql.TypeDatetime {
defaultFsp := 0
if len(expr.Args) == 1 {
if val := expr.Args[0].(*driver.ValueExpr); val != nil {
defaultFsp = int(val.GetInt64())
}
}
if defaultFsp != fsp {
return nil, false, dbterror.ErrInvalidDefaultValue.GenWithStackByArgs(col.Name.O)
}
}
return nil, false, nil
default:
return nil, false, dbterror.ErrDefValGeneratedNamedFunctionIsNotAllowed.GenWithStackByArgs(col.Name.String(), expr.FnName.String())
}
}

// getDefaultValue will get the default value for column.
// 1: get the expr restored string for the column which uses sequence next value as default value.
// 2: get specific default value for the other column.
func getDefaultValue(ctx sessionctx.Context, col *table.Column, c *ast.ColumnOption) (interface{}, bool, error) {
func getDefaultValue(ctx sessionctx.Context, col *table.Column, option *ast.ColumnOption) (interface{}, bool, error) {
// handle default value with function call
tp, fsp := col.FieldType.Tp, col.FieldType.Decimal
if tp == mysql.TypeTimestamp || tp == mysql.TypeDatetime {
switch x := c.Expr.(type) {
case *ast.FuncCallExpr:
if x.FnName.L == ast.CurrentTimestamp {
defaultFsp := 0
if len(x.Args) == 1 {
if val := x.Args[0].(*driver.ValueExpr); val != nil {
defaultFsp = int(val.GetInt64())
}
}
if defaultFsp != fsp {
return nil, false, dbterror.ErrInvalidDefaultValue.GenWithStackByArgs(col.Name.O)
}
}
if x, ok := option.Expr.(*ast.FuncCallExpr); ok {
val, isSeqExpr, err := getFuncCallDefaultValue(col, option, x)
if val != nil || isSeqExpr || err != nil {
return val, isSeqExpr, err
}
vd, err := expression.GetTimeValue(ctx, c.Expr, tp, fsp)
// If the function call is ast.CurrentTimestamp, it needs to be continuously processed.
}

if tp == mysql.TypeTimestamp || tp == mysql.TypeDatetime {
vd, err := expression.GetTimeValue(ctx, option.Expr, tp, fsp)
value := vd.GetValue()
if err != nil {
return nil, false, dbterror.ErrInvalidDefaultValue.GenWithStackByArgs(col.Name.O)
Expand All @@ -1073,17 +1110,9 @@ func getDefaultValue(ctx sessionctx.Context, col *table.Column, c *ast.ColumnOpt

return value, false, nil
}
// handle default next value of sequence. (keep the expr string)
str, isSeqExpr, err := tryToGetSequenceDefaultValue(c)
if err != nil {
return nil, false, errors.Trace(err)
}
if isSeqExpr {
return str, true, nil
}

// evaluate the non-sequence expr to a certain value.
v, err := expression.EvalAstExpr(ctx, c.Expr)
// evaluate the non-function-call expr to a certain value.
v, err := expression.EvalAstExpr(ctx, option.Expr)
if err != nil {
return nil, false, errors.Trace(err)
}
Expand Down Expand Up @@ -1138,18 +1167,15 @@ func getDefaultValue(ctx sessionctx.Context, col *table.Column, c *ast.ColumnOpt
return val, false, err
}

func tryToGetSequenceDefaultValue(c *ast.ColumnOption) (expr string, isExpr bool, err error) {
if f, ok := c.Expr.(*ast.FuncCallExpr); ok && f.FnName.L == ast.NextVal {
var sb strings.Builder
restoreFlags := format.RestoreStringSingleQuotes | format.RestoreKeyWordLowercase | format.RestoreNameBackQuotes |
format.RestoreSpacesAroundBinaryOperation
restoreCtx := format.NewRestoreCtx(restoreFlags, &sb)
if err := c.Expr.Restore(restoreCtx); err != nil {
return "", true, err
}
return sb.String(), true, nil
func getSequenceDefaultValue(c *ast.ColumnOption) (expr string, err error) {
var sb strings.Builder
restoreFlags := format.RestoreStringSingleQuotes | format.RestoreKeyWordLowercase | format.RestoreNameBackQuotes |
format.RestoreSpacesAroundBinaryOperation
restoreCtx := format.NewRestoreCtx(restoreFlags, &sb)
if err := c.Expr.Restore(restoreCtx); err != nil {
return "", err
}
return "", false, nil
return sb.String(), nil
}

// getSetDefaultValue gets the default value for the set type. See https://dev.mysql.com/doc/refman/5.7/en/set.html.
Expand Down Expand Up @@ -3410,12 +3436,16 @@ func checkAndCreateNewColumn(ctx sessionctx.Context, ti ast.Ident, schema *model
// known rows with specific sequence next value under current add column logic.
// More explanation can refer: TestSequenceDefaultLogic's comment in sequence_test.go
if option.Tp == ast.ColumnOptionDefaultValue {
_, isSeqExpr, err := tryToGetSequenceDefaultValue(option)
if err != nil {
return nil, errors.Trace(err)
}
if isSeqExpr {
return nil, errors.Trace(dbterror.ErrAddColumnWithSequenceAsDefault.GenWithStackByArgs(specNewColumn.Name.Name.O))
if f, ok := option.Expr.(*ast.FuncCallExpr); ok {
switch f.FnName.L {
case ast.NextVal:
if _, err := getSequenceDefaultValue(option); err != nil {
return nil, errors.Trace(err)
}
return nil, errors.Trace(dbterror.ErrAddColumnWithSequenceAsDefault.GenWithStackByArgs(specNewColumn.Name.Name.O))
case ast.Rand:
return nil, errors.Trace(dbterror.ErrBinlogUnsafeSystemFunction.GenWithStackByArgs())
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions errno/errcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@ const (
ErrBinlogUnsafeLimit = 1668
ErrBinlogUnsafeInsertDelayed = 1669
ErrBinlogUnsafeAutoincColumns = 1671
ErrBinlogUnsafeSystemFunction = 1674
ErrBinlogUnsafeNontransAfterTrans = 1675
ErrMessageAndStatement = 1676
ErrInsideTransactionPreventsSwitchBinlogFormat = 1679
Expand Down Expand Up @@ -899,6 +900,7 @@ const (
ErrWrongKeyColumnFunctionalIndex = 3761
ErrFunctionalIndexOnField = 3762
ErrGeneratedColumnRowValueIsNotAllowed = 3764
ErrDefValGeneratedNamedFunctionIsNotAllowed = 3770
ErrFKIncompatibleColumns = 3780
ErrFunctionalIndexRowValueIsNotAllowed = 3800
ErrDependentByFunctionalIndex = 3837
Expand Down
2 changes: 2 additions & 0 deletions errno/errname.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
ErrBinlogUnsafeLimit: mysql.Message("The statement is unsafe because it uses a LIMIT clause. This is unsafe because the set of rows included cannot be predicted.", nil),
ErrBinlogUnsafeInsertDelayed: mysql.Message("The statement is unsafe because it uses INSERT DELAYED. This is unsafe because the times when rows are inserted cannot be predicted.", nil),
ErrBinlogUnsafeAutoincColumns: mysql.Message("Statement is unsafe because it invokes a trigger or a stored function that inserts into an AUTOINCREMENT column. Inserted values cannot be logged correctly.", nil),
ErrBinlogUnsafeSystemFunction: mysql.Message("Statement is unsafe because it uses a system function that may return a different value on the slave", nil),
ErrBinlogUnsafeNontransAfterTrans: mysql.Message("Statement is unsafe because it accesses a non-transactional table after accessing a transactional table within the same transaction.", nil),
ErrMessageAndStatement: mysql.Message("%s Statement: %s", nil),
ErrInsideTransactionPreventsSwitchBinlogFormat: mysql.Message("Cannot modify @@session.binlogFormat inside a transaction", nil),
Expand Down Expand Up @@ -826,6 +827,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
ErrRowInWrongPartition: mysql.Message("Found a row in wrong partition %s", []int{0}),
ErrGeneratedColumnFunctionIsNotAllowed: mysql.Message("Expression of generated column '%s' contains a disallowed function.", nil),
ErrGeneratedColumnRowValueIsNotAllowed: mysql.Message("Expression of generated column '%s' cannot refer to a row value", nil),
ErrDefValGeneratedNamedFunctionIsNotAllowed: mysql.Message("Default value expression of column '%s' contains a disallowed function: `%s`.", nil),
ErrUnsupportedAlterInplaceOnVirtualColumn: mysql.Message("INPLACE ADD or DROP of virtual columns cannot be combined with other ALTER TABLE actions.", nil),
ErrWrongFKOptionForGeneratedColumn: mysql.Message("Cannot define foreign key with %s clause on a generated column.", nil),
ErrBadGeneratedColumn: mysql.Message("The value specified for generated column '%s' in table '%s' is not allowed.", nil),
Expand Down
10 changes: 10 additions & 0 deletions errors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,11 @@ error = '''
Field '%-.192s' is of a not allowed type for this type of partitioning
'''

["ddl:1674"]
error = '''
Statement is unsafe because it uses a system function that may return a different value on the slave
'''

["ddl:1688"]
error = '''
Comment for index '%-.64s' is too long (max = %d)
Expand Down Expand Up @@ -1001,6 +1006,11 @@ error = '''
Expression of generated column '%s' cannot refer to a row value
'''

["ddl:3770"]
error = '''
Default value expression of column '%s' contains a disallowed function: `%s`.
'''

["ddl:3780"]
error = '''
Referencing column '%s' in foreign key constraint '%s' are incompatible
Expand Down
Loading