Skip to content

Fix SQLPrepare discarding SQL_ATTR_ROWS_FETCHED_PTR and SQL_ATTR_ROW_STATUS_PTR (#301) - #303

Open
fdcastel wants to merge 2 commits into
FirebirdSQL:masterfrom
fdcastel:fix/issue-301-rows-fetched-ptr-across-prepare
Open

Fix SQLPrepare discarding SQL_ATTR_ROWS_FETCHED_PTR and SQL_ATTR_ROW_STATUS_PTR (#301)#303
fdcastel wants to merge 2 commits into
FirebirdSQL:masterfrom
fdcastel:fix/issue-301-rows-fetched-ptr-across-prepare

Conversation

@fdcastel

@fdcastel fdcastel commented Sep 6, 2026

Copy link
Copy Markdown
Member

Fixes #301.

The problem

OdbcDesc::setDefaultImplDesc rebuilds the implementation row descriptor on every prepare, and its IRD branch also reset SQL_DESC_ROWS_PROCESSED_PTR and SQL_DESC_ARRAY_STATUS_PTR to NULL. sqlPrepare calls it (so SQLExecDirect too), as does setResultSet for catalog functions. An application that set SQL_ATTR_ROWS_FETCHED_PTR / SQL_ATTR_ROW_STATUS_PTR before SQLPrepare or SQLExecDirect — the usual order — never had them written by SQLFetch: the rows-fetched counter kept whatever it held and the status array stayed untouched, on every fetch including the last partial one. Set after the prepare, both worked. The IPD branch of the same function never resets its own counterparts, which is why SQL_ATTR_PARAMS_PROCESSED_PTR survives a prepare and the row-side pointers did not.

Statement attributes persist until the statement is freed or the attribute is set again; SQLPrepare is not supposed to clear them. The reset dates from 2004/2008, so every released version is affected.

The fix

Drop the two pointer resets from the IRD branch of setDefaultImplDesc. The other header fields reset there (headAllocType, headArraySize, headBindOffsetPtr) are ARD-only fields in ODBC terms and nothing reads them from the IRD, so they are left as they were.

Tests

tests/test_block_cursor.cpp: four cases over a six-row UNION ALL with SQL_ATTR_ROW_ARRAY_SIZE = 4, each asserting the counter (4, then 2), the status array (SQL_ROW_SUCCESS / SQL_ROW_NOROW) and the bound values on both rowsets, then SQL_NO_DATA.

  • RowsFetchedPtrSetBeforePrepare — also asserts that SQLGetStmtAttr(SQL_ATTR_ROW_STATUS_PTR) still returns the pointer after SQLPrepare. Fails on master.
  • RowsFetchedPtrSetBeforeExecDirect — fails on master.
  • RowsFetchedPtrSurvivesHandleReuse — attributes kept across SQLFreeStmt(SQL_CLOSE) and a second SQLExecDirect on the same handle. Fails on master.
  • RowsFetchedPtrSetAfterPrepare — control, passes both ways.

Verified on Windows x64 against Firebird 5.0.3 with the Microsoft driver manager. The reporter's program reproduces the output from the issue verbatim on master and prints the correct counters with this branch for all five orderings (before / between / after SQLPrepare + SQLExecute; before / after SQLExecDirect). Full suite: 392 ran, 227 passed, 165 skipped, 0 failed.

Still open

  • SQLGetStmtAttr(SQL_ATTR_ROWS_FETCHED_PTR) returns HYC00: sqlGetStmtAttr has no case for it (nor for SQL_ATTR_PARAMS_PROCESSED_PTR, SQL_ATTR_PARAM_STATUS_PTR, SQL_ATTR_PARAM_OPERATION_PTR, SQL_ATTR_ROW_OPERATION_PTR and the two *_BIND_OFFSET_PTR attributes), so the default: branch answers. Setting them works, and once the statement is prepared the same pointers come back through SQLGetDescField on the IRD (SQL_DESC_ROWS_PROCESSED_PTR, SQL_DESC_ARRAY_STATUS_PTR; before prepare the IRD answers HY091). Only the statement-attribute read-back is missing. Separate change; the test above reads back SQL_ATTR_ROW_STATUS_PTR, which does have a case.
  • The same two lines exist in the v5 RFC (v5 - Firebird ODBC Driver Modernization (fb-cpp edition) #283), whose block-fetch test hides the symptom behind an if (rowsFetched > 0). Both will be corrected there on its next rebase.

…Prepare

OdbcDesc::setDefaultImplDesc rebuilds the implementation row descriptor
on every prepare and, while doing so, reset SQL_DESC_ROWS_PROCESSED_PTR
and SQL_DESC_ARRAY_STATUS_PTR to NULL. An application that set
SQL_ATTR_ROWS_FETCHED_PTR / SQL_ATTR_ROW_STATUS_PTR before SQLPrepare or
SQLExecDirect never had them written by SQLFetch, so a block cursor could
not tell how many rows the last rowset held. The same attributes set
after the prepare worked, and the parameter descriptor never reset its
own counterparts. Statement attributes persist until the statement is
freed or the attribute is set again; SQLPrepare must not clear them.

Reported in FirebirdSQL#301.
Three cases fail on master: SQL_ATTR_ROWS_FETCHED_PTR and
SQL_ATTR_ROW_STATUS_PTR set before SQLPrepare, before SQLExecDirect, and
kept across SQLFreeStmt(SQL_CLOSE) on a reused handle. The fourth sets
them between SQLPrepare and SQLExecute and is the control that already
passed.
@singhpratech

singhpratech commented Sep 7, 2026

Copy link
Copy Markdown

Confirmed on Linux with the linux-x64-binaries artifact from this PR's CI run, unixODBC 2.3.12, Firebird 5.0.4, CHARSET=UTF8. The program from #301, PR build:

attrs set before SQLPrepare        :  fetch -> rows_fetched=4 status=0,0,0,0 values=1,2,3,4  fetch -> rows_fetched=2 status=0,0,3,3 values=5,6,-1,-1
attrs set between Prepare/Execute  :  fetch -> rows_fetched=4 status=0,0,0,0 values=1,2,3,4  fetch -> rows_fetched=2 status=0,0,3,3 values=5,6,-1,-1
attrs set after SQLExecute         :  fetch -> rows_fetched=4 status=0,0,0,0 values=1,2,3,4  fetch -> rows_fetched=2 status=0,0,3,3 values=5,6,-1,-1

All three orderings now agree: 4 rows then 2, with SQL_ROW_SUCCESS for the fetched slots and SQL_ROW_NOROW for the two unused ones. The 3.5.0-rc1 release build, same session, still leaves rows_fetched=777 and the status array untouched for the "before SQLPrepare" case. That matches your diagnosis that only the row descriptor was being rebuilt on prepare.

Thank you for this one as well; two root-caused fixes with tests in a single evening, on two issues from the same reporter, is generous with your time in a way I do not take for granted. Block-cursor readers can now trust the counters in whichever order they set things up, which is exactly what makes the driver usable from bulk-reading clients. Noted on the SQLGetStmtAttr(SQL_ATTR_ROWS_FETCHED_PTR) HYC00 for the follow-up; happy to test that too when it is up.

(Where this comes from: adbcBridge, an ADBC driver over ODBC that runs Firebird in its compatibility matrix on Linux and Windows, https://github.com/singhpratech/adbcbridge. Its Firebird row and upstream tracker now record this fix.)

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.

SQLPrepare discards SQL_ATTR_ROWS_FETCHED_PTR and SQL_ATTR_ROW_STATUS_PTR set before it, so block cursors never learn how many rows a fetch returned

2 participants