mysql: Implement transaction handling

This commit is contained in:
Tobias Brunner
2013-10-11 15:16:04 +02:00
parent 947b76cda8
commit f3cb889c9b
@@ -50,6 +50,11 @@ struct private_mysql_database_t {
*/ */
linked_list_t *pool; linked_list_t *pool;
/**
* thread-specific transaction, as transaction_t
*/
thread_value_t *transaction;
/** /**
* mutex to lock pool * mutex to lock pool
*/ */
@@ -99,6 +104,28 @@ struct conn_t {
bool in_use; bool in_use;
}; };
/**
* database transaction
*/
typedef struct {
/**
* Reference to the specific connection we started the transaction on
*/
conn_t *conn;
/**
* Refcounter if transaction() is called multiple times
*/
refcount_t refs;
/**
* TRUE if transaction was rolled back
*/
bool rollback;
} transaction_t;
/** /**
* Release a mysql connection * Release a mysql connection
*/ */
@@ -109,6 +136,16 @@ static void conn_release(private_mysql_database_t *this, conn_t *conn)
this->mutex->unlock(this->mutex); this->mutex->unlock(this->mutex);
} }
/**
* Destroy a transaction and release the connection
*/
static void transaction_destroy(private_mysql_database_t *this,
transaction_t *trans)
{
conn_release(this, trans->conn);
free(trans);
}
/** /**
* thread specific initialization flag * thread specific initialization flag
*/ */
@@ -161,13 +198,24 @@ static void conn_destroy(conn_t *this)
/** /**
* Acquire/Reuse a mysql connection * Acquire/Reuse a mysql connection
*/ */
static conn_t *conn_get(private_mysql_database_t *this) static conn_t *conn_get(private_mysql_database_t *this, transaction_t **trans)
{ {
conn_t *current, *found = NULL; conn_t *current, *found = NULL;
enumerator_t *enumerator; enumerator_t *enumerator;
transaction_t *transaction;
thread_initialize(); thread_initialize();
transaction = this->transaction->get(this->transaction);
if (transaction)
{
if (trans)
{
*trans = transaction;
}
return transaction->conn;
}
while (TRUE) while (TRUE)
{ {
this->mutex->lock(this->mutex); this->mutex->lock(this->mutex);
@@ -490,7 +538,7 @@ METHOD(database_t, query, enumerator_t*,
mysql_enumerator_t *enumerator = NULL; mysql_enumerator_t *enumerator = NULL;
conn_t *conn; conn_t *conn;
conn = conn_get(this); conn = conn_get(this, NULL);
if (!conn) if (!conn)
{ {
return NULL; return NULL;
@@ -582,7 +630,7 @@ METHOD(database_t, execute, int,
conn_t *conn; conn_t *conn;
int affected = -1; int affected = -1;
conn = conn_get(this); conn = conn_get(this, NULL);
if (!conn) if (!conn)
{ {
return -1; return -1;
@@ -606,19 +654,81 @@ METHOD(database_t, execute, int,
METHOD(database_t, transaction, bool, METHOD(database_t, transaction, bool,
private_mysql_database_t *this) private_mysql_database_t *this)
{ {
return FALSE; transaction_t *trans = NULL;
conn_t *conn;
conn = conn_get(this, &trans);
if (!conn)
{
return FALSE;
}
else if (trans)
{
ref_get(&trans->refs);
return TRUE;
}
/* these statements are not supported in prepared statements that are used
* by the execute() method */
if (mysql_query(conn->mysql, "START TRANSACTION") != 0)
{
DBG1(DBG_LIB, "starting transaction failed: %s",
mysql_error(conn->mysql));
conn_release(this, conn);
return FALSE;
}
INIT(trans,
.conn = conn,
.refs = 1,
);
this->transaction->set(this->transaction, trans);
return TRUE;
}
/**
* Finalize a transaction depending on the reference count and if it should be
* rolled back.
*/
static bool finalize_transaction(private_mysql_database_t *this,
bool rollback)
{
transaction_t *trans;
char *command = "COMMIT";
bool success;
trans = this->transaction->get(this->transaction);
if (!trans)
{
DBG1(DBG_LIB, "no database transaction found");
return FALSE;
}
/* set flag, can't be unset */
trans->rollback |= rollback;
if (ref_put(&trans->refs))
{
if (trans->rollback)
{
command = "ROLLBACK";
}
success = mysql_query(trans->conn->mysql, command) == 0;
this->transaction->set(this->transaction, NULL);
transaction_destroy(this, trans);
return success;
}
return TRUE;
} }
METHOD(database_t, commit, bool, METHOD(database_t, commit, bool,
private_mysql_database_t *this) private_mysql_database_t *this)
{ {
return FALSE; return finalize_transaction(this, FALSE);
} }
METHOD(database_t, rollback, bool, METHOD(database_t, rollback, bool,
private_mysql_database_t *this) private_mysql_database_t *this)
{ {
return FALSE; return finalize_transaction(this, TRUE);
} }
METHOD(database_t, get_driver,db_driver_t, METHOD(database_t, get_driver,db_driver_t,
@@ -630,6 +740,7 @@ METHOD(database_t, get_driver,db_driver_t,
METHOD(database_t, destroy, void, METHOD(database_t, destroy, void,
private_mysql_database_t *this) private_mysql_database_t *this)
{ {
this->transaction->destroy(this->transaction);
this->pool->destroy_function(this->pool, (void*)conn_destroy); this->pool->destroy_function(this->pool, (void*)conn_destroy);
this->mutex->destroy(this->mutex); this->mutex->destroy(this->mutex);
free(this->host); free(this->host);
@@ -721,9 +832,10 @@ mysql_database_t *mysql_database_create(char *uri)
} }
this->mutex = mutex_create(MUTEX_TYPE_DEFAULT); this->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
this->pool = linked_list_create(); this->pool = linked_list_create();
this->transaction = thread_value_create(NULL);
/* check connectivity */ /* check connectivity */
conn = conn_get(this); conn = conn_get(this, NULL);
if (!conn) if (!conn)
{ {
destroy(this); destroy(this);