Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions downstreamadapter/routing/ddl_query_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (r Router) rewriteParserBackedDDLQuery(ddl *commonEvent.DDLEvent) (string,
)
for i := range queries {
query := queries[i]
newQuery, err := r.rewriteSingleDDLQuery(query)
newQuery, err := r.rewriteSingleDDLQuery(query, ddl.GetSchemaName())
if err != nil {
return "", err
}
Expand Down Expand Up @@ -78,7 +78,7 @@ func splitMultiStmtDDLQuery(query string) ([]string, error) {
return queries, nil
}

func (r Router) rewriteSingleDDLQuery(query string) (string, error) {
func (r Router) rewriteSingleDDLQuery(query string, defaultSchema string) (string, error) {
p := parser.New()
stmt, err := p.ParseOneStmt(query, "", "")
if err != nil {
Expand All @@ -89,6 +89,7 @@ func (r Router) rewriteSingleDDLQuery(query string) (string, error) {
if len(sourceTables) == 0 {
return query, nil
}
fillDefaultSchema(sourceTables, defaultSchema)

var (
routed bool
Expand Down Expand Up @@ -119,6 +120,18 @@ func (r Router) rewriteSingleDDLQuery(query string) (string, error) {
return newQuery, nil
}

func fillDefaultSchema(tables []commonEvent.SchemaTableName, defaultSchema string) {
if defaultSchema == "" {
return
}

for i := range tables {
if tables[i].SchemaName == "" && tables[i].TableName != "" {
tables[i].SchemaName = defaultSchema
}
}
}

// tableNameExtractor extracts table names from DDL AST nodes.
// ref: https://github.com/pingcap/tidb/blob/09feccb529be2830944e11f5fed474020f50370f/server/sql_info_fetcher.go#L46
type tableNameExtractor struct {
Expand Down
2 changes: 1 addition & 1 deletion downstreamadapter/routing/router_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ func TestRewriteParserBackedDDLQueryError(t *testing.T) {
TargetTable: TablePlaceholder,
}})

_, err := router.rewriteSingleDDLQuery("INVALID SQL !!!")
_, err := router.rewriteSingleDDLQuery("INVALID SQL !!!", "")
code, ok := errors.RFCCode(err)
require.True(t, ok)
require.Equal(t, errors.ErrTableRoutingFailed.RFCCode(), code)
Expand Down
82 changes: 66 additions & 16 deletions downstreamadapter/routing/router_supported_ddl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ package routing
import (
"testing"

"github.com/pingcap/ticdc/pkg/common"
"github.com/pingcap/ticdc/pkg/common/event"
"github.com/pingcap/ticdc/pkg/config"
ddlutil "github.com/pingcap/tidb/pkg/ddl/util"
timodel "github.com/pingcap/tidb/pkg/meta/model"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -1007,7 +1009,7 @@ func TestRewriteDDLQueryWithRoutingSupportsParserBackedDDLTypes(t *testing.T) {
}
}

func TestApplyToDDLEventSupportsCreateTables(t *testing.T) {
func TestApplyToDDLEventRoutesDDLEventMetadata(t *testing.T) {
router := newTestRouter(t, false, []*config.DispatchRule{{
Matcher: []string{"source_db.*"},
TargetSchema: "target_db",
Expand All @@ -1016,25 +1018,73 @@ func TestApplyToDDLEventSupportsCreateTables(t *testing.T) {

helper := event.NewEventTestHelper(t)
defer helper.Close()
schemaDDL := helper.DDL2Event("CREATE DATABASE `source_db`")
// TiDB ActionCreateTables is same-schema only. Cross-schema CREATE TABLE
// statements are emitted as separate DDL jobs upstream, not one ActionCreateTables event.
ddl := helper.BatchCreateTableDDLs2Event("source_db",
"CREATE TABLE `source_db`.`t1` (`id` INT PRIMARY KEY)",
"CREATE TABLE `source_db`.`t2` (`id` INT PRIMARY KEY)",
)

schemaDDL := helper.DDL2Event("CREATE DATABASE `source_db`")
routedSchema, err := router.ApplyToDDLEvent(schemaDDL)
require.NoError(t, err)
require.Contains(t, routedSchema.Query, "`target_db`")

routed, err := router.ApplyToDDLEvent(ddl)
helper.Tk().MustExec("USE `source_db`")
singleCreateDDL := helper.DDL2Event("CREATE TABLE `source_table` (`id` INT PRIMARY KEY)")
singleCreateDDL.DispatcherID = common.NewDispatcherID()
singleCreateDDL.Seq = 1
singleCreateDDL.Epoch = 2
singleCreateDDL.TiDBOnly = true
singleCreateDDL.BDRMode = string(ast.BDRRolePrimary)
singleCreateDDL.PostTxnFlushed = []func(){func() {}, func() {}}

originalQuery := singleCreateDDL.Query
originalTableInfo := singleCreateDDL.TableInfo
require.Equal(t, "source_db", singleCreateDDL.SchemaName)
require.NotContains(t, originalQuery, "`source_db`")
routedSingleCreate, err := router.ApplyToDDLEvent(singleCreateDDL)
require.NoError(t, err)
require.NotSame(t, singleCreateDDL, routedSingleCreate)
require.Equal(t, singleCreateDDL.Version, routedSingleCreate.Version)
require.Equal(t, singleCreateDDL.DispatcherID, routedSingleCreate.DispatcherID)
require.Equal(t, singleCreateDDL.Type, routedSingleCreate.Type)
require.Equal(t, singleCreateDDL.SchemaID, routedSingleCreate.SchemaID)
require.Equal(t, singleCreateDDL.SchemaName, routedSingleCreate.SchemaName)
require.Equal(t, singleCreateDDL.TableName, routedSingleCreate.TableName)
require.Equal(t, singleCreateDDL.FinishedTs, routedSingleCreate.FinishedTs)
require.Equal(t, singleCreateDDL.Seq, routedSingleCreate.Seq)
require.Equal(t, singleCreateDDL.Epoch, routedSingleCreate.Epoch)
require.Equal(t, singleCreateDDL.TiDBOnly, routedSingleCreate.TiDBOnly)
require.Equal(t, singleCreateDDL.BDRMode, routedSingleCreate.BDRMode)
require.Equal(t, originalQuery, singleCreateDDL.Query)
require.Same(t, originalTableInfo, singleCreateDDL.TableInfo)
require.Equal(t, "source_db", singleCreateDDL.GetTargetSchemaName())
require.Equal(t, "source_table", singleCreateDDL.GetTargetTableName())
require.Equal(t, "target_db", routedSingleCreate.GetTargetSchemaName())
require.Equal(t, "source_table_r", routedSingleCreate.GetTargetTableName())
require.Contains(t, routedSingleCreate.Query, "`target_db`.`source_table_r`")
require.NotContains(t, routedSingleCreate.Query, "`source_db`")
require.NotSame(t, originalTableInfo, routedSingleCreate.TableInfo)
require.Equal(t, "target_db", routedSingleCreate.TableInfo.GetTargetSchemaName())
require.Equal(t, "source_table_r", routedSingleCreate.TableInfo.GetTargetTableName())
require.Len(t, routedSingleCreate.PostTxnFlushed, 2)
require.Len(t, singleCreateDDL.PostTxnFlushed, 2)
require.NotEqual(t, &singleCreateDDL.PostTxnFlushed[0], &routedSingleCreate.PostTxnFlushed[0])
routedSingleCreate.AddPostFlushFunc(func() {})
require.Len(t, routedSingleCreate.PostTxnFlushed, 3)
require.Len(t, singleCreateDDL.PostTxnFlushed, 2)

createTablesDDL := helper.BatchCreateTableDDLs2Event("source_db",
"CREATE TABLE `source_db`.`t1` (`id` INT PRIMARY KEY)",
"CREATE TABLE `source_db`.`t2` (`id` INT PRIMARY KEY)",
)
routedCreateTables, err := router.ApplyToDDLEvent(createTablesDDL)
require.NoError(t, err)
require.Contains(t, routed.Query, "CREATE TABLE `target_db`.`t1_r`")
require.Contains(t, routed.Query, "CREATE TABLE `target_db`.`t2_r`")
require.Len(t, routed.MultipleTableInfos, 2)
require.Equal(t, "target_db", routed.MultipleTableInfos[0].GetTargetSchemaName())
require.Equal(t, "t1_r", routed.MultipleTableInfos[0].GetTargetTableName())
require.Equal(t, "target_db", routed.MultipleTableInfos[1].GetTargetSchemaName())
require.Equal(t, "t2_r", routed.MultipleTableInfos[1].GetTargetTableName())
require.Contains(t, routedCreateTables.Query, "CREATE TABLE `target_db`.`t1_r`")
require.Contains(t, routedCreateTables.Query, "CREATE TABLE `target_db`.`t2_r`")
require.Len(t, createTablesDDL.MultipleTableInfos, 2)
require.Len(t, routedCreateTables.MultipleTableInfos, 2)
require.Equal(t, "source_db", createTablesDDL.MultipleTableInfos[0].GetTargetSchemaName())
require.Equal(t, "t1", createTablesDDL.MultipleTableInfos[0].GetTargetTableName())
require.Equal(t, "source_db", createTablesDDL.MultipleTableInfos[1].GetTargetSchemaName())
require.Equal(t, "t2", createTablesDDL.MultipleTableInfos[1].GetTargetTableName())
require.Equal(t, "target_db", routedCreateTables.MultipleTableInfos[0].GetTargetSchemaName())
require.Equal(t, "t1_r", routedCreateTables.MultipleTableInfos[0].GetTargetTableName())
require.Equal(t, "target_db", routedCreateTables.MultipleTableInfos[1].GetTargetSchemaName())
require.Equal(t, "t2_r", routedCreateTables.MultipleTableInfos[1].GetTargetTableName())
}
200 changes: 0 additions & 200 deletions pkg/common/event/ddl_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import (

"github.com/pingcap/ticdc/pkg/common"
"github.com/pingcap/ticdc/pkg/errors"
"github.com/pingcap/tidb/pkg/meta/model"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -523,201 +521,3 @@ CREATE TABLE test2 (id INT);
})
}
}

// TestNewRoutedDDLEvent ensures routed DDL construction preserves the origin event
// while producing an independent routed event for downstream use.
func TestNewRoutedDDLEvent(t *testing.T) {
helper := NewEventTestHelper(t)
defer helper.Close()

helper.tk.MustExec("use test")
ddlJob := helper.DDL2Job(createTableSQL)
require.NotNil(t, ddlJob)

// Create original DDL event with all fields populated
originalTableInfo := common.WrapTableInfo(ddlJob.SchemaName, ddlJob.BinlogInfo.TableInfo)
originalTableInfo.InitPrivateFields()

multipleTableInfo1 := common.WrapTableInfo("schema1", ddlJob.BinlogInfo.TableInfo)
multipleTableInfo1.InitPrivateFields()
multipleTableInfo2 := common.WrapTableInfo("schema2", ddlJob.BinlogInfo.TableInfo)
multipleTableInfo2.InitPrivateFields()

postFlushFunc1 := func() {}
postFlushFunc2 := func() {}

original := &DDLEvent{
Version: DDLEventVersion1,
DispatcherID: common.NewDispatcherID(),
Type: byte(ddlJob.Type),
SchemaID: ddlJob.SchemaID,
SchemaName: ddlJob.SchemaName,
TableName: ddlJob.TableName,
Query: ddlJob.Query,
TableInfo: originalTableInfo,
FinishedTs: ddlJob.BinlogInfo.FinishedTS,
Seq: 1,
Epoch: 2,
MultipleTableInfos: []*common.TableInfo{multipleTableInfo1, multipleTableInfo2},
PostTxnFlushed: []func(){postFlushFunc1, postFlushFunc2},
TiDBOnly: true,
BDRMode: "test-mode",
}

newRoutedTableInfo := originalTableInfo.CloneWithRouting("routed_schema", "test")
routedMultipleTableInfos := []*common.TableInfo{
multipleTableInfo1.CloneWithRouting("routed_schema1", "table1"),
multipleTableInfo2.CloneWithRouting("routed_schema2", "table2"),
}

routed := NewRoutedDDLEvent(
original,
"CREATE TABLE routed_schema.test ...",
"routed_schema",
"",
"",
"",
newRoutedTableInfo,
routedMultipleTableInfos,
original.BlockedTableNames,
)
require.NotNil(t, routed)

// Verify that the routed event is a separate object.
require.False(t, original == routed, "routed event should be a different object")

// Verify that non-routing fields are copied as-is.
require.Equal(t, original.Version, routed.Version)
require.Equal(t, original.DispatcherID, routed.DispatcherID)
require.Equal(t, original.Type, routed.Type)
require.Equal(t, original.SchemaID, routed.SchemaID)
require.Equal(t, original.SchemaName, routed.SchemaName)
require.Equal(t, original.TableName, routed.TableName)
require.Equal(t, original.FinishedTs, routed.FinishedTs)
require.Equal(t, original.Seq, routed.Seq)
require.Equal(t, original.Epoch, routed.Epoch)
require.Equal(t, original.TiDBOnly, routed.TiDBOnly)
require.Equal(t, original.BDRMode, routed.BDRMode)

// Verify that MultipleTableInfos is a new slice so later mutations remain isolated.
require.False(t, &original.MultipleTableInfos[0] == &routed.MultipleTableInfos[0], "MultipleTableInfos should be a new slice")

// Verify that PostTxnFlushed is an independent copy (not shared)
// This is defensive: currently DDL events arrive with nil PostTxnFlushed,
// but we copy it to prevent races if callbacks are ever added before building the routed event.
require.NotNil(t, routed.PostTxnFlushed)
require.Equal(t, 2, len(routed.PostTxnFlushed), "PostTxnFlushed should have same length as original")
require.Equal(t, 2, len(original.PostTxnFlushed), "Original PostTxnFlushed should remain unchanged")
// Verify independent backing arrays.
require.NotEqual(t, &original.PostTxnFlushed[0], &routed.PostTxnFlushed[0], "PostTxnFlushed should have independent backing arrays")

// Verify that appending to the routed event doesn't affect the original.
routed.AddPostFlushFunc(func() {})
require.Equal(t, 3, len(routed.PostTxnFlushed), "Routed event should have appended callback")
require.Equal(t, 2, len(original.PostTxnFlushed), "Original should be unaffected by routed event append")

// Verify that routed state doesn't affect the original.
require.Equal(t, ddlJob.SchemaName, original.SchemaName, "Original SchemaName should be unchanged")
require.Equal(t, ddlJob.Query, original.Query, "Original Query should be unchanged")
require.True(t, original.TableInfo == originalTableInfo, "Original TableInfo should be unchanged")
require.True(t, original.MultipleTableInfos[0] == multipleTableInfo1, "Original MultipleTableInfos[0] should be unchanged")
require.True(t, original.MultipleTableInfos[1] == multipleTableInfo2, "Original MultipleTableInfos[1] should be unchanged")

// Verify that the routed event has the routed state.
require.Equal(t, "routed_schema", routed.GetTargetSchemaName())
require.Equal(t, "CREATE TABLE routed_schema.test ...", routed.Query)
require.True(t, routed.TableInfo == newRoutedTableInfo)
require.Equal(t, "routed_schema", routed.TableInfo.TableName.TargetSchema)
require.Equal(t, original.SchemaName, routed.GetSchemaName())
require.Equal(t, original.TableName, routed.GetTableName())
require.True(t, routed.MultipleTableInfos[0] == routedMultipleTableInfos[0])
require.True(t, routed.MultipleTableInfos[1] == routedMultipleTableInfos[1])

// Test nil origin event.
var nilEvent *DDLEvent
routedNil := NewRoutedDDLEvent(nilEvent, "", "", "", "", "", nil, nil, nil)
require.Nil(t, routedNil)
}

func TestNewRoutedDDLEventPreservesSourceFields(t *testing.T) {
original := &DDLEvent{
SchemaName: "source_db",
TableName: "new_orders",
ExtraSchemaName: "source_db",
ExtraTableName: "old_orders",
targetSchemaName: "target_db",
targetTableName: "new_orders_routed",
targetExtraSchemaName: "target_db",
targetExtraTableName: "old_orders_routed",
}

routed := NewRoutedDDLEvent(
original,
original.Query,
"target_db_v2",
"new_orders_routed_v2",
"target_db_v2",
"old_orders_routed_v2",
original.TableInfo,
original.MultipleTableInfos,
original.BlockedTableNames,
)

require.Equal(t, "source_db", routed.GetSchemaName())
require.Equal(t, "new_orders", routed.GetTableName())
require.Equal(t, "source_db", routed.GetExtraSchemaName())
require.Equal(t, "old_orders", routed.GetExtraTableName())
require.Equal(t, "target_db_v2", routed.GetTargetSchemaName())
require.Equal(t, "new_orders_routed_v2", routed.GetTargetTableName())
require.Equal(t, "target_db_v2", routed.GetTargetExtraSchemaName())
require.Equal(t, "old_orders_routed_v2", routed.GetTargetExtraTableName())
}

func TestGetEventsForRenameTablesPreservesSourceAndTargetNames(t *testing.T) {
sourceTable1 := common.WrapTableInfo("new_db1", &model.TableInfo{
ID: 100,
Name: ast.NewCIStr("new_table1"),
UpdateTS: 10,
})
sourceTable2 := common.WrapTableInfo("new_db2", &model.TableInfo{
ID: 101,
Name: ast.NewCIStr("new_table2"),
UpdateTS: 11,
})

ddl := &DDLEvent{
Type: byte(model.ActionRenameTables),
Query: "RENAME TABLE `old_target_db1`.`old_target_table1` TO `new_target_db1`.`new_target_table1`; RENAME TABLE `old_target_db2`.`old_target_table2` TO `new_target_db2`.`new_target_table2`",
MultipleTableInfos: []*common.TableInfo{
sourceTable1.CloneWithRouting("new_target_db1", "new_target_table1"),
sourceTable2.CloneWithRouting("new_target_db2", "new_target_table2"),
},
TableNameChange: &TableNameChange{
DropName: []SchemaTableName{
{SchemaName: "old_db1", TableName: "old_table1"},
{SchemaName: "old_db2", TableName: "old_table2"},
},
},
}

events := ddl.GetEvents()
require.Len(t, events, 2)

require.Equal(t, "new_db1", events[0].SchemaName)
require.Equal(t, "new_table1", events[0].TableName)
require.Equal(t, "new_target_db1", events[0].GetTargetSchemaName())
require.Equal(t, "new_target_table1", events[0].GetTargetTableName())
require.Equal(t, "old_db1", events[0].ExtraSchemaName)
require.Equal(t, "old_table1", events[0].ExtraTableName)
require.Equal(t, "old_target_db1", events[0].GetTargetExtraSchemaName())
require.Equal(t, "old_target_table1", events[0].GetTargetExtraTableName())

require.Equal(t, "new_db2", events[1].SchemaName)
require.Equal(t, "new_table2", events[1].TableName)
require.Equal(t, "new_target_db2", events[1].GetTargetSchemaName())
require.Equal(t, "new_target_table2", events[1].GetTargetTableName())
require.Equal(t, "old_db2", events[1].ExtraSchemaName)
require.Equal(t, "old_table2", events[1].ExtraTableName)
require.Equal(t, "old_target_db2", events[1].GetTargetExtraSchemaName())
require.Equal(t, "old_target_table2", events[1].GetTargetExtraTableName())
}
2 changes: 1 addition & 1 deletion pkg/sink/mysql/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func CreateMysqlDBConn(dsnStr string) (*sql.DB, error) {
}

func needSwitchDB(event *commonEvent.DDLEvent) bool {
if len(event.GetSchemaName()) == 0 {
if len(event.GetTargetSchemaName()) == 0 {
return false
}
if event.GetDDLType() == timodel.ActionCreateSchema || event.GetDDLType() == timodel.ActionDropSchema {
Expand Down
2 changes: 1 addition & 1 deletion pkg/sink/mysql/mysql_writer_ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (w *Writer) execDDL(event *commonEvent.DDLEvent) error {
}

if shouldSwitchDB {
_, err = tx.ExecContext(ctx, "USE "+common.QuoteName(event.GetSchemaName())+";")
_, err = tx.ExecContext(ctx, "USE "+common.QuoteName(event.GetTargetSchemaName())+";")
if err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
log.Error("Failed to rollback", zap.Error(err))
Expand Down
Loading
Loading