Fix read-only queries stopping after the first row

Since #1046, QETSql::execReadOnly() runs a query with PRAGMA query_only
set and switches it off before returning. Switching it off aborts a
statement SQLite is still stepping through ("abort due to ROLLBACK"), and
QSQLITE has already stepped to the first row by then. A query that
produces its rows as it goes -- a UNION ALL without ORDER BY -- therefore
came back with its first row only and no error. A sorted query was not
affected, because SQLite has read every row before returning the first.

Every query from the SQL box of a table, a saved <graphics_table> query
and the scripting qet.query() goes through here.

The checked run is now finished before query_only is switched off, and a
query that passed is run again for the caller. SQLite refuses a write at
its first step, so passing that step is what proves a statement reads
only; the second run is of a statement already shown to be read-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
This commit is contained in:
ispyisail
2026-09-27 14:00:07 +13:00
parent d7036ad807
commit b740508b7f
2 changed files with 24 additions and 0 deletions
+17
View File
@@ -40,6 +40,7 @@
#include <QtTest>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
class TstSqlReadOnly : public QObject
@@ -63,6 +64,7 @@ class TstSqlReadOnly : public QObject
void doesNotExecuteWhatItRefuses();
void refusedQueryCannotBeRunAgain();
void acceptedQueryRunAgainStillReads();
void returnsEveryRowOfACompoundSelect();
void leavesTheConnectionWritable();
private:
@@ -242,6 +244,21 @@ void TstSqlReadOnly::acceptedQueryRunAgainStillReads()
QCOMPARE(n, 2);
}
void TstSqlReadOnly::returnsEveryRowOfACompoundSelect()
{
// Switching query_only off aborts a statement SQLite is still
// stepping through, and an unsorted UNION ALL is one: it came back
// with its first row only.
QSqlQuery q = QETSql::execReadOnly(
m_db, "SELECT uuid FROM element UNION ALL SELECT uuid FROM element");
QStringList uuids;
while (q.next()) {
uuids << q.value(0).toString();
}
QVERIFY(!q.lastError().isValid());
QCOMPARE(uuids, QStringList({"a", "b", "a", "b"}));
}
void TstSqlReadOnly::leavesTheConnectionWritable()
{
// query_only must not outlive the call, whatever its outcome: the