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

source code of qtdeclarative/src/qmllocalstorage/qqmllocalstorage.cpp