package sqkv import ( "database/sql" "errors" "fmt" "strings" ) const DefaultBucket = "SQKV" var ( ErrBucketNameEmpty = errors.New("bucket name cannot be empty") ErrBucketDoesNotExist = errors.New("bucket does not exist") ErrBucketExists = errors.New("bucket already exists") ErrKeyDoesNotExist = errors.New("key does not exist in bucket") ) // Bucket is a KV bucket type Bucket struct { db *sql.DB table string } // Bucket gets a bucket by name, or returns nil if it doesn't exist func (d *DB) Bucket(name string) (*Bucket, error) { name = escapeIdent(name) if !d.bucketExists(name) { return nil, ErrBucketDoesNotExist } return &Bucket{ db: d.db, table: name, }, nil } // CreateBucket creates a new bucket and returns it. Returns an error if the bucket already exists or name is empty. func (d *DB) CreateBucket(name string) (*Bucket, error) { name = escapeIdent(name) if strings.TrimSpace(name) == "" { return nil, ErrBucketNameEmpty } if ok := d.bucketExists(name); ok { return nil, ErrBucketExists } if _, err := d.db.Exec(fmt.Sprintf(`CREATE TABLE %s ( ID INTEGER PRIMARY KEY AUTOINCREMENT, KEY TEXT UNIQUE, VALUE TEXT )`, name)); err != nil { return nil, err } return &Bucket{ db: d.db, table: name, }, nil } // CreateBucketIfNotExist creates a new bucket (if it does not exist) and returns it. Returns an error if name is empty. func (d *DB) CreateBucketIfNotExist(name string) (*Bucket, error) { name = escapeIdent(name) b, err := d.CreateBucket(name) if err != nil { if errors.Is(err, ErrBucketExists) { return d.Bucket(name) } return nil, err } return b, nil } func (d *DB) bucketExists(name string) bool { res, err := d.db.Query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?;", name) if err != nil { return false } defer res.Close() return res.Next() } // Get returns a value from the bucket func (b *Bucket) Get(key string) (string, error) { row := b.db.QueryRow(fmt.Sprintf("SELECT value FROM %s WHERE key = ?", b.table), key) var value string if err := row.Scan(&value); err != nil { if errors.Is(err, sql.ErrNoRows) { return "", ErrKeyDoesNotExist } return "", err } return value, row.Err() } // Put sets a value in the bucket func (b *Bucket) Put(key, value string) error { _, err := b.db.Exec(fmt.Sprintf("INSERT INTO %s ( key, value ) VALUES( ?, ? ) ON CONFLICT(key) DO UPDATE SET value=excluded.value", b.table), key, value) return err } // Delete removes a value from the bucket func (b *Bucket) Delete(key string) error { _, err := b.db.Exec(fmt.Sprintf("DELETE FROM %s WHERE key = ?", b.table), key) return err }