From 4d34d3be5ffe965f7fc2f8dd11d845bc1dd8cde8 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Tue, 16 Jun 2020 21:39:29 +0400 Subject: [PATCH 01/16] WIP --- README.md | 4 +- driver/api/impl/impl.cpp | 69 ++++++++++++++++------ driver/api/impl/impl.h | 32 +++++++--- driver/api/odbc.cpp | 124 +++++++++++++++------------------------ driver/statement.cpp | 2 - driver/statement.h | 4 -- 6 files changed, 126 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 198d8c280..cca1af563 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ The general requirements for building the driver from sources are as follows: - C++17 and C11 capable compiler toolchain: - Clang 4 and later - GCC 7 and later - - Xcode 10 and later + - Xcode 10 and later (on macOS 10.14 and later) - Microsoft Visual Studio 2017 and later Additional requirements exist for each platform, which also depend on whether packaging and/or testing is performed. @@ -361,7 +361,7 @@ cmake --open . #### Build-time dependencies -You will need Xcode 10 or later and Command Line Tools to be installed, as well as [Homebrew](https://brew.sh/). +You will need macOS 10.14 or later, Xcode 10 or later with Command Line Tools installed, as well as up-to-date [Homebrew](https://brew.sh/) available in the system. #### Build-time dependencies: iODBC diff --git a/driver/api/impl/impl.cpp b/driver/api/impl/impl.cpp index f0c7ef911..67bfbd428 100644 --- a/driver/api/impl/impl.cpp +++ b/driver/api/impl/impl.cpp @@ -640,6 +640,21 @@ SQLRETURN GetDiagField( return CALL_WITH_TYPED_HANDLE_SKIP_DIAG(handle_type, handle, func); } +SQLRETURN BindCol( + SQLHSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, + SQLSMALLINT TargetType, + SQLPOINTER TargetValuePtr, + SQLLEN BufferLength, + SQLLEN * StrLen_or_Ind +) noexcept { + auto func = [&] (Statement & statement) { + return SQL_SUCCESS; + }; + + return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func); +} + SQLRETURN BindParameter( SQLHSTMT handle, SQLUSMALLINT parameter_number, @@ -1164,31 +1179,15 @@ SQLRETURN fillBinding( } return result_set.extractField(row_idx, column_idx, binding_info); -} -SQLRETURN GetData( - Statement & statement, - SQLUSMALLINT column_or_param_number, - const BindingInfo & binding_info -) { - if (!statement.hasResultSet()) - throw SqlException("Column info is not available", "07005"); - auto & result_set = statement.getResultSet(); - if (result_set.getCurrentRowPosition() < 1) - throw SqlException("Invalid cursor state", "24000"); - if (column_or_param_number < 1) - throw SqlException("Invalid descriptor index", "07009"); - const auto column_idx = column_or_param_number - 1; - const auto row_idx = result_set.getCurrentRowPosition() - result_set.getCurrentRowSetPosition(); - return fillBinding(statement, result_set, row_idx, column_idx, binding_info); } -SQLRETURN FetchScroll( +SQLRETURN fetchBindings( Statement & statement, SQLSMALLINT orientation, SQLLEN offset @@ -1236,4 +1235,40 @@ SQLRETURN FetchScroll( return res; } +SQLRETURN GetData( + SQLHSTMT StatementHandle, + SQLUSMALLINT Col_or_Param_Num, + SQLSMALLINT TargetType, + SQLPOINTER TargetValuePtr, + SQLLEN BufferLength, + SQLLEN * StrLen_or_IndPtr +) noexcept { + auto func = [&] (Statement & statement) { + }; + + return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func); +} + +SQLRETURN Fetch( + SQLHSTMT StatementHandle +) noexcept { + auto func = [&] (Statement & statement) { + return fetchBindings(statement, SQL_FETCH_NEXT, 0); + }; + + return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func); +} + +SQLRETURN FetchScroll( + SQLHSTMT StatementHandle, + SQLSMALLINT FetchOrientation, + SQLLEN FetchOffset +) noexcept { + auto func = [&] (Statement & statement) { + return fetchBindings(statement, FetchOrientation, FetchOffset); + }; + + return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func); +} + } // namespace impl diff --git a/driver/api/impl/impl.h b/driver/api/impl/impl.h index c8430fb24..8d5eb5e70 100644 --- a/driver/api/impl/impl.h +++ b/driver/api/impl/impl.h @@ -94,6 +94,15 @@ namespace impl { SQLSMALLINT * out_message_size ) noexcept; + SQLRETURN BindCol( + SQLHSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, + SQLSMALLINT TargetType, + SQLPOINTER TargetValuePtr, + SQLLEN BufferLength, + SQLLEN * StrLen_or_Ind + ) noexcept; + SQLRETURN BindParameter( SQLHSTMT handle, SQLUSMALLINT parameter_number, @@ -177,15 +186,22 @@ namespace impl { ) noexcept; SQLRETURN GetData( - Statement & statement, - SQLUSMALLINT column_or_param_number, - const BindingInfo & binding_info - ); + SQLHSTMT StatementHandle, + SQLUSMALLINT Col_or_Param_Num, + SQLSMALLINT TargetType, + SQLPOINTER TargetValuePtr, + SQLLEN BufferLength, + SQLLEN * StrLen_or_IndPtr + ) noexcept; + + SQLRETURN Fetch( + SQLHSTMT StatementHandle + ) noexcept; SQLRETURN FetchScroll( - Statement & statement, - SQLSMALLINT orientation, - SQLLEN offset - ); + SQLHSTMT StatementHandle, + SQLSMALLINT FetchOrientation, + SQLLEN FetchOffset + ) noexcept; } // namespace impl diff --git a/driver/api/odbc.cpp b/driver/api/odbc.cpp index 7cca74f88..df6fa9b5d 100755 --- a/driver/api/odbc.cpp +++ b/driver/api/odbc.cpp @@ -700,92 +700,64 @@ SQLRETURN SQL_API EXPORTED_FUNCTION_MAYBE_W(SQLDescribeCol)(HSTMT statement_hand return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, statement_handle, func); } -SQLRETURN SQL_API EXPORTED_FUNCTION(SQLFetch)(HSTMT statement_handle) { - auto func = [&] (Statement & statement) { - return impl::FetchScroll(statement, SQL_FETCH_NEXT, 0); - }; - - return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, statement_handle, func); +SQLRETURN SQL_API EXPORTED_FUNCTION(SQLFetch)( + SQLHSTMT StatementHandle +) { + LOG(__FUNCTION__); + return impl::Fetch( + StatementHandle + ); } -SQLRETURN SQL_API EXPORTED_FUNCTION(SQLFetchScroll)(HSTMT statement_handle, SQLSMALLINT orientation, SQLLEN offset) { - auto func = [&] (Statement & statement) { - return impl::FetchScroll(statement, orientation, offset); - }; - - return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, statement_handle, func); +SQLRETURN SQL_API EXPORTED_FUNCTION(SQLFetchScroll)( + SQLHSTMT StatementHandle, + SQLSMALLINT FetchOrientation, + SQLLEN FetchOffset +) { + LOG(__FUNCTION__); + return impl::FetchScroll( + StatementHandle, + FetchOrientation, + FetchOffset + ); } SQLRETURN SQL_API EXPORTED_FUNCTION(SQLGetData)( - HSTMT statement_handle, - SQLUSMALLINT column_or_param_number, - SQLSMALLINT target_type, - PTR out_value, - SQLLEN out_value_max_size, - SQLLEN * out_value_size_or_indicator + SQLHSTMT StatementHandle, + SQLUSMALLINT Col_or_Param_Num, + SQLSMALLINT TargetType, + SQLPOINTER TargetValuePtr, + SQLLEN BufferLength, + SQLLEN * StrLen_or_IndPtr ) { - auto func = [&] (Statement & statement) { - BindingInfo binding_info; - binding_info.c_type = target_type; - binding_info.value = out_value; - binding_info.value_max_size = out_value_max_size; - binding_info.value_size = out_value_size_or_indicator; - binding_info.indicator = out_value_size_or_indicator; - - return impl::GetData( - statement, - column_or_param_number, - binding_info - ); - }; - - return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, statement_handle, func); + LOG(__FUNCTION__); + return impl::GetData( + StatementHandle, + Col_or_Param_Num, + TargetType, + TargetValuePtr, + BufferLength, + StrLen_or_IndPtr + ); } SQLRETURN SQL_API EXPORTED_FUNCTION(SQLBindCol)( - HSTMT statement_handle, - SQLUSMALLINT column_number, - SQLSMALLINT target_type, - PTR out_value, - SQLLEN out_value_max_size, - SQLLEN * out_value_size_or_indicator + SQLHSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, + SQLSMALLINT TargetType, + SQLPOINTER TargetValuePtr, + SQLLEN BufferLength, + SQLLEN * StrLen_or_Ind ) { - auto func = [&] (Statement & statement) { - if (out_value_max_size < 0) - throw SqlException("Invalid string or buffer length", "HY090"); - - if (!statement.hasResultSet()) - throw SqlException("Column info is not available", "07005"); - - auto & result_set = statement.getResultSet(); - - if (column_number < 1) - throw SqlException("Invalid descriptor index", "07009"); - - // Unbinding column - if (out_value_size_or_indicator == nullptr) { - statement.bindings.erase(column_number); - return SQL_SUCCESS; - } - - const auto column_idx = column_number - 1; - - if (target_type == SQL_C_DEFAULT) - target_type = statement.getTypeInfo(result_set.getColumnInfo(column_idx).type_without_parameters).sql_type; - - BindingInfo binding; - binding.c_type = target_type; - binding.value = out_value; - binding.value_max_size = out_value_max_size; - binding.value_size = out_value_size_or_indicator; - binding.indicator = out_value_size_or_indicator; - - statement.bindings[column_number] = binding; - - return SQL_SUCCESS; - }; - - return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, statement_handle, func); + LOG(__FUNCTION__); + return impl::BindCol( + StatementHandle, + ColumnNumber, + TargetType, + TargetValuePtr, + BufferLength, + StrLen_or_Ind + ); } SQLRETURN SQL_API EXPORTED_FUNCTION(SQLRowCount)(HSTMT statement_handle, SQLLEN * out_row_count) { diff --git a/driver/statement.cpp b/driver/statement.cpp index 26261fd60..9ae69b67b 100644 --- a/driver/statement.cpp +++ b/driver/statement.cpp @@ -397,8 +397,6 @@ void Statement::closeCursor() { } void Statement::resetColBindings() { - bindings.clear(); - getEffectiveDescriptor(SQL_ATTR_APP_ROW_DESC).setAttr(SQL_DESC_COUNT, 0); } diff --git a/driver/statement.h b/driver/statement.h index 80874b933..6a9f6b84c 100644 --- a/driver/statement.h +++ b/driver/statement.h @@ -106,8 +106,4 @@ class Statement std::istream* in = nullptr; std::unique_ptr result_reader; std::size_t next_param_set = 0; - -public: - // TODO: switch to using the corresponding descriptor attributes. - std::map bindings; }; From 9f65b9a203d1d74087c596caa44f4156a8cd1148 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Mon, 22 Jun 2020 01:56:12 +0400 Subject: [PATCH 02/16] WIP Fix/rework column vs row based binding of parameters Implement column vs row based binding of columns Handle SQL_DESC_ARRAY_SIZE properly Fix record count management when it comes to unbind parameters or columns Other small fixes/clarifications --- driver/api/impl/impl.cpp | 275 ++++++++++++++++++++++++++++++++------- driver/descriptor.cpp | 6 +- driver/descriptor.h | 2 + driver/statement.cpp | 45 ++++--- driver/statement.h | 2 +- driver/utils/type_info.h | 14 +- 6 files changed, 276 insertions(+), 68 deletions(-) diff --git a/driver/api/impl/impl.cpp b/driver/api/impl/impl.cpp index 67bfbd428..badec135c 100644 --- a/driver/api/impl/impl.cpp +++ b/driver/api/impl/impl.cpp @@ -377,6 +377,7 @@ SQLRETURN SetStmtAttr( CASE_SET_IN_DESC(SQL_ATTR_PARAM_STATUS_PTR, SQL_ATTR_IMP_PARAM_DESC, SQL_DESC_ARRAY_STATUS_PTR, SQLUSMALLINT *); CASE_SET_IN_DESC(SQL_ATTR_PARAMS_PROCESSED_PTR, SQL_ATTR_IMP_PARAM_DESC, SQL_DESC_ROWS_PROCESSED_PTR, SQLULEN *); CASE_SET_IN_DESC(SQL_ATTR_PARAMSET_SIZE, SQL_ATTR_APP_PARAM_DESC, SQL_DESC_ARRAY_SIZE, SQLULEN); + CASE_SET_IN_DESC(SQL_ATTR_ROW_ARRAY_SIZE, SQL_ATTR_APP_ROW_DESC, SQL_DESC_ARRAY_SIZE, SQLULEN); CASE_SET_IN_DESC(SQL_ATTR_ROW_BIND_OFFSET_PTR, SQL_ATTR_APP_ROW_DESC, SQL_DESC_BIND_OFFSET_PTR, SQLULEN *); CASE_SET_IN_DESC(SQL_ATTR_ROW_BIND_TYPE, SQL_ATTR_APP_ROW_DESC, SQL_DESC_BIND_TYPE, SQLULEN); CASE_SET_IN_DESC(SQL_ATTR_ROW_OPERATION_PTR, SQL_ATTR_APP_ROW_DESC, SQL_DESC_ARRAY_STATUS_PTR, SQLUSMALLINT *); @@ -385,15 +386,6 @@ SQLRETURN SetStmtAttr( #undef CASE_SET_IN_DESC - case SQL_ATTR_ROW_ARRAY_SIZE: { - // TODO: implement arbitrary array size. Currently only the default (1) is supported. - if (reinterpret_cast(value) != 1) - throw SqlException("Option value changed", "01S02", SQL_SUCCESS_WITH_INFO); - - statement.getEffectiveDescriptor(SQL_ATTR_APP_ROW_DESC).setAttr(SQL_DESC_ARRAY_SIZE, reinterpret_cast(value)); - return SQL_SUCCESS; - } - case SQL_ATTR_NOSCAN: statement.setAttr(SQL_ATTR_NOSCAN, value); return SQL_SUCCESS; @@ -649,6 +641,69 @@ SQLRETURN BindCol( SQLLEN * StrLen_or_Ind ) noexcept { auto func = [&] (Statement & statement) { + if (ColumnNumber < 1) + throw SqlException("Invalid descriptor index", "07009"); + + if (BufferLength < 0) + throw SqlException("Invalid string or buffer length", "HY090"); + + auto & ard_desc = statement.getEffectiveDescriptor(SQL_ATTR_APP_ROW_DESC); + const auto ard_record_count = ard_desc.getRecordCount(); + + // If TargetValuePtr and StrLen_or_Ind pointers are both NULL, then unbind the column, + // and if the column number is [greater than or] equal to the highest bound column number, + // then adjust SQL_DESC_COUNT to reflect that the highest bound column has chaged. + if ( + TargetValuePtr == nullptr && + StrLen_or_Ind == nullptr && + ColumnNumber >= ard_record_count + ) { + auto nextHighestBoundColumnNumber = std::min(ard_record_count, ColumnNumber - 1); + + while (nextHighestBoundColumnNumber > 0) { + auto & ard_record = ard_desc.getRecord(nextHighestBoundColumnNumber, SQL_ATTR_APP_ROW_DESC); + + if ( + ard_record.getAttrAs(SQL_DESC_DATA_PTR, 0) != nullptr || + ard_record.getAttrAs(SQL_DESC_OCTET_LENGTH_PTR, 0) != nullptr || + ard_record.getAttrAs(SQL_DESC_INDICATOR_PTR, 0) != nullptr + ) { + break; + } + + --nextHighestBoundColumnNumber; + } + + ard_desc.setAttr(SQL_DESC_COUNT, nextHighestBoundColumnNumber); + return SQL_SUCCESS; + } + + auto & ard_record = ard_desc.getRecord(ColumnNumber, SQL_ATTR_APP_ROW_DESC); + + try { + // This will trigger automatic (re)setting of SQL_DESC_TYPE and SQL_DESC_DATETIME_INTERVAL_CODE, + // and resetting of SQL_DESC_DATA_PTR. + ard_record.setAttr(SQL_DESC_CONCISE_TYPE, TargetType); + + switch (TargetType) { + case SQL_C_CHAR: + case SQL_C_WCHAR: + case SQL_C_BINARY: +// case SQL_C_VARBOOKMARK: + ard_record.setAttr(SQL_DESC_LENGTH, BufferLength); + break; + } + + ard_record.setAttr(SQL_DESC_OCTET_LENGTH, BufferLength); + ard_record.setAttr(SQL_DESC_OCTET_LENGTH_PTR, StrLen_or_Ind); + ard_record.setAttr(SQL_DESC_INDICATOR_PTR, StrLen_or_Ind); + ard_record.setAttr(SQL_DESC_DATA_PTR, TargetValuePtr); + } + catch (...) { + ard_desc.setAttr(SQL_DESC_COUNT, ard_record_count); + throw; + } + return SQL_SUCCESS; }; @@ -674,6 +729,34 @@ SQLRETURN BindParameter( const auto apd_record_count = apd_desc.getRecordCount(); const auto ipd_record_count = ipd_desc.getRecordCount(); + // If parameter_value_ptr and StrLen_or_IndPtr pointers are both NULL, then unbind the parameter, + // and if the parameter number is [greater than or] equal to the highest bound parameter number, + // then adjust SQL_DESC_COUNT to reflect that the highest bound parameter has chaged. + if ( + parameter_value_ptr == nullptr && + StrLen_or_IndPtr == nullptr && + parameter_number >= apd_record_count + ) { + auto nextHighestBoundParameterNumber = std::min(apd_record_count, parameter_number - 1); + + while (nextHighestBoundParameterNumber > 0) { + auto & apd_record = apd_desc.getRecord(nextHighestBoundParameterNumber, SQL_ATTR_APP_PARAM_DESC); + + if ( + apd_record.getAttrAs(SQL_DESC_DATA_PTR, 0) != nullptr || + apd_record.getAttrAs(SQL_DESC_OCTET_LENGTH_PTR, 0) != nullptr || + apd_record.getAttrAs(SQL_DESC_INDICATOR_PTR, 0) != nullptr + ) { + break; + } + + --nextHighestBoundParameterNumber; + } + + apd_desc.setAttr(SQL_DESC_COUNT, nextHighestBoundParameterNumber); + return SQL_SUCCESS; + } + auto & apd_record = apd_desc.getRecord(parameter_number, SQL_ATTR_APP_PARAM_DESC); auto & ipd_record = ipd_desc.getRecord(parameter_number, SQL_ATTR_IMP_PARAM_DESC); @@ -825,13 +908,13 @@ SQLRETURN GetDescField( #define CASE_FIELD_NUM_DEF(NAME, TYPE, DEFAULT) \ case NAME: return fillOutputPOD(descriptor.getAttrAs(NAME, DEFAULT), ValuePtr, StringLengthPtr); - CASE_FIELD_NUM ( SQL_DESC_ALLOC_TYPE, SQLSMALLINT ); - CASE_FIELD_NUM_DEF ( SQL_DESC_ARRAY_SIZE, SQLULEN, 1 ); - CASE_FIELD_NUM ( SQL_DESC_ARRAY_STATUS_PTR, SQLUSMALLINT * ); - CASE_FIELD_NUM ( SQL_DESC_BIND_OFFSET_PTR, SQLLEN * ); - CASE_FIELD_NUM_DEF ( SQL_DESC_BIND_TYPE, SQLUINTEGER, SQL_BIND_BY_COLUMN ); - CASE_FIELD_NUM ( SQL_DESC_COUNT, SQLSMALLINT ); - CASE_FIELD_NUM ( SQL_DESC_ROWS_PROCESSED_PTR, SQLULEN * ); + CASE_FIELD_NUM ( SQL_DESC_ALLOC_TYPE, SQLSMALLINT ); + CASE_FIELD_NUM_DEF ( SQL_DESC_ARRAY_SIZE, SQLULEN, 1 ); + CASE_FIELD_NUM ( SQL_DESC_ARRAY_STATUS_PTR, SQLUSMALLINT * ); + CASE_FIELD_NUM ( SQL_DESC_BIND_OFFSET_PTR, SQLLEN * ); + CASE_FIELD_NUM_DEF ( SQL_DESC_BIND_TYPE, SQLUINTEGER, SQL_BIND_TYPE_DEFAULT ); + CASE_FIELD_NUM ( SQL_DESC_COUNT, SQLSMALLINT ); + CASE_FIELD_NUM ( SQL_DESC_ROWS_PROCESSED_PTR, SQLULEN * ); #undef CASE_FIELD_NUM_DEF #undef CASE_FIELD_NUM @@ -1142,16 +1225,11 @@ SQLRETURN fillBinding( std::size_t column_idx, BindingInfo binding_info ) { - - - // TODO: revisit the code, use descriptors for all cases, add support for row sets of size > 1. - - SQLINTEGER desc_type = SQL_ATTR_APP_ROW_DESC; // if (binding_info.c_type == SQL_APD_TYPE) { // desc_type = SQL_ATTR_APP_PARAM_DESC; -// throw SqlException("Unable to read parameter data using SQLGetData"); +// throw SqlException("Unable to read output parameter data"); // } if ( @@ -1166,6 +1244,10 @@ SQLRETURN fillBinding( binding_info.c_type = record.getAttrAs(SQL_DESC_CONCISE_TYPE, SQL_C_DEFAULT); } + if (binding_info.c_type == SQL_C_DEFAULT) { + binding_info.c_type = convertSQLTypeToCType(statement.getTypeInfo(result_set.getColumnInfo(column_idx).type_without_parameters).sql_type); + } + if ( binding_info.c_type == SQL_C_NUMERIC && binding_info.precision == 0 @@ -1179,12 +1261,6 @@ SQLRETURN fillBinding( } return result_set.extractField(row_idx, column_idx, binding_info); - - - - - - } SQLRETURN fetchBindings( @@ -1192,47 +1268,130 @@ SQLRETURN fetchBindings( SQLSMALLINT orientation, SQLLEN offset ) { - const auto row_set_size = statement.getEffectiveDescriptor(SQL_ATTR_APP_ROW_DESC).getAttrAs(SQL_DESC_ARRAY_SIZE, 1); - auto * rows_fetched_ptr = statement.getEffectiveDescriptor(SQL_ATTR_IMP_ROW_DESC).getAttrAs(SQL_DESC_ROWS_PROCESSED_PTR, 0); + auto & ard_desc = statement.getEffectiveDescriptor(SQL_ATTR_APP_ROW_DESC); + auto & ird_desc = statement.getEffectiveDescriptor(SQL_ATTR_IMP_ROW_DESC); + + const auto row_set_size = ard_desc.getAttrAs(SQL_DESC_ARRAY_SIZE, 1); + auto * rows_fetched_ptr = ird_desc.getAttrAs(SQL_DESC_ROWS_PROCESSED_PTR, 0); + auto * array_status_ptr = ird_desc.getAttrAs(SQL_DESC_ARRAY_STATUS_PTR, 0); if (rows_fetched_ptr) *rows_fetched_ptr = 0; - if (!statement.hasResultSet()) + if (!statement.hasResultSet() || row_set_size == 0) { + if (array_status_ptr) { + for (std::size_t row_idx = 0; row_idx < row_set_size; ++row_idx) { + array_status_ptr[row_idx] = SQL_ROW_NOROW; + } + } + return SQL_NO_DATA; + } auto & result_set = statement.getResultSet(); - const auto rows_fetched = result_set.fetchRowSet(orientation, offset, row_set_size); if (rows_fetched == 0) { statement.getDiagHeader().setAttr(SQL_DIAG_ROW_COUNT, result_set.getAffectedRowCount()); + + if (array_status_ptr) { + for (std::size_t row_idx = 0; row_idx < row_set_size; ++row_idx) { + array_status_ptr[row_idx] = SQL_ROW_NOROW; + } + } + return SQL_NO_DATA; } if (rows_fetched_ptr) *rows_fetched_ptr = rows_fetched; - auto res = SQL_SUCCESS; + const auto ard_record_count = ard_desc.getRecordCount(); + ard_desc.getRecord(ard_record_count, SQL_ATTR_APP_ROW_DESC); // ...just to make sure that record container is in sync with SQL_DESC_COUNT. + const auto & ard_records = ard_desc.getRecordContainer(); // ...only for faster access in a loop. - for (std::size_t i = 0; i < rows_fetched; ++i) { - for (auto & col_num_binding : statement.bindings) { - const auto code = fillBinding( - statement, - result_set, - i, - col_num_binding.first - 1, - col_num_binding.second - ); + const auto bind_type = ard_desc.getAttrAs(SQL_DESC_BIND_TYPE, SQL_BIND_TYPE_DEFAULT); + const auto * bind_offset_ptr = ard_desc.getAttrAs(SQL_DESC_BIND_OFFSET_PTR, 0); + const auto bind_offset = (bind_offset_ptr ? *bind_offset_ptr : 0); + + bool success_with_info_met = false; + std::size_t error_num = 0; - if (code == SQL_SUCCESS_WITH_INFO) - res = code; - else if (code != SQL_SUCCESS) - return code; + for (std::size_t row_idx = 0; row_idx < rows_fetched; ++row_idx) { + for (std::size_t column_num = 1; column_num <= ard_record_count; ++column_num) { // Skipping the bookmark (0) column. + const auto column_idx = column_num - 1; + const auto & ard_record = ard_records[column_num]; + + const auto * data_ptr = ard_record.getAttrAs(SQL_DESC_DATA_PTR, 0); + const auto * sz_ptr = ard_record.getAttrAs(SQL_DESC_OCTET_LENGTH_PTR, 0); + const auto * ind_ptr = ard_record.getAttrAs(SQL_DESC_INDICATOR_PTR, 0); + + SQLRETURN code = SQL_SUCCESS; + + if (data_ptr || sz_ptr || ind_ptr) { // Only if the column is bound... + BindingInfo binding_info; + binding_info.c_type = ard_record.getAttrAs(SQL_DESC_CONCISE_TYPE, SQL_C_DEFAULT); + binding_info.value_max_size = ard_record.getAttrAs(SQL_DESC_OCTET_LENGTH, 0); + + const auto next_value_ptr_increment = (bind_type == SQL_BIND_BY_COLUMN ? binding_info.value_max_size : bind_type); + const auto next_sz_ind_ptr_increment = (bind_type == SQL_BIND_BY_COLUMN ? sizeof(SQLLEN) : bind_type); + + binding_info.value = (void *)(data_ptr ? ((char *)(data_ptr) + row_idx * next_value_ptr_increment + bind_offset) : 0); + binding_info.value_size = (SQLLEN *)(sz_ptr ? ((char *)(sz_ptr) + row_idx * next_sz_ind_ptr_increment + bind_offset) : 0); + binding_info.indicator = (SQLLEN *)(ind_ptr ? ((char *)(ind_ptr) + row_idx * next_sz_ind_ptr_increment + bind_offset) : 0); + + // TODO: fill per-row and per-column diagnostics on (some soft?) errors. + const auto code = fillBinding( + statement, + result_set, + row_idx, + column_idx, + binding_info + ); + } + + switch (code) { + case SQL_SUCCESS: { + if (array_status_ptr) + array_status_ptr[row_idx] = SQL_ROW_SUCCESS; + + break; + } + + case SQL_SUCCESS_WITH_INFO: { + success_with_info_met = true; + + if (array_status_ptr) + array_status_ptr[row_idx] = SQL_ROW_SUCCESS_WITH_INFO; + + break; + } + + default: { + ++error_num; + + if (array_status_ptr) + array_status_ptr[row_idx] = SQL_ROW_ERROR; + + break; + } + } + } + } + + if (array_status_ptr) { + for (std::size_t row_idx = rows_fetched; row_idx < row_set_size; ++row_idx) { + array_status_ptr[row_idx] = SQL_ROW_NOROW; } } - return res; + if (error_num >= rows_fetched) + return SQL_ERROR; + + if (error_num > 0 || success_with_info_met) + return SQL_SUCCESS_WITH_INFO; + + return SQL_SUCCESS; } SQLRETURN GetData( @@ -1244,6 +1403,28 @@ SQLRETURN GetData( SQLLEN * StrLen_or_IndPtr ) noexcept { auto func = [&] (Statement & statement) { + if (!statement.hasResultSet()) + throw SqlException("Column info is not available", "07005"); + + auto & result_set = statement.getResultSet(); + + if (result_set.getCurrentRowPosition() < 1) + throw SqlException("Invalid cursor state", "24000"); + + if (Col_or_Param_Num < 1) + throw SqlException("Invalid descriptor index", "07009"); + + const auto row_idx = result_set.getCurrentRowPosition() - result_set.getCurrentRowSetPosition(); + const auto column_idx = Col_or_Param_Num - 1; + + BindingInfo binding_info; + binding_info.c_type = TargetType; + binding_info.value = TargetValuePtr; + binding_info.value_max_size = BufferLength; + binding_info.value_size = StrLen_or_IndPtr; + binding_info.indicator = StrLen_or_IndPtr; + + return fillBinding(statement, result_set, row_idx, column_idx, binding_info); }; return CALL_WITH_TYPED_HANDLE(SQL_HANDLE_STMT, StatementHandle, func); diff --git a/driver/descriptor.cpp b/driver/descriptor.cpp index d253b32fd..6bfa669f3 100644 --- a/driver/descriptor.cpp +++ b/driver/descriptor.cpp @@ -105,7 +105,7 @@ void DescriptorRecord::onAttrChange(int attr) { break; } case SQL_DESC_DATA_PTR: { - if (getAttrAs(SQL_DESC_DATA_PTR, nullptr)) + if (getAttrAs(SQL_DESC_DATA_PTR, 0)) consistencyCheck(); break; } @@ -222,3 +222,7 @@ DescriptorRecord & Descriptor::getRecord(std::size_t num, SQLINTEGER current_rol return records[num]; } + +const std::vector & Descriptor::getRecordContainer() const { + return records; +} diff --git a/driver/descriptor.h b/driver/descriptor.h index 3ca99df65..24ec93a25 100644 --- a/driver/descriptor.h +++ b/driver/descriptor.h @@ -35,6 +35,8 @@ class Descriptor std::size_t getRecordCount() const; DescriptorRecord& getRecord(std::size_t num, SQLINTEGER current_role); + const std::vector & getRecordContainer() const; + private: std::vector records; }; diff --git a/driver/statement.cpp b/driver/statement.cpp index 9ae69b67b..c23c65ed9 100644 --- a/driver/statement.cpp +++ b/driver/statement.cpp @@ -57,7 +57,7 @@ void Statement::executeQuery(std::unique_ptr && mutator) { if (param_set_processed_ptr) *param_set_processed_ptr = 0; - next_param_set = 0; + next_param_set_idx = 0; requestNextPackOfResultSets(std::move(mutator)); is_executed = true; } @@ -66,7 +66,7 @@ void Statement::requestNextPackOfResultSets(std::unique_ptr && mu result_reader.reset(); const auto param_set_array_size = getEffectiveDescriptor(SQL_ATTR_APP_PARAM_DESC).getAttrAs(SQL_DESC_ARRAY_SIZE, 1); - if (next_param_set >= param_set_array_size) + if (next_param_set_idx >= param_set_array_size) return; getDiagHeader().setAttr(SQL_DIAG_ROW_COUNT, 0); @@ -103,7 +103,7 @@ void Statement::requestNextPackOfResultSets(std::unique_ptr && mu if (!database_set) uri.addQueryParameter("database", connection.database); - const auto param_bindings = getParamsBindingInfo(next_param_set); + const auto param_bindings = getParamsBindingInfo(next_param_set_idx); for (std::size_t i = 0; i < parameters.size(); ++i) { std::string value; @@ -132,7 +132,7 @@ void Statement::requestNextPackOfResultSets(std::unique_ptr && mu // TODO: set this only after this single query is fully fetched (when output parameter support is added) auto * param_set_processed_ptr = getEffectiveDescriptor(SQL_ATTR_IMP_PARAM_DESC).getAttrAs(SQL_DESC_ROWS_PROCESSED_PTR, 0); if (param_set_processed_ptr) - *param_set_processed_ptr = next_param_set; + *param_set_processed_ptr = next_param_set_idx; Poco::Net::HTTPRequest request; request.setMethod(Poco::Net::HTTPRequest::HTTP_POST); @@ -190,7 +190,7 @@ void Statement::requestNextPackOfResultSets(std::unique_ptr && mu } result_reader = make_result_reader(response->get("X-ClickHouse-Format", connection.default_format), *in, std::move(mutator)); - ++next_param_set; + ++next_param_set_idx; } void Statement::processEscapeSequences() { @@ -205,7 +205,7 @@ void Statement::extractParametersinfo() { const auto apd_record_count = apd_desc.getRecordCount(); auto ipd_record_count = ipd_desc.getRecordCount(); - // Reset IPD records but preserve possible info set by SQLBindParameter. + // Reset IPD records but preserve those that may have been modified by SQLBindParameter and are still relevant. ipd_record_count = std::min(ipd_record_count, apd_record_count); ipd_desc.setAttr(SQL_DESC_COUNT, ipd_record_count); @@ -295,8 +295,9 @@ void Statement::extractParametersinfo() { } } - ipd_record_count = std::max(ipd_record_count, parameters.size()); - ipd_desc.setAttr(SQL_DESC_COUNT, ipd_record_count); + // Access the biggest record to [possibly] create all missing ones. + if (ipd_record_count < parameters.size()) + ipd_desc.getRecord(parameters.size(), SQL_ATTR_IMP_PARAM_DESC); } std::string Statement::buildFinalQuery(const std::vector& param_bindings) { @@ -435,27 +436,32 @@ std::vector Statement::getParamsBindingInfo(std::size_t param_ if (fully_bound_param_count > 0) param_bindings.reserve(fully_bound_param_count); - const auto single_set_struct_size = apd_desc.getAttrAs(SQL_DESC_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN); + auto * array_status_ptr = ipd_desc.getAttrAs(SQL_DESC_ARRAY_STATUS_PTR, 0); + + const auto bind_type = apd_desc.getAttrAs(SQL_DESC_BIND_TYPE, SQL_PARAM_BIND_TYPE_DEFAULT); const auto * bind_offset_ptr = apd_desc.getAttrAs(SQL_DESC_BIND_OFFSET_PTR, 0); const auto bind_offset = (bind_offset_ptr ? *bind_offset_ptr : 0); - for (std::size_t i = 1; i <= fully_bound_param_count; ++i) { - ParamBindingInfo binding_info; - - auto & apd_record = apd_desc.getRecord(i, SQL_ATTR_APP_PARAM_DESC); - auto & ipd_record = ipd_desc.getRecord(i, SQL_ATTR_IMP_PARAM_DESC); + for (std::size_t param_num = 1; param_num <= fully_bound_param_count; ++param_num) { + auto & apd_record = apd_desc.getRecord(param_num, SQL_ATTR_APP_PARAM_DESC); + auto & ipd_record = ipd_desc.getRecord(param_num, SQL_ATTR_IMP_PARAM_DESC); const auto * data_ptr = apd_record.getAttrAs(SQL_DESC_DATA_PTR, 0); const auto * sz_ptr = apd_record.getAttrAs(SQL_DESC_OCTET_LENGTH_PTR, 0); const auto * ind_ptr = apd_record.getAttrAs(SQL_DESC_INDICATOR_PTR, 0); + ParamBindingInfo binding_info; binding_info.io_type = ipd_record.getAttrAs(SQL_DESC_PARAMETER_TYPE, SQL_PARAM_INPUT); binding_info.c_type = apd_record.getAttrAs(SQL_DESC_CONCISE_TYPE, SQL_C_DEFAULT); binding_info.sql_type = ipd_record.getAttrAs(SQL_DESC_CONCISE_TYPE, SQL_UNKNOWN_TYPE); - binding_info.value_max_size = ipd_record.getAttrAs(SQL_DESC_LENGTH, 0); // TODO: or SQL_DESC_OCTET_LENGTH ? - binding_info.value = (void *)(data_ptr ? ((char *)(data_ptr) + param_set_idx * single_set_struct_size + bind_offset) : 0); - binding_info.value_size = (SQLLEN *)(sz_ptr ? ((char *)(sz_ptr) + param_set_idx * sizeof(SQLLEN) + bind_offset) : 0); - binding_info.indicator = (SQLLEN *)(ind_ptr ? ((char *)(ind_ptr) + param_set_idx * sizeof(SQLLEN) + bind_offset) : 0); + binding_info.value_max_size = ipd_record.getAttrAs(SQL_DESC_OCTET_LENGTH, 0); + + const auto next_value_ptr_increment = (bind_type == SQL_PARAM_BIND_BY_COLUMN ? binding_info.value_max_size : bind_type); + const auto next_sz_ind_ptr_increment = (bind_type == SQL_PARAM_BIND_BY_COLUMN ? sizeof(SQLLEN) : bind_type); + + binding_info.value = (void *)(data_ptr ? ((char *)(data_ptr) + param_set_idx * next_value_ptr_increment + bind_offset) : 0); + binding_info.value_size = (SQLLEN *)(sz_ptr ? ((char *)(sz_ptr) + param_set_idx * next_sz_ind_ptr_increment + bind_offset) : 0); + binding_info.indicator = (SQLLEN *)(ind_ptr ? ((char *)(ind_ptr) + param_set_idx * next_sz_ind_ptr_increment + bind_offset) : 0); // TODO: always use SQL_NULLABLE as a default when https://github.com/ClickHouse/ClickHouse/issues/7488 is fixed. binding_info.is_nullable = ( @@ -472,6 +478,9 @@ std::vector Statement::getParamsBindingInfo(std::size_t param_ param_bindings.emplace_back(binding_info); } + if (array_status_ptr) + array_status_ptr[param_set_idx] = SQL_PARAM_SUCCESS; // TODO: elaborate? + return param_bindings; } diff --git a/driver/statement.h b/driver/statement.h index 6a9f6b84c..8f41797ec 100644 --- a/driver/statement.h +++ b/driver/statement.h @@ -105,5 +105,5 @@ class Statement std::unique_ptr response; std::istream* in = nullptr; std::unique_ptr result_reader; - std::size_t next_param_set = 0; + std::size_t next_param_set_idx = 0; }; diff --git a/driver/utils/type_info.h b/driver/utils/type_info.h index 2c3dc2efe..f0d17374c 100755 --- a/driver/utils/type_info.h +++ b/driver/utils/type_info.h @@ -100,7 +100,7 @@ bool isStreamParam(SQLSMALLINT param_io_type) noexcept; /// how to get or put values when reading or writing bound buffers. struct BindingInfo { SQLSMALLINT c_type = SQL_C_DEFAULT; - PTR value = nullptr; + SQLPOINTER value = nullptr; SQLLEN value_max_size = 0; SQLLEN * value_size = nullptr; SQLLEN * indicator = nullptr; @@ -1813,6 +1813,9 @@ namespace value_manip { template struct from_value { static inline SQLRETURN convert(const SourceType & src, BindingInfo & dest) { + if (dest.indicator && dest.indicator != dest.value_size) + *dest.indicator = 0; // Value is not null here... + if constexpr (std::is_same_v) { return fillOutputPOD(src, dest.value, dest.value_size); } @@ -1834,6 +1837,9 @@ namespace value_manip { struct from_value { template static inline SQLRETURN convert(const SourceType & src, BindingInfo & dest, ConversionContext && context) { + if (dest.indicator && dest.indicator != dest.value_size) + *dest.indicator = 0; // Value is not null here... + if constexpr (std::is_same_v) { return fillOutputString(src, dest.value, dest.value_max_size, dest.value_size, true, std::forward(context)); } @@ -1858,6 +1864,9 @@ namespace value_manip { struct from_value { template static inline SQLRETURN convert(const SourceType & src, BindingInfo & dest, ConversionContext && context) { + if (dest.indicator && dest.indicator != dest.value_size) + *dest.indicator = 0; // Value is not null here... + if constexpr (std::is_same_v) { return fillOutputString(src, dest.value, dest.value_max_size, dest.value_size, true, std::forward(context)); } @@ -1881,6 +1890,9 @@ namespace value_manip { template struct from_value { static inline SQLRETURN convert(const SourceType & src, BindingInfo & dest) { + if (dest.indicator && dest.indicator != dest.value_size) + *dest.indicator = 0; // Value is not null here... + if constexpr (std::is_same_v) { if ( src.precision == dest.precision && From fb7a79809e66080ad6eb5cd5affd2a80dc3a1ac9 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Mon, 22 Jun 2020 18:54:26 +0400 Subject: [PATCH 03/16] Fix the test - SQL_ATTR_ROW_ARRAY_SIZE is now accepting any values --- driver/test/misc_it.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/driver/test/misc_it.cpp b/driver/test/misc_it.cpp index 6c6f10203..bd77cc667 100755 --- a/driver/test/misc_it.cpp +++ b/driver/test/misc_it.cpp @@ -14,11 +14,23 @@ TEST_F(MiscellaneousTest, RowArraySizeAttribute) { SQLULEN size = 0; { - size = 0; + size = 123; rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); ASSERT_EQ(size, 1); } + { + size = 0; + rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); + ASSERT_EQ(rc, SQL_SUCCESS); + } + + { + size = 123; + rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); + ASSERT_EQ(size, 0); + } + { size = 1; rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); @@ -26,21 +38,20 @@ TEST_F(MiscellaneousTest, RowArraySizeAttribute) { } { - size = 0; + size = 123; rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); ASSERT_EQ(size, 1); } { - size = 1234; + size = 456; rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); - ASSERT_EQ(rc, SQL_SUCCESS_WITH_INFO); // TODO: remove this when row arrays bigger than 1 are allowed. + ASSERT_EQ(rc, SQL_SUCCESS); } { size = 0; rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); - ASSERT_EQ(size, 1); // TODO: remove this when row arrays bigger than 1 are allowed. -// ASSERT_EQ(size, 1234); // TODO: uncomment this, when row arrays bigger than 1 are allowed. + ASSERT_EQ(size, 456); } } From d1eec0702921df4134c7c9f7de2873c7f6f5f2e8 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Tue, 23 Jun 2020 20:06:13 +0400 Subject: [PATCH 04/16] Typo fix --- driver/api/impl/impl.cpp | 4 ++-- driver/test/misc_it.cpp | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/driver/api/impl/impl.cpp b/driver/api/impl/impl.cpp index badec135c..3b4dff9f9 100644 --- a/driver/api/impl/impl.cpp +++ b/driver/api/impl/impl.cpp @@ -899,7 +899,7 @@ SQLRETURN GetDescField( ) noexcept { auto func = [&] (Descriptor & descriptor) -> SQLRETURN { - // Process header fields first, withour running cheecks on record number. + // Process header fields first, withour running checks on record number. switch (FieldIdentifier) { #define CASE_FIELD_NUM(NAME, TYPE) \ @@ -1045,7 +1045,7 @@ SQLRETURN SetDescField( ) noexcept { auto func = [&] (Descriptor & descriptor) -> SQLRETURN { - // Process header fields first, withour running cheecks on record number. + // Process header fields first, withour running checks on record number. switch (FieldIdentifier) { #define CASE_FIELD_NUM(NAME, TYPE) \ diff --git a/driver/test/misc_it.cpp b/driver/test/misc_it.cpp index bd77cc667..20b1883ec 100755 --- a/driver/test/misc_it.cpp +++ b/driver/test/misc_it.cpp @@ -15,43 +15,43 @@ TEST_F(MiscellaneousTest, RowArraySizeAttribute) { { size = 123; - rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); + rc = ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); ASSERT_EQ(size, 1); } { size = 0; - rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); + rc = ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); ASSERT_EQ(rc, SQL_SUCCESS); } { size = 123; - rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); + rc = ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); ASSERT_EQ(size, 0); } { size = 1; - rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); + rc = ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); ASSERT_EQ(rc, SQL_SUCCESS); } { size = 123; - rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); + rc = ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); ASSERT_EQ(size, 1); } { size = 456; - rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); + rc = ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)size, 0)); ASSERT_EQ(rc, SQL_SUCCESS); } { size = 0; - rc = ODBC_CALL_ON_DBC_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); + rc = ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, &size, sizeof(size), 0)); ASSERT_EQ(size, 456); } } From 5ced11a2b946d8acc33c163e89e74402131330ff Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Tue, 23 Jun 2020 20:11:02 +0400 Subject: [PATCH 05/16] Implement PerformanceTest.FetchArrayBindColSingleType_Int --- driver/test/performance_it.cpp | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/driver/test/performance_it.cpp b/driver/test/performance_it.cpp index 315646a6a..8afb421ea 100755 --- a/driver/test/performance_it.cpp +++ b/driver/test/performance_it.cpp @@ -553,3 +553,65 @@ TEST_F(PerformanceTest, ENABLE_FOR_OPTIMIZED_BUILDS_ONLY(FetchBindColSingleType_ ASSERT_EQ(total_rows, total_rows_expected); } + +TEST_F(PerformanceTest, ENABLE_FOR_OPTIMIZED_BUILDS_ONLY(FetchArrayBindColSingleType_Int)) { + constexpr std::size_t total_rows_expected = 10'000'000; + const std::string query_orig = "SELECT CAST('12345', 'Int') AS col FROM numbers(" + std::to_string(total_rows_expected) + ")"; + + std::cout << "Executing query:\n\t" << query_orig << std::endl; + + const auto query = fromUTF8(query_orig); + auto * query_wptr = const_cast(query.c_str()); + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); + + constexpr std::size_t array_size = 1'000; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)array_size, 0)); + + SQLHDESC ird = nullptr; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_ROW_DESC, &ird, 0, nullptr)); + + SQLULEN rows_processed = 0; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetDescField(ird, 0, SQL_DESC_ROWS_PROCESSED_PTR, &rows_processed, 0)); + + SQLINTEGER col[array_size] = {}; + SQLLEN col_ind[array_size] = {}; + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 1, + getCTypeFor>(), + col, + sizeof(*col), + col_ind + ) + ); + + std::size_t total_rows = 0; + + START_MEASURING_TIME(); + + while (true) { + const SQLRETURN rc = SQLFetch(hstmt); + + if (rc == SQL_NO_DATA) + break; + + if (rc == SQL_ERROR) + throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); + + if (rc == SQL_SUCCESS_WITH_INFO) + std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; + + if (!SQL_SUCCEEDED(rc)) + throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); + + total_rows += rows_processed; + } + + STOP_MEASURING_TIME_AND_REPORT(total_rows); + + ASSERT_EQ(total_rows, total_rows_expected); +} From 67c373bfd958f231cfb8ae86c6e584700a4184c4 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Wed, 24 Jun 2020 03:45:04 +0400 Subject: [PATCH 06/16] Add Column-wise [array] column binding tests --- driver/test/CMakeLists.txt | 2 + driver/test/client_test_base.h | 2 +- driver/test/column_bindings_it.cpp | 257 ++++++++++++++++++ .../test/statement_parameter_bindings_it.cpp | 17 ++ driver/test/statement_parameters_it.cpp | 6 +- 5 files changed, 280 insertions(+), 4 deletions(-) create mode 100755 driver/test/column_bindings_it.cpp create mode 100755 driver/test/statement_parameter_bindings_it.cpp diff --git a/driver/test/CMakeLists.txt b/driver/test/CMakeLists.txt index 27a9185ae..da332c8e9 100644 --- a/driver/test/CMakeLists.txt +++ b/driver/test/CMakeLists.txt @@ -67,6 +67,8 @@ function (declare_odbc_test_targets libname UNICODE) ${PROJECT_SOURCE_DIR}/driver/utils/conversion_std.h ${PROJECT_SOURCE_DIR}/driver/utils/conversion_icu.h misc_it.cpp + column_bindings_it.cpp + statement_parameter_bindings_it.cpp statement_parameters_it.cpp performance_it.cpp ) diff --git a/driver/test/client_test_base.h b/driver/test/client_test_base.h index 437af286b..72d28554f 100755 --- a/driver/test/client_test_base.h +++ b/driver/test/client_test_base.h @@ -36,7 +36,7 @@ class ClientTestBase ODBC_CALL_ON_ENV_THROW(henv, SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc)); const auto dsn = fromUTF8(TestEnvironment::getInstance().getDSN()); - auto * dsn_wptr = const_cast(dsn.c_str()); + auto * dsn_wptr = const_cast(dsn.c_str()); ODBC_CALL_ON_DBC_THROW(hdbc, SQLConnect(hdbc, dsn_wptr, SQL_NTS, NULL, 0, NULL, 0)); ODBC_CALL_ON_DBC_THROW(hdbc, SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt)); diff --git a/driver/test/column_bindings_it.cpp b/driver/test/column_bindings_it.cpp new file mode 100755 index 000000000..c79a8906b --- /dev/null +++ b/driver/test/column_bindings_it.cpp @@ -0,0 +1,257 @@ +#include "driver/platform/platform.h" +#include "driver/test/client_utils.h" +#include "driver/test/client_test_base.h" + +#include +#include + +class ColumnBindingsTest + : public ClientTestBase +{ +}; + +class ColumnArrayBindingsTest + : public ColumnBindingsTest + , public ::testing::WithParamInterface> +{ +}; + +template +struct FixedStringBuffer { + FixedStringBuffer() { + // Fill with a special placeholder sequence. + std::fill(data, data + Size, 0b11011011); + } + + CharType data[Size]; +}; + +TEST_P(ColumnArrayBindingsTest, ColumnWise) { + const std::size_t total_rows_expected = std::get<0>(GetParam()); + const std::string query_orig = R"SQL( +SELECT + CAST(number, 'Int32') AS col1, + CAST(CAST(number, 'String'), 'FixedString(30)') AS col2, + CAST(number, 'Float64') AS col3, + CAST(if((number % 8) = 3, NULL, repeat('x', number % 41)), 'Nullable(String)') AS col4, + CAST(number, 'UInt64') AS col5, + CAST(number, 'Float32') AS col6 +FROM numbers( + )SQL" + std::to_string(total_rows_expected) + ")"; + + const auto query = fromUTF8(query_orig); + auto * query_wptr = const_cast(query.c_str()); + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); + + const std::size_t array_size = std::get<1>(GetParam()); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)array_size, 0)); + + SQLHDESC ird = nullptr; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_ROW_DESC, &ird, 0, nullptr)); + + SQLULEN rows_processed = 0; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetDescField(ird, 0, SQL_DESC_ROWS_PROCESSED_PTR, &rows_processed, 0)); + + std::vector col1(array_size); + std::vector col1_ind(array_size); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 1, + getCTypeFor>(), + &col1[0], + sizeof(col1[0]), + &col1_ind[0] + ) + ); + + std::vector> col2(array_size); + std::vector col2_ind(array_size); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 2, + getCTypeFor(), + &col2[0], + sizeof(col2[0].data) * sizeof(col2[0].data[0]), + &col2_ind[0] + ) + ); + + std::vector col3(array_size); + std::vector col3_ind(array_size); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 3, + getCTypeFor>(), + &col3[0], + sizeof(col3[0]), + &col3_ind[0] + ) + ); + + std::vector> col4(array_size); + std::vector col4_ind(array_size); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 4, + getCTypeFor(), + &col4[0], + sizeof(col4[0].data) * sizeof(col4[0].data[0]), + &col4_ind[0] + ) + ); + + std::vector col5(array_size); + std::vector col5_ind(array_size); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 5, + getCTypeFor>(), + &col5[0], + sizeof(col5[0]), + &col5_ind[0] + ) + ); + + std::vector col6(array_size); + std::vector col6_ind(array_size); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 6, + getCTypeFor>(), + &col6[0], + sizeof(col6[0]), + &col6_ind[0] + ) + ); + + std::size_t total_rows = 0; + + while (true) { + std::fill(col1.begin(), col1.end(), -123); + std::fill(col1_ind.begin(), col1_ind.end(), -123); + + std::fill(col2.begin(), col2.end(), FixedStringBuffer{}); + std::fill(col2_ind.begin(), col2_ind.end(), -123); + + std::fill(col3.begin(), col3.end(), -123); + std::fill(col3_ind.begin(), col3_ind.end(), -123); + + std::fill(col4.begin(), col4.end(), FixedStringBuffer{}); + std::fill(col4_ind.begin(), col4_ind.end(), -123); + + std::fill(col5.begin(), col5.end(), 123456789); + std::fill(col5_ind.begin(), col5_ind.end(), -123); + + std::fill(col6.begin(), col6.end(), -123); + std::fill(col6_ind.begin(), col6_ind.end(), -123); + + rows_processed = 0; + + const SQLRETURN rc = SQLFetch(hstmt); + + if (rc == SQL_NO_DATA) + break; + + if (rc == SQL_ERROR) + throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); + + if (rc == SQL_SUCCESS_WITH_INFO) + std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; + + if (!SQL_SUCCEEDED(rc)) + throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); + + ASSERT_LE(rows_processed, array_size); + + for (std::size_t i = 0; i < col1.size(); ++i) { + if (i < rows_processed) { + const std::int64_t number = total_rows + i; + const auto number_str = std::to_string(number); + + EXPECT_EQ(col1[i], number); + EXPECT_EQ(col1_ind[i], sizeof(col1[i])); + + { + auto number_str_col2 = number_str; + number_str_col2.resize(31, '\0'); // Because the column is 'FixedString(30)' + \0 from ODBC. + + EXPECT_THAT(col2[i].data, ::testing::ElementsAreArray(number_str_col2)); + EXPECT_EQ(col2_ind[i], 30); // Because the column is 'FixedString(30)'. + } + + EXPECT_DOUBLE_EQ(col3[i], number); + EXPECT_EQ(col3_ind[i], sizeof(col3[i])); + + if ((number % 8) == 3) { + EXPECT_THAT(col4[i].data, ::testing::Each(0b11011011)); + EXPECT_EQ(col4_ind[i], SQL_NULL_DATA); + } + else { + std::string number_str_col4(number % 41, 'x'); + number_str_col4.push_back('\0'); + number_str_col4.resize(sizeof(col4[i].data), 0b11011011); + + EXPECT_THAT(col4[i].data, ::testing::ElementsAreArray(number_str_col4)); + EXPECT_EQ(col4_ind[i], number % 41); + } + + EXPECT_EQ(col5[i], number); + EXPECT_EQ(col5_ind[i], sizeof(col5[i])); + + EXPECT_FLOAT_EQ(col6[i], number); + EXPECT_EQ(col6_ind[i], sizeof(col6[i])); + } + else { + EXPECT_EQ(col1[i], -123); + EXPECT_EQ(col1_ind[i], -123); + + EXPECT_THAT(col2[i].data, ::testing::Each(0b11011011)); + EXPECT_EQ(col2_ind[i], -123); + + EXPECT_DOUBLE_EQ(col3[i], -123); + EXPECT_EQ(col3_ind[i], -123); + + EXPECT_THAT(col4[i].data, ::testing::Each(0b11011011)); + EXPECT_EQ(col4_ind[i], -123); + + EXPECT_EQ(col5[i], 123456789); + EXPECT_EQ(col5_ind[i], -123); + + EXPECT_FLOAT_EQ(col6[i], -123); + EXPECT_EQ(col6_ind[i], -123); + } + } + + total_rows += rows_processed; + } + + ASSERT_EQ(total_rows, total_rows_expected); +} + +INSTANTIATE_TEST_SUITE_P(ArrayBindings, ColumnArrayBindingsTest, + ::testing::Combine( + ::testing::Values(1, 2, 10, 75, 377, 1000, 2053, 4289), // Result set sizes. + ::testing::Values(1, 2, 3, 5, 10, 30, 47, 111, 500, 1000) // Row set sizes. + ), + [] (const auto & param_info) { + return ( + "ResultSet_" +std::to_string(std::get<0>(param_info.param)) + + "_vs_" + + "RowSet_" +std::to_string(std::get<1>(param_info.param)) + ); + } +); diff --git a/driver/test/statement_parameter_bindings_it.cpp b/driver/test/statement_parameter_bindings_it.cpp new file mode 100755 index 000000000..95ee38a8d --- /dev/null +++ b/driver/test/statement_parameter_bindings_it.cpp @@ -0,0 +1,17 @@ +#include "driver/platform/platform.h" +#include "driver/test/client_utils.h" +#include "driver/test/client_test_base.h" + +#include + +class StatementParameterBindingsTest + : public ClientTestBase +{ +}; + +class StatementParameterArrayBindingsTest + : public StatementParameterBindingsTest + , public ::testing::WithParamInterface +{ +}; + diff --git a/driver/test/statement_parameters_it.cpp b/driver/test/statement_parameters_it.cpp index fa13bb284..5b86d4eb7 100755 --- a/driver/test/statement_parameters_it.cpp +++ b/driver/test/statement_parameters_it.cpp @@ -547,9 +547,9 @@ INSTANTIATE_TEST_SUITE_P(TypeConversion, DISABLED_ParameterColumnRoundTripGUIDSy ); -using ParameterColumnRoundTripNumericSymmetric = ParameterColumnRoundTripSymmetric; +using ParameterColumnRoundTripNumericSymmetric = ParameterColumnRoundTripSymmetric; -TEST_P(ParameterColumnRoundTripNumericSymmetric, Execute) { +TEST_P(ParameterColumnRoundTripNumericSymmetric, Execute) { execute(GetParam(), GetParam(), type_info_for("Decimal")); } @@ -593,7 +593,7 @@ INSTANTIATE_TEST_SUITE_P(TypeConversion, ParameterColumnRoundTripNumericAsymmetr ); -using ParameterColumnRoundTripDecimalAsStringSymmetric = ParameterColumnRoundTripSymmetric; +using ParameterColumnRoundTripDecimalAsStringSymmetric = ParameterColumnRoundTripSymmetric; TEST_P(ParameterColumnRoundTripDecimalAsStringSymmetric, Execute) { execute_with_decimal_as_string(GetParam(), GetParam()); From bbb260795c285b3245ff9e3a3c8207949cdf7baa Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Wed, 24 Jun 2020 17:34:43 +0400 Subject: [PATCH 07/16] Typo fix Add binding offset testing Reduce cases --- driver/test/column_bindings_it.cpp | 61 ++++++++++++++++++------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/driver/test/column_bindings_it.cpp b/driver/test/column_bindings_it.cpp index c79a8906b..2fa927d46 100755 --- a/driver/test/column_bindings_it.cpp +++ b/driver/test/column_bindings_it.cpp @@ -12,7 +12,7 @@ class ColumnBindingsTest class ColumnArrayBindingsTest : public ColumnBindingsTest - , public ::testing::WithParamInterface> + , public ::testing::WithParamInterface> { }; @@ -27,7 +27,7 @@ struct FixedStringBuffer { }; TEST_P(ColumnArrayBindingsTest, ColumnWise) { - const std::size_t total_rows_expected = std::get<0>(GetParam()); + const std::size_t total_rows_expected = std::get<1>(GetParam()); const std::string query_orig = R"SQL( SELECT CAST(number, 'Int32') AS col1, @@ -39,20 +39,30 @@ SELECT FROM numbers( )SQL" + std::to_string(total_rows_expected) + ")"; - const auto query = fromUTF8(query_orig); - auto * query_wptr = const_cast(query.c_str()); + const auto query = fromUTF8(query_orig); + auto * query_wptr = const_cast(query.c_str()); ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); - const std::size_t array_size = std::get<1>(GetParam()); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)SQL_BIND_BY_COLUMN, 0)); + + SQLULEN rows_processed = 0; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR, (SQLPOINTER)&rows_processed, 0)); + + const SQLULEN binding_offset = std::get<0>(GetParam()); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, (SQLPOINTER)&binding_offset, 0)); + + const std::size_t array_size = std::get<2>(GetParam()); ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)array_size, 0)); - SQLHDESC ird = nullptr; - ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_ROW_DESC, &ird, 0, nullptr)); + const auto adjsut_ptr = [&] (auto * ptr) { + return reinterpret_cast(ptr) - binding_offset; + }; - SQLULEN rows_processed = 0; - ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetDescField(ird, 0, SQL_DESC_ROWS_PROCESSED_PTR, &rows_processed, 0)); + const auto adjsut_ind_ptr = [&] (auto * ptr) { + return reinterpret_cast(adjsut_ptr(ptr)); + }; std::vector col1(array_size); std::vector col1_ind(array_size); @@ -62,9 +72,9 @@ FROM numbers( hstmt, 1, getCTypeFor>(), - &col1[0], + adjsut_ptr(&col1[0]), sizeof(col1[0]), - &col1_ind[0] + adjsut_ind_ptr(&col1_ind[0]) ) ); @@ -76,9 +86,9 @@ FROM numbers( hstmt, 2, getCTypeFor(), - &col2[0], + adjsut_ptr(&col2[0]), sizeof(col2[0].data) * sizeof(col2[0].data[0]), - &col2_ind[0] + adjsut_ind_ptr(&col2_ind[0]) ) ); @@ -90,9 +100,9 @@ FROM numbers( hstmt, 3, getCTypeFor>(), - &col3[0], + adjsut_ptr(&col3[0]), sizeof(col3[0]), - &col3_ind[0] + adjsut_ind_ptr(&col3_ind[0]) ) ); @@ -104,9 +114,9 @@ FROM numbers( hstmt, 4, getCTypeFor(), - &col4[0], + adjsut_ptr(&col4[0]), sizeof(col4[0].data) * sizeof(col4[0].data[0]), - &col4_ind[0] + adjsut_ind_ptr(&col4_ind[0]) ) ); @@ -118,9 +128,9 @@ FROM numbers( hstmt, 5, getCTypeFor>(), - &col5[0], + adjsut_ptr(&col5[0]), sizeof(col5[0]), - &col5_ind[0] + adjsut_ind_ptr(&col5_ind[0]) ) ); @@ -132,9 +142,9 @@ FROM numbers( hstmt, 6, getCTypeFor>(), - &col6[0], + adjsut_ptr(&col6[0]), sizeof(col6[0]), - &col6_ind[0] + adjsut_ind_ptr(&col6_ind[0]) ) ); @@ -244,14 +254,17 @@ FROM numbers( INSTANTIATE_TEST_SUITE_P(ArrayBindings, ColumnArrayBindingsTest, ::testing::Combine( - ::testing::Values(1, 2, 10, 75, 377, 1000, 2053, 4289), // Result set sizes. + ::testing::Values(0, 1, 1234), // Binding offset. + ::testing::Values(1, 2, 10, 75, 377, 4289), // Result set sizes. ::testing::Values(1, 2, 3, 5, 10, 30, 47, 111, 500, 1000) // Row set sizes. ), [] (const auto & param_info) { return ( - "ResultSet_" +std::to_string(std::get<0>(param_info.param)) + "BindingOffset_" +std::to_string(std::get<0>(param_info.param)) + + "_vs_" + + "ResultSet_" +std::to_string(std::get<1>(param_info.param)) + "_vs_" + - "RowSet_" +std::to_string(std::get<1>(param_info.param)) + "RowSet_" +std::to_string(std::get<2>(param_info.param)) ); } ); From 78a207bbdad2f2505c5f54243939c4648e12f091 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Wed, 24 Jun 2020 18:35:16 +0400 Subject: [PATCH 08/16] Add row-wise binding scenario --- driver/test/column_bindings_it.cpp | 219 ++++++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 1 deletion(-) diff --git a/driver/test/column_bindings_it.cpp b/driver/test/column_bindings_it.cpp index 2fa927d46..e82ce1737 100755 --- a/driver/test/column_bindings_it.cpp +++ b/driver/test/column_bindings_it.cpp @@ -151,6 +151,8 @@ FROM numbers( std::size_t total_rows = 0; while (true) { + // Must not reallocate the underlying buffers. + std::fill(col1.begin(), col1.end(), -123); std::fill(col1_ind.begin(), col1_ind.end(), -123); @@ -187,7 +189,7 @@ FROM numbers( ASSERT_LE(rows_processed, array_size); - for (std::size_t i = 0; i < col1.size(); ++i) { + for (std::size_t i = 0; i < array_size; ++i) { if (i < rows_processed) { const std::int64_t number = total_rows + i; const auto number_str = std::to_string(number); @@ -252,6 +254,221 @@ FROM numbers( ASSERT_EQ(total_rows, total_rows_expected); } +TEST_P(ColumnArrayBindingsTest, RowWise) { + const std::size_t total_rows_expected = std::get<1>(GetParam()); + const std::string query_orig = R"SQL( +SELECT + CAST(number, 'Int32') AS col1, + CAST(CAST(number, 'String'), 'FixedString(30)') AS col2, + CAST(number, 'Float64') AS col3, + CAST(if((number % 8) = 3, NULL, repeat('x', number % 41)), 'Nullable(String)') AS col4, + CAST(number, 'UInt64') AS col5, + CAST(number, 'Float32') AS col6 +FROM numbers( + )SQL" + std::to_string(total_rows_expected) + ")"; + + const auto query = fromUTF8(query_orig); + auto * query_wptr = const_cast(query.c_str()); + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); + + struct Bindings { + SQLINTEGER col1 = -123; + SQLLEN col1_ind = -123; + + FixedStringBuffer col2; + SQLLEN col2_ind = -123; + + SQLDOUBLE col3 = -123; + SQLLEN col3_ind = -123; + + FixedStringBuffer col4; + SQLLEN col4_ind = -123; + + SQLUBIGINT col5 = 123456789; + SQLLEN col5_ind = -123; + + SQLREAL col6 = -123; + SQLLEN col6_ind = -123; + }; + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE, (SQLPOINTER)sizeof(Bindings), 0)); + + SQLULEN rows_processed = 0; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR, (SQLPOINTER)&rows_processed, 0)); + + const SQLULEN binding_offset = std::get<0>(GetParam()); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, (SQLPOINTER)&binding_offset, 0)); + + const std::size_t array_size = std::get<2>(GetParam()); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)array_size, 0)); + + const auto adjsut_ptr = [&] (auto * ptr) { + return reinterpret_cast(ptr) - binding_offset; + }; + + const auto adjsut_ind_ptr = [&] (auto * ptr) { + return reinterpret_cast(adjsut_ptr(ptr)); + }; + + std::vector buf(array_size); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 1, + getCTypeFor>(), + adjsut_ptr(&(buf[0].col1)), + sizeof(buf[0].col1), + adjsut_ind_ptr(&(buf[0].col1_ind)) + ) + ); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 2, + getCTypeFor(), + adjsut_ptr(&(buf[0].col2)), + sizeof(buf[0].col2.data) * sizeof(buf[0].col2.data[0]), + adjsut_ind_ptr(&(buf[0].col2_ind)) + ) + ); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 3, + getCTypeFor>(), + adjsut_ptr(&(buf[0].col3)), + sizeof(buf[0].col3), + adjsut_ind_ptr(&(buf[0].col3_ind)) + ) + ); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 4, + getCTypeFor(), + adjsut_ptr(&(buf[0].col4)), + sizeof(buf[0].col4.data) * sizeof(buf[0].col4.data[0]), + adjsut_ind_ptr(&(buf[0].col4_ind)) + ) + ); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 5, + getCTypeFor>(), + adjsut_ptr(&(buf[0].col5)), + sizeof(buf[0].col5), + adjsut_ind_ptr(&(buf[0].col5_ind)) + ) + ); + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindCol( + hstmt, + 6, + getCTypeFor>(), + adjsut_ptr(&(buf[0].col6)), + sizeof(buf[0].col6), + adjsut_ind_ptr(&(buf[0].col6_ind)) + ) + ); + + std::size_t total_rows = 0; + + while (true) { + // Must not reallocate the underlying buffer. + std::fill(buf.begin(), buf.end(), Bindings{}); + + rows_processed = 0; + + const SQLRETURN rc = SQLFetch(hstmt); + + if (rc == SQL_NO_DATA) + break; + + if (rc == SQL_ERROR) + throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); + + if (rc == SQL_SUCCESS_WITH_INFO) + std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; + + if (!SQL_SUCCEEDED(rc)) + throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); + + ASSERT_LE(rows_processed, array_size); + + for (std::size_t i = 0; i < array_size; ++i) { + if (i < rows_processed) { + const std::int64_t number = total_rows + i; + const auto number_str = std::to_string(number); + + EXPECT_EQ(buf[i].col1, number); + EXPECT_EQ(buf[i].col1_ind, sizeof(buf[i].col1)); + + { + auto number_str_col2 = number_str; + number_str_col2.resize(31, '\0'); // Because the column is 'FixedString(30)' + \0 from ODBC. + + EXPECT_THAT(buf[i].col2.data, ::testing::ElementsAreArray(number_str_col2)); + EXPECT_EQ(buf[i].col2_ind, 30); // Because the column is 'FixedString(30)'. + } + + EXPECT_DOUBLE_EQ(buf[i].col3, number); + EXPECT_EQ(buf[i].col3_ind, sizeof(buf[i].col3)); + + if ((number % 8) == 3) { + EXPECT_THAT(buf[i].col4.data, ::testing::Each(0b11011011)); + EXPECT_EQ(buf[i].col4_ind, SQL_NULL_DATA); + } + else { + std::string number_str_col4(number % 41, 'x'); + number_str_col4.push_back('\0'); + number_str_col4.resize(sizeof(buf[i].col4.data), 0b11011011); + + EXPECT_THAT(buf[i].col4.data, ::testing::ElementsAreArray(number_str_col4)); + EXPECT_EQ(buf[i].col4_ind, number % 41); + } + + EXPECT_EQ(buf[i].col5, number); + EXPECT_EQ(buf[i].col5_ind, sizeof(buf[i].col5)); + + EXPECT_FLOAT_EQ(buf[i].col6, number); + EXPECT_EQ(buf[i].col6_ind, sizeof(buf[i].col6)); + } + else { + EXPECT_EQ(buf[i].col1, -123); + EXPECT_EQ(buf[i].col1_ind, -123); + + EXPECT_THAT(buf[i].col2.data, ::testing::Each(0b11011011)); + EXPECT_EQ(buf[i].col2_ind, -123); + + EXPECT_DOUBLE_EQ(buf[i].col3, -123); + EXPECT_EQ(buf[i].col3_ind, -123); + + EXPECT_THAT(buf[i].col4.data, ::testing::Each(0b11011011)); + EXPECT_EQ(buf[i].col4_ind, -123); + + EXPECT_EQ(buf[i].col5, 123456789); + EXPECT_EQ(buf[i].col5_ind, -123); + + EXPECT_FLOAT_EQ(buf[i].col6, -123); + EXPECT_EQ(buf[i].col6_ind, -123); + } + } + + total_rows += rows_processed; + } + + ASSERT_EQ(total_rows, total_rows_expected); +} + INSTANTIATE_TEST_SUITE_P(ArrayBindings, ColumnArrayBindingsTest, ::testing::Combine( ::testing::Values(0, 1, 1234), // Binding offset. From c7fad7b87f4c3b81e308501f0c806fa7c8fd2019 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Wed, 24 Jun 2020 19:11:20 +0400 Subject: [PATCH 09/16] Move/rename parameter binding tests --- .../test/statement_parameter_bindings_it.cpp | 249 ++++++++++++++++++ driver/test/statement_parameters_it.cpp | 249 ------------------ 2 files changed, 249 insertions(+), 249 deletions(-) diff --git a/driver/test/statement_parameter_bindings_it.cpp b/driver/test/statement_parameter_bindings_it.cpp index 95ee38a8d..b1e6eb166 100755 --- a/driver/test/statement_parameter_bindings_it.cpp +++ b/driver/test/statement_parameter_bindings_it.cpp @@ -9,6 +9,255 @@ class StatementParameterBindingsTest { }; +TEST_F(StatementParameterBindingsTest, Missing) { + const auto query = fromUTF8("SELECT isNull(?)"); + auto * query_wptr = const_cast(query.c_str()); + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); + SQLRETURN rc = SQLFetch(hstmt); + + if (rc == SQL_ERROR) + throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); + + if (rc == SQL_SUCCESS_WITH_INFO) + std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; + + if (!SQL_SUCCEEDED(rc)) + throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); + + SQLINTEGER col = 0; + SQLLEN col_ind = 0; + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLGetData( + hstmt, + 1, + getCTypeFor(), + &col, + sizeof(col), + &col_ind + ) + ); + + ASSERT_TRUE(col_ind >= 0 || col_ind == SQL_NTS); + ASSERT_EQ(col, 1); + + ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA); +} + +TEST_F(StatementParameterBindingsTest, NoBuffer) { + const auto query = fromUTF8("SELECT isNull(?)"); + auto * query_wptr = const_cast(query.c_str()); + + SQLINTEGER param = 0; + SQLLEN param_ind = 0; + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindParameter( + hstmt, + 1, + SQL_PARAM_INPUT, + getCTypeFor(), + SQL_INTEGER, + 0, + 0, + nullptr, // N.B.: not ¶m here! + sizeof(param), + ¶m_ind + ) + ); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); + SQLRETURN rc = SQLFetch(hstmt); + + if (rc == SQL_ERROR) + throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); + + if (rc == SQL_SUCCESS_WITH_INFO) + std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; + + if (!SQL_SUCCEEDED(rc)) + throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); + + SQLINTEGER col = 0; + SQLLEN col_ind = 0; + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLGetData( + hstmt, + 1, + getCTypeFor(), + &col, + sizeof(col), + &col_ind + ) + ); + + ASSERT_TRUE(col_ind >= 0 || col_ind == SQL_NTS); + ASSERT_EQ(col, 1); + + ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA); +} + +TEST_F(StatementParameterBindingsTest, NullStringValueForInteger) { + const auto query = fromUTF8("SELECT isNull(?)"); + auto * query_wptr = const_cast(query.c_str()); + +#if defined(_IODBCUNIX_H) + // iODBC workaround: disable potential use of SQLWCHAR in this test case, + // since iODBC, for reasons unknown, changes the 4th argument of SQLBindParameter() + // from SQL_C_WCHAR to SQL_C_CHAR, if this client is Unicode and the driver pointed by DSN is ANSI, + // but does not convert the actual buffer (naturally). This makes the driver unable to interpret the buffer correctly. + // TODO: eventually review and fix or report a defect on iODBC, if it doesn't have any reasonable explanation. +# define SQLmyTCHAR SQLCHAR +# define SQL_C_myTCHAR SQL_C_CHAR +#else +# define SQLmyTCHAR SQLTCHAR +# define SQL_C_myTCHAR SQL_C_TCHAR +#endif + + auto param = fromUTF8("\\N"); + SQLLEN param_ind = 0; + + auto * param_wptr = const_cast(param.c_str()); + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindParameter( + hstmt, + 1, + SQL_PARAM_INPUT, + SQL_C_myTCHAR, + SQL_INTEGER, + param.size(), + 0, + param_wptr, + param.size() * sizeof(SQLTCHAR), + ¶m_ind + ) + ); + +#undef SQLmyTCHAR +#undef SQL_C_myTCHAR + + // TODO: Workaround for workaround for https://github.com/ClickHouse/ClickHouse/issues/7488 . Remove when sorted-out. + // Strictly speaking, this is not allowed, and parameters must always be nullable. + SQLHDESC hdesc = 0; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_PARAM_DESC, &hdesc, 0, NULL)); + ODBC_CALL_ON_DESC_THROW(hdesc, SQLSetDescField(hdesc, 1, SQL_DESC_NULLABLE, reinterpret_cast(SQL_NULLABLE), 0)); + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); + SQLRETURN rc = SQLFetch(hstmt); + + if (rc == SQL_ERROR) + throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); + + if (rc == SQL_SUCCESS_WITH_INFO) + std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; + + if (!SQL_SUCCEEDED(rc)) + throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); + + SQLINTEGER col = 0; + SQLLEN col_ind = 0; + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLGetData( + hstmt, + 1, + getCTypeFor(), + &col, + sizeof(col), + &col_ind + ) + ); + + ASSERT_TRUE(col_ind >= 0 || col_ind == SQL_NTS); + ASSERT_EQ(col, 1); + + ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA); +} + +TEST_F(StatementParameterBindingsTest, NullStringValueForString) { + const auto query = fromUTF8("SELECT isNull(?)"); + auto * query_wptr = const_cast(query.c_str()); + +#if defined(_IODBCUNIX_H) + // iODBC workaround: disable potential use of SQLWCHAR in this test case, + // since iODBC, for reasons unknown, changes the 4th argument of SQLBindParameter() + // from SQL_C_WCHAR to SQL_C_CHAR, if this client is Unicode and the driver pointed by DSN is ANSI, + // but does not convert the actual buffer (naturally). This makes the driver unable to interpret the buffer correctly. + // TODO: eventually review and fix or report a defect on iODBC, if it doesn't have any reasonable explanation. +# define SQLmyTCHAR SQLCHAR +# define SQL_C_myTCHAR SQL_C_CHAR +#else +# define SQLmyTCHAR SQLTCHAR +# define SQL_C_myTCHAR SQL_C_TCHAR +#endif + + auto param = fromUTF8("\\N"); + SQLLEN param_ind = 0; + + auto * param_wptr = const_cast(param.c_str()); + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLBindParameter( + hstmt, + 1, + SQL_PARAM_INPUT, + SQL_C_myTCHAR, + SQL_CHAR, + param.size(), + 0, + param_wptr, + param.size() * sizeof(SQLTCHAR), + ¶m_ind + ) + ); + +#undef SQLmyTCHAR +#undef SQL_C_myTCHAR + + // TODO: Workaround for workaround for https://github.com/ClickHouse/ClickHouse/issues/7488 . Remove when sorted-out. + // Strictly speaking, this is not allowed, and parameters must always be nullable. + SQLHDESC hdesc = 0; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_PARAM_DESC, &hdesc, 0, NULL)); + ODBC_CALL_ON_DESC_THROW(hdesc, SQLSetDescField(hdesc, 1, SQL_DESC_NULLABLE, reinterpret_cast(SQL_NULLABLE), 0)); + + ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); + SQLRETURN rc = SQLFetch(hstmt); + + if (rc == SQL_ERROR) + throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); + + if (rc == SQL_SUCCESS_WITH_INFO) + std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; + + if (!SQL_SUCCEEDED(rc)) + throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); + + SQLINTEGER col = 0; + SQLLEN col_ind = 0; + + ODBC_CALL_ON_STMT_THROW(hstmt, + SQLGetData( + hstmt, + 1, + getCTypeFor(), + &col, + sizeof(col), + &col_ind + ) + ); + + ASSERT_TRUE(col_ind >= 0 || col_ind == SQL_NTS); + ASSERT_EQ(col, 1); + + ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA); +} + class StatementParameterArrayBindingsTest : public StatementParameterBindingsTest , public ::testing::WithParamInterface diff --git a/driver/test/statement_parameters_it.cpp b/driver/test/statement_parameters_it.cpp index b51b4d708..cfc26916b 100755 --- a/driver/test/statement_parameters_it.cpp +++ b/driver/test/statement_parameters_it.cpp @@ -14,255 +14,6 @@ class StatementParametersTest { }; -TEST_F(StatementParametersTest, BindingMissing) { - const auto query = fromUTF8("SELECT isNull(?)"); - auto * query_wptr = const_cast(query.c_str()); - - ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); - ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); - SQLRETURN rc = SQLFetch(hstmt); - - if (rc == SQL_ERROR) - throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); - - if (rc == SQL_SUCCESS_WITH_INFO) - std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; - - if (!SQL_SUCCEEDED(rc)) - throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); - - SQLINTEGER col = 0; - SQLLEN col_ind = 0; - - ODBC_CALL_ON_STMT_THROW(hstmt, - SQLGetData( - hstmt, - 1, - getCTypeFor(), - &col, - sizeof(col), - &col_ind - ) - ); - - ASSERT_TRUE(col_ind >= 0 || col_ind == SQL_NTS); - ASSERT_EQ(col, 1); - - ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA); -} - -TEST_F(StatementParametersTest, BindingNoBuffer) { - const auto query = fromUTF8("SELECT isNull(?)"); - auto * query_wptr = const_cast(query.c_str()); - - SQLINTEGER param = 0; - SQLLEN param_ind = 0; - - ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); - ODBC_CALL_ON_STMT_THROW(hstmt, - SQLBindParameter( - hstmt, - 1, - SQL_PARAM_INPUT, - getCTypeFor(), - SQL_INTEGER, - 0, - 0, - nullptr, // N.B.: not ¶m here! - sizeof(param), - ¶m_ind - ) - ); - ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); - SQLRETURN rc = SQLFetch(hstmt); - - if (rc == SQL_ERROR) - throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); - - if (rc == SQL_SUCCESS_WITH_INFO) - std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; - - if (!SQL_SUCCEEDED(rc)) - throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); - - SQLINTEGER col = 0; - SQLLEN col_ind = 0; - - ODBC_CALL_ON_STMT_THROW(hstmt, - SQLGetData( - hstmt, - 1, - getCTypeFor(), - &col, - sizeof(col), - &col_ind - ) - ); - - ASSERT_TRUE(col_ind >= 0 || col_ind == SQL_NTS); - ASSERT_EQ(col, 1); - - ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA); -} - -TEST_F(StatementParametersTest, BindingNullStringValueForInteger) { - const auto query = fromUTF8("SELECT isNull(?)"); - auto * query_wptr = const_cast(query.c_str()); - -#if defined(_IODBCUNIX_H) - // iODBC workaround: disable potential use of SQLWCHAR in this test case, - // since iODBC, for reasons unknown, changes the 4th argument of SQLBindParameter() - // from SQL_C_WCHAR to SQL_C_CHAR, if this client is Unicode and the driver pointed by DSN is ANSI, - // but does not convert the actual buffer (naturally). This makes the driver unable to interpret the buffer correctly. - // TODO: eventually review and fix or report a defect on iODBC, if it doesn't have any reasonable explanation. -# define SQLmyTCHAR SQLCHAR -# define SQL_C_myTCHAR SQL_C_CHAR -#else -# define SQLmyTCHAR SQLTCHAR -# define SQL_C_myTCHAR SQL_C_TCHAR -#endif - - auto param = fromUTF8("\\N"); - SQLLEN param_ind = 0; - - auto * param_wptr = const_cast(param.c_str()); - - ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); - ODBC_CALL_ON_STMT_THROW(hstmt, - SQLBindParameter( - hstmt, - 1, - SQL_PARAM_INPUT, - SQL_C_myTCHAR, - SQL_INTEGER, - param.size(), - 0, - param_wptr, - param.size() * sizeof(SQLTCHAR), - ¶m_ind - ) - ); - -#undef SQLmyTCHAR -#undef SQL_C_myTCHAR - - // TODO: Workaround for workaround for https://github.com/ClickHouse/ClickHouse/issues/7488 . Remove when sorted-out. - // Strictly speaking, this is not allowed, and parameters must always be nullable. - SQLHDESC hdesc = 0; - ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_PARAM_DESC, &hdesc, 0, NULL)); - ODBC_CALL_ON_DESC_THROW(hdesc, SQLSetDescField(hdesc, 1, SQL_DESC_NULLABLE, reinterpret_cast(SQL_NULLABLE), 0)); - - ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); - SQLRETURN rc = SQLFetch(hstmt); - - if (rc == SQL_ERROR) - throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); - - if (rc == SQL_SUCCESS_WITH_INFO) - std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; - - if (!SQL_SUCCEEDED(rc)) - throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); - - SQLINTEGER col = 0; - SQLLEN col_ind = 0; - - ODBC_CALL_ON_STMT_THROW(hstmt, - SQLGetData( - hstmt, - 1, - getCTypeFor(), - &col, - sizeof(col), - &col_ind - ) - ); - - ASSERT_TRUE(col_ind >= 0 || col_ind == SQL_NTS); - ASSERT_EQ(col, 1); - - ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA); -} - -TEST_F(StatementParametersTest, BindingNullStringValueForString) { - const auto query = fromUTF8("SELECT isNull(?)"); - auto * query_wptr = const_cast(query.c_str()); - -#if defined(_IODBCUNIX_H) - // iODBC workaround: disable potential use of SQLWCHAR in this test case, - // since iODBC, for reasons unknown, changes the 4th argument of SQLBindParameter() - // from SQL_C_WCHAR to SQL_C_CHAR, if this client is Unicode and the driver pointed by DSN is ANSI, - // but does not convert the actual buffer (naturally). This makes the driver unable to interpret the buffer correctly. - // TODO: eventually review and fix or report a defect on iODBC, if it doesn't have any reasonable explanation. -# define SQLmyTCHAR SQLCHAR -# define SQL_C_myTCHAR SQL_C_CHAR -#else -# define SQLmyTCHAR SQLTCHAR -# define SQL_C_myTCHAR SQL_C_TCHAR -#endif - - auto param = fromUTF8("\\N"); - SQLLEN param_ind = 0; - - auto * param_wptr = const_cast(param.c_str()); - - ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); - ODBC_CALL_ON_STMT_THROW(hstmt, - SQLBindParameter( - hstmt, - 1, - SQL_PARAM_INPUT, - SQL_C_myTCHAR, - SQL_CHAR, - param.size(), - 0, - param_wptr, - param.size() * sizeof(SQLTCHAR), - ¶m_ind - ) - ); - -#undef SQLmyTCHAR -#undef SQL_C_myTCHAR - - // TODO: Workaround for workaround for https://github.com/ClickHouse/ClickHouse/issues/7488 . Remove when sorted-out. - // Strictly speaking, this is not allowed, and parameters must always be nullable. - SQLHDESC hdesc = 0; - ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_PARAM_DESC, &hdesc, 0, NULL)); - ODBC_CALL_ON_DESC_THROW(hdesc, SQLSetDescField(hdesc, 1, SQL_DESC_NULLABLE, reinterpret_cast(SQL_NULLABLE), 0)); - - ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); - SQLRETURN rc = SQLFetch(hstmt); - - if (rc == SQL_ERROR) - throw std::runtime_error(extract_diagnostics(hstmt, SQL_HANDLE_STMT)); - - if (rc == SQL_SUCCESS_WITH_INFO) - std::cout << extract_diagnostics(hstmt, SQL_HANDLE_STMT) << std::endl; - - if (!SQL_SUCCEEDED(rc)) - throw std::runtime_error("SQLFetch return code: " + std::to_string(rc)); - - SQLINTEGER col = 0; - SQLLEN col_ind = 0; - - ODBC_CALL_ON_STMT_THROW(hstmt, - SQLGetData( - hstmt, - 1, - getCTypeFor(), - &col, - sizeof(col), - &col_ind - ) - ); - - ASSERT_TRUE(col_ind >= 0 || col_ind == SQL_NTS); - ASSERT_EQ(col, 1); - - ASSERT_EQ(SQLFetch(hstmt), SQL_NO_DATA); -} - class ParameterColumnRoundTrip : public StatementParametersTest { From af5932507e87e5c2043228bdc85729ea1074acd2 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Wed, 24 Jun 2020 19:16:48 +0400 Subject: [PATCH 10/16] Using typedef in during the cast --- driver/api/impl/impl.cpp | 2 +- driver/statement.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/driver/api/impl/impl.cpp b/driver/api/impl/impl.cpp index 3b4dff9f9..f9e806d0b 100644 --- a/driver/api/impl/impl.cpp +++ b/driver/api/impl/impl.cpp @@ -1336,7 +1336,7 @@ SQLRETURN fetchBindings( const auto next_value_ptr_increment = (bind_type == SQL_BIND_BY_COLUMN ? binding_info.value_max_size : bind_type); const auto next_sz_ind_ptr_increment = (bind_type == SQL_BIND_BY_COLUMN ? sizeof(SQLLEN) : bind_type); - binding_info.value = (void *)(data_ptr ? ((char *)(data_ptr) + row_idx * next_value_ptr_increment + bind_offset) : 0); + binding_info.value = (SQLPOINTER)(data_ptr ? ((char *)(data_ptr) + row_idx * next_value_ptr_increment + bind_offset) : 0); binding_info.value_size = (SQLLEN *)(sz_ptr ? ((char *)(sz_ptr) + row_idx * next_sz_ind_ptr_increment + bind_offset) : 0); binding_info.indicator = (SQLLEN *)(ind_ptr ? ((char *)(ind_ptr) + row_idx * next_sz_ind_ptr_increment + bind_offset) : 0); diff --git a/driver/statement.cpp b/driver/statement.cpp index 49ce7384f..81c5e8be7 100644 --- a/driver/statement.cpp +++ b/driver/statement.cpp @@ -459,7 +459,7 @@ std::vector Statement::getParamsBindingInfo(std::size_t param_ const auto next_value_ptr_increment = (bind_type == SQL_PARAM_BIND_BY_COLUMN ? binding_info.value_max_size : bind_type); const auto next_sz_ind_ptr_increment = (bind_type == SQL_PARAM_BIND_BY_COLUMN ? sizeof(SQLLEN) : bind_type); - binding_info.value = (void *)(data_ptr ? ((char *)(data_ptr) + param_set_idx * next_value_ptr_increment + bind_offset) : 0); + binding_info.value = (SQLPOINTER)(data_ptr ? ((char *)(data_ptr) + param_set_idx * next_value_ptr_increment + bind_offset) : 0); binding_info.value_size = (SQLLEN *)(sz_ptr ? ((char *)(sz_ptr) + param_set_idx * next_sz_ind_ptr_increment + bind_offset) : 0); binding_info.indicator = (SQLLEN *)(ind_ptr ? ((char *)(ind_ptr) + param_set_idx * next_sz_ind_ptr_increment + bind_offset) : 0); From edbc9fbcaeb5636696e45120400296e4549f41b2 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Wed, 24 Jun 2020 20:24:46 +0400 Subject: [PATCH 11/16] Set SQL_DESC_ROWS_PROCESSED_PTR directly --- driver/test/performance_it.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/driver/test/performance_it.cpp b/driver/test/performance_it.cpp index 8afb421ea..872e3bf24 100755 --- a/driver/test/performance_it.cpp +++ b/driver/test/performance_it.cpp @@ -566,14 +566,11 @@ TEST_F(PerformanceTest, ENABLE_FOR_OPTIMIZED_BUILDS_ONLY(FetchArrayBindColSingle ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); - constexpr std::size_t array_size = 1'000; - ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)array_size, 0)); - - SQLHDESC ird = nullptr; - ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_ROW_DESC, &ird, 0, nullptr)); - SQLULEN rows_processed = 0; - ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetDescField(ird, 0, SQL_DESC_ROWS_PROCESSED_PTR, &rows_processed, 0)); + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR, (SQLPOINTER)&rows_processed, 0)); + + const std::size_t array_size = 1'000; + ODBC_CALL_ON_STMT_THROW(hstmt, SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)array_size, 0)); SQLINTEGER col[array_size] = {}; SQLLEN col_ind[array_size] = {}; From 613ed323420e106859035f9ff5c7818f0afdcd5a Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Wed, 24 Jun 2020 21:47:23 +0400 Subject: [PATCH 12/16] Pre-extract columns binding info before iterating over row set --- driver/api/impl/impl.cpp | 50 ++++++++++++++++++++++++++++------------ driver/utils/utils.h | 6 +++++ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/driver/api/impl/impl.cpp b/driver/api/impl/impl.cpp index f9e806d0b..562343af6 100644 --- a/driver/api/impl/impl.cpp +++ b/driver/api/impl/impl.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace impl { @@ -1317,28 +1318,47 @@ SQLRETURN fetchBindings( bool success_with_info_met = false; std::size_t error_num = 0; + BindingInfo * base_bindings = nullptr; + if (ard_record_count > 0) { + base_bindings = static_cast(stack_alloc(ard_record_count * sizeof(BindingInfo))); + if (base_bindings == nullptr) + throw std::bad_alloc(); + std::fill(base_bindings, base_bindings + ard_record_count, BindingInfo{}); + } + + // Prepare the base binding info for all columns before iterating over the row set. + for (std::size_t column_num = 1; column_num <= ard_record_count; ++column_num) { // Skipping the bookmark (0) column. + const auto column_idx = column_num - 1; + const auto & ard_record = ard_records[column_num]; + auto & base_binding = base_bindings[column_idx]; + + base_binding.c_type = ard_record.getAttrAs(SQL_DESC_CONCISE_TYPE, SQL_C_DEFAULT); + base_binding.value = ard_record.getAttrAs(SQL_DESC_DATA_PTR, 0); + base_binding.value_max_size = ard_record.getAttrAs(SQL_DESC_OCTET_LENGTH, 0); + base_binding.value_size = ard_record.getAttrAs(SQL_DESC_OCTET_LENGTH_PTR, 0); + base_binding.indicator = ard_record.getAttrAs(SQL_DESC_INDICATOR_PTR, 0); + } + for (std::size_t row_idx = 0; row_idx < rows_fetched; ++row_idx) { for (std::size_t column_num = 1; column_num <= ard_record_count; ++column_num) { // Skipping the bookmark (0) column. const auto column_idx = column_num - 1; - const auto & ard_record = ard_records[column_num]; - - const auto * data_ptr = ard_record.getAttrAs(SQL_DESC_DATA_PTR, 0); - const auto * sz_ptr = ard_record.getAttrAs(SQL_DESC_OCTET_LENGTH_PTR, 0); - const auto * ind_ptr = ard_record.getAttrAs(SQL_DESC_INDICATOR_PTR, 0); - + auto & base_binding = base_bindings[column_idx]; SQLRETURN code = SQL_SUCCESS; - if (data_ptr || sz_ptr || ind_ptr) { // Only if the column is bound... - BindingInfo binding_info; - binding_info.c_type = ard_record.getAttrAs(SQL_DESC_CONCISE_TYPE, SQL_C_DEFAULT); - binding_info.value_max_size = ard_record.getAttrAs(SQL_DESC_OCTET_LENGTH, 0); - - const auto next_value_ptr_increment = (bind_type == SQL_BIND_BY_COLUMN ? binding_info.value_max_size : bind_type); + if ( + base_binding.value || + base_binding.value_size || + base_binding.indicator + ) { // Only if the column is bound... + const auto next_value_ptr_increment = (bind_type == SQL_BIND_BY_COLUMN ? base_binding.value_max_size : bind_type); const auto next_sz_ind_ptr_increment = (bind_type == SQL_BIND_BY_COLUMN ? sizeof(SQLLEN) : bind_type); - binding_info.value = (SQLPOINTER)(data_ptr ? ((char *)(data_ptr) + row_idx * next_value_ptr_increment + bind_offset) : 0); - binding_info.value_size = (SQLLEN *)(sz_ptr ? ((char *)(sz_ptr) + row_idx * next_sz_ind_ptr_increment + bind_offset) : 0); - binding_info.indicator = (SQLLEN *)(ind_ptr ? ((char *)(ind_ptr) + row_idx * next_sz_ind_ptr_increment + bind_offset) : 0); + BindingInfo binding_info; + binding_info.c_type = base_binding.c_type; + binding_info.value_max_size = base_binding.value_max_size; + binding_info.value = (SQLPOINTER)(base_binding.value ? ((char *)(base_binding.value) + row_idx * next_value_ptr_increment + bind_offset) : 0); + binding_info.value_size = (SQLLEN *)(base_binding.value_size ? ((char *)(base_binding.value_size) + row_idx * next_sz_ind_ptr_increment + bind_offset) : 0); + binding_info.indicator = (SQLLEN *)(base_binding.indicator ? ((char *)(base_binding.indicator) + row_idx * next_sz_ind_ptr_increment + bind_offset) : 0); // TODO: fill per-row and per-column diagnostics on (some soft?) errors. const auto code = fillBinding( diff --git a/driver/utils/utils.h b/driver/utils/utils.h index 662fcc089..c08d90708 100755 --- a/driver/utils/utils.h +++ b/driver/utils/utils.h @@ -34,6 +34,12 @@ #include +#ifdef _win_ +# define stack_alloc _alloca +#else +# define stack_alloc alloca +#endif + class Environment; class Connection; class Descriptor; From c6ae7b56d661633739db71ca02d5ff6758fc0b79 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Thu, 25 Jun 2020 21:07:58 +0400 Subject: [PATCH 13/16] Fix macOS CI builds --- test/run_clickhouse_macos.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/run_clickhouse_macos.sh b/test/run_clickhouse_macos.sh index 6f3e1bb0f..8a3128872 100755 --- a/test/run_clickhouse_macos.sh +++ b/test/run_clickhouse_macos.sh @@ -29,6 +29,7 @@ echo " ${CLICKHOUSE_DATADIR}/tmp/ ${CLICKHOUSE_DATADIR}/user_files/ ${CLICKHOUSE_DATADIR}/format_schemas/ + ${CLICKHOUSE_DATADIR}/access/ " > $CLICKHOUSE_CONFDIR/conf.d/paths.xml From 26f8058a0e731faf9411543d8434d651ac0a363f Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Mon, 29 Jun 2020 22:37:42 +0400 Subject: [PATCH 14/16] Preserve the prepared state of the query when closing the cursor --- driver/statement.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/driver/statement.cpp b/driver/statement.cpp index 81c5e8be7..3b27c23ae 100644 --- a/driver/statement.cpp +++ b/driver/statement.cpp @@ -30,6 +30,8 @@ const TypeInfo & Statement::getTypeInfo(const std::string & type_name, const std void Statement::prepareQuery(const std::string & q) { closeCursor(); + + is_prepared = false; query = q; processEscapeSequences(); extractParametersinfo(); @@ -390,11 +392,8 @@ void Statement::closeCursor() { in = nullptr; response.reset(); - parameters.clear(); - query.clear(); is_executed = false; is_forward_executed = false; - is_prepared = false; } void Statement::resetColBindings() { From f50b11a4025ee2b237891c9428de8eb168261aa1 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Fri, 3 Jul 2020 17:51:10 +0400 Subject: [PATCH 15/16] Add comments Reduce row set sizes to reduce test running time --- driver/test/column_bindings_it.cpp | 45 ++++++++++++++++++++++++++---- driver/utils/type_info.h | 8 +++--- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/driver/test/column_bindings_it.cpp b/driver/test/column_bindings_it.cpp index e82ce1737..96357527d 100755 --- a/driver/test/column_bindings_it.cpp +++ b/driver/test/column_bindings_it.cpp @@ -27,6 +27,10 @@ struct FixedStringBuffer { }; TEST_P(ColumnArrayBindingsTest, ColumnWise) { + // The test executes a query and bings array buffers (column-wise), then fetches and tests values in that buffers. + + // A query that generates columns with increasing values and strings of increasing/cycling lengths, + // the resulting values are predictable and will be tested after SQLFetch calls. const std::size_t total_rows_expected = std::get<1>(GetParam()); const std::string query_orig = R"SQL( SELECT @@ -42,6 +46,8 @@ FROM numbers( const auto query = fromUTF8(query_orig); auto * query_wptr = const_cast(query.c_str()); + // Prepare, execute, and set attribues on te statement. + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); @@ -64,6 +70,9 @@ FROM numbers( return reinterpret_cast(adjsut_ptr(ptr)); }; + // Define the vectors underlying storage of whose will be used as bound buffers (both, value and size/indicator buffers), + // and bind them. + std::vector col1(array_size); std::vector col1_ind(array_size); @@ -151,7 +160,9 @@ FROM numbers( std::size_t total_rows = 0; while (true) { - // Must not reallocate the underlying buffers. + // Fill the bound buffers with a specific value, that will be considered as an "original garbage" here by us. + // Must not reallocate the underlying buffers, since pointers to them are stored as binding addresses + // by the driver during SQLBindCol calls. std::fill(col1.begin(), col1.end(), -123); std::fill(col1_ind.begin(), col1_ind.end(), -123); @@ -171,6 +182,9 @@ FROM numbers( std::fill(col6.begin(), col6.end(), -123); std::fill(col6_ind.begin(), col6_ind.end(), -123); + // Perform a fetch and verify everything: the number of processed rows, values in the bound buffers, + // values in size/indicator buffers etc. + rows_processed = 0; const SQLRETURN rc = SQLFetch(hstmt); @@ -190,6 +204,9 @@ FROM numbers( ASSERT_LE(rows_processed, array_size); for (std::size_t i = 0; i < array_size; ++i) { + // Check the fetched values, and also make sure that anything that should not be modified is not modified. + // The values can be deterministically deduced and are in sync with the query we executed. + if (i < rows_processed) { const std::int64_t number = total_rows + i; const auto number_str = std::to_string(number); @@ -255,6 +272,10 @@ FROM numbers( } TEST_P(ColumnArrayBindingsTest, RowWise) { + // The test executes a query and bings array buffers (row-wise), then fetches and tests values in that buffers. + + // A query that generates columns with increasing values and strings of increasing/cycling lengths, + // the resulting values are predictable and will be tested after SQLFetch calls. const std::size_t total_rows_expected = std::get<1>(GetParam()); const std::string query_orig = R"SQL( SELECT @@ -270,6 +291,8 @@ FROM numbers( const auto query = fromUTF8(query_orig); auto * query_wptr = const_cast(query.c_str()); + // Prepare, execute, and set attribues on te statement. + ODBC_CALL_ON_STMT_THROW(hstmt, SQLPrepare(hstmt, query_wptr, SQL_NTS)); ODBC_CALL_ON_STMT_THROW(hstmt, SQLExecute(hstmt)); @@ -312,6 +335,10 @@ FROM numbers( return reinterpret_cast(adjsut_ptr(ptr)); }; + // Define the vector underlying storage of which will be used as bound buffers (both, value and size/indicator buffers), + // and bind them, using pinters to each struct member. The value of SQL_ATTR_ROW_BIND_TYPE will tell the driver + // what offset to use to calculate the next elemet location in each bound buffer. + std::vector buf(array_size); ODBC_CALL_ON_STMT_THROW(hstmt, @@ -383,9 +410,14 @@ FROM numbers( std::size_t total_rows = 0; while (true) { - // Must not reallocate the underlying buffer. + // Fill the bound buffer with a specific value, that will be considered as an "original garbage" here by us. + // Must not reallocate the underlying buffer, since the pointer to it is stored as binding addresses + // by the driver during SQLBindCol calls. std::fill(buf.begin(), buf.end(), Bindings{}); + // Perform a fetch and verify everything: the number of processed rows, values in the bound buffers, + // values in size/indicator buffers etc. + rows_processed = 0; const SQLRETURN rc = SQLFetch(hstmt); @@ -405,6 +437,9 @@ FROM numbers( ASSERT_LE(rows_processed, array_size); for (std::size_t i = 0; i < array_size; ++i) { + // Check the fetched values, and also make sure that anything that should not be modified is not modified. + // The values can be deterministically deduced and are in sync with the query we executed. + if (i < rows_processed) { const std::int64_t number = total_rows + i; const auto number_str = std::to_string(number); @@ -471,9 +506,9 @@ FROM numbers( INSTANTIATE_TEST_SUITE_P(ArrayBindings, ColumnArrayBindingsTest, ::testing::Combine( - ::testing::Values(0, 1, 1234), // Binding offset. - ::testing::Values(1, 2, 10, 75, 377, 4289), // Result set sizes. - ::testing::Values(1, 2, 3, 5, 10, 30, 47, 111, 500, 1000) // Row set sizes. + ::testing::Values(0, 1, 1234), // Binding offset. + ::testing::Values(1, 2, 10, 75, 377, 4289), // Result set sizes. + ::testing::Values(1, 2, 3, 10, 47, 111, 500, 1000) // Row set sizes. ), [] (const auto & param_info) { return ( diff --git a/driver/utils/type_info.h b/driver/utils/type_info.h index f0d17374c..f40fefb9f 100755 --- a/driver/utils/type_info.h +++ b/driver/utils/type_info.h @@ -1814,7 +1814,7 @@ namespace value_manip { struct from_value { static inline SQLRETURN convert(const SourceType & src, BindingInfo & dest) { if (dest.indicator && dest.indicator != dest.value_size) - *dest.indicator = 0; // Value is not null here... + *dest.indicator = 0; // (Null) indicator pointer of the binding. Value is not null here so we store 0 in it. if constexpr (std::is_same_v) { return fillOutputPOD(src, dest.value, dest.value_size); @@ -1838,7 +1838,7 @@ namespace value_manip { template static inline SQLRETURN convert(const SourceType & src, BindingInfo & dest, ConversionContext && context) { if (dest.indicator && dest.indicator != dest.value_size) - *dest.indicator = 0; // Value is not null here... + *dest.indicator = 0; // (Null) indicator pointer of the binding. Value is not null here so we store 0 in it. if constexpr (std::is_same_v) { return fillOutputString(src, dest.value, dest.value_max_size, dest.value_size, true, std::forward(context)); @@ -1865,7 +1865,7 @@ namespace value_manip { template static inline SQLRETURN convert(const SourceType & src, BindingInfo & dest, ConversionContext && context) { if (dest.indicator && dest.indicator != dest.value_size) - *dest.indicator = 0; // Value is not null here... + *dest.indicator = 0; // (Null) indicator pointer of the binding. Value is not null here so we store 0 in it. if constexpr (std::is_same_v) { return fillOutputString(src, dest.value, dest.value_max_size, dest.value_size, true, std::forward(context)); @@ -1891,7 +1891,7 @@ namespace value_manip { struct from_value { static inline SQLRETURN convert(const SourceType & src, BindingInfo & dest) { if (dest.indicator && dest.indicator != dest.value_size) - *dest.indicator = 0; // Value is not null here... + *dest.indicator = 0; // (Null) indicator pointer of the binding. Value is not null here so we store 0 in it. if constexpr (std::is_same_v) { if ( From 43d722bf13a72db39146fa65a5c8a333c8583b06 Mon Sep 17 00:00:00 2001 From: Denis Glazachev Date: Thu, 9 Jul 2020 04:10:55 +0400 Subject: [PATCH 16/16] Speed up tests a bit --- driver/test/column_bindings_it.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/driver/test/column_bindings_it.cpp b/driver/test/column_bindings_it.cpp index 96357527d..b86fa3c67 100755 --- a/driver/test/column_bindings_it.cpp +++ b/driver/test/column_bindings_it.cpp @@ -506,9 +506,9 @@ FROM numbers( INSTANTIATE_TEST_SUITE_P(ArrayBindings, ColumnArrayBindingsTest, ::testing::Combine( - ::testing::Values(0, 1, 1234), // Binding offset. - ::testing::Values(1, 2, 10, 75, 377, 4289), // Result set sizes. - ::testing::Values(1, 2, 3, 10, 47, 111, 500, 1000) // Row set sizes. + ::testing::Values(0, 1, 1234), // Binding offset. + ::testing::Values(1, 2, 10, 75, 377, 4289), // Result set sizes. + ::testing::Values(1, 2, 10, 47, 111, 500, 1000) // Row set sizes. ), [] (const auto & param_info) { return (