github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/sink/codec/canal_test.go (about) 1 // Copyright 2020 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 codec 15 16 import ( 17 "github.com/golang/protobuf/proto" 18 "github.com/pingcap/check" 19 mm "github.com/pingcap/parser/model" 20 "github.com/pingcap/parser/mysql" 21 "golang.org/x/text/encoding/charmap" 22 23 "github.com/pingcap/ticdc/cdc/model" 24 "github.com/pingcap/ticdc/pkg/util/testleak" 25 canal "github.com/pingcap/ticdc/proto/canal" 26 ) 27 28 type canalBatchSuite struct { 29 rowCases [][]*model.RowChangedEvent 30 ddlCases [][]*model.DDLEvent 31 } 32 33 var _ = check.Suite(&canalBatchSuite{ 34 rowCases: [][]*model.RowChangedEvent{{{ 35 CommitTs: 1, 36 Table: &model.TableName{Schema: "a", Table: "b"}, 37 Columns: []*model.Column{{Name: "col1", Type: 1, Value: "aa"}}, 38 }}, {{ 39 CommitTs: 1, 40 Table: &model.TableName{Schema: "a", Table: "b"}, 41 Columns: []*model.Column{{Name: "col1", Type: 1, Value: "aa"}}, 42 }, { 43 CommitTs: 2, 44 Table: &model.TableName{Schema: "a", Table: "b"}, 45 Columns: []*model.Column{{Name: "col1", Type: 1, Value: "bb"}}, 46 }, { 47 CommitTs: 3, 48 Table: &model.TableName{Schema: "a", Table: "b"}, 49 Columns: []*model.Column{{Name: "col1", Type: 1, Value: "bb"}}, 50 }, { 51 CommitTs: 4, 52 Table: &model.TableName{Schema: "a", Table: "c", TableID: 6, IsPartition: true}, 53 Columns: []*model.Column{{Name: "col1", Type: 1, Value: "cc"}}, 54 }}, {}}, 55 ddlCases: [][]*model.DDLEvent{{{ 56 CommitTs: 1, 57 TableInfo: &model.SimpleTableInfo{ 58 Schema: "a", Table: "b", 59 }, 60 Query: "create table a", 61 Type: 1, 62 }}, {{ 63 CommitTs: 1, 64 TableInfo: &model.SimpleTableInfo{ 65 Schema: "a", Table: "b", 66 }, 67 Query: "create table a", 68 Type: 1, 69 }, { 70 CommitTs: 2, 71 TableInfo: &model.SimpleTableInfo{ 72 Schema: "a", Table: "b", 73 }, 74 Query: "create table b", 75 Type: 2, 76 }, { 77 CommitTs: 3, 78 TableInfo: &model.SimpleTableInfo{ 79 Schema: "a", Table: "b", 80 }, 81 Query: "create table c", 82 Type: 3, 83 }}, {}}, 84 }) 85 86 func (s *canalBatchSuite) TestCanalEventBatchEncoder(c *check.C) { 87 defer testleak.AfterTest(c)() 88 for _, cs := range s.rowCases { 89 encoder := NewCanalEventBatchEncoder() 90 for _, row := range cs { 91 _, err := encoder.AppendRowChangedEvent(row) 92 c.Assert(err, check.IsNil) 93 } 94 size := encoder.Size() 95 res := encoder.Build() 96 97 if len(cs) == 0 { 98 c.Assert(res, check.IsNil) 99 continue 100 } 101 102 c.Assert(res, check.HasLen, 1) 103 c.Assert(res[0].Key, check.IsNil) 104 c.Assert(len(res[0].Value), check.Equals, size) 105 106 packet := &canal.Packet{} 107 err := proto.Unmarshal(res[0].Value, packet) 108 c.Assert(err, check.IsNil) 109 c.Assert(packet.GetType(), check.Equals, canal.PacketType_MESSAGES) 110 messages := &canal.Messages{} 111 err = proto.Unmarshal(packet.GetBody(), messages) 112 c.Assert(err, check.IsNil) 113 c.Assert(len(messages.GetMessages()), check.Equals, len(cs)) 114 } 115 116 for _, cs := range s.ddlCases { 117 encoder := NewCanalEventBatchEncoder() 118 for _, ddl := range cs { 119 msg, err := encoder.EncodeDDLEvent(ddl) 120 c.Assert(err, check.IsNil) 121 c.Assert(msg, check.NotNil) 122 c.Assert(msg.Key, check.IsNil) 123 124 packet := &canal.Packet{} 125 err = proto.Unmarshal(msg.Value, packet) 126 c.Assert(err, check.IsNil) 127 c.Assert(packet.GetType(), check.Equals, canal.PacketType_MESSAGES) 128 messages := &canal.Messages{} 129 err = proto.Unmarshal(packet.GetBody(), messages) 130 c.Assert(err, check.IsNil) 131 c.Assert(len(messages.GetMessages()), check.Equals, 1) 132 c.Assert(err, check.IsNil) 133 } 134 } 135 } 136 137 type canalEntrySuite struct{} 138 139 var _ = check.Suite(&canalEntrySuite{}) 140 141 func (s *canalEntrySuite) TestConvertEntry(c *check.C) { 142 defer testleak.AfterTest(c)() 143 testInsert(c) 144 testUpdate(c) 145 testDelete(c) 146 testDdl(c) 147 } 148 149 func testInsert(c *check.C) { 150 testCaseInsert := &model.RowChangedEvent{ 151 CommitTs: 417318403368288260, 152 Table: &model.TableName{ 153 Schema: "cdc", 154 Table: "person", 155 }, 156 Columns: []*model.Column{ 157 {Name: "id", Type: mysql.TypeLong, Flag: model.PrimaryKeyFlag, Value: 1}, 158 {Name: "name", Type: mysql.TypeVarchar, Value: "Bob"}, 159 {Name: "tiny", Type: mysql.TypeTiny, Value: 255}, 160 {Name: "comment", Type: mysql.TypeBlob, Value: []byte("测试")}, 161 {Name: "blob", Type: mysql.TypeBlob, Value: []byte("测试blob"), Flag: model.BinaryFlag}, 162 }, 163 } 164 165 builder := NewCanalEntryBuilder() 166 entry, err := builder.FromRowEvent(testCaseInsert) 167 c.Assert(err, check.IsNil) 168 c.Assert(entry.GetEntryType(), check.Equals, canal.EntryType_ROWDATA) 169 header := entry.GetHeader() 170 c.Assert(header.GetExecuteTime(), check.Equals, int64(1591943372224)) 171 c.Assert(header.GetSourceType(), check.Equals, canal.Type_MYSQL) 172 c.Assert(header.GetSchemaName(), check.Equals, testCaseInsert.Table.Schema) 173 c.Assert(header.GetTableName(), check.Equals, testCaseInsert.Table.Table) 174 c.Assert(header.GetEventType(), check.Equals, canal.EventType_INSERT) 175 store := entry.GetStoreValue() 176 c.Assert(store, check.NotNil) 177 rc := &canal.RowChange{} 178 err = proto.Unmarshal(store, rc) 179 c.Assert(err, check.IsNil) 180 c.Assert(rc.GetIsDdl(), check.IsFalse) 181 rowDatas := rc.GetRowDatas() 182 c.Assert(len(rowDatas), check.Equals, 1) 183 184 columns := rowDatas[0].AfterColumns 185 c.Assert(len(columns), check.Equals, len(testCaseInsert.Columns)) 186 for _, col := range columns { 187 c.Assert(col.GetUpdated(), check.IsTrue) 188 switch col.GetName() { 189 case "id": 190 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeBIGINT)) 191 c.Assert(col.GetIsKey(), check.IsTrue) 192 c.Assert(col.GetIsNull(), check.IsFalse) 193 c.Assert(col.GetValue(), check.Equals, "1") 194 c.Assert(col.GetMysqlType(), check.Equals, "int") 195 case "name": 196 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeVARCHAR)) 197 c.Assert(col.GetIsKey(), check.IsFalse) 198 c.Assert(col.GetIsNull(), check.IsFalse) 199 c.Assert(col.GetValue(), check.Equals, "Bob") 200 c.Assert(col.GetMysqlType(), check.Equals, "varchar") 201 case "tiny": 202 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeSMALLINT)) 203 c.Assert(col.GetIsKey(), check.IsFalse) 204 c.Assert(col.GetIsNull(), check.IsFalse) 205 c.Assert(col.GetValue(), check.Equals, "255") 206 case "comment": 207 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeVARCHAR)) 208 c.Assert(col.GetIsKey(), check.IsFalse) 209 c.Assert(col.GetIsNull(), check.IsFalse) 210 c.Assert(err, check.IsNil) 211 c.Assert(col.GetValue(), check.Equals, "测试") 212 c.Assert(col.GetMysqlType(), check.Equals, "text") 213 case "blob": 214 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeBLOB)) 215 c.Assert(col.GetIsKey(), check.IsFalse) 216 c.Assert(col.GetIsNull(), check.IsFalse) 217 s, err := charmap.ISO8859_1.NewEncoder().String(col.GetValue()) 218 c.Assert(err, check.IsNil) 219 c.Assert(s, check.Equals, "测试blob") 220 c.Assert(col.GetMysqlType(), check.Equals, "blob") 221 } 222 } 223 } 224 225 func testUpdate(c *check.C) { 226 testCaseUpdate := &model.RowChangedEvent{ 227 CommitTs: 417318403368288260, 228 Table: &model.TableName{ 229 Schema: "cdc", 230 Table: "person", 231 }, 232 Columns: []*model.Column{ 233 {Name: "id", Type: mysql.TypeLong, Flag: model.PrimaryKeyFlag, Value: 1}, 234 {Name: "name", Type: mysql.TypeVarchar, Value: "Bob"}, 235 }, 236 PreColumns: []*model.Column{ 237 {Name: "id", Type: mysql.TypeLong, Flag: model.PrimaryKeyFlag, Value: 2}, 238 {Name: "name", Type: mysql.TypeVarchar, Value: "Nancy"}, 239 }, 240 } 241 builder := NewCanalEntryBuilder() 242 entry, err := builder.FromRowEvent(testCaseUpdate) 243 c.Assert(err, check.IsNil) 244 c.Assert(entry.GetEntryType(), check.Equals, canal.EntryType_ROWDATA) 245 246 header := entry.GetHeader() 247 c.Assert(header.GetExecuteTime(), check.Equals, int64(1591943372224)) 248 c.Assert(header.GetSourceType(), check.Equals, canal.Type_MYSQL) 249 c.Assert(header.GetSchemaName(), check.Equals, testCaseUpdate.Table.Schema) 250 c.Assert(header.GetTableName(), check.Equals, testCaseUpdate.Table.Table) 251 c.Assert(header.GetEventType(), check.Equals, canal.EventType_UPDATE) 252 store := entry.GetStoreValue() 253 c.Assert(store, check.NotNil) 254 rc := &canal.RowChange{} 255 err = proto.Unmarshal(store, rc) 256 c.Assert(err, check.IsNil) 257 c.Assert(rc.GetIsDdl(), check.IsFalse) 258 rowDatas := rc.GetRowDatas() 259 c.Assert(len(rowDatas), check.Equals, 1) 260 261 beforeColumns := rowDatas[0].BeforeColumns 262 c.Assert(len(beforeColumns), check.Equals, len(testCaseUpdate.PreColumns)) 263 for _, col := range beforeColumns { 264 c.Assert(col.GetUpdated(), check.IsTrue) 265 switch col.GetName() { 266 case "id": 267 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeBIGINT)) 268 c.Assert(col.GetIsKey(), check.IsTrue) 269 c.Assert(col.GetIsNull(), check.IsFalse) 270 c.Assert(col.GetValue(), check.Equals, "2") 271 c.Assert(col.GetMysqlType(), check.Equals, "int") 272 case "name": 273 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeVARCHAR)) 274 c.Assert(col.GetIsKey(), check.IsFalse) 275 c.Assert(col.GetIsNull(), check.IsFalse) 276 c.Assert(col.GetValue(), check.Equals, "Nancy") 277 c.Assert(col.GetMysqlType(), check.Equals, "varchar") 278 } 279 } 280 281 afterColumns := rowDatas[0].AfterColumns 282 c.Assert(len(afterColumns), check.Equals, len(testCaseUpdate.Columns)) 283 for _, col := range afterColumns { 284 c.Assert(col.GetUpdated(), check.IsTrue) 285 switch col.GetName() { 286 case "id": 287 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeBIGINT)) 288 c.Assert(col.GetIsKey(), check.IsTrue) 289 c.Assert(col.GetIsNull(), check.IsFalse) 290 c.Assert(col.GetValue(), check.Equals, "1") 291 c.Assert(col.GetMysqlType(), check.Equals, "int") 292 case "name": 293 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeVARCHAR)) 294 c.Assert(col.GetIsKey(), check.IsFalse) 295 c.Assert(col.GetIsNull(), check.IsFalse) 296 c.Assert(col.GetValue(), check.Equals, "Bob") 297 c.Assert(col.GetMysqlType(), check.Equals, "varchar") 298 } 299 } 300 } 301 302 func testDelete(c *check.C) { 303 testCaseDelete := &model.RowChangedEvent{ 304 CommitTs: 417318403368288260, 305 Table: &model.TableName{ 306 Schema: "cdc", 307 Table: "person", 308 }, 309 PreColumns: []*model.Column{ 310 {Name: "id", Type: mysql.TypeLong, Flag: model.PrimaryKeyFlag, Value: 1}, 311 }, 312 } 313 314 builder := NewCanalEntryBuilder() 315 entry, err := builder.FromRowEvent(testCaseDelete) 316 c.Assert(err, check.IsNil) 317 c.Assert(entry.GetEntryType(), check.Equals, canal.EntryType_ROWDATA) 318 header := entry.GetHeader() 319 c.Assert(header.GetSchemaName(), check.Equals, testCaseDelete.Table.Schema) 320 c.Assert(header.GetTableName(), check.Equals, testCaseDelete.Table.Table) 321 c.Assert(header.GetEventType(), check.Equals, canal.EventType_DELETE) 322 store := entry.GetStoreValue() 323 c.Assert(store, check.NotNil) 324 rc := &canal.RowChange{} 325 err = proto.Unmarshal(store, rc) 326 c.Assert(err, check.IsNil) 327 c.Assert(rc.GetIsDdl(), check.IsFalse) 328 rowDatas := rc.GetRowDatas() 329 c.Assert(len(rowDatas), check.Equals, 1) 330 331 columns := rowDatas[0].BeforeColumns 332 c.Assert(len(columns), check.Equals, len(testCaseDelete.PreColumns)) 333 for _, col := range columns { 334 c.Assert(col.GetUpdated(), check.IsFalse) 335 switch col.GetName() { 336 case "id": 337 c.Assert(col.GetSqlType(), check.Equals, int32(JavaSQLTypeBIGINT)) 338 c.Assert(col.GetIsKey(), check.IsTrue) 339 c.Assert(col.GetIsNull(), check.IsFalse) 340 c.Assert(col.GetValue(), check.Equals, "1") 341 c.Assert(col.GetMysqlType(), check.Equals, "int") 342 } 343 } 344 } 345 346 func testDdl(c *check.C) { 347 testCaseDdl := &model.DDLEvent{ 348 CommitTs: 417318403368288260, 349 TableInfo: &model.SimpleTableInfo{ 350 Schema: "cdc", Table: "person", 351 }, 352 Query: "create table person(id int, name varchar(32), tiny tinyint unsigned, comment text, primary key(id))", 353 Type: mm.ActionCreateTable, 354 } 355 builder := NewCanalEntryBuilder() 356 entry, err := builder.FromDdlEvent(testCaseDdl) 357 c.Assert(err, check.IsNil) 358 c.Assert(entry.GetEntryType(), check.Equals, canal.EntryType_ROWDATA) 359 header := entry.GetHeader() 360 c.Assert(header.GetSchemaName(), check.Equals, testCaseDdl.TableInfo.Schema) 361 c.Assert(header.GetTableName(), check.Equals, testCaseDdl.TableInfo.Table) 362 c.Assert(header.GetEventType(), check.Equals, canal.EventType_CREATE) 363 store := entry.GetStoreValue() 364 c.Assert(store, check.NotNil) 365 rc := &canal.RowChange{} 366 err = proto.Unmarshal(store, rc) 367 c.Assert(err, check.IsNil) 368 c.Assert(rc.GetIsDdl(), check.IsTrue) 369 c.Assert(rc.GetDdlSchemaName(), check.Equals, testCaseDdl.TableInfo.Schema) 370 }