github.com/shuguocloud/go-zero@v1.3.0/core/stores/sqlc/cachedsql_test.go (about)

     1  package sqlc
     2  
     3  import (
     4  	"database/sql"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"runtime"
    12  	"sync"
    13  	"sync/atomic"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/alicebob/miniredis/v2"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/shuguocloud/go-zero/core/fx"
    20  	"github.com/shuguocloud/go-zero/core/logx"
    21  	"github.com/shuguocloud/go-zero/core/stat"
    22  	"github.com/shuguocloud/go-zero/core/stores/cache"
    23  	"github.com/shuguocloud/go-zero/core/stores/redis"
    24  	"github.com/shuguocloud/go-zero/core/stores/redis/redistest"
    25  	"github.com/shuguocloud/go-zero/core/stores/sqlx"
    26  )
    27  
    28  func init() {
    29  	logx.Disable()
    30  	stat.SetReporter(nil)
    31  }
    32  
    33  func TestCachedConn_GetCache(t *testing.T) {
    34  	resetStats()
    35  	r, clean, err := redistest.CreateRedis()
    36  	assert.Nil(t, err)
    37  	defer clean()
    38  
    39  	c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
    40  	var value string
    41  	err = c.GetCache("any", &value)
    42  	assert.Equal(t, ErrNotFound, err)
    43  	r.Set("any", `"value"`)
    44  	err = c.GetCache("any", &value)
    45  	assert.Nil(t, err)
    46  	assert.Equal(t, "value", value)
    47  }
    48  
    49  func TestStat(t *testing.T) {
    50  	resetStats()
    51  	r, clean, err := redistest.CreateRedis()
    52  	assert.Nil(t, err)
    53  	defer clean()
    54  
    55  	c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
    56  
    57  	for i := 0; i < 10; i++ {
    58  		var str string
    59  		err = c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error {
    60  			*v.(*string) = "zero"
    61  			return nil
    62  		})
    63  		if err != nil {
    64  			t.Error(err)
    65  		}
    66  	}
    67  
    68  	assert.Equal(t, uint64(10), atomic.LoadUint64(&stats.Total))
    69  	assert.Equal(t, uint64(9), atomic.LoadUint64(&stats.Hit))
    70  }
    71  
    72  func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
    73  	resetStats()
    74  	r, clean, err := redistest.CreateRedis()
    75  	assert.Nil(t, err)
    76  	defer clean()
    77  
    78  	c := NewConn(dummySqlConn{}, cache.CacheConf{
    79  		{
    80  			RedisConf: redis.RedisConf{
    81  				Host: r.Addr,
    82  				Type: redis.NodeType,
    83  			},
    84  			Weight: 100,
    85  		},
    86  	}, cache.WithExpiry(time.Second*10))
    87  
    88  	var str string
    89  	err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
    90  		return fmt.Sprintf("%s/1234", s)
    91  	}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
    92  		*v.(*string) = "zero"
    93  		return "primary", errors.New("foo")
    94  	}, func(conn sqlx.SqlConn, v, pri interface{}) error {
    95  		assert.Equal(t, "primary", pri)
    96  		*v.(*string) = "xin"
    97  		return nil
    98  	})
    99  	assert.NotNil(t, err)
   100  
   101  	err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
   102  		return fmt.Sprintf("%s/1234", s)
   103  	}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
   104  		*v.(*string) = "zero"
   105  		return "primary", nil
   106  	}, func(conn sqlx.SqlConn, v, pri interface{}) error {
   107  		assert.Equal(t, "primary", pri)
   108  		*v.(*string) = "xin"
   109  		return nil
   110  	})
   111  	assert.Nil(t, err)
   112  	assert.Equal(t, "zero", str)
   113  	val, err := r.Get("index")
   114  	assert.Nil(t, err)
   115  	assert.Equal(t, `"primary"`, val)
   116  	val, err = r.Get("primary/1234")
   117  	assert.Nil(t, err)
   118  	assert.Equal(t, `"zero"`, val)
   119  }
   120  
   121  func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
   122  	resetStats()
   123  	r, clean, err := redistest.CreateRedis()
   124  	assert.Nil(t, err)
   125  	defer clean()
   126  
   127  	c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
   128  		cache.WithNotFoundExpiry(time.Second))
   129  
   130  	var str string
   131  	r.Set("index", `"primary"`)
   132  	err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
   133  		return fmt.Sprintf("%s/1234", s)
   134  	}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
   135  		assert.Fail(t, "should not go here")
   136  		return "primary", nil
   137  	}, func(conn sqlx.SqlConn, v, primary interface{}) error {
   138  		*v.(*string) = "xin"
   139  		assert.Equal(t, "primary", primary)
   140  		return nil
   141  	})
   142  	assert.Nil(t, err)
   143  	assert.Equal(t, "xin", str)
   144  	val, err := r.Get("index")
   145  	assert.Nil(t, err)
   146  	assert.Equal(t, `"primary"`, val)
   147  	val, err = r.Get("primary/1234")
   148  	assert.Nil(t, err)
   149  	assert.Equal(t, `"xin"`, val)
   150  }
   151  
   152  func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
   153  	const (
   154  		primaryInt8   int8   = 100
   155  		primaryInt16  int16  = 10000
   156  		primaryInt32  int32  = 10000000
   157  		primaryInt64  int64  = 10000000
   158  		primaryUint8  uint8  = 100
   159  		primaryUint16 uint16 = 10000
   160  		primaryUint32 uint32 = 10000000
   161  		primaryUint64 uint64 = 10000000
   162  	)
   163  	tests := []struct {
   164  		name         string
   165  		primary      interface{}
   166  		primaryCache string
   167  	}{
   168  		{
   169  			name:         "int8 primary",
   170  			primary:      primaryInt8,
   171  			primaryCache: fmt.Sprint(primaryInt8),
   172  		},
   173  		{
   174  			name:         "int16 primary",
   175  			primary:      primaryInt16,
   176  			primaryCache: fmt.Sprint(primaryInt16),
   177  		},
   178  		{
   179  			name:         "int32 primary",
   180  			primary:      primaryInt32,
   181  			primaryCache: fmt.Sprint(primaryInt32),
   182  		},
   183  		{
   184  			name:         "int64 primary",
   185  			primary:      primaryInt64,
   186  			primaryCache: fmt.Sprint(primaryInt64),
   187  		},
   188  		{
   189  			name:         "uint8 primary",
   190  			primary:      primaryUint8,
   191  			primaryCache: fmt.Sprint(primaryUint8),
   192  		},
   193  		{
   194  			name:         "uint16 primary",
   195  			primary:      primaryUint16,
   196  			primaryCache: fmt.Sprint(primaryUint16),
   197  		},
   198  		{
   199  			name:         "uint32 primary",
   200  			primary:      primaryUint32,
   201  			primaryCache: fmt.Sprint(primaryUint32),
   202  		},
   203  		{
   204  			name:         "uint64 primary",
   205  			primary:      primaryUint64,
   206  			primaryCache: fmt.Sprint(primaryUint64),
   207  		},
   208  	}
   209  
   210  	for _, test := range tests {
   211  		t.Run(test.name, func(t *testing.T) {
   212  			resetStats()
   213  			r, clean, err := redistest.CreateRedis()
   214  			assert.Nil(t, err)
   215  			defer clean()
   216  
   217  			c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
   218  				cache.WithNotFoundExpiry(time.Second))
   219  
   220  			var str string
   221  			r.Set("index", test.primaryCache)
   222  			err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
   223  				return fmt.Sprintf("%v/1234", s)
   224  			}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
   225  				assert.Fail(t, "should not go here")
   226  				return test.primary, nil
   227  			}, func(conn sqlx.SqlConn, v, primary interface{}) error {
   228  				*v.(*string) = "xin"
   229  				assert.Equal(t, primary, primary)
   230  				return nil
   231  			})
   232  			assert.Nil(t, err)
   233  			assert.Equal(t, "xin", str)
   234  			val, err := r.Get("index")
   235  			assert.Nil(t, err)
   236  			assert.Equal(t, test.primaryCache, val)
   237  			val, err = r.Get(test.primaryCache + "/1234")
   238  			assert.Nil(t, err)
   239  			assert.Equal(t, `"xin"`, val)
   240  		})
   241  	}
   242  }
   243  
   244  func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
   245  	caches := map[string]string{
   246  		"index":        "primary",
   247  		"primary/1234": "xin",
   248  	}
   249  
   250  	for k, v := range caches {
   251  		t.Run(k+"/"+v, func(t *testing.T) {
   252  			resetStats()
   253  			r, clean, err := redistest.CreateRedis()
   254  			assert.Nil(t, err)
   255  			defer clean()
   256  
   257  			c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
   258  				cache.WithNotFoundExpiry(time.Second))
   259  
   260  			var str string
   261  			r.Set(k, v)
   262  			err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
   263  				return fmt.Sprintf("%s/1234", s)
   264  			}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
   265  				*v.(*string) = "xin"
   266  				return "primary", nil
   267  			}, func(conn sqlx.SqlConn, v, primary interface{}) error {
   268  				*v.(*string) = "xin"
   269  				assert.Equal(t, "primary", primary)
   270  				return nil
   271  			})
   272  			assert.Nil(t, err)
   273  			assert.Equal(t, "xin", str)
   274  			val, err := r.Get("index")
   275  			assert.Nil(t, err)
   276  			assert.Equal(t, `"primary"`, val)
   277  			val, err = r.Get("primary/1234")
   278  			assert.Nil(t, err)
   279  			assert.Equal(t, `"xin"`, val)
   280  		})
   281  	}
   282  }
   283  
   284  func TestStatCacheFails(t *testing.T) {
   285  	resetStats()
   286  	log.SetOutput(ioutil.Discard)
   287  	defer log.SetOutput(os.Stdout)
   288  
   289  	r := redis.New("localhost:59999")
   290  	c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
   291  
   292  	for i := 0; i < 20; i++ {
   293  		var str string
   294  		err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error {
   295  			return errors.New("db failed")
   296  		})
   297  		assert.NotNil(t, err)
   298  	}
   299  
   300  	assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Total))
   301  	assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.Hit))
   302  	assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Miss))
   303  	assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.DbFails))
   304  }
   305  
   306  func TestStatDbFails(t *testing.T) {
   307  	resetStats()
   308  	r, clean, err := redistest.CreateRedis()
   309  	assert.Nil(t, err)
   310  	defer clean()
   311  
   312  	c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
   313  
   314  	for i := 0; i < 20; i++ {
   315  		var str string
   316  		err = c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error {
   317  			return errors.New("db failed")
   318  		})
   319  		assert.NotNil(t, err)
   320  	}
   321  
   322  	assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.Total))
   323  	assert.Equal(t, uint64(0), atomic.LoadUint64(&stats.Hit))
   324  	assert.Equal(t, uint64(20), atomic.LoadUint64(&stats.DbFails))
   325  }
   326  
   327  func TestStatFromMemory(t *testing.T) {
   328  	resetStats()
   329  	r, clean, err := redistest.CreateRedis()
   330  	assert.Nil(t, err)
   331  	defer clean()
   332  
   333  	c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
   334  
   335  	var all sync.WaitGroup
   336  	var wait sync.WaitGroup
   337  	all.Add(10)
   338  	wait.Add(4)
   339  	go func() {
   340  		var str string
   341  		err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error {
   342  			*v.(*string) = "zero"
   343  			return nil
   344  		})
   345  		if err != nil {
   346  			t.Error(err)
   347  		}
   348  		wait.Wait()
   349  		runtime.Gosched()
   350  		all.Done()
   351  	}()
   352  
   353  	for i := 0; i < 4; i++ {
   354  		go func() {
   355  			var str string
   356  			wait.Done()
   357  			err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error {
   358  				*v.(*string) = "zero"
   359  				return nil
   360  			})
   361  			if err != nil {
   362  				t.Error(err)
   363  			}
   364  			all.Done()
   365  		}()
   366  	}
   367  	for i := 0; i < 5; i++ {
   368  		go func() {
   369  			var str string
   370  			err := c.QueryRow(&str, "name", func(conn sqlx.SqlConn, v interface{}) error {
   371  				*v.(*string) = "zero"
   372  				return nil
   373  			})
   374  			if err != nil {
   375  				t.Error(err)
   376  			}
   377  			all.Done()
   378  		}()
   379  	}
   380  	all.Wait()
   381  
   382  	assert.Equal(t, uint64(10), atomic.LoadUint64(&stats.Total))
   383  	assert.Equal(t, uint64(9), atomic.LoadUint64(&stats.Hit))
   384  }
   385  
   386  func TestCachedConnQueryRow(t *testing.T) {
   387  	r, clean, err := redistest.CreateRedis()
   388  	assert.Nil(t, err)
   389  	defer clean()
   390  
   391  	const (
   392  		key   = "user"
   393  		value = "any"
   394  	)
   395  	var conn trackedConn
   396  	var user string
   397  	var ran bool
   398  	c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
   399  	err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
   400  		ran = true
   401  		user = value
   402  		return nil
   403  	})
   404  	assert.Nil(t, err)
   405  	actualValue, err := r.Get(key)
   406  	assert.Nil(t, err)
   407  	var actual string
   408  	assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
   409  	assert.Equal(t, value, actual)
   410  	assert.Equal(t, value, user)
   411  	assert.True(t, ran)
   412  }
   413  
   414  func TestCachedConnQueryRowFromCache(t *testing.T) {
   415  	r, clean, err := redistest.CreateRedis()
   416  	assert.Nil(t, err)
   417  	defer clean()
   418  
   419  	const (
   420  		key   = "user"
   421  		value = "any"
   422  	)
   423  	var conn trackedConn
   424  	var user string
   425  	var ran bool
   426  	c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
   427  	assert.Nil(t, c.SetCache(key, value))
   428  	err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
   429  		ran = true
   430  		user = value
   431  		return nil
   432  	})
   433  	assert.Nil(t, err)
   434  	actualValue, err := r.Get(key)
   435  	assert.Nil(t, err)
   436  	var actual string
   437  	assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
   438  	assert.Equal(t, value, actual)
   439  	assert.Equal(t, value, user)
   440  	assert.False(t, ran)
   441  }
   442  
   443  func TestQueryRowNotFound(t *testing.T) {
   444  	r, clean, err := redistest.CreateRedis()
   445  	assert.Nil(t, err)
   446  	defer clean()
   447  
   448  	const key = "user"
   449  	var conn trackedConn
   450  	var user string
   451  	var ran int
   452  	c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
   453  	for i := 0; i < 20; i++ {
   454  		err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
   455  			ran++
   456  			return sql.ErrNoRows
   457  		})
   458  		assert.Exactly(t, sqlx.ErrNotFound, err)
   459  	}
   460  	assert.Equal(t, 1, ran)
   461  }
   462  
   463  func TestCachedConnExec(t *testing.T) {
   464  	r, clean, err := redistest.CreateRedis()
   465  	assert.Nil(t, err)
   466  	defer clean()
   467  
   468  	var conn trackedConn
   469  	c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
   470  	_, err = c.ExecNoCache("delete from user_table where id='kevin'")
   471  	assert.Nil(t, err)
   472  	assert.True(t, conn.execValue)
   473  }
   474  
   475  func TestCachedConnExecDropCache(t *testing.T) {
   476  	r, err := miniredis.Run()
   477  	assert.Nil(t, err)
   478  	defer fx.DoWithTimeout(func() error {
   479  		r.Close()
   480  		return nil
   481  	}, time.Second)
   482  
   483  	const (
   484  		key   = "user"
   485  		value = "any"
   486  	)
   487  	var conn trackedConn
   488  	c := NewNodeConn(&conn, redis.New(r.Addr()), cache.WithExpiry(time.Second*30))
   489  	assert.Nil(t, c.SetCache(key, value))
   490  	_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
   491  		return conn.Exec("delete from user_table where id='kevin'")
   492  	}, key)
   493  	assert.Nil(t, err)
   494  	assert.True(t, conn.execValue)
   495  	_, err = r.Get(key)
   496  	assert.Exactly(t, miniredis.ErrKeyNotFound, err)
   497  	_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
   498  		return nil, errors.New("foo")
   499  	}, key)
   500  	assert.NotNil(t, err)
   501  }
   502  
   503  func TestCachedConnExecDropCacheFailed(t *testing.T) {
   504  	const key = "user"
   505  	var conn trackedConn
   506  	r := redis.New("anyredis:8888")
   507  	c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
   508  	_, err := c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
   509  		return conn.Exec("delete from user_table where id='kevin'")
   510  	}, key)
   511  	// async background clean, retry logic
   512  	assert.Nil(t, err)
   513  }
   514  
   515  func TestCachedConnQueryRows(t *testing.T) {
   516  	r, clean, err := redistest.CreateRedis()
   517  	assert.Nil(t, err)
   518  	defer clean()
   519  
   520  	var conn trackedConn
   521  	c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
   522  	var users []string
   523  	err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
   524  	assert.Nil(t, err)
   525  	assert.True(t, conn.queryRowsValue)
   526  }
   527  
   528  func TestCachedConnTransact(t *testing.T) {
   529  	r, clean, err := redistest.CreateRedis()
   530  	assert.Nil(t, err)
   531  	defer clean()
   532  
   533  	var conn trackedConn
   534  	c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
   535  	err = c.Transact(func(session sqlx.Session) error {
   536  		return nil
   537  	})
   538  	assert.Nil(t, err)
   539  	assert.True(t, conn.transactValue)
   540  }
   541  
   542  func TestQueryRowNoCache(t *testing.T) {
   543  	r, clean, err := redistest.CreateRedis()
   544  	assert.Nil(t, err)
   545  	defer clean()
   546  
   547  	const (
   548  		key   = "user"
   549  		value = "any"
   550  	)
   551  	var user string
   552  	var ran bool
   553  	conn := dummySqlConn{queryRow: func(v interface{}, q string, args ...interface{}) error {
   554  		user = value
   555  		ran = true
   556  		return nil
   557  	}}
   558  	c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
   559  	err = c.QueryRowNoCache(&user, key)
   560  	assert.Nil(t, err)
   561  	assert.Equal(t, value, user)
   562  	assert.True(t, ran)
   563  }
   564  
   565  func TestNewConnWithCache(t *testing.T) {
   566  	r, clean, err := redistest.CreateRedis()
   567  	assert.Nil(t, err)
   568  	defer clean()
   569  
   570  	var conn trackedConn
   571  	c := NewConnWithCache(&conn, cache.NewNode(r, exclusiveCalls, stats, sql.ErrNoRows))
   572  	_, err = c.ExecNoCache("delete from user_table where id='kevin'")
   573  	assert.Nil(t, err)
   574  	assert.True(t, conn.execValue)
   575  }
   576  
   577  func resetStats() {
   578  	atomic.StoreUint64(&stats.Total, 0)
   579  	atomic.StoreUint64(&stats.Hit, 0)
   580  	atomic.StoreUint64(&stats.Miss, 0)
   581  	atomic.StoreUint64(&stats.DbFails, 0)
   582  }
   583  
   584  type dummySqlConn struct {
   585  	queryRow func(interface{}, string, ...interface{}) error
   586  }
   587  
   588  func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) {
   589  	return nil, nil
   590  }
   591  
   592  func (d dummySqlConn) Prepare(query string) (sqlx.StmtSession, error) {
   593  	return nil, nil
   594  }
   595  
   596  func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error {
   597  	if d.queryRow != nil {
   598  		return d.queryRow(v, query, args...)
   599  	}
   600  	return nil
   601  }
   602  
   603  func (d dummySqlConn) QueryRowPartial(v interface{}, query string, args ...interface{}) error {
   604  	return nil
   605  }
   606  
   607  func (d dummySqlConn) QueryRows(v interface{}, query string, args ...interface{}) error {
   608  	return nil
   609  }
   610  
   611  func (d dummySqlConn) QueryRowsPartial(v interface{}, query string, args ...interface{}) error {
   612  	return nil
   613  }
   614  
   615  func (d dummySqlConn) RawDB() (*sql.DB, error) {
   616  	return nil, nil
   617  }
   618  
   619  func (d dummySqlConn) Transact(func(session sqlx.Session) error) error {
   620  	return nil
   621  }
   622  
   623  type trackedConn struct {
   624  	dummySqlConn
   625  	execValue      bool
   626  	queryRowsValue bool
   627  	transactValue  bool
   628  }
   629  
   630  func (c *trackedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
   631  	c.execValue = true
   632  	return c.dummySqlConn.Exec(query, args...)
   633  }
   634  
   635  func (c *trackedConn) QueryRows(v interface{}, query string, args ...interface{}) error {
   636  	c.queryRowsValue = true
   637  	return c.dummySqlConn.QueryRows(v, query, args...)
   638  }
   639  
   640  func (c *trackedConn) RawDB() (*sql.DB, error) {
   641  	return nil, nil
   642  }
   643  
   644  func (c *trackedConn) Transact(fn func(session sqlx.Session) error) error {
   645  	c.transactValue = true
   646  	return c.dummySqlConn.Transact(fn)
   647  }