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_TREE_BUILDER = tests/test_investigation_tree_builder
|
||||||
TEST_PROJECT = tests/test_investigation_project
|
TEST_PROJECT = tests/test_investigation_project
|
||||||
TEST_DATABASE = tests/test_database
|
TEST_DATABASE = tests/test_database
|
||||||
|
TEST_STATEMENT = tests/test_statement
|
||||||
|
TEST_TRANSACTION = tests/test_transaction
|
||||||
|
TEST_ERROR = tests/test_error
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -72,18 +75,47 @@ $(TEST_DATABASE): \
|
||||||
src/database/schema.c
|
src/database/schema.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(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: \
|
||||||
$(TEST_NODE) \
|
$(TEST_NODE) \
|
||||||
$(TEST_TREE_MODEL) \
|
$(TEST_TREE_MODEL) \
|
||||||
$(TEST_TREE_BUILDER) \
|
$(TEST_TREE_BUILDER) \
|
||||||
$(TEST_PROJECT) \
|
$(TEST_PROJECT) \
|
||||||
$(TEST_DATABASE)
|
$(TEST_DATABASE) \
|
||||||
|
$(TEST_STATEMENT) \
|
||||||
|
$(TEST_TRANSACTION) \
|
||||||
|
$(TEST_ERROR)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
@./$(TEST_TREE_BUILDER)
|
@./$(TEST_TREE_BUILDER)
|
||||||
@./$(TEST_PROJECT)
|
@./$(TEST_PROJECT)
|
||||||
@./$(TEST_DATABASE)
|
@./$(TEST_DATABASE)
|
||||||
|
@$(TEST_STATEMENT)
|
||||||
|
@$(TEST_TRANSACTION)
|
||||||
|
@$(TEST_ERROR)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -98,6 +130,9 @@ clean:
|
||||||
$(TEST_TREE_MODEL) \
|
$(TEST_TREE_MODEL) \
|
||||||
$(TEST_TREE_BUILDER) \
|
$(TEST_TREE_BUILDER) \
|
||||||
$(TEST_PROJECT) \
|
$(TEST_PROJECT) \
|
||||||
$(TEST_DATABASE)
|
$(TEST_DATABASE) \
|
||||||
|
$(TEST_STATEMENT) \
|
||||||
|
$(TEST_TRANSACTION) \
|
||||||
|
$(TEST_ERROR)
|
||||||
|
|
||||||
.PHONY: clean run test
|
.PHONY: clean run test
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
* Schéma SQLite officiel V1
|
* Schéma SQLite officiel V1
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
PRAGMA foreign_keys = ON;
|
|
||||||
|
|
||||||
CREATE TABLE metadata
|
CREATE TABLE metadata
|
||||||
(
|
(
|
||||||
key TEXT PRIMARY KEY,
|
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
|
* @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
|
#ifndef LABFY_INVESTIGATION_DATABASE_H
|
||||||
|
|
@ -9,24 +9,50 @@
|
||||||
#include <stdbool.h>
|
#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
|
* La fonction :
|
||||||
* courant, puis enregistre les métadonnées de l'enquête.
|
|
||||||
*
|
*
|
||||||
* 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
|
* @param database_path Chemin du fichier SQLite.
|
||||||
* Chemin complet du fichier Enquete.sqlite.
|
|
||||||
*
|
*
|
||||||
* @param investigation_name
|
* @return Une nouvelle instance de Database, ou NULL en cas d'échec.
|
||||||
* Nom de l'enquête.
|
*/
|
||||||
|
Database *database_open(
|
||||||
|
const char *database_path
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ferme une base SQLite et libère ses ressources.
|
||||||
*
|
*
|
||||||
* @param investigation_root_path
|
* Cette fonction accepte NULL.
|
||||||
* Chemin complet du dossier racine de l'enquête.
|
|
||||||
*
|
*
|
||||||
* @return true si la base a été correctement initialisée,
|
* @param database Instance à fermer.
|
||||||
* sinon false.
|
*/
|
||||||
|
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(
|
bool database_initialize(
|
||||||
const char *database_path,
|
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/database.h"
|
||||||
#include "database/schema.h"
|
#include "database/schema.h"
|
||||||
|
|
||||||
|
#include "database_internal.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
|
@ -19,6 +21,18 @@
|
||||||
*/
|
*/
|
||||||
#define DATABASE_APPLICATION_NAME "Labfy Investigation"
|
#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.
|
* @brief Requête d'insertion d'une métadonnée.
|
||||||
*/
|
*/
|
||||||
|
|
@ -461,6 +475,109 @@ static char *database_create_utc_timestamp(void)
|
||||||
return timestamp;
|
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(
|
bool database_initialize(
|
||||||
const char *database_path,
|
const char *database_path,
|
||||||
const char *investigation_name,
|
const char *investigation_name,
|
||||||
|
|
@ -618,3 +735,98 @@ cleanup:
|
||||||
|
|
||||||
return success;
|
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