Qt IVI Query Language

Automotive systems are getting bigger and including more features, particularly in 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 IVI 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 IVI Query Language does not specify the data itself, but is only used to filter and sort.

Work with the Query Language

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

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

Suppose you use the QIviSearchAndBrowseModel to access a list of QIviAudioTrackItems, then every property of the QIviAudioTrackItem are your identifiers.

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

album='Nevermind'

Filter and Sort

The Qt IVI 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 identifier's value 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 identifier's value is not equal to the given value. For strings, the comparison is case-sensitive.
~=Tests whether the identifier's value is equal to the given value. For strings, the comparison is case-insensitive.
>Tests whether the lefthand-side value is greater than the righthand-side value. This comparison only works for numbers.
>=Tests whether the lefthand-side value is greater than or equal to the righthand-side value. This comparison only works for numbers.
<Tests whether the lefthand-side value is less than the righthand-side value. This comparison only works for numbers.
<=Tests whether the lefthand-side value is less than or equal to the righthand-side value. This comparison only works for numbers.

When you compare an identifier against a string, the string always needs to be encapsulated either between two quotation marks - 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 backend 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 intendend 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 IVI 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 Backend

For the backend, 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 QIviAbstractQueryTerm, which can be cast to one of the following types:

    QIviConjunctionTerm

    The representation of a conjunction between two query terms

    QIviFilterTerm

    The representation of a filter

    QIviScopeTerm

    The representation of a scope which can hold another term

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

Usually, this C++ representation is translated into any form the backend 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<QIviOrderTerm> &orderTerms)
{
    QStringList order;
    int i = 0;
    for (const QIviOrderTerm & 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(QIviAbstractQueryTerm *term)
{
    if (!term)
        return QString();

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

        QString string;
        QListIterator<QIviAbstractQueryTerm*> it(conjunctionTerm->terms());
        while (it.hasNext()) {
            string += createWhereClause(it.next());
            if (it.hasNext())
                string += QLatin1Literal(" ") + conjunction + QLatin1Literal(" ");
        }
        return string;
    }
    case QIviAbstractQueryTerm::FilterTerm: {
        QIviFilterTerm *filter = static_cast<QIviFilterTerm*>(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 QIviFilterTerm::Equals: operatorString = QLatin1String("="); break;
            case QIviFilterTerm::EqualsCaseInsensitive: operatorString = QLatin1String("LIKE"); break;
            case QIviFilterTerm::Unequals: operatorString = QLatin1String("="); negated = !negated; break;
            case QIviFilterTerm::GreaterThan: operatorString = QLatin1String(">"); break;
            case QIviFilterTerm::GreaterEquals: operatorString = QLatin1String(">="); break;
            case QIviFilterTerm::LowerThan: operatorString = QLatin1String("<"); break;
            case QIviFilterTerm::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));

© 2020 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.