refactor(database): introduce database infrastructure layer
This commit is contained in:
parent
c2df647b46
commit
0c6630e6cf
22 changed files with 2655 additions and 17 deletions
39
Makefile
39
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
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
* Schéma SQLite officiel V1
|
||||
******************************************************************************/
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE metadata
|
||||
(
|
||||
key TEXT PRIMARY KEY,
|
||||
|
|
|
|||
215
docs/tickets/closed/TICKET-025.md
Normal file
215
docs/tickets/closed/TICKET-025.md
Normal file
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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 <stdbool.h>
|
||||
|
||||
/**
|
||||
* @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,
|
||||
|
|
|
|||
56
include/database/error.h
Normal file
56
include/database/error.h
Normal file
|
|
@ -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
|
||||
217
include/database/statement.h
Normal file
217
include/database/statement.h
Normal file
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @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
|
||||
48
include/database/transaction.h
Normal file
48
include/database/transaction.h
Normal file
|
|
@ -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 <stdbool.h>
|
||||
|
||||
/**
|
||||
* @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
|
||||
Binary file not shown.
|
|
@ -6,6 +6,8 @@
|
|||
#include "database/database.h"
|
||||
#include "database/schema.h"
|
||||
|
||||
#include "database_internal.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
73
src/database/database_internal.h
Normal file
73
src/database/database_internal.h
Normal file
|
|
@ -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 <sqlite3.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @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
|
||||
31
src/database/error.c
Normal file
31
src/database/error.c
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/******************************************************************************
|
||||
* @file error.c
|
||||
* @brief Gestion des erreurs de la couche Database.
|
||||
******************************************************************************/
|
||||
|
||||
#include "database/error.h"
|
||||
|
||||
#include "database_internal.h"
|
||||
#include <stddef.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
523
src/database/statement.c
Normal file
523
src/database/statement.c
Normal file
|
|
@ -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 <glib.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
142
src/database/transaction.c
Normal file
142
src/database/transaction.c
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/******************************************************************************
|
||||
* @file transaction.c
|
||||
* @brief Gestion des transactions SQLite.
|
||||
******************************************************************************/
|
||||
|
||||
#include "database/transaction.h"
|
||||
|
||||
#include "database_internal.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
Binary file not shown.
BIN
tests/test_error
Executable file
BIN
tests/test_error
Executable file
Binary file not shown.
135
tests/test_error.c
Normal file
135
tests/test_error.c
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/******************************************************************************
|
||||
* @file test_error.c
|
||||
* @brief Tests de l'infrastructure d'erreurs Database.
|
||||
******************************************************************************/
|
||||
|
||||
#include "database/database.h"
|
||||
#include "database/error.h"
|
||||
#include "database/statement.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
Binary file not shown.
BIN
tests/test_statement
Executable file
BIN
tests/test_statement
Executable file
Binary file not shown.
689
tests/test_statement.c
Normal file
689
tests/test_statement.c
Normal file
|
|
@ -0,0 +1,689 @@
|
|||
/******************************************************************************
|
||||
* @file test_statement.c
|
||||
* @brief Tests du module DatabaseStatement.
|
||||
******************************************************************************/
|
||||
|
||||
#include "database/database.h"
|
||||
#include "database/statement.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <glib.h>
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
BIN
tests/test_transaction
Executable file
BIN
tests/test_transaction
Executable file
Binary file not shown.
238
tests/test_transaction.c
Normal file
238
tests/test_transaction.c
Normal file
|
|
@ -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 <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
Loading…
Reference in a new issue