Skip to content

ODBC: use the length indicator of the current row of a parameter array - #13610

Open
singhpratech wants to merge 1 commit into
apache:masterfrom
singhpratech:odbc-param-array-null-indicator
Open

singhpratech wants to merge 1 commit into
apache:masterfrom
singhpratech:odbc-param-array-null-indicator

Conversation

@singhpratech

Copy link
Copy Markdown

Fixes #13537.

With SQL_ATTR_PARAMSET_SIZE > 1 and column-wise binding, the ODBC driver decides whether a parameter is NULL from the indicator of the first row of the array, so a SQL_NULL_DATA below the first row is never sent as NULL.

Binding three rows whose indicators are {SQL_NTS, SQL_NULL_DATA, SQL_NTS}:

column type expected 2.17 actual
VARCHAR row 2 is NULL row 2 is a non-NULL empty string, SQL_SUCCESS, every parameter status SQL_PARAM_SUCCESS
BINARY row 2 is NULL client segfaults inside SQLExecute

A NULL in row 0 makes every row NULL, and row-wise binding is unaffected — which is what points at the indicator being read at the wrong index.

Cause

Parameter::Write tested buffer.GetInputSize() on the parameter's own buffer, before the byte and element offsets of the row being written were applied. The value was then read from a copy with the offsets applied, so the indicator and the value came from different rows. For the BINARY path the resulting -1 length reached memcpy as an array length, which is the crash.

Fix

Apply both offsets to the copy first and test that copy, so the indicator is read at the same offset as the value. Separately, treat a negative length on the binary path as NULL rather than passing it on as a length. Two small changes in modules/platforms/cpp/odbc/src/app/parameter.cpp.

Tests

modules/platforms/cpp/odbc-test/src/parameter_test.cpp, new, added to that module's CMakeLists.txt. It covers the indicator being taken from the current row for character, binary and integer parameters, a NULL in row 0, a NULL below row 0, and the negative-length binary case, by exercising Parameter::Write against a binary writer — no server needed, so it runs in the unit part of the ODBC suite.

The self-contained C reproducer from #13537 was re-run against a build with this change: all three rows round-trip, row 2 reads back as NULL for both VARCHAR and BINARY, and the BINARY case no longer crashes.

On the JIRA ticket

CONTRIBUTING.md asks for an IGNITE ticket and an IGNITE-NNNNN title, and I would rather not invent a number. I have asked on dev@ignite.apache.org for JIRA contributor access so that I can file it properly; as soon as the ticket exists I will rename the branch and retitle this pull request, or close it and reopen under the ticket if you prefer that. The issue has been open on GitHub since 29 August with the reproducer, and I did not want the fix to sit unavailable in the meantime. Happy to follow whatever process suits you.

For context on where it came from: this surfaced while testing Ignite through adbcBridge (https://github.com/singhpratech/adbcbridge), a driver for ADBC — Apache Arrow's database connectivity API — that works over any ODBC driver, where binding a column of values containing NULLs is an everyday operation.

Parameter::Write tested buffer.GetInputSize() on the parameter's own
buffer, before the byte and element offsets of the row being written were
applied, so the NULL indicator of the first row decided the NULL-ness of
every row of the array. A NULL below the first row was written as an
empty string for a character parameter and made the client pass a length
of -1 to memcpy for a binary one, while a NULL in the first row made
every row NULL.

Apply both offsets to the copy of the buffer first and test that copy, so
that the indicator is read at the same offset as the value. Also treat a
negative length of a binary parameter as NULL instead of passing it on as
an array length.
singhpratech added a commit to singhpratech/adbcbridge that referenced this pull request Sep 24, 2026
) (#144)

* docs: link omniload's actual release tag, and the install extra

The README pointed at omniload's generic releases page. It now links
v0.17.0, which is the release that first carried the adbcbridge backend:
our merge commit is dated 2026-09-17T13:57:29Z and the v0.17.0 release
commit 14:01:26Z, four minutes later, with the extra present in that
tag's pyproject and absent from v0.16.0's.

Adds the install line, since that is what a reader wants. PyPI's metadata
for the published package declares the extra as adbcbridge>=0.1.3,<0.2,
and omniload's own full and test extras pull it in.

* scripts: a version inside a link belongs to the project the link points at

The README's new omniload line names our own backend and cites omniload's
release tag on the same line, so looks_like_ours() read v0.17.0 as an
adbcbridge version and the version-agreement check failed.

A markdown link now decides on its own: a version inside [text](url) is ours
only when the url is. A stale version of ours in a link to our own repo is
still caught, and another project's version in a link to theirs is not.

* docs: Ignite ODBC parameter-array fix sent upstream (apache/ignite#13610)

The fix prepared on 2026-09-18 is now a pull request. It had been held for
the ASF JIRA ticket Ignite asks for; the ticket still does not exist, since
self-signup on issues.apache.org is closed and contributor access has to be
requested on dev@ignite.apache.org. Rather than invent an IGNITE-NNNNN
number, the pull request states the position openly and offers to be
retitled or reopened once the ticket is filed.

The issue has had no reply since 2026-08-29.
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