2
* Copyright (C) 2013 Canonical, Ltd.
5
* Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
7
* This file is part of telepathy-ofono.
9
* telepathy-ofono is free software; you can redistribute it and/or modify
10
* it under the terms of the GNU General Public License as published by
11
* the Free Software Foundation; version 3.
13
* telepathy-ofono is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU General Public License for more details.
18
* You should have received a copy of the GNU General Public License
19
* along with this program. If not, see <http://www.gnu.org/licenses/>.
22
#include "phoneutils_p.h"
24
#include "sqlitedatabase.h"
25
#include <QStandardPaths>
33
Q_DECLARE_OPAQUE_POINTER(sqlite3*)
34
Q_DECLARE_METATYPE(sqlite3*)
36
// custom sqlite function "comparePhoneNumbers" used to compare IDs if necessary
37
void comparePhoneNumbers(sqlite3_context *context, int argc, sqlite3_value **argv)
39
QString arg1((const char*)sqlite3_value_text(argv[0]));
40
QString arg2((const char*)sqlite3_value_text(argv[1]));
41
sqlite3_result_int(context, (int)PhoneUtils::comparePhoneNumbers(arg1, arg2));
44
SQLiteDatabase::SQLiteDatabase(QObject *parent) :
45
QObject(parent), mSchemaVersion(0)
50
SQLiteDatabase *SQLiteDatabase::instance()
52
static SQLiteDatabase *self = new SQLiteDatabase();
56
bool SQLiteDatabase::initializeDatabase()
58
mDatabasePath = qgetenv("TP_OFONO_SQLITE_DBPATH");
60
if (mDatabasePath.isEmpty()) {
61
mDatabasePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
63
QDir dir(mDatabasePath);
64
if (!dir.exists("telepathy-ofono") && !dir.mkpath("telepathy-ofono")) {
65
qCritical() << "Failed to create dir";
68
dir.cd("telepathy-ofono");
70
mDatabasePath = dir.absoluteFilePath("telepathy-ofono.sqlite");
73
mDatabase = QSqlDatabase::addDatabase("QSQLITE");
74
mDatabase.setDatabaseName(mDatabasePath);
76
// always run the createDatabase function at least during the development
77
if (!createOrUpdateDatabase()) {
78
qCritical() << "Failed to create or update the database";
85
QSqlDatabase SQLiteDatabase::database() const
90
bool SQLiteDatabase::beginTransation()
92
return mDatabase.transaction();
95
bool SQLiteDatabase::finishTransaction()
97
return mDatabase.commit();
100
bool SQLiteDatabase::rollbackTransaction()
102
return mDatabase.rollback();
105
/// this method is to be used mainly by unit tests in order to clean up the database between
107
bool SQLiteDatabase::reopen()
112
// make sure the database is up-to-date after reopening.
113
// this is mainly required for the memory backend used for testing
114
createOrUpdateDatabase();
117
bool SQLiteDatabase::createOrUpdateDatabase()
119
bool create = !QFile(mDatabasePath).exists();
121
if (!mDatabase.open()) {
125
// create the comparePhoneNumbers custom sqlite function
126
sqlite3 *handle = database().driver()->handle().value<sqlite3*>();
127
sqlite3_create_function(handle, "comparePhoneNumbers", 2, SQLITE_ANY, NULL, &comparePhoneNumbers, NULL, NULL);
131
QSqlQuery query(mDatabase);
133
QStringList statements;
136
statements = parseSchemaFile(":/database/schema/schema.sql");
138
// if the database already exists, we don´t need to create the tables
139
// only check if an update is needed
140
query.exec("SELECT * FROM schema_version");
141
if (!query.exec() || !query.next()) {
145
int upgradeToVersion = query.value(0).toInt() + 1;
146
while (upgradeToVersion <= mSchemaVersion) {
147
statements += parseSchemaFile(QString(":/database/schema/v%1.sql").arg(QString::number(upgradeToVersion)));
152
// if at this point needsUpdate is still false, it means the database is up-to-date
153
if (statements.isEmpty()) {
159
Q_FOREACH(const QString &statement, statements) {
160
if (!query.exec(statement)) {
161
qCritical() << "Failed to create or update database. SQL Statements:" << query.lastQuery() << "Error:" << query.lastError();
162
rollbackTransaction();
167
// now set the new database schema version
168
if (!query.exec("DELETE FROM schema_version")) {
169
qCritical() << "Failed to remove previous schema versions. SQL Statement:" << query.lastQuery() << "Error:" << query.lastError();
170
rollbackTransaction();
174
if (!query.exec(QString("INSERT INTO schema_version VALUES (%1)").arg(mSchemaVersion))) {
175
qCritical() << "Failed to insert new schema version. SQL Statement:" << query.lastQuery() << "Error:" << query.lastError();
176
rollbackTransaction();
185
QStringList SQLiteDatabase::parseSchemaFile(const QString &fileName)
187
QFile schema(fileName);
188
if (!schema.open(QFile::ReadOnly)) {
189
qCritical() << "Failed to open " << fileName;
190
return QStringList();
193
bool parsingBlock = false;
195
QStringList statements;
197
// FIXME: this parser is very basic, it needs to be improved in the future
198
// it does a lot of assumptions based on the structure of the schema.sql file
200
QTextStream stream(&schema);
201
while (!stream.atEnd()) {
202
QString line = stream.readLine();
203
bool statementEnded = false;
207
// check if we are parsing a trigger command
208
if (line.trimmed().startsWith("CREATE TRIGGER", Qt::CaseInsensitive)) {
210
} else if (parsingBlock) {
211
if (line.contains("END;")) {
212
parsingBlock = false;
213
statementEnded = true;
215
} else if (statement.contains(";")) {
216
statementEnded = true;
221
if (statementEnded) {
222
statements.append(statement);
230
void SQLiteDatabase::parseVersionInfo()
232
QFile schema(":/database/schema/version.info");
233
if (!schema.open(QFile::ReadOnly)) {
234
qDebug() << schema.error();
235
qCritical() << "Failed to get database version";
238
QString version = schema.readAll();
239
mSchemaVersion = version.toInt();