@@ -443,21 +443,28 @@ func TestCancelAfterReorgTimeout(t *testing.T) {
443443 }, 10 * time .Second , 300 * time .Millisecond )
444444}
445445
446+ // setupAddIndexClassicVars enables fast-reorg and toggles the dist-task mode
447+ // in classic kernel. It is a no-op in next-gen.
448+ func setupAddIndexClassicVars (tk * testkit.TestKit , distTaskOn bool ) {
449+ if ! kerneltype .IsClassic () {
450+ return
451+ }
452+ tk .MustExec ("set global tidb_ddl_enable_fast_reorg = 1" )
453+ if distTaskOn {
454+ tk .MustExec ("set global tidb_enable_dist_task = 1" )
455+ } else {
456+ tk .MustExec ("set global tidb_enable_dist_task = 0" )
457+ }
458+ }
459+
446460func TestAddIndexResumesFromCheckpointAfterPartialImport (t * testing.T ) {
447461 runCase := func (t * testing.T , distTaskOn bool ) {
448462 store := realtikvtest .CreateMockStoreAndSetup (t )
449463
450464 tk := testkit .NewTestKit (t , store )
451465 tk .MustExec ("use test" )
452466
453- if kerneltype .IsClassic () {
454- tk .MustExec ("set global tidb_ddl_enable_fast_reorg = 1" )
455- if distTaskOn {
456- tk .MustExec ("set global tidb_enable_dist_task = 1" )
457- } else {
458- tk .MustExec ("set global tidb_enable_dist_task = 0" )
459- }
460- }
467+ setupAddIndexClassicVars (tk , distTaskOn )
461468 ingest .ForceSyncFlagForTest .Store (true )
462469
463470 tk .MustExec ("drop table if exists t" )
@@ -489,3 +496,46 @@ func TestAddIndexResumesFromCheckpointAfterPartialImport(t *testing.T) {
489496 t .Run ("dist_task_off" , func (t * testing.T ) { runCase (t , false ) })
490497 t .Run ("dist_task_on" , func (t * testing.T ) { runCase (t , true ) })
491498}
499+
500+ func TestAddIndexResumesFromCheckpointAfterPartialScan (t * testing.T ) {
501+ runCase := func (t * testing.T , distTaskOn bool ) {
502+ store := realtikvtest .CreateMockStoreAndSetup (t )
503+
504+ tk := testkit .NewTestKit (t , store )
505+ tk .MustExec ("use test" )
506+
507+ setupAddIndexClassicVars (tk , distTaskOn )
508+ ingest .ForceSyncFlagForTest .Store (true )
509+
510+ tk .MustExec ("drop table if exists t" )
511+ tk .MustExec ("create table t (a bigint primary key, b bigint)" )
512+ for i := range 2000 {
513+ tk .MustExec ("insert into t values (?, ?)" , i , i )
514+ }
515+
516+ // Let the first chunk fetch in scanRecords succeed, then inject an
517+ // error on the second fetch. The local-ingest path buffers scan
518+ // results until the scan finishes, so this exercises the partial-scan
519+ // checkpoint path where the retry must restart without a flushed chunk
520+ // advancing the checkpoint. Subsequent fetches (in the retry) see the
521+ // failpoint as already consumed and proceed normally.
522+ testfailpoint .Enable (t ,
523+ "github.com/pingcap/tidb/pkg/ddl/mockScanRecordPartialError" ,
524+ "1*return(false)->1*return(true)" )
525+
526+ tk .MustExec ("alter table t add unique index idx_b(b)" )
527+
528+ tblCntStr := tk .MustQuery ("select count(*) from t" ).Rows ()[0 ][0 ].(string )
529+ idxCntStr := tk .MustQuery ("select count(*) from t use index(idx_b)" ).Rows ()[0 ][0 ].(string )
530+ tblCnt , err := strconv .Atoi (tblCntStr )
531+ require .NoError (t , err )
532+ idxCnt , err := strconv .Atoi (idxCntStr )
533+ require .NoError (t , err )
534+ require .Equal (t , tblCnt , idxCnt )
535+
536+ tk .MustExec ("admin check table t" )
537+ }
538+
539+ t .Run ("dist_task_off" , func (t * testing.T ) { runCase (t , false ) })
540+ t .Run ("dist_task_on" , func (t * testing.T ) { runCase (t , true ) })
541+ }
0 commit comments