Skip to content

Commit 8a2368b

Browse files
committed
avoid running queries on the WriteOnly state table
Signed-off-by: Yang Keao <[email protected]>
1 parent 2a3352c commit 8a2368b

File tree

4 files changed

+70
-3
lines changed

4 files changed

+70
-3
lines changed

pkg/ddl/create_table.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,13 +200,20 @@ func createTableWithForeignKeys(jobCtx *jobContext, t *meta.Meta, job *model.Job
200200
if err != nil {
201201
return ver, errors.Trace(err)
202202
}
203-
tbInfo.State = model.StateWriteOnly
203+
tbInfo.State = model.StateDeleteOnly
204204
ver, err = updateVersionAndTableInfo(jobCtx, t, job, tbInfo, true)
205205
if err != nil {
206206
return ver, errors.Trace(err)
207207
}
208-
job.SchemaState = model.StateWriteOnly
209-
case model.StateWriteOnly:
208+
job.SchemaState = model.StateDeleteOnly
209+
// The `tblInfo.State` should be transformed from `None/Public` to `DeleteOnly`. In the `DeleteOnly` state, the table cannot be used explicitly
210+
// in any SQL statement, but if this table has a `ON DELETE CASCADE` or `ON UPDATE CASCADE`, it'll still be deleted/updated automatically to keep
211+
// consistency.
212+
//
213+
// This branch handles both `StateDeleteOnly` and `StateWriteOnly` to avoid compatibility issues. If the TiDB is upgraded from an old version,
214+
// there may be a DDL job in the `StateWriteOnly` state. Now, we handle it in the same way as the `StateDeleteOnly` state. When we believe it's
215+
// impossible to upgrade from a too old version to the current version, we can remove the `StateWriteOnly` branch.
216+
case model.StateDeleteOnly, model.StateWriteOnly:
210217
tbInfo.State = model.StatePublic
211218
ver, err = updateVersionAndTableInfo(jobCtx, t, job, tbInfo, true)
212219
if err != nil {

pkg/ddl/foreign_key_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,41 @@ func TestAddForeignKey3(t *testing.T) {
402402
tk.MustQuery("select * from t1 order by id").Check(testkit.Rows("1 1", "2 2", "3 3"))
403403
tk.MustQuery("select * from t2 order by id").Check(testkit.Rows("1 1", "2 2", "3 3"))
404404
}
405+
406+
func TestForeignKeyInWriteOnlyMode(t *testing.T) {
407+
store := testkit.CreateMockStore(t)
408+
tk := testkit.NewTestKit(t, store)
409+
tk.MustExec("use test")
410+
411+
tkDDL := testkit.NewTestKit(t, store)
412+
tkDDL.MustExec("use test")
413+
tkDDL.MustExec("create table parent (id int key)")
414+
tkDDL.MustExec("insert into parent values(1)")
415+
416+
var notExistErrs []error
417+
testfailpoint.EnableCall(t, "github.com/pingcap/tidb/pkg/ddl/onJobRunBefore", func(job *model.Job) {
418+
if job.Type == model.ActionCreateTable && job.TableName == "child" {
419+
if job.SchemaState == model.StateDeleteOnly {
420+
// tk with the latest schema will insert data into child
421+
_, err := tk.Exec("insert into child values (1, 1)")
422+
notExistErrs = append(notExistErrs, err)
423+
_, err = tk.Exec("update child set id = 2 where id = 1")
424+
notExistErrs = append(notExistErrs, err)
425+
_, err = tk.Exec("delete from child where id = 1")
426+
notExistErrs = append(notExistErrs, err)
427+
_, err = tk.Exec("delete child from child inner join parent where child.pid = parent.id")
428+
notExistErrs = append(notExistErrs, err)
429+
_, err = tk.Exec("delete parent from child inner join parent where child.pid = parent.id")
430+
notExistErrs = append(notExistErrs, err)
431+
}
432+
}
433+
})
434+
tkDDL.MustExec("create table child (id int, pid int, index idx_pid(pid), foreign key (pid) references parent(id) on delete cascade);")
435+
436+
testfailpoint.Disable(t, "github.com/pingcap/tidb/pkg/ddl/onJobRunBefore")
437+
438+
for _, err := range notExistErrs {
439+
require.Error(t, err)
440+
require.Contains(t, err.Error(), "Table 'test.child' doesn't exist")
441+
}
442+
}

pkg/infoschema/perfschema/init.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func Init() {
6262
c.ID = int64(i) + 1
6363
}
6464
meta.DBID = dbID
65+
meta.State = model.StatePublic
6566
}
6667
dbInfo := &model.DBInfo{
6768
ID: dbID,

pkg/planner/core/preprocess.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,6 +1634,10 @@ func (p *preprocessor) handleTableName(tn *ast.TableName) {
16341634
return
16351635
}
16361636
}
1637+
if err := p.checkTableVisible(tableInfo); err != nil {
1638+
p.err = err
1639+
return
1640+
}
16371641
p.resolveCtx.AddTableName(&resolve.TableNameW{
16381642
TableName: tn,
16391643
DBInfo: dbInfo,
@@ -1975,3 +1979,20 @@ func (p *preprocessor) skipLockMDL() bool {
19751979
// skip lock mdl for ANALYZE statement.
19761980
return p.flag&inImportInto > 0 || p.flag&inAnalyze > 0
19771981
}
1982+
1983+
// checkTableVisible returns an `ErrTableNotExists` error if the table is not visible (in non-Public state).
1984+
func (p *preprocessor) checkTableVisible(tblInfo *model.TableInfo) error {
1985+
is := p.ensureInfoSchema()
1986+
1987+
if tblInfo.State != model.StatePublic {
1988+
schemaName := "unknown"
1989+
schema, ok := infoschema.SchemaByTable(is, tblInfo)
1990+
if ok {
1991+
// theoretically, this should always happen
1992+
schemaName = schema.Name.O
1993+
}
1994+
return infoschema.ErrTableNotExists.FastGenByArgs(schemaName, tblInfo.Name.O)
1995+
}
1996+
1997+
return nil
1998+
}

0 commit comments

Comments
 (0)