libmpm

morphux C package management library
Log | Files | Refs | Submodules | README | LICENSE | git clone https://git.ne02ptzero.me/git/libmpm

commit 4d989d5e9295e27fdddcfd9fdd1633e33c61e395
parent 9c4989e2aa2bfb4c57383f7afe7b1ed6da7b4b1d
Author: Ne02ptzero <louis@ne02ptzero.me>
Date:   Mon, 30 Jan 2017 16:04:05 +0100

Add(Database): Init, some functions:

Add tests with it too

Diffstat:
Minc/database.h | 25++++++++++++++++++-------
Minc/package.h | 1+
Msrc/database.c | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtests/Makefile | 2+-
Mtests/test.h | 7+++++++
Mtests/test_database.c | 146++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
6 files changed, 248 insertions(+), 35 deletions(-)

diff --git a/inc/database.h b/inc/database.h @@ -23,19 +23,30 @@ # define DB_FN "test.db" # define SQL_CALLBACK_PTR(name) int (*name)(void *, int, char**, char**) # define SQL_CALLBACK_DEF(name) int name(void *context, int col_num, char **col_txt, char **col_name) -# define QUERY_GET_PACKAGE_BY_ID(id) "SELECT * FROM pkgs WHERE id = %lld", id -typedef struct s_database { +# define QUERY_GET_PACKAGE_BY_ID(id) "SELECT * FROM " PKG_TABLE " WHERE id = %lld", id +# define QUERY_GET_PACKAGE_BY_NAME(name) "SELECT * FROM " PKG_TABLE " WHERE name = %s", name + +# define SQL_CREATE_TABLE "CREATE table " +# define SQL_TYPE_PRIMARY_KEY " primary key " +# define SQL_TYPE_NOT_NULL " not null " +# define SQL_TYPE_INT " integer " +# define SQL_TYPE_TEXT " text " + +typedef struct s_database { sqlite3 *sql; -} mdatabase_t; +} database_t; -mdatabase_t *mpm_database_open(u8_t *ret, const char *fn); -u8_t mpm_database_close(mdatabase_t *ptr); -u8_t mpm_database_exec(mdatabase_t *ptr, const char *query, +database_t *mpm_database_open(u8_t *ret, const char *fn); +u8_t mpm_database_close(database_t *ptr); +u8_t mpm_database_exec(database_t *ptr, const char *query, SQL_CALLBACK_PTR(cl), void *ct, char **err); -u8_t mpm_get_package_by_id(mdatabase_t *ptr, u64_t id, +u8_t mpm_get_package_by_id(database_t *ptr, u64_t id, + mlist_t **pkg); +u8_t mpm_get_package_by_name(database_t *ptr, const char *name, mlist_t **pkg); package_t *sql_to_package(package_t *ptr, char *name, char *val); +u8_t mpm_database_init(database_t *ptr); #endif /* DATABASE_H */ diff --git a/inc/package.h b/inc/package.h @@ -27,6 +27,7 @@ enum { PACKAGE_STATE_ORPHAN }; +# define PKG_TABLE "pkgs" # define PKG_COL_ID "id" # define PKG_COL_NAME "name" # define PKG_COL_VERSION "version" diff --git a/src/database.c b/src/database.c @@ -20,19 +20,19 @@ * \brief Open a connection to a database * \param ret Return code, if any error * \param fn Database path - * \return A fresh mdatabase_t structure + * \return A fresh database_t structure * - * This function will create a new mdatabase_t struct, allocate it, and open + * This function will create a new database_t struct, allocate it, and open * a new database connection. In case of any error, the return value will be * NULL, and the ret pointer set to an error code. * If a value is passed to the param fn, the library will use this value as a * database file path. */ -mdatabase_t *mpm_database_open(u8_t *ret, const char *fn) { - mdatabase_t *ptr; +database_t *mpm_database_open(u8_t *ret, const char *fn) { + database_t *ptr; u8_t error = 0; - ptr = malloc(sizeof(mdatabase_t)); + ptr = malloc(sizeof(database_t)); assert(ptr != NULL); if (fn != NULL) error = sqlite3_open(fn, &ptr->sql); @@ -57,7 +57,7 @@ error: * This function will close a database connection and free the passed struct. * On any error, this function will return the error code. */ -u8_t mpm_database_close(mdatabase_t *ptr) { +u8_t mpm_database_close(database_t *ptr) { u8_t error = 1; if (ptr != NULL) { @@ -85,7 +85,7 @@ u8_t mpm_database_close(mdatabase_t *ptr) { * This string is allocated by the sqlite library, and should be freed by the * caller. */ -u8_t mpm_database_exec(mdatabase_t *ptr, const char *query, +u8_t mpm_database_exec(database_t *ptr, const char *query, SQL_CALLBACK_PTR(cl), void *ct, char **err) { if (ptr == NULL || query == NULL) return 1; @@ -96,7 +96,7 @@ u8_t mpm_database_exec(mdatabase_t *ptr, const char *query, /** * int name(void *context, int col_num, char **col_txt, char **col_name) */ -SQL_CALLBACK_DEF(callback_package_id) { +SQL_CALLBACK_DEF(callback_package) { mlist_t *head = context; package_t *ptr; @@ -130,7 +130,7 @@ SQL_CALLBACK_DEF(callback_package_id) { * \note This function will set pkg to NULL before filling it with the results. * You should not call this function with an existing package list. */ -u8_t mpm_get_package_by_id(mdatabase_t *ptr, u64_t id, +u8_t mpm_get_package_by_id(database_t *ptr, u64_t id, mlist_t **pkg) { char *query; u8_t ret; @@ -140,14 +140,47 @@ u8_t mpm_get_package_by_id(mdatabase_t *ptr, u64_t id, *pkg = NULL; asprintf(&query, QUERY_GET_PACKAGE_BY_ID(id)); - ret = sqlite3_exec(ptr->sql, query, &callback_package_id, *pkg, NULL); + ret = sqlite3_exec(ptr->sql, query, &callback_package, *pkg, NULL); free(query); return ret; } /*! + * \brief Get a package by his name + * \param ptr Opened Database connection + * \param name Name to search for + * \param pkg Pointer on a list, used to store the results + * \return Error code + * + * This function will search in an already opened database package(s) with a + * given name. + * A sql QUERY is constructed in this function, with the following content: + * SELECT * FROM pkgs WHERE name = %s, where %s is the given name + * This function will call list_add to add results to the given list, + * caller should properly free this list. + * + * \note This function will set pkg to NULL before filling it with the results. + * You should not call this function with an existing package list. + */ +u8_t mpm_get_package_by_name(database_t *ptr, const char *name, + mlist_t **pkg) { + char *query; + u8_t ret; + + if (ptr == NULL) + return 1; + + *pkg = NULL; + asprintf(&query, QUERY_GET_PACKAGE_BY_NAME(name)); + ret = sqlite3_exec(ptr->sql, query, &callback_package, *pkg, NULL); + free(query); + return ret; +} + + +/*! * \brief Fill a package_t structure with a SQL result - * \param ptr Pointer to package_t. Must be NULL. + * \param ptr Pointer to package_t. Must not be NULL. * \param name Name of the column * \param val Value of the column * @@ -174,3 +207,50 @@ package_t *sql_to_package(package_t *ptr, char *name, char *val) { } return ptr; } + +/*! + * \brief Init an empty database + * \param ptr Open connection to a database + * + * This function will create tables in an empty database. + * Careful, this function do not check if a database is empty before making + * requests. + * If an error happens, m_error is called, and an appropriate return code is + * returned. + * + * \return Status code + */ +u8_t mpm_database_init(database_t *ptr) { + static const char *query_table[] = { \ + SQL_CREATE_TABLE PKG_TABLE "(" \ + PKG_COL_ID SQL_TYPE_INT SQL_TYPE_PRIMARY_KEY "," \ + PKG_COL_NAME SQL_TYPE_TEXT SQL_TYPE_NOT_NULL "," \ + PKG_COL_VERSION SQL_TYPE_TEXT SQL_TYPE_NOT_NULL "," \ + PKG_COL_CATEG SQL_TYPE_TEXT SQL_TYPE_NOT_NULL "," \ + PKG_COL_STATE SQL_TYPE_INT SQL_TYPE_NOT_NULL "," \ + PKG_COL_DEPS SQL_TYPE_TEXT "," \ + PKG_COL_FILES SQL_TYPE_TEXT SQL_TYPE_NOT_NULL "," \ + PKG_COL_BINARIES SQL_TYPE_TEXT "," \ + PKG_COL_CONFIG SQL_TYPE_TEXT "," \ + PKG_COL_DOCS SQL_TYPE_TEXT \ + ");" \ + }; + char *err = NULL; + u8_t ret = 0; + + if (ptr == NULL) + return 1; + + for (u8_t i = 0; i < sizeof(query_table) / sizeof(query_table[0]); i++) { + ret = mpm_database_exec(ptr, query_table[i], NULL, NULL, &err); + if (ret != 0 || err != NULL) + goto error; + } + + return 0; + +error: + m_error("An error happened in the database init: %s\n", err); + sqlite3_free(err); + return ret; +} diff --git a/tests/Makefile b/tests/Makefile @@ -38,7 +38,7 @@ valgrind: $(NAME) valgrind --leak-check=full --error-exitcode=1 ./$(NAME) coverage: - $(MAKE) fclean all OFLAGS="-g -O0 -coverage -lgcov" + $(MAKE) fclean all OFLAGS="-g -O0 -coverage -lgcov -std=c99" clean: rm -f $(OBJS) diff --git a/tests/test.h b/tests/test.h @@ -14,5 +14,12 @@ void register_flags_test(void); void register_test_database(void); void clean_db(const char *name); +int free_sql_results(void *ptr); +int free_single_result_sql(void *ptr); + +typedef struct sql_result_s { + char *name; + char *value; +} sql_result_t; #endif /* TEST_H */ diff --git a/tests/test_database.c b/tests/test_database.c @@ -1,7 +1,7 @@ #include "test.h" TEST(database_open_1) { - mdatabase_t *ptr = NULL; + database_t *ptr = NULL; u8_t ret = 0; ptr = mpm_database_open(&ret, NULL); @@ -13,7 +13,7 @@ TEST(database_open_1) { } TEST(database_open_2) { - mdatabase_t *ptr = NULL; + database_t *ptr = NULL; u8_t ret = 0; ptr = mpm_database_open(&ret, "/illogical/path"); @@ -36,25 +36,27 @@ TEST(database_close_1) { * int name(void *context, int col_num, char **col_txt, char **col_name) */ SQL_CALLBACK_DEF(exec_callback) { - (void)context; - fprintf(stdout, "\n\nCol_num: %d\n", col_num); + sql_result_t *ptr = NULL; + mlist_t **head = context, *new = NULL; + if (col_num == 0) return 0; - while (*col_txt) { - fprintf(stdout, "> %s\n", *col_txt); - col_txt++; - } - - while (*col_name) { - fprintf(stdout, "> %s\n", *col_name); - col_name++; + for (u8_t i = 0; i < col_num; i++) { + ptr = malloc(sizeof(sql_result_t)); + assert(ptr != NULL); + + ptr->name = col_name[i] ? strdup(col_name[i]) : NULL; + ptr->value = col_txt[i] ? strdup(col_txt[i]) : NULL; + list_add(new, ptr, sizeof(sql_result_t)); + free(ptr); } - + list_add(*head, new, sizeof(mlist_t)); + free(new); return 0; } TEST(database_exec_1) { - mdatabase_t *ptr = NULL; + database_t *ptr = NULL; u8_t ret = 0; char *err = NULL; @@ -68,7 +70,7 @@ TEST(database_exec_1) { } TEST(database_exec_2) { - mdatabase_t *ptr = NULL; + database_t *ptr = NULL; u8_t ret = 0; char *err = NULL; @@ -78,10 +80,85 @@ TEST(database_exec_2) { ret = mpm_database_exec(NULL, NULL, &exec_callback, ptr, &err); TEST_ASSERT((ret == 1), "NULL not handled correctly."); mpm_database_close(ptr); - clean_db("test.db"); + clean_db(DB_FN); return TEST_SUCCESS; } +TEST(database_init_1) { + database_t *ptr = NULL; + u8_t ret = 0; + + ptr = mpm_database_open(&ret, NULL); + TEST_ASSERT((ret == 0), "Can't open the database"); + TEST_ASSERT((ptr != NULL), "Can't open the database"); + ret = mpm_database_init(ptr); + TEST_ASSERT((ret == 0), "Can't init the database"); + mpm_database_close(ptr); + return TEST_SUCCESS; +} + +TEST(database_init_2) { + u8_t ret = 0; + + ret = mpm_database_init(NULL); + TEST_ASSERT((ret == 1), "Can't handle NULL pointer"); + return TEST_SUCCESS; +} + +TEST(database_init_test_pkg_table) { + database_t *ptr = NULL; + u8_t ret = 0; + mlist_t *res = NULL, *tmp, *tmp2, *tmp3, *test = NULL; + char *err = NULL; + sql_result_t *result; + + ptr = mpm_database_open(&ret, NULL); + TEST_ASSERT((ret == 0), "Can't open the database"); + TEST_ASSERT((ptr != NULL), "Can't open the database"); + ret = mpm_database_exec(ptr, "PRAGMA table_info([" PKG_TABLE "])", + &exec_callback, &res, &err); + TEST_ASSERT((ret == 0), "An error happened"); + TEST_ASSERT((err == NULL), "An error happened"); + mpm_database_close(ptr); + + list_for_each(res, tmp, tmp2) { + list_for_each(tmp2, tmp3, result) { + if (!strcmp(result->name, "name")) + list_add(test, result->value, strlen(result->value)); + } + } + TEST_ASSERT((list_size(test) == 10), "Number of columns is wrong"); + list_free(test, NULL); + free_sql_results(res); + return TEST_SUCCESS; +} + +TEST(database_init_3) { + database_t *ptr = NULL; + u8_t ret = 0; + int st, fd[2]; + pid_t pid; + + + ptr = mpm_database_open(&ret, NULL); + TEST_ASSERT((ret == 0), "Can't open the database"); + TEST_ASSERT((ptr != NULL), "Can't open the database"); + + pipe(fd); + if ((pid = fork()) == 0) { + DUP_ALL_OUTPUTS(fd); + ret = mpm_database_init(ptr); + TEST_ASSERT((ret != 0), "Error did not trigger"); + } else { + WAIT_AND_CLOSE(pid, st, fd); + TEST_ASSERT((WEXITSTATUS(st) == 0), "Exit status is wrong"); + } + mpm_database_close(ptr); + clean_db(DB_FN); + return TEST_SUCCESS; +} + + void clean_db(const char *name) { int st, fd[2]; pid_t pid; @@ -95,10 +172,47 @@ void clean_db(const char *name) { } } +int free_sql_results(void *ptr) { + mlist_t *tmp = ptr, *it, *z, *j; + + if (tmp) { + while (tmp != NULL) { + it = tmp->next; + z = tmp->member; + while (z != NULL) { + j = z->next; + free_single_result_sql(z->member); + free(z->member); + free(z); + z = j; + } + free(tmp); + tmp = it; + } + } + return 1; +} + +int free_single_result_sql(void *ptr) { + sql_result_t *tmp = ptr; + + if (tmp) { + free(tmp->name); + free(tmp->value); + tmp->name = NULL; + tmp->value = NULL; + } + return 1; +} + void register_test_database(void) { reg_test("database", database_open_1); reg_test("database", database_open_2); reg_test("database", database_close_1); reg_test("database", database_exec_1); reg_test("database", database_exec_2); + reg_test("database", database_init_1); + reg_test("database", database_init_2); + reg_test("database", database_init_test_pkg_table); + reg_test("database", database_init_3); }