github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/gokv/sqlkv/sqlkv.go (about) 1 package sqlkv 2 3 import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "log" 8 "sync" 9 "time" 10 11 "github.com/bingoohuang/gg/pkg/sqx" 12 "go.uber.org/multierr" 13 ) 14 15 type Config struct { 16 DriverName string 17 DataSourceName string 18 19 AllSQL string 20 GetSQL string 21 UpdateSQL string 22 InsertSQL string 23 DelSQL string 24 25 // RefreshInterval will Refresh the key values from the database in every Refresh interval. 26 RefreshInterval time.Duration 27 } 28 29 // Client is a gokv.Store implementation for SQL databases. 30 type Client struct { 31 Config 32 33 cache map[string]string 34 cacheLock sync.Mutex 35 } 36 37 func DefaultDuration(s, defaultValue time.Duration) time.Duration { 38 if s == 0 { 39 return defaultValue 40 } 41 return s 42 } 43 44 func Default(s, defaultValue string) string { 45 if s == "" { 46 return defaultValue 47 } 48 49 return s 50 } 51 52 const ( 53 DefaultAllSQL = `select k,v from kv where state = 1` 54 DefaultGetSQL = `select v from kv where k = :k and state = 1` 55 DefaultInsertSQL = `insert into kv(k, v, state, created) values(:k, :v, 1, :time)` 56 DefaultUpdateSQL = `update kv set v = :v, updated = :time, state = 1 where k = :k` 57 DefaultDelSQL = `update kv set state = 0 where k = :k` 58 ) 59 60 func NewClient(c Config) (*Client, error) { 61 c.RefreshInterval = DefaultDuration(c.RefreshInterval, 60*time.Second) 62 c.DriverName = Default(c.DriverName, "mysql") 63 c.AllSQL = Default(c.AllSQL, DefaultAllSQL) 64 c.GetSQL = Default(c.GetSQL, DefaultGetSQL) 65 c.UpdateSQL = Default(c.UpdateSQL, DefaultUpdateSQL) 66 c.InsertSQL = Default(c.InsertSQL, DefaultInsertSQL) 67 c.DelSQL = Default(c.DelSQL, DefaultDelSQL) 68 69 cli := &Client{ 70 Config: c, 71 cache: make(map[string]string), 72 } 73 74 go cli.tickerRefresh() 75 76 return cli, nil 77 } 78 79 func (c *Client) tickerRefresh() { 80 ticker := time.NewTicker(c.RefreshInterval) 81 for range ticker.C { 82 if _, err := c.All(); err != nil { 83 log.Printf("W! refersh error %v", err) 84 } 85 } 86 } 87 88 // All list the keys in the store. 89 func (c *Client) All() (kvs map[string]string, er error) { 90 _, dbx, err := sqx.Open(c.DriverName, c.DataSourceName) 91 if err != nil { 92 return nil, err 93 } 94 95 defer func() { er = multierr.Append(er, dbx.Close()) }() 96 97 kvs = make(map[string]string) 98 sqx.SQL{Q: c.AllSQL}.QueryRaw(dbx, sqx.WithScanRow(func(_ []string, rows *sql.Rows, _ int) (bool, error) { 99 rowValues, err := sqx.ScanRowValues(rows) 100 if err != nil { 101 return false, err 102 } 103 104 k := fmt.Sprintf("%v", rowValues[0]) 105 kvs[k] = fmt.Sprintf("%v", rowValues[1]) 106 return true, nil 107 })) 108 109 c.cacheLock.Lock() 110 c.cache = kvs 111 c.cacheLock.Unlock() 112 113 return kvs, nil 114 } 115 116 // Set stores the given value for the given key. 117 // Values are automatically marshalled to JSON or gob (depending on the configuration). 118 // The key must not be "" and the value must not be nil. 119 func (c *Client) Set(k, v string) (er error) { 120 m := map[string]string{ 121 "k": k, 122 "v": v, 123 "time": time.Now().Format(`2006-01-02 15:04:05.000`), 124 } 125 126 _, dbx, err := sqx.Open(c.DriverName, c.DataSourceName) 127 if err != nil { 128 return err 129 } 130 131 defer func() { er = multierr.Append(er, dbx.Close()) }() 132 133 effectedRows, err := sqx.SQL{Q: c.UpdateSQL, Vars: sqx.Vars(m)}.Update(dbx) 134 if err == nil && effectedRows > 0 { 135 c.set(k, v) 136 return nil 137 } 138 139 _, err = sqx.SQL{Q: c.InsertSQL, Vars: sqx.Vars(m)}.Update(dbx) 140 return err 141 } 142 143 func (c *Client) set(k, v string) { 144 c.cacheLock.Lock() 145 c.cache[k] = v 146 c.cacheLock.Unlock() 147 } 148 149 // Get retrieves the stored value for the given key. 150 // You need to pass a pointer to the value, so in case of a struct 151 // the automatic unmarshalling can populate the fields of the object 152 // that v points to with the values of the retrieved object's values. 153 // If no value is found it returns (false, nil). 154 func (c *Client) Get(k string) (found bool, v string, er error) { 155 c.cacheLock.Lock() 156 if v, ok := c.cache[k]; ok { 157 c.cacheLock.Unlock() 158 return true, v, nil 159 } 160 c.cacheLock.Unlock() 161 162 _, dbx, err := sqx.Open(c.DriverName, c.DataSourceName) 163 if err != nil { 164 return false, "", err 165 } 166 167 defer func() { er = multierr.Append(er, dbx.Close()) }() 168 169 m := map[string]string{"k": k} 170 v, err = sqx.SQL{Q: c.GetSQL, Vars: sqx.Vars(m)}.QueryAsString(dbx) 171 if err != nil { 172 if errors.Is(err, sql.ErrNoRows) { 173 err = nil 174 } 175 return false, "", err 176 } 177 178 c.set(k, v) 179 return true, v, nil 180 } 181 182 // Del deletes the stored value for the given key. 183 // Deleting a non-existing key-value pair does NOT lead to an error. 184 // The key must not be "". 185 func (c *Client) Del(k string) (er error) { 186 c.cacheLock.Lock() 187 delete(c.cache, k) 188 c.cacheLock.Unlock() 189 190 defer func() { 191 if err := c.del(k); err != nil { 192 log.Printf("W! failed to del %v", err) 193 } 194 }() 195 196 return nil 197 } 198 199 func (c *Client) del(k string) (er error) { 200 m := map[string]string{ 201 "k": k, 202 "time": time.Now().Format(`2006-01-02 15:04:05.000`), 203 } 204 _, db, err := sqx.Open(c.DriverName, c.DataSourceName) 205 if err != nil { 206 return err 207 } 208 209 defer func() { er = multierr.Append(er, db.Close()) }() 210 211 if _, err := (sqx.SQL{Q: c.DelSQL, Vars: sqx.Vars(m)}).Update(db); err != nil { 212 return err 213 } 214 215 return nil 216 }