mysqlbinlog/parse_event_convert_unsigned_test.go
starainrt 8469c11373
refactor(parse): 拆分 parse.go 并修复事务/过滤一致性问题
- 将臃肿的 parse.go 按职责拆分为多个模块:
    parse_types.go、parse_io.go、parse_event_convert.go、parse_stream.go、parse_filter.go
  - parse.go 保留为模块入口说明,提升可维护性与可读性
  - 修复事务状态被覆盖问题(BEGIN/COMMIT/ROLLBACK 不再被重置为 PREPARE)
  - 增加 include-tables 与 exclude-tables 互斥校验,同时配置时直接报配置错误
  - 强化表匹配器模式校验,并补充非法模式测试
  - 在明细过滤后重算事务统计(RowsCount/StartPos/EndPos/Size),避免统计失真
  - 增加 TABLE_MAP 事件转换,补充列元信息透传(ColumnTypes/ColumnCollationIDs)
  - 基于 unsigned 元数据规范化行值,避免无符号整型被渲染为负数
  - 优化事件解析报错信息:增加有界 body 十六进制预览
  - 补充单元测试:payload/tablemap 转换、unsigned 规范化、过滤逻辑、IO 预览
2026-03-19 17:04:35 +08:00

96 lines
3.0 KiB
Go

package binlog
import (
"testing"
"github.com/starainrt/go-mysql/mysql"
"github.com/starainrt/go-mysql/replication"
)
func TestNormalizeRowsByUnsigned_AllIntegerKinds(t *testing.T) {
event := &replication.RowsEvent{
Table: &replication.TableMapEvent{
ColumnCount: 5,
ColumnType: []byte{mysql.MYSQL_TYPE_TINY, mysql.MYSQL_TYPE_SHORT, mysql.MYSQL_TYPE_INT24, mysql.MYSQL_TYPE_LONG, mysql.MYSQL_TYPE_LONGLONG},
SignednessBitmap: []byte{0xF8},
},
Rows: [][]interface{}{{int8(-1), int16(-2), int32(-1), int32(-1), int64(-1)}},
}
got := normalizeRowsByUnsigned(event)
row := got[0]
if v, ok := row[0].(uint8); !ok || v != 255 {
t.Fatalf("tiny unsigned mismatch: %T %v", row[0], row[0])
}
if v, ok := row[1].(uint16); !ok || v != 65534 {
t.Fatalf("short unsigned mismatch: %T %v", row[1], row[1])
}
if v, ok := row[2].(uint32); !ok || v != 16777215 {
t.Fatalf("int24 unsigned mismatch: %T %v", row[2], row[2])
}
if v, ok := row[3].(uint32); !ok || v != 4294967295 {
t.Fatalf("long unsigned mismatch: %T %v", row[3], row[3])
}
if v, ok := row[4].(uint64); !ok || v != 18446744073709551615 {
t.Fatalf("longlong unsigned mismatch: %T %v", row[4], row[4])
}
}
func TestNormalizeRowsByUnsigned_NoSignednessMetadata(t *testing.T) {
event := &replication.RowsEvent{
Table: &replication.TableMapEvent{
ColumnCount: 1,
ColumnType: []byte{mysql.MYSQL_TYPE_LONGLONG},
},
Rows: [][]interface{}{{int64(-1)}},
}
got := normalizeRowsByUnsigned(event)
if v, ok := got[0][0].(int64); !ok || v != -1 {
t.Fatalf("value should remain signed when metadata missing: %T %v", got[0][0], got[0][0])
}
}
func TestParseBinlogEvent_IncludeColumnMetadata(t *testing.T) {
event := &replication.RowsEvent{
Table: &replication.TableMapEvent{
ColumnCount: 3,
ColumnType: []byte{mysql.MYSQL_TYPE_VAR_STRING, mysql.MYSQL_TYPE_LONG, mysql.MYSQL_TYPE_VAR_STRING},
DefaultCharset: []uint64{45}, // utf8mb4_general_ci
},
Rows: [][]interface{}{{"name", int32(1), "desc"}},
}
ev := &replication.BinlogEvent{
Header: &replication.EventHeader{EventType: replication.WRITE_ROWS_EVENTv2},
Event: event,
}
events := ParseBinlogEvent(ev)
if len(events) != 1 {
t.Fatalf("expected 1 event, got %d", len(events))
}
got := events[0]
if len(got.ColumnTypes) != 3 {
t.Fatalf("unexpected column type length: %d", len(got.ColumnTypes))
}
if got.ColumnTypes[0] != int(mysql.MYSQL_TYPE_VAR_STRING) || got.ColumnTypes[1] != int(mysql.MYSQL_TYPE_LONG) {
t.Fatalf("unexpected column types: %v", got.ColumnTypes)
}
if len(got.ColumnCollationIDs) != 3 {
t.Fatalf("unexpected column collation length: %d", len(got.ColumnCollationIDs))
}
if got.ColumnCollationIDs[0] != 45 {
t.Fatalf("unexpected collation for column 0: %d", got.ColumnCollationIDs[0])
}
if got.ColumnCollationIDs[2] != 45 {
t.Fatalf("unexpected collation for column 2: %d", got.ColumnCollationIDs[2])
}
if got.ColumnCollationIDs[1] != 0 {
t.Fatalf("non-character column should keep zero collation: %d", got.ColumnCollationIDs[1])
}
}