]> git.ipfire.org Git - pakfire.git/blobdiff - src/libpakfire/db.c
dependencies: Drop the legacy logger
[pakfire.git] / src / libpakfire / db.c
index 36c85409d3b06df3320d36b7200f64879add185f..1737a9304d9f01229530bad7ebd28c94194faaf2 100644 (file)
 #############################################################################*/
 
 #include <errno.h>
+#include <linux/limits.h>
 #include <stdlib.h>
+#include <time.h>
 
+#include <openssl/sha.h>
+#include <solv/solver.h>
 #include <sqlite3.h>
 
 #include <pakfire/archive.h>
+#include <pakfire/ctx.h>
 #include <pakfire/db.h>
+#include <pakfire/dependencies.h>
+#include <pakfire/digest.h>
 #include <pakfire/file.h>
+#include <pakfire/filelist.h>
 #include <pakfire/logging.h>
 #include <pakfire/package.h>
 #include <pakfire/pakfire.h>
-#include <pakfire/private.h>
-#include <pakfire/relationlist.h>
 #include <pakfire/repo.h>
-#include <pakfire/types.h>
+#include <pakfire/string.h>
 #include <pakfire/util.h>
 
 #define DATABASE_PATH PAKFIRE_PRIVATE_DIR "/packages.db"
 #define SCHEMA_MIN_SUP 7
 
 struct pakfire_db {
-       Pakfire pakfire;
+       struct pakfire_ctx* ctx;
+       struct pakfire* pakfire;
        int nrefs;
 
+       char path[PATH_MAX];
        int mode;
 
        sqlite3* handle;
        int schema;
+       time_t last_modified_at;
 };
 
 static void logging_callback(void* data, int r, const char* msg) {
-       Pakfire pakfire = (Pakfire)data;
+       struct pakfire_ctx* ctx = data;
 
-       ERROR(pakfire, "Database Error: %s: %s\n",
+       CTX_ERROR(ctx, "Database Error: %s: %s\n",
                sqlite3_errstr(r), msg);
 }
 
-static int pakfire_db_execute(struct pakfire_db* db, const char* stmt) {
+static int pakfire_db_check_table(struct pakfire_db* db, const char* table) {
+       sqlite3_stmt* stmt = NULL;
        int r;
 
-       DEBUG(db->pakfire, "Executing database query: %s\n", stmt);
-
-       do {
-               r = sqlite3_exec(db->handle, stmt, NULL, NULL, NULL);
-       } while (r == SQLITE_BUSY);
+       const char* sql = "SELECT * FROM sqlite_master WHERE type = ? AND name = ?";
 
-       // Log any errors
-       if (r) {
-               ERROR(db->pakfire, "Database query failed: %s\n", sqlite3_errmsg(db->handle));
+       // Prepare the statement
+       r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
+       if (r != SQLITE_OK) {
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s: %s\n",
+                       sql, sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
-       return r;
-}
-
-static int pakfire_db_begin_transaction(struct pakfire_db* db) {
-       return pakfire_db_execute(db, "BEGIN TRANSACTION");
-}
-
-static int pakfire_db_commit(struct pakfire_db* db) {
-       return pakfire_db_execute(db, "COMMIT");
-}
-
-static int pakfire_db_rollback(struct pakfire_db* db) {
-       return pakfire_db_execute(db, "ROLLBACK");
-}
+       // Bind type
+       r = sqlite3_bind_text(stmt, 1, "table", -1, NULL);
+       if (r != SQLITE_OK) {
+               CTX_ERROR(db->ctx, "Could not bind type: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
 
-/*
-       This function performs any fast optimization and tries to truncate the WAL log file
-       to keep the database as compact as possible on disk.
-*/
-static void pakfire_db_optimize(struct pakfire_db* db) {
-       pakfire_db_execute(db, "PRAGMA optimize");
-       pakfire_db_execute(db, "PRAGMA wal_checkpoint = TRUNCATE");
-}
+       // Bind name
+       r = sqlite3_bind_text(stmt, 2, table, -1, NULL);
+       if (r != SQLITE_OK) {
+               CTX_ERROR(db->ctx, "Could not bind name: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
 
-static void pakfire_db_free(struct pakfire_db* db) {
-       DEBUG(db->pakfire, "Releasing database at %p\n", db);
+       // Execute the statement
+       do {
+               r = sqlite3_step(stmt);
+       } while (r == SQLITE_BUSY);
 
-       if (db->handle) {
-               // Optimize the database before it is being closed
-               pakfire_db_optimize(db);
+       // We should have read a row
+       if (r != SQLITE_ROW)
+               goto ERROR;
 
-               // Close database handle
-               int r = sqlite3_close(db->handle);
-               if (r != SQLITE_OK) {
-                       ERROR(db->pakfire, "Could not close database handle: %s\n",
-                               sqlite3_errmsg(db->handle));
-               }
-       }
+       // If we get here, the table exists.
+       r = 0;
 
-       pakfire_unref(db->pakfire);
+ERROR:
+       if (stmt)
+               sqlite3_finalize(stmt);
 
-       pakfire_free(db);
+       return r;
 }
 
 static sqlite3_value* pakfire_db_get(struct pakfire_db* db, const char* key) {
@@ -120,12 +118,19 @@ static sqlite3_value* pakfire_db_get(struct pakfire_db* db, const char* key) {
        sqlite3_value* val = NULL;
        int r;
 
+       // If the schema has not been initialized, yet, check if the settings table exist, at all
+       if (!db->schema) {
+               r = pakfire_db_check_table(db, "settings");
+               if (r)
+                       return NULL;
+       }
+
        const char* sql = "SELECT val FROM settings WHERE key = ?";
 
        // Prepare the statement
        r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
        if (r != SQLITE_OK) {
-               //ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n",
+               //CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s: %s\n",
                //      sql, sqlite3_errmsg(db->handle));
                return NULL;
        }
@@ -133,7 +138,7 @@ static sqlite3_value* pakfire_db_get(struct pakfire_db* db, const char* key) {
        // Bind key
        r = sqlite3_bind_text(stmt, 1, key, strlen(key), NULL);
        if (r != SQLITE_OK) {
-               ERROR(db->pakfire, "Could not bind key: %s\n", sqlite3_errmsg(db->handle));
+               CTX_ERROR(db->ctx, "Could not bind key: %s\n", sqlite3_errmsg(db->handle));
                goto ERROR;
        }
 
@@ -149,7 +154,7 @@ static sqlite3_value* pakfire_db_get(struct pakfire_db* db, const char* key) {
        // Read value
        val = sqlite3_column_value(stmt, 0);
        if (!val) {
-               ERROR(db->pakfire, "Could not read value\n");
+               CTX_ERROR(db->ctx, "Could not read value\n");
                goto ERROR;
        }
 
@@ -163,17 +168,88 @@ ERROR:
        return val;
 }
 
+static char* pakfire_db_get_string(struct pakfire_db* db, const char* key) {
+       char* s = NULL;
+
+       // Fetch the value from the database
+       sqlite3_value* value = pakfire_db_get(db, key);
+       if (!value)
+               return NULL;
+
+       // Extract the value as string
+       const char* p = (const char*)sqlite3_value_text(value);
+       if (!p)
+               goto ERROR;
+
+       // Copy string to heap
+       s = strdup(p);
+
+ERROR:
+       if (value)
+               sqlite3_value_free(value);
+
+       return s;
+}
+
+static int pakfire_db_set_string(struct pakfire_db* db, const char* key, const char* val) {
+       sqlite3_stmt* stmt = NULL;
+       int r;
+
+       CTX_DEBUG(db->ctx, "Setting %s to '%s'\n", key, val);
+
+       const char* sql = "INSERT INTO settings(key, val) VALUES(?, ?) \
+               ON CONFLICT (key) DO UPDATE SET val = excluded.val";
+
+       // Prepare statement
+       r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
+       if (r != SQLITE_OK) {
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s: %s\n",
+                       sql, sqlite3_errmsg(db->handle));
+               return 1;
+       }
+
+       // Bind key
+       r = sqlite3_bind_text(stmt, 1, key, strlen(key), NULL);
+       if (r != SQLITE_OK) {
+               CTX_ERROR(db->ctx, "Could not bind key: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // Bind val
+       r = sqlite3_bind_text(stmt, 2, val, strlen(val), NULL);
+       if (r != SQLITE_OK) {
+               CTX_ERROR(db->ctx, "Could not bind val: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // Execute the statement
+       do {
+               r = sqlite3_step(stmt);
+       } while (r == SQLITE_BUSY);
+
+       // Set return code
+       r = (r == SQLITE_OK);
+
+ERROR:
+       if (stmt)
+               sqlite3_finalize(stmt);
+
+       return r;
+}
+
 static int pakfire_db_set_int(struct pakfire_db* db, const char* key, int val) {
        sqlite3_stmt* stmt = NULL;
        int r;
 
+       CTX_DEBUG(db->ctx, "Setting %s to '%d'\n", key, val);
+
        const char* sql = "INSERT INTO settings(key, val) VALUES(?, ?) \
                ON CONFLICT (key) DO UPDATE SET val = excluded.val";
 
        // Prepare statement
        r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
        if (r != SQLITE_OK) {
-               ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s: %s\n",
                        sql, sqlite3_errmsg(db->handle));
                return 1;
        }
@@ -181,14 +257,14 @@ static int pakfire_db_set_int(struct pakfire_db* db, const char* key, int val) {
        // Bind key
        r = sqlite3_bind_text(stmt, 1, key, strlen(key), NULL);
        if (r != SQLITE_OK) {
-               ERROR(db->pakfire, "Could not bind key: %s\n", sqlite3_errmsg(db->handle));
+               CTX_ERROR(db->ctx, "Could not bind key: %s\n", sqlite3_errmsg(db->handle));
                goto ERROR;
        }
 
        // Bind val
        r = sqlite3_bind_int64(stmt, 2, val);
        if (r != SQLITE_OK) {
-               ERROR(db->pakfire, "Could not bind val: %s\n", sqlite3_errmsg(db->handle));
+               CTX_ERROR(db->ctx, "Could not bind val: %s\n", sqlite3_errmsg(db->handle));
                goto ERROR;
        }
 
@@ -207,15 +283,115 @@ ERROR:
        return r;
 }
 
+static time_t pakfire_read_modification_time(struct pakfire_db* db) {
+       time_t t = 0;
+
+       // Fetch the value from the database
+       sqlite3_value* value = pakfire_db_get(db, "last_modified_at");
+       if (value) {
+               t = sqlite3_value_int64(value);
+               sqlite3_value_free(value);
+       } else {
+               CTX_DEBUG(db->ctx, "Could not find last modification timestamp\n");
+       }
+
+       return t;
+}
+
+static int pakfire_update_modification_time(struct pakfire_db* db) {
+       // Get the current time in UTC
+       time_t t = time(NULL);
+
+       // Store it in the database
+       int r = pakfire_db_set_int(db, "last_modified_at", t);
+       if (r)
+               return r;
+
+       // Update the last modification timestamp
+       db->last_modified_at = t;
+
+       return r;
+}
+
+static int pakfire_db_execute(struct pakfire_db* db, const char* stmt) {
+       int r;
+
+       CTX_DEBUG(db->ctx, "Executing database query: %s\n", stmt);
+
+       do {
+               r = sqlite3_exec(db->handle, stmt, NULL, NULL, NULL);
+       } while (r == SQLITE_BUSY);
+
+       // Log any errors
+       if (r) {
+               CTX_ERROR(db->ctx, "Database query failed: %s\n", sqlite3_errmsg(db->handle));
+       }
+
+       return r;
+}
+
+static int pakfire_db_begin_transaction(struct pakfire_db* db) {
+       return pakfire_db_execute(db, "BEGIN TRANSACTION");
+}
+
+static int pakfire_db_commit(struct pakfire_db* db) {
+       /*
+               If the database was opened in read-write mode, we will store the
+               timestamp of the latest modification to compare whether the database
+               has been changed mid-transaction.
+       */
+       if (db->mode == PAKFIRE_DB_READWRITE) {
+               int r = pakfire_update_modification_time(db);
+               if (r)
+                       return r;
+       }
+
+       return pakfire_db_execute(db, "COMMIT");
+}
+
+static int pakfire_db_rollback(struct pakfire_db* db) {
+       return pakfire_db_execute(db, "ROLLBACK");
+}
+
+/*
+       This function performs any fast optimization and tries to truncate the WAL log file
+       to keep the database as compact as possible on disk.
+*/
+static void pakfire_db_optimize(struct pakfire_db* db) {
+       pakfire_db_execute(db, "PRAGMA optimize");
+       pakfire_db_execute(db, "PRAGMA wal_checkpoint = TRUNCATE");
+}
+
+static void pakfire_db_free(struct pakfire_db* db) {
+       if (db->handle) {
+               // Optimize the database before it is being closed
+               pakfire_db_optimize(db);
+
+               // Close database handle
+               int r = sqlite3_close(db->handle);
+               if (r != SQLITE_OK) {
+                       CTX_ERROR(db->ctx, "Could not close database handle: %s\n",
+                               sqlite3_errmsg(db->handle));
+               }
+       }
+
+       if (db->pakfire)
+               pakfire_unref(db->pakfire);
+       if (db->ctx)
+               pakfire_ctx_unref(db->ctx);
+       free(db);
+}
+
 static int pakfire_db_get_schema(struct pakfire_db* db) {
+       // Fetch the schema version
        sqlite3_value* value = pakfire_db_get(db, "schema");
        if (!value)
-               return -1;
+               return 0;
 
        int schema = sqlite3_value_int64(value);
        sqlite3_value_free(value);
 
-       DEBUG(db->pakfire, "Database has schema version %d\n", schema);
+       CTX_DEBUG(db->ctx, "Database has schema version %d\n", schema);
 
        return schema;
 }
@@ -236,27 +412,30 @@ static int pakfire_db_create_schema(struct pakfire_db* db) {
        // Create packages table
        r = pakfire_db_execute(db,
                "CREATE TABLE IF NOT EXISTS packages("
-                       "id             INTEGER PRIMARY KEY, "
-                       "name           TEXT, "
-                       "epoch          INTEGER, "
-                       "version        TEXT, "
-                       "release        TEXT, "
-                       "arch           TEXT, "
-                       "groups         TEXT, "
-                       "filename       TEXT, "
-                       "size           INTEGER, "
-                       "inst_size      INTEGER, "
-                       "hash1          TEXT, "
-                       "license        TEXT, "
-                       "summary        TEXT, "
-                       "description    TEXT, "
-                       "uuid           TEXT, "
-                       "vendor         TEXT, "
-                       "build_host     TEXT, "
-                       "build_time     INTEGER, "
-                       "installed      INTEGER, "
-                       "reason         TEXT, "
-                       "repository     TEXT"
+                       "id              INTEGER PRIMARY KEY, "
+                       "name            TEXT, "
+                       "evr             TEXT, "
+                       "arch            TEXT, "
+                       "groups          TEXT, "
+                       "filename        TEXT, "
+                       "size            INTEGER, "
+                       "inst_size       INTEGER, "
+                       "digest_type     INTEGER, "
+                       "digest          BLOB, "
+                       "license         TEXT, "
+                       "summary         TEXT, "
+                       "description     TEXT, "
+                       "uuid            TEXT, "
+                       "vendor          TEXT, "
+                       "build_host      TEXT, "
+                       "build_time      INTEGER, "
+                       "installed       INTEGER, "
+                       "userinstalled   INTEGER, "
+                       "repository      TEXT, "
+                       "source_name     TEXT, "
+                       "source_evr      TEXT, "
+                       "source_arch     TEXT, "
+                       "distribution    TEXT"
                ")");
        if (r)
                return 1;
@@ -290,19 +469,25 @@ static int pakfire_db_create_schema(struct pakfire_db* db) {
        // Create files table
        r = pakfire_db_execute(db,
                "CREATE TABLE IF NOT EXISTS files("
-                       "id             INTEGER PRIMARY KEY, "
-                       "name           TEXT, "
-                       "pkg            INTEGER, "
-                       "size           INTEGER, "
-                       "type           INTEGER, "
-                       "config         INTEGER, "
-                       "datafile       INTEGER, "
-                       "mode           INTEGER, "
-                       "user           TEXT, "
-                       "'group'        TEXT, "
-                       "hash1          TEXT, "
-                       "mtime          INTEGER, "
-                       "capabilities   TEXT, "
+                       "id                INTEGER PRIMARY KEY, "
+                       "path              TEXT, "
+                       "pkg               INTEGER, "
+                       "size              INTEGER, "
+                       "config            INTEGER, "
+                       "datafile          INTEGER, "
+                       "mode              INTEGER, "
+                       "uname             TEXT, "
+                       "gname             TEXT, "
+                       "ctime             INTEGER, "
+                       "mtime             INTEGER, "
+                       "mimetype          TEXT, "
+                       "capabilities      TEXT, "
+                       "digest_sha2_512   BLOB, "
+                       "digest_sha2_256   BLOB, "
+                       "digest_blake2b512 BLOB, "
+                       "digest_blake2s256 BLOB, "
+                       "digest_sha3_512   BLOB, "
+                       "digest_sha3_256   BLOB, "
                        "FOREIGN KEY (pkg) REFERENCES packages(id) ON DELETE CASCADE"
                ")");
        if (r)
@@ -313,6 +498,11 @@ static int pakfire_db_create_schema(struct pakfire_db* db) {
        if (r)
                return 1;
 
+       // files: Add index over path
+       r = pakfire_db_execute(db, "CREATE INDEX IF NOT EXISTS files_path_index ON files(path)");
+       if (r)
+               return 1;
+
        // Create scriptlets table
        r = pakfire_db_execute(db,
                "CREATE TABLE IF NOT EXISTS scriptlets("
@@ -330,6 +520,15 @@ static int pakfire_db_create_schema(struct pakfire_db* db) {
        if (r)
                return 1;
 
+       const char* arch = pakfire_get_effective_arch(db->pakfire);
+
+       // Set architecture
+       r = pakfire_db_set_string(db, "arch", arch);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not set architecture\n");
+               return r;
+       }
+
        return 0;
 }
 
@@ -354,7 +553,7 @@ static int pakfire_db_migrate_schema(struct pakfire_db* db) {
 
                switch (db->schema) {
                        // No schema exists
-                       case -1:
+                       case 0:
                                r = pakfire_db_create_schema(db);
                                if (r)
                                        goto ROLLBACK;
@@ -371,7 +570,7 @@ static int pakfire_db_migrate_schema(struct pakfire_db* db) {
                                break;
 
                        default:
-                               ERROR(db->pakfire, "Cannot migrate database from schema %d\n", db->schema);
+                               CTX_ERROR(db->ctx, "Cannot migrate database from schema %d\n", db->schema);
                                goto ROLLBACK;
                }
 
@@ -394,11 +593,37 @@ ROLLBACK:
        return 1;
 }
 
+static int pakfire_db_check_arch(struct pakfire_db* db) {
+       int r = 1;
+
+       // Fetch database architecture
+       char* db_arch = pakfire_db_get_string(db, "arch");
+       if (!db_arch) {
+               CTX_ERROR(db->ctx, "Database is of an unknown architecture\n");
+               goto ERROR;
+       }
+
+       // Fetch the running architecture
+       const char* arch = pakfire_get_effective_arch(db->pakfire);
+       if (!arch)
+               goto ERROR;
+
+       // They should match
+       if (strcmp(db_arch, arch) == 0)
+               r = 0;
+
+ERROR:
+       if (db_arch)
+               free(db_arch);
+
+       return r;
+}
+
 static int pakfire_db_setup(struct pakfire_db* db) {
        int r;
 
        // Setup logging
-       sqlite3_config(SQLITE_CONFIG_LOG, logging_callback, db->pakfire);
+       sqlite3_config(SQLITE_CONFIG_LOG, logging_callback, db->ctx);
 
        // Enable foreign keys
        pakfire_db_execute(db, "PRAGMA foreign_keys = ON");
@@ -410,12 +635,17 @@ static int pakfire_db_setup(struct pakfire_db* db) {
        db->schema = pakfire_db_get_schema(db);
 
        // Check if the schema is recent enough
-       if (db->schema > 0 && db->schema < SCHEMA_MIN_SUP) {
-               ERROR(db->pakfire, "Database schema %d is not supported by this version of Pakfire\n",
+       if (db->schema && db->schema < SCHEMA_MIN_SUP) {
+               CTX_ERROR(db->ctx, "Database schema %d is not supported by this version of Pakfire\n",
                        db->schema);
                return 1;
        }
 
+       // Read modification timestamp
+       db->last_modified_at = pakfire_read_modification_time(db);
+
+       CTX_DEBUG(db->ctx, "The database was last modified at %ld\n", db->last_modified_at);
+
        // Done when not in read-write mode
        if (db->mode != PAKFIRE_DB_READWRITE)
                return 0;
@@ -426,7 +656,7 @@ static int pakfire_db_setup(struct pakfire_db* db) {
        // Set database journal to WAL
        r = pakfire_db_execute(db, "PRAGMA journal_mode = WAL");
        if (r != SQLITE_OK) {
-               ERROR(db->pakfire, "Could not set journal mode to WAL: %s\n",
+               CTX_ERROR(db->ctx, "Could not set journal mode to WAL: %s\n",
                        sqlite3_errmsg(db->handle));
                return 1;
        }
@@ -434,7 +664,7 @@ static int pakfire_db_setup(struct pakfire_db* db) {
        // Disable autocheckpoint
        r = sqlite3_wal_autocheckpoint(db->handle, 0);
        if (r != SQLITE_OK) {
-               ERROR(db->pakfire, "Could not disable autocheckpoint: %s\n",
+               CTX_ERROR(db->ctx, "Could not disable autocheckpoint: %s\n",
                        sqlite3_errmsg(db->handle));
                return 1;
        }
@@ -447,14 +677,15 @@ static int pakfire_db_setup(struct pakfire_db* db) {
        return 0;
 }
 
-PAKFIRE_EXPORT int pakfire_db_open(struct pakfire_db** db, Pakfire pakfire, int flags) {
+int pakfire_db_open(struct pakfire_db** db, struct pakfire* pakfire, int flags) {
        int r = 1;
 
-       struct pakfire_db* o = pakfire_calloc(1, sizeof(*o));
+       struct pakfire_db* o = calloc(1, sizeof(*o));
        if (!o)
                return -ENOMEM;
 
-       DEBUG(pakfire, "Allocated database at %p\n", o);
+       // Store a reference to the context
+       o->ctx = pakfire_ctx(pakfire);
 
        o->pakfire = pakfire_ref(pakfire);
        o->nrefs = 1;
@@ -471,15 +702,15 @@ PAKFIRE_EXPORT int pakfire_db_open(struct pakfire_db** db, Pakfire pakfire, int
        }
 
        // Make the filename
-       char* path = pakfire_make_path(o->pakfire, DATABASE_PATH);
-       if (!path)
+       r = pakfire_path(o->pakfire, o->path, "%s", DATABASE_PATH);
+       if (r)
                goto END;
 
        // Try to open the sqlite3 database file
-       r = sqlite3_open_v2(path, &o->handle, sqlite3_flags, NULL);
+       r = sqlite3_open_v2(o->path, &o->handle, sqlite3_flags, NULL);
        if (r != SQLITE_OK) {
-               ERROR(pakfire, "Could not open database %s: %s\n",
-                       path, sqlite3_errmsg(o->handle));
+               CTX_ERROR(o->ctx, "Could not open database %s: %s\n",
+                       o->path, sqlite3_errmsg(o->handle));
 
                r = 1;
                goto END;
@@ -490,6 +721,11 @@ PAKFIRE_EXPORT int pakfire_db_open(struct pakfire_db** db, Pakfire pakfire, int
        if (r)
                goto END;
 
+       // Check for compatible architecture
+       r = pakfire_db_check_arch(o);
+       if (r)
+               goto END;
+
        *db = o;
        r = 0;
 
@@ -497,19 +733,16 @@ END:
        if (r)
                pakfire_db_free(o);
 
-       if (path)
-               free(path);
-
        return r;
 }
 
-PAKFIRE_EXPORT struct pakfire_db* pakfire_db_ref(struct pakfire_db* db) {
+struct pakfire_db* pakfire_db_ref(struct pakfire_db* db) {
        db->nrefs++;
 
        return db;
 }
 
-PAKFIRE_EXPORT struct pakfire_db* pakfire_db_unref(struct pakfire_db* db) {
+struct pakfire_db* pakfire_db_unref(struct pakfire_db* db) {
        if (--db->nrefs > 0)
                return db;
 
@@ -524,7 +757,7 @@ static unsigned long pakfire_db_integrity_check(struct pakfire_db* db) {
 
        r = sqlite3_prepare_v2(db->handle, "PRAGMA integrity_check", -1, &stmt, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not prepare integrity check: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare integrity check: %s\n",
                        sqlite3_errmsg(db->handle));
                return 1;
        }
@@ -548,7 +781,7 @@ static unsigned long pakfire_db_integrity_check(struct pakfire_db* db) {
                        errors++;
 
                        // Log the message
-                       ERROR(db->pakfire, "%s\n", error);
+                       CTX_ERROR(db->ctx, "%s\n", error);
 
                // Break on anything else
                } else
@@ -558,9 +791,9 @@ static unsigned long pakfire_db_integrity_check(struct pakfire_db* db) {
        sqlite3_finalize(stmt);
 
        if (errors)
-               ERROR(db->pakfire, "Database integrity check failed\n");
+               CTX_ERROR(db->ctx, "Database integrity check failed\n");
        else
-               INFO(db->pakfire, "Database integrity check passed\n");
+               CTX_INFO(db->ctx, "Database integrity check passed\n");
 
        return errors;
 }
@@ -571,7 +804,7 @@ static unsigned long pakfire_db_foreign_key_check(struct pakfire_db* db) {
 
        r = sqlite3_prepare_v2(db->handle, "PRAGMA foreign_key_check", -1, &stmt, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not prepare foreign key check: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare foreign key check: %s\n",
                        sqlite3_errmsg(db->handle));
                return 1;
        }
@@ -594,7 +827,7 @@ static unsigned long pakfire_db_foreign_key_check(struct pakfire_db* db) {
                        errors++;
 
                        // Log the message
-                       ERROR(db->pakfire, "Foreign key violation found in %s, row %lu: "
+                       CTX_ERROR(db->ctx, "Foreign key violation found in %s, row %lu: "
                                "%lu does not exist in table %s\n", table, rowid, foreign_rowid, foreign_table);
 
                // Break on anything else
@@ -605,9 +838,9 @@ static unsigned long pakfire_db_foreign_key_check(struct pakfire_db* db) {
        sqlite3_finalize(stmt);
 
        if (errors)
-               ERROR(db->pakfire, "Foreign key check failed\n");
+               CTX_ERROR(db->ctx, "Foreign key check failed\n");
        else
-               INFO(db->pakfire, "Foreign key check passed\n");
+               CTX_INFO(db->ctx, "Foreign key check passed\n");
 
        return errors;
 }
@@ -615,7 +848,7 @@ static unsigned long pakfire_db_foreign_key_check(struct pakfire_db* db) {
 /*
        This function performs an integrity check of the database
 */
-PAKFIRE_EXPORT int pakfire_db_check(struct pakfire_db* db) {
+int pakfire_db_check(struct pakfire_db* db) {
        int r;
 
        // Perform integrity check
@@ -632,13 +865,13 @@ PAKFIRE_EXPORT int pakfire_db_check(struct pakfire_db* db) {
 }
 
 // Returns the number of packages installed
-PAKFIRE_EXPORT ssize_t pakfire_db_packages(struct pakfire_db* db) {
+ssize_t pakfire_db_packages(struct pakfire_db* db) {
        sqlite3_stmt* stmt = NULL;
        ssize_t packages = -1;
 
        int r = sqlite3_prepare_v2(db->handle, "SELECT COUNT(*) FROM packages", -1, &stmt, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not prepare SQL statement: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s\n",
                        sqlite3_errmsg(db->handle));
                return -1;
        }
@@ -657,8 +890,19 @@ PAKFIRE_EXPORT ssize_t pakfire_db_packages(struct pakfire_db* db) {
        return packages;
 }
 
-static int pakfire_db_add_dependencies(struct pakfire_db* db, unsigned long id, PakfirePackage pkg) {
+static void pakfire_db_add_userinstalled(struct pakfire* pakfire, const char* name) {
+       Pool* pool = pakfire_get_solv_pool(pakfire);
+
+       // Convert name to ID
+       Id id = pool_str2id(pool, name, 1);
+
+       // Append it to pooljobs
+       queue_push2(&pool->pooljobs, SOLVER_USERINSTALLED|SOLVER_SOLVABLE_NAME, id);
+}
+
+static int pakfire_db_add_dependencies(struct pakfire_db* db, unsigned long id, struct pakfire_package* pkg) {
        sqlite3_stmt* stmt = NULL;
+       char** list = NULL;
        int r = 1;
 
        const char* sql = "INSERT INTO dependencies(pkg, type, dependency) VALUES(?, ?, ?)";
@@ -666,66 +910,40 @@ static int pakfire_db_add_dependencies(struct pakfire_db* db, unsigned long id,
        // Prepare the statement
        r = sqlite3_prepare_v2(db->handle, sql, -1, &stmt, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s: %s\n",
                        sql, sqlite3_errmsg(db->handle));
                goto END;
        }
 
-       const struct __relation {
-               const char* type;
-               PakfireRelationList (*func)(PakfirePackage);
-       } relations[] = {
-               { "provides", pakfire_package_get_provides },
-               { "prerequires", pakfire_package_get_prerequires },
-               { "requires", pakfire_package_get_requires },
-               { "conflicts", pakfire_package_get_conflicts },
-               { "obsoletes", pakfire_package_get_obsoletes },
-               { "recommends", pakfire_package_get_recommends },
-               { "suggests", pakfire_package_get_suggests },
-               { NULL, NULL },
-       };
-
-       for (const struct __relation* relation = relations; relation->type; relation++) {
-               PakfireRelationList list = relation->func(pkg);
-               if (!list)
-                       continue;
-
-               for (unsigned int i = 0; i < pakfire_relationlist_size(list); i++) {
-                       PakfireRelation rel = pakfire_relationlist_get(list, i);
-                       if (!rel)
-                               goto END;
-
-                       char* dependency = pakfire_relation_str(rel);
-                       if (!dependency) {
-                               pakfire_relation_unref(rel);
-                               r = 1;
-                               goto END;
-                       }
+       for (const struct pakfire_dep* dep = pakfire_deps; dep->key; dep++) {
+               list = pakfire_package_get_deps(pkg, dep->key);
+               if (!list) {
+                       r = 1;
+                       goto END;
+               }
 
+               for (char** d = list; *d; d++) {
                        // Bind package ID
                        r = sqlite3_bind_int64(stmt, 1, id);
                        if (r) {
-                               ERROR(db->pakfire, "Could not bind id: %s\n",
+                               CTX_ERROR(db->ctx, "Could not bind id: %s\n",
                                        sqlite3_errmsg(db->handle));
-                               pakfire_relation_unref(rel);
                                goto END;
                        }
 
                        // Bind type
-                       r = sqlite3_bind_text(stmt, 2, relation->type, -1, NULL);
+                       r = sqlite3_bind_text(stmt, 2, dep->name, -1, NULL);
                        if (r) {
-                               ERROR(db->pakfire, "Could not bind type: %s\n",
+                               CTX_ERROR(db->ctx, "Could not bind type: %s\n",
                                        sqlite3_errmsg(db->handle));
-                               pakfire_relation_unref(rel);
                                goto END;
                        }
 
                        // Bind dependency
-                       r = sqlite3_bind_text(stmt, 3, dependency, -1, NULL);
+                       r = sqlite3_bind_text(stmt, 3, *d, -1, NULL);
                        if (r) {
-                               ERROR(db->pakfire, "Could not bind dependency: %s\n",
+                               CTX_ERROR(db->ctx, "Could not bind dependency: %s\n",
                                        sqlite3_errmsg(db->handle));
-                               pakfire_relation_unref(rel);
                                goto END;
                        }
 
@@ -734,14 +952,9 @@ static int pakfire_db_add_dependencies(struct pakfire_db* db, unsigned long id,
                                r = sqlite3_step(stmt);
                        } while (r == SQLITE_BUSY);
 
-                       pakfire_relation_unref(rel);
-                       free(dependency);
-
                        // Reset bound values
                        sqlite3_reset(stmt);
                }
-
-               pakfire_relationlist_unref(list);
        }
 
        // All okay
@@ -750,53 +963,97 @@ static int pakfire_db_add_dependencies(struct pakfire_db* db, unsigned long id,
 END:
        if (stmt)
                sqlite3_finalize(stmt);
+       if (list) {
+               for (char** dep = list; *dep; dep++)
+                       free(*dep);
+               free(list);
+       }
 
        return r;
 }
 
-static int pakfire_db_add_files(struct pakfire_db* db, unsigned long id, PakfireArchive archive) {
+static int pakfire_db_bind_digest(struct pakfire_db* db, sqlite3_stmt* stmt, const int field,
+               struct pakfire_file* file, const enum pakfire_digest_types type) {
+       const unsigned char* digest = NULL;
+       size_t length = 0;
+
+       // Fetch the digest
+       digest = pakfire_file_get_digest(file, type, &length);
+
+       // If this digest isn't set, just bind NULL
+       if (!digest)
+               return sqlite3_bind_null(stmt, field);
+
+       // Otherwise bind the data blob
+       return sqlite3_bind_blob(stmt, field, digest, length, NULL);
+}
+
+static int pakfire_db_add_files(struct pakfire_db* db, unsigned long id, struct pakfire_archive* archive) {
        sqlite3_stmt* stmt = NULL;
        int r = 1;
 
        // Get the filelist from the archive
-       PakfireFilelist filelist = pakfire_archive_get_filelist(archive);
+       struct pakfire_filelist* filelist = pakfire_archive_get_filelist(archive);
        if (!filelist) {
-               ERROR(db->pakfire, "Could not fetch filelist from archive\n");
+               CTX_ERROR(db->ctx, "Could not fetch filelist from archive\n");
                return 1;
        }
 
        // Nothing to do if the list is empty
-       if (pakfire_filelist_is_empty(filelist))
+       if (pakfire_filelist_is_empty(filelist)) {
+               r = 0;
                goto END;
+       }
 
-       const char* sql = "INSERT INTO files(pkg, name, size, type, config, datafile, mode, "
-               "user, 'group', hash1, mtime, capabilities) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+       const char* sql =
+               "INSERT INTO "
+               "       files("
+                               "pkg, "
+                               "path, "
+                               "size, "
+                               "config, "
+                               "datafile, "
+                               "mode, "
+                               "uname, "
+                               "gname, "
+                               "ctime, "
+                               "mtime, "
+                               "mimetype, "
+                               "capabilities, "
+                               "digest_sha2_512, "
+                               "digest_sha2_256, "
+                               "digest_blake2b512, "
+                               "digest_blake2s256, "
+                               "digest_sha3_512, "
+                               "digest_sha3_256"
+                       ") "
+                       "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
 
        // Prepare the statement
-       r = sqlite3_prepare_v2(db->handle, sql, -1, &stmt, NULL);
+       r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s: %s\n",
                        sql, sqlite3_errmsg(db->handle));
                goto END;
        }
 
-       for (unsigned int i = 0; i < pakfire_filelist_size(filelist); i++) {
-               PakfireFile file = pakfire_filelist_get(filelist, i);
+       for (unsigned int i = 0; i < pakfire_filelist_length(filelist); i++) {
+               struct pakfire_file* file = pakfire_filelist_get(filelist, i);
 
                // Bind package ID
                r = sqlite3_bind_int64(stmt, 1, id);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind id: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind id: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
 
                // Bind name
-               const char* name = pakfire_file_get_name(file);
+               const char* path = pakfire_file_get_path(file);
 
-               r = sqlite3_bind_text(stmt, 2, name, -1, NULL);
+               r = sqlite3_bind_text(stmt, 2, path, -1, NULL);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind name: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind path: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
@@ -806,33 +1063,23 @@ static int pakfire_db_add_files(struct pakfire_db* db, unsigned long id, Pakfire
 
                r = sqlite3_bind_int64(stmt, 3, size);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind size: %s\n", sqlite3_errmsg(db->handle));
-                       pakfire_file_unref(file);
-                       goto END;
-               }
-
-               // Bind type - XXX this is char which isn't very helpful
-               //char type = pakfire_file_get_type(file);
-
-               r = sqlite3_bind_null(stmt, 4);
-               if (r) {
-                       ERROR(db->pakfire, "Could not bind type: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind size: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
 
                // Bind config - XXX TODO
-               r = sqlite3_bind_null(stmt, 5);
+               r = sqlite3_bind_null(stmt, 4);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind config: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind config: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
 
                // Bind datafile - XXX TODO
-               r = sqlite3_bind_null(stmt, 6);
+               r = sqlite3_bind_null(stmt, 5);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind datafile: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind datafile: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
@@ -840,57 +1087,132 @@ static int pakfire_db_add_files(struct pakfire_db* db, unsigned long id, Pakfire
                // Bind mode
                mode_t mode = pakfire_file_get_mode(file);
 
-               r = sqlite3_bind_int64(stmt, 7, mode);
+               r = sqlite3_bind_int64(stmt, 6, mode);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind mode: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind mode: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
 
-               // Bind user
-               const char* user = pakfire_file_get_user(file);
+               // Bind uname
+               const char* uname = pakfire_file_get_uname(file);
 
-               r = sqlite3_bind_text(stmt, 8, user, -1, NULL);
+               r = sqlite3_bind_text(stmt, 7, uname, -1, NULL);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind user: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind uname: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
 
-               // Bind group
-               const char* group = pakfire_file_get_group(file);
+               // Bind gname
+               const char* gname = pakfire_file_get_gname(file);
 
-               r = sqlite3_bind_text(stmt, 9, group, -1, NULL);
+               r = sqlite3_bind_text(stmt, 8, gname, -1, NULL);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind group: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind gname: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
 
-               // Bind hash1
-               const char* chksum = pakfire_file_get_chksum(file);
+               // Bind ctime
+               time_t ctime = pakfire_file_get_ctime(file);
 
-               r = sqlite3_bind_text(stmt, 10, chksum, -1, NULL);
+               r = sqlite3_bind_int64(stmt, 9, ctime);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind hash1: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind ctime: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
 
                // Bind mtime
-               time_t mtime = pakfire_file_get_time(file);
+               time_t mtime = pakfire_file_get_mtime(file);
 
-               r = sqlite3_bind_int64(stmt, 11, mtime);
+               r = sqlite3_bind_int64(stmt, 10, mtime);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind mtime: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind mtime: %s\n", sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
 
-               // Bind capabilities - XXX TODO
-               r = sqlite3_bind_null(stmt, 12);
+               // Bind MIME type
+               const char* mimetype = pakfire_file_get_mimetype(file);
+
+               if (mimetype) {
+                       r = sqlite3_bind_text(stmt, 11, mimetype, -1, NULL);
+                       if (r) {
+                               CTX_ERROR(db->ctx, "Could not bind MIME type: %s\n",
+                                       sqlite3_errmsg(db->handle));
+                               pakfire_file_unref(file);
+                               goto END;
+                       }
+               } else {
+                       r = sqlite3_bind_null(stmt, 11);
+                       if (r) {
+                               CTX_ERROR(db->ctx, "Could not bind an empty MIME type: %s\n",
+                                       sqlite3_errmsg(db->handle));
+                               pakfire_file_unref(file);
+                               goto END;
+                       }
+               }
+
+               // Bind capabilities - XXX TODO
+               r = sqlite3_bind_null(stmt, 12);
+               if (r) {
+                       CTX_ERROR(db->ctx, "Could not bind capabilities: %s\n", sqlite3_errmsg(db->handle));
+                       pakfire_file_unref(file);
+                       goto END;
+               }
+
+               // SHA2-512 Digest
+               r = pakfire_db_bind_digest(db, stmt, 13, file, PAKFIRE_DIGEST_SHA2_512);
+               if (r) {
+                       CTX_ERROR(db->ctx, "Could not bind SHA2-512 digest: %s\n",
+                               sqlite3_errmsg(db->handle));
+                       pakfire_file_unref(file);
+                       goto END;
+               }
+
+               // SHA2-256 Digest
+               r = pakfire_db_bind_digest(db, stmt, 14, file, PAKFIRE_DIGEST_SHA2_256);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind capabilities: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind SHA2-256 digest: %s\n",
+                               sqlite3_errmsg(db->handle));
+                       pakfire_file_unref(file);
+                       goto END;
+               }
+
+               // BLAKE2b512 Digest
+               r = pakfire_db_bind_digest(db, stmt, 15, file, PAKFIRE_DIGEST_BLAKE2B512);
+               if (r) {
+                       CTX_ERROR(db->ctx, "Could not bind BLAKE2b512 digest: %s\n",
+                               sqlite3_errmsg(db->handle));
+                       pakfire_file_unref(file);
+                       goto END;
+               }
+
+               // BLAKE2s256 Digest
+               r = pakfire_db_bind_digest(db, stmt, 16, file, PAKFIRE_DIGEST_BLAKE2S256);
+               if (r) {
+                       CTX_ERROR(db->ctx, "Could not bind BLAKE2s256 digest: %s\n",
+                               sqlite3_errmsg(db->handle));
+                       pakfire_file_unref(file);
+                       goto END;
+               }
+
+               // SHA3-512 Digest
+               r = pakfire_db_bind_digest(db, stmt, 17, file, PAKFIRE_DIGEST_SHA3_512);
+               if (r) {
+                       CTX_ERROR(db->ctx, "Could not bind SHA3-512 digest: %s\n",
+                               sqlite3_errmsg(db->handle));
+                       pakfire_file_unref(file);
+                       goto END;
+               }
+
+               // SHA3-256 Digest
+               r = pakfire_db_bind_digest(db, stmt, 18, file, PAKFIRE_DIGEST_SHA3_256);
+               if (r) {
+                       CTX_ERROR(db->ctx, "Could not bind SHA3-256 digest: %s\n",
+                               sqlite3_errmsg(db->handle));
                        pakfire_file_unref(file);
                        goto END;
                }
@@ -900,6 +1222,14 @@ static int pakfire_db_add_files(struct pakfire_db* db, unsigned long id, Pakfire
                        r = sqlite3_step(stmt);
                } while (r == SQLITE_BUSY);
 
+               // Check for errors
+               if (r != SQLITE_DONE) {
+                       CTX_ERROR(db->ctx, "Could not add file to database: %s\n",
+                               sqlite3_errmsg(db->handle));
+                       pakfire_file_unref(file);
+                       goto END;
+               }
+
                // Move on to next file
                pakfire_file_unref(file);
 
@@ -913,57 +1243,55 @@ static int pakfire_db_add_files(struct pakfire_db* db, unsigned long id, Pakfire
 END:
        if (stmt)
                sqlite3_finalize(stmt);
-
        pakfire_filelist_unref(filelist);
 
        return r;
 }
 
-static int pakfire_db_add_scriptlets(struct pakfire_db* db, unsigned long id, PakfireArchive archive) {
+static int pakfire_db_add_scriptlets(struct pakfire_db* db, unsigned long id, struct pakfire_archive* archive) {
        sqlite3_stmt* stmt = NULL;
+       size_t size;
        int r = 1;
 
-       struct pakfire_scriptlet_type* scriptlet_type = PAKFIRE_SCRIPTLET_TYPES;
-       struct pakfire_scriptlet* scriptlet;
-
        const char* sql = "INSERT INTO scriptlets(pkg, type, scriptlet) VALUES(?, ?, ?)";
 
        // Prepare the statement
        r = sqlite3_prepare_v2(db->handle, sql, -1, &stmt, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s: %s\n",
                        sql, sqlite3_errmsg(db->handle));
                goto END;
        }
 
-       while (scriptlet_type->type) {
+       for (const char** type = pakfire_scriptlet_types; *type; type++) {
                // Fetch the scriptlet
-               scriptlet = pakfire_archive_get_scriptlet(archive, scriptlet_type->type);
-
-               // Go to next one if the archive does not have a scriptlet of the given type
-               if (!scriptlet) {
-                       scriptlet_type++;
+               struct pakfire_scriptlet* scriptlet = pakfire_archive_get_scriptlet(archive, *type);
+               if (!scriptlet)
                        continue;
-               }
 
                // Bind package ID
                r = sqlite3_bind_int64(stmt, 1, id);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind id: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind id: %s\n", sqlite3_errmsg(db->handle));
+                       pakfire_scriptlet_unref(scriptlet);
                        goto END;
                }
 
                // Bind handle
-               r = sqlite3_bind_text(stmt, 2, scriptlet_type->handle, -1, NULL);
+               r = sqlite3_bind_text(stmt, 2, *type, -1, NULL);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind type: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind type: %s\n", sqlite3_errmsg(db->handle));
+                       pakfire_scriptlet_unref(scriptlet);
                        goto END;
                }
 
+               const char* data = pakfire_scriptlet_get_data(scriptlet, &size);
+
                // Bind scriptlet
-               r = sqlite3_bind_text(stmt, 3, scriptlet->data, scriptlet->size, NULL);
+               r = sqlite3_bind_text(stmt, 3, data, size, NULL);
                if (r) {
-                       ERROR(db->pakfire, "Could not bind scriptlet: %s\n", sqlite3_errmsg(db->handle));
+                       CTX_ERROR(db->ctx, "Could not bind scriptlet: %s\n", sqlite3_errmsg(db->handle));
+                       pakfire_scriptlet_unref(scriptlet);
                        goto END;
                }
 
@@ -972,10 +1300,18 @@ static int pakfire_db_add_scriptlets(struct pakfire_db* db, unsigned long id, Pa
                        r = sqlite3_step(stmt);
                } while (r == SQLITE_BUSY);
 
+               // Check for errors
+               if (r != SQLITE_DONE) {
+                       CTX_ERROR(db->ctx, "Could not add scriptlet to database: %s\n",
+                               sqlite3_errmsg(db->handle));
+                       pakfire_scriptlet_unref(scriptlet);
+                       goto END;
+               }
+
+               pakfire_scriptlet_unref(scriptlet);
+
                // Reset bound values
                sqlite3_reset(stmt);
-
-               scriptlet_type++;
        }
 
        // All okay
@@ -988,203 +1324,315 @@ END:
        return r;
 }
 
-PAKFIRE_EXPORT int pakfire_db_add_package(struct pakfire_db* db,
-               PakfirePackage pkg, PakfireArchive archive) {
+int pakfire_db_add_package(struct pakfire_db* db,
+               struct pakfire_package* pkg, struct pakfire_archive* archive, int userinstalled) {
        sqlite3_stmt* stmt = NULL;
        int r;
 
+       char** groups = NULL;
+       char* __groups = NULL;
+
        // Begin a new transaction
        r = pakfire_db_begin_transaction(db);
        if (r)
-               goto ROLLBACK;
+               goto ERROR;
 
-       const char* sql = "INSERT INTO packages(name, epoch, version, release, arch, groups, "
-               "filename, size, inst_size, hash1, license, summary, description, uuid, vendor, "
-               "build_host, build_time, installed, repository, reason) VALUES(?, ?, "
-               "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?, ?)";
+       const char* sql = "INSERT INTO "
+               "packages("
+                       "name, "
+                       "evr, "
+                       "arch, "
+                       "groups, "
+                       "filename, "
+                       "size, "
+                       "inst_size, "
+                       "digest_type, "
+                       "digest, "
+                       "license, "
+                       "summary, "
+                       "description, "
+                       "uuid, "
+                       "vendor, "
+                       "build_host, "
+                       "build_time, "
+                       "installed, "
+                       "repository, "
+                       "userinstalled, "
+                       "source_name, "
+                       "source_evr, "
+                       "source_arch, "
+                       "distribution"
+               ") VALUES("
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "CURRENT_TIMESTAMP, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?, "
+                       "?"
+               ")";
 
        // Prepare the statement
        r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
        if (r != SQLITE_OK) {
-               ERROR(db->pakfire, "Could not prepare SQL statement: %s: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s: %s\n",
                        sql, sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               goto ERROR;
        }
 
        // Bind name
-       const char* name = pakfire_package_get_name(pkg);
+       const char* name = pakfire_package_get_string(pkg, PAKFIRE_PKG_NAME);
 
        r = sqlite3_bind_text(stmt, 1, name, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind name: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
-       }
-
-       // Bind epoch
-       unsigned long epoch = pakfire_package_get_epoch(pkg);
-
-       r = sqlite3_bind_int64(stmt, 2, epoch);
-       if (r) {
-               ERROR(db->pakfire, "Could not bind epoch: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
-       }
-
-       // Bind version
-       const char* version = pakfire_package_get_version(pkg);
-
-       r = sqlite3_bind_text(stmt, 3, version, -1, NULL);
-       if (r) {
-               ERROR(db->pakfire, "Could not bind version: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind name: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
-       // Bind release
-       const char* release = pakfire_package_get_release(pkg);
+       // Bind evr
+       const char* evr = pakfire_package_get_string(pkg, PAKFIRE_PKG_EVR);
 
-       r = sqlite3_bind_text(stmt, 4, release, -1, NULL);
+       r = sqlite3_bind_text(stmt, 2, evr, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind release: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind evr: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind arch
-       const char* arch = pakfire_package_get_arch(pkg);
+       const char* arch = pakfire_package_get_string(pkg, PAKFIRE_PKG_ARCH);
 
-       r = sqlite3_bind_text(stmt, 5, arch, -1, NULL);
+       r = sqlite3_bind_text(stmt, 3, arch, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind arch: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind arch: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind groups
-       const char* groups = pakfire_package_get_groups(pkg);
+       groups = pakfire_package_get_strings(pkg, PAKFIRE_PKG_GROUPS);
+       if (groups) {
+               // Join everything together as SQLite doesn't support arrays
+               __groups = pakfire_string_join(groups, " ");
+               if (!__groups) {
+                       r = 1;
+                       goto ERROR;
+               }
 
-       r = sqlite3_bind_text(stmt, 6, groups, -1, NULL);
-       if (r) {
-               ERROR(db->pakfire, "Could not bind groups: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               r = sqlite3_bind_text(stmt, 4, __groups, -1, NULL);
+               if (r) {
+                       CTX_ERROR(db->ctx, "Could not bind groups: %s\n", sqlite3_errmsg(db->handle));
+                       goto ERROR;
+               }
+
+       // No groups
+       } else {
+               r = sqlite3_bind_null(stmt, 4);
+               if (r)
+                       goto ERROR;
        }
 
        // Bind filename
-       const char* filename = pakfire_package_get_filename(pkg);
+       const char* filename = pakfire_package_get_string(pkg, PAKFIRE_PKG_FILENAME);
 
-       r = sqlite3_bind_text(stmt, 7, filename, -1, NULL);
+       r = sqlite3_bind_text(stmt, 5, filename, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind filename: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind filename: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind size
-       unsigned long long size = pakfire_package_get_downloadsize(pkg);
+       unsigned long long size = pakfire_package_get_num(pkg, PAKFIRE_PKG_DOWNLOADSIZE, 0);
 
-       r = sqlite3_bind_int64(stmt, 8, size);
+       r = sqlite3_bind_int64(stmt, 6, size);
        if (r) {
-               ERROR(db->pakfire, "Could not bind size: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind size: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind installed size
-       unsigned long long inst_size = pakfire_package_get_installsize(pkg);
+       unsigned long long inst_size = pakfire_package_get_num(pkg, PAKFIRE_PKG_INSTALLSIZE, 0);
 
-       r = sqlite3_bind_int64(stmt, 9, inst_size);
+       r = sqlite3_bind_int64(stmt, 7, inst_size);
        if (r) {
-               ERROR(db->pakfire, "Could not bind inst_size: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind inst_size: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       const unsigned char* digest = NULL;
+       enum pakfire_digest_types digest_type = 0;
+       size_t digest_length = 0;
+
+       // Fetch the digest
+       digest = pakfire_package_get_digest(pkg, &digest_type, &digest_length);
+       if (!digest) {
+               CTX_ERROR(db->ctx, "Could not fetch the package's digest: %m\n");
+               r = 1;
+               goto ERROR;
        }
 
-       // Bind hash1
-       const char* hash1 = pakfire_package_get_checksum(pkg);
+       // Set the digest type
+       r = sqlite3_bind_int64(stmt, 8, digest_type);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not bind digest type: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
 
-       r = sqlite3_bind_text(stmt, 10, hash1, -1, NULL);
+       // Set the digest
+       r = sqlite3_bind_blob(stmt, 9, digest, digest_length, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind hash1: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind digest: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind license
-       const char* license = pakfire_package_get_license(pkg);
+       const char* license = pakfire_package_get_string(pkg, PAKFIRE_PKG_LICENSE);
 
-       r = sqlite3_bind_text(stmt, 11, license, -1, NULL);
+       r = sqlite3_bind_text(stmt, 10, license, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind license: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind license: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind summary
-       const char* summary = pakfire_package_get_summary(pkg);
+       const char* summary = pakfire_package_get_string(pkg, PAKFIRE_PKG_SUMMARY);
 
-       r = sqlite3_bind_text(stmt, 12, summary, -1, NULL);
+       r = sqlite3_bind_text(stmt, 11, summary, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind summary: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind summary: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind description
-       const char* description = pakfire_package_get_description(pkg);
+       const char* description = pakfire_package_get_string(pkg, PAKFIRE_PKG_DESCRIPTION);
 
-       r = sqlite3_bind_text(stmt, 13, description, -1, NULL);
+       r = sqlite3_bind_text(stmt, 12, description, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind description: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind description: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind uuid
-       const char* uuid = pakfire_package_get_uuid(pkg);
+       const char* uuid = pakfire_package_get_string(pkg, PAKFIRE_PKG_UUID);
 
-       r = sqlite3_bind_text(stmt, 14, uuid, -1, NULL);
+       r = sqlite3_bind_text(stmt, 13, uuid, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind uuid: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind uuid: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind vendor
-       const char* vendor = pakfire_package_get_vendor(pkg);
+       const char* vendor = pakfire_package_get_string(pkg, PAKFIRE_PKG_VENDOR);
 
-       r = sqlite3_bind_text(stmt, 15, vendor, -1, NULL);
+       r = sqlite3_bind_text(stmt, 14, vendor, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind vendor: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind vendor: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind build_host
-       const char* buildhost = pakfire_package_get_buildhost(pkg);
+       const char* build_host = pakfire_package_get_string(pkg, PAKFIRE_PKG_BUILD_HOST);
 
-       r = sqlite3_bind_text(stmt, 16, buildhost, -1, NULL);
+       r = sqlite3_bind_text(stmt, 15, build_host, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind build_host: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind build_host: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind build_time
-       unsigned long long build_time = pakfire_package_get_buildtime(pkg);
+       time_t build_time = pakfire_package_get_num(pkg, PAKFIRE_PKG_BUILD_TIME, 0);
 
-       r = sqlite3_bind_int64(stmt, 17, build_time);
+       r = sqlite3_bind_int64(stmt, 16, build_time);
        if (r) {
-               ERROR(db->pakfire, "Could not bind build_time: %s\n", sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               CTX_ERROR(db->ctx, "Could not bind build_time: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Bind repository name
-       PakfireRepo repo = pakfire_package_get_repo(pkg);
+       struct pakfire_repo* repo = pakfire_package_get_repo(pkg);
        if (repo) {
                const char* repo_name = pakfire_repo_get_name(repo);
                pakfire_repo_unref(repo);
 
-               r = sqlite3_bind_text(stmt, 18, repo_name, -1, NULL);
+               r = sqlite3_bind_text(stmt, 17, repo_name, -1, NULL);
                if (r)
-                       goto ROLLBACK;
+                       goto ERROR;
 
        // No repository?
        } else {
-               r = sqlite3_bind_null(stmt, 18);
+               r = sqlite3_bind_null(stmt, 17);
                if (r)
-                       goto ROLLBACK;
+                       goto ERROR;
        }
 
-       // XXX TODO Bind reason
-       r = sqlite3_bind_null(stmt, 19);
-       if (r)
-               goto ROLLBACK;
+       // installed by the user?
+       r = sqlite3_bind_int(stmt, 18, userinstalled);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not bind userinstalled: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // Source package name
+       const char* source_name = pakfire_package_get_string(pkg, PAKFIRE_PKG_SOURCE_NAME);
+       if (source_name) {
+               r = sqlite3_bind_text(stmt, 19, source_name, -1, NULL);
+               if (r)
+                       goto ERROR;
+       } else {
+               r = sqlite3_bind_null(stmt, 19);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Source EVR
+       const char* source_evr = pakfire_package_get_string(pkg, PAKFIRE_PKG_SOURCE_EVR);
+       if (source_evr) {
+               r = sqlite3_bind_text(stmt, 20, source_evr, -1, NULL);
+               if (r)
+                       goto ERROR;
+       } else {
+               r = sqlite3_bind_null(stmt, 20);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Source arch
+       const char* source_arch = pakfire_package_get_string(pkg, PAKFIRE_PKG_SOURCE_ARCH);
+       if (source_arch) {
+               r = sqlite3_bind_text(stmt, 21, source_arch, -1, NULL);
+               if (r)
+                       goto ERROR;
+       } else {
+               r = sqlite3_bind_null(stmt, 21);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Distribution
+       const char* distribution = pakfire_package_get_string(pkg, PAKFIRE_PKG_DISTRO);
+       if (distribution) {
+               r = sqlite3_bind_text(stmt, 22, distribution, -1, NULL);
+               if (r)
+                       goto ERROR;
+       } else {
+               r = sqlite3_bind_null(stmt, 22);
+               if (r)
+                       goto ERROR;
+       }
 
        // Run query
        do {
@@ -1192,65 +1640,68 @@ PAKFIRE_EXPORT int pakfire_db_add_package(struct pakfire_db* db,
        } while (r == SQLITE_BUSY);
 
        if (r != SQLITE_DONE) {
-               ERROR(db->pakfire, "Could not add package to database: %s\n",
+               CTX_ERROR(db->ctx, "Could not add package to database: %s\n",
                        sqlite3_errmsg(db->handle));
-               goto ROLLBACK;
+               r = 1;
+               goto ERROR;
        }
 
        // Save package ID
        unsigned long packages_id = sqlite3_last_insert_rowid(db->handle);
 
-       // This is done
-       r = sqlite3_finalize(stmt);
-       if (r == SQLITE_OK)
-               stmt = NULL;
-
        // Add dependencies
        r = pakfire_db_add_dependencies(db, packages_id, pkg);
        if (r)
-               goto ROLLBACK;
+               goto ERROR;
 
        // Add files
        r = pakfire_db_add_files(db, packages_id, archive);
        if (r)
-               goto ROLLBACK;
+               goto ERROR;
 
        // Add scriptlets
        r = pakfire_db_add_scriptlets(db, packages_id, archive);
        if (r)
-               goto ROLLBACK;
-
-       // All done, commit!
-       r = pakfire_db_commit(db);
-       if (r)
-               goto ROLLBACK;
-
-       return 0;
+               goto ERROR;
 
-ROLLBACK:
+ERROR:
+       if (groups)
+               pakfire_strings_free(groups);
+       if (__groups)
+               free(__groups);
        if (stmt)
                sqlite3_finalize(stmt);
 
-       pakfire_db_rollback(db);
+       // Commit or rollback
+       if (r)
+               pakfire_db_rollback(db);
+       else
+               r = pakfire_db_commit(db);
 
-       return 1;
+       return r;
 }
 
-PAKFIRE_EXPORT int pakfire_db_remove_package(struct pakfire_db* db, PakfirePackage pkg) {
+int pakfire_db_remove_package(struct pakfire_db* db, struct pakfire_package* pkg) {
        sqlite3_stmt* stmt = NULL;
        int r = 1;
 
+       // Begin a new transaction
+       r = pakfire_db_begin_transaction(db);
+       if (r)
+               goto ERROR;
+
        // Fetch the package's UUID
-       const char* uuid = pakfire_package_get_uuid(pkg);
+       const char* uuid = pakfire_package_get_string(pkg, PAKFIRE_PKG_UUID);
        if (!uuid) {
-               ERROR(db->pakfire, "Package has no UUID\n");
+               CTX_ERROR(db->ctx, "Package has no UUID\n");
+               r = 1;
                goto ERROR;
        }
 
        r = sqlite3_prepare_v2(db->handle,
                "DELETE FROM packages WHERE uuid = ?", -1, &stmt, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not prepare SQL statement: %s\n",
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s\n",
                        sqlite3_errmsg(db->handle));
                goto ERROR;
        }
@@ -1258,8 +1709,8 @@ PAKFIRE_EXPORT int pakfire_db_remove_package(struct pakfire_db* db, PakfirePacka
        // Bind UUID
        r = sqlite3_bind_text(stmt, 1, uuid, -1, NULL);
        if (r) {
-               ERROR(db->pakfire, "Could not bind uuid: %s\n", sqlite3_errmsg(db->handle));
-               return 1;
+               CTX_ERROR(db->ctx, "Could not bind uuid: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
        }
 
        // Execute query
@@ -1269,16 +1720,811 @@ PAKFIRE_EXPORT int pakfire_db_remove_package(struct pakfire_db* db, PakfirePacka
 
        // Check if we have been successful
        if (r != SQLITE_DONE) {
-               ERROR(db->pakfire, "Could not delete package %s\n", uuid);
+               CTX_ERROR(db->ctx, "Could not delete package %s\n", uuid);
                r = 1;
                goto ERROR;
        }
 
+       // All done
+       r = 0;
+
+ERROR:
+       if (stmt)
+               sqlite3_finalize(stmt);
+
+       // Commit or rollback
+       if (r)
+               pakfire_db_rollback(db);
+       else
+               r = pakfire_db_commit(db);
+
+       return r;
+}
+
+struct pakfire_scriptlet* pakfire_db_get_scriptlet(struct pakfire_db* db,
+               struct pakfire_package* pkg, const char* type) {
+       struct pakfire_scriptlet* scriptlet = NULL;
+       sqlite3_stmt* stmt = NULL;
+       int r = 1;
+
+       // Fetch the package's UUID
+       const char* uuid = pakfire_package_get_string(pkg, PAKFIRE_PKG_UUID);
+       if (!uuid) {
+               CTX_ERROR(db->ctx, "Package has no UUID\n");
+               goto ERROR;
+       }
+
+       const char* sql = "SELECT scriptlets.scriptlet FROM packages \
+               JOIN scriptlets ON packages.id = scriptlets.pkg \
+               WHERE packages.uuid = ? AND scriptlets.type = ?";
+
+       r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s %s\n",
+                       sql, sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // Bind UUID
+       r = sqlite3_bind_text(stmt, 1, uuid, -1, NULL);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not bind uuid: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       r = sqlite3_bind_text(stmt, 2, type, -1, NULL);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not bind type: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       CTX_DEBUG(db->ctx, "Searching for scriptlet for package %s of type %s\n", uuid, type);
+
+       // Execute query
+       do {
+               r = sqlite3_step(stmt);
+       } while (r == SQLITE_BUSY);
+
+       // We have some payload
+       if (r == SQLITE_ROW) {
+               const void* data = sqlite3_column_blob(stmt, 1);
+               ssize_t size = sqlite3_column_bytes(stmt, 1);
+
+               // Create a scriptlet object
+               r = pakfire_scriptlet_create(&scriptlet, db->ctx, type, data, size);
+               if (r)
+                       goto ERROR;
+       }
+
+ERROR:
+       if (stmt)
+               sqlite3_finalize(stmt);
+
+       return scriptlet;
+}
+
+static int pakfire_db_load_package(struct pakfire_db* db, struct pakfire_repo* repo, sqlite3_stmt* stmt) {
+       struct pakfire_package* pkg = NULL;
+       int r = 1;
+
+       // Name
+       const char* name = (const char*)sqlite3_column_text(stmt, 0);
+       if (!name) {
+               CTX_ERROR(db->ctx, "Could not read name: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // EVR
+       const char* evr = (const char*)sqlite3_column_text(stmt, 1);
+       if (!evr) {
+               CTX_ERROR(db->ctx, "Could not read evr: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // Arch
+       const char* arch = (const char*)sqlite3_column_text(stmt, 2);
+       if (!arch) {
+               CTX_ERROR(db->ctx, "Could not read arch: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // Create package
+       r = pakfire_package_create(&pkg, db->pakfire, repo, name, evr, arch);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not create package '%s-%s.%s': %m\n", name, evr, arch);
+               goto ERROR;
+       }
+
+       // ID
+       uint64_t id = sqlite3_column_int64(stmt, 3);
+       if (id) {
+               r = pakfire_package_set_num(pkg, PAKFIRE_PKG_DBID, id);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Groups
+       const char* groups = (const char*)sqlite3_column_text(stmt, 4);
+       if (groups) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_GROUPS, groups);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Filename
+       const char* filename = (const char*)sqlite3_column_text(stmt, 5);
+       if (filename) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_FILENAME, filename);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Size
+       size_t size = sqlite3_column_int64(stmt, 6);
+       if (size) {
+               r = pakfire_package_set_num(pkg, PAKFIRE_PKG_DOWNLOADSIZE, size);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Installed size
+       size = sqlite3_column_int64(stmt, 7);
+       if (size) {
+               r = pakfire_package_set_num(pkg, PAKFIRE_PKG_INSTALLSIZE, size);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Digest type
+       enum pakfire_digest_types digest_type = sqlite3_column_int64(stmt, 8);
+       size_t digest_length = 0;
+
+       // Digest length
+
+       // Digest
+       const unsigned char* digest = sqlite3_column_blob(stmt, 9);
+       if (digest_type && digest) {
+               pakfire_package_set_digest(pkg, digest_type, digest, digest_length);
+       }
+
+       // License
+       const char* license = (const char*)sqlite3_column_text(stmt, 10);
+       if (license) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_LICENSE, license);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Summary
+       const char* summary = (const char*)sqlite3_column_text(stmt, 11);
+       if (summary) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_SUMMARY, summary);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Description
+       const char* description = (const char*)sqlite3_column_text(stmt, 12);
+       if (description) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_DESCRIPTION, description);
+               if (r)
+                       goto ERROR;
+       }
+
+       // UUID
+       const char* uuid = (const char*)sqlite3_column_text(stmt, 13);
+       if (uuid) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_UUID, uuid);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Vendor
+       const char* vendor = (const char*)sqlite3_column_text(stmt, 14);
+       if (vendor) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_VENDOR, vendor);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Build Host
+       const char* build_host = (const char*)sqlite3_column_text(stmt, 15);
+       if (build_host) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_BUILD_HOST, build_host);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Build Time
+       time_t build_time = sqlite3_column_int64(stmt, 16);
+       if (build_time) {
+               r = pakfire_package_set_num(pkg, PAKFIRE_PKG_BUILD_TIME, build_time);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Install Time
+       time_t install_time = sqlite3_column_int64(stmt, 17);
+       if (install_time) {
+               r = pakfire_package_set_num(pkg, PAKFIRE_PKG_INSTALLTIME, install_time);
+               if (r)
+                       goto ERROR;
+       }
+
+       // installed by user?
+       int userinstalled = sqlite3_column_int(stmt, 18);
+       if (userinstalled)
+               pakfire_db_add_userinstalled(db->pakfire, name);
+
+       // Files
+       const char* files = (const char*)sqlite3_column_text(stmt, 19);
+       if (files) {
+               r = pakfire_package_set_filelist_from_string(pkg, files);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Dependencies
+
+       const struct dependency {
+               unsigned int field;
+               const enum pakfire_package_key key;
+       } dependencies[] = {
+               { 20, PAKFIRE_PKG_PROVIDES },
+               { 21, PAKFIRE_PKG_PREREQUIRES },
+               { 22, PAKFIRE_PKG_REQUIRES },
+               { 23, PAKFIRE_PKG_CONFLICTS },
+               { 24, PAKFIRE_PKG_OBSOLETES },
+               { 25, PAKFIRE_PKG_RECOMMENDS },
+               { 26, PAKFIRE_PKG_SUGGESTS },
+               { 27, PAKFIRE_PKG_SUPPLEMENTS },
+               { 28, PAKFIRE_PKG_ENHANCES },
+               { 0 },
+       };
+
+       for (const struct dependency* deps = dependencies; deps->field; deps++) {
+               const char* relations = (const char*)sqlite3_column_text(stmt, deps->field);
+               if (relations) {
+                       r = pakfire_str2deps(db->ctx, pkg, deps->key, relations);
+                       if (r)
+                               goto ERROR;
+               }
+       }
+
+       // Source package
+       const char* source_name = (const char*)sqlite3_column_text(stmt, 29);
+       if (source_name) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_SOURCE_NAME, source_name);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Source EVR
+       const char* source_evr = (const char*)sqlite3_column_text(stmt, 30);
+       if (source_evr) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_SOURCE_EVR, source_evr);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Source arch
+       const char* source_arch = (const char*)sqlite3_column_text(stmt, 31);
+       if (source_arch) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_SOURCE_ARCH, source_arch);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Distribution
+       const char* distribution = (const char*)sqlite3_column_text(stmt, 32);
+       if (distribution) {
+               r = pakfire_package_set_string(pkg, PAKFIRE_PKG_DESCRIPTION, description);
+               if (r)
+                       goto ERROR;
+       }
+
+       // Success
+       r = 0;
+
+ERROR:
+       if (pkg)
+               pakfire_package_unref(pkg);
+
+       return r;
+}
+
+int pakfire_db_load(struct pakfire_db* db, struct pakfire_repo* repo) {
+       sqlite3_stmt* stmt = NULL;
+       int r = 1;
+
+       CTX_DEBUG(db->ctx, "Loading package database...\n");
+
+       // Drop contents of the repository
+       pakfire_repo_clear(repo);
+
+       // Save starting time
+       clock_t t_start = clock();
+       clock_t t_end;
+
+       const char* sql =
+               "SELECT "
+                       "name, "
+                       "evr, "
+                       "arch, "
+                       "id, "
+                       "groups, "
+                       "filename, "
+                       "size, "
+                       "inst_size, "
+                       "digest_type, "
+                       "digest, "
+                       "license, "
+                       "summary, "
+                       "description, "
+                       "uuid, "
+                       "vendor, "
+                       "build_host, "
+                       "build_time, "
+                       "strftime('%s', installed) AS installed, "
+                       "userinstalled, "
+                       "("
+                               "SELECT group_concat(path, '\n') FROM files WHERE files.pkg = packages.id"
+                       ") AS files, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'provides'"
+                       ") AS provides, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'prerequires'"
+                       ") AS prerequires, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'requires'"
+                       ") AS requires, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'conflicts'"
+                       ") AS conflicts, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'obsoletes'"
+                       ") AS obsoletes, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'recommends'"
+                       ") AS recommends, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'suggests'"
+                       ") AS suggests, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'supplements'"
+                       ") AS supplements, "
+                       "("
+                               "SELECT group_concat(dependency, '\n') FROM dependencies d "
+                                       "WHERE d.pkg = packages.id AND d.type = 'enhances'"
+                       ") AS enhances, "
+                       "source_name, "
+                       "source_evr, "
+                       "source_arch, "
+                       "distribution "
+               "FROM "
+                       "packages"
+               ";";
+
+       // Prepare the statement
+       r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s %s\n",
+                       sql, sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       for (;;) {
+               // Execute query
+               r = sqlite3_step(stmt);
+
+               switch (r) {
+                       // Retry if the database was busy
+                       case SQLITE_BUSY:
+                               continue;
+
+                       // Read a row
+                       case SQLITE_ROW:
+                               r = pakfire_db_load_package(db, repo, stmt);
+                               if (r)
+                                       goto ERROR;
+                               break;
+
+                       // All rows have been processed
+                       case SQLITE_DONE:
+                               goto END;
+
+                       // Go to error in any other cases
+                       default:
+                               goto ERROR;
+               }
+       }
+
+END:
+       // Save time when we finished
+       t_end = clock();
+
+       CTX_DEBUG(db->ctx, "Loading package database completed in %.4fms\n",
+               (double)(t_end - t_start) * 1000 / CLOCKS_PER_SEC);
+
+       // Mark repository as changed
+       pakfire_repo_has_changed(repo);
+
+       // All done
+       r = 0;
+
+ERROR:
+       if (r) {
+               CTX_ERROR(db->ctx, "Failed reading package database: %m\n");
+               pakfire_repo_clear(repo);
+       }
+
+       if (stmt)
+               sqlite3_finalize(stmt);
+
+       return r;
+}
+
+static int pakfire_db_load_file_digest(struct pakfire_db* db, struct pakfire_file* file,
+               sqlite3_stmt* stmt, const enum pakfire_digest_types type, const int field) {
+       // Fetch digest
+       const unsigned char* digest = sqlite3_column_blob(stmt, field);
+
+       // Nothing further to do if field is NULL
+       if (!digest)
+               return 0;
+
+       // Length of the stored value
+       const size_t length = sqlite3_column_bytes(stmt, field);
+
+       // Store digest
+       return pakfire_file_set_digest(file, type, digest, length);
+}
+
+static int pakfire_db_load_file(struct pakfire_db* db, struct pakfire_filelist* filelist,
+               sqlite3_stmt* stmt) {
+       struct pakfire_file* file = NULL;
+       char abspath[PATH_MAX];
+       int r;
+
+       // Create a new file object
+       r = pakfire_file_create(&file, db->pakfire);
+       if (r)
+               goto ERROR;
+
+       // Path
+       const char* path = (const char*)sqlite3_column_text(stmt, 0);
+
+       // Abort if no path is set
+       if (!path) {
+               CTX_ERROR(db->ctx, "File has no path\n");
+               r = 1;
+               goto ERROR;
+       }
+
+       // Set path
+       r = pakfire_file_set_path(file, path);
+       if (r) {
+               CTX_ERROR(db->ctx, "%s: Could not set path '%s': %m\n", path, path);
+               goto ERROR;
+       }
+
+       // Make the absolute path
+       r = pakfire_path(db->pakfire, abspath, "%s", path);
+       if (r) {
+               CTX_ERROR(db->ctx, "%s: Could not make absolute path: %m\n", path);
+               goto ERROR;
+       }
+
+       // Set the absolute path
+       r = pakfire_file_set_abspath(file, abspath);
+       if (r) {
+               CTX_ERROR(db->ctx, "%s: Could not set absolute path %s: %m\n", path, abspath);
+               goto ERROR;
+       }
+
+       // Size
+       size_t size = sqlite3_column_int64(stmt, 1);
+       if (size)
+               pakfire_file_set_size(file, size);
+
+       // Mode
+       mode_t mode = sqlite3_column_int(stmt, 2);
+       if (mode)
+               pakfire_file_set_mode(file, mode);
+
+       // uname
+       const char* uname = (const char*)sqlite3_column_text(stmt, 3);
+
+       // Abort if no user is set
+       if (!uname) {
+               CTX_ERROR(db->ctx, "%s: No user\n", path);
+               r = 1;
+               goto ERROR;
+       }
+
+       // Set uname
+       r = pakfire_file_set_uname(file, uname);
+       if (r) {
+               CTX_ERROR(db->ctx, "%s: Could not set user '%s': %m\n", path, uname);
+               goto ERROR;
+       }
+
+       // gname
+       const char* gname = (const char*)sqlite3_column_text(stmt, 4);
+
+       // Abort if no group is set
+       if (!gname) {
+               CTX_ERROR(db->ctx, "%s: No group\n", path);
+               r = 1;
+               goto ERROR;
+       }
+
+       // Set gname
+       r = pakfire_file_set_gname(file, gname);
+       if (r) {
+               CTX_ERROR(db->ctx, "%s: Could not set group '%s': %m\n", path, gname);
+               goto ERROR;
+       }
+
+       // ctime
+       time_t ctime = sqlite3_column_int64(stmt, 5);
+       if (ctime)
+               pakfire_file_set_ctime(file, ctime);
+
+       // mtime
+       time_t mtime = sqlite3_column_int64(stmt, 6);
+       if (mtime)
+               pakfire_file_set_mtime(file, mtime);
+
+       const char* mimetype = (const char*)sqlite3_column_text(stmt, 7);
+
+       // MIME type
+       r = pakfire_file_set_mimetype(file, mimetype);
+       if (r)
+               goto ERROR;
+
+       // SHA2-512 Digest
+       r = pakfire_db_load_file_digest(db, file, stmt, PAKFIRE_DIGEST_SHA2_512, 8);
+       if (r)
+               goto ERROR;
+
+       // SHA2-256 Digest
+       r = pakfire_db_load_file_digest(db, file, stmt, PAKFIRE_DIGEST_SHA2_256, 9);
+       if (r)
+               goto ERROR;
+
+       // BLAKE2b512 Digest
+       r = pakfire_db_load_file_digest(db, file, stmt, PAKFIRE_DIGEST_BLAKE2B512, 10);
+       if (r)
+               goto ERROR;
+
+       // BLAKE2s256 Digest
+       r = pakfire_db_load_file_digest(db, file, stmt, PAKFIRE_DIGEST_BLAKE2S256, 11);
+       if (r)
+               goto ERROR;
+
+       // SHA3-512 Digest
+       r = pakfire_db_load_file_digest(db, file, stmt, PAKFIRE_DIGEST_SHA3_512, 12);
+       if (r)
+               goto ERROR;
+
+       // SHA3-256 Digest
+       r = pakfire_db_load_file_digest(db, file, stmt, PAKFIRE_DIGEST_SHA3_256, 13);
+       if (r)
+               goto ERROR;
+
+       // Append the file to the filelist
+       r = pakfire_filelist_add(filelist, file);
+       if (r)
+               goto ERROR;
+
+ERROR:
+       if (file)
+               pakfire_file_unref(file);
+
+       return r;
+}
+
+int pakfire_db_filelist(struct pakfire_db* db, struct pakfire_filelist** filelist) {
+       struct pakfire_filelist* list = NULL;
+       sqlite3_stmt* stmt = NULL;
+       int r;
+
+       const char* sql =
+               "SELECT "
+                       "path, "
+                       "size, "
+                       "mode, "
+                       "uname, "
+                       "gname, "
+                       "ctime, "
+                       "mtime, "
+                       "mimetype, "
+                       "digest_sha2_512, "
+                       "digest_sha2_256, "
+                       "digest_blake2b512, "
+                       "digest_blake2s256, "
+                       "digest_sha3_512, "
+                       "digest_sha3_256 "
+               "FROM files "
+               "ORDER BY path"
+               ";";
+
+       // Prepare the statement
+       r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s %s\n",
+                       sql, sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // Create a new filelist
+       r = pakfire_filelist_create(&list, db->pakfire);
+       if (r)
+               goto ERROR;
+
+       // Iterate over all rows
+       for (;;) {
+               // Execute query
+               r = sqlite3_step(stmt);
+
+               switch (r) {
+                       // Retry if the database was busy
+                       case SQLITE_BUSY:
+                               continue;
+
+                       // Read a row
+                       case SQLITE_ROW:
+                               r = pakfire_db_load_file(db, list, stmt);
+                               if (r)
+                                       goto ERROR;
+                               break;
+
+                       // All rows have been processed
+                       case SQLITE_DONE:
+                               r = 0;
+                               goto END;
+
+                       // Go to error in any other cases
+                       default:
+                               goto ERROR;
+               }
+       }
+
+END:
+       // Return the filelist
+       *filelist = pakfire_filelist_ref(list);
+
+ERROR:
+       if (r)
+               CTX_ERROR(db->ctx, "Could not fetch filelist: %m\n");
+
+       if (stmt)
+               sqlite3_finalize(stmt);
+       if (list)
+               pakfire_filelist_unref(list);
+
+       return r;
+}
+
+int pakfire_db_package_filelist(struct pakfire_db* db, struct pakfire_filelist** filelist,
+               struct pakfire_package* pkg) {
+       struct pakfire_filelist* fl = NULL;
+       sqlite3_stmt* stmt = NULL;
+       int r = 1;
+
+       // Fetch the package ID
+       uint64_t id = pakfire_package_get_num(pkg, PAKFIRE_PKG_DBID, 0);
+       if (!id) {
+               CTX_ERROR(db->ctx, "Package did not have an ID\n");
+               return 1;
+       }
+
+       // Create a new filelist
+       r = pakfire_filelist_create(&fl, db->pakfire);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not create filelist: %m\n");
+               goto ERROR;
+       }
+
+       const char* sql =
+               "SELECT "
+                       "path, "
+                       "size, "
+                       "mode, "
+                       "uname, "
+                       "gname, "
+                       "ctime, "
+                       "mtime, "
+                       "mimetype, "
+                       "digest_sha2_512, "
+                       "digest_sha2_256, "
+                       "digest_blake2b512, "
+                       "digest_blake2s256, "
+                       "digest_sha3_512, "
+                       "digest_sha3_256 "
+               "FROM files "
+
+               // Select all files that belong to this package
+               "WHERE "
+                       "pkg = ? "
+
+               // Filter out any files that are also in a different package (i.e. an update
+               // that has already been installed and this is the cleanup of the obsolete pkg)
+               "AND "
+                       "NOT EXISTS ("
+                               "SELECT "
+                                       "1 "
+                               "FROM files AS duplicates "
+                               "WHERE "
+                                       "files.path = duplicates.path "
+                               "AND "
+                                       "files.pkg != duplicates.pkg"
+                       ") "
+
+               // Return ordered by path
+               "ORDER BY path"
+               ";";
+
+       // Prepare the statement
+       r = sqlite3_prepare_v2(db->handle, sql, strlen(sql), &stmt, NULL);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not prepare SQL statement: %s %s\n",
+                       sql, sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       // Bind package ID
+       r = sqlite3_bind_int64(stmt, 1, id);
+       if (r) {
+               CTX_ERROR(db->ctx, "Could not bind package ID: %s\n", sqlite3_errmsg(db->handle));
+               goto ERROR;
+       }
+
+       for (;;) {
+               // Execute query
+               r = sqlite3_step(stmt);
+
+               switch (r) {
+                       // Retry if the database was busy
+                       case SQLITE_BUSY:
+                               continue;
+
+                       // Read a row
+                       case SQLITE_ROW:
+                               r = pakfire_db_load_file(db, fl, stmt);
+                               if (r)
+                                       goto ERROR;
+                               break;
+
+                       // All rows have been processed
+                       case SQLITE_DONE:
+                               goto END;
+
+                       // Go to error in any other cases
+                       default:
+                               goto ERROR;
+               }
+       }
+
+END:
+       *filelist = pakfire_filelist_ref(fl);
        r = 0;
 
 ERROR:
        if (stmt)
                sqlite3_finalize(stmt);
+       if (fl)
+               pakfire_filelist_unref(fl);
 
        return r;
 }