Files
store/store.go

161 lines
2.8 KiB
Go
Raw Normal View History

2023-04-20 18:08:42 +00:00
package store
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"github.com/gopatchy/metadata"
// Register sqlite3 db handler.
_ "github.com/mattn/go-sqlite3"
)
2023-04-20 18:21:44 +00:00
type Store struct {
2023-04-20 18:08:42 +00:00
db *sql.DB
}
2023-04-20 18:21:44 +00:00
func NewStore(conn string) (*Store, error) {
2023-04-20 18:08:42 +00:00
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/
2023-04-20 18:21:44 +00:00
return &Store{
2023-04-20 18:08:42 +00:00
db: db,
}, nil
}
2023-04-20 18:21:44 +00:00
func (s *Store) Close() {
s.db.Close()
2023-04-20 18:08:42 +00:00
}
2023-04-20 18:21:44 +00:00
func (s *Store) Write(ctx context.Context, t string, obj any) error {
2023-04-20 18:08:42 +00:00
id := metadata.GetMetadata(obj).ID
js, err := json.Marshal(obj)
if err != nil {
return err
}
2023-04-20 18:21:44 +00:00
err = s.exec(ctx, "INSERT INTO `%s` (id, obj) VALUES (?,?) ON CONFLICT(id) DO UPDATE SET obj=?;", t, id, js, js)
2023-04-20 18:08:42 +00:00
if err != nil {
return err
}
return nil
}
2023-04-20 18:21:44 +00:00
func (s *Store) Delete(ctx context.Context, t, id string) error {
err := s.exec(ctx, "DELETE FROM `%s` WHERE id=?", t, id)
2023-04-20 18:08:42 +00:00
if err != nil {
return err
}
return nil
}
2023-04-20 18:21:44 +00:00
func (s *Store) Read(ctx context.Context, t, id string, factory func() any) (any, error) {
rows, err := s.query(ctx, "SELECT obj FROM `%s` WHERE id=?;", t, id)
2023-04-20 18:08:42 +00:00
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
}
2023-04-20 18:21:44 +00:00
func (s *Store) List(ctx context.Context, t string, factory func() any) ([]any, error) {
rows, err := s.query(ctx, "SELECT obj FROM `%s`;", t)
2023-04-20 18:08:42 +00:00
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
}
2023-04-20 18:21:44 +00:00
func (s *Store) exec(ctx context.Context, query, t string, args ...any) error {
2023-04-20 18:08:42 +00:00
query = fmt.Sprintf(query, t)
2023-04-20 18:21:44 +00:00
_, err := s.db.ExecContext(ctx, query, args...)
2023-04-20 18:08:42 +00:00
if err == nil {
return nil
}
2023-04-20 18:21:44 +00:00
_, err = s.db.ExecContext(ctx, s.tableSQL(t))
2023-04-20 18:08:42 +00:00
if err != nil {
return err
}
2023-04-20 18:21:44 +00:00
_, err = s.db.ExecContext(ctx, query, args...)
2023-04-20 18:08:42 +00:00
if err != nil {
return err
}
return nil
}
2023-04-20 18:21:44 +00:00
func (s *Store) query(ctx context.Context, query, t string, args ...any) (*sql.Rows, error) {
2023-04-20 18:08:42 +00:00
query = fmt.Sprintf(query, t)
2023-04-20 18:21:44 +00:00
rows, err := s.db.QueryContext(ctx, query, args...)
2023-04-20 18:08:42 +00:00
if err == nil {
return rows, nil
}
2023-04-20 18:21:44 +00:00
_, err = s.db.ExecContext(ctx, s.tableSQL(t))
2023-04-20 18:08:42 +00:00
if err != nil {
return nil, err
}
2023-04-20 18:21:44 +00:00
return s.db.QueryContext(ctx, query, args...)
2023-04-20 18:08:42 +00:00
}
2023-04-20 18:21:44 +00:00
func (s *Store) tableSQL(t string) string {
2023-04-20 18:08:42 +00:00
return fmt.Sprintf("CREATE TABLE IF NOT EXISTS `%s` (id TEXT NOT NULL PRIMARY KEY, obj TEXT NOT NULL);", t)
}