From a18d579acbc8621948f46815b5ddf5cef5197ee8 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 5 Aug 2025 14:27:30 -0700 Subject: [PATCH 1/7] Initialize SQLHandle values * add test to free null handles. Without handle value initialization, segfault error was seen --- .../flight/sql/odbc/tests/connection_test.cc | 18 ++++++++++++++++++ .../flight/sql/odbc/tests/odbc_test_suite.h | 6 +++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc index 81c4abe70cfe..302a3ab8aa73 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc @@ -128,6 +128,24 @@ TEST(SQLFreeHandle, TestSQLFreeHandleConnect) { EXPECT_TRUE(return_free_handle == SQL_SUCCESS); } +TYPED_TEST(FlightSQLODBCTestBase, TestFreeNullHandles) { + // Verifies attempt to free invalid handle does not cause segfault + // Attempt to free null statement handle + SQLRETURN ret = SQLFreeHandle(SQL_HANDLE_STMT, this->stmt); + + EXPECT_EQ(ret, SQL_INVALID_HANDLE); + + // Attempt to free null connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, this->conn); + + EXPECT_EQ(ret, SQL_INVALID_HANDLE); + + // Attempt to free null environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, this->env); + + EXPECT_EQ(ret, SQL_INVALID_HANDLE); +} + TEST(SQLFreeConnect, TestSQLFreeConnect) { // ODBC Environment SQLHENV env; diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h index 960da101fd3c..dcd342a62c6e 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h @@ -66,13 +66,13 @@ class FlightSQLODBCRemoteTestBase : public ::testing::Test { std::wstring virtual getQueryAllDataTypes(); /** ODBC Environment. */ - SQLHENV env; + SQLHENV env = 0; /** ODBC Connect. */ - SQLHDBC conn; + SQLHDBC conn = 0; /** ODBC Statement. */ - SQLHSTMT stmt; + SQLHSTMT stmt = 0; protected: void SetUp() override; From d1ae5e0d388ad3006f86987dc9dc0826cee11ec6 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 5 Aug 2025 13:25:07 -0700 Subject: [PATCH 2/7] Add initial dummy test Move `SQLGetDiagField` and `SQLGetDiagRec` tests to `errors_test.cc` --- .../flight/sql/odbc/tests/CMakeLists.txt | 1 + .../flight/sql/odbc/tests/connection_test.cc | 244 ---------------- .../flight/sql/odbc/tests/errors_test.cc | 275 ++++++++++++++++++ 3 files changed, 276 insertions(+), 244 deletions(-) create mode 100644 cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt index 0262011ddaad..606b3cb7e4ab 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 columns_test.cc connection_attr_test.cc connection_info_test.cc + errors_test.cc statement_attr_test.cc statement_test.cc tables_test.cc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc index 302a3ab8aa73..c935646de5b7 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc @@ -829,250 +829,6 @@ TEST(SQLDisconnect, TestSQLDisconnectWithoutConnection) { EXPECT_EQ(ret, SQL_SUCCESS); } -TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagFieldWForConnectFailure) { - // ODBC Environment - SQLHENV env; - SQLHDBC conn; - - // Allocate an environment handle - SQLRETURN ret = SQLAllocEnv(&env); - - EXPECT_EQ(ret, SQL_SUCCESS); - - ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Allocate a connection using alloc handle - ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Invalid connect string - std::string connect_str = this->getInvalidConnectionString(); - - ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, - arrow::util::UTF8ToWideString(connect_str)); - std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); - - SQLWCHAR outstr[ODBC_BUFFER_SIZE]; - SQLSMALLINT outstrlen; - - // Connecting to ODBC server. - ret = SQLDriverConnect(conn, NULL, &connect_str0[0], - static_cast(connect_str0.size()), outstr, - ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); - - EXPECT_TRUE(ret == SQL_ERROR); - - // Retrieve all supported header level and record level data - SQLSMALLINT HEADER_LEVEL = 0; - SQLSMALLINT RECORD_1 = 1; - - // SQL_DIAG_NUMBER - SQLINTEGER diag_number; - SQLSMALLINT diag_number_length; - - ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, HEADER_LEVEL, SQL_DIAG_NUMBER, &diag_number, - sizeof(SQLINTEGER), &diag_number_length); - - EXPECT_EQ(ret, SQL_SUCCESS); - - EXPECT_EQ(diag_number, 1); - - // SQL_DIAG_SERVER_NAME - SQLWCHAR server_name[ODBC_BUFFER_SIZE]; - SQLSMALLINT server_name_length; - - ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_SERVER_NAME, server_name, - ODBC_BUFFER_SIZE, &server_name_length); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // SQL_DIAG_MESSAGE_TEXT - SQLWCHAR message_text[ODBC_BUFFER_SIZE]; - SQLSMALLINT message_text_length; - - ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_MESSAGE_TEXT, - message_text, ODBC_BUFFER_SIZE, &message_text_length); - - EXPECT_EQ(ret, SQL_SUCCESS); - - EXPECT_GT(message_text_length, 100); - - // SQL_DIAG_NATIVE - SQLINTEGER diag_native; - SQLSMALLINT diag_native_length; - - ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_NATIVE, &diag_native, - sizeof(diag_native), &diag_native_length); - - EXPECT_EQ(ret, SQL_SUCCESS); - - EXPECT_EQ(diag_native, 200); - - // SQL_DIAG_SQLSTATE - const SQLSMALLINT sql_state_size = 6; - SQLWCHAR sql_state[sql_state_size]; - SQLSMALLINT sql_state_length; - ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_SQLSTATE, sql_state, - sql_state_size * driver::odbcabstraction::GetSqlWCharSize(), - &sql_state_length); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // 28000 - EXPECT_EQ(sql_state[0], '2'); - EXPECT_EQ(sql_state[1], '8'); - EXPECT_EQ(sql_state[2], '0'); - EXPECT_EQ(sql_state[3], '0'); - EXPECT_EQ(sql_state[4], '0'); - - // Free connection handle - ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Free environment handle - ret = SQLFreeHandle(SQL_HANDLE_ENV, env); - - EXPECT_EQ(ret, SQL_SUCCESS); -} - -TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagFieldWForConnectFailureNTS) { - // Test is disabled because driver manager on Windows does not pass through SQL_NTS - // This test case can be potentially used on macOS/Linux - GTEST_SKIP(); - // ODBC Environment - SQLHENV env; - SQLHDBC conn; - - // Allocate an environment handle - SQLRETURN ret = SQLAllocEnv(&env); - - EXPECT_EQ(ret, SQL_SUCCESS); - - ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Allocate a connection using alloc handle - ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Invalid connect string - std::string connect_str = this->getInvalidConnectionString(); - - ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, - arrow::util::UTF8ToWideString(connect_str)); - std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); - - SQLWCHAR outstr[ODBC_BUFFER_SIZE]; - SQLSMALLINT outstrlen; - - // Connecting to ODBC server. - ret = SQLDriverConnect(conn, NULL, &connect_str0[0], - static_cast(connect_str0.size()), outstr, - ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); - - EXPECT_TRUE(ret == SQL_ERROR); - - // Retrieve all supported header level and record level data - SQLSMALLINT RECORD_1 = 1; - - // SQL_DIAG_MESSAGE_TEXT SQL_NTS - SQLWCHAR message_text[ODBC_BUFFER_SIZE]; - SQLSMALLINT message_text_length; - - message_text[ODBC_BUFFER_SIZE - 1] = '\0'; - - ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_MESSAGE_TEXT, - message_text, SQL_NTS, &message_text_length); - - EXPECT_EQ(ret, SQL_SUCCESS); - - EXPECT_GT(message_text_length, 100); - - // Free connection handle - ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Free environment handle - ret = SQLFreeHandle(SQL_HANDLE_ENV, env); - - EXPECT_EQ(ret, SQL_SUCCESS); -} - -TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagRecForConnectFailure) { - // ODBC Environment - SQLHENV env; - SQLHDBC conn; - - // Allocate an environment handle - SQLRETURN ret = SQLAllocEnv(&env); - - EXPECT_EQ(ret, SQL_SUCCESS); - - ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Allocate a connection using alloc handle - ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Invalid connect string - std::string connect_str = this->getInvalidConnectionString(); - - ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, - arrow::util::UTF8ToWideString(connect_str)); - std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); - - SQLWCHAR outstr[ODBC_BUFFER_SIZE]; - SQLSMALLINT outstrlen; - - // Connecting to ODBC server. - ret = SQLDriverConnect(conn, NULL, &connect_str0[0], - static_cast(connect_str0.size()), outstr, - ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); - - EXPECT_TRUE(ret == SQL_ERROR); - - SQLWCHAR sql_state[6]; - SQLINTEGER native_error; - SQLWCHAR message[ODBC_BUFFER_SIZE]; - SQLSMALLINT message_length; - - ret = SQLGetDiagRec(SQL_HANDLE_DBC, conn, 1, sql_state, &native_error, message, - ODBC_BUFFER_SIZE, &message_length); - - EXPECT_EQ(ret, SQL_SUCCESS); - - EXPECT_GT(message_length, 120); - - EXPECT_EQ(native_error, 200); - - // 28000 - EXPECT_EQ(sql_state[0], '2'); - EXPECT_EQ(sql_state[1], '8'); - EXPECT_EQ(sql_state[2], '0'); - EXPECT_EQ(sql_state[3], '0'); - EXPECT_EQ(sql_state[4], '0'); - - // Free connection handle - ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); - - EXPECT_EQ(ret, SQL_SUCCESS); - - // Free environment handle - ret = SQLFreeHandle(SQL_HANDLE_ENV, env); - - EXPECT_EQ(ret, SQL_SUCCESS); -} - TYPED_TEST(FlightSQLODBCTestBase, TestConnect) { // Verifies connect and disconnect works on its own this->connect(); diff --git a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc new file mode 100644 index 000000000000..c294b47f275d --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc @@ -0,0 +1,275 @@ +// 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" + +#ifdef _WIN32 +# include +#endif + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace arrow::flight::sql::odbc { + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagFieldWForConnectFailure) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // Allocate an environment handle + SQLRETURN ret = SQLAllocEnv(&env); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Invalid connect string + std::string connect_str = this->getInvalidConnectionString(); + + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + EXPECT_TRUE(ret == SQL_ERROR); + + // Retrieve all supported header level and record level data + SQLSMALLINT HEADER_LEVEL = 0; + SQLSMALLINT RECORD_1 = 1; + + // SQL_DIAG_NUMBER + SQLINTEGER diag_number; + SQLSMALLINT diag_number_length; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, HEADER_LEVEL, SQL_DIAG_NUMBER, &diag_number, + sizeof(SQLINTEGER), &diag_number_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(diag_number, 1); + + // SQL_DIAG_SERVER_NAME + SQLWCHAR server_name[ODBC_BUFFER_SIZE]; + SQLSMALLINT server_name_length; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_SERVER_NAME, server_name, + ODBC_BUFFER_SIZE, &server_name_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // SQL_DIAG_MESSAGE_TEXT + SQLWCHAR message_text[ODBC_BUFFER_SIZE]; + SQLSMALLINT message_text_length; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_MESSAGE_TEXT, + message_text, ODBC_BUFFER_SIZE, &message_text_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_text_length, 100); + + // SQL_DIAG_NATIVE + SQLINTEGER diag_native; + SQLSMALLINT diag_native_length; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_NATIVE, &diag_native, + sizeof(diag_native), &diag_native_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(diag_native, 200); + + // SQL_DIAG_SQLSTATE + const SQLSMALLINT sql_state_size = 6; + SQLWCHAR sql_state[sql_state_size]; + SQLSMALLINT sql_state_length; + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_SQLSTATE, sql_state, + sql_state_size * driver::odbcabstraction::GetSqlWCharSize(), + &sql_state_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // 28000 + EXPECT_EQ(sql_state[0], '2'); + EXPECT_EQ(sql_state[1], '8'); + EXPECT_EQ(sql_state[2], '0'); + EXPECT_EQ(sql_state[3], '0'); + EXPECT_EQ(sql_state[4], '0'); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagFieldWForConnectFailureNTS) { + // Test is disabled because driver manager on Windows does not pass through SQL_NTS + // This test case can be potentially used on macOS/Linux + GTEST_SKIP(); + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // Allocate an environment handle + SQLRETURN ret = SQLAllocEnv(&env); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Invalid connect string + std::string connect_str = this->getInvalidConnectionString(); + + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + EXPECT_TRUE(ret == SQL_ERROR); + + // Retrieve all supported header level and record level data + SQLSMALLINT RECORD_1 = 1; + + // SQL_DIAG_MESSAGE_TEXT SQL_NTS + SQLWCHAR message_text[ODBC_BUFFER_SIZE]; + SQLSMALLINT message_text_length; + + message_text[ODBC_BUFFER_SIZE - 1] = '\0'; + + ret = SQLGetDiagField(SQL_HANDLE_DBC, conn, RECORD_1, SQL_DIAG_MESSAGE_TEXT, + message_text, SQL_NTS, &message_text_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_text_length, 100); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagRecForConnectFailure) { + // ODBC Environment + SQLHENV env; + SQLHDBC conn; + + // Allocate an environment handle + SQLRETURN ret = SQLAllocEnv(&env); + + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Allocate a connection using alloc handle + ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Invalid connect string + std::string connect_str = this->getInvalidConnectionString(); + + ASSERT_OK_AND_ASSIGN(std::wstring wconnect_str, + arrow::util::UTF8ToWideString(connect_str)); + std::vector connect_str0(wconnect_str.begin(), wconnect_str.end()); + + SQLWCHAR outstr[ODBC_BUFFER_SIZE]; + SQLSMALLINT outstrlen; + + // Connecting to ODBC server. + ret = SQLDriverConnect(conn, NULL, &connect_str0[0], + static_cast(connect_str0.size()), outstr, + ODBC_BUFFER_SIZE, &outstrlen, SQL_DRIVER_NOPROMPT); + + EXPECT_TRUE(ret == SQL_ERROR); + + SQLWCHAR sql_state[6]; + SQLINTEGER native_error; + SQLWCHAR message[ODBC_BUFFER_SIZE]; + SQLSMALLINT message_length; + + ret = SQLGetDiagRec(SQL_HANDLE_DBC, conn, 1, sql_state, &native_error, message, + ODBC_BUFFER_SIZE, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 120); + + EXPECT_EQ(native_error, 200); + + // 28000 + EXPECT_EQ(sql_state[0], '2'); + EXPECT_EQ(sql_state[1], '8'); + EXPECT_EQ(sql_state[2], '0'); + EXPECT_EQ(sql_state[3], '0'); + EXPECT_EQ(sql_state[4], '0'); + + // Free connection handle + ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); + + EXPECT_EQ(ret, SQL_SUCCESS); + + // Free environment handle + ret = SQLFreeHandle(SQL_HANDLE_ENV, env); + + EXPECT_EQ(ret, SQL_SUCCESS); +} + +} // namespace arrow::flight::sql::odbc From 8bb97463d4c63f9c7cae27c0f08844a7013d42a0 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Tue, 5 Aug 2025 14:22:09 -0700 Subject: [PATCH 3/7] in-progress SQLError test * Update SQLGetDiagRec and SQLGetDiagField tests also --- .../flight/sql/odbc/tests/errors_test.cc | 81 ++++++++++++++++--- 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc index c294b47f275d..51fad61411c1 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc @@ -120,12 +120,7 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagFieldWForConnectFailure) { EXPECT_EQ(ret, SQL_SUCCESS); - // 28000 - EXPECT_EQ(sql_state[0], '2'); - EXPECT_EQ(sql_state[1], '8'); - EXPECT_EQ(sql_state[2], '0'); - EXPECT_EQ(sql_state[3], '0'); - EXPECT_EQ(sql_state[4], '0'); + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"28000")); // Free connection handle ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); @@ -254,12 +249,9 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagRecForConnectFailure) { EXPECT_EQ(native_error, 200); - // 28000 - EXPECT_EQ(sql_state[0], '2'); - EXPECT_EQ(sql_state[1], '8'); - EXPECT_EQ(sql_state[2], '0'); - EXPECT_EQ(sql_state[3], '0'); - EXPECT_EQ(sql_state[4], '0'); + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"28000")); + + EXPECT_TRUE(!std::wstring(message).empty()); // Free connection handle ret = SQLFreeHandle(SQL_HANDLE_DBC, conn); @@ -272,4 +264,69 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagRecForConnectFailure) { EXPECT_EQ(ret, SQL_SUCCESS); } +// -AL- todo check for returns. Separate input tests from actual tests with values. +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorInputData) { + // Test ODBC 2.0 API SQLError. Driver manager maps SQLError to SQLGetDiagRec. + // SQLError does not post diagnostic records for itself. + this->connect(); + + SQLRETURN ret = SQLError(this->env, 0, 0, 0, 0, 0, 0, 0); + + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLError(0, this->conn, 0, 0, 0, 0, 0, 0); + + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLError(0, 0, this->stmt, 0, 0, 0, 0, 0); + + EXPECT_EQ(ret, SQL_NO_DATA); + + ret = SQLError(0, 0, 0, 0, 0, 0, 0, 0); + + EXPECT_EQ(ret, SQL_INVALID_HANDLE); + + this->disconnect(); +} + +// -AL- todo: +// add an [ ] 1) env error -> wrong env attribute? - see TestSQLSetEnvAttrODBCVersionInvalid +// [ ] 2) conn error -> set unsettable conn attr SQLSetConnectAttr? - see TestSQLSetConnectAttrAsyncDbcEventUnsupported +// [x] 3) stmt error. + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorStmtError) { + // Test ODBC 2.0 API SQLError. + // Known Windows Driver Manager (DM) behavior: + // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), + // DM passes 512 as buffer length to SQLError. + this->connect(); + + std::wstring wsql = L"1"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + + EXPECT_EQ(ret, SQL_ERROR); + + SQLWCHAR sql_state[6] = {0}; + SQLINTEGER native_error = 0; + SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; + SQLSMALLINT message_length = 0; + ret = SQLError(0, 0, this->stmt, sql_state, &native_error, message, + SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 70); + + EXPECT_EQ(native_error, 100); + + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"HY000")); + + EXPECT_TRUE(!std::wstring(message).empty()); + + this->disconnect(); +} + } // namespace arrow::flight::sql::odbc From 0a262e791101239edcfeca3d9e30489dc5997422 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 6 Aug 2025 10:22:06 -0700 Subject: [PATCH 4/7] Add `TestSQLErrorConnError` and `TestSQLErrorEnvError` --- .../flight/sql/odbc/tests/errors_test.cc | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc index 51fad61411c1..5b2b99fe8819 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc @@ -264,7 +264,6 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagRecForConnectFailure) { EXPECT_EQ(ret, SQL_SUCCESS); } -// -AL- todo check for returns. Separate input tests from actual tests with values. TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorInputData) { // Test ODBC 2.0 API SQLError. Driver manager maps SQLError to SQLGetDiagRec. // SQLError does not post diagnostic records for itself. @@ -289,10 +288,72 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorInputData) { this->disconnect(); } -// -AL- todo: -// add an [ ] 1) env error -> wrong env attribute? - see TestSQLSetEnvAttrODBCVersionInvalid -// [ ] 2) conn error -> set unsettable conn attr SQLSetConnectAttr? - see TestSQLSetConnectAttrAsyncDbcEventUnsupported -// [x] 3) stmt error. +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorEnvError) { + // Test ODBC 2.0 API SQLError. + // Known Windows Driver Manager (DM) behavior: + // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), + // DM passes 512 as buffer length to SQLError. + this->connect(); + + // Attempt to set environment attribute after connection handle allocation + SQLRETURN ret = SQLSetEnvAttr(this->env, SQL_ATTR_ODBC_VERSION, + reinterpret_cast(SQL_OV_ODBC2), 0); + + EXPECT_EQ(ret, SQL_ERROR); + + SQLWCHAR sql_state[6] = {0}; + SQLINTEGER native_error = 0; + SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; + SQLSMALLINT message_length = 0; + ret = SQLError(this->env, 0, 0, sql_state, &native_error, message, + SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 50); + + EXPECT_EQ(native_error, 0); + + // Function sequence error state from driver manager + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"HY010")); + + EXPECT_TRUE(!std::wstring(message).empty()); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorConnError) { + // Test ODBC 2.0 API SQLError. + // Known Windows Driver Manager (DM) behavior: + // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), + // DM passes 512 as buffer length to SQLError. + this->connect(); + + // Attempt to set unsettable attribute + SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_EVENT, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + + SQLWCHAR sql_state[6] = {0}; + SQLINTEGER native_error = 0; + SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; + SQLSMALLINT message_length = 0; + ret = SQLError(0, this->conn, 0, sql_state, &native_error, message, + SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 70); + + EXPECT_EQ(native_error, 0); + + // HY118 returned from driver manager + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"HY118")); + + EXPECT_TRUE(!std::wstring(message).empty()); + + this->disconnect(); +} TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorStmtError) { // Test ODBC 2.0 API SQLError. From f88d276a08cd21b1c76030c4244b91e4fe7b699e Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Wed, 6 Aug 2025 10:55:54 -0700 Subject: [PATCH 5/7] Add `TestSQLGetDiagRecInputData` --- .../flight/sql/odbc/tests/errors_test.cc | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc index 5b2b99fe8819..2a17e78c61dc 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc @@ -264,11 +264,40 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagRecForConnectFailure) { EXPECT_EQ(ret, SQL_SUCCESS); } +TYPED_TEST(FlightSQLODBCTestBase, TestSQLGetDiagRecInputData) { + // SQLGetDiagRec does not post diagnostic records for itself. + this->connect(); + + SQLWCHAR sql_state[6]; + SQLINTEGER native_error; + SQLWCHAR message[ODBC_BUFFER_SIZE]; + SQLSMALLINT message_length; + + // Pass invalid record number + SQLRETURN ret = SQLGetDiagRec(SQL_HANDLE_DBC, this->conn, 0, sql_state, &native_error, + message, ODBC_BUFFER_SIZE, &message_length); + + EXPECT_EQ(ret, SQL_ERROR); + + // Pass valid record number with null inputs + ret = SQLGetDiagRec(SQL_HANDLE_DBC, this->conn, 1, 0, 0, 0, 0, 0); + + EXPECT_EQ(ret, SQL_NO_DATA); + + // Invalid handle + ret = SQLGetDiagRec(0, 0, 0, 0, 0, 0, 0, 0); + + EXPECT_EQ(ret, SQL_INVALID_HANDLE); + + this->disconnect(); +} + TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorInputData) { // Test ODBC 2.0 API SQLError. Driver manager maps SQLError to SQLGetDiagRec. // SQLError does not post diagnostic records for itself. this->connect(); + // Pass valid handles with null inputs SQLRETURN ret = SQLError(this->env, 0, 0, 0, 0, 0, 0, 0); EXPECT_EQ(ret, SQL_NO_DATA); @@ -281,6 +310,7 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorInputData) { EXPECT_EQ(ret, SQL_NO_DATA); + // Invalid handle ret = SQLError(0, 0, 0, 0, 0, 0, 0, 0); EXPECT_EQ(ret, SQL_INVALID_HANDLE); From 5d391b42c06bd078d0a06808027f36011b75167a Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 7 Aug 2025 15:55:29 -0700 Subject: [PATCH 6/7] Address comments from James * Add ODBC Ver 2 tests --- .../flight/sql/odbc/tests/errors_test.cc | 117 +++++++++++++++++- 1 file changed, 111 insertions(+), 6 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc index 2a17e78c61dc..b24f8a1a133b 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc @@ -359,8 +359,8 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorConnError) { // DM passes 512 as buffer length to SQLError. this->connect(); - // Attempt to set unsettable attribute - SQLRETURN ret = SQLSetConnectAttr(this->conn, SQL_ATTR_ASYNC_DBC_EVENT, 0, 0); + // Attempt to set unsupported attribute + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, 0, 0, 0); EXPECT_EQ(ret, SQL_ERROR); @@ -373,12 +373,12 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorConnError) { EXPECT_EQ(ret, SQL_SUCCESS); - EXPECT_GT(message_length, 70); + EXPECT_GT(message_length, 60); - EXPECT_EQ(native_error, 0); + EXPECT_EQ(native_error, 100); - // HY118 returned from driver manager - EXPECT_EQ(std::wstring(sql_state), std::wstring(L"HY118")); + // optional feature not supported error state + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"HYC00")); EXPECT_TRUE(!std::wstring(message).empty()); @@ -420,4 +420,109 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorStmtError) { this->disconnect(); } + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorEnvErrorODBCVer2) { + // Test ODBC 2.0 API SQLError with ODBC ver 2. + // Known Windows Driver Manager (DM) behavior: + // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), + // DM passes 512 as buffer length to SQLError. + this->connect(SQL_OV_ODBC2); + + // Attempt to set environment attribute after connection handle allocation + SQLRETURN ret = SQLSetEnvAttr(this->env, SQL_ATTR_ODBC_VERSION, + reinterpret_cast(SQL_OV_ODBC2), 0); + + EXPECT_EQ(ret, SQL_ERROR); + + SQLWCHAR sql_state[6] = {0}; + SQLINTEGER native_error = 0; + SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; + SQLSMALLINT message_length = 0; + ret = SQLError(this->env, 0, 0, sql_state, &native_error, message, + SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 50); + + EXPECT_EQ(native_error, 0); + + // Function sequence error state from driver manager + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"S1010")); + + EXPECT_TRUE(!std::wstring(message).empty()); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorConnErrorODBCVer2) { + // Test ODBC 2.0 API SQLError with ODBC ver 2. + // Known Windows Driver Manager (DM) behavior: + // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), + // DM passes 512 as buffer length to SQLError. + this->connect(SQL_OV_ODBC2); + + // Attempt to set unsupported attribute + SQLRETURN ret = SQLGetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, 0, 0, 0); + + EXPECT_EQ(ret, SQL_ERROR); + + SQLWCHAR sql_state[6] = {0}; + SQLINTEGER native_error = 0; + SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; + SQLSMALLINT message_length = 0; + ret = SQLError(0, this->conn, 0, sql_state, &native_error, message, + SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 60); + + EXPECT_EQ(native_error, 100); + + // optional feature not supported error state. Driver Manager maps state to S1C00 + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"S1C00")); + + EXPECT_TRUE(!std::wstring(message).empty()); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorStmtErrorODBCVer2) { + // Test ODBC 2.0 API SQLError with ODBC ver 2. + // Known Windows Driver Manager (DM) behavior: + // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), + // DM passes 512 as buffer length to SQLError. + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"1"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + + EXPECT_EQ(ret, SQL_ERROR); + + SQLWCHAR sql_state[6] = {0}; + SQLINTEGER native_error = 0; + SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; + SQLSMALLINT message_length = 0; + ret = SQLError(0, 0, this->stmt, sql_state, &native_error, message, + SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 70); + + EXPECT_EQ(native_error, 100); + + // Driver Manager maps error state to S1000 + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"S1000")); + + EXPECT_TRUE(!std::wstring(message).empty()); + + this->disconnect(); +} + + } // namespace arrow::flight::sql::odbc From 97e3c2d6d6de0ce421fcd86ffdbd44afdde384d3 Mon Sep 17 00:00:00 2001 From: "Alina (Xi) Li" Date: Thu, 7 Aug 2025 17:57:09 -0700 Subject: [PATCH 7/7] Address comments from James * update test name to indicate if error handling is from driver manager. * add tests for warnings. * fix lint errors. * remove SQL_ATTR_APP_ROW_DESC that is not applicable to Env Attr. --- cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 6 +- .../flight/sql/odbc/tests/errors_test.cc | 86 ++++++++++++++++++- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index 0331306e31d8..e4a5148f57d0 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -617,8 +617,7 @@ SQLRETURN SQLGetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER valuePtr, return SQL_SUCCESS; } - case SQL_ATTR_CONNECTION_POOLING: - case SQL_ATTR_APP_ROW_DESC: { + case SQL_ATTR_CONNECTION_POOLING: { throw DriverException("Optional feature not supported.", "HYC00"); } @@ -669,8 +668,7 @@ SQLRETURN SQLSetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER valuePtr, } } - case SQL_ATTR_CONNECTION_POOLING: - case SQL_ATTR_APP_ROW_DESC: { + case SQL_ATTR_CONNECTION_POOLING: { throw DriverException("Optional feature not supported.", "HYC00"); } diff --git a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc index b24f8a1a133b..ee0a2846194d 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc @@ -318,7 +318,7 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorInputData) { this->disconnect(); } -TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorEnvError) { +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorEnvErrorFromDriverManager) { // Test ODBC 2.0 API SQLError. // Known Windows Driver Manager (DM) behavior: // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), @@ -420,8 +420,49 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorStmtError) { this->disconnect(); } +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorStmtWarning) { + // Test ODBC 2.0 API SQLError. + this->connect(); + + std::wstring wsql = L"SELECT 'VERY LONG STRING here' AS string_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + const int len = 17; + SQLCHAR char_val[len]; + SQLLEN buf_len = sizeof(SQLCHAR) * len; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + + SQLWCHAR sql_state[6] = {0}; + SQLINTEGER native_error = 0; + SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; + SQLSMALLINT message_length = 0; + ret = SQLError(0, 0, this->stmt, sql_state, &native_error, message, + SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 50); + + EXPECT_EQ(native_error, 1000100); + + // Verify string truncation warning is reported + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"01004")); + + EXPECT_TRUE(!std::wstring(message).empty()); +} -TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorEnvErrorODBCVer2) { +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorEnvErrorODBCVer2FromDriverManager) { // Test ODBC 2.0 API SQLError with ODBC ver 2. // Known Windows Driver Manager (DM) behavior: // When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512), @@ -524,5 +565,46 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorStmtErrorODBCVer2) { this->disconnect(); } +TYPED_TEST(FlightSQLODBCTestBase, TestSQLErrorStmtWarningODBCVer2) { + // Test ODBC 2.0 API SQLError. + this->connect(SQL_OV_ODBC2); + + std::wstring wsql = L"SELECT 'VERY LONG STRING here' AS string_col;"; + std::vector sql0(wsql.begin(), wsql.end()); + + SQLRETURN ret = + SQLExecDirect(this->stmt, &sql0[0], static_cast(sql0.size())); + EXPECT_EQ(ret, SQL_SUCCESS); + + ret = SQLFetch(this->stmt); + EXPECT_EQ(ret, SQL_SUCCESS); + + const int len = 17; + SQLCHAR char_val[len]; + SQLLEN buf_len = sizeof(SQLCHAR) * len; + SQLLEN ind; + + ret = SQLGetData(this->stmt, 1, SQL_C_CHAR, &char_val, buf_len, &ind); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + + SQLWCHAR sql_state[6] = {0}; + SQLINTEGER native_error = 0; + SQLWCHAR message[SQL_MAX_MESSAGE_LENGTH] = {0}; + SQLSMALLINT message_length = 0; + ret = SQLError(0, 0, this->stmt, sql_state, &native_error, message, + SQL_MAX_MESSAGE_LENGTH, &message_length); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_GT(message_length, 50); + + EXPECT_EQ(native_error, 1000100); + + // Verify string truncation warning is reported + EXPECT_EQ(std::wstring(sql_state), std::wstring(L"01004")); + + EXPECT_TRUE(!std::wstring(message).empty()); +} } // namespace arrow::flight::sql::odbc