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 }