2 * Copyright (C) 2007 Martin Willi
3 * Hochschule fuer Technik Rapperswil
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 #include "mysql_database.h"
24 #include <utils/mutex.h>
25 #include <utils/linked_list.h>
27 /* Older mysql.h headers do not define it, but we need it. It is not returned
28 * in in MySQL 4 by default, but by MySQL 5. To avoid this problem, we catch
30 #ifndef MYSQL_DATA_TRUNCATED
31 #define MYSQL_DATA_TRUNCATED 101
34 typedef struct private_mysql_database_t private_mysql_database_t
;
37 * private data of mysql_database
39 struct private_mysql_database_t
{
44 mysql_database_t
public;
47 * connection pool, contains conn_t
57 * hostname to connect to
82 typedef struct conn_t conn_t
;
85 * connection pool entry
90 * MySQL database connection
101 * Release a mysql connection
103 static void conn_release(conn_t
*conn
)
105 conn
->in_use
= FALSE
;
108 * thread specific initialization flag
110 pthread_key_t initialized
;
113 * Initialize a thread for mysql usage
115 static void thread_initialize()
117 if (pthread_getspecific(initialized
) == NULL
)
119 pthread_setspecific(initialized
, (void*)TRUE
);
125 * mysql library initialization function
127 bool mysql_database_init()
129 if (mysql_library_init(0, NULL
, NULL
))
133 if (pthread_key_create(&initialized
, (void*)mysql_thread_end
))
142 * mysql library cleanup function
144 void mysql_database_deinit()
146 pthread_key_delete(initialized
);
148 /* mysql_library_end(); would be the clean way, however, it hangs... */
152 * Destroy a mysql connection
154 static void conn_destroy(conn_t
*this)
156 mysql_close(this->mysql
);
161 * Acquire/Reuse a mysql connection
163 static conn_t
*conn_get(private_mysql_database_t
*this)
165 conn_t
*current
, *found
= NULL
;
166 enumerator_t
*enumerator
;
172 this->mutex
->lock(this->mutex
);
173 enumerator
= this->pool
->create_enumerator(this->pool
);
174 while (enumerator
->enumerate(enumerator
, ¤t
))
176 if (!current
->in_use
)
179 found
->in_use
= TRUE
;
183 enumerator
->destroy(enumerator
);
184 this->mutex
->unlock(this->mutex
);
186 { /* check connection if found, release if ping fails */
187 if (mysql_ping(found
->mysql
) == 0)
191 this->mutex
->lock(this->mutex
);
192 this->pool
->remove(this->pool
, found
, NULL
);
193 this->mutex
->unlock(this->mutex
);
202 found
= malloc_thing(conn_t
);
203 found
->in_use
= TRUE
;
204 found
->mysql
= mysql_init(NULL
);
205 if (!mysql_real_connect(found
->mysql
, this->host
, this->username
,
206 this->password
, this->database
, this->port
,
209 DBG1("connecting to mysql://%s:***@%s:%d/%s failed: %s",
210 this->username
, this->host
, this->port
, this->database
,
211 mysql_error(found
->mysql
));
217 this->mutex
->lock(this->mutex
);
218 this->pool
->insert_last(this->pool
, found
);
219 DBG2("increased MySQL connection pool size to %d",
220 this->pool
->get_count(this->pool
));
221 this->mutex
->unlock(this->mutex
);
228 * Create and run a MySQL stmt using a sql string and args
230 static MYSQL_STMT
* run(MYSQL
*mysql
, char *sql
, va_list *args
)
235 stmt
= mysql_stmt_init(mysql
);
238 DBG1("creating MySQL statement failed: %s", mysql_error(mysql
));
241 if (mysql_stmt_prepare(stmt
, sql
, strlen(sql
)))
243 DBG1("preparing MySQL statement failed: %s", mysql_stmt_error(stmt
));
244 mysql_stmt_close(stmt
);
247 params
= mysql_stmt_param_count(stmt
);
253 bind
= alloca(sizeof(MYSQL_BIND
) * params
);
254 memset(bind
, 0, sizeof(MYSQL_BIND
) * params
);
256 for (i
= 0; i
< params
; i
++)
258 switch (va_arg(*args
, db_type_t
))
262 bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
263 bind
[i
].buffer
= (char*)alloca(sizeof(int));
264 *(int*)bind
[i
].buffer
= va_arg(*args
, int);
265 bind
[i
].buffer_length
= sizeof(int);
270 bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
271 bind
[i
].buffer
= (char*)alloca(sizeof(u_int
));
272 *(u_int
*)bind
[i
].buffer
= va_arg(*args
, u_int
);
273 bind
[i
].buffer_length
= sizeof(u_int
);
274 bind
[i
].is_unsigned
= TRUE
;
279 bind
[i
].buffer_type
= MYSQL_TYPE_STRING
;;
280 bind
[i
].buffer
= va_arg(*args
, char*);
283 bind
[i
].buffer_length
= strlen(bind
[i
].buffer
);
289 chunk_t chunk
= va_arg(*args
, chunk_t
);
290 bind
[i
].buffer_type
= MYSQL_TYPE_BLOB
;
291 bind
[i
].buffer
= chunk
.ptr
;
292 bind
[i
].buffer_length
= chunk
.len
;
297 bind
[i
].buffer_type
= MYSQL_TYPE_DOUBLE
;
298 bind
[i
].buffer
= (char*)alloca(sizeof(double));
299 *(double*)bind
[i
].buffer
= va_arg(*args
, double);
300 bind
[i
].buffer_length
= sizeof(double);
305 bind
[i
].buffer_type
= MYSQL_TYPE_NULL
;
309 DBG1("invalid data type supplied");
310 mysql_stmt_close(stmt
);
314 if (mysql_stmt_bind_param(stmt
, bind
))
316 DBG1("binding MySQL param failed: %s", mysql_stmt_error(stmt
));
317 mysql_stmt_close(stmt
);
321 if (mysql_stmt_execute(stmt
))
323 DBG1("executing MySQL statement failed: %s", mysql_stmt_error(stmt
));
324 mysql_stmt_close(stmt
);
331 /** implements enumerator_t */
333 /** associated MySQL statement */
335 /** result bindings */
337 /** pooled connection handle */
339 /** value for INT, UINT, double */
346 /* length for TEXT and BLOB */
347 unsigned long *length
;
348 } mysql_enumerator_t
;
351 * create a mysql enumerator
353 static void mysql_enumerator_destroy(mysql_enumerator_t
*this)
357 columns
= mysql_stmt_field_count(this->stmt
);
359 for (i
= 0; i
< columns
; i
++)
361 switch (this->bind
[i
].buffer_type
)
363 case MYSQL_TYPE_STRING
:
364 case MYSQL_TYPE_BLOB
:
366 free(this->bind
[i
].buffer
);
373 mysql_stmt_close(this->stmt
);
374 conn_release(this->conn
);
376 free(this->val
.p_void
);
382 * Implementation of database.query().enumerate
384 static bool mysql_enumerator_enumerate(mysql_enumerator_t
*this, ...)
389 columns
= mysql_stmt_field_count(this->stmt
);
391 /* free/reset data set of previous call */
392 for (i
= 0; i
< columns
; i
++)
394 switch (this->bind
[i
].buffer_type
)
396 case MYSQL_TYPE_STRING
:
397 case MYSQL_TYPE_BLOB
:
399 free(this->bind
[i
].buffer
);
400 this->bind
[i
].buffer
= NULL
;
401 this->bind
[i
].buffer_length
= 0;
402 this->bind
[i
].length
= &this->length
[i
];
411 switch (mysql_stmt_fetch(this->stmt
))
414 case MYSQL_DATA_TRUNCATED
:
419 DBG1("fetching MySQL row failed: %s", mysql_stmt_error(this->stmt
));
423 va_start(args
, this);
424 for (i
= 0; i
< columns
; i
++)
426 switch (this->bind
[i
].buffer_type
)
428 case MYSQL_TYPE_LONG
:
430 if (this->bind
[i
].is_unsigned
)
432 u_int
*value
= va_arg(args
, u_int
*);
433 *value
= this->val
.p_uint
[i
];
437 int *value
= va_arg(args
, int*);
438 *value
= this->val
.p_int
[i
];
442 case MYSQL_TYPE_STRING
:
444 char **value
= va_arg(args
, char**);
445 this->bind
[i
].buffer
= malloc(this->length
[i
]+1);
446 this->bind
[i
].buffer_length
= this->length
[i
];
447 *value
= this->bind
[i
].buffer
;
448 mysql_stmt_fetch_column(this->stmt
, &this->bind
[i
], i
, 0);
449 ((char*)this->bind
[i
].buffer
)[this->length
[i
]] = '\0';
452 case MYSQL_TYPE_BLOB
:
454 chunk_t
*value
= va_arg(args
, chunk_t
*);
455 this->bind
[i
].buffer
= malloc(this->length
[i
]);
456 this->bind
[i
].buffer_length
= this->length
[i
];
457 value
->ptr
= this->bind
[i
].buffer
;
458 value
->len
= this->length
[i
];
459 mysql_stmt_fetch_column(this->stmt
, &this->bind
[i
], i
, 0);
462 case MYSQL_TYPE_DOUBLE
:
464 double *value
= va_arg(args
, double*);
465 *value
= this->val
.p_double
[i
];
476 * Implementation of database_t.query.
478 static enumerator_t
* query(private_mysql_database_t
*this, char *sql
, ...)
482 mysql_enumerator_t
*enumerator
= NULL
;
485 conn
= conn_get(this);
492 stmt
= run(conn
->mysql
, sql
, &args
);
497 enumerator
= malloc_thing(mysql_enumerator_t
);
498 enumerator
->public.enumerate
= (void*)mysql_enumerator_enumerate
;
499 enumerator
->public.destroy
= (void*)mysql_enumerator_destroy
;
500 enumerator
->stmt
= stmt
;
501 enumerator
->conn
= conn
;
502 columns
= mysql_stmt_field_count(stmt
);
503 enumerator
->bind
= calloc(columns
, sizeof(MYSQL_BIND
));
504 enumerator
->length
= calloc(columns
, sizeof(unsigned long));
505 enumerator
->val
.p_void
= calloc(columns
, sizeof(enumerator
->val
));
506 for (i
= 0; i
< columns
; i
++)
508 switch (va_arg(args
, db_type_t
))
512 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
513 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_int
[i
];
518 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
519 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_uint
[i
];
520 enumerator
->bind
[i
].is_unsigned
= TRUE
;
525 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_STRING
;
526 enumerator
->bind
[i
].length
= &enumerator
->length
[i
];
531 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_BLOB
;
532 enumerator
->bind
[i
].length
= &enumerator
->length
[i
];
537 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_DOUBLE
;
538 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_double
[i
];
542 DBG1("invalid result data type supplied");
543 mysql_enumerator_destroy(enumerator
);
548 if (mysql_stmt_bind_result(stmt
, enumerator
->bind
))
550 DBG1("binding MySQL result failed: %s", mysql_stmt_error(stmt
));
551 mysql_enumerator_destroy(enumerator
);
560 return (enumerator_t
*)enumerator
;
564 * Implementation of database_t.execute.
566 static int execute(private_mysql_database_t
*this, int *rowid
, char *sql
, ...)
573 conn
= conn_get(this);
579 stmt
= run(conn
->mysql
, sql
, &args
);
584 *rowid
= mysql_stmt_insert_id(stmt
);
586 affected
= mysql_stmt_affected_rows(stmt
);
587 mysql_stmt_close(stmt
);
595 * Implementation of database_t.get_driver
597 static db_driver_t
get_driver(private_mysql_database_t
*this)
603 * Implementation of database_t.destroy
605 static void destroy(private_mysql_database_t
*this)
607 this->pool
->destroy_function(this->pool
, (void*)conn_destroy
);
608 this->mutex
->destroy(this->mutex
);
610 free(this->username
);
611 free(this->password
);
612 free(this->database
);
616 static bool parse_uri(private_mysql_database_t
*this, char *uri
)
618 char *username
, *password
, *host
, *port
= "0", *database
, *pos
;
621 * parse mysql://username:pass@host:port/database uri
623 username
= strdupa(uri
+ 8);
624 pos
= strchr(username
, ':');
629 pos
= strrchr(password
, '@');
634 pos
= strrchr(host
, ':');
639 pos
= strchr(port
, '/');
643 pos
= strchr(host
, '/');
650 this->host
= strdup(host
);
651 this->username
= strdup(username
);
652 this->password
= strdup(password
);
653 this->database
= strdup(database
);
654 this->port
= atoi(port
);
659 DBG1("parsing MySQL database uri '%s' failed", uri
);
667 mysql_database_t
*mysql_database_create(char *uri
)
670 private_mysql_database_t
*this;
672 if (!strneq(uri
, "mysql://", 8))
677 this = malloc_thing(private_mysql_database_t
);
679 this->public.db
.query
= (enumerator_t
* (*)(database_t
*this, char *sql
, ...))query
;
680 this->public.db
.execute
= (int (*)(database_t
*this, int *rowid
, char *sql
, ...))execute
;
681 this->public.db
.get_driver
= (db_driver_t(*)(database_t
*))get_driver
;
682 this->public.db
.destroy
= (void(*)(database_t
*))destroy
;
684 if (!parse_uri(this, uri
))
689 this->mutex
= mutex_create(MUTEX_TYPE_DEFAULT
);
690 this->pool
= linked_list_create();
692 /* check connectivity */
693 conn
= conn_get(this);
700 return &this->public;