diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index 01780f0efe2..629c5eb29c8 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -39,9 +39,6 @@ SQLRETURN SQLAllocHandle(SQLSMALLINT type, SQLHANDLE parent, SQLHANDLE* result) // GH-47706 TODO: Add tests for SQLAllocStmt, pre-requisite requires // SQLDriverConnect implementation - // GH-47707 TODO: Add tests for SQL_HANDLE_DESC implementation for - // descriptor handle, pre-requisite requires SQLAllocStmt - *result = nullptr; switch (type) { @@ -142,8 +139,6 @@ SQLRETURN SQLFreeHandle(SQLSMALLINT type, SQLHANDLE handle) { // GH-47706 TODO: Add tests for SQLFreeStmt, pre-requisite requires // SQLAllocStmt tests - // GH-47707 TODO: Add tests for SQL_HANDLE_DESC implementation for - // descriptor handle switch (type) { case SQL_HANDLE_ENV: { using ODBC::ODBCEnvironment; diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h index 8e128db1bda..37f3691f329 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h @@ -69,6 +69,9 @@ class ODBCStatement : public ODBCHandle { void SetStmtAttr(SQLINTEGER statement_attribute, SQLPOINTER value, SQLINTEGER buffer_size, bool is_unicode); + /// \brief Revert back to implicitly allocated internal descriptors. + /// isApd as True indicates APD descritor is to be reverted. + /// isApd as False indicates ARD descritor is to be reverted. void RevertAppDescriptor(bool is_apd); inline ODBCDescriptor* GetIRD() { return ird_.get(); } diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt index 4bc240637e7..4aff500353a 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt +++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt @@ -35,6 +35,8 @@ add_arrow_test(flight_sql_odbc_test odbc_test_suite.cc odbc_test_suite.h connection_test.cc + connection_info_test.cc + errors_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/connection_info_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc new file mode 100644 index 00000000000..a1e2bed3e72 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_info_test.cc @@ -0,0 +1,51 @@ +// 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 +#include +#include + +#include + +namespace arrow::flight::sql::odbc { + +template +class ConnectionInfoTest : public T {}; + +class ConnectionInfoMockTest : public FlightSQLODBCMockTestBase {}; +using TestTypes = ::testing::Types; +TYPED_TEST_SUITE(ConnectionInfoTest, TestTypes); +// These information types are implemented by the Driver Manager alone. +TYPED_TEST(ConnectionInfoTest, TestSQLGetInfoDriverHdesc) { + SQLHDESC descriptor; + + // Allocate a descriptor using alloc handle + ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn, &descriptor)); + + // Value returned from driver manager is the desc address + SQLHDESC local_desc = descriptor; + EXPECT_EQ(SQL_SUCCESS, SQLGetInfo(this->conn, SQL_HANDLE_DESC, &local_desc, 0, 0)); + EXPECT_GT(local_desc, static_cast(0)); + + // Free descriptor handle + ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, descriptor)); +} + +} // namespace arrow::flight::sql::odbc diff --git a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc index c5646b42bef..fd5622808fa 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc @@ -220,4 +220,71 @@ TEST(SQLSetEnvAttr, TestSQLSetEnvAttrNullValuePointer) { ASSERT_EQ(SQL_SUCCESS, SQLFreeEnv(env)); } +TYPED_TEST(ConnectionTest, TestSQLAllocFreeDesc) { + SQLHDESC descriptor; + + // Allocate a descriptor using alloc handle + ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn, &descriptor)); + + // Free descriptor handle + ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, descriptor)); +} + +TYPED_TEST(ConnectionTest, TestSQLSetStmtAttrDescriptor) { + SQLHDESC apd_descriptor, ard_descriptor; + + // Allocate an APD descriptor using alloc handle + ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn, &apd_descriptor)); + + // Allocate an ARD descriptor using alloc handle + ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn, &ard_descriptor)); + + // Save implicitly allocated internal APD and ARD descriptor pointers + SQLPOINTER internal_apd, internal_ard = nullptr; + + EXPECT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, + &internal_apd, sizeof(internal_apd), 0)); + + EXPECT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, &internal_ard, + sizeof(internal_ard), 0)); + + // Set APD descriptor to explicitly allocated handle + EXPECT_EQ(SQL_SUCCESS, SQLSetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, + reinterpret_cast(apd_descriptor), 0)); + + // Set ARD descriptor to explicitly allocated handle + EXPECT_EQ(SQL_SUCCESS, SQLSetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, + reinterpret_cast(ard_descriptor), 0)); + + // Verify APD and ARD descriptors are set to explicitly allocated pointers + SQLPOINTER value = nullptr; + EXPECT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, &value, + sizeof(value), 0)); + + EXPECT_EQ(apd_descriptor, value); + + EXPECT_EQ(SQL_SUCCESS, + SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, &value, sizeof(value), 0)); + + EXPECT_EQ(ard_descriptor, value); + + // Free explicitly allocated APD and ARD descriptor handles + ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, apd_descriptor)); + + ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, ard_descriptor)); + + // Verify APD and ARD descriptors has been reverted to implicit descriptors + value = nullptr; + + EXPECT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC, &value, + sizeof(value), 0)); + + EXPECT_EQ(internal_apd, value); + + EXPECT_EQ(SQL_SUCCESS, + SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, &value, sizeof(value), 0)); + + EXPECT_EQ(internal_ard, value); +} + } // namespace arrow::flight::sql::odbc 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 00000000000..2dba6d1cdb9 --- /dev/null +++ b/cpp/src/arrow/flight/sql/odbc/tests/errors_test.cc @@ -0,0 +1,147 @@ +// 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 +#include +#include + +#include + +namespace arrow::flight::sql::odbc { + +template +class ErrorsTest : public T {}; + +using TestTypes = + ::testing::Types; +TYPED_TEST_SUITE(ErrorsTest, TestTypes); + +template +class ErrorsOdbcV2Test : public T {}; + +using TestTypesOdbcV2 = + ::testing::Types; +TYPED_TEST_SUITE(ErrorsOdbcV2Test, TestTypesOdbcV2); + +template +class ErrorsHandleTest : public T {}; + +using TestTypesHandle = + ::testing::Types; +TYPED_TEST_SUITE(ErrorsHandleTest, TestTypesHandle); + +TYPED_TEST(ErrorsTest, TestSQLGetDiagFieldWForDescriptorFailureFromDriverManager) { + SQLHDESC descriptor; + + // Allocate a descriptor using alloc handle + ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn, &descriptor)); + + EXPECT_EQ(SQL_ERROR, + SQLGetDescField(descriptor, 1, SQL_DESC_DATETIME_INTERVAL_CODE, 0, 0, 0)); + + // 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; + + EXPECT_EQ(SQL_SUCCESS, + SQLGetDiagField(SQL_HANDLE_DESC, descriptor, HEADER_LEVEL, SQL_DIAG_NUMBER, + &diag_number, sizeof(SQLINTEGER), &diag_number_length)); + + EXPECT_EQ(1, diag_number); + + // SQL_DIAG_SERVER_NAME + SQLWCHAR server_name[kOdbcBufferSize]; + SQLSMALLINT server_name_length; + + EXPECT_EQ(SQL_SUCCESS, + SQLGetDiagField(SQL_HANDLE_DESC, descriptor, RECORD_1, SQL_DIAG_SERVER_NAME, + server_name, kOdbcBufferSize, &server_name_length)); + + // SQL_DIAG_MESSAGE_TEXT + SQLWCHAR message_text[kOdbcBufferSize]; + SQLSMALLINT message_text_length; + + EXPECT_EQ(SQL_SUCCESS, + SQLGetDiagField(SQL_HANDLE_DESC, descriptor, RECORD_1, SQL_DIAG_MESSAGE_TEXT, + message_text, kOdbcBufferSize, &message_text_length)); + + EXPECT_GT(message_text_length, 100); + + // SQL_DIAG_NATIVE + SQLINTEGER diag_native; + SQLSMALLINT diag_native_length; + + EXPECT_EQ(SQL_SUCCESS, + SQLGetDiagField(SQL_HANDLE_DESC, descriptor, RECORD_1, SQL_DIAG_NATIVE, + &diag_native, sizeof(diag_native), &diag_native_length)); + + EXPECT_EQ(0, diag_native); + + // SQL_DIAG_SQLSTATE + const SQLSMALLINT sql_state_size = 6; + SQLWCHAR sql_state[sql_state_size]; + SQLSMALLINT sql_state_length; + EXPECT_EQ( + SQL_SUCCESS, + SQLGetDiagField(SQL_HANDLE_DESC, descriptor, RECORD_1, SQL_DIAG_SQLSTATE, sql_state, + sql_state_size * GetSqlWCharSize(), &sql_state_length)); + + EXPECT_EQ(std::wstring(L"IM001"), std::wstring(sql_state)); + + // Free descriptor handle + EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, descriptor)); +} + +TYPED_TEST(ErrorsTest, TestSQLGetDiagRecForDescriptorFailureFromDriverManager) { + SQLHDESC descriptor; + + // Allocate a descriptor using alloc handle + ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn, &descriptor)); + + EXPECT_EQ(SQL_ERROR, + SQLGetDescField(descriptor, 1, SQL_DESC_DATETIME_INTERVAL_CODE, 0, 0, 0)); + + SQLWCHAR sql_state[6]; + SQLINTEGER native_error; + SQLWCHAR message[kOdbcBufferSize]; + SQLSMALLINT message_length; + + ASSERT_EQ(SQL_SUCCESS, + SQLGetDiagRec(SQL_HANDLE_DESC, descriptor, 1, sql_state, &native_error, + message, kOdbcBufferSize, &message_length)); + + EXPECT_GT(message_length, 60); + + EXPECT_EQ(0, native_error); + + // API not implemented error from driver manager + EXPECT_EQ(std::wstring(L"IM001"), std::wstring(sql_state)); + + EXPECT_TRUE(!std::wstring(message).empty()); + + // Free descriptor handle + EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, descriptor)); +} + +} // namespace arrow::flight::sql::odbc