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  }