Skip to content

Commit 442a071

Browse files
committed
fix(cdc): skip foreign key tables in scanner
1 parent e6c70b6 commit 442a071

7 files changed

Lines changed: 639 additions & 39 deletions

File tree

pkg/cdc/sql_builder.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -248,19 +248,20 @@ const (
248248
"table_name = '%s'"
249249

250250
CDCCollectTableInfoSqlTemplate = "SELECT " +
251-
" rel_id, " +
252-
" relname, " +
253-
" reldatabase_id, " +
254-
" reldatabase, " +
255-
" rel_createsql, " +
256-
" account_id " +
257-
"FROM `mo_catalog`.`mo_tables` " +
251+
" tbl.rel_id, " +
252+
" tbl.relname, " +
253+
" tbl.reldatabase_id, " +
254+
" tbl.reldatabase, " +
255+
" tbl.rel_createsql, " +
256+
" tbl.account_id, " +
257+
" tbl.`constraint` " +
258+
"FROM `mo_catalog`.`mo_tables` tbl " +
258259
"WHERE " +
259-
" account_id IN (%s) " +
260+
" tbl.account_id IN (%s) " +
260261
"%s" +
261262
"%s" +
262-
" AND relkind = '%s' " +
263-
" AND reldatabase NOT IN (%s)"
263+
" AND tbl.relkind = '%s' " +
264+
" AND tbl.reldatabase NOT IN (%s)"
264265
CDCInsertMOISCPLogSqlTemplate = `REPLACE INTO mo_catalog.mo_iscp_log (` +
265266
`account_id,` +
266267
`table_id,` +
@@ -475,6 +476,7 @@ var CDCSQLTemplates = [CDCSqlTemplateCount]struct {
475476
"reldatabase",
476477
"rel_createsql",
477478
"account_id",
479+
"constraint",
478480
},
479481
},
480482
CDCGetWatermarkWhereSqlTemplate_Idx: {
@@ -1071,13 +1073,13 @@ func (b cdcSQLBuilder) CollectTableInfoSQL(accountIDs string, dbNames string, ta
10711073
if dbNames == "*" {
10721074
return ""
10731075
}
1074-
return " AND reldatabase IN (" + dbNames + ") "
1076+
return " AND tbl.reldatabase IN (" + dbNames + ") "
10751077
}(),
10761078
func() string {
10771079
if tableNames == "*" {
10781080
return ""
10791081
}
1080-
return " AND relname IN (" + tableNames + ") "
1082+
return " AND tbl.relname IN (" + tableNames + ") "
10811083
}(),
10821084
catalog.SystemOrdinaryRel,
10831085
AddSingleQuotesJoin(catalog.SystemDatabases),

pkg/cdc/table_change_stream.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,8 +603,8 @@ func (s *TableChangeStream) cleanup(ctx context.Context) {
603603
)
604604
}()
605605

606-
// Remove from running readers
607-
s.runningReaders.Delete(s.runningReaderKey)
606+
// Remove from running readers only if this stream still owns the entry.
607+
s.runningReaders.CompareAndDelete(s.runningReaderKey, s)
608608

609609
// Remove watermark cache
610610
removeStart := time.Now()

pkg/cdc/table_change_stream_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,43 @@ func TestTableChangeStream_Run_DuplicateReader(t *testing.T) {
378378
}
379379
}
380380

381+
func TestTableChangeStreamCleanupDoesNotDeleteReplacedReader(t *testing.T) {
382+
runningReaders := &sync.Map{}
383+
384+
key := "db1.t1"
385+
oldStream := &TableChangeStream{
386+
accountId: 1,
387+
taskId: "task1",
388+
tableInfo: &DbTableInfo{SourceDbName: "db1", SourceTblName: "t1", SourceTblId: 1},
389+
sinker: newTableStreamRecordingSinker(),
390+
watermarkUpdater: newWatermarkUpdaterStub(),
391+
watermarkKey: &WatermarkKey{AccountId: 1, TaskId: "task1", DBName: "db1", TableName: "t1"},
392+
runningReaders: runningReaders,
393+
runningReaderKey: key,
394+
progressTracker: nil,
395+
watermarkStallThreshold: defaultWatermarkStallThreshold,
396+
}
397+
newStream := &TableChangeStream{
398+
accountId: 1,
399+
taskId: "task1",
400+
tableInfo: &DbTableInfo{SourceDbName: "db1", SourceTblName: "t1", SourceTblId: 1},
401+
sinker: newTableStreamRecordingSinker(),
402+
watermarkUpdater: newWatermarkUpdaterStub(),
403+
watermarkKey: &WatermarkKey{AccountId: 1, TaskId: "task1", DBName: "db1", TableName: "t1"},
404+
runningReaders: runningReaders,
405+
runningReaderKey: key,
406+
}
407+
runningReaders.Store(key, oldStream)
408+
runningReaders.Store(key, newStream)
409+
410+
oldStream.wg.Add(1)
411+
oldStream.cleanup(context.Background())
412+
413+
stored, ok := runningReaders.Load(key)
414+
require.True(t, ok, "replaced reader ownership should remain")
415+
require.Same(t, newStream, stored, "old cleanup must not delete a newer reader")
416+
}
417+
381418
// Integration: commit failure triggers EnsureCleanup rollback, then recovery succeeds
382419
func TestTableChangeStream_CommitFailure_EnsureCleanup_ThenRecover(t *testing.T) {
383420
updaterStub := newWatermarkUpdaterStub()

pkg/cdc/table_scanner.go

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"fmt"
2020
"runtime/debug"
2121
"slices"
22-
"strings"
2322
"sync"
2423
"sync/atomic"
2524
"time"
@@ -37,6 +36,7 @@ import (
3736
"github.com/matrixorigin/matrixone/pkg/defines"
3837
"github.com/matrixorigin/matrixone/pkg/logutil"
3938
"github.com/matrixorigin/matrixone/pkg/util/executor"
39+
"github.com/matrixorigin/matrixone/pkg/vm/engine"
4040
)
4141

4242
const (
@@ -744,6 +744,7 @@ func (s *TableDetector) scanTable() error {
744744
}
745745
defer result.Close()
746746

747+
var scanErr error
747748
result.ReadRows(func(rows int, cols []*vector.Vector) bool {
748749
for i := 0; i < rows; i++ {
749750
tblId := vector.MustFixedColWithTypeCheck[uint64](cols[0])[i]
@@ -752,9 +753,21 @@ func (s *TableDetector) scanTable() error {
752753
dbName := cols[3].GetStringAt(i)
753754
createSql := cols[4].GetStringAt(i)
754755
accountId := vector.MustFixedColWithTypeCheck[uint32](cols[5])[i]
756+
hasForeignKey, decodeErr := tableHasForeignKeyConstraint(cols[6].GetBytesAt(i))
757+
if decodeErr != nil {
758+
scanErr = decodeErr
759+
logutil.Warn(
760+
"cdc.table_detector.scan_constraint_failed",
761+
zap.Uint32("account-id", accountId),
762+
zap.String("db", dbName),
763+
zap.String("table", tblName),
764+
zap.Error(decodeErr),
765+
)
766+
return false
767+
}
755768

756769
// skip table with foreign key
757-
if strings.Contains(strings.ToLower("createSql"), "foreign key") {
770+
if hasForeignKey {
758771
continue
759772
}
760773

@@ -776,21 +789,48 @@ func (s *TableDetector) scanTable() error {
776789
mp[accountId][key] = newInfo
777790
} else {
778791
idChanged := oldInfo.OnlyDiffinTblId(newInfo)
779-
oldInfo.SourceDbId = dbId
780-
oldInfo.SourceDbName = dbName
781-
oldInfo.SourceTblId = tblId
782-
oldInfo.SourceTblName = tblName
783-
oldInfo.SourceCreateSql = createSql
784-
oldInfo.IdChanged = oldInfo.IdChanged || idChanged
785-
mp[accountId][key] = oldInfo
792+
updatedInfo := oldInfo.Clone()
793+
updatedInfo.SourceDbId = dbId
794+
updatedInfo.SourceDbName = dbName
795+
updatedInfo.SourceTblId = tblId
796+
updatedInfo.SourceTblName = tblName
797+
updatedInfo.SourceCreateSql = createSql
798+
updatedInfo.IdChanged = updatedInfo.IdChanged || idChanged
799+
mp[accountId][key] = updatedInfo
786800
}
787801
}
788802
return true
789803
})
804+
if scanErr != nil {
805+
return scanErr
806+
}
790807

791808
// replace the old table map
792809
s.mu.Lock()
793810
s.Mp = mp
794811
s.mu.Unlock()
795812
return nil
796813
}
814+
815+
func tableHasForeignKeyConstraint(data []byte) (hasForeignKey bool, err error) {
816+
if len(data) == 0 {
817+
return false, nil
818+
}
819+
820+
defer func() {
821+
if r := recover(); r != nil {
822+
err = moerr.NewInternalErrorNoCtxf("unmarshal table constraint failed: %v", r)
823+
}
824+
}()
825+
826+
constraintDef := &engine.ConstraintDef{}
827+
if err := constraintDef.UnmarshalBinary(data); err != nil {
828+
return false, err
829+
}
830+
for _, constraint := range constraintDef.Cts {
831+
if foreignKeyDef, ok := constraint.(*engine.ForeignKeyDef); ok && len(foreignKeyDef.Fkeys) > 0 {
832+
return true, nil
833+
}
834+
}
835+
return false, nil
836+
}

0 commit comments

Comments
 (0)