larc r22

323 lines ยท 8.4 KB Raw
1 package storage
2
3 import (
4 "database/sql"
5 "errors"
6 "fmt"
7 "time"
8
9 _ "github.com/mattn/go-sqlite3"
10
11 "larc.wejust.rest/larc/internal/core"
12 )
13
14 /* SQLite storage for larc metadata.
15 * Handles revisions, branches, trees, and state.
16 * All operations use single connection with WAL mode for speed. */
17
18 var (
19 ErrRevisionNotFound = errors.New("revision not found")
20 ErrBranchNotFound = errors.New("branch not found")
21 ErrTreeNotFound = errors.New("tree not found")
22 )
23
24 const schema = `
25 CREATE TABLE IF NOT EXISTS revisions (
26 number INTEGER PRIMARY KEY,
27 timestamp INTEGER NOT NULL,
28 author TEXT NOT NULL,
29 message TEXT NOT NULL,
30 branch TEXT NOT NULL,
31 parent INTEGER,
32 merge_parent INTEGER,
33 tree_hash TEXT NOT NULL
34 );
35
36 CREATE TABLE IF NOT EXISTS branches (
37 name TEXT PRIMARY KEY,
38 head_rev INTEGER NOT NULL,
39 created_at INTEGER NOT NULL,
40 created_from INTEGER
41 );
42
43 CREATE TABLE IF NOT EXISTS trees (
44 hash TEXT PRIMARY KEY,
45 data BLOB NOT NULL
46 );
47
48 CREATE TABLE IF NOT EXISTS state (
49 key TEXT PRIMARY KEY,
50 value TEXT NOT NULL
51 );
52
53 CREATE INDEX IF NOT EXISTS idx_revisions_branch ON revisions(branch, number DESC);
54 CREATE INDEX IF NOT EXISTS idx_revisions_timestamp ON revisions(timestamp);
55 `
56
57 // MetaStore manages SQLite metadata storage
58 type MetaStore struct {
59 db *sql.DB
60 }
61
62 // NewMetaStore opens or creates a metadata store
63 func NewMetaStore(path string) (*MetaStore, error) {
64 db, err := sql.Open("sqlite3", path+"?_journal_mode=WAL&_synchronous=NORMAL&_busy_timeout=5000")
65 if err != nil {
66 return nil, fmt.Errorf("open sqlite: %w", err)
67 }
68
69 /* single connection for consistency */
70 db.SetMaxOpenConns(1)
71
72 if _, err := db.Exec(schema); err != nil {
73 db.Close()
74 return nil, fmt.Errorf("init schema: %w", err)
75 }
76
77 return &MetaStore{db: db}, nil
78 }
79
80 // Close closes the database connection
81 func (m *MetaStore) Close() error {
82 return m.db.Close()
83 }
84
85 // NextRevision returns the next revision number and increments counter
86 func (m *MetaStore) NextRevision() (int64, error) {
87 tx, err := m.db.Begin()
88 if err != nil {
89 return 0, fmt.Errorf("begin tx: %w", err)
90 }
91 defer tx.Rollback()
92
93 var next int64
94 row := tx.QueryRow("SELECT COALESCE(MAX(number), 0) + 1 FROM revisions")
95 if err := row.Scan(&next); err != nil {
96 return 0, fmt.Errorf("get max revision: %w", err)
97 }
98
99 if err := tx.Commit(); err != nil {
100 return 0, fmt.Errorf("commit: %w", err)
101 }
102
103 return next, nil
104 }
105
106 // CreateRevision creates a new revision
107 func (m *MetaStore) CreateRevision(rev *core.Revision) error {
108 _, err := m.db.Exec(`
109 INSERT INTO revisions (number, timestamp, author, message, branch, parent, merge_parent, tree_hash)
110 VALUES (?, ?, ?, ?, ?, ?, ?, ?)
111 `, rev.Number, rev.Timestamp, rev.Author, rev.Message, rev.Branch, rev.Parent, rev.MergeParent, rev.TreeHash)
112
113 if err != nil {
114 return fmt.Errorf("insert revision: %w", err)
115 }
116
117 return nil
118 }
119
120 // GetRevision retrieves a revision by number
121 func (m *MetaStore) GetRevision(number int64) (*core.Revision, error) {
122 rev := &core.Revision{}
123 err := m.db.QueryRow(`
124 SELECT number, timestamp, author, message, branch, parent, merge_parent, tree_hash
125 FROM revisions WHERE number = ?
126 `, number).Scan(&rev.Number, &rev.Timestamp, &rev.Author, &rev.Message, &rev.Branch, &rev.Parent, &rev.MergeParent, &rev.TreeHash)
127
128 if err != nil {
129 if errors.Is(err, sql.ErrNoRows) {
130 return nil, ErrRevisionNotFound
131 }
132 return nil, fmt.Errorf("get revision: %w", err)
133 }
134
135 return rev, nil
136 }
137
138 // GetLatestRevision returns the most recent revision number
139 func (m *MetaStore) GetLatestRevision() (int64, error) {
140 var latest int64
141 err := m.db.QueryRow("SELECT COALESCE(MAX(number), 0) FROM revisions").Scan(&latest)
142 if err != nil {
143 return 0, fmt.Errorf("get latest revision: %w", err)
144 }
145 return latest, nil
146 }
147
148 // ListRevisions returns revisions in descending order
149 func (m *MetaStore) ListRevisions(branch string, limit, offset int) ([]*core.Revision, error) {
150 var rows *sql.Rows
151 var err error
152
153 if branch == "" {
154 rows, err = m.db.Query(`
155 SELECT number, timestamp, author, message, branch, parent, merge_parent, tree_hash
156 FROM revisions ORDER BY number DESC LIMIT ? OFFSET ?
157 `, limit, offset)
158 } else {
159 rows, err = m.db.Query(`
160 SELECT number, timestamp, author, message, branch, parent, merge_parent, tree_hash
161 FROM revisions WHERE branch = ? ORDER BY number DESC LIMIT ? OFFSET ?
162 `, branch, limit, offset)
163 }
164
165 if err != nil {
166 return nil, fmt.Errorf("list revisions: %w", err)
167 }
168 defer rows.Close()
169
170 var revs []*core.Revision
171 for rows.Next() {
172 rev := &core.Revision{}
173 if err := rows.Scan(&rev.Number, &rev.Timestamp, &rev.Author, &rev.Message, &rev.Branch, &rev.Parent, &rev.MergeParent, &rev.TreeHash); err != nil {
174 return nil, fmt.Errorf("scan revision: %w", err)
175 }
176 revs = append(revs, rev)
177 }
178
179 return revs, nil
180 }
181
182 // CreateBranch creates a new branch
183 func (m *MetaStore) CreateBranch(branch *core.Branch) error {
184 _, err := m.db.Exec(`
185 INSERT INTO branches (name, head_rev, created_at, created_from)
186 VALUES (?, ?, ?, ?)
187 `, branch.Name, branch.HeadRev, branch.CreatedAt, branch.CreatedFrom)
188
189 if err != nil {
190 return fmt.Errorf("insert branch: %w", err)
191 }
192
193 return nil
194 }
195
196 // GetBranch retrieves a branch by name
197 func (m *MetaStore) GetBranch(name string) (*core.Branch, error) {
198 branch := &core.Branch{}
199 err := m.db.QueryRow(`
200 SELECT name, head_rev, created_at, created_from
201 FROM branches WHERE name = ?
202 `, name).Scan(&branch.Name, &branch.HeadRev, &branch.CreatedAt, &branch.CreatedFrom)
203
204 if err != nil {
205 if errors.Is(err, sql.ErrNoRows) {
206 return nil, ErrBranchNotFound
207 }
208 return nil, fmt.Errorf("get branch: %w", err)
209 }
210
211 return branch, nil
212 }
213
214 // UpdateBranchHead updates the head revision of a branch
215 func (m *MetaStore) UpdateBranchHead(name string, headRev int64) error {
216 result, err := m.db.Exec("UPDATE branches SET head_rev = ? WHERE name = ?", headRev, name)
217 if err != nil {
218 return fmt.Errorf("update branch head: %w", err)
219 }
220
221 rows, _ := result.RowsAffected()
222 if rows == 0 {
223 return ErrBranchNotFound
224 }
225
226 return nil
227 }
228
229 // ListBranches returns all branches
230 func (m *MetaStore) ListBranches() ([]*core.Branch, error) {
231 rows, err := m.db.Query("SELECT name, head_rev, created_at, created_from FROM branches ORDER BY name")
232 if err != nil {
233 return nil, fmt.Errorf("list branches: %w", err)
234 }
235 defer rows.Close()
236
237 var branches []*core.Branch
238 for rows.Next() {
239 branch := &core.Branch{}
240 if err := rows.Scan(&branch.Name, &branch.HeadRev, &branch.CreatedAt, &branch.CreatedFrom); err != nil {
241 return nil, fmt.Errorf("scan branch: %w", err)
242 }
243 branches = append(branches, branch)
244 }
245
246 return branches, nil
247 }
248
249 // DeleteBranch removes a branch
250 func (m *MetaStore) DeleteBranch(name string) error {
251 result, err := m.db.Exec("DELETE FROM branches WHERE name = ?", name)
252 if err != nil {
253 return fmt.Errorf("delete branch: %w", err)
254 }
255
256 rows, _ := result.RowsAffected()
257 if rows == 0 {
258 return ErrBranchNotFound
259 }
260
261 return nil
262 }
263
264 // StoreTree stores a tree manifest
265 func (m *MetaStore) StoreTree(hash string, data []byte) error {
266 _, err := m.db.Exec("INSERT OR IGNORE INTO trees (hash, data) VALUES (?, ?)", hash, data)
267 if err != nil {
268 return fmt.Errorf("store tree: %w", err)
269 }
270 return nil
271 }
272
273 // GetTree retrieves a tree manifest
274 func (m *MetaStore) GetTree(hash string) ([]byte, error) {
275 var data []byte
276 err := m.db.QueryRow("SELECT data FROM trees WHERE hash = ?", hash).Scan(&data)
277 if err != nil {
278 if errors.Is(err, sql.ErrNoRows) {
279 return nil, ErrTreeNotFound
280 }
281 return nil, fmt.Errorf("get tree: %w", err)
282 }
283 return data, nil
284 }
285
286 // SetState sets a key-value pair in state table
287 func (m *MetaStore) SetState(key, value string) error {
288 _, err := m.db.Exec("INSERT OR REPLACE INTO state (key, value) VALUES (?, ?)", key, value)
289 if err != nil {
290 return fmt.Errorf("set state: %w", err)
291 }
292 return nil
293 }
294
295 // GetState gets a value from state table
296 func (m *MetaStore) GetState(key string) (string, error) {
297 var value string
298 err := m.db.QueryRow("SELECT value FROM state WHERE key = ?", key).Scan(&value)
299 if err != nil {
300 if errors.Is(err, sql.ErrNoRows) {
301 return "", nil
302 }
303 return "", fmt.Errorf("get state: %w", err)
304 }
305 return value, nil
306 }
307
308 // InitRepo initializes a new repository with default branch
309 func (m *MetaStore) InitRepo() error {
310 branch := &core.Branch{
311 Name: core.DefaultBranchName,
312 HeadRev: 0,
313 CreatedAt: time.Now().Unix(),
314 CreatedFrom: 0,
315 }
316
317 if err := m.CreateBranch(branch); err != nil {
318 return fmt.Errorf("create default branch: %w", err)
319 }
320
321 return nil
322 }
323