github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sink/codec/open/open_protocol_encoder_test.go (about)

     1  // Copyright 2022 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 open
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"testing"
    20  
    21  	"github.com/pingcap/tiflow/cdc/entry"
    22  	"github.com/pingcap/tiflow/cdc/model"
    23  	"github.com/pingcap/tiflow/pkg/compression"
    24  	"github.com/pingcap/tiflow/pkg/config"
    25  	cerror "github.com/pingcap/tiflow/pkg/errors"
    26  	"github.com/pingcap/tiflow/pkg/sink/codec"
    27  	"github.com/pingcap/tiflow/pkg/sink/codec/common"
    28  	"github.com/pingcap/tiflow/pkg/sink/codec/internal"
    29  	"github.com/pingcap/tiflow/pkg/sink/codec/utils"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  func TestBuildOpenProtocolBatchEncoder(t *testing.T) {
    34  	t.Parallel()
    35  	codecConfig := common.NewConfig(config.ProtocolOpen)
    36  	builder := &batchEncoderBuilder{config: codecConfig}
    37  	encoder, ok := builder.Build().(*BatchEncoder)
    38  	require.True(t, ok)
    39  	require.NotNil(t, encoder.config)
    40  }
    41  
    42  func TestMaxMessageBytes(t *testing.T) {
    43  	helper := entry.NewSchemaTestHelper(t)
    44  	defer helper.Close()
    45  
    46  	_ = helper.DDL2Event(`create table test.t(a varchar(10) primary key, b varchar(10))`)
    47  	event := helper.DML2Event(`insert into test.t values ("aa", "bb")`, "test", "t")
    48  
    49  	ctx := context.Background()
    50  	topic := ""
    51  	// just can hold it.
    52  	a := 188
    53  	codecConfig := common.NewConfig(config.ProtocolOpen).WithMaxMessageBytes(a)
    54  	builder, err := NewBatchEncoderBuilder(ctx, codecConfig)
    55  	require.NoError(t, err)
    56  	encoder := builder.Build()
    57  	err = encoder.AppendRowChangedEvent(ctx, topic, event, nil)
    58  	require.NoError(t, err)
    59  
    60  	// cannot hold a single message
    61  	codecConfig = codecConfig.WithMaxMessageBytes(a - 1)
    62  	builder, err = NewBatchEncoderBuilder(ctx, codecConfig)
    63  	require.NoError(t, err)
    64  	encoder = builder.Build()
    65  	err = encoder.AppendRowChangedEvent(ctx, topic, event, nil)
    66  	require.ErrorIs(t, err, cerror.ErrMessageTooLarge)
    67  
    68  	// make sure each batch's `Length` not greater than `max-message-bytes`
    69  	codecConfig = codecConfig.WithMaxMessageBytes(256)
    70  	builder, err = NewBatchEncoderBuilder(ctx, codecConfig)
    71  	require.NoError(t, err)
    72  	encoder = builder.Build()
    73  	for i := 0; i < 10000; i++ {
    74  		err := encoder.AppendRowChangedEvent(ctx, topic, event, nil)
    75  		require.NoError(t, err)
    76  	}
    77  
    78  	messages := encoder.Build()
    79  	for _, msg := range messages {
    80  		require.LessOrEqual(t, msg.Length(), 256)
    81  	}
    82  }
    83  
    84  func TestMaxBatchSize(t *testing.T) {
    85  	helper := entry.NewSchemaTestHelper(t)
    86  	defer helper.Close()
    87  
    88  	_ = helper.DDL2Event(`create table test.t(a varchar(10) primary key, b varchar(10))`)
    89  	event := helper.DML2Event(`insert into test.t values ("aa", "bb")`, "test", "t")
    90  
    91  	ctx := context.Background()
    92  	codecConfig := common.NewConfig(config.ProtocolOpen).WithMaxMessageBytes(1048576)
    93  	codecConfig.MaxBatchSize = 64
    94  	builder, err := NewBatchEncoderBuilder(ctx, codecConfig)
    95  	require.NoError(t, err)
    96  	encoder := builder.Build()
    97  
    98  	for i := 0; i < 10000; i++ {
    99  		err := encoder.AppendRowChangedEvent(ctx, "", event, nil)
   100  		require.NoError(t, err)
   101  	}
   102  
   103  	messages := encoder.Build()
   104  
   105  	decoder, err := NewBatchDecoder(context.Background(), codecConfig, nil)
   106  	require.NoError(t, err)
   107  	sum := 0
   108  	for _, msg := range messages {
   109  		err := decoder.AddKeyValue(msg.Key, msg.Value)
   110  		require.NoError(t, err)
   111  		count := 0
   112  		for {
   113  			v, hasNext, err := decoder.HasNext()
   114  			require.NoError(t, err)
   115  			if !hasNext {
   116  				break
   117  			}
   118  
   119  			require.Equal(t, model.MessageTypeRow, v)
   120  			_, err = decoder.NextRowChangedEvent()
   121  			require.NoError(t, err)
   122  			count++
   123  		}
   124  		require.LessOrEqual(t, count, 64)
   125  		sum += count
   126  	}
   127  	require.Equal(t, 10000, sum)
   128  }
   129  
   130  func TestOpenProtocolAppendRowChangedEventWithCallback(t *testing.T) {
   131  	helper := entry.NewSchemaTestHelper(t)
   132  	defer helper.Close()
   133  
   134  	_ = helper.DDL2Event(`create table test.t(a varchar(10) primary key, b varchar(10))`)
   135  	event := helper.DML2Event(`insert into test.t values ("aa", "bb")`, "test", "t")
   136  
   137  	cfg := common.NewConfig(config.ProtocolOpen)
   138  	// Set the max batch size to 2, so that we can test the callback.
   139  	cfg.MaxBatchSize = 2
   140  	builder := &batchEncoderBuilder{config: cfg}
   141  	encoder, ok := builder.Build().(*BatchEncoder)
   142  	require.True(t, ok)
   143  
   144  	count := 0
   145  
   146  	tests := []struct {
   147  		row      *model.RowChangedEvent
   148  		callback func()
   149  	}{
   150  		{
   151  			row: event,
   152  			callback: func() {
   153  				count += 1
   154  			},
   155  		},
   156  		{
   157  			row: event,
   158  			callback: func() {
   159  				count += 2
   160  			},
   161  		},
   162  		{
   163  			row: event,
   164  			callback: func() {
   165  				count += 3
   166  			},
   167  		},
   168  		{
   169  			row: event,
   170  			callback: func() {
   171  				count += 4
   172  			},
   173  		},
   174  		{
   175  			row: event,
   176  			callback: func() {
   177  				count += 5
   178  			},
   179  		},
   180  	}
   181  
   182  	// Empty build makes sure that the callback build logic not broken.
   183  	msgs := encoder.Build()
   184  	require.Len(t, msgs, 0, "no message should be built and no panic")
   185  
   186  	// Append the events.
   187  	for _, test := range tests {
   188  		err := encoder.AppendRowChangedEvent(context.Background(), "", test.row, test.callback)
   189  		require.NoError(t, err)
   190  	}
   191  	require.Equal(t, 0, count, "nothing should be called")
   192  
   193  	msgs = encoder.Build()
   194  	require.Len(t, msgs, 3, "expected 3 messages")
   195  	msgs[0].Callback()
   196  	require.Equal(t, 3, count, "expected 2 callbacks be called")
   197  	msgs[1].Callback()
   198  	require.Equal(t, 10, count, "expected 2 callbacks be called")
   199  	msgs[2].Callback()
   200  	require.Equal(t, 15, count, "expected 1 callback be called")
   201  }
   202  
   203  func TestOpenProtocolBatchCodec(t *testing.T) {
   204  	codecConfig := common.NewConfig(config.ProtocolOpen).WithMaxMessageBytes(8192)
   205  	codecConfig.MaxBatchSize = 64
   206  	builder, err := NewBatchEncoderBuilder(context.Background(), codecConfig)
   207  	require.NoError(t, err)
   208  	internal.TestBatchCodec(t, builder,
   209  		func(key []byte, value []byte) (codec.RowEventDecoder, error) {
   210  			decoder, err := NewBatchDecoder(context.Background(), codecConfig, nil)
   211  			require.NoError(t, err)
   212  			err = decoder.AddKeyValue(key, value)
   213  			return decoder, err
   214  		})
   215  }
   216  
   217  func TestEncodeDecodeE2E(t *testing.T) {
   218  	helper := entry.NewSchemaTestHelper(t)
   219  	defer helper.Close()
   220  
   221  	_ = helper.DDL2Event(`create table test.t(a varchar(10) primary key, b varchar(10))`)
   222  	event := helper.DML2Event(`insert into test.t values ("aa", "bb")`, "test", "t")
   223  
   224  	ctx := context.Background()
   225  	topic := "test"
   226  
   227  	codecConfig := common.NewConfig(config.ProtocolOpen)
   228  	codecConfig.OpenOutputOldValue = false
   229  	builder, err := NewBatchEncoderBuilder(ctx, codecConfig)
   230  	require.NoError(t, err)
   231  	encoder := builder.Build()
   232  
   233  	err = encoder.AppendRowChangedEvent(ctx, topic, event, func() {})
   234  	require.NoError(t, err)
   235  
   236  	message := encoder.Build()[0]
   237  
   238  	decoder, err := NewBatchDecoder(ctx, codecConfig, nil)
   239  	require.NoError(t, err)
   240  
   241  	err = decoder.AddKeyValue(message.Key, message.Value)
   242  	require.NoError(t, err)
   243  
   244  	messageType, hasNext, err := decoder.HasNext()
   245  	require.NoError(t, err)
   246  	require.True(t, hasNext)
   247  	require.Equal(t, messageType, model.MessageTypeRow)
   248  
   249  	decoded, err := decoder.NextRowChangedEvent()
   250  	require.NoError(t, err)
   251  
   252  	obtainedColumns := make(map[string]*model.ColumnData, len(decoded.Columns))
   253  	for _, column := range decoded.Columns {
   254  		colName := decoded.TableInfo.ForceGetColumnName(column.ColumnID)
   255  		obtainedColumns[colName] = column
   256  	}
   257  	for _, col := range event.Columns {
   258  		colName := event.TableInfo.ForceGetColumnName(col.ColumnID)
   259  		decoded, ok := obtainedColumns[colName]
   260  		require.True(t, ok)
   261  		require.EqualValues(t, col.Value, decoded.Value)
   262  	}
   263  }
   264  
   265  func TestE2EDDLCompression(t *testing.T) {
   266  	helper := entry.NewSchemaTestHelper(t)
   267  	defer helper.Close()
   268  
   269  	ddlEvent := helper.DDL2Event(`create table test.person(id int, name varchar(32), tiny tinyint unsigned, comment text, primary key(id))`)
   270  
   271  	ctx := context.Background()
   272  
   273  	codecConfig := common.NewConfig(config.ProtocolOpen)
   274  	codecConfig.LargeMessageHandle.LargeMessageHandleCompression = compression.Snappy
   275  
   276  	builder, err := NewBatchEncoderBuilder(ctx, codecConfig)
   277  	require.NoError(t, err)
   278  	encoder := builder.Build()
   279  
   280  	// encode DDL event
   281  	message, err := encoder.EncodeDDLEvent(ddlEvent)
   282  	require.NoError(t, err)
   283  
   284  	decoder, err := NewBatchDecoder(ctx, codecConfig, nil)
   285  	require.NoError(t, err)
   286  
   287  	err = decoder.AddKeyValue(message.Key, message.Value)
   288  	require.NoError(t, err)
   289  
   290  	messageType, hasNext, err := decoder.HasNext()
   291  	require.NoError(t, err)
   292  	require.True(t, hasNext)
   293  	require.Equal(t, messageType, model.MessageTypeDDL)
   294  
   295  	decodedDDL, err := decoder.NextDDLEvent()
   296  	require.NoError(t, err)
   297  
   298  	require.Equal(t, decodedDDL.Query, ddlEvent.Query)
   299  	require.Equal(t, decodedDDL.CommitTs, ddlEvent.CommitTs)
   300  	require.Equal(t, decodedDDL.TableInfo.TableName.Schema, ddlEvent.TableInfo.TableName.Schema)
   301  	require.Equal(t, decodedDDL.TableInfo.TableName.Table, ddlEvent.TableInfo.TableName.Table)
   302  
   303  	// encode checkpoint event
   304  	waterMark := uint64(2333)
   305  	message, err = encoder.EncodeCheckpointEvent(waterMark)
   306  	require.NoError(t, err)
   307  
   308  	err = decoder.AddKeyValue(message.Key, message.Value)
   309  	require.NoError(t, err)
   310  
   311  	messageType, hasNext, err = decoder.HasNext()
   312  	require.NoError(t, err)
   313  	require.True(t, hasNext)
   314  	require.Equal(t, messageType, model.MessageTypeResolved)
   315  
   316  	decodedWatermark, err := decoder.NextResolvedEvent()
   317  	require.NoError(t, err)
   318  	require.Equal(t, decodedWatermark, waterMark)
   319  }
   320  
   321  func TestE2EHandleKeyOnlyEvent(t *testing.T) {
   322  	_, insertEvent, _, _ := utils.NewLargeEvent4Test(t, config.GetDefaultReplicaConfig())
   323  
   324  	codecConfig := common.NewConfig(config.ProtocolOpen)
   325  	codecConfig.LargeMessageHandle.LargeMessageHandleOption = config.LargeMessageHandleOptionHandleKeyOnly
   326  	codecConfig.LargeMessageHandle.LargeMessageHandleCompression = compression.Snappy
   327  
   328  	codecConfig.MaxMessageBytes = 251
   329  
   330  	ctx := context.Background()
   331  	builder, err := NewBatchEncoderBuilder(ctx, codecConfig)
   332  	require.NoError(t, err)
   333  	encoder := builder.Build()
   334  
   335  	topic := "test"
   336  	err = encoder.AppendRowChangedEvent(ctx, topic, insertEvent, nil)
   337  	require.NoError(t, err)
   338  
   339  	message := encoder.Build()[0]
   340  
   341  	decoder, err := NewBatchDecoder(ctx, codecConfig, &sql.DB{})
   342  	require.NoError(t, err)
   343  	err = decoder.AddKeyValue(message.Key, message.Value)
   344  	require.NoError(t, err)
   345  	tp, hasNext, err := decoder.HasNext()
   346  	require.NoError(t, err)
   347  	require.True(t, hasNext)
   348  	require.Equal(t, model.MessageTypeRow, tp)
   349  
   350  	require.True(t, decoder.(*BatchDecoder).nextKey.OnlyHandleKey)
   351  
   352  	nextEvent := decoder.(*BatchDecoder).nextEvent
   353  	require.NotNil(t, nextEvent)
   354  
   355  	obtainedColumns := make(map[string]*model.ColumnData, len(nextEvent.Columns))
   356  	for _, col := range nextEvent.Columns {
   357  		colName := nextEvent.TableInfo.ForceGetColumnName(col.ColumnID)
   358  		obtainedColumns[colName] = col
   359  		require.True(t, nextEvent.TableInfo.ForceGetColumnFlagType(col.ColumnID).IsHandleKey())
   360  	}
   361  
   362  	for _, col := range insertEvent.Columns {
   363  		colName := insertEvent.TableInfo.ForceGetColumnName(col.ColumnID)
   364  		if insertEvent.TableInfo.ForceGetColumnFlagType(col.ColumnID).IsHandleKey() {
   365  			require.Contains(t, obtainedColumns, colName)
   366  		}
   367  	}
   368  }
   369  
   370  func TestE2EClaimCheckMessage(t *testing.T) {
   371  	_, insertEvent, _, _ := utils.NewLargeEvent4Test(t, config.GetDefaultReplicaConfig())
   372  
   373  	ctx := context.Background()
   374  	topic := ""
   375  
   376  	a := 258
   377  	codecConfig := common.NewConfig(config.ProtocolOpen).WithMaxMessageBytes(a)
   378  	codecConfig.LargeMessageHandle.LargeMessageHandleOption = config.LargeMessageHandleOptionClaimCheck
   379  	codecConfig.LargeMessageHandle.LargeMessageHandleCompression = compression.LZ4
   380  	codecConfig.LargeMessageHandle.ClaimCheckStorageURI = "file:///tmp/claim-check"
   381  
   382  	builder, err := NewBatchEncoderBuilder(ctx, codecConfig)
   383  	require.NoError(t, err)
   384  	encoder := builder.Build()
   385  
   386  	err = encoder.AppendRowChangedEvent(ctx, topic, insertEvent, func() {})
   387  	require.NoError(t, err)
   388  
   389  	// cannot hold this message, it's encoded as the claim check location message.
   390  	err = encoder.AppendRowChangedEvent(ctx, topic, insertEvent, func() {})
   391  	require.NoError(t, err)
   392  
   393  	messages := encoder.Build()
   394  	require.Len(t, messages, 2)
   395  
   396  	claimCheckLocationMessage := messages[1]
   397  	decoder, err := NewBatchDecoder(ctx, codecConfig, nil)
   398  	require.NoError(t, err)
   399  	err = decoder.AddKeyValue(claimCheckLocationMessage.Key, claimCheckLocationMessage.Value)
   400  	require.NoError(t, err)
   401  
   402  	messageType, ok, err := decoder.HasNext()
   403  	require.NoError(t, err)
   404  	require.Equal(t, messageType, model.MessageTypeRow)
   405  	require.True(t, ok)
   406  
   407  	decodedLargeEvent, err := decoder.NextRowChangedEvent()
   408  	require.NoError(t, err)
   409  
   410  	require.Equal(t, insertEvent.CommitTs, decodedLargeEvent.CommitTs)
   411  	require.Equal(t, insertEvent.TableInfo.GetTableName(), decodedLargeEvent.TableInfo.GetTableName())
   412  
   413  	decodedColumns := make(map[string]*model.ColumnData, len(decodedLargeEvent.Columns))
   414  	for _, column := range decodedLargeEvent.Columns {
   415  		colName := decodedLargeEvent.TableInfo.ForceGetColumnName(column.ColumnID)
   416  		decodedColumns[colName] = column
   417  	}
   418  
   419  	for _, column := range insertEvent.Columns {
   420  		colName := insertEvent.TableInfo.ForceGetColumnName(column.ColumnID)
   421  		decodedColumn, ok := decodedColumns[colName]
   422  		require.True(t, ok)
   423  		require.Equal(t, column.Value, decodedColumn.Value, colName)
   424  	}
   425  }
   426  
   427  func TestOutputOldValueFalse(t *testing.T) {
   428  	helper := entry.NewSchemaTestHelper(t)
   429  	defer helper.Close()
   430  
   431  	_ = helper.DDL2Event(`create table test.t(a varchar(10) primary key, b varchar(10))`)
   432  	event := helper.DML2Event(`insert into test.t values ("aa", "bb")`, "test", "t")
   433  	event.PreColumns = event.Columns
   434  
   435  	ctx := context.Background()
   436  	topic := "test"
   437  
   438  	codecConfig := common.NewConfig(config.ProtocolOpen)
   439  	codecConfig.OpenOutputOldValue = false
   440  	builder, err := NewBatchEncoderBuilder(ctx, codecConfig)
   441  	require.NoError(t, err)
   442  	encoder := builder.Build()
   443  
   444  	err = encoder.AppendRowChangedEvent(ctx, topic, event, func() {})
   445  	require.NoError(t, err)
   446  
   447  	message := encoder.Build()[0]
   448  
   449  	decoder, err := NewBatchDecoder(ctx, codecConfig, nil)
   450  	require.NoError(t, err)
   451  
   452  	err = decoder.AddKeyValue(message.Key, message.Value)
   453  	require.NoError(t, err)
   454  
   455  	messageType, hasNext, err := decoder.HasNext()
   456  	require.NoError(t, err)
   457  	require.True(t, hasNext)
   458  	require.Equal(t, messageType, model.MessageTypeRow)
   459  
   460  	decoded, err := decoder.NextRowChangedEvent()
   461  	require.NoError(t, err)
   462  	require.Nil(t, decoded.PreColumns)
   463  }