QPSQL: Add support for forward-only queries

With this change, it is possible to significantly reduce memory
consumption of applications that fetch large result sets from databases.
The implementation is based on the new functionality called "single-row
mode" that was introduced in PostgreSQL version 9.2:

https://www.postgresql.org/docs/9.2/static/libpq-async.html

It also uses asynchronous commands PQsendQuery(), PQgetResult():

https://www.postgresql.org/docs/9.2/static/libpq-single-row-mode.html

[ChangeLog][QtSql][QPSQL] Added support for forward-only queries (requires
libpq version 9.2 or later)

[Important Behavior Changes] The QPSQL driver now supports forward-only
queries. To use this feature, you must build QPSQL plugin with PostreSQL
client library version 9.2 or later. See the Qt SQL documentation for
more information about QPSQL limitations of forward-only queries
(sql-driver.html).

[Important Behavior Changes] If you build the QPSQL plugin with PostgreSQL
version 9.2 or later, then you must distribute your application with
libpq version 9.2 or later. Otherwise, the QPSQL plugin will fail to load.

Task-number: QTBUG-63714
Change-Id: I15db8c8fd664f2a1f719329f5d113511fa69010c
Reviewed-by: Andy Shaw <andy.shaw@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Robert Szefner 2017-11-09 22:57:51 +01:00 committed by Robert Szefner
parent 3ed91da499
commit f99d2b21b8
8 changed files with 591 additions and 33 deletions

View File

@ -125,6 +125,14 @@ inline void qPQfreemem(void *buffer)
PQfreemem(buffer);
}
/* Missing declaration of PGRES_SINGLE_TUPLE for PSQL below 9.2 */
#if !defined PG_VERSION_NUM || PG_VERSION_NUM-0 < 90200
static const int PGRES_SINGLE_TUPLE = 9;
#endif
typedef int StatementId;
static const StatementId InvalidStatementId = 0;
class QPSQLResultPrivate;
class QPSQLResult: public QSqlResult
@ -143,6 +151,7 @@ protected:
bool fetch(int i) override;
bool fetchFirst() override;
bool fetchLast() override;
bool fetchNext() override;
QVariant data(int i) override;
bool isNull(int field) override;
bool reset (const QString &query) override;
@ -164,7 +173,9 @@ public:
pro(QPSQLDriver::Version6),
sn(0),
pendingNotifyCheck(false),
hasBackslashEscape(false)
hasBackslashEscape(false),
stmtCount(0),
currentStmtId(InvalidStatementId)
{ dbmsType = QSqlDriver::PostgreSQL; }
PGconn *connection;
@ -174,10 +185,19 @@ public:
QStringList seid;
mutable bool pendingNotifyCheck;
bool hasBackslashEscape;
int stmtCount;
StatementId currentStmtId;
void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
PGresult * exec(const char * stmt) const;
PGresult * exec(const QString & stmt) const;
PGresult *exec(const char *stmt);
PGresult *exec(const QString &stmt);
StatementId sendQuery(const QString &stmt);
bool setSingleRowMode() const;
PGresult *getResult(StatementId stmtId) const;
void finishQuery(StatementId stmtId);
void discardResults() const;
StatementId generateStatementId();
void checkPendingNotifications() const;
QPSQLDriver::Protocol getPSQLVersion();
bool setEncodingUtf8();
void setDatestyle();
@ -202,20 +222,89 @@ void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type)
}
}
PGresult * QPSQLDriverPrivate::exec(const char * stmt) const
PGresult *QPSQLDriverPrivate::exec(const char *stmt)
{
// PQexec() silently discards any prior query results that the application didn't eat.
PGresult *result = PQexec(connection, stmt);
currentStmtId = result ? generateStatementId() : InvalidStatementId;
checkPendingNotifications();
return result;
}
PGresult *QPSQLDriverPrivate::exec(const QString &stmt)
{
return exec((isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData());
}
StatementId QPSQLDriverPrivate::sendQuery(const QString &stmt)
{
// Discard any prior query results that the application didn't eat.
// This is required for PQsendQuery()
discardResults();
const int result = PQsendQuery(connection,
(isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData());
currentStmtId = result ? generateStatementId() : InvalidStatementId;
return currentStmtId;
}
bool QPSQLDriverPrivate::setSingleRowMode() const
{
// Activates single-row mode for last sent query, see:
// https://www.postgresql.org/docs/9.2/static/libpq-single-row-mode.html
// This method should be called immediately after the sendQuery() call.
#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 90200
return PQsetSingleRowMode(connection) > 0;
#else
return false;
#endif
}
PGresult *QPSQLDriverPrivate::getResult(StatementId stmtId) const
{
// Make sure the results of stmtId weren't discaded. This might
// happen for forward-only queries if somebody executed another
// SQL query on the same db connection.
if (stmtId != currentStmtId) {
// If you change the following warning, remember to update it
// on sql-driver.html page too.
qWarning("QPSQLDriver::getResult: Query results lost - "
"probably discarded on executing another SQL query.");
return nullptr;
}
PGresult *result = PQgetResult(connection);
checkPendingNotifications();
return result;
}
void QPSQLDriverPrivate::finishQuery(StatementId stmtId)
{
if (stmtId != InvalidStatementId && stmtId == currentStmtId) {
discardResults();
currentStmtId = InvalidStatementId;
}
}
void QPSQLDriverPrivate::discardResults() const
{
while (PGresult *result = PQgetResult(connection))
PQclear(result);
}
StatementId QPSQLDriverPrivate::generateStatementId()
{
int stmtId = ++stmtCount;
if (stmtId <= 0)
stmtId = stmtCount = 1;
return stmtId;
}
void QPSQLDriverPrivate::checkPendingNotifications() const
{
Q_Q(const QPSQLDriver);
PGresult *result = PQexec(connection, stmt);
if (seid.size() && !pendingNotifyCheck) {
pendingNotifyCheck = true;
QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), "_q_handleNotification", Qt::QueuedConnection, Q_ARG(int,0));
}
return result;
}
PGresult * QPSQLDriverPrivate::exec(const QString & stmt) const
{
return exec(isUtf8 ? stmt.toUtf8().constData() : stmt.toLocal8Bit().constData());
}
class QPSQLResultPrivate : public QSqlResultPrivate
@ -227,6 +316,8 @@ public:
: QSqlResultPrivate(q, drv),
result(0),
currentSize(-1),
canFetchMoreRows(false),
stmtId(InvalidStatementId),
preparedQueriesEnabled(false)
{ }
@ -235,6 +326,8 @@ public:
PGresult *result;
int currentSize;
bool canFetchMoreRows;
StatementId stmtId;
bool preparedQueriesEnabled;
QString preparedStmtId;
@ -257,21 +350,45 @@ static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
bool QPSQLResultPrivate::processResults()
{
Q_Q(QPSQLResult);
if (!result)
if (!result) {
q->setSelect(false);
q->setActive(false);
currentSize = -1;
canFetchMoreRows = false;
if (stmtId != drv_d_func()->currentStmtId) {
q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
"Query results lost - probably discarded on executing "
"another SQL query."), QSqlError::StatementError, drv_d_func(), result));
}
return false;
}
int status = PQresultStatus(result);
if (status == PGRES_TUPLES_OK) {
switch (status) {
case PGRES_TUPLES_OK:
q->setSelect(true);
q->setActive(true);
currentSize = PQntuples(result);
currentSize = q->isForwardOnly() ? -1 : PQntuples(result);
canFetchMoreRows = false;
return true;
} else if (status == PGRES_COMMAND_OK) {
case PGRES_SINGLE_TUPLE:
q->setSelect(true);
q->setActive(true);
currentSize = -1;
canFetchMoreRows = true;
return true;
case PGRES_COMMAND_OK:
q->setSelect(false);
q->setActive(true);
currentSize = -1;
canFetchMoreRows = false;
return true;
default:
break;
}
q->setSelect(false);
q->setActive(false);
currentSize = -1;
canFetchMoreRows = false;
q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
"Unable to create query"), QSqlError::StatementError, drv_d_func(), result));
return false;
@ -361,9 +478,13 @@ void QPSQLResult::cleanup()
Q_D(QPSQLResult);
if (d->result)
PQclear(d->result);
d->result = 0;
d->result = nullptr;
if (d->stmtId != InvalidStatementId)
d->drv_d_func()->finishQuery(d->stmtId);
d->stmtId = InvalidStatementId;
setAt(QSql::BeforeFirstRow);
d->currentSize = -1;
d->canFetchMoreRows = false;
setActive(false);
}
@ -374,23 +495,117 @@ bool QPSQLResult::fetch(int i)
return false;
if (i < 0)
return false;
if (i >= d->currentSize)
return false;
if (at() == i)
return true;
if (isForwardOnly()) {
if (i < at())
return false;
bool ok = true;
while (ok && i > at())
ok = fetchNext();
return ok;
}
if (i >= d->currentSize)
return false;
setAt(i);
return true;
}
bool QPSQLResult::fetchFirst()
{
Q_D(const QPSQLResult);
if (!isActive())
return false;
if (at() == 0)
return true;
if (isForwardOnly()) {
if (at() == QSql::BeforeFirstRow) {
// First result has been already fetched by exec() or
// nextResult(), just check it has at least one row.
if (d->result && PQntuples(d->result) > 0) {
setAt(0);
return true;
}
}
return false;
}
return fetch(0);
}
bool QPSQLResult::fetchLast()
{
Q_D(const QPSQLResult);
return fetch(PQntuples(d->result) - 1);
if (!isActive())
return false;
if (isForwardOnly()) {
// Cannot seek to last row in forwardOnly mode, so we have to use brute force
int i = at();
if (i == QSql::AfterLastRow)
return false;
if (i == QSql::BeforeFirstRow)
i = 0;
while (fetchNext())
++i;
setAt(i);
return true;
}
return fetch(d->currentSize - 1);
}
bool QPSQLResult::fetchNext()
{
Q_D(QPSQLResult);
if (!isActive())
return false;
const int currentRow = at(); // Small optimalization
if (currentRow == QSql::BeforeFirstRow)
return fetchFirst();
if (currentRow == QSql::AfterLastRow)
return false;
if (isForwardOnly()) {
if (!d->canFetchMoreRows)
return false;
PQclear(d->result);
d->result = d->drv_d_func()->getResult(d->stmtId);
if (!d->result) {
setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
"Unable to get result"), QSqlError::StatementError, d->drv_d_func(), d->result));
d->canFetchMoreRows = false;
return false;
}
int status = PQresultStatus(d->result);
switch (status) {
case PGRES_SINGLE_TUPLE:
// Fetched next row of current result set
Q_ASSERT(PQntuples(d->result) == 1);
Q_ASSERT(d->canFetchMoreRows);
setAt(currentRow + 1);
return true;
case PGRES_TUPLES_OK:
// In single-row mode PGRES_TUPLES_OK means end of current result set
Q_ASSERT(PQntuples(d->result) == 0);
d->canFetchMoreRows = false;
return false;
default:
setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
"Unable to get result"), QSqlError::StatementError, d->drv_d_func(), d->result));
d->canFetchMoreRows = false;
return false;
}
}
if (currentRow + 1 >= d->currentSize)
return false;
setAt(currentRow + 1);
return true;
}
QVariant QPSQLResult::data(int i)
@ -400,10 +615,11 @@ QVariant QPSQLResult::data(int i)
qWarning("QPSQLResult::data: column %d out of range", i);
return QVariant();
}
const int currentRow = isForwardOnly() ? 0 : at();
int ptype = PQftype(d->result, i);
QVariant::Type type = qDecodePSQLType(ptype);
const char *val = PQgetvalue(d->result, at(), i);
if (PQgetisnull(d->result, at(), i))
const char *val = PQgetvalue(d->result, currentRow, i);
if (PQgetisnull(d->result, currentRow, i))
return QVariant(type);
switch (type) {
case QVariant::Bool:
@ -488,8 +704,9 @@ QVariant QPSQLResult::data(int i)
bool QPSQLResult::isNull(int field)
{
Q_D(const QPSQLResult);
PQgetvalue(d->result, at(), field);
return PQgetisnull(d->result, at(), field);
const int currentRow = isForwardOnly() ? 0 : at();
PQgetvalue(d->result, currentRow, field);
return PQgetisnull(d->result, currentRow, field);
}
bool QPSQLResult::reset (const QString& query)
@ -500,7 +717,18 @@ bool QPSQLResult::reset (const QString& query)
return false;
if (!driver()->isOpen() || driver()->isOpenError())
return false;
d->result = d->drv_d_func()->exec(query);
d->stmtId = d->drv_d_func()->sendQuery(query);
if (d->stmtId == InvalidStatementId) {
setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
"Unable to send query"), QSqlError::StatementError, d->drv_d_func()));
return false;
}
if (isForwardOnly())
setForwardOnly(d->drv_d_func()->setSingleRowMode());
d->result = d->drv_d_func()->getResult(d->stmtId);
return d->processResults();
}
@ -546,10 +774,17 @@ QSqlRecord QPSQLResult::record() const
f.setName(QString::fromUtf8(PQfname(d->result, i)));
else
f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
QSqlQuery qry(driver()->createResult());
if (qry.exec(QStringLiteral("SELECT relname FROM pg_class WHERE pg_class.oid = %1")
.arg(PQftable(d->result, i))) && qry.next()) {
f.setTableName(qry.value(0).toString());
// WARNING: We cannot execute any other SQL queries on
// the same db connection while forward-only mode is active
// (this would discard all results of forward-only query).
// So we just skip this...
if (!isForwardOnly()) {
QSqlQuery qry(driver()->createResult());
if (qry.exec(QStringLiteral("SELECT relname FROM pg_class WHERE pg_class.oid = %1")
.arg(PQftable(d->result, i))) && qry.next()) {
f.setTableName(qry.value(0).toString());
}
}
int ptype = PQftype(d->result, i);
f.setType(qDecodePSQLType(ptype));
@ -668,8 +903,17 @@ bool QPSQLResult::exec()
else
stmt = QString::fromLatin1("EXECUTE %1 (%2)").arg(d->preparedStmtId, params);
d->result = d->drv_d_func()->exec(stmt);
d->stmtId = d->drv_d_func()->sendQuery(stmt);
if (d->stmtId == InvalidStatementId) {
setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
"Unable to send query"), QSqlError::StatementError, d->drv_d_func()));
return false;
}
if (isForwardOnly())
setForwardOnly(d->drv_d_func()->setSingleRowMode());
d->result = d->drv_d_func()->getResult(d->stmtId);
return d->processResults();
}
@ -996,7 +1240,7 @@ QSqlResult *QPSQLDriver::createResult() const
bool QPSQLDriver::beginTransaction()
{
Q_D(const QPSQLDriver);
Q_D(QPSQLDriver);
if (!isOpen()) {
qWarning("QPSQLDriver::beginTransaction: Database not open");
return false;

View File

@ -95,3 +95,35 @@ QSqlDatabase: available drivers: QMYSQL
//! [34]
column.contains(QRegularExpression("pattern"));
//! [34]
//! [36]
QSqlQuery query(db);
query.setForwardOnly(true);
query.exec("SELECT * FROM table");
while (query.next()) {
// Handle changes in every iteration of the loop
QVariant v = query.result()->handle();
if (qstrcmp(v.typeName(), "PGresult*") == 0) {
PGresult *handle = *static_cast<PGresult **>(v.data());
if (handle != 0) {
// Do something...
}
}
}
//! [36]
//! [37]
int value;
QSqlQuery query1(db);
query1.setForwardOnly(true);
query1.exec("select * FROM table1");
while (query1.next()) {
value = query1.value(0).toInt();
if (value == 1) {
QSqlQuery query2(db);
query2.exec("update table2 set col=2"); // WRONG: This will discard all results of
} // query1, and cause the loop to quit
}
//! [37]

View File

@ -225,3 +225,15 @@ cd $QTDIR/qtbase/src/plugins/sqldrivers
qmake -- OCI_INCDIR=/usr/include/oracle/10.1.0.3/client OCI_LIBDIR=/usr/lib/oracle/10.1.0.3/client/lib "OCI_LIBS=-Wl,-rpath,/usr/lib/oracle/10.1.0.3/client/lib -lclntsh -lnnz10"
make sub-oci
//! [33]
//! [35]
QSqlDatabase: QPSQL driver not loaded
QSqlDatabase: available drivers: QSQLITE QMYSQL QMYSQL3 QODBC QODBC3 QPSQL QPSQL7
Could not create database object
//! [35]
//! [38]
QPSQLDriver::getResult: Query results lost - probably discarded on executing another SQL query.
//! [38]

View File

@ -380,6 +380,45 @@
Binary Large Objects are supported through the \c BYTEA field type in
PostgreSQL server versions >= 7.1.
\section3 QPSQL Forward-only query support
To use forward-only queries, you must build the QPSQL plugin with
PostreSQL client library version 9.2 or later. If the plugin is
built with an older version, then forward-only mode will not be
available - calling QSqlQuery::setForwardOnly() with \c true will
have no effect.
\warning If you build the QPSQL plugin with PostgreSQL version 9.2 or later,
then you must distribute your application with libpq version 9.2 or later.
Otherwise, loading the QPSQL plugin will fail with the following message:
\snippet code/doc_src_sql-driver.qdoc 35
While navigating the results in forward-only mode, the handle of
QSqlResult may change. Applications that use the low-level handle of
SQL result must get a new handle after each call to any of QSqlResult
fetch functions. Example:
\snippet code/doc_src_sql-driver.cpp 36
While reading the results of a forward-only query with PostgreSQL,
the database connection cannot be used to execute other queries.
This is a limitation of libpq library. Example:
\snippet code/doc_src_sql-driver.cpp 37
This problem will not occur if query1 and query2 use different
database connections, or if we execute query2 after the while loop.
\note Some methods of QSqlDatabase like tables(), primaryIndex()
implicity execute SQL queries, so these also cannot be used while
navigating the results of forward-only query.
\note QPSQL will print the following warning if it detects a loss of
query results:
\snippet code/doc_src_sql-driver.qdoc 38
\section3 How to Build the QPSQL Plugin on Unix and \macos
You need the PostgreSQL client library and headers installed.

View File

@ -588,7 +588,6 @@ void QSqlField::setAutoValue(bool autoVal)
\sa tableName()
*/
void QSqlField::setTableName(const QString &table)
{
detach();
@ -598,9 +597,12 @@ void QSqlField::setTableName(const QString &table)
/*!
Returns the tableName of the field.
\note When using the QPSQL driver, due to limitations in the libpq library,
the \c tableName() field is not populated in a QSqlField resulting
from a QSqlRecord obtained by QSqlQuery::record() of a forward-only query.
\sa setTableName()
*/
QString QSqlField::tableName() const
{
return d->table;

View File

@ -878,6 +878,14 @@ bool QSqlQuery::isForwardOnly() const
\note Calling setForwardOnly after execution of the query will result
in unexpected results at best, and crashes at worst.
\note To make sure the forward-only query completed successfully,
the application should check lastError() for an error not only after
executing the query, but also after navigating the query results.
\warning PostgreSQL: While navigating the query results in forward-only
mode, do not execute any other SQL command on the same database
connection. This will cause the query results to be lost.
\sa isForwardOnly(), next(), seek(), QSqlResult::setForwardOnly()
*/
void QSqlQuery::setForwardOnly(bool forward)

View File

@ -565,6 +565,14 @@ bool QSqlResult::isForwardOnly() const
\note Calling setForwardOnly after execution of the query will result
in unexpected results at best, and crashes at worst.
\note To make sure the forward-only query completed successfully,
the application should check lastError() for an error not only after
executing the query, but also after navigating the query results.
\warning PostgreSQL: While navigating the query results in forward-only
mode, do not execute any other SQL command on the same database
connection. This will cause the query results to be lost.
\sa isForwardOnly(), fetchNext(), QSqlQuery::setForwardOnly()
*/
void QSqlResult::setForwardOnly(bool forward)
@ -1002,6 +1010,10 @@ bool QSqlResult::nextResult()
\warning The handle can be NULL if the result was not executed yet.
\warning PostgreSQL: in forward-only mode, the handle of QSqlResult can change
after calling fetch(), fetchFirst(), fetchLast(), fetchNext(), fetchPrevious(),
nextResult().
The handle returned here is database-dependent, you should query the type
name of the variant before accessing it.

View File

@ -94,6 +94,10 @@ private slots:
// forwardOnly mode need special treatment
void forwardOnly_data() { generic_data(); }
void forwardOnly();
void forwardOnlyMultipleResultSet_data() { generic_data(); }
void forwardOnlyMultipleResultSet();
void psql_forwardOnlyQueryResultsLost_data() { generic_data("QPSQL"); }
void psql_forwardOnlyQueryResultsLost();
// bug specific tests
void tds_bitField_data() { generic_data("QTDS"); }
@ -1359,6 +1363,8 @@ void tst_QSqlQuery::forwardOnly()
QVERIFY( q.isForwardOnly() );
QVERIFY( q.at() == QSql::BeforeFirstRow );
QVERIFY_SQL( q, exec( "select * from " + qtest + " order by id" ) );
if (!q.isForwardOnly())
QSKIP("DBMS doesn't support forward-only queries");
QVERIFY( q.at() == QSql::BeforeFirstRow );
QVERIFY( q.first() );
QCOMPARE( q.at(), 0 );
@ -1432,6 +1438,209 @@ void tst_QSqlQuery::forwardOnly()
QCOMPARE( q.at(), int( QSql::AfterLastRow ) );
}
void tst_QSqlQuery::forwardOnlyMultipleResultSet()
{
QFETCH(QString, dbName);
QSqlDatabase db = QSqlDatabase::database(dbName);
CHECK_DATABASE(db);
QSqlDriver::DbmsType dbType = tst_Databases::getDatabaseType(db);
if (!db.driver()->hasFeature(QSqlDriver::MultipleResultSets))
QSKIP("DBMS doesn't support multiple result sets");
QSqlQuery q(db);
q.setForwardOnly(true);
QVERIFY_SQL(q, exec("select id, t_varchar from " + qtest + " order by id;" // 1.
"select id, t_varchar, t_char from " + qtest + " where id<4 order by id;" // 2.
"update " + qtest + " set t_varchar='VarChar555' where id=5;" // 3.
"select * from " + qtest + " order by id;" // 4.
"select * from " + qtest + " where id=5 order by id;" // 5.
"select * from " + qtest + " where id=-1 order by id;" // 6.
"select * from " + qtest + " order by id")); // 7.
if (!q.isForwardOnly())
QSKIP("DBMS doesn't support forward-only queries");
// 1. Result set with 2 columns and 5 rows
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), true);
QCOMPARE(q.isSelect(), true);
// Test record() of first result set
QSqlRecord record = q.record();
QCOMPARE(record.count(), 2);
QCOMPARE(record.indexOf("id"), 0);
QCOMPARE(record.indexOf("t_varchar"), 1);
if (dbType != QSqlDriver::PostgreSQL) { // tableName() is not available in forward-only mode of QPSQL
QCOMPARE(record.field(0).tableName(), qtest); // BUG: This fails for Microsoft SQL Server 2016 (QODBC), need fix
QCOMPARE(record.field(1).tableName(), qtest);
}
// Test navigation
QVERIFY(q.first());
QCOMPARE(q.at(), 0);
QCOMPARE(q.value(0).toInt(), 1);
QVERIFY(q.next());
QCOMPARE(q.at(), 1);
QCOMPARE(q.value(0).toInt(), 2);
QVERIFY(q.seek(3));
QCOMPARE(q.at(), 3);
QCOMPARE(q.value(0).toInt(), 4);
QTest::ignoreMessage(QtWarningMsg, "QSqlQuery::seek: cannot seek backwards in a forward only query");
QVERIFY(q.first() == false);
QCOMPARE(q.at(), 3);
QCOMPARE(q.value(0).toInt(), 4);
QTest::ignoreMessage(QtWarningMsg, "QSqlQuery::seek: cannot seek backwards in a forward only query");
QVERIFY(q.previous() == false);
QCOMPARE(q.at(), 3);
QCOMPARE(q.value(0).toInt(), 4);
QTest::ignoreMessage(QtWarningMsg, "QSqlQuery::seek: cannot seek backwards in a forward only query");
QVERIFY(q.seek(1) == false);
QCOMPARE(q.at(), 3);
QCOMPARE(q.value(0).toInt(), 4);
QVERIFY(q.last());
QCOMPARE(q.at(), 4);
// Try move after last row
QVERIFY(q.next() == false);
QCOMPARE(q.at(), QSql::AfterLastRow);
QCOMPARE(q.isActive(), true);
// 2. Result set with 3 columns and 3 rows
QVERIFY(q.nextResult());
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), true);
QCOMPARE(q.isSelect(), true);
// Test record() of second result set
record = q.record();
QCOMPARE(record.count(), 3);
QCOMPARE(record.indexOf("id"), 0);
QCOMPARE(record.indexOf("t_varchar"), 1);
QCOMPARE(record.indexOf("t_char"), 2);
// Test iteration
QVERIFY(q.at() == QSql::BeforeFirstRow);
int index = 0;
while (q.next()) {
QCOMPARE(q.at(), index);
QCOMPARE(q.value(0).toInt(), index+1);
index++;
}
QVERIFY(q.at() == QSql::AfterLastRow);
QCOMPARE(index, 3);
// 3. Update statement
QVERIFY(q.nextResult());
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), true);
QCOMPARE(q.isSelect(), false);
QCOMPARE(q.numRowsAffected(), 1);
// 4. Result set with 5 rows
QVERIFY(q.nextResult());
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), true);
QCOMPARE(q.isSelect(), true);
// Test forward seek(n)
QVERIFY(q.seek(2));
QCOMPARE(q.at(), 2);
QCOMPARE(q.value(0).toInt(), 3);
// Test value(string)
QCOMPARE(q.value("id").toInt(), 3);
QCOMPARE(q.value("t_varchar").toString(), "VarChar3");
QCOMPARE(q.value("t_char").toString().trimmed(), "Char3");
// Next 2 rows of current result set will be
// discarded by next call of nextResult()
// 5. Result set with 1 row
QVERIFY(q.nextResult());
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), true);
QCOMPARE(q.isSelect(), true);
QVERIFY(q.first());
QCOMPARE(q.at(), 0);
QCOMPARE(q.value(0).toInt(), 5);
QVERIFY(q.next() == false);
QVERIFY(q.at() == QSql::AfterLastRow);
// 6. Result set without rows
QVERIFY(q.nextResult());
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), true);
QCOMPARE(q.isSelect(), true);
QVERIFY(q.next() == false);
// 7. Result set with 5 rows
QVERIFY(q.nextResult());
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), true);
QCOMPARE(q.isSelect(), true);
// Just skip it, so we move after last result set.
QVERIFY(q.nextResult() == false);
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), false);
// See if we can execute another query
QVERIFY_SQL(q, exec("select id from " + qtest + " where id=5"));
QVERIFY(q.at() == QSql::BeforeFirstRow);
QCOMPARE(q.isActive(), true);
QCOMPARE(q.isSelect(), true);
QVERIFY(q.first());
QCOMPARE(q.at(), 0);
QCOMPARE(q.value("id").toInt(), 5);
QCOMPARE(q.record().count(), 1);
QCOMPARE(q.record().indexOf("id"), 0);
}
void tst_QSqlQuery::psql_forwardOnlyQueryResultsLost()
{
QFETCH(QString, dbName);
QSqlDatabase db = QSqlDatabase::database(dbName);
CHECK_DATABASE(db);
QSqlQuery q1(db);
q1.setForwardOnly(true);
QVERIFY_SQL(q1, exec("select * from " + qtest + " where id<=3 order by id"));
if (!q1.isForwardOnly())
QSKIP("DBMS doesn't support forward-only queries");
// Read first row of q1
QVERIFY(q1.next());
QCOMPARE(q1.at(), 0);
QCOMPARE(q1.value(0).toInt(), 1);
// Executing another query on the same db connection
// will cause the query results of q1 to be lost.
QSqlQuery q2(db);
q2.setForwardOnly(true);
QVERIFY_SQL(q2, exec("select * from " + qtest + " where id>3 order by id"));
QTest::ignoreMessage(QtWarningMsg, "QPSQLDriver::getResult: Query results lost - "
"probably discarded on executing another SQL query.");
// Reading next row of q1 will not possible.
QVERIFY(!q1.next());
QCOMPARE(q1.at(), QSql::AfterLastRow);
QVERIFY(q1.lastError().type() != QSqlError::NoError);
// See if we can read rows from q2
QVERIFY(q2.seek(1));
QCOMPARE(q2.at(), 1);
QCOMPARE(q2.value(0).toInt(), 5);
}
void tst_QSqlQuery::query_exec()
{
QFETCH( QString, dbName );