lidavidm commented on code in PR #40939:
URL: https://github.com/apache/arrow/pull/40939#discussion_r2099229799


##########
cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.h:
##########
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#pragma once
+
+#include "arrow/flight/sql/client.h"
+#include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h"
+#include "arrow/flight/sql/odbc/flight_sql/record_batch_transformer.h"
+#include 
"arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/diagnostics.h"
+#include 
"arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h"
+#include 
"arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h"
+#include "arrow/flight/types.h"
+#include "arrow/type.h"
+
+namespace driver {
+namespace flight_sql {
+
+using arrow::flight::FlightCallOptions;
+using arrow::flight::sql::FlightSqlClient;
+using odbcabstraction::MetadataSettings;
+using odbcabstraction::ResultSet;
+
+typedef struct {

Review Comment:
   Why the C-ism? Just `struct ColumnNames`.



##########
cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_connection.cc:
##########
@@ -0,0 +1,439 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "arrow/flight/sql/odbc/flight_sql/flight_sql_connection.h"
+
+#include 
"arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h"
+#include 
"arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/utils.h"
+
+#include "arrow/flight/client_cookie_middleware.h"
+#include "arrow/flight/sql/odbc/flight_sql/address_info.h"
+#include "arrow/flight/sql/odbc/flight_sql/flight_sql_auth_method.h"
+#include "arrow/flight/sql/odbc/flight_sql/flight_sql_ssl_config.h"
+#include "arrow/flight/sql/odbc/flight_sql/flight_sql_statement.h"
+#include "arrow/flight/sql/odbc/flight_sql/utils.h"
+#include "arrow/flight/types.h"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/asio/ip/address.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/optional.hpp>
+#include 
"arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h"
+
+#include <sql.h>
+#include <sqlext.h>
+
+#include "arrow/flight/sql/odbc/flight_sql/system_trust_store.h"
+
+#ifndef NI_MAXHOST
+#  define NI_MAXHOST 1025
+#endif
+
+namespace driver {
+namespace flight_sql {
+
+using arrow::Result;
+using arrow::Status;
+using arrow::flight::FlightCallOptions;
+using arrow::flight::FlightClient;
+using arrow::flight::FlightClientOptions;
+using arrow::flight::Location;
+using arrow::flight::TimeoutDuration;
+using arrow::flight::sql::FlightSqlClient;
+using driver::odbcabstraction::AsBool;
+using driver::odbcabstraction::CommunicationException;
+using driver::odbcabstraction::Connection;
+using driver::odbcabstraction::DriverException;
+using driver::odbcabstraction::OdbcVersion;
+using driver::odbcabstraction::Statement;
+
+const std::vector<std::string_view> FlightSqlConnection::ALL_KEYS = {
+    FlightSqlConnection::DSN,
+    FlightSqlConnection::DRIVER,
+    FlightSqlConnection::HOST,
+    FlightSqlConnection::PORT,
+    FlightSqlConnection::TOKEN,
+    FlightSqlConnection::UID,
+    FlightSqlConnection::USER_ID,
+    FlightSqlConnection::PWD,
+    FlightSqlConnection::USE_ENCRYPTION,
+    FlightSqlConnection::TRUSTED_CERTS,
+    FlightSqlConnection::USE_SYSTEM_TRUST_STORE,
+    FlightSqlConnection::DISABLE_CERTIFICATE_VERIFICATION,
+    FlightSqlConnection::STRING_COLUMN_LENGTH,
+    FlightSqlConnection::USE_WIDE_CHAR,
+    FlightSqlConnection::CHUNK_BUFFER_CAPACITY};
+
+namespace {
+
+#if _WIN32 || _WIN64
+constexpr auto SYSTEM_TRUST_STORE_DEFAULT = true;
+constexpr auto STORES = {"CA", "MY", "ROOT", "SPC"};
+
+inline std::string GetCerts() {

Review Comment:
   Why is this `inline`?



##########
cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc:
##########
@@ -0,0 +1,787 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h>
+
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/statement.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h>
+#include <sql.h>
+#include <sqlext.h>
+#include <sqltypes.h>
+#include <boost/optional.hpp>
+#include <boost/variant.hpp>
+#include <utility>
+
+using ODBC::DescriptorRecord;
+using ODBC::ODBCConnection;
+using ODBC::ODBCDescriptor;
+using ODBC::ODBCStatement;
+
+using driver::odbcabstraction::DriverException;
+using driver::odbcabstraction::ResultSetMetadata;
+using driver::odbcabstraction::Statement;
+
+namespace {
+void DescriptorToHandle(SQLPOINTER output, ODBCDescriptor* descriptor,
+                        SQLINTEGER* lenPtr) {
+  if (output) {
+    SQLHANDLE* outputHandle = static_cast<SQLHANDLE*>(output);
+    *outputHandle = reinterpret_cast<SQLHANDLE>(descriptor);
+  }
+  if (lenPtr) {
+    *lenPtr = sizeof(SQLHANDLE);
+  }
+}
+
+size_t GetLength(const DescriptorRecord& record) {
+  switch (record.m_type) {
+    case SQL_C_CHAR:
+    case SQL_C_WCHAR:
+    case SQL_C_BINARY:
+      return record.m_length;
+
+    case SQL_C_BIT:
+    case SQL_C_TINYINT:
+    case SQL_C_STINYINT:
+    case SQL_C_UTINYINT:
+      return sizeof(SQLSCHAR);
+
+    case SQL_C_SHORT:
+    case SQL_C_SSHORT:
+    case SQL_C_USHORT:
+      return sizeof(SQLSMALLINT);
+
+    case SQL_C_LONG:
+    case SQL_C_SLONG:
+    case SQL_C_ULONG:
+    case SQL_C_FLOAT:
+      return sizeof(SQLINTEGER);
+
+    case SQL_C_SBIGINT:
+    case SQL_C_UBIGINT:
+    case SQL_C_DOUBLE:
+      return sizeof(SQLBIGINT);
+
+    case SQL_C_NUMERIC:
+      return sizeof(SQL_NUMERIC_STRUCT);
+
+    case SQL_C_DATE:
+    case SQL_C_TYPE_DATE:
+      return sizeof(SQL_DATE_STRUCT);
+
+    case SQL_C_TIME:
+    case SQL_C_TYPE_TIME:
+      return sizeof(SQL_TIME_STRUCT);
+
+    case SQL_C_TIMESTAMP:
+    case SQL_C_TYPE_TIMESTAMP:
+      return sizeof(SQL_TIMESTAMP_STRUCT);
+
+    case SQL_C_INTERVAL_DAY:
+    case SQL_C_INTERVAL_DAY_TO_HOUR:
+    case SQL_C_INTERVAL_DAY_TO_MINUTE:
+    case SQL_C_INTERVAL_DAY_TO_SECOND:
+    case SQL_C_INTERVAL_HOUR:
+    case SQL_C_INTERVAL_HOUR_TO_MINUTE:
+    case SQL_C_INTERVAL_HOUR_TO_SECOND:
+    case SQL_C_INTERVAL_MINUTE:
+    case SQL_C_INTERVAL_MINUTE_TO_SECOND:
+    case SQL_C_INTERVAL_SECOND:
+    case SQL_C_INTERVAL_YEAR:
+    case SQL_C_INTERVAL_YEAR_TO_MONTH:
+    case SQL_C_INTERVAL_MONTH:
+      return sizeof(SQL_INTERVAL_STRUCT);
+    default:
+      return record.m_length;
+  }
+}
+
+SQLSMALLINT getCTypeForSQLType(const DescriptorRecord& record) {
+  switch (record.m_conciseType) {
+    case SQL_CHAR:
+    case SQL_VARCHAR:
+    case SQL_LONGVARCHAR:
+      return SQL_C_CHAR;
+
+    case SQL_WCHAR:
+    case SQL_WVARCHAR:
+    case SQL_WLONGVARCHAR:
+      return SQL_C_WCHAR;
+
+    case SQL_BINARY:
+    case SQL_VARBINARY:
+    case SQL_LONGVARBINARY:
+      return SQL_C_BINARY;
+
+    case SQL_TINYINT:
+      return record.m_unsigned ? SQL_C_UTINYINT : SQL_C_STINYINT;
+
+    case SQL_SMALLINT:
+      return record.m_unsigned ? SQL_C_USHORT : SQL_C_SSHORT;
+
+    case SQL_INTEGER:
+      return record.m_unsigned ? SQL_C_ULONG : SQL_C_SLONG;
+
+    case SQL_BIGINT:
+      return record.m_unsigned ? SQL_C_UBIGINT : SQL_C_SBIGINT;
+
+    case SQL_REAL:
+      return SQL_C_FLOAT;
+
+    case SQL_FLOAT:
+    case SQL_DOUBLE:
+      return SQL_C_DOUBLE;
+
+    case SQL_DATE:
+    case SQL_TYPE_DATE:
+      return SQL_C_TYPE_DATE;
+
+    case SQL_TIME:
+    case SQL_TYPE_TIME:
+      return SQL_C_TYPE_TIME;
+
+    case SQL_TIMESTAMP:
+    case SQL_TYPE_TIMESTAMP:
+      return SQL_C_TYPE_TIMESTAMP;
+
+    case SQL_C_INTERVAL_DAY:
+      return SQL_INTERVAL_DAY;
+    case SQL_C_INTERVAL_DAY_TO_HOUR:
+      return SQL_INTERVAL_DAY_TO_HOUR;
+    case SQL_C_INTERVAL_DAY_TO_MINUTE:
+      return SQL_INTERVAL_DAY_TO_MINUTE;
+    case SQL_C_INTERVAL_DAY_TO_SECOND:
+      return SQL_INTERVAL_DAY_TO_SECOND;
+    case SQL_C_INTERVAL_HOUR:
+      return SQL_INTERVAL_HOUR;
+    case SQL_C_INTERVAL_HOUR_TO_MINUTE:
+      return SQL_INTERVAL_HOUR_TO_MINUTE;
+    case SQL_C_INTERVAL_HOUR_TO_SECOND:
+      return SQL_INTERVAL_HOUR_TO_SECOND;
+    case SQL_C_INTERVAL_MINUTE:
+      return SQL_INTERVAL_MINUTE;
+    case SQL_C_INTERVAL_MINUTE_TO_SECOND:
+      return SQL_INTERVAL_MINUTE_TO_SECOND;
+    case SQL_C_INTERVAL_SECOND:
+      return SQL_INTERVAL_SECOND;
+    case SQL_C_INTERVAL_YEAR:
+      return SQL_INTERVAL_YEAR;
+    case SQL_C_INTERVAL_YEAR_TO_MONTH:
+      return SQL_INTERVAL_YEAR_TO_MONTH;
+    case SQL_C_INTERVAL_MONTH:
+      return SQL_INTERVAL_MONTH;
+
+    default:
+      throw DriverException("Unknown SQL type: " + 
std::to_string(record.m_conciseType),
+                            "HY003");
+  }
+}
+
+void CopyAttribute(Statement& source, Statement& target,
+                   Statement::StatementAttributeId attributeId) {
+  auto optionalValue = source.GetAttribute(attributeId);
+  if (optionalValue) {
+    target.SetAttribute(attributeId, *optionalValue);
+  }
+}
+}  // namespace
+
+// Public
+// 
=========================================================================================
+ODBCStatement::ODBCStatement(
+    ODBCConnection& connection,
+    std::shared_ptr<driver::odbcabstraction::Statement> spiStatement)
+    : m_connection(connection),

Review Comment:
   We generally don't use the `m_` prefix in this codebase. It should be 
`connection_` if it's private.



##########
cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc:
##########
@@ -0,0 +1,787 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h>
+
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/statement.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h>
+#include <sql.h>
+#include <sqlext.h>
+#include <sqltypes.h>
+#include <boost/optional.hpp>
+#include <boost/variant.hpp>
+#include <utility>
+
+using ODBC::DescriptorRecord;
+using ODBC::ODBCConnection;
+using ODBC::ODBCDescriptor;
+using ODBC::ODBCStatement;
+
+using driver::odbcabstraction::DriverException;
+using driver::odbcabstraction::ResultSetMetadata;
+using driver::odbcabstraction::Statement;
+
+namespace {
+void DescriptorToHandle(SQLPOINTER output, ODBCDescriptor* descriptor,
+                        SQLINTEGER* lenPtr) {
+  if (output) {
+    SQLHANDLE* outputHandle = static_cast<SQLHANDLE*>(output);
+    *outputHandle = reinterpret_cast<SQLHANDLE>(descriptor);
+  }
+  if (lenPtr) {
+    *lenPtr = sizeof(SQLHANDLE);
+  }
+}
+
+size_t GetLength(const DescriptorRecord& record) {
+  switch (record.m_type) {
+    case SQL_C_CHAR:
+    case SQL_C_WCHAR:
+    case SQL_C_BINARY:
+      return record.m_length;
+
+    case SQL_C_BIT:
+    case SQL_C_TINYINT:
+    case SQL_C_STINYINT:
+    case SQL_C_UTINYINT:
+      return sizeof(SQLSCHAR);
+
+    case SQL_C_SHORT:
+    case SQL_C_SSHORT:
+    case SQL_C_USHORT:
+      return sizeof(SQLSMALLINT);
+
+    case SQL_C_LONG:
+    case SQL_C_SLONG:
+    case SQL_C_ULONG:
+    case SQL_C_FLOAT:
+      return sizeof(SQLINTEGER);
+
+    case SQL_C_SBIGINT:
+    case SQL_C_UBIGINT:
+    case SQL_C_DOUBLE:
+      return sizeof(SQLBIGINT);
+
+    case SQL_C_NUMERIC:
+      return sizeof(SQL_NUMERIC_STRUCT);
+
+    case SQL_C_DATE:
+    case SQL_C_TYPE_DATE:
+      return sizeof(SQL_DATE_STRUCT);
+
+    case SQL_C_TIME:
+    case SQL_C_TYPE_TIME:
+      return sizeof(SQL_TIME_STRUCT);
+
+    case SQL_C_TIMESTAMP:
+    case SQL_C_TYPE_TIMESTAMP:
+      return sizeof(SQL_TIMESTAMP_STRUCT);
+
+    case SQL_C_INTERVAL_DAY:
+    case SQL_C_INTERVAL_DAY_TO_HOUR:
+    case SQL_C_INTERVAL_DAY_TO_MINUTE:
+    case SQL_C_INTERVAL_DAY_TO_SECOND:
+    case SQL_C_INTERVAL_HOUR:
+    case SQL_C_INTERVAL_HOUR_TO_MINUTE:
+    case SQL_C_INTERVAL_HOUR_TO_SECOND:
+    case SQL_C_INTERVAL_MINUTE:
+    case SQL_C_INTERVAL_MINUTE_TO_SECOND:
+    case SQL_C_INTERVAL_SECOND:
+    case SQL_C_INTERVAL_YEAR:
+    case SQL_C_INTERVAL_YEAR_TO_MONTH:
+    case SQL_C_INTERVAL_MONTH:
+      return sizeof(SQL_INTERVAL_STRUCT);
+    default:
+      return record.m_length;
+  }
+}
+
+SQLSMALLINT getCTypeForSQLType(const DescriptorRecord& record) {
+  switch (record.m_conciseType) {
+    case SQL_CHAR:
+    case SQL_VARCHAR:
+    case SQL_LONGVARCHAR:
+      return SQL_C_CHAR;
+
+    case SQL_WCHAR:
+    case SQL_WVARCHAR:
+    case SQL_WLONGVARCHAR:
+      return SQL_C_WCHAR;
+
+    case SQL_BINARY:
+    case SQL_VARBINARY:
+    case SQL_LONGVARBINARY:
+      return SQL_C_BINARY;
+
+    case SQL_TINYINT:
+      return record.m_unsigned ? SQL_C_UTINYINT : SQL_C_STINYINT;
+
+    case SQL_SMALLINT:
+      return record.m_unsigned ? SQL_C_USHORT : SQL_C_SSHORT;
+
+    case SQL_INTEGER:
+      return record.m_unsigned ? SQL_C_ULONG : SQL_C_SLONG;
+
+    case SQL_BIGINT:
+      return record.m_unsigned ? SQL_C_UBIGINT : SQL_C_SBIGINT;
+
+    case SQL_REAL:
+      return SQL_C_FLOAT;
+
+    case SQL_FLOAT:
+    case SQL_DOUBLE:
+      return SQL_C_DOUBLE;
+
+    case SQL_DATE:
+    case SQL_TYPE_DATE:
+      return SQL_C_TYPE_DATE;
+
+    case SQL_TIME:
+    case SQL_TYPE_TIME:
+      return SQL_C_TYPE_TIME;
+
+    case SQL_TIMESTAMP:
+    case SQL_TYPE_TIMESTAMP:
+      return SQL_C_TYPE_TIMESTAMP;
+
+    case SQL_C_INTERVAL_DAY:
+      return SQL_INTERVAL_DAY;
+    case SQL_C_INTERVAL_DAY_TO_HOUR:
+      return SQL_INTERVAL_DAY_TO_HOUR;
+    case SQL_C_INTERVAL_DAY_TO_MINUTE:
+      return SQL_INTERVAL_DAY_TO_MINUTE;
+    case SQL_C_INTERVAL_DAY_TO_SECOND:
+      return SQL_INTERVAL_DAY_TO_SECOND;
+    case SQL_C_INTERVAL_HOUR:
+      return SQL_INTERVAL_HOUR;
+    case SQL_C_INTERVAL_HOUR_TO_MINUTE:
+      return SQL_INTERVAL_HOUR_TO_MINUTE;
+    case SQL_C_INTERVAL_HOUR_TO_SECOND:
+      return SQL_INTERVAL_HOUR_TO_SECOND;
+    case SQL_C_INTERVAL_MINUTE:
+      return SQL_INTERVAL_MINUTE;
+    case SQL_C_INTERVAL_MINUTE_TO_SECOND:
+      return SQL_INTERVAL_MINUTE_TO_SECOND;
+    case SQL_C_INTERVAL_SECOND:
+      return SQL_INTERVAL_SECOND;
+    case SQL_C_INTERVAL_YEAR:
+      return SQL_INTERVAL_YEAR;
+    case SQL_C_INTERVAL_YEAR_TO_MONTH:
+      return SQL_INTERVAL_YEAR_TO_MONTH;
+    case SQL_C_INTERVAL_MONTH:
+      return SQL_INTERVAL_MONTH;
+
+    default:
+      throw DriverException("Unknown SQL type: " + 
std::to_string(record.m_conciseType),
+                            "HY003");
+  }
+}
+
+void CopyAttribute(Statement& source, Statement& target,
+                   Statement::StatementAttributeId attributeId) {
+  auto optionalValue = source.GetAttribute(attributeId);
+  if (optionalValue) {
+    target.SetAttribute(attributeId, *optionalValue);
+  }
+}
+}  // namespace
+
+// Public
+// 
=========================================================================================
+ODBCStatement::ODBCStatement(
+    ODBCConnection& connection,
+    std::shared_ptr<driver::odbcabstraction::Statement> spiStatement)
+    : m_connection(connection),
+      m_spiStatement(std::move(spiStatement)),
+      m_diagnostics(&m_spiStatement->GetDiagnostics()),
+      
m_builtInArd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(),
+                                                    nullptr, this, true, true,
+                                                    
connection.IsOdbc2Connection())),
+      
m_builtInApd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(),
+                                                    nullptr, this, true, true,
+                                                    
connection.IsOdbc2Connection())),
+      m_ipd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(), 
nullptr,
+                                             this, false, true,
+                                             connection.IsOdbc2Connection())),
+      m_ird(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(), 
nullptr,
+                                             this, false, false,
+                                             connection.IsOdbc2Connection())),
+      m_currentArd(m_builtInApd.get()),
+      m_currentApd(m_builtInApd.get()),
+      m_rowNumber(0),
+      m_maxRows(0),
+      m_rowsetSize(1),
+      m_isPrepared(false),
+      m_hasReachedEndOfResult(false) {}
+
+ODBCConnection& ODBCStatement::GetConnection() { return m_connection; }
+
+void ODBCStatement::CopyAttributesFromConnection(ODBCConnection& connection) {
+  ODBCStatement& trackingStatement = connection.GetTrackingStatement();
+
+  // Get abstraction attributes and copy to this m_spiStatement.
+  // Possible ODBC attributes are below, but many of these are not supported 
by warpdrive
+  // or ODBCAbstaction:
+  // SQL_ATTR_ASYNC_ENABLE:
+  // SQL_ATTR_METADATA_ID:
+  // SQL_ATTR_CONCURRENCY:
+  // SQL_ATTR_CURSOR_TYPE:
+  // SQL_ATTR_KEYSET_SIZE:
+  // SQL_ATTR_MAX_LENGTH:
+  // SQL_ATTR_MAX_ROWS:
+  // SQL_ATTR_NOSCAN:
+  // SQL_ATTR_QUERY_TIMEOUT:
+  // SQL_ATTR_RETRIEVE_DATA:
+  // SQL_ATTR_SIMULATE_CURSOR:
+  // SQL_ATTR_USE_BOOKMARKS:
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::METADATA_ID);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::MAX_LENGTH);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement, 
Statement::NOSCAN);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::QUERY_TIMEOUT);
+
+  // SQL_ATTR_ROW_BIND_TYPE:
+  m_currentArd->SetHeaderField(
+      SQL_DESC_BIND_TYPE,
+      reinterpret_cast<SQLPOINTER>(
+          
static_cast<SQLLEN>(trackingStatement.m_currentArd->GetBoundStructOffset())),
+      0);
+}
+
+bool ODBCStatement::isPrepared() const { return m_isPrepared; }
+
+void ODBCStatement::Prepare(const std::string& query) {
+  boost::optional<std::shared_ptr<ResultSetMetadata> > metadata =
+      m_spiStatement->Prepare(query);
+
+  if (metadata) {
+    m_ird->PopulateFromResultSetMetadata(metadata->get());
+  }
+  m_isPrepared = true;
+}
+
+void ODBCStatement::ExecutePrepared() {
+  if (!m_isPrepared) {
+    throw DriverException("Function sequence error", "HY010");
+  }
+
+  if (m_spiStatement->ExecutePrepared()) {
+    m_currenResult = m_spiStatement->GetResultSet();
+    m_ird->PopulateFromResultSetMetadata(
+        m_spiStatement->GetResultSet()->GetMetadata().get());
+    m_hasReachedEndOfResult = false;
+  }
+}
+
+void ODBCStatement::ExecuteDirect(const std::string& query) {
+  if (m_spiStatement->Execute(query)) {
+    m_currenResult = m_spiStatement->GetResultSet();
+    m_ird->PopulateFromResultSetMetadata(m_currenResult->GetMetadata().get());
+    m_hasReachedEndOfResult = false;
+  }
+
+  // Direct execution wipes out the prepared state.
+  m_isPrepared = false;
+}
+
+bool ODBCStatement::Fetch(size_t rows) {
+  if (m_hasReachedEndOfResult) {
+    m_ird->SetRowsProcessed(0);
+    return false;
+  }
+
+  if (m_maxRows) {
+    rows = std::min(rows, m_maxRows - m_rowNumber);
+  }
+
+  if (m_currentArd->HaveBindingsChanged()) {
+    // TODO: Deal handle when offset != bufferlength.
+
+    // Wipe out all bindings in the ResultSet.
+    // Note that the number of ARD records can both be more or less
+    // than the number of columns.
+    for (size_t i = 0; i < m_ird->GetRecords().size(); i++) {
+      if (i < m_currentArd->GetRecords().size() &&
+          m_currentArd->GetRecords()[i].m_isBound) {
+        const DescriptorRecord& ardRecord = m_currentArd->GetRecords()[i];
+        m_currenResult->BindColumn(i + 1, ardRecord.m_type, 
ardRecord.m_precision,
+                                   ardRecord.m_scale, ardRecord.m_dataPtr,
+                                   GetLength(ardRecord), 
ardRecord.m_indicatorPtr);
+      } else {
+        m_currenResult->BindColumn(i + 1,
+                                   driver::odbcabstraction::CDataType_CHAR
+                                   /* arbitrary type, not used */,
+                                   0, 0, nullptr, 0, nullptr);
+      }
+    }
+    m_currentArd->NotifyBindingsHavePropagated();
+  }
+
+  size_t rowsFetched = m_currenResult->Move(rows, 
m_currentArd->GetBindOffset(),
+                                            
m_currentArd->GetBoundStructOffset(),
+                                            m_ird->GetArrayStatusPtr());
+  m_ird->SetRowsProcessed(static_cast<SQLULEN>(rowsFetched));
+
+  m_rowNumber += rowsFetched;
+  m_hasReachedEndOfResult = rowsFetched != rows;
+  return rowsFetched != 0;
+}
+
+void ODBCStatement::GetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER 
output,
+                                SQLINTEGER bufferSize, SQLINTEGER* strLenPtr,
+                                bool isUnicode) {
+  using driver::odbcabstraction::Statement;
+  boost::optional<Statement::Attribute> spiAttribute;
+  switch (statementAttribute) {
+    // Descriptor accessor attributes
+    case SQL_ATTR_APP_PARAM_DESC:
+      DescriptorToHandle(output, m_currentApd, strLenPtr);
+      return;
+    case SQL_ATTR_APP_ROW_DESC:
+      DescriptorToHandle(output, m_currentArd, strLenPtr);
+      return;
+    case SQL_ATTR_IMP_PARAM_DESC:
+      DescriptorToHandle(output, m_ipd.get(), strLenPtr);
+      return;
+    case SQL_ATTR_IMP_ROW_DESC:
+      DescriptorToHandle(output, m_ird.get(), strLenPtr);
+      return;
+
+    // Attributes that are descriptor fields
+    case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
+      m_currentApd->GetHeaderField(SQL_DESC_BIND_OFFSET_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_BIND_TYPE:
+      m_currentApd->GetHeaderField(SQL_DESC_BIND_TYPE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_OPERATION_PTR:
+      m_currentApd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_STATUS_PTR:
+      m_ipd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAMS_PROCESSED_PTR:
+      m_ipd->GetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAMSET_SIZE:
+      m_currentApd->GetHeaderField(SQL_DESC_ARRAY_SIZE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_ARRAY_SIZE:
+      m_currentArd->GetHeaderField(SQL_DESC_ARRAY_SIZE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_BIND_OFFSET_PTR:
+      m_currentArd->GetHeaderField(SQL_DESC_BIND_OFFSET_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_ROW_BIND_TYPE:
+      m_currentArd->GetHeaderField(SQL_DESC_BIND_TYPE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_OPERATION_PTR:
+      m_currentArd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_ROW_STATUS_PTR:
+      m_ird->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROWS_FETCHED_PTR:
+      m_ird->GetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_ASYNC_ENABLE:
+      GetAttribute(static_cast<SQLULEN>(SQL_ASYNC_ENABLE_OFF), output, 
bufferSize,
+                   strLenPtr);
+      return;
+
+#ifdef SQL_ATTR_ASYNC_STMT_EVENT
+    case SQL_ATTR_ASYNC_STMT_EVENT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK
+    case SQL_ATTR_ASYNC_STMT_PCALLBACK:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT
+    case SQL_ATTR_ASYNC_STMT_PCONTEXT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+    case SQL_ATTR_CURSOR_SCROLLABLE:
+      GetAttribute(static_cast<SQLULEN>(SQL_NONSCROLLABLE), output, bufferSize,
+                   strLenPtr);
+      return;
+
+    case SQL_ATTR_CURSOR_SENSITIVITY:
+      GetAttribute(static_cast<SQLULEN>(SQL_UNSPECIFIED), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_CURSOR_TYPE:
+      GetAttribute(static_cast<SQLULEN>(SQL_CURSOR_FORWARD_ONLY), output, 
bufferSize,
+                   strLenPtr);
+      return;
+
+    case SQL_ATTR_ENABLE_AUTO_IPD:
+      GetAttribute(static_cast<SQLULEN>(SQL_FALSE), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_FETCH_BOOKMARK_PTR:
+      GetAttribute(static_cast<SQLPOINTER>(NULL), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_KEYSET_SIZE:
+      GetAttribute(static_cast<SQLULEN>(0), output, bufferSize, strLenPtr);
+      return;
+
+    case SQL_ATTR_ROW_NUMBER:
+      GetAttribute(static_cast<SQLULEN>(m_rowNumber), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_SIMULATE_CURSOR:
+      GetAttribute(static_cast<SQLULEN>(SQL_SC_UNIQUE), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_USE_BOOKMARKS:
+      GetAttribute(static_cast<SQLULEN>(SQL_UB_OFF), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_CONCURRENCY:
+      GetAttribute(static_cast<SQLULEN>(SQL_CONCUR_READ_ONLY), output, 
bufferSize,
+                   strLenPtr);
+      return;
+    case SQL_ATTR_MAX_ROWS:
+      GetAttribute(static_cast<SQLULEN>(m_maxRows), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_RETRIEVE_DATA:
+      GetAttribute(static_cast<SQLULEN>(SQL_RD_ON), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ROWSET_SIZE:
+      GetAttribute(static_cast<SQLULEN>(m_rowsetSize), output, bufferSize, 
strLenPtr);
+      return;
+
+    // Driver-level statement attributes. These are all SQLULEN attributes.
+    case SQL_ATTR_MAX_LENGTH:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::MAX_LENGTH);
+      break;
+    case SQL_ATTR_METADATA_ID:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::METADATA_ID);
+      break;
+    case SQL_ATTR_NOSCAN:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::NOSCAN);
+      break;
+    case SQL_ATTR_QUERY_TIMEOUT:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::QUERY_TIMEOUT);
+      break;
+    default:
+      throw DriverException(
+          "Invalid statement attribute: " + 
std::to_string(statementAttribute), "HY092");
+  }
+
+  if (spiAttribute) {
+    GetAttribute(static_cast<SQLULEN>(boost::get<size_t>(*spiAttribute)), 
output,
+                 bufferSize, strLenPtr);
+    return;
+  }
+
+  throw DriverException(
+      "Invalid statement attribute: " + std::to_string(statementAttribute), 
"HY092");
+}
+
+void ODBCStatement::SetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER 
value,
+                                SQLINTEGER bufferSize, bool isUnicode) {
+  size_t attributeToWrite = 0;
+  bool successfully_written = false;
+
+  switch (statementAttribute) {
+    case SQL_ATTR_APP_PARAM_DESC: {
+      ODBCDescriptor* desc = static_cast<ODBCDescriptor*>(value);
+      if (m_currentApd != desc) {
+        if (m_currentApd != m_builtInApd.get()) {
+          m_currentApd->DetachFromStatement(this, true);
+        }
+        m_currentApd = desc;
+        if (m_currentApd != m_builtInApd.get()) {
+          desc->RegisterToStatement(this, true);
+        }
+      }
+      return;
+    }
+    case SQL_ATTR_APP_ROW_DESC: {
+      ODBCDescriptor* desc = static_cast<ODBCDescriptor*>(value);
+      if (m_currentArd != desc) {
+        if (m_currentArd != m_builtInArd.get()) {
+          m_currentArd->DetachFromStatement(this, false);
+        }
+        m_currentArd = desc;
+        if (m_currentArd != m_builtInArd.get()) {
+          desc->RegisterToStatement(this, false);
+        }
+      }
+      return;
+    }
+    case SQL_ATTR_IMP_PARAM_DESC:
+      throw DriverException("Cannot assign implementation descriptor.", 
"HY017");
+    case SQL_ATTR_IMP_ROW_DESC:
+      throw DriverException("Cannot assign implementation descriptor.", 
"HY017");
+      // Attributes that are descriptor fields
+    case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
+      m_currentApd->SetHeaderField(SQL_DESC_BIND_OFFSET_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_PARAM_BIND_TYPE:
+      m_currentApd->SetHeaderField(SQL_DESC_BIND_TYPE, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAM_OPERATION_PTR:
+      m_currentApd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_PARAM_STATUS_PTR:
+      m_ipd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAMS_PROCESSED_PTR:
+      m_ipd->SetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAMSET_SIZE:
+      m_currentApd->SetHeaderField(SQL_DESC_ARRAY_SIZE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_ARRAY_SIZE:
+      m_currentArd->SetHeaderField(SQL_DESC_ARRAY_SIZE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_BIND_OFFSET_PTR:
+      m_currentArd->SetHeaderField(SQL_DESC_BIND_OFFSET_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_ROW_BIND_TYPE:
+      m_currentArd->SetHeaderField(SQL_DESC_BIND_TYPE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_OPERATION_PTR:
+      m_currentArd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_ROW_STATUS_PTR:
+      m_ird->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_ROWS_FETCHED_PTR:
+      m_ird->SetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, value, bufferSize);
+      return;
+
+    case SQL_ATTR_ASYNC_ENABLE:
+#ifdef SQL_ATTR_ASYNC_STMT_EVENT
+    case SQL_ATTR_ASYNC_STMT_EVENT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK
+    case SQL_ATTR_ASYNC_STMT_PCALLBACK:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT
+    case SQL_ATTR_ASYNC_STMT_PCONTEXT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+    case SQL_ATTR_CONCURRENCY:
+      CheckIfAttributeIsSetToOnlyValidValue(value,
+                                            
static_cast<SQLULEN>(SQL_CONCUR_READ_ONLY));
+      return;
+    case SQL_ATTR_CURSOR_SCROLLABLE:
+      CheckIfAttributeIsSetToOnlyValidValue(value,
+                                            
static_cast<SQLULEN>(SQL_NONSCROLLABLE));
+      return;
+    case SQL_ATTR_CURSOR_SENSITIVITY:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_UNSPECIFIED));
+      return;
+    case SQL_ATTR_CURSOR_TYPE:
+      CheckIfAttributeIsSetToOnlyValidValue(
+          value, static_cast<SQLULEN>(SQL_CURSOR_FORWARD_ONLY));
+      return;
+    case SQL_ATTR_ENABLE_AUTO_IPD:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_FALSE));
+      return;
+    case SQL_ATTR_FETCH_BOOKMARK_PTR:
+      if (value != NULL) {
+        throw DriverException("Optional feature not implemented", "HYC00");
+      }
+      return;
+    case SQL_ATTR_KEYSET_SIZE:
+      CheckIfAttributeIsSetToOnlyValidValue(value, static_cast<SQLULEN>(0));
+      return;
+    case SQL_ATTR_ROW_NUMBER:
+      throw DriverException("Cannot set read-only attribute", "HY092");
+    case SQL_ATTR_SIMULATE_CURSOR:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_SC_UNIQUE));
+      return;
+    case SQL_ATTR_USE_BOOKMARKS:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_UB_OFF));
+      return;
+    case SQL_ATTR_RETRIEVE_DATA:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_TRUE));
+      return;
+    case SQL_ROWSET_SIZE:
+      SetAttribute(value, m_rowsetSize);
+      return;

Review Comment:
   Why do some cases in this switch return, and others break?



##########
cpp/src/arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.cc:
##########
@@ -0,0 +1,181 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "arrow/flight/sql/odbc/flight_sql/flight_sql_statement_get_tables.h"
+#include "arrow/flight/api.h"
+#include "arrow/flight/sql/odbc/flight_sql/flight_sql_result_set.h"
+#include "arrow/flight/sql/odbc/flight_sql/record_batch_transformer.h"
+#include "arrow/flight/sql/odbc/flight_sql/utils.h"
+#include 
"arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/platform.h"
+#include "arrow/flight/types.h"
+
+namespace driver {
+namespace flight_sql {
+
+using arrow::Result;
+using arrow::flight::FlightClientOptions;
+using arrow::flight::FlightInfo;
+using arrow::flight::sql::FlightSqlClient;
+
+void ParseTableTypes(const std::string& table_type,
+                     std::vector<std::string>& table_types) {
+  bool encountered = false;  // for checking if there is a single quote
+  std::string curr_parse;    // the current string
+
+  for (char temp : table_type) {  // while still in the string
+    switch (temp) {               // switch depending on the character

Review Comment:
   Many of these comments are just noise and aren't necessary. I would rather 
we have docstrings since the purpose of many functions is not necessarily 
clear. Another thing is that for functions which parse a value, it would be 
more helpful to document the format being parsed/have examples of valid values



##########
cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc:
##########
@@ -0,0 +1,787 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h>
+
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/statement.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h>
+#include <sql.h>
+#include <sqlext.h>
+#include <sqltypes.h>
+#include <boost/optional.hpp>
+#include <boost/variant.hpp>
+#include <utility>
+
+using ODBC::DescriptorRecord;
+using ODBC::ODBCConnection;
+using ODBC::ODBCDescriptor;
+using ODBC::ODBCStatement;
+
+using driver::odbcabstraction::DriverException;
+using driver::odbcabstraction::ResultSetMetadata;
+using driver::odbcabstraction::Statement;
+
+namespace {
+void DescriptorToHandle(SQLPOINTER output, ODBCDescriptor* descriptor,
+                        SQLINTEGER* lenPtr) {
+  if (output) {
+    SQLHANDLE* outputHandle = static_cast<SQLHANDLE*>(output);
+    *outputHandle = reinterpret_cast<SQLHANDLE>(descriptor);
+  }
+  if (lenPtr) {
+    *lenPtr = sizeof(SQLHANDLE);
+  }
+}
+
+size_t GetLength(const DescriptorRecord& record) {
+  switch (record.m_type) {
+    case SQL_C_CHAR:
+    case SQL_C_WCHAR:
+    case SQL_C_BINARY:
+      return record.m_length;
+
+    case SQL_C_BIT:
+    case SQL_C_TINYINT:
+    case SQL_C_STINYINT:
+    case SQL_C_UTINYINT:
+      return sizeof(SQLSCHAR);
+
+    case SQL_C_SHORT:
+    case SQL_C_SSHORT:
+    case SQL_C_USHORT:
+      return sizeof(SQLSMALLINT);
+
+    case SQL_C_LONG:
+    case SQL_C_SLONG:
+    case SQL_C_ULONG:
+    case SQL_C_FLOAT:
+      return sizeof(SQLINTEGER);
+
+    case SQL_C_SBIGINT:
+    case SQL_C_UBIGINT:
+    case SQL_C_DOUBLE:
+      return sizeof(SQLBIGINT);
+
+    case SQL_C_NUMERIC:
+      return sizeof(SQL_NUMERIC_STRUCT);
+
+    case SQL_C_DATE:
+    case SQL_C_TYPE_DATE:
+      return sizeof(SQL_DATE_STRUCT);
+
+    case SQL_C_TIME:
+    case SQL_C_TYPE_TIME:
+      return sizeof(SQL_TIME_STRUCT);
+
+    case SQL_C_TIMESTAMP:
+    case SQL_C_TYPE_TIMESTAMP:
+      return sizeof(SQL_TIMESTAMP_STRUCT);
+
+    case SQL_C_INTERVAL_DAY:
+    case SQL_C_INTERVAL_DAY_TO_HOUR:
+    case SQL_C_INTERVAL_DAY_TO_MINUTE:
+    case SQL_C_INTERVAL_DAY_TO_SECOND:
+    case SQL_C_INTERVAL_HOUR:
+    case SQL_C_INTERVAL_HOUR_TO_MINUTE:
+    case SQL_C_INTERVAL_HOUR_TO_SECOND:
+    case SQL_C_INTERVAL_MINUTE:
+    case SQL_C_INTERVAL_MINUTE_TO_SECOND:
+    case SQL_C_INTERVAL_SECOND:
+    case SQL_C_INTERVAL_YEAR:
+    case SQL_C_INTERVAL_YEAR_TO_MONTH:
+    case SQL_C_INTERVAL_MONTH:
+      return sizeof(SQL_INTERVAL_STRUCT);
+    default:
+      return record.m_length;
+  }
+}
+
+SQLSMALLINT getCTypeForSQLType(const DescriptorRecord& record) {
+  switch (record.m_conciseType) {
+    case SQL_CHAR:
+    case SQL_VARCHAR:
+    case SQL_LONGVARCHAR:
+      return SQL_C_CHAR;
+
+    case SQL_WCHAR:
+    case SQL_WVARCHAR:
+    case SQL_WLONGVARCHAR:
+      return SQL_C_WCHAR;
+
+    case SQL_BINARY:
+    case SQL_VARBINARY:
+    case SQL_LONGVARBINARY:
+      return SQL_C_BINARY;
+
+    case SQL_TINYINT:
+      return record.m_unsigned ? SQL_C_UTINYINT : SQL_C_STINYINT;
+
+    case SQL_SMALLINT:
+      return record.m_unsigned ? SQL_C_USHORT : SQL_C_SSHORT;
+
+    case SQL_INTEGER:
+      return record.m_unsigned ? SQL_C_ULONG : SQL_C_SLONG;
+
+    case SQL_BIGINT:
+      return record.m_unsigned ? SQL_C_UBIGINT : SQL_C_SBIGINT;
+
+    case SQL_REAL:
+      return SQL_C_FLOAT;
+
+    case SQL_FLOAT:
+    case SQL_DOUBLE:
+      return SQL_C_DOUBLE;
+
+    case SQL_DATE:
+    case SQL_TYPE_DATE:
+      return SQL_C_TYPE_DATE;
+
+    case SQL_TIME:
+    case SQL_TYPE_TIME:
+      return SQL_C_TYPE_TIME;
+
+    case SQL_TIMESTAMP:
+    case SQL_TYPE_TIMESTAMP:
+      return SQL_C_TYPE_TIMESTAMP;
+
+    case SQL_C_INTERVAL_DAY:
+      return SQL_INTERVAL_DAY;
+    case SQL_C_INTERVAL_DAY_TO_HOUR:
+      return SQL_INTERVAL_DAY_TO_HOUR;
+    case SQL_C_INTERVAL_DAY_TO_MINUTE:
+      return SQL_INTERVAL_DAY_TO_MINUTE;
+    case SQL_C_INTERVAL_DAY_TO_SECOND:
+      return SQL_INTERVAL_DAY_TO_SECOND;
+    case SQL_C_INTERVAL_HOUR:
+      return SQL_INTERVAL_HOUR;
+    case SQL_C_INTERVAL_HOUR_TO_MINUTE:
+      return SQL_INTERVAL_HOUR_TO_MINUTE;
+    case SQL_C_INTERVAL_HOUR_TO_SECOND:
+      return SQL_INTERVAL_HOUR_TO_SECOND;
+    case SQL_C_INTERVAL_MINUTE:
+      return SQL_INTERVAL_MINUTE;
+    case SQL_C_INTERVAL_MINUTE_TO_SECOND:
+      return SQL_INTERVAL_MINUTE_TO_SECOND;
+    case SQL_C_INTERVAL_SECOND:
+      return SQL_INTERVAL_SECOND;
+    case SQL_C_INTERVAL_YEAR:
+      return SQL_INTERVAL_YEAR;
+    case SQL_C_INTERVAL_YEAR_TO_MONTH:
+      return SQL_INTERVAL_YEAR_TO_MONTH;
+    case SQL_C_INTERVAL_MONTH:
+      return SQL_INTERVAL_MONTH;
+
+    default:
+      throw DriverException("Unknown SQL type: " + 
std::to_string(record.m_conciseType),
+                            "HY003");
+  }
+}
+
+void CopyAttribute(Statement& source, Statement& target,
+                   Statement::StatementAttributeId attributeId) {
+  auto optionalValue = source.GetAttribute(attributeId);
+  if (optionalValue) {
+    target.SetAttribute(attributeId, *optionalValue);
+  }
+}
+}  // namespace
+
+// Public
+// 
=========================================================================================
+ODBCStatement::ODBCStatement(
+    ODBCConnection& connection,
+    std::shared_ptr<driver::odbcabstraction::Statement> spiStatement)
+    : m_connection(connection),
+      m_spiStatement(std::move(spiStatement)),
+      m_diagnostics(&m_spiStatement->GetDiagnostics()),
+      
m_builtInArd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(),
+                                                    nullptr, this, true, true,
+                                                    
connection.IsOdbc2Connection())),
+      
m_builtInApd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(),
+                                                    nullptr, this, true, true,
+                                                    
connection.IsOdbc2Connection())),
+      m_ipd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(), 
nullptr,
+                                             this, false, true,
+                                             connection.IsOdbc2Connection())),
+      m_ird(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(), 
nullptr,
+                                             this, false, false,
+                                             connection.IsOdbc2Connection())),
+      m_currentArd(m_builtInApd.get()),
+      m_currentApd(m_builtInApd.get()),
+      m_rowNumber(0),
+      m_maxRows(0),
+      m_rowsetSize(1),
+      m_isPrepared(false),
+      m_hasReachedEndOfResult(false) {}
+
+ODBCConnection& ODBCStatement::GetConnection() { return m_connection; }
+
+void ODBCStatement::CopyAttributesFromConnection(ODBCConnection& connection) {
+  ODBCStatement& trackingStatement = connection.GetTrackingStatement();
+
+  // Get abstraction attributes and copy to this m_spiStatement.
+  // Possible ODBC attributes are below, but many of these are not supported 
by warpdrive
+  // or ODBCAbstaction:
+  // SQL_ATTR_ASYNC_ENABLE:
+  // SQL_ATTR_METADATA_ID:
+  // SQL_ATTR_CONCURRENCY:
+  // SQL_ATTR_CURSOR_TYPE:
+  // SQL_ATTR_KEYSET_SIZE:
+  // SQL_ATTR_MAX_LENGTH:
+  // SQL_ATTR_MAX_ROWS:
+  // SQL_ATTR_NOSCAN:
+  // SQL_ATTR_QUERY_TIMEOUT:
+  // SQL_ATTR_RETRIEVE_DATA:
+  // SQL_ATTR_SIMULATE_CURSOR:
+  // SQL_ATTR_USE_BOOKMARKS:
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::METADATA_ID);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::MAX_LENGTH);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement, 
Statement::NOSCAN);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::QUERY_TIMEOUT);
+
+  // SQL_ATTR_ROW_BIND_TYPE:
+  m_currentArd->SetHeaderField(
+      SQL_DESC_BIND_TYPE,
+      reinterpret_cast<SQLPOINTER>(
+          
static_cast<SQLLEN>(trackingStatement.m_currentArd->GetBoundStructOffset())),
+      0);
+}
+
+bool ODBCStatement::isPrepared() const { return m_isPrepared; }
+
+void ODBCStatement::Prepare(const std::string& query) {
+  boost::optional<std::shared_ptr<ResultSetMetadata> > metadata =
+      m_spiStatement->Prepare(query);
+
+  if (metadata) {
+    m_ird->PopulateFromResultSetMetadata(metadata->get());
+  }
+  m_isPrepared = true;
+}
+
+void ODBCStatement::ExecutePrepared() {
+  if (!m_isPrepared) {
+    throw DriverException("Function sequence error", "HY010");
+  }
+
+  if (m_spiStatement->ExecutePrepared()) {
+    m_currenResult = m_spiStatement->GetResultSet();
+    m_ird->PopulateFromResultSetMetadata(
+        m_spiStatement->GetResultSet()->GetMetadata().get());
+    m_hasReachedEndOfResult = false;
+  }
+}
+
+void ODBCStatement::ExecuteDirect(const std::string& query) {
+  if (m_spiStatement->Execute(query)) {
+    m_currenResult = m_spiStatement->GetResultSet();
+    m_ird->PopulateFromResultSetMetadata(m_currenResult->GetMetadata().get());
+    m_hasReachedEndOfResult = false;
+  }
+
+  // Direct execution wipes out the prepared state.
+  m_isPrepared = false;
+}
+
+bool ODBCStatement::Fetch(size_t rows) {
+  if (m_hasReachedEndOfResult) {
+    m_ird->SetRowsProcessed(0);
+    return false;
+  }
+
+  if (m_maxRows) {
+    rows = std::min(rows, m_maxRows - m_rowNumber);
+  }
+
+  if (m_currentArd->HaveBindingsChanged()) {
+    // TODO: Deal handle when offset != bufferlength.
+
+    // Wipe out all bindings in the ResultSet.
+    // Note that the number of ARD records can both be more or less
+    // than the number of columns.
+    for (size_t i = 0; i < m_ird->GetRecords().size(); i++) {
+      if (i < m_currentArd->GetRecords().size() &&
+          m_currentArd->GetRecords()[i].m_isBound) {
+        const DescriptorRecord& ardRecord = m_currentArd->GetRecords()[i];
+        m_currenResult->BindColumn(i + 1, ardRecord.m_type, 
ardRecord.m_precision,
+                                   ardRecord.m_scale, ardRecord.m_dataPtr,
+                                   GetLength(ardRecord), 
ardRecord.m_indicatorPtr);
+      } else {
+        m_currenResult->BindColumn(i + 1,
+                                   driver::odbcabstraction::CDataType_CHAR
+                                   /* arbitrary type, not used */,
+                                   0, 0, nullptr, 0, nullptr);
+      }
+    }
+    m_currentArd->NotifyBindingsHavePropagated();
+  }
+
+  size_t rowsFetched = m_currenResult->Move(rows, 
m_currentArd->GetBindOffset(),
+                                            
m_currentArd->GetBoundStructOffset(),
+                                            m_ird->GetArrayStatusPtr());
+  m_ird->SetRowsProcessed(static_cast<SQLULEN>(rowsFetched));
+
+  m_rowNumber += rowsFetched;
+  m_hasReachedEndOfResult = rowsFetched != rows;
+  return rowsFetched != 0;
+}
+
+void ODBCStatement::GetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER 
output,
+                                SQLINTEGER bufferSize, SQLINTEGER* strLenPtr,
+                                bool isUnicode) {
+  using driver::odbcabstraction::Statement;
+  boost::optional<Statement::Attribute> spiAttribute;
+  switch (statementAttribute) {
+    // Descriptor accessor attributes
+    case SQL_ATTR_APP_PARAM_DESC:
+      DescriptorToHandle(output, m_currentApd, strLenPtr);
+      return;
+    case SQL_ATTR_APP_ROW_DESC:
+      DescriptorToHandle(output, m_currentArd, strLenPtr);
+      return;
+    case SQL_ATTR_IMP_PARAM_DESC:
+      DescriptorToHandle(output, m_ipd.get(), strLenPtr);
+      return;
+    case SQL_ATTR_IMP_ROW_DESC:
+      DescriptorToHandle(output, m_ird.get(), strLenPtr);
+      return;
+
+    // Attributes that are descriptor fields
+    case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
+      m_currentApd->GetHeaderField(SQL_DESC_BIND_OFFSET_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_BIND_TYPE:
+      m_currentApd->GetHeaderField(SQL_DESC_BIND_TYPE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_OPERATION_PTR:
+      m_currentApd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_STATUS_PTR:
+      m_ipd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAMS_PROCESSED_PTR:
+      m_ipd->GetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAMSET_SIZE:
+      m_currentApd->GetHeaderField(SQL_DESC_ARRAY_SIZE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_ARRAY_SIZE:
+      m_currentArd->GetHeaderField(SQL_DESC_ARRAY_SIZE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_BIND_OFFSET_PTR:
+      m_currentArd->GetHeaderField(SQL_DESC_BIND_OFFSET_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_ROW_BIND_TYPE:
+      m_currentArd->GetHeaderField(SQL_DESC_BIND_TYPE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_OPERATION_PTR:
+      m_currentArd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_ROW_STATUS_PTR:
+      m_ird->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROWS_FETCHED_PTR:
+      m_ird->GetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_ASYNC_ENABLE:
+      GetAttribute(static_cast<SQLULEN>(SQL_ASYNC_ENABLE_OFF), output, 
bufferSize,
+                   strLenPtr);
+      return;
+
+#ifdef SQL_ATTR_ASYNC_STMT_EVENT
+    case SQL_ATTR_ASYNC_STMT_EVENT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK
+    case SQL_ATTR_ASYNC_STMT_PCALLBACK:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT
+    case SQL_ATTR_ASYNC_STMT_PCONTEXT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+    case SQL_ATTR_CURSOR_SCROLLABLE:
+      GetAttribute(static_cast<SQLULEN>(SQL_NONSCROLLABLE), output, bufferSize,
+                   strLenPtr);
+      return;
+
+    case SQL_ATTR_CURSOR_SENSITIVITY:
+      GetAttribute(static_cast<SQLULEN>(SQL_UNSPECIFIED), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_CURSOR_TYPE:
+      GetAttribute(static_cast<SQLULEN>(SQL_CURSOR_FORWARD_ONLY), output, 
bufferSize,
+                   strLenPtr);
+      return;
+
+    case SQL_ATTR_ENABLE_AUTO_IPD:
+      GetAttribute(static_cast<SQLULEN>(SQL_FALSE), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_FETCH_BOOKMARK_PTR:
+      GetAttribute(static_cast<SQLPOINTER>(NULL), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_KEYSET_SIZE:
+      GetAttribute(static_cast<SQLULEN>(0), output, bufferSize, strLenPtr);
+      return;
+
+    case SQL_ATTR_ROW_NUMBER:
+      GetAttribute(static_cast<SQLULEN>(m_rowNumber), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_SIMULATE_CURSOR:
+      GetAttribute(static_cast<SQLULEN>(SQL_SC_UNIQUE), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_USE_BOOKMARKS:
+      GetAttribute(static_cast<SQLULEN>(SQL_UB_OFF), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_CONCURRENCY:
+      GetAttribute(static_cast<SQLULEN>(SQL_CONCUR_READ_ONLY), output, 
bufferSize,
+                   strLenPtr);
+      return;
+    case SQL_ATTR_MAX_ROWS:
+      GetAttribute(static_cast<SQLULEN>(m_maxRows), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_RETRIEVE_DATA:
+      GetAttribute(static_cast<SQLULEN>(SQL_RD_ON), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ROWSET_SIZE:
+      GetAttribute(static_cast<SQLULEN>(m_rowsetSize), output, bufferSize, 
strLenPtr);
+      return;
+
+    // Driver-level statement attributes. These are all SQLULEN attributes.
+    case SQL_ATTR_MAX_LENGTH:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::MAX_LENGTH);
+      break;
+    case SQL_ATTR_METADATA_ID:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::METADATA_ID);
+      break;
+    case SQL_ATTR_NOSCAN:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::NOSCAN);
+      break;
+    case SQL_ATTR_QUERY_TIMEOUT:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::QUERY_TIMEOUT);
+      break;
+    default:
+      throw DriverException(
+          "Invalid statement attribute: " + 
std::to_string(statementAttribute), "HY092");
+  }
+
+  if (spiAttribute) {
+    GetAttribute(static_cast<SQLULEN>(boost::get<size_t>(*spiAttribute)), 
output,
+                 bufferSize, strLenPtr);
+    return;
+  }
+
+  throw DriverException(
+      "Invalid statement attribute: " + std::to_string(statementAttribute), 
"HY092");
+}
+
+void ODBCStatement::SetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER 
value,
+                                SQLINTEGER bufferSize, bool isUnicode) {
+  size_t attributeToWrite = 0;
+  bool successfully_written = false;
+
+  switch (statementAttribute) {
+    case SQL_ATTR_APP_PARAM_DESC: {
+      ODBCDescriptor* desc = static_cast<ODBCDescriptor*>(value);
+      if (m_currentApd != desc) {
+        if (m_currentApd != m_builtInApd.get()) {
+          m_currentApd->DetachFromStatement(this, true);
+        }
+        m_currentApd = desc;
+        if (m_currentApd != m_builtInApd.get()) {
+          desc->RegisterToStatement(this, true);
+        }
+      }
+      return;
+    }
+    case SQL_ATTR_APP_ROW_DESC: {
+      ODBCDescriptor* desc = static_cast<ODBCDescriptor*>(value);
+      if (m_currentArd != desc) {
+        if (m_currentArd != m_builtInArd.get()) {
+          m_currentArd->DetachFromStatement(this, false);
+        }
+        m_currentArd = desc;
+        if (m_currentArd != m_builtInArd.get()) {
+          desc->RegisterToStatement(this, false);
+        }
+      }
+      return;
+    }
+    case SQL_ATTR_IMP_PARAM_DESC:
+      throw DriverException("Cannot assign implementation descriptor.", 
"HY017");
+    case SQL_ATTR_IMP_ROW_DESC:
+      throw DriverException("Cannot assign implementation descriptor.", 
"HY017");
+      // Attributes that are descriptor fields
+    case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
+      m_currentApd->SetHeaderField(SQL_DESC_BIND_OFFSET_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_PARAM_BIND_TYPE:
+      m_currentApd->SetHeaderField(SQL_DESC_BIND_TYPE, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAM_OPERATION_PTR:
+      m_currentApd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_PARAM_STATUS_PTR:
+      m_ipd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAMS_PROCESSED_PTR:
+      m_ipd->SetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAMSET_SIZE:
+      m_currentApd->SetHeaderField(SQL_DESC_ARRAY_SIZE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_ARRAY_SIZE:
+      m_currentArd->SetHeaderField(SQL_DESC_ARRAY_SIZE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_BIND_OFFSET_PTR:
+      m_currentArd->SetHeaderField(SQL_DESC_BIND_OFFSET_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_ROW_BIND_TYPE:
+      m_currentArd->SetHeaderField(SQL_DESC_BIND_TYPE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_OPERATION_PTR:
+      m_currentArd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_ROW_STATUS_PTR:
+      m_ird->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_ROWS_FETCHED_PTR:
+      m_ird->SetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, value, bufferSize);
+      return;
+
+    case SQL_ATTR_ASYNC_ENABLE:
+#ifdef SQL_ATTR_ASYNC_STMT_EVENT
+    case SQL_ATTR_ASYNC_STMT_EVENT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK
+    case SQL_ATTR_ASYNC_STMT_PCALLBACK:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT
+    case SQL_ATTR_ASYNC_STMT_PCONTEXT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+    case SQL_ATTR_CONCURRENCY:
+      CheckIfAttributeIsSetToOnlyValidValue(value,
+                                            
static_cast<SQLULEN>(SQL_CONCUR_READ_ONLY));
+      return;
+    case SQL_ATTR_CURSOR_SCROLLABLE:
+      CheckIfAttributeIsSetToOnlyValidValue(value,
+                                            
static_cast<SQLULEN>(SQL_NONSCROLLABLE));
+      return;
+    case SQL_ATTR_CURSOR_SENSITIVITY:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_UNSPECIFIED));
+      return;
+    case SQL_ATTR_CURSOR_TYPE:
+      CheckIfAttributeIsSetToOnlyValidValue(
+          value, static_cast<SQLULEN>(SQL_CURSOR_FORWARD_ONLY));
+      return;
+    case SQL_ATTR_ENABLE_AUTO_IPD:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_FALSE));
+      return;
+    case SQL_ATTR_FETCH_BOOKMARK_PTR:
+      if (value != NULL) {
+        throw DriverException("Optional feature not implemented", "HYC00");
+      }
+      return;
+    case SQL_ATTR_KEYSET_SIZE:
+      CheckIfAttributeIsSetToOnlyValidValue(value, static_cast<SQLULEN>(0));
+      return;
+    case SQL_ATTR_ROW_NUMBER:
+      throw DriverException("Cannot set read-only attribute", "HY092");
+    case SQL_ATTR_SIMULATE_CURSOR:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_SC_UNIQUE));
+      return;
+    case SQL_ATTR_USE_BOOKMARKS:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_UB_OFF));
+      return;
+    case SQL_ATTR_RETRIEVE_DATA:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_TRUE));
+      return;
+    case SQL_ROWSET_SIZE:
+      SetAttribute(value, m_rowsetSize);
+      return;
+
+    case SQL_ATTR_MAX_ROWS:
+      throw DriverException("Cannot set read-only attribute", "HY092");
+
+    // Driver-leve statement attributes. These are all size_t attributes
+    case SQL_ATTR_MAX_LENGTH:
+      SetAttribute(value, attributeToWrite);
+      successfully_written =
+          m_spiStatement->SetAttribute(Statement::MAX_LENGTH, 
attributeToWrite);
+      break;
+    case SQL_ATTR_METADATA_ID:
+      SetAttribute(value, attributeToWrite);
+      successfully_written =
+          m_spiStatement->SetAttribute(Statement::METADATA_ID, 
attributeToWrite);
+      break;
+    case SQL_ATTR_NOSCAN:
+      SetAttribute(value, attributeToWrite);
+      successfully_written =
+          m_spiStatement->SetAttribute(Statement::NOSCAN, attributeToWrite);
+      break;
+    case SQL_ATTR_QUERY_TIMEOUT:
+      SetAttribute(value, attributeToWrite);
+      successfully_written =
+          m_spiStatement->SetAttribute(Statement::QUERY_TIMEOUT, 
attributeToWrite);
+      break;
+    default:
+      throw DriverException("Invalid attribute: " + 
std::to_string(attributeToWrite),
+                            "HY092");
+  }
+  if (!successfully_written) {
+    GetDiagnostics().AddWarning("Optional value changed.", "01S02",

Review Comment:
   Why is it a warning that an optional value changed?



##########
cpp/src/arrow/flight/sql/odbc/odbcabstraction/utils.cc:
##########
@@ -0,0 +1,116 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "arrow/vendored/whereami/whereami.h"
+
+#include 
"arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/utils.h"
+
+#include <fstream>
+#include <sstream>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/token_functions.hpp>
+#include <boost/tokenizer.hpp>
+#include <iostream>
+
+namespace driver {
+namespace odbcabstraction {
+
+boost::optional<bool> AsBool(const std::string& value) {
+  if (boost::iequals(value, "true") || boost::iequals(value, "1")) {
+    return true;
+  } else if (boost::iequals(value, "false") || boost::iequals(value, "0")) {
+    return false;
+  } else {
+    return boost::none;
+  }
+}
+
+boost::optional<bool> AsBool(const Connection::ConnPropertyMap& 
connPropertyMap,
+                             const std::string_view& property_name) {
+  auto extracted_property = connPropertyMap.find(property_name);
+
+  if (extracted_property != connPropertyMap.end()) {
+    return AsBool(extracted_property->second);
+  }
+
+  return boost::none;
+}
+
+boost::optional<int32_t> AsInt32(int32_t min_value,
+                                 const Connection::ConnPropertyMap& 
connPropertyMap,
+                                 const std::string_view& property_name) {
+  auto extracted_property = connPropertyMap.find(property_name);
+
+  if (extracted_property != connPropertyMap.end()) {
+    const int32_t stringColumnLength = std::stoi(extracted_property->second);
+
+    if (stringColumnLength >= min_value && stringColumnLength <= INT32_MAX) {
+      return stringColumnLength;
+    }
+  }
+  return boost::none;
+}
+
+std::string GetModulePath() {
+  std::vector<char> path;
+  int length, dirname_length;
+  length = wai_getModulePath(NULL, 0, &dirname_length);
+
+  if (length != 0) {
+    path.resize(length);
+    wai_getModulePath(path.data(), length, &dirname_length);
+  } else {
+    throw DriverException("Could not find module path.");
+  }
+
+  return std::string(path.begin(), path.begin() + dirname_length);
+}
+
+void ReadConfigFile(PropertyMap& properties, const std::string& 
config_file_name) {
+  auto config_path = GetModulePath();
+
+  std::ifstream config_file;
+  auto config_file_path = config_path + "/" + config_file_name;
+  config_file.open(config_file_path);
+
+  if (config_file.fail()) {
+    auto error_msg = "Arrow Flight SQL ODBC driver config file not found on 
\"" +
+                     config_file_path + "\"";
+    std::cerr << error_msg << std::endl;

Review Comment:
   Let's not spam console. If we're going to do this, at least use the logger.



##########
cpp/src/arrow/flight/sql/odbc/odbcabstraction/odbc_impl/odbc_statement.cc:
##########
@@ -0,0 +1,787 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_statement.h>
+
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/exceptions.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/attribute_utils.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_connection.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/odbc_impl/odbc_descriptor.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/result_set_metadata.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/spi/statement.h>
+#include 
<arrow/flight/sql/odbc/odbcabstraction/include/odbcabstraction/types.h>
+#include <sql.h>
+#include <sqlext.h>
+#include <sqltypes.h>
+#include <boost/optional.hpp>
+#include <boost/variant.hpp>
+#include <utility>
+
+using ODBC::DescriptorRecord;
+using ODBC::ODBCConnection;
+using ODBC::ODBCDescriptor;
+using ODBC::ODBCStatement;
+
+using driver::odbcabstraction::DriverException;
+using driver::odbcabstraction::ResultSetMetadata;
+using driver::odbcabstraction::Statement;
+
+namespace {
+void DescriptorToHandle(SQLPOINTER output, ODBCDescriptor* descriptor,
+                        SQLINTEGER* lenPtr) {
+  if (output) {
+    SQLHANDLE* outputHandle = static_cast<SQLHANDLE*>(output);
+    *outputHandle = reinterpret_cast<SQLHANDLE>(descriptor);
+  }
+  if (lenPtr) {
+    *lenPtr = sizeof(SQLHANDLE);
+  }
+}
+
+size_t GetLength(const DescriptorRecord& record) {
+  switch (record.m_type) {
+    case SQL_C_CHAR:
+    case SQL_C_WCHAR:
+    case SQL_C_BINARY:
+      return record.m_length;
+
+    case SQL_C_BIT:
+    case SQL_C_TINYINT:
+    case SQL_C_STINYINT:
+    case SQL_C_UTINYINT:
+      return sizeof(SQLSCHAR);
+
+    case SQL_C_SHORT:
+    case SQL_C_SSHORT:
+    case SQL_C_USHORT:
+      return sizeof(SQLSMALLINT);
+
+    case SQL_C_LONG:
+    case SQL_C_SLONG:
+    case SQL_C_ULONG:
+    case SQL_C_FLOAT:
+      return sizeof(SQLINTEGER);
+
+    case SQL_C_SBIGINT:
+    case SQL_C_UBIGINT:
+    case SQL_C_DOUBLE:
+      return sizeof(SQLBIGINT);
+
+    case SQL_C_NUMERIC:
+      return sizeof(SQL_NUMERIC_STRUCT);
+
+    case SQL_C_DATE:
+    case SQL_C_TYPE_DATE:
+      return sizeof(SQL_DATE_STRUCT);
+
+    case SQL_C_TIME:
+    case SQL_C_TYPE_TIME:
+      return sizeof(SQL_TIME_STRUCT);
+
+    case SQL_C_TIMESTAMP:
+    case SQL_C_TYPE_TIMESTAMP:
+      return sizeof(SQL_TIMESTAMP_STRUCT);
+
+    case SQL_C_INTERVAL_DAY:
+    case SQL_C_INTERVAL_DAY_TO_HOUR:
+    case SQL_C_INTERVAL_DAY_TO_MINUTE:
+    case SQL_C_INTERVAL_DAY_TO_SECOND:
+    case SQL_C_INTERVAL_HOUR:
+    case SQL_C_INTERVAL_HOUR_TO_MINUTE:
+    case SQL_C_INTERVAL_HOUR_TO_SECOND:
+    case SQL_C_INTERVAL_MINUTE:
+    case SQL_C_INTERVAL_MINUTE_TO_SECOND:
+    case SQL_C_INTERVAL_SECOND:
+    case SQL_C_INTERVAL_YEAR:
+    case SQL_C_INTERVAL_YEAR_TO_MONTH:
+    case SQL_C_INTERVAL_MONTH:
+      return sizeof(SQL_INTERVAL_STRUCT);
+    default:
+      return record.m_length;
+  }
+}
+
+SQLSMALLINT getCTypeForSQLType(const DescriptorRecord& record) {
+  switch (record.m_conciseType) {
+    case SQL_CHAR:
+    case SQL_VARCHAR:
+    case SQL_LONGVARCHAR:
+      return SQL_C_CHAR;
+
+    case SQL_WCHAR:
+    case SQL_WVARCHAR:
+    case SQL_WLONGVARCHAR:
+      return SQL_C_WCHAR;
+
+    case SQL_BINARY:
+    case SQL_VARBINARY:
+    case SQL_LONGVARBINARY:
+      return SQL_C_BINARY;
+
+    case SQL_TINYINT:
+      return record.m_unsigned ? SQL_C_UTINYINT : SQL_C_STINYINT;
+
+    case SQL_SMALLINT:
+      return record.m_unsigned ? SQL_C_USHORT : SQL_C_SSHORT;
+
+    case SQL_INTEGER:
+      return record.m_unsigned ? SQL_C_ULONG : SQL_C_SLONG;
+
+    case SQL_BIGINT:
+      return record.m_unsigned ? SQL_C_UBIGINT : SQL_C_SBIGINT;
+
+    case SQL_REAL:
+      return SQL_C_FLOAT;
+
+    case SQL_FLOAT:
+    case SQL_DOUBLE:
+      return SQL_C_DOUBLE;
+
+    case SQL_DATE:
+    case SQL_TYPE_DATE:
+      return SQL_C_TYPE_DATE;
+
+    case SQL_TIME:
+    case SQL_TYPE_TIME:
+      return SQL_C_TYPE_TIME;
+
+    case SQL_TIMESTAMP:
+    case SQL_TYPE_TIMESTAMP:
+      return SQL_C_TYPE_TIMESTAMP;
+
+    case SQL_C_INTERVAL_DAY:
+      return SQL_INTERVAL_DAY;
+    case SQL_C_INTERVAL_DAY_TO_HOUR:
+      return SQL_INTERVAL_DAY_TO_HOUR;
+    case SQL_C_INTERVAL_DAY_TO_MINUTE:
+      return SQL_INTERVAL_DAY_TO_MINUTE;
+    case SQL_C_INTERVAL_DAY_TO_SECOND:
+      return SQL_INTERVAL_DAY_TO_SECOND;
+    case SQL_C_INTERVAL_HOUR:
+      return SQL_INTERVAL_HOUR;
+    case SQL_C_INTERVAL_HOUR_TO_MINUTE:
+      return SQL_INTERVAL_HOUR_TO_MINUTE;
+    case SQL_C_INTERVAL_HOUR_TO_SECOND:
+      return SQL_INTERVAL_HOUR_TO_SECOND;
+    case SQL_C_INTERVAL_MINUTE:
+      return SQL_INTERVAL_MINUTE;
+    case SQL_C_INTERVAL_MINUTE_TO_SECOND:
+      return SQL_INTERVAL_MINUTE_TO_SECOND;
+    case SQL_C_INTERVAL_SECOND:
+      return SQL_INTERVAL_SECOND;
+    case SQL_C_INTERVAL_YEAR:
+      return SQL_INTERVAL_YEAR;
+    case SQL_C_INTERVAL_YEAR_TO_MONTH:
+      return SQL_INTERVAL_YEAR_TO_MONTH;
+    case SQL_C_INTERVAL_MONTH:
+      return SQL_INTERVAL_MONTH;
+
+    default:
+      throw DriverException("Unknown SQL type: " + 
std::to_string(record.m_conciseType),
+                            "HY003");
+  }
+}
+
+void CopyAttribute(Statement& source, Statement& target,
+                   Statement::StatementAttributeId attributeId) {
+  auto optionalValue = source.GetAttribute(attributeId);
+  if (optionalValue) {
+    target.SetAttribute(attributeId, *optionalValue);
+  }
+}
+}  // namespace
+
+// Public
+// 
=========================================================================================
+ODBCStatement::ODBCStatement(
+    ODBCConnection& connection,
+    std::shared_ptr<driver::odbcabstraction::Statement> spiStatement)
+    : m_connection(connection),
+      m_spiStatement(std::move(spiStatement)),
+      m_diagnostics(&m_spiStatement->GetDiagnostics()),
+      
m_builtInArd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(),
+                                                    nullptr, this, true, true,
+                                                    
connection.IsOdbc2Connection())),
+      
m_builtInApd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(),
+                                                    nullptr, this, true, true,
+                                                    
connection.IsOdbc2Connection())),
+      m_ipd(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(), 
nullptr,
+                                             this, false, true,
+                                             connection.IsOdbc2Connection())),
+      m_ird(std::make_shared<ODBCDescriptor>(m_spiStatement->GetDiagnostics(), 
nullptr,
+                                             this, false, false,
+                                             connection.IsOdbc2Connection())),
+      m_currentArd(m_builtInApd.get()),
+      m_currentApd(m_builtInApd.get()),
+      m_rowNumber(0),
+      m_maxRows(0),
+      m_rowsetSize(1),
+      m_isPrepared(false),
+      m_hasReachedEndOfResult(false) {}
+
+ODBCConnection& ODBCStatement::GetConnection() { return m_connection; }
+
+void ODBCStatement::CopyAttributesFromConnection(ODBCConnection& connection) {
+  ODBCStatement& trackingStatement = connection.GetTrackingStatement();
+
+  // Get abstraction attributes and copy to this m_spiStatement.
+  // Possible ODBC attributes are below, but many of these are not supported 
by warpdrive
+  // or ODBCAbstaction:
+  // SQL_ATTR_ASYNC_ENABLE:
+  // SQL_ATTR_METADATA_ID:
+  // SQL_ATTR_CONCURRENCY:
+  // SQL_ATTR_CURSOR_TYPE:
+  // SQL_ATTR_KEYSET_SIZE:
+  // SQL_ATTR_MAX_LENGTH:
+  // SQL_ATTR_MAX_ROWS:
+  // SQL_ATTR_NOSCAN:
+  // SQL_ATTR_QUERY_TIMEOUT:
+  // SQL_ATTR_RETRIEVE_DATA:
+  // SQL_ATTR_SIMULATE_CURSOR:
+  // SQL_ATTR_USE_BOOKMARKS:
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::METADATA_ID);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::MAX_LENGTH);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement, 
Statement::NOSCAN);
+  CopyAttribute(*trackingStatement.m_spiStatement, *m_spiStatement,
+                Statement::QUERY_TIMEOUT);
+
+  // SQL_ATTR_ROW_BIND_TYPE:
+  m_currentArd->SetHeaderField(
+      SQL_DESC_BIND_TYPE,
+      reinterpret_cast<SQLPOINTER>(
+          
static_cast<SQLLEN>(trackingStatement.m_currentArd->GetBoundStructOffset())),
+      0);
+}
+
+bool ODBCStatement::isPrepared() const { return m_isPrepared; }
+
+void ODBCStatement::Prepare(const std::string& query) {
+  boost::optional<std::shared_ptr<ResultSetMetadata> > metadata =
+      m_spiStatement->Prepare(query);
+
+  if (metadata) {
+    m_ird->PopulateFromResultSetMetadata(metadata->get());
+  }
+  m_isPrepared = true;
+}
+
+void ODBCStatement::ExecutePrepared() {
+  if (!m_isPrepared) {
+    throw DriverException("Function sequence error", "HY010");
+  }
+
+  if (m_spiStatement->ExecutePrepared()) {
+    m_currenResult = m_spiStatement->GetResultSet();
+    m_ird->PopulateFromResultSetMetadata(
+        m_spiStatement->GetResultSet()->GetMetadata().get());
+    m_hasReachedEndOfResult = false;
+  }
+}
+
+void ODBCStatement::ExecuteDirect(const std::string& query) {
+  if (m_spiStatement->Execute(query)) {
+    m_currenResult = m_spiStatement->GetResultSet();
+    m_ird->PopulateFromResultSetMetadata(m_currenResult->GetMetadata().get());
+    m_hasReachedEndOfResult = false;
+  }
+
+  // Direct execution wipes out the prepared state.
+  m_isPrepared = false;
+}
+
+bool ODBCStatement::Fetch(size_t rows) {
+  if (m_hasReachedEndOfResult) {
+    m_ird->SetRowsProcessed(0);
+    return false;
+  }
+
+  if (m_maxRows) {
+    rows = std::min(rows, m_maxRows - m_rowNumber);
+  }
+
+  if (m_currentArd->HaveBindingsChanged()) {
+    // TODO: Deal handle when offset != bufferlength.
+
+    // Wipe out all bindings in the ResultSet.
+    // Note that the number of ARD records can both be more or less
+    // than the number of columns.
+    for (size_t i = 0; i < m_ird->GetRecords().size(); i++) {
+      if (i < m_currentArd->GetRecords().size() &&
+          m_currentArd->GetRecords()[i].m_isBound) {
+        const DescriptorRecord& ardRecord = m_currentArd->GetRecords()[i];
+        m_currenResult->BindColumn(i + 1, ardRecord.m_type, 
ardRecord.m_precision,
+                                   ardRecord.m_scale, ardRecord.m_dataPtr,
+                                   GetLength(ardRecord), 
ardRecord.m_indicatorPtr);
+      } else {
+        m_currenResult->BindColumn(i + 1,
+                                   driver::odbcabstraction::CDataType_CHAR
+                                   /* arbitrary type, not used */,
+                                   0, 0, nullptr, 0, nullptr);
+      }
+    }
+    m_currentArd->NotifyBindingsHavePropagated();
+  }
+
+  size_t rowsFetched = m_currenResult->Move(rows, 
m_currentArd->GetBindOffset(),
+                                            
m_currentArd->GetBoundStructOffset(),
+                                            m_ird->GetArrayStatusPtr());
+  m_ird->SetRowsProcessed(static_cast<SQLULEN>(rowsFetched));
+
+  m_rowNumber += rowsFetched;
+  m_hasReachedEndOfResult = rowsFetched != rows;
+  return rowsFetched != 0;
+}
+
+void ODBCStatement::GetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER 
output,
+                                SQLINTEGER bufferSize, SQLINTEGER* strLenPtr,
+                                bool isUnicode) {
+  using driver::odbcabstraction::Statement;
+  boost::optional<Statement::Attribute> spiAttribute;
+  switch (statementAttribute) {
+    // Descriptor accessor attributes
+    case SQL_ATTR_APP_PARAM_DESC:
+      DescriptorToHandle(output, m_currentApd, strLenPtr);
+      return;
+    case SQL_ATTR_APP_ROW_DESC:
+      DescriptorToHandle(output, m_currentArd, strLenPtr);
+      return;
+    case SQL_ATTR_IMP_PARAM_DESC:
+      DescriptorToHandle(output, m_ipd.get(), strLenPtr);
+      return;
+    case SQL_ATTR_IMP_ROW_DESC:
+      DescriptorToHandle(output, m_ird.get(), strLenPtr);
+      return;
+
+    // Attributes that are descriptor fields
+    case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
+      m_currentApd->GetHeaderField(SQL_DESC_BIND_OFFSET_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_BIND_TYPE:
+      m_currentApd->GetHeaderField(SQL_DESC_BIND_TYPE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_OPERATION_PTR:
+      m_currentApd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_PARAM_STATUS_PTR:
+      m_ipd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAMS_PROCESSED_PTR:
+      m_ipd->GetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_PARAMSET_SIZE:
+      m_currentApd->GetHeaderField(SQL_DESC_ARRAY_SIZE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_ARRAY_SIZE:
+      m_currentArd->GetHeaderField(SQL_DESC_ARRAY_SIZE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_BIND_OFFSET_PTR:
+      m_currentArd->GetHeaderField(SQL_DESC_BIND_OFFSET_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_ROW_BIND_TYPE:
+      m_currentArd->GetHeaderField(SQL_DESC_BIND_TYPE, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROW_OPERATION_PTR:
+      m_currentArd->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, 
bufferSize,
+                                   strLenPtr);
+      return;
+    case SQL_ATTR_ROW_STATUS_PTR:
+      m_ird->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_ROWS_FETCHED_PTR:
+      m_ird->GetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_ASYNC_ENABLE:
+      GetAttribute(static_cast<SQLULEN>(SQL_ASYNC_ENABLE_OFF), output, 
bufferSize,
+                   strLenPtr);
+      return;
+
+#ifdef SQL_ATTR_ASYNC_STMT_EVENT
+    case SQL_ATTR_ASYNC_STMT_EVENT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK
+    case SQL_ATTR_ASYNC_STMT_PCALLBACK:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT
+    case SQL_ATTR_ASYNC_STMT_PCONTEXT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+    case SQL_ATTR_CURSOR_SCROLLABLE:
+      GetAttribute(static_cast<SQLULEN>(SQL_NONSCROLLABLE), output, bufferSize,
+                   strLenPtr);
+      return;
+
+    case SQL_ATTR_CURSOR_SENSITIVITY:
+      GetAttribute(static_cast<SQLULEN>(SQL_UNSPECIFIED), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_CURSOR_TYPE:
+      GetAttribute(static_cast<SQLULEN>(SQL_CURSOR_FORWARD_ONLY), output, 
bufferSize,
+                   strLenPtr);
+      return;
+
+    case SQL_ATTR_ENABLE_AUTO_IPD:
+      GetAttribute(static_cast<SQLULEN>(SQL_FALSE), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_FETCH_BOOKMARK_PTR:
+      GetAttribute(static_cast<SQLPOINTER>(NULL), output, bufferSize, 
strLenPtr);
+      return;
+
+    case SQL_ATTR_KEYSET_SIZE:
+      GetAttribute(static_cast<SQLULEN>(0), output, bufferSize, strLenPtr);
+      return;
+
+    case SQL_ATTR_ROW_NUMBER:
+      GetAttribute(static_cast<SQLULEN>(m_rowNumber), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_SIMULATE_CURSOR:
+      GetAttribute(static_cast<SQLULEN>(SQL_SC_UNIQUE), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_USE_BOOKMARKS:
+      GetAttribute(static_cast<SQLULEN>(SQL_UB_OFF), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_CONCURRENCY:
+      GetAttribute(static_cast<SQLULEN>(SQL_CONCUR_READ_ONLY), output, 
bufferSize,
+                   strLenPtr);
+      return;
+    case SQL_ATTR_MAX_ROWS:
+      GetAttribute(static_cast<SQLULEN>(m_maxRows), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ATTR_RETRIEVE_DATA:
+      GetAttribute(static_cast<SQLULEN>(SQL_RD_ON), output, bufferSize, 
strLenPtr);
+      return;
+    case SQL_ROWSET_SIZE:
+      GetAttribute(static_cast<SQLULEN>(m_rowsetSize), output, bufferSize, 
strLenPtr);
+      return;
+
+    // Driver-level statement attributes. These are all SQLULEN attributes.
+    case SQL_ATTR_MAX_LENGTH:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::MAX_LENGTH);
+      break;
+    case SQL_ATTR_METADATA_ID:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::METADATA_ID);
+      break;
+    case SQL_ATTR_NOSCAN:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::NOSCAN);
+      break;
+    case SQL_ATTR_QUERY_TIMEOUT:
+      spiAttribute = m_spiStatement->GetAttribute(Statement::QUERY_TIMEOUT);
+      break;
+    default:
+      throw DriverException(
+          "Invalid statement attribute: " + 
std::to_string(statementAttribute), "HY092");
+  }
+
+  if (spiAttribute) {
+    GetAttribute(static_cast<SQLULEN>(boost::get<size_t>(*spiAttribute)), 
output,
+                 bufferSize, strLenPtr);
+    return;
+  }
+
+  throw DriverException(
+      "Invalid statement attribute: " + std::to_string(statementAttribute), 
"HY092");
+}
+
+void ODBCStatement::SetStmtAttr(SQLINTEGER statementAttribute, SQLPOINTER 
value,
+                                SQLINTEGER bufferSize, bool isUnicode) {
+  size_t attributeToWrite = 0;
+  bool successfully_written = false;
+
+  switch (statementAttribute) {
+    case SQL_ATTR_APP_PARAM_DESC: {
+      ODBCDescriptor* desc = static_cast<ODBCDescriptor*>(value);
+      if (m_currentApd != desc) {
+        if (m_currentApd != m_builtInApd.get()) {
+          m_currentApd->DetachFromStatement(this, true);
+        }
+        m_currentApd = desc;
+        if (m_currentApd != m_builtInApd.get()) {
+          desc->RegisterToStatement(this, true);
+        }
+      }
+      return;
+    }
+    case SQL_ATTR_APP_ROW_DESC: {
+      ODBCDescriptor* desc = static_cast<ODBCDescriptor*>(value);
+      if (m_currentArd != desc) {
+        if (m_currentArd != m_builtInArd.get()) {
+          m_currentArd->DetachFromStatement(this, false);
+        }
+        m_currentArd = desc;
+        if (m_currentArd != m_builtInArd.get()) {
+          desc->RegisterToStatement(this, false);
+        }
+      }
+      return;
+    }
+    case SQL_ATTR_IMP_PARAM_DESC:
+      throw DriverException("Cannot assign implementation descriptor.", 
"HY017");
+    case SQL_ATTR_IMP_ROW_DESC:
+      throw DriverException("Cannot assign implementation descriptor.", 
"HY017");
+      // Attributes that are descriptor fields
+    case SQL_ATTR_PARAM_BIND_OFFSET_PTR:
+      m_currentApd->SetHeaderField(SQL_DESC_BIND_OFFSET_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_PARAM_BIND_TYPE:
+      m_currentApd->SetHeaderField(SQL_DESC_BIND_TYPE, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAM_OPERATION_PTR:
+      m_currentApd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_PARAM_STATUS_PTR:
+      m_ipd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAMS_PROCESSED_PTR:
+      m_ipd->SetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_PARAMSET_SIZE:
+      m_currentApd->SetHeaderField(SQL_DESC_ARRAY_SIZE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_ARRAY_SIZE:
+      m_currentArd->SetHeaderField(SQL_DESC_ARRAY_SIZE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_BIND_OFFSET_PTR:
+      m_currentArd->SetHeaderField(SQL_DESC_BIND_OFFSET_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_ROW_BIND_TYPE:
+      m_currentArd->SetHeaderField(SQL_DESC_BIND_TYPE, value, bufferSize);
+      return;
+    case SQL_ATTR_ROW_OPERATION_PTR:
+      m_currentArd->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, 
bufferSize);
+      return;
+    case SQL_ATTR_ROW_STATUS_PTR:
+      m_ird->SetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, value, bufferSize);
+      return;
+    case SQL_ATTR_ROWS_FETCHED_PTR:
+      m_ird->SetHeaderField(SQL_DESC_ROWS_PROCESSED_PTR, value, bufferSize);
+      return;
+
+    case SQL_ATTR_ASYNC_ENABLE:
+#ifdef SQL_ATTR_ASYNC_STMT_EVENT
+    case SQL_ATTR_ASYNC_STMT_EVENT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK
+    case SQL_ATTR_ASYNC_STMT_PCALLBACK:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT
+    case SQL_ATTR_ASYNC_STMT_PCONTEXT:
+      throw DriverException("Unsupported attribute", "HYC00");
+#endif
+    case SQL_ATTR_CONCURRENCY:
+      CheckIfAttributeIsSetToOnlyValidValue(value,
+                                            
static_cast<SQLULEN>(SQL_CONCUR_READ_ONLY));
+      return;
+    case SQL_ATTR_CURSOR_SCROLLABLE:
+      CheckIfAttributeIsSetToOnlyValidValue(value,
+                                            
static_cast<SQLULEN>(SQL_NONSCROLLABLE));
+      return;
+    case SQL_ATTR_CURSOR_SENSITIVITY:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_UNSPECIFIED));
+      return;
+    case SQL_ATTR_CURSOR_TYPE:
+      CheckIfAttributeIsSetToOnlyValidValue(
+          value, static_cast<SQLULEN>(SQL_CURSOR_FORWARD_ONLY));
+      return;
+    case SQL_ATTR_ENABLE_AUTO_IPD:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_FALSE));
+      return;
+    case SQL_ATTR_FETCH_BOOKMARK_PTR:
+      if (value != NULL) {
+        throw DriverException("Optional feature not implemented", "HYC00");
+      }
+      return;
+    case SQL_ATTR_KEYSET_SIZE:
+      CheckIfAttributeIsSetToOnlyValidValue(value, static_cast<SQLULEN>(0));
+      return;
+    case SQL_ATTR_ROW_NUMBER:
+      throw DriverException("Cannot set read-only attribute", "HY092");
+    case SQL_ATTR_SIMULATE_CURSOR:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_SC_UNIQUE));
+      return;
+    case SQL_ATTR_USE_BOOKMARKS:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_UB_OFF));
+      return;
+    case SQL_ATTR_RETRIEVE_DATA:
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_TRUE));
+      return;
+    case SQL_ROWSET_SIZE:
+      SetAttribute(value, m_rowsetSize);
+      return;
+
+    case SQL_ATTR_MAX_ROWS:
+      throw DriverException("Cannot set read-only attribute", "HY092");
+
+    // Driver-leve statement attributes. These are all size_t attributes
+    case SQL_ATTR_MAX_LENGTH:
+      SetAttribute(value, attributeToWrite);
+      successfully_written =
+          m_spiStatement->SetAttribute(Statement::MAX_LENGTH, 
attributeToWrite);
+      break;
+    case SQL_ATTR_METADATA_ID:
+      SetAttribute(value, attributeToWrite);
+      successfully_written =
+          m_spiStatement->SetAttribute(Statement::METADATA_ID, 
attributeToWrite);
+      break;
+    case SQL_ATTR_NOSCAN:
+      SetAttribute(value, attributeToWrite);
+      successfully_written =
+          m_spiStatement->SetAttribute(Statement::NOSCAN, attributeToWrite);
+      break;
+    case SQL_ATTR_QUERY_TIMEOUT:
+      SetAttribute(value, attributeToWrite);
+      successfully_written =
+          m_spiStatement->SetAttribute(Statement::QUERY_TIMEOUT, 
attributeToWrite);
+      break;
+    default:
+      throw DriverException("Invalid attribute: " + 
std::to_string(attributeToWrite),
+                            "HY092");
+  }
+  if (!successfully_written) {
+    GetDiagnostics().AddWarning("Optional value changed.", "01S02",
+                                
driver::odbcabstraction::ODBCErrorCodes_GENERAL_WARNING);
+  }
+}
+
+void ODBCStatement::RevertAppDescriptor(bool isApd) {
+  if (isApd) {
+    m_currentApd = m_builtInApd.get();
+  } else {
+    m_currentArd = m_builtInArd.get();
+  }
+}
+
+void ODBCStatement::closeCursor(bool suppressErrors) {
+  if (!suppressErrors && !m_currenResult) {
+    throw DriverException("Invalid cursor state", "28000");
+  }
+
+  if (m_currenResult) {
+    m_currenResult->Close();
+    m_currenResult = nullptr;
+  }
+
+  // Reset the fetching state of this statement.
+  m_currentArd->NotifyBindingsHaveChanged();
+  m_rowNumber = 0;
+  m_hasReachedEndOfResult = false;
+}
+
+bool ODBCStatement::GetData(SQLSMALLINT recordNumber, SQLSMALLINT cType,
+                            SQLPOINTER dataPtr, SQLLEN bufferLength,
+                            SQLLEN* indicatorPtr) {
+  if (recordNumber == 0) {
+    throw DriverException("Bookmarks are not supported", "07009");
+  } else if (recordNumber > m_ird->GetRecords().size()) {
+    throw DriverException("Invalid column index: " + 
std::to_string(recordNumber),
+                          "07009");
+  }
+
+  SQLSMALLINT evaluatedCType = cType;
+
+  // TODO: Get proper default precision and scale from abstraction.

Review Comment:
   Can we file issues for these TODOs? Is there any plan to tackle them?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: github-unsubscr...@arrow.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to