mirror of
https://github.com/nextcloud/desktop.git
synced 2024-12-22 13:44:34 +03:00
4ad190a558
This gives more insight about the logs and allow setting fine-tuned logging rules. The categories are set to only output Info by default so this allows us to provide more concise logging while keeping the ability to extract more information for a specific category when developping or debugging customer issues. Issue #5647
405 lines
10 KiB
C++
405 lines
10 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 <QLoggingCategory>
|
|
#include <QString>
|
|
#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 {
|
|
|
|
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<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) {
|
|
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<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
|