summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/plugins/sqldrivers/psql/qsql_psql.cpp304
-rw-r--r--src/sql/doc/snippets/code/doc_src_sql-driver.cpp32
-rw-r--r--src/sql/doc/snippets/code/doc_src_sql-driver.qdoc12
-rw-r--r--src/sql/doc/src/sql-driver.qdoc39
-rw-r--r--src/sql/kernel/qsqlfield.cpp6
-rw-r--r--src/sql/kernel/qsqlquery.cpp8
-rw-r--r--src/sql/kernel/qsqlresult.cpp12
-rw-r--r--tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp209
8 files changed, 590 insertions, 32 deletions
diff --git a/src/plugins/sqldrivers/psql/qsql_psql.cpp b/src/plugins/sqldrivers/psql/qsql_psql.cpp
index ab9aa7f176..6de7797c19 100644
--- a/src/plugins/sqldrivers/psql/qsql_psql.cpp
+++ b/src/plugins/sqldrivers/psql/qsql_psql.cpp
@@ -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)
{
- Q_Q(const QPSQLDriver);
+ // PQexec() silently discards any prior query results that the application didn't eat.
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));
+ 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;
}
-PGresult * QPSQLDriverPrivate::exec(const QString & stmt) const
+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()
{
- return exec(isUtf8 ? stmt.toUtf8().constData() : stmt.toLocal8Bit().constData());
+ int stmtId = ++stmtCount;
+ if (stmtId <= 0)
+ stmtId = stmtCount = 1;
+ return stmtId;
+}
+
+void QPSQLDriverPrivate::checkPendingNotifications() const
+{
+ Q_Q(const QPSQLDriver);
+ if (seid.size() && !pendingNotifyCheck) {
+ pendingNotifyCheck = true;
+ QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), "_q_handleNotification", Qt::QueuedConnection, Q_ARG(int,0));
+ }
}
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;
diff --git a/src/sql/doc/snippets/code/doc_src_sql-driver.cpp b/src/sql/doc/snippets/code/doc_src_sql-driver.cpp
index 7983386642..e7dfbd90d5 100644
--- a/src/sql/doc/snippets/code/doc_src_sql-driver.cpp
+++ b/src/sql/doc/snippets/code/doc_src_sql-driver.cpp
@@ -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]
diff --git a/src/sql/doc/snippets/code/doc_src_sql-driver.qdoc b/src/sql/doc/snippets/code/doc_src_sql-driver.qdoc
index 04ea30915d..d127bdf8a5 100644
--- a/src/sql/doc/snippets/code/doc_src_sql-driver.qdoc
+++ b/src/sql/doc/snippets/code/doc_src_sql-driver.qdoc
@@ -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]
diff --git a/src/sql/doc/src/sql-driver.qdoc b/src/sql/doc/src/sql-driver.qdoc
index 025cd43ef7..70205d5552 100644
--- a/src/sql/doc/src/sql-driver.qdoc
+++ b/src/sql/doc/src/sql-driver.qdoc
@@ -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.
diff --git a/src/sql/kernel/qsqlfield.cpp b/src/sql/kernel/qsqlfield.cpp
index a258d44df7..782ab0d71c 100644
--- a/src/sql/kernel/qsqlfield.cpp
+++ b/src/sql/kernel/qsqlfield.cpp
@@ -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;
diff --git a/src/sql/kernel/qsqlquery.cpp b/src/sql/kernel/qsqlquery.cpp
index b89d20976f..628bfa1880 100644
--- a/src/sql/kernel/qsqlquery.cpp
+++ b/src/sql/kernel/qsqlquery.cpp
@@ -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)
diff --git a/src/sql/kernel/qsqlresult.cpp b/src/sql/kernel/qsqlresult.cpp
index f79c1c71cd..cdb1379502 100644
--- a/src/sql/kernel/qsqlresult.cpp
+++ b/src/sql/kernel/qsqlresult.cpp
@@ -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.
diff --git a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
index 1935bb6501..664ba9a9dc 100644
--- a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
+++ b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp
@@ -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 );