Code

utils dbi: Added initial version.
authorSebastian Harl <sh@tokkee.org>
Mon, 10 Dec 2012 14:37:48 +0000 (15:37 +0100)
committerSebastian Harl <sh@tokkee.org>
Mon, 10 Dec 2012 14:37:48 +0000 (15:37 +0100)
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
src/Makefile.am
src/include/utils/dbi.h [new file with mode: 0644]
src/utils/dbi.c [new file with mode: 0644]

index fe9331d..d6e21c1 100644 (file)
@@ -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()
index b2816eb..4544b5d 100644 (file)
@@ -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 (file)
index 0000000..2371286
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * syscollector - src/include/utils/dbi.h
+ * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 <inttypes.h>
+#include <stddef.h>
+
+#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 (file)
index 0000000..3aef35e
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ * syscollector - src/utils/dbi.c
+ * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
+ * 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 <assert.h>
+
+#include <dbi/dbi.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * 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 : */
+