github.com/lingyao2333/mo-zero@v1.4.1/core/stores/sqlc/cachedsql_test.go (about)

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