github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/utils/cache/cachedb/cachedb.go (about) 1 package cachedb 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "fmt" 7 "os" 8 "time" 9 10 "github.com/lmorg/murex/debug" 11 "github.com/lmorg/murex/utils/consts" 12 ) 13 14 const ( 15 sqlCreateTable = `CREATE TABLE IF NOT EXISTS %s (key STRING PRIMARY KEY, value STRING, ttl DATETIME KEY);` 16 17 sqlRead = `SELECT value FROM %s WHERE key == ? AND ttl > unixepoch();` 18 19 sqlWrite = `INSERT OR REPLACE INTO %s (key, value, ttl) VALUES (?, ?, ?);` 20 ) 21 22 var ( 23 disabled bool = true 24 Path string = os.TempDir() + "/murex-temp-cache.db" // allows tests to run without contaminating regular cachedb 25 db *sql.DB 26 ) 27 28 func dbConnect() *sql.DB { 29 if debug.Enabled { 30 fmt.Printf("cache DB: %s\n", Path) 31 } 32 33 db, err := sql.Open(driverName, fmt.Sprintf("file:%s?cache=shared", Path)) 34 if err != nil { 35 dbFailed("opening cache database", err) 36 return nil 37 } 38 39 db.SetMaxOpenConns(1) 40 41 disabled = false 42 return db 43 } 44 45 func CreateTable(namespace string) { 46 if db == nil { 47 db = dbConnect() 48 } 49 50 if disabled { 51 return 52 } 53 54 _, err := db.Exec(fmt.Sprintf(sqlCreateTable, namespace)) 55 if err != nil { 56 dbFailed("creating table "+namespace, err) 57 } 58 } 59 60 func dbFailed(message string, err error) { 61 if debug.Enabled { 62 os.Stderr.WriteString(fmt.Sprintf("Error %s: %s: '%s'\n%s\n", message, err.Error(), Path, consts.IssueTrackerURL)) 63 os.Stderr.WriteString("!!! Disabling persistent cache !!!\n") 64 } 65 disabled = true 66 } 67 68 func CloseDb() { 69 if db != nil { 70 _ = db.Close() 71 } 72 } 73 74 func Read(namespace string, key string, ptr any) bool { 75 if disabled || ptr == nil { 76 return false 77 } 78 79 rows, err := db.Query(fmt.Sprintf(sqlRead, namespace), key) 80 if err != nil { 81 dbFailed("querying cache in "+namespace, err) 82 return false 83 } 84 85 ok := rows.Next() 86 if !ok { 87 return false 88 } 89 90 var s string 91 err = rows.Scan(&s) 92 if err != nil { 93 dbFailed("reading cache in "+namespace, err) 94 return false 95 } 96 97 if err = rows.Close(); err != nil { 98 dbFailed("closing cache post read in "+namespace, err) 99 return false 100 } 101 102 if len(s) == 0 { // nothing returned 103 return false 104 } 105 106 if err := json.Unmarshal([]byte(s), ptr); err != nil { 107 dbFailed(fmt.Sprintf("unmarshalling cache in %s: %T (%s)", namespace, ptr, s), err) 108 return false 109 } 110 111 return true 112 } 113 114 func Write(namespace string, key string, value any, ttl time.Time) { 115 if disabled || value == nil { 116 return 117 } 118 119 b, err := json.Marshal(value) 120 if err != nil { 121 dbFailed(fmt.Sprintf("marshalling cache in %s: %T (%v)", namespace, value, value), err) 122 return 123 } 124 125 _, err = db.Exec(fmt.Sprintf(sqlWrite, namespace), key, string(b), ttl.Unix()) 126 if err != nil { 127 dbFailed("writing to cache in "+namespace, err) 128 return 129 } 130 }