Monorepo for Tangled
tangled.org
1package cursor
2
3import (
4 "database/sql"
5 "errors"
6 "fmt"
7 "log/slog"
8
9 _ "github.com/mattn/go-sqlite3"
10)
11
12type SqliteStore struct {
13 db *sql.DB
14 tableName string
15}
16
17type SqliteStoreOpt func(*SqliteStore)
18
19func WithTableName(name string) SqliteStoreOpt {
20 return func(s *SqliteStore) {
21 s.tableName = name
22 }
23}
24
25func NewSQLiteStore(dbPath string, opts ...SqliteStoreOpt) (*SqliteStore, error) {
26 db, err := sql.Open("sqlite3", dbPath+"?_foreign_keys=1")
27 if err != nil {
28 return nil, fmt.Errorf("failed to open sqlite database: %w", err)
29 }
30
31 store := &SqliteStore{
32 db: db,
33 tableName: "cursors",
34 }
35
36 for _, o := range opts {
37 o(store)
38 }
39
40 if err := store.init(); err != nil {
41 return nil, err
42 }
43
44 return store, nil
45}
46
47func (s *SqliteStore) init() error {
48 createTable := fmt.Sprintf(`
49 create table if not exists %s (
50 knot text primary key,
51 cursor integer
52 );`, s.tableName)
53 _, err := s.db.Exec(createTable)
54 return err
55}
56
57func (s *SqliteStore) Set(key string, cursor int64) {
58 query := fmt.Sprintf(`
59 insert into %s (knot, cursor)
60 values (?, ?)
61 on conflict(knot) do update set cursor=excluded.cursor;
62 `, s.tableName)
63
64 if _, err := s.db.Exec(query, key, cursor); err != nil {
65 slog.Default().Error("cursor sqlite set failed", "key", key, "cursor", cursor, "err", err)
66 }
67}
68
69func (s *SqliteStore) Get(key string) (cursor int64) {
70 query := fmt.Sprintf(`
71 select cursor from %s where knot = ?;
72 `, s.tableName)
73 err := s.db.QueryRow(query, key).Scan(&cursor)
74
75 if err != nil {
76 if !errors.Is(err, sql.ErrNoRows) {
77 slog.Default().Error("cursor sqlite get failed", "key", key, "err", err)
78 }
79 return 0
80 }
81
82 return cursor
83}