466 lines
9.4 KiB
C
466 lines
9.4 KiB
C
/*
|
|
* Copyright (C) 2013 Tobias Brunner
|
|
* Copyright (C) 2007 Martin Willi
|
|
* HSR Hochschule fuer Technik Rapperswil
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include "sqlite_database.h"
|
|
|
|
#include <sqlite3.h>
|
|
#include <unistd.h>
|
|
#include <library.h>
|
|
#include <utils/debug.h>
|
|
#include <threading/mutex.h>
|
|
#include <threading/thread_value.h>
|
|
|
|
typedef struct private_sqlite_database_t private_sqlite_database_t;
|
|
|
|
/**
|
|
* private data of sqlite_database
|
|
*/
|
|
struct private_sqlite_database_t {
|
|
|
|
/**
|
|
* public functions
|
|
*/
|
|
sqlite_database_t public;
|
|
|
|
/**
|
|
* sqlite database connection
|
|
*/
|
|
sqlite3 *db;
|
|
|
|
/**
|
|
* thread-specific transaction, as transaction_t
|
|
*/
|
|
thread_value_t *transaction;
|
|
|
|
/**
|
|
* mutex used to lock execute(), if necessary
|
|
*/
|
|
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;
|
|
|
|
/**
|
|
* Check if the SQLite library is thread safe
|
|
*/
|
|
static bool is_threadsafe()
|
|
{
|
|
#if SQLITE_VERSION_NUMBER >= 3005000
|
|
return sqlite3_threadsafe() > 0;
|
|
#endif
|
|
/* sqlite connections prior to 3.5 may be used by a single thread only */
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Create and run a sqlite stmt using a sql string and args
|
|
*/
|
|
static sqlite3_stmt* run(private_sqlite_database_t *this, char *sql,
|
|
va_list *args)
|
|
{
|
|
sqlite3_stmt *stmt = NULL;
|
|
int params, i, res = SQLITE_OK;
|
|
|
|
#ifdef HAVE_SQLITE3_PREPARE_V2
|
|
if (sqlite3_prepare_v2(this->db, sql, -1, &stmt, NULL) == SQLITE_OK)
|
|
#else
|
|
if (sqlite3_prepare(this->db, sql, -1, &stmt, NULL) == SQLITE_OK)
|
|
#endif
|
|
{
|
|
params = sqlite3_bind_parameter_count(stmt);
|
|
for (i = 1; i <= params; i++)
|
|
{
|
|
switch (va_arg(*args, db_type_t))
|
|
{
|
|
case DB_INT:
|
|
{
|
|
res = sqlite3_bind_int(stmt, i, va_arg(*args, int));
|
|
break;
|
|
}
|
|
case DB_UINT:
|
|
{
|
|
res = sqlite3_bind_int64(stmt, i, va_arg(*args, u_int));
|
|
break;
|
|
}
|
|
case DB_TEXT:
|
|
{
|
|
const char *text = va_arg(*args, const char*);
|
|
res = sqlite3_bind_text(stmt, i, text, -1,
|
|
SQLITE_TRANSIENT);
|
|
break;
|
|
}
|
|
case DB_BLOB:
|
|
{
|
|
chunk_t c = va_arg(*args, chunk_t);
|
|
res = sqlite3_bind_blob(stmt, i, c.ptr, c.len,
|
|
SQLITE_TRANSIENT);
|
|
break;
|
|
}
|
|
case DB_DOUBLE:
|
|
{
|
|
res = sqlite3_bind_double(stmt, i, va_arg(*args, double));
|
|
break;
|
|
}
|
|
case DB_NULL:
|
|
{
|
|
res = sqlite3_bind_null(stmt, i);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
res = SQLITE_MISUSE;
|
|
break;
|
|
}
|
|
}
|
|
if (res != SQLITE_OK)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_LIB, "preparing sqlite statement failed: %s",
|
|
sqlite3_errmsg(this->db));
|
|
}
|
|
if (res != SQLITE_OK)
|
|
{
|
|
DBG1(DBG_LIB, "binding sqlite statement failed: %s",
|
|
sqlite3_errmsg(this->db));
|
|
sqlite3_finalize(stmt);
|
|
return NULL;
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
typedef struct {
|
|
/** implements enumerator_t */
|
|
enumerator_t public;
|
|
/** associated sqlite statement */
|
|
sqlite3_stmt *stmt;
|
|
/** number of result columns */
|
|
int count;
|
|
/** column types */
|
|
db_type_t *columns;
|
|
/** back reference to parent */
|
|
private_sqlite_database_t *database;
|
|
} sqlite_enumerator_t;
|
|
|
|
METHOD(enumerator_t, sqlite_enumerator_destroy, void,
|
|
sqlite_enumerator_t *this)
|
|
{
|
|
sqlite3_finalize(this->stmt);
|
|
if (!is_threadsafe())
|
|
{
|
|
this->database->mutex->unlock(this->database->mutex);
|
|
}
|
|
free(this->columns);
|
|
free(this);
|
|
}
|
|
|
|
METHOD(enumerator_t, sqlite_enumerator_enumerate, bool,
|
|
sqlite_enumerator_t *this, va_list args)
|
|
{
|
|
int i;
|
|
|
|
switch (sqlite3_step(this->stmt))
|
|
{
|
|
case SQLITE_ROW:
|
|
break;
|
|
default:
|
|
DBG1(DBG_LIB, "stepping sqlite statement failed: %s",
|
|
sqlite3_errmsg(this->database->db));
|
|
/* fall */
|
|
case SQLITE_DONE:
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < this->count; i++)
|
|
{
|
|
switch (this->columns[i])
|
|
{
|
|
case DB_INT:
|
|
{
|
|
int *value = va_arg(args, int*);
|
|
*value = sqlite3_column_int(this->stmt, i);
|
|
break;
|
|
}
|
|
case DB_UINT:
|
|
{
|
|
u_int *value = va_arg(args, u_int*);
|
|
*value = (u_int)sqlite3_column_int64(this->stmt, i);
|
|
break;
|
|
}
|
|
case DB_TEXT:
|
|
{
|
|
const unsigned char **value = va_arg(args, const unsigned char**);
|
|
*value = sqlite3_column_text(this->stmt, i);
|
|
break;
|
|
}
|
|
case DB_BLOB:
|
|
{
|
|
chunk_t *chunk = va_arg(args, chunk_t*);
|
|
chunk->len = sqlite3_column_bytes(this->stmt, i);
|
|
chunk->ptr = (u_char*)sqlite3_column_blob(this->stmt, i);
|
|
break;
|
|
}
|
|
case DB_DOUBLE:
|
|
{
|
|
double *value = va_arg(args, double*);
|
|
*value = sqlite3_column_double(this->stmt, i);
|
|
break;
|
|
}
|
|
default:
|
|
DBG1(DBG_LIB, "invalid result type supplied");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
METHOD(database_t, query, enumerator_t*,
|
|
private_sqlite_database_t *this, char *sql, ...)
|
|
{
|
|
sqlite3_stmt *stmt;
|
|
va_list args;
|
|
sqlite_enumerator_t *enumerator = NULL;
|
|
int i;
|
|
|
|
if (!is_threadsafe())
|
|
{
|
|
this->mutex->lock(this->mutex);
|
|
}
|
|
|
|
va_start(args, sql);
|
|
stmt = run(this, sql, &args);
|
|
if (stmt)
|
|
{
|
|
INIT(enumerator,
|
|
.public = {
|
|
.enumerate = enumerator_enumerate_default,
|
|
.venumerate = _sqlite_enumerator_enumerate,
|
|
.destroy = _sqlite_enumerator_destroy,
|
|
},
|
|
.stmt = stmt,
|
|
.count = sqlite3_column_count(stmt),
|
|
.database = this,
|
|
);
|
|
enumerator->columns = malloc(sizeof(db_type_t) * enumerator->count);
|
|
for (i = 0; i < enumerator->count; i++)
|
|
{
|
|
enumerator->columns[i] = va_arg(args, db_type_t);
|
|
}
|
|
}
|
|
va_end(args);
|
|
return (enumerator_t*)enumerator;
|
|
}
|
|
|
|
METHOD(database_t, execute, int,
|
|
private_sqlite_database_t *this, int *rowid, char *sql, ...)
|
|
{
|
|
sqlite3_stmt *stmt;
|
|
int affected = -1;
|
|
va_list args;
|
|
|
|
/* we need a lock to get our rowid/changes correctly */
|
|
this->mutex->lock(this->mutex);
|
|
va_start(args, sql);
|
|
stmt = run(this, sql, &args);
|
|
va_end(args);
|
|
if (stmt)
|
|
{
|
|
if (sqlite3_step(stmt) == SQLITE_DONE)
|
|
{
|
|
if (rowid)
|
|
{
|
|
*rowid = sqlite3_last_insert_rowid(this->db);
|
|
}
|
|
affected = sqlite3_changes(this->db);
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_LIB, "sqlite execute failed: %s",
|
|
sqlite3_errmsg(this->db));
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
this->mutex->unlock(this->mutex);
|
|
return affected;
|
|
}
|
|
|
|
METHOD(database_t, transaction, bool,
|
|
private_sqlite_database_t *this, bool serializable)
|
|
{
|
|
transaction_t *trans;
|
|
char *cmd = serializable ? "BEGIN EXCLUSIVE TRANSACTION"
|
|
: "BEGIN TRANSACTION";
|
|
|
|
trans = this->transaction->get(this->transaction);
|
|
if (trans)
|
|
{
|
|
ref_get(&trans->refs);
|
|
return TRUE;
|
|
}
|
|
if (execute(this, NULL, cmd) == -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,
|
|
private_sqlite_database_t *this)
|
|
{
|
|
return finalize_transaction(this, FALSE);
|
|
}
|
|
|
|
METHOD(database_t, rollback, bool,
|
|
private_sqlite_database_t *this)
|
|
{
|
|
return finalize_transaction(this, TRUE);
|
|
}
|
|
|
|
METHOD(database_t, get_driver, db_driver_t,
|
|
private_sqlite_database_t *this)
|
|
{
|
|
return DB_SQLITE;
|
|
}
|
|
|
|
/**
|
|
* Busy handler implementation
|
|
*/
|
|
static int busy_handler(private_sqlite_database_t *this, int count)
|
|
{
|
|
/* add a backoff time, quadratically increasing with every try */
|
|
usleep(count * count * 1000);
|
|
/* always retry */
|
|
return 1;
|
|
}
|
|
|
|
METHOD(database_t, destroy, void,
|
|
private_sqlite_database_t *this)
|
|
{
|
|
if (sqlite3_close(this->db) == SQLITE_BUSY)
|
|
{
|
|
DBG1(DBG_LIB, "sqlite close failed because database is busy");
|
|
}
|
|
this->transaction->destroy(this->transaction);
|
|
this->mutex->destroy(this->mutex);
|
|
free(this);
|
|
}
|
|
|
|
/*
|
|
* see header file
|
|
*/
|
|
sqlite_database_t *sqlite_database_create(char *uri)
|
|
{
|
|
char *file;
|
|
private_sqlite_database_t *this;
|
|
|
|
/**
|
|
* parse sqlite:///path/to/file.db uri
|
|
*/
|
|
if (!strpfx(uri, "sqlite://"))
|
|
{
|
|
return NULL;
|
|
}
|
|
file = uri + 9;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.db = {
|
|
.query = _query,
|
|
.execute = _execute,
|
|
.transaction = _transaction,
|
|
.commit = _commit_,
|
|
.rollback = _rollback,
|
|
.get_driver = _get_driver,
|
|
.destroy = _destroy,
|
|
},
|
|
},
|
|
.mutex = mutex_create(MUTEX_TYPE_RECURSIVE),
|
|
.transaction = thread_value_create(NULL),
|
|
);
|
|
|
|
if (sqlite3_open(file, &this->db) != SQLITE_OK)
|
|
{
|
|
DBG1(DBG_LIB, "opening SQLite database '%s' failed: %s",
|
|
file, sqlite3_errmsg(this->db));
|
|
destroy(this);
|
|
return NULL;
|
|
}
|
|
|
|
sqlite3_busy_handler(this->db, (void*)busy_handler, this);
|
|
|
|
return &this->public;
|
|
}
|