mysql: Implement transaction handling
authorTobias Brunner <tobias@strongswan.org>
Thu, 5 Sep 2013 14:46:24 +0000 (16:46 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 11 Oct 2013 13:16:04 +0000 (15:16 +0200)
src/libstrongswan/plugins/mysql/mysql_database.c

index 0757c2a..7a612ff 100644 (file)
@@ -51,6 +51,11 @@ struct private_mysql_database_t {
        linked_list_t *pool;
 
        /**
+        * thread-specific transaction, as transaction_t
+        */
+       thread_value_t *transaction;
+
+       /**
         * mutex to lock pool
         */
        mutex_t *mutex;
@@ -100,6 +105,28 @@ struct conn_t {
 };
 
 /**
+ * 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
  */
 static void conn_release(private_mysql_database_t *this, conn_t *conn)
@@ -110,6 +137,16 @@ static void conn_release(private_mysql_database_t *this, conn_t *conn)
 }
 
 /**
+ * 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_value_t *initialized;
@@ -161,13 +198,24 @@ static void conn_destroy(conn_t *this)
 /**
  * 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;
        enumerator_t *enumerator;
+       transaction_t *transaction;
 
        thread_initialize();
 
+       transaction = this->transaction->get(this->transaction);
+       if (transaction)
+       {
+               if (trans)
+               {
+                       *trans = transaction;
+               }
+               return transaction->conn;
+       }
+
        while (TRUE)
        {
                this->mutex->lock(this->mutex);
@@ -490,7 +538,7 @@ METHOD(database_t, query, enumerator_t*,
        mysql_enumerator_t *enumerator = NULL;
        conn_t *conn;
 
-       conn = conn_get(this);
+       conn = conn_get(this, NULL);
        if (!conn)
        {
                return NULL;
@@ -582,7 +630,7 @@ METHOD(database_t, execute, int,
        conn_t *conn;
        int affected = -1;
 
-       conn = conn_get(this);
+       conn = conn_get(this, NULL);
        if (!conn)
        {
                return -1;
@@ -606,19 +654,81 @@ METHOD(database_t, execute, int,
 METHOD(database_t, transaction, bool,
        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,
        private_mysql_database_t *this)
 {
-       return FALSE;
+       return finalize_transaction(this, FALSE);
 }
 
 METHOD(database_t, rollback, bool,
        private_mysql_database_t *this)
 {
-       return FALSE;
+       return finalize_transaction(this, TRUE);
 }
 
 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,
        private_mysql_database_t *this)
 {
+       this->transaction->destroy(this->transaction);
        this->pool->destroy_function(this->pool, (void*)conn_destroy);
        this->mutex->destroy(this->mutex);
        free(this->host);
@@ -721,9 +832,10 @@ mysql_database_t *mysql_database_create(char *uri)
        }
        this->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
        this->pool = linked_list_create();
+       this->transaction = thread_value_create(NULL);
 
        /* check connectivity */
-       conn = conn_get(this);
+       conn = conn_get(this, NULL);
        if (!conn)
        {
                destroy(this);