Akonadi

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

KDE's Doxygen guidelines are available online.