fd4356efd9f96b6679942960796815c8dac8ed4f
[profile/ivi/qtdeclarative.git] / src / declarative / qml / qdeclarativesqldatabase.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qdeclarativesqldatabase_p.h"
43
44 #include "qdeclarativeengine.h"
45 #include "qdeclarativeengine_p.h"
46 #include <private/qdeclarativerefcount_p.h>
47
48 #include <QtCore/qobject.h>
49 #include <QtSql/qsqldatabase.h>
50 #include <QtSql/qsqlquery.h>
51 #include <QtSql/qsqlerror.h>
52 #include <QtSql/qsqlrecord.h>
53 #include <QtSql/qsqlfield.h>
54 #include <QtCore/qstandardpaths.h>
55 #include <QtCore/qstack.h>
56 #include <QtCore/qcryptographichash.h>
57 #include <QtCore/qsettings.h>
58 #include <QtCore/qdir.h>
59 #include <QtCore/qdebug.h>
60
61 #include <private/qv8engine_p.h>
62 #include <private/qv8sqlerrors_p.h>
63
64 QT_BEGIN_NAMESPACE
65
66
67 #define THROW_SQL(error, desc)
68
69 #define V8THROW_SQL(error, desc) \
70 { \
71     v8::Local<v8::Value> v = v8::Exception::Error(engine->toString(desc)); \
72     v->ToObject()->Set(v8::String::New("code"), v8::Integer::New(error)); \
73     v8::ThrowException(v); \
74     return v8::Handle<v8::Value>(); \
75 }
76
77 #define V8THROW_REFERENCE(string) { \
78     v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
79     return v8::Handle<v8::Value>(); \
80 }
81
82 #define V8THROW_REFERENCE_VOID(string) { \
83     v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
84     return; \
85 }
86
87 struct QDeclarativeSqlDatabaseData {
88     QDeclarativeSqlDatabaseData(QV8Engine *engine);
89     ~QDeclarativeSqlDatabaseData();
90
91     v8::Persistent<v8::Function> constructor;
92     v8::Persistent<v8::Function> queryConstructor;
93     v8::Persistent<v8::Function> rowsConstructor;
94
95     static inline QDeclarativeSqlDatabaseData *data(QV8Engine *e) {
96         return (QDeclarativeSqlDatabaseData *)e->sqlDatabaseData();
97     }
98     static inline QDeclarativeSqlDatabaseData *data(void *d) {
99         return (QDeclarativeSqlDatabaseData *)d;
100     }
101 };
102
103 class QV8SqlDatabaseResource : public QV8ObjectResource
104 {
105     V8_RESOURCE_TYPE(SQLDatabaseType)
106
107 public:
108     enum Type { Database, Query, Rows };
109
110     QV8SqlDatabaseResource(QV8Engine *e) 
111     : QV8ObjectResource(e), type(Database), inTransaction(false), readonly(false), forwardOnly(false) {}
112
113     Type type;
114     QSqlDatabase database;
115
116     QString version; // type == Database
117
118     bool inTransaction; // type == Query
119     bool readonly;   // type == Query
120
121     QSqlQuery query; // type == Rows
122     bool forwardOnly; // type == Rows
123 };
124
125 static v8::Handle<v8::Value> qmlsqldatabase_version(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
126 {
127     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
128     if (!r || r->type != QV8SqlDatabaseResource::Database)
129         V8THROW_REFERENCE("Not a SQLDatabase object");
130
131     return r->engine->toString(r->version);
132 }
133
134 static v8::Handle<v8::Value> qmlsqldatabase_rows_length(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
135 {
136     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
137     if (!r || r->type != QV8SqlDatabaseResource::Rows)
138         V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
139
140     int s = r->query.size();
141     if (s < 0) {
142         // Inefficient
143         if (r->query.last()) {
144             s = r->query.at() + 1;
145         } else {
146             s = 0;
147         }
148     }
149     return v8::Integer::New(s);
150 }
151
152 static v8::Handle<v8::Value> qmlsqldatabase_rows_forwardOnly(v8::Local<v8::String> /* property */,
153                                                              const v8::AccessorInfo& info)
154 {
155     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
156     if (!r || r->type != QV8SqlDatabaseResource::Rows) 
157         V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
158
159     return v8::Boolean::New(r->query.isForwardOnly());
160 }
161
162 static void qmlsqldatabase_rows_setForwardOnly(v8::Local<v8::String> /* property */,
163                                                v8::Local<v8::Value> value,
164                                                const v8::AccessorInfo& info)
165 {
166     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
167     if (!r || r->type != QV8SqlDatabaseResource::Rows)
168         V8THROW_REFERENCE_VOID("Not a SQLDatabase::Rows object");
169
170     r->query.setForwardOnly(value->BooleanValue());
171 }
172
173 QDeclarativeSqlDatabaseData::~QDeclarativeSqlDatabaseData()
174 {
175     qPersistentDispose(constructor);
176     qPersistentDispose(queryConstructor);
177     qPersistentDispose(rowsConstructor);
178 }
179
180 static QString qmlsqldatabase_databasesPath(QV8Engine *engine)
181 {
182     return engine->engine()->offlineStoragePath() +
183            QDir::separator() + QLatin1String("Databases");
184 }
185
186 static void qmlsqldatabase_initDatabasesPath(QV8Engine *engine)
187 {
188     QDir().mkpath(qmlsqldatabase_databasesPath(engine));
189 }
190
191 static QString qmlsqldatabase_databaseFile(const QString& connectionName, QV8Engine *engine)
192 {
193     return qmlsqldatabase_databasesPath(engine) + QDir::separator() + connectionName;
194 }
195
196 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(QV8SqlDatabaseResource *r, uint32_t index)
197 {
198     if (r->query.at() == (int)index || r->query.seek(index)) {
199
200         QSqlRecord record = r->query.record();
201         // XXX optimize
202         v8::Local<v8::Object> row = v8::Object::New();
203         for (int ii = 0; ii < record.count(); ++ii) {
204             QVariant v = record.value(ii);
205             if (v.isNull()) {
206                 row->Set(r->engine->toString(record.fieldName(ii)), v8::Null());
207             } else {
208                 row->Set(r->engine->toString(record.fieldName(ii)),
209                          r->engine->fromVariant(v));
210             }
211         }
212         return row;
213     } else {
214         return v8::Undefined();
215     }
216 }
217
218 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(uint32_t index, const v8::AccessorInfo& info)
219 {
220     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
221     if (!r || r->type != QV8SqlDatabaseResource::Rows)
222         V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
223
224     return qmlsqldatabase_rows_index(r, index);
225 }
226
227 static v8::Handle<v8::Value> qmlsqldatabase_rows_item(const v8::Arguments& args)
228 {
229     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
230     if (!r || r->type != QV8SqlDatabaseResource::Rows)
231         V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
232
233     return qmlsqldatabase_rows_index(r, args.Length()?args[0]->Uint32Value():0);
234 }
235
236 static v8::Handle<v8::Value> qmlsqldatabase_executeSql(const v8::Arguments& args)
237 {
238     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
239     if (!r || r->type != QV8SqlDatabaseResource::Query)
240         V8THROW_REFERENCE("Not a SQLDatabase::Query object");
241
242     QV8Engine *engine = r->engine;
243
244     if (!r->inTransaction)
245         V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,QDeclarativeEngine::tr("executeSql called outside transaction()"));
246
247     QSqlDatabase db = r->database;
248
249     QString sql = engine->toString(args[0]);
250
251     if (r->readonly && !sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
252         V8THROW_SQL(SQLEXCEPTION_SYNTAX_ERR, QDeclarativeEngine::tr("Read-only Transaction"));
253     }
254
255     QSqlQuery query(db);
256     bool err = false;
257
258     v8::Handle<v8::Value> result = v8::Undefined();
259
260     if (query.prepare(sql)) {
261         if (args.Length() > 1) {
262             v8::Local<v8::Value> values = args[1];
263             if (values->IsArray()) {
264                 v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(values);
265                 uint32_t size = array->Length();
266                 for (uint32_t ii = 0; ii < size; ++ii) 
267                     query.bindValue(ii, engine->toVariant(array->Get(ii), -1));
268             } else if (values->IsObject() && !values->ToObject()->GetExternalResource()) {
269                 v8::Local<v8::Object> object = values->ToObject();
270                 v8::Local<v8::Array> names = object->GetPropertyNames();
271                 uint32_t size = names->Length();
272                 for (uint32_t ii = 0; ii < size; ++ii) 
273                     query.bindValue(engine->toString(names->Get(ii)), 
274                                     engine->toVariant(object->Get(names->Get(ii)), -1));
275             } else {
276                 query.bindValue(0, engine->toVariant(values, -1));
277             }
278         }
279         if (query.exec()) {
280             v8::Handle<v8::Object> rows = QDeclarativeSqlDatabaseData::data(engine)->rowsConstructor->NewInstance();
281             QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
282             r->type = QV8SqlDatabaseResource::Rows;
283             r->database = db;
284             r->query = query;
285             rows->SetExternalResource(r);
286
287             v8::Local<v8::Object> resultObject = v8::Object::New();
288             result = resultObject;
289             // XXX optimize
290             resultObject->Set(v8::String::New("rowsAffected"), v8::Integer::New(query.numRowsAffected()));
291             resultObject->Set(v8::String::New("insertId"), engine->toString(query.lastInsertId().toString()));
292             resultObject->Set(v8::String::New("rows"), rows);
293         } else {
294             err = true;
295         }
296     } else {
297         err = true;
298     }
299     if (err)
300         V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,query.lastError().text());
301
302     return result;
303 }
304
305 static v8::Handle<v8::Value> qmlsqldatabase_changeVersion(const v8::Arguments& args)
306 {
307     if (args.Length() < 2)
308         return v8::Undefined();
309
310     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
311     if (!r || r->type != QV8SqlDatabaseResource::Database)
312         V8THROW_REFERENCE("Not a SQLDatabase object");
313
314     QV8Engine *engine = r->engine;
315
316     QSqlDatabase db = r->database;
317     QString from_version = engine->toString(args[0]);
318     QString to_version = engine->toString(args[1]);
319     v8::Handle<v8::Value> callback = args[2];
320
321     if (from_version != r->version) 
322         V8THROW_SQL(SQLEXCEPTION_VERSION_ERR, QDeclarativeEngine::tr("Version mismatch: expected %1, found %2").arg(from_version).arg(r->version));
323
324     v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->queryConstructor->NewInstance();
325     QV8SqlDatabaseResource *r2 = new QV8SqlDatabaseResource(engine);
326     r2->type = QV8SqlDatabaseResource::Query;
327     r2->database = db;
328     r2->version = r->version;
329     r2->inTransaction = true;
330     instance->SetExternalResource(r2);
331
332     bool ok = true;
333     if (callback->IsFunction()) {
334         ok = false;
335         db.transaction();
336
337         v8::TryCatch tc;
338         v8::Handle<v8::Value> callbackArgs[] = { instance };
339         v8::Handle<v8::Function>::Cast(callback)->Call(engine->global(), 1, callbackArgs);
340
341         if (tc.HasCaught()) {
342             db.rollback();
343             tc.ReThrow();
344             return v8::Handle<v8::Value>();
345         } else if (!db.commit()) {
346             db.rollback();
347             V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QDeclarativeEngine::tr("SQL transaction failed"));
348         } else {
349             ok = true;
350         }
351     }
352
353     r2->inTransaction = false;
354
355     if (ok) {
356         r2->version = to_version;
357 #ifndef QT_NO_SETTINGS
358         QSettings ini(qmlsqldatabase_databaseFile(db.connectionName(),engine) + QLatin1String(".ini"), QSettings::IniFormat);
359         ini.setValue(QLatin1String("Version"), to_version);
360 #endif
361     }
362
363     return v8::Undefined();
364 }
365
366 static v8::Handle<v8::Value> qmlsqldatabase_transaction_shared(const v8::Arguments& args, bool readOnly)
367 {
368     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
369     if (!r || r->type != QV8SqlDatabaseResource::Database)
370         V8THROW_REFERENCE("Not a SQLDatabase object");
371
372     QV8Engine *engine = r->engine;
373
374     if (args.Length() == 0 || !args[0]->IsFunction())
375         V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QDeclarativeEngine::tr("transaction: missing callback"));
376     
377     QSqlDatabase db = r->database;
378     v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(args[0]);
379
380     v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->queryConstructor->NewInstance();
381     QV8SqlDatabaseResource *q = new QV8SqlDatabaseResource(engine);
382     q->type = QV8SqlDatabaseResource::Query;
383     q->database = db;
384     q->readonly = readOnly;
385     q->inTransaction = true;
386     instance->SetExternalResource(q);
387
388     db.transaction();
389     v8::TryCatch tc;
390     v8::Handle<v8::Value> callbackArgs[] = { instance };
391     callback->Call(engine->global(), 1, callbackArgs);
392
393     q->inTransaction = false;
394
395     if (tc.HasCaught()) {
396         db.rollback();
397         tc.ReThrow();
398         return v8::Handle<v8::Value>();
399     } else if (!db.commit()) {
400         db.rollback();
401     }
402
403     return v8::Undefined();
404 }
405
406 static v8::Handle<v8::Value> qmlsqldatabase_transaction(const v8::Arguments& args)
407 {
408     return qmlsqldatabase_transaction_shared(args, false);
409 }
410
411 static v8::Handle<v8::Value> qmlsqldatabase_read_transaction(const v8::Arguments& args)
412 {
413     return qmlsqldatabase_transaction_shared(args, true);
414 }
415
416 /*
417     Currently documented in doc/src/declarative/globalobject.qdoc
418 */
419 static v8::Handle<v8::Value> qmlsqldatabase_open_sync(const v8::Arguments& args)
420 {
421 #ifndef QT_NO_SETTINGS
422     QV8Engine *engine = V8ENGINE();
423     qmlsqldatabase_initDatabasesPath(engine);
424
425     QSqlDatabase database;
426
427     QString dbname = engine->toString(args[0]);
428     QString dbversion = engine->toString(args[1]);
429     QString dbdescription = engine->toString(args[2]);
430     int dbestimatedsize = args[3]->Int32Value();
431     v8::Handle<v8::Value> dbcreationCallback = args[4];
432
433     QCryptographicHash md5(QCryptographicHash::Md5);
434     md5.addData(dbname.toUtf8());
435     QString dbid(QLatin1String(md5.result().toHex()));
436
437     QString basename = qmlsqldatabase_databaseFile(dbid, engine);
438     bool created = false;
439     QString version = dbversion;
440
441     {
442         QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat);
443
444         if (QSqlDatabase::connectionNames().contains(dbid)) {
445             database = QSqlDatabase::database(dbid);
446             version = ini.value(QLatin1String("Version")).toString();
447             if (version != dbversion && !dbversion.isEmpty() && !version.isEmpty())
448                 V8THROW_SQL(SQLEXCEPTION_VERSION_ERR, QDeclarativeEngine::tr("SQL: database version mismatch"));
449         } else {
450             created = !QFile::exists(basename+QLatin1String(".sqlite"));
451             database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
452             if (created) {
453                 ini.setValue(QLatin1String("Name"), dbname);
454                 if (dbcreationCallback->IsFunction())
455                     version = QString();
456                 ini.setValue(QLatin1String("Version"), version);
457                 ini.setValue(QLatin1String("Description"), dbdescription);
458                 ini.setValue(QLatin1String("EstimatedSize"), dbestimatedsize);
459                 ini.setValue(QLatin1String("Driver"), QLatin1String("QSQLITE"));
460             } else {
461                 if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
462                     // Incompatible
463                     V8THROW_SQL(SQLEXCEPTION_VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
464                 }
465                 version = ini.value(QLatin1String("Version")).toString();
466             }
467             database.setDatabaseName(basename+QLatin1String(".sqlite"));
468         }
469         if (!database.isOpen())
470             database.open();
471     }
472
473     v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->constructor->NewInstance();
474     QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
475     r->database = database;
476     r->version = version;
477     instance->SetExternalResource(r);
478
479     if (created && dbcreationCallback->IsFunction()) {
480         v8::TryCatch tc;
481         v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(dbcreationCallback);
482         v8::Handle<v8::Value> args[] = { instance };
483         callback->Call(engine->global(), 1, args);
484         if (tc.HasCaught()) {
485             tc.ReThrow();
486             return v8::Handle<v8::Value>();
487         }
488     }
489
490     return instance;
491 #else
492     return v8::Undefined();
493 #endif // QT_NO_SETTINGS
494 }
495
496 QDeclarativeSqlDatabaseData::QDeclarativeSqlDatabaseData(QV8Engine *engine)
497 {
498     {
499     v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
500     ft->InstanceTemplate()->SetHasExternalResource(true);
501     ft->PrototypeTemplate()->Set(v8::String::New("transaction"), 
502                                  V8FUNCTION(qmlsqldatabase_transaction, engine));
503     ft->PrototypeTemplate()->Set(v8::String::New("readTransaction"), 
504                                  V8FUNCTION(qmlsqldatabase_read_transaction, engine));
505     ft->PrototypeTemplate()->SetAccessor(v8::String::New("version"), qmlsqldatabase_version);
506     ft->PrototypeTemplate()->Set(v8::String::New("changeVersion"), 
507                                  V8FUNCTION(qmlsqldatabase_changeVersion, engine));
508     constructor = qPersistentNew<v8::Function>(ft->GetFunction());
509     }
510
511     {
512     v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
513     ft->InstanceTemplate()->SetHasExternalResource(true);
514     ft->PrototypeTemplate()->Set(v8::String::New("executeSql"), 
515                                  V8FUNCTION(qmlsqldatabase_executeSql, engine));
516     queryConstructor = qPersistentNew<v8::Function>(ft->GetFunction());
517     }
518     {
519     v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
520     ft->InstanceTemplate()->SetHasExternalResource(true);
521     ft->PrototypeTemplate()->Set(v8::String::New("item"), V8FUNCTION(qmlsqldatabase_rows_item, engine));
522     ft->PrototypeTemplate()->SetAccessor(v8::String::New("length"), qmlsqldatabase_rows_length);
523     ft->InstanceTemplate()->SetAccessor(v8::String::New("forwardOnly"), qmlsqldatabase_rows_forwardOnly, 
524                                         qmlsqldatabase_rows_setForwardOnly);
525     ft->InstanceTemplate()->SetIndexedPropertyHandler(qmlsqldatabase_rows_index);
526     rowsConstructor = qPersistentNew<v8::Function>(ft->GetFunction());
527     }
528 }
529
530 void *qt_add_qmlsqldatabase(QV8Engine *engine)
531 {
532     v8::Local<v8::Function> openDatabase = V8FUNCTION(qmlsqldatabase_open_sync, engine);
533     engine->global()->Set(v8::String::New("openDatabaseSync"), openDatabase);
534
535     return (void *)new QDeclarativeSqlDatabaseData(engine);
536 }
537
538 void qt_rem_qmlsqldatabase(QV8Engine * /* engine */, void *d)
539 {
540     QDeclarativeSqlDatabaseData *data = (QDeclarativeSqlDatabaseData *)d;
541     delete data;
542 }
543
544 /*
545 HTML5 "spec" says "rs.rows[n]", but WebKit only impelments "rs.rows.item(n)". We do both (and property iterator).
546 We add a "forwardOnly" property that stops Qt caching results (code promises to only go forward
547 through the data.
548 */
549
550 QT_END_NAMESPACE