Code

store: Don't report an error in scan() if the store is still empty.
[sysdb.git] / src / utils / dbi.c
1 /*
2  * SysDB - src/utils/dbi.c
3  * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 #if HAVE_CONFIG_H
29 #       include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "utils/dbi.h"
33 #include "utils/error.h"
35 #include <assert.h>
37 #include <dbi/dbi.h>
39 #include <stdarg.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
44 /*
45  * pre 0.9 DBI compat layer
46  */
48 #if LIBDBI_VERSION < 900
49 #include <pthread.h>
51 typedef void *dbi_inst;
53 static pthread_mutex_t dbi_lock = PTHREAD_MUTEX_INITIALIZER;
54 static int dbi_initialized = 0;
56 static int
57 initialize_r(const char *driverdir, dbi_inst __attribute__((unused)) *pInst)
58 {
59         int status = 0;
60         pthread_mutex_lock(&dbi_lock);
61         if (! dbi_initialized)
62                 status = dbi_initialize(driverdir);
63         dbi_initialized = 1;
64         pthread_mutex_unlock(&dbi_lock);
65         return status;
66 } /* initialize_r */
68 static dbi_driver
69 driver_open_r(const char *name, dbi_inst __attribute__((unused)) Inst)
70 {
71         dbi_driver driver;
72         pthread_mutex_lock(&dbi_lock);
73         driver = dbi_driver_open(name);
74         pthread_mutex_unlock(&dbi_lock);
75         return driver;
76 } /* driver_open_r */
78 static dbi_driver
79 driver_list_r(dbi_driver Current, dbi_inst __attribute__((unused)) Inst)
80 {
81         dbi_driver driver;
82         pthread_mutex_lock(&dbi_lock);
83         driver = dbi_driver_list(Current);
84         pthread_mutex_unlock(&dbi_lock);
85         return driver;
86 } /* driver_list_r */
88 static void
89 shutdown_r(dbi_inst __attribute__((unused)) Inst)
90 {
91         /* do nothing; we don't know if DBI is still in use */
92 } /* shutdown_r */
94 #define dbi_initialize_r initialize_r
95 #define dbi_driver_open_r driver_open_r
96 #define dbi_driver_list_r driver_list_r
97 #define dbi_shutdown_r shutdown_r
98 #endif /* LIBDBI_VERSION < 900 */
100 /*
101  * private data types
102  */
104 typedef struct {
105         char *key;
106         char *value;
107 } sdb_dbi_option_t;
109 struct sdb_dbi_options {
110         sdb_dbi_option_t *options;
111         size_t options_num;
112 };
114 struct sdb_dbi_client {
115         char *driver;
116         char *database;
118         dbi_conn conn;
119         dbi_inst inst;
121         sdb_dbi_options_t *options;
122 };
124 /*
125  * private helper functions
126  */
128 static const char *
129 sdb_dbi_strerror(dbi_conn conn)
131         const char *errmsg = NULL;
132         dbi_conn_error(conn, &errmsg);
133         return errmsg;
134 } /* sdb_dbi_strerror */
136 static int
137 sdb_dbi_get_field(dbi_result res, unsigned int i,
138                 int type, sdb_data_t *data)
140         switch (type) {
141                 case SDB_TYPE_INTEGER:
142                         data->data.integer = dbi_result_get_longlong_idx(res, i);
143                         break;
144                 case SDB_TYPE_DECIMAL:
145                         data->data.decimal = dbi_result_get_double_idx(res, i);
146                         break;
147                 case SDB_TYPE_STRING:
148                         data->data.string = dbi_result_get_string_copy_idx(res, i);
149                         break;
150                 case SDB_TYPE_DATETIME:
151                         {
152                                 /* libdbi does not provide any higher resolutions than that */
153                                 time_t datetime = dbi_result_get_datetime_idx(res, i);
154                                 data->data.datetime = SECS_TO_SDB_TIME(datetime);
155                         }
156                         break;
157                 case SDB_TYPE_BINARY:
158                         {
159                                 size_t length = dbi_result_get_field_length_idx(res, i);
160                                 unsigned char *datum = dbi_result_get_binary_copy_idx(res, i);
161                                 data->data.binary.length = length;
162                                 data->data.binary.datum = datum;
163                         }
164                         break;
165                 default:
166                         sdb_log(SDB_LOG_ERR, "dbi: Unexpected type %i while "
167                                         "parsing query result.", type);
168                         return -1;
169         }
171         data->type = type;
172         return 0;
173 } /* sdb_dbi_get_field */
175 static int
176 sdb_dbi_get_data(sdb_dbi_client_t *client, dbi_result res,
177                 unsigned int num_fields, sdb_dbi_data_cb callback,
178                 sdb_object_t *user_data)
180         sdb_data_t data[num_fields];
181         int types[num_fields];
182         unsigned int i;
184         unsigned long long num_rows;
185         unsigned long long success = 0, n;
187         assert(client && res && callback);
188         assert(num_fields > 0);
190         for (i = 0; i < num_fields; ++i) {
191                 types[i] = dbi_result_get_field_type_idx(res, i + 1);
192                 if (types[i] == DBI_TYPE_ERROR) {
193                         sdb_log(SDB_LOG_ERR, "dbi: failed to fetch data: %s",
194                                         sdb_dbi_strerror(client->conn));
195                         return -1;
196                 }
197                 types[i] = DBI_TYPE_TO_SDB(types[i]);
198         }
200         num_rows = dbi_result_get_numrows(res);
201         if (num_rows < 1)
202                 return -1;
204         for (n = 0; n < num_rows; ++n) {
205                 int status;
207                 if (! dbi_result_seek_row(res, n + 1)) {
208                         sdb_log(SDB_LOG_ERR, "dbi: Failed to retrieve row %llu: %s",
209                                         n, sdb_dbi_strerror(client->conn));
210                         continue;
211                 }
213                 for (i = 0; i < num_fields; ++i)
214                         if (sdb_dbi_get_field(res, (unsigned int)(i + 1),
215                                                 types[i], &data[i]))
216                                 continue;
218                 status = callback(client, num_fields, data, user_data);
219                 for (i = 0; i < num_fields; ++i)
220                         sdb_data_free_datum(&data[i]);
222                 if (status)
223                         continue;
225                 ++success;
226         }
228         if (! success)
229                 return -1;
230         return 0;
231 } /* sdb_dbi_get_data */
233 /*
234  * public API
235  */
237 sdb_dbi_options_t *
238 sdb_dbi_options_create(void)
240         sdb_dbi_options_t *options;
242         options = malloc(sizeof(*options));
243         if (! options)
244                 return NULL;
246         options->options = NULL;
247         options->options_num = 0;
248         return options;
249 } /* sdb_dbi_options_create */
251 int
252 sdb_dbi_options_add(sdb_dbi_options_t *options,
253                 const char *key, const char *value)
255         sdb_dbi_option_t *new;
257         if ((! options) || (! key) || (! value))
258                 return -1;
260         new = realloc(options->options,
261                         (options->options_num + 1) * sizeof(*options->options));
262         if (! new)
263                 return -1;
265         options->options = new;
266         new = options->options + options->options_num;
268         new->key = strdup(key);
269         new->value = strdup(value);
271         if ((! new->key) || (! new->value)) {
272                 if (new->key)
273                         free(new->key);
274                 if (new->value)
275                         free(new->value);
276                 return -1;
277         }
279         ++options->options_num;
280         return 0;
281 } /* sdb_dbi_options_add */
283 void
284 sdb_dbi_options_destroy(sdb_dbi_options_t *options)
286         size_t i;
288         if (! options)
289                 return;
291         for (i = 0; i < options->options_num; ++i) {
292                 sdb_dbi_option_t *opt = options->options + i;
294                 if (opt->key)
295                         free(opt->key);
296                 if (opt->value)
297                         free(opt->value);
298         }
300         if (options->options)
301                 free(options->options);
302         options->options = NULL;
303         options->options_num = 0;
304         free(options);
305 } /* sdb_dbi_options_destroy */
307 sdb_dbi_client_t *
308 sdb_dbi_client_create(const char *driver, const char *database)
310         sdb_dbi_client_t *client;
312         if ((! driver) || (! database))
313                 return NULL;
315         client = malloc(sizeof(*client));
316         if (! client)
317                 return NULL;
318         memset(client, 0, sizeof(*client));
320         client->conn = NULL;
321         client->options = NULL;
323         if (dbi_initialize_r(/* driverdir = */ NULL, &client->inst) < 0) {
324                 free(client);
325                 return NULL;
326         }
328         client->driver = strdup(driver);
329         client->database = strdup(database);
330         if ((! client->driver) || (! client->database)) {
331                 sdb_dbi_client_destroy(client);
332                 return NULL;
333         }
334         return client;
335 } /* sdb_dbi_client_create */
337 int
338 sdb_dbi_client_set_options(sdb_dbi_client_t *client,
339                 sdb_dbi_options_t *options)
341         if (! client)
342                 return -1;
344         if (client->options)
345                 sdb_dbi_options_destroy(client->options);
346         client->options = options;
347         return 0;
348 } /* sdb_dbi_client_set_options */
350 int
351 sdb_dbi_client_connect(sdb_dbi_client_t *client)
353         dbi_driver driver;
354         size_t i;
356         if ((! client) || (! client->driver) || (! client->database))
357                 return -1;
359         if (client->conn) {
360                 dbi_conn_close(client->conn);
361                 client->conn = NULL;
362         }
364         driver = dbi_driver_open_r(client->driver, client->inst);
365         if (! driver) {
366                 sdb_error_set("dbi: failed to open DBI driver '%s'; "
367                                 "possibly it's not installed.\n",
368                                 client->driver);
370                 sdb_error_append("dbi: known drivers:\n");
371                 for (driver = dbi_driver_list_r(NULL, client->inst); driver;
372                                 driver = dbi_driver_list_r(driver, client->inst)) {
373                         sdb_error_append("\t- %s\n", dbi_driver_get_name(driver));
374                 }
375                 sdb_error_chomp();
376                 sdb_error_log(SDB_LOG_ERR);
377                 return -1;
378         }
380         client->conn = dbi_conn_open(driver);
381         if (! client->conn) {
382                 sdb_log(SDB_LOG_ERR, "dbi: failed to open connection object.");
383                 return -1;
384         }
386         if (client->options) {
387                 for (i = 0; i < client->options->options_num; ++i) {
388                         const char *opt;
390                         if (! dbi_conn_set_option(client->conn,
391                                                 client->options->options[i].key,
392                                                 client->options->options[i].value))
393                                 continue;
394                         /* else: error */
396                         sdb_error_set("dbi: failed to set option '%s': %s\n",
397                                         client->options->options[i].key,
398                                         sdb_dbi_strerror(client->conn));
400                         sdb_error_append("dbi: known driver options:\n");
401                         for (opt = dbi_conn_get_option_list(client->conn, NULL); opt;
402                                         opt = dbi_conn_get_option_list(client->conn, opt))
403                                 sdb_error_append("\t- %s\n", opt);
404                         sdb_error_chomp();
405                         sdb_error_log(SDB_LOG_ERR);
407                         dbi_conn_close(client->conn);
408                         client->conn = NULL;
409                         return -1;
410                 }
411         }
413         if (dbi_conn_set_option(client->conn, "dbname", client->database)) {
414                 sdb_log(SDB_LOG_ERR, "dbi: failed to set option 'dbname': %s",
415                                 sdb_dbi_strerror(client->conn));
416                 dbi_conn_close(client->conn);
417                 client->conn = NULL;
418                 return -1;
419         }
421         if (dbi_conn_connect(client->conn) < 0) {
422                 sdb_log(SDB_LOG_ERR, "dbi: failed to connect to database '%s': %s",
423                                 client->database, sdb_dbi_strerror(client->conn));
424                 dbi_conn_close(client->conn);
425                 client->conn = NULL;
426                 return -1;
427         }
428         return 0;
429 } /* sdb_dbi_client_connect */
431 int
432 sdb_dbi_client_check_conn(sdb_dbi_client_t *client)
434         if (! client)
435                 return -1;
437         if (! client->conn)
438                 return sdb_dbi_client_connect(client);
440         if (dbi_conn_ping(client->conn))
441                 return 0;
442         return sdb_dbi_client_connect(client);
443 } /* sdb_dbi_client_check_conn */
445 int
446 sdb_dbi_exec_query(sdb_dbi_client_t *client, const char *query,
447                 sdb_dbi_data_cb callback, sdb_object_t *user_data, int n, ...)
449         dbi_result res;
450         unsigned int num_fields;
452         int status;
454         if ((! client) || (! client->conn) || (! query))
455                 return -1;
457         res = dbi_conn_query(client->conn, query);
458         if (! res) {
459                 sdb_log(SDB_LOG_ERR, "dbi: failed to execute query '%s': %s",
460                                 query, sdb_dbi_strerror(client->conn));
461                 return -1;
462         }
464         if (dbi_result_get_numrows(res) == DBI_ROW_ERROR) {
465                 sdb_log(SDB_LOG_ERR, "dbi: failed to fetch rows for query "
466                                 "'%s': %s", query, sdb_dbi_strerror(client->conn));
467                 dbi_result_free(res);
468                 return -1;
469         }
471         if (dbi_result_get_numrows(res) < 1) { /* no data */
472                 dbi_result_free(res);
473                 return 0;
474         }
476         num_fields = dbi_result_get_numfields(res);
478         if (n >= 0) {
479                 va_list types;
480                 int i;
482                 if (n != (int)num_fields) {
483                         sdb_log(SDB_LOG_ERR, "dbi: number of returned fields (%i) "
484                                         "does not match the number of requested fields (%i) "
485                                         "for query '%s'.", num_fields, n, query);
486                         dbi_result_free(res);
487                         return -1;
488                 }
490                 va_start(types, n);
491                 status = 0;
493                 for (i = 0; i < n; ++i) {
494                         unsigned short field_type = dbi_result_get_field_type_idx(res,
495                                         (unsigned int)(i + 1));
497                         unsigned int type = va_arg(types, unsigned int);
499                         field_type = DBI_TYPE_TO_SDB(field_type);
501                         /* column count starts at 1 */
502                         if ((unsigned int)field_type != type) {
503                                 sdb_log(SDB_LOG_ERR, "dbi: type of column '%s' (%u) "
504                                                 "does not match requested type (%u).",
505                                                 dbi_result_get_field_name(res, (unsigned int)i + 1),
506                                                 field_type, type);
507                                 status = -1;
508                         }
509                 }
511                 va_end(types);
513                 if (status) {
514                         dbi_result_free(res);
515                         return status;
516                 }
517         }
519         if (num_fields < 1) { /* no data */
520                 dbi_result_free(res);
521                 return 0;
522         }
524         status = sdb_dbi_get_data(client, res, num_fields, callback, user_data);
526         dbi_result_free(res);
527         return status;
528 } /* sdb_dbi_exec_query */
530 void
531 sdb_dbi_client_destroy(sdb_dbi_client_t *client)
533         if (! client)
534                 return;
536         if (client->driver)
537                 free(client->driver);
538         client->driver = NULL;
540         if (client->database)
541                 free(client->database);
542         client->database = NULL;
544         if (client->conn)
545                 dbi_conn_close(client->conn);
546         client->conn = NULL;
548         if (client->inst)
549                 dbi_shutdown_r(client->inst);
550         client->inst = NULL;
552         if (client->options)
553                 sdb_dbi_options_destroy(client->options);
554         client->options = NULL;
556         free(client);
557 } /* sdb_dbi_client_destroy */
559 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */