This chapter is from the book

This chapter is from the book

13. Databases

Connecting and Querying

Viewing Tables

Editing Records Using Forms

Presenting Data in Tabular Forms

The QtSql module provides a platform- and database-independent interface for accessing SQL databases. This interface is supported by a set of classes that use Qt's model/view architecture to provide database integration with the user interface. This chapter assumes familiarity with Qt's model/view classes, covered in Chapter 10.

A database connection is represented by a QSqlDatabase object. Qt uses drivers to communicate with the various database APIs. The Qt Desktop Edition includes the following drivers:

Driver

Database

QDB2

IBM DB2 version 7.1and later

QIBASE

Borland InterBase

QMYSQL

MySQL

QOCI

Oracle (Oracle Call Interface)

QODBC

ODBC (includes Microsoft SQL Server)

QPSQL

PostgreSQL 7.3 and later

QSQLITE

SQLite version 3

QSQLITE2

SQLite version 2

QTDS

Sybase Adaptive Server

Due to license restrictions, not all of the drivers are provided with the Qt Open Source Edition. When configuring Qt, we can choose between including the SQL drivers inside Qt itself and building them as plugins. Qt is supplied with the SQLite database, a public domain in-process database.
[*]

For users who are comfortable with SQL syntax, the QSqlQuery class provides a means of directly executing arbitrary SQL statements and handling their results. For users who prefer a higher-level database interface that avoids SQL syntax, QSqlTableModel and QSqlRelationalTableModel provide suitable abstractions. These classes represent an SQL table in the same way as Qt's other model classes (covered in Chapter 10). They can be used stand-alone to traverse and edit data in code, or they can be attached to views through which end-users can view and edit the data themselves.

Qt also makes it straightforward to program the common database idioms, such as master–detail and drill-down, and to view database tables using forms or GUI tables, as the examples in this chapter will demonstrate.

Connecting and Querying

To execute SQL queries, we must first establish a connection with a database. Typically, database connections are set up in a separate function that we call at application startup. For example:

First, we call QSqlDatabase::addDatabase() to create a QSqlDatabase object. The first argument to addDatabase() specifies which database driver Qt must use to access the database. In this case, we use MySQL.

Next, we set the database host name, the database name, the user name, and the password, and we open the connection. If open() fails, we show an error message.

We call next() once to position the QSqlQuery on the first record of the result set. Subsequent calls to next() advance the record pointer by one record each time, until the end is reached, at which point next() returns false. If the result set is empty (or if the query failed), the first call to next() will return false.

The value() function returns the value of a field as a QVariant. The fields are numbered from 0 in the order given in the SELECT statement. The QVariant class can hold many C++ and Qt types, including int and QString. The different types of data that can be stored in a database are mapped into the corresponding C++ and Qt types and stored in QVariants. For example, a VARCHAR is represented as a QString and a DATETIME as a QDateTime.

QSqlQuery provides some other functions to navigate through the result set: first(), last(), previous(), and seek(). These functions are convenient, but for some databases they can be slower and more memory-hungry than next(). For an easy optimization when operating on large data sets, we can call QSqlQuery::setForwardOnly(true) before calling exec(), and then only use next() for navigating through the result set.

Earlier we specified the SQL query as an argument to QSqlQuery::exec(), but we can also pass it directly to the constructor, which executes it immediately:

After this, numRowsAffected() returns the number of rows that were affected by the SQL statement (or -1 on error).

If we need to insert a lot of records, or if we want to avoid converting values to strings (and escaping them correctly), we can use prepare() to specify a query that contains placeholders and then bind the values we want to insert. Qt supports both the Oracle-style and the ODBC-style syntax for placeholders for all databases, using native support where it is available and simulating it otherwise. Here's an example that uses the Oracle-style syntax with named placeholders:

After the call to exec(), we can call bindValue() or addBindValue() to bind new values, and then call exec() again to execute the query with the new values.

Placeholders are often used to specify binary data or strings that contain non-ASCII or non-Latin-1 characters. Behind the scenes, Qt uses Unicode with those databases that support Unicode, and for those that don't, Qt transparently converts strings to the appropriate encoding.

Qt supports SQL transactions on databases where they are available. To start a transaction, we call transaction() on the QSqlDatabase object that represents the database connection. To finish the transaction, we call either commit() or rollback(). For example, here's how we would look up a foreign key and execute an INSERT statement inside a transaction:

The QSqlDatabase::database() function returns a QSqlDatabase object representing the connection we created in createConnection(). If a transaction cannot be started, QSqlDatabase::transaction() returns false. Some databases don't support transactions. For those, the transaction(), commit(), and rollback() functions do nothing. We can test whether a database supports transactions using hasFeature() on the QSqlDriver associated with the database:

Several other database features can be tested for, including whether the database supports BLOBs (binary large objects), Unicode, and prepared queries.

It is also possible to access the low-level database driver handle and the low-level handle to a query's result set, using QSqlDriver::handle() and QSqlResult::handle(). However, both functions are dangerous unless you know exactly what you are doing and are very careful. See their documentation for examples and an explanation of the risks.

In the examples so far, we have assumed that the application is using a single database connection. If we want to create multiple connections, we can pass a name as a second argument to addDatabase(). For example:

Multiple connections are useful if we want to perform more than one transaction at a time, since each connection can handle only a single active transaction. When we use multiple database connections, we can still have one unnamed connection, and QSqlQuery will use that connection if none is specified.

In addition to QSqlQuery, Qt provides the QSqlTableModel class as a higher-level interface, allowing us to avoid using raw SQL for performing the most common SQL operations (SELECT, INSERT, UPDATE, and DELETE). The class can also be used stand-alone to manipulate a database without any GUI involvement, or it can be used as a data source for QListView or QTableView.

After the call to submitAll(), the record might be moved to a different row position, depending on how the table is ordered. The submitAll() call will return false if the insertion failed.

An important difference between an SQL model and a standard model is that for an SQL model we must call submitAll() to have any changes written to the database.

To update a record, we must first position the QSqlTableModel on the record we want to modify (e.g., using select()). We then extract the record, update the fields we want to change, and write our changes back to the database:

The QSqlQuery and QSqlTableModel classes provide an interface between Qt and an SQL database. Using these classes, we can create forms that present data to users and that let them insert, update, and delete records.

For projects that use the SQL classes, we must add the line

QT += sql

to their .pro file. This will ensure that the application is linked against the QtSql library.