1/***************************************************************************
2 * Copyright (C) 2006 by Andreas Gungl <a.gungl@gmx.de> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU Library General Public License as *
6 * published by the Free Software Foundation; either version 2 of the *
7 * License, or (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Library General Public *
15 * License along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ***************************************************************************/
19
20#ifndef DATASTORE_H
21#define DATASTORE_H
22
23#include <QtCore/QObject>
24#include <QtCore/QList>
25#include <QtCore/QMutex>
26#include <QtCore/QVector>
27#include <QtCore/QWaitCondition>
28#include <QtCore/QThreadStorage>
29#include <QtSql/QSqlDatabase>
30
31class QSqlQuery;
32class QTimer;
33
34#include "entities.h"
35#include "notificationcollector.h"
36
37namespace Akonadi {
38namespace Server {
39
40class NotificationCollector;
41
42/**
43 This class handles all the database access.
44
45 <h4>Database configuration</h4>
46
47 You can select between various database backends during runtime using the
48 @c $HOME/.config/akonadi/akonadiserverrc configuration file.
49
50 Example:
51@verbatim
52[%General]
53Driver=QMYSQL
54
55[QMYSQL_EMBEDDED]
56Name=akonadi
57Options=SERVER_DATADIR=/home/foo/.local/share/akonadi/db_data
58
59[QMYSQL]
60Name=akonadi
61Host=localhost
62User=foo
63Password=*****
64#Options=UNIX_SOCKET=/home/foo/.local/share/akonadi/socket-bar/mysql.socket
65StartServer=true
66ServerPath=/usr/sbin/mysqld
67
68[QSQLITE]
69Name=/home/foo/.local/share/akonadi/akonadi.db
70@endverbatim
71
72 Use @c General/Driver to select the QSql driver to use for databse
73 access. The following drivers are currently supported, other might work
74 but are untested:
75
76 - QMYSQL
77 - QMYSQL_EMBEDDED
78 - QSQLITE
79
80 The options for each driver are read from the corresponding group.
81 The following options are supported, dependent on the driver not all of them
82 might have an effect:
83
84 - Name: Database name, for sqlite that's the file name of the database.
85 - Host: Hostname of the database server
86 - User: Username for the database server
87 - Password: Password for the database server
88 - Options: Additional options, format is driver-dependent
89 - StartServer: Start the database locally just for Akonadi instead of using an existing one
90 - ServerPath: Path to the server executable
91*/
92class DataStore : public QObject
93{
94 Q_OBJECT
95public:
96 /**
97 Closes the database connection and destroys the DataStore object.
98 */
99 virtual ~DataStore();
100
101 /**
102 Opens the database connection.
103 */
104 virtual void open();
105
106 /**
107 Closes the databse connection.
108 */
109 virtual void close();
110
111 /**
112 Initializes the database. Should be called during startup by the main thread.
113 */
114 virtual bool init();
115
116 /**
117 Per thread singleton.
118 */
119 static DataStore *self();
120
121 /* --- ItemFlags ----------------------------------------------------- */
122 virtual bool setItemsFlags(const PimItem::List &items, const QVector<Flag> &flags,
123 bool *flagsChanged = 0, bool silent = false);
124 virtual bool appendItemsFlags(const PimItem::List &items, const QVector<Flag> &flags,
125 bool *flagsChanged = 0, bool checkIfExists = true,
126 const Collection &col = Collection(), bool silent = false);
127 virtual bool removeItemsFlags(const PimItem::List &items, const QVector<Flag> &flags,
128 bool *tagsChanged = 0, bool silent = false);
129
130 /* --- ItemTags ----------------------------------------------------- */
131 virtual bool setItemsTags(const PimItem::List &items, const Tag::List &tags,
132 bool *tagsChanged = 0, bool silent = false);
133 virtual bool appendItemsTags(const PimItem::List &items, const Tag::List &tags,
134 bool *tagsChanged = 0, bool checkIfExists = true,
135 const Collection &col = Collection(), bool silent = false);
136 virtual bool removeItemsTags(const PimItem::List &items, const Tag::List &tags,
137 bool *tagsChanged = 0, bool silent = false);
138
139 /* --- ItemParts ----------------------------------------------------- */
140 virtual bool removeItemParts(const PimItem &item, const QList<QByteArray> &parts);
141
142 // removes all payload parts for this item.
143 virtual bool invalidateItemCache(const PimItem &item);
144
145 /* --- Collection ------------------------------------------------------ */
146 virtual bool appendCollection(Collection &collection);
147
148 /// removes the given collection and all its content
149 virtual bool cleanupCollection(Collection &collection);
150 /// same as the above but for database backends without working referential actions on foreign keys
151 virtual bool cleanupCollection_slow(Collection &collection);
152
153 /// moves the collection @p collection to @p newParent.
154 virtual bool moveCollection(Collection &collection, const Collection &newParent);
155
156 virtual bool appendMimeTypeForCollection(qint64 collectionId, const QStringList &mimeTypes);
157
158 static QString collectionDelimiter()
159 {
160 return QLatin1String("/");
161 }
162
163 /**
164 Determines the active cache policy for this Collection.
165 The active cache policy is set in the corresponding Collection fields.
166 */
167 virtual void activeCachePolicy(Collection &col);
168
169 /// Returns all virtual collections the @p item is linked to
170 QVector<Collection> virtualCollections(const PimItem &item);
171
172 QMap< Server::Entity::Id, QList< PimItem > > virtualCollections(const Akonadi::Server::PimItem::List &items);
173
174 /* --- MimeType ------------------------------------------------------ */
175 virtual bool appendMimeType(const QString &mimetype, qint64 *insertId = 0);
176
177 /* --- PimItem ------------------------------------------------------- */
178 virtual bool appendPimItem(QVector<Part> &parts,
179 const MimeType &mimetype,
180 const Collection &collection,
181 const QDateTime &dateTime,
182 const QString &remote_id,
183 const QString &remoteRevision,
184 const QString &gid,
185 PimItem &pimItem);
186 /**
187 * Removes the pim item and all referenced data ( e.g. flags )
188 */
189 virtual bool cleanupPimItems(const PimItem::List &items);
190
191 /**
192 * Unhides the specified PimItem. Emits the itemAdded() notification as
193 * the hidden flag is assumed to have been set by appendPimItem() before
194 * pushing the item to the preprocessor chain. The hidden item had his
195 * notifications disabled until now (so for the clients the "unhide" operation
196 * is actually a new item arrival).
197 *
198 * This function does NOT verify if the item was *really* hidden: this is
199 * responsibility of the caller.
200 */
201 virtual bool unhidePimItem(PimItem &pimItem);
202
203 /**
204 * Unhides all the items which have the "hidden" flag set.
205 * This function doesn't emit any notification about the items
206 * being unhidden so it's meant to be called only in rare circumstances.
207 * The most notable call to this function is at server startup
208 * when we attempt to restore a clean state of the database.
209 */
210 virtual bool unhideAllPimItems();
211
212 /* --- Collection attributes ------------------------------------------ */
213 virtual bool addCollectionAttribute(const Collection &col, const QByteArray &key,
214 const QByteArray &value);
215 /**
216 * Removes the given collection attribute for @p col.
217 * @throws HandlerException on database errors
218 * @returns @c true if the attribute existed, @c false otherwise
219 */
220 virtual bool removeCollectionAttribute(const Collection &col, const QByteArray &key);
221
222 /* --- Helper functions ---------------------------------------------- */
223
224 /**
225 Begins a transaction. No changes will be written to the database and
226 no notification signal will be emitted unless you call commitTransaction().
227 @return @c true if successful.
228 */
229 virtual bool beginTransaction();
230
231 /**
232 Reverts all changes within the current transaction.
233 */
234 virtual bool rollbackTransaction();
235
236 /**
237 Commits all changes within the current transaction and emits all
238 collected notfication signals. If committing fails, the transaction
239 will be rolled back.
240 */
241 virtual bool commitTransaction();
242
243 /**
244 Returns true if there is a transaction in progress.
245 */
246 virtual bool inTransaction() const;
247
248 /**
249 Returns the notification collector of this DataStore object.
250 Use this to listen to change notification signals.
251 */
252 virtual NotificationCollector *notificationCollector();
253
254 /**
255 Returns the QSqlDatabase object. Use this for generating queries yourself.
256 */
257 QSqlDatabase database() const
258 {
259 return m_database;
260 }
261
262 /**
263 Sets the current session id.
264 */
265 void setSessionId(const QByteArray &sessionId)
266 {
267 mSessionId = sessionId;
268 }
269
270Q_SIGNALS:
271 /**
272 Emitted if a transaction has been successfully committed.
273 */
274 void transactionCommitted();
275 /**
276 Emitted if a transaction has been aborted.
277 */
278 void transactionRolledBack();
279
280protected:
281 /**
282 Creates a new DataStore object and opens it.
283 */
284 DataStore();
285
286 void debugLastDbError(const char *actionDescription) const;
287 void debugLastQueryError(const QSqlQuery &query, const char *actionDescription) const;
288
289private:
290 bool doAppendItemsFlag(const PimItem::List &items, const Flag &flag,
291 const QSet<PimItem::Id> &existing, const Collection &col,
292 bool silent);
293
294 bool doAppendItemsTag(const PimItem::List &items, const Tag &tag,
295 const QSet<Entity::Id> &existing, const Collection &col,
296 bool silent);
297
298 /** Converts the given date/time to the database format, i.e.
299 "YYYY-MM-DD HH:MM:SS".
300 @param dateTime the date/time in UTC
301 @return the date/time in database format
302 @see dateTimeToQDateTime
303 */
304 static QString dateTimeFromQDateTime(const QDateTime &dateTime);
305
306 /** Converts the given date/time from database format to QDateTime.
307 @param dateTime the date/time in database format
308 @return the date/time as QDateTime
309 @see dateTimeFromQDateTime
310 */
311 static QDateTime dateTimeToQDateTime(const QByteArray &dateTime);
312
313 /**
314 * Adds the @p query to current transaction, so that it can be replayed in
315 * case the transaction deadlocks or timeouts.
316 *
317 * When DataStore is not in transaction or SQLite is configured, this method
318 * does nothing.
319 *
320 * All queries will automatically be removed when transaction is committed.
321 *
322 * This method should only be used by QueryBuilder.
323 */
324 void addQueryToTransaction(const QSqlQuery &query, bool isBatch);
325
326 /**
327 * Tries to execute all queries from last transaction again. If any of the
328 * queries fails, the entire transaction is rolled back and fails.
329 *
330 * This method can only be used by QueryBuilder when database rolls back
331 * transaction due to deadlock or timeout.
332 *
333 * @return Returns an invalid query when error occurs, or the last replayed
334 * query on success.
335 */
336 QSqlQuery retryLastTransaction(bool rollbackFirst);
337
338private Q_SLOTS:
339 void sendKeepAliveQuery();
340
341protected:
342 static QThreadStorage<DataStore *> sInstances;
343
344 QString m_connectionName;
345 QSqlDatabase m_database;
346 bool m_dbOpened;
347 uint m_transactionLevel;
348 QVector<QPair<QSqlQuery, bool /* isBatch */> > m_transactionQueries;
349 QByteArray mSessionId;
350 NotificationCollector *mNotificationCollector;
351 QTimer *m_keepAliveTimer;
352 static bool s_hasForeignKeyConstraints;
353
354 // Gives QueryBuilder access to addQueryToTransaction() and retryLastTransaction()
355 friend class QueryBuilder;
356
357};
358
359} // namespace Server
360} // namespace Akonadi
361
362#endif
363