Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmllocalstorage.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
6
7#include <QtQml/private/qqmlengine_p.h>
8#include <QtQml/private/qv4global_p.h>
9#include <QtQml/private/qv4scopedvalue_p.h>
10#include <QtQml/private/qv4object_p.h>
11#include <QtQml/private/qv4sqlerrors_p.h>
12#include <QtQml/private/qv4jscall_p.h>
13#include <QtQml/private/qv4objectiterator_p.h>
14
15#include <QtCore/qfileinfo.h>
16#include <QtCore/qdir.h>
17
18#include <QtSql/qsqldatabase.h>
19#include <QtSql/qsqlquery.h>
20#include <QtSql/qsqlrecord.h>
21#include <QtSql/qsqlerror.h>
22
23#if QT_CONFIG(settings)
24#include <QtCore/qsettings.h>
25#endif
26
28
29#define V4THROW_SQL(error, desc) {
30 QV4::ScopedString v(scope, scope.engine->newString(desc));
31 QV4::ScopedObject ex(scope, scope.engine->newErrorObject(v));
32 ex->put(QV4::ScopedString(scope, scope.engine->newIdentifier(QStringLiteral("code"))).getPointer(), QV4::ScopedValue(scope, Value::fromInt32(error)));
33 scope.engine->throwError(ex);
34 RETURN_UNDEFINED(); \
35}
36
37#define V4THROW_SQL2(error, desc) {
38 QV4::ScopedString v(scope, scope.engine->newString(desc));
39 QV4::ScopedObject ex(scope, scope.engine->newErrorObject(v));
40 ex->put(QV4::ScopedString(scope, scope.engine->newIdentifier(QStringLiteral("code"))).getPointer(), QV4::ScopedValue(scope, Value::fromInt32(error)));
41 args->setReturnValue(scope.engine->throwError(ex));
42 return; \
43}
44
45#define V4THROW_REFERENCE(string) {
46 QV4::ScopedString v(scope, scope.engine->newString(QLatin1String(string)));
47 scope.engine->throwReferenceError(v);
48 RETURN_UNDEFINED(); \
49}
50
51
62
63V4_DEFINE_EXTENSION(QQmlSqlDatabaseData, databaseData)
64
65namespace QV4 {
66
67namespace Heap {
69 enum Type { Database, Query, Rows };
70 void init()
71 {
72 Object::init();
73 type = Database;
74 database = new QSqlDatabase;
75 version = new QString;
76 sqlQuery = new QSqlQuery;
77 }
78
79 void destroy() {
80 delete database;
81 delete version;
82 delete sqlQuery;
83 Object::destroy();
84 }
85
88
89 QString *version; // type == Database
90
91 bool inTransaction; // type == Query
92 bool readonly; // type == Query
93
94 QSqlQuery *sqlQuery; // type == Rows
95 bool forwardOnly; // type == Rows
96 };
97}
98
100{
101public:
102 V4_OBJECT2(QQmlSqlDatabaseWrapper, Object)
104
109
112
114};
115
116}
117
118using namespace QV4;
119
121
122
123
124static ReturnedValue qmlsqldatabase_version(const FunctionObject *b, const Value *thisObject, const QV4::Value *, int)
125{
126 Scope scope(b);
127 QV4::Scoped<QQmlSqlDatabaseWrapper> r(scope, thisObject->as<QQmlSqlDatabaseWrapper>());
128 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Database)
129 V4THROW_REFERENCE("Not a SQLDatabase object");
130
131 RETURN_RESULT(Encode(scope.engine->newString(*r->d()->version)));
132}
133
134static ReturnedValue qmlsqldatabase_rows_length(const FunctionObject *b, const Value *thisObject, const QV4::Value *, int)
135{
136 Scope scope(b);
137 QV4::Scoped<QQmlSqlDatabaseWrapper> r(scope, thisObject->as<QQmlSqlDatabaseWrapper>());
138 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Rows)
139 V4THROW_REFERENCE("Not a SQLDatabase::Rows object");
140
141 int s = r->d()->sqlQuery->size();
142 if (s < 0) {
143 // Inefficient
144 if (r->d()->sqlQuery->last()) {
145 s = r->d()->sqlQuery->at() + 1;
146 } else {
147 s = 0;
148 }
149 }
150 RETURN_RESULT(Encode(s));
151}
152
153static ReturnedValue qmlsqldatabase_rows_forwardOnly(const FunctionObject *b, const Value *thisObject, const QV4::Value *, int)
154{
155 Scope scope(b);
156 QV4::Scoped<QQmlSqlDatabaseWrapper> r(scope, thisObject->as<QQmlSqlDatabaseWrapper>());
157 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Rows)
158 V4THROW_REFERENCE("Not a SQLDatabase::Rows object");
159 RETURN_RESULT(Encode(r->d()->sqlQuery->isForwardOnly()));
160}
161
162static ReturnedValue qmlsqldatabase_rows_setForwardOnly(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
163{
164 Scope scope(b);
165 QV4::Scoped<QQmlSqlDatabaseWrapper> r(scope, thisObject->as<QQmlSqlDatabaseWrapper>());
166 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Rows)
167 V4THROW_REFERENCE("Not a SQLDatabase::Rows object");
168 if (argc < 1)
169 RETURN_RESULT(scope.engine->throwTypeError());
170
171 r->d()->sqlQuery->setForwardOnly(argv[0].toBoolean());
172 RETURN_UNDEFINED();
173}
174
178
179static ReturnedValue qmlsqldatabase_rows_index(const QQmlSqlDatabaseWrapper *r, ExecutionEngine *v4, quint32 index, bool *hasProperty = nullptr)
180{
181 Scope scope(v4);
182
183 if (r->d()->sqlQuery->at() == (int)index || r->d()->sqlQuery->seek(index)) {
184 QSqlRecord record = r->d()->sqlQuery->record();
185 // XXX optimize
186 ScopedObject row(scope, v4->newObject());
187 for (int ii = 0; ii < record.count(); ++ii) {
188 QVariant v = record.value(ii);
189 ScopedString s(scope, v4->newIdentifier(record.fieldName(ii)));
190 ScopedValue val(scope, v.isNull() ? Encode::null() : v4->fromVariant(v));
191 row->put(s.getPointer(), val);
192 }
193 if (hasProperty)
194 *hasProperty = true;
195 return row.asReturnedValue();
196 } else {
197 if (hasProperty)
198 *hasProperty = false;
199 return Encode::undefined();
200 }
201}
202
203ReturnedValue QQmlSqlDatabaseWrapper::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
204{
205 if (!id.isArrayIndex())
206 return Object::virtualGet(m, id, receiver, hasProperty);
207
208 uint index = id.asArrayIndex();
209 Q_ASSERT(m->as<QQmlSqlDatabaseWrapper>());
210 const QQmlSqlDatabaseWrapper *r = static_cast<const QQmlSqlDatabaseWrapper *>(m);
211 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Rows)
212 return Object::virtualGet(m, id, receiver, hasProperty);
213
214 return qmlsqldatabase_rows_index(r, r->engine(), index, hasProperty);
215}
216
217static ReturnedValue qmlsqldatabase_rows_item(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
218{
219 Scope scope(b);
220 QV4::Scoped<QQmlSqlDatabaseWrapper> r(scope, thisObject->as<QQmlSqlDatabaseWrapper>());
221 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Rows)
222 V4THROW_REFERENCE("Not a SQLDatabase::Rows object");
223
224 RETURN_RESULT(qmlsqldatabase_rows_index(r, scope.engine, argc ? argv[0].toUInt32() : 0));
225}
226
227static QVariant toSqlVariant(const QV4::ScopedValue &value)
228{
229 // toVariant() maps a null JS value to QVariant(VoidStar), but the SQL module
230 // expects a null variant. (this is because of QTBUG-40880)
231 if (value->isNull())
232 return QVariant();
233 return QV4::ExecutionEngine::toVariant(value, /*typehint*/ QMetaType {});
234}
235
236static ReturnedValue qmlsqldatabase_executeSql(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
237{
238 Scope scope(b);
239 QV4::Scoped<QQmlSqlDatabaseWrapper> r(scope, thisObject->as<QQmlSqlDatabaseWrapper>());
240 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Query)
241 V4THROW_REFERENCE("Not a SQLDatabase::Query object");
242
243 if (!r->d()->inTransaction)
244 V4THROW_SQL(SQLEXCEPTION_DATABASE_ERR,QQmlEngine::tr("executeSql called outside transaction()"));
245
246 QSqlDatabase db = *r->d()->database;
247
248 QString sql = argc ? argv[0].toQString() : QString();
249
250 if (r->d()->readonly && !sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
251 V4THROW_SQL(SQLEXCEPTION_SYNTAX_ERR, QQmlEngine::tr("Read-only Transaction"));
252 }
253
254 QSqlQuery query(db);
255 bool err = false;
256
257 ScopedValue result(scope, Value::undefinedValue());
258
259 if (query.prepare(sql)) {
260 if (argc > 1) {
261 ScopedValue values(scope, argv[1]);
262 if (values->as<ArrayObject>()) {
263 ScopedArrayObject array(scope, values);
264 quint32 size = array->getLength();
265 QV4::ScopedValue v(scope);
266 for (quint32 ii = 0; ii < size; ++ii) {
267 query.bindValue(ii, toSqlVariant((v = array->get(ii))));
268 }
269 } else if (values->as<Object>()) {
270 ScopedObject object(scope, values);
271 ObjectIterator it(scope, object, ObjectIterator::EnumerableOnly);
272 ScopedValue key(scope);
273 QV4::ScopedValue val(scope);
274 while (1) {
275 key = it.nextPropertyName(val);
276 if (key->isNull())
277 break;
278 QVariant v = toSqlVariant(val);
279 if (key->isString()) {
280 query.bindValue(key->stringValue()->toQString(), v);
281 } else {
282 Q_ASSERT(key->isInteger());
283 query.bindValue(key->integerValue(), v);
284 }
285 }
286 } else {
287 query.bindValue(0, toSqlVariant(values));
288 }
289 }
290 if (query.exec()) {
291 QV4::Scoped<QQmlSqlDatabaseWrapper> rows(scope, QQmlSqlDatabaseWrapper::create(scope.engine));
292 QV4::ScopedObject p(scope, databaseData(scope.engine)->rowsProto.value());
293 rows->setPrototypeUnchecked(p.getPointer());
294 rows->d()->type = Heap::QQmlSqlDatabaseWrapper::Rows;
295 *rows->d()->database = db;
296 *rows->d()->sqlQuery = std::move(query);
297 QSqlQuery *queryPtr = rows->d()->sqlQuery;
298
299 ScopedObject resultObject(scope, scope.engine->newObject());
300 result = resultObject.asReturnedValue();
301 // XXX optimize
302 ScopedString s(scope);
303 ScopedValue v(scope);
304 resultObject->put((s = scope.engine->newIdentifier(QLatin1String("rowsAffected"))).getPointer(),
305 (v = Value::fromInt32(queryPtr->numRowsAffected())));
306 resultObject->put((s = scope.engine->newIdentifier(QLatin1String("insertId"))).getPointer(),
307 (v = scope.engine->newString(queryPtr->lastInsertId().toString())));
308 resultObject->put((s = scope.engine->newIdentifier(QLatin1String("rows"))).getPointer(),
309 rows);
310 } else {
311 err = true;
312 }
313 } else {
314 err = true;
315 }
316 if (err)
317 V4THROW_SQL(SQLEXCEPTION_DATABASE_ERR,query.lastError().text());
318
319 RETURN_RESULT(result->asReturnedValue());
320}
321
325
326 TransactionRollback(QSqlDatabase *database, bool *transactionFlag)
327 : db(database)
328 , inTransactionFlag(transactionFlag)
329 {
331 *inTransactionFlag = true;
332 }
333
335 {
337 *inTransactionFlag = false;
338 if (db)
339 db->rollback();
340 }
341
342 void clear() {
343 db = nullptr;
345 *inTransactionFlag = false;
346 inTransactionFlag = nullptr;
347 }
348};
349
350
351static ReturnedValue qmlsqldatabase_changeVersion(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
352{
353 Scope scope(b);
354 if (argc < 2)
355 RETURN_UNDEFINED();
356
357 Scoped<QQmlSqlDatabaseWrapper> r(scope, *thisObject);
358 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Database)
359 V4THROW_REFERENCE("Not a SQLDatabase object");
360
361 QSqlDatabase db = *r->d()->database;
362 QString from_version = argv[0].toQString();
363 QString to_version = argv[1].toQString();
364 ScopedFunctionObject callback(scope, argc > 2 ? argv[2] : Value::undefinedValue());
365
366 if (from_version != *r->d()->version)
367 V4THROW_SQL(SQLEXCEPTION_VERSION_ERR, QQmlEngine::tr("Version mismatch: expected %1, found %2").arg(from_version).arg(*r->d()->version));
368
369 bool ok = true;
370 if (!!callback) {
371 Scoped<QQmlSqlDatabaseWrapper> query(scope, QQmlSqlDatabaseWrapper::create(scope.engine));
372 ScopedObject p(scope, databaseData(scope.engine)->queryProto.value());
373 query->setPrototypeUnchecked(p.getPointer());
374 query->d()->type = Heap::QQmlSqlDatabaseWrapper::Query;
375 *query->d()->database = db;
376 *query->d()->version = *r->d()->version;
377
378 ok = false;
379 db.transaction();
380
381 JSCallArguments jsCall(scope, 1);
382 *jsCall.thisObject = scope.engine->globalObject;
383 jsCall.args[0] = query;
384
385 TransactionRollback rollbackOnException(&db, &query->d()->inTransaction);
386 callback->call(jsCall);
387 rollbackOnException.clear();
388 if (!db.commit()) {
389 db.rollback();
390 V4THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QQmlEngine::tr("SQL transaction failed"));
391 } else {
392 ok = true;
393 }
394 }
395
396 if (ok) {
397 Scoped<QQmlSqlDatabaseWrapper> w(scope, QQmlSqlDatabaseWrapper::create(scope.engine));
398 ScopedObject p(scope, databaseData(scope.engine)->databaseProto.value());
399 w->setPrototypeUnchecked(p.getPointer());
400 w->d()->type = Heap::QQmlSqlDatabaseWrapper::Database;
401 *w->d()->database = db;
402 *w->d()->version = to_version;
403#if QT_CONFIG(settings)
404 const QQmlEnginePrivate *enginePrivate = QQmlEnginePrivate::get(scope.engine->qmlEngine());
405 QSettings ini(enginePrivate->offlineStorageDatabaseDirectory() + db.connectionName() + QLatin1String(".ini"), QSettings::IniFormat);
406 ini.setValue(QLatin1String("Version"), to_version);
407#endif
408 RETURN_RESULT(w.asReturnedValue());
409 }
410
411 RETURN_UNDEFINED();
412}
413
414static ReturnedValue qmlsqldatabase_transaction_shared(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc, bool readOnly)
415{
416 Scope scope(b);
417 QV4::Scoped<QQmlSqlDatabaseWrapper> r(scope, thisObject->as<QQmlSqlDatabaseWrapper>());
418 if (!r || r->d()->type != Heap::QQmlSqlDatabaseWrapper::Database)
419 V4THROW_REFERENCE("Not a SQLDatabase object");
420
421 const FunctionObject *callback = argc ? argv[0].as<FunctionObject>() : nullptr;
422 if (!callback)
423 V4THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR, QQmlEngine::tr("transaction: missing callback"));
424
425 QSqlDatabase db = *r->d()->database;
426
427 Scoped<QQmlSqlDatabaseWrapper> w(scope, QQmlSqlDatabaseWrapper::create(scope.engine));
428 QV4::ScopedObject p(scope, databaseData(scope.engine)->queryProto.value());
429 w->setPrototypeUnchecked(p.getPointer());
430 w->d()->type = Heap::QQmlSqlDatabaseWrapper::Query;
431 *w->d()->database = db;
432 *w->d()->version = *r->d()->version;
433 w->d()->readonly = readOnly;
434
435 db.transaction();
436 if (callback) {
437 JSCallArguments jsCall(scope, 1);
438 *jsCall.thisObject = scope.engine->globalObject;
439 jsCall.args[0] = w;
440 TransactionRollback rollbackOnException(&db, &w->d()->inTransaction);
441 callback->call(jsCall);
442 rollbackOnException.clear();
443
444 if (!db.commit())
445 db.rollback();
446 }
447
448 RETURN_UNDEFINED();
449}
450
451static ReturnedValue qmlsqldatabase_transaction(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
452{
453 return qmlsqldatabase_transaction_shared(f, thisObject, argv, argc, false);
454}
455
456static ReturnedValue qmlsqldatabase_read_transaction(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
457{
458 return qmlsqldatabase_transaction_shared(f, thisObject, argv, argc, true);
459}
460
462{
463 Scope scope(v4);
464 {
465 ScopedObject proto(scope, v4->newObject());
466 proto->defineDefaultProperty(QStringLiteral("transaction"), qmlsqldatabase_transaction);
467 proto->defineDefaultProperty(QStringLiteral("readTransaction"), qmlsqldatabase_read_transaction);
468 proto->defineAccessorProperty(QStringLiteral("version"), qmlsqldatabase_version, nullptr);
469 proto->defineDefaultProperty(QStringLiteral("changeVersion"), qmlsqldatabase_changeVersion);
470 databaseProto = proto;
471 }
472
473 {
474 ScopedObject proto(scope, v4->newObject());
475 proto->defineDefaultProperty(QStringLiteral("executeSql"), qmlsqldatabase_executeSql);
476 queryProto = proto;
477 }
478 {
479 ScopedObject proto(scope, v4->newObject());
480 proto->defineDefaultProperty(QStringLiteral("item"), qmlsqldatabase_rows_item);
481 proto->defineAccessorProperty(QStringLiteral("length"), qmlsqldatabase_rows_length, nullptr);
482 proto->defineAccessorProperty(QStringLiteral("forwardOnly"),
483 qmlsqldatabase_rows_forwardOnly, qmlsqldatabase_rows_setForwardOnly);
484 rowsProto = proto;
485 }
486}
487
488/*
489HTML5 "spec" says "rs.rows[n]", but WebKit only impelments "rs.rows.item(n)". We do both (and property iterator).
490We add a "forwardOnly" property that stops Qt caching results (code promises to only go forward
491through the data.
492*/
493
494/*!
495 \qmlmodule QtQuick.LocalStorage
496 \title Qt Quick Local Storage QML Types
497 \ingroup qmlmodules
498 \brief Provides a JavaScript object singleton type for accessing a local
499 SQLite database
500
501 This is a singleton type for reading and writing to SQLite databases.
502
503
504 \section1 Methods
505
506 \list
507 \li object \b{\l{#openDatabaseSync}{openDatabaseSync}}(string name, string version, string
508description, int estimated_size, jsobject callback(db)) \endlist
509
510
511 \section1 Detailed Description
512
513 To use the types in this module, import the module and call the
514 relevant functions using the \c LocalStorage type:
515
516 \qml
517 import QtQuick
518 import QtQuick.LocalStorage
519
520 Item {
521 Component.onCompleted: {
522 var db = LocalStorage.openDatabaseSync(...)
523 }
524 }
525 \endqml
526
527
528These databases are user-specific and QML-specific, but accessible to all QML applications.
529They are stored in the \c Databases subdirectory
530of QQmlEngine::offlineStoragePath(), currently as SQLite databases.
531
532Database connections are automatically closed during Javascript garbage collection.
533
534The API can be used from JavaScript functions in your QML:
535
536\snippet qml/localstorage/hello.qml 0
537
538The API conforms to the Synchronous API of the HTML5 Web Database API,
539\link http://www.w3.org/TR/2009/WD-webdatabase-20091029/ W3C Working Draft 29 October 2009\endlink.
540
541The \l{Qt Quick Examples - Local Storage}{SQL Local Storage example} demonstrates the basics of
542using the Offline Storage API.
543
544\section3 Open or Create a Database
545
546\qml
547import QtQuick.LocalStorage as Sql
548
549db = Sql.openDatabaseSync(identifier, version, description, estimated_size, callback(db))
550\endqml
551
552The above code returns the database identified by \e identifier. If the database does not already
553exist, it is created, and the function \e callback is called with the database as a parameter.
554\e identifier is the name of the physical file (with or without relative path) containing the
555database. \e description and \e estimated_size are written to the INI file (described below), but
556are currently unused.
557
558May throw exception with code property SQLException.DATABASE_ERR, or SQLException.VERSION_ERR.
559
560When a database is first created, an INI file is also created specifying its characteristics:
561
562\table
563\header \li \b {Key} \li \b {Value}
564\row \li Identifier \li The name of the database passed to \c openDatabase()
565\row \li Version \li The version of the database passed to \c openDatabase()
566\row \li Description \li The description of the database passed to \c openDatabase()
567\row \li EstimatedSize \li The estimated size (in bytes) of the database passed to \c openDatabase()
568\row \li Driver \li Currently "QSQLITE"
569\endtable
570
571This data can be used by application tools.
572
573\section3 db.changeVersion(from, to, callback(tx))
574
575This method allows you to perform a \e{Scheme Upgrade}. If it succeeds it returns a new
576database object of version \e to. Otherwise it returns \e undefined.
577
578If the current version of \e db is not \e from, then an exception is thrown.
579
580Otherwise, a database transaction is created and passed to \e callback. In this function,
581you can call \e executeSql on \e tx to upgrade the database.
582
583May throw exception with code property SQLException.DATABASE_ERR or SQLException.UNKNOWN_ERR.
584
585See example below.
586
587\badcode
588 var db = LocalStorage.openDatabaseSync("ActivityTrackDB", "", "Database tracking sports
589activities", 1000000); if (db.version == "0.1") { db.changeVersion("0.1", "0.2", function(tx) {
590 tx.executeSql("INSERT INTO trip_log VALUES(?, ?, ?)",
591 [ "01/10/2016","Sylling - Vikersund", "53" ]);
592 }
593 });
594\endcode
595
596\section3 db.transaction(callback(tx))
597
598This method creates a read/write transaction and passed to \e callback. In this function,
599you can call \e executeSql on \e tx to read and modify the database.
600
601If the callback throws exceptions, the transaction is rolled back.
602Below you will find an example of a database transaction which catches exceptions.
603
604
605\quotefromfile localstorage/Database.js
606\skipuntil dbInit()
607\printto dbGetHandle
608
609In the example you can see an \c insert statement where values are assigned to the fields,
610and the record is written into the table. That is an \c insert statement with a syntax that is usual
611for a relational database. It is however also possible to work with JSON objects and
612store them in a table.
613
614Let's suppose a simple example where we store trips in JSON format using \c date as the unique key.
615An example of a table that could be used for that purpose:
616
617\badcode
618 create table trip_log(date text, data text)
619\endcode
620
621The assignment of values to a JSON object:
622
623\badcode
624 var obj = {description = "Vikersund - Noresund", distance = "60"}
625\endcode
626
627In that case, the data could be saved in the following way:
628
629\badcode
630 db.transaction(function(tx) {
631 result = tx.executeSQL("insert into trip_log values (?,?)",
632 ["01/11/2016", JSON.stringify(obj)])
633
634\endcode
635
636\section3 db.readTransaction(callback(tx))
637
638This method creates a read-only transaction and passed to \e callback. In this function,
639you can call \e executeSql on \e tx to read the database (with \c select statements).
640
641\section3 results = tx.executeSql(statement, values)
642
643This method executes an SQL \e statement, binding the list of \e values to SQL positional parameters
644("?").
645
646It returns a results object, with the following properties:
647
648\table
649\header \li \b {Type} \li \b {Property} \li \b {Value} \li \b {Applicability}
650\row \li int \li rows.length \li The number of rows in the result \li SELECT
651\row \li var \li rows.item(i) \li Function that returns row \e i of the result \li SELECT
652\row \li int \li rowsAffected \li The number of rows affected by a modification \li UPDATE, DELETE
653\row \li string \li insertId \li The id of the row inserted \li INSERT
654\endtable
655
656May throw exception with code property SQLException.DATABASE_ERR, SQLException.SYNTAX_ERR, or
657SQLException.UNKNOWN_ERR.
658
659See below for an example:
660
661\quotefromfile localstorage/Database.js
662\skipto dbReadAll()
663\printto dbUpdate(Pdate
664
665\section1 Method Documentation
666
667\target openDatabaseSync
668\code
669object openDatabaseSync(string name, string version, string description, int estimated_size,
670jsobject callback(db)) \endcode
671
672Opens or creates a local storage sql database by the given parameters.
673
674\list
675\li \c name is the database name
676\li \c version is the database version
677\li \c description is the database display name
678\li \c estimated_size is the database's estimated size, in bytes
679\li \c callback is an optional parameter, which is invoked if the database has not yet been created.
680\endlist
681
682Returns the created database object.
683
684*/
685
686void QQmlLocalStorage::openDatabaseSync(QQmlV4FunctionPtr args)
687{
688#if QT_CONFIG(settings)
689 QV4::Scope scope(args->v4engine());
690 if (scope.engine->qmlEngine()->offlineStoragePath().isEmpty())
691 V4THROW_SQL2(SQLEXCEPTION_DATABASE_ERR, QQmlEngine::tr("SQL: can't create database, offline storage is disabled."));
692
693 QV4::ScopedValue v(scope);
694 QString dbname = (v = (*args)[0])->toQStringNoThrow();
695 QString dbversion = (v = (*args)[1])->toQStringNoThrow();
696 QString dbdescription = (v = (*args)[2])->toQStringNoThrow();
697 int dbestimatedsize = (v = (*args)[3])->toInt32();
698 FunctionObject *dbcreationCallback = (v = (*args)[4])->as<FunctionObject>();
699 QString basename = args->v4engine()->qmlEngine()->offlineStorageDatabaseFilePath(dbname);
700 QFileInfo dbFile(basename);
701 if (!QDir().mkpath(dbFile.dir().absolutePath())) {
702 const QString message = QQmlEngine::tr("LocalStorage: can't create path %1").
703 arg(QDir::toNativeSeparators(dbFile.dir().absolutePath()));
704 V4THROW_SQL2(SQLEXCEPTION_DATABASE_ERR, message);
705 }
706 QString dbid = dbFile.fileName();
707 bool created = false;
708 QString version = dbversion;
709 QSqlDatabase database;
710
711 {
712 QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat);
713
714 if (QSqlDatabase::connectionNames().contains(dbid)) {
715 database = QSqlDatabase::database(dbid);
716 version = ini.value(QLatin1String("Version")).toString();
717 if (version != dbversion && !dbversion.isEmpty() && !version.isEmpty())
718 V4THROW_SQL2(SQLEXCEPTION_VERSION_ERR, QQmlEngine::tr("SQL: database version mismatch"));
719 } else {
720 created = !QFile::exists(basename+QLatin1String(".sqlite"));
721 if (created) {
722 ini.setValue(QLatin1String("Name"), dbname);
723 if (dbcreationCallback)
724 version = QString();
725 ini.setValue(QLatin1String("Version"), version);
726 ini.setValue(QLatin1String("Description"), dbdescription);
727 ini.setValue(QLatin1String("EstimatedSize"), dbestimatedsize);
728 ini.setValue(QLatin1String("Driver"), QLatin1String("QSQLITE"));
729 } else {
730 if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
731 // Incompatible
732 V4THROW_SQL2(SQLEXCEPTION_VERSION_ERR,QQmlEngine::tr("SQL: database version mismatch"));
733 }
734 version = ini.value(QLatin1String("Version")).toString();
735 }
736 database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
737 database.setDatabaseName(basename+QLatin1String(".sqlite"));
738 }
739 if (!database.isOpen() && !database.open())
740 V4THROW_SQL2(SQLEXCEPTION_DATABASE_ERR, QQmlEngine::tr("SQL: Cannot open database"));
741 }
742
743 QV4::Scoped<QQmlSqlDatabaseWrapper> db(scope, QQmlSqlDatabaseWrapper::create(scope.engine));
744 QV4::ScopedObject p(scope, databaseData(scope.engine)->databaseProto.value());
745 db->setPrototypeUnchecked(p.getPointer());
746 *db->d()->database = database;
747 *db->d()->version = version;
748
749 if (created && dbcreationCallback) {
750 JSCallArguments jsCall(scope, 1);
751 *jsCall.thisObject = scope.engine->globalObject;
752 jsCall.args[0] = db;
753 dbcreationCallback->call(jsCall);
754 }
755
756 args->setReturnValue(db.asReturnedValue());
757#else
758 Q_UNUSED(args);
759#endif // settings
760}
761
762QT_END_NAMESPACE
763
764#include "moc_qqmllocalstorage_p.cpp"
QV4::PersistentValue rowsProto
QV4::PersistentValue databaseProto
QV4::PersistentValue queryProto
QQmlSqlDatabaseData(QV4::ExecutionEngine *engine)
Definition qjsvalue.h:23
static ReturnedValue qmlsqldatabase_version(const FunctionObject *b, const Value *thisObject, const QV4::Value *, int)
static ReturnedValue qmlsqldatabase_transaction(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
static ReturnedValue qmlsqldatabase_rows_length(const FunctionObject *b, const Value *thisObject, const QV4::Value *, int)
static ReturnedValue qmlsqldatabase_read_transaction(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
static ReturnedValue qmlsqldatabase_executeSql(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
static ReturnedValue qmlsqldatabase_rows_setForwardOnly(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
DEFINE_OBJECT_VTABLE(QV4::QQmlSqlDatabaseWrapper)
#define V4THROW_SQL(error, desc)
static QVariant toSqlVariant(const QV4::ScopedValue &value)
static ReturnedValue qmlsqldatabase_rows_item(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
static ReturnedValue qmlsqldatabase_rows_forwardOnly(const FunctionObject *b, const Value *thisObject, const QV4::Value *, int)
static ReturnedValue qmlsqldatabase_rows_index(const QQmlSqlDatabaseWrapper *r, ExecutionEngine *v4, quint32 index, bool *hasProperty=nullptr)
static ReturnedValue qmlsqldatabase_transaction_shared(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc, bool readOnly)
static ReturnedValue qmlsqldatabase_changeVersion(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
#define V4THROW_REFERENCE(string)
TransactionRollback(QSqlDatabase *database, bool *transactionFlag)