• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dangernoodle-io / mcpkit / 29180589992

12 Jul 2026 05:02AM UTC coverage: 95.512% (-0.05%) from 95.557%
29180589992

Pull #21

github

web-flow
Merge 5d3d64250 into f87bdc19a
Pull Request #21: feat(store): sqlstore adapter over injected *sql.DB (MC-38)

50 of 54 new or added lines in 1 file covered. (92.59%)

1277 of 1337 relevant lines covered (95.51%)

1.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.59
/store/sqlstore/sqlstore.go
1
// Package sqlstore provides a store.Store backed by a SQL key/value table
2
// via the stdlib database/sql interface. It takes a caller-provided *sql.DB
3
// and never opens or owns a connection — so mcpkit takes no SQL-driver
4
// dependency; the consumer brings its own driver and owns lifecycle/pragmas.
5
// It targets SQLite-compatible dialects (`?` placeholders, INSERT OR
6
// REPLACE); tested with modernc.org/sqlite. The adapter does NOT create or
7
// migrate the table — the consumer owns the schema (key TEXT PRIMARY KEY,
8
// value TEXT NOT NULL).
9
package sqlstore
10

11
import (
12
        "context"
13
        "database/sql"
14
        "errors"
15
        "fmt"
16
        "regexp"
17

18
        "github.com/dangernoodle-io/mcpkit/store"
19
)
20

21
// validTableName matches a valid, unquoted SQL identifier: it must start
22
// with a letter or underscore, followed by any number of letters, digits,
23
// or underscores.
24
var validTableName = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
25

26
// New returns a store.Store reading/writing the given key/value table on
27
// db. table must be a valid SQL identifier ([A-Za-z_][A-Za-z0-9_]*); New
28
// returns an error for a nil db or an invalid/empty table name (the name is
29
// interpolated into SQL, not parameterizable, so it must be validated). New
30
// does not touch the database.
31
func New(db *sql.DB, table string) (store.Store, error) {
1✔
32
        if db == nil {
2✔
33
                return nil, errors.New("sqlstore: db must not be nil")
1✔
34
        }
1✔
35
        if !validTableName.MatchString(table) {
2✔
36
                return nil, fmt.Errorf("sqlstore: invalid table name %q", table)
1✔
37
        }
1✔
38

39
        return &sqlStore{db: db, table: table}, nil
1✔
40
}
41

42
type sqlStore struct {
43
        db    *sql.DB
44
        table string
45
}
46

47
var _ store.Store = (*sqlStore)(nil)
48

49
// Get returns the value for key, and whether it was found.
50
func (s *sqlStore) Get(ctx context.Context, key string) (string, bool, error) {
1✔
51
        query := fmt.Sprintf("SELECT value FROM %s WHERE key = ?", s.table)
1✔
52

1✔
53
        var value string
1✔
54
        if err := s.db.QueryRowContext(ctx, query, key).Scan(&value); err != nil {
2✔
55
                if errors.Is(err, sql.ErrNoRows) {
2✔
56
                        return "", false, nil
1✔
57
                }
1✔
58

59
                return "", false, err
1✔
60
        }
61

62
        return value, true, nil
1✔
63
}
64

65
// Load returns every key/value pair currently held in the table.
66
func (s *sqlStore) Load(ctx context.Context) (map[string]string, error) {
1✔
67
        query := fmt.Sprintf("SELECT key, value FROM %s", s.table)
1✔
68

1✔
69
        rows, err := s.db.QueryContext(ctx, query)
1✔
70
        if err != nil {
2✔
71
                return nil, err
1✔
72
        }
1✔
73
        defer func() { _ = rows.Close() }()
2✔
74

75
        out := make(map[string]string)
1✔
76
        for rows.Next() {
2✔
77
                var key, value string
1✔
78
                if err := rows.Scan(&key, &value); err != nil {
1✔
NEW
79
                        return nil, err
×
NEW
80
                }
×
81

82
                out[key] = value
1✔
83
        }
84

85
        if err := rows.Err(); err != nil {
1✔
NEW
86
                return nil, err
×
NEW
87
        }
×
88

89
        return out, nil
1✔
90
}
91

92
// Set writes value for key immediately (write-through): INSERT OR REPLACE
93
// so an existing key is overwritten.
94
func (s *sqlStore) Set(ctx context.Context, key, value string) error {
1✔
95
        query := fmt.Sprintf("INSERT OR REPLACE INTO %s (key, value) VALUES (?, ?)", s.table)
1✔
96

1✔
97
        _, err := s.db.ExecContext(ctx, query, key, value)
1✔
98

1✔
99
        return err
1✔
100
}
1✔
101

102
// Save is a no-op: Set is write-through (every write already hits the
103
// database immediately), so there is nothing buffered to flush.
104
func (s *sqlStore) Save(_ context.Context) error {
1✔
105
        return nil
1✔
106
}
1✔
107

108
// Delete removes key. Deleting an absent key is not an error.
109
func (s *sqlStore) Delete(ctx context.Context, key string) error {
1✔
110
        query := fmt.Sprintf("DELETE FROM %s WHERE key = ?", s.table)
1✔
111

1✔
112
        _, err := s.db.ExecContext(ctx, query, key)
1✔
113

1✔
114
        return err
1✔
115
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc