github.com/woocoos/entcache@v0.0.0-20231206055445-856f0148efa5/driver_test.go (about)

     1  package entcache
     2  
     3  import (
     4  	"context"
     5  	"entgo.io/ent/dialect/sql"
     6  	"github.com/alicebob/miniredis/v2"
     7  	"github.com/stretchr/testify/suite"
     8  	"github.com/tsingsun/woocoo/pkg/cache"
     9  	"github.com/tsingsun/woocoo/pkg/cache/lfu"
    10  	"github.com/tsingsun/woocoo/pkg/cache/redisc"
    11  	"github.com/tsingsun/woocoo/pkg/conf"
    12  	"testing"
    13  	"time"
    14  
    15  	_ "github.com/mattn/go-sqlite3"
    16  )
    17  
    18  var _ cache.Cache = (*mockCache)(nil)
    19  
    20  type mockCache struct {
    21  }
    22  
    23  func (m mockCache) Get(ctx context.Context, key string, value any, opts ...cache.Option) error {
    24  	//TODO implement me
    25  	panic("implement me")
    26  }
    27  
    28  func (m mockCache) Set(ctx context.Context, key string, value any, opts ...cache.Option) error {
    29  	//TODO implement me
    30  	panic("implement me")
    31  }
    32  
    33  func (m mockCache) Has(ctx context.Context, key string) bool {
    34  	//TODO implement me
    35  	panic("implement me")
    36  }
    37  
    38  func (m mockCache) Del(ctx context.Context, key string) error {
    39  	//TODO implement me
    40  	panic("implement me")
    41  }
    42  
    43  func (m mockCache) IsNotFound(err error) bool {
    44  	//TODO implement me
    45  	panic("implement me")
    46  }
    47  
    48  type driverSuite struct {
    49  	suite.Suite
    50  	DB    *sql.Driver
    51  	Redis *miniredis.Miniredis
    52  }
    53  
    54  func TestDriverSuite(t *testing.T) {
    55  	suite.Run(t, new(driverSuite))
    56  }
    57  
    58  func (t *driverSuite) SetupSuite() {
    59  	db, err := sql.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
    60  	t.Require().NoError(err)
    61  	t.DB = db
    62  	t.Require().NoError(t.DB.Exec(context.Background(),
    63  		"create table users (id integer primary key autoincrement, age float)", []any{}, nil))
    64  	t.Require().NoError(t.DB.Exec(context.Background(),
    65  		"insert into users values (?,?)", []any{1, 20.1}, nil))
    66  	t.Redis, err = miniredis.Run()
    67  	t.Require().NoError(err)
    68  }
    69  
    70  func (t *driverSuite) TestDriver() {
    71  	query := func(drv *Driver) {
    72  		rows := &sql.Rows{}
    73  		err := drv.Query(context.Background(), "SELECT age FROM users", []any{20.1, 30.2, 40.5}, rows)
    74  		t.Require().NoError(err)
    75  		defer rows.Close()
    76  	}
    77  	t.Run("default", func() {
    78  		drv := NewDriver(t.DB)
    79  		query(drv)
    80  	})
    81  	t.Run("withTTL", func() {
    82  		drv := NewDriver(t.DB, WithConfiguration(conf.NewFromStringMap(map[string]any{
    83  			"hashQueryTTL": time.Second,
    84  			"name":         "withTTL",
    85  		})))
    86  		tlfu := drv.Cache.(*lfu.TinyLFU)
    87  		t.Equal(time.Second, tlfu.TTL)
    88  
    89  		query(drv)
    90  		query(drv)
    91  		time.Sleep(time.Second)
    92  		query(drv)
    93  		t.Equal(3, int(drv.stats.Gets))
    94  		t.Equal(1, int(drv.stats.Hits))
    95  	})
    96  	t.Run("redis", func() {
    97  		cnfstr := `
    98  driverName: drvierTest
    99  storeKey: drvierTest
   100  `
   101  		cnf := conf.NewFromBytes([]byte(cnfstr))
   102  		cnf.Parser().Set("addrs", []string{t.Redis.Addr()})
   103  		_, err := redisc.New(cnf)
   104  		t.Require().NoError(err)
   105  		drv := NewDriver(t.DB, WithConfiguration(cnf))
   106  		query(drv)
   107  	})
   108  	t.Run("with cache", func() {
   109  		drv := NewDriver(t.DB, WithCache(mockCache{}))
   110  		t.Panics(func() {
   111  			query(drv)
   112  		})
   113  	})
   114  }
   115  
   116  func (t *driverSuite) TestWithXXEntryKey() {
   117  	var dest struct {
   118  		id  int
   119  		age float64
   120  	}
   121  
   122  	query := func(drv *Driver, ctx context.Context, query string, args any) {
   123  		rows := &sql.Rows{}
   124  		err := drv.Query(ctx, query, args, rows)
   125  		t.Require().NoError(err)
   126  		rows.Next()
   127  		_ = rows.Scan(&dest.id, &dest.age)
   128  		_ = rows.Close()
   129  	}
   130  	t.Run("fieldQuery", func() {
   131  		drv := NewDriver(t.DB, WithConfiguration(conf.NewFromStringMap(map[string]any{
   132  			"hashQueryTTL": time.Second,
   133  			"name":         "fieldQuery",
   134  			"cachePrefix":  "fieldQuery:",
   135  		})))
   136  		all := "SELECT * FROM users where id=?"
   137  		query(drv, WithEntryKey(context.Background(), "User", 1), all, []any{1})
   138  		query(drv, WithRefEntryKey(context.Background(), "User", 1), "SELECT age FROM users where id=?", []any{1})
   139  
   140  		t.Equal(uint64(2), drv.stats.Gets)
   141  		t.Equal(uint64(0), drv.stats.Hits)
   142  		time.Sleep(time.Second * 2)
   143  		key, _ := drv.Hash(all, []any{1})
   144  		t.True(drv.Cache.Has(context.Background(), drv.CachePrefix+string(key)), "entry key query ttl set no expired")
   145  	})
   146  	t.Run("refChanged", func() {
   147  		drv := NewDriver(t.DB, WithConfiguration(conf.NewFromStringMap(map[string]any{
   148  			"hashQueryTTL": time.Minute,
   149  			"name":         "refChanged",
   150  		})))
   151  		query(drv, WithRefEntryKey(context.Background(), "User", 1), "SELECT age FROM users where id=?", []any{1})
   152  		drv.ChangeSet.Store("User:1")
   153  		query(drv, WithRefEntryKey(context.Background(), "User", 1), "SELECT age FROM users where id=?", []any{1})
   154  		t.Equal(uint64(2), drv.stats.Gets)
   155  		t.Equal(uint64(0), drv.stats.Hits, "first query will be evicted")
   156  		query(drv, context.Background(), "SELECT age FROM users where id=?", []any{1})
   157  		t.Equal(uint64(3), drv.stats.Gets)
   158  		t.Equal(uint64(1), drv.stats.Hits, "common query should use the the cached")
   159  		ctx := WithTTL(context.Background(), time.Second)
   160  		query(drv, WithRefEntryKey(ctx, "User", 1), "SELECT age FROM users where id=?", []any{1})
   161  		t.Equal(uint64(2), drv.stats.Hits)
   162  	})
   163  	t.Run("context", func() {
   164  		drv := NewDriver(t.DB, WithConfiguration(conf.NewFromStringMap(map[string]any{
   165  			"hashQueryTTL": time.Minute,
   166  			"name":         "context",
   167  		})))
   168  		all := "SELECT * FROM users where id=?"
   169  		ctx := WithTTL(context.Background(), time.Second)
   170  		query(drv, WithEntryKey(ctx, "User", 1), all, []any{1})
   171  		time.Sleep(time.Second * 2)
   172  		query(drv, WithEntryKey(ctx, "User", 1), all, []any{1})
   173  		t.Equal(uint64(0), drv.stats.Hits)
   174  		drv.ChangeSet.Store("User:1")
   175  		query(drv, WithEntryKey(ctx, "User", 1), all, []any{1})
   176  		t.Len(drv.ChangeSet.changes, 0)
   177  
   178  		query(drv, Evict(context.Background()), all, []any{1})
   179  		t.Equal(uint64(0), drv.stats.Hits)
   180  		key, _ := drv.Hash(all, []any{1})
   181  		t.True(drv.Cache.Has(context.Background(), string(key)), "evict should refresh the cache")
   182  		query(drv, Skip(ctx), all, []any{1})
   183  		t.Equal(uint64(0), drv.stats.Hits)
   184  	})
   185  }
   186  
   187  func (t *driverSuite) TestTx() {
   188  	var dest struct {
   189  		id  int
   190  		age float64
   191  	}
   192  	drv := NewDriver(t.DB, WithConfiguration(conf.NewFromStringMap(map[string]any{
   193  		"hashQueryTTL": time.Minute,
   194  		"name":         "tx",
   195  	})))
   196  	ctx := context.Background()
   197  	tx, err := drv.Tx(ctx)
   198  	t.Require().NoError(err)
   199  	t.NoError(tx.Exec(ctx, "insert into users values (?,?)", []any{2, 30.1}, nil))
   200  	rows := &sql.Rows{}
   201  	t.NoError(tx.Query(ctx, "SELECT age FROM users where id=?", []any{2}, rows))
   202  	rows.Next()
   203  	_ = rows.Scan(&dest.age)
   204  	_ = rows.Close()
   205  }
   206  
   207  func (t *driverSuite) TestGC() {
   208  	drv := NewDriver(t.DB, WithConfiguration(conf.NewFromStringMap(map[string]any{
   209  		"hashQueryTTL": time.Second,
   210  		"name":         "gc",
   211  	})), WithChangeSet(NewChangeSet(time.Second*2)))
   212  	ctx, concel := context.WithTimeout(context.Background(), time.Second*5)
   213  	defer concel()
   214  	go drv.ChangeSet.Start(ctx)
   215  	drv.ChangeSet.Store("gc:1")
   216  	drv.ChangeSet.Store("gc:2")
   217  	drv.ChangeSet.LoadOrStoreRef("ref:1")
   218  	drv.ChangeSet.LoadOrStoreRef("ref:2")
   219  	time.Sleep(time.Second * 3)
   220  	t.Equal(0, len(drv.ChangeSet.changes))
   221  	t.Equal(0, len(drv.ChangeSet.refs))
   222  }