Repository navigation
Process every parameter set during SQLExecute - #588
Conversation
A parameter array stopped after the first set on a statement that returns nothing. SQLExecute sent set 0 and left the rest to SQLMoreResults, which the driver uses to send one set per call; but an INSERT produces no result sets, so a conforming caller has no reason to call SQLMoreResults at all and the remaining sets were never sent. A five-set array inserted one row, under SQL_SUCCESS and with no diagnostic. ODBC has the driver execute the statement once per parameter set during SQLExecute; SQLMoreResults then walks the result sets those executions produced. executeQuery now keeps sending sets while the last one produced no result set, and stops as soon as one does, so the one-result-set-per- SQLMoreResults behaviour a SELECT with a parameter array relies on is unchanged. SQL_ATTR_PARAMS_PROCESSED_PTR was also assigned next_param_set_idx before the set was sent, while that index still named the set about to go, so it reported one less than the number processed: 0 after a five-set INSERT and 2 after a three-set SELECT. It is now written after the send, where the index equals the count completed. The existing TODO about output parameters is kept. Verified against ClickHouse 26.7.5.10 with two standalone ODBC programs: - INSERT, 5 sets: 1 row and params_processed=0 before, 5 and 5 after. - SELECT ? , 3 sets: 3 result sets in order both before and after, with params_processed going from 2 to 3. The four tests from ClickHouse#586 also still pass on this build.
slabko
left a comment
There was a problem hiding this comment.
Hi @singhpratech,
Thank you for your PR. This is indeed a pretty common case.
I have a couple of remarks here:
- The comments in the code are very specific to this PR and its example. After this is merged, the comment in
Statement::executeQuerywill make little sense without the context of this PR. I think it can be much shorter and doesn't require that much justification. Similarly, the comment inStatement::requestNextPackOfResultSetsmentions the five-set array, which is hard to follow without example in this PR. - Also, would you mind adding a simple test case?
Just a heads-up: I merged master into your branch to allow the CI to pass for external contributions.
|
After some testing and reading the documentation, I think this PR requires a couple of additional fixes around error handling. Here I quite https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/using-arrays-of-parameters?view=sql-server-ver17 section Error Processing.
The CH ODBC driver indeed does not continue when it faces an error, which is permitted. However it must set the
This, I think, one key difference from the current implementation, because if an error occurs on the row 2 (the row numbers start from 1, not 0 - the ODBC style), the Additionally, the same page https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlsetstmtattr-function?view=sql-server-ver17 describes
As I understand, if I have a five parameter sets and the second parameter set fails, To test it I crated a table that permits inserting Now, the following query will fail but only if the first parameter in the parameter set is grater than Binding these parameters will cause an error on the parameter set 3: The result will be: |
Review feedback on the first version. Two things ODBC requires on the error
path that it did not do:
SQL_ATTR_PARAMS_PROCESSED_PTR counts the sets processed including the one
that fails, so it is now written before each attempt rather than after it: an
error on the third of five sets leaves it at 3.
SQL_ATTR_PARAM_STATUS_PTR has to say which set failed. getParamsBindingInfo
marked every set SQL_PARAM_SUCCESS at binding time, before the server had seen
it, so five sets always read as five successes. The array now starts as
SQL_PARAM_UNUSED and each set overwrites its own entry once the server has
answered: SQL_PARAM_SUCCESS, or SQL_PARAM_ERROR when the request throws. That
bookkeeping lives in requestNextPackOfResultSets, around a sendParamSet() split
off from the HTTP send, so the sets that SQLMoreResults sends are counted the
same way as the ones SQLExecute sends.
The comments are cut down to the invariant.
Two tests: a five-set INSERT that must land five rows, and the reviewer's
case, a table whose copy_id is accurateCast(id, 'Int16') so that
{1, 2, 40000, 4, 5} fails on the third set and must report
processed = 3 and {SUCCESS, SUCCESS, ERROR, UNUSED, UNUSED}.
|
Thank you — you were right on both counts, and the error-path spec quotes were exactly what I needed. Pushed as c6a9cf8: the comments are cut down to the invariant, and there are two tests, the second being your The all- One honest note on verification: I could only build the test suite under GCC with local workarounds for the in-class |
|
Thank you for the review and the merge, slabko. The error-path questions made this a better change than the one I opened with, and the |
The second half of #582, as offered on #586.
A parameter array stops after the first set on a statement that returns nothing.
SQLExecutesends set 0 and leaves the rest toSQLMoreResults, which this driver uses to send one set per call — but anINSERTproduces no result sets, so a conforming caller has no reason to callSQLMoreResultsat all, and the remaining sets are never sent. A five-set array inserts one row, underSQL_SUCCESS, with no diagnostic.The change
ODBC has the driver execute the statement once per parameter set during
SQLExecute;SQLMoreResultsthen walks the result sets those executions produced.executeQuerynow keeps sending sets while the last one produced no result set, and stops as soon as one does — so the one-result-set-per-SQLMoreResultsbehaviour that aSELECTwith a parameter array relies on (the path #324/#325 built, and whatStatementParameterBindingsTest.IntArray/StringArraycover) is unchanged.Second, smaller thing in the same area:
SQL_ATTR_PARAMS_PROCESSED_PTRwas assignednext_param_set_idxbefore the set was sent, while that index still named the set about to go — so it reported one fewer than the number processed. It is now written after the send, where the index equals the count completed. YourTODOabout output parameters is kept as it was. Happy to split that into its own commit or drop it if you would rather keep this to one thing.Verified against a live server
ClickHouse 26.7.5.10, two standalone ODBC programs, driver built from this branch:
INSERT, 5 setsparams_processed=0params_processed=5SELECT ?, 3 setsparams_processed=2params_processed=3The second row is the regression guard: the values come back
111, 222, 333in order across threeSQLMoreResultscalls on both builds, so lazy sending for result-producing statements still works.The four tests from #586 also still pass on this build — the reproducer from #582 part 1 reports 0 problems here against 5 on the released 1.5.5.20260810.
One caveat about my local build
I could not run the gtest suite: this machine has no Clang, and with GCC
driver/test/result_set_reader.hppandscalar_functions_it.cppfail with explicit specialization in non-namespace scope, which Clang accepts as an extension. That is pre-existing and unrelated to this change — the driver itself builds and the standalone programs above exercise the behaviour end to end — but it does mean CI is the first place the existing tests will run against this. Two local, uncommitted tweaks were needed to build at all under GCC (skipping the bundled libc++, and a missing<memory>include in the vendored Poco); neither is in this pull request.Found through adbcBridge (https://github.com/singhpratech/adbcbridge), a driver for ADBC — Apache Arrow's database connectivity API — that works over any ODBC driver, where a bulk insert is a parameter array and losing four rows in five is silent data loss.