From ef0e79927cbd64832b7351074cf71853212a331b Mon Sep 17 00:00:00 2001 From: Jonathan Buchanan Date: Tue, 2 Jun 2020 11:57:39 -0400 Subject: [PATCH] created taler_sq_lib --- .gitignore | 1 + configure.ac | 41 +++++++++ src/Makefile.am | 4 + src/include/taler_sq_lib.h | 52 +++++++++++ src/sq/Makefile.am | 40 +++++++++ src/sq/sq_query_helper.c | 77 ++++++++++++++++ src/sq/sq_result_helper.c | 92 +++++++++++++++++++ src/sq/test_sq.c | 180 +++++++++++++++++++++++++++++++++++++ 8 files changed, 487 insertions(+) create mode 100644 src/include/taler_sq_lib.h create mode 100644 src/sq/Makefile.am create mode 100644 src/sq/sq_query_helper.c create mode 100644 src/sq/sq_result_helper.c create mode 100644 src/sq/test_sq.c diff --git a/.gitignore b/.gitignore index e763b19e7..9ed741759 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ src/wire-plugins/test_ebics_wireformat src/wire-plugins/test_wire_plugin src/wire-plugins/test_wire_plugin_transactions_taler_bank src/pq/test_pq +src/sq/test_sq src/util/test_amount src/util/test_crypto src/util/test_json diff --git a/configure.ac b/configure.ac index 401f84c2a..a51b32475 100644 --- a/configure.ac +++ b/configure.ac @@ -261,6 +261,16 @@ AS_IF([test $libgnunetpq != 1], *** ]])]) +# Check for GNUnet's libgnunetsq +libgnunetsq=0 +AC_MSG_CHECKING([for libgnunetsq]) +AC_CHECK_HEADERS([gnunet/gnunet_sq_lib.h], + [AC_CHECK_LIB([gnunetsq], [GNUNET_SQ_result_spec_string], libgnunetsq=1)], + [], [#ifdef HAVE_GNUNET_PLATFORM_H + #include + #endif]) + + # check for libmicrohttpd microhttpd=0 AC_MSG_CHECKING([for microhttpd]) @@ -326,6 +336,35 @@ CFLAGS=$CFLAGS_SAVE LDFLAGS=$LDFLAGS_SAVE LIBS=$LIBS_SAVE +# test for sqlite +sqlite=false +AC_MSG_CHECKING(for SQLite) +AC_ARG_WITH(sqlite, + [ --with-sqlite=PFX base of SQLite installation], + [AC_MSG_RESULT("$with_sqlite") + AS_CASE([$with_sqlite], + [no],[], + [yes],[ + AC_CHECK_HEADERS(sqlite3.h, + sqlite=true)], + [ + LDFLAGS="-L$with_sqlite/lib $LDFLAGS" + CPPFLAGS="-I$with_sqlite/include $CPPFLAGS" + AC_CHECK_HEADERS(sqlite3.h, + EXT_LIB_PATH="-L$with_sqlite/lib $EXT_LIB_PATH" + SQLITE_LDFLAGS="-L$with_sqlite/lib" + SQLITE_CPPFLAGS="-I$with_sqlite/include" + sqlite=true) + LDFLAGS=$SAVE_LDFLAGS + CPPFLAGS=$SAVE_CPPFLAGS + ]) + ], + [AC_MSG_RESULT([--with-sqlite not specified]) + AC_CHECK_HEADERS(sqlite3.h, sqlite=true)]) +AM_CONDITIONAL(HAVE_SQLITE, [test x$sqlite = xtrue] && [test $libgnunetsq = 1]) +AC_SUBST(SQLITE_CPPFLAGS) +AC_SUBST(SQLITE_LDFLAGS) + # check for libtalertwistertesting talertwister=0 AC_MSG_CHECKING([for talertwister]) @@ -459,6 +498,7 @@ AM_CONDITIONAL([ENABLE_DOC], [test "x$enable_doc" = "xyes"]) AM_CONDITIONAL([HAVE_EXPENSIVE_TESTS], [false]) AM_CONDITIONAL([MHD_HAVE_EPOLL], [false]) AM_CONDITIONAL([HAVE_POSTGRESQL], [false]) +AM_CONDITIONAL([HAVE_SQLITE], [false]) AM_CONDITIONAL([HAVE_LIBCURL], [false]) AM_CONDITIONAL([HAVE_LIBGNURL], [false]) AM_CONDITIONAL([HAVE_DEVELOPER], [false]) @@ -488,6 +528,7 @@ AC_CONFIG_FILES([Makefile src/json/Makefile src/mhd/Makefile src/pq/Makefile + src/sq/Makefile src/util/Makefile ]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 4b07a1161..4d4e533e3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,6 +3,9 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include if HAVE_POSTGRESQL PQ_DIR = pq endif +if HAVE_SQLITE + SQ_DIR = sq +endif pkgcfgdir = $(prefix)/share/taler/config.d/ pkgcfg_DATA = \ @@ -17,6 +20,7 @@ SUBDIRS = \ json \ curl \ $(PQ_DIR) \ + $(SQ_DIR) \ mhd \ bank-lib \ exchangedb \ diff --git a/src/include/taler_sq_lib.h b/src/include/taler_sq_lib.h new file mode 100644 index 000000000..f6a35225d --- /dev/null +++ b/src/include/taler_sq_lib.h @@ -0,0 +1,52 @@ +/* + This file is part of TALER + Copyright (C) 2020 Taler Systems SA + + TALER 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, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see +*/ +/** + * @file include/taler_sq_lib.h + * @brief helper functions for DB interactions with SQLite + * @author Jonathan Buchanan + */ +#ifndef TALER_SQ_LIB_H_ +#define TALER_SQ_LIB_H_ + +#include +#include +#include +#include "taler_util.h" + +/** + * Generate query parameter for a currency, consisting of the three + * components "value", "fraction" in this order. The + * types must be a 64-bit integer and a 64-bit integer. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_SQ_QueryParam +TALER_SQ_query_param_amount_nbo (const struct TALER_AmountNBO *x); + +/** + * Currency amount expected. + * + * @param currency currency to use for @a amount + * @param[out] amount where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_SQ_ResultSpec +TALER_SQ_result_spec_amount_nbo (const char *currency, + struct TALER_AmountNBO *amount); + +#endif /* TALER_SQ_LIB_H_ */ + +/* end of include/taler_sq_lib.h */ diff --git a/src/sq/Makefile.am b/src/sq/Makefile.am new file mode 100644 index 000000000..ee4c5eba7 --- /dev/null +++ b/src/sq/Makefile.am @@ -0,0 +1,40 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) $(SQLITE_CPPFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalersq.la + +libtalersq_la_SOURCES = \ + sq_query_helper.c \ + sq_result_helper.c + +libtalersq_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil -ljansson \ + -lsqlite3 $(XLIB) + +libtalersq_la_LDFLAGS = \ + $(SQLITE_LDFLAGS) \ + -version-info 0:0:0 \ + -export-dynamic -no-undefined + +TESTS = \ + test_sq + +check_PROGRAMS= \ + test_sq + +test_sq_SOURCES = \ + test_sq.c +test_sq_LDADD = \ + libtalersq.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetsq \ + -lgnunetutil \ + -ljansson \ + -lsqlite3 $(XLIB) diff --git a/src/sq/sq_query_helper.c b/src/sq/sq_query_helper.c new file mode 100644 index 000000000..8116622a5 --- /dev/null +++ b/src/sq/sq_query_helper.c @@ -0,0 +1,77 @@ +/* + This file is part of TALER + Copyright (C) 2020 Taler Systems SA + + TALER 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, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see +*/ +/** + * @file sq/sq_query_helper.c + * @brief helper functions for Taler-specific SQLite3 interactions + * @author Jonathan Buchanan + */ +#include "platform.h" +#include +#include +#include +#include "taler_sq_lib.h" + + +/** + * Function called to convert input argument into SQL parameters. + * + * @param cls closure + * @param data pointer to input argument, here a `struct TALER_AmountNBO` + * @param data_len number of bytes in @a data (if applicable) + * @param stmt sqlite statement to parameters for + * @param off offset of the argument to bind in @a stmt, numbered from 1, + * so immediately suitable for passing to `sqlite3_bind`-functions. + * @return #GNUNET_SYSERR on error, #GNUNET_OK on success + */ +static int +qconv_amount_nbo (void *cls, + const void *data, + size_t data_len, + sqlite3_stmt *stmt, + unsigned int off) +{ + const struct TALER_AmountNBO *amount = data; + + GNUNET_assert (sizeof (struct TALER_AmountNBO) == data_len); + if (SQLITE_OK != sqlite3_bind_int64 (stmt, + (int) off, + (sqlite3_int64) amount->value)) + return GNUNET_SYSERR; + if (SQLITE_OK != sqlite3_bind_int64 (stmt, + (int) off + 1, + (sqlite3_int64) amount->fraction)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Generate query parameter for a currency, consisting of the three + * components "value", "fraction" in this order. The + * types must be a 64-bit integer and a 64-bit integer. + * + * @param x pointer to the query parameter to pass + */ +struct GNUNET_SQ_QueryParam +TALER_SQ_query_param_amount_nbo (const struct TALER_AmountNBO *x) +{ + struct GNUNET_SQ_QueryParam res = + { &qconv_amount_nbo, NULL, x, sizeof (*x), 2 }; + return res; +} + + +/* end of sq/sq_query_helper.c */ diff --git a/src/sq/sq_result_helper.c b/src/sq/sq_result_helper.c new file mode 100644 index 000000000..ef36d3e90 --- /dev/null +++ b/src/sq/sq_result_helper.c @@ -0,0 +1,92 @@ +/* + This file is part of TALER + Copyright (C) 2020 Taler Systems SA + + TALER 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, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see +*/ +/** + * @file sq/sq_result_helper.c + * @brief functions to initialize parameter arrays + * @author Jonathan Buchanan + */ +#include "platform.h" +#include +#include +#include +#include "taler_sq_lib.h" +#include "taler_amount_lib.h" + + +/** + * Extract amount data from a SQLite database + * + * @param cls closure, a `const char *` giving the currency + * @param result where to extract data from + * @param column column to extract data from + * @param[in,out] dst_size where to store size of result, may be NULL + * @param[out] dst where to store the result + * @return + * #GNUNET_YES if all results could be extracted + * #GNUNET_SYSERR if a result was invalid (non-existing field or NULL) + */ +static int +extract_amount_nbo (void *cls, + sqlite3_stmt *result, + unsigned int column, + size_t *dst_size, + void *dst) +{ + struct TALER_AmountNBO *amount = dst; + const char *currency = cls; + if ((sizeof (struct TALER_AmountNBO) != *dst_size) || + (SQLITE_INTEGER != sqlite3_column_type (result, + (int) column)) || + (SQLITE_INTEGER != sqlite3_column_type (result, + (int) column + 1))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_strlcpy (amount->currency, currency, TALER_CURRENCY_LEN); + amount->value = (uint64_t) sqlite3_column_int64 (result, + (int) column); + uint64_t frac = (uint64_t) sqlite3_column_int64 (result, + column + 1); + amount->fraction = (uint32_t) frac; + return GNUNET_YES; +} + + +/** + * Currency amount expected. + * + * @param currency the currency to use for @a amount + * @param[out] amount where to store the result + * @return array entry for the result specification to use + */ +struct GNUNET_SQ_ResultSpec +TALER_SQ_result_spec_amount_nbo (const char *currency, + struct TALER_AmountNBO *amount) +{ + struct GNUNET_SQ_ResultSpec res = { + .conv = &extract_amount_nbo, + .cls = (void *) currency, + .dst = (void *) amount, + .dst_size = sizeof (struct TALER_AmountNBO), + .num_params = 2 + }; + + return res; +} + + +/* end of sq/sq_result_helper.c */ diff --git a/src/sq/test_sq.c b/src/sq/test_sq.c new file mode 100644 index 000000000..85f837e40 --- /dev/null +++ b/src/sq/test_sq.c @@ -0,0 +1,180 @@ +/* + This file is part of TALER + Copyright (C) 2020 Taler Systems SA + + TALER 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, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see +*/ +/** + * @file sq/test_sq.c + * @brief Tests for SQLite3 convenience API + * @author Jonathan Buchanan + */ +#include "platform.h" +#include "taler_sq_lib.h" + + +/** + * Run actual test queries. + * + * @return 0 on success + */ +static int +run_queries (sqlite3 *db) +{ + struct TALER_Amount hamount; + struct TALER_AmountNBO namount; + sqlite3_stmt *test_insert; + sqlite3_stmt *test_select; + struct GNUNET_SQ_PrepareStatement ps[] = { + GNUNET_SQ_make_prepare ("INSERT INTO test_sq (" + " namount_val" + ",namount_frac" + ") VALUES " + "($1, $2)", + &test_insert), + GNUNET_SQ_make_prepare ("SELECT" + " namount_val" + ",namount_frac" + " FROM test_sq", + &test_select), + GNUNET_SQ_PREPARE_END + }; + + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("EUR:1.23", + &hamount)); + TALER_amount_hton (&namount, + &hamount); + + GNUNET_assert (GNUNET_OK == GNUNET_SQ_prepare (db, + ps)); + + { + struct GNUNET_SQ_QueryParam params_insert[] = { + TALER_SQ_query_param_amount_nbo (&namount), + GNUNET_SQ_query_param_end + }; + GNUNET_SQ_reset (db, + test_insert); + GNUNET_assert (GNUNET_OK == GNUNET_SQ_bind (test_insert, + params_insert)); + GNUNET_assert (SQLITE_DONE == sqlite3_step (test_insert)); + sqlite3_finalize (test_insert); + } + + { + struct TALER_AmountNBO nresult_amount; + struct TALER_Amount result_amount; + struct GNUNET_SQ_QueryParam params_select[] = { + GNUNET_SQ_query_param_end + }; + struct GNUNET_SQ_ResultSpec results_select[] = { + TALER_SQ_result_spec_amount_nbo ("EUR", + &nresult_amount), + GNUNET_SQ_result_spec_end + }; + + GNUNET_SQ_reset (db, + test_select); + GNUNET_assert (GNUNET_OK == GNUNET_SQ_bind (test_select, + params_select)); + GNUNET_assert (SQLITE_ROW == sqlite3_step (test_select)); + + GNUNET_assert (GNUNET_OK == GNUNET_SQ_extract_result (test_select, + results_select)); + GNUNET_SQ_cleanup_result (results_select); + sqlite3_finalize (test_select); + TALER_amount_ntoh (&result_amount, &nresult_amount); + if ((GNUNET_OK != TALER_amount_cmp_currency (&hamount, + &result_amount)) || + (0 != TALER_amount_cmp (&hamount, + &result_amount))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Result from database doesn't match input\n"); + return 1; + } + } + + return 0; +} + + +int +main (int argc, + const char *const argv[]) +{ + struct GNUNET_SQ_ExecuteStatement es[] = { + GNUNET_SQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_sq (" + " namount_val INT8 NOT NULL" + ",namount_frac INT4 NOT NULL" + ")"), + GNUNET_SQ_EXECUTE_STATEMENT_END + }; + sqlite3 *db; + int ret; + + GNUNET_log_setup ("test-pq", + "WARNING", + NULL); + + if (SQLITE_OK != sqlite3_open ("talercheck.db", + &db)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to open SQLite3 database\n"); + return 77; + } + + if (GNUNET_OK != GNUNET_SQ_exec_statements (db, + es)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to create new table\n"); + if ((SQLITE_OK != sqlite3_close (db)) || + (0 != unlink ("talercheck.db"))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to close db or unlink\n"); + } + return 1; + } + + ret = run_queries (db); + + if (SQLITE_OK != + sqlite3_exec (db, + "DROP TABLE test_sq", + NULL, NULL, NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to drop table\n"); + ret = 1; + } + + if (SQLITE_OK != sqlite3_close (db)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to close database\n"); + ret = 1; + } + if (0 != unlink ("talercheck.db")) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to unlink test database file\n"); + ret = 1; + } + return ret; +} + + +/* end of sq/test_sq.c */