Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Move sqldatabase into a module API plugin
Change-Id: Icd0bbfe16804abf1bbadbabddf3a30b5b18df30c
Reviewed-by: Martin Jones <martin.jones@nokia.com>
  • Loading branch information
yinyunqiao authored and Qt by Nokia committed Jan 25, 2012
1 parent 149f6af commit b286901
Show file tree
Hide file tree
Showing 33 changed files with 916 additions and 171 deletions.
2 changes: 1 addition & 1 deletion doc/src/declarative/advtutorial.qdoc
Expand Up @@ -428,7 +428,7 @@ Here is the \c saveHighScore() function in \c samegame.js:

First we call \c sendHighScore() (explained in the section below) if it is possible to send the high scores to an online database.

Then, we use the \l{Offline Storage API} to maintain a persistent SQL database unique to this application. We create an offline storage database for the high scores using \c openDatabase() and prepare the data and SQL query that we want to use to save it. The offline storage API uses SQL queries for data manipulation and retrieval, and in the \c db.transaction() call we use three SQL queries to initialize the database (if necessary), and then add to and retrieve high scores. To use the returned data, we turn it into a string with one line per row returned, and show a dialog containing that string.
Then, we use the \l{Offline Storage API} to maintain a persistent SQL database unique to this application. We create an offline storage database for the high scores using \c openDatabaseSync() and prepare the data and SQL query that we want to use to save it. The offline storage API uses SQL queries for data manipulation and retrieval, and in the \c db.transaction() call we use three SQL queries to initialize the database (if necessary), and then add to and retrieve high scores. To use the returned data, we turn it into a string with one line per row returned, and show a dialog containing that string.

This is one way of storing and displaying high scores locally, but certainly not the only way. A more complex alternative would be to create a high score dialog component, and pass it the results for processing and display (instead of reusing the \c Dialog). This would allow a more themeable dialog that could better present the high scores. If your QML is the UI for a C++ application, you could also have passed the score to a C++ function to store it locally in a variety of ways, including a simple format without SQL or in another SQL database.

Expand Down
84 changes: 0 additions & 84 deletions doc/src/declarative/globalobject.qdoc
Expand Up @@ -118,90 +118,6 @@ browser. The following objects and properties are supported by the QML implemen
The \l{declarative/xml/xmlhttprequest}{XMLHttpRequest example} demonstrates how to
use the XMLHttpRequest object to make a request and read the response headers.

\section1 Offline Storage API

\section2 Database API

The \c openDatabaseSync() and related functions
provide the ability to access local offline storage in an SQL database.

These databases are user-specific and QML-specific, but accessible to all QML applications.
They are stored in the \c Databases subdirectory
of QDeclarativeEngine::offlineStoragePath(), currently as SQLite databases.

Database connections are automatically closed during Javascript garbage collection.

The API can be used from JavaScript functions in your QML:

\snippet declarative/sqllocalstorage/hello.qml 0

The API conforms to the Synchronous API of the HTML5 Web Database API,
\link http://www.w3.org/TR/2009/WD-webdatabase-20091029/ W3C Working Draft 29 October 2009\endlink.

The \l{declarative/sqllocalstorage}{SQL Local Storage example} demonstrates the basics of
using the Offline Storage API.

\section3 db = openDatabaseSync(identifier, version, description, estimated_size, callback(db))

Returns the database identified by \i identifier. If the database does not already exist, it
is created, and the function \i callback is called with the database as a parameter. \i description
and \i estimated_size are written to the INI file (described below), but are otherwise currently
unused.

May throw exception with code property SQLException.DATABASE_ERR, or SQLException.VERSION_ERR.

When a database is first created, an INI file is also created specifying its characteristics:

\table
\header \o \bold {Key} \o \bold {Value}
\row \o Name \o The name of the database passed to \c openDatabase()
\row \o Version \o The version of the database passed to \c openDatabase()
\row \o Description \o The description of the database passed to \c openDatabase()
\row \o EstimatedSize \o The estimated size (in bytes) of the database passed to \c openDatabase()
\row \o Driver \o Currently "QSQLITE"
\endtable

This data can be used by application tools.

\section3 db.changeVersion(from, to, callback(tx))

This method allows you to perform a \i{Scheme Upgrade}.

If the current version of \i db is not \i from, then an exception is thrown.

Otherwise, a database transaction is created and passed to \i callback. In this function,
you can call \i executeSql on \i tx to upgrade the database.

May throw exception with code property SQLException.DATABASE_ERR or SQLException.UNKNOWN_ERR.

\section3 db.transaction(callback(tx))

This method creates a read/write transaction and passed to \i callback. In this function,
you can call \i executeSql on \i tx to read and modify the database.

If the callback throws exceptions, the transaction is rolled back.

\section3 db.readTransaction(callback(tx))

This method creates a read-only transaction and passed to \i callback. In this function,
you can call \i executeSql on \i tx to read the database (with SELECT statements).

\section3 results = tx.executeSql(statement, values)

This method executes a SQL \i statement, binding the list of \i values to SQL positional parameters ("?").

It returns a results object, with the following properties:

\table
\header \o \bold {Type} \o \bold {Property} \o \bold {Value} \o \bold {Applicability}
\row \o int \o rows.length \o The number of rows in the result \o SELECT
\row \o var \o rows.item(i) \o Function that returns row \i i of the result \o SELECT
\row \o int \o rowsAffected \o The number of rows affected by a modification \o UPDATE, DELETE
\row \o string \o insertId \o The id of the row inserted \o INSERT
\endtable

May throw exception with code property SQLException.DATABASE_ERR, SQLException.SYNTAX_ERR, or SQLException.UNKNOWN_ERR.

\section1 Logging

\c console.log(), \c console.debug(), \c console.time(), and \c console.timeEnd() can be used to print information
Expand Down
5 changes: 3 additions & 2 deletions examples/declarative/samegame/content/samegame.js
@@ -1,5 +1,6 @@
/* This script file handles the game logic */
.pragma library
.import QtQuick.LocalStorage 2.0 as Sql

var maxColumn = 10;
var maxRow = 15;
Expand Down Expand Up @@ -229,7 +230,7 @@ function createBlock(column,row)

function initHighScoreBar()
{
var db = openDatabaseSync(
var db = Sql.openDatabaseSync(
"SameGameScores",
"1.0",
"Local SameGame High Scores",
Expand All @@ -254,7 +255,7 @@ function saveHighScore(name)
if (scoresURL != "")
sendHighScore(name);
// Offline storage
var db = openDatabaseSync(
var db = Sql.openDatabaseSync(
"SameGameScores",
"1.0",
"Local SameGame High Scores",
Expand Down
3 changes: 2 additions & 1 deletion examples/declarative/snake/content/HighScoreModel.qml
Expand Up @@ -40,6 +40,7 @@
****************************************************************************/

import QtQuick 2.0
import QtQuick.LocalStorage 2.0 as Sql

// Models a high score table.
//
Expand Down Expand Up @@ -84,7 +85,7 @@ ListModel {

function __db()
{
return openDatabaseSync("HighScoreModel", "1.0", "Generic High Score Functionality for QML", 1000000);
return Sql.openDatabaseSync("HighScoreModel", "1.0", "Generic High Score Functionality for QML", 1000000);
}
function __ensureTables(tx)
{
Expand Down
3 changes: 2 additions & 1 deletion examples/declarative/sqllocalstorage/hello.qml
Expand Up @@ -39,6 +39,7 @@
****************************************************************************/
//![0]
import QtQuick 2.0
import QtQuick.LocalStorage 2.0 as LS

Rectangle {
color: "white"
Expand All @@ -49,7 +50,7 @@ Rectangle {
text: "?"
anchors.horizontalCenter: parent.horizontalCenter
function findGreetings() {
var db = openDatabaseSync("QDeclarativeExampleDB", "1.0", "The Example QML SQL!", 1000000);
var db = LS.openDatabaseSync("QDeclarativeExampleDB", "1.0", "The Example QML SQL!", 1000000);

db.transaction(
function(tx) {
Expand Down
@@ -1,4 +1,6 @@
/* This script file handles the game logic */
.import QtQuick.LocalStorage 2.0 as Sql

var maxColumn = 10;
var maxRow = 15;
var maxIndex = maxColumn * maxRow;
Expand Down Expand Up @@ -191,7 +193,7 @@ function saveHighScore(name) {
if (scoresURL != "")
sendHighScore(name);

var db = openDatabaseSync("SameGameScores", "1.0", "Local SameGame High Scores", 100);
var db = Sql.openDatabaseSync("SameGameScores", "1.0", "Local SameGame High Scores", 100);
var dataStr = "INSERT INTO Scores VALUES(?, ?, ?, ?)";
var data = [name, gameCanvas.score, maxColumn + "x" + maxRow, Math.floor(gameDuration / 1000)];
db.transaction(function(tx) {
Expand Down Expand Up @@ -222,4 +224,3 @@ function sendHighScore(name) {
postman.send(postData);
}
//![1]

12 changes: 10 additions & 2 deletions src/declarative/qml/qdeclarativeengine.cpp
Expand Up @@ -74,6 +74,9 @@
#include "qdeclarativeincubator.h"
#include <private/qv8profilerservice_p.h>

#include <QtCore/qstandardpaths.h>
#include <QtCore/qsettings.h>

#include <QtCore/qmetaobject.h>
#include <QNetworkAccessManager>
#include <QDebug>
Expand Down Expand Up @@ -454,6 +457,11 @@ void QDeclarativeEnginePrivate::init()
QDeclarativeDebugTrace::initialize();
QDebugMessageService::instance();
}

QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
offlineStoragePath = dataLocation.replace(QLatin1Char('/'), QDir::separator()) +
QDir::separator() + QLatin1String("QML") +
QDir::separator() + QLatin1String("OfflineStorage");
}

QDeclarativeWorkerScriptEngine *QDeclarativeEnginePrivate::getWorkerScriptEngine()
Expand Down Expand Up @@ -1543,13 +1551,13 @@ bool QDeclarativeEngine::importPlugin(const QString &filePath, const QString &ur
void QDeclarativeEngine::setOfflineStoragePath(const QString& dir)
{
Q_D(QDeclarativeEngine);
qt_qmlsqldatabase_setOfflineStoragePath(d->v8engine(), dir);
d->offlineStoragePath = dir;
}

QString QDeclarativeEngine::offlineStoragePath() const
{
Q_D(const QDeclarativeEngine);
return qt_qmlsqldatabase_getOfflineStoragePath(d->v8engine());
return d->offlineStoragePath;
}

static void voidptr_destructor(void *v)
Expand Down
63 changes: 10 additions & 53 deletions src/declarative/qml/qdeclarativesqldatabase.cpp
Expand Up @@ -59,31 +59,10 @@
#include <QtCore/qdebug.h>

#include <private/qv8engine_p.h>
#include <private/qv8sqlerrors_p.h>

QT_BEGIN_NAMESPACE

enum SqlException {
UNKNOWN_ERR,
DATABASE_ERR,
VERSION_ERR,
TOO_LARGE_ERR,
QUOTA_ERR,
SYNTAX_ERR,
CONSTRAINT_ERR,
TIMEOUT_ERR
};

static const char* sqlerror[] = {
"UNKNOWN_ERR",
"DATABASE_ERR",
"VERSION_ERR",
"TOO_LARGE_ERR",
"QUOTA_ERR",
"SYNTAX_ERR",
"CONSTRAINT_ERR",
"TIMEOUT_ERR",
0
};

#define THROW_SQL(error, desc)

Expand All @@ -109,7 +88,6 @@ struct QDeclarativeSqlDatabaseData {
QDeclarativeSqlDatabaseData(QV8Engine *engine);
~QDeclarativeSqlDatabaseData();

QString offlineStoragePath;
v8::Persistent<v8::Function> constructor;
v8::Persistent<v8::Function> queryConstructor;
v8::Persistent<v8::Function> rowsConstructor;
Expand Down Expand Up @@ -201,7 +179,7 @@ QDeclarativeSqlDatabaseData::~QDeclarativeSqlDatabaseData()

static QString qmlsqldatabase_databasesPath(QV8Engine *engine)
{
return QDeclarativeSqlDatabaseData::data(engine)->offlineStoragePath +
return engine->engine()->offlineStoragePath() +
QDir::separator() + QLatin1String("Databases");
}

Expand Down Expand Up @@ -264,14 +242,14 @@ static v8::Handle<v8::Value> qmlsqldatabase_executeSql(const v8::Arguments& args
QV8Engine *engine = r->engine;

if (!r->inTransaction)
V8THROW_SQL(DATABASE_ERR,QDeclarativeEngine::tr("executeSql called outside transaction()"));
V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,QDeclarativeEngine::tr("executeSql called outside transaction()"));

QSqlDatabase db = r->database;

QString sql = engine->toString(args[0]);

if (r->readonly && !sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
V8THROW_SQL(SYNTAX_ERR, QDeclarativeEngine::tr("Read-only Transaction"));
V8THROW_SQL(SQLEXCEPTION_SYNTAX_ERR, QDeclarativeEngine::tr("Read-only Transaction"));
}

QSqlQuery query(db);
Expand Down Expand Up @@ -319,7 +297,7 @@ static v8::Handle<v8::Value> qmlsqldatabase_executeSql(const v8::Arguments& args
err = true;
}
if (err)
V8THROW_SQL(DATABASE_ERR,query.lastError().text());
V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,query.lastError().text());

return result;
}
Expand All @@ -341,7 +319,7 @@ static v8::Handle<v8::Value> qmlsqldatabase_changeVersion(const v8::Arguments& a
v8::Handle<v8::Value> callback = args[2];

if (from_version != r->version)
V8THROW_SQL(VERSION_ERR, QDeclarativeEngine::tr("Version mismatch: expected %1, found %2").arg(from_version).arg(r->version));
V8THROW_SQL(SQLEXCEPTION_VERSION_ERR, QDeclarativeEngine::tr("Version mismatch: expected %1, found %2").arg(from_version).arg(r->version));

v8::Local<v8::Object> instance = QDeclarativeSqlDatabaseData::data(engine)->queryConstructor->NewInstance();
QV8SqlDatabaseResource *r2 = new QV8SqlDatabaseResource(engine);
Expand All @@ -366,7 +344,7 @@ static v8::Handle<v8::Value> qmlsqldatabase_changeVersion(const v8::Arguments& a
return v8::Handle<v8::Value>();
} else if (!db.commit()) {
db.rollback();
V8THROW_SQL(UNKNOWN_ERR,QDeclarativeEngine::tr("SQL transaction failed"));
V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QDeclarativeEngine::tr("SQL transaction failed"));
} else {
ok = true;
}
Expand Down Expand Up @@ -394,7 +372,7 @@ static v8::Handle<v8::Value> qmlsqldatabase_transaction_shared(const v8::Argumen
QV8Engine *engine = r->engine;

if (args.Length() == 0 || !args[0]->IsFunction())
V8THROW_SQL(UNKNOWN_ERR,QDeclarativeEngine::tr("transaction: missing callback"));
V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QDeclarativeEngine::tr("transaction: missing callback"));

QSqlDatabase db = r->database;
v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(args[0]);
Expand Down Expand Up @@ -467,7 +445,7 @@ static v8::Handle<v8::Value> qmlsqldatabase_open_sync(const v8::Arguments& args)
database = QSqlDatabase::database(dbid);
version = ini.value(QLatin1String("Version")).toString();
if (version != dbversion && !dbversion.isEmpty() && !version.isEmpty())
V8THROW_SQL(VERSION_ERR, QDeclarativeEngine::tr("SQL: database version mismatch"));
V8THROW_SQL(SQLEXCEPTION_VERSION_ERR, QDeclarativeEngine::tr("SQL: database version mismatch"));
} else {
created = !QFile::exists(basename+QLatin1String(".sqlite"));
database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
Expand All @@ -482,7 +460,7 @@ static v8::Handle<v8::Value> qmlsqldatabase_open_sync(const v8::Arguments& args)
} else {
if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
// Incompatible
V8THROW_SQL(VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
V8THROW_SQL(SQLEXCEPTION_VERSION_ERR,QDeclarativeEngine::tr("SQL: database version mismatch"));
}
version = ini.value(QLatin1String("Version")).toString();
}
Expand Down Expand Up @@ -517,11 +495,6 @@ static v8::Handle<v8::Value> qmlsqldatabase_open_sync(const v8::Arguments& args)

QDeclarativeSqlDatabaseData::QDeclarativeSqlDatabaseData(QV8Engine *engine)
{
QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
offlineStoragePath = dataLocation.replace(QLatin1Char('/'), QDir::separator()) +
QDir::separator() + QLatin1String("QML") +
QDir::separator() + QLatin1String("OfflineStorage");

{
v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
ft->InstanceTemplate()->SetHasExternalResource(true);
Expand Down Expand Up @@ -559,12 +532,6 @@ void *qt_add_qmlsqldatabase(QV8Engine *engine)
v8::Local<v8::Function> openDatabase = V8FUNCTION(qmlsqldatabase_open_sync, engine);
engine->global()->Set(v8::String::New("openDatabaseSync"), openDatabase);

v8::PropertyAttribute attributes = (v8::PropertyAttribute)(v8::ReadOnly | v8::DontEnum | v8::DontDelete);
v8::Local<v8::Object> sqlExceptionPrototype = v8::Object::New();
for (int i=0; sqlerror[i]; ++i)
sqlExceptionPrototype->Set(v8::String::New(sqlerror[i]), v8::Integer::New(i), attributes);
engine->global()->Set(v8::String::New("SQLException"), sqlExceptionPrototype);

return (void *)new QDeclarativeSqlDatabaseData(engine);
}

Expand All @@ -574,16 +541,6 @@ void qt_rem_qmlsqldatabase(QV8Engine * /* engine */, void *d)
delete data;
}

void qt_qmlsqldatabase_setOfflineStoragePath(QV8Engine *engine, const QString &path)
{
QDeclarativeSqlDatabaseData::data(engine)->offlineStoragePath = path;
}

QString qt_qmlsqldatabase_getOfflineStoragePath(const QV8Engine *engine)
{
return QDeclarativeSqlDatabaseData::data(const_cast<QV8Engine *>(engine))->offlineStoragePath;
}

/*
HTML5 "spec" says "rs.rows[n]", but WebKit only impelments "rs.rows.item(n)". We do both (and property iterator).
We add a "forwardOnly" property that stops Qt caching results (code promises to only go forward
Expand Down
2 changes: 0 additions & 2 deletions src/declarative/qml/qdeclarativesqldatabase_p.h
Expand Up @@ -62,8 +62,6 @@ class QV8Engine;

void *qt_add_qmlsqldatabase(QV8Engine *engine);
void qt_rem_qmlsqldatabase(QV8Engine *engine, void *);
void qt_qmlsqldatabase_setOfflineStoragePath(QV8Engine *engine, const QString &);
QString qt_qmlsqldatabase_getOfflineStoragePath(const QV8Engine *);

QT_END_NAMESPACE

Expand Down

0 comments on commit b286901

Please sign in to comment.