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/mysql.h>
23 #include "mysql_database.h"
26 #include <utils/mutex.h>
27 #include <utils/linked_list.h>
29 /* Older mysql.h headers do not define it, but we need it. It is not returned
30 * in in MySQL 4 by default, but by MySQL 5. To avoid this problem, we catch
32 #ifndef MYSQL_DATA_TRUNCATED
33 #define MYSQL_DATA_TRUNCATED 101
36 typedef struct private_mysql_database_t private_mysql_database_t
;
39 * private data of mysql_database
41 struct private_mysql_database_t
{
46 mysql_database_t
public;
49 * connection pool, contains conn_t
59 * hostname to connect to
84 typedef struct conn_t conn_t
;
87 * connection pool entry
92 * MySQL database connection
103 * Release a mysql connection
105 static void conn_release(conn_t
*conn
)
107 conn
->in_use
= FALSE
;
110 * thread specific initialization flag
112 pthread_key_t initialized
;
115 * Initialize a thread for mysql usage
117 static void thread_initialize()
119 if (pthread_getspecific(initialized
) == NULL
)
121 pthread_setspecific(initialized
, (void*)TRUE
);
127 * mysql library initialization function
129 bool mysql_database_init()
131 if (mysql_library_init(0, NULL
, NULL
))
135 if (pthread_key_create(&initialized
, (void*)mysql_thread_end
))
144 * mysql library cleanup function
146 void mysql_database_deinit()
148 pthread_key_delete(initialized
);
150 /* mysql_library_end(); would be the clean way, however, it hangs... */
154 * Destroy a mysql connection
156 static void conn_destroy(conn_t
*this)
158 mysql_close(this->mysql
);
163 * Acquire/Reuse a mysql connection
165 static conn_t
*conn_get(private_mysql_database_t
*this)
167 conn_t
*current
, *found
= NULL
;
168 enumerator_t
*enumerator
;
174 this->mutex
->lock(this->mutex
);
175 enumerator
= this->pool
->create_enumerator(this->pool
);
176 while (enumerator
->enumerate(enumerator
, ¤t
))
178 if (!current
->in_use
)
181 found
->in_use
= TRUE
;
185 enumerator
->destroy(enumerator
);
186 this->mutex
->unlock(this->mutex
);
188 { /* check connection if found, release if ping fails */
189 if (mysql_ping(found
->mysql
) == 0)
193 this->mutex
->lock(this->mutex
);
194 this->pool
->remove(this->pool
, found
, NULL
);
195 this->mutex
->unlock(this->mutex
);
204 found
= malloc_thing(conn_t
);
205 found
->in_use
= TRUE
;
206 found
->mysql
= mysql_init(NULL
);
207 if (!mysql_real_connect(found
->mysql
, this->host
, this->username
,
208 this->password
, this->database
, this->port
,
211 DBG1("connecting to mysql://%s:***@%s:%d/%s failed: %s",
212 this->username
, this->host
, this->port
, this->database
,
213 mysql_error(found
->mysql
));
219 this->mutex
->lock(this->mutex
);
220 this->pool
->insert_last(this->pool
, found
);
221 DBG2("increased MySQL connection pool size to %d",
222 this->pool
->get_count(this->pool
));
223 this->mutex
->unlock(this->mutex
);
230 * Create and run a MySQL stmt using a sql string and args
232 static MYSQL_STMT
* run(MYSQL
*mysql
, char *sql
, va_list *args
)
237 stmt
= mysql_stmt_init(mysql
);
240 DBG1("creating MySQL statement failed: %s", mysql_error(mysql
));
243 if (mysql_stmt_prepare(stmt
, sql
, strlen(sql
)))
245 DBG1("preparing MySQL statement failed: %s", mysql_stmt_error(stmt
));
246 mysql_stmt_close(stmt
);
249 params
= mysql_stmt_param_count(stmt
);
255 bind
= alloca(sizeof(MYSQL_BIND
) * params
);
256 memset(bind
, 0, sizeof(MYSQL_BIND
) * params
);
258 for (i
= 0; i
< params
; i
++)
260 switch (va_arg(*args
, db_type_t
))
264 bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
265 bind
[i
].buffer
= (char*)alloca(sizeof(int));
266 *(int*)bind
[i
].buffer
= va_arg(*args
, int);
267 bind
[i
].buffer_length
= sizeof(int);
272 bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
273 bind
[i
].buffer
= (char*)alloca(sizeof(u_int
));
274 *(u_int
*)bind
[i
].buffer
= va_arg(*args
, u_int
);
275 bind
[i
].buffer_length
= sizeof(u_int
);
276 bind
[i
].is_unsigned
= TRUE
;
281 bind
[i
].buffer_type
= MYSQL_TYPE_STRING
;;
282 bind
[i
].buffer
= va_arg(*args
, char*);
285 bind
[i
].buffer_length
= strlen(bind
[i
].buffer
);
291 chunk_t chunk
= va_arg(*args
, chunk_t
);
292 bind
[i
].buffer_type
= MYSQL_TYPE_BLOB
;
293 bind
[i
].buffer
= chunk
.ptr
;
294 bind
[i
].buffer_length
= chunk
.len
;
299 bind
[i
].buffer_type
= MYSQL_TYPE_DOUBLE
;
300 bind
[i
].buffer
= (char*)alloca(sizeof(double));
301 *(double*)bind
[i
].buffer
= va_arg(*args
, double);
302 bind
[i
].buffer_length
= sizeof(double);
307 bind
[i
].buffer_type
= MYSQL_TYPE_NULL
;
311 DBG1("invalid data type supplied");
312 mysql_stmt_close(stmt
);
316 if (mysql_stmt_bind_param(stmt
, bind
))
318 DBG1("binding MySQL param failed: %s", mysql_stmt_error(stmt
));
319 mysql_stmt_close(stmt
);
323 if (mysql_stmt_execute(stmt
))
325 DBG1("executing MySQL statement failed: %s", 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("fetching MySQL row failed: %s", mysql_stmt_error(this->stmt
));
425 va_start(args
, this);
426 for (i
= 0; i
< columns
; i
++)
428 switch (this->bind
[i
].buffer_type
)
430 case MYSQL_TYPE_LONG
:
432 if (this->bind
[i
].is_unsigned
)
434 u_int
*value
= va_arg(args
, u_int
*);
435 *value
= this->val
.p_uint
[i
];
439 int *value
= va_arg(args
, int*);
440 *value
= this->val
.p_int
[i
];
444 case MYSQL_TYPE_STRING
:
446 char **value
= va_arg(args
, char**);
447 this->bind
[i
].buffer
= malloc(this->length
[i
]+1);
448 this->bind
[i
].buffer_length
= this->length
[i
];
449 *value
= this->bind
[i
].buffer
;
450 mysql_stmt_fetch_column(this->stmt
, &this->bind
[i
], i
, 0);
451 ((char*)this->bind
[i
].buffer
)[this->length
[i
]] = '\0';
454 case MYSQL_TYPE_BLOB
:
456 chunk_t
*value
= va_arg(args
, chunk_t
*);
457 this->bind
[i
].buffer
= malloc(this->length
[i
]);
458 this->bind
[i
].buffer_length
= this->length
[i
];
459 value
->ptr
= this->bind
[i
].buffer
;
460 value
->len
= this->length
[i
];
461 mysql_stmt_fetch_column(this->stmt
, &this->bind
[i
], i
, 0);
464 case MYSQL_TYPE_DOUBLE
:
466 double *value
= va_arg(args
, double*);
467 *value
= this->val
.p_double
[i
];
478 * Implementation of database_t.query.
480 static enumerator_t
* query(private_mysql_database_t
*this, char *sql
, ...)
484 mysql_enumerator_t
*enumerator
= NULL
;
487 conn
= conn_get(this);
494 stmt
= run(conn
->mysql
, sql
, &args
);
499 enumerator
= malloc_thing(mysql_enumerator_t
);
500 enumerator
->public.enumerate
= (void*)mysql_enumerator_enumerate
;
501 enumerator
->public.destroy
= (void*)mysql_enumerator_destroy
;
502 enumerator
->stmt
= stmt
;
503 enumerator
->conn
= conn
;
504 columns
= mysql_stmt_field_count(stmt
);
505 enumerator
->bind
= calloc(columns
, sizeof(MYSQL_BIND
));
506 enumerator
->length
= calloc(columns
, sizeof(unsigned long));
507 enumerator
->val
.p_void
= calloc(columns
, sizeof(enumerator
->val
));
508 for (i
= 0; i
< columns
; i
++)
510 switch (va_arg(args
, db_type_t
))
514 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
515 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_int
[i
];
520 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_LONG
;
521 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_uint
[i
];
522 enumerator
->bind
[i
].is_unsigned
= TRUE
;
527 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_STRING
;
528 enumerator
->bind
[i
].length
= &enumerator
->length
[i
];
533 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_BLOB
;
534 enumerator
->bind
[i
].length
= &enumerator
->length
[i
];
539 enumerator
->bind
[i
].buffer_type
= MYSQL_TYPE_DOUBLE
;
540 enumerator
->bind
[i
].buffer
= (char*)&enumerator
->val
.p_double
[i
];
544 DBG1("invalid result data type supplied");
545 mysql_enumerator_destroy(enumerator
);
550 if (mysql_stmt_bind_result(stmt
, enumerator
->bind
))
552 DBG1("binding MySQL result failed: %s", mysql_stmt_error(stmt
));
553 mysql_enumerator_destroy(enumerator
);
562 return (enumerator_t
*)enumerator
;
566 * Implementation of database_t.execute.
568 static int execute(private_mysql_database_t
*this, int *rowid
, char *sql
, ...)
575 conn
= conn_get(this);
581 stmt
= run(conn
->mysql
, sql
, &args
);
586 *rowid
= mysql_stmt_insert_id(stmt
);
588 affected
= mysql_stmt_affected_rows(stmt
);
589 mysql_stmt_close(stmt
);
597 * Implementation of database_t.destroy
599 static void destroy(private_mysql_database_t
*this)
601 this->pool
->destroy_function(this->pool
, (void*)conn_destroy
);
602 this->mutex
->destroy(this->mutex
);
604 free(this->username
);
605 free(this->password
);
606 free(this->database
);
610 static bool parse_uri(private_mysql_database_t
*this, char *uri
)
612 char *username
, *password
, *host
, *port
= "0", *database
, *pos
;
615 * parse mysql://username:pass@host:port/database uri
617 username
= strdupa(uri
+ 8);
618 pos
= strchr(username
, ':');
623 pos
= strrchr(password
, '@');
628 pos
= strrchr(host
, ':');
633 pos
= strchr(port
, '/');
637 pos
= strchr(host
, '/');
644 this->host
= strdup(host
);
645 this->username
= strdup(username
);
646 this->password
= strdup(password
);
647 this->database
= strdup(database
);
648 this->port
= atoi(port
);
653 DBG1("parsing MySQL database uri '%s' failed", uri
);
661 mysql_database_t
*mysql_database_create(char *uri
)
664 private_mysql_database_t
*this;
666 if (!strneq(uri
, "mysql://", 8))
671 this = malloc_thing(private_mysql_database_t
);
673 this->public.db
.query
= (enumerator_t
* (*)(database_t
*this, char *sql
, ...))query
;
674 this->public.db
.execute
= (int (*)(database_t
*this, int *rowid
, char *sql
, ...))execute
;
675 this->public.db
.destroy
= (void(*)(database_t
*))destroy
;
677 if (!parse_uri(this, uri
))
682 this->mutex
= mutex_create(MUTEX_DEFAULT
);
683 this->pool
= linked_list_create();
685 /* check connectivity */
686 conn
= conn_get(this);
693 return &this->public;