db.go
2211 bytes
1package main
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "os"
8 "path/filepath"
9 "time"
10
11 _ "modernc.org/sqlite"
12)
13
14type database struct {
15 connection *sql.DB
16}
17
18func openDatabase(path string) (*database, error) {
19 if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
20 return nil, fmt.Errorf("create database directory: %w", err)
21 }
22
23 connection, err := sql.Open("sqlite", path)
24 if err != nil {
25 return nil, fmt.Errorf("open database: %w", err)
26 }
27 connection.SetMaxOpenConns(1)
28
29 database := &database{connection: connection}
30 if err := database.bootstrap(context.Background()); err != nil {
31 _ = connection.Close()
32 return nil, err
33 }
34 return database, nil
35}
36
37func (database *database) bootstrap(ctx context.Context) error {
38 _, err := database.connection.ExecContext(ctx, `
39 CREATE TABLE IF NOT EXISTS builds (
40 build_id TEXT PRIMARY KEY,
41 repository TEXT NOT NULL,
42 sha TEXT NOT NULL,
43 state TEXT NOT NULL,
44 queued_at INTEGER NOT NULL,
45 started_at INTEGER,
46 finished_at INTEGER
47 );
48 CREATE INDEX IF NOT EXISTS builds_repository_queued_at ON builds (repository, queued_at DESC);
49 `)
50 if err != nil {
51 return fmt.Errorf("bootstrap database: %w", err)
52 }
53 return nil
54}
55
56func (database *database) close() error {
57 return database.connection.Close()
58}
59
60func (database *database) queue(job buildJob, queuedAt time.Time) error {
61 _, err := database.connection.Exec(
62 `INSERT INTO builds (build_id, repository, sha, state, queued_at) VALUES (?, ?, ?, ?, ?)`,
63 job.ID,
64 job.Request.Repository,
65 job.Request.SHA,
66 "queued",
67 queuedAt.Unix(),
68 )
69 if err != nil {
70 return fmt.Errorf("save queued build: %w", err)
71 }
72 return nil
73}
74
75func (database *database) update(job buildJob, state string, updatedAt time.Time) error {
76 var query string
77 switch state {
78 case "running":
79 query = `UPDATE builds SET state = ?, started_at = ? WHERE build_id = ?`
80 case "passed", "failed":
81 query = `UPDATE builds SET state = ?, finished_at = ? WHERE build_id = ?`
82 default:
83 return fmt.Errorf("unsupported build state %q", state)
84 }
85
86 if _, err := database.connection.Exec(query, state, updatedAt.Unix(), job.ID); err != nil {
87 return fmt.Errorf("save build state: %w", err)
88 }
89 return nil
90}