/* Chrysalide - Outil d'analyse de fichiers binaires * sqlite.c - extension des définitions propres à SQLite * * Copyright (C) 2015-2019 Cyrille Bagard * * This file is part of Chrysalide. * * Chrysalide is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Chrysalide is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Chrysalide. If not, see . */ #include "sqlite.h" #include #include #include #include "extstr.h" /* Attribue une définition aux valeurs paramétrées. */ static bool bind_bound_values(sqlite3 *, sqlite3_stmt *, const char *, const bound_value *, size_t, int *); /****************************************************************************** * * * Paramètres : values = tableau d'éléments à consulter. * * count = nombre de descriptions renseignées. * * * * Description : Libère de la mémoire un ensemble de valeurs en fin de vie. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void free_all_bound_values(bound_value *values, size_t count) { size_t i; /* Boucle de parcours */ bound_value *value; /* Valeur à traiter */ return; for (i = 0; i < count; i++) { value = values + i; if (value->built_name) free(value->name); } if (values != NULL) free(values); } /****************************************************************************** * * * Paramètres : values = tableau d'éléments à consulter. * * count = nombre de descriptions renseignées. * * name = désignation de la valeur recherchée. * * * * Description : Effectue une recherche au sein d'un ensemble de valeurs. * * * * Retour : Elément retrouvé ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ const bound_value *find_bound_value(const bound_value *values, size_t count, const char *name) { const bound_value *result; /* Trouvaille à retourner */ size_t i; /* Boucle de parcours */ result = NULL; for (i = 0; i < count && result == NULL; i++) if (strcmp(values[i].name, name) == 0) result = &values[i]; return result; } /****************************************************************************** * * * Paramètres : db = base de données à consulter. * * stmt = requête SQL en préparation à faire évoluer. * * sql = définition brute de cette requête SQL. * * values = tableau d'éléments à consulter. * * count = nombre de descriptions renseignées. * * index = indice évolutif des valeurs paramétrées. [OUT] * * * * Description : Attribue une définition aux valeurs paramétrées. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool bind_bound_values(sqlite3 *db, sqlite3_stmt *stmt, const char *sql, const bound_value *values, size_t count, int *index) { bool result; /* Bilan à retourner */ size_t i; /* Boucle de parcours */ int ret; /* Bilan d'un appel à SQLite */ result = true; for (i = 0; i < count && result; i++) { if (!values[i].has_value) continue; switch (values[i].type) { case SQLITE_BOOLEAN: ret = sqlite3_bind_int(stmt, *index, values[i].boolean); break; case SQLITE_INTEGER: ret = sqlite3_bind_int(stmt, *index, values[i].integer); break; case SQLITE_INT64: ret = sqlite3_bind_int64(stmt, *index, values[i].integer64); break; case SQLITE_TEXT: ret = sqlite3_bind_text(stmt, *index, values[i].string, -1, values[i].delete); break; case SQLITE_NULL: ret = sqlite3_bind_null(stmt, *index); break; default: assert(false); ret = SQLITE_ERROR; break; } if (ret == SQLITE_OK) (*index)++; else { result = false; fprintf(stderr, "Can't bind value for parameter nb %d in '%s' (ret=%d): %s\n", *index, sql, ret, sqlite3_errmsg(db)); } } return result; } /****************************************************************************** * * * Paramètres : db = base de données à consulter. * * table = nom de la table concernée. * * values = champs avec leur valeur. * * count = quantité de ces champs. * * cb = procédure à appeler pour chaque nouvelle série. * * data = éventuelles données associées à transmettre. * * * * Description : Charge une série de valeurs depuis une base de données. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool load_db_values(sqlite3 *db, const char *table, bound_value *values, size_t count, db_load_cb cb, void *data) { bool result; /* Conclusion à faire remonter */ char *sql; /* Requête SQL à construire */ bool first; /* Marque du premier passage */ size_t i; /* Boucle de parcours */ sqlite3_stmt *stmt; /* Déclaration mise en place */ int ret; /* Bilan d'un appel à SQLite */ int index; /* Indice de valeur attachée */ int native_type; /* Type de valeur dans la base */ result = false; /* Préparation de la requête */ sql = strdup("SELECT "); first = true; for (i = 0; i < count; i++) { if (values[i].has_value) continue; if (!first) sql = stradd(sql, ", "); sql = stradd(sql, values[i].name); first = false; } assert(!first); sql = stradd(sql, " FROM "); sql = stradd(sql, table); first = true; for (i = 0; i < count; i++) { if (!values[i].has_value) continue; if (first) sql = stradd(sql, " WHERE "); else sql = stradd(sql, " AND "); sql = stradd(sql, values[i].name); sql = stradd(sql, " = ?"); first = false; } sql = stradd(sql, ";"); ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (ret != SQLITE_OK) { fprintf(stderr, "Can't prepare SELECT statment '%s' (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto prepare_error; } /* Attribution des valeurs */ index = 1; if (!bind_bound_values(db, stmt, sql, values, count, &index)) goto bind_error; /* Chargement des valeurs existantes */ result = true; for (ret = sqlite3_step(stmt); ret == SQLITE_ROW && result; ret = sqlite3_step(stmt)) { /* Conversion des valeurs */ index = 0; for (i = 0; i < count; i++) { if (values[i].has_value) continue; native_type = sqlite3_column_type(stmt, index); /** * On réalise une petite conversion selon le champ. * * Le filtre SQLITE_NATIVE est destiné à conserver le type choisi par * SQLite. Typiquement, une chaîne peut être à SQLITE_NULL ou SQLITE_TEXT * selon la valeur conservée dans la base. * * D'autres éléments, comme les localisations en mémoire, peuvent aussi * avoir un champ éventuellement nul, donc la définition à partir des * indications de la base de données reste importante. * * En ce qui concerne les valeurs numériques, SQLite ne fait pas de * distinction : tout passe par la fonction sqlite3VdbeIntValue(), * qui effectue des transtypages au besoin pour tout ce qui n'est * pas numérique. * * Pour les types internes SQLITE_INTEGER et SQLITE_BOOLEAN, * il est donc nécessaire d'ajuster en interne. */ if (native_type == SQLITE_INTEGER) native_type = SQLITE_INT64; if (values[i].type == SQLITE_NATIVE) values[i].type = native_type; else assert(values[i].type == native_type || values[i].type == SQLITE_INTEGER || values[i].type == SQLITE_BOOLEAN); switch (values[i].type) { case SQLITE_BOOLEAN: values[i].boolean = (bool)sqlite3_column_int(stmt, index); break; case SQLITE_INTEGER: values[i].integer = sqlite3_column_int(stmt, index); break; case SQLITE_INT64: values[i].integer64 = sqlite3_column_int64(stmt, index); break; case SQLITE_FLOAT: assert(0); break; case SQLITE_TEXT: values[i].cstring = (const char *)sqlite3_column_text(stmt, index); break; case SQLITE_BLOB: assert(0); break; case SQLITE_NULL: break; default: assert(0); break; } index++; } /* Chargement d'un nouvel élément */ cb(values, count, data); } bind_error: sqlite3_finalize(stmt); prepare_error: free(sql); return result; } /****************************************************************************** * * * Paramètres : db = base de données à mettre à jour. * * table = nom de la table concernée. * * values = champs avec leur valeur. * * count = quantité de ces champs. * * * * Description : Enregistre une série de valeurs dans une base de données. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool store_db_values(sqlite3 *db, const char *table, const bound_value *values, size_t count) { bool result; /* Conclusion à faire remonter */ char *sql; /* Requête SQL à construire */ size_t i; /* Boucle de parcours */ sqlite3_stmt *stmt; /* Déclaration mise en place */ int ret; /* Bilan d'un appel à SQLite */ int index; /* Indice de valeur attachée */ result = false; /* Préparation de la requête */ sql = strdup("INSERT INTO "); sql = stradd(sql, table); sql = stradd(sql, " ("); for (i = 0; i < count; i++) { assert(values[i].has_value); if (i > 0) sql = stradd(sql, ", "); sql = stradd(sql, values[i].name); } sql = stradd(sql, ") VALUES ("); for (i = 0; i < count; i++) { if (i > 0) sql = stradd(sql, ", "); if (values[i].type == SQLITE_RAW) sql = stradd(sql, values[i].cstring); else sql = stradd(sql, "?"); } sql = stradd(sql, ");"); ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (ret != SQLITE_OK) { fprintf(stderr, "Can't prepare INSERT statment '%s' (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto prepare_error; } /* Attribution des valeurs */ index = 1; if (!bind_bound_values(db, stmt, sql, values, count, &index)) goto bind_error; /* Exécution finale */ ret = sqlite3_step(stmt); if (ret != SQLITE_DONE) { fprintf(stderr, "INSERT statement '%s' didn't return DONE (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto insert_error; } result = true; insert_error: bind_error: sqlite3_finalize(stmt); prepare_error: free(sql); return result; } /****************************************************************************** * * * Paramètres : db = base de données à mettre à jour. * * table = nom de la table concernée. * * values = champs avec leur valeur nouvelle. * * vcount = quantité de ces champs. * * values = champs avec leur valeur de condition. * * ccount = quantité de ces champs. * * * * Description : Met à jour une série de valeurs dans une base de données. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool update_db_values(sqlite3 *db, const char *table, const bound_value *values, size_t vcount, const bound_value *conds, size_t ccount) { bool result; /* Conclusion à faire remonter */ char *sql; /* Requête SQL à construire */ size_t i; /* Boucle de parcours */ sqlite3_stmt *stmt; /* Déclaration mise en place */ int ret; /* Bilan d'un appel à SQLite */ int index; /* Indice de valeur attachée */ result = false; /* Préparation de la requête */ sql = strdup("UPDATE "); sql = stradd(sql, table); sql = stradd(sql, " SET"); for (i = 0; i < vcount; i++) { assert(values[i].has_value); if (i > 0) sql = stradd(sql, " ,"); sql = stradd(sql, " "); sql = stradd(sql, values[i].name); sql = stradd(sql, " = ?"); } if (ccount > 0) { sql = stradd(sql, " WHERE"); for (i = 0; i < ccount; i++) { if (i > 0) sql = stradd(sql, " AND"); sql = stradd(sql, " "); sql = stradd(sql, conds[i].name); sql = stradd(sql, " = ?"); } } sql = stradd(sql, ";"); ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (ret != SQLITE_OK) { fprintf(stderr, "Can't prepare UPDATE statment '%s' (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto prepare_error; } /* Attribution des valeurs */ index = 1; if (!bind_bound_values(db, stmt, sql, values, vcount, &index)) goto bind_error; if (!bind_bound_values(db, stmt, sql, conds, ccount, &index)) goto bind_error; /* Exécution finale */ ret = sqlite3_step(stmt); if (ret != SQLITE_DONE) { fprintf(stderr, "UPDATE statement '%s' didn't return DONE (ret=%d): %s\n", sql, ret, sqlite3_errmsg(db)); goto update_error; } result = true; update_error: bind_error: sqlite3_finalize(stmt); prepare_error: free(sql); return result; } /****************************************************************************** * * * Paramètres : db = base de données à sauvegarder. * * filename = fichier de destination pour la sauvegarde. * * * * Description : Effectue une copie d'une base de données en cours d'usage. * * * * Retour : Bilan de l'exécution de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool backup_db(sqlite3 *db, const char *filename) { bool result; /* Conclusion à faire remonter */ sqlite3 *copy; /* Copie de la base de données */ int ret; /* Bilan d'un appel à SQLite */ sqlite3_backup *backup; /* Gestionnaire de sauvegarde */ /** * Cf. https://www.sqlite.org/backup.html */ ret = sqlite3_open(filename, ©); if (ret != SQLITE_OK) { if (copy != NULL) sqlite3_close(copy); result = false; goto exit; } backup = sqlite3_backup_init(copy, "main", db, "main"); if (backup == NULL) result = false; else { sqlite3_backup_step(backup, -1); sqlite3_backup_finish(backup); ret = sqlite3_errcode(copy); result = (ret == SQLITE_OK); } exit: return result; }