Skip to content

Step a column-wise SQL_C_TYPE_DATE parameter array by the C struct - #1478

Open
singhpratech wants to merge 1 commit into
openlink:develop/7from
singhpratech:fix-column-wise-date-param-stride
Open

singhpratech wants to merge 1 commit into
openlink:develop/7from
singhpratech:fix-column-wise-date-param-stride

Conversation

@singhpratech

Copy link
Copy Markdown

Fixes #1473.

With SQL_ATTR_PARAMSET_SIZE greater than 1 and column-wise binding, a SQL_C_TYPE_DATE parameter array is walked by the ColumnSize argument of SQLBindParameter rather than by the size of the C type. Every set executes, SQLRowCount reports the right number of rows and every parameter status is SQL_PARAM_SUCCESS, so nothing tells the caller which dates were sent:

sizeof(SQL_DATE_STRUCT)=6; expected 2024-01-01 2024-02-29 2024-03-15 2024-12-31
packed 6-byte elements, ColumnSize=10    rc=0 rows=4 -> 2024-01-01-05:00 0197-08-05-04:00 0014-07-03-04:00 -002-11-30-04:00
packed 6-byte elements, ColumnSize=0     rc=0 rows=4 -> 2024-01-01-05:00 2024-01-01-05:00 2024-01-01-05:00 2024-01-01-05:00
packed 6-byte elements, ColumnSize=6     rc=0 rows=4 -> 2024-01-01-05:00 2024-02-29-05:00 2024-03-15-04:00 2024-12-31-05:00

ColumnSize 0 — a natural way to bind a date, the type having a fixed size — makes every set read the first element; any other value that is not sizeof (SQL_DATE_STRUCT) reads from the wrong offsets and past the end of the buffer. SQL_C_TYPE_TIME and SQL_C_TYPE_TIMESTAMP behave the same way; SQL_C_CHAR, SQL_C_DOUBLE and SQL_C_SLONG arrays walk correctly, and so does row-wise binding through SQL_ATTR_PARAM_BIND_TYPE.

The cause is in sqlc_sizeof_1 (), libsrc/Wi/CLIuti.c:1586. The switch lists SQL_C_TIMESTAMP, SQL_C_DATE and SQL_C_TIME, but not the ODBC 3 spellings SQL_C_TYPE_TIMESTAMP, SQL_C_TYPE_DATE and SQL_C_TYPE_TIME, so those fall through to default: return cbColDef; and the element size becomes ColumnSize. stmt_parm_to_dv () passes that size to stmt_param_place_ptr () (CLIuti.c:1613 and CLIuti.c:39), which multiplies it by the set number whenever SQL_ATTR_PARAM_BIND_TYPE is 0.

The change. The three ODBC 3 identifiers are added to the cases that already return the size of the C struct, guarded by #if (ODBCVER >= 0x0300) in the same shape as the other switches in this file (sql_type_to_sqlc_default (), buffer_to_dv () and dv_to_place () all list both spellings that way). A column-wise array of a fixed-length C type is then stepped by that type's size, as the specification prescribes, and SQL_C_TYPE_DATE behaves exactly like SQL_C_DATE does today. One consequence worth naming: a set whose elements are further apart than the struct — bound column-wise and relying on ColumnSize as the stride — is now read from the packed offsets instead. SQL_ATTR_PARAM_BIND_TYPE is the supported way to keep a wider stride, and it is correct before and after this change.

Test. I built the four driver flavours from this tree before and after (make -C binsrc/driver, exit 0, no new warnings) and ran the reproduction from #1473 against openlink/virtuoso-opensource-7 in Docker through unixODBC 2.3.12. Before: ColumnSize 0 inserts row 0's date four times and ColumnSize 10 over a packed array inserts 0197-08-05, 0014-07-03 and -002-11-30 for the other three. After: all three packed-array variants insert 2024-01-01 2024-02-29 2024-03-15 2024-12-31, and a row-wise variant I added to the program (SQL_ATTR_PARAM_BIND_TYPE = the row struct size) is correct in both runs. As a regression pass I also ran a pyodbc script over the same server with both drivers — connect, SQLGetInfo, DDL, single-row and executemany inserts of int/varchar/double/DATE/DATETIME, ordered fetch, COUNT(*), a date parameter, a UTF-8 round trip, SQLTables, SQLColumns — and the output is byte-identical. I did not run binsrc/tests/suite, which needs a full server build.

Found while verifying Virtuoso for adbcBridge, an ADBC driver over ODBC, which sends batches as column-wise parameter arrays and binds dates as SQL_C_TYPE_DATE.

One note for a possible follow-up, not part of this PR: sqlc_sizeof () just above has the same three names missing, which is why SQLBindCol with SQL_C_TYPE_DATE and BufferLength 0 leaves the column stride at 0. I have not changed it here, since #1473 is about parameters and I have not measured the fetch side.

sqlc_sizeof_1 () lists SQL_C_DATE, SQL_C_TIME and SQL_C_TIMESTAMP but not
their ODBC 3 spellings SQL_C_TYPE_DATE, SQL_C_TYPE_TIME and
SQL_C_TYPE_TIMESTAMP, so a parameter bound with one of those falls into the
default clause and its element size becomes the ColumnSize argument of
SQLBindParameter.  With SQL_ATTR_PARAMSET_SIZE greater than 1 and column-wise
binding, stmt_param_place_ptr () then steps through the array by ColumnSize:
ColumnSize 0 makes every set read the first element, and any other value that
is not the size of the C struct reads from the wrong offsets and past the end
of the buffer.  Every set is still reported as SQL_PARAM_SUCCESS.

Add the three ODBC 3 identifiers to the cases that already return the size of
the C struct, in the same way as the other switches in this file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant