1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "private/qdeclarativesqldatabase_p.h"
44 #include "qdeclarativeengine.h"
45 #include "private/qdeclarativeengine_p.h"
46 #include "private/qdeclarativerefcount_p.h"
47 #include "private/qdeclarativeengine_p.h"
49 #include <QtCore/qobject.h>
50 #include <QtSql/qsqldatabase.h>
51 #include <QtSql/qsqlquery.h>
52 #include <QtSql/qsqlerror.h>
53 #include <QtSql/qsqlrecord.h>
54 #include <QtSql/qsqlfield.h>
55 #include <QtGui/qdesktopservices.h>
56 #include <QtCore/qstack.h>
57 #include <QtCore/qcryptographichash.h>
58 #include <QtCore/qsettings.h>
59 #include <QtCore/qdir.h>
60 #include <QtCore/qdebug.h>
62 #include <private/qv8engine_p.h>
77 static const char* sqlerror[] = {
89 #define THROW_SQL(error, desc)
91 #define V8THROW_SQL(error, desc) \
93 v8::Local<v8::Value> v = v8::Exception::Error(engine->toString(desc)); \
94 v->ToObject()->Set(v8::String::New("code"), v8::Integer::New(error)); \
95 v8::ThrowException(v); \
96 return v8::Handle<v8::Value>(); \
99 #define V8THROW_REFERENCE(string) { \
100 v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
101 return v8::Handle<v8::Value>(); \
104 #define V8THROW_REFERENCE_VOID(string) { \
105 v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
109 struct QDeclarativeSqlDatabaseData {
110 QDeclarativeSqlDatabaseData(QV8Engine *engine);
111 ~QDeclarativeSqlDatabaseData();
113 QString offlineStoragePath;
114 v8::Persistent<v8::Function> constructor;
115 v8::Persistent<v8::Function> queryConstructor;
116 v8::Persistent<v8::Function> rowsConstructor;
118 static inline QDeclarativeSqlDatabaseData *data(QV8Engine *e) {
119 return (QDeclarativeSqlDatabaseData *)e->sqlDatabaseData();
121 static inline QDeclarativeSqlDatabaseData *data(void *d) {
122 return (QDeclarativeSqlDatabaseData *)d;
126 class QV8SqlDatabaseResource : public QV8ObjectResource
128 V8_RESOURCE_TYPE(SQLDatabaseType)
131 enum Type { Database, Query, Rows };
133 QV8SqlDatabaseResource(QV8Engine *e)
134 : QV8ObjectResource(e), type(Database), inTransaction(false), readonly(false), forwardOnly(false) {}
137 QSqlDatabase database;
139 QString version; // type == Database
141 bool inTransaction; // type == Query
142 bool readonly; // type == Query
144 QSqlQuery query; // type == Rows
145 bool forwardOnly; // type == Rows
148 static v8::Handle<v8::Value> qmlsqldatabase_version(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
150 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
151 if (!r || r->type != QV8SqlDatabaseResource::Database)
152 V8THROW_REFERENCE("Not a SQLDatabase object");
154 return r->engine->toString(r->version);
157 static v8::Handle<v8::Value> qmlsqldatabase_rows_length(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
159 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
160 if (!r || r->type != QV8SqlDatabaseResource::Rows)
161 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
163 int s = r->query.size();
166 if (r->query.last()) {
167 s = r->query.at() + 1;
172 return v8::Integer::New(s);
175 static v8::Handle<v8::Value> qmlsqldatabase_rows_forwardOnly(v8::Local<v8::String> /* property */,
176 const v8::AccessorInfo& info)
178 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
179 if (!r || r->type != QV8SqlDatabaseResource::Rows)
180 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
182 return v8::Boolean::New(r->query.isForwardOnly());
185 static void qmlsqldatabase_rows_setForwardOnly(v8::Local<v8::String> /* property */,
186 v8::Local<v8::Value> value,
187 const v8::AccessorInfo& info)
189 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
190 if (!r || r->type != QV8SqlDatabaseResource::Rows)
191 V8THROW_REFERENCE_VOID("Not a SQLDatabase::Rows object");
193 r->query.setForwardOnly(value->BooleanValue());
196 QDeclarativeSqlDatabaseData::~QDeclarativeSqlDatabaseData()
198 qPersistentDispose(constructor);
199 qPersistentDispose(queryConstructor);
202 static QString qmlsqldatabase_databasesPath(QV8Engine *engine)
204 return QDeclarativeSqlDatabaseData::data(engine)->offlineStoragePath +
205 QDir::separator() + QLatin1String("Databases");
208 static void qmlsqldatabase_initDatabasesPath(QV8Engine *engine)
210 QDir().mkpath(qmlsqldatabase_databasesPath(engine));
213 static QString qmlsqldatabase_databaseFile(const QString& connectionName, QV8Engine *engine)
215 return qmlsqldatabase_databasesPath(engine) + QDir::separator() + connectionName;
218 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(QV8SqlDatabaseResource *r, uint32_t index)
220 if (r->query.at() == index || r->query.seek(index)) {
222 QSqlRecord record = r->query.record();
224 v8::Local<v8::Object> row = v8::Object::New();
225 for (int ii = 0; ii < record.count(); ++ii) {
226 QVariant v = record.value(ii);
228 row->Set(r->engine->toString(record.fieldName(ii)), v8::Null());
230 row->Set(r->engine->toString(record.fieldName(ii)),
231 r->engine->fromVariant(v));
236 return v8::Undefined();
240 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(uint32_t index, const v8::AccessorInfo& info)
242 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
243 if (!r || r->type != QV8SqlDatabaseResource::Rows)
244 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
246 return qmlsqldatabase_rows_index(r, index);
249 static v8::Handle<v8::Value> qmlsqldatabase_rows_item(const v8::Arguments& args)
251 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
252 if (!r || r->type != QV8SqlDatabaseResource::Rows)
253 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
255 return qmlsqldatabase_rows_index(r, args.Length()?args[0]->Uint32Value():0);
258 static v8::Handle<v8::Value> qmlsqldatabase_executeSql(const v8::Arguments& args)
260 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
261 if (!r || r->type != QV8SqlDatabaseResource::Query)
262 V8THROW_REFERENCE("Not a SQLDatabase::Query object");
264 QV8Engine *engine = r->engine;
266 if (!r->inTransaction)
267 V8THROW_SQL(DATABASE_ERR,QDeclarativeEngine::tr("executeSql called outside transaction()"));
269 QSqlDatabase db = r->database;
271 QString sql = engine->toString(args[0]);
273 if (r->readonly && !sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
274 V8THROW_SQL(SYNTAX_ERR, QDeclarativeEngine::tr("Read-only Transaction"));
280 v8::Handle<v8::Value> result = v8::Undefined();
282 if (query.prepare(sql)) {
283 if (args.Length() > 1) {
284 v8::Local<v8::Value> values = args[1];
285 if (values->IsArray()) {
286 v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(values);
287 uint32_t size = array->Length();
288 for (uint32_t ii = 0; ii < size; ++ii)
289 query.bindValue(ii, engine->toVariant(array->Get(ii), -1));
290 } else if (values->IsObject() && !values->ToObject()->GetExternalResource()) {
291 v8::Local<v8::Object> object = values->ToObject();
292 v8::Local<v8::Array> names = object->GetPropertyNames();
293 uint32_t size = names->Length();
294 for (uint32_t ii = 0; ii < size; ++ii)
295 query.bindValue(engine->toString(names->Get(ii)),
296 engine->toVariant(object->Get(names->Get(ii)), -1));
298 query.bindValue(0, engine->toVariant(values, -1));
302 v8::Handle<v8::Object> rows = QDeclarativeSqlDatabaseData::data(engine)->rowsConstructor->NewInstance();
303 QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
304 r->type = QV8SqlDatabaseResource::Rows;
307 rows->SetExternalResource(r);
309 v8::Local<v8::Object> resultObject = v8::Object::New();
310 result = resultObject;
312 resultObject->Set(v8::String::New("rowsAffected"), v8::Integer::New(query.numRowsAffected()));
313 resultObject->Set(v8::String::New("insertId"), engine->toString(query.lastInsertId().toString()));
314 resultObject->Set(v8::String::New("rows"), rows);
322 V8THROW_SQL(DATABASE_ERR,query.lastError().text());
327 static v8::Handle<v8::Value> qmlsqldatabase_changeVersion(const v8::Arguments& args)
329 if (args.Length() < 2)
330 return v8::Undefined();
332 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
333 if (!r || r->type != QV8SqlDatabaseResource::Database)
334 V8THROW_REFERENCE("Not a SQLDatabase object");
336 QV8Engine *engine = r->engine;
338 QSqlDatabase db = r->database;
339 QString from_version = engine->toString(args[0]);
340 QString to_version = engine->toString(args[1]);
341 v8::Handle<v8::Value> callback = args[2];
343 if (from_version != r->version)
344 V8THROW_SQL(VERSION_ERR, QDeclarativeEngine::tr("Version mismatch: expected %1, found %2").arg(from_version).arg(r->version));
346 v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->queryConstructor->NewInstance();
347 QV8SqlDatabaseResource *r2 = new QV8SqlDatabaseResource(engine);
348 r2->type = QV8SqlDatabaseResource::Query;
350 r2->version = r->version;
351 r2->inTransaction = true;
352 instance->SetExternalResource(r2);
355 if (callback->IsFunction()) {
360 v8::Handle<v8::Value> callbackArgs[] = { instance };
361 v8::Handle<v8::Function>::Cast(callback)->Call(engine->global(), 1, callbackArgs);
363 if (tc.HasCaught()) {
366 return v8::Handle<v8::Value>();
367 } else if (!db.commit()) {
369 V8THROW_SQL(UNKNOWN_ERR,QDeclarativeEngine::tr("SQL transaction failed"));
375 r2->inTransaction = false;
378 r2->version = to_version;
379 #ifndef QT_NO_SETTINGS
380 QSettings ini(qmlsqldatabase_databaseFile(db.connectionName(),engine) + QLatin1String(".ini"), QSettings::IniFormat);
381 ini.setValue(QLatin1String("Version"), to_version);
385 return v8::Undefined();
388 static v8::Handle<v8::Value> qmlsqldatabase_transaction_shared(const v8::Arguments& args, bool readOnly)
390 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
391 if (!r || r->type != QV8SqlDatabaseResource::Database)
392 V8THROW_REFERENCE("Not a SQLDatabase object");
394 QV8Engine *engine = r->engine;
396 if (args.Length() == 0 || !args[0]->IsFunction())
397 V8THROW_SQL(UNKNOWN_ERR,QDeclarativeEngine::tr("transaction: missing callback"));
399 QSqlDatabase db = r->database;
400 v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(args[0]);
402 v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->queryConstructor->NewInstance();
403 QV8SqlDatabaseResource *q = new QV8SqlDatabaseResource(engine);
404 q->type = QV8SqlDatabaseResource::Query;
406 q->readonly = readOnly;
407 q->inTransaction = true;
408 instance->SetExternalResource(q);
412 v8::Handle<v8::Value> callbackArgs[] = { instance };
413 callback->Call(engine->global(), 1, callbackArgs);
415 q->inTransaction = false;
417 if (tc.HasCaught()) {
420 return v8::Handle<v8::Value>();
421 } else if (!db.commit()) {
425 return v8::Undefined();
428 static v8::Handle<v8::Value> qmlsqldatabase_transaction(const v8::Arguments& args)
430 return qmlsqldatabase_transaction_shared(args, false);
433 static v8::Handle<v8::Value> qmlsqldatabase_read_transaction(const v8::Arguments& args)
435 return qmlsqldatabase_transaction_shared(args, true);
439 Currently documented in doc/src/declarative/globalobject.qdoc
441 static v8::Handle<v8::Value> qmlsqldatabase_open_sync(const v8::Arguments& args)
443 #ifndef QT_NO_SETTINGS
444 QV8Engine *engine = V8ENGINE();
445 qmlsqldatabase_initDatabasesPath(engine);
447 QSqlDatabase database;
449 QString dbname = engine->toString(args[0]);
450 QString dbversion = engine->toString(args[1]);
451 QString dbdescription = engine->toString(args[2]);
452 int dbestimatedsize = args[3]->Int32Value();
453 v8::Handle<v8::Value> dbcreationCallback = args[4];
455 QCryptographicHash md5(QCryptographicHash::Md5);
456 md5.addData(dbname.toUtf8());
457 QString dbid(QLatin1String(md5.result().toHex()));
459 QString basename = qmlsqldatabase_databaseFile(dbid, engine);
460 bool created = false;
461 QString version = dbversion;
464 QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat);
466 if (QSqlDatabase::connectionNames().contains(dbid)) {
467 database = QSqlDatabase::database(dbid);
468 version = ini.value(QLatin1String("Version")).toString();
469 if (version != dbversion && !dbversion.isEmpty() && !version.isEmpty())
470 V8THROW_SQL(VERSION_ERR, QDeclarativeEngine::tr("SQL: database version mismatch"));
472 created = !QFile::exists(basename+QLatin1String(".sqlite"));
473 database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
475 ini.setValue(QLatin1String("Name"), dbname);
476 if (dbcreationCallback->IsFunction())
478 ini.setValue(QLatin1String("Version"), version);
479 ini.setValue(QLatin1String("Description"), dbdescription);
480 ini.setValue(QLatin1String("EstimatedSize"), dbestimatedsize);
481 ini.setValue(QLatin1String("Driver"), QLatin1String("QSQLITE"));
483 if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
485 V8THROW_SQL(VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
487 version = ini.value(QLatin1String("Version")).toString();
489 database.setDatabaseName(basename+QLatin1String(".sqlite"));
491 if (!database.isOpen())
495 v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->constructor->NewInstance();
496 QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
497 r->database = database;
498 r->version = version;
499 instance->SetExternalResource(r);
501 if (created && dbcreationCallback->IsFunction()) {
503 v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(dbcreationCallback);
504 v8::Handle<v8::Value> args[] = { instance };
505 callback->Call(engine->global(), 1, args);
506 if (tc.HasCaught()) {
508 return v8::Handle<v8::Value>();
514 return v8::Undefined();
515 #endif // QT_NO_SETTINGS
518 QDeclarativeSqlDatabaseData::QDeclarativeSqlDatabaseData(QV8Engine *engine)
520 QString dataLocation = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
521 offlineStoragePath = dataLocation.replace(QLatin1Char('/'), QDir::separator()) +
522 QDir::separator() + QLatin1String("QML") +
523 QDir::separator() + QLatin1String("OfflineStorage");
526 v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
527 ft->InstanceTemplate()->SetHasExternalResource(true);
528 ft->PrototypeTemplate()->Set(v8::String::New("transaction"),
529 V8FUNCTION(qmlsqldatabase_transaction, engine));
530 ft->PrototypeTemplate()->Set(v8::String::New("readTransaction"),
531 V8FUNCTION(qmlsqldatabase_read_transaction, engine));
532 ft->PrototypeTemplate()->SetAccessor(v8::String::New("version"), qmlsqldatabase_version);
533 ft->PrototypeTemplate()->Set(v8::String::New("changeVersion"),
534 V8FUNCTION(qmlsqldatabase_changeVersion, engine));
535 constructor = qPersistentNew<v8::Function>(ft->GetFunction());
539 v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
540 ft->InstanceTemplate()->SetHasExternalResource(true);
541 ft->PrototypeTemplate()->Set(v8::String::New("executeSql"),
542 V8FUNCTION(qmlsqldatabase_executeSql, engine));
543 queryConstructor = qPersistentNew<v8::Function>(ft->GetFunction());
546 v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
547 ft->InstanceTemplate()->SetHasExternalResource(true);
548 ft->PrototypeTemplate()->Set(v8::String::New("item"), V8FUNCTION(qmlsqldatabase_rows_item, engine));
549 ft->PrototypeTemplate()->SetAccessor(v8::String::New("length"), qmlsqldatabase_rows_length);
550 ft->InstanceTemplate()->SetAccessor(v8::String::New("forwardOnly"), qmlsqldatabase_rows_forwardOnly,
551 qmlsqldatabase_rows_setForwardOnly);
552 ft->InstanceTemplate()->SetIndexedPropertyHandler(qmlsqldatabase_rows_index);
553 rowsConstructor = qPersistentNew<v8::Function>(ft->GetFunction());
557 void *qt_add_qmlsqldatabase(QV8Engine *engine)
559 v8::Local<v8::Function> openDatabase = V8FUNCTION(qmlsqldatabase_open_sync, engine);
560 engine->global()->Set(v8::String::New("openDatabaseSync"), openDatabase);
562 v8::PropertyAttribute attributes = (v8::PropertyAttribute)(v8::ReadOnly | v8::DontEnum | v8::DontDelete);
563 v8::Local<v8::Object> sqlExceptionPrototype = v8::Object::New();
564 for (int i=0; sqlerror[i]; ++i)
565 sqlExceptionPrototype->Set(v8::String::New(sqlerror[i]), v8::Integer::New(i), attributes);
566 engine->global()->Set(v8::String::New("SQLException"), sqlExceptionPrototype);
568 return (void *)new QDeclarativeSqlDatabaseData(engine);
571 void qt_rem_qmlsqldatabase(QV8Engine * /* engine */, void *d)
573 QDeclarativeSqlDatabaseData *data = (QDeclarativeSqlDatabaseData *)d;
577 void qt_qmlsqldatabase_setOfflineStoragePath(QV8Engine *engine, const QString &path)
579 QDeclarativeSqlDatabaseData::data(engine)->offlineStoragePath = path;
582 QString qt_qmlsqldatabase_getOfflineStoragePath(const QV8Engine *engine)
584 return QDeclarativeSqlDatabaseData::data(const_cast<QV8Engine *>(engine))->offlineStoragePath;
588 HTML5 "spec" says "rs.rows[n]", but WebKit only impelments "rs.rows.item(n)". We do both (and property iterator).
589 We add a "forwardOnly" property that stops Qt caching results (code promises to only go forward