365 lines
10 KiB
C
365 lines
10 KiB
C
/******************************************************************************
|
||
* @file schema.c
|
||
* @brief Installation des versions du schéma SQLite.
|
||
******************************************************************************/
|
||
|
||
#include "database/schema.h"
|
||
|
||
#include "database_internal.h"
|
||
#include "core/relation_type_normalizer.h"
|
||
|
||
#include <glib.h>
|
||
#include <sqlite3.h>
|
||
|
||
/**
|
||
* @brief Charge un fichier SQL du projet.
|
||
*
|
||
* @param database Connexion recevant les erreurs.
|
||
* @param schema_path Chemin du fichier SQL.
|
||
* @param schema_name Nom utilisé dans les diagnostics.
|
||
*
|
||
* @return Nouvelle chaîne SQL, ou NULL.
|
||
*/
|
||
static char *schema_load_sql(
|
||
Database *database,
|
||
const char *schema_path,
|
||
const char *schema_name
|
||
)
|
||
{
|
||
char *schema_sql = NULL;
|
||
GError *error = NULL;
|
||
|
||
if (database == NULL ||
|
||
schema_path == NULL ||
|
||
schema_path[0] == '\0' ||
|
||
schema_name == NULL ||
|
||
schema_name[0] == '\0')
|
||
{
|
||
return NULL;
|
||
}
|
||
|
||
if (!g_file_get_contents(
|
||
schema_path,
|
||
&schema_sql,
|
||
NULL,
|
||
&error
|
||
))
|
||
{
|
||
database_set_error(
|
||
database,
|
||
DATABASE_ERROR_INVALID_STATE,
|
||
error != NULL
|
||
? error->message
|
||
: "Impossible de charger le schéma SQLite."
|
||
);
|
||
|
||
g_warning(
|
||
"Impossible de charger %s depuis '%s' : %s",
|
||
schema_name,
|
||
schema_path,
|
||
error != NULL
|
||
? error->message
|
||
: "erreur inconnue"
|
||
);
|
||
|
||
g_clear_error(
|
||
&error
|
||
);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
return schema_sql;
|
||
}
|
||
|
||
/**
|
||
* @brief Exécute le contenu d’un fichier de schéma SQL.
|
||
*
|
||
* La transaction reste sous la responsabilité du code appelant.
|
||
*/
|
||
static bool schema_execute_file(
|
||
Database *database,
|
||
const char *schema_path,
|
||
const char *schema_name
|
||
)
|
||
{
|
||
sqlite3 *database_handle = NULL;
|
||
|
||
char *schema_sql = NULL;
|
||
char *error_message = NULL;
|
||
|
||
int result = SQLITE_ERROR;
|
||
|
||
if (database == NULL)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
database_handle =
|
||
database_get_handle(
|
||
database
|
||
);
|
||
|
||
if (database_handle == NULL)
|
||
{
|
||
database_set_error(
|
||
database,
|
||
DATABASE_ERROR_INVALID_STATE,
|
||
"La connexion SQLite est absente."
|
||
);
|
||
|
||
return false;
|
||
}
|
||
|
||
schema_sql =
|
||
schema_load_sql(
|
||
database,
|
||
schema_path,
|
||
schema_name
|
||
);
|
||
|
||
if (schema_sql == NULL)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
result =
|
||
sqlite3_exec(
|
||
database_handle,
|
||
schema_sql,
|
||
NULL,
|
||
NULL,
|
||
&error_message
|
||
);
|
||
|
||
g_free(
|
||
schema_sql
|
||
);
|
||
|
||
if (result != SQLITE_OK)
|
||
{
|
||
database_set_error(
|
||
database,
|
||
DATABASE_ERROR_SQLITE,
|
||
error_message != NULL
|
||
? error_message
|
||
: sqlite3_errmsg(database_handle)
|
||
);
|
||
|
||
g_warning(
|
||
"Impossible d’installer %s : %s",
|
||
schema_name,
|
||
error_message != NULL
|
||
? error_message
|
||
: sqlite3_errmsg(database_handle)
|
||
);
|
||
|
||
sqlite3_free(
|
||
error_message
|
||
);
|
||
|
||
return false;
|
||
}
|
||
|
||
sqlite3_free(
|
||
error_message
|
||
);
|
||
|
||
database_clear_error_internal(
|
||
database
|
||
);
|
||
|
||
return true;
|
||
}
|
||
|
||
bool schema_install_v1(
|
||
Database *database
|
||
)
|
||
{
|
||
return schema_execute_file(
|
||
database,
|
||
"database/schema_v1.sql",
|
||
"le schéma SQLite V1"
|
||
);
|
||
}
|
||
|
||
bool schema_install_v2(
|
||
Database *database
|
||
)
|
||
{
|
||
return schema_execute_file(
|
||
database,
|
||
"database/schema_v2.sql",
|
||
"la migration SQLite V2"
|
||
);
|
||
}
|
||
|
||
bool schema_install_v3(
|
||
Database *database
|
||
)
|
||
{
|
||
return schema_execute_file(
|
||
database,
|
||
"database/schema_v3.sql",
|
||
"la migration SQLite V3"
|
||
);
|
||
}
|
||
|
||
bool schema_install_v4(
|
||
Database *database
|
||
)
|
||
{
|
||
return schema_execute_file(
|
||
database,
|
||
"database/schema_v4.sql",
|
||
"la migration SQLite V4"
|
||
);
|
||
}
|
||
|
||
bool schema_install_v5(Database *database)
|
||
{
|
||
return schema_execute_file(database, "database/schema_v5.sql",
|
||
"la migration SQLite V5");
|
||
}
|
||
|
||
bool schema_install_v6(Database *database)
|
||
{
|
||
return schema_execute_file(database, "database/schema_v6.sql",
|
||
"la migration SQLite V6");
|
||
}
|
||
|
||
bool schema_install_v7(Database *database)
|
||
{
|
||
return schema_execute_file(database, "database/schema_v7.sql",
|
||
"la migration SQLite V7");
|
||
}
|
||
|
||
bool schema_install_v8(Database *database)
|
||
{
|
||
return schema_execute_file(database, "database/schema_v8.sql",
|
||
"la migration SQLite V8");
|
||
}
|
||
|
||
static gboolean schema_v9_column_exists(sqlite3 *handle)
|
||
{
|
||
sqlite3_stmt *statement = NULL;
|
||
gboolean found = FALSE;
|
||
if (sqlite3_prepare_v2(handle, "PRAGMA table_info(relations);", -1,
|
||
&statement, NULL) != SQLITE_OK) return FALSE;
|
||
while (sqlite3_step(statement) == SQLITE_ROW)
|
||
if (g_strcmp0((const char *) sqlite3_column_text(statement, 1),
|
||
"relation_type_id") == 0) found = TRUE;
|
||
sqlite3_finalize(statement);
|
||
return found;
|
||
}
|
||
|
||
static gboolean schema_v9_migrate_relation_types(Database *database)
|
||
{
|
||
sqlite3 *handle = database_get_handle(database);
|
||
sqlite3_stmt *read_statement = NULL;
|
||
sqlite3_stmt *find_statement = NULL;
|
||
sqlite3_stmt *insert_statement = NULL;
|
||
sqlite3_stmt *update_statement = NULL;
|
||
gboolean success = FALSE;
|
||
|
||
if (!schema_v9_column_exists(handle) &&
|
||
sqlite3_exec(handle, "ALTER TABLE relations ADD COLUMN "
|
||
"relation_type_id INTEGER REFERENCES relation_types(id) "
|
||
"ON UPDATE CASCADE ON DELETE RESTRICT;", NULL, NULL, NULL) != SQLITE_OK)
|
||
return FALSE;
|
||
if (sqlite3_prepare_v2(handle, "SELECT id,type_relation FROM relations "
|
||
"WHERE relation_type_id IS NULL ORDER BY created_at,id;", -1,
|
||
&read_statement, NULL) != SQLITE_OK ||
|
||
sqlite3_prepare_v2(handle, "SELECT id FROM relation_types WHERE "
|
||
"code=?1 OR normalized_key=?2 ORDER BY code IS NULL LIMIT 1;", -1,
|
||
&find_statement, NULL) != SQLITE_OK ||
|
||
sqlite3_prepare_v2(handle, "INSERT INTO relation_types(code,label,"
|
||
"normalized_key,description,is_system) VALUES(NULL,?1,?2,NULL,0);",
|
||
-1, &insert_statement, NULL) != SQLITE_OK ||
|
||
sqlite3_prepare_v2(handle, "UPDATE relations SET relation_type_id=?1 "
|
||
"WHERE id=?2;", -1, &update_statement, NULL) != SQLITE_OK)
|
||
goto cleanup;
|
||
|
||
while (sqlite3_step(read_statement) == SQLITE_ROW)
|
||
{
|
||
const char *relation_id =
|
||
(const char *) sqlite3_column_text(read_statement, 0);
|
||
const char *label =
|
||
(const char *) sqlite3_column_text(read_statement, 1);
|
||
char *key = relation_type_normalize_key(label);
|
||
sqlite3_int64 type_id = 0;
|
||
if (key == NULL) goto cleanup;
|
||
sqlite3_bind_text(find_statement, 1, label, -1, SQLITE_TRANSIENT);
|
||
sqlite3_bind_text(find_statement, 2, key, -1, SQLITE_TRANSIENT);
|
||
if (sqlite3_step(find_statement) == SQLITE_ROW)
|
||
type_id = sqlite3_column_int64(find_statement, 0);
|
||
sqlite3_reset(find_statement); sqlite3_clear_bindings(find_statement);
|
||
if (type_id == 0)
|
||
{
|
||
sqlite3_bind_text(insert_statement, 1, label, -1, SQLITE_TRANSIENT);
|
||
sqlite3_bind_text(insert_statement, 2, key, -1, SQLITE_TRANSIENT);
|
||
if (sqlite3_step(insert_statement) != SQLITE_DONE)
|
||
{ g_free(key); goto cleanup; }
|
||
type_id = sqlite3_last_insert_rowid(handle);
|
||
sqlite3_reset(insert_statement);
|
||
sqlite3_clear_bindings(insert_statement);
|
||
}
|
||
sqlite3_bind_int64(update_statement, 1, type_id);
|
||
sqlite3_bind_text(update_statement, 2, relation_id, -1, SQLITE_TRANSIENT);
|
||
if (sqlite3_step(update_statement) != SQLITE_DONE)
|
||
{ g_free(key); goto cleanup; }
|
||
sqlite3_reset(update_statement); sqlite3_clear_bindings(update_statement);
|
||
g_free(key);
|
||
}
|
||
if (sqlite3_exec(handle,
|
||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_relations_canonical_type "
|
||
"ON relations(entite_source_id,entite_cible_id,relation_type_id);"
|
||
"CREATE INDEX IF NOT EXISTS idx_relations_relation_type_id "
|
||
"ON relations(relation_type_id);"
|
||
"CREATE TRIGGER IF NOT EXISTS relations_require_canonical_type_insert "
|
||
"BEFORE INSERT ON relations WHEN NEW.relation_type_id IS NULL "
|
||
"BEGIN SELECT RAISE(ABORT,'relation_type_id is required'); END;"
|
||
"CREATE TRIGGER IF NOT EXISTS relations_require_canonical_type_update "
|
||
"BEFORE UPDATE OF relation_type_id ON relations "
|
||
"WHEN NEW.relation_type_id IS NULL "
|
||
"BEGIN SELECT RAISE(ABORT,'relation_type_id is required'); END;",
|
||
NULL, NULL, NULL) != SQLITE_OK)
|
||
goto cleanup;
|
||
{
|
||
sqlite3_stmt *check = NULL;
|
||
if (sqlite3_prepare_v2(handle, "PRAGMA foreign_key_check;", -1,
|
||
&check, NULL) != SQLITE_OK) goto cleanup;
|
||
success = sqlite3_step(check) == SQLITE_DONE;
|
||
sqlite3_finalize(check);
|
||
}
|
||
cleanup:
|
||
sqlite3_finalize(update_statement); sqlite3_finalize(insert_statement);
|
||
sqlite3_finalize(find_statement); sqlite3_finalize(read_statement);
|
||
if (!success) database_set_error(database, DATABASE_ERROR_SQLITE,
|
||
sqlite3_errmsg(handle));
|
||
return success;
|
||
}
|
||
|
||
bool schema_install_v9(Database *database)
|
||
{
|
||
return schema_execute_file(database, "database/schema_v9.sql",
|
||
"la migration SQLite V9") &&
|
||
schema_v9_migrate_relation_types(database);
|
||
}
|
||
|
||
bool schema_install_v10(Database *database)
|
||
{
|
||
return schema_execute_file(database, "database/schema_v10.sql",
|
||
"la migration SQLite V10");
|
||
}
|
||
|
||
bool schema_ensure_current(
|
||
Database *database
|
||
)
|
||
{
|
||
return schema_execute_file(
|
||
database,
|
||
"database/schema_current.sql",
|
||
"les extensions du schéma SQLite courant"
|
||
);
|
||
}
|