~ps-jenkins/telepathy-ofono/utopic-proposed

« back to all changes in this revision

Viewing changes to sqlitedatabase.cpp

  • Committer: Tiago Salem Herrmann
  • Date: 2013-12-11 14:52:03 UTC
  • mto: This revision was merged to the branch mainline in revision 57.
  • Revision ID: tiago.herrmann@canonical.com-20131211145203-qarhg90rntygc1v2
add support for delivery reports

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2013 Canonical, Ltd.
 
3
 *
 
4
 * Authors:
 
5
 *  Gustavo Pichorim Boiko <gustavo.boiko@canonical.com>
 
6
 *
 
7
 * This file is part of telepathy-ofono.
 
8
 *
 
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.
 
12
 *
 
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.
 
17
 *
 
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/>.
 
20
 */
 
21
 
 
22
#include "phoneutils_p.h"
 
23
#include "sqlite3.h"
 
24
#include "sqlitedatabase.h"
 
25
#include <QStandardPaths>
 
26
#include <QSqlDriver>
 
27
#include <QSqlQuery>
 
28
#include <QSqlError>
 
29
#include <QDebug>
 
30
#include <QFile>
 
31
#include <QDir>
 
32
 
 
33
Q_DECLARE_OPAQUE_POINTER(sqlite3*)
 
34
Q_DECLARE_METATYPE(sqlite3*)
 
35
 
 
36
// custom sqlite function "comparePhoneNumbers" used to compare IDs if necessary
 
37
void comparePhoneNumbers(sqlite3_context *context, int argc, sqlite3_value **argv)
 
38
{
 
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));
 
42
}
 
43
 
 
44
SQLiteDatabase::SQLiteDatabase(QObject *parent) :
 
45
    QObject(parent), mSchemaVersion(0)
 
46
{
 
47
    initializeDatabase();
 
48
}
 
49
 
 
50
SQLiteDatabase *SQLiteDatabase::instance()
 
51
{
 
52
    static SQLiteDatabase *self = new SQLiteDatabase();
 
53
    return self;
 
54
}
 
55
 
 
56
bool SQLiteDatabase::initializeDatabase()
 
57
{
 
58
    mDatabasePath = qgetenv("TP_OFONO_SQLITE_DBPATH");
 
59
 
 
60
    if (mDatabasePath.isEmpty()) {
 
61
        mDatabasePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
 
62
 
 
63
        QDir dir(mDatabasePath);
 
64
        if (!dir.exists("telepathy-ofono") && !dir.mkpath("telepathy-ofono")) {
 
65
            qCritical() << "Failed to create dir";
 
66
            return false;
 
67
        }
 
68
        dir.cd("telepathy-ofono");
 
69
 
 
70
        mDatabasePath = dir.absoluteFilePath("telepathy-ofono.sqlite");
 
71
    }
 
72
 
 
73
    mDatabase = QSqlDatabase::addDatabase("QSQLITE");
 
74
    mDatabase.setDatabaseName(mDatabasePath);
 
75
 
 
76
    // always run the createDatabase function at least during the development
 
77
    if (!createOrUpdateDatabase()) {
 
78
        qCritical() << "Failed to create or update the database";
 
79
        return false;
 
80
    }
 
81
 
 
82
    return true;
 
83
}
 
84
 
 
85
QSqlDatabase SQLiteDatabase::database() const
 
86
{
 
87
    return mDatabase;
 
88
}
 
89
 
 
90
bool SQLiteDatabase::beginTransation()
 
91
{
 
92
    return mDatabase.transaction();
 
93
}
 
94
 
 
95
bool SQLiteDatabase::finishTransaction()
 
96
{
 
97
    return mDatabase.commit();
 
98
}
 
99
 
 
100
bool SQLiteDatabase::rollbackTransaction()
 
101
{
 
102
    return mDatabase.rollback();
 
103
}
 
104
 
 
105
/// this method is to be used mainly by unit tests in order to clean up the database between
 
106
/// tests.
 
107
bool SQLiteDatabase::reopen()
 
108
{
 
109
    mDatabase.close();
 
110
    mDatabase.open();
 
111
 
 
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();
 
115
}
 
116
 
 
117
bool SQLiteDatabase::createOrUpdateDatabase()
 
118
{
 
119
    bool create = !QFile(mDatabasePath).exists();
 
120
 
 
121
    if (!mDatabase.open()) {
 
122
        return false;
 
123
    }
 
124
 
 
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);
 
128
 
 
129
    parseVersionInfo();
 
130
 
 
131
    QSqlQuery query(mDatabase);
 
132
 
 
133
    QStringList statements;
 
134
 
 
135
    if (create) {
 
136
         statements = parseSchemaFile(":/database/schema/schema.sql");
 
137
    } else {
 
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()) {
 
142
            return false;
 
143
        }
 
144
 
 
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)));
 
148
            ++upgradeToVersion;
 
149
        }
 
150
    }
 
151
 
 
152
    // if at this point needsUpdate is still false, it means the database is up-to-date
 
153
    if (statements.isEmpty()) {
 
154
        return true;
 
155
    }
 
156
 
 
157
    beginTransation();
 
158
 
 
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();
 
163
            return false;
 
164
        }
 
165
    }
 
166
 
 
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();
 
171
        return false;
 
172
    }
 
173
 
 
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();
 
177
        return false;
 
178
    }
 
179
 
 
180
    finishTransaction();
 
181
 
 
182
    return true;
 
183
}
 
184
 
 
185
QStringList SQLiteDatabase::parseSchemaFile(const QString &fileName)
 
186
{
 
187
    QFile schema(fileName);
 
188
    if (!schema.open(QFile::ReadOnly)) {
 
189
        qCritical() << "Failed to open " << fileName;
 
190
        return QStringList();
 
191
    }
 
192
 
 
193
    bool parsingBlock = false;
 
194
    QString statement;
 
195
    QStringList statements;
 
196
 
 
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
 
199
 
 
200
    QTextStream stream(&schema);
 
201
    while (!stream.atEnd()) {
 
202
        QString line = stream.readLine();
 
203
        bool statementEnded = false;
 
204
 
 
205
        statement += line;
 
206
 
 
207
        // check if we are parsing a trigger command
 
208
        if (line.trimmed().startsWith("CREATE TRIGGER", Qt::CaseInsensitive)) {
 
209
            parsingBlock = true;
 
210
        } else if (parsingBlock) {
 
211
            if (line.contains("END;")) {
 
212
                parsingBlock = false;
 
213
                statementEnded = true;
 
214
            }
 
215
        } else if (statement.contains(";")) {
 
216
            statementEnded = true;
 
217
        }
 
218
 
 
219
        statement += "\n";
 
220
 
 
221
        if (statementEnded) {
 
222
            statements.append(statement);
 
223
            statement.clear();
 
224
        }
 
225
    }
 
226
 
 
227
    return statements;
 
228
}
 
229
 
 
230
void SQLiteDatabase::parseVersionInfo()
 
231
{
 
232
    QFile schema(":/database/schema/version.info");
 
233
    if (!schema.open(QFile::ReadOnly)) {
 
234
        qDebug() << schema.error();
 
235
        qCritical() << "Failed to get database version";
 
236
    }
 
237
 
 
238
    QString version = schema.readAll();
 
239
    mSchemaVersion = version.toInt();
 
240
}