Qt Interface Framework Query Language

Automotive systems are getting bigger and including more features, particularly around entertainment and connectivity. Modern systems can handle phone calls, access the mobile phone's address book, and have a media player that manages a media database. Since address books and media databases can be quite large these days, it's important for users to be able to filter, sort, and search through them in a convenient way.

The Qt Interface Framework Query Language provides you with a minimal language to express what you want to be displayed and how it should be sorted. This language is independent from both the underlying architecture and potentially underlying database languages: like SQL or PSQL. The Qt Interface Framework Query Language does not specify the data itself, but is only used to filter and sort.

Work with the Query Language

Currently the QIfFilterAndBrowseModel is the only class that supports the use of the query language. In this case, the model's back end informs the query parser about the identifiers available to filter and sort by.

An identifier is the name of a column or property which can be used in the query language.

Suppose you use the QIfFilterAndBrowseModel to access a list of QIfAudioTrackItems: then every property of the QIfAudioTrackItem are your identifiers.

The following query results in a search for tracks on the album "Nevermind":

album='Nevermind'

Filter and Sort

The Qt Interface Framework Query Language has two parts:

  1. The filter specification
  2. The sort order definition
album='Nevermind' [/trackNumber]

In the query above, the first part is album='Nevermind', and it indicates to only show tracks which are from the album "Nevermind". The second part is specified by [] and defines the order: the result should be sorted by trackNumber in ascending order.

Filter

To filter a result, the following operators can be used to match an identifier to a value:

OperatorDescription
=Tests whether the value of the identifier is equal to the given value. For strings, the comparison is case-sensitive.

Note: The == operator is an alias and delivers the same result.

!=Tests whether the value of the identifier is not equal to the given value. For strings, the comparison is case-sensitive.
~=Tests whether the value of the identifier is equal to the given value. For strings, the comparison is case-insensitive.
>Tests whether the leftmost value is greater than the rightmost value. This comparison only works for numbers.
>=Tests whether the leftmost value is greater than or equal to the rightmost value. This comparison only works for numbers.
<Tests whether the leftmost value is less than the rightmost value. This comparison only works for numbers.
<=Tests whether the leftmost value is less than or equal to the rightmost value. This comparison only works for numbers.

Note: When you compare an identifier against a string, the string always needs to be encapsulated either between single ' or double ". Numbers are supported in various formats, including signed numbers, unsigned numbers, and floats using an exponent, such as -5.0E15.

Sort

The second part of a query is used to sort the result. It's optional and if not provided, the back end can decide the order in which the result should be listed.

To sort trackNumber in ascending order, write the following:

[/trackNumber]

To sort in descending order:

[\trackNumber]

Using this query on a big list of songs may not give the intended result, as there could be two different tracks with the same trackNumber.

In this case, you can provide multiple sort orders.

[\trackNumber][/album]

The query above sorts the tracks by trackNumber in descending order, and the track with the same trackNumber by album name in ascending order.

Conjunctions

Often, to achieve the expected result, it is not sufficient to only add a specific condition and define the order. It may be necessary to use basic Boolean algebra, which the Qt Interface Framework Query Language supports. You can combine multiple filters by using AND / OR conjunctions.

The following query only lists the first 5 tracks in the album 'Metallica':

album='Metallica' & trackNumber<=5

You can also use brackets to negate filters:

(album='Metallica') & !(trackNumber>5)

Integrate with the Back End

For the back end, the query is translated from a string to a binary representation, similar to an Abstract Syntax Tree (AST). Like the query itself, the representation is split into two parts:

  1. A pointer to a QIfAbstractQueryTerm, which can be cast to one of the following types:

    QIfConjunctionTerm

    The representation of a conjunction between two query terms

    QIfFilterTerm

    The representation of a filter

    QIfScopeTerm

    The representation of a scope which can hold another term

  2. A QList<QIfOrderTerm> that provides the order in which the user wants the result to be in.

Usually, this C++ representation is translated into any form the back end supports.

For example, to get the results from an SQL database:

First we need a function to translate the identifiers used in the query into the column names used in the database:

QString SearchAndBrowseBackend::mapIdentifiers(const QString &type)
{
    if (type == QLatin1String("artist"))
        return QLatin1String("artistName");
    else if (type == QLatin1String("album"))
        return QLatin1String("albumName");
    else if (type == QLatin1String("track"))
        return QLatin1String("trackName");
    else
        return type;
}

Next, we need functions to translate the QueryTerms and the OrderTerms into SQL clauses:

QString SearchAndBrowseBackend::createSortOrder(const QString &type, const QList<QIfOrderTerm> &orderTerms)
{
    QStringList order;
    int i = 0;
    for (const QIfOrderTerm & term : orderTerms) {
        if (i)
            order.append(",");

        order.append(mapIdentifiers(term.propertyName()));
        if (term.isAscending())
            order.append("ASC");
        else
            order.append("DESC");

        i++;
    }

    return order.join(' ');
}

QString SearchAndBrowseBackend::createWhereClause(QIfAbstractQueryTerm *term)
{
    if (!term)
        return QString();

    switch (term->type()) {
    case QIfAbstractQueryTerm::ScopeTerm: {
        QIfScopeTerm *scope = static_cast<QIfScopeTerm*>(term);
        return QString(QLatin1String("%1 (%2)")).arg(scope->isNegated() ? "NOT" : "",createWhereClause(scope->term()));
    }
    case QIfAbstractQueryTerm::ConjunctionTerm: {
        QIfConjunctionTerm *conjunctionTerm = static_cast<QIfConjunctionTerm*>(term);
        QString conjunction = QLatin1String("AND");
        if (conjunctionTerm->conjunction() == QIfConjunctionTerm::Or)
            conjunction = QLatin1String("OR");

        QString string;
        QListIterator<QIfAbstractQueryTerm*> it(conjunctionTerm->terms());
        while (it.hasNext()) {
            string += createWhereClause(it.next());
            if (it.hasNext())
                string += QLatin1Literal(" ") + conjunction + QLatin1Literal(" ");
        }
        return string;
    }
    case QIfAbstractQueryTerm::FilterTerm: {
        QIfFilterTerm *filter = static_cast<QIfFilterTerm*>(term);
        QString operatorString;
        bool negated = filter->isNegated();
        QString value;
        if (filter->value().type() == QVariant::String)
            value = QString(QLatin1String("'%1'")).arg(filter->value().toString().replace('*', '%'));
        else
            value = filter->value().toString();

        switch (filter->operatorType()){
            case QIfFilterTerm::Equals: operatorString = QLatin1String("="); break;
            case QIfFilterTerm::EqualsCaseInsensitive: operatorString = QLatin1String("LIKE"); break;
            case QIfFilterTerm::Unequals: operatorString = QLatin1String("="); negated = !negated; break;
            case QIfFilterTerm::GreaterThan: operatorString = QLatin1String(">"); break;
            case QIfFilterTerm::GreaterEquals: operatorString = QLatin1String(">="); break;
            case QIfFilterTerm::LowerThan: operatorString = QLatin1String("<"); break;
            case QIfFilterTerm::LowerEquals: operatorString = QLatin1String("<="); break;
        }

        QStringList clause;
        if (negated)
            clause.append(QLatin1String("NOT"));
        clause.append(mapIdentifiers(filter->propertyName()));
        clause.append(operatorString);
        clause.append(value);

        return clause.join(" ");
    }
    }

    return QString();
}

Finally, you can then create the following query:

QString query = QString(QLatin1String("SELECT * FROM tracks WHERE %1 ORDER BY %2")).arg(createWhereClause(queryTerm), createSortOrder(orderTerms));

© 2021 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.