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 <QtScript/qscriptvalue.h>
51 #include <QtScript/qscriptvalueiterator.h>
52 #include <QtScript/qscriptcontext.h>
53 #include <QtScript/qscriptengine.h>
54 #include <QtScript/qscriptclasspropertyiterator.h>
55 #include <QtSql/qsqldatabase.h>
56 #include <QtSql/qsqlquery.h>
57 #include <QtSql/qsqlerror.h>
58 #include <QtSql/qsqlrecord.h>
59 #include <QtCore/qstack.h>
60 #include <QtCore/qcryptographichash.h>
61 #include <QtCore/qsettings.h>
62 #include <QtCore/qdir.h>
63 #include <QtCore/qdebug.h>
65 Q_DECLARE_METATYPE(QSqlDatabase)
66 Q_DECLARE_METATYPE(QSqlQuery)
70 class QDeclarativeSqlQueryScriptClass: public QScriptClass {
72 QDeclarativeSqlQueryScriptClass(QScriptEngine *engine) : QScriptClass(engine)
74 str_length = engine->toStringHandle(QLatin1String("length"));
75 str_forwardOnly = engine->toStringHandle(QLatin1String("forwardOnly")); // not in HTML5 (an optimization)
78 QueryFlags queryProperty(const QScriptValue &,
79 const QScriptString &name,
80 QueryFlags flags, uint *)
82 if (flags & HandlesReadAccess) {
83 if (name == str_length) {
84 return HandlesReadAccess;
85 } else if (name == str_forwardOnly) {
89 if (flags & HandlesWriteAccess)
90 if (name == str_forwardOnly)
95 QScriptValue property(const QScriptValue &object,
96 const QScriptString &name, uint)
98 QSqlQuery query = qscriptvalue_cast<QSqlQuery>(object.data());
99 if (name == str_length) {
100 int s = query.size();
111 } else if (name == str_forwardOnly) {
112 return query.isForwardOnly();
114 return engine()->undefinedValue();
117 void setProperty(QScriptValue &object,
118 const QScriptString &name, uint, const QScriptValue & value)
120 if (name == str_forwardOnly) {
121 QSqlQuery query = qscriptvalue_cast<QSqlQuery>(object.data());
122 query.setForwardOnly(value.toBool());
126 QScriptValue::PropertyFlags propertyFlags(const QScriptValue &/*object*/, const QScriptString &name, uint /*id*/)
128 if (name == str_length) {
129 return QScriptValue::Undeletable
130 | QScriptValue::SkipInEnumeration;
132 return QScriptValue::Undeletable;
136 QScriptString str_length;
137 QScriptString str_forwardOnly;
140 // If the spec changes to allow iteration, check git history...
141 // class QDeclarativeSqlQueryScriptClassPropertyIterator : public QScriptClassPropertyIterator
156 static const char* sqlerror[] = {
168 #define THROW_SQL(error, desc) \
170 QScriptValue errorValue = context->throwError(desc); \
171 errorValue.setProperty(QLatin1String("code"), error); \
175 static QString qmlsqldatabase_databasesPath(QScriptEngine *engine)
177 QDeclarativeScriptEngine *qmlengine = static_cast<QDeclarativeScriptEngine*>(engine);
178 return qmlengine->offlineStoragePath
179 + QDir::separator() + QLatin1String("Databases");
182 static void qmlsqldatabase_initDatabasesPath(QScriptEngine *engine)
184 QDir().mkpath(qmlsqldatabase_databasesPath(engine));
187 static QString qmlsqldatabase_databaseFile(const QString& connectionName, QScriptEngine *engine)
189 return qmlsqldatabase_databasesPath(engine) + QDir::separator()
194 static QScriptValue qmlsqldatabase_item(QScriptContext *context, QScriptEngine *engine)
196 QSqlQuery query = qscriptvalue_cast<QSqlQuery>(context->thisObject().data());
197 int i = context->argument(0).toNumber();
198 if (query.at() == i || query.seek(i)) { // Qt 4.6 doesn't optimize seek(at())
199 QSqlRecord r = query.record();
200 QScriptValue row = engine->newObject();
201 for (int j=0; j<r.count(); ++j) {
202 row.setProperty(r.fieldName(j), QScriptValue(engine,r.value(j).toString()));
206 return engine->undefinedValue();
209 static QScriptValue qmlsqldatabase_executeSql_outsidetransaction(QScriptContext *context, QScriptEngine * /*engine*/)
211 THROW_SQL(DATABASE_ERR,QDeclarativeEngine::tr("executeSql called outside transaction()"));
214 static QScriptValue qmlsqldatabase_executeSql(QScriptContext *context, QScriptEngine *engine)
216 QSqlDatabase db = qscriptvalue_cast<QSqlDatabase>(context->thisObject());
217 QString sql = context->argument(0).toString();
223 if (query.prepare(sql)) {
224 if (context->argumentCount() > 1) {
225 QScriptValue values = context->argument(1);
226 if (values.isObject()) {
227 if (values.isArray()) {
228 int size = values.property(QLatin1String("length")).toInt32();
229 for (int i = 0; i < size; ++i)
230 query.bindValue(i, values.property(i).toVariant());
232 for (QScriptValueIterator it(values); it.hasNext();) {
234 query.bindValue(it.name(),it.value().toVariant());
238 query.bindValue(0,values.toVariant());
242 result = engine->newObject();
243 QDeclarativeScriptEngine *qmlengine = static_cast<QDeclarativeScriptEngine*>(engine);
244 if (!qmlengine->sqlQueryClass)
245 qmlengine->sqlQueryClass = new QDeclarativeSqlQueryScriptClass(engine);
246 QScriptValue rows = engine->newObject(qmlengine->sqlQueryClass);
247 rows.setData(engine->newVariant(QVariant::fromValue(query)));
248 rows.setProperty(QLatin1String("item"), engine->newFunction(qmlsqldatabase_item,1), QScriptValue::SkipInEnumeration);
249 result.setProperty(QLatin1String("rows"),rows);
250 result.setProperty(QLatin1String("rowsAffected"),query.numRowsAffected());
251 result.setProperty(QLatin1String("insertId"),query.lastInsertId().toString());
259 THROW_SQL(DATABASE_ERR,query.lastError().text());
263 static QScriptValue qmlsqldatabase_executeSql_readonly(QScriptContext *context, QScriptEngine *engine)
265 QString sql = context->argument(0).toString();
266 if (sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
267 return qmlsqldatabase_executeSql(context,engine);
269 THROW_SQL(SYNTAX_ERR,QDeclarativeEngine::tr("Read-only Transaction"))
273 static QScriptValue qmlsqldatabase_change_version(QScriptContext *context, QScriptEngine *engine)
275 if (context->argumentCount() < 2)
276 return engine->undefinedValue();
278 QSqlDatabase db = qscriptvalue_cast<QSqlDatabase>(context->thisObject());
279 QString from_version = context->argument(0).toString();
280 QString to_version = context->argument(1).toString();
281 QScriptValue callback = context->argument(2);
283 QScriptValue instance = engine->newObject();
284 instance.setProperty(QLatin1String("executeSql"), engine->newFunction(qmlsqldatabase_executeSql,1));
285 QScriptValue tx = engine->newVariant(instance,QVariant::fromValue(db));
287 QString foundvers = context->thisObject().property(QLatin1String("version")).toString();
288 if (from_version!=foundvers) {
289 THROW_SQL(VERSION_ERR,QDeclarativeEngine::tr("Version mismatch: expected %1, found %2").arg(from_version).arg(foundvers));
290 return engine->undefinedValue();
294 if (callback.isFunction()) {
297 callback.call(QScriptValue(), QScriptValueList() << tx);
298 if (engine->hasUncaughtException()) {
303 THROW_SQL(UNKNOWN_ERR,QDeclarativeEngine::tr("SQL transaction failed"));
311 context->thisObject().setProperty(QLatin1String("version"), to_version, QScriptValue::ReadOnly);
312 #ifndef QT_NO_SETTINGS
313 QSettings ini(qmlsqldatabase_databaseFile(db.connectionName(),engine) + QLatin1String(".ini"), QSettings::IniFormat);
314 ini.setValue(QLatin1String("Version"), to_version);
318 return engine->undefinedValue();
321 static QScriptValue qmlsqldatabase_transaction_shared(QScriptContext *context, QScriptEngine *engine, bool readOnly)
323 QSqlDatabase db = qscriptvalue_cast<QSqlDatabase>(context->thisObject());
324 QScriptValue callback = context->argument(0);
325 if (!callback.isFunction())
326 THROW_SQL(UNKNOWN_ERR,QDeclarativeEngine::tr("transaction: missing callback"));
328 QScriptValue instance = engine->newObject();
329 instance.setProperty(QLatin1String("executeSql"),
330 engine->newFunction(readOnly ? qmlsqldatabase_executeSql_readonly : qmlsqldatabase_executeSql,1));
331 QScriptValue tx = engine->newVariant(instance,QVariant::fromValue(db));
334 callback.call(QScriptValue(), QScriptValueList() << tx);
335 instance.setProperty(QLatin1String("executeSql"),
336 engine->newFunction(qmlsqldatabase_executeSql_outsidetransaction));
337 if (engine->hasUncaughtException()) {
343 return engine->undefinedValue();
346 static QScriptValue qmlsqldatabase_transaction(QScriptContext *context, QScriptEngine *engine)
348 return qmlsqldatabase_transaction_shared(context,engine,false);
350 static QScriptValue qmlsqldatabase_read_transaction(QScriptContext *context, QScriptEngine *engine)
352 return qmlsqldatabase_transaction_shared(context,engine,true);
356 Currently documented in doc/src/declarative/globalobject.qdoc
358 static QScriptValue qmlsqldatabase_open_sync(QScriptContext *context, QScriptEngine *engine)
360 #ifndef QT_NO_SETTINGS
361 qmlsqldatabase_initDatabasesPath(engine);
363 QSqlDatabase database;
365 QString dbname = context->argument(0).toString();
366 QString dbversion = context->argument(1).toString();
367 QString dbdescription = context->argument(2).toString();
368 int dbestimatedsize = context->argument(3).toNumber();
369 QScriptValue dbcreationCallback = context->argument(4);
371 QCryptographicHash md5(QCryptographicHash::Md5);
372 md5.addData(dbname.toUtf8());
373 QString dbid(QLatin1String(md5.result().toHex()));
375 QString basename = qmlsqldatabase_databaseFile(dbid, engine);
376 bool created = false;
377 QString version = dbversion;
380 QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat);
382 if (QSqlDatabase::connectionNames().contains(dbid)) {
383 database = QSqlDatabase::database(dbid);
384 version = ini.value(QLatin1String("Version")).toString();
385 if (version != dbversion && !dbversion.isEmpty() && !version.isEmpty())
386 THROW_SQL(VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
388 created = !QFile::exists(basename+QLatin1String(".sqlite"));
389 database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
391 ini.setValue(QLatin1String("Name"), dbname);
392 if (dbcreationCallback.isFunction())
394 ini.setValue(QLatin1String("Version"), version);
395 ini.setValue(QLatin1String("Description"), dbdescription);
396 ini.setValue(QLatin1String("EstimatedSize"), dbestimatedsize);
397 ini.setValue(QLatin1String("Driver"), QLatin1String("QSQLITE"));
399 if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
401 THROW_SQL(VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
403 version = ini.value(QLatin1String("Version")).toString();
405 database.setDatabaseName(basename+QLatin1String(".sqlite"));
407 if (!database.isOpen())
411 QScriptValue instance = engine->newObject();
412 instance.setProperty(QLatin1String("transaction"), engine->newFunction(qmlsqldatabase_transaction,1));
413 instance.setProperty(QLatin1String("readTransaction"), engine->newFunction(qmlsqldatabase_read_transaction,1));
414 instance.setProperty(QLatin1String("version"), version, QScriptValue::ReadOnly);
415 instance.setProperty(QLatin1String("changeVersion"), engine->newFunction(qmlsqldatabase_change_version,3));
417 QScriptValue result = engine->newVariant(instance,QVariant::fromValue(database));
419 if (created && dbcreationCallback.isFunction()) {
420 dbcreationCallback.call(QScriptValue(), QScriptValueList() << result);
425 return engine->undefinedValue();
426 #endif // QT_NO_SETTINGS
429 void qt_add_qmlsqldatabase(QScriptEngine *engine)
431 QScriptValue openDatabase = engine->newFunction(qmlsqldatabase_open_sync, 4);
432 engine->globalObject().setProperty(QLatin1String("openDatabaseSync"), openDatabase);
434 QScriptValue sqlExceptionPrototype = engine->newObject();
435 for (int i=0; sqlerror[i]; ++i)
436 sqlExceptionPrototype.setProperty(QLatin1String(sqlerror[i]),
437 i,QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
439 engine->globalObject().setProperty(QLatin1String("SQLException"), sqlExceptionPrototype);
443 HTML5 "spec" says "rs.rows[n]", but WebKit only impelments "rs.rows.item(n)". We do both (and property iterator).
444 We add a "forwardOnly" property that stops Qt caching results (code promises to only go forward