GCC Code Coverage Report


Directory: src/
File: src/storage_sqlite.c
Date: 2024-04-25 03:45:42
Exec Total Coverage
Lines: 245 338 72.5%
Branches: 66 156 42.3%

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