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  }