github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/entry/schema/snapshot_test.go (about)

     1  // Copyright 2022 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 schema
    15  
    16  import (
    17  	"fmt"
    18  	"testing"
    19  
    20  	timodel "github.com/pingcap/tidb/pkg/parser/model"
    21  	"github.com/pingcap/tiflow/cdc/model"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func TestTablesInSchema(t *testing.T) {
    26  	snap := NewEmptySnapshot(true)
    27  	require.Nil(t, snap.inner.createSchema(newDBInfo(1), 100))
    28  	var vname versionedEntityName
    29  
    30  	vname = newVersionedEntityName(1, "tb1", negative(80))
    31  	vname.target = 1
    32  	snap.inner.tableNameToID.ReplaceOrInsert(vname)
    33  
    34  	vname = newVersionedEntityName(1, "tb1", negative(90))
    35  	vname.target = 2
    36  	snap.inner.tableNameToID.ReplaceOrInsert(vname)
    37  
    38  	vname = newVersionedEntityName(1, "tb1", negative(110))
    39  	vname.target = 3
    40  	snap.inner.tableNameToID.ReplaceOrInsert(vname)
    41  
    42  	vname = newVersionedEntityName(1, "tb2", negative(100))
    43  	vname.target = 4
    44  	snap.inner.tableNameToID.ReplaceOrInsert(vname)
    45  
    46  	vname = newVersionedEntityName(1, "tb3", negative(120))
    47  	vname.target = 5
    48  	snap.inner.tableNameToID.ReplaceOrInsert(vname)
    49  
    50  	vname = newVersionedEntityName(2, "tb1", negative(80))
    51  	vname.target = 6
    52  	snap.inner.tableNameToID.ReplaceOrInsert(vname)
    53  
    54  	require.Equal(t, []int64{2, 4}, snap.inner.tablesInSchema("DB_1"))
    55  
    56  	vname = newVersionedEntityName(1, "tb1", negative(130))
    57  	vname.target = -1
    58  	snap.inner.tableNameToID.ReplaceOrInsert(vname)
    59  	snap.inner.currentTs = 130
    60  	require.Equal(t, []int64{4, 5}, snap.inner.tablesInSchema("DB_1"))
    61  }
    62  
    63  func TestIterSchemas(t *testing.T) {
    64  	snap := NewEmptySnapshot(true)
    65  	require.Nil(t, snap.inner.createSchema(newDBInfo(1), 90))
    66  	require.Nil(t, snap.inner.replaceSchema(newDBInfo(1), 100))
    67  	require.Nil(t, snap.inner.createSchema(newDBInfo(2), 110))
    68  	require.Nil(t, snap.inner.createSchema(newDBInfo(3), 90))
    69  	snap.inner.currentTs = 100
    70  
    71  	var schemas []int64 = make([]int64, 0, 3)
    72  	snap.IterSchemas(func(i *timodel.DBInfo) {
    73  		schemas = append(schemas, i.ID)
    74  	})
    75  	require.Equal(t, []int64{1, 3}, schemas)
    76  }
    77  
    78  func TestSchema(t *testing.T) {
    79  	snap := NewEmptySnapshot(true)
    80  
    81  	// createSchema fails if the schema ID or name already exist.
    82  	dbName := timodel.CIStr{O: "DB_1", L: "db_1"}
    83  	require.Nil(t, snap.inner.createSchema(&timodel.DBInfo{ID: 1, Name: dbName}, 100))
    84  	require.Nil(t, snap.inner.createTable(newTbInfo(1, "DB_1", 11), 100))
    85  	require.Error(t, snap.inner.createSchema(&timodel.DBInfo{ID: 1}, 110))
    86  	require.Error(t, snap.inner.createSchema(&timodel.DBInfo{ID: 2, Name: dbName}, 120))
    87  	snap1 := snap.Copy()
    88  
    89  	// replaceSchema only success if the schema ID exists.
    90  	dbName = timodel.CIStr{O: "DB_2", L: "db_2"}
    91  	require.Error(t, snap.inner.replaceSchema(&timodel.DBInfo{ID: 2}, 130))
    92  	require.Nil(t, snap.inner.replaceSchema(&timodel.DBInfo{ID: 1, Name: dbName}, 140))
    93  	snap2 := snap.Copy()
    94  
    95  	// dropSchema only success if the schema ID exists.
    96  	require.Error(t, snap.inner.dropSchema(2, 150))
    97  	require.Nil(t, snap.inner.dropSchema(1, 170))
    98  	snap3 := snap.Copy()
    99  
   100  	var db *timodel.DBInfo
   101  	var ok bool
   102  
   103  	// The schema and table should be available based on snap1.
   104  	db, ok = snap1.SchemaByID(1)
   105  	require.True(t, ok)
   106  	require.Equal(t, db.Name.O, "DB_1")
   107  	_, ok = snap1.TableIDByName("DB_1", "TB_11")
   108  	require.True(t, ok)
   109  	_, ok = snap1.PhysicalTableByID(11)
   110  	require.True(t, ok)
   111  
   112  	// The schema and table should be available based on snap2, but with a different schema name.
   113  	db, ok = snap2.SchemaByID(1)
   114  	require.True(t, ok)
   115  	require.Equal(t, db.Name.O, "DB_2")
   116  	_, ok = snap2.TableIDByName("DB_2", "TB_11")
   117  	require.True(t, ok)
   118  	_, ok = snap2.PhysicalTableByID(11)
   119  	require.True(t, ok)
   120  	_, ok = snap2.TableIDByName("DB_1", "TB_11")
   121  	require.False(t, ok)
   122  
   123  	// The schema and table should be unavailable based on snap3.
   124  	_, ok = snap3.SchemaByID(1)
   125  	require.False(t, ok)
   126  	_, ok = snap3.PhysicalTableByID(11)
   127  	require.False(t, ok)
   128  	_, ok = snap3.TableIDByName("DB_2", "TB_11")
   129  	require.False(t, ok)
   130  }
   131  
   132  func TestTable(t *testing.T) {
   133  	var ok bool
   134  	for _, forceReplicate := range []bool{true, false} {
   135  		snap := NewEmptySnapshot(forceReplicate)
   136  
   137  		// createTable should check whether the schema or table exist or not.
   138  		require.Error(t, snap.inner.createTable(newTbInfo(1, "DB_1", 11), 100))
   139  		snap.inner.createSchema(newDBInfo(1), 110)
   140  		require.Nil(t, snap.inner.createTable(newTbInfo(1, "DB_1", 11), 120))
   141  		require.Error(t, snap.inner.createTable(newTbInfo(1, "DB_1", 11), 130))
   142  		_, ok = snap.PhysicalTableByID(11)
   143  		require.True(t, ok)
   144  		_, ok = snap.PhysicalTableByID(11 + 65536)
   145  		require.True(t, ok)
   146  		_, ok = snap.TableByName("DB_1", "TB_11")
   147  		require.True(t, ok)
   148  		if !forceReplicate {
   149  			require.True(t, snap.IsIneligibleTableID(11))
   150  			require.True(t, snap.IsIneligibleTableID(11+65536))
   151  		}
   152  
   153  		// replaceTable should check whether the schema or table exist or not.
   154  		require.Error(t, snap.inner.replaceTable(newTbInfo(2, "DB_2", 11), 140))
   155  		require.Error(t, snap.inner.replaceTable(newTbInfo(1, "DB_1", 12), 150))
   156  		require.Nil(t, snap.inner.replaceTable(newTbInfo(1, "DB_1", 11), 160))
   157  		_, ok = snap.PhysicalTableByID(11)
   158  		require.True(t, ok)
   159  		_, ok = snap.PhysicalTableByID(11 + 65536)
   160  		require.True(t, ok)
   161  		_, ok = snap.TableByName("DB_1", "TB_11")
   162  		require.True(t, ok)
   163  		if !forceReplicate {
   164  			require.True(t, snap.IsIneligibleTableID(11))
   165  			require.True(t, snap.IsIneligibleTableID(11+65536))
   166  		}
   167  
   168  		// truncateTable should replace the old one.
   169  		require.Error(t, snap.inner.truncateTable(12, newTbInfo(1, "DB_1", 13), 170))
   170  		require.Nil(t, snap.inner.truncateTable(11, newTbInfo(1, "DB_1", 12), 180))
   171  		_, ok = snap.PhysicalTableByID(11)
   172  		require.False(t, ok)
   173  		_, ok = snap.PhysicalTableByID(11 + 65536)
   174  		require.False(t, ok)
   175  		require.True(t, snap.IsTruncateTableID(11))
   176  		_, ok = snap.PhysicalTableByID(12)
   177  		require.True(t, ok)
   178  		_, ok = snap.PhysicalTableByID(12 + 65536)
   179  		require.True(t, ok)
   180  		_, ok = snap.TableByName("DB_1", "TB_12")
   181  		require.True(t, ok)
   182  		if !forceReplicate {
   183  			require.False(t, snap.IsIneligibleTableID(11))
   184  			require.False(t, snap.IsIneligibleTableID(11+65536))
   185  			require.True(t, snap.IsIneligibleTableID(12))
   186  			require.True(t, snap.IsIneligibleTableID(12+65536))
   187  		}
   188  
   189  		// dropTable should check the table exists or not.
   190  		require.Error(t, snap.inner.dropTable(11, 190))
   191  		require.Nil(t, snap.inner.dropTable(12, 200))
   192  		_, ok = snap.PhysicalTableByID(12)
   193  		require.False(t, ok)
   194  		_, ok = snap.PhysicalTableByID(12 + 65536)
   195  		require.False(t, ok)
   196  		_, ok = snap.TableByName("DB_1", "TB_12")
   197  		require.False(t, ok)
   198  		if !forceReplicate {
   199  			require.False(t, snap.IsIneligibleTableID(12))
   200  			require.False(t, snap.IsIneligibleTableID(12+65536))
   201  		}
   202  		// IterTables should get no available tables.
   203  		require.Equal(t, snap.TableCount(true, func(table, schema string) bool {
   204  			return true
   205  		}), 0)
   206  	}
   207  }
   208  
   209  func TestUpdatePartition(t *testing.T) {
   210  	var oldTb, newTb *model.TableInfo
   211  	var snap1, snap2 *Snapshot
   212  	var info *model.TableInfo
   213  	var ok bool
   214  
   215  	snap := NewEmptySnapshot(false)
   216  	require.Nil(t, snap.inner.createSchema(newDBInfo(1), 100))
   217  
   218  	// updatePartition fails if the old table is not partitioned.
   219  	oldTb = newTbInfo(1, "DB_1", 11)
   220  	oldTb.Partition = nil
   221  	require.Nil(t, snap.inner.createTable(oldTb, 110))
   222  	require.Error(t, snap.inner.updatePartition(newTbInfo(1, "DB_1", 11), false, 120))
   223  
   224  	// updatePartition fails if the new table is not partitioned.
   225  	require.Nil(t, snap.inner.dropTable(11, 130))
   226  	require.Nil(t, snap.inner.createTable(newTbInfo(1, "DB_1", 11), 140))
   227  	newTb = newTbInfo(1, "DB_1", 11)
   228  	newTb.Partition = nil
   229  	require.Error(t, snap.inner.updatePartition(newTb, false, 150))
   230  	snap1 = snap.Copy()
   231  
   232  	newTb = newTbInfo(1, "DB_1", 11)
   233  	newTb.Partition.Definitions[0] = timodel.PartitionDefinition{ID: 11 + 65536*2}
   234  	require.Nil(t, snap.inner.updatePartition(newTb, false, 160))
   235  	snap2 = snap.Copy()
   236  
   237  	info, _ = snap1.PhysicalTableByID(11)
   238  	require.Equal(t, info.Partition.Definitions[0].ID, int64(11+65536))
   239  	_, ok = snap1.PhysicalTableByID(11 + 65536)
   240  	require.True(t, ok)
   241  	require.True(t, snap1.IsIneligibleTableID(11+65536))
   242  	_, ok = snap1.PhysicalTableByID(11 + 65536*2)
   243  	require.False(t, ok)
   244  	require.False(t, snap1.IsIneligibleTableID(11+65536*2))
   245  
   246  	info, _ = snap2.PhysicalTableByID(11)
   247  	require.Equal(t, info.Partition.Definitions[0].ID, int64(11+65536*2))
   248  	_, ok = snap2.PhysicalTableByID(11 + 65536)
   249  	require.False(t, ok)
   250  	require.False(t, snap2.IsIneligibleTableID(11+65536))
   251  	_, ok = snap2.PhysicalTableByID(11 + 65536*2)
   252  	require.True(t, ok)
   253  	require.True(t, snap2.IsIneligibleTableID(11+65536*2))
   254  }
   255  
   256  func TestTruncateTablePartition(t *testing.T) {
   257  	var oldTb, newTb *model.TableInfo
   258  
   259  	snap := NewEmptySnapshot(false)
   260  	require.Nil(t, snap.inner.createSchema(newDBInfo(1), 100))
   261  
   262  	// updatePartition fails if the old table is not partitioned.
   263  	oldTb = newTbInfo(1, "DB_1", 11)
   264  	oldTb.Partition = nil
   265  	require.Nil(t, snap.inner.createTable(oldTb, 110))
   266  	require.Error(t, snap.inner.updatePartition(newTbInfo(1, "DB_1", 11), false, 120))
   267  
   268  	// updatePartition fails if the new table is not partitioned.
   269  	require.Nil(t, snap.inner.dropTable(11, 130))
   270  	require.Nil(t, snap.inner.createTable(newTbInfo(1, "DB_1", 11), 140))
   271  	newTb = newTbInfo(1, "DB_1", 11)
   272  	newTb.Partition = nil
   273  	require.Error(t, snap.inner.updatePartition(newTb, false, 150))
   274  
   275  	newTb = newTbInfo(1, "DB_1", 11)
   276  	newTb.Partition.Definitions[0] = timodel.PartitionDefinition{ID: 11 + 65536*2}
   277  	require.Nil(t, snap.inner.updatePartition(newTb, true, 160))
   278  	require.True(t, snap.IsTruncateTableID(11+65536))
   279  }
   280  
   281  func TestExchangePartition(t *testing.T) {
   282  	var targetTb, sourceTb *model.TableInfo
   283  
   284  	snap := NewEmptySnapshot(false)
   285  	require.Nil(t, snap.inner.createSchema(newDBInfo(1), 100))
   286  
   287  	// exchange partition fails if the target table is not a partitioned table.
   288  	targetTbID := int64(11)
   289  	targetTb = newTbInfo(1, "DB_1", targetTbID)
   290  	targetTb.Partition = nil
   291  	require.Nil(t, snap.inner.createTable(targetTb, 110))
   292  	require.Error(t, snap.inner.exchangePartition(newTbInfo(1, "DB_1", 11), 120))
   293  	require.Nil(t, snap.inner.dropTable(targetTbID, 125))
   294  
   295  	// prepare the target table.
   296  	targetTb = newTbInfo(1, "DB_1", targetTbID)
   297  	p1ID := int64(11 + 65536*1)
   298  	p2ID := int64(11 + 65536*2)
   299  	targetTb.Partition.Definitions[0] = timodel.PartitionDefinition{ID: p1ID}
   300  	targetTb.Partition.Definitions = append(targetTb.Partition.Definitions, timodel.PartitionDefinition{ID: p2ID})
   301  	// update the target table to a partition table.
   302  	require.Nil(t, snap.inner.createTable(targetTb, 140))
   303  
   304  	// create source table.
   305  	sourceTbID := int64(12)
   306  	sourceTb = newTbInfo(1, "DB_1", sourceTbID)
   307  	require.Nil(t, snap.inner.createTable(sourceTb, 150))
   308  
   309  	// exchange partition, p1ID should be exchange by sourceTbID.
   310  	exchangedTargetTb := newTbInfo(1, "DB_1", targetTbID)
   311  	exchangedTargetTb.Partition.Definitions[0] = timodel.PartitionDefinition{ID: sourceTbID}
   312  	exchangedTargetTb.Partition.Definitions = append(exchangedTargetTb.Partition.Definitions, timodel.PartitionDefinition{ID: p2ID})
   313  	require.Nil(t, snap.inner.exchangePartition(exchangedTargetTb, 160))
   314  
   315  	// make sure we can use the exchanged source table's id to get the target table info,
   316  	// since the source table has become a partition of target table.
   317  	tarInfo1, _ := snap.PhysicalTableByID(targetTbID)
   318  	require.Equal(t, tarInfo1.Partition.Definitions[0].ID, sourceTbID)
   319  	tarInfo2, ok := snap.PhysicalTableByID(sourceTbID)
   320  	require.True(t, ok)
   321  	require.Equal(t, tarInfo1.Name, tarInfo2.Name)
   322  
   323  	// make sure we can use the exchanged partition's id to get the source table info.
   324  	exchangedSourceTb, ok := snap.PhysicalTableByID(p1ID)
   325  	require.True(t, ok)
   326  	require.Equal(t, sourceTb.Name, exchangedSourceTb.Name)
   327  }
   328  
   329  func TestDrop(t *testing.T) {
   330  	snap := NewEmptySnapshot(false)
   331  
   332  	require.Nil(t, snap.inner.createSchema(newDBInfo(1), 11))
   333  	require.Nil(t, snap.inner.createSchema(newDBInfo(2), 12))
   334  	require.Nil(t, snap.inner.replaceSchema(newDBInfo(2), 13))
   335  	require.Nil(t, snap.inner.dropSchema(2, 14))
   336  
   337  	require.Nil(t, snap.inner.createTable(newTbInfo(1, "DB_1", 3), 15))
   338  	require.Nil(t, snap.inner.createTable(newTbInfo(1, "DB_1", 4), 16))
   339  	require.Nil(t, snap.inner.replaceTable(newTbInfo(1, "DB_1", 4), 17))
   340  	require.Nil(t, snap.inner.truncateTable(4, newTbInfo(1, "DB_1", 5), 18))
   341  	require.Nil(t, snap.inner.dropTable(5, 19))
   342  	snap.Drop()
   343  
   344  	// After the latest snapshot is dropped, check schema and table count.
   345  	require.Equal(t, 1, snap.inner.schemas.Len())
   346  	require.Equal(t, 1, snap.inner.tables.Len())
   347  	require.Equal(t, 1, snap.inner.schemaNameToID.Len())
   348  	require.Equal(t, 1, snap.inner.tableNameToID.Len())
   349  	require.Equal(t, 1, snap.inner.partitions.Len())
   350  	require.Equal(t, 0, snap.inner.truncatedTables.Len())
   351  	require.Equal(t, 2, snap.inner.ineligibleTables.Len())
   352  }
   353  
   354  func newDBInfo(id int64) *timodel.DBInfo {
   355  	return &timodel.DBInfo{
   356  		ID: id,
   357  		Name: timodel.CIStr{
   358  			O: fmt.Sprintf("DB_%d", id),
   359  			L: fmt.Sprintf("db_%d", id),
   360  		},
   361  	}
   362  }
   363  
   364  // newTbInfo constructs a test TableInfo with a partition and a sequence.
   365  // The partition ID will be tableID + 65536.
   366  func newTbInfo(schemaID int64, schemaName string, tableID int64) *model.TableInfo {
   367  	return &model.TableInfo{
   368  		TableInfo: &timodel.TableInfo{
   369  			ID: tableID,
   370  			Name: timodel.CIStr{
   371  				O: fmt.Sprintf("TB_%d", tableID),
   372  				L: fmt.Sprintf("TB_%d", tableID),
   373  			},
   374  			Partition: &timodel.PartitionInfo{
   375  				Enable:      true,
   376  				Definitions: []timodel.PartitionDefinition{{ID: 65536 + tableID}},
   377  			},
   378  			Sequence: &timodel.SequenceInfo{Start: 0},
   379  		},
   380  		SchemaID: schemaID,
   381  		TableName: model.TableName{
   382  			Schema: schemaName,
   383  			Table:  fmt.Sprintf("TB_%d", tableID),
   384  		},
   385  	}
   386  }