github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/meta/internal/sqlkv/sql_impl_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 sqlkv
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"database/sql/driver"
    20  	"fmt"
    21  	"reflect"
    22  	"regexp"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/DATA-DOG/go-sqlmock"
    27  	"github.com/VividCortex/mysqlerr"
    28  	"github.com/go-sql-driver/mysql"
    29  	"github.com/pingcap/log"
    30  	sqlkvModel "github.com/pingcap/tiflow/engine/pkg/meta/internal/sqlkv/model"
    31  	metaModel "github.com/pingcap/tiflow/engine/pkg/meta/model"
    32  	"github.com/pingcap/tiflow/pkg/errors"
    33  	"github.com/stretchr/testify/require"
    34  	"go.uber.org/zap/zapcore"
    35  )
    36  
    37  const (
    38  	fakeJob              = "fakeJob"
    39  	fakeTable            = "fakeTable"
    40  	defaultTestStoreType = metaModel.StoreTypeMySQL
    41  )
    42  
    43  type tCase struct {
    44  	caseName string        // case name
    45  	fn       string        // function name
    46  	inputs   []interface{} // function args
    47  
    48  	output interface{} // function output
    49  	err    error       // function error
    50  
    51  	mockExpectResFn func(mock sqlmock.Sqlmock) // sqlmock expectation
    52  }
    53  
    54  func mockGetDBConn(t *testing.T, table string) (*sql.DB, sqlmock.Sqlmock) {
    55  	db, mock, err := sqlmock.New()
    56  	require.Nil(t, err)
    57  	// common execution for orm
    58  	mock.ExpectQuery("SELECT VERSION()").
    59  		WillReturnRows(sqlmock.NewRows([]string{"VERSION()"}).
    60  			AddRow("5.7.35-log"))
    61  	mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("CREATE TABLE `%s` (`seq_id` bigint unsigned AUTO_INCREMENT,"+
    62  		"`created_at` datetime(3) NULL,`updated_at` datetime(3) NULL,`meta_key` varbinary(2048) not null,`meta_value` longblob,"+
    63  		"`job_id` varchar(64) not null,PRIMARY KEY (`seq_id`),UNIQUE INDEX `uidx_jk` (`job_id`,`meta_key`))", table))).
    64  		WillReturnResult(sqlmock.NewResult(1, 1))
    65  	return db, mock
    66  }
    67  
    68  type anyTime struct{}
    69  
    70  func (a anyTime) Match(v driver.Value) bool {
    71  	_, ok := v.(time.Time)
    72  	return ok
    73  }
    74  
    75  func TestNewSQLImpl(t *testing.T) {
    76  	t.Parallel()
    77  
    78  	sqlDB, mock := mockGetDBConn(t, fakeTable)
    79  
    80  	defer sqlDB.Close() //nolint:staticcheck
    81  	defer mock.ExpectClose()
    82  	cli, err := NewSQLKVClientImpl(sqlDB, defaultTestStoreType, fakeTable, fakeJob)
    83  	defer cli.Close() //nolint:staticcheck
    84  	require.Nil(t, err)
    85  	require.NotNil(t, cli)
    86  }
    87  
    88  func TestPut(t *testing.T) {
    89  	t.Parallel()
    90  
    91  	sqlDB, mock := mockGetDBConn(t, fakeTable)
    92  	defer sqlDB.Close() //nolint:staticcheck
    93  	defer mock.ExpectClose()
    94  	cli, err := NewSQLKVClientImpl(sqlDB, defaultTestStoreType, fakeTable, fakeJob)
    95  	defer cli.Close() //nolint:staticcheck
    96  	require.Nil(t, err)
    97  	require.NotNil(t, cli)
    98  	var anyT anyTime
    99  
   100  	testCases := []tCase{
   101  		{
   102  			fn: "Put",
   103  			inputs: []interface{}{
   104  				"key0",
   105  				"value0",
   106  			},
   107  			output: &metaModel.PutResponse{
   108  				Header: &metaModel.ResponseHeader{},
   109  			},
   110  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   111  				mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `fakeTable` (`created_at`,`updated_at`,"+
   112  					"`meta_key`,`meta_value`,`job_id`) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE `updated_at`=?,"+
   113  					"`meta_key`=VALUES(`meta_key`),`meta_value`=VALUES(`meta_value`),`job_id`=VALUES(`job_id`)")).
   114  					WithArgs(anyT, anyT, []byte("key0"), []byte("value0"), fakeJob, anyT).
   115  					WillReturnResult(sqlmock.NewResult(1, 1))
   116  			},
   117  		},
   118  	}
   119  
   120  	for _, tc := range testCases {
   121  		testInner(t, mock, cli, tc)
   122  	}
   123  }
   124  
   125  func TestGet(t *testing.T) {
   126  	t.Parallel()
   127  
   128  	sqlDB, mock := mockGetDBConn(t, fakeTable)
   129  	defer sqlDB.Close() //nolint:staticcheck
   130  	defer mock.ExpectClose()
   131  	cli, err := NewSQLKVClientImpl(sqlDB, defaultTestStoreType, fakeTable, fakeJob)
   132  	require.Nil(t, err)
   133  	require.NotNil(t, cli)
   134  
   135  	testCases := []tCase{
   136  		{
   137  			caseName: "RecordNotFoundErrReturnEmptyResp",
   138  			fn:       "Get",
   139  			inputs: []interface{}{
   140  				"key0",
   141  			},
   142  			output: &metaModel.GetResponse{
   143  				Header: &metaModel.ResponseHeader{},
   144  				Kvs:    []*metaModel.KeyValue{},
   145  			},
   146  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   147  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `fakeTable` WHERE job_id = ? AND "+
   148  					"meta_key = ? ORDER BY `fakeTable`.`seq_id` LIMIT 1")).
   149  					WithArgs(fakeJob, []byte("key0")).
   150  					WillReturnRows(sqlmock.NewRows([]string{"meta_key", "meta_value"}))
   151  			},
   152  		},
   153  		{
   154  			caseName: "NormalGet",
   155  			fn:       "Get",
   156  			inputs: []interface{}{
   157  				"key0",
   158  			},
   159  			output: &metaModel.GetResponse{
   160  				Header: &metaModel.ResponseHeader{},
   161  				Kvs: []*metaModel.KeyValue{
   162  					{
   163  						Key:   []byte("key0"),
   164  						Value: []byte("value0"),
   165  					},
   166  				},
   167  			},
   168  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   169  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `fakeTable` WHERE job_id = ? AND "+
   170  					"meta_key = ? ORDER BY `fakeTable`.`seq_id` LIMIT 1")).
   171  					WithArgs(fakeJob, []byte("key0")).
   172  					WillReturnRows(sqlmock.NewRows([]string{"meta_key", "meta_value"}).AddRow("key0", "value0"))
   173  			},
   174  		},
   175  		{
   176  			caseName: "RangeGet",
   177  			fn:       "Get",
   178  			inputs: []interface{}{
   179  				"key0",
   180  				metaModel.WithRange("key999"),
   181  			},
   182  			output: &metaModel.GetResponse{
   183  				Header: &metaModel.ResponseHeader{},
   184  				Kvs: []*metaModel.KeyValue{
   185  					{
   186  						Key:   []byte("key0"),
   187  						Value: []byte("value0"),
   188  					},
   189  					{
   190  						Key:   []byte("key1"),
   191  						Value: []byte("value1"),
   192  					},
   193  				},
   194  			},
   195  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   196  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `fakeTable` WHERE "+
   197  					"job_id = ? AND (meta_key >= ? AND meta_key < ?)")).
   198  					WithArgs(fakeJob, []byte("key0"), []byte("key999")).
   199  					WillReturnRows(sqlmock.NewRows([]string{"meta_key", "meta_value"}).
   200  						AddRow("key0", "value0").AddRow("key1", "value1"))
   201  			},
   202  		},
   203  		{
   204  			caseName: "FromKeyGet",
   205  			fn:       "Get",
   206  			inputs: []interface{}{
   207  				"key0",
   208  				metaModel.WithFromKey(),
   209  			},
   210  			output: &metaModel.GetResponse{
   211  				Header: &metaModel.ResponseHeader{},
   212  				Kvs: []*metaModel.KeyValue{
   213  					{
   214  						Key:   []byte("key0"),
   215  						Value: []byte("value0"),
   216  					},
   217  				},
   218  			},
   219  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   220  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `fakeTable` WHERE job_id = ? AND meta_key >= ?")).
   221  					WithArgs(fakeJob, []byte("key0")).
   222  					WillReturnRows(sqlmock.NewRows([]string{"meta_key", "meta_value"}).
   223  						AddRow("key0", "value0"))
   224  			},
   225  		},
   226  		{
   227  			caseName: "PrefixGet",
   228  			fn:       "Get",
   229  			inputs: []interface{}{
   230  				"key0",
   231  				metaModel.WithPrefix(),
   232  			},
   233  			output: &metaModel.GetResponse{
   234  				Header: &metaModel.ResponseHeader{},
   235  				Kvs: []*metaModel.KeyValue{
   236  					{
   237  						Key:   []byte("key0"),
   238  						Value: []byte("value0"),
   239  					},
   240  				},
   241  			},
   242  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   243  				mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `fakeTable` WHERE job_id = ? AND meta_key like ?")).
   244  					WithArgs(fakeJob, []byte("key0%")).
   245  					WillReturnRows(sqlmock.NewRows([]string{"meta_key", "meta_value"}).
   246  						AddRow("key0", "value0"))
   247  			},
   248  		},
   249  	}
   250  
   251  	for _, tc := range testCases {
   252  		testInner(t, mock, cli, tc)
   253  	}
   254  }
   255  
   256  func TestDelete(t *testing.T) {
   257  	t.Parallel()
   258  
   259  	sqlDB, mock := mockGetDBConn(t, fakeTable)
   260  	defer sqlDB.Close() //nolint:staticcheck
   261  	defer mock.ExpectClose()
   262  	cli, err := NewSQLKVClientImpl(sqlDB, defaultTestStoreType, fakeTable, fakeJob)
   263  	require.Nil(t, err)
   264  	require.NotNil(t, cli)
   265  
   266  	testCases := []tCase{
   267  		{
   268  			fn: "Delete",
   269  			inputs: []interface{}{
   270  				"key0",
   271  			},
   272  			output: &metaModel.DeleteResponse{
   273  				Header: &metaModel.ResponseHeader{},
   274  			},
   275  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   276  				mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `fakeTable` WHERE job_id = ? AND meta_key = ?")).
   277  					WithArgs(fakeJob, []byte("key0")).
   278  					WillReturnResult(sqlmock.NewResult(1, 1))
   279  			},
   280  		},
   281  		{
   282  			fn: "Delete",
   283  			inputs: []interface{}{
   284  				"key0",
   285  				metaModel.WithRange("key999"),
   286  			},
   287  			output: &metaModel.DeleteResponse{
   288  				Header: &metaModel.ResponseHeader{},
   289  			},
   290  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   291  				mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `fakeTable` "+
   292  					"WHERE job_id = ? AND (meta_key >= ? AND meta_key < ?)")).
   293  					WithArgs(fakeJob, []byte("key0"), []byte("key999")).
   294  					WillReturnResult(sqlmock.NewResult(1, 1))
   295  			},
   296  		},
   297  		{
   298  			fn: "Delete",
   299  			inputs: []interface{}{
   300  				"key0",
   301  				metaModel.WithFromKey(),
   302  			},
   303  			output: &metaModel.DeleteResponse{
   304  				Header: &metaModel.ResponseHeader{},
   305  			},
   306  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   307  				mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `fakeTable` WHERE job_id = ? AND meta_key >= ?")).
   308  					WithArgs(fakeJob, []byte("key0")).
   309  					WillReturnResult(sqlmock.NewResult(1, 1))
   310  			},
   311  		},
   312  		{
   313  			fn: "Delete",
   314  			inputs: []interface{}{
   315  				"key0",
   316  				metaModel.WithPrefix(),
   317  			},
   318  			output: &metaModel.DeleteResponse{
   319  				Header: &metaModel.ResponseHeader{},
   320  			},
   321  			mockExpectResFn: func(mock sqlmock.Sqlmock) {
   322  				mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `fakeTable` WHERE job_id = ? AND meta_key like ?")).
   323  					WithArgs(fakeJob, []byte("key0%")).
   324  					WillReturnResult(sqlmock.NewResult(1, 1))
   325  			},
   326  		},
   327  	}
   328  
   329  	for _, tc := range testCases {
   330  		testInner(t, mock, cli, tc)
   331  	}
   332  }
   333  
   334  func TestTxn(t *testing.T) {
   335  	t.Parallel()
   336  
   337  	sqlDB, mock := mockGetDBConn(t, fakeTable)
   338  	defer sqlDB.Close() //nolint:staticcheck
   339  	defer mock.ExpectClose()
   340  	cli, err := NewSQLKVClientImpl(sqlDB, defaultTestStoreType, fakeTable, fakeJob)
   341  	require.Nil(t, err)
   342  	require.NotNil(t, cli)
   343  	var anyT anyTime
   344  
   345  	txn := cli.Txn(context.Background())
   346  	txn.Do(metaModel.OpGet("key0", metaModel.WithRange("key999")))
   347  	txn.Do(metaModel.OpPut("key1", "value1"))
   348  	txn.Do(metaModel.OpDelete("key2", metaModel.WithPrefix()))
   349  
   350  	mock.ExpectBegin()
   351  	mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `fakeTable` WHERE job_id = ? AND (meta_key >= ? AND meta_key < ?)")).
   352  		WithArgs(fakeJob, []byte("key0"), []byte("key999")).
   353  		WillReturnRows(sqlmock.NewRows([]string{"meta_key", "meta_value"}).AddRow("key0", "value0").AddRow("key1", "value1"))
   354  	mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `fakeTable` (`created_at`,`updated_at`,"+
   355  		"`meta_key`,`meta_value`,`job_id`) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE `updated_at`=?,"+
   356  		"`meta_key`=VALUES(`meta_key`),`meta_value`=VALUES(`meta_value`),`job_id`=VALUES(`job_id`)")).
   357  		WithArgs(anyT, anyT, []byte("key1"), []byte("value1"), fakeJob, anyT).
   358  		WillReturnResult(sqlmock.NewResult(1, 1))
   359  	mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `fakeTable` WHERE job_id = ? AND meta_key like ?")).
   360  		WithArgs(fakeJob, []byte("key2%")).
   361  		WillReturnResult(sqlmock.NewResult(1, 1))
   362  	mock.ExpectCommit()
   363  
   364  	rsp, err := txn.Commit()
   365  	require.NoError(t, err)
   366  	require.Len(t, rsp.Responses, 3)
   367  
   368  	rr := rsp.Responses[0].GetResponseGet()
   369  	require.NotNil(t, rr)
   370  	require.Len(t, rr.Kvs, 2)
   371  	require.Equal(t, []byte("key0"), rr.Kvs[0].Key)
   372  	require.Equal(t, []byte("value0"), rr.Kvs[0].Value)
   373  	require.Equal(t, []byte("key1"), rr.Kvs[1].Key)
   374  	require.Equal(t, []byte("value1"), rr.Kvs[1].Value)
   375  
   376  	rr1 := rsp.Responses[1].GetResponsePut()
   377  	require.NotNil(t, rr1)
   378  
   379  	rr2 := rsp.Responses[2].GetResponseDelete()
   380  	require.NotNil(t, rr2)
   381  }
   382  
   383  func testInner(t *testing.T, m sqlmock.Sqlmock, cli *sqlKVClientImpl, c tCase) {
   384  	// set the mock expectation
   385  	c.mockExpectResFn(m)
   386  
   387  	var args []reflect.Value
   388  	args = append(args, reflect.ValueOf(context.Background()))
   389  	for _, ip := range c.inputs {
   390  		args = append(args, reflect.ValueOf(ip))
   391  	}
   392  	result := reflect.ValueOf(cli).MethodByName(c.fn).Call(args)
   393  	// only error
   394  	if len(result) == 1 {
   395  		if c.err == nil {
   396  			require.Nil(t, result[0].Interface())
   397  		} else {
   398  			require.NotNil(t, result[0].Interface())
   399  			require.Error(t, result[0].Interface().(error))
   400  		}
   401  	} else if len(result) == 2 {
   402  		// result and error
   403  		if c.err != nil {
   404  			require.NotNil(t, result[1].Interface())
   405  			require.Error(t, result[1].Interface().(error))
   406  		} else {
   407  			require.Equal(t, c.output, result[0].Interface())
   408  		}
   409  	}
   410  	require.NoError(t, m.ExpectationsWereMet())
   411  }
   412  
   413  func TestSQLImplWithoutNamespace(t *testing.T) {
   414  	t.Parallel()
   415  
   416  	sqlDB, mock := mockGetDBConn(t, sqlkvModel.MetaKVTableName)
   417  	defer sqlDB.Close() //nolint:staticcheck
   418  	defer mock.ExpectClose()
   419  	cli, err := NewSQLKVClientImpl(sqlDB, defaultTestStoreType, sqlkvModel.MetaKVTableName, "")
   420  	require.Nil(t, err)
   421  	require.NotNil(t, cli)
   422  	var anyT anyTime
   423  	ctx := context.TODO()
   424  
   425  	mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `meta_kvs` (`created_at`,`updated_at`,"+
   426  		"`meta_key`,`meta_value`,`job_id`) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE `updated_at`=?,"+
   427  		"`meta_key`=VALUES(`meta_key`),`meta_value`=VALUES(`meta_value`),`job_id`=VALUES(`job_id`)")).
   428  		WithArgs(anyT, anyT, []byte("key0"), []byte("value0"), "", anyT).
   429  		WillReturnResult(sqlmock.NewResult(1, 1))
   430  	cli.Put(ctx, "key0", "value0")
   431  
   432  	mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `meta_kvs` WHERE job_id = ? AND "+
   433  		"meta_key = ? ORDER BY `meta_kvs`.`seq_id` LIMIT 1")).
   434  		WithArgs("", []byte("key1")).
   435  		WillReturnRows(sqlmock.NewRows([]string{"key", "value"}))
   436  	cli.Get(ctx, "key1")
   437  
   438  	mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `meta_kvs` WHERE job_id = ? AND meta_key = ?")).
   439  		WithArgs("", []byte("key2")).
   440  		WillReturnResult(sqlmock.NewResult(1, 1))
   441  	cli.Delete(ctx, "key2")
   442  
   443  	mock.ExpectBegin()
   444  	mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `meta_kvs` WHERE job_id = ? AND (meta_key >= ? AND meta_key < ?)")).
   445  		WithArgs("", []byte("key0"), []byte("key999")).
   446  		WillReturnRows(sqlmock.NewRows([]string{"meta_key", "meta_value"}).AddRow("key0", "value0").AddRow("key1", "value1"))
   447  	mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `meta_kvs` (`created_at`,`updated_at`,"+
   448  		"`meta_key`,`meta_value`,`job_id`) VALUES (?,?,?,?,?) ON DUPLICATE KEY UPDATE `updated_at`=?,"+
   449  		"`meta_key`=VALUES(`meta_key`),`meta_value`=VALUES(`meta_value`),`job_id`=VALUES(`job_id`)")).
   450  		WithArgs(anyT, anyT, []byte("key1"), []byte("value1"), "", anyT).
   451  		WillReturnResult(sqlmock.NewResult(1, 1))
   452  	mock.ExpectExec(regexp.QuoteMeta("DELETE FROM `meta_kvs` WHERE job_id = ? AND meta_key like ?")).
   453  		WithArgs("", []byte("key2%")).
   454  		WillReturnResult(sqlmock.NewResult(1, 1))
   455  	mock.ExpectCommit()
   456  	txn := cli.Txn(context.Background())
   457  	txn.Do(metaModel.OpGet("key0", metaModel.WithRange("key999")))
   458  	txn.Do(metaModel.OpPut("key1", "value1"))
   459  	txn.Do(metaModel.OpDelete("key2", metaModel.WithPrefix()))
   460  	txn.Commit()
   461  	require.NoError(t, mock.ExpectationsWereMet())
   462  }
   463  
   464  func TestInitializeError(t *testing.T) {
   465  	t.Parallel()
   466  
   467  	log.SetLevel(zapcore.DebugLevel)
   468  	defer log.SetLevel(zapcore.InfoLevel)
   469  
   470  	db, mock, err := sqlmock.New()
   471  	require.Nil(t, err)
   472  	defer db.Close()
   473  	defer mock.ExpectClose()
   474  
   475  	// table exists error
   476  	mock.ExpectQuery("SELECT VERSION()").
   477  		WillReturnRows(sqlmock.NewRows([]string{"VERSION()"}).
   478  			AddRow("5.7.35-log"))
   479  	mock.ExpectQuery(".*").WillReturnRows(sqlmock.NewRows([]string{"SCHEMA_NAME"}))
   480  	mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("CREATE TABLE `%s`", "test"))).
   481  		WillReturnError(&mysql.MySQLError{Number: mysqlerr.ER_TABLE_EXISTS_ERROR, Message: "table already exists"})
   482  	cli, err := NewSQLKVClientImpl(db, defaultTestStoreType, "test", "")
   483  	require.Nil(t, err)
   484  	require.NotNil(t, cli)
   485  	defer cli.Close()
   486  
   487  	// other mysql error
   488  	mock.ExpectQuery("SELECT VERSION()").
   489  		WillReturnRows(sqlmock.NewRows([]string{"VERSION()"}).
   490  			AddRow("5.7.35-log"))
   491  	mock.ExpectQuery(".*").WillReturnRows(sqlmock.NewRows([]string{"SCHEMA_NAME"}))
   492  	mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("CREATE TABLE `%s`", "test"))).
   493  		WillReturnError(&mysql.MySQLError{Number: mysqlerr.ER_WRONG_OUTER_JOIN, Message: "other mysql error"})
   494  	_, err = NewSQLKVClientImpl(db, defaultTestStoreType, "test", "")
   495  	require.Regexp(t, "other mysql error", err.Error())
   496  	// other error
   497  	mock.ExpectQuery("SELECT VERSION()").
   498  		WillReturnRows(sqlmock.NewRows([]string{"VERSION()"}).
   499  			AddRow("5.7.35-log"))
   500  	mock.ExpectQuery(".*").WillReturnRows(sqlmock.NewRows([]string{"SCHEMA_NAME"}))
   501  	mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("CREATE TABLE `%s`", "test"))).
   502  		WillReturnError(errors.New("other error"))
   503  	_, err = NewSQLKVClientImpl(db, defaultTestStoreType, "test", "")
   504  	require.Regexp(t, "other error", err.Error())
   505  	require.NoError(t, mock.ExpectationsWereMet())
   506  }