Fix column-wise parameter arrays reading element 0 for fixed-length C types (#299) - #308
Fix column-wise parameter arrays reading element 0 for fixed-length C types (#299)#308fdcastel wants to merge 3 commits into
Conversation
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.
|
Verified on Linux with this PR's First program from #299 (column-wise with The release build still stores Second program (seven C types, with and without a NULL in row 3), PR build: every type stores five distinct rows, and the NULL lands in row 3 for all of them. Two things the release build got wrong are both gone: the fixed-length types repeating element 0, and the
Thank you, again. Three reports, three root-caused fixes with regression tests in the space of a day and a half, and the third one closes the defect that made me turn parameter arrays off for this driver in the first place; once a release carries #308 that switch can go back on, which is the difference between one round-trip per row and one per batch for everyone loading Firebird through ODBC. On odbc-crusher: I had not seen it and it is exactly the kind of tool I have been wishing existed, an executable version of the specification rather than a reading of it. The compatibility matrix behind these reports runs one workload through 53 ODBC drivers on Linux, macOS and Windows, so I have a ready-made set of drivers to point it at. I will run it across that set and bring anything useful, results included, to your issues page as you suggest. (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.) |
Fixes #299.
The problem
SQLBindParameterstoresBufferLengthas the element stride of a column-wise parameter array (sizeColumnExtendedFetch). The ODBC specification ignoresBufferLengthfor fixed-length C types and applications pass 0 there, soinputParammultiplied the set number by 0 and every parameter set read element 0.SQLExecutereturnedSQL_SUCCESS,SQL_ATTR_PARAMS_PROCESSED_PTRsaid N and the status array was allSQL_PARAM_SUCCESS; the table held N copies of the first set.SQL_C_CHARarrays were stepped correctly because thereBufferLengthis the element size, which is why the string-only tests never saw it.The output side has always had the fallback:
bindOutputColumncomputes the stride from the C type whenSQLBindColgets 0. The input side never did.A second defect sits on the same path. The executor is chosen at prepare time (
execute = &executeStatementParamArrayonly if the paramset size is already greater than 1). An application that prepares first and setsSQL_ATTR_PARAMSET_SIZEafterwards, which the specification allows, getsexecuteStatementonce: one row, first parameter set,SQL_SUCCESS. pyodbc'sfast_executemanydoes exactly that sequence; against master a five-rowexecutemanystores one row.Two of the issue's observations are not defects:
ORDER BY idover equal keys returned the rows in arbitrary order and the NULL appeared to move.SQLRowCount= 1 after an array execute matches theSQL_PARC_BATCHthe driver reports forSQL_PARAM_ARRAY_ROW_COUNTS: the count is per parameter set. What is missing isSQLMoreResultsstepping to the next set's count; not touched here.The fix
Three commits.
tests:elevenArrayBindingTestcases were skipped citing a crash in the indicator offset that Fix bug in null-indicator offset calculation, SQLINTEGER->SQLLEN #279 fixed. The skips come out, with the Firebird 6 guard the row-wise cases already carry;ParamOperationPtrSkipRowsstays skipped for the reason its row-wise twin gives. Two new cases bind fixed-length C types withBufferLength = 0: the shape from the issue (INTEGERandBIGINT, NULL in the third set) andDOUBLE/DATE/TIMESTAMP.bindInputOutputParamfalls back togetConciseSizewhen the stride is 0, the same switchbindOutputColumnuses.getConciseSizegainsSQL_C_GUID, for which it returned the type code (-11), a negative stride on either side.sqlExecuteandsqlExecDirectswitch toexecuteStatementParamArraywhen the paramset size is greater than 1 at execute time and the prepare-time choice was something else.Tests
On master (35fae3e) with the skips removed, 3 of the 17 active array cases fail:
ColumnWisePrepareExecute(sets the paramset size afterSQLPrepare) and the two new cases. With this branch all 17 pass; the twoOPERATION_PTRcases stay skipped.A ctypes port of the issue's program, extended with the prepare-then-set-paramset order and pyodbc
fast_executemany. Windows x64, Firebird 5.0.3, Microsoft driver manager:BufferLength = 0(1,NULL) (1,10) (1,10) (1,10) (1,10)(1,10) (2,20) (3,NULL) (4,40) (5,50)BufferLength = sizeof(type)SQL_ATTR_PARAMSET_SIZEset afterSQLPrepare(1,10)fast_executemany, five rows(1,10)Full suite with this branch,
CHARSET=UTF8andNONE: 390 ran, 235 passed, 155 skipped, 0 failed.Still open
SQL_ATTR_PARAM_OPERATION_PTRis ignored (ParamOperationPtrSkipRowsandRowWiseWithOperationPtrstay skipped).SQLMoreResultsafter an array execute does not step through the per-set row counts.SQLBindColwithSQL_ATTR_ROW_BIND_OFFSET_PTR), is a different branch offetchDataand is not touched here.fb-cppedition) #283) already carries both fixes.