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 }