github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/sink/codec/avro_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  	"context"
    18  	"time"
    19  
    20  	"github.com/linkedin/goavro/v2"
    21  	"github.com/pingcap/check"
    22  	"github.com/pingcap/errors"
    23  	"github.com/pingcap/log"
    24  	model2 "github.com/pingcap/parser/model"
    25  	"github.com/pingcap/parser/mysql"
    26  	"github.com/pingcap/ticdc/cdc/model"
    27  	"github.com/pingcap/ticdc/cdc/puller"
    28  	"github.com/pingcap/ticdc/pkg/regionspan"
    29  	"github.com/pingcap/ticdc/pkg/security"
    30  	"github.com/pingcap/ticdc/pkg/util/testleak"
    31  	"github.com/pingcap/tidb/types"
    32  	"go.uber.org/zap"
    33  )
    34  
    35  type avroBatchEncoderSuite struct {
    36  	encoder *AvroEventBatchEncoder
    37  }
    38  
    39  var _ = check.Suite(&avroBatchEncoderSuite{})
    40  
    41  func (s *avroBatchEncoderSuite) SetUpSuite(c *check.C) {
    42  	startHTTPInterceptForTestingRegistry(c)
    43  
    44  	keyManager, err := NewAvroSchemaManager(context.Background(), &security.Credential{}, "http://127.0.0.1:8081", "-key")
    45  	c.Assert(err, check.IsNil)
    46  
    47  	valueManager, err := NewAvroSchemaManager(context.Background(), &security.Credential{}, "http://127.0.0.1:8081", "-value")
    48  	c.Assert(err, check.IsNil)
    49  
    50  	s.encoder = &AvroEventBatchEncoder{
    51  		valueSchemaManager: valueManager,
    52  		keySchemaManager:   keyManager,
    53  		resultBuf:          make([]*MQMessage, 0, 4096),
    54  	}
    55  }
    56  
    57  func (s *avroBatchEncoderSuite) TearDownSuite(c *check.C) {
    58  	stopHTTPInterceptForTestingRegistry()
    59  }
    60  
    61  func (s *avroBatchEncoderSuite) TestAvroEncodeOnly(c *check.C) {
    62  	defer testleak.AfterTest(c)()
    63  	avroCodec, err := goavro.NewCodec(`
    64          {
    65            "type": "record",
    66            "name": "test1",
    67            "fields" : [
    68              {"name": "id", "type": ["null", "int"], "default": null},
    69  			{"name": "myint", "type": ["null", "int"], "default": null},
    70  			{"name": "mybool", "type": ["null", "int"], "default": null},
    71  			{"name": "myfloat", "type": ["null", "float"], "default": null},
    72  			{"name": "mybytes", "type": ["null", "bytes"], "default": null},
    73  			{"name": "ts", "type": ["null", {"type": "long", "logicalType": "timestamp-millis"}], "default": null}
    74            ]
    75          }`)
    76  
    77  	c.Assert(err, check.IsNil)
    78  
    79  	table := model.TableName{
    80  		Schema: "testdb",
    81  		Table:  "test1",
    82  	}
    83  
    84  	r, err := avroEncode(&table, s.encoder.valueSchemaManager, 1, []*model.Column{
    85  		{Name: "id", Value: int64(1), Type: mysql.TypeLong},
    86  		{Name: "myint", Value: int64(2), Type: mysql.TypeLong},
    87  		{Name: "mybool", Value: int64(1), Type: mysql.TypeTiny},
    88  		{Name: "myfloat", Value: float64(3.14), Type: mysql.TypeFloat},
    89  		{Name: "mybytes", Value: []byte("Hello World"), Type: mysql.TypeBlob},
    90  		{Name: "ts", Value: time.Now().Format(types.TimeFSPFormat), Type: mysql.TypeTimestamp},
    91  	}, time.Local)
    92  	c.Assert(err, check.IsNil)
    93  
    94  	res, _, err := avroCodec.NativeFromBinary(r.data)
    95  	c.Check(err, check.IsNil)
    96  	c.Check(res, check.NotNil)
    97  
    98  	txt, err := avroCodec.TextualFromNative(nil, res)
    99  	c.Check(err, check.IsNil)
   100  	log.Info("TestAvroEncodeOnly", zap.ByteString("result", txt))
   101  }
   102  
   103  func (s *avroBatchEncoderSuite) TestAvroTimeZone(c *check.C) {
   104  	defer testleak.AfterTest(c)()
   105  	avroCodec, err := goavro.NewCodec(`
   106          {
   107            "type": "record",
   108            "name": "test1",
   109            "fields" : [
   110              {"name": "id", "type": ["null", "int"], "default": null},
   111  			{"name": "myint", "type": ["null", "int"], "default": null},
   112  			{"name": "mybool", "type": ["null", "int"], "default": null},
   113  			{"name": "myfloat", "type": ["null", "float"], "default": null},
   114  			{"name": "mybytes", "type": ["null", "bytes"], "default": null},
   115  			{"name": "ts", "type": ["null", {"type": "long", "logicalType": "timestamp-millis"}], "default": null}
   116            ]
   117          }`)
   118  
   119  	c.Assert(err, check.IsNil)
   120  
   121  	table := model.TableName{
   122  		Schema: "testdb",
   123  		Table:  "test1",
   124  	}
   125  
   126  	location, err := time.LoadLocation("UTC")
   127  	c.Check(err, check.IsNil)
   128  
   129  	timestamp := time.Now()
   130  	r, err := avroEncode(&table, s.encoder.valueSchemaManager, 1, []*model.Column{
   131  		{Name: "id", Value: int64(1), Type: mysql.TypeLong},
   132  		{Name: "myint", Value: int64(2), Type: mysql.TypeLong},
   133  		{Name: "mybool", Value: int64(1), Type: mysql.TypeTiny},
   134  		{Name: "myfloat", Value: float64(3.14), Type: mysql.TypeFloat},
   135  		{Name: "mybytes", Value: []byte("Hello World"), Type: mysql.TypeBlob},
   136  		{Name: "ts", Value: timestamp.In(location).Format(types.TimeFSPFormat), Type: mysql.TypeTimestamp},
   137  	}, location)
   138  	c.Assert(err, check.IsNil)
   139  
   140  	res, _, err := avroCodec.NativeFromBinary(r.data)
   141  	c.Check(err, check.IsNil)
   142  	c.Check(res, check.NotNil)
   143  	actual := (res.(map[string]interface{}))["ts"].(map[string]interface{})["long.timestamp-millis"].(time.Time)
   144  	c.Check(actual.Local().Sub(timestamp), check.LessEqual, time.Millisecond)
   145  }
   146  
   147  func (s *avroBatchEncoderSuite) TestAvroEnvelope(c *check.C) {
   148  	defer testleak.AfterTest(c)()
   149  	avroCodec, err := goavro.NewCodec(`
   150          {
   151            "type": "record",
   152            "name": "test2",
   153            "fields" : [
   154              {"name": "id", "type": "int", "default": 0}
   155            ]
   156          }`)
   157  
   158  	c.Assert(err, check.IsNil)
   159  
   160  	testNativeData := make(map[string]interface{})
   161  	testNativeData["id"] = 7
   162  
   163  	bin, err := avroCodec.BinaryFromNative(nil, testNativeData)
   164  	c.Check(err, check.IsNil)
   165  
   166  	res := avroEncodeResult{
   167  		data:       bin,
   168  		registryID: 7,
   169  	}
   170  
   171  	evlp, err := res.toEnvelope()
   172  	c.Check(err, check.IsNil)
   173  
   174  	c.Assert(evlp[0], check.Equals, magicByte)
   175  	c.Assert(evlp[1:5], check.BytesEquals, []byte{0, 0, 0, 7})
   176  
   177  	parsed, _, err := avroCodec.NativeFromBinary(evlp[5:])
   178  	c.Assert(err, check.IsNil)
   179  	c.Assert(parsed, check.NotNil)
   180  
   181  	id, exists := parsed.(map[string]interface{})["id"]
   182  	c.Assert(exists, check.IsTrue)
   183  	c.Assert(id, check.Equals, int32(7))
   184  }
   185  
   186  func (s *avroBatchEncoderSuite) TestAvroEncode(c *check.C) {
   187  	defer testleak.AfterTest(c)()
   188  	testCaseUpdate := &model.RowChangedEvent{
   189  		CommitTs: 417318403368288260,
   190  		Table: &model.TableName{
   191  			Schema: "test",
   192  			Table:  "person",
   193  		},
   194  		Columns: []*model.Column{
   195  			{Name: "id", Type: mysql.TypeLong, Flag: model.HandleKeyFlag, Value: int64(1)},
   196  			{Name: "name", Type: mysql.TypeVarchar, Value: "Bob"},
   197  			{Name: "tiny", Type: mysql.TypeTiny, Value: int64(255)},
   198  			{Name: "utiny", Type: mysql.TypeTiny, Flag: model.UnsignedFlag, Value: uint64(100)},
   199  			{Name: "comment", Type: mysql.TypeBlob, Value: []byte("测试")},
   200  		},
   201  	}
   202  
   203  	testCaseDdl := &model.DDLEvent{
   204  		CommitTs: 417318403368288260,
   205  		TableInfo: &model.SimpleTableInfo{
   206  			Schema: "test", Table: "person",
   207  		},
   208  		Query: "create table person(id int, name varchar(32), tiny tinyint unsigned, comment text, primary key(id))",
   209  		Type:  model2.ActionCreateTable,
   210  	}
   211  
   212  	ctx, cancel := context.WithCancel(context.Background())
   213  	defer cancel()
   214  
   215  	pm := puller.NewMockPullerManager(c, true)
   216  	defer pm.TearDown()
   217  	pm.MustExec(testCaseDdl.Query)
   218  	ddlPlr := pm.CreatePuller(0, []regionspan.ComparableSpan{regionspan.ToComparableSpan(regionspan.GetDDLSpan())})
   219  	go func() {
   220  		err := ddlPlr.Run(ctx)
   221  		if err != nil && errors.Cause(err) != context.Canceled {
   222  			c.Fail()
   223  		}
   224  	}()
   225  
   226  	info := pm.GetTableInfo("test", "person")
   227  	testCaseDdl.TableInfo = new(model.SimpleTableInfo)
   228  	testCaseDdl.TableInfo.Schema = "test"
   229  	testCaseDdl.TableInfo.Table = "person"
   230  	testCaseDdl.TableInfo.ColumnInfo = make([]*model.ColumnInfo, len(info.Columns))
   231  	for i, v := range info.Columns {
   232  		testCaseDdl.TableInfo.ColumnInfo[i] = new(model.ColumnInfo)
   233  		testCaseDdl.TableInfo.ColumnInfo[i].FromTiColumnInfo(v)
   234  	}
   235  
   236  	_, err := s.encoder.EncodeDDLEvent(testCaseDdl)
   237  	c.Check(err, check.IsNil)
   238  
   239  	_, err = s.encoder.AppendRowChangedEvent(testCaseUpdate)
   240  	c.Check(err, check.IsNil)
   241  }