This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 95cdc0c11d GH-47708: [C++][FlightRPC] Connection Attribute Support for
ODBC (#47772)
95cdc0c11d is described below
commit 95cdc0c11d20fab563416afeba055fe43113b75b
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Sat Nov 29 23:03:55 2025 -0800
GH-47708: [C++][FlightRPC] Connection Attribute Support for ODBC (#47772)
### Rationale for this change
Add connection attribute support for ODBC driver.
### What changes are included in this PR?
- Implementation of `SQLGetConnectAttr` and `SQLSetConnectAttr` to get and
set connection attributes
- Tests
### Are these changes tested?
Tested on local MSVC
### Are there any user-facing changes?
No
* GitHub Issue: #47708
Authored-by: Alina (Xi) Li <[email protected]>
Signed-off-by: David Li <[email protected]>
---
cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 13 +-
.../flight/sql/odbc/odbc_impl/odbc_connection.cc | 36 +-
.../flight/sql/odbc/odbc_impl/odbc_connection.h | 5 +-
cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt | 3 +-
.../flight/sql/odbc/tests/connection_attr_test.cc | 362 +++++++++++++++++++++
5 files changed, 395 insertions(+), 24 deletions(-)
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index 19c6e62eba..fc50e4a816 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -714,8 +714,15 @@ SQLRETURN SQLGetConnectAttr(SQLHDBC conn, SQLINTEGER
attribute, SQLPOINTER value
<< ", attribute: " << attribute << ", value_ptr: " <<
value_ptr
<< ", buffer_length: " << buffer_length << ",
string_length_ptr: "
<< static_cast<const void*>(string_length_ptr);
- // GH-47708 TODO: Implement SQLGetConnectAttr
- return SQL_INVALID_HANDLE;
+
+ using ODBC::ODBCConnection;
+
+ return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {
+ const bool is_unicode = true;
+ ODBCConnection* connection = reinterpret_cast<ODBCConnection*>(conn);
+ return connection->GetConnectAttr(attribute, value_ptr, buffer_length,
+ string_length_ptr, is_unicode);
+ });
}
SQLRETURN SQLSetConnectAttr(SQLHDBC conn, SQLINTEGER attr, SQLPOINTER
value_ptr,
@@ -723,7 +730,7 @@ 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: Add tests for SQLSetConnectAttr
+
using ODBC::ODBCConnection;
return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {
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 ead2beada4..ce6ec67f23 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
@@ -528,58 +528,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<Connection::Attribute> 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<SQLPOINTER>(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<SQLUINTEGER>(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<SQLPOINTER>(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<SQLPOINTER>(NULL), value, buffer_length,
output_length);
- return;
+ return SQL_SUCCESS;
#endif
case SQL_ATTR_ASYNC_ENABLE:
GetAttribute(static_cast<SQLULEN>(SQL_ASYNC_ENABLE_OFF), value,
buffer_length,
output_length);
- return;
+ return SQL_SUCCESS;
case SQL_ATTR_AUTO_IPD:
GetAttribute(static_cast<SQLUINTEGER>(SQL_FALSE), value, buffer_length,
output_length);
- return;
+ return SQL_SUCCESS;
case SQL_ATTR_AUTOCOMMIT:
GetAttribute(static_cast<SQLUINTEGER>(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<SQLPOINTER>(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<SQLPOINTER>(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:
@@ -589,7 +589,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);
@@ -597,9 +597,8 @@ void ODBCConnection::GetConnectAttr(SQLINTEGER attribute,
SQLPOINTER value,
throw DriverException("Optional feature not supported.", "HYC00");
}
const std::string& info_value = boost::get<std::string>(*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.
@@ -628,6 +627,7 @@ void ODBCConnection::GetConnectAttr(SQLINTEGER attribute,
SQLPOINTER value,
GetAttribute(static_cast<SQLUINTEGER>(boost::get<uint32_t>(*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 2e5ab57ad4..c2fc190da8 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
@@ -57,8 +57,9 @@ class ODBCConnection : public ODBCHandle<ODBCConnection> {
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 7be318039d..9efae5c2a9 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
+++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
@@ -34,8 +34,9 @@ add_arrow_test(flight_sql_odbc_test
SOURCES
odbc_test_suite.cc
odbc_test_suite.h
- statement_attr_test.cc
+ connection_attr_test.cc
connection_test.cc
+ statement_attr_test.cc
# Enable Protobuf cleanup after test execution
# GH-46889: move protobuf_test_util to a more common location
../../../../engine/substrait/protobuf_test_util.cc
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 0000000000..ed1db2560c
--- /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 <sql.h>
+#include <sqltypes.h>
+#include <sqlucode.h>
+
+#include <gtest/gtest.h>
+
+namespace arrow::flight::sql::odbc {
+
+template <typename T>
+class ConnectionAttributeTest : public T {};
+
+using TestTypes =
+ ::testing::Types<FlightSQLODBCMockTestBase, FlightSQLODBCRemoteTestBase>;
+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<SQLPOINTER>(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<SQLPOINTER>(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<SQLWCHAR> trace_file0(trace_file.begin(), trace_file.end());
+ ASSERT_EQ(SQL_ERROR, SQLSetConnectAttr(this->conn, SQL_ATTR_TRACEFILE,
&trace_file0[0],
+
static_cast<SQLINTEGER>(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<SQLPOINTER>(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<SQLSMALLINT>(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<SQLPOINTER>(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<SQLPOINTER>(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<SQLPOINTER>(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<SQLUINTEGER>(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<SQLPOINTER>(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<SQLPOINTER>(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<SQLPOINTER>(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<SQLPOINTER>(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<SQLPOINTER>(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<SQLPOINTER>(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<SQLPOINTER>(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<SQLPOINTER>(2), 0));
+
+ // Verify warning status
+ VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorState01S02);
+}
+
+} // namespace arrow::flight::sql::odbc