GCC Code Coverage Report


Directory: src/
File: src/storage_sqlite.c
Date: 2023-08-28 07:33:56
Exec Total Coverage
Lines: 197 303 65.0%
Branches: 54 146 37.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2022 Egor Tensin <Egor.Tensin@gmail.com>
3 * This file is part of the "cimple" project.
4 * For details, see https://github.com/egor-tensin/cimple.
5 * Distributed under the MIT License.
6 */
7
8 #include "storage_sqlite.h"
9 #include "log.h"
10 #include "process.h"
11 #include "run_queue.h"
12 #include "sql/sqlite_sql.h"
13 #include "sqlite.h"
14 #include "storage.h"
15
16 #include <sqlite3.h>
17
18 #include <pthread.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 struct storage_sqlite_settings {
24 char *path;
25 };
26
27 29 int storage_sqlite_settings_create(struct storage_settings *settings, const char *path)
28 {
29 29 struct storage_sqlite_settings *sqlite = malloc(sizeof(struct storage_sqlite_settings));
30
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (!sqlite) {
31 log_errno("malloc");
32 return -1;
33 }
34
35 29 sqlite->path = strdup(path);
36
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (!sqlite->path) {
37 log_errno("strdup");
38 goto free;
39 }
40
41 29 settings->type = STORAGE_TYPE_SQLITE;
42 29 settings->sqlite = sqlite;
43 29 return 0;
44
45 free:
46 free(sqlite);
47
48 return -1;
49 }
50
51 29 void storage_sqlite_settings_destroy(const struct storage_settings *settings)
52 {
53 29 free(settings->sqlite->path);
54 29 free(settings->sqlite);
55 29 }
56
57 enum run_status {
58 RUN_STATUS_CREATED = 1,
59 RUN_STATUS_FINISHED = 2,
60 };
61
62 struct prepared_stmt {
63 pthread_mutex_t mtx;
64 sqlite3_stmt *impl;
65 };
66
67 116 static int prepared_stmt_init(struct prepared_stmt *stmt, sqlite3 *db, const char *sql)
68 {
69 116 int ret = 0;
70
71 116 ret = pthread_mutex_init(&stmt->mtx, NULL);
72
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 116 times.
116 if (ret) {
73 pthread_errno(ret, "pthread_mutex_init");
74 return ret;
75 }
76
77 116 ret = sqlite_prepare(db, sql, &stmt->impl);
78
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 116 times.
116 if (ret < 0)
79 goto destroy_mtx;
80
81 116 return ret;
82
83 destroy_mtx:
84 pthread_errno_if(pthread_mutex_destroy(&stmt->mtx), "pthread_mutex_destroy");
85
86 return ret;
87 }
88
89 116 static void prepared_stmt_destroy(struct prepared_stmt *stmt)
90 {
91
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 116 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
116 pthread_errno_if(pthread_mutex_destroy(&stmt->mtx), "pthread_mutex_destroy");
92 116 sqlite_finalize(stmt->impl);
93 116 }
94
95 36720 static int prepared_stmt_lock(struct prepared_stmt *stmt)
96 {
97 36720 int ret = pthread_mutex_lock(&stmt->mtx);
98
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36720 times.
36720 if (ret) {
99 pthread_errno(ret, "pthread_mutex_unlock");
100 return ret;
101 }
102 36720 return ret;
103 }
104
105 36720 static void prepared_stmt_unlock(struct prepared_stmt *stmt)
106 {
107
1/4
✗ Branch 1 not taken.
✓ Branch 2 taken 36720 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
36720 pthread_errno_if(pthread_mutex_unlock(&stmt->mtx), "pthread_mutex_unlock");
108 36720 }
109
110 struct storage_sqlite {
111 sqlite3 *db;
112
113 struct prepared_stmt stmt_repo_find;
114 struct prepared_stmt stmt_repo_insert;
115 struct prepared_stmt stmt_run_insert;
116 struct prepared_stmt stmt_run_finished;
117 };
118
119 29 static int storage_sqlite_upgrade_to(struct storage_sqlite *storage, size_t version)
120 {
121 static const char *const fmt = "%s PRAGMA user_version = %zu;";
122
123 29 const char *script = sqlite_schemas[version];
124 29 int ret = 0;
125
126 29 ret = snprintf(NULL, 0, fmt, script, version + 1);
127 29 size_t nb = (size_t)ret + 1;
128 29 ret = 0;
129
130 29 char *full_script = malloc(nb);
131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (!full_script) {
132 log_errno("malloc");
133 return -1;
134 }
135 29 snprintf(full_script, nb, fmt, script, version + 1);
136
137 29 ret = sqlite_exec_as_transaction(storage->db, full_script);
138 29 goto free;
139
140 29 free:
141 29 free(full_script);
142
143 29 return ret;
144 }
145
146 29 static int storage_sqlite_upgrade_from_to(struct storage_sqlite *storage, size_t from, size_t to)
147 {
148 29 int ret = 0;
149
150
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 29 times.
58 for (size_t i = from; i < to; ++i) {
151
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
29 log("Upgrading SQLite database from version %zu to version %zu\n", i, i + 1);
152 29 ret = storage_sqlite_upgrade_to(storage, i);
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0) {
154 log_err("Failed to upgrade to version %zu\n", i + 1);
155 return ret;
156 }
157 }
158
159 29 return ret;
160 }
161
162 29 static int storage_sqlite_upgrade(struct storage_sqlite *storage)
163 {
164 29 unsigned int current_version = 0;
165 29 int ret = 0;
166
167 29 ret = sqlite_get_user_version(storage->db, &current_version);
168
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
169 return ret;
170
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
29 log("SQLite database version: %u\n", current_version);
171
172 29 size_t newest_version = sizeof(sqlite_schemas) / sizeof(sqlite_schemas[0]);
173
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
29 log("Newest database version: %zu\n", newest_version);
174
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (current_version > newest_version) {
176 log_err("Unknown database version: %u\n", current_version);
177 return -1;
178 }
179
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (current_version == newest_version) {
181 log("SQLite database already at the newest version\n");
182 return 0;
183 }
184
185 29 return storage_sqlite_upgrade_from_to(storage, current_version, newest_version);
186 }
187
188 29 static int storage_sqlite_setup(struct storage_sqlite *storage)
189 {
190 29 int ret = 0;
191
192 29 ret = sqlite_set_foreign_keys(storage->db);
193
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
194 return ret;
195
196 29 ret = storage_sqlite_upgrade(storage);
197
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
198 return ret;
199
200 29 return ret;
201 }
202
203 29 static int storage_sqlite_prepare_statements(struct storage_sqlite *storage)
204 {
205 static const char *const fmt_repo_find = "SELECT id FROM cimple_repos WHERE url = ?;";
206 static const char *const fmt_repo_insert =
207 "INSERT INTO cimple_repos(url) VALUES (?) ON CONFLICT(url) DO NOTHING;";
208 static const char *const fmt_run_insert =
209 "INSERT INTO cimple_runs(status, exit_code, output, repo_id, repo_rev) VALUES (?, -1, x'', ?, ?) RETURNING id;";
210 static const char *const fmt_run_finished =
211 "UPDATE cimple_runs SET status = ?, exit_code = ?, output = ? WHERE id = ?;";
212
213 29 int ret = 0;
214
215 29 ret = prepared_stmt_init(&storage->stmt_repo_find, storage->db, fmt_repo_find);
216
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
217 return ret;
218 29 ret = prepared_stmt_init(&storage->stmt_repo_insert, storage->db, fmt_repo_insert);
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
220 goto finalize_repo_find;
221 29 ret = prepared_stmt_init(&storage->stmt_run_insert, storage->db, fmt_run_insert);
222
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
223 goto finalize_repo_insert;
224 29 ret = prepared_stmt_init(&storage->stmt_run_finished, storage->db, fmt_run_finished);
225
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
226 goto finalize_run_insert;
227
228 29 return ret;
229
230 finalize_run_insert:
231 prepared_stmt_destroy(&storage->stmt_run_insert);
232 finalize_repo_insert:
233 prepared_stmt_destroy(&storage->stmt_repo_insert);
234 finalize_repo_find:
235 prepared_stmt_destroy(&storage->stmt_repo_find);
236
237 return ret;
238 }
239
240 29 static void storage_sqlite_finalize_statements(struct storage_sqlite *storage)
241 {
242 29 prepared_stmt_destroy(&storage->stmt_run_finished);
243 29 prepared_stmt_destroy(&storage->stmt_run_insert);
244 29 prepared_stmt_destroy(&storage->stmt_repo_insert);
245 29 prepared_stmt_destroy(&storage->stmt_repo_find);
246 29 }
247
248 29 int storage_sqlite_create(struct storage *storage, const struct storage_settings *settings)
249 {
250 29 int ret = 0;
251
252
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
29 log("Using SQLite database at %s\n", settings->sqlite->path);
253
254 29 struct storage_sqlite *sqlite = malloc(sizeof(struct storage_sqlite));
255
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (!sqlite) {
256 log_errno("malloc");
257 return -1;
258 }
259
260 29 ret = sqlite_init();
261
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
262 goto free;
263 29 ret = sqlite_open_rw(settings->sqlite->path, &sqlite->db);
264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
265 goto destroy;
266 29 ret = storage_sqlite_setup(sqlite);
267
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
268 goto close;
269 29 ret = storage_sqlite_prepare_statements(sqlite);
270
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
271 goto close;
272
273 29 storage->sqlite = sqlite;
274 29 return ret;
275
276 close:
277 sqlite_close(storage->sqlite->db);
278 destroy:
279 sqlite_destroy();
280 free:
281 free(sqlite);
282
283 return ret;
284 }
285
286 29 void storage_sqlite_destroy(struct storage *storage)
287 {
288 29 storage_sqlite_finalize_statements(storage->sqlite);
289 29 sqlite_close(storage->sqlite->db);
290 29 sqlite_destroy();
291 29 free(storage->sqlite);
292 29 }
293
294 9180 static int storage_sqlite_find_repo(struct storage_sqlite *storage, const char *url)
295 {
296 9180 struct prepared_stmt *stmt = &storage->stmt_repo_find;
297 9180 int ret = 0;
298
299 9180 ret = prepared_stmt_lock(stmt);
300
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
301 return ret;
302 9180 ret = sqlite_bind_text(stmt->impl, 1, url);
303
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
304 goto reset;
305 9180 ret = sqlite_step(stmt->impl);
306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
307 goto reset;
308
309
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (!ret)
310 goto reset;
311
312 9180 ret = sqlite_column_int(stmt->impl, 0);
313 9180 goto reset;
314
315 9180 reset:
316 9180 sqlite_reset(stmt->impl);
317 9180 prepared_stmt_unlock(stmt);
318
319 9180 return ret;
320 }
321
322 9180 static int storage_sqlite_insert_repo(struct storage_sqlite *storage, const char *url)
323 {
324 9180 struct prepared_stmt *stmt = &storage->stmt_repo_insert;
325 9180 int ret = 0;
326
327 9180 ret = prepared_stmt_lock(stmt);
328
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
329 return ret;
330 9180 ret = sqlite_bind_text(stmt->impl, 1, url);
331
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
332 goto reset;
333 9180 ret = sqlite_step(stmt->impl);
334
1/2
✓ Branch 0 taken 9180 times.
✗ Branch 1 not taken.
9180 if (ret < 0)
335 goto reset;
336
337 9180 reset:
338 9180 sqlite_reset(stmt->impl);
339 9180 prepared_stmt_unlock(stmt);
340
341
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
342 return ret;
343
344 9180 return storage_sqlite_find_repo(storage, url);
345 }
346
347 9180 static int storage_sqlite_insert_run(struct storage_sqlite *storage, int repo_id, const char *rev)
348 {
349 9180 struct prepared_stmt *stmt = &storage->stmt_run_insert;
350 9180 int ret = 0;
351
352 9180 ret = prepared_stmt_lock(stmt);
353
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
354 return ret;
355 9180 ret = sqlite_bind_int(stmt->impl, 1, RUN_STATUS_CREATED);
356
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
357 goto reset;
358 9180 ret = sqlite_bind_int(stmt->impl, 2, repo_id);
359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
360 goto reset;
361 9180 ret = sqlite_bind_text(stmt->impl, 3, rev);
362
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
363 goto reset;
364 9180 ret = sqlite_step(stmt->impl);
365
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
366 goto reset;
367
368
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (!ret) {
369 ret = -1;
370 log_err("Failed to insert a run\n");
371 goto reset;
372 }
373
374 9180 ret = sqlite_column_int(stmt->impl, 0);
375 9180 goto reset;
376
377 9180 reset:
378 9180 sqlite_reset(stmt->impl);
379 9180 prepared_stmt_unlock(stmt);
380
381 9180 return ret;
382 }
383
384 9180 int storage_sqlite_run_create(struct storage *storage, const char *repo_url, const char *rev)
385 {
386 9180 int ret = 0;
387
388 9180 ret = storage_sqlite_insert_repo(storage->sqlite, repo_url);
389
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
390 return ret;
391
392 9180 ret = storage_sqlite_insert_run(storage->sqlite, ret, rev);
393
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
394 return ret;
395
396 9180 return ret;
397 }
398
399 9180 int storage_sqlite_run_finished(struct storage *storage, int run_id,
400 const struct proc_output *output)
401 {
402 9180 struct prepared_stmt *stmt = &storage->sqlite->stmt_run_finished;
403 9180 int ret = 0;
404
405 9180 ret = prepared_stmt_lock(stmt);
406
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
407 return ret;
408 9180 ret = sqlite_bind_int(stmt->impl, 1, RUN_STATUS_FINISHED);
409
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
410 goto reset;
411 9180 ret = sqlite_bind_int(stmt->impl, 2, output->ec);
412
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
413 goto reset;
414 9180 ret = sqlite_bind_blob(stmt->impl, 3, output->data, output->data_size);
415
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
416 goto reset;
417 9180 ret = sqlite_bind_int(stmt->impl, 4, run_id);
418
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9180 times.
9180 if (ret < 0)
419 goto reset;
420 9180 ret = sqlite_step(stmt->impl);
421
1/2
✓ Branch 0 taken 9180 times.
✗ Branch 1 not taken.
9180 if (ret < 0)
422 goto reset;
423
424 9180 reset:
425 9180 sqlite_reset(stmt->impl);
426 9180 prepared_stmt_unlock(stmt);
427
428 9180 return ret;
429 }
430
431 static int storage_sqlite_row_to_run(struct sqlite3_stmt *stmt, struct run **run)
432 {
433 int ret = 0;
434
435 int id = sqlite_column_int(stmt, 0);
436
437 char *url = NULL;
438 ret = sqlite_column_text(stmt, 1, &url);
439 if (ret < 0)
440 return ret;
441
442 char *rev = NULL;
443 ret = sqlite_column_text(stmt, 2, &rev);
444 if (ret < 0)
445 goto free_url;
446
447 ret = run_create(run, id, url, rev);
448 if (ret < 0)
449 goto free_rev;
450
451 log("Adding a run %d for repository %s to the queue\n", id, url);
452
453 free_rev:
454 free(rev);
455
456 free_url:
457 free(url);
458
459 return ret;
460 }
461
462 29 int storage_sqlite_get_run_queue(struct storage *storage, struct run_queue *queue)
463 {
464 static const char *const fmt =
465 "SELECT id, repo_url, repo_rev FROM cimple_runs_view WHERE status = ?;";
466
467 sqlite3_stmt *stmt;
468 29 int ret = 0;
469
470 29 ret = sqlite_prepare(storage->sqlite->db, fmt, &stmt);
471
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
472 return ret;
473 29 ret = sqlite_bind_int(stmt, 1, RUN_STATUS_CREATED);
474
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
29 if (ret < 0)
475 goto finalize;
476
477 29 run_queue_create(queue);
478
479 while (1) {
480 29 ret = sqlite_step(stmt);
481
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 if (!ret)
482 29 break;
483 if (ret < 0)
484 goto run_queue_destroy;
485
486 struct run *run = NULL;
487
488 ret = storage_sqlite_row_to_run(stmt, &run);
489 if (ret < 0)
490 goto run_queue_destroy;
491
492 run_queue_add_last(queue, run);
493 }
494
495 29 goto finalize;
496
497 run_queue_destroy:
498 run_queue_destroy(queue);
499
500 29 finalize:
501 29 sqlite_finalize(stmt);
502
503 29 return ret;
504 }
505