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 "
383 "object.");
384 return -1;
385 }
387 if (client->options) {
388 for (i = 0; i < client->options->options_num; ++i) {
389 const char *opt;
391 if (! dbi_conn_set_option(client->conn,
392 client->options->options[i].key,
393 client->options->options[i].value))
394 continue;
395 /* else: error */
397 sdb_error_set("dbi: failed to set option '%s': %s\n",
398 client->options->options[i].key,
399 sdb_dbi_strerror(client->conn));
401 sdb_error_append("dbi: known driver options:\n");
402 for (opt = dbi_conn_get_option_list(client->conn, NULL); opt;
403 opt = dbi_conn_get_option_list(client->conn, opt))
404 sdb_error_append("\t- %s\n", opt);
405 sdb_error_chomp();
406 sdb_error_log(SDB_LOG_ERR);
408 dbi_conn_close(client->conn);
409 client->conn = NULL;
410 return -1;
411 }
412 }
414 if (dbi_conn_set_option(client->conn, "dbname", client->database)) {
415 sdb_log(SDB_LOG_ERR, "dbi: failed to set option 'dbname': %s",
416 sdb_dbi_strerror(client->conn));
417 dbi_conn_close(client->conn);
418 client->conn = NULL;
419 return -1;
420 }
422 if (dbi_conn_connect(client->conn) < 0) {
423 sdb_log(SDB_LOG_ERR, "dbi: failed to connect to database '%s': %s",
424 client->database, sdb_dbi_strerror(client->conn));
425 dbi_conn_close(client->conn);
426 client->conn = NULL;
427 return -1;
428 }
429 return 0;
430 } /* sdb_dbi_client_connect */
432 int
433 sdb_dbi_client_check_conn(sdb_dbi_client_t *client)
434 {
435 if (! client)
436 return -1;
438 if (! client->conn)
439 return sdb_dbi_client_connect(client);
441 if (dbi_conn_ping(client->conn))
442 return 0;
443 return sdb_dbi_client_connect(client);
444 } /* sdb_dbi_client_check_conn */
446 int
447 sdb_dbi_exec_query(sdb_dbi_client_t *client, const char *query,
448 sdb_dbi_data_cb callback, sdb_object_t *user_data, int n, ...)
449 {
450 dbi_result res;
451 unsigned int num_fields;
453 int status;
455 if ((! client) || (! client->conn) || (! query))
456 return -1;
458 res = dbi_conn_query(client->conn, query);
459 if (! res) {
460 sdb_log(SDB_LOG_ERR, "dbi: failed to execute query '%s': %s",
461 query, sdb_dbi_strerror(client->conn));
462 return -1;
463 }
465 if (dbi_result_get_numrows(res) == DBI_ROW_ERROR) {
466 sdb_log(SDB_LOG_ERR, "dbi: failed to fetch rows for query "
467 "'%s': %s", query, sdb_dbi_strerror(client->conn));
468 dbi_result_free(res);
469 return -1;
470 }
472 if (dbi_result_get_numrows(res) < 1) { /* no data */
473 dbi_result_free(res);
474 return 0;
475 }
477 num_fields = dbi_result_get_numfields(res);
479 if (n >= 0) {
480 va_list types;
481 int i;
483 if (n != (int)num_fields) {
484 sdb_log(SDB_LOG_ERR, "dbi: number of returned fields (%i) "
485 "does not match the number of requested fields (%i) "
486 "for query '%s'.", num_fields, n, query);
487 dbi_result_free(res);
488 return -1;
489 }
491 va_start(types, n);
492 status = 0;
494 for (i = 0; i < n; ++i) {
495 unsigned short field_type = dbi_result_get_field_type_idx(res,
496 (unsigned int)(i + 1));
498 unsigned int type = va_arg(types, unsigned int);
500 field_type = DBI_TYPE_TO_SDB(field_type);
502 /* column count starts at 1 */
503 if ((unsigned int)field_type != type) {
504 sdb_log(SDB_LOG_ERR, "dbi: type of column '%s' (%u) "
505 "does not match requested type (%u).",
506 dbi_result_get_field_name(res, (unsigned int)i + 1),
507 field_type, type);
508 status = -1;
509 }
510 }
512 va_end(types);
514 if (status) {
515 dbi_result_free(res);
516 return status;
517 }
518 }
520 if (num_fields < 1) { /* no data */
521 dbi_result_free(res);
522 return 0;
523 }
525 status = sdb_dbi_get_data(client, res, num_fields, callback, user_data);
527 dbi_result_free(res);
528 return status;
529 } /* sdb_dbi_exec_query */
531 void
532 sdb_dbi_client_destroy(sdb_dbi_client_t *client)
533 {
534 if (! client)
535 return;
537 if (client->driver)
538 free(client->driver);
539 client->driver = NULL;
541 if (client->database)
542 free(client->database);
543 client->database = NULL;
545 if (client->conn)
546 dbi_conn_close(client->conn);
547 client->conn = NULL;
549 dbi_shutdown_r(client->inst);
551 if (client->options)
552 sdb_dbi_options_destroy(client->options);
553 client->options = NULL;
555 free(client);
556 } /* sdb_dbi_client_destroy */
558 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */