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)
130 {
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)
139 {
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)
179 {
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)
239 {
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)
254 {
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)
285 {
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)
309 {
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)
340 {
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)
352 {
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)
433 {
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, ...)
448 {
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)
532 {
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 : */