comparison scid/db.c @ 27:dae2de19ca5d

misc: switch to JSON everywhere
author David Demelier <markand@malikania.fr>
date Wed, 03 Aug 2022 15:18:09 +0200
parents lib/db.c@7e10cace67a3
children 081e1c258e64
comparison
equal deleted inserted replaced
26:7e10cace67a3 27:dae2de19ca5d
1 /*
2 * db.c -- scid database access
3 *
4 * Copyright (c) 2021 David Demelier <markand@malikania.fr>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <assert.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include <sqlite3.h>
24
25 #include <utlist.h>
26
27 #include "db.h"
28 #include "log.h"
29 #include "util.h"
30
31 #include "sql/init.h"
32 #include "sql/job-add.h"
33 #include "sql/job-list.h"
34 #include "sql/job-todo.h"
35 #include "sql/jobresult-add.h"
36 #include "sql/jobresult-list-by-job.h"
37 #include "sql/jobresult-list-by-job-group.h"
38 #include "sql/jobresult-list-by-worker.h"
39 #include "sql/project-find.h"
40 #include "sql/project-list.h"
41 #include "sql/project-save.h"
42 #include "sql/worker-find.h"
43 #include "sql/worker-list.h"
44 #include "sql/worker-save.h"
45
46 #define CHAR(v) (const char *)(v)
47
48 static sqlite3 *db;
49
50 static json_t *
51 project_packer(sqlite3_stmt *stmt)
52 {
53 return json_pack("{ss ss ss ss sI}",
54 "name", sqlite3_column_text(stmt, 0),
55 "desc", sqlite3_column_text(stmt, 1),
56 "url", sqlite3_column_text(stmt, 2),
57 "script", sqlite3_column_text(stmt, 3),
58 "date", (json_int_t)sqlite3_column_int64(stmt, 4)
59 );
60 }
61
62 static int
63 project_binder(json_t *doc, sqlite3_stmt *stmt)
64 {
65 const char *name, *desc, *url, *script;
66 int ret;
67
68 ret = json_unpack(doc, "{ss ss ss ss}",
69 "name", &name,
70 "desc", &desc,
71 "url", &url,
72 "script", &script
73 );
74
75 if (ret < 0)
76 return -1;
77
78 sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
79 sqlite3_bind_text(stmt, 2, desc, -1, SQLITE_STATIC);
80 sqlite3_bind_text(stmt, 3, url, -1, SQLITE_STATIC);
81 sqlite3_bind_text(stmt, 4, script, -1, SQLITE_STATIC);
82
83 return 0;
84 }
85
86 static json_t *
87 worker_packer(sqlite3_stmt *stmt)
88 {
89 return json_pack("{ss ss}",
90 "name", sqlite3_column_text(stmt, 0),
91 "desc", sqlite3_column_text(stmt, 1)
92 );
93 }
94
95 static int
96 worker_binder(json_t *doc, sqlite3_stmt *stmt)
97 {
98 const char *name, *desc;
99 int ret;
100
101 ret = json_unpack(doc, "{ss ss}",
102 "name", &name,
103 "desc", &desc
104 );
105
106 if (ret < 0)
107 return -1;
108
109 sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
110 sqlite3_bind_text(stmt, 2, desc, -1, SQLITE_STATIC);
111
112 return 0;
113 }
114
115 static json_t *
116 job_packer(sqlite3_stmt *stmt)
117 {
118 return json_pack("{sI ss ss sI}",
119 "id", (json_int_t)sqlite3_column_int64(stmt, 0),
120 "tag", sqlite3_column_text(stmt, 1),
121 "project_name", sqlite3_column_text(stmt, 2),
122 "date", (json_int_t)sqlite3_column_int64(stmt, 3)
123 );
124 }
125
126 static int
127 job_binder(json_t *doc, sqlite3_stmt *stmt)
128 {
129 const char *tag, *project_name;
130 int ret;
131
132 ret = json_unpack(doc, "{ss ss}",
133 "tag", &tag,
134 "project_name", &project_name
135 );
136
137 if (ret)
138 return -1;
139
140 sqlite3_bind_text(stmt, 1, tag, -1, SQLITE_STATIC);
141 sqlite3_bind_text(stmt, 2, project_name, -1, SQLITE_STATIC);
142
143 return 0;
144 }
145
146 static json_t *
147 jobresult_packer(sqlite3_stmt *stmt)
148 {
149 return json_pack("{sI sI ss ss si si sI}",
150 "id", (json_int_t)sqlite3_column_int64(stmt, 0),
151 "job_id", (json_int_t)sqlite3_column_int64(stmt, 1),
152 "worker_name", sqlite3_column_text(stmt, 2),
153 "console", sqlite3_column_text(stmt, 3),
154 "exitcode", sqlite3_column_int(stmt, 4),
155 "sigcode", sqlite3_column_int(stmt, 5),
156 "date", (json_int_t)sqlite3_column_int64(stmt, 6)
157 );
158 }
159
160 static int
161 jobresult_binder(json_t *doc, sqlite3_stmt *stmt)
162 {
163 json_int_t job_id;
164 int exitcode, sigcode, ret;
165 const char *worker_name, *console;
166
167 ret = json_unpack(doc, "{sI ss ss si si}",
168 "job_id", &job_id,
169 "worker_name", &worker_name,
170 "console", &console,
171 "exitcode", &exitcode,
172 "sigcode", &sigcode
173 );
174
175 if (ret < 0)
176 return -1;
177
178 sqlite3_bind_int64(stmt, 1, job_id);
179 sqlite3_bind_text(stmt, 2, worker_name, -1, SQLITE_STATIC);
180 sqlite3_bind_text(stmt, 3, console, -1, SQLITE_STATIC);
181 sqlite3_bind_int(stmt, 4, exitcode);
182 sqlite3_bind_int(stmt, 5, sigcode);
183
184 return 0;
185 }
186
187 static void
188 bindva(sqlite3_stmt *stmt, const char *fmt, va_list ap)
189 {
190 for (int index = 1; *fmt; ++fmt) {
191 switch (*fmt) {
192 case 'i':
193 sqlite3_bind_int(stmt, index++, va_arg(ap, int));
194 break;
195 case 'j':
196 sqlite3_bind_int64(stmt, index++, va_arg(ap, intmax_t));
197 break;
198 case 's':
199 sqlite3_bind_text(stmt, index++, va_arg(ap, const char *), -1, SQLITE_STATIC);
200 break;
201 case 'z':
202 sqlite3_bind_int64(stmt, index++, va_arg(ap, size_t));
203 break;
204 default:
205 break;
206 }
207 }
208 }
209
210 static int
211 insert(int (*binder)(json_t *, sqlite3_stmt *), json_t *obj, const char *sql)
212 {
213 assert(binder);
214 assert(obj);
215 assert(sql);
216
217 sqlite3_stmt *stmt = NULL;
218
219 if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK)
220 return log_warn("db: %s", sqlite3_errmsg(db)), -1;
221
222 if (binder(obj, stmt))
223 log_warn("db: unable to bind parameter");
224 else {
225 if (sqlite3_step(stmt) != SQLITE_DONE)
226 log_warn("db: %s", sqlite3_errmsg(db));
227 else
228 json_object_set(obj, "id", json_integer(sqlite3_last_insert_rowid(db)));
229 }
230
231 sqlite3_finalize(stmt);
232
233 return 0;
234 }
235
236 static json_t *
237 listva(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, va_list ap)
238 {
239 sqlite3_stmt *stmt = NULL;
240 json_t *array, *obj;
241
242 if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK)
243 return log_warn("db: %s", sqlite3_errmsg(db)), NULL;
244
245 bindva(stmt, args, ap);
246 array = json_array();
247
248 while (sqlite3_step(stmt) == SQLITE_ROW)
249 if ((obj = unpacker(stmt)))
250 json_array_append(array, obj);
251
252 sqlite3_finalize(stmt);
253
254 return array;
255 }
256
257 static json_t *
258 list(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, ...)
259 {
260 va_list ap;
261 json_t *ret;
262
263 va_start(ap, args);
264 ret = listva(unpacker, sql, args, ap);
265 va_end(ap);
266
267 return ret;
268 }
269
270 /*
271 * Same as list but the array should have only one element that we extract for
272 * convenience.
273 */
274 static json_t *
275 get(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, ...)
276 {
277 va_list ap;
278 json_t *ret, *obj = NULL;
279
280 va_start(ap, args);
281 ret = listva(unpacker, sql, args, ap);
282 va_end(ap);
283
284 if (json_array_size(ret) == 1) {
285 obj = json_array_get(ret, 0);
286 json_incref(obj);
287 }
288
289 if (ret)
290 json_decref(ret);
291
292 return obj;
293 }
294
295 int
296 db_open(const char *path)
297 {
298 assert(path);
299
300 if (sqlite3_open(path, &db) != SQLITE_OK)
301 return log_warn("db: open error: %s", sqlite3_errmsg(db)), -1;
302
303 /* Wait for 30 seconds to lock the database. */
304 sqlite3_busy_timeout(db, 30000);
305
306 if (sqlite3_exec(db, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK)
307 return log_warn("db: initialization error: %s", sqlite3_errmsg(db)), -1;
308
309 return 0;
310 }
311
312 int
313 db_job_add(json_t *job)
314 {
315 assert(job);
316
317 return insert(job_binder, job, CHAR(sql_job_add));
318 }
319
320 json_t *
321 db_job_todo(const char *worker)
322 {
323 assert(worker);
324
325 return list(job_packer, CHAR(sql_job_todo), "ss", worker, worker);
326 }
327
328 json_t *
329 db_job_list(const char *project)
330 {
331 assert(project);
332
333 return list(job_packer, CHAR(sql_job_list), "s", project);
334 }
335
336 int
337 db_jobresult_add(json_t *res)
338 {
339 assert(res);
340
341 return insert(jobresult_binder, res, CHAR(sql_jobresult_add));
342 }
343
344 json_t *
345 db_jobresult_list_by_job(intmax_t job_id)
346 {
347 return list(jobresult_packer, CHAR(sql_jobresult_list_by_job), "j", job_id);
348 }
349
350 json_t *
351 db_jobresult_list_by_job_group(intmax_t job_id)
352 {
353 return list(jobresult_packer, CHAR(sql_jobresult_list_by_job_group), "j", job_id);
354 }
355
356 json_t *
357 db_jobresult_list_by_worker(const char *worker)
358 {
359 assert(worker);
360
361 return list(jobresult_packer, CHAR(sql_jobresult_list_by_worker), "s", worker);
362 }
363
364 int
365 db_project_save(json_t *p)
366 {
367 assert(p);
368
369 return insert(project_binder, p, CHAR(sql_project_save));
370 }
371
372 json_t *
373 db_project_list(void)
374 {
375 return list(project_packer, CHAR(sql_project_list), "");
376 }
377
378 json_t *
379 db_project_find(const char *name)
380 {
381 return get(project_packer, CHAR(sql_project_find), "s", name);
382 }
383
384 int
385 db_worker_save(json_t *wk)
386 {
387 assert(wk);
388
389 return insert(worker_binder, wk, CHAR(sql_worker_save));
390 }
391
392 json_t *
393 db_worker_list(void)
394 {
395 return list(worker_packer, CHAR(sql_worker_list), "");
396 }
397
398 json_t *
399 db_worker_find(const char *name)
400 {
401 assert(name);
402
403 return get(worker_packer, CHAR(sql_worker_find), "s", name);
404 }
405
406 void
407 db_finish(void)
408 {
409 if (db) {
410 sqlite3_close(db);
411 db = NULL;
412 }
413 }