/* * 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 #include #include #include #include #include #include #include #include #include #include "DB.h" #if HAVE_SQL_TRACE static void xTrace(void*connectionLabel,const char*zSql) { const char *label = static_cast(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 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(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; }