Skip to content

Commit 74027bc

Browse files
authored
Merge pull request #588 from singhpratech/param-array-processes-every-set
Process every parameter set during SQLExecute
2 parents 703638b + c6a9cf8 commit 74027bc

3 files changed

Lines changed: 137 additions & 10 deletions

File tree

‎driver/statement.cpp‎

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,38 @@ void Statement::executeQuery(std::unique_ptr<ResultMutator> && mutator) {
7272
*param_set_processed_ptr = 0;
7373

7474
next_param_set_idx = 0;
75+
76+
const auto param_set_array_size =
77+
getEffectiveDescriptor(SQL_ATTR_APP_PARAM_DESC).getAttrAs<SQLULEN>(SQL_DESC_ARRAY_SIZE, 1);
78+
79+
// A set the driver never reaches is SQL_PARAM_UNUSED; every set that is sent overwrites its
80+
// own entry in requestNextPackOfResultSets.
81+
if (auto * param_status_ptr = getEffectiveDescriptor(SQL_ATTR_IMP_PARAM_DESC)
82+
.getAttrAs<SQLUSMALLINT *>(SQL_DESC_ARRAY_STATUS_PTR, 0)) {
83+
for (SQLULEN i = 0; i < param_set_array_size; ++i)
84+
param_status_ptr[i] = SQL_PARAM_UNUSED;
85+
}
86+
7587
requestNextPackOfResultSets(std::move(mutator));
88+
89+
// ODBC executes the statement once per parameter set during SQLExecute; SQLMoreResults then
90+
// walks the result sets those executions produced. A statement that returns nothing produces
91+
// none, so its remaining sets have to be sent here rather than left to SQLMoreResults. Stop
92+
// as soon as a set produces a result set, so a result-returning statement keeps handing them
93+
// back one per SQLMoreResults.
94+
if (!hasResultSet()) {
95+
while (next_param_set_idx < param_set_array_size) {
96+
std::unique_ptr<ResultMutator> carried_mutator;
97+
if (result_reader)
98+
carried_mutator = result_reader->releaseMutator();
99+
100+
requestNextPackOfResultSets(std::move(carried_mutator));
101+
102+
if (hasResultSet())
103+
break;
104+
}
105+
}
106+
76107
is_executed = true;
77108
}
78109

@@ -122,6 +153,30 @@ void Statement::requestNextPackOfResultSets(std::unique_ptr<ResultMutator> && mu
122153
if (next_param_set_idx >= param_set_array_size)
123154
return;
124155

156+
const auto param_set_idx = next_param_set_idx;
157+
auto & ipd_desc = getEffectiveDescriptor(SQL_ATTR_IMP_PARAM_DESC);
158+
auto * param_status_ptr = ipd_desc.getAttrAs<SQLUSMALLINT *>(SQL_DESC_ARRAY_STATUS_PTR, 0);
159+
160+
// SQL_ATTR_PARAMS_PROCESSED_PTR counts the sets processed including one that fails, so it is
161+
// written before the attempt rather than after it.
162+
// TODO: set this only after this single query is fully fetched (when output parameter support is added)
163+
if (auto * processed_ptr = ipd_desc.getAttrAs<SQLULEN *>(SQL_DESC_ROWS_PROCESSED_PTR, 0))
164+
*processed_ptr = param_set_idx + 1;
165+
166+
try {
167+
sendParamSet(std::move(mutator));
168+
}
169+
catch (...) {
170+
if (param_status_ptr)
171+
param_status_ptr[param_set_idx] = SQL_PARAM_ERROR;
172+
throw;
173+
}
174+
175+
if (param_status_ptr)
176+
param_status_ptr[param_set_idx] = SQL_PARAM_SUCCESS;
177+
}
178+
179+
void Statement::sendParamSet(std::unique_ptr<ResultMutator> && mutator) {
125180
getDiagHeader().setAttr(SQL_DIAG_ROW_COUNT, -1);
126181

127182
auto & connection = getParent();
@@ -135,11 +190,6 @@ void Statement::requestNextPackOfResultSets(std::unique_ptr<ResultMutator> && mu
135190
uri.addQueryParameter(key, value);
136191
}
137192

138-
// TODO: set this only after this single query is fully fetched (when output parameter support is added)
139-
auto * param_set_processed_ptr = getEffectiveDescriptor(SQL_ATTR_IMP_PARAM_DESC).getAttrAs<SQLULEN *>(SQL_DESC_ROWS_PROCESSED_PTR, 0);
140-
if (param_set_processed_ptr)
141-
*param_set_processed_ptr = next_param_set_idx;
142-
143193
Poco::Net::HTTPRequest request;
144194
request.setMethod(Poco::Net::HTTPRequest::HTTP_POST);
145195
request.setVersion(Poco::Net::HTTPRequest::HTTP_1_1);
@@ -489,8 +539,6 @@ std::vector<ParamBindingInfo> Statement::getParamsBindingInfo(std::size_t param_
489539
if (fully_bound_param_count > 0)
490540
param_bindings.reserve(fully_bound_param_count);
491541

492-
auto * array_status_ptr = ipd_desc.getAttrAs<SQLUSMALLINT *>(SQL_DESC_ARRAY_STATUS_PTR, 0);
493-
494542
const auto bind_type = apd_desc.getAttrAs<SQLULEN>(SQL_DESC_BIND_TYPE, SQL_PARAM_BIND_TYPE_DEFAULT);
495543
const auto * bind_offset_ptr = apd_desc.getAttrAs<SQLULEN *>(SQL_DESC_BIND_OFFSET_PTR, 0);
496544
const auto bind_offset = (bind_offset_ptr ? *bind_offset_ptr : 0);
@@ -531,9 +579,6 @@ std::vector<ParamBindingInfo> Statement::getParamsBindingInfo(std::size_t param_
531579
param_bindings.emplace_back(binding_info);
532580
}
533581

534-
if (array_status_ptr)
535-
array_status_ptr[param_set_idx] = SQL_PARAM_SUCCESS; // TODO: elaborate?
536-
537582
return param_bindings;
538583
}
539584

‎driver/statement.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class Statement
9999

100100
private:
101101
void requestNextPackOfResultSets(std::unique_ptr<ResultMutator> && mutator);
102+
void sendParamSet(std::unique_ptr<ResultMutator> && mutator);
102103

103104
/// Drops the keep-alive HTTP connection unless the previous response body was
104105
/// fully and cleanly consumed.

‎driver/test/statement_parameter_bindings_it.cpp‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,3 +562,84 @@ TEST_P(StringParameterBindingTest, SpecialCharactersRoundTrip)
562562

563563
STMT_OK(SQLFreeStmt(hstmt, SQL_CLOSE));
564564
}
565+
566+
TEST_F(StatementParameterBindingsTest, IntArrayInsertProcessesEverySet) {
567+
auto create_query = fromUTF8<PTChar>(
568+
"CREATE OR REPLACE TABLE param_array_insert (i Int32) engine MergeTree order by i");
569+
STMT_OK(SQLExecDirect(hstmt, ptcharCast(create_query.data()), SQL_NTS));
570+
STMT_OK(SQLFreeStmt(hstmt, SQL_CLOSE));
571+
572+
SQLINTEGER param[] = { 10, 20, 30, 40, 50 };
573+
SQLLEN param_ind[] = { 0, 0, 0, 0, 0 };
574+
SQLULEN params_processed = 0;
575+
576+
auto insert_query = fromUTF8<PTChar>("INSERT INTO param_array_insert (i) VALUES (?)");
577+
STMT_OK(SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)lengthof(param), 0));
578+
STMT_OK(SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &params_processed, 0));
579+
STMT_OK(SQLPrepare(hstmt, ptcharCast(insert_query.data()), SQL_NTS));
580+
STMT_OK(SQLBindParameter(
581+
hstmt,
582+
1,
583+
SQL_PARAM_INPUT,
584+
getCTypeFor<std::decay_t<decltype(param[0])>>(),
585+
SQL_INTEGER,
586+
0,
587+
0,
588+
param,
589+
0,
590+
param_ind));
591+
592+
// An INSERT returns no result sets, so nothing would prompt a caller to call SQLMoreResults:
593+
// this one call has to process the whole array.
594+
STMT_OK(SQLExecute(hstmt));
595+
ASSERT_EQ(params_processed, lengthof(param));
596+
STMT_OK(SQLFreeStmt(hstmt, SQL_CLOSE));
597+
598+
auto select_query = fromUTF8<PTChar>("SELECT i FROM param_array_insert ORDER BY i");
599+
STMT_OK(SQLExecDirect(hstmt, ptcharCast(select_query.data()), SQL_NTS));
600+
for (std::size_t i = 0; i < lengthof(param); ++i) {
601+
SQLINTEGER value = 0;
602+
STMT_OK(SQLFetch(hstmt));
603+
STMT_OK(SQLGetData(hstmt, 1, SQL_C_SLONG, &value, sizeof(value), nullptr));
604+
ASSERT_EQ(value, param[i]) << "row " << i;
605+
}
606+
ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA);
607+
STMT_OK(SQLFreeStmt(hstmt, SQL_CLOSE));
608+
}
609+
610+
TEST_F(StatementParameterBindingsTest, IntArrayInsertReportsTheFailingSet) {
611+
// copy_id is derived from id with accurateCast, so an id that does not fit Int16 makes the
612+
// server reject that parameter set and nothing after it is sent.
613+
auto create_query = fromUTF8<PTChar>(
614+
"CREATE OR REPLACE TABLE param_array_error "
615+
"(id Int32, value String, copy_id Int16 DEFAULT accurateCast(id, 'Int16')) "
616+
"engine MergeTree order by id");
617+
STMT_OK(SQLExecDirect(hstmt, ptcharCast(create_query.data()), SQL_NTS));
618+
STMT_OK(SQLFreeStmt(hstmt, SQL_CLOSE));
619+
620+
SQLINTEGER ids[] = { 1, 2, 40000, 4, 5 };
621+
SQLLEN id_ind[] = { 0, 0, 0, 0, 0 };
622+
char values[][2] = { "a", "b", "c", "d", "e" };
623+
SQLLEN value_ind[] = { SQL_NTS, SQL_NTS, SQL_NTS, SQL_NTS, SQL_NTS };
624+
SQLULEN params_processed = 0;
625+
SQLUSMALLINT param_status[lengthof(ids)] = {};
626+
627+
auto insert_query = fromUTF8<PTChar>("INSERT INTO param_array_error (id, value) VALUES (?, ?)");
628+
STMT_OK(SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)lengthof(ids), 0));
629+
STMT_OK(SQLSetStmtAttr(hstmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &params_processed, 0));
630+
STMT_OK(SQLSetStmtAttr(hstmt, SQL_ATTR_PARAM_STATUS_PTR, param_status, 0));
631+
STMT_OK(SQLPrepare(hstmt, ptcharCast(insert_query.data()), SQL_NTS));
632+
STMT_OK(SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, ids, 0, id_ind));
633+
STMT_OK(SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 255, 0, values, sizeof(values[0]), value_ind));
634+
635+
ASSERT_EQ(SQLExecute(hstmt), SQL_ERROR);
636+
637+
// The count includes the set that failed, and the status array says which one.
638+
ASSERT_EQ(params_processed, 3u);
639+
ASSERT_EQ(param_status[0], SQL_PARAM_SUCCESS);
640+
ASSERT_EQ(param_status[1], SQL_PARAM_SUCCESS);
641+
ASSERT_EQ(param_status[2], SQL_PARAM_ERROR);
642+
ASSERT_EQ(param_status[3], SQL_PARAM_UNUSED);
643+
ASSERT_EQ(param_status[4], SQL_PARAM_UNUSED);
644+
STMT_OK(SQLFreeStmt(hstmt, SQL_CLOSE));
645+
}

0 commit comments

Comments
 (0)