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 79695c2a68 GH-47723: [C++][FlightRPC] ODBC SQLNativeSQL implementation
(#48020)
79695c2a68 is described below
commit 79695c2a680833a77da464c8d2505e3c409a4d62
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Wed Dec 3 00:03:50 2025 -0800
GH-47723: [C++][FlightRPC] ODBC SQLNativeSQL implementation (#48020)
### Rationale for this change
Implement SQLNativeSQL to return the same string as input as ODBC doesn't
support
### What changes are included in this PR?
- SQLNativeSQL implementation
### Are these changes tested?
- Will be tested in CI when PR is ready for review
### Are there any user-facing changes?
N/A
* GitHub Issue: #47723
Authored-by: Alina (Xi) Li <[email protected]>
Signed-off-by: David Li <[email protected]>
---
cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 19 ++-
cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt | 1 +
.../arrow/flight/sql/odbc/tests/statement_test.cc | 143 +++++++++++++++++++++
3 files changed, 161 insertions(+), 2 deletions(-)
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index 4a6b9b5061..dee5f934fb 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -1178,8 +1178,23 @@ SQLRETURN SQLNativeSql(SQLHDBC conn, SQLWCHAR*
in_statement_text,
<< ", buffer_length: " << buffer_length
<< ", out_statement_text_length: "
<< static_cast<const void*>(out_statement_text_length);
- // GH-47723 TODO: Implement SQLNativeSql
- return SQL_INVALID_HANDLE;
+
+ using ODBC::GetAttributeSQLWCHAR;
+ using ODBC::ODBCConnection;
+ using ODBC::SqlWcharToString;
+
+ return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {
+ const bool is_length_in_bytes = false;
+
+ ODBCConnection* connection = reinterpret_cast<ODBCConnection*>(conn);
+ Diagnostics& diagnostics = connection->GetDiagnostics();
+
+ std::string in_statement_str =
+ SqlWcharToString(in_statement_text, in_statement_text_length);
+
+ return GetAttributeSQLWCHAR(in_statement_str, is_length_in_bytes,
out_statement_text,
+ buffer_length, out_statement_text_length,
diagnostics);
+ });
}
SQLRETURN SQLDescribeCol(SQLHSTMT stmt, SQLUSMALLINT column_number, SQLWCHAR*
column_name,
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
index 9efae5c2a9..c614874c01 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
+++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
@@ -37,6 +37,7 @@ add_arrow_test(flight_sql_odbc_test
connection_attr_test.cc
connection_test.cc
statement_attr_test.cc
+ statement_test.cc
# Enable Protobuf cleanup after test execution
# GH-46889: move protobuf_test_util to a more common location
../../../../engine/substrait/protobuf_test_util.cc
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
new file mode 100644
index 0000000000..9d6d42c4a1
--- /dev/null
+++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
@@ -0,0 +1,143 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h"
+
+#include "arrow/flight/sql/odbc/odbc_impl/platform.h"
+
+#include <sql.h>
+#include <sqltypes.h>
+#include <sqlucode.h>
+
+#include <limits>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace arrow::flight::sql::odbc {
+
+template <typename T>
+class StatementTest : public T {};
+
+class StatementMockTest : public FlightSQLODBCMockTestBase {};
+class StatementRemoteTest : public FlightSQLODBCRemoteTestBase {};
+using TestTypes = ::testing::Types<StatementMockTest, StatementRemoteTest>;
+TYPED_TEST_SUITE(StatementTest, TestTypes);
+
+TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsInputString) {
+ SQLWCHAR buf[1024];
+ SQLINTEGER buf_char_len = sizeof(buf) / ODBC::GetSqlWCharSize();
+ SQLWCHAR input_str[] = L"SELECT * FROM mytable WHERE id == 1";
+ SQLINTEGER input_char_len = static_cast<SQLINTEGER>(wcslen(input_str));
+ SQLINTEGER output_char_len = 0;
+ std::wstring expected_string = std::wstring(input_str);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLNativeSql(this->conn, input_str, input_char_len,
buf,
+ buf_char_len, &output_char_len));
+
+ EXPECT_EQ(input_char_len, output_char_len);
+
+ // returned length is in characters
+ std::wstring returned_string(buf, buf + output_char_len);
+
+ EXPECT_EQ(expected_string, returned_string);
+}
+
+TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsNTSInputString) {
+ SQLWCHAR buf[1024];
+ SQLINTEGER buf_char_len = sizeof(buf) / ODBC::GetSqlWCharSize();
+ SQLWCHAR input_str[] = L"SELECT * FROM mytable WHERE id == 1";
+ SQLINTEGER input_char_len = static_cast<SQLINTEGER>(wcslen(input_str));
+ SQLINTEGER output_char_len = 0;
+ std::wstring expected_string = std::wstring(input_str);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLNativeSql(this->conn, input_str, SQL_NTS, buf,
buf_char_len,
+ &output_char_len));
+
+ EXPECT_EQ(input_char_len, output_char_len);
+
+ // returned length is in characters
+ std::wstring returned_string(buf, buf + output_char_len);
+
+ EXPECT_EQ(expected_string, returned_string);
+}
+
+TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsInputStringLength) {
+ SQLWCHAR input_str[] = L"SELECT * FROM mytable WHERE id == 1";
+ SQLINTEGER input_char_len = static_cast<SQLINTEGER>(wcslen(input_str));
+ SQLINTEGER output_char_len = 0;
+ std::wstring expected_string = std::wstring(input_str);
+
+ ASSERT_EQ(SQL_SUCCESS, SQLNativeSql(this->conn, input_str, input_char_len,
nullptr, 0,
+ &output_char_len));
+
+ EXPECT_EQ(input_char_len, output_char_len);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLNativeSql(this->conn, input_str, SQL_NTS, nullptr, 0,
&output_char_len));
+
+ EXPECT_EQ(input_char_len, output_char_len);
+}
+
+TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsTruncatedString) {
+ const SQLINTEGER small_buf_size_in_char = 11;
+ SQLWCHAR small_buf[small_buf_size_in_char];
+ SQLINTEGER small_buf_char_len = sizeof(small_buf) / ODBC::GetSqlWCharSize();
+ SQLWCHAR input_str[] = L"SELECT * FROM mytable WHERE id == 1";
+ SQLINTEGER input_char_len = static_cast<SQLINTEGER>(wcslen(input_str));
+ SQLINTEGER output_char_len = 0;
+
+ // Create expected return string based on buf size
+ SQLWCHAR expected_string_buf[small_buf_size_in_char];
+ wcsncpy(expected_string_buf, input_str, 10);
+ expected_string_buf[10] = L'\0';
+ std::wstring expected_string(expected_string_buf,
+ expected_string_buf + small_buf_size_in_char);
+
+ ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
+ SQLNativeSql(this->conn, input_str, input_char_len, small_buf,
+ small_buf_char_len, &output_char_len));
+ VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorState01004);
+
+ // Returned text length represents full string char length regardless of
truncation
+ EXPECT_EQ(input_char_len, output_char_len);
+
+ std::wstring returned_string(small_buf, small_buf + small_buf_char_len);
+
+ EXPECT_EQ(expected_string, returned_string);
+}
+
+TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsErrorOnBadInputs) {
+ SQLWCHAR buf[1024];
+ SQLINTEGER buf_char_len = sizeof(buf) / ODBC::GetSqlWCharSize();
+ SQLWCHAR input_str[] = L"SELECT * FROM mytable WHERE id == 1";
+ SQLINTEGER input_char_len = static_cast<SQLINTEGER>(wcslen(input_str));
+ SQLINTEGER output_char_len = 0;
+
+ ASSERT_EQ(SQL_ERROR, SQLNativeSql(this->conn, nullptr, input_char_len, buf,
+ buf_char_len, &output_char_len));
+ VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY009);
+
+ ASSERT_EQ(SQL_ERROR, SQLNativeSql(this->conn, nullptr, SQL_NTS, buf,
buf_char_len,
+ &output_char_len));
+ VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY009);
+
+ ASSERT_EQ(SQL_ERROR, SQLNativeSql(this->conn, input_str, -100, buf,
buf_char_len,
+ &output_char_len));
+ VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY090);
+}
+
+} // namespace arrow::flight::sql::odbc