-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathsqlite.go
257 lines (238 loc) · 7.42 KB
/
sqlite.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package git
import (
"fmt"
"log/slog"
"github.com/jmoiron/sqlx"
_ "modernc.org/sqlite"
)
var sqliteSchema = `
CREATE TABLE IF NOT EXISTS app_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pubkey TEXT NOT NULL UNIQUE,
name TEXT NOT NULL UNIQUE,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS repos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, name),
CONSTRAINT repo_user_id_fk
FOREIGN KEY(user_id) REFERENCES app_users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS acl (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pubkey string,
ip_address string,
permission string NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS patch_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
repo_id TEXT NOT NULL,
name TEXT NOT NULL,
text TEXT NOT NULL,
status TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL,
CONSTRAINT pr_user_id_fk
FOREIGN KEY(user_id) REFERENCES app_users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS patchsets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
patch_request_id INTEGER NOT NULL,
review BOOLEAN NOT NULL DEFAULT false,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT patchset_user_id_fk
FOREIGN KEY(user_id) REFERENCES app_users(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT patchset_patch_request_id_fk
FOREIGN KEY(patch_request_id) REFERENCES patch_requests(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS patches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
patchset_id INTEGER NOT NULL,
author_name TEXT NOT NULL,
author_email TEXT NOT NULL,
author_date DATETIME NOT NULL,
title TEXT NOT NULL,
body TEXT NOT NULL,
body_appendix TEXT NOT NULL,
commit_sha TEXT NOT NULL,
content_sha TEXT NOT NULL,
raw_text TEXT NOT NULL,
base_commit_sha TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT patches_user_id_fk
FOREIGN KEY(user_id) REFERENCES app_users(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT patches_patchset_id_fk
FOREIGN KEY(patchset_id) REFERENCES patchsets(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS event_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
repo_id TEXT,
patch_request_id INTEGER,
patchset_id INTEGER,
event TEXT NOT NULL,
data TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT event_logs_pr_id_fk
FOREIGN KEY(patch_request_id) REFERENCES patch_requests(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT event_logs_patchset_id_fk
FOREIGN KEY(patchset_id) REFERENCES patchsets(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT event_logs_user_id_fk
FOREIGN KEY(user_id) REFERENCES app_users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
`
var sqliteMigrations = []string{
"", // migration #0 is reserved for schema initialization
"ALTER TABLE patches ADD COLUMN base_commit_sha TEXT",
// added this by accident
"",
// create repos table
`CREATE TABLE IF NOT EXISTS repos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, name),
CONSTRAINT repo_user_id_fk
FOREIGN KEY(user_id) REFERENCES app_users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);`,
// migrate existing repo info from patch_requests
`INSERT INTO repos (user_id, name) SELECT user_id, repo_id from patch_requests group by repo_id;`,
// convert patch_requests.repo_id to integer with FK constraint
`CREATE TABLE IF NOT EXISTS tmp_patch_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
repo_id INTEGER NOT NULL,
name TEXT NOT NULL,
text TEXT NOT NULL,
status TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL,
CONSTRAINT pr_user_id_fk
FOREIGN KEY(user_id) REFERENCES app_users(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT pr_repo_id_fk
FOREIGN KEY(repo_id) REFERENCES repos(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO tmp_patch_requests (user_id, repo_id, name, text, status, created_at, updated_at)
SELECT pr.user_id, repos.id, pr.name, pr.text, pr.status, pr.created_at, pr.updated_at
FROM patch_requests AS pr
INNER JOIN repos ON repos.name = pr.repo_id;
DROP TABLE patch_requests;
ALTER TABLE tmp_patch_requests RENAME TO patch_requests;`,
// convert event_logs.repo_id to integer with FK constraint
`CREATE TABLE IF NOT EXISTS tmp_event_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
repo_id INTEGER,
patch_request_id INTEGER,
patchset_id INTEGER,
event TEXT NOT NULL,
data TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT event_logs_pr_id_fk
FOREIGN KEY(patch_request_id) REFERENCES patch_requests(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT event_logs_patchset_id_fk
FOREIGN KEY(patchset_id) REFERENCES patchsets(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT event_logs_user_id_fk
FOREIGN KEY(user_id) REFERENCES app_users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
CONSTRAINT event_logs_repo_id_fk
FOREIGN KEY(repo_id) REFERENCES repos(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
INSERT INTO tmp_event_logs (user_id, repo_id, patch_request_id, patchset_id, event, data, created_at)
SELECT ev.user_id, repos.id, ev.patch_request_id, ev.patchset_id, ev.event, ev.data, ev.created_at
FROM event_logs AS ev
LEFT JOIN repos ON repos.name = ev.repo_id;
DROP TABLE event_logs;
ALTER TABLE tmp_event_logs RENAME TO event_logs;`,
}
// Open opens a database connection.
func SqliteOpen(dsn string, logger *slog.Logger) (*sqlx.DB, error) {
logger.Info("opening db file", "dsn", dsn)
db, err := sqlx.Connect("sqlite", dsn)
if err != nil {
return nil, err
}
err = sqliteUpgrade(db)
if err != nil {
db.Close()
return nil, err
}
return db, nil
}
func sqliteUpgrade(db *sqlx.DB) error {
var version int
if err := db.QueryRow("PRAGMA user_version").Scan(&version); err != nil {
return fmt.Errorf("failed to query schema version: %v", err)
}
if version == len(sqliteMigrations) {
return nil
} else if version > len(sqliteMigrations) {
return fmt.Errorf("git-pr (version %d) older than schema (version %d)", len(sqliteMigrations), version)
}
tx, err := db.Beginx()
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
if version == 0 {
if _, err := tx.Exec(sqliteSchema); err != nil {
return fmt.Errorf("failed to initialize schema: %v", err)
}
} else {
for i := version; i < len(sqliteMigrations); i++ {
if _, err := tx.Exec(sqliteMigrations[i]); err != nil {
return fmt.Errorf("failed to execute migration #%v: %v", i, err)
}
}
}
// For some reason prepared statements don't work here
_, err = tx.Exec(fmt.Sprintf("PRAGMA user_version = %d", len(sqliteMigrations)))
if err != nil {
return fmt.Errorf("failed to bump schema version: %v", err)
}
return tx.Commit()
}