@@ -18,6 +18,7 @@ import (
1818 "context"
1919 "fmt"
2020 "math"
21+ "strconv"
2122 "strings"
2223 "sync"
2324 "sync/atomic"
@@ -643,6 +644,213 @@ func TestRecoverTableByJobID(t *testing.T) {
643644 require .Equal (t , false , gcEnable )
644645}
645646
647+ func TestRecoverTableUsesRealStartTSForQueuedDropTable (t * testing.T ) {
648+ store := createMockStore (t )
649+ tk := testkit .NewTestKit (t , store )
650+ tk .MustExec ("create database if not exists test_recover" )
651+ tk .MustExec ("use test_recover" )
652+ tk .MustExec ("drop table if exists t_recover_snapshot" )
653+ tk .MustExec ("create table t_recover_snapshot (id int primary key, col_a int, col_b int)" )
654+ tk .MustExec ("insert into t_recover_snapshot values (1, 11, 21)" )
655+
656+ defer func (originGC bool ) {
657+ if originGC {
658+ util .EmulatorGCEnable ()
659+ } else {
660+ util .EmulatorGCDisable ()
661+ }
662+ }(util .IsEmulatorGCEnable ())
663+ util .EmulatorGCDisable ()
664+
665+ var pauseSchedule atomic.Bool
666+ waitSchCh := make (chan struct {})
667+ var closeSchedule sync.Once
668+ releaseSchedule := func () {
669+ pauseSchedule .Store (false )
670+ closeSchedule .Do (func () { close (waitSchCh ) })
671+ }
672+ t .Cleanup (releaseSchedule )
673+ testfailpoint .EnableCall (t , "github.com/pingcap/tidb/pkg/ddl/beforeLoadAndDeliverJobs" , func () {
674+ if pauseSchedule .Load () {
675+ <- waitSchCh
676+ }
677+ })
678+ pauseSchedule .Store (true )
679+
680+ submittedCh := make (chan struct {}, 2 )
681+ submitGate := make (chan struct {})
682+ testfailpoint .EnableCall (t , "github.com/pingcap/tidb/pkg/ddl/waitJobSubmitted" , func () {
683+ submittedCh <- struct {}{}
684+ <- submitGate
685+ })
686+ waitSubmitted := func () {
687+ select {
688+ case <- submittedCh :
689+ submitGate <- struct {}{}
690+ case <- time .After (5 * time .Second ):
691+ require .FailNow (t , "DDL job was not submitted" )
692+ }
693+ }
694+
695+ // Two independent sessions are needed so the drop-table job can be queued
696+ // after drop-column is submitted but before drop-column has changed metadata.
697+ tkAlter := testkit .NewTestKit (t , store )
698+ tkAlter .MustExec ("use test_recover" )
699+ alterDoneCh := make (chan error , 1 )
700+ go func () {
701+ _ , err := tkAlter .Exec ("alter table t_recover_snapshot drop column col_a" )
702+ alterDoneCh <- err
703+ }()
704+ waitSubmitted ()
705+
706+ tkDrop := testkit .NewTestKit (t , store )
707+ tkDrop .MustExec ("use test_recover" )
708+ dropDoneCh := make (chan error , 1 )
709+ go func () {
710+ _ , err := tkDrop .Exec ("drop table t_recover_snapshot" )
711+ dropDoneCh <- err
712+ }()
713+ waitSubmitted ()
714+
715+ testfailpoint .Disable (t , "github.com/pingcap/tidb/pkg/ddl/waitJobSubmitted" )
716+ releaseSchedule ()
717+ require .NoError (t , <- alterDoneCh )
718+ require .NoError (t , <- dropDoneCh )
719+
720+ getHistoryJobID := func (jobType string ) int64 {
721+ rows := tk .MustQuery (fmt .Sprintf (
722+ "admin show ddl jobs where db_name = 'test_recover' and table_name = 't_recover_snapshot' and job_type = '%s'" ,
723+ jobType ,
724+ )).Rows ()
725+ require .NotEmpty (t , rows )
726+ jobID , err := strconv .ParseInt (rows [0 ][0 ].(string ), 10 , 64 )
727+ require .NoError (t , err )
728+ return jobID
729+ }
730+
731+ dropJobID := getHistoryJobID ("drop table" )
732+ dropJob , err := ddl .GetHistoryJobByID (tk .Session (), dropJobID )
733+ require .NoError (t , err )
734+ require .NotNil (t , dropJob )
735+ require .Greater (t , dropJob .RealStartTS , dropJob .StartTS )
736+
737+ gcTimeFormat := "20060102-15:04:05 -0700 MST"
738+ timeBeforeDrop := time .Now ().Add (- 48 * time .Hour ).Format (gcTimeFormat )
739+ safePointSQL := `INSERT HIGH_PRIORITY INTO mysql.tidb VALUES ('tikv_gc_safe_point', '%[1]s', '')
740+ ON DUPLICATE KEY
741+ UPDATE variable_value = '%[1]s'`
742+ tk .MustExec ("delete from mysql.tidb where variable_name in ('tikv_gc_safe_point','tikv_gc_enable')" )
743+ tk .MustExec (fmt .Sprintf (safePointSQL , timeBeforeDrop ))
744+ require .NoError (t , gcutil .EnableGC (tk .Session ()))
745+
746+ tk .MustExec (fmt .Sprintf ("recover table by job %d" , dropJobID ))
747+ tk .MustQuery ("select column_name from information_schema.columns where table_schema = 'test_recover' and table_name = 't_recover_snapshot' order by ordinal_position" ).Check (testkit .Rows ("id" , "col_b" ))
748+ tk .MustQuery ("select id, col_b from t_recover_snapshot" ).Check (testkit .Rows ("1 21" ))
749+
750+ recoverJobID := getHistoryJobID ("recover table" )
751+ recoverJob , err := ddl .GetHistoryJobByID (tk .Session (), recoverJobID )
752+ require .NoError (t , err )
753+ require .NotNil (t , recoverJob )
754+ require .NotNil (t , recoverJob .BinlogInfo .TableInfo )
755+ colNames := make ([]string , 0 , len (recoverJob .BinlogInfo .TableInfo .Columns ))
756+ for _ , col := range recoverJob .BinlogInfo .TableInfo .Columns {
757+ colNames = append (colNames , col .Name .L )
758+ }
759+ require .Equal (t , []string {"id" , "col_b" }, colNames )
760+ }
761+
762+ func TestFlashbackDatabaseUsesRealStartTSForQueuedDropSchema (t * testing.T ) {
763+ store := createMockStore (t )
764+ tk := testkit .NewTestKit (t , store )
765+ tk .MustExec ("drop database if exists test_recover_schema_snapshot" )
766+ tk .MustExec ("create database test_recover_schema_snapshot" )
767+ tk .MustExec ("create table test_recover_schema_snapshot.t (id int primary key, col_a int, col_b int)" )
768+ tk .MustExec ("insert into test_recover_schema_snapshot.t values (1, 11, 21)" )
769+
770+ defer func (originGC bool ) {
771+ if originGC {
772+ util .EmulatorGCEnable ()
773+ } else {
774+ util .EmulatorGCDisable ()
775+ }
776+ }(util .IsEmulatorGCEnable ())
777+ util .EmulatorGCDisable ()
778+
779+ var pauseSchedule atomic.Bool
780+ waitSchCh := make (chan struct {})
781+ var closeSchedule sync.Once
782+ releaseSchedule := func () {
783+ pauseSchedule .Store (false )
784+ closeSchedule .Do (func () { close (waitSchCh ) })
785+ }
786+ t .Cleanup (releaseSchedule )
787+ testfailpoint .EnableCall (t , "github.com/pingcap/tidb/pkg/ddl/beforeLoadAndDeliverJobs" , func () {
788+ if pauseSchedule .Load () {
789+ <- waitSchCh
790+ }
791+ })
792+ pauseSchedule .Store (true )
793+
794+ submittedCh := make (chan struct {}, 2 )
795+ submitGate := make (chan struct {})
796+ testfailpoint .EnableCall (t , "github.com/pingcap/tidb/pkg/ddl/waitJobSubmitted" , func () {
797+ submittedCh <- struct {}{}
798+ <- submitGate
799+ })
800+ waitSubmitted := func () {
801+ select {
802+ case <- submittedCh :
803+ submitGate <- struct {}{}
804+ case <- time .After (5 * time .Second ):
805+ require .FailNow (t , "DDL job was not submitted" )
806+ }
807+ }
808+
809+ tkAlter := testkit .NewTestKit (t , store )
810+ tkAlter .MustExec ("use test_recover_schema_snapshot" )
811+ alterDoneCh := make (chan error , 1 )
812+ go func () {
813+ _ , err := tkAlter .Exec ("alter table t drop column col_a" )
814+ alterDoneCh <- err
815+ }()
816+ waitSubmitted ()
817+
818+ tkDrop := testkit .NewTestKit (t , store )
819+ dropDoneCh := make (chan error , 1 )
820+ go func () {
821+ _ , err := tkDrop .Exec ("drop database test_recover_schema_snapshot" )
822+ dropDoneCh <- err
823+ }()
824+ waitSubmitted ()
825+
826+ testfailpoint .Disable (t , "github.com/pingcap/tidb/pkg/ddl/waitJobSubmitted" )
827+ releaseSchedule ()
828+ require .NoError (t , <- alterDoneCh )
829+ require .NoError (t , <- dropDoneCh )
830+
831+ rows := tk .MustQuery ("admin show ddl jobs where db_name = 'test_recover_schema_snapshot' and job_type = 'drop schema'" ).Rows ()
832+ require .NotEmpty (t , rows )
833+ dropJobID , err := strconv .ParseInt (rows [0 ][0 ].(string ), 10 , 64 )
834+ require .NoError (t , err )
835+ dropJob , err := ddl .GetHistoryJobByID (tk .Session (), dropJobID )
836+ require .NoError (t , err )
837+ require .NotNil (t , dropJob )
838+ require .Greater (t , dropJob .RealStartTS , dropJob .StartTS )
839+
840+ gcTimeFormat := "20060102-15:04:05 -0700 MST"
841+ timeBeforeDrop := time .Now ().Add (- 48 * time .Hour ).Format (gcTimeFormat )
842+ safePointSQL := `INSERT HIGH_PRIORITY INTO mysql.tidb VALUES ('tikv_gc_safe_point', '%[1]s', '')
843+ ON DUPLICATE KEY
844+ UPDATE variable_value = '%[1]s'`
845+ tk .MustExec ("delete from mysql.tidb where variable_name in ('tikv_gc_safe_point','tikv_gc_enable')" )
846+ tk .MustExec (fmt .Sprintf (safePointSQL , timeBeforeDrop ))
847+ require .NoError (t , gcutil .EnableGC (tk .Session ()))
848+
849+ tk .MustExec ("flashback database test_recover_schema_snapshot" )
850+ tk .MustQuery ("select column_name from information_schema.columns where table_schema = 'test_recover_schema_snapshot' and table_name = 't' order by ordinal_position" ).Check (testkit .Rows ("id" , "col_b" ))
851+ tk .MustQuery ("select id, col_b from test_recover_schema_snapshot.t" ).Check (testkit .Rows ("1 21" ))
852+ }
853+
646854func TestRecoverTableByJobIDFail (t * testing.T ) {
647855 store := createMockStore (t )
648856 tk := testkit .NewTestKit (t , store )
0 commit comments