| /* |
| * 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; |
| } |