sqlite: Implement transaction handling
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
#include <library.h>
|
#include <library.h>
|
||||||
#include <utils/debug.h>
|
#include <utils/debug.h>
|
||||||
#include <threading/mutex.h>
|
#include <threading/mutex.h>
|
||||||
|
#include <threading/thread_value.h>
|
||||||
|
|
||||||
typedef struct private_sqlite_database_t private_sqlite_database_t;
|
typedef struct private_sqlite_database_t private_sqlite_database_t;
|
||||||
|
|
||||||
@@ -40,11 +41,33 @@ struct private_sqlite_database_t {
|
|||||||
sqlite3 *db;
|
sqlite3 *db;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mutex used to lock execute()
|
* thread-specific transaction, as transaction_t
|
||||||
|
*/
|
||||||
|
thread_value_t *transaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mutex used to lock execute(), if necessary
|
||||||
*/
|
*/
|
||||||
mutex_t *mutex;
|
mutex_t *mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Database transaction
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refcounter if transaction() is called multiple times
|
||||||
|
*/
|
||||||
|
refcount_t refs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TRUE if transaction was rolled back
|
||||||
|
*/
|
||||||
|
bool rollback;
|
||||||
|
|
||||||
|
} transaction_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and run a sqlite stmt using a sql string and args
|
* Create and run a sqlite stmt using a sql string and args
|
||||||
*/
|
*/
|
||||||
@@ -284,19 +307,72 @@ METHOD(database_t, execute, int,
|
|||||||
METHOD(database_t, transaction, bool,
|
METHOD(database_t, transaction, bool,
|
||||||
private_sqlite_database_t *this)
|
private_sqlite_database_t *this)
|
||||||
{
|
{
|
||||||
return FALSE;
|
transaction_t *trans;
|
||||||
|
|
||||||
|
trans = this->transaction->get(this->transaction);
|
||||||
|
if (trans)
|
||||||
|
{
|
||||||
|
ref_get(&trans->refs);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (execute(this, NULL, "BEGIN EXCLUSIVE TRANSACTION") == -1)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
INIT(trans,
|
||||||
|
.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_sqlite_database_t *this,
|
||||||
|
bool rollback)
|
||||||
|
{
|
||||||
|
transaction_t *trans;
|
||||||
|
char *command = "COMMIT TRANSACTION";
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
trans = this->transaction->get(this->transaction);
|
||||||
|
if (!trans)
|
||||||
|
{
|
||||||
|
DBG1(DBG_LIB, "no database transaction found");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref_put(&trans->refs))
|
||||||
|
{
|
||||||
|
if (trans->rollback)
|
||||||
|
{
|
||||||
|
command = "ROLLBACK TRANSACTION";
|
||||||
|
}
|
||||||
|
success = execute(this, NULL, command) != -1;
|
||||||
|
|
||||||
|
this->transaction->set(this->transaction, NULL);
|
||||||
|
free(trans);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ /* set flag, can't be unset */
|
||||||
|
trans->rollback |= rollback;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
METHOD(database_t, commit, bool,
|
METHOD(database_t, commit, bool,
|
||||||
private_sqlite_database_t *this)
|
private_sqlite_database_t *this)
|
||||||
{
|
{
|
||||||
return FALSE;
|
return finalize_transaction(this, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
METHOD(database_t, rollback, bool,
|
METHOD(database_t, rollback, bool,
|
||||||
private_sqlite_database_t *this)
|
private_sqlite_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,
|
||||||
@@ -323,6 +399,7 @@ METHOD(database_t, destroy, void,
|
|||||||
{
|
{
|
||||||
DBG1(DBG_LIB, "sqlite close failed because database is busy");
|
DBG1(DBG_LIB, "sqlite close failed because database is busy");
|
||||||
}
|
}
|
||||||
|
this->transaction->destroy(this->transaction);
|
||||||
this->mutex->destroy(this->mutex);
|
this->mutex->destroy(this->mutex);
|
||||||
free(this);
|
free(this);
|
||||||
}
|
}
|
||||||
@@ -357,13 +434,14 @@ sqlite_database_t *sqlite_database_create(char *uri)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
.mutex = mutex_create(MUTEX_TYPE_RECURSIVE),
|
.mutex = mutex_create(MUTEX_TYPE_RECURSIVE),
|
||||||
|
.transaction = thread_value_create(NULL),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sqlite3_open(file, &this->db) != SQLITE_OK)
|
if (sqlite3_open(file, &this->db) != SQLITE_OK)
|
||||||
{
|
{
|
||||||
DBG1(DBG_LIB, "opening SQLite database '%s' failed: %s",
|
DBG1(DBG_LIB, "opening SQLite database '%s' failed: %s",
|
||||||
file, sqlite3_errmsg(this->db));
|
file, sqlite3_errmsg(this->db));
|
||||||
_destroy(this);
|
destroy(this);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,4 +449,3 @@ sqlite_database_t *sqlite_database_create(char *uri)
|
|||||||
|
|
||||||
return &this->public;
|
return &this->public;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user