From 0c6630e6cf8141a2293cd1fc5cc7f5093200eaa5 Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Thu, 16 Jul 2026 09:14:34 +0200 Subject: [PATCH] refactor(database): introduce database infrastructure layer --- Makefile | 39 +- database/schema_v1.sql | 2 - docs/tickets/{open => closed}/TICKET-024.md | 0 docs/tickets/closed/TICKET-025.md | 215 ++++++ include/database/database.h | 52 +- include/database/error.h | 56 ++ include/database/statement.h | 217 ++++++ include/database/transaction.h | 48 ++ labfy-investigation | Bin 129216 -> 144504 bytes src/database/database.c | 212 ++++++ src/database/database_internal.h | 73 +++ src/database/error.c | 31 + src/database/statement.c | 523 +++++++++++++++ src/database/transaction.c | 142 ++++ tests/test_database | Bin 27000 -> 31592 bytes tests/test_error | Bin 0 -> 32104 bytes tests/test_error.c | 135 ++++ tests/test_investigation_project | Bin 45120 -> 45608 bytes tests/test_statement | Bin 0 -> 37208 bytes tests/test_statement.c | 689 ++++++++++++++++++++ tests/test_transaction | Bin 0 -> 32440 bytes tests/test_transaction.c | 238 +++++++ 22 files changed, 2655 insertions(+), 17 deletions(-) rename docs/tickets/{open => closed}/TICKET-024.md (100%) create mode 100644 docs/tickets/closed/TICKET-025.md create mode 100644 include/database/error.h create mode 100644 include/database/statement.h create mode 100644 include/database/transaction.h create mode 100644 src/database/database_internal.h create mode 100644 src/database/error.c create mode 100644 src/database/statement.c create mode 100644 src/database/transaction.c create mode 100755 tests/test_error create mode 100644 tests/test_error.c create mode 100755 tests/test_statement create mode 100644 tests/test_statement.c create mode 100755 tests/test_transaction create mode 100644 tests/test_transaction.c diff --git a/Makefile b/Makefile index a6a646d..6876067 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,9 @@ TEST_TREE_MODEL = tests/test_investigation_tree_model TEST_TREE_BUILDER = tests/test_investigation_tree_builder TEST_PROJECT = tests/test_investigation_project TEST_DATABASE = tests/test_database +TEST_STATEMENT = tests/test_statement +TEST_TRANSACTION = tests/test_transaction +TEST_ERROR = tests/test_error all: $(TARGET) @@ -72,18 +75,47 @@ $(TEST_DATABASE): \ src/database/schema.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 +$(TEST_STATEMENT): \ + tests/test_statement.c \ + src/database/database.c \ + src/database/schema.c \ + src/database/statement.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 + +$(TEST_TRANSACTION): \ + tests/test_transaction.c \ + src/database/database.c \ + src/database/schema.c \ + src/database/statement.c \ + src/database/transaction.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 + +$(TEST_ERROR): \ + tests/test_error.c \ + src/database/database.c \ + src/database/schema.c \ + src/database/statement.c \ + src/database/error.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3 + test: \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ $(TEST_TREE_BUILDER) \ $(TEST_PROJECT) \ - $(TEST_DATABASE) + $(TEST_DATABASE) \ + $(TEST_STATEMENT) \ + $(TEST_TRANSACTION) \ + $(TEST_ERROR) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @./$(TEST_TREE_BUILDER) @./$(TEST_PROJECT) @./$(TEST_DATABASE) + @$(TEST_STATEMENT) + @$(TEST_TRANSACTION) + @$(TEST_ERROR) @echo "Tous les tests sont valides." %.o: %.c @@ -98,6 +130,9 @@ clean: $(TEST_TREE_MODEL) \ $(TEST_TREE_BUILDER) \ $(TEST_PROJECT) \ - $(TEST_DATABASE) + $(TEST_DATABASE) \ + $(TEST_STATEMENT) \ + $(TEST_TRANSACTION) \ + $(TEST_ERROR) .PHONY: clean run test diff --git a/database/schema_v1.sql b/database/schema_v1.sql index 010e433..5fba212 100644 --- a/database/schema_v1.sql +++ b/database/schema_v1.sql @@ -4,8 +4,6 @@ * Schéma SQLite officiel V1 ******************************************************************************/ -PRAGMA foreign_keys = ON; - CREATE TABLE metadata ( key TEXT PRIMARY KEY, diff --git a/docs/tickets/open/TICKET-024.md b/docs/tickets/closed/TICKET-024.md similarity index 100% rename from docs/tickets/open/TICKET-024.md rename to docs/tickets/closed/TICKET-024.md diff --git a/docs/tickets/closed/TICKET-025.md b/docs/tickets/closed/TICKET-025.md new file mode 100644 index 0000000..e4594b0 --- /dev/null +++ b/docs/tickets/closed/TICKET-025.md @@ -0,0 +1,215 @@ +# Ticket #025 + +## Titre + +Refactoriser la couche Database et établir l'infrastructure d'accès aux données. + +--- + +## Objectif + +Mettre en place l'architecture définitive de la couche Database afin de fournir +une API interne homogène pour tous les futurs modules métier. + +Ce ticket ne comprend **aucun CRUD métier**. + +Son objectif est uniquement de construire les fondations techniques qui seront +réutilisées par l'ensemble de l'application. + +--- + +## Motivations + +À terme, Labfy Investigation manipulera de nombreux objets métier : + +- investigations ; +- sources ; +- recherches ; +- preuves ; +- entités ; +- relations ; +- chronologie ; +- journal ; +- hypothèses ; +- catégories ; +- tags. + +Sans une couche d'abstraction, chaque module devrait manipuler directement +SQLite, ce qui entraînerait : + +- une forte duplication de code ; +- une gestion incohérente des erreurs ; +- des transactions difficiles à maintenir ; +- une architecture difficile à faire évoluer. + +L'objectif de ce ticket est d'éviter cette dette technique. + +--- + +## Architecture cible + +La couche Database devra évoluer vers l'organisation suivante : + +```text +include/database/ +├── database.h +├── connection.h +├── transaction.h +├── statement.h +├── error.h +├── schema.h +``` + +```text +src/database/ +├── database.c +├── connection.c +├── transaction.c +├── statement.c +├── error.c +├── schema.c +``` + +Les futurs modules métier seront ajoutés ultérieurement. + +--- + +## Structure Database + +Créer un type opaque : + +```c +typedef struct Database Database; +``` + +L'implémentation reste privée. + +La structure interne contiendra au minimum : + +- le pointeur `sqlite3 *`; +- le chemin de la base ; +- l'état de la transaction ; +- la version du schéma. + +Cette abstraction permettra de faire évoluer facilement l'implémentation sans +modifier l'API publique. + +--- + +## Connexion + +Créer les fonctions : + +```c +Database *database_open( + const char *database_path +); + +void database_close( + Database *database +); +``` + +Ces fonctions sont responsables de : + +- l'ouverture de SQLite ; +- l'activation des PRAGMA nécessaires ; +- la fermeture propre de la base. + +--- + +## Transactions + +Créer les fonctions : + +```c +bool database_begin( + Database *database +); + +bool database_commit( + Database *database +); + +bool database_rollback( + Database *database +); +``` + +Toute gestion des transactions devra passer exclusivement par ces fonctions. + +--- + +## Gestion des requêtes préparées + +Créer une couche d'abstraction pour les instructions préparées. + +L'objectif est de centraliser : + +- `sqlite3_prepare_v2()` +- `sqlite3_bind_*()` +- `sqlite3_step()` +- `sqlite3_reset()` +- `sqlite3_finalize()` + +Aucun futur module métier ne devra appeler directement ces fonctions SQLite. + +--- + +## Gestion des erreurs + +Créer un point d'entrée unique pour les erreurs SQLite. + +Toutes les erreurs devront passer par une fonction dédiée. + +Cette centralisation facilitera : + +- le débogage ; +- la journalisation ; +- l'intégration future avec le journal d'audit. + +--- + +## Responsabilités + +À la fin du ticket : + +- la couche Database est la seule autorisée à manipuler SQLite ; +- les autres modules ne connaissent pas `sqlite3`; +- les transactions sont centralisées ; +- les erreurs sont uniformisées ; +- les requêtes préparées sont encapsulées. + +--- + +## Hors périmètre + +Ce ticket ne doit pas : + +- ajouter de nouvelles tables ; +- modifier le schéma SQL ; +- créer un CRUD métier ; +- modifier GTK ; +- implémenter des migrations ; +- ajouter des fonctionnalités d'investigation. + +--- + +## Critères d'acceptation + +- [ ] Création du type opaque `Database`. +- [ ] API publique de connexion disponible. +- [ ] API publique des transactions disponible. +- [ ] Encapsulation des requêtes préparées. +- [ ] Gestion centralisée des erreurs. +- [ ] Aucune utilisation directe de `sqlite3` en dehors du dossier `database`. +- [ ] Documentation des nouvelles API. +- [ ] Tous les tests existants restent valides. + +--- + +## Commit attendu + +```text +refactor(database): introduce database infrastructure layer +``` diff --git a/include/database/database.h b/include/database/database.h index 74301e8..585b079 100644 --- a/include/database/database.h +++ b/include/database/database.h @@ -1,6 +1,6 @@ /****************************************************************************** * @file database.h - * @brief Interface publique d'initialisation de la base SQLite d'une enquête. + * @brief API principale de la couche Database. ******************************************************************************/ #ifndef LABFY_INVESTIGATION_DATABASE_H @@ -9,24 +9,50 @@ #include /** - * @brief Initialise la base SQLite d'une enquête. + * @brief Contexte opaque représentant une connexion SQLite. + */ +typedef struct Database Database; + +/** + * @brief Ouvre une base SQLite existante ou à créer. * - * La fonction crée ou ouvre le fichier SQLite indiqué, installe le schéma - * courant, puis enregistre les métadonnées de l'enquête. + * La fonction : * - * Aucun handle SQLite n'est exposé au code appelant. + * - valide le chemin ; + * - ouvre la connexion SQLite ; + * - active les clés étrangères ; + * - conserve une copie du chemin. * - * @param database_path - * Chemin complet du fichier Enquete.sqlite. + * @param database_path Chemin du fichier SQLite. * - * @param investigation_name - * Nom de l'enquête. + * @return Une nouvelle instance de Database, ou NULL en cas d'échec. + */ +Database *database_open( + const char *database_path +); + +/** + * @brief Ferme une base SQLite et libère ses ressources. * - * @param investigation_root_path - * Chemin complet du dossier racine de l'enquête. + * Cette fonction accepte NULL. * - * @return true si la base a été correctement initialisée, - * sinon false. + * @param database Instance à fermer. + */ +void database_close( + Database *database +); + +/** + * @brief Initialise la base SQLite d'une nouvelle enquête. + * + * Cette fonction conserve temporairement son rôle actuel pendant le + * refactoring du ticket #025. + * + * @param database_path Chemin complet du fichier Enquete.sqlite. + * @param investigation_name Nom de l'enquête. + * @param investigation_root_path Chemin racine de l'enquête. + * + * @return true si l'initialisation réussit, sinon false. */ bool database_initialize( const char *database_path, diff --git a/include/database/error.h b/include/database/error.h new file mode 100644 index 0000000..3eff2ff --- /dev/null +++ b/include/database/error.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * @file error.h + * @brief Gestion des erreurs de la couche Database. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_DATABASE_ERROR_H +#define LABFY_INVESTIGATION_DATABASE_ERROR_H + +#include "database/database.h" + +/** + * @brief Catégories d'erreurs de la couche Database. + */ +typedef enum +{ + DATABASE_ERROR_NONE = 0, + DATABASE_ERROR_INVALID_ARGUMENT, + DATABASE_ERROR_MEMORY, + DATABASE_ERROR_SQLITE, + DATABASE_ERROR_INVALID_STATE +} DatabaseErrorCode; + +/** + * @brief Retourne le code de la dernière erreur. + * + * @param database Connexion Database. + * + * @return Code d'erreur, ou DATABASE_ERROR_NONE. + */ +DatabaseErrorCode database_error_get_code( + const Database *database +); + +/** + * @brief Retourne le message de la dernière erreur. + * + * Le pointeur retourné appartient à Database et ne doit pas être libéré. + * + * @param database Connexion Database. + * + * @return Message d'erreur, ou NULL. + */ +const char *database_error_get_message( + const Database *database +); + +/** + * @brief Efface la dernière erreur enregistrée. + * + * @param database Connexion Database. + */ +void database_error_clear( + Database *database +); + +#endif diff --git a/include/database/statement.h b/include/database/statement.h new file mode 100644 index 0000000..6ed5ae0 --- /dev/null +++ b/include/database/statement.h @@ -0,0 +1,217 @@ +/****************************************************************************** + * @file statement.h + * @brief API des requêtes préparées de la couche Database. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_STATEMENT_H +#define LABFY_INVESTIGATION_STATEMENT_H + +#include "database/database.h" + +#include +#include + +/** + * @brief Résultat de l'exécution d'une requête préparée. + */ +typedef enum +{ + DATABASE_STATEMENT_STEP_ERROR = 0, + DATABASE_STATEMENT_STEP_ROW, + DATABASE_STATEMENT_STEP_DONE +} DatabaseStatementStepResult; + +/** + * @brief Requête SQLite préparée encapsulée. + */ +typedef struct DatabaseStatement DatabaseStatement; + +/** + * @brief Prépare une requête SQL. + * + * La requête doit contenir une seule instruction SQL. + * + * @param database Connexion ouverte. + * @param sql Requête SQL à préparer. + * + * @return Une nouvelle requête préparée, ou NULL en cas d'échec. + */ +DatabaseStatement *database_statement_prepare( + Database *database, + const char *sql +); + +/** + * @brief Exécute une étape d'une requête préparée. + * + * Pour une requête SELECT, DATABASE_STATEMENT_STEP_ROW indique + * qu'une ligne est disponible. + * + * DATABASE_STATEMENT_STEP_DONE indique que l'exécution est terminée. + * + * @param statement Requête préparée. + * + * @return Résultat de l'étape. + */ +DatabaseStatementStepResult database_statement_step( + DatabaseStatement *statement +); + +/** + * @brief Réinitialise une requête préparée afin de pouvoir la réexécuter. + * + * Les paramètres déjà liés sont conservés. + * + * @param statement Requête préparée. + * + * @return true en cas de succès, sinon false. + */ +bool database_statement_reset( + DatabaseStatement *statement +); + +/** + * @brief Efface tous les paramètres liés à une requête préparée. + * + * Après cet appel, les paramètres SQL redeviennent non liés et seront + * interprétés comme NULL si la requête est exécutée sans nouveau bind. + * + * @param statement Requête préparée. + * + * @return true en cas de succès, sinon false. + */ +bool database_statement_clear_bindings( + DatabaseStatement *statement +); + +/** + * @brief Indique si une colonne contient la valeur SQL NULL. + * + * Les indices de colonnes SQLite commencent à 0. + * + * @param statement Requête positionnée sur une ligne. + * @param column_index Indice de la colonne. + * @param is_null Destination du résultat. + * + * @return true si la colonne a pu être inspectée, sinon false. + */ +bool database_statement_column_is_null( + DatabaseStatement *statement, + int column_index, + bool *is_null +); + +/** + * @brief Lit un entier signé sur 64 bits depuis une colonne. + * + * Les indices de colonnes SQLite commencent à 0. + * La colonne doit être de type SQLITE_INTEGER. + * + * @param statement Requête positionnée sur une ligne. + * @param column_index Indice de la colonne. + * @param value Destination de la valeur. + * + * @return true si l'entier a pu être lu, sinon false. + */ +bool database_statement_column_int64( + DatabaseStatement *statement, + int column_index, + int64_t *value +); + +/** + * @brief Copie le contenu texte d'une colonne. + * + * Les indices de colonnes SQLite commencent à 0. + * + * Si la colonne contient SQL NULL, la fonction réussit et place NULL + * dans value. + * + * La chaîne retournée doit être libérée avec g_free(). + * + * @param statement Requête positionnée sur une ligne. + * @param column_index Indice de la colonne. + * @param value Destination de la chaîne allouée. + * + * @return true si la colonne a pu être lue, sinon false. + */ +bool database_statement_column_text( + DatabaseStatement *statement, + int column_index, + char **value +); + +/** + * @brief Lie une chaîne de caractères à un paramètre SQL. + * + * Les indices SQLite commencent à 1. + * + * @param statement Requête préparée. + * @param index Indice du paramètre. + * @param value Chaîne à lier. + * + * @return true en cas de succès, sinon false. + */ +bool database_statement_bind_text( + DatabaseStatement *statement, + int index, + const char *value +); + +/** + * @brief Lie un entier signé sur 64 bits à un paramètre SQL. + * + * @param statement Requête préparée. + * @param index Indice du paramètre. + * @param value Valeur à lier. + * + * @return true en cas de succès, sinon false. + */ +bool database_statement_bind_int64( + DatabaseStatement *statement, + int index, + int64_t value +); + +/** + * @brief Lie la valeur SQL NULL à un paramètre. + * + * @param statement Requête préparée. + * @param index Indice du paramètre. + * + * @return true en cas de succès, sinon false. + */ +bool database_statement_bind_null( + DatabaseStatement *statement, + int index +); + +/** + * @brief Lie une chaîne de caractères à un paramètre SQL. + * + * Les indices des paramètres SQLite commencent à 1. + * + * @param statement Requête préparée. + * @param index Indice du paramètre SQL. + * @param value Chaîne de caractères à lier. + * + * @return true en cas de succès, sinon false. + */ +bool database_statement_bind_text( + DatabaseStatement *statement, + int index, + const char *value +); + +/** + * @brief Finalise une requête préparée et libère ses ressources. + * + * Cette fonction accepte NULL. + * + * @param statement Requête à finaliser. + */ +void database_statement_finalize( + DatabaseStatement *statement +); + +#endif diff --git a/include/database/transaction.h b/include/database/transaction.h new file mode 100644 index 0000000..8b46b02 --- /dev/null +++ b/include/database/transaction.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * @file transaction.h + * @brief Gestion des transactions SQLite. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_TRANSACTION_H +#define LABFY_INVESTIGATION_TRANSACTION_H + +#include "database/database.h" + +#include + +/** + * @brief Démarre une transaction d'écriture immédiate. + * + * Une seule transaction peut être active sur une instance Database. + * + * @param database Connexion à la base. + * + * @return true en cas de succès, sinon false. + */ +bool database_transaction_begin( + Database *database +); + +/** + * @brief Valide la transaction active. + * + * @param database Connexion à la base. + * + * @return true en cas de succès, sinon false. + */ +bool database_transaction_commit( + Database *database +); + +/** + * @brief Annule la transaction active. + * + * @param database Connexion à la base. + * + * @return true en cas de succès, sinon false. + */ +bool database_transaction_rollback( + Database *database +); + +#endif diff --git a/labfy-investigation b/labfy-investigation index e7b631749a454c3433a9f0ef6dc72602cbb72792..56a51c4567f5f048bb5fb9a9d9592a3f024e4d1b 100755 GIT binary patch delta 52912 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 delta 36773 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%0D4f`2&Y*igyi{4`(r`=FnPH4abv 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 IP35@%0l$*8YXATM diff --git a/src/database/database.c b/src/database/database.c index 04af0e0..f03cd6a 100644 --- a/src/database/database.c +++ b/src/database/database.c @@ -6,6 +6,8 @@ #include "database/database.h" #include "database/schema.h" +#include "database_internal.h" + #include #include @@ -19,6 +21,18 @@ */ #define DATABASE_APPLICATION_NAME "Labfy Investigation" +struct Database +{ + sqlite3 *handle; + char *database_path; + + bool transaction_active; + int schema_version; + + DatabaseErrorCode error_code; + char *error_message; +}; + /** * @brief Requête d'insertion d'une métadonnée. */ @@ -461,6 +475,109 @@ static char *database_create_utc_timestamp(void) return timestamp; } +sqlite3 *database_get_handle( + Database *database +) +{ + if (database == NULL) + { + return NULL; + } + + return database->handle; +} + +bool database_get_transaction_active( + const Database *database +) +{ + if (database == NULL) + { + return false; + } + + return database->transaction_active; +} + +void database_set_transaction_active( + Database *database, + bool transaction_active +) +{ + if (database == NULL) + { + return; + } + + database->transaction_active = transaction_active; +} + +Database *database_open( + const char *database_path +) +{ + Database *database = NULL; + int result = SQLITE_ERROR; + + if (database_path == NULL || database_path[0] == '\0') + { + return NULL; + } + + database = g_new0(Database, 1); + + if (database == NULL) + { + return NULL; + } + + database->database_path = g_strdup(database_path); + + if (database->database_path == NULL) + { + g_free(database); + return NULL; + } + + result = sqlite3_open_v2( + database_path, + &database->handle, + SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE | + SQLITE_OPEN_PRIVATECACHE, + NULL + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible d'ouvrir la base '%s' : %s", + database_path, + database->handle != NULL + ? sqlite3_errmsg(database->handle) + : sqlite3_errstr(result) + ); + + database_close(database); + + return NULL; + } + + if (!database_execute_sql( + database->handle, + "PRAGMA foreign_keys = ON;" + )) + { + database_close(database); + return NULL; + } + + database->transaction_active = false; + database->schema_version = 0; + + return database; +} + bool database_initialize( const char *database_path, const char *investigation_name, @@ -618,3 +735,98 @@ cleanup: return success; } + +void database_set_error( + Database *database, + DatabaseErrorCode error_code, + const char *error_message +) +{ + char *error_message_copy = NULL; + + if (database == NULL) + { + return; + } + + if (error_message != NULL) + { + error_message_copy = g_strdup(error_message); + } + + g_free(database->error_message); + + database->error_code = error_code; + database->error_message = error_message_copy; +} + +DatabaseErrorCode database_get_error_code_internal( + const Database *database +) +{ + if (database == NULL) + { + return DATABASE_ERROR_NONE; + } + + return database->error_code; +} + +const char *database_get_error_message_internal( + const Database *database +) +{ + if (database == NULL) + { + return NULL; + } + + return database->error_message; +} + +void database_clear_error_internal( + Database *database +) +{ + if (database == NULL) + { + return; + } + + g_free(database->error_message); + + database->error_message = NULL; + database->error_code = DATABASE_ERROR_NONE; +} + +void database_close( + Database *database +) +{ + int result = SQLITE_OK; + + if (database == NULL) + { + return; + } + + if (database->handle != NULL) + { + result = sqlite3_close(database->handle); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de fermer proprement la base '%s' : %s", + database->database_path != NULL + ? database->database_path + : "(chemin inconnu)", + sqlite3_errstr(result) + ); + } + } + + g_free(database->error_message); + g_free(database->database_path); + g_free(database); +} diff --git a/src/database/database_internal.h b/src/database/database_internal.h new file mode 100644 index 0000000..9070400 --- /dev/null +++ b/src/database/database_internal.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * @file database_internal.h + * @brief API interne de la couche Database. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_DATABASE_INTERNAL_H +#define LABFY_INVESTIGATION_DATABASE_INTERNAL_H + +#include "database/database.h" +#include "database/error.h" + +#include +#include + +/** + * @brief Retourne le handle SQLite interne. + * + * Cette fonction est réservée aux modules de src/database/. + * + * @param database Connexion Database. + * + * @return Handle SQLite, ou NULL si l'instance est invalide. + */ +sqlite3 *database_get_handle( + Database *database +); + +/** + * @brief Indique si une transaction est active. + */ +bool database_get_transaction_active( + const Database *database +); + +/** + * @brief Modifie l'état interne de la transaction. + */ +void database_set_transaction_active( + Database *database, + bool transaction_active +); + +/** + * @brief Enregistre une erreur dans une connexion Database. + */ +void database_set_error( + Database *database, + DatabaseErrorCode error_code, + const char *error_message +); + +/** + * @brief Retourne le code d'erreur interne. + */ +DatabaseErrorCode database_get_error_code_internal( + const Database *database +); + +/** + * @brief Retourne le message d'erreur interne. + */ +const char *database_get_error_message_internal( + const Database *database +); + +/** + * @brief Efface l'erreur interne. + */ +void database_clear_error_internal( + Database *database +); + +#endif diff --git a/src/database/error.c b/src/database/error.c new file mode 100644 index 0000000..1a77f9a --- /dev/null +++ b/src/database/error.c @@ -0,0 +1,31 @@ +/****************************************************************************** + * @file error.c + * @brief Gestion des erreurs de la couche Database. + ******************************************************************************/ + +#include "database/error.h" + +#include "database_internal.h" +#include + +DatabaseErrorCode database_error_get_code( + const Database *database +) +{ + return database_get_error_code_internal(database); +} + +const char *database_error_get_message( + const Database *database +) +{ + return database_get_error_message_internal(database); +} + +void database_error_clear( + Database *database +) +{ + database_clear_error_internal(database); +} + diff --git a/src/database/statement.c b/src/database/statement.c new file mode 100644 index 0000000..f0bb005 --- /dev/null +++ b/src/database/statement.c @@ -0,0 +1,523 @@ +/****************************************************************************** + * @file statement.c + * @brief Encapsulation des requêtes préparées SQLite. + ******************************************************************************/ + +#include "database/statement.h" + +#include "database_internal.h" + +#include +#include + +/** + * @brief Représentation privée d'une requête préparée. + */ +struct DatabaseStatement +{ + Database *database; + sqlite3_stmt *handle; +}; + +DatabaseStatement *database_statement_prepare( + Database *database, + const char *sql +) +{ + DatabaseStatement *statement = NULL; + sqlite3 *database_handle = NULL; + int result = SQLITE_ERROR; + + if (database == NULL) + { + return NULL; + } + + if (sql == NULL || sql[0] == '\0') + { + database_set_error( + database, + DATABASE_ERROR_INVALID_ARGUMENT, + "La requête SQL est absente." + ); + + return NULL; + } + + database_handle = database_get_handle(database); + + if (database_handle == NULL) + { + database_set_error( + database, + DATABASE_ERROR_INVALID_STATE, + "La connexion SQLite est absente." + ); + + return NULL; + } + + statement = g_new0(DatabaseStatement, 1); + + if (statement == NULL) + { + database_set_error( + database, + DATABASE_ERROR_MEMORY, + "Impossible d'allouer la requête préparée." + ); + + return NULL; + } + + result = sqlite3_prepare_v2( + database_handle, + sql, + -1, + &statement->handle, + NULL + ); + + if (result != SQLITE_OK) + { + database_set_error( + database, + DATABASE_ERROR_SQLITE, + sqlite3_errmsg(database_handle) + ); + + g_warning( + "Impossible de préparer la requête SQL : %s", + sqlite3_errmsg(database_handle) + ); + + g_free(statement); + + return NULL; + } + + statement->database = database; + + database_clear_error_internal(database); + + return statement; +} + +bool database_statement_bind_text( + DatabaseStatement *statement, + int index, + const char *value +) +{ + sqlite3 *database_handle = NULL; + int result = SQLITE_ERROR; + + if (statement == NULL || + statement->handle == NULL || + index <= 0 || + value == NULL) + { + return false; + } + + result = sqlite3_bind_text( + statement->handle, + index, + value, + -1, + SQLITE_TRANSIENT + ); + + if (result != SQLITE_OK) + { + database_handle = database_get_handle( + statement->database + ); + + g_warning( + "Impossible de lier le paramètre texte %d : %s", + index, + database_handle != NULL + ? sqlite3_errmsg(database_handle) + : sqlite3_errstr(result) + ); + + return false; + } + + return true; +} + +bool database_statement_bind_int64( + DatabaseStatement *statement, + int index, + int64_t value +) +{ + sqlite3 *database_handle = NULL; + int result = SQLITE_ERROR; + + if (statement == NULL || + statement->handle == NULL || + index <= 0) + { + return false; + } + + result = sqlite3_bind_int64( + statement->handle, + index, + value + ); + + if (result != SQLITE_OK) + { + database_handle = database_get_handle( + statement->database + ); + + g_warning( + "Impossible de lier le paramètre entier %d : %s", + index, + database_handle != NULL + ? sqlite3_errmsg(database_handle) + : sqlite3_errstr(result) + ); + + return false; + } + + return true; +} + +bool database_statement_bind_null( + DatabaseStatement *statement, + int index +) +{ + sqlite3 *database_handle = NULL; + int result = SQLITE_ERROR; + + if (statement == NULL || + statement->handle == NULL || + index <= 0) + { + return false; + } + + result = sqlite3_bind_null( + statement->handle, + index + ); + + if (result != SQLITE_OK) + { + database_handle = database_get_handle( + statement->database + ); + + g_warning( + "Impossible de lier le paramètre NULL %d : %s", + index, + database_handle != NULL + ? sqlite3_errmsg(database_handle) + : sqlite3_errstr(result) + ); + + return false; + } + + return true; +} + +DatabaseStatementStepResult database_statement_step( + DatabaseStatement *statement +) +{ + sqlite3 *database_handle = NULL; + int result = SQLITE_ERROR; + + if (statement == NULL || + statement->handle == NULL) + { + return DATABASE_STATEMENT_STEP_ERROR; + } + + result = sqlite3_step( + statement->handle + ); + + if (result == SQLITE_ROW) + { + return DATABASE_STATEMENT_STEP_ROW; + } + + if (result == SQLITE_DONE) + { + return DATABASE_STATEMENT_STEP_DONE; + } + + database_handle = database_get_handle( + statement->database + ); + + g_warning( + "Impossible d'exécuter la requête SQL : %s", + database_handle != NULL + ? sqlite3_errmsg(database_handle) + : sqlite3_errstr(result) + ); + + return DATABASE_STATEMENT_STEP_ERROR; +} + +bool database_statement_reset( + DatabaseStatement *statement +) +{ + sqlite3 *database_handle = NULL; + int result = SQLITE_ERROR; + + if (statement == NULL || + statement->handle == NULL) + { + return false; + } + + result = sqlite3_reset( + statement->handle + ); + + if (result != SQLITE_OK) + { + database_handle = database_get_handle( + statement->database + ); + + g_warning( + "Impossible de réinitialiser la requête SQL : %s", + database_handle != NULL + ? sqlite3_errmsg(database_handle) + : sqlite3_errstr(result) + ); + + return false; + } + + return true; +} + +bool database_statement_clear_bindings( + DatabaseStatement *statement +) +{ + sqlite3 *database_handle = NULL; + int result = SQLITE_ERROR; + + if (statement == NULL || + statement->handle == NULL) + { + return false; + } + + result = sqlite3_clear_bindings( + statement->handle + ); + + if (result != SQLITE_OK) + { + database_handle = database_get_handle( + statement->database + ); + + g_warning( + "Impossible d'effacer les paramètres de la requête SQL : %s", + database_handle != NULL + ? sqlite3_errmsg(database_handle) + : sqlite3_errstr(result) + ); + + return false; + } + + return true; +} + +bool database_statement_column_is_null( + DatabaseStatement *statement, + int column_index, + bool *is_null +) +{ + int column_count = 0; + + if (statement == NULL || + statement->handle == NULL || + is_null == NULL) + { + return false; + } + + column_count = sqlite3_column_count( + statement->handle + ); + + if (column_index < 0 || + column_index >= column_count) + { + return false; + } + + *is_null = + sqlite3_column_type( + statement->handle, + column_index + ) == SQLITE_NULL; + + return true; +} + +bool database_statement_column_text( + DatabaseStatement *statement, + int column_index, + char **value +) +{ + const unsigned char *column_text = NULL; + int column_count = 0; + int text_length = 0; + + if (statement == NULL || + statement->handle == NULL || + value == NULL) + { + return false; + } + + *value = NULL; + + column_count = sqlite3_column_count( + statement->handle + ); + + if (column_index < 0 || + column_index >= column_count) + { + return false; + } + + if (sqlite3_column_type( + statement->handle, + column_index + ) == SQLITE_NULL) + { + return true; + } + + column_text = sqlite3_column_text( + statement->handle, + column_index + ); + + if (column_text == NULL) + { + return false; + } + + text_length = sqlite3_column_bytes( + statement->handle, + column_index + ); + + *value = g_strndup( + (const char *) column_text, + (gsize) text_length + ); + + if (*value == NULL) + { + return false; + } + + return true; +} + +bool database_statement_column_int64( + DatabaseStatement *statement, + int column_index, + int64_t *value +) +{ + int column_count = 0; + int column_type = SQLITE_NULL; + + if (statement == NULL || + statement->handle == NULL || + value == NULL) + { + return false; + } + + column_count = sqlite3_column_count( + statement->handle + ); + + if (column_index < 0 || + column_index >= column_count) + { + return false; + } + + column_type = sqlite3_column_type( + statement->handle, + column_index + ); + + if (column_type != SQLITE_INTEGER) + { + return false; + } + + *value = sqlite3_column_int64( + statement->handle, + column_index + ); + + return true; +} + +void database_statement_finalize( + DatabaseStatement *statement +) +{ + sqlite3 *database_handle = NULL; + int result = SQLITE_OK; + + if (statement == NULL) + { + return; + } + + database_handle = database_get_handle( + statement->database + ); + + if (statement->handle != NULL) + { + result = sqlite3_finalize( + statement->handle + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible de finaliser la requête SQL : %s", + database_handle != NULL + ? sqlite3_errmsg(database_handle) + : sqlite3_errstr(result) + ); + } + } + + g_free(statement); +} diff --git a/src/database/transaction.c b/src/database/transaction.c new file mode 100644 index 0000000..fcc9845 --- /dev/null +++ b/src/database/transaction.c @@ -0,0 +1,142 @@ +/****************************************************************************** + * @file transaction.c + * @brief Gestion des transactions SQLite. + ******************************************************************************/ + +#include "database/transaction.h" + +#include "database_internal.h" + +#include +#include + +/** + * @brief Exécute une commande SQL de transaction. + */ +static bool database_transaction_execute( + Database *database, + const char *sql +) +{ + sqlite3 *database_handle = NULL; + char *error_message = NULL; + int result = SQLITE_ERROR; + + if (database == NULL || + sql == NULL || + sql[0] == '\0') + { + return false; + } + + database_handle = database_get_handle(database); + + if (database_handle == NULL) + { + return false; + } + + result = sqlite3_exec( + database_handle, + sql, + NULL, + NULL, + &error_message + ); + + if (result != SQLITE_OK) + { + g_warning( + "Impossible d'exécuter la transaction SQL : %s", + error_message != NULL + ? error_message + : sqlite3_errmsg(database_handle) + ); + + sqlite3_free(error_message); + + return false; + } + + sqlite3_free(error_message); + + return true; +} + +bool database_transaction_begin( + Database *database +) +{ + if (database == NULL || + database_get_transaction_active(database)) + { + return false; + } + + if (!database_transaction_execute( + database, + "BEGIN IMMEDIATE;" + )) + { + return false; + } + + database_set_transaction_active( + database, + true + ); + + return true; +} + +bool database_transaction_commit( + Database *database +) +{ + if (database == NULL || + !database_get_transaction_active(database)) + { + return false; + } + + if (!database_transaction_execute( + database, + "COMMIT;" + )) + { + return false; + } + + database_set_transaction_active( + database, + false + ); + + return true; +} + +bool database_transaction_rollback( + Database *database +) +{ + if (database == NULL || + !database_get_transaction_active(database)) + { + return false; + } + + if (!database_transaction_execute( + database, + "ROLLBACK;" + )) + { + return false; + } + + database_set_transaction_active( + database, + false + ); + + return true; +} diff --git a/tests/test_database b/tests/test_database index a6f4d909841d87d36b2f694cc9d5d02d56205acd..adf363d47e6308e8852c3d95338aef03b1439e29 100755 GIT binary patch literal 31592 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 QY{ZOmp^j1^qAuzG0o^(IAOHXW diff --git a/tests/test_error b/tests/test_error new file mode 100755 index 0000000000000000000000000000000000000000..48f2c16c3d6ac60d122e02c04b230d4173a75955 GIT binary patch literal 32104 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 +#include + +/** + * @brief Vérifie l'état initial d'une connexion. + */ +static void test_initial_error_state(void) +{ + Database *database = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + assert( + database_error_get_code(database) == + DATABASE_ERROR_NONE + ); + + assert( + database_error_get_message(database) == NULL + ); + + database_close(database); +} + +/** + * @brief Vérifie l'enregistrement d'une erreur SQLite. + */ +static void test_statement_error(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + const char *error_message = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT FROM;" + ); + + assert(statement == NULL); + + assert( + database_error_get_code(database) == + DATABASE_ERROR_SQLITE + ); + + error_message = database_error_get_message(database); + + assert(error_message != NULL); + assert(error_message[0] != '\0'); + + database_error_clear(database); + + assert( + database_error_get_code(database) == + DATABASE_ERROR_NONE + ); + + assert( + database_error_get_message(database) == NULL + ); + + database_close(database); +} + +/** + * @brief Vérifie qu'une opération valide efface l'erreur précédente. + */ +static void test_success_clears_previous_error(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT FROM;" + ); + + assert(statement == NULL); + + assert( + database_error_get_code(database) == + DATABASE_ERROR_SQLITE + ); + + statement = database_statement_prepare( + database, + "SELECT 1;" + ); + + assert(statement != NULL); + + assert( + database_error_get_code(database) == + DATABASE_ERROR_NONE + ); + + assert( + database_error_get_message(database) == NULL + ); + + database_statement_finalize(statement); + database_close(database); +} + +int main(void) +{ + test_initial_error_state(); + test_statement_error(); + test_success_clears_previous_error(); + + printf( + "DatabaseError : tous les tests sont valides.\n" + ); + + return 0; +} diff --git a/tests/test_investigation_project b/tests/test_investigation_project index 18cee8edc37fe77fd96bf664654544a4e73493b0..ee880919fb46300e47cb5fc16b75e647e726b029 100755 GIT binary patch delta 9730 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 D%5!lL delta 8027 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 SdBT@C{Aw!=%?y@q@9;kps;Wf* diff --git a/tests/test_statement b/tests/test_statement new file mode 100755 index 0000000000000000000000000000000000000000..e72da1b839090d741ef289bbac8a8777968c1b8c GIT binary patch literal 37208 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

_#7Fw)2)*uK z|049;r32=*1n9pI`XZsnrw92u1sxyRyJetCPZa!E;7NY9(Chwry3l_h^t_e>&*ehD zUg)*G^ocT(-zfrkZ3p@$q2D6(y1woa`muuGwI1kyFZ8>GUiZ(>3w z75ZwS*UhW}c)vJrV)`?z)N`FS2|o2)r{{nty9UW~&ttn&M5xkCv-k1Oq{dJ=i zA+ITMYYUz}4oMfUB>|@hJ~x}u7Ox?3dzIjSnWOMhJ=SXA{p|X-&~KQa=(j0g(VD@J z{>}h7zW_cBGuxJHMSubPN*(X=F!0;fKx@D) zno_~HK(Fi6%=v`zeXWlrFn3C3CI)gp`ZF2d*Scz|BILCWZpR1E&k*{>mneE(JK}b+ z;9n|G_}dk*>I3AAhh3*49+rwyy1m>ca<+a=33y86{2X{&?oIGefc&Qe@P7`#zZHN_ z1mHgnz+=Vb>t{OvpC5pqMfe_=<<2mzwXC@T^j9*zul3A$MX$$~8w2Rq1>o-xIm<6r za&-OPBlz6sjIiYE1D4|<<;15VzkZs^<25yI&*1p) zPM-sOrcb+=!g1A|zBm9M175mX4^u+`{SO21zh-=2>vkE?_?ZbL()xkoyzWvZp86fS zUI+e^9s`~EjKW&hyFx!&>hVs==n{H8uhaDDnB9~73#Kc~%aSpa@tVqo7X{!8M9!O1 zt}QI30raZ^@J#{uUj^VF55VsLKGT@*X$xKop#OmJeXYT{ic;%;5+xo0z_P{5%ps z|3U!%Wybflh7~|LTsl4v3I3ll&g%Z857vhiZgeu z(AGsle?;PKrOsGTQWMY3uA(zMmz720(G?aD%W%kRRT-o6KTE43 zE6S^uMav_lact&|MCth|F9xZ~DT*`ST)??A&o)p6 ze7P=CT$G_H!WuuaxaH(A{rOy^6KVGS0??zi+Yo&UPEt|e%QnOkh zpA+m?aE`Md)cA=6l;*>9^^TheZD`Ot6?7)t=k8(hQt+lMI%rvM<+&k-y-dyyH%Ahk z9*mdkUgngtyOoPA2obGVw@i;xZm!ygLnkrw+0Tfl3LI?AdwF2CY}_cXik3!J<+3*D zfnt@V)c-^xGmEFr4MjrpX5h5yNCXd7BB8J#!!wGl$gDZ@r%jy`nLl&pg3!VU@4E-# zj4S3%om)6P^;~0aU5*2sz>aWp<@289m~0;!v(BA^pw;=xYFm=*4uKJ_>247^7S!fz zM=@?Ue)N3$bINJmboz3EKJJ)KHg@%(6T8U@KG@pXl&rQVcR$tJb6~Tt#MINQMSbu2 z)2`dNZ+Fz;bEzE{(&^pUk5g5HGrUVFDzTvhCqX;Ze6%*J%;$_deCqIOC+WJ9i&dKf zk9H_|q;Nj=M#3YJx>`O<+&!b*N2snJ)mG`ObKf(}F|uLDA?>-WaSzD$@aj4-we*~S z?dxynaBR&kGWoD>A9ZEq^NSo6Qjy5QxznZHxcs^*(3DDRt0F6LZaR){ck}gbT=%eU ztq+^S?MGucLLFAOmMdblwb2zuMVrU6>vP|~!s+cMyWb)4k;t;zTB(uLqsbj#k8_PA zP2)H*zBXDy+YKWGR=Fpli{sT1=#F_Q(dX?*+>X`>>XdWUk))&D9dXpW&^WuFZjYH! zsUyUh-sD-481bUoQqER)48-Z@Dcqmt1#@{GWiA{IB`-`0QR`HSjy5%~l%(}v<58 +#include +#include + +/** + * @brief Vérifie la préparation et la finalisation d'une requête valide. + */ +static void test_prepare_valid_statement(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT 1;" + ); + + assert(statement != NULL); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie qu'une requête SQL invalide est refusée. + */ +static void test_prepare_invalid_statement(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT FROM;" + ); + + assert(statement == NULL); + + database_close(database); +} + +/** + * @brief Vérifie le traitement des paramètres invalides. + */ +static void test_invalid_parameters(void) +{ + Database *database = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + assert( + database_statement_prepare( + NULL, + "SELECT 1;" + ) == NULL + ); + + assert( + database_statement_prepare( + database, + NULL + ) == NULL + ); + + assert( + database_statement_prepare( + database, + "" + ) == NULL + ); + + /* + * La fonction doit accepter NULL sans provoquer d'erreur. + */ + database_statement_finalize(NULL); + + database_close(database); +} + +/** + * @brief Vérifie la liaison valide d'un paramètre texte. + */ +static void test_bind_valid_text(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT ?;" + ); + + assert(statement != NULL); + + assert( + database_statement_bind_text( + statement, + 1, + "Labfy Investigation" + ) + ); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie le refus des paramètres invalides. + */ +static void test_bind_invalid_text(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT ?;" + ); + + assert(statement != NULL); + + assert( + !database_statement_bind_text( + NULL, + 1, + "test" + ) + ); + + assert( + !database_statement_bind_text( + statement, + 0, + "test" + ) + ); + + assert( + !database_statement_bind_text( + statement, + 1, + NULL + ) + ); + + /* + * La requête ne possède qu'un seul paramètre. + */ + assert( + !database_statement_bind_text( + statement, + 2, + "hors limite" + ) + ); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie l'exécution d'une requête retournant une ligne. + */ +static void test_step_select_statement(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT 1;" + ); + + assert(statement != NULL); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_DONE + ); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie l'exécution d'une requête sans ligne de résultat. + */ +static void test_step_non_query_statement(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "CREATE TABLE test_step" + "(" + " id INTEGER PRIMARY KEY" + ");" + ); + + assert(statement != NULL); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_DONE + ); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie le traitement d'une requête absente. + */ +static void test_step_invalid_statement(void) +{ + assert( + database_statement_step(NULL) == + DATABASE_STATEMENT_STEP_ERROR + ); +} + +/** + * @brief Vérifie qu'une requête peut être réexécutée après reset. + */ +static void test_reset_statement(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT ?;" + ); + + assert(statement != NULL); + + assert( + database_statement_bind_text( + statement, + 1, + "Labfy Investigation" + ) + ); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_DONE + ); + + assert( + database_statement_reset(statement) + ); + + /* + * Le paramètre texte est toujours lié après le reset. + */ + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_DONE + ); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie le refus d'une requête absente. + */ +static void test_reset_invalid_statement(void) +{ + assert( + !database_statement_reset(NULL) + ); +} + +/** + * @brief Vérifie l'effacement des paramètres d'une requête préparée. + */ +static void test_clear_bindings(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT ?;" + ); + + assert(statement != NULL); + + assert( + database_statement_bind_text( + statement, + 1, + "Première valeur" + ) + ); + + assert( + database_statement_clear_bindings( + statement + ) + ); + + /* + * Le paramètre peut recevoir une nouvelle valeur après effacement. + */ + assert( + database_statement_bind_text( + statement, + 1, + "Nouvelle valeur" + ) + ); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_DONE + ); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie le refus d'une requête absente. + */ +static void test_clear_bindings_invalid_statement(void) +{ + assert( + !database_statement_clear_bindings(NULL) + ); +} + +/** + * @brief Vérifie la détection des colonnes SQL NULL. + */ +static void test_column_is_null(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + bool is_null = false; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT NULL, 'Labfy Investigation';" + ); + + assert(statement != NULL); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_column_is_null( + statement, + 0, + &is_null + ) + ); + + assert(is_null); + + assert( + database_statement_column_is_null( + statement, + 1, + &is_null + ) + ); + + assert(!is_null); + + assert( + !database_statement_column_is_null( + NULL, + 0, + &is_null + ) + ); + + assert( + !database_statement_column_is_null( + statement, + -1, + &is_null + ) + ); + + assert( + !database_statement_column_is_null( + statement, + 2, + &is_null + ) + ); + + assert( + !database_statement_column_is_null( + statement, + 0, + NULL + ) + ); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie la lecture sécurisée des colonnes texte. + */ +static void test_column_text(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + char *value = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT 'Labfy Investigation', NULL;" + ); + + assert(statement != NULL); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_column_text( + statement, + 0, + &value + ) + ); + + assert(value != NULL); + assert( + g_strcmp0( + value, + "Labfy Investigation" + ) == 0 + ); + + g_free(value); + value = NULL; + + /* + * Une colonne SQL NULL produit un pointeur NULL, + * mais la lecture reste valide. + */ + assert( + database_statement_column_text( + statement, + 1, + &value + ) + ); + + assert(value == NULL); + + assert( + !database_statement_column_text( + NULL, + 0, + &value + ) + ); + + assert( + !database_statement_column_text( + statement, + -1, + &value + ) + ); + + assert( + !database_statement_column_text( + statement, + 2, + &value + ) + ); + + assert( + !database_statement_column_text( + statement, + 0, + NULL + ) + ); + + database_statement_finalize(statement); + database_close(database); +} + +/** + * @brief Vérifie la lecture stricte des colonnes entières. + */ +static void test_column_int64(void) +{ + Database *database = NULL; + DatabaseStatement *statement = NULL; + int64_t value = INT64_C(0); + + database = database_open(":memory:"); + + assert(database != NULL); + + statement = database_statement_prepare( + database, + "SELECT 42, NULL, '42';" + ); + + assert(statement != NULL); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_column_int64( + statement, + 0, + &value + ) + ); + + assert(value == INT64_C(42)); + + /* + * SQL NULL n'est pas un entier valide. + */ + assert( + !database_statement_column_int64( + statement, + 1, + &value + ) + ); + + /* + * Le texte '42' n'est pas accepté comme entier. + */ + assert( + !database_statement_column_int64( + statement, + 2, + &value + ) + ); + + assert( + !database_statement_column_int64( + NULL, + 0, + &value + ) + ); + + assert( + !database_statement_column_int64( + statement, + -1, + &value + ) + ); + + assert( + !database_statement_column_int64( + statement, + 3, + &value + ) + ); + + assert( + !database_statement_column_int64( + statement, + 0, + NULL + ) + ); + + database_statement_finalize(statement); + database_close(database); +} + +int main(void) +{ + test_prepare_valid_statement(); + test_prepare_invalid_statement(); + test_invalid_parameters(); + test_bind_valid_text(); + test_bind_invalid_text(); + test_step_select_statement(); + test_step_non_query_statement(); + test_step_invalid_statement(); + test_reset_statement(); + test_reset_invalid_statement(); + test_clear_bindings(); + test_clear_bindings_invalid_statement(); + test_column_is_null(); + test_column_text(); + test_column_int64(); + + printf( + "DatabaseStatement : tous les tests sont valides.\n" + ); + + return 0; +} diff --git a/tests/test_transaction b/tests/test_transaction new file mode 100755 index 0000000000000000000000000000000000000000..004b7cd4dd92706f1f428c38ede573d485915683 GIT binary patch literal 32440 zcmeHw4Rlr2weC*BpMWG-q7+fKR3sq8fPfa{8h#F*D1j6bs}|j!BnLQ}oD&;- zSS7~1QfjTwYV~?wed4v&tJIa!KFN?1uMpxgTJt_y)37o zNes9YRQ@EN(BCH3=Y^+}5H1CYmo9?zcgb=}^@o)LOLZ^MoEMtRub|3r`Mc@H z|4M!}EH`|yRP2Q-I6xFs`PH)Ao@G+b3vXpR6;$Q>7UZZNj`mVvwT$x%s`m|Ce+shM zq7FtOGpfD|med)&BeyIcyzsGGU-;4C_>QjM?Rfu|?yYAv#p-9xIIF2~YEvwpSvz&@ zxwEFuno*uglus9JmUJn7M{9Q%1h}cYL`aVx{$K$Al>vD2r|Hrx6LaAQKmC6J*$;nG z0NxD1FAvaP6JY1V0r;^2_+0_`@*sSGo#z5iW$<3LK=}FLO5jcWd9OJE`F=M5pMrnJ zdzBy^58(eK06#PU|IY#V>yU?ExpoKOUk|`jd-c1eXHdO=en9<7bkH$}nK9BFDwG*nn|$rr%Y7)eL1bgVgQW#Y-`O2|2w zm5F3?Bn=cQ{qG_uk5l=_s=@f8HiBf+IBt$5;EE0b<8Qr9%a($TZIVD+(hBZN{mnh;hxx;E{`#4}AzHb%J2){|(;G{>!2 zJUwfM&5>w{#;rBe?Q}AliaHus#^RACl*uNGOx+sb@#fSjn*&~Mu@en4%ldWcsAG6^ zZB#3q->u=#(C|i! zObiA<*JoctG(5XT&`LDCUIU{wyi*$(%QQTdO*Lx64Nvn!b#2n{ zr3wkSS;NzuNnLkn_|Xapc&~>4x`uy1!;jJMk7)RlHT-rBub$b6+@ax5(fIdh_)|4} zmxdpw;rD5HQ^R*__|r6eR>P0i@Vy%T8ydb(!=J9<4Q`JVgA+9T5Dm|djvDp9&+T7| zPyK6n;|D{+Z3F+!LbLdON4hZI72f!pA-hB&@-v?YIzQnB{4X9?i91TKCR(mLk88rS zl%}pB*Co3YU;U4kxo*Yns}~4q_3hhHSL@!()E<4CY>u0 z>B}fhO*v_p)_^PxeX$H0;Q=b=UPO1D5a?h=dKj#LP}H9&DDtXU#~!# znryC8q(7lFHPu{&NWV{MYN9z)q~E4AHO*X!NbjfgsgyQE`bA2Qqjc}zsr=7V+N5;1 zNIy$yYI?aYq$!@JeOT4H_lj`q+u@A|duwV#(>kU-7jB>1i%QIwUX6u~Lo3UR$K41K z1YG;v{|N=*_JLane6ufIicl;E6NTb&`R?Lz8;EZgL-5B0|IB&$bb$vG68R{dW!|kOzrU0~s zvfUm(8rfo~9K-TUOJ!l=&d@o*```$%>T zDs5w5VVa~eQtEao^$bgyEcNPPN9r*kH$S(_WnOEhTQl)Fl6Fjd2eIZhu}o$*NoF>O zGdq_vyGmqM*G*a)K&1$+^)=@hU6{@>RaZW}P6Oz?*KrgfmKy+{~GHou~?#e=imFO|-R*eFdUgPL)#m$)c=7xUAn{sY;S+ zugk(yy-y26AbusPnmyHBPGU26O6e0=`bw5A)be}-LZZsr>iQbPXO0quO%KeDfH&>B zE^+F3;NL|7IIqZDFyS zS?u{QoFcr1e%gr@-9ZP)d=z;5FoJpmy+vw2Q)+)o@a=u_4%XhJY5zit$_hj!S8J72 z@IC?(rTP&oxQi8>NebFa2e}2nj;b#yCfaA-BUNQt)z4W~cb`+z4@m)fQ0gr9 zNU4~V+Rsv3S?WPeYG+B1e|Avu;h#H(di`^!)ILmVzgzI_eey}xzF5H5LC8vptcx&kI}J*l!^RK9 z0D)Y(0tSiB&_0{{Svz)0uq=$Z+dUQNs4pRjB8x z);&OY3-?=9pxh-gA9AQWA9{o!mCqj{r+V_KbLBHJFrUlqe0oV8@*tHr+bW}u>4h$p z0|HeJwN>7&sT}ne$I5e*H-3qH!mZCD;@_p-v2`yJUQhfxcw}gQOPQDb#79?yt!rwOn^WW5h4emv9z8(lNGg&^D2SEE-ST5-qX z*3kB&t;Vsk8mX=Ck!`(0R_b=UQhQ~kmSKTI_HTZbS_q3&=Vn*x@BgeyIw!ECuYpHa z>L$_F;WJc-Z=do;uboHn_d8|tBJMpdZq+k9qH~M+6xBytU5AUCmL90GZ*aIX8Bud> zb=zH>v?xKGmuQ?zd^u^kf;h`H&WS1-CfUenrWDPopr>SW&+(gkhHmZ|yt!w<=AMGh zRO4aLZxF5oG5Hi3j7!XqNjaXoO8-X5S?F`jnYdD2+j!RnrJB9(`HEV4{WF)bQ*j zV_AyE|H=|)f>60B^SMw#59~y&`+QSP34H1q;kf{=;hd%u`{d++hxE%3kx!$^8_(dz z$d33(FD3KLl2MD6O{U6c@wyL*@mVC%0o0J02)xQ681FN4!g934<4Ha}I^6lJ=oK9g zU4}fw96)p(J3dg2<3C`4?1)19L^yro9eOGSAC;gp+lk`~tWy)pjwkBCFb&ASkUF1^s~bgkp3d!>J3w`^JM=pK_Cs~u_HbKu z2lPVIAIRoC7>T7#TvLyrXUKk&=9sORj?jEiP2FxM$|LZ#6T?CEgn5jJIsO> z?5G}9PZvQ|xE(Vp7~-y{7Vxx&4s2Y1z(`ZS(M=CvBg73f+(-IQLkDnw6Ip_84K?)G z_#@QN&%l>6W&Cxcu{{K4HgBpbeDU|Gg^FC-OMmV#40jmzOFzdCzw8-i(QeBz}$Sub^jz z)BSB&|GJUBh{c9EVwWnh^mq{AozlObMg{ISeY^>tCdGD#Ds;iyQYp+!=bXBjC7nJJ zLUL|Ontun){&UkwLOCMt$nAK``+7S*l!bDC0ToaYB_IN77X;7`?}uAm1OG+fvfE7s zP2XEH2gU4Nb;;s6rv>4SIs0f7+|@DdAUkL!OS5IH(+Qy_D@~4|y>L-a2^280GkX>Z zBi&Yau(gI%gM!d#tufKR9;pX!s~qk0(5?=C2(#Q72w~m>-5{t(j!9qSOzOhucB$P_@pO`^W2@znO*@aN5 zo-!7=D$)9~FlCQPQ4bQ4k262LYGY`cEsf!hlftK z-PyfRjPdVcsP;bilHbGtDg5?J5^OJhLdf@P+YeIpw#!lJ%XjTbvh=*Wt&9V!tF7$o zZ@rqvh*EThX#bjgzEMNNAA!hzN$ZLB0x%CJ^af0>*U~eD&-4B-VP0$KpkBqA!_jcW zKoTPt(?dTHo!Q?VMAx|fpj;rx`YaG+odtpf)h5>Oumb13Iu9A zp+sptJxmS+x7Wmzp{ih>@#zyM&8Z=hou*_9S+)}k#4^)Y z2ty8Ur@5I}t?BFkm|7vKPdug;-?&MNh_UT+kr&PG_C2j0AFk3?pI>Eq`@lb9Db9bc z{toeIZQmy_YQW+I#-nemSU*{qP7}CR>Yjz`o{`x*Q>qI0&_Htlqce1fd?jW+=`j$s zJFep?7tds9n54RJ`wU zv%Se%uIhtwVp9VT`<13%tW5MP&7Z-pO7nkkFH3XL5lZv1U4@>~n5weu#-&wMa7k{^ zNVvQcfRAs2nLN}gb=>*&E zMl9x1W%tx0u%bv%1MaEQ;;Tz6EA%Ui1r@4B7T_M8z*rQ-r*DjcD&ooxC{YDpZ9G4h z(gJ}j5G+6UO~>Q3tP6r@1(W@)e4^X2@^0M2%KN|WYvnVb`bPi%gR%emhQG1v#ri-$ zmk!sA?L|4MU-dWkgdk(}o=Bf>(TjLHD#TJc`X2T#Js#QqT`l~J72bo!+97SBgL6WM zuV=n96rUabRKAUU1B-7H-SeiBqv(cs@xU~&^M)82E zYY46v(w?^a{pmnO|aKNfiuB=eLqt6)6-YKe?k}4Z>pL;q2H{F z!|pNZ1TjVUDJ?2^=dk?F3SJPiAUwT$-dXj)KFndMGQ~Tq;3=t?z8-j8cxOE}EctZ1 z9}$&&jwrbgie=|!Pf343C^19vOi3?>oZVBGI1h+4{Sh;ZH}Fi3<$MmH*{Gk%WP#HZ zeufFWSaDE1Clb+~k3J1~cVua~&;TrQSqmE zJEIr=?lDG|NM-Z^)(N;o_8vZa*B&t)yN-F_m@Cl)!<1X4#W+x|^bLW~1=fRM9%R|=k zT3A&bT2hO

r>4>5`DtQ?+DyXj!dUwWM}wKXc78Ve2IG;(3efLd(sv^I<4oKKa`p z7?zo~6;sSJcYZ`sms2iwUX z2#T)~U8Yt{G0za+1e%kK+0D`BL~`A1+Yq<=ZM0rnRz1ENjh89YCmC>GG?S$A#~OES zjgm=<=#pzlG&jf6nxG-#0bhgS14TShBfx<8SB#1#oiguhEL6MKaTZOrw;XBs(Ud z;(S5#`Hwy;luS}PT>h=a_&^%fHUZ^mZb_t4F?`l-Hb%{+STt!iMa%}YAamk`)I`Yy z*ZA|nJ){tlE@lt^MAYl9tx2i6dDm8WD-n;QjcQVE{mEThWARuzh7JP}K^7&W*JO4* z2&-*p3-eueO^mKw8EGJXL?XUnjx_IjAdL^Ry)snO?)+MiA${}=Q8E@EH={;SEQo66 zrldEf8L5WV(dLM?CYnrv&sZF(U%Ae#(reR*w6rwE8br++PQ4kfDi>$C3B4Vp$n>(m{rx)p@mf#%I6ph zmR46+)y^@NEnU2L{=5Yj@yNWZ6?N2LHfBuK&_#SlOgkspF?swRO*SJ4T9OHT1urHW z2t8Xu7OjjbKNdG*ar{U@Ji`Nm8V=4VICC~4gj5s5nW3G&mDu8 zhpJAudm{b%LxytKlym5zRSUg3e-sY2+m|Ln_Q+^->sS2{{1Y{g(0{q4y!iz5U(_=nmEZjP z4LwWU5AJelgChlk^z%_Qw}O+4=3Ij1S$vORjQUMJe=F`6?#}1afO9+Z`E7s)0AB`t zYEM2-?=bf~m(P#FBSh}`e0~PtaWCZawSde206DQ+I0MiEtOe`@ zOauNM>-Dz*{u-}twgG;KcTFz?-j5fAhXB8imz|^V3S>E6M9%>HI^N0G0=@#62Hc1} z0=EHHV6(+Gz#V`u1HOi>Cx-xU#TJ}VgJ3tdDa`=ffMwZQ!1n;tfJ?E`dmA9ucEwe2 z-7=$KZArn&!v_!94F3@xdkBm@@b?+Sv2bt+fz$Cf1AiNy&F3rNCu2m(q7kDnEFO04 zkPXK9W9Cdg`}Fa^3fUEa-}`Moe-9z%jVQUPaKZ6|3hzV?l72nmQIBuA=m%{2UBK@I zeHiSq<%VWJ~WE~IiO`-(z^N z`g1n@0O%*7f7$J#@3iTML4OMLr(E>yHhnDmq07)O{lZ0mz^0!E`k#(KUjcd=Z|84y z$=__tUk`dW=%={KKiVlj=zm0iNG}s@{}(=N%kKca1^wiUF8c3n`T@|#qyHT5(tnDr z|1jvMqd#5gl8@N(W6_UZ1$xXymmL7b-+7=v7CoK6W=2KO^a)|3w;TTt!0$;Esx96qO7zibfY2Bk5;@iiQjWg1CZd z@)w6FZo;3aDC0)ZMuzvva zcuxQ39C?Gizv5EAb9pkSv6LV#Y_}Gd`n^k@H2*kPT>3*p22Vy!$k}wmtm;iUAxV(`J=VE1zag zub4ioV%GGTiqjubJPa`YocB^Gz6y-58S3})O=h4U2=wEpHJ3uP+6^B2PGkHd9z3lX z62IM_AWheW_$x3@Hq2{8((Xqt7Wh&_wP#7i`YUcokLsTkzE$v#GF1Pd@DJd|&;EZ0 zp7c~v0dd_UfkC_M3ycv)#X^Zt#drrv;?FYwTg?9{^VfLzY0Z-OpW}uv)-b_KYaOJ& z#lwF#^S{g;i&)bHKYgY~{F|9ywf_k7e~RtkbeTxg^^eT|2=l9c@rTS`$_m7qC;0!3 z`Ma6lBqg{yf%o&p1tc!977BPDGYPZ8ruv7yeHQ0z3$6c}H( z#~+1XB=9F0-hTFRbo{0<#wg)-r}&qGU&Xw_5o{3n;f8gV#O`L|h5-JXnSal8$tc$N zaC?6MKfOaa0dca6{V&!`!M~gF=dm8KmIyqJ(|&e7iE@#i8YZf>v(H%1gbHboSo6f~ zhymn>{?{qM`{^$az=s0xO9JpK0`O}B@HYkEe;R=Q1>p;iH`M$|mGH3u{tkg3ZcHhY zoZOs@w;2B&o`;FGP_X0!^b8mNSGhUxM6Q>YSih>@35@@tc&lp|Vl5kD3xGErar@2! zekA%sqf(+^hPdKF4<|Hm9qWJhY>9k~X?F(br*~;3KJ8*>fc{qk@Hv4WZrnOcX1J2` z9e|1Ec)r)cIN<%_ts(%wP~e9fU*K2v=o0S`04>J9&2cW)?txzm{K&#F_Bcy2blu4O zKba`gKbFAw3G=J@2BVGt#r${BfR5`$35*Vb=Y$4c48Xt1dM@I4Fj=Mu6Jb9;lm_4@ z2jJ%i;Ohi_xbX?si;CxK0{H(4_!4b?qKbA)0RPWfe=AqAD%WoV`1dpaqf@2kH#vjP z8E>5~5#k*gZifx=_s^IB{3L-NZoI(uh<9<&b4~#NMZi;e%RJ*kO#pv`;O`$-R|W9j z9Dr{Z_~FJ6rpfZExcw#LuVVXEzqE_-TN(ciDQ3LGc#G#1lNg^Rd?6lwc;3%w<0Iz3 zx=dnLE5h=IWO`b8f=;8Tk6G!+Dr~b#i~WzwfptzUvC_>r2_zAZ(n%JL32RkTqCV1O zHKr5EloiRWHLy{zr74PybU2&BmxGS7h*^9!CMfuEs=hFGgzO#HPVmo zY{{qX@=#kO{kW(1=T`fuP~!g9PE*zfZkY6ws<5{K!!2qL6nrR(tTy^tw2#V5bZt3v za|jD)o2lACcwH1*D5=Jnf8Jti>7qr;L$#LJmk-8;moAxCUA4e_B8)gz#`d{wk0rLY zi<5HrSRo%Cb?%IQ!VaHIBa6zr*HBu=M@|-MS9;2Foya54*YU|toSWl0-H6VusZvDjjYo9rp^Oc?RqNlMr$P@j-!{0mdFfA&mQwZ&_Hm9pF%2Vr_@fj3g zIw|35br(-yqwlJCro28A!vT=7Mx$JGF)fC`u8yQu8|97b;?N)=jt3JIwYgnSSfC}N zO%W2{yOyRjX@rgCB8tmtvIJT}_^v#6lcpn#PSNN_acg)c=d}Oi$24<0hRwAmRC^8t9&ti zc3qW7(JKrkpJjOkJ2;KUV_~O~SL>Q#;E6@atM@Yss`oKq5trBg8er+&hmu$CI}{{3 zUCMsNr{LwF(Yq9tR_{|3tR!JvUi&51Fq&{fx|O_opQB(eGphW(UoDDFRLo5?FWVUN8IT0Cd(-(_WUYlK#zLkCXc*Yhf^>; zK@-;G6_(cb^!(NOm4e4BMuy6d3cZEpmH&-OrZ61J{$#9SsvCxP0#g1;Uahk#xPpk< z<<0*A&~*9L)sk7k6%;tyrRqooQ#LSLA@43J&ijkpLp4P4P{^OG6l2O4*mQ%nh z|9cR0dG$U>K~o7b^p^j1;ITR<+Ay)G_c7|+VQ+pat>B*^Q>@9W_dlDcVd7GJGUcQd z{x2Yq|GAs7-&1U6c@?i-d6gz%YCB3^t(&WJuT2gNRgEfNrS~|<>-nqm5j!lILz$hC zyixKBj_}B<_sLl$Ps+7R$tie}M_#S(mqetHvQw#0aw<*h^n_F8SMTLamiO|5-F4CX z4#HKqu!f=Jz5S1p*P&{Ax{At(OjYvxar{Q7F$I6h9yNX(2b{89O342knj{=f4BDmg VRj84;@sqzbBRS6W2zZjl{{;%x9rpkL literal 0 HcmV?d00001 diff --git a/tests/test_transaction.c b/tests/test_transaction.c new file mode 100644 index 0000000..27c694d --- /dev/null +++ b/tests/test_transaction.c @@ -0,0 +1,238 @@ +/****************************************************************************** + * @file test_transaction.c + * @brief Tests du module DatabaseTransaction. + ******************************************************************************/ + +#include "database/database.h" +#include "database/statement.h" +#include "database/transaction.h" + +#include +#include + +/** + * @brief Crée la table utilisée par les tests. + */ +static void test_create_table( + Database *database +) +{ + DatabaseStatement *statement = NULL; + + statement = database_statement_prepare( + database, + "CREATE TABLE transaction_test" + "(" + " value TEXT NOT NULL" + ");" + ); + + assert(statement != NULL); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_DONE + ); + + database_statement_finalize(statement); +} + +/** + * @brief Insère une valeur dans la table de test. + */ +static void test_insert_value( + Database *database, + const char *value +) +{ + DatabaseStatement *statement = NULL; + + statement = database_statement_prepare( + database, + "INSERT INTO transaction_test (value)" + " VALUES (?);" + ); + + assert(statement != NULL); + + assert( + database_statement_bind_text( + statement, + 1, + value + ) + ); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_DONE + ); + + database_statement_finalize(statement); +} + +/** + * @brief Compte les lignes de la table de test. + */ +static int64_t test_count_values( + Database *database +) +{ + DatabaseStatement *statement = NULL; + int64_t count = INT64_C(0); + + statement = database_statement_prepare( + database, + "SELECT COUNT(*) FROM transaction_test;" + ); + + assert(statement != NULL); + + assert( + database_statement_step(statement) == + DATABASE_STATEMENT_STEP_ROW + ); + + assert( + database_statement_column_int64( + statement, + 0, + &count + ) + ); + + database_statement_finalize(statement); + + return count; +} + +/** + * @brief Vérifie qu'un commit conserve les modifications. + */ +static void test_transaction_commit(void) +{ + Database *database = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + test_create_table(database); + + assert( + database_transaction_begin(database) + ); + + test_insert_value( + database, + "valeur validée" + ); + + assert( + database_transaction_commit(database) + ); + + assert( + test_count_values(database) == + INT64_C(1) + ); + + database_close(database); +} + +/** + * @brief Vérifie qu'un rollback annule les modifications. + */ +static void test_transaction_rollback(void) +{ + Database *database = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + test_create_table(database); + + assert( + database_transaction_begin(database) + ); + + test_insert_value( + database, + "valeur annulée" + ); + + assert( + database_transaction_rollback(database) + ); + + assert( + test_count_values(database) == + INT64_C(0) + ); + + database_close(database); +} + +/** + * @brief Vérifie le refus des états de transaction invalides. + */ +static void test_invalid_transaction_states(void) +{ + Database *database = NULL; + + database = database_open(":memory:"); + + assert(database != NULL); + + assert( + !database_transaction_commit(database) + ); + + assert( + !database_transaction_rollback(database) + ); + + assert( + database_transaction_begin(database) + ); + + assert( + !database_transaction_begin(database) + ); + + assert( + database_transaction_rollback(database) + ); + + assert( + !database_transaction_rollback(database) + ); + + assert( + !database_transaction_begin(NULL) + ); + + assert( + !database_transaction_commit(NULL) + ); + + assert( + !database_transaction_rollback(NULL) + ); + + database_close(database); +} + +int main(void) +{ + test_transaction_commit(); + test_transaction_rollback(); + test_invalid_transaction_states(); + + printf( + "DatabaseTransaction : tous les tests sont valides.\n" + ); + + return 0; +}