161 lines
2.9 KiB
Go
161 lines
2.9 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/gopatchy/metadata"
|
|
// Register sqlite3 db handler.
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
type SQLiteStore struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
func NewSQLiteStore(conn string) (*SQLiteStore, error) {
|
|
db, err := sql.Open("sqlite3", conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO: Keep a set of prepared statements with PrepareContext()
|
|
// TODO: Consider tuning per https://phiresky.github.io/blog/2020/sqlite-performance-tuning/
|
|
|
|
return &SQLiteStore{
|
|
db: db,
|
|
}, nil
|
|
}
|
|
|
|
func (sls *SQLiteStore) Close() {
|
|
sls.db.Close()
|
|
}
|
|
|
|
func (sls *SQLiteStore) Write(ctx context.Context, t string, obj any) error {
|
|
id := metadata.GetMetadata(obj).ID
|
|
|
|
js, err := json.Marshal(obj)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = sls.exec(ctx, "INSERT INTO `%s` (id, obj) VALUES (?,?) ON CONFLICT(id) DO UPDATE SET obj=?;", t, id, js, js)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sls *SQLiteStore) Delete(ctx context.Context, t, id string) error {
|
|
err := sls.exec(ctx, "DELETE FROM `%s` WHERE id=?", t, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sls *SQLiteStore) Read(ctx context.Context, t, id string, factory func() any) (any, error) {
|
|
rows, err := sls.query(ctx, "SELECT obj FROM `%s` WHERE id=?;", t, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
if !rows.Next() {
|
|
return nil, nil
|
|
}
|
|
|
|
var js []byte
|
|
|
|
err = rows.Scan(&js)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
obj := factory()
|
|
|
|
err = json.Unmarshal(js, obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return obj, nil
|
|
}
|
|
|
|
func (sls *SQLiteStore) List(ctx context.Context, t string, factory func() any) ([]any, error) {
|
|
rows, err := sls.query(ctx, "SELECT obj FROM `%s`;", t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
ret := []any{}
|
|
|
|
for rows.Next() {
|
|
var js []byte
|
|
|
|
err = rows.Scan(&js)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
obj := factory()
|
|
|
|
err = json.Unmarshal(js, obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ret = append(ret, obj)
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (sls *SQLiteStore) exec(ctx context.Context, query, t string, args ...any) error {
|
|
query = fmt.Sprintf(query, t)
|
|
|
|
_, err := sls.db.ExecContext(ctx, query, args...)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
_, err = sls.db.ExecContext(ctx, sls.tableSQL(t))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = sls.db.ExecContext(ctx, query, args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sls *SQLiteStore) query(ctx context.Context, query, t string, args ...any) (*sql.Rows, error) {
|
|
query = fmt.Sprintf(query, t)
|
|
|
|
rows, err := sls.db.QueryContext(ctx, query, args...)
|
|
if err == nil {
|
|
return rows, nil
|
|
}
|
|
|
|
_, err = sls.db.ExecContext(ctx, sls.tableSQL(t))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sls.db.QueryContext(ctx, query, args...)
|
|
}
|
|
|
|
func (sls *SQLiteStore) tableSQL(t string) string {
|
|
return fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%s` (id TEXT NOT NULL PRIMARY KEY, obj TEXT NOT NULL);", t)
|
|
}
|