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 56e38362de GH-46098 : [C++][FlightRPC] ODBC Environment Attribute
Implementation (#47760)
56e38362de is described below
commit 56e38362def18a5822e137c14b3d10bf7516a5e2
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Thu Oct 23 19:22:55 2025 -0700
GH-46098 : [C++][FlightRPC] ODBC Environment Attribute Implementation
(#47760)
### Rationale for this change
ODBC driver needs to set and get environment attributes, such as ODBC
driver version.
### What changes are included in this PR?
- Implement SQLGetEnvAttr and SQLSetEnvAttr APIs for retrieving and setting
environment attribute values
- Tests
### Are these changes tested?
Tested locally on Windows MSVC.
Will be tested in CI after https://github.com/apache/arrow/pull/47689 is
merged
### Are there any user-facing changes?
No
* GitHub Issue: #46098
Lead-authored-by: Alina (Xi) Li <[email protected]>
Co-authored-by: rscales <[email protected]>
Signed-off-by: David Li <[email protected]>
---
cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 97 +++++++++++++++++-
.../arrow/flight/sql/odbc/tests/connection_test.cc | 114 +++++++++++++++++++++
2 files changed, 207 insertions(+), 4 deletions(-)
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index bce7c10e82..ade3ecbe2b 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -253,16 +253,105 @@ SQLRETURN SQLGetEnvAttr(SQLHENV env, SQLINTEGER attr,
SQLPOINTER value_ptr,
ARROW_LOG(DEBUG) << "SQLGetEnvAttr called with env: " << env << ", attr: "
<< attr
<< ", value_ptr: " << value_ptr << ", buffer_length: " <<
buffer_length
<< ", str_len_ptr: " << static_cast<const
void*>(str_len_ptr);
- // GH-46575 TODO: Implement SQLGetEnvAttr
- return SQL_INVALID_HANDLE;
+
+ using ODBC::ODBCEnvironment;
+
+ ODBCEnvironment* environment = reinterpret_cast<ODBCEnvironment*>(env);
+
+ return ODBCEnvironment::ExecuteWithDiagnostics(environment, SQL_ERROR, [=]()
{
+ switch (attr) {
+ case SQL_ATTR_ODBC_VERSION: {
+ if (!value_ptr && !str_len_ptr) {
+ throw DriverException("Invalid null pointer for attribute.",
"HY000");
+ }
+
+ if (value_ptr) {
+ SQLINTEGER* value = reinterpret_cast<SQLINTEGER*>(value_ptr);
+ *value = static_cast<SQLSMALLINT>(environment->GetODBCVersion());
+ }
+
+ if (str_len_ptr) {
+ *str_len_ptr = sizeof(SQLINTEGER);
+ }
+
+ return SQL_SUCCESS;
+ }
+
+ case SQL_ATTR_OUTPUT_NTS: {
+ if (!value_ptr && !str_len_ptr) {
+ throw DriverException("Invalid null pointer for attribute.",
"HY000");
+ }
+
+ if (value_ptr) {
+ // output nts always returns SQL_TRUE
+ SQLINTEGER* value = reinterpret_cast<SQLINTEGER*>(value_ptr);
+ *value = SQL_TRUE;
+ }
+
+ if (str_len_ptr) {
+ *str_len_ptr = sizeof(SQLINTEGER);
+ }
+
+ return SQL_SUCCESS;
+ }
+
+ case SQL_ATTR_CONNECTION_POOLING: {
+ throw DriverException("Optional feature not supported.", "HYC00");
+ }
+
+ default: {
+ throw DriverException("Invalid attribute", "HYC00");
+ }
+ }
+ });
}
SQLRETURN SQLSetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER value_ptr,
SQLINTEGER str_len) {
ARROW_LOG(DEBUG) << "SQLSetEnvAttr called with env: " << env << ", attr: "
<< attr
<< ", value_ptr: " << value_ptr << ", str_len: " << str_len;
- // GH-46575 TODO: Implement SQLSetEnvAttr
- return SQL_INVALID_HANDLE;
+
+ using ODBC::ODBCEnvironment;
+
+ ODBCEnvironment* environment = reinterpret_cast<ODBCEnvironment*>(env);
+
+ return ODBCEnvironment::ExecuteWithDiagnostics(environment, SQL_ERROR, [=]()
{
+ if (!value_ptr) {
+ throw DriverException("Invalid null pointer for attribute.", "HY024");
+ }
+
+ switch (attr) {
+ case SQL_ATTR_ODBC_VERSION: {
+ SQLINTEGER version =
+ static_cast<SQLINTEGER>(reinterpret_cast<intptr_t>(value_ptr));
+ if (version == SQL_OV_ODBC2 || version == SQL_OV_ODBC3) {
+ environment->SetODBCVersion(version);
+
+ return SQL_SUCCESS;
+ } else {
+ throw DriverException("Invalid value for attribute", "HY024");
+ }
+ }
+
+ case SQL_ATTR_OUTPUT_NTS: {
+ // output nts can not be set to SQL_FALSE, is always SQL_TRUE
+ SQLINTEGER value =
static_cast<SQLINTEGER>(reinterpret_cast<intptr_t>(value_ptr));
+ if (value == SQL_TRUE) {
+ return SQL_SUCCESS;
+ } else {
+ throw DriverException("Invalid value for attribute", "HY024");
+ }
+ }
+
+ case SQL_ATTR_CONNECTION_POOLING: {
+ throw DriverException("Optional feature not supported.", "HYC00");
+ }
+
+ default: {
+ throw DriverException("Invalid attribute", "HY092");
+ }
+ }
+ });
}
SQLRETURN SQLGetConnectAttr(SQLHDBC conn, SQLINTEGER attribute, SQLPOINTER
value_ptr,
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc
b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc
index 9500b8fa83..c5646b42be 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc
@@ -106,4 +106,118 @@ TEST(SQLFreeHandle, TestFreeNullHandles) {
ASSERT_EQ(SQL_INVALID_HANDLE, SQLFreeHandle(SQL_HANDLE_ENV, env));
}
+TEST(SQLGetEnvAttr, TestSQLGetEnvAttrODBCVersion) {
+ SQLHENV env;
+
+ SQLINTEGER version;
+
+ // Allocate an environment handle
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLGetEnvAttr(env, SQL_ATTR_ODBC_VERSION, &version,
0, 0));
+
+ ASSERT_EQ(SQL_OV_ODBC2, version);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
+}
+
+TEST(SQLSetEnvAttr, TestSQLSetEnvAttrODBCVersionValid) {
+ SQLHENV env;
+
+ // Allocate an environment handle
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
+
+ // Attempt to set to supported version
+ ASSERT_EQ(SQL_SUCCESS, SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION,
+ reinterpret_cast<void*>(SQL_OV_ODBC2),
0));
+
+ SQLINTEGER version;
+ // Check ODBC version is set
+ ASSERT_EQ(SQL_SUCCESS, SQLGetEnvAttr(env, SQL_ATTR_ODBC_VERSION, &version,
0, 0));
+
+ ASSERT_EQ(SQL_OV_ODBC2, version);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
+}
+
+TEST(SQLSetEnvAttr, TestSQLSetEnvAttrODBCVersionInvalid) {
+ SQLHENV env;
+
+ // Allocate an environment handle
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
+
+ // Attempt to set to unsupported version
+ ASSERT_EQ(SQL_ERROR,
+ SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION,
reinterpret_cast<void*>(1), 0));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
+}
+
+// GH-46574 TODO: enable TestSQLGetEnvAttrOutputNTS which requires connection
support
+TYPED_TEST(ConnectionTest, DISABLED_TestSQLGetEnvAttrOutputNTS) {
+ SQLINTEGER output_nts;
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetEnvAttr(this->env, SQL_ATTR_OUTPUT_NTS, &output_nts, 0, 0));
+
+ ASSERT_EQ(SQL_TRUE, output_nts);
+}
+
+TYPED_TEST(ConnectionTest, DISABLED_TestSQLGetEnvAttrGetLength) {
+ // Test is disabled because call to SQLGetEnvAttr is handled by the driver
manager on
+ // Windows. Windows driver manager ignores the length pointer.
+ // This test case can be potentially used on macOS/Linux
+ SQLINTEGER length;
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLGetEnvAttr(this->env, SQL_ATTR_ODBC_VERSION, nullptr, 0,
&length));
+
+ EXPECT_EQ(sizeof(SQLINTEGER), length);
+}
+
+TYPED_TEST(ConnectionTest, DISABLED_TestSQLGetEnvAttrNullValuePointer) {
+ // Test is disabled because call to SQLGetEnvAttr is handled by the driver
manager on
+ // Windows. The Windows driver manager doesn't error out when null pointer
is passed.
+ // This test case can be potentially used on macOS/Linux
+ ASSERT_EQ(SQL_ERROR,
+ SQLGetEnvAttr(this->env, SQL_ATTR_ODBC_VERSION, nullptr, 0,
nullptr));
+}
+
+TEST(SQLSetEnvAttr, TestSQLSetEnvAttrOutputNTSValid) {
+ SQLHENV env;
+
+ // Allocate an environment handle
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
+
+ // Attempt to set to output nts to supported version
+ ASSERT_EQ(SQL_SUCCESS, SQLSetEnvAttr(env, SQL_ATTR_OUTPUT_NTS,
+ reinterpret_cast<void*>(SQL_TRUE), 0));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
+}
+
+TEST(SQLSetEnvAttr, TestSQLSetEnvAttrOutputNTSInvalid) {
+ SQLHENV env;
+
+ // Allocate an environment handle
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
+
+ // Attempt to set to output nts to unsupported false
+ ASSERT_EQ(SQL_ERROR, SQLSetEnvAttr(env, SQL_ATTR_OUTPUT_NTS,
+ reinterpret_cast<void*>(SQL_FALSE), 0));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
+}
+
+TEST(SQLSetEnvAttr, TestSQLSetEnvAttrNullValuePointer) {
+ SQLHENV env;
+
+ // Allocate an environment handle
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
+
+ // Attempt to set using bad data pointer
+ ASSERT_EQ(SQL_ERROR, SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, nullptr, 0));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
+}
+
} // namespace arrow::flight::sql::odbc