refactor(database): migrate initialization to database layer
This commit is contained in:
parent
0c6630e6cf
commit
564d67a65b
17 changed files with 1297 additions and 293 deletions
11
Makefile
11
Makefile
|
|
@ -66,12 +66,16 @@ $(TEST_PROJECT): \
|
||||||
tests/test_investigation_project.c \
|
tests/test_investigation_project.c \
|
||||||
src/core/investigation_project.c \
|
src/core/investigation_project.c \
|
||||||
src/database/database.c \
|
src/database/database.c \
|
||||||
src/database/schema.c
|
src/database/schema.c \
|
||||||
|
src/database/statement.c \
|
||||||
|
src/database/transaction.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
$(TEST_DATABASE): \
|
$(TEST_DATABASE): \
|
||||||
tests/test_database.c \
|
tests/test_database.c \
|
||||||
src/database/database.c \
|
src/database/database.c \
|
||||||
|
src/database/transaction.c \
|
||||||
|
src/database/statement.c \
|
||||||
src/database/schema.c
|
src/database/schema.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
|
|
@ -79,6 +83,7 @@ $(TEST_STATEMENT): \
|
||||||
tests/test_statement.c \
|
tests/test_statement.c \
|
||||||
src/database/database.c \
|
src/database/database.c \
|
||||||
src/database/schema.c \
|
src/database/schema.c \
|
||||||
|
src/database/transaction.c \
|
||||||
src/database/statement.c
|
src/database/statement.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
|
|
@ -87,7 +92,8 @@ $(TEST_TRANSACTION): \
|
||||||
src/database/database.c \
|
src/database/database.c \
|
||||||
src/database/schema.c \
|
src/database/schema.c \
|
||||||
src/database/statement.c \
|
src/database/statement.c \
|
||||||
src/database/transaction.c
|
src/database/transaction.c \
|
||||||
|
src/database/error.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
$(TEST_ERROR): \
|
$(TEST_ERROR): \
|
||||||
|
|
@ -95,6 +101,7 @@ $(TEST_ERROR): \
|
||||||
src/database/database.c \
|
src/database/database.c \
|
||||||
src/database/schema.c \
|
src/database/schema.c \
|
||||||
src/database/statement.c \
|
src/database/statement.c \
|
||||||
|
src/database/transaction.c \
|
||||||
src/database/error.c
|
src/database/error.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
|
|
|
||||||
366
docs/tickets/open/TICKET-026.md
Normal file
366
docs/tickets/open/TICKET-026.md
Normal file
|
|
@ -0,0 +1,366 @@
|
||||||
|
# Ticket #026 — Migrer l’initialisation vers la couche Database
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Le ticket #025 a introduit l’infrastructure principale de la couche Database :
|
||||||
|
|
||||||
|
- contexte opaque `Database` ;
|
||||||
|
- ouverture et fermeture des connexions ;
|
||||||
|
- requêtes préparées avec `DatabaseStatement` ;
|
||||||
|
- bindings et lecture typée ;
|
||||||
|
- gestion des transactions ;
|
||||||
|
- infrastructure d’erreurs.
|
||||||
|
|
||||||
|
Cependant, `database_initialize()` utilise encore directement plusieurs fonctions de l’API SQLite :
|
||||||
|
|
||||||
|
```c
|
||||||
|
sqlite3_open_v2()
|
||||||
|
sqlite3_prepare_v2()
|
||||||
|
sqlite3_bind_text()
|
||||||
|
sqlite3_step()
|
||||||
|
sqlite3_reset()
|
||||||
|
sqlite3_clear_bindings()
|
||||||
|
sqlite3_finalize()
|
||||||
|
sqlite3_close()
|
||||||
|
|
||||||
|
La fonction exécute également directement les commandes SQL suivantes :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
|
||||||
|
BEGIN IMMEDIATE;
|
||||||
|
COMMIT;
|
||||||
|
ROLLBACK;
|
||||||
|
```
|
||||||
|
|
||||||
|
De son côté, `schema_install_v1()` reçoit toujours directement un `sqlite3 *`, ce qui contourne l’abstraction `Database`.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Réécrire l’initialisation d’une nouvelle enquête afin qu’elle utilise l’infrastructure créée au ticket #025.
|
||||||
|
|
||||||
|
L’initialisation doit rester atomique :
|
||||||
|
|
||||||
|
- soit la base est entièrement initialisée ;
|
||||||
|
- soit aucune donnée partielle n’est conservée.
|
||||||
|
|
||||||
|
## Travail à réaliser
|
||||||
|
|
||||||
|
### Adapter l’installation du schéma
|
||||||
|
|
||||||
|
Modifier la signature actuelle :
|
||||||
|
|
||||||
|
```C
|
||||||
|
bool schema_install_v1(
|
||||||
|
sqlite3 *database
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
afin qu’elle reçoive un contexte Database :
|
||||||
|
|
||||||
|
```C
|
||||||
|
bool schema_install_v1(
|
||||||
|
Database *database
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
L’installation du fichier SQL complet pourra continuer à utiliser `sqlite3_exec()` en interne, car le schéma contient plusieurs instructions SQL.
|
||||||
|
|
||||||
|
L’accès au handle SQLite devra passer par l’API interne :
|
||||||
|
|
||||||
|
```C
|
||||||
|
database_get_handle()
|
||||||
|
```
|
||||||
|
|
||||||
|
La fonction ne devra réaliser ni `COMMIT` ni `ROLLBACK`.
|
||||||
|
|
||||||
|
### Migrer l’insertion des métadonnées
|
||||||
|
|
||||||
|
Réécrire les fonctions responsables de l’insertion dans la table `metadata` avec `DatabaseStatement`.
|
||||||
|
|
||||||
|
Les opérations devront utiliser l’API suivante :
|
||||||
|
|
||||||
|
```C
|
||||||
|
database_statement_prepare()
|
||||||
|
database_statement_bind_text()
|
||||||
|
database_statement_step()
|
||||||
|
database_statement_reset()
|
||||||
|
database_statement_clear_bindings()
|
||||||
|
database_statement_finalize()
|
||||||
|
```
|
||||||
|
|
||||||
|
Les métadonnées obligatoires restent :
|
||||||
|
|
||||||
|
```
|
||||||
|
schema_version
|
||||||
|
application
|
||||||
|
created_at
|
||||||
|
investigation_uuid
|
||||||
|
```
|
||||||
|
|
||||||
|
Une seule requête préparée devra pouvoir être réutilisée pour insérer les quatre métadonnées.
|
||||||
|
|
||||||
|
### Migrer l’insertion de l’enquête
|
||||||
|
|
||||||
|
Réécrire l’insertion dans la table `investigation` avec `DatabaseStatement`.
|
||||||
|
|
||||||
|
Les champs insérés restent :
|
||||||
|
|
||||||
|
```C
|
||||||
|
database_open()
|
||||||
|
database_transaction_begin()
|
||||||
|
schema_install_v1()
|
||||||
|
database_transaction_commit()
|
||||||
|
database_transaction_rollback()
|
||||||
|
database_close()
|
||||||
|
```
|
||||||
|
|
||||||
|
Le déroulement attendu est :
|
||||||
|
|
||||||
|
```
|
||||||
|
validation des paramètres
|
||||||
|
↓
|
||||||
|
création du timestamp UTC
|
||||||
|
↓
|
||||||
|
création de l’UUID
|
||||||
|
↓
|
||||||
|
ouverture de Database
|
||||||
|
↓
|
||||||
|
début de transaction
|
||||||
|
↓
|
||||||
|
installation du schéma V1
|
||||||
|
↓
|
||||||
|
insertion des métadonnées
|
||||||
|
↓
|
||||||
|
insertion de l’enquête
|
||||||
|
↓
|
||||||
|
commit
|
||||||
|
↓
|
||||||
|
fermeture de Database
|
||||||
|
```
|
||||||
|
|
||||||
|
Tout échec après le début de la transaction doit provoquer un rollback.
|
||||||
|
|
||||||
|
### Utiliser l’infrastructure d’erreurs
|
||||||
|
|
||||||
|
Les erreurs rencontrées pendant :
|
||||||
|
|
||||||
|
- l’installation du schéma ;
|
||||||
|
- la préparation d’une requête ;
|
||||||
|
- le binding d’un paramètre ;
|
||||||
|
- l’exécution d’une requête ;
|
||||||
|
- le début d’une transaction ;
|
||||||
|
- le commit ;
|
||||||
|
- le rollback ;
|
||||||
|
|
||||||
|
doivent être enregistrées dans le contexte Database lorsque celui-ci est disponible.
|
||||||
|
|
||||||
|
L’infrastructure interne existante pourra être utilisée :
|
||||||
|
|
||||||
|
```C
|
||||||
|
database_set_error()
|
||||||
|
database_clear_error_internal()
|
||||||
|
```
|
||||||
|
|
||||||
|
```C
|
||||||
|
DATABASE_ERROR_SQLITE
|
||||||
|
```
|
||||||
|
|
||||||
|
```C
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT
|
||||||
|
```
|
||||||
|
|
||||||
|
```C
|
||||||
|
DATABASE_ERROR_INVALID_STATE
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nettoyer l’ancien code SQLite
|
||||||
|
|
||||||
|
Supprimer de la logique d’initialisation les appels directs à :
|
||||||
|
|
||||||
|
```C
|
||||||
|
sqlite3_open_v2()
|
||||||
|
sqlite3_prepare_v2()
|
||||||
|
sqlite3_bind_text()
|
||||||
|
sqlite3_step()
|
||||||
|
sqlite3_reset()
|
||||||
|
sqlite3_clear_bindings()
|
||||||
|
sqlite3_finalize()
|
||||||
|
sqlite3_close()
|
||||||
|
```
|
||||||
|
|
||||||
|
Supprimer également les commandes manuelles :
|
||||||
|
|
||||||
|
```SQL
|
||||||
|
BEGIN IMMEDIATE;
|
||||||
|
COMMIT;
|
||||||
|
ROLLBACK;
|
||||||
|
```
|
||||||
|
|
||||||
|
La fonction utilitaire `database_execute_sql()` pourra être conservée uniquement si elle reste nécessaire à `database_open()` pour l’activation des clés étrangères.
|
||||||
|
|
||||||
|
### Mettre à jour la documentation publique
|
||||||
|
|
||||||
|
Dans `include/database/database.h`, supprimer la mention temporaire :
|
||||||
|
|
||||||
|
```
|
||||||
|
Cette fonction conserve temporairement son rôle actuel pendant le
|
||||||
|
refactoring du ticket #025.
|
||||||
|
```
|
||||||
|
|
||||||
|
La documentation de `database_initialize()` devra préciser que :
|
||||||
|
|
||||||
|
- l’initialisation est transactionnelle ;
|
||||||
|
- un échec provoque un rollback ;
|
||||||
|
- la base est fermée avant le retour de la fonction.
|
||||||
|
|
||||||
|
## Tests à ajouter ou adapter
|
||||||
|
|
||||||
|
### Initialisation réussie
|
||||||
|
|
||||||
|
Vérifier qu’une initialisation valide crée :
|
||||||
|
|
||||||
|
- le schéma V1 ;
|
||||||
|
- les tables attendues ;
|
||||||
|
- les métadonnées obligatoires ;
|
||||||
|
- une seule ligne dans investigation ;
|
||||||
|
- le bon nom d’enquête ;
|
||||||
|
- le bon chemin racine ;
|
||||||
|
- un UUID non vide ;
|
||||||
|
- une date de création non vide ;
|
||||||
|
- une date de modification non vide.
|
||||||
|
|
||||||
|
### Paramètres invalides
|
||||||
|
|
||||||
|
Vérifier le refus des cas suivants :
|
||||||
|
|
||||||
|
```
|
||||||
|
database_path == NULL
|
||||||
|
database_path vide
|
||||||
|
investigation_name == NULL
|
||||||
|
investigation_name vide
|
||||||
|
investigation_root_path == NULL
|
||||||
|
investigation_root_path vide
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
|
||||||
|
Provoquer un échec après le démarrage de la transaction.
|
||||||
|
|
||||||
|
Une seconde tentative d’initialisation sur une base déjà initialisée pourra être utilisée pour provoquer une erreur de contrainte ou de création de schéma.
|
||||||
|
|
||||||
|
Après l’échec, vérifier que :
|
||||||
|
|
||||||
|
- aucune donnée partielle supplémentaire n’est conservée ;
|
||||||
|
- aucune seconde enquête n’est ajoutée ;
|
||||||
|
- les métadonnées existantes ne sont pas dupliquées ;
|
||||||
|
- la transaction est annulée ;
|
||||||
|
- la base reste lisible ;
|
||||||
|
- la connexion peut être fermée proprement.
|
||||||
|
|
||||||
|
### Régression
|
||||||
|
|
||||||
|
Tous les tests existants doivent rester valides :
|
||||||
|
|
||||||
|
```
|
||||||
|
InvestigationNode
|
||||||
|
InvestigationTreeModel
|
||||||
|
InvestigationTreeBuilder
|
||||||
|
InvestigationProject
|
||||||
|
Database
|
||||||
|
DatabaseStatement
|
||||||
|
DatabaseTransaction
|
||||||
|
DatabaseError
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critères d’acceptation
|
||||||
|
|
||||||
|
- [ ] `database_initialize()` n’appelle plus directement `sqlite3_open_v2()`.
|
||||||
|
- [ ] `database_initialize()` utilise `database_open()`.
|
||||||
|
- [ ] `database_initialize()` utilise `database_close()`.
|
||||||
|
- [ ] Les transactions utilisent le module `transaction`.
|
||||||
|
- [ ] Les insertions utilisent `DatabaseStatement`.
|
||||||
|
- [ ] `InvestigationNode` Investigation `schema_install_v1()` reçoit un `Database *`.
|
||||||
|
InvestigationNode
|
||||||
|
Investigation `schema_install_v1()` reçoit un `Database *`.
|
||||||
|
- [ ] `schema_install_v1()` récupère le handle avec l’API interne.
|
||||||
|
- [ ] Aucun `sqlite3_stmt *` n’est manipulé dans le code d’initialisation.
|
||||||
|
- [ ] Aucun `BEGIN`, `COMMIT` ou `ROLLBACK` manuel ne reste dans `database.c`.
|
||||||
|
- [ ] Tout échec après le début de la transaction provoque un rollback.
|
||||||
|
- [ ] Les erreurs sont enregistrées dans la couche Database.
|
||||||
|
- [ ] Les tests de réussite sont valides.
|
||||||
|
- [ ] Les tests de paramètres invalides sont valides.
|
||||||
|
- [ ] Les tests de rollback sont valides.
|
||||||
|
- [ ] Les tests de régression sont valides.
|
||||||
|
- [ ] `make` réussit sans erreur.
|
||||||
|
- [ ] `make test` réussit.
|
||||||
|
- [ ] `git diff --check` ne retourne aucune erreur.
|
||||||
|
|
||||||
|
## Hors périmètre
|
||||||
|
|
||||||
|
Ce ticket ne doit pas ajouter :
|
||||||
|
|
||||||
|
- de CRUD métier ;
|
||||||
|
- de dépôt ou repository métier ;
|
||||||
|
- de migration entre plusieurs versions de schéma ;
|
||||||
|
- de système GResource pour embarquer `schema_v1.sql` ;
|
||||||
|
- de création automatique des dossiers parents ;
|
||||||
|
- de modification du schéma SQL V1 ;
|
||||||
|
- de nouvelles tables ;
|
||||||
|
- de logique d’interface graphique ;
|
||||||
|
- de nouvelles fonctionnalités utilisateur.
|
||||||
|
|
||||||
|
## Fichiers principalement concernés
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/database/database.h
|
||||||
|
include/database/schema.h
|
||||||
|
|
||||||
|
src/database/database.c
|
||||||
|
src/database/database_internal.h
|
||||||
|
src/database/schema.c
|
||||||
|
|
||||||
|
tests/test_database.c
|
||||||
|
|
||||||
|
Makefile
|
||||||
|
```
|
||||||
|
|
||||||
|
## Résultat attendu
|
||||||
|
|
||||||
|
À la fin du ticket, `database_initialize()` doit être un utilisateur normal de la couche Database.
|
||||||
|
|
||||||
|
La fonction ne doit plus contourner cette couche avec des appels SQLite directs.
|
||||||
|
|
||||||
|
L’initialisation d’une enquête doit être entièrement transactionnelle, testée et cohérente avec l’architecture mise en place au ticket #025.
|
||||||
|
|
||||||
|
## Commit attendu
|
||||||
|
|
||||||
|
Une fois tous les critères d’acceptation validés, le ticket pourra être enregistré avec le commit suivant :
|
||||||
|
|
||||||
|
```text
|
||||||
|
refactor(database): migrate initialization to database layer
|
||||||
|
```
|
||||||
|
|
||||||
|
Le commit doit principalement contenir :
|
||||||
|
|
||||||
|
```
|
||||||
|
include/database/database.h
|
||||||
|
include/database/schema.h
|
||||||
|
|
||||||
|
src/database/database.c
|
||||||
|
src/database/database_internal.h
|
||||||
|
src/database/schema.c
|
||||||
|
|
||||||
|
tests/test_database.c
|
||||||
|
|
||||||
|
Makefile
|
||||||
|
```
|
||||||
|
Avant le commit, exécuter :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
make
|
||||||
|
make test
|
||||||
|
git diff --check
|
||||||
|
git status --short
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -45,8 +45,21 @@ void database_close(
|
||||||
/**
|
/**
|
||||||
* @brief Initialise la base SQLite d'une nouvelle enquête.
|
* @brief Initialise la base SQLite d'une nouvelle enquête.
|
||||||
*
|
*
|
||||||
* Cette fonction conserve temporairement son rôle actuel pendant le
|
* Cette fonction :
|
||||||
* refactoring du ticket #025.
|
*
|
||||||
|
* - valide les paramètres ;
|
||||||
|
* - ouvre la connexion Database ;
|
||||||
|
* - démarre une transaction ;
|
||||||
|
* - installe le schéma SQLite V1 ;
|
||||||
|
* - insère les métadonnées obligatoires ;
|
||||||
|
* - insère l'enquête courante ;
|
||||||
|
* - valide la transaction ;
|
||||||
|
* - ferme la connexion avant de retourner.
|
||||||
|
*
|
||||||
|
* Tout échec survenant après le début de la transaction provoque
|
||||||
|
* l'annulation des modifications.
|
||||||
|
*
|
||||||
|
* La fonction ne crée pas les dossiers parents du fichier SQLite.
|
||||||
*
|
*
|
||||||
* @param database_path Chemin complet du fichier Enquete.sqlite.
|
* @param database_path Chemin complet du fichier Enquete.sqlite.
|
||||||
* @param investigation_name Nom de l'enquête.
|
* @param investigation_name Nom de l'enquête.
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
#ifndef LABFY_INVESTIGATION_SCHEMA_H
|
#ifndef LABFY_INVESTIGATION_SCHEMA_H
|
||||||
#define LABFY_INVESTIGATION_SCHEMA_H
|
#define LABFY_INVESTIGATION_SCHEMA_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "database/database.h"
|
||||||
|
|
||||||
#include <sqlite3.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Installe le schéma SQLite V1 dans une base ouverte.
|
* @brief Installe le schéma SQLite V1 dans une base ouverte.
|
||||||
|
|
@ -23,12 +23,12 @@
|
||||||
*
|
*
|
||||||
* Elle ne réalise ni COMMIT ni ROLLBACK.
|
* Elle ne réalise ni COMMIT ni ROLLBACK.
|
||||||
*
|
*
|
||||||
* @param database Connexion SQLite ouverte.
|
* @param database Connexion Database ouverte.
|
||||||
*
|
*
|
||||||
* @return true si le schéma a été correctement installé, sinon false.
|
* @return true si le schéma a été correctement installé, sinon false.
|
||||||
*/
|
*/
|
||||||
bool schema_install_v1(
|
bool schema_install_v1(
|
||||||
sqlite3 *database
|
Database *database
|
||||||
);
|
);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "database/database.h"
|
#include "database/database.h"
|
||||||
#include "database/schema.h"
|
#include "database/schema.h"
|
||||||
|
#include "database/transaction.h"
|
||||||
|
#include "database/statement.h"
|
||||||
|
|
||||||
#include "database_internal.h"
|
#include "database_internal.h"
|
||||||
|
|
||||||
|
|
@ -135,97 +137,55 @@ static bool database_execute_sql(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Insère une paire clé-valeur dans la table metadata.
|
* @brief Insère une paire clé-valeur avec une requête préparée.
|
||||||
|
*
|
||||||
|
* La requête est réinitialisée après chaque insertion afin de pouvoir
|
||||||
|
* être réutilisée.
|
||||||
*/
|
*/
|
||||||
static bool database_insert_metadata(
|
static bool database_insert_metadata(
|
||||||
sqlite3 *database,
|
DatabaseStatement *statement,
|
||||||
sqlite3_stmt *statement,
|
|
||||||
const char *key,
|
const char *key,
|
||||||
const char *value
|
const char *value
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
int result = SQLITE_ERROR;
|
if (statement == NULL ||
|
||||||
|
|
||||||
if (database == NULL ||
|
|
||||||
statement == NULL ||
|
|
||||||
key == NULL ||
|
key == NULL ||
|
||||||
value == NULL)
|
value == NULL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_bind_text(
|
if (!database_statement_bind_text(
|
||||||
statement,
|
statement,
|
||||||
1,
|
1,
|
||||||
key,
|
key
|
||||||
-1,
|
))
|
||||||
SQLITE_TRANSIENT
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
{
|
||||||
g_warning(
|
|
||||||
"Impossible de lier la clé '%s' : %s",
|
|
||||||
key,
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_bind_text(
|
if (!database_statement_bind_text(
|
||||||
statement,
|
statement,
|
||||||
2,
|
2,
|
||||||
value,
|
value
|
||||||
-1,
|
))
|
||||||
SQLITE_TRANSIENT
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
{
|
||||||
g_warning(
|
|
||||||
"Impossible de lier la valeur de '%s' : %s",
|
|
||||||
key,
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_step(statement);
|
if (database_statement_step(statement) !=
|
||||||
|
DATABASE_STATEMENT_STEP_DONE)
|
||||||
if (result != SQLITE_DONE)
|
|
||||||
{
|
{
|
||||||
g_warning(
|
|
||||||
"Impossible d'insérer la métadonnée '%s' : %s",
|
|
||||||
key,
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_reset(statement);
|
if (!database_statement_reset(statement))
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
{
|
||||||
g_warning(
|
|
||||||
"Impossible de réinitialiser la requête metadata : %s",
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_clear_bindings(statement);
|
if (!database_statement_clear_bindings(statement))
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
{
|
||||||
g_warning(
|
|
||||||
"Impossible d'effacer les paramètres metadata : %s",
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,13 +196,12 @@ static bool database_insert_metadata(
|
||||||
* @brief Insère toutes les métadonnées obligatoires.
|
* @brief Insère toutes les métadonnées obligatoires.
|
||||||
*/
|
*/
|
||||||
static bool database_insert_all_metadata(
|
static bool database_insert_all_metadata(
|
||||||
sqlite3 *database,
|
Database *database,
|
||||||
const char *created_at,
|
const char *created_at,
|
||||||
const char *investigation_uuid
|
const char *investigation_uuid
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
sqlite3_stmt *statement = NULL;
|
DatabaseStatement *statement = NULL;
|
||||||
int result = SQLITE_ERROR;
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
if (database == NULL ||
|
if (database == NULL ||
|
||||||
|
|
@ -252,59 +211,39 @@ static bool database_insert_all_metadata(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_prepare_v2(
|
statement = database_statement_prepare(
|
||||||
database,
|
database,
|
||||||
database_insert_metadata_sql,
|
database_insert_metadata_sql
|
||||||
-1,
|
|
||||||
&statement,
|
|
||||||
NULL
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
if (statement == NULL)
|
||||||
{
|
{
|
||||||
g_warning(
|
|
||||||
"Impossible de préparer l'insertion des métadonnées : %s",
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
success =
|
success =
|
||||||
database_insert_metadata(
|
database_insert_metadata(
|
||||||
database,
|
|
||||||
statement,
|
statement,
|
||||||
"schema_version",
|
"schema_version",
|
||||||
DATABASE_SCHEMA_VERSION
|
DATABASE_SCHEMA_VERSION
|
||||||
) &&
|
) &&
|
||||||
database_insert_metadata(
|
database_insert_metadata(
|
||||||
database,
|
|
||||||
statement,
|
statement,
|
||||||
"application",
|
"application",
|
||||||
DATABASE_APPLICATION_NAME
|
DATABASE_APPLICATION_NAME
|
||||||
) &&
|
) &&
|
||||||
database_insert_metadata(
|
database_insert_metadata(
|
||||||
database,
|
|
||||||
statement,
|
statement,
|
||||||
"created_at",
|
"created_at",
|
||||||
created_at
|
created_at
|
||||||
) &&
|
) &&
|
||||||
database_insert_metadata(
|
database_insert_metadata(
|
||||||
database,
|
|
||||||
statement,
|
statement,
|
||||||
"investigation_uuid",
|
"investigation_uuid",
|
||||||
investigation_uuid
|
investigation_uuid
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sqlite3_finalize(statement) != SQLITE_OK)
|
database_statement_finalize(statement);
|
||||||
{
|
|
||||||
g_warning(
|
|
||||||
"Impossible de finaliser la requête metadata : %s",
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
@ -313,15 +252,14 @@ static bool database_insert_all_metadata(
|
||||||
* @brief Insère la ligne représentant l'enquête courante.
|
* @brief Insère la ligne représentant l'enquête courante.
|
||||||
*/
|
*/
|
||||||
static bool database_insert_investigation(
|
static bool database_insert_investigation(
|
||||||
sqlite3 *database,
|
Database *database,
|
||||||
const char *investigation_uuid,
|
const char *investigation_uuid,
|
||||||
const char *investigation_name,
|
const char *investigation_name,
|
||||||
const char *investigation_root_path,
|
const char *investigation_root_path,
|
||||||
const char *created_at
|
const char *created_at
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
sqlite3_stmt *statement = NULL;
|
DatabaseStatement *statement = NULL;
|
||||||
int result = SQLITE_ERROR;
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
if (database == NULL ||
|
if (database == NULL ||
|
||||||
|
|
@ -333,117 +271,46 @@ static bool database_insert_investigation(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_prepare_v2(
|
statement = database_statement_prepare(
|
||||||
database,
|
database,
|
||||||
database_insert_investigation_sql,
|
database_insert_investigation_sql
|
||||||
-1,
|
|
||||||
&statement,
|
|
||||||
NULL
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
if (statement == NULL)
|
||||||
{
|
{
|
||||||
g_warning(
|
|
||||||
"Impossible de préparer l'insertion de l'enquête : %s",
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_bind_text(
|
success =
|
||||||
|
database_statement_bind_text(
|
||||||
statement,
|
statement,
|
||||||
1,
|
1,
|
||||||
investigation_uuid,
|
investigation_uuid
|
||||||
-1,
|
) &&
|
||||||
SQLITE_TRANSIENT
|
database_statement_bind_text(
|
||||||
);
|
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sqlite3_bind_text(
|
|
||||||
statement,
|
statement,
|
||||||
2,
|
2,
|
||||||
investigation_name,
|
investigation_name
|
||||||
-1,
|
) &&
|
||||||
SQLITE_TRANSIENT
|
database_statement_bind_text(
|
||||||
);
|
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sqlite3_bind_text(
|
|
||||||
statement,
|
statement,
|
||||||
3,
|
3,
|
||||||
investigation_root_path,
|
investigation_root_path
|
||||||
-1,
|
) &&
|
||||||
SQLITE_TRANSIENT
|
database_statement_bind_text(
|
||||||
);
|
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sqlite3_bind_text(
|
|
||||||
statement,
|
statement,
|
||||||
4,
|
4,
|
||||||
created_at,
|
created_at
|
||||||
-1,
|
) &&
|
||||||
SQLITE_TRANSIENT
|
database_statement_bind_text(
|
||||||
);
|
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sqlite3_bind_text(
|
|
||||||
statement,
|
statement,
|
||||||
5,
|
5,
|
||||||
created_at,
|
created_at
|
||||||
-1,
|
) &&
|
||||||
SQLITE_TRANSIENT
|
database_statement_step(statement) ==
|
||||||
);
|
DATABASE_STATEMENT_STEP_DONE;
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
database_statement_finalize(statement);
|
||||||
{
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sqlite3_step(statement);
|
|
||||||
|
|
||||||
if (result != SQLITE_DONE)
|
|
||||||
{
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
g_warning(
|
|
||||||
"Impossible d'insérer l'enquête : %s",
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sqlite3_finalize(statement) != SQLITE_OK)
|
|
||||||
{
|
|
||||||
g_warning(
|
|
||||||
"Impossible de finaliser la requête investigation : %s",
|
|
||||||
sqlite3_errmsg(database)
|
|
||||||
);
|
|
||||||
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
@ -584,11 +451,11 @@ bool database_initialize(
|
||||||
const char *investigation_root_path
|
const char *investigation_root_path
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
sqlite3 *database = NULL;
|
Database *database = NULL;
|
||||||
|
|
||||||
char *created_at = NULL;
|
char *created_at = NULL;
|
||||||
char *investigation_uuid = NULL;
|
char *investigation_uuid = NULL;
|
||||||
|
|
||||||
int result = SQLITE_ERROR;
|
|
||||||
bool transaction_started = false;
|
bool transaction_started = false;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
|
|
@ -612,43 +479,27 @@ bool database_initialize(
|
||||||
|
|
||||||
if (investigation_uuid == NULL)
|
if (investigation_uuid == NULL)
|
||||||
{
|
{
|
||||||
g_free(created_at);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sqlite3_open_v2(
|
|
||||||
database_path,
|
|
||||||
&database,
|
|
||||||
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 != NULL
|
|
||||||
? sqlite3_errmsg(database)
|
|
||||||
: sqlite3_errstr(result)
|
|
||||||
);
|
|
||||||
|
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!database_execute_sql(
|
database = database_open(
|
||||||
database,
|
database_path
|
||||||
"PRAGMA foreign_keys = ON;"
|
);
|
||||||
))
|
|
||||||
|
if (database == NULL)
|
||||||
{
|
{
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!database_execute_sql(
|
/*
|
||||||
database,
|
* Accès temporaire au handle SQLite.
|
||||||
"BEGIN IMMEDIATE;"
|
*
|
||||||
|
* Il reste nécessaire tant que schema_install_v1() et les anciennes
|
||||||
|
* fonctions d'insertion n'ont pas encore été migrées vers Database.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!database_transaction_begin(
|
||||||
|
database
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
@ -656,7 +507,9 @@ bool database_initialize(
|
||||||
|
|
||||||
transaction_started = true;
|
transaction_started = true;
|
||||||
|
|
||||||
if (!schema_install_v1(database))
|
if (!schema_install_v1(
|
||||||
|
database
|
||||||
|
))
|
||||||
{
|
{
|
||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
|
|
@ -681,9 +534,8 @@ bool database_initialize(
|
||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!database_execute_sql(
|
if (!database_transaction_commit(
|
||||||
database,
|
database
|
||||||
"COMMIT;"
|
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
goto rollback;
|
goto rollback;
|
||||||
|
|
@ -698,9 +550,8 @@ rollback:
|
||||||
|
|
||||||
if (transaction_started)
|
if (transaction_started)
|
||||||
{
|
{
|
||||||
if (!database_execute_sql(
|
if (!database_transaction_rollback(
|
||||||
database,
|
database
|
||||||
"ROLLBACK;"
|
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
g_warning(
|
g_warning(
|
||||||
|
|
@ -714,21 +565,7 @@ rollback:
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
|
||||||
if (database != NULL)
|
database_close(database);
|
||||||
{
|
|
||||||
result = sqlite3_close(database);
|
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
|
||||||
{
|
|
||||||
g_warning(
|
|
||||||
"Impossible de fermer proprement la base '%s' : %s",
|
|
||||||
database_path,
|
|
||||||
sqlite3_errstr(result)
|
|
||||||
);
|
|
||||||
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(investigation_uuid);
|
g_free(investigation_uuid);
|
||||||
g_free(created_at);
|
g_free(created_at);
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,22 @@
|
||||||
|
|
||||||
#include "database/schema.h"
|
#include "database/schema.h"
|
||||||
|
|
||||||
|
#include "database_internal.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Charge le schéma SQL V1 depuis les ressources intégrées.
|
* @brief Charge le schéma SQL V1 depuis le fichier du projet.
|
||||||
|
*
|
||||||
|
* @param database Connexion utilisée pour enregistrer une éventuelle erreur.
|
||||||
*
|
*
|
||||||
* @return Une nouvelle chaîne terminée par zéro, à libérer avec g_free(),
|
* @return Une nouvelle chaîne terminée par zéro, à libérer avec g_free(),
|
||||||
* ou NULL en cas d'échec.
|
* ou NULL en cas d'échec.
|
||||||
*/
|
*/
|
||||||
static char *schema_load_v1_sql(void)
|
static char *schema_load_v1_sql(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
{
|
{
|
||||||
char *schema_sql = NULL;
|
char *schema_sql = NULL;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
|
@ -25,6 +32,14 @@ static char *schema_load_v1_sql(void)
|
||||||
&error
|
&error
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
error != NULL
|
||||||
|
? error->message
|
||||||
|
: "Impossible de charger le schéma SQLite V1."
|
||||||
|
);
|
||||||
|
|
||||||
g_warning(
|
g_warning(
|
||||||
"Impossible de charger database/schema_v1.sql : %s",
|
"Impossible de charger database/schema_v1.sql : %s",
|
||||||
error != NULL
|
error != NULL
|
||||||
|
|
@ -41,9 +56,10 @@ static char *schema_load_v1_sql(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool schema_install_v1(
|
bool schema_install_v1(
|
||||||
sqlite3 *database
|
Database *database
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
sqlite3 *database_handle = NULL;
|
||||||
char *schema_sql = NULL;
|
char *schema_sql = NULL;
|
||||||
char *error_message = NULL;
|
char *error_message = NULL;
|
||||||
|
|
||||||
|
|
@ -54,7 +70,24 @@ bool schema_install_v1(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
schema_sql = schema_load_v1_sql();
|
database_handle = database_get_handle(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
if (database_handle == NULL)
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La connexion SQLite est absente."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
schema_sql = schema_load_v1_sql(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
if (schema_sql == NULL)
|
if (schema_sql == NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -62,7 +95,7 @@ bool schema_install_v1(
|
||||||
}
|
}
|
||||||
|
|
||||||
result = sqlite3_exec(
|
result = sqlite3_exec(
|
||||||
database,
|
database_handle,
|
||||||
schema_sql,
|
schema_sql,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
|
|
@ -73,11 +106,19 @@ bool schema_install_v1(
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
if (result != SQLITE_OK)
|
||||||
{
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
error_message != NULL
|
||||||
|
? error_message
|
||||||
|
: sqlite3_errmsg(database_handle)
|
||||||
|
);
|
||||||
|
|
||||||
g_warning(
|
g_warning(
|
||||||
"Impossible d'installer le schéma SQLite V1 : %s",
|
"Impossible d'installer le schéma SQLite V1 : %s",
|
||||||
error_message != NULL
|
error_message != NULL
|
||||||
? error_message
|
? error_message
|
||||||
: sqlite3_errmsg(database)
|
: sqlite3_errmsg(database_handle)
|
||||||
);
|
);
|
||||||
|
|
||||||
sqlite3_free(error_message);
|
sqlite3_free(error_message);
|
||||||
|
|
@ -87,5 +128,9 @@ bool schema_install_v1(
|
||||||
|
|
||||||
sqlite3_free(error_message);
|
sqlite3_free(error_message);
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,13 +110,24 @@ bool database_statement_bind_text(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
sqlite3 *database_handle = NULL;
|
sqlite3 *database_handle = NULL;
|
||||||
|
const char *error_message = NULL;
|
||||||
int result = SQLITE_ERROR;
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
if (statement == NULL ||
|
if (statement == NULL)
|
||||||
statement->handle == NULL ||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statement->handle == NULL ||
|
||||||
index <= 0 ||
|
index <= 0 ||
|
||||||
value == NULL)
|
value == NULL)
|
||||||
{
|
{
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Paramètres invalides pour la liaison d'un texte."
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,17 +145,30 @@ bool database_statement_bind_text(
|
||||||
statement->database
|
statement->database
|
||||||
);
|
);
|
||||||
|
|
||||||
|
error_message =
|
||||||
|
database_handle != NULL
|
||||||
|
? sqlite3_errmsg(database_handle)
|
||||||
|
: sqlite3_errstr(result);
|
||||||
|
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
|
||||||
g_warning(
|
g_warning(
|
||||||
"Impossible de lier le paramètre texte %d : %s",
|
"Impossible de lier le paramètre texte %d : %s",
|
||||||
index,
|
index,
|
||||||
database_handle != NULL
|
error_message
|
||||||
? sqlite3_errmsg(database_handle)
|
|
||||||
: sqlite3_errstr(result)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
statement->database
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,12 +179,23 @@ bool database_statement_bind_int64(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
sqlite3 *database_handle = NULL;
|
sqlite3 *database_handle = NULL;
|
||||||
|
const char *error_message = NULL;
|
||||||
int result = SQLITE_ERROR;
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
if (statement == NULL ||
|
if (statement == NULL)
|
||||||
statement->handle == NULL ||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statement->handle == NULL ||
|
||||||
index <= 0)
|
index <= 0)
|
||||||
{
|
{
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Paramètres invalides pour la liaison d'un entier."
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,17 +211,30 @@ bool database_statement_bind_int64(
|
||||||
statement->database
|
statement->database
|
||||||
);
|
);
|
||||||
|
|
||||||
|
error_message =
|
||||||
|
database_handle != NULL
|
||||||
|
? sqlite3_errmsg(database_handle)
|
||||||
|
: sqlite3_errstr(result);
|
||||||
|
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
|
||||||
g_warning(
|
g_warning(
|
||||||
"Impossible de lier le paramètre entier %d : %s",
|
"Impossible de lier le paramètre entier %d : %s",
|
||||||
index,
|
index,
|
||||||
database_handle != NULL
|
error_message
|
||||||
? sqlite3_errmsg(database_handle)
|
|
||||||
: sqlite3_errstr(result)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
statement->database
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,12 +244,23 @@ bool database_statement_bind_null(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
sqlite3 *database_handle = NULL;
|
sqlite3 *database_handle = NULL;
|
||||||
|
const char *error_message = NULL;
|
||||||
int result = SQLITE_ERROR;
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
if (statement == NULL ||
|
if (statement == NULL)
|
||||||
statement->handle == NULL ||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statement->handle == NULL ||
|
||||||
index <= 0)
|
index <= 0)
|
||||||
{
|
{
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Paramètres invalides pour la liaison de NULL."
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,17 +275,30 @@ bool database_statement_bind_null(
|
||||||
statement->database
|
statement->database
|
||||||
);
|
);
|
||||||
|
|
||||||
|
error_message =
|
||||||
|
database_handle != NULL
|
||||||
|
? sqlite3_errmsg(database_handle)
|
||||||
|
: sqlite3_errstr(result);
|
||||||
|
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
|
||||||
g_warning(
|
g_warning(
|
||||||
"Impossible de lier le paramètre NULL %d : %s",
|
"Impossible de lier le paramètre NULL %d : %s",
|
||||||
index,
|
index,
|
||||||
database_handle != NULL
|
error_message
|
||||||
? sqlite3_errmsg(database_handle)
|
|
||||||
: sqlite3_errstr(result)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
statement->database
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,25 +307,44 @@ DatabaseStatementStepResult database_statement_step(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
sqlite3 *database_handle = NULL;
|
sqlite3 *database_handle = NULL;
|
||||||
|
const char *error_message = NULL;
|
||||||
int result = SQLITE_ERROR;
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
if (statement == NULL ||
|
if (statement == NULL)
|
||||||
statement->handle == NULL)
|
|
||||||
{
|
{
|
||||||
return DATABASE_STATEMENT_STEP_ERROR;
|
return DATABASE_STATEMENT_STEP_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (statement->handle == NULL)
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La requête préparée SQLite est absente."
|
||||||
|
);
|
||||||
|
|
||||||
|
return DATABASE_STATEMENT_STEP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
result = sqlite3_step(
|
result = sqlite3_step(
|
||||||
statement->handle
|
statement->handle
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == SQLITE_ROW)
|
if (result == SQLITE_ROW)
|
||||||
{
|
{
|
||||||
|
database_clear_error_internal(
|
||||||
|
statement->database
|
||||||
|
);
|
||||||
|
|
||||||
return DATABASE_STATEMENT_STEP_ROW;
|
return DATABASE_STATEMENT_STEP_ROW;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == SQLITE_DONE)
|
if (result == SQLITE_DONE)
|
||||||
{
|
{
|
||||||
|
database_clear_error_internal(
|
||||||
|
statement->database
|
||||||
|
);
|
||||||
|
|
||||||
return DATABASE_STATEMENT_STEP_DONE;
|
return DATABASE_STATEMENT_STEP_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,11 +352,20 @@ DatabaseStatementStepResult database_statement_step(
|
||||||
statement->database
|
statement->database
|
||||||
);
|
);
|
||||||
|
|
||||||
g_warning(
|
error_message =
|
||||||
"Impossible d'exécuter la requête SQL : %s",
|
|
||||||
database_handle != NULL
|
database_handle != NULL
|
||||||
? sqlite3_errmsg(database_handle)
|
? sqlite3_errmsg(database_handle)
|
||||||
: sqlite3_errstr(result)
|
: sqlite3_errstr(result);
|
||||||
|
|
||||||
|
database_set_error(
|
||||||
|
statement->database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
error_message
|
||||||
|
);
|
||||||
|
|
||||||
|
g_warning(
|
||||||
|
"Impossible d'exécuter la requête SQL : %s",
|
||||||
|
error_message
|
||||||
);
|
);
|
||||||
|
|
||||||
return DATABASE_STATEMENT_STEP_ERROR;
|
return DATABASE_STATEMENT_STEP_ERROR;
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,34 @@ static bool database_transaction_execute(
|
||||||
char *error_message = NULL;
|
char *error_message = NULL;
|
||||||
int result = SQLITE_ERROR;
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
if (database == NULL ||
|
if (database == NULL)
|
||||||
sql == NULL ||
|
|
||||||
sql[0] == '\0')
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
database_handle = database_get_handle(database);
|
if (sql == NULL || sql[0] == '\0')
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La commande SQL de transaction est absente."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
database_handle = database_get_handle(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
if (database_handle == NULL)
|
if (database_handle == NULL)
|
||||||
{
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"La connexion SQLite est absente."
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,6 +63,14 @@ static bool database_transaction_execute(
|
||||||
|
|
||||||
if (result != SQLITE_OK)
|
if (result != SQLITE_OK)
|
||||||
{
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
error_message != NULL
|
||||||
|
? error_message
|
||||||
|
: sqlite3_errmsg(database_handle)
|
||||||
|
);
|
||||||
|
|
||||||
g_warning(
|
g_warning(
|
||||||
"Impossible d'exécuter la transaction SQL : %s",
|
"Impossible d'exécuter la transaction SQL : %s",
|
||||||
error_message != NULL
|
error_message != NULL
|
||||||
|
|
@ -67,12 +92,22 @@ bool database_transaction_begin(
|
||||||
Database *database
|
Database *database
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (database == NULL ||
|
if (database == NULL)
|
||||||
database_get_transaction_active(database))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (database_get_transaction_active(database))
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"Une transaction est déjà active."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!database_transaction_execute(
|
if (!database_transaction_execute(
|
||||||
database,
|
database,
|
||||||
"BEGIN IMMEDIATE;"
|
"BEGIN IMMEDIATE;"
|
||||||
|
|
@ -86,6 +121,10 @@ bool database_transaction_begin(
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,12 +132,22 @@ bool database_transaction_commit(
|
||||||
Database *database
|
Database *database
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (database == NULL ||
|
if (database == NULL)
|
||||||
!database_get_transaction_active(database))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!database_get_transaction_active(database))
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"Aucune transaction active à valider."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!database_transaction_execute(
|
if (!database_transaction_execute(
|
||||||
database,
|
database,
|
||||||
"COMMIT;"
|
"COMMIT;"
|
||||||
|
|
@ -112,6 +161,10 @@ bool database_transaction_commit(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
database_clear_error_internal(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,12 +172,22 @@ bool database_transaction_rollback(
|
||||||
Database *database
|
Database *database
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (database == NULL ||
|
if (database == NULL)
|
||||||
!database_get_transaction_active(database))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!database_get_transaction_active(database))
|
||||||
|
{
|
||||||
|
database_set_error(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_INVALID_STATE,
|
||||||
|
"Aucune transaction active à annuler."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!database_transaction_execute(
|
if (!database_transaction_execute(
|
||||||
database,
|
database,
|
||||||
"ROLLBACK;"
|
"ROLLBACK;"
|
||||||
|
|
@ -138,5 +201,12 @@ bool database_transaction_rollback(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On n'efface pas automatiquement l'erreur précédente.
|
||||||
|
*
|
||||||
|
* Un rollback est souvent exécuté à la suite d'une autre erreur.
|
||||||
|
* Cette erreur initiale doit rester disponible pour le diagnostic.
|
||||||
|
*/
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -346,9 +346,169 @@ static void test_database_initialize_missing_parent(void)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie qu'une seconde initialisation échouée est annulée.
|
||||||
|
*
|
||||||
|
* La première initialisation crée une base valide.
|
||||||
|
* La seconde tentative doit échouer sans modifier les données existantes.
|
||||||
|
*/
|
||||||
|
static void test_database_initialize_rollback(void)
|
||||||
|
{
|
||||||
|
char *temporary_directory = NULL;
|
||||||
|
char *database_path = NULL;
|
||||||
|
|
||||||
|
char *investigation_count = NULL;
|
||||||
|
char *metadata_count = NULL;
|
||||||
|
char *investigation_name = NULL;
|
||||||
|
char *investigation_root_path = NULL;
|
||||||
|
char *integrity_result = NULL;
|
||||||
|
|
||||||
|
sqlite3 *database = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
|
temporary_directory = g_dir_make_tmp(
|
||||||
|
"labfy-database-rollback-test-XXXXXX",
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(temporary_directory != NULL);
|
||||||
|
assert(error == NULL);
|
||||||
|
|
||||||
|
database_path = g_build_filename(
|
||||||
|
temporary_directory,
|
||||||
|
"Enquete.sqlite",
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(database_path != NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Première initialisation valide.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
database_initialize(
|
||||||
|
database_path,
|
||||||
|
"Enquete_Initiale",
|
||||||
|
temporary_directory
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La seconde initialisation doit échouer.
|
||||||
|
*
|
||||||
|
* Elle provoquera une erreur pendant l'installation du schéma
|
||||||
|
* ou lors de l'insertion des métadonnées déjà existantes.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_initialize(
|
||||||
|
database_path,
|
||||||
|
"Enquete_Seconde",
|
||||||
|
"/tmp/racine-seconde"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
result = sqlite3_open_v2(
|
||||||
|
database_path,
|
||||||
|
&database,
|
||||||
|
SQLITE_OPEN_READONLY,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
assert(database != NULL);
|
||||||
|
|
||||||
|
investigation_count = test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM investigation;"
|
||||||
|
);
|
||||||
|
|
||||||
|
metadata_count = test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT CAST(COUNT(*) AS TEXT) "
|
||||||
|
"FROM metadata "
|
||||||
|
"WHERE key IN "
|
||||||
|
"("
|
||||||
|
" 'schema_version',"
|
||||||
|
" 'application',"
|
||||||
|
" 'created_at',"
|
||||||
|
" 'investigation_uuid'"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
investigation_name = test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT name "
|
||||||
|
"FROM investigation "
|
||||||
|
"LIMIT 1;"
|
||||||
|
);
|
||||||
|
|
||||||
|
investigation_root_path = test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"SELECT root_path "
|
||||||
|
"FROM investigation "
|
||||||
|
"LIMIT 1;"
|
||||||
|
);
|
||||||
|
|
||||||
|
integrity_result = test_database_read_single_text(
|
||||||
|
database,
|
||||||
|
"PRAGMA integrity_check;"
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Aucune seconde enquête ne doit avoir été ajoutée.
|
||||||
|
*/
|
||||||
|
assert(strcmp(investigation_count, "1") == 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Les quatre métadonnées initiales doivent toujours être présentes
|
||||||
|
* sans duplication.
|
||||||
|
*/
|
||||||
|
assert(strcmp(metadata_count, "4") == 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Les données de la première initialisation doivent être intactes.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
investigation_name,
|
||||||
|
"Enquete_Initiale"
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
strcmp(
|
||||||
|
investigation_root_path,
|
||||||
|
temporary_directory
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La base doit rester cohérente après le rollback.
|
||||||
|
*/
|
||||||
|
assert(strcmp(integrity_result, "ok") == 0);
|
||||||
|
|
||||||
|
result = sqlite3_close(database);
|
||||||
|
|
||||||
|
assert(result == SQLITE_OK);
|
||||||
|
|
||||||
|
assert(g_remove(database_path) == 0);
|
||||||
|
assert(g_rmdir(temporary_directory) == 0);
|
||||||
|
|
||||||
|
g_free(integrity_result);
|
||||||
|
g_free(investigation_root_path);
|
||||||
|
g_free(investigation_name);
|
||||||
|
g_free(metadata_count);
|
||||||
|
g_free(investigation_count);
|
||||||
|
g_free(database_path);
|
||||||
|
g_free(temporary_directory);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
test_database_initialize_valid_database();
|
test_database_initialize_valid_database();
|
||||||
|
test_database_initialize_rollback();
|
||||||
test_database_initialize_invalid_parameters();
|
test_database_initialize_invalid_parameters();
|
||||||
test_database_initialize_missing_parent();
|
test_database_initialize_missing_parent();
|
||||||
|
|
||||||
|
|
|
||||||
BIN
tests/test_error
BIN
tests/test_error
Binary file not shown.
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Vérifie l'état initial d'une connexion.
|
* @brief Vérifie l'état initial d'une connexion.
|
||||||
|
|
@ -121,11 +122,333 @@ static void test_success_clears_previous_error(void)
|
||||||
database_close(database);
|
database_close(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie les erreurs produites par un binding texte.
|
||||||
|
*/
|
||||||
|
static void test_statement_bind_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 ?;"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(statement != NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La requête ne possède qu'un seul paramètre.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
2,
|
||||||
|
"hors limite"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Un binding valide efface l'erreur précédente.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
"valeur valide"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Un indice invalide appartient aux erreurs d'argument.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
0,
|
||||||
|
"valeur"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
database_close(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie les erreurs produites par les bindings entier et NULL.
|
||||||
|
*/
|
||||||
|
static void test_statement_other_bind_errors(void)
|
||||||
|
{
|
||||||
|
Database *database = NULL;
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
|
||||||
|
database = database_open(":memory:");
|
||||||
|
|
||||||
|
assert(database != NULL);
|
||||||
|
|
||||||
|
statement = database_statement_prepare(
|
||||||
|
database,
|
||||||
|
"SELECT ?;"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(statement != NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Indice hors limite : erreur SQLite.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_statement_bind_int64(
|
||||||
|
statement,
|
||||||
|
2,
|
||||||
|
INT64_C(42)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_SQLITE
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Un binding valide efface l'erreur précédente.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
database_statement_bind_int64(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
INT64_C(42)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Indice invalide : erreur d'argument.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_statement_bind_int64(
|
||||||
|
statement,
|
||||||
|
0,
|
||||||
|
INT64_C(42)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Le binding NULL valide efface l'erreur précédente.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
database_statement_bind_null(
|
||||||
|
statement,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Indice hors limite : erreur SQLite.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_statement_bind_null(
|
||||||
|
statement,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_SQLITE
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Indice invalide : erreur d'argument.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_statement_bind_null(
|
||||||
|
statement,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_INVALID_ARGUMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
database_close(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie l'enregistrement d'une erreur pendant sqlite3_step().
|
||||||
|
*/
|
||||||
|
static void test_statement_step_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,
|
||||||
|
"CREATE TABLE step_error_test"
|
||||||
|
"("
|
||||||
|
" value TEXT NOT NULL UNIQUE"
|
||||||
|
");"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(statement != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_statement_step(statement) ==
|
||||||
|
DATABASE_STATEMENT_STEP_DONE
|
||||||
|
);
|
||||||
|
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Première insertion valide.
|
||||||
|
*/
|
||||||
|
statement = database_statement_prepare(
|
||||||
|
database,
|
||||||
|
"INSERT INTO step_error_test (value) "
|
||||||
|
"VALUES (?);"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(statement != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
"valeur unique"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_statement_step(statement) ==
|
||||||
|
DATABASE_STATEMENT_STEP_DONE
|
||||||
|
);
|
||||||
|
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* La seconde insertion viole la contrainte UNIQUE.
|
||||||
|
* La préparation et le binding réussissent, mais step() échoue.
|
||||||
|
*/
|
||||||
|
statement = database_statement_prepare(
|
||||||
|
database,
|
||||||
|
"INSERT INTO step_error_test (value) "
|
||||||
|
"VALUES (?);"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(statement != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_statement_bind_text(
|
||||||
|
statement,
|
||||||
|
1,
|
||||||
|
"valeur unique"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_statement_step(statement) ==
|
||||||
|
DATABASE_STATEMENT_STEP_ERROR
|
||||||
|
);
|
||||||
|
|
||||||
|
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_statement_finalize(statement);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Une opération valide ultérieure efface l'erreur.
|
||||||
|
*/
|
||||||
|
statement = database_statement_prepare(
|
||||||
|
database,
|
||||||
|
"SELECT 1;"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(statement != NULL);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_statement_step(statement) ==
|
||||||
|
DATABASE_STATEMENT_STEP_ROW
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
database_close(database);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
test_initial_error_state();
|
test_initial_error_state();
|
||||||
test_statement_error();
|
test_statement_error();
|
||||||
test_success_clears_previous_error();
|
test_success_clears_previous_error();
|
||||||
|
test_statement_bind_error();
|
||||||
|
test_statement_other_bind_errors();
|
||||||
|
test_statement_step_error();
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
"DatabaseError : tous les tests sont valides.\n"
|
"DatabaseError : tous les tests sont valides.\n"
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -6,6 +6,7 @@
|
||||||
#include "database/database.h"
|
#include "database/database.h"
|
||||||
#include "database/statement.h"
|
#include "database/statement.h"
|
||||||
#include "database/transaction.h"
|
#include "database/transaction.h"
|
||||||
|
#include "database/error.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -224,11 +225,93 @@ static void test_invalid_transaction_states(void)
|
||||||
database_close(database);
|
database_close(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie l'enregistrement des erreurs de transaction.
|
||||||
|
*/
|
||||||
|
static void test_transaction_error_state(void)
|
||||||
|
{
|
||||||
|
Database *database = NULL;
|
||||||
|
const char *error_message = NULL;
|
||||||
|
|
||||||
|
database = database_open(":memory:");
|
||||||
|
|
||||||
|
assert(database != NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Aucun commit n'est possible sans transaction active.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_transaction_commit(database)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_INVALID_STATE
|
||||||
|
);
|
||||||
|
|
||||||
|
error_message = database_error_get_message(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(error_message != NULL);
|
||||||
|
assert(error_message[0] != '\0');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Un début de transaction valide efface l'erreur précédente.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
database_transaction_begin(database)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Une transaction imbriquée est refusée.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_transaction_begin(database)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_INVALID_STATE
|
||||||
|
);
|
||||||
|
|
||||||
|
database_error_clear(database);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_transaction_rollback(database)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_NONE
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Un second rollback est impossible.
|
||||||
|
*/
|
||||||
|
assert(
|
||||||
|
!database_transaction_rollback(database)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
database_error_get_code(database) ==
|
||||||
|
DATABASE_ERROR_INVALID_STATE
|
||||||
|
);
|
||||||
|
|
||||||
|
database_close(database);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
test_transaction_commit();
|
test_transaction_commit();
|
||||||
test_transaction_rollback();
|
test_transaction_rollback();
|
||||||
test_invalid_transaction_states();
|
test_invalid_transaction_states();
|
||||||
|
test_transaction_error_state();
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
"DatabaseTransaction : tous les tests sont valides.\n"
|
"DatabaseTransaction : tous les tests sont valides.\n"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue