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 }