]> git.ipfire.org Git - people/ms/pakfire.git/blame - src/libpakfire/db.c
libpakfire: db: Self-populate schema
[people/ms/pakfire.git] / src / libpakfire / db.c
CommitLineData
33d55ab4
MT
1/*#############################################################################
2# #
3# Pakfire - The IPFire package management system #
4# Copyright (C) 2021 Pakfire development team #
5# #
6# This program is free software: you can redistribute it and/or modify #
7# it under the terms of the GNU General Public License as published by #
8# the Free Software Foundation, either version 3 of the License, or #
9# (at your option) any later version. #
10# #
11# This program is distributed in the hope that it will be useful, #
12# but WITHOUT ANY WARRANTY; without even the implied warranty of #
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14# GNU General Public License for more details. #
15# #
16# You should have received a copy of the GNU General Public License #
17# along with this program. If not, see <http://www.gnu.org/licenses/>. #
18# #
19#############################################################################*/
20
21#include <errno.h>
22#include <stdlib.h>
23
26affd69
MT
24#include <sqlite3.h>
25
33d55ab4 26#include <pakfire/db.h>
33d55ab4 27#include <pakfire/logging.h>
18bf891d
MT
28#include <pakfire/pakfire.h>
29#include <pakfire/private.h>
33d55ab4
MT
30#include <pakfire/types.h>
31#include <pakfire/util.h>
32
0cb487ff
MT
33#define DATABASE_PATH PAKFIRE_PRIVATE_DIR "/packages.db"
34
c745fb2d
MT
35#define CURRENT_SCHEMA 7
36#define SCHEMA_MIN_SUP 7
37
33d55ab4
MT
38struct pakfire_db {
39 Pakfire pakfire;
40 int nrefs;
77a4b3a3
MT
41
42 int mode;
26affd69
MT
43
44 sqlite3* handle;
c745fb2d 45 int schema;
33d55ab4
MT
46};
47
9c5938ea
MT
48static void logging_callback(void* data, int r, const char* msg) {
49 Pakfire pakfire = (Pakfire)data;
50
51 ERROR(pakfire, "Database Error: %s: %s\n",
52 sqlite3_errstr(r), msg);
53}
54
0076c50d
MT
55static int pakfire_db_execute(struct pakfire_db* db, const char* stmt) {
56 int r;
c745fb2d
MT
57
58 DEBUG(db->pakfire, "Executing database query: %s\n", stmt);
0076c50d
MT
59
60 do {
c745fb2d 61 r = sqlite3_exec(db->handle, stmt, NULL, NULL, NULL);
0076c50d
MT
62 } while (r == SQLITE_BUSY);
63
c745fb2d
MT
64 // Log any errors
65 if (r) {
66 ERROR(db->pakfire, "Database query failed: %s\n", sqlite3_errmsg(db->handle));
67 }
68
25753290
MT
69 return r;
70}
71
c745fb2d
MT
72static int pakfire_db_begin_transaction(struct pakfire_db* db) {
73 return pakfire_db_execute(db, "BEGIN TRANSACTION");
74}
75
76static int pakfire_db_commit(struct pakfire_db* db) {
77 return pakfire_db_execute(db, "COMMIT");
78}
79
80static int pakfire_db_rollback(struct pakfire_db* db) {
81 return pakfire_db_execute(db, "ROLLBACK");
82}
83
25753290
MT
84/*
85 This function performs any fast optimization and tries to truncate the WAL log file
86 to keep the database as compact as possible on disk.
87*/
88static void pakfire_db_optimize(struct pakfire_db* db) {
89 pakfire_db_execute(db, "PRAGMA optmize");
90 pakfire_db_execute(db, "PRAGMA wal_checkpoint = TRUNCATE");
0076c50d
MT
91}
92
26affd69
MT
93static void pakfire_db_free(struct pakfire_db* db) {
94 DEBUG(db->pakfire, "Releasing database at %p\n", db);
95
26affd69 96 if (db->handle) {
25753290
MT
97 // Optimize the database before it is being closed
98 pakfire_db_optimize(db);
99
100 // Close database handle
26affd69
MT
101 int r = sqlite3_close(db->handle);
102 if (r != SQLITE_OK) {
103 ERROR(db->pakfire, "Could not close database handle: %s\n",
104 sqlite3_errmsg(db->handle));
105 }
106 }
107
108 pakfire_unref(db->pakfire);
109
110 pakfire_free(db);
111}
112
c745fb2d
MT
113static sqlite3_value* pakfire_db_get(struct pakfire_db* db, const char* key) {
114 sqlite3_stmt* stmt = NULL;
115 sqlite3_value* val = NULL;
116 int r;
117
118 const char* sql = "SELECT val FROM settings WHERE key = ?";
119
120 // Prepare the statement
121 r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
122 if (r != SQLITE_OK) {
123 //ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n",
124 // sql, sqlite3_errmsg(db->handle));
125 return NULL;
126 }
127
128 // Bind key
129 r = sqlite3_bind_text(stmt, 1, key, strlen(key), NULL);
130 if (r != SQLITE_OK) {
131 ERROR(db->pakfire, "Could not bind key: %s\n", sqlite3_errmsg(db->handle));
132 goto ERROR;
133 }
134
135 // Execute the statement
136 do {
137 r = sqlite3_step(stmt);
138 } while (r == SQLITE_BUSY);
139
140 // Read value
141 val = sqlite3_column_value(stmt, 1);
142 if (!val) {
143 ERROR(db->pakfire, "Could not read value\n");
144 goto ERROR;
145 }
146
147 // Copy value onto the heap
148 val = sqlite3_value_dup(val);
149
150ERROR:
151 if (stmt)
152 sqlite3_finalize(stmt);
153
154 return val;
155}
156
157static int pakfire_db_set_int(struct pakfire_db* db, const char* key, int val) {
158 sqlite3_stmt* stmt = NULL;
159 int r;
160
161 const char* sql = "INSERT INTO settings(key, val) VALUES(?, ?) \
162 ON CONFLICT (key) DO UPDATE SET val = excluded.val";
163
164 // Prepare statement
165 r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
166 if (r != SQLITE_OK) {
167 ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n",
168 sql, sqlite3_errmsg(db->handle));
169 return 1;
170 }
171
172 // Bind key
173 r = sqlite3_bind_text(stmt, 1, key, strlen(key), NULL);
174 if (r != SQLITE_OK) {
175 ERROR(db->pakfire, "Could not bind key: %s\n", sqlite3_errmsg(db->handle));
176 goto ERROR;
177 }
178
179 // Bind val
180 r = sqlite3_bind_int64(stmt, 2, val);
181 if (r != SQLITE_OK) {
182 ERROR(db->pakfire, "Could not bind val: %s\n", sqlite3_errmsg(db->handle));
183 goto ERROR;
184 }
185
186 // Execute the statement
187 do {
188 r = sqlite3_step(stmt);
189 } while (r == SQLITE_BUSY);
190
191 // Set return code
192 r = (r == SQLITE_OK);
193
194ERROR:
195 if (stmt)
196 sqlite3_finalize(stmt);
197
198 return r;
199}
200
201static int pakfire_db_get_schema(struct pakfire_db* db) {
202 sqlite3_value* value = pakfire_db_get(db, "schema");
203 if (!value)
204 return 0;
205
206 int schema = sqlite3_value_int64(value);
207 sqlite3_value_free(value);
208
209 DEBUG(db->pakfire, "Database has schema version %d\n", schema);
210
211 return schema;
212}
213
214static int pakfire_db_create_schema(struct pakfire_db* db) {
215 int r;
216
217 // Create settings table
218 r = pakfire_db_execute(db, "CREATE TABLE IF NOT EXISTS settings(key TEXT, val TEXT)");
219 if (r)
220 return 1;
221
222 // settings: Add a unique index on key
223 r = pakfire_db_execute(db, "CREATE UNIQUE INDEX IF NOT EXISTS settings_key ON settings(key)");
224 if (r)
225 return 1;
226
227 return 0;
228}
229
230static int pakfire_db_migrate_schema(struct pakfire_db* db) {
231 int r;
232
233 while (db->schema < CURRENT_SCHEMA) {
234 // Begin a new transaction
235 r = pakfire_db_begin_transaction(db);
236 if (r)
237 goto ROLLBACK;
238
239 switch (db->schema) {
240 // No schema exists
241 case 0:
242 r = pakfire_db_create_schema(db);
243 if (r)
244 goto ROLLBACK;
245
246 db->schema = CURRENT_SCHEMA;
247 break;
248
249 default:
250 ERROR(db->pakfire, "Cannot migrate database from schema %d\n", db->schema);
251 goto ROLLBACK;
252 }
253
254 // Update the schema version
255 r = pakfire_db_set_int(db, "schema", CURRENT_SCHEMA);
256 if (r)
257 goto ROLLBACK;
258
259 // All done, commit!
260 r = pakfire_db_commit(db);
261 if (r)
262 goto ROLLBACK;
263 }
264
265 return 0;
266
267ROLLBACK:
268 pakfire_db_rollback(db);
269
270 return 1;
271}
272
9c5938ea 273static int pakfire_db_setup(struct pakfire_db* db) {
25753290
MT
274 int r;
275
9c5938ea
MT
276 // Setup logging
277 sqlite3_config(SQLITE_CONFIG_LOG, logging_callback, db->pakfire);
278
0076c50d
MT
279 // Make LIKE case-sensitive
280 pakfire_db_execute(db, "PRAGMA case_sensitive_like = ON");
281
c745fb2d
MT
282 // Fetch the current schema
283 db->schema = pakfire_db_get_schema(db);
284
285 // Check if the schema is recent enough
286 if (db->schema > 0 && db->schema < SCHEMA_MIN_SUP) {
287 ERROR(db->pakfire, "Database schema %d is not supported by this version of Pakfire\n",
288 db->schema);
289 return 1;
290 }
291
9c5938ea
MT
292 // Done when not in read-write mode
293 if (db->mode != PAKFIRE_DB_READWRITE)
294 return 0;
295
0076c50d
MT
296 // Disable secure delete
297 pakfire_db_execute(db, "PRAGMA secure_delete = OFF");
298
25753290
MT
299 // Set database journal to WAL
300 r = pakfire_db_execute(db, "PRAGMA journal_mode = WAL");
301 if (r != SQLITE_OK) {
302 ERROR(db->pakfire, "Could not set journal mode to WAL: %s\n",
303 sqlite3_errmsg(db->handle));
304 return 1;
305 }
306
307 // Disable autocheckpoint
308 r = sqlite3_wal_autocheckpoint(db->handle, 0);
309 if (r != SQLITE_OK) {
310 ERROR(db->pakfire, "Could not disable autocheckpoint: %s\n",
311 sqlite3_errmsg(db->handle));
312 return 1;
313 }
314
c745fb2d
MT
315 // Create or migrate schema
316 r = pakfire_db_migrate_schema(db);
317 if (r)
318 return r;
9c5938ea
MT
319
320 return 0;
321}
322
77a4b3a3 323PAKFIRE_EXPORT int pakfire_db_open(struct pakfire_db** db, Pakfire pakfire, int flags) {
26affd69
MT
324 int r = 1;
325
33d55ab4
MT
326 struct pakfire_db* o = pakfire_calloc(1, sizeof(*o));
327 if (!o)
328 return -ENOMEM;
329
330 DEBUG(pakfire, "Allocated database at %p\n", o);
331
332 o->pakfire = pakfire_ref(pakfire);
333 o->nrefs = 1;
334
26affd69
MT
335 int sqlite3_flags = 0;
336
337 // Store mode & forward it to sqlite3
338 if (flags & PAKFIRE_DB_READWRITE) {
77a4b3a3 339 o->mode = PAKFIRE_DB_READWRITE;
26affd69
MT
340 sqlite3_flags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
341 } else {
77a4b3a3 342 o->mode = PAKFIRE_DB_READONLY;
26affd69
MT
343 sqlite3_flags |= SQLITE_OPEN_READONLY;
344 }
345
346 // Make the filename
347 char* path = pakfire_make_path(o->pakfire, DATABASE_PATH);
348 if (!path)
349 goto END;
350
351 // Try to open the sqlite3 database file
352 r = sqlite3_open_v2(path, &o->handle, sqlite3_flags, NULL);
353 if (r != SQLITE_OK) {
354 ERROR(pakfire, "Could not open database %s: %s\n",
355 path, sqlite3_errmsg(o->handle));
356
357 r = 1;
358 goto END;
359 }
77a4b3a3 360
9c5938ea
MT
361 // Setup the database
362 r = pakfire_db_setup(o);
363 if (r)
364 goto END;
365
33d55ab4 366 *db = o;
26affd69
MT
367 r = 0;
368
369END:
370 if (r)
371 pakfire_db_free(o);
33d55ab4 372
26affd69
MT
373 if (path)
374 free(path);
375
376 return r;
33d55ab4
MT
377}
378
18bf891d 379PAKFIRE_EXPORT struct pakfire_db* pakfire_db_ref(struct pakfire_db* db) {
33d55ab4
MT
380 db->nrefs++;
381
382 return db;
383}
384
18bf891d 385PAKFIRE_EXPORT struct pakfire_db* pakfire_db_unref(struct pakfire_db* db) {
33d55ab4
MT
386 if (--db->nrefs > 0)
387 return db;
388
389 pakfire_db_free(db);
390
391 return NULL;
392}
eafbe2ce 393
18bf891d 394PAKFIRE_EXPORT int pakfire_db_add_package(struct pakfire_db* db, PakfirePackage pkg) {
eafbe2ce
MT
395 return 0; // TODO
396}
397
18bf891d 398PAKFIRE_EXPORT int pakfire_db_remove_package(struct pakfire_db* db, PakfirePackage pkg) {
eafbe2ce
MT
399 return 0; // TODO
400}