Akonadi

dbconfigsqlite.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Tobias Koenig <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "dbconfigsqlite.h"
8 #include "akonadiserver_debug.h"
9 #include "utils.h"
10 
11 #include <private/standarddirs_p.h>
12 
13 #include <QDir>
14 #include <QSqlDriver>
15 #include <QSqlError>
16 #include <QSqlQuery>
17 
18 using namespace Akonadi;
19 using namespace Akonadi::Server;
20 
21 static QString dataDir()
22 {
23  QString akonadiHomeDir = StandardDirs::saveDir("data");
24  if (!QDir(akonadiHomeDir).exists()) {
25  if (!QDir().mkpath(akonadiHomeDir)) {
26  qCCritical(AKONADISERVER_LOG) << "Unable to create" << akonadiHomeDir << "during database initialization";
27  return QString();
28  }
29  }
30 
31  akonadiHomeDir += QDir::separator();
32 
33  return akonadiHomeDir;
34 }
35 
36 static QString sqliteDataFile()
37 {
38  const QString dir = dataDir();
39  if (dir.isEmpty()) {
40  return QString();
41  }
42  const QString akonadiPath = dir + QLatin1String("akonadi.db");
43  if (!QFile::exists(akonadiPath)) {
44  QFile file(akonadiPath);
45  if (!file.open(QIODevice::WriteOnly)) {
46  qCCritical(AKONADISERVER_LOG) << "Unable to create file" << akonadiPath << "during database initialization.";
47  return QString();
48  }
49  file.close();
50  }
51 
52  return akonadiPath;
53 }
54 
55 DbConfigSqlite::DbConfigSqlite(Version driverVersion)
56  : mDriverVersion(driverVersion)
57 {
58 }
59 
60 QString DbConfigSqlite::driverName() const
61 {
62  if (mDriverVersion == Default) {
63  return QStringLiteral("QSQLITE");
64  } else {
65  return QStringLiteral("QSQLITE3");
66  }
67 }
68 
69 QString DbConfigSqlite::databaseName() const
70 {
71  return mDatabaseName;
72 }
73 
74 bool DbConfigSqlite::init(QSettings &settings, bool storeSettings)
75 {
76  // determine default settings depending on the driver
77  const QString defaultDbName = sqliteDataFile();
78  if (defaultDbName.isEmpty()) {
79  return false;
80  }
81 
82  // read settings for current driver
83  settings.beginGroup(driverName());
84  mDatabaseName = settings.value(QStringLiteral("Name"), defaultDbName).toString();
85  mHostName = settings.value(QStringLiteral("Host")).toString();
86  mUserName = settings.value(QStringLiteral("User")).toString();
87  mPassword = settings.value(QStringLiteral("Password")).toString();
88  mConnectionOptions = settings.value(QStringLiteral("Options")).toString();
89  settings.endGroup();
90 
91  if (storeSettings) {
92  // store back the default values
93  settings.beginGroup(driverName());
94  settings.setValue(QStringLiteral("Name"), mDatabaseName);
95  settings.endGroup();
96  settings.sync();
97  }
98 
99  return true;
100 }
101 
102 bool DbConfigSqlite::isAvailable(QSettings &settings)
103 {
104  if (!QSqlDatabase::drivers().contains(driverName())) {
105  return false;
106  }
107 
108  if (!init(settings, false)) {
109  return false;
110  }
111 
112  return true;
113 }
114 
115 void DbConfigSqlite::apply(QSqlDatabase &database)
116 {
117  if (!mDatabaseName.isEmpty()) {
118  database.setDatabaseName(mDatabaseName);
119  }
120  if (!mHostName.isEmpty()) {
121  database.setHostName(mHostName);
122  }
123  if (!mUserName.isEmpty()) {
124  database.setUserName(mUserName);
125  }
126  if (!mPassword.isEmpty()) {
127  database.setPassword(mPassword);
128  }
129 
130  if (driverName() == QLatin1String("QSQLITE3") && !mConnectionOptions.contains(QLatin1String("SQLITE_ENABLE_SHARED_CACHE"))) {
131  mConnectionOptions += QLatin1String(";QSQLITE_ENABLE_SHARED_CACHE");
132  }
133  database.setConnectOptions(mConnectionOptions);
134 
135  // can we check that during init() already?
136  Q_ASSERT(database.driver()->hasFeature(QSqlDriver::LastInsertId));
137 }
138 
139 bool DbConfigSqlite::useInternalServer() const
140 {
141  return false;
142 }
143 
144 bool DbConfigSqlite::setPragma(QSqlDatabase &db, QSqlQuery &query, const QString &pragma)
145 {
146  if (!query.exec(QStringLiteral("PRAGMA %1").arg(pragma))) {
147  qCCritical(AKONADISERVER_LOG) << "Could not set sqlite PRAGMA " << pragma;
148  qCCritical(AKONADISERVER_LOG) << "Database: " << mDatabaseName;
149  qCCritical(AKONADISERVER_LOG) << "Query error: " << query.lastError().text();
150  qCCritical(AKONADISERVER_LOG) << "Database error: " << db.lastError().text();
151  return false;
152  }
153  return true;
154 }
155 
156 void DbConfigSqlite::setup()
157 {
158  const QLatin1String connectionName("initConnection");
159 
160  {
161  QSqlDatabase db = QSqlDatabase::addDatabase(driverName(), connectionName);
162 
163  if (!db.isValid()) {
164  qCCritical(AKONADISERVER_LOG) << "Invalid database for" << mDatabaseName << "with driver" << driverName();
165  return;
166  }
167 
168  QFileInfo finfo(mDatabaseName);
169  if (!finfo.dir().exists()) {
170  QDir dir;
171  dir.mkpath(finfo.path());
172  }
173 
174 #ifdef Q_OS_LINUX
175  QFile dbFile(mDatabaseName);
176  // It is recommended to disable CoW feature when running on Btrfs to improve
177  // database performance. It does not have any effect on non-empty files, so
178  // we check, whether the database has not yet been initialized.
179  if (dbFile.size() == 0) {
180  if (Utils::getDirectoryFileSystem(mDatabaseName) == QLatin1String("btrfs")) {
181  Utils::disableCoW(mDatabaseName);
182  }
183  }
184 #endif
185 
186  db.setDatabaseName(mDatabaseName);
187  if (!db.open()) {
188  qCCritical(AKONADISERVER_LOG) << "Could not open sqlite database" << mDatabaseName << "with driver" << driverName() << "for initialization";
189  db.close();
190  return;
191  }
192 
193  apply(db);
194 
195  QSqlQuery query(db);
196  if (!query.exec(QStringLiteral("SELECT sqlite_version()"))) {
197  qCCritical(AKONADISERVER_LOG) << "Could not query sqlite version";
198  qCCritical(AKONADISERVER_LOG) << "Database: " << mDatabaseName;
199  qCCritical(AKONADISERVER_LOG) << "Query error: " << query.lastError().text();
200  qCCritical(AKONADISERVER_LOG) << "Database error: " << db.lastError().text();
201  db.close();
202  return;
203  }
204 
205  if (!query.next()) { // should never occur
206  qCCritical(AKONADISERVER_LOG) << "Could not query sqlite version";
207  qCCritical(AKONADISERVER_LOG) << "Database: " << mDatabaseName;
208  qCCritical(AKONADISERVER_LOG) << "Query error: " << query.lastError().text();
209  qCCritical(AKONADISERVER_LOG) << "Database error: " << db.lastError().text();
210  db.close();
211  return;
212  }
213 
214  const QString sqliteVersion = query.value(0).toString();
215  qCDebug(AKONADISERVER_LOG) << "sqlite version is " << sqliteVersion;
216 
217  const QStringList list = sqliteVersion.split(QLatin1Char('.'));
218  const int sqliteVersionMajor = list[0].toInt();
219  const int sqliteVersionMinor = list[1].toInt();
220 
221  // set synchronous mode to NORMAL; see http://www.sqlite.org/pragma.html#pragma_synchronous
222  if (!setPragma(db, query, QStringLiteral("synchronous=1"))) {
223  db.close();
224  return;
225  }
226 
227  if (sqliteVersionMajor < 3 && sqliteVersionMinor < 7) {
228  // wal mode is only supported with >= sqlite 3.7.0
229  db.close();
230  return;
231  }
232 
233  // set write-ahead-log mode; see http://www.sqlite.org/wal.html
234  if (!setPragma(db, query, QStringLiteral("journal_mode=wal"))) {
235  db.close();
236  return;
237  }
238 
239  if (!query.next()) { // should never occur
240  qCCritical(AKONADISERVER_LOG) << "Could not query sqlite journal mode";
241  qCCritical(AKONADISERVER_LOG) << "Database: " << mDatabaseName;
242  qCCritical(AKONADISERVER_LOG) << "Query error: " << query.lastError().text();
243  qCCritical(AKONADISERVER_LOG) << "Database error: " << db.lastError().text();
244  db.close();
245  return;
246  }
247 
248  const QString journalMode = query.value(0).toString();
249  qCDebug(AKONADISERVER_LOG) << "sqlite journal mode is " << journalMode;
250 
251  // as of sqlite 3.12 this is default, previously was 1024.
252  if (!setPragma(db, query, QStringLiteral("page_size=4096"))) {
253  db.close();
254  return;
255  }
256 
257  // set cache_size to 100000 pages; see https://www.sqlite.org/pragma.html#pragma_cache_size
258  if (!setPragma(db, query, QStringLiteral("cache_size=100000"))) {
259  db.close();
260  return;
261  }
262 
263  // construct temporary tables in memory; see https://www.sqlite.org/pragma.html#pragma_temp_store
264  if (!setPragma(db, query, QStringLiteral("temp_store=MEMORY"))) {
265  db.close();
266  return;
267  }
268 
269  // enable foreign key support; see https://www.sqlite.org/pragma.html#pragma_foreign_keys
270  if (!setPragma(db, query, QStringLiteral("foreign_keys=ON"))) {
271  db.close();
272  return;
273  }
274 
275  db.close();
276  }
277 
278  QSqlDatabase::removeDatabase(connectionName);
279 }
void sync()
void beginGroup(const QString &prefix)
KGuiItem apply()
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QCA_EXPORT void init()
QChar separator()
void setHostName(const QString &host)
void removeDatabase(const QString &connectionName)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
bool exists() const const
virtual bool hasFeature(QSqlDriver::DriverFeature feature) const const=0
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
QSqlDatabase addDatabase(const QString &type, const QString &connectionName)
bool isValid() const const
void setValue(const QString &key, const QVariant &value)
void setDatabaseName(const QString &name)
bool isEmpty() const const
QVariant value(const QString &key, const QVariant &defaultValue) const const
void setPassword(const QString &password)
void setUserName(const QString &name)
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
void endGroup()
QString text() const const
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
bool contains(QStringView str, Qt::CaseSensitivity cs) const const
QSqlDriver * driver() const const
void setConnectOptions(const QString &options)
QSqlError lastError() const const
QStringList drivers()
T value(int i) const const
QString toString() const const
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jun 30 2022 03:51:46 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.