diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index bce7c10e82b..df0ecf05713 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -271,8 +271,16 @@ SQLRETURN SQLGetConnectAttr(SQLHDBC conn, SQLINTEGER attribute, SQLPOINTER value << ", attribute: " << attribute << ", value_ptr: " << value_ptr << ", buffer_length: " << buffer_length << ", string_length_ptr: " << static_cast(string_length_ptr); - // GH-47708 TODO: Implement SQLGetConnectAttr - return SQL_INVALID_HANDLE; + + using arrow::flight::sql::odbc::Connection; + using ODBC::ODBCConnection; + + return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { + const bool is_unicode = true; + ODBCConnection* connection = reinterpret_cast(conn); + return connection->GetConnectAttr(attribute, value_ptr, buffer_length, + string_length_ptr, is_unicode); + }); } SQLRETURN SQLSetConnectAttr(SQLHDBC conn, SQLINTEGER attr, SQLPOINTER value_ptr, @@ -280,8 +288,16 @@ SQLRETURN SQLSetConnectAttr(SQLHDBC conn, SQLINTEGER attr, SQLPOINTER value_ptr, ARROW_LOG(DEBUG) << "SQLSetConnectAttrW called with conn: " << conn << ", attr: " << attr << ", value_ptr: " << value_ptr << ", value_len: " << value_len; - // GH-47708 TODO: Implement SQLSetConnectAttr - return SQL_INVALID_HANDLE; + + using arrow::flight::sql::odbc::Connection; + using ODBC::ODBCConnection; + + return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() { + const bool is_unicode = true; + ODBCConnection* connection = reinterpret_cast(conn); + connection->SetConnectAttr(attr, value_ptr, value_len, is_unicode); + return SQL_SUCCESS; + }); } SQLRETURN SQLDriverConnect(SQLHDBC conn, SQLHWND window_handle, diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc index c0a55840d56..186172d4dcf 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.cc @@ -578,58 +578,58 @@ void ODBCConnection::SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, } } -void ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, - SQLINTEGER buffer_length, SQLINTEGER* output_length, - bool is_unicode) { +SQLRETURN ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, + SQLINTEGER buffer_length, + SQLINTEGER* output_length, bool is_unicode) { boost::optional spi_attribute; switch (attribute) { // Internal connection attributes -#ifdef SQL_ATR_ASYNC_DBC_EVENT +#ifdef SQL_ATTR_ASYNC_DBC_EVENT case SQL_ATTR_ASYNC_DBC_EVENT: GetAttribute(static_cast(NULL), value, buffer_length, output_length); - return; + return SQL_SUCCESS; #endif #ifdef SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE case SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE: GetAttribute(static_cast(SQL_ASYNC_DBC_ENABLE_OFF), value, buffer_length, output_length); - return; + return SQL_SUCCESS; #endif -#ifdef SQL_ATTR_ASYNC_PCALLBACK +#ifdef SQL_ATTR_ASYNC_DBC_PCALLBACK case SQL_ATTR_ASYNC_DBC_PCALLBACK: GetAttribute(static_cast(NULL), value, buffer_length, output_length); - return; + return SQL_SUCCESS; #endif #ifdef SQL_ATTR_ASYNC_DBC_PCONTEXT case SQL_ATTR_ASYNC_DBC_PCONTEXT: GetAttribute(static_cast(NULL), value, buffer_length, output_length); - return; + return SQL_SUCCESS; #endif case SQL_ATTR_ASYNC_ENABLE: GetAttribute(static_cast(SQL_ASYNC_ENABLE_OFF), value, buffer_length, output_length); - return; + return SQL_SUCCESS; case SQL_ATTR_AUTO_IPD: GetAttribute(static_cast(SQL_FALSE), value, buffer_length, output_length); - return; + return SQL_SUCCESS; case SQL_ATTR_AUTOCOMMIT: GetAttribute(static_cast(SQL_AUTOCOMMIT_ON), value, buffer_length, output_length); - return; + return SQL_SUCCESS; #ifdef SQL_ATTR_DBC_INFO_TOKEN case SQL_ATTR_DBC_INFO_TOKEN: throw DriverException("Cannot read set-only attribute", "HY092"); #endif case SQL_ATTR_ENLIST_IN_DTC: GetAttribute(static_cast(NULL), value, buffer_length, output_length); - return; + return SQL_SUCCESS; case SQL_ATTR_ODBC_CURSORS: // DM-only. throw DriverException("Invalid attribute", "HY092"); case SQL_ATTR_QUIET_MODE: GetAttribute(static_cast(NULL), value, buffer_length, output_length); - return; + return SQL_SUCCESS; case SQL_ATTR_TRACE: // DM-only throw DriverException("Invalid attribute", "HY092"); case SQL_ATTR_TRACEFILE: @@ -639,7 +639,7 @@ void ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, case SQL_ATTR_TRANSLATE_OPTION: throw DriverException("Optional feature not supported.", "HYC00"); case SQL_ATTR_TXN_ISOLATION: - throw DriverException("Optional feature not supported.", "HCY00"); + throw DriverException("Optional feature not supported.", "HYC00"); case SQL_ATTR_CURRENT_CATALOG: { const auto& catalog = spi_connection_->GetAttribute(Connection::CURRENT_CATALOG); @@ -647,9 +647,8 @@ void ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, throw DriverException("Optional feature not supported.", "HYC00"); } const std::string& info_value = boost::get(*catalog); - GetStringAttribute(is_unicode, info_value, true, value, buffer_length, - output_length, GetDiagnostics()); - return; + return GetStringAttribute(is_unicode, info_value, true, value, buffer_length, + output_length, GetDiagnostics()); } // These all are uint32_t attributes. @@ -678,6 +677,7 @@ void ODBCConnection::GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, GetAttribute(static_cast(boost::get(*spi_attribute)), value, buffer_length, output_length); + return SQL_SUCCESS; } void ODBCConnection::Disconnect() { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h index 8157c2f5f94..46aac616f21 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_connection.h @@ -56,8 +56,9 @@ class ODBCConnection : public ODBCHandle { SQLSMALLINT* output_length, bool is_unicode); void SetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER string_length, bool isUnicode); - void GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, SQLINTEGER buffer_length, - SQLINTEGER* output_length, bool is_unicode); + SQLRETURN GetConnectAttr(SQLINTEGER attribute, SQLPOINTER value, + SQLINTEGER buffer_length, SQLINTEGER* output_length, + bool is_unicode); ~ODBCConnection() = default; diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt index 4bc240637e7..f82f5416957 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt @@ -34,6 +34,7 @@ add_arrow_test(flight_sql_odbc_test SOURCES odbc_test_suite.cc odbc_test_suite.h + connection_attr_test.cc connection_test.cc # Enable Protobuf cleanup after test execution # GH-46889: move protobuf_test_util to a more common location diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_attr_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_attr_test.cc new file mode 100644 index 00000000000..ed1db2560ca --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_attr_test.cc @@ -0,0 +1,362 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h" + +#include "arrow/flight/sql/odbc/odbc_impl/platform.h" + +#include +#include +#include + +#include + +namespace arrow::flight::sql::odbc { + +template +class ConnectionAttributeTest : public T {}; + +using TestTypes = + ::testing::Types; +TYPED_TEST_SUITE(ConnectionAttributeTest, TestTypes); + +#ifdef SQL_ATTR_ASYNC_DBC_EVENT +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrAsyncDbcEventUnsupported) { + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_EVENT, 0, 0)); + // Driver Manager on Windows returns error code HY118 + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY118); +} +#endif + +#ifdef SQL_ATTR_ASYNC_ENABLE +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrAyncEnableUnsupported) { + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_ENABLE, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} +#endif + +#ifdef SQL_ATTR_ASYNC_DBC_PCALLBACK +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrAyncDbcPcCallbackUnsupported) { + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_PCALLBACK, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} +#endif + +#ifdef SQL_ATTR_ASYNC_DBC_PCONTEXT +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrAyncDbcPcContextUnsupported) { + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_PCONTEXT, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} +#endif + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrAutoIpdReadOnly) { + // Verify read-only attribute cannot be set + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_AUTO_IPD, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY092); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrConnectionDeadReadOnly) { + // Verify read-only attribute cannot be set + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_CONNECTION_DEAD, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY092); +} + +#ifdef SQL_ATTR_DBC_INFO_TOKEN +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrDbcInfoTokenUnsupported) { + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_DBC_INFO_TOKEN, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} +#endif + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrEnlistInDtcUnsupported) { + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_ENLIST_IN_DTC, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrOdbcCursorsDMOnly) { + this->AllocEnvConnHandles(); + + // Verify DM-only attribute is settable via Driver Manager + ASSERT_EQ(SQL_SUCCESS, + SQLSetConnectAttr(this->conn, SQL_ATTR_ODBC_CURSORS, + reinterpret_cast(SQL_CUR_USE_DRIVER), 0)); + + std::string connect_str = this->GetConnectionString(); + this->ConnectWithString(connect_str); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrQuietModeReadOnly) { + // Verify read-only attribute cannot be set + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_QUIET_MODE, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY092); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrTraceDMOnly) { + // Verify DM-only attribute is settable via Driver Manager + ASSERT_EQ(SQL_SUCCESS, + SQLSetConnectAttr(this->conn, SQL_ATTR_TRACE, + reinterpret_cast(SQL_OPT_TRACE_OFF), 0)); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrTracefileDMOnly) { + // Verify DM-only attribute is handled by Driver Manager + + // Use placeholder value as we want the call to fail, or else + // the driver manager will produce a trace file. + std::wstring trace_file = L"invalid/file/path"; + std::vector trace_file0(trace_file.begin(), trace_file.end()); + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_TRACEFILE, &trace_file0[0], + static_cast(trace_file0.size()))); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY000); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrTranslateLabDMOnly) { + // Verify DM-only attribute is handled by Driver Manager + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_TRANSLATE_LIB, 0, 0)); + // Checks for invalid argument return error + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY024); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrTranslateOptionUnsupported) { + ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_TRANSLATE_OPTION, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrTxnIsolationUnsupported) { + ASSERT_EQ(SQL_ERROR, + SQLSetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, + reinterpret_cast(SQL_TXN_READ_UNCOMMITTED), 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} + +#ifdef SQL_ATTR_DBC_INFO_TOKEN +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrDbcInfoTokenSetOnly) { + // Verify that set-only attribute cannot be read + SQLPOINTER ptr = NULL; + ASSERT_EQ(SQL_ERROR, SQLGetConnectAttr(this->conn, SQL_ATTR_DBC_INFO_TOKEN, ptr, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY092); +} +#endif + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrOdbcCursorsDMOnly) { + // Verify that DM-only attribute is handled by driver manager + SQLULEN cursor_attr; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_ODBC_CURSORS, &cursor_attr, 0, 0)); + EXPECT_EQ(SQL_CUR_USE_DRIVER, cursor_attr); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrTraceDMOnly) { + // Verify that DM-only attribute is handled by driver manager + SQLUINTEGER trace; + ASSERT_EQ(SQL_SUCCESS, SQLGetConnectAttr(this->conn, SQL_ATTR_TRACE, &trace, 0, 0)); + EXPECT_EQ(SQL_OPT_TRACE_OFF, trace); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrTraceFileDMOnly) { + // Verify that DM-only attribute is handled by driver manager + SQLWCHAR out_str[kOdbcBufferSize]; + SQLINTEGER out_str_len; + ASSERT_EQ(SQL_SUCCESS, SQLGetConnectAttr(this->conn, SQL_ATTR_TRACEFILE, out_str, + kOdbcBufferSize, &out_str_len)); + // Length is returned in bytes for SQLGetConnectAttr, + // we want the number of characters + out_str_len /= arrow::flight::sql::odbc::GetSqlWCharSize(); + std::string out_connection_string = + ODBC::SqlWcharToString(out_str, static_cast(out_str_len)); + EXPECT_FALSE(out_connection_string.empty()); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrTranslateLibUnsupported) { + SQLWCHAR out_str[kOdbcBufferSize]; + SQLINTEGER out_str_len; + ASSERT_EQ(SQL_ERROR, SQLGetConnectAttr(this->conn, SQL_ATTR_TRANSLATE_LIB, out_str, + kOdbcBufferSize, &out_str_len)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrTranslateOptionUnsupported) { + SQLINTEGER option; + ASSERT_EQ(SQL_ERROR, + SQLGetConnectAttr(this->conn, SQL_ATTR_TRANSLATE_OPTION, &option, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrTxnIsolationUnsupported) { + SQLINTEGER isolation; + ASSERT_EQ(SQL_ERROR, + SQLGetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, &isolation, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHYC00); +} + +#ifdef SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE +TYPED_TEST(ConnectionAttributeTest, + TestSQLGetConnectAttrAsyncDbcFunctionsEnableUnsupported) { + // Verifies that the Windows driver manager returns HY114 for unsupported functionality + SQLUINTEGER enable; + ASSERT_EQ(SQL_ERROR, SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE, + &enable, 0, 0)); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY114); +} +#endif + +// Tests for supported attributes + +#ifdef SQL_ATTR_ASYNC_DBC_EVENT +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrAsyncDbcEventDefault) { + SQLPOINTER ptr = NULL; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_EVENT, ptr, 0, 0)); + EXPECT_EQ(reinterpret_cast(NULL), ptr); +} +#endif + +#ifdef SQL_ATTR_ASYNC_DBC_PCALLBACK +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrAsyncDbcPcallbackDefault) { + SQLPOINTER ptr = NULL; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_PCALLBACK, ptr, 0, 0)); + EXPECT_EQ(reinterpret_cast(NULL), ptr); +} +#endif + +#ifdef SQL_ATTR_ASYNC_DBC_PCONTEXT +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrAsyncDbcPcontextDefault) { + SQLPOINTER ptr = NULL; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_PCONTEXT, ptr, 0, 0)); + EXPECT_EQ(reinterpret_cast(NULL), ptr); +} +#endif + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrAsyncEnableDefault) { + SQLULEN enable; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_ASYNC_ENABLE, &enable, 0, 0)); + EXPECT_EQ(SQL_ASYNC_ENABLE_OFF, enable); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrAutoIpdDefault) { + SQLUINTEGER ipd; + ASSERT_EQ(SQL_SUCCESS, SQLGetConnectAttr(this->conn, SQL_ATTR_AUTO_IPD, &ipd, 0, 0)); + EXPECT_EQ(static_cast(SQL_FALSE), ipd); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrAutocommitDefault) { + SQLUINTEGER auto_commit; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_AUTOCOMMIT, &auto_commit, 0, 0)); + EXPECT_EQ(SQL_AUTOCOMMIT_ON, auto_commit); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrEnlistInDtcDefault) { + SQLPOINTER ptr = NULL; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_ENLIST_IN_DTC, ptr, 0, 0)); + EXPECT_EQ(reinterpret_cast(NULL), ptr); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLGetConnectAttrQuietModeDefault) { + HWND ptr = NULL; + ASSERT_EQ(SQL_SUCCESS, SQLGetConnectAttr(this->conn, SQL_ATTR_QUIET_MODE, ptr, 0, 0)); + EXPECT_EQ(reinterpret_cast(NULL), ptr); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrAccessModeValid) { + // The driver always returns SQL_MODE_READ_WRITE + + // Check default value first + SQLUINTEGER mode = -1; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_ACCESS_MODE, &mode, 0, 0)); + EXPECT_EQ(SQL_MODE_READ_WRITE, mode); + + ASSERT_EQ(SQL_SUCCESS, + SQLSetConnectAttr(this->conn, SQL_ATTR_ACCESS_MODE, + reinterpret_cast(SQL_MODE_READ_WRITE), 0)); + + mode = -1; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_ACCESS_MODE, &mode, 0, 0)); + EXPECT_EQ(SQL_MODE_READ_WRITE, mode); + + // Attempt to set to SQL_MODE_READ_ONLY, driver should return warning and not error + EXPECT_EQ(SQL_SUCCESS_WITH_INFO, + SQLSetConnectAttr(this->conn, SQL_ATTR_ACCESS_MODE, + reinterpret_cast(SQL_MODE_READ_ONLY), 0)); + + // Verify warning status + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorState01S02); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrConnectionTimeoutValid) { + // Check default value first + SQLUINTEGER timeout = -1; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_CONNECTION_TIMEOUT, &timeout, 0, 0)); + EXPECT_EQ(0, timeout); + + ASSERT_EQ(SQL_SUCCESS, SQLSetConnectAttr(this->conn, SQL_ATTR_CONNECTION_TIMEOUT, + reinterpret_cast(42), 0)); + + timeout = -1; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_CONNECTION_TIMEOUT, &timeout, 0, 0)); + EXPECT_EQ(42, timeout); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrLoginTimeoutValid) { + // Check default value first + SQLUINTEGER timeout = -1; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_LOGIN_TIMEOUT, &timeout, 0, 0)); + EXPECT_EQ(0, timeout); + + ASSERT_EQ(SQL_SUCCESS, SQLSetConnectAttr(this->conn, SQL_ATTR_LOGIN_TIMEOUT, + reinterpret_cast(42), 0)); + + timeout = -1; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_LOGIN_TIMEOUT, &timeout, 0, 0)); + EXPECT_EQ(42, timeout); +} + +TYPED_TEST(ConnectionAttributeTest, TestSQLSetConnectAttrPacketSizeValid) { + // The driver always returns 0. PACKET_SIZE value is unused by the driver. + + // Check default value first + SQLUINTEGER size = -1; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_PACKET_SIZE, &size, 0, 0)); + EXPECT_EQ(0, size); + + ASSERT_EQ(SQL_SUCCESS, SQLSetConnectAttr(this->conn, SQL_ATTR_PACKET_SIZE, + reinterpret_cast(0), 0)); + + size = -1; + ASSERT_EQ(SQL_SUCCESS, + SQLGetConnectAttr(this->conn, SQL_ATTR_PACKET_SIZE, &size, 0, 0)); + EXPECT_EQ(0, size); + + // Attempt to set to non-zero value, driver should return warning and not error + EXPECT_EQ(SQL_SUCCESS_WITH_INFO, SQLSetConnectAttr(this->conn, SQL_ATTR_PACKET_SIZE, + reinterpret_cast(2), 0)); + + // Verify warning status + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorState01S02); +} + +} // namespace arrow::flight::sql::odbc