]> git.ipfire.org Git - people/ms/pakfire.git/blob - src/libpakfire/db.c
libpakfire: db: Self-populate schema
[people/ms/pakfire.git] / src / libpakfire / db.c
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
24 #include <sqlite3.h>
25
26 #include <pakfire/db.h>
27 #include <pakfire/logging.h>
28 #include <pakfire/pakfire.h>
29 #include <pakfire/private.h>
30 #include <pakfire/types.h>
31 #include <pakfire/util.h>
32
33 #define DATABASE_PATH PAKFIRE_PRIVATE_DIR "/packages.db"
34
35 #define CURRENT_SCHEMA 7
36 #define SCHEMA_MIN_SUP 7
37
38 struct pakfire_db {
39 Pakfire pakfire;
40 int nrefs;
41
42 int mode;
43
44 sqlite3* handle;
45 int schema;
46 };
47
48 static 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
55 static int pakfire_db_execute(struct pakfire_db* db, const char* stmt) {
56 int r;
57
58 DEBUG(db->pakfire, "Executing database query: %s\n", stmt);
59
60 do {
61 r = sqlite3_exec(db->handle, stmt, NULL, NULL, NULL);
62 } while (r == SQLITE_BUSY);
63
64 // Log any errors
65 if (r) {
66 ERROR(db->pakfire, "Database query failed: %s\n", sqlite3_errmsg(db->handle));
67 }
68
69 return r;
70 }
71
72 static int pakfire_db_begin_transaction(struct pakfire_db* db) {
73 return pakfire_db_execute(db, "BEGIN TRANSACTION");
74 }
75
76 static int pakfire_db_commit(struct pakfire_db* db) {
77 return pakfire_db_execute(db, "COMMIT");
78 }
79
80 static int pakfire_db_rollback(struct pakfire_db* db) {
81 return pakfire_db_execute(db, "ROLLBACK");
82 }
83
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 */
88 static void pakfire_db_optimize(struct pakfire_db* db) {
89 pakfire_db_execute(db, "PRAGMA optmize");
90 pakfire_db_execute(db, "PRAGMA wal_checkpoint = TRUNCATE");
91 }
92
93 static void pakfire_db_free(struct pakfire_db* db) {
94 DEBUG(db->pakfire, "Releasing database at %p\n", db);
95
96 if (db->handle) {
97 // Optimize the database before it is being closed
98 pakfire_db_optimize(db);
99
100 // Close database handle
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
113 static 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
150 ERROR:
151 if (stmt)
152 sqlite3_finalize(stmt);
153
154 return val;
155 }
156
157 static 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
194 ERROR:
195 if (stmt)
196 sqlite3_finalize(stmt);
197
198 return r;
199 }
200
201 static 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
214 static 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
230 static 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
267 ROLLBACK:
268 pakfire_db_rollback(db);
269
270 return 1;
271 }
272
273 static int pakfire_db_setup(struct pakfire_db* db) {
274 int r;
275
276 // Setup logging
277 sqlite3_config(SQLITE_CONFIG_LOG, logging_callback, db->pakfire);
278
279 // Make LIKE case-sensitive
280 pakfire_db_execute(db, "PRAGMA case_sensitive_like = ON");
281
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
292 // Done when not in read-write mode
293 if (db->mode != PAKFIRE_DB_READWRITE)
294 return 0;
295
296 // Disable secure delete
297 pakfire_db_execute(db, "PRAGMA secure_delete = OFF");
298
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
315 // Create or migrate schema
316 r = pakfire_db_migrate_schema(db);
317 if (r)
318 return r;
319
320 return 0;
321 }
322
323 PAKFIRE_EXPORT int pakfire_db_open(struct pakfire_db** db, Pakfire pakfire, int flags) {
324 int r = 1;
325
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
335 int sqlite3_flags = 0;
336
337 // Store mode & forward it to sqlite3
338 if (flags & PAKFIRE_DB_READWRITE) {
339 o->mode = PAKFIRE_DB_READWRITE;
340 sqlite3_flags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
341 } else {
342 o->mode = PAKFIRE_DB_READONLY;
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 }
360
361 // Setup the database
362 r = pakfire_db_setup(o);
363 if (r)
364 goto END;
365
366 *db = o;
367 r = 0;
368
369 END:
370 if (r)
371 pakfire_db_free(o);
372
373 if (path)
374 free(path);
375
376 return r;
377 }
378
379 PAKFIRE_EXPORT struct pakfire_db* pakfire_db_ref(struct pakfire_db* db) {
380 db->nrefs++;
381
382 return db;
383 }
384
385 PAKFIRE_EXPORT struct pakfire_db* pakfire_db_unref(struct pakfire_db* db) {
386 if (--db->nrefs > 0)
387 return db;
388
389 pakfire_db_free(db);
390
391 return NULL;
392 }
393
394 PAKFIRE_EXPORT int pakfire_db_add_package(struct pakfire_db* db, PakfirePackage pkg) {
395 return 0; // TODO
396 }
397
398 PAKFIRE_EXPORT int pakfire_db_remove_package(struct pakfire_db* db, PakfirePackage pkg) {
399 return 0; // TODO
400 }