github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/lightning/backend/sql2kv_test.go (about)

     1  // Copyright 2019 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 backend
    15  
    16  import (
    17  	"errors"
    18  
    19  	. "github.com/pingcap/check"
    20  	"github.com/pingcap/parser"
    21  	"github.com/pingcap/parser/ast"
    22  	"github.com/pingcap/parser/model"
    23  	"github.com/pingcap/parser/mysql"
    24  	"github.com/pingcap/tidb/ddl"
    25  	"github.com/pingcap/tidb/kv"
    26  	"github.com/pingcap/tidb/meta/autoid"
    27  	"github.com/pingcap/tidb/sessionctx"
    28  	"github.com/pingcap/tidb/table"
    29  	"github.com/pingcap/tidb/table/tables"
    30  	"github.com/pingcap/tidb/types"
    31  	"github.com/pingcap/tidb/util/mock"
    32  	"go.uber.org/zap"
    33  	"go.uber.org/zap/zapcore"
    34  
    35  	"github.com/pingcap/tidb-lightning/lightning/common"
    36  	"github.com/pingcap/tidb-lightning/lightning/log"
    37  	"github.com/pingcap/tidb-lightning/lightning/verification"
    38  )
    39  
    40  func (s *kvSuite) TestMarshal(c *C) {
    41  	nullDatum := types.Datum{}
    42  	nullDatum.SetNull()
    43  	minNotNull := types.Datum{}
    44  	minNotNull.SetMinNotNull()
    45  	encoder := zapcore.NewMapObjectEncoder()
    46  	err := encoder.AddArray("test", rowArrayMarshaler{types.NewStringDatum("1"), nullDatum, minNotNull, types.MaxValueDatum()})
    47  	c.Assert(err, IsNil)
    48  	c.Assert(encoder.Fields["test"], DeepEquals, []interface{}{
    49  		map[string]interface{}{"kind": "string", "val": "1"},
    50  		map[string]interface{}{"kind": "null", "val": "NULL"},
    51  		map[string]interface{}{"kind": "min", "val": "-inf"},
    52  		map[string]interface{}{"kind": "max", "val": "+inf"},
    53  	})
    54  
    55  	invalid := types.Datum{}
    56  	invalid.SetInterface(1)
    57  	err = encoder.AddArray("bad-test", rowArrayMarshaler{minNotNull, invalid})
    58  	c.Assert(err, ErrorMatches, "cannot convert.*")
    59  	c.Assert(encoder.Fields["bad-test"], DeepEquals, []interface{}{
    60  		map[string]interface{}{"kind": "min", "val": "-inf"},
    61  	})
    62  }
    63  
    64  type mockTable struct {
    65  	table.Table
    66  }
    67  
    68  func (mockTable) AddRecord(ctx sessionctx.Context, r []types.Datum, opts ...table.AddRecordOption) (recordID kv.Handle, err error) {
    69  	return kv.IntHandle(-1), errors.New("mock error")
    70  }
    71  
    72  func (s *kvSuite) TestEncode(c *C) {
    73  	c1 := &model.ColumnInfo{ID: 1, Name: model.NewCIStr("c1"), State: model.StatePublic, Offset: 0, FieldType: *types.NewFieldType(mysql.TypeTiny)}
    74  	cols := []*model.ColumnInfo{c1}
    75  	tblInfo := &model.TableInfo{ID: 1, Columns: cols, PKIsHandle: false, State: model.StatePublic}
    76  	tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tblInfo)
    77  	c.Assert(err, IsNil)
    78  
    79  	logger := log.Logger{Logger: zap.NewNop()}
    80  	rows := []types.Datum{
    81  		types.NewIntDatum(10000000),
    82  	}
    83  
    84  	// Strict mode
    85  	strictMode, err := NewTableKVEncoder(tbl, &SessionOptions{
    86  		SQLMode:   mysql.ModeStrictAllTables,
    87  		Timestamp: 1234567890,
    88  	})
    89  	c.Assert(err, IsNil)
    90  	pairs, err := strictMode.Encode(logger, rows, 1, []int{0, 1})
    91  	c.Assert(err, ErrorMatches, "failed to cast value as tinyint\\(4\\) for column `c1` \\(#1\\):.*overflows tinyint")
    92  	c.Assert(pairs, IsNil)
    93  
    94  	rowsWithPk := []types.Datum{
    95  		types.NewIntDatum(1),
    96  		types.NewStringDatum("invalid-pk"),
    97  	}
    98  	pairs, err = strictMode.Encode(logger, rowsWithPk, 2, []int{0, 1})
    99  	c.Assert(err, ErrorMatches, "failed to cast value as bigint\\(20\\) for column `_tidb_rowid`.*Truncated.*")
   100  
   101  	rowsWithPk2 := []types.Datum{
   102  		types.NewIntDatum(1),
   103  		types.NewStringDatum("1"),
   104  	}
   105  	pairs, err = strictMode.Encode(logger, rowsWithPk2, 2, []int{0, 1})
   106  	c.Assert(err, IsNil)
   107  	c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{
   108  		{
   109  			Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
   110  			Val: []uint8{0x8, 0x2, 0x8, 0x2},
   111  		},
   112  	}))
   113  
   114  	// Mock add record error
   115  	mockTbl := &mockTable{Table: tbl}
   116  	mockMode, err := NewTableKVEncoder(mockTbl, &SessionOptions{
   117  		SQLMode:   mysql.ModeStrictAllTables,
   118  		Timestamp: 1234567891,
   119  	})
   120  	c.Assert(err, IsNil)
   121  	pairs, err = mockMode.Encode(logger, rowsWithPk2, 2, []int{0, 1})
   122  	c.Assert(err, ErrorMatches, "mock error")
   123  
   124  	// Non-strict mode
   125  	noneMode, err := NewTableKVEncoder(tbl, &SessionOptions{
   126  		SQLMode:   mysql.ModeNone,
   127  		Timestamp: 1234567892,
   128  		SysVars:   map[string]string{"tidb_row_format_version": "1"},
   129  	})
   130  	c.Assert(err, IsNil)
   131  	pairs, err = noneMode.Encode(logger, rows, 1, []int{0, 1})
   132  	c.Assert(err, IsNil)
   133  	c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{
   134  		{
   135  			Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
   136  			Val: []uint8{0x8, 0x2, 0x8, 0xfe, 0x1},
   137  		},
   138  	}))
   139  }
   140  
   141  func (s *kvSuite) TestEncodeRowFormatV2(c *C) {
   142  	// Test encoding in row format v2, as described in <https://github.com/pingcap/tidb/blob/master/docs/design/2018-07-19-row-format.md>.
   143  
   144  	c1 := &model.ColumnInfo{ID: 1, Name: model.NewCIStr("c1"), State: model.StatePublic, Offset: 0, FieldType: *types.NewFieldType(mysql.TypeTiny)}
   145  	cols := []*model.ColumnInfo{c1}
   146  	tblInfo := &model.TableInfo{ID: 1, Columns: cols, PKIsHandle: false, State: model.StatePublic}
   147  	tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tblInfo)
   148  	c.Assert(err, IsNil)
   149  
   150  	logger := log.Logger{Logger: zap.NewNop()}
   151  	rows := []types.Datum{
   152  		types.NewIntDatum(10000000),
   153  	}
   154  
   155  	noneMode, err := NewTableKVEncoder(tbl, &SessionOptions{
   156  		SQLMode:   mysql.ModeNone,
   157  		Timestamp: 1234567892,
   158  		SysVars:   map[string]string{"tidb_row_format_version": "2"},
   159  	})
   160  	c.Assert(err, IsNil)
   161  	pairs, err := noneMode.Encode(logger, rows, 1, []int{0, 1})
   162  	c.Assert(err, IsNil)
   163  	c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{
   164  		{
   165  			// the key should be the same as TestEncode()
   166  			Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
   167  			Val: []uint8{
   168  				0x80,     // version
   169  				0x0,      // flag = 0 = not big
   170  				0x1, 0x0, // number of not null columns = 1
   171  				0x0, 0x0, // number of null columns = 0
   172  				0x1,      // column IDs = [1]
   173  				0x1, 0x0, // not null offsets = [1]
   174  				0x7f, // column version = 127 (10000000 clamped to TINYINT)
   175  			},
   176  		},
   177  	}))
   178  }
   179  
   180  func (s *kvSuite) TestEncodeTimestamp(c *C) {
   181  	ty := *types.NewFieldType(mysql.TypeDatetime)
   182  	ty.Flag |= mysql.NotNullFlag
   183  	c1 := &model.ColumnInfo{
   184  		ID:           1,
   185  		Name:         model.NewCIStr("c1"),
   186  		State:        model.StatePublic,
   187  		Offset:       0,
   188  		FieldType:    ty,
   189  		DefaultValue: "CURRENT_TIMESTAMP",
   190  		Version:      1,
   191  	}
   192  	cols := []*model.ColumnInfo{c1}
   193  	tblInfo := &model.TableInfo{ID: 1, Columns: cols, PKIsHandle: false, State: model.StatePublic}
   194  	tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tblInfo)
   195  	c.Assert(err, IsNil)
   196  
   197  	logger := log.Logger{Logger: zap.NewNop()}
   198  
   199  	encoder, err := NewTableKVEncoder(tbl, &SessionOptions{
   200  		SQLMode:   mysql.ModeStrictAllTables,
   201  		Timestamp: 1234567893,
   202  		SysVars: map[string]string{
   203  			"tidb_row_format_version": "1",
   204  			"time_zone":               "+08:00",
   205  		},
   206  	})
   207  	c.Assert(err, IsNil)
   208  	pairs, err := encoder.Encode(logger, nil, 70, []int{-1, 1})
   209  	c.Assert(err, IsNil)
   210  	c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{
   211  		{
   212  			Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46},
   213  			Val: []uint8{0x8, 0x2, 0x9, 0x80, 0x80, 0x80, 0xf0, 0xfd, 0x8e, 0xf7, 0xc0, 0x19},
   214  		},
   215  	}))
   216  }
   217  
   218  func mockTableInfo(c *C, createSql string) *model.TableInfo {
   219  	parser := parser.New()
   220  	node, err := parser.ParseOneStmt(createSql, "", "")
   221  	c.Assert(err, IsNil)
   222  	sctx := mock.NewContext()
   223  	info, err := ddl.MockTableInfo(sctx, node.(*ast.CreateTableStmt), 1)
   224  	c.Assert(err, IsNil)
   225  	info.State = model.StatePublic
   226  	return info
   227  }
   228  
   229  func (s *kvSuite) TestDefaultAutoRandoms(c *C) {
   230  	tblInfo := mockTableInfo(c, "create table t (id bigint unsigned NOT NULL auto_random primary key, a varchar(100));")
   231  	// seems parser can't parse auto_random properly.
   232  	tblInfo.AutoRandomBits = 5
   233  	tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tblInfo)
   234  	c.Assert(err, IsNil)
   235  	encoder, err := NewTableKVEncoder(tbl, &SessionOptions{
   236  		SQLMode:        mysql.ModeStrictAllTables,
   237  		Timestamp:      1234567893,
   238  		SysVars:        map[string]string{"tidb_row_format_version": "2"},
   239  		AutoRandomSeed: 456,
   240  	})
   241  	c.Assert(err, IsNil)
   242  	logger := log.Logger{Logger: zap.NewNop()}
   243  	pairs, err := encoder.Encode(logger, []types.Datum{types.NewStringDatum("")}, 70, []int{-1, 0})
   244  	c.Assert(err, IsNil)
   245  	c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{
   246  		{
   247  			Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46},
   248  			Val: []uint8{0x80, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0},
   249  		},
   250  	}))
   251  	c.Assert(tbl.Allocators(encoder.(*tableKVEncoder).se).Get(autoid.AutoRandomType).Base(), Equals, int64(70))
   252  
   253  	pairs, err = encoder.Encode(logger, []types.Datum{types.NewStringDatum("")}, 71, []int{-1, 0})
   254  	c.Assert(err, IsNil)
   255  	c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{
   256  		{
   257  			Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x47},
   258  			Val: []uint8{0x80, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0},
   259  		},
   260  	}))
   261  	c.Assert(tbl.Allocators(encoder.(*tableKVEncoder).se).Get(autoid.AutoRandomType).Base(), Equals, int64(71))
   262  }
   263  
   264  func (s *kvSuite) TestSplitIntoChunks(c *C) {
   265  	pairs := []common.KvPair{
   266  		{
   267  			Key: []byte{1, 2, 3},
   268  			Val: []byte{4, 5, 6},
   269  		},
   270  		{
   271  			Key: []byte{7, 8},
   272  			Val: []byte{9, 0},
   273  		},
   274  		{
   275  			Key: []byte{1, 2, 3, 4},
   276  			Val: []byte{5, 6, 7, 8},
   277  		},
   278  		{
   279  			Key: []byte{9, 0},
   280  			Val: []byte{1, 2},
   281  		},
   282  	}
   283  
   284  	splitBy10 := MakeRowsFromKvPairs(pairs).SplitIntoChunks(10)
   285  	c.Assert(splitBy10, DeepEquals, []Rows{
   286  		MakeRowsFromKvPairs(pairs[0:2]),
   287  		MakeRowsFromKvPairs(pairs[2:3]),
   288  		MakeRowsFromKvPairs(pairs[3:4]),
   289  	})
   290  
   291  	splitBy12 := MakeRowsFromKvPairs(pairs).SplitIntoChunks(12)
   292  	c.Assert(splitBy12, DeepEquals, []Rows{
   293  		MakeRowsFromKvPairs(pairs[0:2]),
   294  		MakeRowsFromKvPairs(pairs[2:4]),
   295  	})
   296  
   297  	splitBy1000 := MakeRowsFromKvPairs(pairs).SplitIntoChunks(1000)
   298  	c.Assert(splitBy1000, DeepEquals, []Rows{
   299  		MakeRowsFromKvPairs(pairs[0:4]),
   300  	})
   301  
   302  	splitBy1 := MakeRowsFromKvPairs(pairs).SplitIntoChunks(1)
   303  	c.Assert(splitBy1, DeepEquals, []Rows{
   304  		MakeRowsFromKvPairs(pairs[0:1]),
   305  		MakeRowsFromKvPairs(pairs[1:2]),
   306  		MakeRowsFromKvPairs(pairs[2:3]),
   307  		MakeRowsFromKvPairs(pairs[3:4]),
   308  	})
   309  }
   310  
   311  func (s *kvSuite) TestClassifyAndAppend(c *C) {
   312  	kvs := MakeRowFromKvPairs([]common.KvPair{
   313  		{
   314  			Key: []byte("txxxxxxxx_ryyyyyyyy"),
   315  			Val: []byte("value1"),
   316  		},
   317  		{
   318  			Key: []byte("txxxxxxxx_rwwwwwwww"),
   319  			Val: []byte("value2"),
   320  		},
   321  		{
   322  			Key: []byte("txxxxxxxx_izzzzzzzz"),
   323  			Val: []byte("index1"),
   324  		},
   325  	})
   326  
   327  	data := MakeRowsFromKvPairs(nil)
   328  	indices := MakeRowsFromKvPairs(nil)
   329  	dataChecksum := verification.MakeKVChecksum(0, 0, 0)
   330  	indexChecksum := verification.MakeKVChecksum(0, 0, 0)
   331  
   332  	kvs.ClassifyAndAppend(&data, &dataChecksum, &indices, &indexChecksum)
   333  
   334  	c.Assert(data, DeepEquals, MakeRowsFromKvPairs([]common.KvPair{
   335  		{
   336  			Key: []byte("txxxxxxxx_ryyyyyyyy"),
   337  			Val: []byte("value1"),
   338  		},
   339  		{
   340  			Key: []byte("txxxxxxxx_rwwwwwwww"),
   341  			Val: []byte("value2"),
   342  		},
   343  	}))
   344  	c.Assert(indices, DeepEquals, MakeRowsFromKvPairs([]common.KvPair{
   345  		{
   346  			Key: []byte("txxxxxxxx_izzzzzzzz"),
   347  			Val: []byte("index1"),
   348  		},
   349  	}))
   350  	c.Assert(dataChecksum.SumKVS(), Equals, uint64(2))
   351  	c.Assert(indexChecksum.SumKVS(), Equals, uint64(1))
   352  }
   353  
   354  type benchSQL2KVSuite struct {
   355  	row     []types.Datum
   356  	colPerm []int
   357  	encoder Encoder
   358  	logger  log.Logger
   359  }
   360  
   361  var _ = Suite(&benchSQL2KVSuite{})
   362  
   363  func (s *benchSQL2KVSuite) SetUpTest(c *C) {
   364  	// First, create the table info corresponding to TPC-C's "CUSTOMER" table.
   365  	p := parser.New()
   366  	se := mock.NewContext()
   367  	node, err := p.ParseOneStmt(`
   368  		create table bmsql_customer(
   369  			c_w_id         integer not null,
   370  			c_d_id         integer not null,
   371  			c_id           integer not null,
   372  			c_discount     decimal(4,4),
   373  			c_credit       char(2),
   374  			c_last         varchar(16),
   375  			c_first        varchar(16),
   376  			c_credit_lim   decimal(12,2),
   377  			c_balance      decimal(12,2),
   378  			c_ytd_payment  decimal(12,2),
   379  			c_payment_cnt  integer,
   380  			c_delivery_cnt integer,
   381  			c_street_1     varchar(20),
   382  			c_street_2     varchar(20),
   383  			c_city         varchar(20),
   384  			c_state        char(2),
   385  			c_zip          char(9),
   386  			c_phone        char(16),
   387  			c_since        timestamp,
   388  			c_middle       char(2),
   389  			c_data         varchar(500),
   390  			primary key (c_w_id, c_d_id, c_id)
   391  		);
   392  	`, "", "")
   393  	c.Assert(err, IsNil)
   394  	tableInfo, err := ddl.MockTableInfo(se, node.(*ast.CreateTableStmt), 123456)
   395  	c.Assert(err, IsNil)
   396  	tableInfo.State = model.StatePublic
   397  
   398  	// Construct the corresponding KV encoder.
   399  	tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tableInfo)
   400  	c.Assert(err, IsNil)
   401  	s.encoder, err = NewTableKVEncoder(tbl, &SessionOptions{SysVars: map[string]string{"tidb_row_format_version": "2"}})
   402  	s.logger = log.Logger{Logger: zap.NewNop()}
   403  
   404  	// Prepare the row to insert.
   405  	s.row = []types.Datum{
   406  		types.NewIntDatum(15),
   407  		types.NewIntDatum(10),
   408  		types.NewIntDatum(3000),
   409  		types.NewStringDatum("0.3646"),
   410  		types.NewStringDatum("GC"),
   411  		types.NewStringDatum("CALLYPRIANTI"),
   412  		types.NewStringDatum("Rg6mDFlVnP5yh"),
   413  		types.NewStringDatum("50000.0"),
   414  		types.NewStringDatum("-10.0"),
   415  		types.NewStringDatum("10.0"),
   416  		types.NewIntDatum(1),
   417  		types.NewIntDatum(0),
   418  		types.NewStringDatum("aJK7CuRnE0NUxNHSX"),
   419  		types.NewStringDatum("Q1rps77cXYoj"),
   420  		types.NewStringDatum("MigXbS6UoUS"),
   421  		types.NewStringDatum("UJ"),
   422  		types.NewStringDatum("638611111"),
   423  		types.NewStringDatum("7743262784364376"),
   424  		types.NewStringDatum("2020-02-05 19:29:58.903970"),
   425  		types.NewStringDatum("OE"),
   426  		types.NewStringDatum("H5p3dpjp7uu8n1l3j0o1buecfV6FngNNgftpNALDhOzJaSzMCMlrQwXuvLAFPIFg215D3wAYB62kiixIuasfbD729oq8TwgKzPPsx8kHE1b4AdhHwpCml3ELKiwuNGQl7CcBQOiq6aFEMMHzjGwQyXwGey0wutjp2KP3Nd4qj3FHtmHbsD8cJ0pH9TswNmdQBgXsFPZeJJhsG3rTimQpS9Tmn3vNeI9fFas3ClDZuQtBjqoTJlyzmBIYT8HeV3TuS93TNFDaXZpQqh8HsvlPq4uTTLOO9CguiY29zlSmIjkZYtva3iscG3YDOQVLeGpP9dtqEJwlRvJ4oe9jWkvRMlCeslSNEuzLxjUBtJBnGRFAzJF6RMlIWCkdCpIhcnIy3jUEsxTuiAU3hsZxUjLg2dnOG62h5qR"),
   427  	}
   428  	s.colPerm = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, -1}
   429  }
   430  
   431  // Run `go test github.com/pingcap/tidb-lightning/lightning/backend -check.b -test.v` to get benchmark result.
   432  func (s *benchSQL2KVSuite) BenchmarkSQL2KV(c *C) {
   433  	for i := 0; i < c.N; i++ {
   434  		rows, err := s.encoder.Encode(s.logger, s.row, 1, s.colPerm)
   435  		c.Assert(err, IsNil)
   436  		c.Assert(rows, HasLen, 2)
   437  	}
   438  }