source: trunk/src/declarative/qml/qdeclarativesqldatabase.cpp@ 928

Last change on this file since 928 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 16.3 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtDeclarative module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "private/qdeclarativesqldatabase_p.h"
43
44#include "qdeclarativeengine.h"
45#include "private/qdeclarativeengine_p.h"
46#include "private/qdeclarativerefcount_p.h"
47#include "private/qdeclarativeengine_p.h"
48
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>
64
65Q_DECLARE_METATYPE(QSqlDatabase)
66Q_DECLARE_METATYPE(QSqlQuery)
67
68QT_BEGIN_NAMESPACE
69
70class QDeclarativeSqlQueryScriptClass: public QScriptClass {
71public:
72 QDeclarativeSqlQueryScriptClass(QScriptEngine *engine) : QScriptClass(engine)
73 {
74 str_length = engine->toStringHandle(QLatin1String("length"));
75 str_forwardOnly = engine->toStringHandle(QLatin1String("forwardOnly")); // not in HTML5 (an optimization)
76 }
77
78 QueryFlags queryProperty(const QScriptValue &,
79 const QScriptString &name,
80 QueryFlags flags, uint *)
81 {
82 if (flags & HandlesReadAccess) {
83 if (name == str_length) {
84 return HandlesReadAccess;
85 } else if (name == str_forwardOnly) {
86 return flags;
87 }
88 }
89 if (flags & HandlesWriteAccess)
90 if (name == str_forwardOnly)
91 return flags;
92 return 0;
93 }
94
95 QScriptValue property(const QScriptValue &object,
96 const QScriptString &name, uint)
97 {
98 QSqlQuery query = qscriptvalue_cast<QSqlQuery>(object.data());
99 if (name == str_length) {
100 int s = query.size();
101 if (s<0) {
102 // Inefficient.
103 if (query.last()) {
104 return query.at()+1;
105 } else {
106 return 0;
107 }
108 } else {
109 return s;
110 }
111 } else if (name == str_forwardOnly) {
112 return query.isForwardOnly();
113 }
114 return engine()->undefinedValue();
115 }
116
117 void setProperty(QScriptValue &object,
118 const QScriptString &name, uint, const QScriptValue & value)
119 {
120 if (name == str_forwardOnly) {
121 QSqlQuery query = qscriptvalue_cast<QSqlQuery>(object.data());
122 query.setForwardOnly(value.toBool());
123 }
124 }
125
126 QScriptValue::PropertyFlags propertyFlags(const QScriptValue &/*object*/, const QScriptString &name, uint /*id*/)
127 {
128 if (name == str_length) {
129 return QScriptValue::Undeletable
130 | QScriptValue::SkipInEnumeration;
131 }
132 return QScriptValue::Undeletable;
133 }
134
135private:
136 QScriptString str_length;
137 QScriptString str_forwardOnly;
138};
139
140// If the spec changes to allow iteration, check git history...
141// class QDeclarativeSqlQueryScriptClassPropertyIterator : public QScriptClassPropertyIterator
142
143
144
145enum SqlException {
146 UNKNOWN_ERR,
147 DATABASE_ERR,
148 VERSION_ERR,
149 TOO_LARGE_ERR,
150 QUOTA_ERR,
151 SYNTAX_ERR,
152 CONSTRAINT_ERR,
153 TIMEOUT_ERR
154};
155
156static const char* sqlerror[] = {
157 "UNKNOWN_ERR",
158 "DATABASE_ERR",
159 "VERSION_ERR",
160 "TOO_LARGE_ERR",
161 "QUOTA_ERR",
162 "SYNTAX_ERR",
163 "CONSTRAINT_ERR",
164 "TIMEOUT_ERR",
165 0
166};
167
168#define THROW_SQL(error, desc) \
169{ \
170 QScriptValue errorValue = context->throwError(desc); \
171 errorValue.setProperty(QLatin1String("code"), error); \
172 return errorValue; \
173}
174
175static QString qmlsqldatabase_databasesPath(QScriptEngine *engine)
176{
177 QDeclarativeScriptEngine *qmlengine = static_cast<QDeclarativeScriptEngine*>(engine);
178 return qmlengine->offlineStoragePath
179 + QDir::separator() + QLatin1String("Databases");
180}
181
182static void qmlsqldatabase_initDatabasesPath(QScriptEngine *engine)
183{
184 QDir().mkpath(qmlsqldatabase_databasesPath(engine));
185}
186
187static QString qmlsqldatabase_databaseFile(const QString& connectionName, QScriptEngine *engine)
188{
189 return qmlsqldatabase_databasesPath(engine) + QDir::separator()
190 + connectionName;
191}
192
193
194static QScriptValue qmlsqldatabase_item(QScriptContext *context, QScriptEngine *engine)
195{
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()));
203 }
204 return row;
205 }
206 return engine->undefinedValue();
207}
208
209static QScriptValue qmlsqldatabase_executeSql_outsidetransaction(QScriptContext *context, QScriptEngine * /*engine*/)
210{
211 THROW_SQL(DATABASE_ERR,QDeclarativeEngine::tr("executeSql called outside transaction()"));
212}
213
214static QScriptValue qmlsqldatabase_executeSql(QScriptContext *context, QScriptEngine *engine)
215{
216 QSqlDatabase db = qscriptvalue_cast<QSqlDatabase>(context->thisObject());
217 QString sql = context->argument(0).toString();
218 QSqlQuery query(db);
219 bool err = false;
220
221 QScriptValue result;
222
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());
231 } else {
232 for (QScriptValueIterator it(values); it.hasNext();) {
233 it.next();
234 query.bindValue(it.name(),it.value().toVariant());
235 }
236 }
237 } else {
238 query.bindValue(0,values.toVariant());
239 }
240 }
241 if (query.exec()) {
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(qVariantFromValue(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());
252 } else {
253 err = true;
254 }
255 } else {
256 err = true;
257 }
258 if (err)
259 THROW_SQL(DATABASE_ERR,query.lastError().text());
260 return result;
261}
262
263static QScriptValue qmlsqldatabase_executeSql_readonly(QScriptContext *context, QScriptEngine *engine)
264{
265 QString sql = context->argument(0).toString();
266 if (sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
267 return qmlsqldatabase_executeSql(context,engine);
268 } else {
269 THROW_SQL(SYNTAX_ERR,QDeclarativeEngine::tr("Read-only Transaction"))
270 }
271}
272
273static QScriptValue qmlsqldatabase_change_version(QScriptContext *context, QScriptEngine *engine)
274{
275 if (context->argumentCount() < 2)
276 return engine->undefinedValue();
277
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);
282
283 QScriptValue instance = engine->newObject();
284 instance.setProperty(QLatin1String("executeSql"), engine->newFunction(qmlsqldatabase_executeSql,1));
285 QScriptValue tx = engine->newVariant(instance,qVariantFromValue(db));
286
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();
291 }
292
293 bool ok = true;
294 if (callback.isFunction()) {
295 ok = false;
296 db.transaction();
297 callback.call(QScriptValue(), QScriptValueList() << tx);
298 if (engine->hasUncaughtException()) {
299 db.rollback();
300 } else {
301 if (!db.commit()) {
302 db.rollback();
303 THROW_SQL(UNKNOWN_ERR,QDeclarativeEngine::tr("SQL transaction failed"));
304 } else {
305 ok = true;
306 }
307 }
308 }
309
310 if (ok) {
311 context->thisObject().setProperty(QLatin1String("version"), to_version, QScriptValue::ReadOnly);
312 QSettings ini(qmlsqldatabase_databaseFile(db.connectionName(),engine) + QLatin1String(".ini"), QSettings::IniFormat);
313 ini.setValue(QLatin1String("Version"), to_version);
314 }
315
316 return engine->undefinedValue();
317}
318
319static QScriptValue qmlsqldatabase_transaction_shared(QScriptContext *context, QScriptEngine *engine, bool readOnly)
320{
321 QSqlDatabase db = qscriptvalue_cast<QSqlDatabase>(context->thisObject());
322 QScriptValue callback = context->argument(0);
323 if (!callback.isFunction())
324 THROW_SQL(UNKNOWN_ERR,QDeclarativeEngine::tr("transaction: missing callback"));
325
326 QScriptValue instance = engine->newObject();
327 instance.setProperty(QLatin1String("executeSql"),
328 engine->newFunction(readOnly ? qmlsqldatabase_executeSql_readonly : qmlsqldatabase_executeSql,1));
329 QScriptValue tx = engine->newVariant(instance,qVariantFromValue(db));
330
331 db.transaction();
332 callback.call(QScriptValue(), QScriptValueList() << tx);
333 instance.setProperty(QLatin1String("executeSql"),
334 engine->newFunction(qmlsqldatabase_executeSql_outsidetransaction));
335 if (engine->hasUncaughtException()) {
336 db.rollback();
337 } else {
338 if (!db.commit())
339 db.rollback();
340 }
341 return engine->undefinedValue();
342}
343
344static QScriptValue qmlsqldatabase_transaction(QScriptContext *context, QScriptEngine *engine)
345{
346 return qmlsqldatabase_transaction_shared(context,engine,false);
347}
348static QScriptValue qmlsqldatabase_read_transaction(QScriptContext *context, QScriptEngine *engine)
349{
350 return qmlsqldatabase_transaction_shared(context,engine,true);
351}
352
353/*
354 Currently documented in doc/src/declarastive/globalobject.qdoc
355*/
356static QScriptValue qmlsqldatabase_open_sync(QScriptContext *context, QScriptEngine *engine)
357{
358 qmlsqldatabase_initDatabasesPath(engine);
359
360 QSqlDatabase database;
361
362 QString dbname = context->argument(0).toString();
363 QString dbversion = context->argument(1).toString();
364 QString dbdescription = context->argument(2).toString();
365 int dbestimatedsize = context->argument(3).toNumber();
366 QScriptValue dbcreationCallback = context->argument(4);
367
368 QCryptographicHash md5(QCryptographicHash::Md5);
369 md5.addData(dbname.toUtf8());
370 QString dbid(QLatin1String(md5.result().toHex()));
371
372 QString basename = qmlsqldatabase_databaseFile(dbid, engine);
373 bool created = false;
374 QString version = dbversion;
375
376 {
377 QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat);
378
379 if (QSqlDatabase::connectionNames().contains(dbid)) {
380 database = QSqlDatabase::database(dbid);
381 version = ini.value(QLatin1String("Version")).toString();
382 if (version != dbversion && !dbversion.isEmpty() && !version.isEmpty())
383 THROW_SQL(VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
384 } else {
385 created = !QFile::exists(basename+QLatin1String(".sqlite"));
386 database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
387 if (created) {
388 ini.setValue(QLatin1String("Name"), dbname);
389 if (dbcreationCallback.isFunction())
390 version = QString();
391 ini.setValue(QLatin1String("Version"), version);
392 ini.setValue(QLatin1String("Description"), dbdescription);
393 ini.setValue(QLatin1String("EstimatedSize"), dbestimatedsize);
394 ini.setValue(QLatin1String("Driver"), QLatin1String("QSQLITE"));
395 } else {
396 if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
397 // Incompatible
398 THROW_SQL(VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
399 }
400 version = ini.value(QLatin1String("Version")).toString();
401 }
402 database.setDatabaseName(basename+QLatin1String(".sqlite"));
403 }
404 if (!database.isOpen())
405 database.open();
406 }
407
408 QScriptValue instance = engine->newObject();
409 instance.setProperty(QLatin1String("transaction"), engine->newFunction(qmlsqldatabase_transaction,1));
410 instance.setProperty(QLatin1String("readTransaction"), engine->newFunction(qmlsqldatabase_read_transaction,1));
411 instance.setProperty(QLatin1String("version"), version, QScriptValue::ReadOnly);
412 instance.setProperty(QLatin1String("changeVersion"), engine->newFunction(qmlsqldatabase_change_version,3));
413
414 QScriptValue result = engine->newVariant(instance,qVariantFromValue(database));
415
416 if (created && dbcreationCallback.isFunction()) {
417 dbcreationCallback.call(QScriptValue(), QScriptValueList() << result);
418 }
419
420 return result;
421}
422
423void qt_add_qmlsqldatabase(QScriptEngine *engine)
424{
425 QScriptValue openDatabase = engine->newFunction(qmlsqldatabase_open_sync, 4);
426 engine->globalObject().setProperty(QLatin1String("openDatabaseSync"), openDatabase);
427
428 QScriptValue sqlExceptionPrototype = engine->newObject();
429 for (int i=0; sqlerror[i]; ++i)
430 sqlExceptionPrototype.setProperty(QLatin1String(sqlerror[i]),
431 i,QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
432
433 engine->globalObject().setProperty(QLatin1String("SQLException"), sqlExceptionPrototype);
434}
435
436/*
437HTML5 "spec" says "rs.rows[n]", but WebKit only impelments "rs.rows.item(n)". We do both (and property iterator).
438We add a "forwardOnly" property that stops Qt caching results (code promises to only go forward
439through the data.
440*/
441
442QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.