Skip to content

Commit c83b192

Browse files
committed
handle commit for non transactional mysql engines
1 parent cd189a0 commit c83b192

4 files changed

Lines changed: 87 additions & 9 deletions

File tree

flow/connectors/mysql/cdc.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,18 @@ func (c *MySqlConnector) PullRecords(
550550
}
551551
}
552552

553+
advanceCheckpoint := func(evGSet mysql.GTIDSet, logPos uint32) {
554+
if gset != nil {
555+
gset = evGSet
556+
updatedOffset = gset.String()
557+
req.RecordStream.UpdateLatestCheckpointText(updatedOffset)
558+
} else if logPos > pos.Pos {
559+
pos.Pos = logPos
560+
updatedOffset = posToOffsetText(pos)
561+
req.RecordStream.UpdateLatestCheckpointText(updatedOffset)
562+
}
563+
}
564+
553565
lastEventAt := time.Now()
554566
var mysqlParser *parser.Parser
555567
processEvent := func(event *replication.BinlogEvent) error {
@@ -559,15 +571,7 @@ func (c *MySqlConnector) PullRecords(
559571
case *replication.GtidTaggedLogEvent: // MySQL 8.4+ tagged GTIDs
560572
recordCommitLagMetric(ctx, ev.ImmediateCommitTimestamp)
561573
case *replication.XIDEvent:
562-
if gset != nil {
563-
gset = ev.GSet
564-
updatedOffset = gset.String() // accumulated from GTID events above
565-
req.RecordStream.UpdateLatestCheckpointText(updatedOffset)
566-
} else if event.Header.LogPos > pos.Pos {
567-
pos.Pos = event.Header.LogPos
568-
updatedOffset = posToOffsetText(pos)
569-
req.RecordStream.UpdateLatestCheckpointText(updatedOffset)
570-
}
574+
advanceCheckpoint(ev.GSet, event.Header.LogPos)
571575
inTx = false
572576
case *replication.RotateEvent:
573577
if gset == nil && (event.Header.Timestamp != 0 || string(ev.NextLogName) != pos.Name) {
@@ -624,6 +628,10 @@ func (c *MySqlConnector) PullRecords(
624628
}
625629
case ddlKindRenameTable:
626630
c.processRenameTableQuery(ctx, otelManager, req, renameStmt, string(ev.Schema))
631+
case ddlKindCommit:
632+
// Non-transactional engines (e.g. MyISAM) end a binlog group with a COMMIT/ROLLBACK
633+
advanceCheckpoint(ev.GSet, event.Header.LogPos)
634+
inTx = false
627635
case ddlKindIgnored:
628636
default:
629637
return fmt.Errorf("unknown stmt kind: %v", kind)

flow/connectors/mysql/query_parser.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ const (
222222
ddlKindIgnored ddlKind = iota
223223
ddlKindAlterTable
224224
ddlKindRenameTable
225+
ddlKindCommit
225226
)
226227

227228
// classifyParsedStatement maps a successfully parsed statement to the handler that
@@ -235,6 +236,8 @@ func classifyParsedStatement(stmt ast.StmtNode) (ddlKind, *ast.AlterTableStmt, *
235236
return ddlKindAlterTable, s, nil
236237
case *ast.RenameTableStmt:
237238
return ddlKindRenameTable, nil, s
239+
case *ast.CommitStmt, *ast.RollbackStmt:
240+
return ddlKindCommit, nil, nil
238241
default:
239242
return ddlKindIgnored, nil, nil
240243
}

flow/connectors/mysql/query_parser_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package connmysql
33
import (
44
"testing"
55

6+
"github.com/pingcap/tidb/pkg/parser"
67
"github.com/stretchr/testify/require"
78
)
89

@@ -185,3 +186,25 @@ func TestIsBenignUnparsedStatement(t *testing.T) {
185186
})
186187
}
187188
}
189+
190+
func TestClassifyParsedStatementTxControl(t *testing.T) {
191+
for _, tc := range []struct {
192+
name string
193+
query string
194+
want ddlKind
195+
}{
196+
{name: "commit", query: "COMMIT", want: ddlKindCommit},
197+
{name: "rollback", query: "ROLLBACK", want: ddlKindCommit},
198+
{name: "begin", query: "BEGIN", want: ddlKindIgnored},
199+
{name: "alter table add column", query: "ALTER TABLE t ADD COLUMN c INT", want: ddlKindAlterTable},
200+
{name: "rename table", query: "RENAME TABLE a TO b", want: ddlKindRenameTable},
201+
} {
202+
t.Run(tc.name, func(t *testing.T) {
203+
stmts, _, err := parseSQL(parser.New(), []byte(tc.query))
204+
require.NoError(t, err)
205+
require.Len(t, stmts, 1)
206+
kind, _, _ := classifyParsedStatement(stmts[0])
207+
require.Equal(t, tc.want, kind)
208+
})
209+
}
210+
}

flow/e2e/clickhouse_mysql_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,50 @@ func (s ClickHouseSuite) Test_UnsignedMySQL() {
8181
RequireEnvCanceled(s.t, env)
8282
}
8383

84+
func (s ClickHouseSuite) Test_MySQL_MyISAM_NonTransactional() {
85+
if _, ok := s.source.(*MySqlSource); !ok {
86+
s.t.Skip("only applies to mysql")
87+
}
88+
89+
srcTableName := "test_myisam"
90+
srcFullName := s.attachSchemaSuffix(srcTableName)
91+
dstTableName := "test_myisam"
92+
93+
require.NoError(s.t, s.source.Exec(s.t.Context(), fmt.Sprintf(`CREATE TABLE %s (
94+
id int primary key,
95+
val text
96+
) ENGINE=MyISAM`, srcFullName)))
97+
98+
require.NoError(s.t, s.source.Exec(s.t.Context(),
99+
fmt.Sprintf(`INSERT INTO %s (id, val) VALUES (1, 'snapshot')`, srcFullName)))
100+
101+
connectionGen := FlowConnectionGenerationConfig{
102+
FlowJobName: srcFullName,
103+
TableNameMapping: map[string]string{srcFullName: dstTableName},
104+
Destination: s.Peer().Name,
105+
}
106+
flowConnConfig := connectionGen.GenerateFlowConnectionConfigs(s)
107+
flowConnConfig.DoInitialSnapshot = true
108+
109+
tc := NewTemporalClient(s.t)
110+
env := ExecutePeerflow(s.t, tc, flowConnConfig)
111+
SetupCDCFlowStatusQuery(s.t, env, flowConnConfig)
112+
113+
EnvWaitForEqualTablesWithNames(env, s, "waiting on initial snapshot", srcTableName, dstTableName, "id,val")
114+
115+
require.NoError(s.t, s.source.Exec(s.t.Context(),
116+
fmt.Sprintf(`INSERT INTO %s (id, val) VALUES (2, 'cdc-insert')`, srcFullName)))
117+
require.NoError(s.t, s.source.Exec(s.t.Context(),
118+
fmt.Sprintf(`UPDATE %s SET val = 'cdc-update' WHERE id = 1`, srcFullName)))
119+
require.NoError(s.t, s.source.Exec(s.t.Context(),
120+
fmt.Sprintf(`DELETE FROM %s WHERE id = 2`, srcFullName)))
121+
122+
EnvWaitForEqualTablesWithNames(env, s, "waiting on cdc changes", srcTableName, dstTableName, "id,val")
123+
124+
env.Cancel(s.t.Context())
125+
RequireEnvCanceled(s.t, env)
126+
}
127+
84128
func (s ClickHouseSuite) Test_MySQL_Time() {
85129
if _, ok := s.source.(*MySqlSource); !ok {
86130
s.t.Skip("only applies to mysql")

0 commit comments

Comments
 (0)