This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 430d4b1352 GH-47710: [C++][FlightRPC] Statement attribute Support in 
ODBC (#47773)
430d4b1352 is described below

commit 430d4b1352aef5acbe7fd9728979bfa3d4a43ca3
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Thu Nov 27 19:50:12 2025 -0800

    GH-47710: [C++][FlightRPC] Statement attribute Support in ODBC (#47773)
    
    ### Rationale for this change
    Support for getting and setting statement attributes in ODBC is added.
    
    ### What changes are included in this PR?
    - Implementation of `SQLGetStmtAttr` and  `SQLSetStmtAttr` to get and set 
statement attributes.
    - Tests
    
    ### Are these changes tested?
    Tested on local MSVC
    
    ### Are there any user-facing changes?
    No
    * GitHub Issue: #47710
    
    Lead-authored-by: Alina (Xi) Li <[email protected]>
    Co-authored-by: alinalibq <[email protected]>
    Co-authored-by: justing-bq <[email protected]>
    Signed-off-by: David Li <[email protected]>
---
 cpp/src/arrow/flight/sql/odbc/odbc_api.cc          |  29 +-
 .../flight/sql/odbc/odbc_impl/odbc_statement.cc    |  18 +-
 cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt |   1 +
 .../arrow/flight/sql/odbc/tests/odbc_test_suite.cc |   6 +-
 .../flight/sql/odbc/tests/statement_attr_test.cc   | 658 +++++++++++++++++++++
 5 files changed, 703 insertions(+), 9 deletions(-)

diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index a028e063b3..19c6e62eba 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -923,8 +923,19 @@ SQLRETURN SQLGetStmtAttr(SQLHSTMT stmt, SQLINTEGER 
attribute, SQLPOINTER value_p
                    << ", attribute: " << attribute << ", value_ptr: " << 
value_ptr
                    << ", buffer_length: " << buffer_length << ", 
string_length_ptr: "
                    << static_cast<const void*>(string_length_ptr);
-  // GH-47710 TODO: Implement SQLGetStmtAttr
-  return SQL_INVALID_HANDLE;
+
+  using ODBC::ODBCStatement;
+
+  return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
+    ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
+
+    bool is_unicode = true;
+
+    statement->GetStmtAttr(attribute, value_ptr, buffer_length, 
string_length_ptr,
+                           is_unicode);
+
+    return SQL_SUCCESS;
+  });
 }
 
 SQLRETURN SQLSetStmtAttr(SQLHSTMT stmt, SQLINTEGER attribute, SQLPOINTER 
value_ptr,
@@ -932,8 +943,18 @@ SQLRETURN SQLSetStmtAttr(SQLHSTMT stmt, SQLINTEGER 
attribute, SQLPOINTER value_p
   ARROW_LOG(DEBUG) << "SQLSetStmtAttrW called with stmt: " << stmt
                    << ", attribute: " << attribute << ", value_ptr: " << 
value_ptr
                    << ", string_length: " << string_length;
-  // GH-47710 TODO: Implement SQLSetStmtAttr
-  return SQL_INVALID_HANDLE;
+
+  using ODBC::ODBCStatement;
+
+  return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
+    ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
+
+    bool is_unicode = true;
+
+    statement->SetStmtAttr(attribute, value_ptr, string_length, is_unicode);
+
+    return SQL_SUCCESS;
+  });
 }
 
 SQLRETURN SQLExecDirect(SQLHSTMT stmt, SQLWCHAR* query_text, SQLINTEGER 
text_length) {
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc 
b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc
index d452e77db1..0f7d2bdefa 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc
@@ -375,6 +375,14 @@ void ODBCStatement::GetStmtAttr(SQLINTEGER 
statement_attribute, SQLPOINTER outpu
       return;
     case SQL_ATTR_PARAM_BIND_TYPE:
       current_apd_->GetHeaderField(SQL_DESC_BIND_TYPE, output, buffer_size, 
str_len_ptr);
+      if (output) {
+        // Convert SQLINTEGER output to SQLULEN, since SQL_DESC_BIND_TYPE is 
SQLINTEGER
+        // and SQL_ATTR_PARAM_BIND_TYPE is SQLULEN
+        SQLINTEGER* output_int_ptr = reinterpret_cast<SQLINTEGER*>(output);
+        SQLINTEGER output_int = *output_int_ptr;
+        SQLULEN* typed_output = reinterpret_cast<SQLULEN*>(output);
+        *typed_output = static_cast<SQLULEN>(output_int);
+      }
       return;
     case SQL_ATTR_PARAM_OPERATION_PTR:
       current_apd_->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, 
buffer_size,
@@ -398,6 +406,14 @@ void ODBCStatement::GetStmtAttr(SQLINTEGER 
statement_attribute, SQLPOINTER outpu
       return;
     case SQL_ATTR_ROW_BIND_TYPE:
       current_ard_->GetHeaderField(SQL_DESC_BIND_TYPE, output, buffer_size, 
str_len_ptr);
+      if (output) {
+        // Convert SQLINTEGER output to SQLULEN, since SQL_DESC_BIND_TYPE is 
SQLINTEGER
+        // and SQL_ATTR_ROW_BIND_TYPE is SQLULEN
+        SQLINTEGER* output_int_ptr = reinterpret_cast<SQLINTEGER*>(output);
+        SQLINTEGER output_int = *output_int_ptr;
+        SQLULEN* typed_output = reinterpret_cast<SQLULEN*>(output);
+        *typed_output = static_cast<SQLULEN>(output_int);
+      }
       return;
     case SQL_ATTR_ROW_OPERATION_PTR:
       current_ard_->GetHeaderField(SQL_DESC_ARRAY_STATUS_PTR, output, 
buffer_size,
@@ -627,7 +643,7 @@ void ODBCStatement::SetStmtAttr(SQLINTEGER 
statement_attribute, SQLPOINTER value
       CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_UB_OFF));
       return;
     case SQL_ATTR_RETRIEVE_DATA:
-      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_TRUE));
+      CheckIfAttributeIsSetToOnlyValidValue(value, 
static_cast<SQLULEN>(SQL_RD_ON));
       return;
     case SQL_ROWSET_SIZE:
       SetAttribute(value, rowset_size_);
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt 
b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
index 4bc240637e..7be318039d 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
+++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
@@ -34,6 +34,7 @@ add_arrow_test(flight_sql_odbc_test
                SOURCES
                odbc_test_suite.cc
                odbc_test_suite.h
+               statement_attr_test.cc
                connection_test.cc
                # Enable Protobuf cleanup after test execution
                # GH-46889: move protobuf_test_util to a more common location
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
index eb6c60b976..dfef9dcd1d 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.cc
@@ -61,13 +61,11 @@ void ODBCRemoteTestBase::ConnectWithString(std::string 
connect_str) {
                              kOdbcBufferSize, &out_str_len, 
SQL_DRIVER_NOPROMPT))
       << GetOdbcErrorMessage(SQL_HANDLE_DBC, conn);
 
-  // GH-47710: TODO Allocate a statement using alloc handle
-  // ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt));
+  ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_STMT, conn, &stmt));
 }
 
 void ODBCRemoteTestBase::Disconnect() {
-  // GH-47710: TODO Close statement
-  // EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_STMT, stmt));
+  EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_STMT, stmt));
 
   // Disconnect from ODBC
   EXPECT_EQ(SQL_SUCCESS, SQLDisconnect(conn))
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/statement_attr_test.cc 
b/cpp/src/arrow/flight/sql/odbc/tests/statement_attr_test.cc
new file mode 100644
index 0000000000..5b6821430a
--- /dev/null
+++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_attr_test.cc
@@ -0,0 +1,658 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h"
+
+#include "arrow/flight/sql/odbc/odbc_impl/odbc_statement.h"
+#include "arrow/flight/sql/odbc/odbc_impl/spi/statement.h"
+
+#include "arrow/flight/sql/odbc/odbc_impl/platform.h"
+
+#include <sql.h>
+#include <sqltypes.h>
+#include <sqlucode.h>
+
+#include <gtest/gtest.h>
+
+namespace arrow::flight::sql::odbc {
+
+template <typename T>
+class StatementAttributeTest : public T {};
+
+using TestTypes =
+    ::testing::Types<FlightSQLODBCMockTestBase, FlightSQLODBCRemoteTestBase>;
+TYPED_TEST_SUITE(StatementAttributeTest, TestTypes);
+
+namespace {
+// Helper Functions
+
+// Get SQLULEN return value
+void GetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLULEN* value) {
+  SQLINTEGER string_length = 0;
+
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLGetStmtAttr(statement, attribute, value, sizeof(*value), 
&string_length));
+}
+
+// Get SQLLEN return value
+void GetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLLEN* value) {
+  SQLINTEGER string_length = 0;
+
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLGetStmtAttr(statement, attribute, value, sizeof(*value), 
&string_length));
+}
+
+// Get SQLPOINTER return value
+void GetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLPOINTER* value) {
+  SQLINTEGER string_length = 0;
+
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLGetStmtAttr(statement, attribute, value, SQL_IS_POINTER, 
&string_length));
+}
+
+// Validate error return value and code
+void ValidateGetStmtAttrErrorCode(SQLHSTMT statement, SQLINTEGER attribute,
+                                  std::string_view error_code) {
+  SQLULEN value = 0;
+  SQLINTEGER string_length_ptr;
+
+  ASSERT_EQ(SQL_ERROR,
+            SQLGetStmtAttr(statement, attribute, &value, 0, 
&string_length_ptr));
+
+  VerifyOdbcErrorState(SQL_HANDLE_STMT, statement, error_code);
+}
+
+// Validate return value for call to SQLSetStmtAttr with SQLULEN
+void ValidateSetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLULEN 
new_value) {
+  SQLINTEGER string_length_ptr = sizeof(SQLULEN);
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLSetStmtAttr(statement, attribute, 
reinterpret_cast<SQLPOINTER>(new_value),
+                           string_length_ptr));
+}
+
+// Validate return value for call to SQLSetStmtAttr with SQLLEN
+void ValidateSetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLLEN 
new_value) {
+  SQLINTEGER string_length_ptr = sizeof(SQLLEN);
+
+  EXPECT_EQ(SQL_SUCCESS,
+            SQLSetStmtAttr(statement, attribute, 
reinterpret_cast<SQLPOINTER>(new_value),
+                           string_length_ptr));
+}
+
+// Validate return value for call to SQLSetStmtAttr with SQLPOINTER
+void ValidateSetStmtAttr(SQLHSTMT statement, SQLINTEGER attribute, SQLPOINTER 
value) {
+  EXPECT_EQ(SQL_SUCCESS, SQLSetStmtAttr(statement, attribute, value, 0));
+}
+
+// Validate error return value and code
+void ValidateSetStmtAttrErrorCode(SQLHSTMT statement, SQLINTEGER attribute,
+                                  SQLULEN new_value, std::string_view 
error_code) {
+  SQLINTEGER string_length_ptr = sizeof(SQLULEN);
+
+  ASSERT_EQ(SQL_ERROR,
+            SQLSetStmtAttr(statement, attribute, 
reinterpret_cast<SQLPOINTER>(new_value),
+                           string_length_ptr));
+
+  VerifyOdbcErrorState(SQL_HANDLE_STMT, statement, error_code);
+}
+}  // namespace
+
+// Test Cases
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrAppParamDesc) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, &value);
+
+  EXPECT_GT(value, static_cast<SQLULEN>(0));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrAppRowDesc) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, &value);
+
+  EXPECT_GT(value, static_cast<SQLULEN>(0));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrAsyncEnable) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_ASYNC_ENABLE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_ASYNC_ENABLE_OFF), value);
+}
+
+#ifdef SQL_ATTR_ASYNC_STMT_EVENT
+TYPED_TEST(StatementAttributeTest, 
TestSQLGetStmtAttrAsyncStmtEventUnsupported) {
+  // Optional feature not implemented
+  ValidateGetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_EVENT, 
kErrorStateHYC00);
+}
+#endif
+
+#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK
+TYPED_TEST(StatementAttributeTest, 
TestSQLGetStmtAttrAsyncStmtPCCallbackUnsupported) {
+  // Optional feature not implemented
+  ValidateGetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_PCALLBACK,
+                               kErrorStateHYC00);
+}
+#endif
+
+#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT
+TYPED_TEST(StatementAttributeTest, 
TestSQLGetStmtAttrAsyncStmtPCContextUnsupported) {
+  // Optional feature not implemented
+  ValidateGetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_PCONTEXT,
+                               kErrorStateHYC00);
+}
+#endif
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrConcurrency) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_CONCURRENCY, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_CONCUR_READ_ONLY), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrCursorScrollable) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_CURSOR_SCROLLABLE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_NONSCROLLABLE), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrCursorSensitivity) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_CURSOR_SENSITIVITY, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_UNSPECIFIED), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrCursorType) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_CURSOR_TYPE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_CURSOR_FORWARD_ONLY), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrEnableAutoIPD) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_ENABLE_AUTO_IPD, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_FALSE), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrFetchBookmarkPointer) {
+  SQLLEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_FETCH_BOOKMARK_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLLEN>(NULL), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrIMPParamDesc) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_IMP_PARAM_DESC, &value);
+
+  EXPECT_GT(value, static_cast<SQLULEN>(0));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrIMPRowDesc) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_IMP_ROW_DESC, &value);
+
+  EXPECT_GT(value, static_cast<SQLULEN>(0));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrKeysetSize) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_KEYSET_SIZE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(0), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrMaxLength) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_MAX_LENGTH, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(0), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrMaxRows) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_MAX_ROWS, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(0), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrMetadataID) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_METADATA_ID, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_FALSE), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrNoscan) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_NOSCAN, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_NOSCAN_OFF), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrParamBindOffsetPtr) {
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(nullptr), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrParamBindType) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_TYPE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_PARAM_BIND_BY_COLUMN), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrParamOperationPtr) {
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAM_OPERATION_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(nullptr), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrParamStatusPtr) {
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAM_STATUS_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(nullptr), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrParamsProcessedPtr) {
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(nullptr), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrParamsetSize) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAMSET_SIZE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(1), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrQueryTimeout) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_QUERY_TIMEOUT, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(0), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRetrieveData) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_RETRIEVE_DATA, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_RD_ON), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowArraySize) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_ARRAY_SIZE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(1), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowBindOffsetPtr) {
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(nullptr), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowBindType) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_TYPE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(0), value);
+}
+
+TYPED_TEST(StatementAttributeTest, DISABLED_TestSQLGetStmtAttrRowNumber) {
+  // GH-47711 TODO: enable test after SQLExecDirect support
+  std::wstring wsql = L"SELECT 1;";
+  std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
+
+  ASSERT_EQ(SQL_SUCCESS,
+            SQLExecDirect(this->stmt, &sql0[0], 
static_cast<SQLINTEGER>(sql0.size())));
+
+  ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_NUMBER, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(1), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowOperationPtr) {
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_OPERATION_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(nullptr), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowStatusPtr) {
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_STATUS_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(nullptr), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowsFetchedPtr) {
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(nullptr), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrSimulateCursor) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_SIMULATE_CURSOR, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_SC_UNIQUE), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrUseBookmarks) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ATTR_USE_BOOKMARKS, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(SQL_UB_OFF), value);
+}
+
+// This is a pre ODBC 3 attribute
+TYPED_TEST(StatementAttributeTest, TestSQLGetStmtAttrRowsetSize) {
+  SQLULEN value;
+  GetStmtAttr(this->stmt, SQL_ROWSET_SIZE, &value);
+
+  EXPECT_EQ(static_cast<SQLULEN>(1), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrAppParamDesc) {
+  SQLULEN app_param_desc = 0;
+  SQLINTEGER string_length_ptr;
+
+  ASSERT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC,
+                                        &app_param_desc, 0, 
&string_length_ptr));
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, 
static_cast<SQLULEN>(0));
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC,
+                      static_cast<SQLULEN>(app_param_desc));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrAppRowDesc) {
+  SQLULEN app_row_desc = 0;
+  SQLINTEGER string_length_ptr;
+
+  ASSERT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, 
&app_row_desc,
+                                        0, &string_length_ptr));
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, 
static_cast<SQLULEN>(0));
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC,
+                      static_cast<SQLULEN>(app_row_desc));
+}
+
+#ifdef SQL_ATTR_ASYNC_ENABLE
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrAsyncEnableUnsupported) {
+  // Optional feature not implemented
+  ValidateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_ENABLE, 
SQL_ASYNC_ENABLE_OFF,
+                               kErrorStateHYC00);
+}
+#endif
+
+#ifdef SQL_ATTR_ASYNC_STMT_EVENT
+TYPED_TEST(StatementAttributeTest, 
TestSQLSetStmtAttrAsyncStmtEventUnsupported) {
+  // Driver does not support asynchronous notification
+  ValidateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_EVENT, 0,
+                               kErrorStateHY118);
+}
+#endif
+
+#ifdef SQL_ATTR_ASYNC_STMT_PCALLBACK
+TYPED_TEST(StatementAttributeTest, 
TestSQLSetStmtAttrAsyncStmtPCCallbackUnsupported) {
+  ValidateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_PCALLBACK, 0,
+                               kErrorStateHYC00);
+}
+#endif
+
+#ifdef SQL_ATTR_ASYNC_STMT_PCONTEXT
+TYPED_TEST(StatementAttributeTest, 
TestSQLSetStmtAttrAsyncStmtPCContextUnsupported) {
+  // Optional feature not implemented
+  ValidateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ASYNC_STMT_PCONTEXT, 0,
+                               kErrorStateHYC00);
+}
+#endif
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrConcurrency) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_CONCURRENCY,
+                      static_cast<SQLULEN>(SQL_CONCUR_READ_ONLY));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrCursorScrollable) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_CURSOR_SCROLLABLE,
+                      static_cast<SQLULEN>(SQL_NONSCROLLABLE));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrCursorSensitivity) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_CURSOR_SENSITIVITY,
+                      static_cast<SQLULEN>(SQL_UNSPECIFIED));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrCursorType) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_CURSOR_TYPE,
+                      static_cast<SQLULEN>(SQL_CURSOR_FORWARD_ONLY));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrEnableAutoIPD) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_ENABLE_AUTO_IPD,
+                      static_cast<SQLULEN>(SQL_FALSE));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrFetchBookmarkPointer) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_FETCH_BOOKMARK_PTR, 
static_cast<SQLLEN>(NULL));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrIMPParamDesc) {
+  // Invalid use of an automatically allocated descriptor handle
+  ValidateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_IMP_PARAM_DESC,
+                               static_cast<SQLULEN>(0), kErrorStateHY017);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrIMPRowDesc) {
+  // Invalid use of an automatically allocated descriptor handle
+  ValidateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_IMP_ROW_DESC, 
static_cast<SQLULEN>(0),
+                               kErrorStateHY017);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrKeysetSizeUnsupported) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_KEYSET_SIZE, 
static_cast<SQLULEN>(0));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrMaxLength) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_MAX_LENGTH, 
static_cast<SQLULEN>(0));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrMaxRows) {
+  // Cannot set read-only attribute
+  ValidateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_MAX_ROWS, 
static_cast<SQLULEN>(0),
+                               kErrorStateHY092);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrMetadataID) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_METADATA_ID, 
static_cast<SQLULEN>(SQL_FALSE));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrNoscan) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_NOSCAN, 
static_cast<SQLULEN>(SQL_NOSCAN_OFF));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrParamBindOffsetPtr) {
+  SQLULEN offset = 1000;
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR,
+                      static_cast<SQLPOINTER>(&offset));
+
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_OFFSET_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(&offset), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrParamBindType) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_PARAM_BIND_TYPE,
+                      static_cast<SQLULEN>(SQL_PARAM_BIND_BY_COLUMN));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrParamOperationPtr) {
+  constexpr SQLULEN param_set_size = 4;
+  SQLUSMALLINT param_operations[param_set_size] = {SQL_PARAM_PROCEED, 
SQL_PARAM_IGNORE,
+                                                   SQL_PARAM_PROCEED, 
SQL_PARAM_IGNORE};
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_PARAM_OPERATION_PTR,
+                      static_cast<SQLPOINTER>(param_operations));
+
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAM_OPERATION_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(param_operations), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrParamStatusPtr) {
+  // Driver does not support parameters, so just check array can be 
saved/retrieved
+  constexpr SQLULEN param_status_size = 4;
+  SQLUSMALLINT param_status[param_status_size] = {SQL_PARAM_PROCEED, 
SQL_PARAM_IGNORE,
+                                                  SQL_PARAM_PROCEED, 
SQL_PARAM_IGNORE};
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_PARAM_STATUS_PTR,
+                      static_cast<SQLPOINTER>(param_status));
+
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAM_STATUS_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(param_status), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrParamsProcessedPtr) {
+  SQLULEN processed_count = 0;
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_PARAMS_PROCESSED_PTR,
+                      static_cast<SQLPOINTER>(&processed_count));
+
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_PARAMS_PROCESSED_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(&processed_count), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrParamsetSize) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_PARAMSET_SIZE, 
static_cast<SQLULEN>(1));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrQueryTimeout) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_QUERY_TIMEOUT, 
static_cast<SQLULEN>(1));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRetrieveData) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_RETRIEVE_DATA,
+                      static_cast<SQLULEN>(SQL_RD_ON));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRowArraySize) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_ROW_ARRAY_SIZE, 
static_cast<SQLULEN>(1));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRowBindOffsetPtr) {
+  SQLULEN offset = 1000;
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_OFFSET_PTR,
+                      static_cast<SQLPOINTER>(&offset));
+
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_OFFSET_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(&offset), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRowBindType) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_ROW_BIND_TYPE, 
static_cast<SQLULEN>(0));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRowNumber) {
+  // Cannot set read-only attribute
+  ValidateSetStmtAttrErrorCode(this->stmt, SQL_ATTR_ROW_NUMBER, 
static_cast<SQLULEN>(0),
+                               kErrorStateHY092);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRowOperationPtr) {
+  constexpr SQLULEN param_set_size = 4;
+  SQLUSMALLINT row_operations[param_set_size] = {SQL_ROW_PROCEED, 
SQL_ROW_IGNORE,
+                                                 SQL_ROW_PROCEED, 
SQL_ROW_IGNORE};
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_ROW_OPERATION_PTR,
+                      static_cast<SQLPOINTER>(row_operations));
+
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_OPERATION_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(row_operations), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRowStatusPtr) {
+  constexpr SQLULEN row_status_size = 4;
+  SQLUSMALLINT values[row_status_size] = {0, 0, 0, 0};
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_ROW_STATUS_PTR,
+                      static_cast<SQLPOINTER>(values));
+
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROW_STATUS_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(values), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRowsFetchedPtr) {
+  SQLULEN rows_fetched = 1;
+
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR,
+                      static_cast<SQLPOINTER>(&rows_fetched));
+
+  SQLPOINTER value = nullptr;
+  GetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR, &value);
+
+  EXPECT_EQ(static_cast<SQLPOINTER>(&rows_fetched), value);
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrSimulateCursor) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_SIMULATE_CURSOR,
+                      static_cast<SQLULEN>(SQL_SC_UNIQUE));
+}
+
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrUseBookmarks) {
+  ValidateSetStmtAttr(this->stmt, SQL_ATTR_USE_BOOKMARKS,
+                      static_cast<SQLULEN>(SQL_UB_OFF));
+}
+
+// This is a pre ODBC 3 attribute
+TYPED_TEST(StatementAttributeTest, TestSQLSetStmtAttrRowsetSize) {
+  ValidateSetStmtAttr(this->stmt, SQL_ROWSET_SIZE, static_cast<SQLULEN>(1));
+}
+
+}  // namespace arrow::flight::sql::odbc

Reply via email to