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
20 #include "mysql_database.h"
24 #include <threading/thread_value.h>
25 #include <threading/mutex.h>
26 #include <utils/linked_list.h>
28 /* Older mysql.h headers do not define it, but we need it. It is not returned
29 * in in MySQL 4 by default, but by MySQL 5. To avoid this problem, we catch
31 #ifndef MYSQL_DATA_TRUNCATED
32 #define MYSQL_DATA_TRUNCATED 101
35 typedef struct private_mysql_database_t private_mysql_database_t
;
38 * private data of mysql_database
40 struct private_mysql_database_t
{
45 mysql_database_t
public;
48 * connection pool, contains conn_t
58 * hostname to connect to
83 typedef struct conn_t conn_t
;
86 * connection pool entry
91 * MySQL database connection
102 * Release a mysql connection
104 static void conn_release(conn_t
*conn
)
106 conn
->in_use
= FALSE
;
110 * thread specific initialization flag
112 thread_value_t
*initialized
;
115 * Initialize a thread for mysql usage
117 static void thread_initialize()
119 if (initialized
->get(initialized
) == NULL
)
121 initialized
->set(initialized
, (void*)TRUE
);
127 * mysql library initialization function
129 bool mysql_database_init()
131 if (mysql_library_init(0, NULL
, NULL
))
135 initialized
= thread_value_create((thread_cleanup_t
)mysql_thread_end
);
140 * mysql library cleanup function
142 void mysql_database_deinit()
144 initialized
->destroy(initialized
);
146 /* mysql_library_end(); would be the clean way, however, it hangs... */
150 * Destroy a mysql connection
152 static void conn_destroy(conn_t
*this)
154 mysql_close(this->mysql
);
159 * Acquire/Reuse a mysql connection
161 static conn_t
*conn_get(private_mysql_database_t
*this)
163 conn_t
*current
, *found
= NULL
;
164 enumerator_t
*enumerator
;
170 this->mutex
->lock(this->mutex
);
171 enumerator
= this->pool
->create_enumerator(this->pool
);
172 while (enumerator
->enumerate(enumerator
, ¤t
))
174 if (!current
->in_use
)
177 found
->in_use
= TRUE
;
181 enumerator
->destroy(enumerator
);
182 this->mutex
->unlock(this->mutex
);
184 { /* check connection if found, release if ping fails */
185 if (mysql_ping(found
->mysql
) == 0)
189 this->mutex
->lock(this->mutex
);
190 this->pool
->remove(this->pool
, found
, NULL
);
191 this->mutex
->unlock(this->mutex
);
200 found
= malloc_thing(conn_t
);
201 found
->in_use
= TRUE
;
202 found
->mysql
= mysql_init(NULL
);
203 if (!mysql_real_connect(found
->mysql
, this->host
, this->username
,
204 this->password
, this->database
, this->port
,
207 DBG1(DBG_LIB
, "connecting to mysql://%s:***@%s:%d/%s failed: %s",
208 this->username
, this->host
, this->port
, this->database
,
209 mysql_error(found
->mysql
));
215 this->mutex
->lock(this->mutex
);
216 this->pool
->insert_last(this->pool
, found
);
217 DBG2(DBG_LIB
, "increased MySQL connection pool size to %d",
218 this->pool
->get_count(this->pool
));
219 this->mutex
->unlock(this->mutex
);
226 * Create and run a MySQL stmt using a sql string and args
228 static MYSQL_STMT
* run(MYSQL
*mysql
, char *sql
, va_list *args
)
233 stmt
= mysql_stmt_init(mysql
);
236 DBG1(DBG_LIB
, "creating MySQL statement failed: %s",
240 if (mysql_stmt_prepare(stmt
, sql
, strlen(sql
)))
242 DBG1(DBG_LIB
, "preparing MySQL statement failed: %s",
243 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(DBG_LIB
, "invalid data type supplied");
310 mysql_stmt_close(stmt
);
314 if (mysql_stmt_bind_param(stmt
, bind
))
316 DBG1(DBG_LIB
, "binding MySQL param failed: %s",
317 mysql_stmt_error(stmt
));
318 mysql_stmt_close(stmt
);
322 if (mysql_stmt_execute(stmt
))
324 DBG1(DBG_LIB
, "executing MySQL statement failed: %s",
325 mysql_stmt_error(stmt
));
326 mysql_stmt_close(stmt
);
333 /** implements enumerator_t */
335 /** associated MySQL statement */
337 /** result bindings */
339 /** pooled connection handle */
341 /** value for INT, UINT, double */
348 /* length for TEXT and BLOB */
349 unsigned long *length
;
350 } mysql_enumerator_t
;
353 * create a mysql enumerator
355 static void mysql_enumerator_destroy(mysql_enumerator_t
*this)
359 columns
= mysql_stmt_field_count(this->stmt
);
361 for (i
= 0; i
< columns
; i
++)
363 switch (this->bind
[i
].buffer_type
)
365 case MYSQL_TYPE_STRING
:
366 case MYSQL_TYPE_BLOB
:
368 free(this->bind
[i
].buffer
);
375 mysql_stmt_close(this->stmt
);
376 conn_release(this->conn
);
378 free(this->val
.p_void
);
384 * Implementation of database.query().enumerate
386 static bool mysql_enumerator_enumerate(mysql_enumerator_t
*this, ...)
391 columns
= mysql_stmt_field_count(this->stmt
);
393 /* free/reset data set of previous call */
394 for (i
= 0; i
< columns
; i
++)
396 switch (this->bind
[i
].buffer_type
)
398 case MYSQL_TYPE_STRING
:
399 case MYSQL_TYPE_BLOB
:
401 free(this->bind
[i
].buffer
);
402 this->bind
[i
].buffer
= NULL
;
403 this->bind
[i
].buffer_length
= 0;
404 this->bind
[i
].length
= &this->length
[i
];
413 switch (mysql_stmt_fetch(this->stmt
))
416 case MYSQL_DATA_TRUNCATED
:
421 DBG1(DBG_LIB
, "fetching MySQL row failed: %s",
422 mysql_stmt_error(this->stmt
));
426 va_start(args
, this);
427 for (i
= 0; i
< columns
; i
++)
429 switch (this->bind
[i
].buffer_type
)
431 case MYSQL_TYPE_LONG
:
433 if (this->bind
[i
].is_unsigned
)
435 u_int
*value
= va_arg(args
, u_int
*);
436 *value
= this->val
.p_uint
[i
];
440 int *value
= va_arg(args
, int*);
441 *value
= this->val
.p_int
[i
];
445 case MYSQL_TYPE_STRING
:
447 char **value
= va_arg(args
, char**);
448 this->bind
[i
].buffer
= malloc(this->length
[i
]+1);
449 this->bind
[i
].buffer_length
= this->length
[i
];
450 *value
= this->bind
[i
].buffer
;
451 mysql_stmt_fetch_column(this->stmt
, &this->bind
[i
], i
, 0);
452 ((char*)this->bind
[i
].buffer
)[this->length
[i
]] = '\0';
455 case MYSQL_TYPE_BLOB
:
457 chunk_t
*value
= va_arg(args
, chunk_t
*);
458 this->bind
[i
].buffer
= malloc(this->length
[i
]);
459 this->bind
[i
].buffer_length
= this->length
[i
];
460 value
->ptr
= this->bind
[i
].buffer
;
461 value
->len
= this->length
[i
];
462 mysql_stmt_fetch_column(this->stmt
, &this->bind
[i
], i
, 0);
465 case MYSQL_TYPE_DOUBLE
:
467 double *value
= va_arg(args
, double*);
468 *value
= this->val
.p_double
[i
];
478 METHOD(database_t
, query
, enumerator_t
*,
479 private_mysql_database_t
*this, char *sql
, ...)
483 mysql_enumerator_t
*enumerator
= NULL
;
486 conn
= conn_get(this);
493 stmt
= run(conn
->mysql
, sql
, &args
);
498 enumerator
= malloc_thing(mysql_enumerator_t
);
499 enumerator
->public.enumerate
= (void*)mysql_enumerator_enumerate
;
500 enumerator
->public.destroy
= (void*)mysql_enumerator_destroy
;
501 enumerator
->stmt
= stmt
;
502 enumerator
->conn
= conn
;
503 columns
= mysql_stmt_field_count(stmt
);
504 enumerator
->bind
= calloc(columns
, sizeof(MYSQL_BIND
));
505 enumerator
->length
= calloc(columns
, sizeof(unsigned long));
506 enumerator
->val
.p_void
= calloc(columns
, sizeof(enumerator
->val
));
507 for (i
= 0; i
< columns
; i
++)
509 switch (va_arg(args
, db_type_t
))
513 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
514 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_int
[i
];
519 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
520 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_uint
[i
];
521 enumerator
->bind
[i
].is_unsigned
= TRUE
;
526 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_STRING
;
527 enumerator
->bind
[i
].length
= &enumerator
->length
[i
];
532 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_BLOB
;
533 enumerator
->bind
[i
].length
= &enumerator
->length
[i
];
538 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_DOUBLE
;
539 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_double
[i
];
543 DBG1(DBG_LIB
, "invalid result data type supplied");
544 mysql_enumerator_destroy(enumerator
);
549 if (mysql_stmt_bind_result(stmt
, enumerator
->bind
))
551 DBG1(DBG_LIB
, "binding MySQL result failed: %s",
552 mysql_stmt_error(stmt
));
553 mysql_enumerator_destroy(enumerator
);
562 return (enumerator_t
*)enumerator
;
565 METHOD(database_t
, execute
, int,
566 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
);
594 METHOD(database_t
, get_driver
,db_driver_t
,
595 private_mysql_database_t
*this)
600 METHOD(database_t
, destroy
, void,
601 private_mysql_database_t
*this)
603 this->pool
->destroy_function(this->pool
, (void*)conn_destroy
);
604 this->mutex
->destroy(this->mutex
);
606 free(this->username
);
607 free(this->password
);
608 free(this->database
);
612 static bool parse_uri(private_mysql_database_t
*this, char *uri
)
614 char *username
, *password
, *host
, *port
= "0", *database
, *pos
;
617 * parse mysql://username:pass@host:port/database uri
619 username
= strdupa(uri
+ 8);
620 pos
= strchr(username
, ':');
625 pos
= strrchr(password
, '@');
630 pos
= strrchr(host
, ':');
635 pos
= strchr(port
, '/');
639 pos
= strchr(host
, '/');
646 this->host
= strdup(host
);
647 this->username
= strdup(username
);
648 this->password
= strdup(password
);
649 this->database
= strdup(database
);
650 this->port
= atoi(port
);
655 DBG1(DBG_LIB
, "parsing MySQL database uri '%s' failed", uri
);
663 mysql_database_t
*mysql_database_create(char *uri
)
666 private_mysql_database_t
*this;
668 if (!strneq(uri
, "mysql://", 8))
678 .get_driver
= _get_driver
,
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;