1683 lines
32 KiB
C
1683 lines
32 KiB
C
/******************************************************************************
|
|
* @file entity_dao.c
|
|
* @brief Persistance des entités OSINT dans SQLite.
|
|
******************************************************************************/
|
|
|
|
#include "dao/entity_dao.h"
|
|
|
|
#include "database/error.h"
|
|
#include "database/statement.h"
|
|
|
|
#include <glib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
/**
|
|
* @brief Représentation privée du DAO.
|
|
*
|
|
* La connexion Database est empruntée.
|
|
*/
|
|
struct EntityDao
|
|
{
|
|
Database *database;
|
|
};
|
|
|
|
/**
|
|
* @brief Requête d'insertion d'une entité.
|
|
*/
|
|
static const char *const entity_dao_insert_sql =
|
|
"INSERT INTO entites"
|
|
"("
|
|
" id,"
|
|
" type_id,"
|
|
" valeur,"
|
|
" label,"
|
|
" description,"
|
|
" confiance,"
|
|
" created_at,"
|
|
" updated_at,"
|
|
" status"
|
|
")"
|
|
"VALUES"
|
|
"("
|
|
" ?,"
|
|
" ("
|
|
" SELECT id "
|
|
" FROM types_entite "
|
|
" WHERE code = ?"
|
|
" ),"
|
|
" ?,"
|
|
" ?,"
|
|
" ?,"
|
|
" ?,"
|
|
" ?,"
|
|
" ?,"
|
|
" ?"
|
|
");";
|
|
|
|
/**
|
|
* @brief Requête de recherche par UUID.
|
|
*/
|
|
static const char *const entity_dao_find_by_identifier_sql =
|
|
"SELECT "
|
|
" entites.id,"
|
|
" types_entite.code,"
|
|
" entites.valeur,"
|
|
" entites.label,"
|
|
" entites.description,"
|
|
" entites.confiance,"
|
|
" entites.created_at,"
|
|
" entites.updated_at,"
|
|
" entites.status "
|
|
"FROM entites "
|
|
"LEFT JOIN types_entite "
|
|
"ON types_entite.id = entites.type_id "
|
|
"WHERE entites.id = ?;";
|
|
|
|
/**
|
|
* @brief Requête de chargement de toutes les entités.
|
|
*/
|
|
static const char *const entity_dao_list_all_sql =
|
|
"SELECT "
|
|
" entites.id,"
|
|
" types_entite.code,"
|
|
" entites.valeur,"
|
|
" entites.label,"
|
|
" entites.description,"
|
|
" entites.confiance,"
|
|
" entites.created_at,"
|
|
" entites.updated_at,"
|
|
" entites.status "
|
|
"FROM entites "
|
|
"LEFT JOIN types_entite "
|
|
"ON types_entite.id = entites.type_id "
|
|
"ORDER BY "
|
|
" entites.created_at ASC,"
|
|
" entites.id ASC;";
|
|
|
|
/**
|
|
* @brief Requête de comptage.
|
|
*/
|
|
static const char *const entity_dao_count_sql =
|
|
"SELECT COUNT(*) "
|
|
"FROM entites;";
|
|
|
|
/**
|
|
* @brief Requête de détection d'un UUID existant.
|
|
*/
|
|
static const char *const entity_dao_identifier_exists_sql =
|
|
"SELECT 1 "
|
|
"FROM entites "
|
|
"WHERE id = ? "
|
|
"LIMIT 1;";
|
|
|
|
/**
|
|
* @brief Requête de détection d'un type.
|
|
*/
|
|
static const char *const entity_dao_type_exists_sql =
|
|
"SELECT 1 "
|
|
"FROM types_entite "
|
|
"WHERE code = ? "
|
|
"LIMIT 1;";
|
|
|
|
/**
|
|
* @brief Requête de détection du couple type et valeur.
|
|
*/
|
|
static const char *const entity_dao_type_value_exists_sql =
|
|
"SELECT 1 "
|
|
"FROM entites "
|
|
"INNER JOIN types_entite "
|
|
"ON types_entite.id = entites.type_id "
|
|
"WHERE types_entite.code = ? "
|
|
"AND entites.valeur = ? "
|
|
"LIMIT 1;";
|
|
|
|
/**
|
|
* @brief Enregistre une erreur littérale.
|
|
*/
|
|
static void entity_dao_set_error_literal(
|
|
GError **error,
|
|
EntityDaoError error_code,
|
|
const char *message
|
|
)
|
|
{
|
|
if (error == NULL ||
|
|
*error != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR,
|
|
error_code,
|
|
message
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @brief Détermine si SQLite signale un schéma incompatible.
|
|
*/
|
|
static gboolean entity_dao_is_schema_error(
|
|
const char *database_message
|
|
)
|
|
{
|
|
if (database_message == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return g_strstr_len(
|
|
database_message,
|
|
-1,
|
|
"no such table"
|
|
) != NULL ||
|
|
g_strstr_len(
|
|
database_message,
|
|
-1,
|
|
"no such column"
|
|
) != NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Transforme la dernière erreur Database en GError du DAO.
|
|
*/
|
|
static void entity_dao_set_database_error(
|
|
EntityDao *entity_dao,
|
|
GError **error,
|
|
EntityDaoError error_code,
|
|
const char *context
|
|
)
|
|
{
|
|
const char *database_message =
|
|
NULL;
|
|
|
|
if (error == NULL ||
|
|
*error != NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (entity_dao != NULL &&
|
|
entity_dao->database != NULL)
|
|
{
|
|
database_message =
|
|
database_error_get_message(
|
|
entity_dao->database
|
|
);
|
|
}
|
|
|
|
if (error_code == ENTITY_DAO_ERROR_PREPARE &&
|
|
entity_dao_is_schema_error(
|
|
database_message
|
|
))
|
|
{
|
|
error_code =
|
|
ENTITY_DAO_ERROR_SCHEMA;
|
|
}
|
|
|
|
if (database_message != NULL &&
|
|
database_message[0] != '\0')
|
|
{
|
|
g_set_error(
|
|
error,
|
|
ENTITY_DAO_ERROR,
|
|
error_code,
|
|
"%s : %s",
|
|
context,
|
|
database_message
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
g_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR,
|
|
error_code,
|
|
context
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @brief Convertit un statut métier en texte SQLite.
|
|
*/
|
|
static const char *entity_dao_status_to_text(
|
|
EntityStatus status
|
|
)
|
|
{
|
|
switch (status)
|
|
{
|
|
case ENTITY_STATUS_ACTIVE:
|
|
return "active";
|
|
|
|
case ENTITY_STATUS_ARCHIVED:
|
|
return "archived";
|
|
|
|
case ENTITY_STATUS_DELETED:
|
|
return "deleted";
|
|
|
|
case ENTITY_STATUS_UNKNOWN:
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Convertit un statut SQLite en statut métier.
|
|
*/
|
|
static gboolean entity_dao_status_from_text(
|
|
const char *status_text,
|
|
EntityStatus *out_status
|
|
)
|
|
{
|
|
if (status_text == NULL ||
|
|
out_status == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_strcmp0(
|
|
status_text,
|
|
"active"
|
|
) == 0)
|
|
{
|
|
*out_status =
|
|
ENTITY_STATUS_ACTIVE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (g_strcmp0(
|
|
status_text,
|
|
"archived"
|
|
) == 0)
|
|
{
|
|
*out_status =
|
|
ENTITY_STATUS_ARCHIVED;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
if (g_strcmp0(
|
|
status_text,
|
|
"deleted"
|
|
) == 0)
|
|
{
|
|
*out_status =
|
|
ENTITY_STATUS_DELETED;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* @brief Lie un texte facultatif ou SQL NULL.
|
|
*/
|
|
static gboolean entity_dao_bind_optional_text(
|
|
DatabaseStatement *statement,
|
|
int parameter_index,
|
|
const char *value
|
|
)
|
|
{
|
|
if (value == NULL)
|
|
{
|
|
return database_statement_bind_null(
|
|
statement,
|
|
parameter_index
|
|
);
|
|
}
|
|
|
|
return database_statement_bind_text(
|
|
statement,
|
|
parameter_index,
|
|
value
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @brief Exécute une recherche d'existence avec une valeur.
|
|
*/
|
|
static gboolean entity_dao_value_exists(
|
|
EntityDao *entity_dao,
|
|
const char *sql,
|
|
const char *value,
|
|
gboolean *out_exists,
|
|
GError **error
|
|
)
|
|
{
|
|
DatabaseStatement *statement =
|
|
NULL;
|
|
|
|
DatabaseStatementStepResult step_result =
|
|
DATABASE_STATEMENT_STEP_ERROR;
|
|
|
|
gboolean success =
|
|
FALSE;
|
|
|
|
if (entity_dao == NULL ||
|
|
entity_dao->database == NULL ||
|
|
sql == NULL ||
|
|
value == NULL ||
|
|
out_exists == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"Les paramètres de la recherche d'existence sont invalides."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
*out_exists =
|
|
FALSE;
|
|
|
|
statement =
|
|
database_statement_prepare(
|
|
entity_dao->database,
|
|
sql
|
|
);
|
|
|
|
if (statement == NULL)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_PREPARE,
|
|
"Impossible de préparer la recherche d'existence"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!database_statement_bind_text(
|
|
statement,
|
|
1,
|
|
value
|
|
))
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_BIND,
|
|
"Impossible de lier la valeur recherchée"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
step_result =
|
|
database_statement_step(
|
|
statement
|
|
);
|
|
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_ERROR)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_EXECUTE,
|
|
"Impossible d'exécuter la recherche d'existence"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
*out_exists =
|
|
step_result ==
|
|
DATABASE_STATEMENT_STEP_ROW;
|
|
|
|
success =
|
|
TRUE;
|
|
|
|
cleanup:
|
|
|
|
database_statement_finalize(
|
|
statement
|
|
);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* @brief Recherche un couple type et valeur existant.
|
|
*/
|
|
static gboolean entity_dao_type_value_exists(
|
|
EntityDao *entity_dao,
|
|
const char *type_identifier,
|
|
const char *value,
|
|
gboolean *out_exists,
|
|
GError **error
|
|
)
|
|
{
|
|
DatabaseStatement *statement =
|
|
NULL;
|
|
|
|
DatabaseStatementStepResult step_result =
|
|
DATABASE_STATEMENT_STEP_ERROR;
|
|
|
|
gboolean success =
|
|
FALSE;
|
|
|
|
if (entity_dao == NULL ||
|
|
entity_dao->database == NULL ||
|
|
type_identifier == NULL ||
|
|
value == NULL ||
|
|
out_exists == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"Les paramètres de détection du doublon sont invalides."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
*out_exists =
|
|
FALSE;
|
|
|
|
statement =
|
|
database_statement_prepare(
|
|
entity_dao->database,
|
|
entity_dao_type_value_exists_sql
|
|
);
|
|
|
|
if (statement == NULL)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_PREPARE,
|
|
"Impossible de préparer la recherche du doublon"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!database_statement_bind_text(
|
|
statement,
|
|
1,
|
|
type_identifier
|
|
) ||
|
|
!database_statement_bind_text(
|
|
statement,
|
|
2,
|
|
value
|
|
))
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_BIND,
|
|
"Impossible de lier le type et la valeur"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
step_result =
|
|
database_statement_step(
|
|
statement
|
|
);
|
|
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_ERROR)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_EXECUTE,
|
|
"Impossible d'exécuter la recherche du doublon"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
*out_exists =
|
|
step_result ==
|
|
DATABASE_STATEMENT_STEP_ROW;
|
|
|
|
success =
|
|
TRUE;
|
|
|
|
cleanup:
|
|
|
|
database_statement_finalize(
|
|
statement
|
|
);
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* @brief Convertit une erreur SQLite d'insertion.
|
|
*/
|
|
static void entity_dao_set_insert_error(
|
|
EntityDao *entity_dao,
|
|
GError **error
|
|
)
|
|
{
|
|
const char *database_message =
|
|
NULL;
|
|
|
|
if (entity_dao != NULL &&
|
|
entity_dao->database != NULL)
|
|
{
|
|
database_message =
|
|
database_error_get_message(
|
|
entity_dao->database
|
|
);
|
|
}
|
|
|
|
if (database_message != NULL &&
|
|
strstr(
|
|
database_message,
|
|
"entites.id"
|
|
) != NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_CONSTRAINT,
|
|
"Une entité possède déjà cet identifiant."
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
if (database_message != NULL &&
|
|
strstr(
|
|
database_message,
|
|
"entites.type_id"
|
|
) != NULL &&
|
|
strstr(
|
|
database_message,
|
|
"entites.valeur"
|
|
) != NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_CONSTRAINT,
|
|
"Une entité de ce type utilise déjà cette valeur."
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
if (database_message != NULL &&
|
|
(
|
|
strstr(
|
|
database_message,
|
|
"constraint"
|
|
) != NULL ||
|
|
strstr(
|
|
database_message,
|
|
"CONSTRAINT"
|
|
) != NULL
|
|
))
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_CONSTRAINT,
|
|
"L'entité viole une contrainte SQLite"
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_EXECUTE,
|
|
"Impossible d'insérer l'entité"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @brief Construit un EntityRecord depuis la ligne courante.
|
|
*/
|
|
static EntityRecord *entity_dao_read_current_record(
|
|
EntityDao *entity_dao,
|
|
DatabaseStatement *statement,
|
|
GError **error
|
|
)
|
|
{
|
|
EntityRecord *entity_record =
|
|
NULL;
|
|
|
|
char *identifier =
|
|
NULL;
|
|
|
|
char *type_identifier =
|
|
NULL;
|
|
|
|
char *value =
|
|
NULL;
|
|
|
|
char *label =
|
|
NULL;
|
|
|
|
char *description =
|
|
NULL;
|
|
|
|
char *created_at =
|
|
NULL;
|
|
|
|
char *updated_at =
|
|
NULL;
|
|
|
|
char *status_text =
|
|
NULL;
|
|
|
|
int64_t confidence_value =
|
|
-1;
|
|
|
|
EntityStatus status =
|
|
ENTITY_STATUS_UNKNOWN;
|
|
|
|
GError *model_error =
|
|
NULL;
|
|
|
|
if (entity_dao == NULL ||
|
|
entity_dao->database == NULL ||
|
|
statement == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"Impossible de lire une entité avec des paramètres invalides."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (!database_statement_column_text(
|
|
statement,
|
|
0,
|
|
&identifier
|
|
) ||
|
|
!database_statement_column_text(
|
|
statement,
|
|
1,
|
|
&type_identifier
|
|
) ||
|
|
!database_statement_column_text(
|
|
statement,
|
|
2,
|
|
&value
|
|
) ||
|
|
!database_statement_column_text(
|
|
statement,
|
|
3,
|
|
&label
|
|
) ||
|
|
!database_statement_column_text(
|
|
statement,
|
|
4,
|
|
&description
|
|
) ||
|
|
!database_statement_column_int64(
|
|
statement,
|
|
5,
|
|
&confidence_value
|
|
) ||
|
|
!database_statement_column_text(
|
|
statement,
|
|
6,
|
|
&created_at
|
|
) ||
|
|
!database_statement_column_text(
|
|
statement,
|
|
7,
|
|
&updated_at
|
|
) ||
|
|
!database_statement_column_text(
|
|
statement,
|
|
8,
|
|
&status_text
|
|
))
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_READ,
|
|
"Impossible de lire les colonnes de l'entité."
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (confidence_value < 0 ||
|
|
confidence_value > 100)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_RANGE,
|
|
"L'entité persistée possède un niveau de confiance invalide."
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!entity_dao_status_from_text(
|
|
status_text,
|
|
&status
|
|
))
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_MODEL,
|
|
"L'entité persistée possède un statut invalide."
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
entity_record =
|
|
entity_record_new(
|
|
identifier,
|
|
type_identifier,
|
|
value,
|
|
label,
|
|
description,
|
|
(gint) confidence_value,
|
|
created_at,
|
|
updated_at,
|
|
status,
|
|
&model_error
|
|
);
|
|
|
|
if (entity_record == NULL)
|
|
{
|
|
if (model_error != NULL)
|
|
{
|
|
g_set_error(
|
|
error,
|
|
ENTITY_DAO_ERROR,
|
|
ENTITY_DAO_ERROR_MODEL,
|
|
"L'entité persistée est invalide : %s",
|
|
model_error->message
|
|
);
|
|
}
|
|
else
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_MODEL,
|
|
"L'entité persistée ne peut pas être reconstruite."
|
|
);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
|
|
g_clear_error(
|
|
&model_error
|
|
);
|
|
|
|
g_free(
|
|
status_text
|
|
);
|
|
|
|
g_free(
|
|
updated_at
|
|
);
|
|
|
|
g_free(
|
|
created_at
|
|
);
|
|
|
|
g_free(
|
|
description
|
|
);
|
|
|
|
g_free(
|
|
label
|
|
);
|
|
|
|
g_free(
|
|
value
|
|
);
|
|
|
|
g_free(
|
|
type_identifier
|
|
);
|
|
|
|
g_free(
|
|
identifier
|
|
);
|
|
|
|
return entity_record;
|
|
}
|
|
|
|
/**
|
|
* @brief Libère un modèle contenu dans un GPtrArray.
|
|
*/
|
|
static void entity_dao_record_destroy(
|
|
gpointer user_data
|
|
)
|
|
{
|
|
entity_record_free(
|
|
user_data
|
|
);
|
|
}
|
|
|
|
GQuark entity_dao_error_quark(void)
|
|
{
|
|
return g_quark_from_static_string(
|
|
"entity-dao-error-quark"
|
|
);
|
|
}
|
|
|
|
EntityDao *entity_dao_new(
|
|
Database *database,
|
|
GError **error
|
|
)
|
|
{
|
|
EntityDao *entity_dao =
|
|
NULL;
|
|
|
|
g_return_val_if_fail(
|
|
error == NULL || *error == NULL,
|
|
NULL
|
|
);
|
|
|
|
if (database == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"La connexion Database est absente."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
entity_dao =
|
|
g_try_new0(
|
|
EntityDao,
|
|
1
|
|
);
|
|
|
|
if (entity_dao == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_MEMORY,
|
|
"Impossible d'allouer le DAO des entités."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
entity_dao->database =
|
|
database;
|
|
|
|
return entity_dao;
|
|
}
|
|
|
|
void entity_dao_free(
|
|
EntityDao *entity_dao
|
|
)
|
|
{
|
|
if (entity_dao == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
entity_dao->database =
|
|
NULL;
|
|
|
|
g_free(
|
|
entity_dao
|
|
);
|
|
}
|
|
|
|
gboolean entity_dao_insert(
|
|
EntityDao *entity_dao,
|
|
const EntityRecord *entity_record,
|
|
GError **error
|
|
)
|
|
{
|
|
DatabaseStatement *statement =
|
|
NULL;
|
|
|
|
const char *identifier =
|
|
NULL;
|
|
|
|
const char *type_identifier =
|
|
NULL;
|
|
|
|
const char *value =
|
|
NULL;
|
|
|
|
const char *label =
|
|
NULL;
|
|
|
|
const char *description =
|
|
NULL;
|
|
|
|
const char *created_at =
|
|
NULL;
|
|
|
|
const char *updated_at =
|
|
NULL;
|
|
|
|
const char *status_text =
|
|
NULL;
|
|
|
|
gint confidence =
|
|
0;
|
|
|
|
gboolean value_exists =
|
|
FALSE;
|
|
|
|
gboolean success =
|
|
FALSE;
|
|
|
|
g_return_val_if_fail(
|
|
error == NULL || *error == NULL,
|
|
FALSE
|
|
);
|
|
|
|
if (entity_dao == NULL ||
|
|
entity_dao->database == NULL ||
|
|
entity_record == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"Le DAO ou l'entité est absent."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
identifier =
|
|
entity_record_get_identifier(
|
|
entity_record
|
|
);
|
|
|
|
type_identifier =
|
|
entity_record_get_type_identifier(
|
|
entity_record
|
|
);
|
|
|
|
value =
|
|
entity_record_get_value(
|
|
entity_record
|
|
);
|
|
|
|
label =
|
|
entity_record_get_label(
|
|
entity_record
|
|
);
|
|
|
|
description =
|
|
entity_record_get_description(
|
|
entity_record
|
|
);
|
|
|
|
confidence =
|
|
entity_record_get_confidence(
|
|
entity_record
|
|
);
|
|
|
|
created_at =
|
|
entity_record_get_created_at(
|
|
entity_record
|
|
);
|
|
|
|
updated_at =
|
|
entity_record_get_updated_at(
|
|
entity_record
|
|
);
|
|
|
|
status_text =
|
|
entity_dao_status_to_text(
|
|
entity_record_get_status(
|
|
entity_record
|
|
)
|
|
);
|
|
|
|
if (identifier == NULL ||
|
|
type_identifier == NULL ||
|
|
value == NULL ||
|
|
created_at == NULL ||
|
|
updated_at == NULL ||
|
|
status_text == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"L'entité contient une valeur obligatoire absente."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (confidence < 0 ||
|
|
confidence > 100)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_RANGE,
|
|
"Le niveau de confiance dépasse les limites autorisées."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (!entity_dao_value_exists(
|
|
entity_dao,
|
|
entity_dao_identifier_exists_sql,
|
|
identifier,
|
|
&value_exists,
|
|
error
|
|
))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (value_exists)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_CONSTRAINT,
|
|
"Une entité possède déjà cet identifiant."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (!entity_dao_value_exists(
|
|
entity_dao,
|
|
entity_dao_type_exists_sql,
|
|
type_identifier,
|
|
&value_exists,
|
|
error
|
|
))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!value_exists)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_CONSTRAINT,
|
|
"Le type d'entité n'existe pas dans le catalogue SQLite."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (!entity_dao_type_value_exists(
|
|
entity_dao,
|
|
type_identifier,
|
|
value,
|
|
&value_exists,
|
|
error
|
|
))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (value_exists)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_CONSTRAINT,
|
|
"Une entité de ce type utilise déjà cette valeur."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
statement =
|
|
database_statement_prepare(
|
|
entity_dao->database,
|
|
entity_dao_insert_sql
|
|
);
|
|
|
|
if (statement == NULL)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_PREPARE,
|
|
"Impossible de préparer l'insertion de l'entité"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!database_statement_bind_text(
|
|
statement,
|
|
1,
|
|
identifier
|
|
) ||
|
|
!database_statement_bind_text(
|
|
statement,
|
|
2,
|
|
type_identifier
|
|
) ||
|
|
!database_statement_bind_text(
|
|
statement,
|
|
3,
|
|
value
|
|
) ||
|
|
!entity_dao_bind_optional_text(
|
|
statement,
|
|
4,
|
|
label
|
|
) ||
|
|
!entity_dao_bind_optional_text(
|
|
statement,
|
|
5,
|
|
description
|
|
) ||
|
|
!database_statement_bind_int64(
|
|
statement,
|
|
6,
|
|
(int64_t) confidence
|
|
) ||
|
|
!database_statement_bind_text(
|
|
statement,
|
|
7,
|
|
created_at
|
|
) ||
|
|
!database_statement_bind_text(
|
|
statement,
|
|
8,
|
|
updated_at
|
|
) ||
|
|
!database_statement_bind_text(
|
|
statement,
|
|
9,
|
|
status_text
|
|
))
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_BIND,
|
|
"Impossible de lier les données de l'entité"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (database_statement_step(
|
|
statement
|
|
) != DATABASE_STATEMENT_STEP_DONE)
|
|
{
|
|
entity_dao_set_insert_error(
|
|
entity_dao,
|
|
error
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
success =
|
|
TRUE;
|
|
|
|
cleanup:
|
|
|
|
database_statement_finalize(
|
|
statement
|
|
);
|
|
|
|
return success;
|
|
}
|
|
|
|
EntityRecord *entity_dao_find_by_identifier(
|
|
EntityDao *entity_dao,
|
|
const char *identifier,
|
|
GError **error
|
|
)
|
|
{
|
|
DatabaseStatement *statement =
|
|
NULL;
|
|
|
|
EntityRecord *entity_record =
|
|
NULL;
|
|
|
|
DatabaseStatementStepResult step_result =
|
|
DATABASE_STATEMENT_STEP_ERROR;
|
|
|
|
g_return_val_if_fail(
|
|
error == NULL || *error == NULL,
|
|
NULL
|
|
);
|
|
|
|
if (entity_dao == NULL ||
|
|
entity_dao->database == NULL ||
|
|
identifier == NULL ||
|
|
!g_uuid_string_is_valid(
|
|
identifier
|
|
))
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"L'identifiant d'entité est invalide."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
statement =
|
|
database_statement_prepare(
|
|
entity_dao->database,
|
|
entity_dao_find_by_identifier_sql
|
|
);
|
|
|
|
if (statement == NULL)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_PREPARE,
|
|
"Impossible de préparer la recherche de l'entité"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!database_statement_bind_text(
|
|
statement,
|
|
1,
|
|
identifier
|
|
))
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_BIND,
|
|
"Impossible de lier l'identifiant de l'entité"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
step_result =
|
|
database_statement_step(
|
|
statement
|
|
);
|
|
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_ERROR)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_EXECUTE,
|
|
"Impossible d'exécuter la recherche de l'entité"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_DONE)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
entity_record =
|
|
entity_dao_read_current_record(
|
|
entity_dao,
|
|
statement,
|
|
error
|
|
);
|
|
|
|
if (entity_record == NULL)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
step_result =
|
|
database_statement_step(
|
|
statement
|
|
);
|
|
|
|
if (step_result !=
|
|
DATABASE_STATEMENT_STEP_DONE)
|
|
{
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_ROW)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_SCHEMA,
|
|
"Plusieurs entités possèdent le même identifiant."
|
|
);
|
|
}
|
|
else
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_EXECUTE,
|
|
"Impossible de terminer la lecture de l'entité"
|
|
);
|
|
}
|
|
|
|
entity_record_free(
|
|
entity_record
|
|
);
|
|
|
|
entity_record =
|
|
NULL;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
database_statement_finalize(
|
|
statement
|
|
);
|
|
|
|
return entity_record;
|
|
}
|
|
|
|
GPtrArray *entity_dao_list_all(
|
|
EntityDao *entity_dao,
|
|
GError **error
|
|
)
|
|
{
|
|
DatabaseStatement *statement =
|
|
NULL;
|
|
|
|
GPtrArray *entity_records =
|
|
NULL;
|
|
|
|
EntityRecord *entity_record =
|
|
NULL;
|
|
|
|
DatabaseStatementStepResult step_result =
|
|
DATABASE_STATEMENT_STEP_ERROR;
|
|
|
|
g_return_val_if_fail(
|
|
error == NULL || *error == NULL,
|
|
NULL
|
|
);
|
|
|
|
if (entity_dao == NULL ||
|
|
entity_dao->database == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"Le DAO des entités est invalide."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
entity_records =
|
|
g_ptr_array_new_with_free_func(
|
|
entity_dao_record_destroy
|
|
);
|
|
|
|
if (entity_records == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_MEMORY,
|
|
"Impossible d'allouer la liste des entités."
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
statement =
|
|
database_statement_prepare(
|
|
entity_dao->database,
|
|
entity_dao_list_all_sql
|
|
);
|
|
|
|
if (statement == NULL)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_PREPARE,
|
|
"Impossible de préparer le chargement des entités"
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
while (TRUE)
|
|
{
|
|
step_result =
|
|
database_statement_step(
|
|
statement
|
|
);
|
|
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_DONE)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_ERROR)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_EXECUTE,
|
|
"Impossible d'exécuter le chargement des entités"
|
|
);
|
|
|
|
goto error;
|
|
}
|
|
|
|
entity_record =
|
|
entity_dao_read_current_record(
|
|
entity_dao,
|
|
statement,
|
|
error
|
|
);
|
|
|
|
if (entity_record == NULL)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
g_ptr_array_add(
|
|
entity_records,
|
|
entity_record
|
|
);
|
|
|
|
entity_record =
|
|
NULL;
|
|
}
|
|
|
|
database_statement_finalize(
|
|
statement
|
|
);
|
|
|
|
return entity_records;
|
|
|
|
error:
|
|
|
|
entity_record_free(
|
|
entity_record
|
|
);
|
|
|
|
database_statement_finalize(
|
|
statement
|
|
);
|
|
|
|
g_ptr_array_unref(
|
|
entity_records
|
|
);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gboolean entity_dao_count(
|
|
EntityDao *entity_dao,
|
|
guint64 *out_count,
|
|
GError **error
|
|
)
|
|
{
|
|
DatabaseStatement *statement =
|
|
NULL;
|
|
|
|
DatabaseStatementStepResult step_result =
|
|
DATABASE_STATEMENT_STEP_ERROR;
|
|
|
|
int64_t count_value =
|
|
-1;
|
|
|
|
gboolean success =
|
|
FALSE;
|
|
|
|
g_return_val_if_fail(
|
|
error == NULL || *error == NULL,
|
|
FALSE
|
|
);
|
|
|
|
if (entity_dao == NULL ||
|
|
entity_dao->database == NULL ||
|
|
out_count == NULL)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_INVALID_ARGUMENT,
|
|
"Les paramètres du comptage des entités sont invalides."
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
*out_count =
|
|
0;
|
|
|
|
statement =
|
|
database_statement_prepare(
|
|
entity_dao->database,
|
|
entity_dao_count_sql
|
|
);
|
|
|
|
if (statement == NULL)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_PREPARE,
|
|
"Impossible de préparer le comptage des entités"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
step_result =
|
|
database_statement_step(
|
|
statement
|
|
);
|
|
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_ERROR)
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_EXECUTE,
|
|
"Impossible d'exécuter le comptage des entités"
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (step_result !=
|
|
DATABASE_STATEMENT_STEP_ROW)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_READ,
|
|
"Le comptage des entités n'a retourné aucune ligne."
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!database_statement_column_int64(
|
|
statement,
|
|
0,
|
|
&count_value
|
|
))
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_READ,
|
|
"Impossible de lire le nombre d'entités."
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
if (count_value < 0)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_RANGE,
|
|
"Le nombre d'entités retourné par SQLite est invalide."
|
|
);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
step_result =
|
|
database_statement_step(
|
|
statement
|
|
);
|
|
|
|
if (step_result !=
|
|
DATABASE_STATEMENT_STEP_DONE)
|
|
{
|
|
if (step_result ==
|
|
DATABASE_STATEMENT_STEP_ROW)
|
|
{
|
|
entity_dao_set_error_literal(
|
|
error,
|
|
ENTITY_DAO_ERROR_SCHEMA,
|
|
"Le comptage des entités a retourné plusieurs lignes."
|
|
);
|
|
}
|
|
else
|
|
{
|
|
entity_dao_set_database_error(
|
|
entity_dao,
|
|
error,
|
|
ENTITY_DAO_ERROR_EXECUTE,
|
|
"Impossible de terminer le comptage des entités"
|
|
);
|
|
}
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
*out_count =
|
|
(guint64) count_value;
|
|
|
|
success =
|
|
TRUE;
|
|
|
|
cleanup:
|
|
|
|
database_statement_finalize(
|
|
statement
|
|
);
|
|
|
|
return success;
|
|
}
|