Skip to content

Fix behaviour of fetch() without any data with PostgreSQL#1330

Open
vadz wants to merge 6 commits intoSOCI:masterfrom
vadz:postgres-fix-empty-fetch
Open

Fix behaviour of fetch() without any data with PostgreSQL#1330
vadz wants to merge 6 commits intoSOCI:masterfrom
vadz:postgres-fix-empty-fetch

Conversation

@vadz
Copy link
Member

@vadz vadz commented Feb 5, 2026

If execute() didn't bring any data from the server, all statement member variables remained uninitialized, resulting in problems (UB, infinite loops, ...) if fetch() is called later.

Fix this by initializing numberOfRows_ and checking for it in fetch() and simply not doing anything there if it is 0.

Closes #1328.

If execute() didn't bring any data from the server, all statement member
variables remained uninitialized, resulting in problems (UB, infinite
loops, ...) if fetch() is called later.

Fix this by initializing numberOfRows_ and checking for it in fetch()
and simply not doing anything there if it is 0.

Closes SOCI#1328.
vadz added 5 commits February 6, 2026 00:48
Same fix is required for MySQL as for PostgreSQL, see the previous
commit message for explanation.
Ensure we don't try doing anything in fetch() if there is no data to
retrieve.
Instead of calling throw_oracle_soci_error(), add oracle_soci_error ctor
taking the parameters that were passed to this function and just throw
the object created using this ctor.

This is more explicit and simpler.

No real changes.
Check for the error apparently triggered by trying to execute a
statement without any results and do not turn it into exception.
Remove ugly get_error_details() function taking multiple output
parameters and just create oracle_soci_error objects directly.

As error.h has now become useless, remove it too.
@vadz vadz force-pushed the postgres-fix-empty-fetch branch from 343c4d1 to 9b03708 Compare February 6, 2026 01:34
@GregTheMadMonk
Copy link

GregTheMadMonk commented Feb 6, 2026

Yes, the random number reads in fetch are (or at least seem to be :) ) fixed by this. Thank you!

normally we shouldn't be even called in this case

How should I have approached writing a "generic" interface for queries with SOCI, e.g. an interactive tool to execute requests and view their results without knowing in advance if the query returns any data or not? Or was this indeed the only way to do it without parsing the requests manually beforehand?

P.S.

From #1329:
I don't see a sanitizer error here. I do see a failed

 CHECK( fetch_all("UPDATE soci_test SET flag=TRUE", flag) == 0 )
due to unexpected exception with message:
 Null value fetched and no indicator defined for the parameter number 1 while
 fetching data from "UPDATE soci_test SET flag=TRUE".
I'm not sure why are you not seeing this, as it seems correct to me.

I'm... not really sure. I guess that might be just UB justifying the "undefined" in its name, because even with everything but that query commented I still get

$ grep -n -A9 'Reads several rows - fine' ../tests/postgresql/test-postgresql.cpp && ninja && ./bin/soci_postgresql_test 'host=localhost port=5432 dbname=test user=postgres password='
1449:        // Reads several rows - fine
1450-        // CHECK(fetch_all("SELECT * FROM soci_test", flag) == 2);
1451-        // Reads zero rows - fine
1452-        // CHECK(fetch_all("SELECT * FROM soci_test WHERE flag = NOT flag", flag) == 0);
1453-        // UPDATE causes a sanitizer error
1454-        CHECK(fetch_all("UPDATE soci_test SET flag=TRUE", flag) == 0);
1455-        // INSERT causes a sanitizer error
1456-        // CHECK(fetch_all("INSERT INTO soci_test(flag) VALUES (FALSE)", flag) == 0);
1457-        // DELETE causes a sanitizer error
1458-        // CHECK(fetch_all("DELETE FROM soci_test WHERE NOT flag", flag) == 0);
--
1460:        // Reads several rows - fine
1461-        // CHECK(fetch_all("SELECT * FROM soci_test", row) == 2);
1462-        // Reads zero rows - fine
1463-        // CHECK(fetch_all("SELECT * FROM soci_test WHERE flag = NOT flag", row) == 0);
1464-        // UPDATE causes a sanitizer error
1465-        // CHECK(fetch_all("UPDATE soci_test SET flag=TRUE", row) == 0);
1466-        // INSERT causes a sanitizer error
1467-        // CHECK(fetch_all("INSERT INTO soci_test(flag) VALUES (FALSE)", row) == 0);
1468-        // DELETE causes a sanitizer error
1469-        // CHECK(fetch_all("DELETE FROM soci_test WHERE NOT flag", row) == 0);
[2/2] Linking CXX executable bin/soci_postgresql_test

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
soci_postgresql_test is a Catch v2.13.10 host application.
Run with -? for options

-------------------------------------------------------------------------------
PostgreSQL ROWID
-------------------------------------------------------------------------------
/media/ssd/projects/cpp/soci/psql-statement-ub/tests/postgresql/test-postgresql.cpp:154
...............................................................................

/media/ssd/projects/cpp/soci/psql-statement-ub/tests/postgresql/test-postgresql.cpp:162: warning:
  Skipping test because OIDs are no longer supported in PostgreSQL 180001

NOTICE:  table "soci_test" does not exist, skipping
NOTICE:  table "soci_test" does not exist, skipping
/media/ssd/projects/cpp/soci/psql-statement-ub/src/backends/postgresql/statement.cpp:568:17: runtime error: signed integer overflow: -1094795586 + -1094795586 cannot be represented in type 'int'
===============================================================================
All tests passed (2425 assertions in 96 test cases)

I wanted to try with the clang's memory sanitizer because I expected the UB sanitizer to report an undefined memory read and... it threw a use-of-uninitialized-value from Catch.hpp:12542:21 at me even before it could get to the test :| Which is weird, could this be a false-positive?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[PSQL] execute(false) into fetch() on a statement that returns no data leads to undefined behavior

2 participants