Adding missing file src/database.c
This commit is contained in:
parent
3050ac8230
commit
95f805f92f
316
src/database.c
Normal file
316
src/database.c
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <openmg/database/statement.h>
|
||||||
|
#include <openmg/database/migrations.h>
|
||||||
|
#include <openmg/database.h>
|
||||||
|
|
||||||
|
const char *const SQLITE_PATH_FORMAT_STRING =
|
||||||
|
"%s/.local/var/openmg.sqlite";
|
||||||
|
struct _MgDatabase {
|
||||||
|
GObject parent_instance;
|
||||||
|
sqlite3 *sqlite;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (MgDatabase, mg_database, G_TYPE_OBJECT)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MG_DATABASE_SQLITE = 1,
|
||||||
|
MG_DATABASE_N_PROPERTIES
|
||||||
|
} MgDatabaseProperties;
|
||||||
|
|
||||||
|
static GParamSpec *database_properties[MG_DATABASE_N_PROPERTIES] = { NULL, };
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_database_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec);
|
||||||
|
static void
|
||||||
|
mg_database_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec);
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_database_dispose (GObject *object);
|
||||||
|
static gboolean
|
||||||
|
mg_database_apply_single_migration (MgDatabase *self,
|
||||||
|
sqlite3 *sqlite, int migration_number);
|
||||||
|
static int
|
||||||
|
mg_database_retrieve_next_migration (MgDatabase *self, sqlite3 *sqlite);
|
||||||
|
static gboolean
|
||||||
|
mg_database_register_next_migration (MgDatabase *self, sqlite3 *sqlite,
|
||||||
|
int next_migration);
|
||||||
|
static void
|
||||||
|
mg_database_apply_migrations (MgDatabase *self, sqlite3 *sqlite);
|
||||||
|
|
||||||
|
MgDatabase *
|
||||||
|
mg_database_new (void) {
|
||||||
|
MgDatabase *self = NULL;
|
||||||
|
sqlite3 *sqlite_handle = NULL;
|
||||||
|
const char *home_dir = g_get_home_dir();
|
||||||
|
size_t database_path_len;
|
||||||
|
char *database_path;
|
||||||
|
char *database_path_directory;
|
||||||
|
|
||||||
|
database_path_len = snprintf
|
||||||
|
(NULL, 0, SQLITE_PATH_FORMAT_STRING,
|
||||||
|
home_dir);
|
||||||
|
database_path = g_malloc
|
||||||
|
(sizeof *database_path
|
||||||
|
* (database_path_len + 1));
|
||||||
|
snprintf (database_path, database_path_len,
|
||||||
|
SQLITE_PATH_FORMAT_STRING,
|
||||||
|
home_dir);
|
||||||
|
database_path_directory = g_path_get_dirname
|
||||||
|
(database_path);
|
||||||
|
// TODO: Control the possible error.
|
||||||
|
g_mkdir_with_parents
|
||||||
|
(database_path_directory, 00700);
|
||||||
|
|
||||||
|
sqlite3_open (database_path, &sqlite_handle);
|
||||||
|
g_free (database_path);
|
||||||
|
self = MG_DATABASE ((g_object_new (MG_TYPE_DATABASE,
|
||||||
|
"sqlite", sqlite_handle, NULL)));
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_database_class_init (MgDatabaseClass *class) {
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||||
|
object_class->set_property = mg_database_set_property;
|
||||||
|
object_class->get_property = mg_database_get_property;
|
||||||
|
object_class->dispose = mg_database_dispose;
|
||||||
|
database_properties[MG_DATABASE_SQLITE] = g_param_spec_pointer (
|
||||||
|
"sqlite",
|
||||||
|
"SQLite",
|
||||||
|
"SQLite handle.",
|
||||||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
|
||||||
|
g_object_class_install_properties
|
||||||
|
(object_class, MG_DATABASE_N_PROPERTIES,
|
||||||
|
database_properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_database_set_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec) {
|
||||||
|
MgDatabase *self = MG_DATABASE (object);
|
||||||
|
switch ((MgDatabaseProperties) property_id) {
|
||||||
|
case MG_DATABASE_SQLITE:
|
||||||
|
if (self->sqlite) {
|
||||||
|
sqlite3_close (self->sqlite);
|
||||||
|
}
|
||||||
|
self->sqlite = g_value_get_pointer (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_database_get_property (GObject *object,
|
||||||
|
guint property_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec) {
|
||||||
|
MgDatabase *self = MG_DATABASE (object);
|
||||||
|
switch ((MgDatabaseProperties) property_id) {
|
||||||
|
case MG_DATABASE_SQLITE:
|
||||||
|
g_value_set_pointer (value, self->sqlite);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_database_init (MgDatabase *self) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static sqlite3 *
|
||||||
|
mg_database_get_sqlite (MgDatabase *self) {
|
||||||
|
sqlite3 *sqlite;
|
||||||
|
GValue value = G_VALUE_INIT;
|
||||||
|
g_value_init (&value, G_TYPE_POINTER);
|
||||||
|
g_object_get_property (G_OBJECT (self),
|
||||||
|
"sqlite",
|
||||||
|
&value);
|
||||||
|
sqlite = g_value_get_pointer (&value);
|
||||||
|
g_value_unset (&value);
|
||||||
|
mg_database_apply_migrations (self, sqlite);
|
||||||
|
return sqlite;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_database_apply_migrations (MgDatabase *self, sqlite3 *sqlite) {
|
||||||
|
size_t migrations_len = sizeof MIGRATIONS / sizeof *MIGRATIONS;
|
||||||
|
int next_migration = mg_database_retrieve_next_migration (self, sqlite);
|
||||||
|
for (int i = next_migration; i < migrations_len; i++) {
|
||||||
|
if (!mg_database_apply_single_migration (self, sqlite, next_migration)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next_migration++;
|
||||||
|
mg_database_register_next_migration (self, sqlite, next_migration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns affected rows.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
mg_database_register_next_migration_atttempt_insert (MgDatabase *self, sqlite3 *sqlite, int next_migration) {
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
int return_value = 0;
|
||||||
|
|
||||||
|
int error = sqlite3_prepare_v2 (sqlite, "insert into options (key, value) values (?, ?);",
|
||||||
|
-1, &statement, NULL);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_register_next_migration_attempt_insert;
|
||||||
|
}
|
||||||
|
error = sqlite3_bind_text (statement, 1, "migration", -1, SQLITE_TRANSIENT);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_register_next_migration_attempt_insert;
|
||||||
|
}
|
||||||
|
error = sqlite3_bind_int (statement, 2, next_migration);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_register_next_migration_attempt_insert;
|
||||||
|
}
|
||||||
|
error = sqlite3_step (statement);
|
||||||
|
if (error != SQLITE_DONE) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_register_next_migration_attempt_insert;
|
||||||
|
}
|
||||||
|
return_value = sqlite3_changes (sqlite);
|
||||||
|
cleanup_mg_database_register_next_migration_attempt_insert:
|
||||||
|
sqlite3_finalize (statement);
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mg_database_register_next_migration_update (MgDatabase *self, sqlite3 *sqlite,
|
||||||
|
int next_migration) {
|
||||||
|
int return_value = 0;
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
int error = sqlite3_prepare_v2 (sqlite, "update options set value = ? where key = ?;",
|
||||||
|
-1, &statement, NULL);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_register_next_migration_update;
|
||||||
|
}
|
||||||
|
error = sqlite3_bind_int (statement, 1, next_migration);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_register_next_migration_update;
|
||||||
|
}
|
||||||
|
error = sqlite3_bind_text (statement, 2, "migration", -1, SQLITE_TRANSIENT);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_register_next_migration_update;
|
||||||
|
}
|
||||||
|
error = sqlite3_step (statement);
|
||||||
|
if (error != SQLITE_DONE) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_register_next_migration_update;
|
||||||
|
}
|
||||||
|
return_value = sqlite3_changes (sqlite);
|
||||||
|
cleanup_mg_database_register_next_migration_update:
|
||||||
|
sqlite3_finalize (statement);
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
mg_database_register_next_migration (MgDatabase *self, sqlite3 *sqlite, int next_migration) {
|
||||||
|
int affected_rows = mg_database_register_next_migration_atttempt_insert
|
||||||
|
(self, sqlite, next_migration);
|
||||||
|
if (affected_rows) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return !!mg_database_register_next_migration_update (self, sqlite, next_migration);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mg_database_retrieve_next_migration (MgDatabase *self, sqlite3 *sqlite) {
|
||||||
|
int next_migration = 0;
|
||||||
|
int error;
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
error = sqlite3_prepare_v2 (sqlite, "select value from options where key = ?;", -1, &statement, NULL);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_retrieve_next_migration;
|
||||||
|
}
|
||||||
|
error = sqlite3_bind_text (statement, 1, "migration", -1, SQLITE_TRANSIENT);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_retrieve_next_migration;
|
||||||
|
}
|
||||||
|
error = sqlite3_step (statement);
|
||||||
|
if (error == SQLITE_ROW) {
|
||||||
|
next_migration = sqlite3_column_int (statement, 0);
|
||||||
|
}
|
||||||
|
cleanup_mg_database_retrieve_next_migration:
|
||||||
|
sqlite3_finalize (statement);
|
||||||
|
return next_migration;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
mg_database_apply_single_migration (MgDatabase *self, sqlite3 *sqlite, int migration_number) {
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
const char *current_migration = MIGRATIONS[migration_number];
|
||||||
|
gboolean return_value = 0;
|
||||||
|
int error = sqlite3_prepare_v2 (sqlite, current_migration, -1, &statement, NULL);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_apply_single_migration;
|
||||||
|
}
|
||||||
|
error = sqlite3_step (statement);
|
||||||
|
if (error != SQLITE_DONE) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
goto cleanup_mg_database_apply_single_migration;
|
||||||
|
}
|
||||||
|
return_value = 1;
|
||||||
|
cleanup_mg_database_apply_single_migration:
|
||||||
|
if (statement) {
|
||||||
|
sqlite3_finalize (statement);
|
||||||
|
}
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
mg_database_get_error_string (MgDatabase *self) {
|
||||||
|
sqlite3 *sqlite = mg_database_get_sqlite (self);
|
||||||
|
return sqlite3_errmsg (sqlite);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_database_dispose (GObject *object) {
|
||||||
|
MgDatabase *self = MG_DATABASE (object);
|
||||||
|
sqlite3_close (self->sqlite);
|
||||||
|
}
|
||||||
|
|
||||||
|
MgDatabaseStatement *
|
||||||
|
mg_database_prepare (MgDatabase *self, char *z_sql, const char **pz_tail) {
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
sqlite3 *sqlite = mg_database_get_sqlite (self);
|
||||||
|
int error = sqlite3_prepare_v2 (sqlite, z_sql, -1, &statement, pz_tail);
|
||||||
|
if (error != SQLITE_OK) {
|
||||||
|
g_warning (sqlite3_errmsg (sqlite));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return mg_database_statement_new (self, statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mg_database_get_affected_rows (MgDatabase *self) {
|
||||||
|
sqlite3 *sqlite = mg_database_get_sqlite (self);
|
||||||
|
return sqlite3_changes (sqlite);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user