OCT
14
2010

SPARQL queries in QML with QSparql

We've been working on QSparql for a few months now, and I feel it is starting to be something that could be used by a wider audience. It is a simple QSql-like library for accessing various RDF stores such as KDE's Nepomuk data in Virtuoso, SPARQL endpoints on the web via HTTP, and Gnome or MeeGo Nepomuk data in Tracker stores. It differs from Soprano in that it is much smaller and lower level, and it is asynchronous and uses Qt slot callbacks by default, whereas Soprano is usually a synchronous style api.

SPARQL is a complex query language, although if you are already familiar with SQL, it shouldn't be too hard to learn. However, a lot of the documentation is written by academics and they tend to make RDF and SPARQL sound more complicated than they actually are. So there is certainly a learning curve involved, but there are a lot of advantages in being familiar with SPARQL. In the same way that, although you can avoid learning SQL by using visual query builders, you will get to a certain point, where not knowing SQL will begin to hold you back. QSparql makes no attempt to hide SPARQL, it just handles the queries as simple strings.

A nice feature that was added to QSparql recently, was a QML model that allows you to make SPARQL queries and display the results entirely in QML. I thought I would write a blog about how it works as it is interesting in itself for playing with SPARQL, and it also might inspire people to try something similar with DBus or SQL queries. I got the idea from a nice project called 'elisa_qt' which does some very impressive animated things with QML. They were just using SPARQL in QML to access Tracker store Nepomuk data, which I expanded and generalized.

Here is an example of what the SparqlResultsList model looks like:


import Qt 4.7
import QSparql 1.0

ListView {
    width: 1000; height: 600

    model: SparqlResultsList {
        options: SparqlConnectionOptions {
            driverName: "QSPARQL_ENDPOINT"
            hostName: "dbpedia.org"
        }

        query: "SELECT $predicate $value WHERE {  $predicate $thing . }"
    }

    delegate: Text { text: "Data: " + predicate + ", " + value }
}

The actual query means "search in the dbpedia.org RDF store for a pattern matching 'The Beatles' and obtain all the attribute names ($predicate) and corresponding attribute values ($value)". QSparql has several different drivers and the one we are using here is 'QSPARQL_ENDPOINT' which allows you to make queries to databases on the web. For each row in the results returned the 'delegate:' is called and you can use the variable names from the query to compose some text to show the user the results ('predicate' and 'value' variables here). I hope the QML is largely self explanatory.

In C++ terms the model is written as a QAbstractListModel, and you can see the code for the model in qsparqlresultslist_p.h and qsparqlresultslist.cpp in Gitorious



class QSparqlResultsList : public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY(QSparqlConnectionOptionsWrapper * options READ options WRITE setOptions)
    Q_PROPERTY(QString query READ query WRITE setQuery)
    ...

The are two Q_PROPERTIES, one to set the query text and another one to set the connection options to use, such as which driver, port or web path. To use Virtuoso you would set the options property like this to use a driver name of 'QVIRTUOSO':


        options: SparqlConnectionOptions {
            driverName: "QVIRTUOSO"
            databaseName: "DRIVER=/usr/lib/odbc/virtodbc_r.so"
         }

The clever bit is how the SPARQL query variables become javascript variables that you can use in the model's 'delegate:' property to show the results. This is the code:


void QSparqlResultsList::queryFinished()
{
    QHash roleNames;
    roleNames = QAbstractItemModel::roleNames();

    if (m_result->first()) {
        QSparqlResultRow resultRow = m_result->current();

        // Create two sets of declarative variables from the variable names used
        // in the select statement
        // 'foo' is just a literal like 1234, but '$foo' is "1234"^^xsd:integer
        // 'bar' is a string 'http://www.w3.org/2002/07/owl#sameAs', but '$bar'
        // is a uri 
        for (int i = 0; i < resultRow.count(); i++) {
            roleNames.insert((Qt::UserRole + 1) + i, resultRow.binding(i).name().toLatin1());
        }

        for (int i = 0; i < resultRow.count(); i++) {
            roleNames.insert((Qt::UserRole + 1) + i + resultRow.count(), QByteArray("$") + resultRow.binding(i).name().toLatin1());
        }

        setRoleNames(roleNames);
    }

    reset();
}

You add the names you want to use in the delegate to the QAbstractItemModel::roleNames() hash table, and give each one of them an integer value. The code above obtains the first result row returned by the query and extracts the variable names from that. In the SPARQL model there are actually two sets of variable names, where a plain 'predicate' or 'value' is just the text of the query result, whereas '$predicate' and '$value' have the RDF datatype added if they are literals, or they will be enclosed in angle brackets if they are Uris. The '$foo' form is useful if you want to build up a follow on query based on the results of an intial query. For instance, you might obtain an album name from your local Nepomuk store, and then want to make a second web based query to DBpedia using the same album name.

Having set up the role names, you need to implement a data() method to return the values to the code using the model:


QVariant QSparqlResultsList::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    m_result->setPos(index.row());
    QSparqlResultRow row = m_result->current();
    int i = role - (Qt::UserRole + 1);

    if (i >= row.count())
        return row.binding(i - row.count()).toString();
    else
        return row.value(i);
}

To actually tell QML about the C++ types in the model you need to have a top level plugin class which looks like this:


class SparqlResultsListPlugin : public QDeclarativeExtensionPlugin
{
public:
    void registerTypes(const char *uri)
    {
        Q_ASSERT(uri == QLatin1String("QSparql"));
        qmlRegisterType(uri, 0, 1, "SparqlResultsList");
        qmlRegisterType(uri, 0, 1, "SparqlConnectionOptions");
    }
};

Q_EXPORT_PLUGIN2(sparqlresultslist, SparqlResultsListPlugin);

You need to install the plugin into a path like /usr/imports/QSparql, along with a file called 'qmldir' containing the name of the plugin:


plugin sparqlresultslist

Then the QML runtime should be able to find it, assuming that '/usr/imports' is on its list of declarative paths.

So please go ahead and try out QSparql and tell us what you think. There isn't a public mailing list yet unfortunately, and so you may need to mail me personally for now (or comment on this blog of course).