nextcloud-desktop/src/libsync/ownsql.cpp

403 lines
9.9 KiB
C++

/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* 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 <QDateTime>
#include <QString>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#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 {
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 ) {
qDebug() << "Error:" << _error << "for" << filename;
close();
return false;
}
if( !_db ) {
qDebug() << "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() ) {
qDebug() << "Error running quick_check on database";
return false;
}
quick_check.next();
QString result = quick_check.stringValue(0);
if( result != "ok" ) {
qDebug() << "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) {
qDebug() << "Consistency check failed, disk space is low, aborting" << freeSpace;
close();
return false;
}
qDebug() << "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() ) {
qDebug() << "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));
qWarning() << "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));
qDebug() << "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<const QString*>(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) {
qDebug() << Q_FUNC_INFO << "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<const ushort*>(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<const char*>(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