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

     1  // Copyright 2024 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 debezium
    15  
    16  import (
    17  	"bytes"
    18  	"encoding/binary"
    19  	"fmt"
    20  	"io"
    21  	"strconv"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/pingcap/log"
    26  	"github.com/pingcap/tidb/pkg/parser/mysql"
    27  	"github.com/pingcap/tidb/pkg/types"
    28  	"github.com/pingcap/tidb/pkg/util/hack"
    29  	"github.com/pingcap/tiflow/cdc/model"
    30  	cerror "github.com/pingcap/tiflow/pkg/errors"
    31  	"github.com/pingcap/tiflow/pkg/sink/codec/common"
    32  	"github.com/pingcap/tiflow/pkg/util"
    33  	"github.com/tikv/client-go/v2/oracle"
    34  	"go.uber.org/zap"
    35  )
    36  
    37  type dbzCodec struct {
    38  	config    *common.Config
    39  	clusterID string
    40  	nowFunc   func() time.Time
    41  }
    42  
    43  func (c *dbzCodec) writeDebeziumFieldValues(
    44  	writer *util.JSONWriter,
    45  	fieldName string,
    46  	cols []*model.Column,
    47  	tableInfo *model.TableInfo,
    48  ) error {
    49  	var err error
    50  	colInfos := tableInfo.GetColInfosForRowChangedEvent()
    51  	writer.WriteObjectField(fieldName, func() {
    52  		for i, col := range cols {
    53  			err = c.writeDebeziumFieldValue(writer, col, colInfos[i].Ft)
    54  			if err != nil {
    55  				break
    56  			}
    57  		}
    58  	})
    59  	return err
    60  }
    61  
    62  func (c *dbzCodec) writeDebeziumFieldSchema(
    63  	writer *util.JSONWriter,
    64  	col *model.Column,
    65  	ft *types.FieldType,
    66  ) {
    67  	switch col.Type {
    68  	case mysql.TypeBit:
    69  		n := ft.GetFlen()
    70  		if n == 1 {
    71  			writer.WriteObjectElement(func() {
    72  				writer.WriteStringField("type", "boolean")
    73  				writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
    74  				writer.WriteStringField("field", col.Name)
    75  			})
    76  		} else {
    77  			writer.WriteObjectElement(func() {
    78  				writer.WriteStringField("type", "bytes")
    79  				writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
    80  				writer.WriteStringField("name", "io.debezium.data.Bits")
    81  				writer.WriteIntField("version", 1)
    82  				writer.WriteObjectField("parameters", func() {
    83  					writer.WriteStringField("length", fmt.Sprintf("%d", n))
    84  				})
    85  				writer.WriteStringField("field", col.Name)
    86  			})
    87  		}
    88  
    89  	case mysql.TypeVarchar, mysql.TypeString, mysql.TypeVarString, mysql.TypeTinyBlob,
    90  		mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeBlob:
    91  		writer.WriteObjectElement(func() {
    92  			writer.WriteStringField("type", "string")
    93  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
    94  			writer.WriteStringField("field", col.Name)
    95  		})
    96  
    97  	case mysql.TypeEnum:
    98  		writer.WriteObjectElement(func() {
    99  			writer.WriteStringField("type", "string")
   100  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   101  			writer.WriteStringField("name", "io.debezium.data.Enum")
   102  			writer.WriteIntField("version", 1)
   103  			writer.WriteObjectField("parameters", func() {
   104  				writer.WriteStringField("allowed", strings.Join(ft.GetElems(), ","))
   105  			})
   106  			writer.WriteStringField("field", col.Name)
   107  		})
   108  
   109  	case mysql.TypeSet:
   110  		writer.WriteObjectElement(func() {
   111  			writer.WriteStringField("type", "string")
   112  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   113  			writer.WriteStringField("name", "io.debezium.data.EnumSet")
   114  			writer.WriteIntField("version", 1)
   115  			writer.WriteObjectField("parameters", func() {
   116  				writer.WriteStringField("allowed", strings.Join(ft.GetElems(), ","))
   117  			})
   118  			writer.WriteStringField("field", col.Name)
   119  		})
   120  
   121  	case mysql.TypeNewDecimal:
   122  		writer.WriteObjectElement(func() {
   123  			writer.WriteStringField("type", "double")
   124  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   125  			writer.WriteStringField("field", col.Name)
   126  		})
   127  
   128  	case mysql.TypeDate, mysql.TypeNewDate:
   129  		writer.WriteObjectElement(func() {
   130  			writer.WriteStringField("type", "int32")
   131  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   132  			writer.WriteStringField("name", "io.debezium.time.Date")
   133  			writer.WriteIntField("version", 1)
   134  			writer.WriteStringField("field", col.Name)
   135  		})
   136  
   137  	case mysql.TypeDatetime:
   138  		writer.WriteObjectElement(func() {
   139  			writer.WriteStringField("type", "int64")
   140  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   141  			if ft.GetDecimal() <= 3 {
   142  				writer.WriteStringField("name", "io.debezium.time.Timestamp")
   143  			} else {
   144  				writer.WriteStringField("name", "io.debezium.time.MicroTimestamp")
   145  			}
   146  			writer.WriteIntField("version", 1)
   147  			writer.WriteStringField("field", col.Name)
   148  		})
   149  
   150  	case mysql.TypeTimestamp:
   151  		writer.WriteObjectElement(func() {
   152  			writer.WriteStringField("type", "string")
   153  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   154  			writer.WriteStringField("name", "io.debezium.time.ZonedTimestamp")
   155  			writer.WriteIntField("version", 1)
   156  			writer.WriteStringField("field", col.Name)
   157  		})
   158  
   159  	case mysql.TypeDuration:
   160  		writer.WriteObjectElement(func() {
   161  			writer.WriteStringField("type", "int64")
   162  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   163  			writer.WriteStringField("name", "io.debezium.time.MicroTime")
   164  			writer.WriteIntField("version", 1)
   165  			writer.WriteStringField("field", col.Name)
   166  		})
   167  
   168  	case mysql.TypeJSON:
   169  		writer.WriteObjectElement(func() {
   170  			writer.WriteStringField("type", "string")
   171  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   172  			writer.WriteStringField("name", "io.debezium.data.Json")
   173  			writer.WriteIntField("version", 1)
   174  			writer.WriteStringField("field", col.Name)
   175  		})
   176  
   177  	case mysql.TypeTiny: // TINYINT
   178  		writer.WriteObjectElement(func() {
   179  			writer.WriteStringField("type", "int16")
   180  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   181  			writer.WriteStringField("field", col.Name)
   182  		})
   183  
   184  	case mysql.TypeShort: // SMALLINT
   185  		writer.WriteObjectElement(func() {
   186  			if mysql.HasUnsignedFlag(ft.GetFlag()) {
   187  				writer.WriteStringField("type", "int32")
   188  			} else {
   189  				writer.WriteStringField("type", "int16")
   190  			}
   191  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   192  			writer.WriteStringField("field", col.Name)
   193  		})
   194  
   195  	case mysql.TypeInt24: // MEDIUMINT
   196  		writer.WriteObjectElement(func() {
   197  			writer.WriteStringField("type", "int32")
   198  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   199  			writer.WriteStringField("field", col.Name)
   200  		})
   201  
   202  	case mysql.TypeLong: // INT
   203  		writer.WriteObjectElement(func() {
   204  			if mysql.HasUnsignedFlag(ft.GetFlag()) {
   205  				writer.WriteStringField("type", "int64")
   206  			} else {
   207  				writer.WriteStringField("type", "int32")
   208  			}
   209  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   210  			writer.WriteStringField("field", col.Name)
   211  		})
   212  
   213  	case mysql.TypeLonglong: // BIGINT
   214  		writer.WriteObjectElement(func() {
   215  			writer.WriteStringField("type", "int64")
   216  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   217  			writer.WriteStringField("field", col.Name)
   218  		})
   219  
   220  	case mysql.TypeFloat:
   221  		writer.WriteObjectElement(func() {
   222  			writer.WriteStringField("type", "float")
   223  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   224  			writer.WriteStringField("field", col.Name)
   225  		})
   226  
   227  	case mysql.TypeDouble:
   228  		writer.WriteObjectElement(func() {
   229  			writer.WriteStringField("type", "double")
   230  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   231  			writer.WriteStringField("field", col.Name)
   232  		})
   233  
   234  	case mysql.TypeYear:
   235  		writer.WriteObjectElement(func() {
   236  			writer.WriteStringField("type", "int32")
   237  			writer.WriteBoolField("optional", !mysql.HasNotNullFlag(ft.GetFlag()))
   238  			writer.WriteStringField("name", "io.debezium.time.Year")
   239  			writer.WriteIntField("version", 1)
   240  			writer.WriteStringField("field", col.Name)
   241  		})
   242  
   243  	default:
   244  		log.Warn(
   245  			"meet unsupported field type",
   246  			zap.Any("fieldType", col.Type),
   247  			zap.Any("column", col.Name),
   248  		)
   249  	}
   250  }
   251  
   252  // See https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-data-types
   253  //
   254  //revive:disable indent-error-flow
   255  func (c *dbzCodec) writeDebeziumFieldValue(
   256  	writer *util.JSONWriter,
   257  	col *model.Column,
   258  	ft *types.FieldType,
   259  ) error {
   260  	if col.Value == nil {
   261  		writer.WriteNullField(col.Name)
   262  		return nil
   263  	}
   264  	switch col.Type {
   265  	case mysql.TypeBit:
   266  		v, ok := col.Value.(uint64)
   267  		if !ok {
   268  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   269  				"unexpected column value type %T for bit column %s",
   270  				col.Value,
   271  				col.Name)
   272  		}
   273  
   274  		// Debezium behavior:
   275  		// BIT(1) → BOOLEAN
   276  		// BIT(>1) → BYTES		The byte[] contains the bits in little-endian form and is sized to
   277  		//						contain the specified number of bits.
   278  		n := ft.GetFlen()
   279  		if n == 1 {
   280  			writer.WriteBoolField(col.Name, v != 0)
   281  			return nil
   282  		} else {
   283  			var buf [8]byte
   284  			binary.LittleEndian.PutUint64(buf[:], v)
   285  			numBytes := n / 8
   286  			if n%8 != 0 {
   287  				numBytes += 1
   288  			}
   289  			c.writeBinaryField(writer, col.Name, buf[:numBytes])
   290  			return nil
   291  		}
   292  
   293  	case mysql.TypeVarchar, mysql.TypeString, mysql.TypeVarString, mysql.TypeTinyBlob,
   294  		mysql.TypeMediumBlob, mysql.TypeLongBlob, mysql.TypeBlob:
   295  		v, ok := col.Value.([]byte)
   296  		if !ok {
   297  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   298  				"unexpected column value type %T for string column %s",
   299  				col.Value,
   300  				col.Name)
   301  		}
   302  
   303  		if col.Flag.IsBinary() {
   304  			c.writeBinaryField(writer, col.Name, v)
   305  			return nil
   306  		} else {
   307  			writer.WriteStringField(col.Name, string(hack.String(v)))
   308  			return nil
   309  		}
   310  
   311  	case mysql.TypeEnum:
   312  		v, ok := col.Value.(uint64)
   313  		if !ok {
   314  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   315  				"unexpected column value type %T for enum column %s",
   316  				col.Value,
   317  				col.Name)
   318  		}
   319  
   320  		enumVar, err := types.ParseEnumValue(ft.GetElems(), v)
   321  		if err != nil {
   322  			// Invalid enum value inserted in non-strict mode.
   323  			writer.WriteStringField(col.Name, "")
   324  			return nil
   325  		}
   326  
   327  		writer.WriteStringField(col.Name, enumVar.Name)
   328  		return nil
   329  
   330  	case mysql.TypeSet:
   331  		v, ok := col.Value.(uint64)
   332  		if !ok {
   333  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   334  				"unexpected column value type %T for set column %s",
   335  				col.Value,
   336  				col.Name)
   337  		}
   338  
   339  		setVar, err := types.ParseSetValue(ft.GetElems(), v)
   340  		if err != nil {
   341  			// Invalid enum value inserted in non-strict mode.
   342  			writer.WriteStringField(col.Name, "")
   343  			return nil
   344  		}
   345  
   346  		writer.WriteStringField(col.Name, setVar.Name)
   347  		return nil
   348  
   349  	case mysql.TypeNewDecimal:
   350  		v, ok := col.Value.(string)
   351  		if !ok {
   352  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   353  				"unexpected column value type %T for decimal column %s",
   354  				col.Value,
   355  				col.Name)
   356  		}
   357  
   358  		floatV, err := strconv.ParseFloat(v, 64)
   359  		if err != nil {
   360  			return cerror.WrapError(
   361  				cerror.ErrDebeziumEncodeFailed,
   362  				err)
   363  		}
   364  
   365  		writer.WriteFloat64Field(col.Name, floatV)
   366  		return nil
   367  
   368  	case mysql.TypeDate, mysql.TypeNewDate:
   369  		v, ok := col.Value.(string)
   370  		if !ok {
   371  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   372  				"unexpected column value type %T for date column %s",
   373  				col.Value,
   374  				col.Name)
   375  		}
   376  
   377  		t, err := time.Parse("2006-01-02", v)
   378  		if err != nil {
   379  			// For example, time may be invalid like 1000-00-00
   380  			// return nil, nil
   381  			if mysql.HasNotNullFlag(ft.GetFlag()) {
   382  				writer.WriteInt64Field(col.Name, 0)
   383  				return nil
   384  			} else {
   385  				writer.WriteNullField(col.Name)
   386  				return nil
   387  			}
   388  		}
   389  
   390  		writer.WriteInt64Field(col.Name, t.Unix()/60/60/24)
   391  		return nil
   392  
   393  	case mysql.TypeDatetime:
   394  		// Debezium behavior from doc:
   395  		// > Such columns are converted into epoch milliseconds or microseconds based on the
   396  		// > column's precision by using UTC.
   397  
   398  		// TODO: For Default Value = CURRENT_TIMESTAMP, the result is incorrect.
   399  		v, ok := col.Value.(string)
   400  		if !ok {
   401  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   402  				"unexpected column value type %T for datetime column %s",
   403  				col.Value,
   404  				col.Name)
   405  		}
   406  
   407  		t, err := time.Parse("2006-01-02 15:04:05.999999", v)
   408  		if err != nil {
   409  			// For example, time may be 1000-00-00
   410  			if mysql.HasNotNullFlag(ft.GetFlag()) {
   411  				writer.WriteInt64Field(col.Name, 0)
   412  				return nil
   413  			} else {
   414  				writer.WriteNullField(col.Name)
   415  				return nil
   416  			}
   417  		}
   418  
   419  		if ft.GetDecimal() <= 3 {
   420  			writer.WriteInt64Field(col.Name, t.UnixMilli())
   421  			return nil
   422  		} else {
   423  			writer.WriteInt64Field(col.Name, t.UnixMicro())
   424  			return nil
   425  		}
   426  
   427  	case mysql.TypeTimestamp:
   428  		// Debezium behavior from doc:
   429  		// > The TIMESTAMP type represents a timestamp without time zone information.
   430  		// > It is converted by MySQL from the server (or session's) current time zone into UTC
   431  		// > when writing and from UTC into the server (or session's) current time zone when reading
   432  		// > back the value.
   433  		// > Such columns are converted into an equivalent io.debezium.time.ZonedTimestamp in UTC
   434  		// > based on the server (or session's) current time zone. The time zone will be queried from
   435  		// > the server by default. If this fails, it must be specified explicitly by the database
   436  		// > connectionTimeZone MySQL configuration option.
   437  		v, ok := col.Value.(string)
   438  		if !ok {
   439  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   440  				"unexpected column value type %T for timestamp column %s",
   441  				col.Value,
   442  				col.Name)
   443  		}
   444  
   445  		t, err := time.ParseInLocation("2006-01-02 15:04:05.999999", v, c.config.TimeZone)
   446  		if err != nil {
   447  			// For example, time may be invalid like 1000-00-00
   448  			if mysql.HasNotNullFlag(ft.GetFlag()) {
   449  				t = time.Unix(0, 0)
   450  			} else {
   451  				writer.WriteNullField(col.Name)
   452  				return nil
   453  			}
   454  		}
   455  
   456  		str := t.UTC().Format("2006-01-02T15:04:05")
   457  		fsp := ft.GetDecimal()
   458  		if fsp > 0 {
   459  			tmp := fmt.Sprintf(".%06d", t.Nanosecond()/1000)
   460  			str = str + tmp[:1+fsp]
   461  		}
   462  		str += "Z"
   463  
   464  		writer.WriteStringField(col.Name, str)
   465  		return nil
   466  
   467  	case mysql.TypeDuration:
   468  		// Debezium behavior from doc:
   469  		// > Represents the time value in microseconds and does not include
   470  		// > time zone information. MySQL allows M to be in the range of 0-6.
   471  		v, ok := col.Value.(string)
   472  		if !ok {
   473  			return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   474  				"unexpected column value type %T for time column %s",
   475  				col.Value,
   476  				col.Name)
   477  		}
   478  
   479  		d, _, _, err := types.StrToDuration(types.DefaultStmtNoWarningContext, v, ft.GetDecimal())
   480  		if err != nil {
   481  			return cerror.WrapError(
   482  				cerror.ErrDebeziumEncodeFailed,
   483  				err)
   484  		}
   485  
   486  		writer.WriteInt64Field(col.Name, d.Microseconds())
   487  		return nil
   488  
   489  	case mysql.TypeLonglong:
   490  		if col.Flag.IsUnsigned() {
   491  			// Handle with BIGINT UNSIGNED.
   492  			// Debezium always produce INT64 instead of UINT64 for BIGINT.
   493  			v, ok := col.Value.(uint64)
   494  			if !ok {
   495  				return cerror.ErrDebeziumEncodeFailed.GenWithStack(
   496  					"unexpected column value type %T for unsigned bigint column %s",
   497  					col.Value,
   498  					col.Name)
   499  			}
   500  
   501  			writer.WriteInt64Field(col.Name, int64(v))
   502  			return nil
   503  		}
   504  
   505  		// Note: Although Debezium's doc claims to use INT32 for INT, but it
   506  		// actually uses INT64. Debezium also uses INT32 for SMALLINT.
   507  		// So we only handle with TypeLonglong here.
   508  	}
   509  
   510  	writer.WriteAnyField(col.Name, col.Value)
   511  	return nil
   512  }
   513  
   514  func (c *dbzCodec) writeBinaryField(writer *util.JSONWriter, fieldName string, value []byte) {
   515  	// TODO: Deal with different binary output later.
   516  	writer.WriteBase64StringField(fieldName, value)
   517  }
   518  
   519  func (c *dbzCodec) EncodeRowChangedEvent(
   520  	e *model.RowChangedEvent,
   521  	dest io.Writer,
   522  ) error {
   523  	jWriter := util.BorrowJSONWriter(dest)
   524  	defer util.ReturnJSONWriter(jWriter)
   525  
   526  	commitTime := oracle.GetTimeFromTS(e.CommitTs)
   527  
   528  	var err error
   529  
   530  	jWriter.WriteObject(func() {
   531  		jWriter.WriteObjectField("payload", func() {
   532  			jWriter.WriteObjectField("source", func() {
   533  				jWriter.WriteStringField("version", "2.4.0.Final")
   534  				jWriter.WriteStringField("connector", "TiCDC")
   535  				jWriter.WriteStringField("name", c.clusterID)
   536  				// ts_ms: In the source object, ts_ms indicates the time that the change was made in the database.
   537  				// https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-create-events
   538  				jWriter.WriteInt64Field("ts_ms", commitTime.UnixMilli())
   539  				// snapshot field is a string of true,last,false,incremental
   540  				jWriter.WriteStringField("snapshot", "false")
   541  				jWriter.WriteStringField("db", e.TableInfo.GetSchemaName())
   542  				jWriter.WriteStringField("table", e.TableInfo.GetTableName())
   543  				jWriter.WriteInt64Field("server_id", 0)
   544  				jWriter.WriteNullField("gtid")
   545  				jWriter.WriteStringField("file", "")
   546  				jWriter.WriteInt64Field("pos", 0)
   547  				jWriter.WriteInt64Field("row", 0)
   548  				jWriter.WriteInt64Field("thread", 0)
   549  				jWriter.WriteNullField("query")
   550  
   551  				// The followings are TiDB extended fields
   552  				jWriter.WriteUint64Field("commit_ts", e.CommitTs)
   553  				jWriter.WriteStringField("cluster_id", c.clusterID)
   554  			})
   555  
   556  			// ts_ms: displays the time at which the connector processed the event
   557  			// https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-create-events
   558  			jWriter.WriteInt64Field("ts_ms", c.nowFunc().UnixMilli())
   559  			jWriter.WriteNullField("transaction")
   560  
   561  			if e.IsInsert() {
   562  				// op: Mandatory string that describes the type of operation that caused the connector to generate the event.
   563  				// Valid values are:
   564  				// c = create
   565  				// u = update
   566  				// d = delete
   567  				// r = read (applies to only snapshots)
   568  				// https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-create-events
   569  				jWriter.WriteStringField("op", "c")
   570  
   571  				// before: An optional field that specifies the state of the row before the event occurred.
   572  				// When the op field is c for create, the before field is null since this change event is for new content.
   573  				// In a delete event value, the before field contains the values that were in the row before
   574  				// it was deleted with the database commit.
   575  				jWriter.WriteNullField("before")
   576  
   577  				// after: An optional field that specifies the state of the row after the event occurred.
   578  				// Optional field that specifies the state of the row after the event occurred.
   579  				// In a delete event value, the after field is null, signifying that the row no longer exists.
   580  				err = c.writeDebeziumFieldValues(jWriter, "after", e.GetColumns(), e.TableInfo)
   581  			} else if e.IsDelete() {
   582  				jWriter.WriteStringField("op", "d")
   583  				jWriter.WriteNullField("after")
   584  				err = c.writeDebeziumFieldValues(jWriter, "before", e.GetPreColumns(), e.TableInfo)
   585  			} else if e.IsUpdate() {
   586  				jWriter.WriteStringField("op", "u")
   587  				if c.config.DebeziumOutputOldValue {
   588  					err = c.writeDebeziumFieldValues(jWriter, "before", e.GetPreColumns(), e.TableInfo)
   589  				}
   590  				if err == nil {
   591  					err = c.writeDebeziumFieldValues(jWriter, "after", e.GetColumns(), e.TableInfo)
   592  				}
   593  			}
   594  		})
   595  
   596  		if !c.config.DebeziumDisableSchema {
   597  			jWriter.WriteObjectField("schema", func() {
   598  				jWriter.WriteStringField("type", "struct")
   599  				jWriter.WriteBoolField("optional", false)
   600  				jWriter.WriteStringField("name", fmt.Sprintf("%s.%s.%s.Envelope",
   601  					c.clusterID,
   602  					e.TableInfo.GetSchemaName(),
   603  					e.TableInfo.GetTableName()))
   604  				jWriter.WriteIntField("version", 1)
   605  				jWriter.WriteArrayField("fields", func() {
   606  					// schema is the same for `before` and `after`. So we build a new buffer to
   607  					// build the JSON, so that content can be reused.
   608  					var fieldsJSON string
   609  					{
   610  						fieldsBuf := &bytes.Buffer{}
   611  						fieldsWriter := util.BorrowJSONWriter(fieldsBuf)
   612  						var validCols []*model.Column
   613  						if e.IsInsert() {
   614  							validCols = e.GetColumns()
   615  						} else if e.IsDelete() {
   616  							validCols = e.GetPreColumns()
   617  						} else if e.IsUpdate() {
   618  							validCols = e.GetColumns()
   619  						}
   620  						colInfos := e.TableInfo.GetColInfosForRowChangedEvent()
   621  						for i, col := range validCols {
   622  							c.writeDebeziumFieldSchema(fieldsWriter, col, colInfos[i].Ft)
   623  						}
   624  						util.ReturnJSONWriter(fieldsWriter)
   625  						fieldsJSON = fieldsBuf.String()
   626  					}
   627  					jWriter.WriteObjectElement(func() {
   628  						jWriter.WriteStringField("type", "struct")
   629  						jWriter.WriteBoolField("optional", true)
   630  						jWriter.WriteStringField("name", fmt.Sprintf("%s.%s.%s.Value",
   631  							c.clusterID,
   632  							e.TableInfo.GetSchemaName(),
   633  							e.TableInfo.GetTableName()))
   634  						jWriter.WriteStringField("field", "before")
   635  						jWriter.WriteArrayField("fields", func() {
   636  							jWriter.WriteRaw(fieldsJSON)
   637  						})
   638  					})
   639  					jWriter.WriteObjectElement(func() {
   640  						jWriter.WriteStringField("type", "struct")
   641  						jWriter.WriteBoolField("optional", true)
   642  						jWriter.WriteStringField("name", fmt.Sprintf("%s.%s.%s.Value",
   643  							c.clusterID,
   644  							e.TableInfo.GetSchemaName(),
   645  							e.TableInfo.GetTableName()))
   646  						jWriter.WriteStringField("field", "after")
   647  						jWriter.WriteArrayField("fields", func() {
   648  							jWriter.WriteRaw(fieldsJSON)
   649  						})
   650  					})
   651  					jWriter.WriteObjectElement(func() {
   652  						jWriter.WriteStringField("type", "struct")
   653  						jWriter.WriteArrayField("fields", func() {
   654  							jWriter.WriteObjectElement(func() {
   655  								jWriter.WriteStringField("type", "string")
   656  								jWriter.WriteBoolField("optional", false)
   657  								jWriter.WriteStringField("field", "version")
   658  							})
   659  							jWriter.WriteObjectElement(func() {
   660  								jWriter.WriteStringField("type", "string")
   661  								jWriter.WriteBoolField("optional", false)
   662  								jWriter.WriteStringField("field", "connector")
   663  							})
   664  							jWriter.WriteObjectElement(func() {
   665  								jWriter.WriteStringField("type", "string")
   666  								jWriter.WriteBoolField("optional", false)
   667  								jWriter.WriteStringField("field", "name")
   668  							})
   669  							jWriter.WriteObjectElement(func() {
   670  								jWriter.WriteStringField("type", "int64")
   671  								jWriter.WriteBoolField("optional", false)
   672  								jWriter.WriteStringField("field", "ts_ms")
   673  							})
   674  							jWriter.WriteObjectElement(func() {
   675  								jWriter.WriteStringField("type", "string")
   676  								jWriter.WriteBoolField("optional", true)
   677  								jWriter.WriteStringField("name", "io.debezium.data.Enum")
   678  								jWriter.WriteIntField("version", 1)
   679  								jWriter.WriteObjectField("parameters", func() {
   680  									jWriter.WriteStringField("allowed", "true,last,false,incremental")
   681  								})
   682  								jWriter.WriteStringField("default", "false")
   683  								jWriter.WriteStringField("field", "snapshot")
   684  							})
   685  							jWriter.WriteObjectElement(func() {
   686  								jWriter.WriteStringField("type", "string")
   687  								jWriter.WriteBoolField("optional", false)
   688  								jWriter.WriteStringField("field", "db")
   689  							})
   690  							jWriter.WriteObjectElement(func() {
   691  								jWriter.WriteStringField("type", "string")
   692  								jWriter.WriteBoolField("optional", true)
   693  								jWriter.WriteStringField("field", "sequence")
   694  							})
   695  							jWriter.WriteObjectElement(func() {
   696  								jWriter.WriteStringField("type", "string")
   697  								jWriter.WriteBoolField("optional", true)
   698  								jWriter.WriteStringField("field", "table")
   699  							})
   700  							jWriter.WriteObjectElement(func() {
   701  								jWriter.WriteStringField("type", "int64")
   702  								jWriter.WriteBoolField("optional", false)
   703  								jWriter.WriteStringField("field", "server_id")
   704  							})
   705  							jWriter.WriteObjectElement(func() {
   706  								jWriter.WriteStringField("type", "string")
   707  								jWriter.WriteBoolField("optional", true)
   708  								jWriter.WriteStringField("field", "gtid")
   709  							})
   710  							jWriter.WriteObjectElement(func() {
   711  								jWriter.WriteStringField("type", "string")
   712  								jWriter.WriteBoolField("optional", false)
   713  								jWriter.WriteStringField("field", "file")
   714  							})
   715  							jWriter.WriteObjectElement(func() {
   716  								jWriter.WriteStringField("type", "int64")
   717  								jWriter.WriteBoolField("optional", false)
   718  								jWriter.WriteStringField("field", "pos")
   719  							})
   720  							jWriter.WriteObjectElement(func() {
   721  								jWriter.WriteStringField("type", "int32")
   722  								jWriter.WriteBoolField("optional", false)
   723  								jWriter.WriteStringField("field", "row")
   724  							})
   725  							jWriter.WriteObjectElement(func() {
   726  								jWriter.WriteStringField("type", "int64")
   727  								jWriter.WriteBoolField("optional", true)
   728  								jWriter.WriteStringField("field", "thread")
   729  							})
   730  							jWriter.WriteObjectElement(func() {
   731  								jWriter.WriteStringField("type", "string")
   732  								jWriter.WriteBoolField("optional", true)
   733  								jWriter.WriteStringField("field", "query")
   734  							})
   735  							// Below are extra TiDB fields
   736  							// jWriter.WriteObjectElement(func() {
   737  							// 	jWriter.WriteStringField("type", "int64")
   738  							// 	jWriter.WriteBoolField("optional", false)
   739  							// 	jWriter.WriteStringField("field", "commit_ts")
   740  							// })
   741  							// jWriter.WriteObjectElement(func() {
   742  							// 	jWriter.WriteStringField("type", "string")
   743  							// 	jWriter.WriteBoolField("optional", false)
   744  							// 	jWriter.WriteStringField("field", "cluster_id")
   745  							// })
   746  						})
   747  						jWriter.WriteBoolField("optional", false)
   748  						jWriter.WriteStringField("name", "io.debezium.connector.mysql.Source")
   749  						jWriter.WriteStringField("field", "source")
   750  					})
   751  					jWriter.WriteObjectElement(func() {
   752  						jWriter.WriteStringField("type", "string")
   753  						jWriter.WriteBoolField("optional", false)
   754  						jWriter.WriteStringField("field", "op")
   755  					})
   756  					jWriter.WriteObjectElement(func() {
   757  						jWriter.WriteStringField("type", "int64")
   758  						jWriter.WriteBoolField("optional", true)
   759  						jWriter.WriteStringField("field", "ts_ms")
   760  					})
   761  					jWriter.WriteObjectElement(func() {
   762  						jWriter.WriteStringField("type", "struct")
   763  						jWriter.WriteArrayField("fields", func() {
   764  							jWriter.WriteObjectElement(func() {
   765  								jWriter.WriteStringField("type", "string")
   766  								jWriter.WriteBoolField("optional", false)
   767  								jWriter.WriteStringField("field", "id")
   768  							})
   769  							jWriter.WriteObjectElement(func() {
   770  								jWriter.WriteStringField("type", "int64")
   771  								jWriter.WriteBoolField("optional", false)
   772  								jWriter.WriteStringField("field", "total_order")
   773  							})
   774  							jWriter.WriteObjectElement(func() {
   775  								jWriter.WriteStringField("type", "int64")
   776  								jWriter.WriteBoolField("optional", false)
   777  								jWriter.WriteStringField("field", "data_collection_order")
   778  							})
   779  						})
   780  						jWriter.WriteBoolField("optional", true)
   781  						jWriter.WriteStringField("name", "event.block")
   782  						jWriter.WriteIntField("version", 1)
   783  						jWriter.WriteStringField("field", "transaction")
   784  					})
   785  				})
   786  			})
   787  		}
   788  	})
   789  
   790  	return err
   791  }