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 94b9bb66aa GH-46574: [C++][FlightRPC] ODBC Driver Connectivity support 
(#47971)
94b9bb66aa is described below

commit 94b9bb66aae66043b790b69219dde1e0f6d32441
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Thu Nov 13 22:26:12 2025 -0800

    GH-46574: [C++][FlightRPC] ODBC Driver Connectivity support (#47971)
    
    ### Rationale for this change
    Add ODBC driver connection support for the driver, so the driver can 
connect to servers.
    
    ### What changes are included in this PR?
    Implementation of unicode APIs:
    - SQLDriverConnect
    - SQLDisconnect
    - SQLConnect
    - DSN Window
    - Test refactoring
    
    ### Are these changes tested?
    Tested locally on MSVC Windows.
    
    ### Are there any user-facing changes?
    N/A
    
    * GitHub Issue: #46574
    
    Lead-authored-by: Alina (Xi) Li <[email protected]>
    Co-authored-by: justing-bq <[email protected]>
    Co-authored-by: justing-bq <[email protected]>
    Signed-off-by: David Li <[email protected]>
---
 .github/workflows/cpp.yml                          |   6 +-
 cpp/src/arrow/flight/sql/odbc/odbc.def             |   3 +-
 cpp/src/arrow/flight/sql/odbc/odbc_api.cc          | 157 +++++++++-
 .../arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt |   4 +-
 .../flight/sql/odbc/odbc_impl/attribute_utils.h    |  22 +-
 .../sql/odbc/odbc_impl/config/configuration.cc     |  21 +-
 .../sql/odbc/odbc_impl/config/configuration.h      |  17 +-
 .../flight/sql/odbc/odbc_impl/encoding_utils.h     |  27 +-
 .../sql/odbc/odbc_impl/flight_sql_connection.cc    |  18 +-
 .../odbc/odbc_impl/flight_sql_connection_test.cc   |  38 +--
 cpp/src/arrow/flight/sql/odbc/odbc_impl/main.cc    |  14 +-
 .../flight/sql/odbc/odbc_impl/odbc_connection.cc   | 103 ++-----
 .../flight/sql/odbc/odbc_impl/odbc_connection.h    |   8 +-
 .../flight/sql/odbc/odbc_impl/spi/connection.h     |   6 +-
 .../arrow/flight/sql/odbc/odbc_impl/system_dsn.cc  | 119 +-------
 .../arrow/flight/sql/odbc/odbc_impl/system_dsn.h   |   4 +
 .../odbc/odbc_impl/ui/dsn_configuration_window.cc  |  11 +-
 cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc    |   8 +-
 cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h     |   4 +-
 .../odbc_impl/{system_dsn.cc => win_system_dsn.cc} | 121 ++------
 .../arrow/flight/sql/odbc/tests/connection_test.cc | 336 ++++++++++++++++++---
 .../arrow/flight/sql/odbc/tests/odbc_test_suite.cc |  95 ++++--
 .../arrow/flight/sql/odbc/tests/odbc_test_suite.h  |  51 +++-
 23 files changed, 728 insertions(+), 465 deletions(-)

diff --git a/.github/workflows/cpp.yml b/.github/workflows/cpp.yml
index bb9571042c..aed25b4748 100644
--- a/.github/workflows/cpp.yml
+++ b/.github/workflows/cpp.yml
@@ -310,7 +310,7 @@ jobs:
       ARROW_DATASET: ON
       ARROW_FLIGHT: ON
       ARROW_FLIGHT_SQL: ON
-      ARROW_FLIGHT_SQL_ODBC: ON
+      ARROW_FLIGHT_SQL_ODBC: OFF
       ARROW_GANDIVA: ON
       ARROW_GCS: ON
       ARROW_HDFS: OFF
@@ -389,10 +389,6 @@ jobs:
           PIPX_BASE_PYTHON: ${{ steps.python-install.outputs.python-path }}
         run: |
           ci/scripts/install_gcs_testbench.sh default
-      - name: Register Flight SQL ODBC Driver
-        shell: cmd
-        run: |
-          call "cpp\src\arrow\flight\sql\odbc\tests\install_odbc.cmd" ${{ 
github.workspace }}\build\cpp\%ARROW_BUILD_TYPE%\libarrow_flight_sql_odbc.dll
       - name: Test
         shell: msys2 {0}
         run: |
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc.def 
b/cpp/src/arrow/flight/sql/odbc/odbc.def
index a8191ff662..8ba5b3fff7 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc.def
+++ b/cpp/src/arrow/flight/sql/odbc/odbc.def
@@ -17,8 +17,7 @@
 
 LIBRARY   arrow_flight_sql_odbc
 EXPORTS
-       ; GH-46574 TODO enable DSN window
-       ; ConfigDSNW
+       ConfigDSNW
        SQLAllocConnect
        SQLAllocEnv
        SQLAllocHandle
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index 01780f0efe..a028e063b3 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -31,6 +31,11 @@
 #include "arrow/flight/sql/odbc/odbc_impl/spi/connection.h"
 #include "arrow/util/logging.h"
 
+#if defined _WIN32
+// For displaying DSN Window
+#  include "arrow/flight/sql/odbc/odbc_impl/system_dsn.h"
+#endif  // defined(_WIN32)
+
 namespace arrow::flight::sql::odbc {
 SQLRETURN SQLAllocHandle(SQLSMALLINT type, SQLHANDLE parent, SQLHANDLE* 
result) {
   ARROW_LOG(DEBUG) << "SQLAllocHandle called with type: " << type
@@ -718,8 +723,30 @@ 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;
+  // GH-47708 TODO: Add tests for SQLSetConnectAttr
+  using ODBC::ODBCConnection;
+
+  return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {
+    const bool is_unicode = true;
+    ODBCConnection* connection = reinterpret_cast<ODBCConnection*>(conn);
+    connection->SetConnectAttr(attr, value_ptr, value_len, is_unicode);
+    return SQL_SUCCESS;
+  });
+}
+
+// Load properties from the given DSN. The properties loaded do _not_ 
overwrite existing
+// entries in the properties.
+void LoadPropertiesFromDSN(const std::string& dsn,
+                           Connection::ConnPropertyMap& properties) {
+  arrow::flight::sql::odbc::config::Configuration config;
+  config.LoadDsn(dsn);
+  Connection::ConnPropertyMap dsn_properties = config.GetProperties();
+  for (auto& [key, value] : dsn_properties) {
+    auto prop_iter = properties.find(key);
+    if (prop_iter == properties.end()) {
+      properties.emplace(std::make_pair(std::move(key), std::move(value)));
+    }
+  }
 }
 
 SQLRETURN SQLDriverConnect(SQLHDBC conn, SQLHWND window_handle,
@@ -740,13 +767,73 @@ SQLRETURN SQLDriverConnect(SQLHDBC conn, SQLHWND 
window_handle,
                    << out_connection_string_buffer_len << ", 
out_connection_string_len: "
                    << static_cast<const void*>(out_connection_string_len)
                    << ", driver_completion: " << driver_completion;
+
   // GH-46449 TODO: Implement FILEDSN and SAVEFILE keywords according to the 
spec
 
   // GH-46560 TODO: Copy connection string properly in SQLDriverConnect 
according to the
   // spec
 
-  // GH-46574 TODO: Implement SQLDriverConnect
-  return SQL_INVALID_HANDLE;
+  using ODBC::ODBCConnection;
+
+  return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {
+    ODBCConnection* connection = reinterpret_cast<ODBCConnection*>(conn);
+    std::string connection_string =
+        ODBC::SqlWcharToString(in_connection_string, in_connection_string_len);
+    Connection::ConnPropertyMap properties;
+    std::string dsn_value = "";
+    std::optional<std::string> dsn = 
ODBCConnection::GetDsnIfExists(connection_string);
+    if (dsn.has_value()) {
+      dsn_value = dsn.value();
+      LoadPropertiesFromDSN(dsn_value, properties);
+    }
+    ODBCConnection::GetPropertiesFromConnString(connection_string, properties);
+
+    std::vector<std::string_view> missing_properties;
+
+    // GH-46448 TODO: Implement SQL_DRIVER_COMPLETE_REQUIRED in 
SQLDriverConnect according
+    // to the spec
+#if defined _WIN32
+    // Load the DSN window according to driver_completion
+    if (driver_completion == SQL_DRIVER_PROMPT) {
+      // Load DSN window before first attempt to connect
+      arrow::flight::sql::odbc::config::Configuration config;
+      if (!DisplayConnectionWindow(window_handle, config, properties)) {
+        return static_cast<SQLRETURN>(SQL_NO_DATA);
+      }
+      connection->Connect(dsn_value, properties, missing_properties);
+    } else if (driver_completion == SQL_DRIVER_COMPLETE ||
+               driver_completion == SQL_DRIVER_COMPLETE_REQUIRED) {
+      try {
+        connection->Connect(dsn_value, properties, missing_properties);
+      } catch (const DriverException&) {
+        // If first connection fails due to missing attributes, load
+        // the DSN window and try to connect again
+        if (!missing_properties.empty()) {
+          arrow::flight::sql::odbc::config::Configuration config;
+          missing_properties.clear();
+
+          if (!DisplayConnectionWindow(window_handle, config, properties)) {
+            return static_cast<SQLRETURN>(SQL_NO_DATA);
+          }
+          connection->Connect(dsn_value, properties, missing_properties);
+        } else {
+          throw;
+        }
+      }
+    } else {
+      // Default case: attempt connection without showing DSN window
+      connection->Connect(dsn_value, properties, missing_properties);
+    }
+#else
+    // Attempt connection without loading DSN window on macOS/Linux
+    connection->Connect(dsn, properties, missing_properties);
+#endif
+    // Copy connection string to out_connection_string after connection attempt
+    return ODBC::GetStringAttribute(true, connection_string, false, 
out_connection_string,
+                                    out_connection_string_buffer_len,
+                                    out_connection_string_len,
+                                    connection->GetDiagnostics());
+  });
 }
 
 SQLRETURN SQLConnect(SQLHDBC conn, SQLWCHAR* dsn_name, SQLSMALLINT 
dsn_name_len,
@@ -759,14 +846,48 @@ SQLRETURN SQLConnect(SQLHDBC conn, SQLWCHAR* dsn_name, 
SQLSMALLINT dsn_name_len,
                    << ", user_name_len: " << user_name_len
                    << ", password: " << static_cast<const void*>(password)
                    << ", password_len: " << password_len;
-  // GH-46574 TODO: Implement SQLConnect
-  return SQL_INVALID_HANDLE;
+
+  using ODBC::ODBCConnection;
+
+  using ODBC::SqlWcharToString;
+
+  return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {
+    ODBCConnection* connection = reinterpret_cast<ODBCConnection*>(conn);
+    std::string dsn = SqlWcharToString(dsn_name, dsn_name_len);
+
+    Configuration config;
+    config.LoadDsn(dsn);
+
+    if (user_name) {
+      std::string uid = SqlWcharToString(user_name, user_name_len);
+      config.Emplace(FlightSqlConnection::UID, std::move(uid));
+    }
+
+    if (password) {
+      std::string pwd = SqlWcharToString(password, password_len);
+      config.Emplace(FlightSqlConnection::PWD, std::move(pwd));
+    }
+
+    std::vector<std::string_view> missing_properties;
+
+    connection->Connect(dsn, config.GetProperties(), missing_properties);
+
+    return SQL_SUCCESS;
+  });
 }
 
 SQLRETURN SQLDisconnect(SQLHDBC conn) {
   ARROW_LOG(DEBUG) << "SQLDisconnect called with conn: " << conn;
-  // GH-46574 TODO: Implement SQLDisconnect
-  return SQL_INVALID_HANDLE;
+
+  using ODBC::ODBCConnection;
+
+  return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {
+    ODBCConnection* connection = reinterpret_cast<ODBCConnection*>(conn);
+
+    connection->Disconnect();
+
+    return SQL_SUCCESS;
+  });
 }
 
 SQLRETURN SQLGetInfo(SQLHDBC conn, SQLUSMALLINT info_type, SQLPOINTER 
info_value_ptr,
@@ -776,8 +897,24 @@ SQLRETURN SQLGetInfo(SQLHDBC conn, SQLUSMALLINT info_type, 
SQLPOINTER info_value
                    << ", info_value_ptr: " << info_value_ptr << ", buf_len: " 
<< buf_len
                    << ", string_length_ptr: "
                    << static_cast<const void*>(string_length_ptr);
-  // GH-47709 TODO: Implement SQLGetInfo
-  return SQL_INVALID_HANDLE;
+
+  // GH-47709 TODO: Update SQLGetInfo implementation and add tests for 
SQLGetInfo
+  using ODBC::ODBCConnection;
+
+  return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {
+    ODBCConnection* connection = reinterpret_cast<ODBCConnection*>(conn);
+
+    // Set character type to be Unicode by default
+    const bool is_unicode = true;
+
+    if (!info_value_ptr && !string_length_ptr) {
+      return static_cast<SQLRETURN>(SQL_ERROR);
+    }
+
+    connection->GetInfo(info_type, info_value_ptr, buf_len, string_length_ptr,
+                        is_unicode);
+    return static_cast<SQLRETURN>(SQL_SUCCESS);
+  });
 }
 
 SQLRETURN SQLGetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER 
value_ptr,
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt
index b232577ee3..8f09fccd71 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/CMakeLists.txt
@@ -124,7 +124,9 @@ if(WIN32)
                          ui/dsn_configuration_window.h
                          ui/window.cc
                          ui/window.h
-                         system_dsn.cc)
+                         win_system_dsn.cc
+                         system_dsn.cc
+                         system_dsn.h)
 endif()
 
 target_link_libraries(arrow_odbc_spi_impl PUBLIC arrow_flight_sql_shared
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h
index 7baea759ed..8c5eae59f7 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/attribute_utils.h
@@ -17,17 +17,17 @@
 
 #pragma once
 
-#include <arrow/flight/sql/odbc/odbc_impl/diagnostics.h>
-#include <arrow/flight/sql/odbc/odbc_impl/exceptions.h>
-#include <arrow/flight/sql/odbc/odbc_impl/platform.h>
 #include <sql.h>
 #include <sqlext.h>
 #include <algorithm>
 #include <cstring>
 #include <memory>
+#include "arrow/flight/sql/odbc/odbc_impl/diagnostics.h"
+#include "arrow/flight/sql/odbc/odbc_impl/encoding_utils.h"
+#include "arrow/flight/sql/odbc/odbc_impl/exceptions.h"
+#include "arrow/flight/sql/odbc/odbc_impl/platform.h"
 
-#include <arrow/flight/sql/odbc/odbc_impl/encoding_utils.h>
-
+// GH-48083 TODO: replace `namespace ODBC` with `namespace 
arrow::flight::sql::odbc`
 namespace ODBC {
 
 using arrow::flight::sql::odbc::Diagnostics;
@@ -48,12 +48,12 @@ inline void GetAttribute(T attribute_value, SQLPOINTER 
output, O output_size,
 }
 
 template <typename O>
-inline SQLRETURN GetAttributeUTF8(const std::string& attribute_value, 
SQLPOINTER output,
+inline SQLRETURN GetAttributeUTF8(std::string_view attribute_value, SQLPOINTER 
output,
                                   O output_size, O* output_len_ptr) {
   if (output) {
     size_t output_len_before_null =
         std::min(static_cast<O>(attribute_value.size()), 
static_cast<O>(output_size - 1));
-    memcpy(output, attribute_value.c_str(), output_len_before_null);
+    std::memcpy(output, attribute_value.data(), output_len_before_null);
     reinterpret_cast<char*>(output)[output_len_before_null] = '\0';
   }
 
@@ -68,7 +68,7 @@ inline SQLRETURN GetAttributeUTF8(const std::string& 
attribute_value, SQLPOINTER
 }
 
 template <typename O>
-inline SQLRETURN GetAttributeUTF8(const std::string& attribute_value, 
SQLPOINTER output,
+inline SQLRETURN GetAttributeUTF8(std::string_view attribute_value, SQLPOINTER 
output,
                                   O output_size, O* output_len_ptr,
                                   Diagnostics& diagnostics) {
   SQLRETURN result =
@@ -80,7 +80,7 @@ inline SQLRETURN GetAttributeUTF8(const std::string& 
attribute_value, SQLPOINTER
 }
 
 template <typename O>
-inline SQLRETURN GetAttributeSQLWCHAR(const std::string& attribute_value,
+inline SQLRETURN GetAttributeSQLWCHAR(std::string_view attribute_value,
                                       bool is_length_in_bytes, SQLPOINTER 
output,
                                       O output_size, O* output_len_ptr) {
   size_t length = ConvertToSqlWChar(
@@ -104,7 +104,7 @@ inline SQLRETURN GetAttributeSQLWCHAR(const std::string& 
attribute_value,
 }
 
 template <typename O>
-inline SQLRETURN GetAttributeSQLWCHAR(const std::string& attribute_value,
+inline SQLRETURN GetAttributeSQLWCHAR(std::string_view attribute_value,
                                       bool is_length_in_bytes, SQLPOINTER 
output,
                                       O output_size, O* output_len_ptr,
                                       Diagnostics& diagnostics) {
@@ -117,7 +117,7 @@ inline SQLRETURN GetAttributeSQLWCHAR(const std::string& 
attribute_value,
 }
 
 template <typename O>
-inline SQLRETURN GetStringAttribute(bool is_unicode, const std::string& 
attribute_value,
+inline SQLRETURN GetStringAttribute(bool is_unicode, std::string_view 
attribute_value,
                                     bool is_length_in_bytes, SQLPOINTER output,
                                     O output_size, O* output_len_ptr,
                                     Diagnostics& diagnostics) {
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc
index cdb889f056..df61f1247c 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc
@@ -35,7 +35,7 @@ static const char DEFAULT_USE_CERT_STORE[] = TRUE_STR;
 static const char DEFAULT_DISABLE_CERT_VERIFICATION[] = FALSE_STR;
 
 namespace {
-std::string ReadDsnString(const std::string& dsn, const std::string_view& key,
+std::string ReadDsnString(const std::string& dsn, std::string_view key,
                           const std::string& dflt = "") {
   CONVERT_WIDE_STR(const std::wstring wdsn, dsn);
   CONVERT_WIDE_STR(const std::wstring wkey, key);
@@ -150,11 +150,11 @@ void Configuration::LoadDsn(const std::string& dsn) {
 
 void Configuration::Clear() { this->properties_.clear(); }
 
-bool Configuration::IsSet(const std::string_view& key) const {
+bool Configuration::IsSet(std::string_view key) const {
   return 0 != this->properties_.count(key);
 }
 
-const std::string& Configuration::Get(const std::string_view& key) const {
+const std::string& Configuration::Get(std::string_view key) const {
   const auto itr = this->properties_.find(key);
   if (itr == this->properties_.cend()) {
     static const std::string empty("");
@@ -163,15 +163,22 @@ const std::string& Configuration::Get(const 
std::string_view& key) const {
   return itr->second;
 }
 
-void Configuration::Set(const std::string_view& key, const std::wstring& 
wvalue) {
+void Configuration::Set(std::string_view key, const std::wstring& wvalue) {
   CONVERT_UTF8_STR(const std::string value, wvalue);
   Set(key, value);
 }
 
-void Configuration::Set(const std::string_view& key, const std::string& value) 
{
+void Configuration::Set(std::string_view key, const std::string& value) {
   const std::string copy = boost::trim_copy(value);
   if (!copy.empty()) {
-    this->properties_[key] = value;
+    this->properties_[std::string(key)] = value;
+  }
+}
+
+void Configuration::Emplace(std::string_view key, std::string&& value) {
+  const std::string copy = boost::trim_copy(value);
+  if (!copy.empty()) {
+    this->properties_.emplace(std::make_pair(key, std::move(value)));
   }
 }
 
@@ -182,7 +189,7 @@ const Connection::ConnPropertyMap& 
Configuration::GetProperties() const {
 std::vector<std::string_view> Configuration::GetCustomKeys() const {
   Connection::ConnPropertyMap copy_props(properties_);
   for (auto& key : FlightSqlConnection::ALL_KEYS) {
-    copy_props.erase(key);
+    copy_props.erase(std::string(key));
   }
   std::vector<std::string_view> keys;
   boost::copy(copy_props | boost::adaptors::map_keys, 
std::back_inserter(keys));
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h
index 77d07b1420..0390a57e52 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h
@@ -46,22 +46,15 @@ class Configuration {
    */
   ~Configuration();
 
-  /**
-   * Convert configure to connect string.
-   *
-   * @return Connect string.
-   */
-  std::string ToConnectString() const;
-
   void LoadDefaults();
   void LoadDsn(const std::string& dsn);
 
   void Clear();
-  bool IsSet(const std::string_view& key) const;
-  const std::string& Get(const std::string_view& key) const;
-  void Set(const std::string_view& key, const std::wstring& wvalue);
-  void Set(const std::string_view& key, const std::string& value);
-
+  bool IsSet(std::string_view key) const;
+  const std::string& Get(std::string_view key) const;
+  void Set(std::string_view key, const std::wstring& wvalue);
+  void Set(std::string_view key, const std::string& value);
+  void Emplace(std::string_view key, std::string&& value);
   /**
    * Get properties map.
    */
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h
index a5cc3a6f4c..66e5c3bf0d 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/encoding_utils.h
@@ -16,7 +16,6 @@
 // under the License.
 
 #pragma once
-
 #include "arrow/flight/sql/odbc/odbc_impl/encoding.h"
 #include "arrow/flight/sql/odbc/odbc_impl/platform.h"
 
@@ -40,15 +39,15 @@ using arrow::flight::sql::odbc::WcsToUtf8;
 
 // Return the number of bytes required for the conversion.
 template <typename CHAR_TYPE>
-inline size_t ConvertToSqlWChar(const std::string& str, SQLWCHAR* buffer,
+inline size_t ConvertToSqlWChar(std::string_view str, SQLWCHAR* buffer,
                                 SQLLEN buffer_size_in_bytes) {
   thread_local std::vector<uint8_t> wstr;
   Utf8ToWcs<CHAR_TYPE>(str.data(), str.size(), &wstr);
   SQLLEN value_length_in_bytes = wstr.size();
 
   if (buffer) {
-    memcpy(buffer, wstr.data(),
-           std::min(static_cast<SQLLEN>(wstr.size()), buffer_size_in_bytes));
+    std::memcpy(buffer, wstr.data(),
+                std::min(static_cast<SQLLEN>(wstr.size()), 
buffer_size_in_bytes));
 
     // Write a NUL terminator
     if (buffer_size_in_bytes >=
@@ -67,7 +66,7 @@ inline size_t ConvertToSqlWChar(const std::string& str, 
SQLWCHAR* buffer,
   return value_length_in_bytes;
 }
 
-inline size_t ConvertToSqlWChar(const std::string& str, SQLWCHAR* buffer,
+inline size_t ConvertToSqlWChar(std::string_view str, SQLWCHAR* buffer,
                                 SQLLEN buffer_size_in_bytes) {
   switch (GetSqlWCharSize()) {
     case sizeof(char16_t):
@@ -101,4 +100,22 @@ inline std::string SqlWcharToString(SQLWCHAR* wchar_msg, 
SQLINTEGER msg_len = SQ
   return std::string(utf8_str.begin(), utf8_str.end());
 }
 
+inline std::string SqlStringToString(const unsigned char* sql_str,
+                                     int32_t sql_str_len = SQL_NTS) {
+  std::string res;
+
+  const char* sql_str_c = reinterpret_cast<const char*>(sql_str);
+
+  if (!sql_str) {
+    return res;
+  }
+
+  if (sql_str_len == SQL_NTS) {
+    res.assign(sql_str_c);
+  } else if (sql_str_len > 0) {
+    res.assign(sql_str_c, sql_str_len);
+  }
+
+  return res;
+}
 }  // namespace ODBC
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.cc
index 479a72f3fe..e18a58d069 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.cc
@@ -99,7 +99,14 @@ inline std::string GetCerts() { return ""; }
 
 #endif
 
-const std::set<std::string_view, CaseInsensitiveComparator> 
BUILT_IN_PROPERTIES = {
+// Case insensitive comparator that takes string_view
+struct CaseInsensitiveComparatorStrView {
+  bool operator()(std::string_view s1, std::string_view s2) const {
+    return boost::lexicographical_compare(s1, s2, boost::is_iless());
+  }
+};
+
+const std::set<std::string_view, CaseInsensitiveComparatorStrView> 
BUILT_IN_PROPERTIES = {
     FlightSqlConnection::HOST,
     FlightSqlConnection::PORT,
     FlightSqlConnection::USER,
@@ -116,7 +123,7 @@ const std::set<std::string_view, CaseInsensitiveComparator> 
BUILT_IN_PROPERTIES
     FlightSqlConnection::USE_WIDE_CHAR};
 
 Connection::ConnPropertyMap::const_iterator TrackMissingRequiredProperty(
-    const std::string_view& property, const Connection::ConnPropertyMap& 
properties,
+    std::string_view property, const Connection::ConnPropertyMap& properties,
     std::vector<std::string_view>& missing_attr) {
   auto prop_iter = properties.find(property);
   if (properties.end() == prop_iter) {
@@ -138,7 +145,7 @@ std::shared_ptr<FlightSqlSslConfig> LoadFlightSslConfigs(
           .value_or(SYSTEM_TRUST_STORE_DEFAULT);
 
   auto trusted_certs_iterator =
-      conn_property_map.find(FlightSqlConnection::TRUSTED_CERTS);
+      conn_property_map.find(std::string(FlightSqlConnection::TRUSTED_CERTS));
   auto trusted_certs = trusted_certs_iterator != conn_property_map.end()
                            ? trusted_certs_iterator->second
                            : "";
@@ -161,6 +168,8 @@ void FlightSqlConnection::Connect(const ConnPropertyMap& 
properties,
 
     std::unique_ptr<FlightClient> flight_client;
     ThrowIfNotOK(FlightClient::Connect(location, 
client_options).Value(&flight_client));
+    PopulateMetadataSettings(properties);
+    PopulateCallOptions(properties);
 
     std::unique_ptr<FlightSqlAuthMethod> auth_method =
         FlightSqlAuthMethod::FromProperties(flight_client, properties);
@@ -175,9 +184,6 @@ void FlightSqlConnection::Connect(const ConnPropertyMap& 
properties,
 
     info_.SetProperty(SQL_USER_NAME, auth_method->GetUser());
     attribute_[CONNECTION_DEAD] = static_cast<uint32_t>(SQL_FALSE);
-
-    PopulateMetadataSettings(properties);
-    PopulateCallOptions(properties);
   } catch (...) {
     attribute_[CONNECTION_DEAD] = static_cast<uint32_t>(SQL_TRUE);
     sql_client_.reset();
diff --git 
a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc
index a42d019852..9c9b0f8f3c 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/flight_sql_connection_test.cc
@@ -33,7 +33,7 @@ TEST(AttributeTests, SetAndGetAttribute) {
 
   EXPECT_TRUE(first_value);
 
-  EXPECT_EQ(boost::get<uint32_t>(*first_value), static_cast<uint32_t>(200));
+  EXPECT_EQ(static_cast<uint32_t>(200), boost::get<uint32_t>(*first_value));
 
   connection.SetAttribute(Connection::CONNECTION_TIMEOUT, 
static_cast<uint32_t>(300));
 
@@ -41,7 +41,7 @@ TEST(AttributeTests, SetAndGetAttribute) {
       connection.GetAttribute(Connection::CONNECTION_TIMEOUT);
 
   EXPECT_TRUE(change_value);
-  EXPECT_EQ(boost::get<uint32_t>(*change_value), static_cast<uint32_t>(300));
+  EXPECT_EQ(static_cast<uint32_t>(300), boost::get<uint32_t>(*change_value));
 
   connection.Close();
 }
@@ -65,10 +65,10 @@ TEST(MetadataSettingsTest, StringColumnLengthTest) {
   const int32_t expected_string_column_length = 100000;
 
   const Connection::ConnPropertyMap properties = {
-      {FlightSqlConnection::HOST, std::string("localhost")},        // expect 
not used
-      {FlightSqlConnection::PORT, std::string("32010")},            // expect 
not used
-      {FlightSqlConnection::USE_ENCRYPTION, std::string("false")},  // expect 
not used
-      {FlightSqlConnection::STRING_COLUMN_LENGTH,
+      {std::string(FlightSqlConnection::HOST), "localhost"},        // expect 
not used
+      {std::string(FlightSqlConnection::PORT), "32010"},            // expect 
not used
+      {std::string(FlightSqlConnection::USE_ENCRYPTION), "false"},  // expect 
not used
+      {std::string(FlightSqlConnection::STRING_COLUMN_LENGTH),
        std::to_string(expected_string_column_length)},
   };
 
@@ -86,10 +86,10 @@ TEST(MetadataSettingsTest, UseWideCharTest) {
   connection.SetClosed(false);
 
   const Connection::ConnPropertyMap properties1 = {
-      {FlightSqlConnection::USE_WIDE_CHAR, std::string("true")},
+      {std::string(FlightSqlConnection::USE_WIDE_CHAR), "true"},
   };
   const Connection::ConnPropertyMap properties2 = {
-      {FlightSqlConnection::USE_WIDE_CHAR, std::string("false")},
+      {std::string(FlightSqlConnection::USE_WIDE_CHAR), "false"},
   };
 
   EXPECT_EQ(true, connection.GetUseWideChar(properties1));
@@ -101,9 +101,9 @@ TEST(MetadataSettingsTest, UseWideCharTest) {
 TEST(BuildLocationTests, ForTcp) {
   std::vector<std::string_view> missing_attr;
   Connection::ConnPropertyMap properties = {
-      {FlightSqlConnection::HOST, std::string("localhost")},
-      {FlightSqlConnection::PORT, std::string("32010")},
-      {FlightSqlConnection::USE_ENCRYPTION, std::string("false")},
+      {std::string(FlightSqlConnection::HOST), "localhost"},
+      {std::string(FlightSqlConnection::PORT), "32010"},
+      {std::string(FlightSqlConnection::USE_ENCRYPTION), "false"},
   };
 
   const std::shared_ptr<FlightSqlSslConfig>& ssl_config =
@@ -113,8 +113,8 @@ TEST(BuildLocationTests, ForTcp) {
       FlightSqlConnection::BuildLocation(properties, missing_attr, ssl_config);
   const Location& actual_location2 = FlightSqlConnection::BuildLocation(
       {
-          {FlightSqlConnection::HOST, std::string("localhost")},
-          {FlightSqlConnection::PORT, std::string("32011")},
+          {std::string(FlightSqlConnection::HOST), "localhost"},
+          {std::string(FlightSqlConnection::PORT), "32011"},
       },
       missing_attr, ssl_config);
 
@@ -127,9 +127,9 @@ TEST(BuildLocationTests, ForTcp) {
 TEST(BuildLocationTests, ForTls) {
   std::vector<std::string_view> missing_attr;
   Connection::ConnPropertyMap properties = {
-      {FlightSqlConnection::HOST, std::string("localhost")},
-      {FlightSqlConnection::PORT, std::string("32010")},
-      {FlightSqlConnection::USE_ENCRYPTION, std::string("1")},
+      {std::string(FlightSqlConnection::HOST), "localhost"},
+      {std::string(FlightSqlConnection::PORT), "32010"},
+      {std::string(FlightSqlConnection::USE_ENCRYPTION), "1"},
   };
 
   const std::shared_ptr<FlightSqlSslConfig>& ssl_config =
@@ -139,9 +139,9 @@ TEST(BuildLocationTests, ForTls) {
       FlightSqlConnection::BuildLocation(properties, missing_attr, ssl_config);
 
   Connection::ConnPropertyMap second_properties = {
-      {FlightSqlConnection::HOST, std::string("localhost")},
-      {FlightSqlConnection::PORT, std::string("32011")},
-      {FlightSqlConnection::USE_ENCRYPTION, std::string("1")},
+      {std::string(FlightSqlConnection::HOST), "localhost"},
+      {std::string(FlightSqlConnection::PORT), "32011"},
+      {std::string(FlightSqlConnection::USE_ENCRYPTION), "1"},
   };
 
   const std::shared_ptr<FlightSqlSslConfig>& second_ssl_config =
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/main.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/main.cc
index 8f649311e9..3336e0160e 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/main.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/main.cc
@@ -43,7 +43,7 @@ using arrow::flight::sql::odbc::Statement;
 
 void TestBindColumn(const std::shared_ptr<Connection>& connection) {
   const std::shared_ptr<Statement>& statement = connection->CreateStatement();
-  statement->Execute("SELECT IncidntNum, Category FROM \"@dremio\".Test LIMIT 
10");
+  statement->Execute("SELECT IncidntNum, Category FROM \"@apache\".Test LIMIT 
10");
 
   const std::shared_ptr<ResultSet>& result_set = statement->GetResultSet();
 
@@ -105,7 +105,7 @@ void TestBindColumnBigInt(const 
std::shared_ptr<Connection>& connection) {
       "  SELECT CONVERT_TO_INTEGER(IncidntNum, 1, 1, 0) AS IncidntNum, "
       "Category\n"
       "  FROM (\n"
-      "    SELECT IncidntNum, Category FROM \"@dremio\".Test LIMIT 10\n"
+      "    SELECT IncidntNum, Category FROM \"@apache\".Test LIMIT 10\n"
       "  ) nested_0\n"
       ") nested_0");
 
@@ -202,11 +202,11 @@ int main() {
       driver.CreateConnection(arrow::flight::sql::odbc::OdbcVersion::V_3);
 
   Connection::ConnPropertyMap properties = {
-      {FlightSqlConnection::HOST, std::string("automaster.drem.io")},
-      {FlightSqlConnection::PORT, std::string("32010")},
-      {FlightSqlConnection::USER, std::string("dremio")},
-      {FlightSqlConnection::PASSWORD, std::string("dremio123")},
-      {FlightSqlConnection::USE_ENCRYPTION, std::string("false")},
+      {std::string(FlightSqlConnection::HOST), "automaster.apache"},
+      {std::string(FlightSqlConnection::PORT), "32010"},
+      {std::string(FlightSqlConnection::USER), "apache"},
+      {std::string(FlightSqlConnection::PASSWORD), "apache123"},
+      {std::string(FlightSqlConnection::USE_ENCRYPTION), "false"},
   };
   std::vector<std::string_view> missing_attr;
   connection->Connect(properties, missing_attr);
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 c0a55840d5..ead2beada4 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
@@ -53,57 +53,7 @@ namespace {
 // characters such as semi-colons and equals signs. NOTE: This can be 
optimized to be
 // built statically.
 const boost::xpressive::sregex CONNECTION_STR_REGEX(
-    boost::xpressive::sregex::compile("([^=;]+)=({.+}|[^=;]+|[^;])"));
-
-// Load properties from the given DSN. The properties loaded do _not_ 
overwrite existing
-// entries in the properties.
-void loadPropertiesFromDSN(const std::string& dsn,
-                           Connection::ConnPropertyMap& properties) {
-  const size_t BUFFER_SIZE = 1024 * 10;
-  std::vector<wchar_t> output_buffer;
-  output_buffer.resize(BUFFER_SIZE, '\0');
-  SQLSetConfigMode(ODBC_BOTH_DSN);
-
-  CONVERT_WIDE_STR(const std::wstring wdsn, dsn);
-
-  SQLGetPrivateProfileString(wdsn.c_str(), NULL, L"", &output_buffer[0], 
BUFFER_SIZE,
-                             L"odbc.ini");
-
-  // The output buffer holds the list of keys in a series of NUL-terminated 
strings.
-  // The series is terminated with an empty string (eg a NUL-terminator 
terminating the
-  // last key followed by a NUL terminator after).
-  std::vector<std::wstring_view> keys;
-  size_t pos = 0;
-  while (pos < BUFFER_SIZE) {
-    std::wstring wkey(&output_buffer[pos]);
-    if (wkey.empty()) {
-      break;
-    }
-    size_t len = wkey.size();
-
-    // Skip over Driver or DSN keys.
-    if (!boost::iequals(wkey, L"DSN") && !boost::iequals(wkey, L"Driver")) {
-      keys.emplace_back(std::move(wkey));
-    }
-    pos += len + 1;
-  }
-
-  for (auto& wkey : keys) {
-    output_buffer.clear();
-    output_buffer.resize(BUFFER_SIZE, '\0');
-    SQLGetPrivateProfileString(wdsn.c_str(), wkey.data(), L"", 
&output_buffer[0],
-                               BUFFER_SIZE, L"odbc.ini");
-
-    std::wstring wvalue = std::wstring(&output_buffer[0]);
-    CONVERT_UTF8_STR(const std::string value, wvalue);
-    CONVERT_UTF8_STR(const std::string key, std::wstring(wkey));
-    auto propIter = properties.find(key);
-    if (propIter == properties.end()) {
-      properties.emplace(std::make_pair(std::move(key), std::move(value)));
-    }
-  }
-}
-
+    boost::xpressive::sregex::compile("([^=;]+)=({.+}|[^;]+|[^;])"));
 }  // namespace
 
 // Public
@@ -734,39 +684,43 @@ void ODBCConnection::DropDescriptor(ODBCDescriptor* desc) 
{
 
 // Public Static
 // 
===================================================================================
-std::string ODBCConnection::GetPropertiesFromConnString(
+std::optional<std::string> ODBCConnection::GetDsnIfExists(const std::string& 
conn_str) {
+  const int groups[] = {1, 2};  // CONNECTION_STR_REGEX has two groups. key: 
1, value: 2
+  boost::xpressive::sregex_token_iterator regex_iter(conn_str.begin(), 
conn_str.end(),
+                                                     CONNECTION_STR_REGEX, 
groups),
+      end;
+
+  // First key in connection string should be either dsn or driver
+  auto it = regex_iter;
+  std::string key = *regex_iter;
+  std::string value = *++regex_iter;
+
+  // Strip wrapping curly braces.
+  if (value.size() >= 2 && value[0] == '{' && value[value.size() - 1] == '}') {
+    value = value.substr(1, value.size() - 2);
+  }
+
+  if (boost::iequals(key, "DSN")) {
+    return value;
+  } else if (boost::iequals(key, "Driver")) {
+    return std::nullopt;
+  } else {
+    throw DriverException(
+        "Connection string is faulty. The first key should be DSN or Driver.", 
"HY000");
+  }
+}
+
+void ODBCConnection::GetPropertiesFromConnString(
     const std::string& conn_str, Connection::ConnPropertyMap& properties) {
   const int groups[] = {1, 2};  // CONNECTION_STR_REGEX has two groups. key: 
1, value: 2
   boost::xpressive::sregex_token_iterator regex_iter(conn_str.begin(), 
conn_str.end(),
                                                      CONNECTION_STR_REGEX, 
groups),
       end;
 
-  bool is_dsn_first = false;
-  bool is_driver_first = false;
-  std::string dsn;
   for (auto it = regex_iter; end != regex_iter; ++regex_iter) {
     std::string key = *regex_iter;
     std::string value = *++regex_iter;
 
-    // If the DSN shows up before driver key, load settings from the DSN.
-    // Only load values from the DSN once regardless of how many times the DSN
-    // key shows up.
-    if (boost::iequals(key, "DSN")) {
-      if (!is_driver_first) {
-        if (!is_dsn_first) {
-          is_dsn_first = true;
-          loadPropertiesFromDSN(value, properties);
-          dsn.swap(value);
-        }
-      }
-      continue;
-    } else if (boost::iequals(key, "Driver")) {
-      if (!is_dsn_first) {
-        is_driver_first = true;
-      }
-      continue;
-    }
-
     // Strip wrapping curly braces.
     if (value.size() >= 2 && value[0] == '{' && value[value.size() - 1] == 
'}') {
       value = value.substr(1, value.size() - 2);
@@ -776,5 +730,4 @@ std::string ODBCConnection::GetPropertiesFromConnString(
     // including over entries in the DSN.
     properties[key] = std::move(value);
   }
-  return dsn;
 }
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 8157c2f5f9..2e5ab57ad4 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
@@ -23,6 +23,7 @@
 #include <sql.h>
 #include <map>
 #include <memory>
+#include <optional>
 #include <vector>
 
 namespace ODBC {
@@ -75,8 +76,11 @@ class ODBCConnection : public ODBCHandle<ODBCConnection> {
 
   inline bool IsOdbc2Connection() const { return is_2x_connection_; }
 
-  /// @return the DSN or empty string if Driver was used.
-  static std::string GetPropertiesFromConnString(
+  /// \return an optional DSN
+  static std::optional<std::string> GetDsnIfExists(const std::string& 
conn_str);
+
+  /// Read properties from connection string, but does not read values from DSN
+  static void GetPropertiesFromConnString(
       const std::string& conn_str,
       arrow::flight::sql::odbc::Connection::ConnPropertyMap& properties);
 
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h
index e24af6c3dd..7a8243e785 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/spi/connection.h
@@ -32,13 +32,15 @@ namespace arrow::flight::sql::odbc {
 
 /// \brief Case insensitive comparator
 struct CaseInsensitiveComparator {
-  bool operator()(const std::string_view& s1, const std::string_view& s2) 
const {
+  using is_transparent = std::true_type;
+
+  bool operator()(std::string_view s1, std::string_view s2) const {
     return boost::lexicographical_compare(s1, s2, boost::is_iless());
   }
 };
 
 // PropertyMap is case-insensitive for keys.
-typedef std::map<std::string_view, std::string, CaseInsensitiveComparator> 
PropertyMap;
+typedef std::map<std::string, std::string, CaseInsensitiveComparator> 
PropertyMap;
 
 class Statement;
 
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.cc
index 75501ac8dd..468f05e4cf 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.cc
@@ -17,18 +17,11 @@
 
 #include "arrow/flight/sql/odbc/odbc_impl/system_dsn.h"
 
-// platform.h includes windows.h, so it needs to be included
-// before winuser.h
-#include "arrow/flight/sql/odbc/odbc_impl/platform.h"
-
-#include <winuser.h>
-#include <utility>
 #include "arrow/flight/sql/odbc/odbc_impl/config/configuration.h"
-#include "arrow/flight/sql/odbc/odbc_impl/config/connection_string_parser.h"
-#include "arrow/flight/sql/odbc/odbc_impl/exceptions.h"
 #include "arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h"
 #include "arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.h"
 #include "arrow/flight/sql/odbc/odbc_impl/ui/window.h"
+#include "arrow/flight/sql/odbc/odbc_impl/util.h"
 #include "arrow/result.h"
 #include "arrow/util/utf8.h"
 
@@ -38,41 +31,6 @@
 namespace arrow::flight::sql::odbc {
 
 using config::Configuration;
-using config::ConnectionStringParser;
-using config::DsnConfigurationWindow;
-using config::Result;
-using config::Window;
-
-bool DisplayConnectionWindow(void* window_parent, Configuration& config) {
-  HWND hwnd_parent = (HWND)window_parent;
-
-  if (!hwnd_parent) return true;
-
-  try {
-    Window parent(hwnd_parent);
-    DsnConfigurationWindow window(&parent, config);
-
-    window.Create();
-
-    window.Show();
-    window.Update();
-
-    return ProcessMessages(window) == Result::OK;
-  } catch (const DriverException& err) {
-    std::stringstream buf;
-    buf << "SQL State: " << err.GetSqlState() << ", Message: " << 
err.GetMessageText()
-        << ", Code: " << err.GetNativeError();
-    std::wstring wmessage =
-        arrow::util::UTF8ToWideString(buf.str()).ValueOr(L"Error during load 
DSN");
-    MessageBox(NULL, wmessage.c_str(), L"Error!", MB_ICONEXCLAMATION | MB_OK);
-
-    std::wstring wmessage_text = 
arrow::util::UTF8ToWideString(err.GetMessageText())
-                                     .ValueOr(L"Error during load DSN");
-    SQLPostInstallerError(err.GetNativeError(), wmessage_text.c_str());
-  }
-
-  return false;
-}
 
 void PostError(DWORD error_code, LPCWSTR error_msg) {
   MessageBox(NULL, error_msg, L"Error!", MB_ICONEXCLAMATION | MB_OK);
@@ -138,7 +96,7 @@ bool RegisterDsn(const Configuration& config, LPCWSTR 
driver) {
 
   const auto& map = config.GetProperties();
   for (auto it = map.begin(); it != map.end(); ++it) {
-    const std::string_view& key = it->first;
+    std::string_view key = it->first;
     if (boost::iequals(FlightSqlConnection::DSN, key) ||
         boost::iequals(FlightSqlConnection::DRIVER, key)) {
       continue;
@@ -167,77 +125,4 @@ bool RegisterDsn(const Configuration& config, LPCWSTR 
driver) {
 
   return true;
 }
-
-BOOL INSTAPI ConfigDSNW(HWND hwnd_parent, WORD req, LPCWSTR wdriver,
-                        LPCWSTR wattributes) {
-  Configuration config;
-  ConnectionStringParser parser(config);
-
-  auto attributes_result = 
arrow::util::WideStringToUTF8(std::wstring(wattributes));
-  if (!attributes_result.status().ok()) {
-    PostArrowUtilError(attributes_result.status());
-    return FALSE;
-  }
-  std::string attributes = attributes_result.ValueOrDie();
-
-  parser.ParseConfigAttributes(attributes.c_str());
-
-  switch (req) {
-    case ODBC_ADD_DSN: {
-      config.LoadDefaults();
-      if (!DisplayConnectionWindow(hwnd_parent, config) || 
!RegisterDsn(config, wdriver))
-        return FALSE;
-
-      break;
-    }
-
-    case ODBC_CONFIG_DSN: {
-      const std::string& dsn = config.Get(FlightSqlConnection::DSN);
-      auto wdsn_result = arrow::util::UTF8ToWideString(dsn);
-      if (!wdsn_result.status().ok()) {
-        PostArrowUtilError(wdsn_result.status());
-        return FALSE;
-      }
-      std::wstring wdsn = wdsn_result.ValueOrDie();
-      if (!SQLValidDSN(wdsn.c_str())) return FALSE;
-
-      Configuration loaded(config);
-      try {
-        loaded.LoadDsn(dsn);
-      } catch (const DriverException& err) {
-        std::string error_msg = err.GetMessageText();
-        std::wstring werror_msg =
-            arrow::util::UTF8ToWideString(error_msg).ValueOr(L"Error during 
DSN load");
-
-        PostError(err.GetNativeError(), werror_msg.c_str());
-        return FALSE;
-      }
-
-      if (!DisplayConnectionWindow(hwnd_parent, loaded) || 
!UnregisterDsn(wdsn.c_str()) ||
-          !RegisterDsn(loaded, wdriver))
-        return FALSE;
-
-      break;
-    }
-
-    case ODBC_REMOVE_DSN: {
-      const std::string& dsn = config.Get(FlightSqlConnection::DSN);
-      auto wdsn_result = arrow::util::UTF8ToWideString(dsn);
-      if (!wdsn_result.status().ok()) {
-        PostArrowUtilError(wdsn_result.status());
-        return FALSE;
-      }
-      std::wstring wdsn = wdsn_result.ValueOrDie();
-      if (!SQLValidDSN(wdsn.c_str()) || !UnregisterDsn(wdsn)) return FALSE;
-
-      break;
-    }
-
-    default:
-      return FALSE;
-  }
-
-  return TRUE;
-}
-
 }  // namespace arrow::flight::sql::odbc
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.h 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.h
index 32d17af675..5d23c3dfca 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.h
@@ -19,6 +19,7 @@
 #include "arrow/flight/sql/odbc/odbc_impl/platform.h"
 
 #include "arrow/flight/sql/odbc/odbc_impl/config/configuration.h"
+#include "arrow/status.h"
 
 namespace arrow::flight::sql::odbc {
 
@@ -65,4 +66,7 @@ bool RegisterDsn(const Configuration& config, LPCWSTR driver);
  */
 bool UnregisterDsn(const std::wstring& dsn);
 
+void PostError(DWORD error_code, LPCWSTR error_msg);
+
+void PostArrowUtilError(arrow::Status error_status);
 }  // namespace arrow::flight::sql::odbc
diff --git 
a/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc
index 3f49690daa..0432836a16 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.cc
@@ -18,9 +18,9 @@
 #include "arrow/result.h"
 #include "arrow/util/utf8.h"
 
-#include "arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.h"
-
 #include "arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h"
+#include "arrow/flight/sql/odbc/odbc_impl/ui/add_property_window.h"
+#include "arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.h"
 #include "arrow/flight/sql/odbc/odbc_impl/util.h"
 
 #include <Shlwapi.h>
@@ -30,16 +30,13 @@
 #include <sql.h>
 #include <sstream>
 
-#include "arrow/flight/sql/odbc/odbc_impl/ui/add_property_window.h"
-
 #define COMMON_TAB 0
 #define ADVANCED_TAB 1
 
 namespace arrow::flight::sql::odbc {
 namespace {
 std::string TestConnection(const config::Configuration& config) {
-  std::unique_ptr<FlightSqlConnection> flight_sql_conn(
-      new FlightSqlConnection(OdbcVersion::V_3));
+  std::unique_ptr<FlightSqlConnection> flight_sql_conn(new 
FlightSqlConnection(V_3));
 
   std::vector<std::string_view> missing_properties;
   flight_sql_conn->Connect(config.GetProperties(), missing_properties);
@@ -250,6 +247,7 @@ int 
DsnConfigurationWindow::CreateEncryptionSettingsGroup(int pos_x, int pos_y,
 
   std::string val = config_.Get(FlightSqlConnection::USE_ENCRYPTION);
 
+  // Enable encryption default value is true
   const bool enable_encryption = util::AsBool(val).value_or(true);
   labels_.push_back(CreateLabel(label_pos_x, row_pos, LABEL_WIDTH, ROW_HEIGHT,
                                 L"Use Encryption:", 
ChildId::ENABLE_ENCRYPTION_LABEL));
@@ -275,6 +273,7 @@ int 
DsnConfigurationWindow::CreateEncryptionSettingsGroup(int pos_x, int pos_y,
 
   val = config_.Get(FlightSqlConnection::USE_SYSTEM_TRUST_STORE).c_str();
 
+  // System trust store default value is true
   const bool use_system_cert_store = util::AsBool(val).value_or(true);
   labels_.push_back(CreateLabel(label_pos_x, row_pos, LABEL_WIDTH, 2 * 
ROW_HEIGHT,
                                 L"Use System Certificate Store:",
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc
index df6aff9cfa..59ee7dda56 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.cc
@@ -1108,8 +1108,8 @@ boost::optional<bool> AsBool(const std::string& value) {
 }
 
 boost::optional<bool> AsBool(const Connection::ConnPropertyMap& 
conn_property_map,
-                             const std::string_view& property_name) {
-  auto extracted_property = conn_property_map.find(std::string(property_name));
+                             std::string_view property_name) {
+  auto extracted_property = conn_property_map.find(property_name);
 
   if (extracted_property != conn_property_map.end()) {
     return AsBool(extracted_property->second);
@@ -1120,8 +1120,8 @@ boost::optional<bool> AsBool(const 
Connection::ConnPropertyMap& conn_property_ma
 
 boost::optional<int32_t> AsInt32(int32_t min_value,
                                  const Connection::ConnPropertyMap& 
conn_property_map,
-                                 const std::string_view& property_name) {
-  auto extracted_property = conn_property_map.find(std::string(property_name));
+                                 std::string_view property_name) {
+  auto extracted_property = conn_property_map.find(property_name);
 
   if (extracted_property != conn_property_map.end()) {
     const int32_t string_column_length = std::stoi(extracted_property->second);
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h
index 8197f741d1..c17e77e7de 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/util.h
@@ -144,7 +144,7 @@ boost::optional<bool> AsBool(const std::string& value);
 /// \param property_name      the name of the property that will be looked up.
 /// \return                   the parsed valued.
 boost::optional<bool> AsBool(const Connection::ConnPropertyMap& 
conn_property_map,
-                             const std::string_view& property_name);
+                             std::string_view property_name);
 
 /// Looks up for a value inside the ConnPropertyMap and then try to parse it.
 /// In case it does not find or it cannot parse, the default value will be 
returned.
@@ -156,7 +156,7 @@ boost::optional<bool> AsBool(const 
Connection::ConnPropertyMap& conn_property_ma
 /// std::out_of_range        exception from std::stoi
 boost::optional<int32_t> AsInt32(int32_t min_value,
                                  const Connection::ConnPropertyMap& 
conn_property_map,
-                                 const std::string_view& property_name);
+                                 std::string_view property_name);
 
 }  // namespace util
 }  // namespace arrow::flight::sql::odbc
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/win_system_dsn.cc
similarity index 65%
copy from cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.cc
copy to cpp/src/arrow/flight/sql/odbc/odbc_impl/win_system_dsn.cc
index 75501ac8dd..2ea9a2451c 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/system_dsn.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/win_system_dsn.cc
@@ -23,26 +23,29 @@
 
 #include <winuser.h>
 #include <utility>
+
+#include "arrow/result.h"
+#include "arrow/util/utf8.h"
+
 #include "arrow/flight/sql/odbc/odbc_impl/config/configuration.h"
 #include "arrow/flight/sql/odbc/odbc_impl/config/connection_string_parser.h"
 #include "arrow/flight/sql/odbc/odbc_impl/exceptions.h"
 #include "arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h"
 #include "arrow/flight/sql/odbc/odbc_impl/ui/dsn_configuration_window.h"
 #include "arrow/flight/sql/odbc/odbc_impl/ui/window.h"
-#include "arrow/result.h"
-#include "arrow/util/utf8.h"
+#include "arrow/util/logging.h"
 
 #include <odbcinst.h>
+#include <codecvt>
+#include <locale>
 #include <sstream>
 
 namespace arrow::flight::sql::odbc {
-
 using config::Configuration;
 using config::ConnectionStringParser;
 using config::DsnConfigurationWindow;
 using config::Result;
 using config::Window;
-
 bool DisplayConnectionWindow(void* window_parent, Configuration& config) {
   HWND hwnd_parent = (HWND)window_parent;
 
@@ -74,102 +77,34 @@ bool DisplayConnectionWindow(void* window_parent, 
Configuration& config) {
   return false;
 }
 
-void PostError(DWORD error_code, LPCWSTR error_msg) {
-  MessageBox(NULL, error_msg, L"Error!", MB_ICONEXCLAMATION | MB_OK);
-  SQLPostInstallerError(error_code, error_msg);
-}
-
-void PostArrowUtilError(arrow::Status error_status) {
-  std::string error_msg = error_status.message();
-  std::wstring werror_msg = arrow::util::UTF8ToWideString(error_msg).ValueOr(
-      L"Error during utf8 to wide string conversion");
-
-  PostError(ODBC_ERROR_GENERAL_ERR, werror_msg.c_str());
-}
-
-void PostLastInstallerError() {
-#define BUFFER_SIZE (1024)
-  DWORD code;
-  wchar_t msg[BUFFER_SIZE];
-  SQLInstallerError(1, &code, msg, BUFFER_SIZE, NULL);
-
-  std::wstringstream buf;
-  buf << L"Message: \"" << msg << L"\", Code: " << code;
-  std::wstring error_msg = buf.str();
-
-  PostError(code, error_msg.c_str());
-}
-
-/**
- * Unregister specified DSN.
- *
- * @param dsn DSN name.
- * @return True on success and false on fail.
- */
-bool UnregisterDsn(const std::wstring& dsn) {
-  if (SQLRemoveDSNFromIni(dsn.c_str())) {
-    return true;
+bool DisplayConnectionWindow(void* window_parent, Configuration& config,
+                             Connection::ConnPropertyMap& properties) {
+  for (const auto& [key, value] : properties) {
+    config.Set(key, value);
   }
 
-  PostLastInstallerError();
-  return false;
-}
-
-/**
- * Register DSN with specified configuration.
- *
- * @param config Configuration.
- * @param driver Driver.
- * @return True on success and false on fail.
- */
-bool RegisterDsn(const Configuration& config, LPCWSTR driver) {
-  const std::string& dsn = config.Get(FlightSqlConnection::DSN);
-  auto wdsn_result = arrow::util::UTF8ToWideString(dsn);
-  if (!wdsn_result.status().ok()) {
-    PostArrowUtilError(wdsn_result.status());
-    return false;
-  }
-  std::wstring wdsn = wdsn_result.ValueOrDie();
-
-  if (!SQLWriteDSNToIni(wdsn.c_str(), driver)) {
-    PostLastInstallerError();
+  if (DisplayConnectionWindow(window_parent, config)) {
+    properties = config.GetProperties();
+    return true;
+  } else {
+    ARROW_LOG(INFO) << "Dialog is cancelled by user";
     return false;
   }
-
-  const auto& map = config.GetProperties();
-  for (auto it = map.begin(); it != map.end(); ++it) {
-    const std::string_view& key = it->first;
-    if (boost::iequals(FlightSqlConnection::DSN, key) ||
-        boost::iequals(FlightSqlConnection::DRIVER, key)) {
-      continue;
-    }
-
-    auto wkey_result = arrow::util::UTF8ToWideString(key);
-    if (!wkey_result.status().ok()) {
-      PostArrowUtilError(wkey_result.status());
-      return false;
-    }
-    std::wstring wkey = wkey_result.ValueOrDie();
-
-    auto wvalue_result = arrow::util::UTF8ToWideString(it->second);
-    if (!wvalue_result.status().ok()) {
-      PostArrowUtilError(wvalue_result.status());
-      return false;
-    }
-    std::wstring wvalue = wvalue_result.ValueOrDie();
-
-    if (!SQLWritePrivateProfileString(wdsn.c_str(), wkey.c_str(), 
wvalue.c_str(),
-                                      L"ODBC.INI")) {
-      PostLastInstallerError();
-      return false;
-    }
-  }
-
-  return true;
 }
+}  // namespace arrow::flight::sql::odbc
 
 BOOL INSTAPI ConfigDSNW(HWND hwnd_parent, WORD req, LPCWSTR wdriver,
                         LPCWSTR wattributes) {
+  using arrow::flight::sql::odbc::DisplayConnectionWindow;
+  using arrow::flight::sql::odbc::DriverException;
+  using arrow::flight::sql::odbc::FlightSqlConnection;
+  using arrow::flight::sql::odbc::PostArrowUtilError;
+  using arrow::flight::sql::odbc::PostError;
+  using arrow::flight::sql::odbc::RegisterDsn;
+  using arrow::flight::sql::odbc::UnregisterDsn;
+  using arrow::flight::sql::odbc::config::Configuration;
+  using arrow::flight::sql::odbc::config::ConnectionStringParser;
+
   Configuration config;
   ConnectionStringParser parser(config);
 
@@ -239,5 +174,3 @@ BOOL INSTAPI ConfigDSNW(HWND hwnd_parent, WORD req, LPCWSTR 
wdriver,
 
   return TRUE;
 }
-
-}  // namespace arrow::flight::sql::odbc
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 c5646b42be..531250b69b 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc
@@ -14,6 +14,7 @@
 // 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"
@@ -29,34 +30,31 @@ namespace arrow::flight::sql::odbc {
 template <typename T>
 class ConnectionTest : public T {};
 
-// GH-46574 TODO: add remote server test cases using `ConnectionRemoteTest`
-class ConnectionRemoteTest : public FlightSQLODBCRemoteTestBase {};
-using TestTypes = ::testing::Types<FlightSQLODBCMockTestBase, 
ConnectionRemoteTest>;
+using TestTypes =
+    ::testing::Types<FlightSQLODBCMockTestBase, FlightSQLODBCRemoteTestBase>;
 TYPED_TEST_SUITE(ConnectionTest, TestTypes);
 
-TEST(SQLAllocHandle, TestSQLAllocHandleEnv) {
-  SQLHENV env;
-
-  // Allocate an environment handle
-  ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, 
&env));
-
-  ASSERT_NE(env, nullptr);
-
-  // Free an environment handle
-  ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_ENV, env));
-}
+template <typename T>
+class ConnectionHandleTest : public T {};
 
-TEST(SQLAllocEnv, TestSQLAllocEnv) {
-  SQLHENV env;
+class ConnectionRemoteTest : public FlightSQLOdbcHandleRemoteTestBase {};
+using TestTypesHandle =
+    ::testing::Types<FlightSQLOdbcHandleMockTestBase, 
FlightSQLOdbcHandleRemoteTestBase>;
+TYPED_TEST_SUITE(ConnectionHandleTest, TestTypesHandle);
 
+TEST(ODBCHandles, TestSQLAllocAndFreeEnv) {
   // Allocate an environment handle
+  SQLHENV env;
   ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
 
-  // Free an environment handle
+  // Check for valid handle
+  ASSERT_NE(nullptr, env);
+
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
 }
 
-TEST(SQLAllocHandle, TestSQLAllocHandleConnect) {
+TEST(ODBCHandles, TestSQLAllocAndFreeHandleConnect) {
   SQLHENV env;
   SQLHDBC conn;
 
@@ -66,14 +64,17 @@ TEST(SQLAllocHandle, TestSQLAllocHandleConnect) {
   // Allocate a connection using alloc handle
   ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DBC, env, &conn));
 
-  // Free a connection handle
+  // Check for valid handle
+  ASSERT_NE(nullptr, conn);
+
+  // Free the created connection using free handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DBC, conn));
 
-  // Free an environment handle
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_ENV, env));
 }
 
-TEST(SQLAllocConnect, TestSQLAllocHandleConnect) {
+TEST(ODBCHandles, TestSQLAllocAndFreeConnect) {
   SQLHENV env;
   SQLHDBC conn;
 
@@ -83,14 +84,17 @@ TEST(SQLAllocConnect, TestSQLAllocHandleConnect) {
   // Allocate a connection using alloc handle
   ASSERT_EQ(SQL_SUCCESS, SQLAllocConnect(env, &conn));
 
-  // Free a connection handle
+  // Check for valid handle
+  ASSERT_NE(nullptr, conn);
+
+  // Free the created connection using free connect
   ASSERT_EQ(SQL_SUCCESS, SQLFreeConnect(conn));
 
-  // Free an environment handle
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
 }
 
-TEST(SQLFreeHandle, TestFreeNullHandles) {
+TEST(ODBCHandles, TestFreeNullHandles) {
   SQLHENV env = NULL;
   SQLHDBC conn = NULL;
   SQLHSTMT stmt = NULL;
@@ -108,7 +112,6 @@ TEST(SQLFreeHandle, TestFreeNullHandles) {
 
 TEST(SQLGetEnvAttr, TestSQLGetEnvAttrODBCVersion) {
   SQLHENV env;
-
   SQLINTEGER version;
 
   // Allocate an environment handle
@@ -118,43 +121,37 @@ TEST(SQLGetEnvAttr, TestSQLGetEnvAttrODBCVersion) {
 
   ASSERT_EQ(SQL_OV_ODBC2, version);
 
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
 }
 
 TEST(SQLSetEnvAttr, TestSQLSetEnvAttrODBCVersionValid) {
-  SQLHENV env;
-
   // Allocate an environment handle
+  SQLHENV env;
   ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
 
-  // Attempt to set to supported version
+  // Attempt to set to unsupported 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);
-
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
 }
 
 TEST(SQLSetEnvAttr, TestSQLSetEnvAttrODBCVersionInvalid) {
-  SQLHENV env;
-
   // Allocate an environment handle
+  SQLHENV env;
   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));
 
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
 }
 
-// GH-46574 TODO: enable TestSQLGetEnvAttrOutputNTS which requires connection 
support
-TYPED_TEST(ConnectionTest, DISABLED_TestSQLGetEnvAttrOutputNTS) {
+TYPED_TEST(ConnectionTest, TestSQLGetEnvAttrOutputNTS) {
   SQLINTEGER output_nts;
 
   ASSERT_EQ(SQL_SUCCESS,
@@ -183,41 +180,292 @@ TYPED_TEST(ConnectionTest, 
DISABLED_TestSQLGetEnvAttrNullValuePointer) {
 }
 
 TEST(SQLSetEnvAttr, TestSQLSetEnvAttrOutputNTSValid) {
-  SQLHENV env;
-
   // Allocate an environment handle
+  SQLHENV env;
   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));
 
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
 }
 
 TEST(SQLSetEnvAttr, TestSQLSetEnvAttrOutputNTSInvalid) {
-  SQLHENV env;
-
   // Allocate an environment handle
+  SQLHENV env;
   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));
 
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
 }
 
 TEST(SQLSetEnvAttr, TestSQLSetEnvAttrNullValuePointer) {
-  SQLHENV env;
-
   // Allocate an environment handle
+  SQLHENV env;
   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));
 
+  // Free environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env));
 }
 
+TYPED_TEST(ConnectionHandleTest, TestSQLDriverConnect) {
+  // Connect string
+  std::string connect_str = this->GetConnectionString();
+  ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str,
+                       arrow::util::UTF8ToWideString(connect_str));
+  std::vector<SQLWCHAR> connect_str0(wconnect_str.begin(), wconnect_str.end());
+
+  SQLWCHAR out_str[kOdbcBufferSize] = L"";
+  SQLSMALLINT out_str_len;
+
+  // Connecting to ODBC server.
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLDriverConnect(this->conn, NULL, &connect_str0[0],
+                             static_cast<SQLSMALLINT>(connect_str0.size()), 
out_str,
+                             kOdbcBufferSize, &out_str_len, 
SQL_DRIVER_NOPROMPT))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
+
+  // Check that out_str has same content as connect_str
+  std::string out_connection_string = ODBC::SqlWcharToString(out_str, 
out_str_len);
+  Connection::ConnPropertyMap out_properties;
+  Connection::ConnPropertyMap in_properties;
+  ODBC::ODBCConnection::GetPropertiesFromConnString(out_connection_string,
+                                                    out_properties);
+  ODBC::ODBCConnection::GetPropertiesFromConnString(connect_str, 
in_properties);
+  ASSERT_TRUE(CompareConnPropertyMap(out_properties, in_properties));
+
+  // Disconnect from ODBC
+  ASSERT_EQ(SQL_SUCCESS, SQLDisconnect(this->conn))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
+}
+
+#if defined _WIN32
+TYPED_TEST(ConnectionHandleTest, TestSQLDriverConnectDsn) {
+  // Connect string
+  std::string connect_str = this->GetConnectionString();
+
+  // Write connection string content into a DSN,
+  // must succeed before continuing
+  ASSERT_TRUE(WriteDSN(connect_str));
+
+  std::string dsn(kTestDsn);
+  ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn));
+
+  // Update connection string to use DSN to connect
+  connect_str = std::string("DSN=") + std::string(kTestDsn) +
+                std::string(";driver={Apache Arrow Flight SQL ODBC Driver};");
+  ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str,
+                       arrow::util::UTF8ToWideString(connect_str));
+  std::vector<SQLWCHAR> connect_str0(wconnect_str.begin(), wconnect_str.end());
+
+  SQLWCHAR out_str[kOdbcBufferSize] = L"";
+  SQLSMALLINT out_str_len;
+
+  // Connecting to ODBC server.
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLDriverConnect(this->conn, NULL, &connect_str0[0],
+                             static_cast<SQLSMALLINT>(connect_str0.size()), 
out_str,
+                             kOdbcBufferSize, &out_str_len, 
SQL_DRIVER_NOPROMPT))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
+
+  // Remove DSN
+  ASSERT_TRUE(UnregisterDsn(wdsn));
+
+  // Disconnect from ODBC
+  ASSERT_EQ(SQL_SUCCESS, SQLDisconnect(this->conn))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
+}
+
+TYPED_TEST(ConnectionHandleTest, TestSQLConnect) {
+  // Connect string
+  std::string connect_str = this->GetConnectionString();
+
+  // Write connection string content into a DSN,
+  // must succeed before continuing
+  std::string uid(""), pwd("");
+  ASSERT_TRUE(WriteDSN(connect_str));
+
+  std::string dsn(kTestDsn);
+  ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn));
+  ASSERT_OK_AND_ASSIGN(std::wstring wuid, arrow::util::UTF8ToWideString(uid));
+  ASSERT_OK_AND_ASSIGN(std::wstring wpwd, arrow::util::UTF8ToWideString(pwd));
+  std::vector<SQLWCHAR> dsn0(wdsn.begin(), wdsn.end());
+  std::vector<SQLWCHAR> uid0(wuid.begin(), wuid.end());
+  std::vector<SQLWCHAR> pwd0(wpwd.begin(), wpwd.end());
+
+  // Connecting to ODBC server. Empty uid and pwd should be ignored.
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLConnect(this->conn, dsn0.data(), 
static_cast<SQLSMALLINT>(dsn0.size()),
+                       uid0.data(), static_cast<SQLSMALLINT>(uid0.size()), 
pwd0.data(),
+                       static_cast<SQLSMALLINT>(pwd0.size())))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
+
+  // Remove DSN
+  ASSERT_TRUE(UnregisterDsn(wdsn));
+
+  // Disconnect from ODBC
+  ASSERT_EQ(SQL_SUCCESS, SQLDisconnect(this->conn))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, this->conn);
+}
+
+TEST_F(ConnectionRemoteTest, TestSQLConnectInputUidPwd) {
+  // Connect string
+  std::string connect_str = GetConnectionString();
+
+  // Retrieve valid uid and pwd, assumes TEST_CONNECT_STR contains uid and pwd
+  Connection::ConnPropertyMap properties;
+  ODBC::ODBCConnection::GetPropertiesFromConnString(connect_str, properties);
+  std::string uid_key("uid");
+  std::string pwd_key("pwd");
+  std::string uid = properties[uid_key];
+  std::string pwd = properties[pwd_key];
+
+  // Write connection string content without uid and pwd into a DSN,
+  // must succeed before continuing
+  properties.erase(uid_key);
+  properties.erase(pwd_key);
+  ASSERT_TRUE(WriteDSN(properties));
+
+  std::string dsn(kTestDsn);
+  ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn));
+  ASSERT_OK_AND_ASSIGN(std::wstring wuid, arrow::util::UTF8ToWideString(uid));
+  ASSERT_OK_AND_ASSIGN(std::wstring wpwd, arrow::util::UTF8ToWideString(pwd));
+  std::vector<SQLWCHAR> dsn0(wdsn.begin(), wdsn.end());
+  std::vector<SQLWCHAR> uid0(wuid.begin(), wuid.end());
+  std::vector<SQLWCHAR> pwd0(wpwd.begin(), wpwd.end());
+
+  // Connecting to ODBC server.
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLConnect(this->conn, dsn0.data(), 
static_cast<SQLSMALLINT>(dsn0.size()),
+                       uid0.data(), static_cast<SQLSMALLINT>(uid0.size()), 
pwd0.data(),
+                       static_cast<SQLSMALLINT>(pwd0.size())))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn);
+
+  // Remove DSN
+  ASSERT_TRUE(UnregisterDsn(wdsn));
+
+  // Disconnect from ODBC
+  ASSERT_EQ(SQL_SUCCESS, SQLDisconnect(this->conn))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn);
+}
+
+TEST_F(ConnectionRemoteTest, TestSQLConnectInvalidUid) {
+  // Connect string
+  std::string connect_str = GetConnectionString();
+
+  // Retrieve valid uid and pwd, assumes TEST_CONNECT_STR contains uid and pwd
+  Connection::ConnPropertyMap properties;
+  ODBC::ODBCConnection::GetPropertiesFromConnString(connect_str, properties);
+  std::string uid = properties["uid"];
+  std::string pwd = properties["pwd"];
+
+  // Append invalid uid to connection string
+  connect_str += "uid=non_existent_id;";
+
+  // Write connection string content into a DSN,
+  // must succeed before continuing
+  ASSERT_TRUE(WriteDSN(connect_str));
+
+  std::string dsn(kTestDsn);
+  ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn));
+  ASSERT_OK_AND_ASSIGN(std::wstring wuid, arrow::util::UTF8ToWideString(uid));
+  ASSERT_OK_AND_ASSIGN(std::wstring wpwd, arrow::util::UTF8ToWideString(pwd));
+  std::vector<SQLWCHAR> dsn0(wdsn.begin(), wdsn.end());
+  std::vector<SQLWCHAR> uid0(wuid.begin(), wuid.end());
+  std::vector<SQLWCHAR> pwd0(wpwd.begin(), wpwd.end());
+
+  // Connecting to ODBC server.
+  // UID specified in DSN will take precedence,
+  // so connection still fails despite passing valid uid in SQLConnect call
+  ASSERT_EQ(SQL_ERROR,
+            SQLConnect(this->conn, dsn0.data(), 
static_cast<SQLSMALLINT>(dsn0.size()),
+                       uid0.data(), static_cast<SQLSMALLINT>(uid0.size()), 
pwd0.data(),
+                       static_cast<SQLSMALLINT>(pwd0.size())));
+
+  VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorState28000);
+
+  // Remove DSN
+  ASSERT_TRUE(UnregisterDsn(wdsn));
+}
+
+TEST_F(ConnectionRemoteTest, TestSQLConnectDSNPrecedence) {
+  // Connect string
+  std::string connect_str = GetConnectionString();
+
+  // Write connection string content into a DSN,
+  // must succeed before continuing
+
+  // Pass incorrect uid and password to SQLConnect, they will be ignored.
+  // Assumes TEST_CONNECT_STR contains uid and pwd
+  std::string uid("non_existent_id"), pwd("non_existent_password");
+  ASSERT_TRUE(WriteDSN(connect_str));
+
+  std::string dsn(kTestDsn);
+  ASSERT_OK_AND_ASSIGN(std::wstring wdsn, arrow::util::UTF8ToWideString(dsn));
+  ASSERT_OK_AND_ASSIGN(std::wstring wuid, arrow::util::UTF8ToWideString(uid));
+  ASSERT_OK_AND_ASSIGN(std::wstring wpwd, arrow::util::UTF8ToWideString(pwd));
+  std::vector<SQLWCHAR> dsn0(wdsn.begin(), wdsn.end());
+  std::vector<SQLWCHAR> uid0(wuid.begin(), wuid.end());
+  std::vector<SQLWCHAR> pwd0(wpwd.begin(), wpwd.end());
+
+  // Connecting to ODBC server.
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLConnect(this->conn, dsn0.data(), 
static_cast<SQLSMALLINT>(dsn0.size()),
+                       uid0.data(), static_cast<SQLSMALLINT>(uid0.size()), 
pwd0.data(),
+                       static_cast<SQLSMALLINT>(pwd0.size())))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn);
+
+  // Remove DSN
+  ASSERT_TRUE(UnregisterDsn(wdsn));
+
+  // Disconnect from ODBC
+  ASSERT_EQ(SQL_SUCCESS, SQLDisconnect(this->conn))
+      << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn);
+}
+
+#endif
+
+TEST_F(ConnectionRemoteTest, TestSQLDriverConnectInvalidUid) {
+  // Invalid connect string
+  std::string connect_str = GetInvalidConnectionString();
+
+  ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str,
+                       arrow::util::UTF8ToWideString(connect_str));
+  std::vector<SQLWCHAR> connect_str0(wconnect_str.begin(), wconnect_str.end());
+
+  SQLWCHAR out_str[kOdbcBufferSize];
+  SQLSMALLINT out_str_len;
+
+  // Connecting to ODBC server.
+  ASSERT_EQ(SQL_ERROR,
+            SQLDriverConnect(this->conn, NULL, &connect_str0[0],
+                             static_cast<SQLSMALLINT>(connect_str0.size()), 
out_str,
+                             kOdbcBufferSize, &out_str_len, 
SQL_DRIVER_NOPROMPT));
+
+  VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorState28000);
+
+  std::string out_connection_string = ODBC::SqlWcharToString(out_str, 
out_str_len);
+  ASSERT_TRUE(out_connection_string.empty());
+}
+
+TYPED_TEST(ConnectionHandleTest, TestSQLDisconnectWithoutConnection) {
+  // Attempt to disconnect without a connection, expect to fail
+  ASSERT_EQ(SQL_ERROR, SQLDisconnect(this->conn));
+
+  // Expect ODBC driver manager to return error state
+  VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorState08003);
+}
+
+TYPED_TEST(ConnectionTest, TestConnect) {
+  // Verifies connect and disconnect works on its own
+}
 }  // namespace arrow::flight::sql::odbc
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
index fccb552575..eb6c60b976 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
@@ -28,7 +28,7 @@
 
 namespace arrow::flight::sql::odbc {
 
-void FlightSQLODBCRemoteTestBase::AllocEnvConnHandles(SQLINTEGER odbc_ver) {
+void ODBCRemoteTestBase::AllocEnvConnHandles(SQLINTEGER odbc_ver) {
   // Allocate an environment handle
   ASSERT_EQ(SQL_SUCCESS, SQLAllocEnv(&env));
 
@@ -41,13 +41,13 @@ void 
FlightSQLODBCRemoteTestBase::AllocEnvConnHandles(SQLINTEGER odbc_ver) {
   ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DBC, env, &conn));
 }
 
-void FlightSQLODBCRemoteTestBase::Connect(SQLINTEGER odbc_ver) {
+void ODBCRemoteTestBase::Connect(SQLINTEGER odbc_ver) {
   ASSERT_NO_FATAL_FAILURE(AllocEnvConnHandles(odbc_ver));
   std::string connect_str = GetConnectionString();
   ASSERT_NO_FATAL_FAILURE(ConnectWithString(connect_str));
 }
 
-void FlightSQLODBCRemoteTestBase::ConnectWithString(std::string connect_str) {
+void ODBCRemoteTestBase::ConnectWithString(std::string connect_str) {
   // Connect string
   std::vector<SQLWCHAR> connect_str0(connect_str.begin(), connect_str.end());
 
@@ -61,18 +61,22 @@ void 
FlightSQLODBCRemoteTestBase::ConnectWithString(std::string connect_str) {
                              kOdbcBufferSize, &out_str_len, 
SQL_DRIVER_NOPROMPT))
       << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn);
 
-  // Allocate a statement using alloc handle
-  ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt));
+  // GH-47710: TODO Allocate a statement using alloc handle
+  // ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt));
 }
 
-void FlightSQLODBCRemoteTestBase::Disconnect() {
-  // Close statement
-  EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_STMT, stmt));
+void ODBCRemoteTestBase::Disconnect() {
+  // GH-47710: TODO Close statement
+  // EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_STMT, stmt));
 
   // Disconnect from ODBC
   EXPECT_EQ(SQL_SUCCESS, SQLDisconnect(conn))
       << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn);
 
+  FreeEnvConnHandles();
+}
+
+void ODBCRemoteTestBase::FreeEnvConnHandles() {
   // Free connection handle
   EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DBC, conn));
 
@@ -80,20 +84,20 @@ void FlightSQLODBCRemoteTestBase::Disconnect() {
   EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_ENV, env));
 }
 
-std::string FlightSQLODBCRemoteTestBase::GetConnectionString() {
+std::string ODBCRemoteTestBase::GetConnectionString() {
   std::string connect_str =
       arrow::internal::GetEnvVar(kTestConnectStr.data()).ValueOrDie();
   return connect_str;
 }
 
-std::string FlightSQLODBCRemoteTestBase::GetInvalidConnectionString() {
+std::string ODBCRemoteTestBase::GetInvalidConnectionString() {
   std::string connect_str = GetConnectionString();
   // Append invalid uid to connection string
   connect_str += std::string("uid=non_existent_id;");
   return connect_str;
 }
 
-std::wstring FlightSQLODBCRemoteTestBase::GetQueryAllDataTypes() {
+std::wstring ODBCRemoteTestBase::GetQueryAllDataTypes() {
   std::wstring wsql =
       LR"( SELECT
            -- Numeric types
@@ -144,10 +148,18 @@ std::wstring 
FlightSQLODBCRemoteTestBase::GetQueryAllDataTypes() {
   return wsql;
 }
 
-void FlightSQLODBCRemoteTestBase::SetUp() {
+void ODBCRemoteTestBase::SetUp() {
   if (arrow::internal::GetEnvVar(kTestConnectStr.data()).ValueOr("").empty()) {
+    skipping_test_ = true;
     GTEST_SKIP() << "Skipping test: kTestConnectStr not set";
   }
+}
+
+void FlightSQLODBCRemoteTestBase::SetUp() {
+  ODBCRemoteTestBase::SetUp();
+  if (skipping_test_) {
+    return;
+  }
 
   this->Connect();
   connected_ = true;
@@ -161,14 +173,32 @@ void FlightSQLODBCRemoteTestBase::TearDown() {
 }
 
 void FlightSQLOdbcV2RemoteTestBase::SetUp() {
-  if (arrow::internal::GetEnvVar(kTestConnectStr.data()).ValueOr("").empty()) {
-    GTEST_SKIP() << "Skipping test: kTestConnectStr not set";
+  ODBCRemoteTestBase::SetUp();
+  if (skipping_test_) {
+    return;
   }
 
   this->Connect(SQL_OV_ODBC2);
   connected_ = true;
 }
 
+void FlightSQLOdbcHandleRemoteTestBase::SetUp() {
+  ODBCRemoteTestBase::SetUp();
+  if (skipping_test_) {
+    return;
+  }
+
+  this->AllocEnvConnHandles();
+  allocated_ = true;
+}
+
+void FlightSQLOdbcHandleRemoteTestBase::TearDown() {
+  if (allocated_) {
+    this->FreeEnvConnHandles();
+    allocated_ = false;
+  }
+}
+
 std::string FindTokenInCallHeaders(const CallHeaders& incoming_headers) {
   // Lambda function to compare characters without case sensitivity.
   auto char_compare = [](const char& char1, const char& char2) {
@@ -209,7 +239,7 @@ Status MockServerMiddlewareFactory::StartCall(
   return Status::OK();
 }
 
-std::string FlightSQLODBCMockTestBase::GetConnectionString() {
+std::string ODBCMockTestBase::GetConnectionString() {
   std::string connect_str(
       "driver={Apache Arrow Flight SQL ODBC Driver};HOST=localhost;port=" +
       std::to_string(port) + ";token=" + std::string(kTestToken) +
@@ -217,14 +247,14 @@ std::string 
FlightSQLODBCMockTestBase::GetConnectionString() {
   return connect_str;
 }
 
-std::string FlightSQLODBCMockTestBase::GetInvalidConnectionString() {
+std::string ODBCMockTestBase::GetInvalidConnectionString() {
   std::string connect_str = GetConnectionString();
   // Append invalid token to connection string
   connect_str += std::string("token=invalid_token;");
   return connect_str;
 }
 
-std::wstring FlightSQLODBCMockTestBase::GetQueryAllDataTypes() {
+std::wstring ODBCMockTestBase::GetQueryAllDataTypes() {
   std::wstring wsql =
       LR"( SELECT
       -- Numeric types
@@ -273,7 +303,7 @@ std::wstring 
FlightSQLODBCMockTestBase::GetQueryAllDataTypes() {
   return wsql;
 }
 
-void FlightSQLODBCMockTestBase::CreateTestTables() {
+void ODBCMockTestBase::CreateTestTables() {
   ASSERT_OK(server_->ExecuteSql(R"(
     CREATE TABLE TestTable (
     id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -286,7 +316,7 @@ void FlightSQLODBCMockTestBase::CreateTestTables() {
   )"));
 }
 
-void FlightSQLODBCMockTestBase::CreateTableAllDataType() {
+void ODBCMockTestBase::CreateTableAllDataType() {
   // Limitation on mock SQLite server:
   // Only int64, float64, binary, and utf8 Arrow Types are supported by
   // SQLiteFlightSqlServer::Impl::DoGetTables
@@ -308,7 +338,7 @@ void FlightSQLODBCMockTestBase::CreateTableAllDataType() {
   )"));
 }
 
-void FlightSQLODBCMockTestBase::CreateUnicodeTable() {
+void ODBCMockTestBase::CreateUnicodeTable() {
   std::string unicode_sql = arrow::util::WideStringToUTF8(
                                 LR"(
     CREATE TABLE 数据(
@@ -322,7 +352,7 @@ void FlightSQLODBCMockTestBase::CreateUnicodeTable() {
   ASSERT_OK(server_->ExecuteSql(unicode_sql));
 }
 
-void FlightSQLODBCMockTestBase::Initialize() {
+void ODBCMockTestBase::SetUp() {
   ASSERT_OK_AND_ASSIGN(auto location, Location::ForGrpcTcp("0.0.0.0", 0));
   arrow::flight::FlightServerOptions options(location);
   options.auth_handler = std::make_unique<NoOpAuthHandler>();
@@ -338,25 +368,40 @@ void FlightSQLODBCMockTestBase::Initialize() {
 }
 
 void FlightSQLODBCMockTestBase::SetUp() {
-  this->Initialize();
+  ODBCMockTestBase::SetUp();
   this->Connect();
   connected_ = true;
 }
 
+void ODBCMockTestBase::TearDown() {
+  ASSERT_OK(server_->Shutdown());
+  ASSERT_OK(server_->Wait());
+}
+
 void FlightSQLODBCMockTestBase::TearDown() {
   if (connected_) {
     this->Disconnect();
     connected_ = false;
   }
-  ASSERT_OK(server_->Shutdown());
+  ODBCMockTestBase::TearDown();
 }
 
 void FlightSQLOdbcV2MockTestBase::SetUp() {
-  this->Initialize();
+  ODBCMockTestBase::SetUp();
   this->Connect(SQL_OV_ODBC2);
   connected_ = true;
 }
 
+void FlightSQLOdbcHandleMockTestBase::SetUp() {
+  ODBCMockTestBase::SetUp();
+  this->AllocEnvConnHandles();
+}
+
+void FlightSQLOdbcHandleMockTestBase::TearDown() {
+  this->FreeEnvConnHandles();
+  ODBCMockTestBase::TearDown();
+}
+
 bool CompareConnPropertyMap(Connection::ConnPropertyMap map1,
                             Connection::ConnPropertyMap map2) {
   if (map1.size() != map2.size()) return false;
@@ -411,7 +456,7 @@ std::string GetOdbcErrorMessage(SQLSMALLINT handle_type, 
SQLHANDLE handle) {
   return res;
 }
 
-// TODO: once RegisterDsn is implemented in Mac and Linux, the following can be
+// GH-47822 TODO: once RegisterDsn is implemented in Mac and Linux, the 
following can be
 // re-enabled.
 #if defined _WIN32
 bool WriteDSN(std::string connection_str) {
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h 
b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h
index e35e6c38f8..e043a459f0 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h
+++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h
@@ -42,14 +42,16 @@ static constexpr std::string_view kTestDsn = "Apache Arrow 
Flight SQL Test DSN";
 namespace arrow::flight::sql::odbc {
 
 /// \brief Base test fixture for running tests against a remote server.
-/// Each test file running remote server tests should define a
-/// fixture inheriting from this base fixture.
 /// The connection string for connecting to this server is defined
 /// in the ARROW_FLIGHT_SQL_ODBC_CONN environment variable.
-class FlightSQLODBCRemoteTestBase : public ::testing::Test {
+/// Note that this fixture does not handle the driver's 
connection/disconnection
+/// during SetUp/Teardown.
+class ODBCRemoteTestBase : public ::testing::Test {
  public:
   /// \brief Allocate environment and connection handles
   void AllocEnvConnHandles(SQLINTEGER odbc_ver = SQL_OV_ODBC3);
+  /// \brief Free environment and connection handles
+  void FreeEnvConnHandles();
   /// \brief Connect to Arrow Flight SQL server using connection string 
defined in
   /// environment variable "ARROW_FLIGHT_SQL_ODBC_CONN", allocate statement 
handle.
   /// Connects using ODBC Ver 3 by default
@@ -75,6 +77,18 @@ class FlightSQLODBCRemoteTestBase : public ::testing::Test {
   /** ODBC Statement. */
   SQLHSTMT stmt = 0;
 
+ protected:
+  void SetUp() override;
+
+  bool skipping_test_ = false;
+};
+
+/// \brief Base test fixture for running tests against a remote server.
+/// Each test file running remote server tests should define a
+/// fixture inheriting from this base fixture.
+/// The connection string for connecting to this server is defined
+/// in the ARROW_FLIGHT_SQL_ODBC_CONN environment variable.
+class FlightSQLODBCRemoteTestBase : public ODBCRemoteTestBase {
  protected:
   void SetUp() override;
 
@@ -91,6 +105,14 @@ class FlightSQLOdbcV2RemoteTestBase : public 
FlightSQLODBCRemoteTestBase {
   void SetUp() override;
 };
 
+class FlightSQLOdbcHandleRemoteTestBase : public FlightSQLODBCRemoteTestBase {
+ protected:
+  void SetUp() override;
+  void TearDown() override;
+
+  bool allocated_ = false;
+};
+
 static constexpr std::string_view kAuthorizationHeader = "authorization";
 static constexpr std::string_view kBearerPrefix = "Bearer ";
 static constexpr std::string_view kTestToken = "t0k3n";
@@ -129,9 +151,7 @@ class MockServerMiddlewareFactory : public 
ServerMiddlewareFactory {
 };
 
 /// \brief Base test fixture for running tests against a mock server.
-/// Each test file running mock server tests should define a
-/// fixture inheriting from this base fixture.
-class FlightSQLODBCMockTestBase : public FlightSQLODBCRemoteTestBase {
+class ODBCMockTestBase : public FlightSQLODBCRemoteTestBase {
   // Sets up a mock server for each test case
  public:
   /// \brief Get connection string for mock server
@@ -152,16 +172,23 @@ class FlightSQLODBCMockTestBase : public 
FlightSQLODBCRemoteTestBase {
   int port;
 
  protected:
-  void Initialize();
-
   void SetUp() override;
 
   void TearDown() override;
 
- private:
   std::shared_ptr<arrow::flight::sql::example::SQLiteFlightSqlServer> server_;
 };
 
+/// \brief Base test fixture for running tests against a mock server.
+/// Each test file running mock server tests should define a
+/// fixture inheriting from this base fixture.
+class FlightSQLODBCMockTestBase : public ODBCMockTestBase {
+ protected:
+  void SetUp() override;
+
+  void TearDown() override;
+};
+
 /// \brief Base test fixture for running ODBC V2 tests against a mock server.
 /// Each test file running mock server ODBC V2 tests should define a
 /// fixture inheriting from this base fixture.
@@ -170,6 +197,12 @@ class FlightSQLOdbcV2MockTestBase : public 
FlightSQLODBCMockTestBase {
   void SetUp() override;
 };
 
+class FlightSQLOdbcHandleMockTestBase : public FlightSQLODBCMockTestBase {
+ protected:
+  void SetUp() override;
+  void TearDown() override;
+};
+
 /** ODBC read buffer size. */
 static constexpr int kOdbcBufferSize = 1024;
 

Reply via email to