Skip to content

Commit d9fb3a5

Browse files
authored
ddl: Implement TableMode feature (#59009)
ref #59008
1 parent 195c367 commit d9fb3a5

26 files changed

+628
-5
lines changed

Diff for: errors.toml

+10
Original file line numberDiff line numberDiff line change
@@ -2946,6 +2946,16 @@ error = '''
29462946
Cannot set resource group for a role
29472947
'''
29482948

2949+
["schema:8258"]
2950+
error = '''
2951+
Table %s is in mode %s
2952+
'''
2953+
2954+
["schema:8259"]
2955+
error = '''
2956+
Invalid mode set from (or by default) %s to %s for table %s
2957+
'''
2958+
29492959
["server:1040"]
29502960
error = '''
29512961
Too many connections

Diff for: pkg/ddl/BUILD.bazel

+3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ go_library(
6666
"stat.go",
6767
"table.go",
6868
"table_lock.go",
69+
"table_mode.go",
6970
"ttl.go",
7071
],
7172
importpath = "github.com/pingcap/tidb/pkg/ddl",
@@ -154,6 +155,7 @@ go_library(
154155
"//pkg/util/dbterror",
155156
"//pkg/util/dbterror/exeerrors",
156157
"//pkg/util/dbterror/plannererrors",
158+
"//pkg/util/dbutil",
157159
"//pkg/util/domainutil",
158160
"//pkg/util/engine",
159161
"//pkg/util/execdetails",
@@ -270,6 +272,7 @@ go_test(
270272
"schema_test.go",
271273
"sequence_test.go",
272274
"stat_test.go",
275+
"table_mode_test.go",
273276
"table_modify_test.go",
274277
"table_split_test.go",
275278
"table_test.go",

Diff for: pkg/ddl/executor.go

+54
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import (
6767
"github.com/pingcap/tidb/pkg/util/collate"
6868
"github.com/pingcap/tidb/pkg/util/dbterror"
6969
"github.com/pingcap/tidb/pkg/util/dbterror/exeerrors"
70+
"github.com/pingcap/tidb/pkg/util/dbutil"
7071
"github.com/pingcap/tidb/pkg/util/domainutil"
7172
"github.com/pingcap/tidb/pkg/util/generic"
7273
"github.com/pingcap/tidb/pkg/util/stringutil"
@@ -119,6 +120,7 @@ type Executor interface {
119120
RenameTable(ctx sessionctx.Context, stmt *ast.RenameTableStmt) error
120121
LockTables(ctx sessionctx.Context, stmt *ast.LockTablesStmt) error
121122
UnlockTables(ctx sessionctx.Context, lockedTables []model.TableLockTpInfo) error
123+
AlterTableMode(ctx sessionctx.Context, args *model.AlterTableModeArgs) error
122124
CleanupTableLock(ctx sessionctx.Context, tables []*ast.TableName) error
123125
UpdateTableReplicaInfo(ctx sessionctx.Context, physicalID int64, available bool) error
124126
RepairTable(ctx sessionctx.Context, createStmt *ast.CreateTableStmt) error
@@ -1062,6 +1064,14 @@ func (e *executor) createTableWithInfoJob(
10621064
switch cfg.OnExist {
10631065
case OnExistIgnore:
10641066
ctx.GetSessionVars().StmtCtx.AppendNote(err)
1067+
// if target TableMode is ModeRestore, we check if the existing mode is consistent the new one
1068+
if tbInfo.Mode == model.TableModeRestore {
1069+
oldTableMode := oldTable.Meta().Mode
1070+
if oldTableMode != model.TableModeRestore {
1071+
return nil, infoschema.ErrInvalidTableModeSet.GenWithStackByArgs(oldTableMode, tbInfo.Mode, tbInfo.Name)
1072+
}
1073+
}
1074+
// Currently, target TableMode will NEVER be ModeImport because ImportInto does not use this function
10651075
return nil, nil
10661076
case OnExistReplace:
10671077
// only CREATE OR REPLACE VIEW is supported at the moment.
@@ -4146,6 +4156,9 @@ func (e *executor) dropTableObject(
41464156
} else if err != nil {
41474157
return err
41484158
}
4159+
if err = dbutil.CheckTableModeIsNormal(tableInfo.Meta().Name, tableInfo.Meta().Mode); err != nil {
4160+
return err
4161+
}
41494162

41504163
// prechecks before build DDL job
41514164

@@ -4337,6 +4350,9 @@ func (e *executor) renameTable(ctx sessionctx.Context, oldIdent, newIdent ast.Id
43374350
if tbl.Meta().TableCacheStatusType != model.TableCacheStatusDisable {
43384351
return errors.Trace(dbterror.ErrOptOnCacheTable.GenWithStackByArgs("Rename Table"))
43394352
}
4353+
if err = dbutil.CheckTableModeIsNormal(tbl.Meta().Name, tbl.Meta().Mode); err != nil {
4354+
return err
4355+
}
43404356
}
43414357

43424358
job := &model.Job{
@@ -4384,6 +4400,9 @@ func (e *executor) renameTables(ctx sessionctx.Context, oldIdents, newIdents []a
43844400
if t.Meta().TableCacheStatusType != model.TableCacheStatusDisable {
43854401
return errors.Trace(dbterror.ErrOptOnCacheTable.GenWithStackByArgs("Rename Tables"))
43864402
}
4403+
if err = dbutil.CheckTableModeIsNormal(t.Meta().Name, t.Meta().Mode); err != nil {
4404+
return err
4405+
}
43874406
}
43884407

43894408
infos = append(infos, &model.RenameTableArgs{
@@ -5691,6 +5710,41 @@ func (e *executor) UnlockTables(ctx sessionctx.Context, unlockTables []model.Tab
56915710
return errors.Trace(err)
56925711
}
56935712

5713+
func (e *executor) AlterTableMode(sctx sessionctx.Context, args *model.AlterTableModeArgs) error {
5714+
is := e.infoCache.GetLatest()
5715+
5716+
schema, ok := is.SchemaByID(args.SchemaID)
5717+
if !ok {
5718+
return infoschema.ErrDatabaseNotExists.GenWithStackByArgs(fmt.Sprintf("SchemaID: %v", args.SchemaID))
5719+
}
5720+
5721+
table, ok := is.TableByID(e.ctx, args.TableID)
5722+
if !ok {
5723+
return infoschema.ErrTableNotExists.GenWithStackByArgs(schema.Name, args.TableID)
5724+
}
5725+
5726+
ok = validateTableMode(table.Meta().Mode, args.TableMode)
5727+
if !ok {
5728+
return infoschema.ErrInvalidTableModeSet.GenWithStackByArgs(table.Meta().Mode, args.TableMode, table.Meta().Name.O)
5729+
}
5730+
if table.Meta().Mode == args.TableMode {
5731+
return nil
5732+
}
5733+
5734+
job := &model.Job{
5735+
Version: model.JobVersion2,
5736+
SchemaID: args.SchemaID,
5737+
TableID: args.TableID,
5738+
Type: model.ActionAlterTableMode,
5739+
BinlogInfo: &model.HistoryInfo{},
5740+
CDCWriteSource: sctx.GetSessionVars().CDCWriteSource,
5741+
SQLMode: sctx.GetSessionVars().SQLMode,
5742+
}
5743+
sctx.SetValue(sessionctx.QueryString, "skip")
5744+
err := e.doDDLJob2(sctx, job, args)
5745+
return errors.Trace(err)
5746+
}
5747+
56945748
func throwErrIfInMemOrSysDB(ctx sessionctx.Context, dbLowerName string) error {
56955749
if util.IsMemOrSysDB(dbLowerName) {
56965750
if ctx.GetSessionVars().User != nil {

Diff for: pkg/ddl/job_worker.go

+2
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,8 @@ func (w *worker) runOneJobStep(
965965
ver, err = onLockTables(jobCtx, job)
966966
case model.ActionUnlockTable:
967967
ver, err = onUnlockTables(jobCtx, job)
968+
case model.ActionAlterTableMode:
969+
ver, err = onAlterTableMode(jobCtx, job)
968970
case model.ActionSetTiFlashReplica:
969971
ver, err = w.onSetTableFlashReplica(jobCtx, job)
970972
case model.ActionUpdateTiFlashReplicaStatus:

Diff for: pkg/ddl/schematracker/checker.go

+5
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ func (d *Checker) UnlockTables(ctx sessionctx.Context, lockedTables []model.Tabl
399399
return d.realExecutor.UnlockTables(ctx, lockedTables)
400400
}
401401

402+
// AlterTableMode implements the DDL interface.
403+
func (d *Checker) AlterTableMode(ctx sessionctx.Context, args *model.AlterTableModeArgs) error {
404+
return d.realExecutor.AlterTableMode(ctx, args)
405+
}
406+
402407
// CleanupTableLock implements the DDL interface.
403408
func (d *Checker) CleanupTableLock(ctx sessionctx.Context, tables []*ast.TableName) error {
404409
return d.realExecutor.CleanupTableLock(ctx, tables)

Diff for: pkg/ddl/schematracker/dm_tracker.go

+5
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,11 @@ func (*SchemaTracker) UnlockTables(_ sessionctx.Context, _ []model.TableLockTpIn
11181118
return nil
11191119
}
11201120

1121+
// AlterTableMode implements the DDL interface, it's no-op in DM's case.
1122+
func (*SchemaTracker) AlterTableMode(_ sessionctx.Context, _ *model.AlterTableModeArgs) error {
1123+
return nil
1124+
}
1125+
11211126
// CleanupTableLock implements the DDL interface, it's no-op in DM's case.
11221127
func (*SchemaTracker) CleanupTableLock(_ sessionctx.Context, _ []*ast.TableName) error {
11231128
return nil

Diff for: pkg/ddl/table_mode.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2025 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ddl
16+
17+
import (
18+
"github.com/pingcap/tidb/pkg/infoschema"
19+
"github.com/pingcap/tidb/pkg/meta/model"
20+
)
21+
22+
// onAlterTableMode should only be called by alterTableMode, will call updateVersionAndTableInfo
23+
func onAlterTableMode(jobCtx *jobContext, job *model.Job) (ver int64, err error) {
24+
args, err := model.GetAlterTableModeArgs(job)
25+
if err != nil {
26+
return ver, err
27+
}
28+
29+
var tbInfo *model.TableInfo
30+
metaMut := jobCtx.metaMut
31+
tbInfo, err = GetTableInfoAndCancelFaultJob(metaMut, job, job.SchemaID)
32+
if err != nil {
33+
return ver, err
34+
}
35+
36+
switch tbInfo.Mode {
37+
case model.TableModeNormal, model.TableModeImport, model.TableModeRestore:
38+
if tbInfo.Mode == args.TableMode {
39+
job.State = model.JobStateDone
40+
return ver, err
41+
}
42+
// directly change table mode to target mode
43+
err = alterTableMode(tbInfo, args)
44+
if err != nil {
45+
job.State = model.JobStateCancelled
46+
return ver, err
47+
}
48+
// update table info and schema version
49+
ver, err = updateVersionAndTableInfo(jobCtx, job, tbInfo, true)
50+
job.FinishTableJob(model.JobStateDone, model.StatePublic, ver, tbInfo)
51+
default:
52+
job.State = model.JobStateCancelled
53+
err = infoschema.ErrInvalidTableModeSet.GenWithStackByArgs(tbInfo.Mode, args.TableMode, tbInfo.Name.O)
54+
}
55+
56+
return ver, err
57+
}
58+
59+
// alterTableMode first checks if the change is valid and changes table mode to target mode
60+
// Currently we can assume args.TableMode will NEVER be model.TableModeRestore.
61+
// Because BR will NOT use this function to set a table into ModeRestore,
62+
// instead BR will use (batch)CreateTableWithInfo.
63+
func alterTableMode(tbInfo *model.TableInfo, args *model.AlterTableModeArgs) error {
64+
ok := validateTableMode(tbInfo.Mode, args.TableMode)
65+
if !ok {
66+
return infoschema.ErrInvalidTableModeSet.GenWithStackByArgs(tbInfo.Mode, args.TableMode, tbInfo.Name.O)
67+
}
68+
69+
tbInfo.Mode = args.TableMode
70+
return nil
71+
}
72+
73+
// validateTableMode validate whether table mode convert is legal.
74+
// Now only block import/restore to convert to each other.
75+
// TODO: Now allow switching between the same table modes, but additional validation will be added later
76+
// to verify that only the same modification source can perform ALTER same table mode.
77+
func validateTableMode(origin, target model.TableMode) bool {
78+
if origin == model.TableModeImport && target == model.TableModeRestore {
79+
return false
80+
}
81+
if origin == model.TableModeRestore && target == model.TableModeImport {
82+
return false
83+
}
84+
return true
85+
}

0 commit comments

Comments
 (0)