diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 6e2373d12f52f..84e64dcb61d6b 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2116,7 +2116,7 @@ func checkTableInfoValidWithStmt(ctx sessionctx.Context, tbInfo *model.TableInfo } } if tbInfo.TTLInfo != nil { - if err := checkTTLInfoValid(ctx, tbInfo); err != nil { + if err := checkTTLInfoValid(ctx, s.Table.Schema, tbInfo); err != nil { return errors.Trace(err) } } @@ -5368,7 +5368,7 @@ func (d *ddl) AlterTableTTLInfoOrEnable(ctx sessionctx.Context, ident ast.Ident, var job *model.Job if ttlInfo != nil { tblInfo.TTLInfo = ttlInfo - err = checkTTLInfoValid(ctx, tblInfo) + err = checkTTLInfoValid(ctx, ident.Schema, tblInfo) if err != nil { return err } diff --git a/ddl/foreign_key.go b/ddl/foreign_key.go index 6f1d02d464ac8..719f763cba342 100644 --- a/ddl/foreign_key.go +++ b/ddl/foreign_key.go @@ -268,6 +268,9 @@ func checkTableForeignKey(referTblInfo, tblInfo *model.TableInfo, fkInfo *model. if referTblInfo.TempTableType != model.TempTableNone || tblInfo.TempTableType != model.TempTableNone { return infoschema.ErrCannotAddForeign } + if referTblInfo.TTLInfo != nil { + return dbterror.ErrUnsupportedTTLReferencedByFK + } // check refer columns in parent table. for i := range fkInfo.RefCols { diff --git a/ddl/ttl.go b/ddl/ttl.go index 7ef35158ca3ef..357481f3eb32f 100644 --- a/ddl/ttl.go +++ b/ddl/ttl.go @@ -26,6 +26,7 @@ import ( "github.com/pingcap/tidb/parser/format" "github.com/pingcap/tidb/parser/model" "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessiontxn" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/dbterror" ) @@ -83,11 +84,15 @@ func onTTLInfoChange(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, err er return ver, nil } -func checkTTLInfoValid(ctx sessionctx.Context, tblInfo *model.TableInfo) error { +func checkTTLInfoValid(ctx sessionctx.Context, schema model.CIStr, tblInfo *model.TableInfo) error { if err := checkTTLIntervalExpr(ctx, tblInfo.TTLInfo); err != nil { return err } + if err := checkTTLTableSuitable(ctx, schema, tblInfo); err != nil { + return err + } + return checkTTLInfoColumnType(tblInfo) } @@ -119,6 +124,22 @@ func checkTTLInfoColumnType(tblInfo *model.TableInfo) error { return nil } +// checkTTLTableSuitable returns whether this table is suitable to be a TTL table +// A temporary table or a parent table referenced by a foreign key cannot be TTL table +func checkTTLTableSuitable(ctx sessionctx.Context, schema model.CIStr, tblInfo *model.TableInfo) error { + if tblInfo.TempTableType != model.TempTableNone { + return dbterror.ErrTempTableNotAllowedWithTTL + } + + // checks even when the foreign key check is not enabled, to keep safe + is := sessiontxn.GetTxnManager(ctx).GetTxnInfoSchema() + if referredFK := checkTableHasForeignKeyReferred(is, schema.L, tblInfo.Name.L, nil, true); referredFK != nil { + return dbterror.ErrUnsupportedTTLReferencedByFK + } + + return nil +} + func checkDropColumnWithTTLConfig(tblInfo *model.TableInfo, colName string) error { if tblInfo.TTLInfo != nil { if tblInfo.TTLInfo.ColumnName.L == colName { diff --git a/errno/errcode.go b/errno/errcode.go index d9c1761887c3c..222ead32ff34e 100644 --- a/errno/errcode.go +++ b/errno/errcode.go @@ -1038,6 +1038,8 @@ const ( ErrUnsupportedColumnInTTLConfig = 8148 ErrTTLColumnCannotDrop = 8149 ErrSetTTLEnableForNonTTLTable = 8150 + ErrTempTableNotAllowedWithTTL = 8151 + ErrUnsupportedTTLReferencedByFK = 8152 // Error codes used by TiDB ddl package ErrUnsupportedDDLOperation = 8200 diff --git a/errno/errname.go b/errno/errname.go index c851e1767aa08..b07c83d5b6258 100644 --- a/errno/errname.go +++ b/errno/errname.go @@ -1033,6 +1033,8 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{ ErrUnsupportedColumnInTTLConfig: mysql.Message("Field '%-.192s' is of a not supported type for TTL config, expect DATETIME, DATE or TIMESTAMP", nil), ErrTTLColumnCannotDrop: mysql.Message("Cannot drop column '%-.192s': needed in TTL config", nil), ErrSetTTLEnableForNonTTLTable: mysql.Message("Cannot set TTL_ENABLE on a table without TTL config", nil), + ErrTempTableNotAllowedWithTTL: mysql.Message("Set TTL for temporary table is not allowed", nil), + ErrUnsupportedTTLReferencedByFK: mysql.Message("Set TTL for a table referenced by foreign key is not allowed", nil), ErrWarnOptimizerHintInvalidInteger: mysql.Message("integer value is out of range in '%s'", nil), ErrWarnOptimizerHintUnsupportedHint: mysql.Message("Optimizer hint %s is not supported by TiDB and is ignored", nil), diff --git a/errors.toml b/errors.toml index 2de8170f05f60..60734aa486341 100644 --- a/errors.toml +++ b/errors.toml @@ -1216,6 +1216,16 @@ error = ''' Cannot set TTL_ENABLE on a table without TTL config ''' +["ddl:8151"] +error = ''' +Set TTL for temporary table is not allowed +''' + +["ddl:8152"] +error = ''' +Set TTL for a table referenced by foreign key is not allowed +''' + ["ddl:8200"] error = ''' Unsupported shard_row_id_bits for table with primary key as row id diff --git a/executor/ddl_test.go b/executor/ddl_test.go index 9128d52bedca9..5c86cd9566cdd 100644 --- a/executor/ddl_test.go +++ b/executor/ddl_test.go @@ -1582,3 +1582,39 @@ func TestAlterTTLInfo(t *testing.T) { tk.MustGetErrMsg("ALTER TABLE t TTL_ENABLE = 'OFF'", "[ddl:8150]Cannot set TTL_ENABLE on a table without TTL config") } + +func TestDisableTTLForTempTable(t *testing.T) { + parser.TTLFeatureGate = true + + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + tk.MustGetDBError("CREATE TEMPORARY TABLE t (created_at datetime) TTL = `created_at` + INTERVAL 5 DAY", dbterror.ErrTempTableNotAllowedWithTTL) +} + +func TestDisableTTLForFKParentTable(t *testing.T) { + parser.TTLFeatureGate = true + + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + + // alter ttl for a FK parent table is not allowed + tk.MustExec("set global tidb_enable_foreign_key='ON'") + tk.MustExec("CREATE TABLE t (id int primary key, created_at datetime)") + tk.MustExec("CREATE TABLE t_1 (t_id int, foreign key fk_t_id(t_id) references t(id))") + tk.MustGetDBError("ALTER TABLE t TTL = created_at + INTERVAL 5 YEAR", dbterror.ErrUnsupportedTTLReferencedByFK) + tk.MustExec("drop table t,t_1") + + // refuse to reference TTL key when create table + tk.MustExec("CREATE TABLE t (id int primary key, created_at datetime) TTL = created_at + INTERVAL 5 YEAR") + tk.MustGetDBError("CREATE TABLE t_1 (t_id int, foreign key fk_t_id(t_id) references t(id))", dbterror.ErrUnsupportedTTLReferencedByFK) + tk.MustExec("drop table t") + + // refuse to add foreign key reference TTL table + tk.MustExec("CREATE TABLE t (id int primary key, created_at datetime) TTL = created_at + INTERVAL 5 YEAR") + tk.MustExec("CREATE TABLE t_1 (t_id int)") + tk.MustGetDBError("ALTER TABLE t_1 ADD FOREIGN KEY fk_t_id(t_id) references t(id)", dbterror.ErrUnsupportedTTLReferencedByFK) + tk.MustExec("drop table t,t_1") +} diff --git a/util/dbterror/ddl_terror.go b/util/dbterror/ddl_terror.go index cce0cd23c8cb7..1db0630977945 100644 --- a/util/dbterror/ddl_terror.go +++ b/util/dbterror/ddl_terror.go @@ -423,4 +423,8 @@ var ( ErrTTLColumnCannotDrop = ClassDDL.NewStd(mysql.ErrTTLColumnCannotDrop) // ErrSetTTLEnableForNonTTLTable returns when the `TTL_ENABLE` option is set on a non-TTL table ErrSetTTLEnableForNonTTLTable = ClassDDL.NewStd(mysql.ErrSetTTLEnableForNonTTLTable) + // ErrTempTableNotAllowedWithTTL returns when setting TTL config for a temp table + ErrTempTableNotAllowedWithTTL = ClassDDL.NewStd(mysql.ErrTempTableNotAllowedWithTTL) + // ErrUnsupportedTTLReferencedByFK returns when the TTL config is set for a table referenced by foreign key + ErrUnsupportedTTLReferencedByFK = ClassDDL.NewStd(mysql.ErrUnsupportedTTLReferencedByFK) )