From 5709c4b683e73cbe5592a1d5e705342cbd3205d1 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Mon, 10 Dec 2012 15:37:48 +0100 Subject: [PATCH] utils dbi: Added initial version. This module provides helper functions for using libdbi. Currently, data types and functions for managing connection options, a database connection as well as a generic query helper are available. The query helper (sc_dbi_exec_query()) uses a specified callback function to handle each row as returned from the SQL query. A custom data-type (based on the libdbi data-types) is used to pass the query results to the handler callback. --- configure.ac | 20 ++ src/Makefile.am | 6 + src/include/utils/dbi.h | 161 ++++++++++++++ src/utils/dbi.c | 455 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 642 insertions(+) create mode 100644 src/include/utils/dbi.h create mode 100644 src/utils/dbi.c diff --git a/configure.ac b/configure.ac index fe9331d..d6e21c1 100644 --- a/configure.ac +++ b/configure.ac @@ -188,6 +188,23 @@ AC_SUBST([STRICT_CFLAGS]) AC_CHECK_HEADERS(libgen.h) dnl Check for dependencies. +AC_ARG_WITH([libdbi], + [AS_HELP_STRING([--with-libdbi], [libdbi support (default: auto)])], + [with_libdbi="$withval"], + [with_libdbi="yes"]) +if test "x$with_libdbi" = "xyes"; then + AC_CHECK_HEADERS([dbi/dbi.h], + [with_libdbi="yes"], + [with_libdbi="no (dbi/dbi.h) not found"]) +fi +if test "x$with_libdbi" = "xyes"; then + AC_CHECK_LIB([dbi], [dbi_initialize], + [with_libdbi="yes"], + [with_libdbi="no (libdbi or symbol 'dbi_initialize' not found)"]) +fi +AM_CONDITIONAL([BUILD_WITH_LIBDBI], test "x$with_libdbi" = "xyes") + +dnl Feature checks. build_documentation="yes" have_xsltproc="yes" @@ -238,6 +255,9 @@ AC_MSG_RESULT() AC_MSG_RESULT([ Features:]) AC_MSG_RESULT([ documentation: . . . . . . $build_documentation]) AC_MSG_RESULT() +AC_MSG_RESULT([ Libraries:]) +AC_MSG_RESULT([ libdbi: . . . . . . . . . $with_libdbi]) +AC_MSG_RESULT() AC_MSG_RESULT([ Backends:]) AC_MSG_RESULT([ collectd: . . . . . . . . . $enable_collectd]) AC_MSG_RESULT() diff --git a/src/Makefile.am b/src/Makefile.am index b2816eb..4544b5d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,6 +33,12 @@ libsyscollector_la_LDFLAGS = -version-info 0:0:0 -pthread libsyscollector_la_LIBADD = $(LIBLTDL) -lrt liboconfig/liboconfig.la libsyscollector_la_DEPENDENCIES = liboconfig/liboconfig.la +if BUILD_WITH_LIBDBI +libsyscollector_la_SOURCES += \ + utils/dbi.c include/utils/dbi.h +libsyscollector_la_LIBADD += -ldbi +endif + bin_PROGRAMS = syscollectord syscollectord_SOURCES = daemon/syscollectord.c include/syscollector.h \ diff --git a/src/include/utils/dbi.h b/src/include/utils/dbi.h new file mode 100644 index 0000000..2371286 --- /dev/null +++ b/src/include/utils/dbi.h @@ -0,0 +1,161 @@ +/* + * syscollector - src/include/utils/dbi.h + * Copyright (C) 2012 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SC_UTILS_DBI_H +#define SC_UTILS_DBI_H 1 + +#include "utils/time.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * sc_dbi_data_t: + * A datum retrieved from a single field of a query result row. + * + * The string and binary objects are managed by libdbi, thus, they must not be + * freed or modified. If you want to keep them, make sure to make a copy. + */ +typedef struct { + int type; + union { + int64_t integer; /* DBI_TYPE_INTEGER */ + double decimal; /* DBI_TYPE_DECIMAL */ + const char *string; /* DBI_TYPE_STRING */ + sc_time_t datetime; /* DBI_TYPE_DATETIME */ + struct { + size_t length; + const unsigned char *datum; + } binary; /* DBI_TYPE_BINARY */ + } data; +} sc_dbi_data_t; + +struct sc_dbi_options; +typedef struct sc_dbi_options sc_dbi_options_t; + +struct sc_dbi_client; +typedef struct sc_dbi_client sc_dbi_client_t; + +typedef int (*sc_dbi_data_cb)(sc_dbi_client_t *, size_t, sc_dbi_data_t *); + +/* + * sc_dbi_options_t: + * This object stores DBI connection options (key/value) (e.g. host, dbname, + * etc.). It may be used to dynamically create the list of options before + * applying it to some client object. + */ +sc_dbi_options_t * +sc_dbi_options_create(void); + +int +sc_dbi_options_add(sc_dbi_options_t *options, + const char *key, const char *value); + +void +sc_dbi_options_destroy(sc_dbi_options_t *options); + +/* + * sc_dbi_client_create: + * Creates a new DBI client object using the specified DBI / DBD 'driver' and + * connecting to the specified 'database'. + * + * Returns: + * - the client object on success + * - NULL else + */ +sc_dbi_client_t * +sc_dbi_client_create(const char *driver, const char *database); + +/* + * sc_dbi_client_set_options: + * Apply connection options to an existing client object. This has to be done + * before actually connecting to the database using sc_dbi_client_connect(). + * + * Returns: + * - 0 on success + * - a negative value else + */ +int +sc_dbi_client_set_options(sc_dbi_client_t *client, + sc_dbi_options_t *options); + +/* + * sc_dbi_client_connect: + * Connect to the database using the options registered beforehand. + * + * This function may also be used to reconnect to the database. + * + * Returns: + * - 0 on success + * - a negative value else + */ +int +sc_dbi_client_connect(sc_dbi_client_t *client); + +/* + * sc_dbi_exec_query: + * Execute an SQL query on the database. The specified 'callback' will be + * called for each row returned from the query. If 'n' is a value equal to or + * greater than zero, it specifies the number of columns that are expected in + * the query result. For each column, the caller then needs to also specify + * the requested type (see the DBI_TYPE_* constants). If the number or types + * do not match, an error will be reported and the query will fail. That is, + * this allows to let sc_dbi_exec_query() do basic verification of the + * returned values. + * + * The callback will receive the client object and an array containing the + * field values of the current row. Any string / binary objects are managed by + * libdbi, thus, it must not be freed or modified. If you need to keep the + * object, make sure to make a copy of it. + * + * Returns: + * - 0 on success + * - a negative value else + */ +int +sc_dbi_exec_query(sc_dbi_client_t *client, const char *query, + sc_dbi_data_cb callback, int n, ...); + +/* + * sc_dbi_client_destroy: + * Disconnect from the database and destroy the client object. + */ +void +sc_dbi_client_destroy(sc_dbi_client_t *client); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ! SC_UTILS_DBI_H */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + diff --git a/src/utils/dbi.c b/src/utils/dbi.c new file mode 100644 index 0000000..3aef35e --- /dev/null +++ b/src/utils/dbi.c @@ -0,0 +1,455 @@ +/* + * syscollector - src/utils/dbi.c + * Copyright (C) 2012 Sebastian 'tokkee' Harl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils/dbi.h" + +#include + +#include + +#include +#include +#include +#include + +/* + * private data types + */ + +typedef struct { + char *key; + char *value; +} sc_dbi_option_t; + +struct sc_dbi_options { + sc_dbi_option_t *options; + size_t options_num; +}; + +struct sc_dbi_client { + char *driver; + char *database; + + dbi_conn conn; + + sc_dbi_options_t *options; +}; + +/* + * private helper functions + */ + +static const char * +sc_dbi_strerror(dbi_conn conn) +{ + const char *errmsg = NULL; + dbi_conn_error(conn, &errmsg); + return errmsg; +} /* sc_dbi_strerror */ + +static int +sc_dbi_get_field(dbi_result res, unsigned int i, + int type, sc_dbi_data_t *data) +{ + switch (type) { + case DBI_TYPE_INTEGER: + data->data.integer = dbi_result_get_longlong_idx(res, i); + break; + case DBI_TYPE_DECIMAL: + data->data.decimal = dbi_result_get_double_idx(res, i); + break; + case DBI_TYPE_STRING: + data->data.string = dbi_result_get_string_idx(res, i); + break; + case DBI_TYPE_DATETIME: + { + /* libdbi does not provide any higher resolutions than that */ + time_t datetime = dbi_result_get_datetime_idx(res, i); + data->data.datetime = SECS_TO_SC_TIME(datetime); + } + break; + case DBI_TYPE_BINARY: + { + size_t length = dbi_result_get_field_length_idx(res, i); + const unsigned char *datum = dbi_result_get_binary_idx(res, i); + data->data.binary.length = length; + data->data.binary.datum = datum; + } + break; + default: + fprintf(stderr, "dbi: Unexpected type %i while " + "parsing query result.\n", type); + return -1; + } + + data->type = type; + return 0; +} /* sc_dbi_get_field */ + +static int +sc_dbi_get_data(sc_dbi_client_t *client, dbi_result res, + unsigned int num_fields, sc_dbi_data_cb callback) +{ + sc_dbi_data_t data[num_fields]; + int types[num_fields]; + unsigned int i; + + unsigned long long num_rows; + unsigned long long success = 0, n; + + assert(client && res && callback); + assert(num_fields > 0); + + for (i = 0; i < num_fields; ++i) { + types[i] = dbi_result_get_field_type_idx(res, i + 1); + if (types[i] == DBI_TYPE_ERROR) { + fprintf(stderr, "dbi: failed to fetch data: %s\n", + sc_dbi_strerror(client->conn)); + return -1; + } + } + + num_rows = dbi_result_get_numrows(res); + if (num_rows < 1) + return -1; + + for (n = 0; n < num_rows; ++n) { + if (! dbi_result_seek_row(res, n + 1)) { + fprintf(stderr, "dbi: Failed to retrieve row %llu: %s\n", + n, sc_dbi_strerror(client->conn)); + continue; + } + + for (i = 0; i < num_fields; ++i) + if (sc_dbi_get_field(res, (unsigned int)(i + 1), + types[i], &data[i])) + continue; + + if (callback(client, num_fields, data)) + continue; + + ++success; + } + + if (! success) + return -1; + return 0; +} /* sc_dbi_get_data */ + +/* + * public API + */ + +sc_dbi_options_t * +sc_dbi_options_create(void) +{ + sc_dbi_options_t *options; + + options = malloc(sizeof(options)); + if (! options) + return NULL; + + options->options = NULL; + options->options_num = 0; + return options; +} /* sc_dbi_options_create */ + +int +sc_dbi_options_add(sc_dbi_options_t *options, + const char *key, const char *value) +{ + sc_dbi_option_t *new; + + if ((! options) || (! key) || (! value)) + return -1; + + new = realloc(options->options, + (options->options_num + 1) * sizeof(*options->options)); + if (! new) + return -1; + + options->options = new; + new = options->options + options->options_num; + + new->key = strdup(key); + new->value = strdup(value); + + if ((! new->key) || (! new->value)) { + if (new->key) + free(new->key); + if (new->value) + free(new->value); + return -1; + } + + ++options->options_num; + return 0; +} /* sc_dbi_options_add */ + +void +sc_dbi_options_destroy(sc_dbi_options_t *options) +{ + size_t i; + + if (! options) + return; + + for (i = 0; i < options->options_num; ++i) { + sc_dbi_option_t *opt = options->options + i; + + if (opt->key) + free(opt->key); + if (opt->value) + free(opt->value); + } + + if (options->options) + free(options->options); + options->options = NULL; + options->options_num = 0; + free(options); +} /* sc_dbi_options_destroy */ + +sc_dbi_client_t * +sc_dbi_client_create(const char *driver, const char *database) +{ + sc_dbi_client_t *client; + + if ((! driver) || (! database)) + return NULL; + + client = malloc(sizeof(*client)); + if (! client) + return NULL; + memset(client, 0, sizeof(*client)); + + client->conn = NULL; + client->options = NULL; + + client->driver = strdup(driver); + client->database = strdup(database); + if ((! client->driver) || (! client->database)) { + sc_dbi_client_destroy(client); + return NULL; + } + return client; +} /* sc_dbi_client_create */ + +int +sc_dbi_client_set_options(sc_dbi_client_t *client, + sc_dbi_options_t *options) +{ + if (! client) + return -1; + + if (client->options) + sc_dbi_options_destroy(client->options); + client->options = options; + return 0; +} /* sc_dbi_client_set_options */ + +int +sc_dbi_client_connect(sc_dbi_client_t *client) +{ + dbi_driver driver; + size_t i; + + if ((! client) || (! client->driver) || (! client->database)) + return -1; + + if (client->conn) + dbi_conn_close(client->conn); + + driver = dbi_driver_open(client->driver); + if (! driver) { + fprintf(stderr, "dbi: failed to open DBI driver '%s'; " + "possibly it's not installed.\n", + client->driver); + + fprintf(stderr, "dbi: known drivers:\n"); + for (driver = dbi_driver_list(NULL); driver; + driver = dbi_driver_list(driver)) { + fprintf(stderr, "\t- %s\n", dbi_driver_get_name(driver)); + } + return -1; + } + + client->conn = dbi_conn_open(driver); + if (! client->conn) { + fprintf(stderr, "dbi: failed to open connection object.\n"); + return -1; + } + + if (client->options) { + for (i = 0; i < client->options->options_num; ++i) { + const char *opt; + + if (! dbi_conn_set_option(client->conn, + client->options->options[i].key, + client->options->options[i].value)) + continue; + /* else: error */ + + fprintf(stderr, "dbi: failed to set option '%s': %s\n", + client->options->options[i].key, + sc_dbi_strerror(client->conn)); + + fprintf(stderr, "dbi: known driver options:\n"); + for (opt = dbi_conn_get_option_list(client->conn, NULL); opt; + opt = dbi_conn_get_option_list(client->conn, opt)) + fprintf(stderr, "\t- %s\n", opt); + + dbi_conn_close(client->conn); + return -1; + } + } + + if (dbi_conn_set_option(client->conn, "dbname", client->database)) { + fprintf(stderr, "dbi: failed to set option 'dbname': %s\n", + sc_dbi_strerror(client->conn)); + dbi_conn_close(client->conn); + return -1; + } + + if (dbi_conn_connect(client->conn) < 0) { + fprintf(stderr, "dbi: failed to connect to database '%s': %s\n", + client->database, sc_dbi_strerror(client->conn)); + dbi_conn_close(client->conn); + return -1; + } + return 0; +} /* sc_dbi_client_connect */ + +int +sc_dbi_exec_query(sc_dbi_client_t *client, const char *query, + sc_dbi_data_cb callback, int n, ...) +{ + dbi_result res; + unsigned int num_fields; + + int status; + + if ((! client) || (! query)) + return -1; + + res = dbi_conn_query(client->conn, query); + if (! res) { + fprintf(stderr, "dbi: failed to execute query '%s': %s\n", + query, sc_dbi_strerror(client->conn)); + return -1; + } + + if (dbi_result_get_numrows(res) == DBI_ROW_ERROR) { + fprintf(stderr, "dbi: failed to fetch rows for query '%s': %s\n", + query, sc_dbi_strerror(client->conn)); + dbi_result_free(res); + return -1; + } + + if (dbi_result_get_numrows(res) < 1) { /* no data */ + dbi_result_free(res); + return 0; + } + + num_fields = dbi_result_get_numfields(res); + + if (n >= 0) { + va_list types; + int i; + + if (n != (int)num_fields) { + fprintf(stderr, "dbi: number of returned fields (%i) does not " + "match the number of requested fields (%i) " + "for query '%s'.\n", num_fields, n, query); + dbi_result_free(res); + return -1; + } + + va_start(types, n); + status = 0; + + for (i = 0; i < n; ++i) { + unsigned short field_type = dbi_result_get_field_type_idx(res, + (unsigned int)(i + 1)); + + unsigned int type = va_arg(types, unsigned int); + + /* column count starts at 1 */ + if ((unsigned int)field_type != type) { + fprintf(stderr, "dbi: type of column '%s' (%u) does not match " + "requested type (%u).\n", + dbi_result_get_field_name(res, (unsigned int)i + 1), + field_type, type); + status = -1; + } + } + + va_end(types); + + if (status) { + dbi_result_free(res); + return status; + } + } + + if (num_fields < 1) { /* no data */ + dbi_result_free(res); + return 0; + } + + status = sc_dbi_get_data(client, res, num_fields, callback); + + dbi_result_free(res); + return status; +} /* sc_dbi_exec_query */ + +void +sc_dbi_client_destroy(sc_dbi_client_t *client) +{ + if (! client) + return; + + if (client->driver) + free(client->driver); + client->driver = NULL; + + if (client->database) + free(client->database); + client->database = NULL; + + if (client->conn) + dbi_conn_close(client->conn); + + if (client->options) + sc_dbi_options_destroy(client->options); + client->options = NULL; + + free(client); +} /* sc_dbi_client_destroy */ + +/* vim: set tw=78 sw=4 ts=4 noexpandtab : */ + -- 2.30.2