Skip to content

Return the cumulative row count after a parameter-array execute - #311

Open
fdcastel wants to merge 7 commits into
FirebirdSQL:masterfrom
fdcastel:fix/param-array-row-counts
Open

Return the cumulative row count after a parameter-array execute#311
fdcastel wants to merge 7 commits into
FirebirdSQL:masterfrom
fdcastel:fix/param-array-row-counts

Conversation

@fdcastel

@fdcastel fdcastel commented Sep 7, 2026

Copy link
Copy Markdown
Member

Builds on #308 and #310: their commits show here until they merge. This PR is the last two commits.

The problem

SQLGetInfo(SQL_PARAM_ARRAY_ROW_COUNTS) answers SQL_PARC_BATCH: one row count per parameter set, stepped with SQLMoreResults. The driver keeps only the last set's update count and SQLMoreResults answers SQL_NO_DATA after a non-select, so an application following the specification gets 1 for a five-row insert (0 when the last set matched nothing), then nothing. The reporter of #299 saw the 1.

SQL_PARAM_ARRAY_SELECTS answers SQL_PAS_BATCH, one result set per set. A result-set statement with a paramset larger than 1 is not routed to the array executor: it runs once with the first set and returns that result set only, with the processed count and the status array untouched.

The fix

executeStatementParamArray adds up the update count of every set; sqlRowCount returns that total while the last execute was an array execute, and a single execute resets it. SQL_PARAM_ARRAY_ROW_COUNTS becomes SQL_PARC_NO_BATCH, which is what the driver now does. SQL_PARAM_ARRAY_SELECTS becomes SQL_PAS_NO_SELECT, the closest description of what the driver does with selects; execution is not changed, so an application that sets a paramset on a select still gets the first set's rows.

Per-set counts through SQLMoreResults would need the statement's post-execute state to change; if anyone asks for them later, nothing here stands in the way.

Tests

RowCountAfterArrayInsert (five sets, count 5, SQLMoreResults = SQL_NO_DATA) and RowCountAfterArrayUpdate (three sets touching one, three and zero rows, count 4, which tells a total apart from a set count and from the last set's count). The two SQLGetInfo cases expect the new values. Against the #310 driver all four fail and the other 17 array cases pass; with this branch the array suite is 21 of 21. Full suite, CHARSET=UTF8 and NONE: 392 ran, 239 passed, 153 skipped, 0 failed.

Probe (ctypes, Windows x64, Firebird 5.0.3, Microsoft driver manager):

case #310 this branch
SQLRowCount after a 5-set INSERT 1 5
SQLRowCount after a 3-set UPDATE, two sets match 0 2
SQL_PARAM_ARRAY_ROW_COUNTS / SQL_PARAM_ARRAY_SELECTS BATCH / BATCH NO_BATCH / NO_SELECT
SELECT with paramset 3 first set's rows only unchanged

Still open

Eleven ArrayBindingTest cases were skipped citing a crash in the
column-wise indicator offset. That crash was fixed in 0a7cc1f (SQLINTEGER
to SQLLEN), so the skips come out. ParamOperationPtrSkipRows stays skipped
because SQL_ATTR_PARAM_OPERATION_PTR is still not honoured, and the
reactivated cases get the Firebird 6 guard the row-wise ones already have.

Two new cases bind fixed-length C types with BufferLength = 0, the usage
the ODBC specification describes for those types: one with the shape
reported in FirebirdSQL#299 (INTEGER and BIGINT with a NULL in the third parameter
set), one across DOUBLE, DATE and TIMESTAMP.
…h is 0

SQLBindParameter stores BufferLength as the element stride of a
column-wise parameter array. The ODBC specification ignores BufferLength
for fixed-length C types and applications pass 0 there, so every
parameter set read element 0: the driver inserted N rows carrying the
first set's values and reported SQL_SUCCESS.

bindInputOutputParam now falls back to the C type size when the stride is
0, the way bindOutputColumn already does for SQLBindCol. getConciseSize
gains SQL_C_GUID, for which it returned the type code (-11) and would have
produced a negative stride.

Character and binary C types keep BufferLength as their stride, so string
arrays behave as before.

Fixes FirebirdSQL#299
…fter SQLPrepare

The executor is chosen at prepare time from the paramset size in force at
that moment. An application that prepares first and sets
SQL_ATTR_PARAMSET_SIZE afterwards, which the specification allows and which
pyodbc's fast_executemany does, got a single execution of the first
parameter set, reported as success.

sqlExecute and sqlExecDirect now switch to executeStatementParamArray when
the paramset size is greater than 1 at execute time.
ParamOperationPtrSkipRows and RowWiseWithOperationPtr were skipped because
the driver does not honour the operation array. Both get the Firebird 6
guard the other array cases carry and run from here on.
The operation array was stored in the APD (SQL_DESC_ARRAY_STATUS_PTR) but
executeStatementParamArray never read it, so a set marked SQL_PARAM_IGNORE
was executed like any other and an application had no way to skip a row
short of rebuilding its arrays.

The loop now skips ignored sets, leaves their status at SQL_PARAM_UNUSED
and does not count them in SQL_ATTR_PARAMS_PROCESSED_PTR. The status array
is filled with SQL_PARAM_UNUSED before the loop, so sets that were never
reached read as unused, and status elements are addressed by set number
instead of through a moving pointer. The processed count is kept per set
instead of being written once after the loop, so it includes a failed set
as the specification describes.
Two cases check the row count after an array INSERT (five sets, five) and
after an array UPDATE whose sets touch one, three and zero rows (four), and
that SQLMoreResults has nothing further. The two SQLGetInfo cases now
expect SQL_PARC_NO_BATCH and SQL_PAS_NO_SELECT, the values the driver's
behaviour matches.
SQLGetInfo advertised SQL_PARC_BATCH, one row count per parameter set
stepped with SQLMoreResults, but the driver kept only the last set's count
and SQLMoreResults answered SQL_NO_DATA, so an application saw 1 (or 0
when the last set matched nothing) for a five-row insert.

executeStatementParamArray now adds up the update count of every set and
sqlRowCount returns that total while the last execute was an array
execute; a single execute resets it. SQL_PARAM_ARRAY_ROW_COUNTS reports
SQL_PARC_NO_BATCH accordingly. SQL_PARAM_ARRAY_SELECTS reports
SQL_PAS_NO_SELECT: a result-set statement with a paramset larger than 1
is not routed to the array executor and runs once with the first set,
which neither SQL_PAS_BATCH nor SQL_PAS_NO_BATCH describes.
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.

1 participant