/* * Copyright (C) by Klaas Freitag * * 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. * * 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 #include #include #include #include #include #include "ownsql.h" #include "utility.h" #include "asserts.h" #define SQLITE_SLEEP_TIME_USEC 100000 #define SQLITE_REPEAT_COUNT 20 #define SQLITE_DO(A) if(1) { \ _errId = (A); if(_errId != SQLITE_OK) { _error= QString::fromUtf8(sqlite3_errmsg(_db)); \ } } namespace OCC { Q_LOGGING_CATEGORY(lcSql, "sync.database.sql", QtInfoMsg) SqlDatabase::SqlDatabase() :_db(0), _errId(0) { } bool SqlDatabase::isOpen() { return _db != 0; } bool SqlDatabase::openHelper( const QString& filename, int sqliteFlags ) { if( isOpen() ) { return true; } sqliteFlags |= SQLITE_OPEN_NOMUTEX; SQLITE_DO( sqlite3_open_v2(filename.toUtf8().constData(), &_db, sqliteFlags, 0) ); if( _errId != SQLITE_OK ) { qCDebug(lcSql) << "Error:" << _error << "for" << filename; close(); return false; } if( !_db ) { qCDebug(lcSql) << "Error: no database for" << filename; return false; } sqlite3_busy_timeout(_db, 5000); return true; } bool SqlDatabase::checkDb() { // quick_check can fail with a disk IO error when diskspace is low SqlQuery quick_check(*this); quick_check.prepare("PRAGMA quick_check;", /*allow_failure=*/true); if( !quick_check.exec() ) { qCDebug(lcSql) << "Error running quick_check on database"; return false; } quick_check.next(); QString result = quick_check.stringValue(0); if( result != "ok" ) { qCDebug(lcSql) << "quick_check returned failure:" << result; return false; } return true; } bool SqlDatabase::openOrCreateReadWrite( const QString& filename ) { if( isOpen() ) { return true; } if( !openHelper(filename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) ) { return false; } if( !checkDb() ) { // When disk space is low, checking the db may fail even though it's fine. qint64 freeSpace = Utility::freeDiskSpace(QFileInfo(filename).dir().absolutePath()); if (freeSpace != -1 && freeSpace < 1000000) { qCDebug(lcSql) << "Consistency check failed, disk space is low, aborting" << freeSpace; close(); return false; } qCDebug(lcSql) << "Consistency check failed, removing broken db" << filename; close(); QFile::remove(filename); return openHelper(filename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); } return true; } bool SqlDatabase::openReadOnly( const QString& filename ) { if( isOpen() ) { return true; } if( !openHelper(filename, SQLITE_OPEN_READONLY) ) { return false; } if( !checkDb() ) { qCDebug(lcSql) << "Consistency check failed in readonly mode, giving up" << filename; close(); return false; } return true; } QString SqlDatabase::error() const { const QString err(_error); // _error.clear(); return err; } void SqlDatabase::close() { if( _db ) { SQLITE_DO(sqlite3_close(_db) ); // Fatal because reopening an unclosed db might be problematic. ENFORCE(_errId == SQLITE_OK, "Error when closing DB"); _db = 0; } } bool SqlDatabase::transaction() { if( ! _db ) { return false; } SQLITE_DO(sqlite3_exec(_db, "BEGIN", 0, 0, 0)); return _errId == SQLITE_OK; } bool SqlDatabase::commit() { if( ! _db ) { return false; } SQLITE_DO(sqlite3_exec(_db, "COMMIT", 0, 0, 0)); return _errId == SQLITE_OK; } sqlite3* SqlDatabase::sqliteDb() { return _db; } /* =========================================================================================== */ SqlQuery::SqlQuery( SqlDatabase& db ) :_db(db.sqliteDb()), _stmt(0), _errId(0) { } SqlQuery::~SqlQuery() { if( _stmt ) { finish(); } } SqlQuery::SqlQuery(const QString& sql, SqlDatabase& db) :_db(db.sqliteDb()), _stmt(0), _errId(0) { prepare(sql); } int SqlQuery::prepare( const QString& sql, bool allow_failure ) { QString s(sql); _sql = s.trimmed(); if(_stmt ) { finish(); } if(!_sql.isEmpty() ) { int n = 0; int rc; do { rc = sqlite3_prepare_v2(_db, _sql.toUtf8().constData(), -1, &_stmt, 0); if( (rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED) ) { n++; OCC::Utility::usleep(SQLITE_SLEEP_TIME_USEC); } } while( (n < SQLITE_REPEAT_COUNT) && ((rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED))); _errId = rc; if( _errId != SQLITE_OK ) { _error = QString::fromUtf8(sqlite3_errmsg(_db)); qCWarning(lcSql) << "Sqlite prepare statement error:" << _error << "in" <<_sql; ENFORCE(allow_failure, "SQLITE Prepare error"); } } return _errId; } bool SqlQuery::isSelect() { return (!_sql.isEmpty() && _sql.startsWith("SELECT", Qt::CaseInsensitive)); } bool SqlQuery::isPragma() { return (!_sql.isEmpty() && _sql.startsWith("PRAGMA", Qt::CaseInsensitive)); } bool SqlQuery::exec() { if (!_stmt) { return false; } // Don't do anything for selects, that is how we use the lib :-| if( !isSelect() && !isPragma() ) { int rc, n = 0; do { rc = sqlite3_step(_stmt); if( rc == SQLITE_LOCKED ) { rc = sqlite3_reset(_stmt); /* This will also return SQLITE_LOCKED */ n++; OCC::Utility::usleep(SQLITE_SLEEP_TIME_USEC); } else if( rc == SQLITE_BUSY ) { OCC::Utility::usleep(SQLITE_SLEEP_TIME_USEC); n++; } } while( (n < SQLITE_REPEAT_COUNT) && ((rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED))); _errId = rc; if (_errId != SQLITE_DONE && _errId != SQLITE_ROW) { _error = QString::fromUtf8(sqlite3_errmsg(_db)); qCDebug(lcSql) << "Sqlite exec statement error:" << _errId << _error << "in" <<_sql; } return (_errId == SQLITE_DONE); // either SQLITE_ROW or SQLITE_DONE } return true; } bool SqlQuery::next() { SQLITE_DO(sqlite3_step(_stmt)); return _errId == SQLITE_ROW; } void SqlQuery::bindValue(int pos, const QVariant& value) { int res = -1; if (!_stmt) { ASSERT(false); return; } switch (value.type()) { case QVariant::Int: case QVariant::Bool: res = sqlite3_bind_int(_stmt, pos, value.toInt()); break; case QVariant::Double: res = sqlite3_bind_double(_stmt, pos, value.toDouble()); break; case QVariant::UInt: case QVariant::LongLong: res = sqlite3_bind_int64(_stmt, pos, value.toLongLong()); break; case QVariant::DateTime: { const QDateTime dateTime = value.toDateTime(); const QString str = dateTime.toString(QLatin1String("yyyy-MM-ddThh:mm:ss.zzz")); res = sqlite3_bind_text16(_stmt, pos, str.utf16(), str.size() * sizeof(ushort), SQLITE_TRANSIENT); break; } case QVariant::Time: { const QTime time = value.toTime(); const QString str = time.toString(QLatin1String("hh:mm:ss.zzz")); res = sqlite3_bind_text16(_stmt, pos, str.utf16(), str.size() * sizeof(ushort), SQLITE_TRANSIENT); break; } case QVariant::String: { if( !value.toString().isNull() ) { // lifetime of string == lifetime of its qvariant const QString *str = static_cast(value.constData()); res = sqlite3_bind_text16(_stmt, pos, str->utf16(), (str->size()) * sizeof(QChar), SQLITE_TRANSIENT); } else { res = sqlite3_bind_null(_stmt, pos); } break; } case QVariant::ByteArray: { auto ba = value.toByteArray(); res = sqlite3_bind_text(_stmt, pos, ba.constData(), ba.size(), SQLITE_TRANSIENT); break; } default: { QString str = value.toString(); // SQLITE_TRANSIENT makes sure that sqlite buffers the data res = sqlite3_bind_text16(_stmt, pos, str.utf16(), (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); break; } } if (res != SQLITE_OK) { qCDebug(lcSql) << "ERROR" << value << res; } ASSERT( res == SQLITE_OK ); } bool SqlQuery::nullValue(int index) { return sqlite3_column_type(_stmt, index) == SQLITE_NULL; } QString SqlQuery::stringValue(int index) { return QString::fromUtf16(static_cast(sqlite3_column_text16(_stmt, index))); } int SqlQuery::intValue(int index) { return sqlite3_column_int(_stmt, index); } quint64 SqlQuery::int64Value(int index) { return sqlite3_column_int64(_stmt, index); } QByteArray SqlQuery::baValue(int index) { return QByteArray( static_cast(sqlite3_column_blob(_stmt, index)), sqlite3_column_bytes(_stmt, index)); } QString SqlQuery::error() const { return _error; } int SqlQuery::errorId() const { return _errId; } QString SqlQuery::lastQuery() const { return _sql; } int SqlQuery::numRowsAffected() { return sqlite3_changes(_db); } void SqlQuery::finish() { SQLITE_DO(sqlite3_finalize(_stmt)); _stmt = 0; } void SqlQuery::reset_and_clear_bindings() { if (_stmt) { SQLITE_DO(sqlite3_reset(_stmt)); SQLITE_DO(sqlite3_clear_bindings(_stmt)); } } } // namespace OCC