1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qdeclarativesqldatabase_p.h"
44 #include "qdeclarativeengine.h"
45 #include "qdeclarativeengine_p.h"
46 #include <private/qdeclarativerefcount_p.h>
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>
61 #include <private/qv8engine_p.h>
62 #include <private/qv8sqlerrors_p.h>
67 #define THROW_SQL(error, desc)
69 #define V8THROW_SQL(error, desc) \
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>(); \
77 #define V8THROW_REFERENCE(string) { \
78 v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
79 return v8::Handle<v8::Value>(); \
82 #define V8THROW_REFERENCE_VOID(string) { \
83 v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
87 struct QDeclarativeSqlDatabaseData {
88 QDeclarativeSqlDatabaseData(QV8Engine *engine);
89 ~QDeclarativeSqlDatabaseData();
91 v8::Persistent<v8::Function> constructor;
92 v8::Persistent<v8::Function> queryConstructor;
93 v8::Persistent<v8::Function> rowsConstructor;
95 static inline QDeclarativeSqlDatabaseData *data(QV8Engine *e) {
96 return (QDeclarativeSqlDatabaseData *)e->sqlDatabaseData();
98 static inline QDeclarativeSqlDatabaseData *data(void *d) {
99 return (QDeclarativeSqlDatabaseData *)d;
103 class QV8SqlDatabaseResource : public QV8ObjectResource
105 V8_RESOURCE_TYPE(SQLDatabaseType)
108 enum Type { Database, Query, Rows };
110 QV8SqlDatabaseResource(QV8Engine *e)
111 : QV8ObjectResource(e), type(Database), inTransaction(false), readonly(false), forwardOnly(false) {}
114 QSqlDatabase database;
116 QString version; // type == Database
118 bool inTransaction; // type == Query
119 bool readonly; // type == Query
121 QSqlQuery query; // type == Rows
122 bool forwardOnly; // type == Rows
125 static v8::Handle<v8::Value> qmlsqldatabase_version(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
127 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
128 if (!r || r->type != QV8SqlDatabaseResource::Database)
129 V8THROW_REFERENCE("Not a SQLDatabase object");
131 return r->engine->toString(r->version);
134 static v8::Handle<v8::Value> qmlsqldatabase_rows_length(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
136 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
137 if (!r || r->type != QV8SqlDatabaseResource::Rows)
138 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
140 int s = r->query.size();
143 if (r->query.last()) {
144 s = r->query.at() + 1;
149 return v8::Integer::New(s);
152 static v8::Handle<v8::Value> qmlsqldatabase_rows_forwardOnly(v8::Local<v8::String> /* property */,
153 const v8::AccessorInfo& info)
155 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
156 if (!r || r->type != QV8SqlDatabaseResource::Rows)
157 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
159 return v8::Boolean::New(r->query.isForwardOnly());
162 static void qmlsqldatabase_rows_setForwardOnly(v8::Local<v8::String> /* property */,
163 v8::Local<v8::Value> value,
164 const v8::AccessorInfo& info)
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");
170 r->query.setForwardOnly(value->BooleanValue());
173 QDeclarativeSqlDatabaseData::~QDeclarativeSqlDatabaseData()
175 qPersistentDispose(constructor);
176 qPersistentDispose(queryConstructor);
177 qPersistentDispose(rowsConstructor);
180 static QString qmlsqldatabase_databasesPath(QV8Engine *engine)
182 return engine->engine()->offlineStoragePath() +
183 QDir::separator() + QLatin1String("Databases");
186 static void qmlsqldatabase_initDatabasesPath(QV8Engine *engine)
188 QDir().mkpath(qmlsqldatabase_databasesPath(engine));
191 static QString qmlsqldatabase_databaseFile(const QString& connectionName, QV8Engine *engine)
193 return qmlsqldatabase_databasesPath(engine) + QDir::separator() + connectionName;
196 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(QV8SqlDatabaseResource *r, uint32_t index)
198 if (r->query.at() == (int)index || r->query.seek(index)) {
200 QSqlRecord record = r->query.record();
202 v8::Local<v8::Object> row = v8::Object::New();
203 for (int ii = 0; ii < record.count(); ++ii) {
204 QVariant v = record.value(ii);
206 row->Set(r->engine->toString(record.fieldName(ii)), v8::Null());
208 row->Set(r->engine->toString(record.fieldName(ii)),
209 r->engine->fromVariant(v));
214 return v8::Undefined();
218 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(uint32_t index, const v8::AccessorInfo& info)
220 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
221 if (!r || r->type != QV8SqlDatabaseResource::Rows)
222 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
224 return qmlsqldatabase_rows_index(r, index);
227 static v8::Handle<v8::Value> qmlsqldatabase_rows_item(const v8::Arguments& args)
229 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
230 if (!r || r->type != QV8SqlDatabaseResource::Rows)
231 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
233 return qmlsqldatabase_rows_index(r, args.Length()?args[0]->Uint32Value():0);
236 static v8::Handle<v8::Value> qmlsqldatabase_executeSql(const v8::Arguments& args)
238 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
239 if (!r || r->type != QV8SqlDatabaseResource::Query)
240 V8THROW_REFERENCE("Not a SQLDatabase::Query object");
242 QV8Engine *engine = r->engine;
244 if (!r->inTransaction)
245 V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,QDeclarativeEngine::tr("executeSql called outside transaction()"));
247 QSqlDatabase db = r->database;
249 QString sql = engine->toString(args[0]);
251 if (r->readonly && !sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
252 V8THROW_SQL(SQLEXCEPTION_SYNTAX_ERR, QDeclarativeEngine::tr("Read-only Transaction"));
258 v8::Handle<v8::Value> result = v8::Undefined();
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));
276 query.bindValue(0, engine->toVariant(values, -1));
280 v8::Handle<v8::Object> rows = QDeclarativeSqlDatabaseData::data(engine)->rowsConstructor->NewInstance();
281 QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
282 r->type = QV8SqlDatabaseResource::Rows;
285 rows->SetExternalResource(r);
287 v8::Local<v8::Object> resultObject = v8::Object::New();
288 result = resultObject;
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);
300 V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,query.lastError().text());
305 static v8::Handle<v8::Value> qmlsqldatabase_changeVersion(const v8::Arguments& args)
307 if (args.Length() < 2)
308 return v8::Undefined();
310 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
311 if (!r || r->type != QV8SqlDatabaseResource::Database)
312 V8THROW_REFERENCE("Not a SQLDatabase object");
314 QV8Engine *engine = r->engine;
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];
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));
324 v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->queryConstructor->NewInstance();
325 QV8SqlDatabaseResource *r2 = new QV8SqlDatabaseResource(engine);
326 r2->type = QV8SqlDatabaseResource::Query;
328 r2->version = r->version;
329 r2->inTransaction = true;
330 instance->SetExternalResource(r2);
333 if (callback->IsFunction()) {
338 v8::Handle<v8::Value> callbackArgs[] = { instance };
339 v8::Handle<v8::Function>::Cast(callback)->Call(engine->global(), 1, callbackArgs);
341 if (tc.HasCaught()) {
344 return v8::Handle<v8::Value>();
345 } else if (!db.commit()) {
347 V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QDeclarativeEngine::tr("SQL transaction failed"));
353 r2->inTransaction = false;
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);
363 return v8::Undefined();
366 static v8::Handle<v8::Value> qmlsqldatabase_transaction_shared(const v8::Arguments& args, bool readOnly)
368 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
369 if (!r || r->type != QV8SqlDatabaseResource::Database)
370 V8THROW_REFERENCE("Not a SQLDatabase object");
372 QV8Engine *engine = r->engine;
374 if (args.Length() == 0 || !args[0]->IsFunction())
375 V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QDeclarativeEngine::tr("transaction: missing callback"));
377 QSqlDatabase db = r->database;
378 v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(args[0]);
380 v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->queryConstructor->NewInstance();
381 QV8SqlDatabaseResource *q = new QV8SqlDatabaseResource(engine);
382 q->type = QV8SqlDatabaseResource::Query;
384 q->readonly = readOnly;
385 q->inTransaction = true;
386 instance->SetExternalResource(q);
390 v8::Handle<v8::Value> callbackArgs[] = { instance };
391 callback->Call(engine->global(), 1, callbackArgs);
393 q->inTransaction = false;
395 if (tc.HasCaught()) {
398 return v8::Handle<v8::Value>();
399 } else if (!db.commit()) {
403 return v8::Undefined();
406 static v8::Handle<v8::Value> qmlsqldatabase_transaction(const v8::Arguments& args)
408 return qmlsqldatabase_transaction_shared(args, false);
411 static v8::Handle<v8::Value> qmlsqldatabase_read_transaction(const v8::Arguments& args)
413 return qmlsqldatabase_transaction_shared(args, true);
417 Currently documented in doc/src/declarative/globalobject.qdoc
419 static v8::Handle<v8::Value> qmlsqldatabase_open_sync(const v8::Arguments& args)
421 #ifndef QT_NO_SETTINGS
422 QV8Engine *engine = V8ENGINE();
423 qmlsqldatabase_initDatabasesPath(engine);
425 QSqlDatabase database;
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];
433 QCryptographicHash md5(QCryptographicHash::Md5);
434 md5.addData(dbname.toUtf8());
435 QString dbid(QLatin1String(md5.result().toHex()));
437 QString basename = qmlsqldatabase_databaseFile(dbid, engine);
438 bool created = false;
439 QString version = dbversion;
442 QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat);
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"));
450 created = !QFile::exists(basename+QLatin1String(".sqlite"));
451 database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
453 ini.setValue(QLatin1String("Name"), dbname);
454 if (dbcreationCallback->IsFunction())
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"));
461 if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
463 V8THROW_SQL(SQLEXCEPTION_VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
465 version = ini.value(QLatin1String("Version")).toString();
467 database.setDatabaseName(basename+QLatin1String(".sqlite"));
469 if (!database.isOpen())
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);
479 if (created && dbcreationCallback->IsFunction()) {
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()) {
486 return v8::Handle<v8::Value>();
492 return v8::Undefined();
493 #endif // QT_NO_SETTINGS
496 QDeclarativeSqlDatabaseData::QDeclarativeSqlDatabaseData(QV8Engine *engine)
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());
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());
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());
530 void *qt_add_qmlsqldatabase(QV8Engine *engine)
532 v8::Local<v8::Function> openDatabase = V8FUNCTION(qmlsqldatabase_open_sync, engine);
533 engine->global()->Set(v8::String::New("openDatabaseSync"), openDatabase);
535 return (void *)new QDeclarativeSqlDatabaseData(engine);
538 void qt_rem_qmlsqldatabase(QV8Engine * /* engine */, void *d)
540 QDeclarativeSqlDatabaseData *data = (QDeclarativeSqlDatabaseData *)d;
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