github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/owner/ddl_manager_test.go (about)

     1  // Copyright 2023 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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package owner
    15  
    16  import (
    17  	"context"
    18  	"encoding/json"
    19  	"fmt"
    20  	"testing"
    21  
    22  	timodel "github.com/pingcap/tidb/pkg/parser/model"
    23  	"github.com/pingcap/tiflow/cdc/entry"
    24  	"github.com/pingcap/tiflow/cdc/model"
    25  	"github.com/pingcap/tiflow/cdc/redo"
    26  	"github.com/pingcap/tiflow/cdc/scheduler/schedulepb"
    27  	config2 "github.com/pingcap/tiflow/pkg/config"
    28  	"github.com/pingcap/tiflow/pkg/filter"
    29  	"github.com/pingcap/tiflow/pkg/util"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  func createDDLManagerForTest(t *testing.T) *ddlManager {
    34  	startTs, checkpointTs := model.Ts(0), model.Ts(1)
    35  	changefeedID := model.DefaultChangeFeedID("ddl-manager-test")
    36  	ddlSink := &mockDDLSink{}
    37  	ddlPuller := &mockDDLPuller{}
    38  	cfg := config2.GetDefaultReplicaConfig()
    39  	f, err := filter.NewFilter(cfg, "")
    40  	require.Nil(t, err)
    41  	schema, err := entry.NewSchemaStorage(nil, startTs, cfg.ForceReplicate, changefeedID, util.RoleTester, f)
    42  	require.Equal(t, nil, err)
    43  	res := newDDLManager(
    44  		changefeedID,
    45  		startTs,
    46  		checkpointTs,
    47  		ddlSink,
    48  		f,
    49  		ddlPuller,
    50  		schema,
    51  		redo.NewDisabledDDLManager(),
    52  		redo.NewDisabledMetaManager(),
    53  		false)
    54  	return res
    55  }
    56  
    57  func newFakeDDLEvent(
    58  	tableID int64,
    59  	tableName string,
    60  	actionType timodel.ActionType,
    61  	commitTs uint64,
    62  ) *model.DDLEvent {
    63  	info := &model.TableInfo{
    64  		TableName: model.TableName{Table: tableName, TableID: tableID},
    65  	}
    66  	info.TableInfo = &timodel.TableInfo{
    67  		ID:   tableID,
    68  		Name: timodel.NewCIStr(tableName),
    69  	}
    70  	return &model.DDLEvent{
    71  		TableInfo: info,
    72  		Type:      actionType,
    73  		CommitTs:  commitTs,
    74  	}
    75  }
    76  
    77  func TestGetNextDDL(t *testing.T) {
    78  	dm := createDDLManagerForTest(t)
    79  	dm.executingDDL = newFakeDDLEvent(1,
    80  		"test_1", timodel.ActionDropColumn, 1)
    81  	require.Equal(t, dm.executingDDL, dm.getNextDDL())
    82  
    83  	dm.executingDDL = nil
    84  	ddl1 := newFakeDDLEvent(1,
    85  		"test_1", timodel.ActionDropColumn, 1)
    86  	ddl2 := newFakeDDLEvent(2,
    87  		"test_2", timodel.ActionDropColumn, 2)
    88  	dm.pendingDDLs[ddl1.TableInfo.TableName] = append(dm.
    89  		pendingDDLs[ddl1.TableInfo.TableName], ddl1)
    90  	dm.pendingDDLs[ddl2.TableInfo.TableName] = append(dm.
    91  		pendingDDLs[ddl2.TableInfo.TableName], ddl2)
    92  	require.Equal(t, ddl1, dm.getNextDDL())
    93  }
    94  
    95  func TestBarriers(t *testing.T) {
    96  	dm := createDDLManagerForTest(t)
    97  
    98  	tableID1 := int64(1)
    99  	tableName1 := model.TableName{Table: "test_1", TableID: tableID1}
   100  	// this ddl commitTs will be minTableBarrierTs
   101  	dm.justSentDDL = newFakeDDLEvent(tableID1,
   102  		"test_1", timodel.ActionDropColumn, 1)
   103  	dm.pendingDDLs[tableName1] = append(dm.pendingDDLs[tableName1],
   104  		newFakeDDLEvent(tableID1, tableName1.Table, timodel.ActionAddColumn, 2))
   105  
   106  	tableID2 := int64(2)
   107  	tableName2 := model.TableName{Table: "test_2", TableID: tableID2}
   108  	dm.pendingDDLs[tableName2] = append(dm.pendingDDLs[tableName2],
   109  		// this ddl commitTs will become globalBarrierTs
   110  		newFakeDDLEvent(tableID2, tableName2.Table, timodel.ActionCreateTable, 4))
   111  
   112  	expectedMinTableBarrier := uint64(1)
   113  	expectedBarrier := &schedulepb.Barrier{
   114  		GlobalBarrierTs: 4,
   115  		TableBarriers: []*schedulepb.TableBarrier{
   116  			{
   117  				TableID:   tableID1,
   118  				BarrierTs: 1,
   119  			},
   120  		},
   121  	}
   122  	// advance the ddlResolvedTs
   123  	dm.ddlResolvedTs = 6
   124  	ddlBarrier := dm.barrier()
   125  	minTableBarrierTs, barrier := ddlBarrier.MinTableBarrierTs, ddlBarrier.Barrier
   126  	require.Equal(t, expectedMinTableBarrier, minTableBarrierTs)
   127  	require.Equal(t, expectedBarrier, barrier)
   128  
   129  	// test tableBarrier limit
   130  	dm.pendingDDLs = make(map[model.TableName][]*model.DDLEvent)
   131  	dm.ddlResolvedTs = 1024
   132  	for i := 0; i < 512; i++ {
   133  		tableID := int64(i)
   134  		tableName := model.TableName{Table: fmt.Sprintf("test_%d", i), TableID: tableID}
   135  		dm.pendingDDLs[tableName] = append(dm.pendingDDLs[tableName],
   136  			newFakeDDLEvent(tableID, tableName.Table, timodel.ActionAddColumn, uint64(i)))
   137  	}
   138  	ddlBarrier = dm.barrier()
   139  	minTableBarrierTs, barrier = ddlBarrier.MinTableBarrierTs, ddlBarrier.Barrier
   140  	require.Equal(t, uint64(0), minTableBarrierTs)
   141  	require.Equal(t, uint64(256), barrier.GlobalBarrierTs)
   142  	require.Equal(t, 256, len(barrier.TableBarriers))
   143  }
   144  
   145  func TestGetSnapshotTs(t *testing.T) {
   146  	dm := createDDLManagerForTest(t)
   147  	dm.startTs = 0
   148  	dm.checkpointTs = 1
   149  	require.Equal(t, dm.startTs, dm.getSnapshotTs())
   150  
   151  	dm.startTs = 1
   152  	dm.checkpointTs = 10
   153  	dm.BDRMode = true
   154  	dm.ddlResolvedTs = 15
   155  	require.Equal(t, dm.checkpointTs, dm.getSnapshotTs())
   156  
   157  	dm.startTs = 1
   158  	dm.checkpointTs = 10
   159  	dm.BDRMode = false
   160  	require.Equal(t, dm.checkpointTs, dm.getSnapshotTs())
   161  }
   162  
   163  func TestExecRenameTablesDDL(t *testing.T) {
   164  	helper := entry.NewSchemaTestHelper(t)
   165  	defer helper.Close()
   166  	ctx := context.Background()
   167  	dm := createDDLManagerForTest(t)
   168  	mockDDLSink := dm.ddlSink.(*mockDDLSink)
   169  
   170  	var oldSchemaIDs, newSchemaIDs, oldTableIDs []int64
   171  	var newTableNames, oldSchemaNames []timodel.CIStr
   172  
   173  	execCreateStmt := func(tp, actualDDL, expectedDDL string) {
   174  		mockDDLSink.ddlDone = false
   175  		job := helper.DDL2Job(actualDDL)
   176  		dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1)
   177  		events, err := dm.schema.BuildDDLEvents(ctx, job)
   178  		require.Nil(t, err)
   179  		err = dm.schema.HandleDDLJob(job)
   180  		require.Nil(t, err)
   181  
   182  		for _, event := range events {
   183  			done, err := dm.ddlSink.emitDDLEvent(ctx, event)
   184  			if tp == "database" {
   185  				oldSchemaIDs = append(oldSchemaIDs, job.SchemaID)
   186  			} else {
   187  				oldTableIDs = append(oldTableIDs, job.TableID)
   188  			}
   189  			require.Nil(t, err)
   190  			require.Equal(t, false, done)
   191  			require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query)
   192  
   193  			mockDDLSink.ddlDone = true
   194  			done, err = dm.ddlSink.emitDDLEvent(ctx, event)
   195  			require.Nil(t, err)
   196  			require.Equal(t, true, done)
   197  			require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query)
   198  		}
   199  	}
   200  
   201  	execCreateStmt("database", "create database test1",
   202  		"create database test1")
   203  	execCreateStmt("table", "create table test1.tb1(id int primary key)",
   204  		"create table test1.tb1(id int primary key)")
   205  	execCreateStmt("database", "create database test2",
   206  		"create database test2")
   207  	execCreateStmt("table", "create table test2.tb2(id int primary key)",
   208  		"create table test2.tb2(id int primary key)")
   209  
   210  	require.Len(t, oldSchemaIDs, 2)
   211  	require.Len(t, oldTableIDs, 2)
   212  	newSchemaIDs = []int64{oldSchemaIDs[1], oldSchemaIDs[0]}
   213  	oldSchemaNames = []timodel.CIStr{
   214  		timodel.NewCIStr("test1"),
   215  		timodel.NewCIStr("test2"),
   216  	}
   217  	newTableNames = []timodel.CIStr{
   218  		timodel.NewCIStr("tb20"),
   219  		timodel.NewCIStr("tb10"),
   220  	}
   221  	require.Len(t, newSchemaIDs, 2)
   222  	require.Len(t, oldSchemaNames, 2)
   223  	require.Len(t, newTableNames, 2)
   224  	args := []interface{}{
   225  		oldSchemaIDs, newSchemaIDs, newTableNames,
   226  		oldTableIDs, oldSchemaNames,
   227  	}
   228  	rawArgs, err := json.Marshal(args)
   229  	require.Nil(t, err)
   230  	job := helper.DDL2Job(
   231  		"rename table test1.tb1 to test2.tb10, test2.tb2 to test1.tb20")
   232  	// the RawArgs field in job fetched from tidb snapshot meta is incorrent,
   233  	// so we manually construct `job.RawArgs` to do the workaround.
   234  	job.RawArgs = rawArgs
   235  
   236  	mockDDLSink.recordDDLHistory = true
   237  	mockDDLSink.ddlDone = false
   238  	dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1)
   239  	events, err := dm.schema.BuildDDLEvents(ctx, job)
   240  	require.Nil(t, err)
   241  	for _, event := range events {
   242  		done, err := dm.ddlSink.emitDDLEvent(ctx, event)
   243  		require.Nil(t, err)
   244  		require.Equal(t, false, done)
   245  
   246  	}
   247  	require.Len(t, mockDDLSink.ddlHistory, 2)
   248  	require.Equal(t, "RENAME TABLE `test1`.`tb1` TO `test2`.`tb10`",
   249  		mockDDLSink.ddlHistory[0])
   250  	require.Equal(t, "RENAME TABLE `test2`.`tb2` TO `test1`.`tb20`",
   251  		mockDDLSink.ddlHistory[1])
   252  
   253  	// mock all rename table statements have been done
   254  	mockDDLSink.resetDDLDone = false
   255  	mockDDLSink.ddlDone = true
   256  	for _, event := range events {
   257  		done, err := dm.ddlSink.emitDDLEvent(ctx, event)
   258  		require.Nil(t, err)
   259  		require.Equal(t, true, done)
   260  	}
   261  }
   262  
   263  func TestExecDropTablesDDL(t *testing.T) {
   264  	helper := entry.NewSchemaTestHelper(t)
   265  	defer helper.Close()
   266  
   267  	ctx := context.Background()
   268  	dm := createDDLManagerForTest(t)
   269  	mockDDLSink := dm.ddlSink.(*mockDDLSink)
   270  
   271  	execCreateStmt := func(actualDDL, expectedDDL string) {
   272  		job := helper.DDL2Job(actualDDL)
   273  		dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1)
   274  		events, err := dm.schema.BuildDDLEvents(ctx, job)
   275  		require.Nil(t, err)
   276  		err = dm.schema.HandleDDLJob(job)
   277  		require.Nil(t, err)
   278  		mockDDLSink.ddlDone = false
   279  
   280  		for _, event := range events {
   281  			done, err := dm.ddlSink.emitDDLEvent(ctx, event)
   282  			require.Nil(t, err)
   283  			require.Equal(t, false, done)
   284  			require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query)
   285  			mockDDLSink.ddlDone = true
   286  			done, err = dm.ddlSink.emitDDLEvent(ctx, event)
   287  			require.Nil(t, err)
   288  			require.Equal(t, true, done)
   289  		}
   290  	}
   291  
   292  	execCreateStmt("create database test1",
   293  		"create database test1")
   294  	execCreateStmt("create table test1.tb1(id int primary key)",
   295  		"create table test1.tb1(id int primary key)")
   296  	execCreateStmt("create table test1.tb2(id int primary key)",
   297  		"create table test1.tb2(id int primary key)")
   298  
   299  	// drop tables is different from rename tables, it will generate
   300  	// multiple DDL jobs instead of one.
   301  	jobs := helper.DDL2Jobs("drop table test1.tb1, test1.tb2", 2)
   302  	require.Len(t, jobs, 2)
   303  
   304  	execDropStmt := func(job *timodel.Job, expectedDDL string) {
   305  		dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1)
   306  		events, err := dm.schema.BuildDDLEvents(ctx, job)
   307  		require.Nil(t, err)
   308  		err = dm.schema.HandleDDLJob(job)
   309  		require.Nil(t, err)
   310  		mockDDLSink.ddlDone = false
   311  
   312  		for _, event := range events {
   313  			done, err := dm.ddlSink.emitDDLEvent(ctx, event)
   314  			require.Nil(t, err)
   315  			require.Equal(t, false, done)
   316  			require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query)
   317  			mockDDLSink.ddlDone = true
   318  			done, err = dm.ddlSink.emitDDLEvent(ctx, event)
   319  			require.Nil(t, err)
   320  			require.Equal(t, true, done)
   321  		}
   322  	}
   323  
   324  	execDropStmt(jobs[0], "DROP TABLE `test1`.`tb2`")
   325  	execDropStmt(jobs[1], "DROP TABLE `test1`.`tb1`")
   326  }
   327  
   328  func TestExecDropViewsDDL(t *testing.T) {
   329  	helper := entry.NewSchemaTestHelper(t)
   330  	defer helper.Close()
   331  
   332  	ctx := context.Background()
   333  	dm := createDDLManagerForTest(t)
   334  	mockDDLSink := dm.ddlSink.(*mockDDLSink)
   335  
   336  	execCreateStmt := func(actualDDL, expectedDDL string) {
   337  		job := helper.DDL2Job(actualDDL)
   338  		dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1)
   339  		events, err := dm.schema.BuildDDLEvents(ctx, job)
   340  		require.Nil(t, err)
   341  		err = dm.schema.HandleDDLJob(job)
   342  		require.Nil(t, err)
   343  		mockDDLSink.ddlDone = false
   344  		for _, event := range events {
   345  			done, err := dm.ddlSink.emitDDLEvent(ctx, event)
   346  			require.Nil(t, err)
   347  			require.Equal(t, false, done)
   348  			require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query)
   349  			mockDDLSink.ddlDone = true
   350  			done, err = dm.ddlSink.emitDDLEvent(ctx, event)
   351  			require.Nil(t, err)
   352  			require.Equal(t, true, done)
   353  		}
   354  	}
   355  
   356  	execCreateStmt("create database test1",
   357  		"create database test1")
   358  	execCreateStmt("create table test1.tb1(id int primary key)",
   359  		"create table test1.tb1(id int primary key)")
   360  	execCreateStmt("create view test1.view1 as "+
   361  		"select * from test1.tb1 where id > 100",
   362  		"create view test1.view1 as "+
   363  			"select * from test1.tb1 where id > 100")
   364  	execCreateStmt("create view test1.view2 as "+
   365  		"select * from test1.tb1 where id > 200",
   366  		"create view test1.view2 as "+
   367  			"select * from test1.tb1 where id > 200")
   368  
   369  	// drop views is similar to drop tables, it will also generate
   370  	// multiple DDL jobs.
   371  	jobs := helper.DDL2Jobs("drop view test1.view1, test1.view2", 2)
   372  	require.Len(t, jobs, 2)
   373  
   374  	execDropStmt := func(job *timodel.Job, expectedDDL string) {
   375  		dm.schema.AdvanceResolvedTs(job.BinlogInfo.FinishedTS - 1)
   376  		events, err := dm.schema.BuildDDLEvents(ctx, job)
   377  		require.Nil(t, err)
   378  		err = dm.schema.HandleDDLJob(job)
   379  		require.Nil(t, err)
   380  		mockDDLSink.ddlDone = false
   381  		for _, event := range events {
   382  			done, err := dm.ddlSink.emitDDLEvent(ctx, event)
   383  			require.Nil(t, err)
   384  			require.Equal(t, false, done)
   385  			require.Equal(t, expectedDDL, mockDDLSink.ddlExecuting.Query)
   386  			mockDDLSink.ddlDone = true
   387  			done, err = dm.ddlSink.emitDDLEvent(ctx, event)
   388  			require.Nil(t, err)
   389  			require.Equal(t, true, done)
   390  		}
   391  	}
   392  
   393  	execDropStmt(jobs[0], "DROP VIEW `test1`.`view2`")
   394  	execDropStmt(jobs[1], "DROP VIEW `test1`.`view1`")
   395  }
   396  
   397  func TestIsGlobalDDL(t *testing.T) {
   398  	cases := []struct {
   399  		ddl *model.DDLEvent
   400  		ret bool
   401  	}{
   402  		{
   403  			ddl: &model.DDLEvent{
   404  				Type: timodel.ActionCreateSchema,
   405  			},
   406  			ret: true,
   407  		},
   408  		{
   409  			ddl: &model.DDLEvent{
   410  				Type: timodel.ActionDropSchema,
   411  			},
   412  			ret: true,
   413  		},
   414  		{
   415  			ddl: &model.DDLEvent{
   416  				Type: timodel.ActionCreateTable,
   417  			},
   418  			ret: true,
   419  		},
   420  		{
   421  			ddl: &model.DDLEvent{
   422  				Type: timodel.ActionRenameTables,
   423  			},
   424  			ret: true,
   425  		},
   426  		{
   427  			ddl: &model.DDLEvent{
   428  				Type: timodel.ActionRenameTable,
   429  			},
   430  			ret: true,
   431  		},
   432  		{
   433  			ddl: &model.DDLEvent{
   434  				Type: timodel.ActionExchangeTablePartition,
   435  			},
   436  			ret: true,
   437  		},
   438  		{
   439  			ddl: &model.DDLEvent{
   440  				Type: timodel.ActionModifySchemaCharsetAndCollate,
   441  			},
   442  			ret: true,
   443  		},
   444  		{
   445  			ddl: &model.DDLEvent{
   446  				Type: timodel.ActionTruncateTable,
   447  			},
   448  			ret: false,
   449  		},
   450  		{
   451  			ddl: &model.DDLEvent{
   452  				Type: timodel.ActionDropColumn,
   453  			},
   454  			ret: false,
   455  		},
   456  	}
   457  
   458  	for _, c := range cases {
   459  		require.Equal(t, c.ret, isGlobalDDL(c.ddl))
   460  	}
   461  }