File indexing completed on 2025-01-05 04:46:57

0001 /*
0002     SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "dbconfigsqlite.h"
0008 #include "akonadiserver_debug.h"
0009 #include "utils.h"
0010 
0011 #include "private/standarddirs_p.h"
0012 
0013 #include <QDir>
0014 #include <QSqlDriver>
0015 #include <QSqlError>
0016 #include <QSqlQuery>
0017 
0018 using namespace Akonadi;
0019 using namespace Akonadi::Server;
0020 
0021 static QString dataDir(const QString &dbPathOverride = {})
0022 {
0023     QString akonadiHomeDir = dbPathOverride.isEmpty() ? StandardDirs::saveDir("data") : dbPathOverride;
0024     if (!QDir(akonadiHomeDir).exists()) {
0025         if (!QDir().mkpath(akonadiHomeDir)) {
0026             qCCritical(AKONADISERVER_LOG) << "Unable to create" << akonadiHomeDir << "during database initialization";
0027             return QString();
0028         }
0029     }
0030 
0031     akonadiHomeDir += QStringLiteral("/");
0032 
0033     return akonadiHomeDir;
0034 }
0035 
0036 static QString sqliteDataFile(const QString &dbPathOverride = {})
0037 {
0038     const QString dir = dataDir(dbPathOverride);
0039     if (dir.isEmpty()) {
0040         return QString();
0041     }
0042     const QString akonadiPath = dir + QLatin1StringView("akonadi.db");
0043     if (!QFile::exists(akonadiPath)) {
0044         QFile file(akonadiPath);
0045         if (!file.open(QIODevice::WriteOnly)) {
0046             qCCritical(AKONADISERVER_LOG) << "Unable to create file" << akonadiPath << "during database initialization.";
0047             return QString();
0048         }
0049         file.close();
0050     }
0051 
0052     return akonadiPath;
0053 }
0054 
0055 DbConfigSqlite::DbConfigSqlite(const QString &configFile)
0056     : DbConfig(configFile)
0057 {
0058 }
0059 
0060 QString DbConfigSqlite::driverName() const
0061 {
0062     return QStringLiteral("QSQLITE");
0063 }
0064 
0065 QString DbConfigSqlite::databaseName() const
0066 {
0067     return mDatabaseName;
0068 }
0069 
0070 QString DbConfigSqlite::databasePath() const
0071 {
0072     return mDatabaseName;
0073 }
0074 
0075 void DbConfigSqlite::setDatabasePath(const QString &path, QSettings &settings)
0076 {
0077     mDatabaseName = path;
0078     settings.beginGroup(driverName());
0079     settings.setValue(QStringLiteral("Name"), mDatabaseName);
0080     settings.endGroup();
0081 }
0082 
0083 bool DbConfigSqlite::init(QSettings &settings, bool storeSettings, const QString &dbPathOverride)
0084 {
0085     // determine default settings depending on the driver
0086     const QString defaultDbName = sqliteDataFile(dbPathOverride);
0087     if (defaultDbName.isEmpty()) {
0088         return false;
0089     }
0090 
0091     // read settings for current driver
0092     settings.beginGroup(driverName());
0093     mDatabaseName = settings.value(QStringLiteral("Name"), defaultDbName).toString();
0094     mHostName = settings.value(QStringLiteral("Host")).toString();
0095     mUserName = settings.value(QStringLiteral("User")).toString();
0096     mPassword = settings.value(QStringLiteral("Password")).toString();
0097     mConnectionOptions = settings.value(QStringLiteral("Options")).toString();
0098     settings.endGroup();
0099 
0100     if (storeSettings) {
0101         // store back the default values
0102         settings.beginGroup(driverName());
0103         settings.setValue(QStringLiteral("Name"), mDatabaseName);
0104         settings.endGroup();
0105         settings.sync();
0106     }
0107 
0108     return true;
0109 }
0110 
0111 bool DbConfigSqlite::isAvailable(QSettings &settings)
0112 {
0113     if (!QSqlDatabase::drivers().contains(driverName())) {
0114         return false;
0115     }
0116 
0117     if (!init(settings, false)) {
0118         return false;
0119     }
0120 
0121     return true;
0122 }
0123 
0124 void DbConfigSqlite::apply(QSqlDatabase &database)
0125 {
0126     if (!mDatabaseName.isEmpty()) {
0127         database.setDatabaseName(mDatabaseName);
0128     }
0129     if (!mHostName.isEmpty()) {
0130         database.setHostName(mHostName);
0131     }
0132     if (!mUserName.isEmpty()) {
0133         database.setUserName(mUserName);
0134     }
0135     if (!mPassword.isEmpty()) {
0136         database.setPassword(mPassword);
0137     }
0138 
0139     database.setConnectOptions(mConnectionOptions);
0140 
0141     // can we check that during init() already?
0142     Q_ASSERT(database.driver()->hasFeature(QSqlDriver::LastInsertId));
0143 }
0144 
0145 bool DbConfigSqlite::useInternalServer() const
0146 {
0147     return false;
0148 }
0149 
0150 bool DbConfigSqlite::setPragma(QSqlDatabase &db, QSqlQuery &query, const QString &pragma)
0151 {
0152     if (!query.exec(QStringLiteral("PRAGMA %1").arg(pragma))) {
0153         qCCritical(AKONADISERVER_LOG) << "Could not set sqlite PRAGMA " << pragma;
0154         qCCritical(AKONADISERVER_LOG) << "Database: " << mDatabaseName;
0155         qCCritical(AKONADISERVER_LOG) << "Query error: " << query.lastError().text();
0156         qCCritical(AKONADISERVER_LOG) << "Database error: " << db.lastError().text();
0157         return false;
0158     }
0159     return true;
0160 }
0161 
0162 void DbConfigSqlite::setup()
0163 {
0164     const QLatin1StringView connectionName("initConnectionSqlite");
0165 
0166     {
0167         QSqlDatabase db = QSqlDatabase::addDatabase(driverName(), connectionName);
0168 
0169         if (!db.isValid()) {
0170             qCCritical(AKONADISERVER_LOG) << "Invalid database for" << mDatabaseName << "with driver" << driverName();
0171             return;
0172         }
0173 
0174         QFileInfo finfo(mDatabaseName);
0175         if (!finfo.dir().exists()) {
0176             QDir dir;
0177             dir.mkpath(finfo.path());
0178         }
0179 
0180 #ifdef Q_OS_LINUX
0181         QFile dbFile(mDatabaseName);
0182         // It is recommended to disable CoW feature when running on Btrfs to improve
0183         // database performance. It does not have any effect on non-empty files, so
0184         // we check, whether the database has not yet been initialized.
0185         if (dbFile.size() == 0) {
0186             if (Utils::getDirectoryFileSystem(mDatabaseName) == QLatin1StringView("btrfs")) {
0187                 Utils::disableCoW(mDatabaseName);
0188             }
0189         }
0190 #endif
0191 
0192         db.setDatabaseName(mDatabaseName);
0193         if (!db.open()) {
0194             qCCritical(AKONADISERVER_LOG) << "Could not open sqlite database" << mDatabaseName << "with driver" << driverName() << "for initialization";
0195             db.close();
0196             return;
0197         }
0198 
0199         apply(db);
0200 
0201         QSqlQuery query(db);
0202         if (!query.exec(QStringLiteral("SELECT sqlite_version()"))) {
0203             qCCritical(AKONADISERVER_LOG) << "Could not query sqlite version";
0204             qCCritical(AKONADISERVER_LOG) << "Database: " << mDatabaseName;
0205             qCCritical(AKONADISERVER_LOG) << "Query error: " << query.lastError().text();
0206             qCCritical(AKONADISERVER_LOG) << "Database error: " << db.lastError().text();
0207             db.close();
0208             return;
0209         }
0210 
0211         if (!query.next()) { // should never occur
0212             qCCritical(AKONADISERVER_LOG) << "Could not query sqlite version";
0213             qCCritical(AKONADISERVER_LOG) << "Database: " << mDatabaseName;
0214             qCCritical(AKONADISERVER_LOG) << "Query error: " << query.lastError().text();
0215             qCCritical(AKONADISERVER_LOG) << "Database error: " << db.lastError().text();
0216             db.close();
0217             return;
0218         }
0219 
0220         const QString sqliteVersion = query.value(0).toString();
0221         qCDebug(AKONADISERVER_LOG) << "sqlite version is " << sqliteVersion;
0222 
0223         const QStringList list = sqliteVersion.split(QLatin1Char('.'));
0224         const int sqliteVersionMajor = list[0].toInt();
0225         const int sqliteVersionMinor = list[1].toInt();
0226 
0227         // set synchronous mode to NORMAL; see http://www.sqlite.org/pragma.html#pragma_synchronous
0228         if (!setPragma(db, query, QStringLiteral("synchronous=1"))) {
0229             db.close();
0230             return;
0231         }
0232 
0233         if (sqliteVersionMajor < 3 && sqliteVersionMinor < 7) {
0234             // wal mode is only supported with >= sqlite 3.7.0
0235             db.close();
0236             return;
0237         }
0238 
0239         // set write-ahead-log mode; see http://www.sqlite.org/wal.html
0240         if (!setPragma(db, query, QStringLiteral("journal_mode=wal"))) {
0241             db.close();
0242             return;
0243         }
0244 
0245         if (!query.next()) { // should never occur
0246             qCCritical(AKONADISERVER_LOG) << "Could not query sqlite journal mode";
0247             qCCritical(AKONADISERVER_LOG) << "Database: " << mDatabaseName;
0248             qCCritical(AKONADISERVER_LOG) << "Query error: " << query.lastError().text();
0249             qCCritical(AKONADISERVER_LOG) << "Database error: " << db.lastError().text();
0250             db.close();
0251             return;
0252         }
0253 
0254         const QString journalMode = query.value(0).toString();
0255         qCDebug(AKONADISERVER_LOG) << "sqlite journal mode is " << journalMode;
0256 
0257         // as of sqlite 3.12 this is default, previously was 1024.
0258         if (!setPragma(db, query, QStringLiteral("page_size=4096"))) {
0259             db.close();
0260             return;
0261         }
0262 
0263         // set cache_size to 100000 pages; see https://www.sqlite.org/pragma.html#pragma_cache_size
0264         if (!setPragma(db, query, QStringLiteral("cache_size=100000"))) {
0265             db.close();
0266             return;
0267         }
0268 
0269         // construct temporary tables in memory; see https://www.sqlite.org/pragma.html#pragma_temp_store
0270         if (!setPragma(db, query, QStringLiteral("temp_store=MEMORY"))) {
0271             db.close();
0272             return;
0273         }
0274 
0275         // enable foreign key support; see https://www.sqlite.org/pragma.html#pragma_foreign_keys
0276         if (!setPragma(db, query, QStringLiteral("foreign_keys=ON"))) {
0277             db.close();
0278             return;
0279         }
0280 
0281         db.close();
0282     }
0283 
0284     QSqlDatabase::removeDatabase(connectionName);
0285 }
0286 
0287 bool DbConfigSqlite::disableConstraintChecks(const QSqlDatabase &db)
0288 {
0289     QSqlQuery query(db);
0290     return query.exec(QStringLiteral("PRAGMA ignore_check_constraints=ON"));
0291 }
0292 
0293 bool DbConfigSqlite::enableConstraintChecks(const QSqlDatabase &db)
0294 {
0295     QSqlQuery query(db);
0296     return query.exec(QStringLiteral("PRAGMA ignore_check_constraints=OFF"));
0297 }