aboutsummaryrefslogtreecommitdiffstats
path: root/SoftHSMv2/src/lib/object_store/DB.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'SoftHSMv2/src/lib/object_store/DB.cpp')
-rw-r--r--SoftHSMv2/src/lib/object_store/DB.cpp926
1 files changed, 926 insertions, 0 deletions
diff --git a/SoftHSMv2/src/lib/object_store/DB.cpp b/SoftHSMv2/src/lib/object_store/DB.cpp
new file mode 100644
index 0000000..d82d7cb
--- /dev/null
+++ b/SoftHSMv2/src/lib/object_store/DB.cpp
@@ -0,0 +1,926 @@
+/*
+ * Copyright (c) 2013 SURFnet bv
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*****************************************************************************
+ DB.cpp
+
+ Specifies classes to access the Token Database
+ *****************************************************************************/
+#define HAVE_SQL_TRACE 0
+
+#include "config.h"
+#include "OSPathSep.h"
+#include "log.h"
+#include <cstdlib>
+#include <cstdio>
+#include <iostream>
+#include <vector>
+#include <sqlite3.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "DB.h"
+
+#if HAVE_SQL_TRACE
+static void xTrace(void*connectionLabel,const char*zSql)
+{
+ const char *label = static_cast<const char *>(connectionLabel);
+ if (label)
+ std::cout << std::endl << label << ": " << zSql ;
+ else
+ std::cout << std::endl << zSql ;
+}
+#endif
+
+static int static_log_err(const char *format, va_list ap)
+{
+ std::vector<char> logMessage;
+ logMessage.resize(4096);
+ vsnprintf(&logMessage[0], 4096, format, ap);
+ ERROR_MSG(&logMessage[0]);
+ return 0;
+}
+
+static DB::LogErrorHandler static_LogErrorhandler = static_log_err;
+
+void DB::logError(const std::string &format, ...)
+{
+ if (!static_LogErrorhandler)
+ return;
+ va_list args;
+ va_start(args, format);
+ static_LogErrorhandler(format.c_str(),args);
+ va_end(args);
+}
+
+DB::LogErrorHandler DB::setLogErrorHandler(DB::LogErrorHandler handler)
+{
+ LogErrorHandler temp = static_LogErrorhandler;
+ static_LogErrorhandler = handler;
+ return temp;
+}
+
+void DB::resetLogErrorHandler()
+{
+ static_LogErrorhandler = static_log_err;
+}
+
+static void reportErrorDB(sqlite3 *db)
+{
+ if (!db) {
+ DB::logError("sqlite3 pointer is NULL");
+ return;
+ }
+
+ int rv = sqlite3_errcode(db);
+ if (rv == SQLITE_OK || rv == SQLITE_ROW || rv == SQLITE_DONE)
+ return;
+
+#ifdef HAVE_SILENT_BUSY_AND_LOCKED_ERRORS
+ // Either the database file is locked (SQLITE_BUSY)
+ // or a table in the database is locked (SQLITE_LOCKED)
+ if (rv == SQLITE_BUSY || rv == SQLITE_LOCKED)
+ return;
+#endif
+
+ DB::logError("SQLITE3: %s (%d)", sqlite3_errmsg(db), rv);
+}
+
+static void reportError(sqlite3_stmt *stmt)
+{
+ if (!stmt) {
+ DB::logError("sqlite3_stmt pointer is NULL");
+ return;
+ }
+ reportErrorDB(sqlite3_db_handle(stmt));
+}
+
+static time_t sqlite3_gmtime(struct tm *tm)
+{
+ // We don't want to depend on timegm() so we use a workaround via the
+ // gmtime_r() function to determine this.
+ // As input we use a moment in time just 10 days after the POSIX epoch.
+ // The POSIX epoch is defined as the moment in time at midnight Coordinated
+ // Universal Time (UTC) of Thursday, January 1, 1970. A time_t value is
+ // the number of seconds elapsed since epoch.
+ struct tm ref_tm = {0,0,0,0,0,0,0,0,0,0,0};
+ ref_tm.tm_year = 70; // Years since 1900;
+ ref_tm.tm_mday = 10; // 10th
+
+ // We need the time difference between local time and UTC time.
+ // mktime will interpret the UTC time stored in tm as local time
+ // so let's assume we are in a time zone 1 hour ahead of UTC (UTC+1)
+ // then a time of 13:00 interpreted as local time needs 1 hour subtracted
+ // to arrive at UTC time. This UTC time is then converted to a POSIX
+ // time_t value.
+ time_t posix_time = mktime(&ref_tm);
+
+ // Use gmtime_r to convert the POSIX time back to a tm struct.
+ // No time adjustment is done this time because POSIX time is
+ // defined in terms of UTC.
+ gmtime_r(&posix_time, &ref_tm);
+ if (ref_tm.tm_isdst != 0) {
+ DB::logError("expected gmtime_r to return zero in tm_isdst member of tm struct");
+ return ((time_t)-1);
+ }
+
+ // Using mktime again to convert tm. This will again subtract 1 hour from
+ // the time (under the assumption that we are 1 hour ahead of UTC).
+ // We can now use this to determine how much local time differred
+ // from UTC time on january the 10th 1970
+ long diff_time = posix_time - mktime(&ref_tm);
+
+ // We explicitly set tm_isdst to zero to prevent errors
+ // when the time we are trying to convert is occuring at
+ // the moment when a dst change is in progress.
+ // We require mktime to respect our setting of tm_isdst
+ // indicating that no dst is in effect.
+ tm->tm_isdst = 0; // Tell (and force) mktime not to take dst into account.
+
+ // We now can calculate and return a correct POSIX time.
+ // So, although mktime() interprets gm_tm as local time adjusts for
+ // the time difference between local time and UTC time. We then undo
+ // that adjustment by adding diff_time.
+ return mktime(tm) + diff_time;
+}
+
+/**************************
+ * Handle
+ **************************/
+
+class DB::Handle {
+public:
+ int _refcount;
+ sqlite3_stmt *_stmt;
+ Handle(sqlite3_stmt *stmt)
+ : _refcount(1), _stmt(stmt)
+ {
+ }
+ ~Handle()
+ {
+ if (_stmt)
+ {
+ sqlite3_finalize(_stmt);
+ _stmt = NULL;
+ }
+ }
+
+ Handle *retain()
+ {
+ if (_refcount)
+ {
+ _refcount++;
+ return this;
+ }
+ return NULL;
+ }
+ void release()
+ {
+ if (_refcount)
+ {
+ _refcount--;
+ if (_refcount)
+ return;
+ delete this;
+ }
+ }
+ bool reset()
+ {
+ if (sqlite3_reset(_stmt) != SQLITE_OK)
+ {
+ reportError(_stmt);
+ return false;
+ }
+ return true;
+ }
+ Statement::ReturnCode step()
+ {
+ int rv = sqlite3_step(_stmt);
+ if (rv != SQLITE_ROW && rv != SQLITE_DONE)
+ {
+ reportError(_stmt);
+ return Statement::ReturnCodeError;
+ }
+
+ if (rv==SQLITE_ROW)
+ {
+ return Statement::ReturnCodeRow;
+ }
+
+ return Statement::ReturnCodeDone;
+ }
+private:
+ // disable evil constructors
+ Handle(const Handle &);
+ Handle & operator=(const Handle &);
+};
+
+DB::Statement::Statement()
+ : _handle(NULL)
+{
+}
+
+DB::Statement::Statement(sqlite3_stmt *statement)
+ : _handle(new Handle(statement))
+{
+}
+
+DB::Statement::Statement(const DB::Statement &statement)
+ : _handle(statement._handle)
+{
+ if (_handle)
+ _handle = _handle->retain();
+}
+
+DB::Statement &DB::Statement::operator=(const DB::Statement &statement)
+{
+ if (this != &statement)
+ {
+ Handle *tmp = NULL;
+ if (statement._handle) {
+ tmp = statement._handle->retain();
+ }
+ if (_handle) {
+ _handle->release();
+ }
+ _handle = tmp;
+ }
+ return *this;
+}
+
+DB::Statement::~Statement()
+{
+ if (_handle) {
+ _handle->release();
+ _handle = NULL;
+ }
+}
+
+bool DB::Statement::isValid()
+{
+ return _handle != NULL && _handle->_stmt != NULL;
+}
+
+int DB::Statement::refcount()
+{
+ return _handle ? _handle->_refcount : 0;
+}
+
+
+bool DB::Statement::reset()
+{
+ if (!isValid()) {
+ DB::logError("Statement::reset: statement is not valid");
+ return false;
+ }
+ return _handle->reset();
+}
+
+DB::Statement::ReturnCode DB::Statement::step()
+{
+ if (!isValid()) {
+ DB::logError("Statement::step: statement is not valid");
+ return ReturnCodeError;
+ }
+ return _handle->step();
+}
+
+DB::Handle *DB::Statement::handle() const
+{
+ return _handle;
+}
+
+/**************************
+ * Bindings
+ **************************/
+
+DB::Bindings::Bindings()
+ : Statement()
+{
+}
+
+DB::Bindings::Bindings(const Statement &statement)
+ : Statement(statement)
+{
+}
+
+bool DB::Bindings::clear()
+{
+ if (!isValid()) {
+ DB::logError("Bindings::clear: statement is not valid");
+ return false;
+ }
+ if (sqlite3_clear_bindings(_handle->_stmt) != SQLITE_OK) {
+ reportError(_handle->_stmt);
+ return false;
+ }
+ return true;
+}
+
+bool DB::Bindings::bindBlob(int index, const void *value, int n, void(*destruct)(void*))
+{
+ if (!isValid()) {
+ DB::logError("Bindings::bindBlob: statement is not valid");
+ return false;
+ }
+ if (sqlite3_bind_blob(_handle->_stmt, index, value, n, destruct) != SQLITE_OK) {
+ reportError(_handle->_stmt);
+ return false;
+ }
+ return true;
+}
+
+bool DB::Bindings::bindDouble(int index, double value)
+{
+ if (!isValid()) {
+ DB::logError("Bindings::bindDouble: statement is not valid");
+ return false;
+ }
+ if (sqlite3_bind_double(_handle->_stmt, index, value) != SQLITE_OK) {
+ reportError(_handle->_stmt);
+ return false;
+ }
+ return true;
+}
+
+bool DB::Bindings::bindInt(int index, int value)
+{
+ if (!isValid()) {
+ DB::logError("Bindings::bindInt: statement is not valid");
+ return false;
+ }
+ if (sqlite3_bind_int(_handle->_stmt, index, value) != SQLITE_OK) {
+ reportError(_handle->_stmt);
+ return false;
+ }
+ return true;
+}
+
+bool DB::Bindings::bindInt64(int index, long long value)
+{
+ if (!isValid()) {
+ DB::logError("Bindings::bindInt64: statement is not valid");
+ return false;
+ }
+ if (sqlite3_bind_int64(_handle->_stmt, index, value) != SQLITE_OK) {
+ reportError(_handle->_stmt);
+ return false;
+ }
+ return true;
+}
+
+//bool DB::Bindings::bindNull(int index)
+//{
+//#if 0
+// int sqlite3_bind_null(sqlite3_stmt*, int);
+//#endif
+// return false;
+//}
+
+bool DB::Bindings::bindText(int index, const char *value, int n, void (*destruct)(void *))
+{
+ if (!isValid()) {
+ DB::logError("Bindings::bindText: statement is not valid");
+ return false;
+ }
+ if (sqlite3_bind_text(_handle->_stmt, index, value, n, destruct) != SQLITE_OK) {
+ reportError(_handle->_stmt);
+ return false;
+ }
+ return true;
+}
+
+//bool DB::Bindings::bindZeroBlob(int index, int n)
+//{
+//#if 0
+// int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
+//#endif
+// return false;
+//}
+
+/**************************
+ * Result
+ **************************/
+
+DB::Result::Result()
+ : Statement()
+{
+}
+
+DB::Result::Result(const Statement &statement)
+ : Statement(statement)
+{
+}
+
+#if 0
+unsigned int DB::Result::getField(const std::string &fieldname)
+{
+ unsigned int fieldidx = fields[fieldname];
+ if (fieldidx == 0)
+ DB::logError("Result: invalid field name \"%s\"",fieldname.c_str());
+ return fieldidx;
+}
+#endif
+
+bool DB::Result::fieldIsNull(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::fieldIsNull: statement is not valid");
+ return true;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return true;
+ }
+ int column_type = sqlite3_column_type(_handle->_stmt, fieldidx-1);
+ return column_type == SQLITE_NULL;
+}
+
+time_t DB::Result::getDatetime(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getDatetime: statement is not valid");
+ return ((time_t)-1);
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return ((time_t)-1);
+ }
+
+ const unsigned char *value = sqlite3_column_text(_handle->_stmt, fieldidx-1);
+ int valuelen = sqlite3_column_bytes(_handle->_stmt, fieldidx-1);
+
+ unsigned long years,mons,days,hours,mins,secs;
+ struct tm gm_tm = {0,0,0,0,0,0,0,0,0,0,0};
+ gm_tm.tm_isdst = 0; // Tell mktime not to take dst into account.
+ gm_tm.tm_year = 70; // 1970
+ gm_tm.tm_mday = 1; // 1th day of the month
+ const char *p = (const char *)value;
+ char *pnext;
+ bool bdateonly = true;
+ switch (valuelen) {
+ case 19: // 2011-12-31 23:59:59
+ bdateonly = false;
+ // falls through to next case
+ case 10: // 2011-12-31
+ years = strtoul(p,&pnext,10);
+ gm_tm.tm_year = ((int)years)-1900; /* years since 1900 */
+ p = pnext+1;
+ mons = strtoul(p,&pnext,10);
+ gm_tm.tm_mon = ((int)mons)-1; /* months since January [0-11] */
+ p = pnext+1;
+ days = strtoul(p,&pnext,10);
+ gm_tm.tm_mday = ((int)days); /* day of the month [1-31] */
+ p = pnext+1;
+ if (bdateonly)
+ break;
+ // falls through to next case
+ case 8: // 23:59:59
+ hours = strtoul(p,&pnext,10);
+ gm_tm.tm_hour = (int)hours; /* hours since midnight [0-23] */
+ if ((pnext-p) != 2) {
+ DB::logError("Result: invalid hours in time: '%s'",value);
+ return 0;
+ }
+ p = pnext+1;
+ mins = strtoul(p,&pnext,10);
+ gm_tm.tm_min = (int)mins; /* minutes after the hour [0-59] */
+ if ((pnext-p) != 2) {
+ DB::logError("Result: invalid minutes in time: '%s'",value);
+ return 0;
+ }
+ p = pnext+1;
+ secs = strtoul(p,&pnext,10);
+ gm_tm.tm_sec = (int)secs; /* seconds after the minute [0-60] */
+ if ((pnext-p) != 2) {
+ DB::logError("Result: invalid seconds in time: '%s'",value);
+ return 0;
+ }
+ break;
+ default:
+ DB::logError("Result: invalid date/time value: '%s'",value);
+ return 0;
+ }
+
+ return sqlite3_gmtime(&gm_tm);
+}
+
+unsigned char DB::Result::getUChar(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getUChar: statement is not valid");
+ return 0;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return 0;
+ }
+ int value = sqlite3_column_int(_handle->_stmt, fieldidx-1);
+ reportError(_handle->_stmt);
+ return (unsigned char)value;
+}
+
+float DB::Result::getFloat(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getFloat: statement is not valid");
+ return 0.0f;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return 0.0f;
+ }
+ double value = sqlite3_column_double(_handle->_stmt, fieldidx-1);
+ reportError(_handle->_stmt);
+ return (float)value;
+}
+
+double DB::Result::getDouble(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getDouble: statement is not valid");
+ return 0.0;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return 0.0;
+ }
+ double value = sqlite3_column_double(_handle->_stmt, fieldidx-1);
+ reportError(_handle->_stmt);
+ return value;
+}
+
+int DB::Result::getInt(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getInt: statement is not valid");
+ return 0;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return 0;
+ }
+ int value = sqlite3_column_int(_handle->_stmt, fieldidx-1);
+ reportError(_handle->_stmt);
+ return value;
+}
+
+unsigned int DB::Result::getUInt(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getUInt: statement is not valid");
+ return 0;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return 0;
+ }
+ int value = sqlite3_column_int(_handle->_stmt, fieldidx-1);
+ reportError(_handle->_stmt);
+ return (unsigned int)value;
+}
+
+long long DB::Result::getLongLong(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getLongLong: statement is not valid");
+ return 0;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return 0;
+ }
+ sqlite3_int64 value = sqlite3_column_int64(_handle->_stmt, fieldidx-1);
+ reportError(_handle->_stmt);
+ return value;
+}
+
+unsigned long long DB::Result::getULongLong(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getULongLong: statement is not valid");
+ return 0;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return 0;
+ }
+ sqlite3_int64 value = sqlite3_column_int64(_handle->_stmt, fieldidx-1);
+ reportError(_handle->_stmt);
+ return (unsigned long long)value;
+}
+
+const char *DB::Result::getString(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getString: statement is not valid");
+ return NULL;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return NULL;
+ }
+ const unsigned char *value = sqlite3_column_text(_handle->_stmt,fieldidx-1);
+ reportError(_handle->_stmt);
+ return (const char *)value;
+}
+
+const unsigned char *DB::Result::getBinary(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getBinary: statement is not valid");
+ return NULL;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return NULL;
+ }
+ const unsigned char *value =
+ (const unsigned char *)sqlite3_column_blob(_handle->_stmt,fieldidx-1);
+ reportError(_handle->_stmt);
+ return value;
+}
+
+size_t DB::Result::getFieldLength(unsigned int fieldidx)
+{
+ if (!isValid()) {
+ DB::logError("Result::getFieldLength: statement is not valid");
+ return 0;
+ }
+ if (fieldidx == 0) {
+ DB::logError("Result: zero is an invalid field index");
+ return 0;
+ }
+ int value = sqlite3_column_bytes(_handle->_stmt,fieldidx-1);
+ reportError(_handle->_stmt);
+ return (size_t)value;
+}
+
+bool DB::Result::firstRow()
+{
+ if (!isValid()) {
+ DB::logError("Result::firstRow: statement is not valid");
+ return false;
+ }
+ return _handle->reset() && _handle->step()==Statement::ReturnCodeRow;
+}
+
+bool DB::Result::nextRow()
+{
+ if (!isValid()) {
+ DB::logError("Result::nextRow: statement is not valid");
+ return false;
+ }
+ return _handle->step()==Statement::ReturnCodeRow;
+}
+
+/**************************
+ * Connection
+ **************************/
+
+DB::Connection *DB::Connection::Create(const std::string &dbdir, const std::string &dbname)
+{
+ if (dbdir.length() == 0) {
+ DB::logError("Connection::Create: database directory parameter dbdir is empty");
+ return NULL;
+ }
+
+ if (dbname.length() == 0) {
+ DB::logError("Connection::Create: database name parameter dbname is empty");
+ return NULL;
+ }
+
+ return new Connection(dbdir,dbname);
+}
+
+DB::Connection::Connection(const std::string &dbdir, const std::string &dbname)
+ : _dbdir(dbdir)
+ , _dbpath(dbdir + OS_PATHSEP + dbname)
+ , _db(NULL)
+{
+}
+
+DB::Connection::~Connection()
+{
+ close();
+}
+
+const std::string &DB::Connection::dbdir()
+{
+ return _dbdir;
+}
+
+const std::string &DB::Connection::dbpath()
+{
+ return _dbpath;
+}
+
+DB::Statement DB::Connection::prepare(const std::string &format, ...){
+ // pstatement will hold a dynamically allocated string that needs to be deleted.
+ char *pstatement = NULL;
+
+ // short form
+ char statement[128];
+ va_list args;
+ va_start(args, format);
+ int cneeded = vsnprintf(statement,sizeof(statement),format.c_str(),args);
+ va_end(args);
+ if (cneeded<0) {
+ DB::logError("Connection::prepare: vsnprintf encoding error");
+ return Statement();
+ }
+ if (((size_t)cneeded)>=sizeof(statement)) {
+ // long form
+ pstatement = new char[cneeded+1];
+ if (!pstatement) {
+ DB::logError("Connection::prepare: out of memory");
+ return Statement();
+ }
+ va_start(args, format);
+ bool ok = vsnprintf(pstatement,cneeded+1,format.c_str(),args)==cneeded;
+ va_end(args);
+ if (!ok) {
+ DB::logError("Connection::prepare: vsnprintf error");
+ delete[] pstatement;
+ return Statement();
+ }
+ }
+
+ sqlite3_stmt *stmt = NULL;
+ int rv = sqlite3_prepare_v2(_db,
+ pstatement ? pstatement : statement,
+ cneeded+1,
+ &stmt,
+ NULL);
+
+ if (pstatement)
+ delete[] pstatement;
+
+ if (rv != SQLITE_OK) {
+ reportErrorDB(_db);
+ if (stmt)
+ sqlite3_finalize(stmt);
+ return Statement();
+ }
+
+ if (!stmt) {
+ DB::logError("Connection::prepare: expected sqlite3_prepare_v2 to return a compiled "
+ "statement, got NULL, out of memory ?");
+ return Statement();
+ }
+
+ return Statement(stmt);
+}
+
+DB::Result DB::Connection::perform(DB::Statement &statement)
+{
+ return (statement.step()==Statement::ReturnCodeRow) ? Result(statement) : Result();
+}
+
+bool DB::Connection::execute(DB::Statement &statement)
+{
+ return statement.step()==Statement::ReturnCodeDone;
+}
+
+bool DB::Connection::connect(const char *
+#if HAVE_SQL_TRACE
+ connectionLabel
+#endif
+ )
+{
+ // Create and set file permissions if the DB does not exist.
+ int fd = open(_dbpath.c_str(), O_CREAT, S_IRUSR | S_IWUSR);
+ if (fd == -1)
+ {
+ DB::logError("Could not open database: %s (errno %i)",
+ _dbpath.c_str(), errno);
+ return false;
+ }
+ ::close(fd);
+
+ int rv = sqlite3_open_v2(_dbpath.c_str(),
+ &_db,
+ SQLITE_OPEN_READWRITE
+ | SQLITE_OPEN_CREATE
+ | SQLITE_OPEN_FULLMUTEX,
+ NULL);
+
+ if (rv != SQLITE_OK) {
+ reportErrorDB(_db);
+ return false;
+ }
+
+ int foreignKeyEnabled = 0;
+ rv = sqlite3_db_config(_db,SQLITE_DBCONFIG_ENABLE_FKEY,1,&foreignKeyEnabled);
+ if (rv != SQLITE_OK) {
+ reportErrorDB(_db);
+ return false;
+ }
+
+ if (foreignKeyEnabled != 1) {
+ DB::logError("Connection::connect: foreign key support not enabled");
+ return false;
+ }
+
+ rv = sqlite3_busy_timeout(_db, 15000); // 15 seconds
+ if (rv != SQLITE_OK) {
+ reportErrorDB(_db);
+ return false;
+ }
+#if HAVE_SQL_TRACE
+ sqlite3_trace(_db, xTrace, const_cast<char *>(connectionLabel));
+#endif
+ return true;
+}
+
+void DB::Connection::close()
+{
+ if (_db) {
+ sqlite3_close(_db);
+ _db = NULL;
+ }
+}
+
+bool DB::Connection::setBusyTimeout(int ms)
+{
+ int rv = sqlite3_busy_timeout(_db, ms);
+ if (rv != SQLITE_OK) {
+ reportErrorDB(_db);
+ return false;
+ }
+
+ return true;
+}
+
+bool DB::Connection::tableExists(const std::string &tablename)
+{
+ Statement statement = prepare("select name from sqlite_master where type='table' and name='%s';",tablename.c_str());
+ return statement.step()==Statement::ReturnCodeRow && statement.step()==Statement::ReturnCodeDone;
+}
+
+long long DB::Connection::lastInsertRowId()
+{
+ return sqlite3_last_insert_rowid(_db);
+}
+
+bool DB::Connection::inTransaction()
+{
+ return sqlite3_get_autocommit(_db)==0;
+}
+
+bool DB::Connection::beginTransactionRO()
+{
+ Statement statement = prepare("begin");
+ return statement.step()==Statement::ReturnCodeDone;
+}
+
+bool DB::Connection::endTransactionRO()
+{
+ Statement statement = prepare("end");
+ return statement.step()==Statement::ReturnCodeDone;
+}
+
+bool DB::Connection::beginTransactionRW()
+{
+ Statement statement = prepare("begin immediate");
+ return statement.step()==Statement::ReturnCodeDone;
+}
+
+bool DB::Connection::commitTransaction()
+{
+ Statement statement = prepare("commit");
+ return statement.step()==Statement::ReturnCodeDone;
+}
+
+bool DB::Connection::rollbackTransaction()
+{
+ Statement statement = prepare("rollback");
+ return statement.step()==Statement::ReturnCodeDone;
+}
+