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 402a71cb4f6 GH-51341: [C++][FlightRPC][ODBC] Fix statement descriptor
and cursor lifecycle (#51342)
402a71cb4f6 is described below
commit 402a71cb4f6780fba1247df6c59f7f34e31b8ddc
Author: Vikrant Puppala <[email protected]>
AuthorDate: Fri Oct 16 06:47:43 2026 +0530
GH-51341: [C++][FlightRPC][ODBC] Fix statement descriptor and cursor
lifecycle (#51342)
### Rationale for this change
The Flight SQL ODBC statement implementation had a cluster of related
descriptor and cursor lifecycle defects:
- the implicit application row descriptor (ARD) was initialized from the
application parameter descriptor (APD), aliasing two descriptor roles;
- `SQL_NULL_HANDLE` did not restore built-in application descriptors;
- freeing a statement left dangling registrations in explicit descriptors;
- `SQLFetch` and `SQLGetData` without an open cursor could dereference a
null result or report the wrong diagnostic; and
- `SQLMoreResults` did not close the current cursor when returning
`SQL_NO_DATA`.
### What changes are included in this PR?
- Initialize the active ARD from `built_in_ard_`.
- Map null APD/ARD assignments to the corresponding built-in descriptor and
detach the outgoing explicit descriptor.
- Detach explicit descriptors before releasing a statement.
- Return SQLSTATE `24000` from `SQLFetch` and `SQLGetData` when no cursor
is open.
- Close the current cursor when `SQLMoreResults` returns `SQL_NO_DATA`.
### Are there any user-facing changes?
Yes. Spec-valid descriptor release and reset sequences no longer retain
dangling state, invalid cursor operations return SQLSTATE `24000`, and
`SQLMoreResults` closes the current cursor as required.
### How was this patch tested?
- Added focused mock/remote typed ODBC regression tests.
Closes #51341.
* GitHub Issue: #51341
Authored-by: Vikrant Puppala <[email protected]>
Signed-off-by: David Li <[email protected]>
---
.../flight/sql/odbc/odbc_impl/odbc_statement.cc | 29 +++-
cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt | 1 +
.../arrow/flight/sql/odbc/tests/connection_test.cc | 43 +++++-
.../sql/odbc/tests/statement_driver_api_test.cc | 167 +++++++++++++++++++++
.../arrow/flight/sql/odbc/tests/statement_test.cc | 60 ++++++++
5 files changed, 292 insertions(+), 8 deletions(-)
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 43a3a95b8f1..3f437945ec8 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
@@ -241,7 +241,7 @@ ODBCStatement::ODBCStatement(ODBCConnection& connection,
ird_(std::make_shared<ODBCDescriptor>(spi_statement_->GetDiagnostics(),
nullptr,
this, false, false,
connection.IsOdbc2Connection())),
- current_ard_(built_in_apd_.get()),
+ current_ard_(built_in_ard_.get()),
current_apd_(built_in_apd_.get()),
row_number_(0),
max_rows_(0),
@@ -323,6 +323,10 @@ void ODBCStatement::ExecuteDirect(const std::string&
query) {
bool ODBCStatement::Fetch(size_t rows, SQLULEN* row_count_ptr,
SQLUSMALLINT* row_status_array) {
+ if (!current_result_) {
+ throw DriverException("Invalid cursor state", "24000");
+ }
+
if (has_reached_end_of_result_) {
ird_->SetRowsProcessed(0);
return false;
@@ -558,8 +562,9 @@ void ODBCStatement::SetStmtAttr(SQLINTEGER
statement_attribute, SQLPOINTER value
switch (statement_attribute) {
case SQL_ATTR_APP_PARAM_DESC: {
- ODBCDescriptor* desc = static_cast<ODBCDescriptor*>(value);
- if (desc && current_apd_ != desc) {
+ ODBCDescriptor* desc =
+ value ? static_cast<ODBCDescriptor*>(value) : built_in_apd_.get();
+ if (current_apd_ != desc) {
if (current_apd_ != built_in_apd_.get()) {
current_apd_->DetachFromStatement(this, true);
}
@@ -571,12 +576,14 @@ void ODBCStatement::SetStmtAttr(SQLINTEGER
statement_attribute, SQLPOINTER value
return;
}
case SQL_ATTR_APP_ROW_DESC: {
- ODBCDescriptor* desc = static_cast<ODBCDescriptor*>(value);
- if (desc && current_ard_ != desc) {
+ ODBCDescriptor* desc =
+ value ? static_cast<ODBCDescriptor*>(value) : built_in_ard_.get();
+ if (current_ard_ != desc) {
if (current_ard_ != built_in_ard_.get()) {
current_ard_->DetachFromStatement(this, false);
}
current_ard_ = desc;
+ current_ard_->NotifyBindingsHaveChanged();
if (current_ard_ != built_in_ard_.get()) {
desc->RegisterToStatement(this, false);
}
@@ -718,6 +725,7 @@ void ODBCStatement::RevertAppDescriptor(bool isApd) {
current_apd_ = built_in_apd_.get();
} else {
current_ard_ = built_in_ard_.get();
+ current_ard_->NotifyBindingsHaveChanged();
}
}
@@ -740,6 +748,10 @@ void ODBCStatement::CloseCursor(bool suppress_errors) {
SQLRETURN ODBCStatement::GetData(SQLSMALLINT record_number, SQLSMALLINT c_type,
SQLPOINTER data_ptr, SQLLEN buffer_length,
SQLLEN* indicator_ptr) {
+ if (!current_result_) {
+ throw DriverException("Invalid cursor state", "24000");
+ }
+
if (record_number == 0) {
throw DriverException("Bookmarks are not supported", "07009");
} else if (static_cast<size_t>(record_number) > ird_->GetRecords().size()) {
@@ -785,6 +797,7 @@ SQLRETURN ODBCStatement::GetData(SQLSMALLINT record_number,
SQLSMALLINT c_type,
SQLRETURN ODBCStatement::GetMoreResults() {
// Multiple result sets are not supported by Arrow protocol.
+ CloseCursor(/*suppress_errors=*/true);
return SQL_NO_DATA;
}
@@ -811,6 +824,12 @@ void ODBCStatement::GetRowCount(SQLLEN* row_count_ptr) {
void ODBCStatement::ReleaseStatement() {
CloseCursor(true);
+ if (current_apd_ != built_in_apd_.get()) {
+ current_apd_->DetachFromStatement(this, true);
+ }
+ if (current_ard_ != built_in_ard_.get()) {
+ current_ard_->DetachFromStatement(this, false);
+ }
connection_.DropStatement(this);
}
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
index 81f8ec891be..f3c345e2449 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
+++ b/cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt
@@ -35,6 +35,7 @@ set(ARROW_FLIGHT_SQL_ODBC_TEST_SRCS
errors_test.cc
get_functions_test.cc
statement_attr_test.cc
+ statement_driver_api_test.cc
statement_test.cc
tables_test.cc
type_info_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 837c18c9cbd..05c0348fbe0 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/connection_test.cc
@@ -528,6 +528,7 @@ TYPED_TEST(ConnectionTest, TestSQLSetStmtAttrDescriptor) {
EXPECT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC,
&internal_ard,
sizeof(internal_ard), 0));
+ EXPECT_NE(internal_apd, internal_ard);
// Set APD descriptor to explicitly allocated handle
EXPECT_EQ(SQL_SUCCESS, SQLSetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC,
@@ -549,12 +550,31 @@ TYPED_TEST(ConnectionTest, TestSQLSetStmtAttrDescriptor) {
EXPECT_EQ(ard_descriptor, value);
- // Free explicitly allocated APD and ARD descriptor handles
- ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, apd_descriptor));
+ // Assign replacement descriptors. Freeing the old descriptors must not
revert these
+ // replacements, which proves switching descriptors detached the old
handles. Null
+ // descriptor restoration is covered through the direct driver API because
some driver
+ // managers intercept it.
+ SQLHDESC replacement_apd, replacement_ard;
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn,
&replacement_apd));
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn,
&replacement_ard));
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLSetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC,
replacement_apd, 0));
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLSetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, replacement_ard,
0));
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, apd_descriptor));
ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, ard_descriptor));
+ EXPECT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC,
&value,
+ sizeof(value), 0));
+ EXPECT_EQ(replacement_apd, value);
+ EXPECT_EQ(SQL_SUCCESS,
+ SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, &value,
sizeof(value), 0));
+ EXPECT_EQ(replacement_ard, value);
- // Verify APD and ARD descriptors has been reverted to implicit descriptors
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, replacement_apd));
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, replacement_ard));
+
+ // Freeing the active explicit descriptors restores the implicit descriptors.
value = nullptr;
EXPECT_EQ(SQL_SUCCESS, SQLGetStmtAttr(this->stmt, SQL_ATTR_APP_PARAM_DESC,
&value,
@@ -568,4 +588,21 @@ TYPED_TEST(ConnectionTest, TestSQLSetStmtAttrDescriptor) {
EXPECT_EQ(internal_ard, value);
}
+TYPED_TEST(ConnectionTest, TestExplicitDescriptorsOutliveStatement) {
+ SQLHSTMT statement;
+ SQLHDESC apd_descriptor, ard_descriptor;
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_STMT, this->conn,
&statement));
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn,
&apd_descriptor));
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn,
&ard_descriptor));
+
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLSetStmtAttr(statement, SQL_ATTR_APP_PARAM_DESC, apd_descriptor,
0));
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLSetStmtAttr(statement, SQL_ATTR_APP_ROW_DESC, ard_descriptor,
0));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_STMT, statement));
+ EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, apd_descriptor));
+ EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, ard_descriptor));
+}
+
} // namespace arrow::flight::sql::odbc
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/statement_driver_api_test.cc
b/cpp/src/arrow/flight/sql/odbc/tests/statement_driver_api_test.cc
new file mode 100644
index 00000000000..bf4be484813
--- /dev/null
+++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_driver_api_test.cc
@@ -0,0 +1,167 @@
+// 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_api_internal.h"
+#include "arrow/flight/sql/odbc/odbc_impl/odbc_statement.h"
+
+#include <cstdint>
+#include <string_view>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace arrow::flight::sql::odbc {
+
+class StatementDriverApiTest : public FlightSQLODBCMockTestBase {
+ protected:
+ void SetUp() override {
+ FlightSQLODBCMockTestBase::SetUp();
+
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLAllocHandle(
+ SQL_HANDLE_ENV, SQL_NULL_HANDLE, &driver_env_));
+ ASSERT_EQ(SQL_SUCCESS,
+ arrow::flight::sql::odbc::SQLSetEnvAttr(
+ driver_env_, SQL_ATTR_ODBC_VERSION,
+
reinterpret_cast<SQLPOINTER>(static_cast<intptr_t>(SQL_OV_ODBC3)), 0));
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLAllocHandle(
+ SQL_HANDLE_DBC, driver_env_, &driver_conn_));
+
+ const std::string connect_string = GetConnectionString();
+ std::vector<SQLWCHAR> wide_connect_string(connect_string.begin(),
+ connect_string.end());
+ SQLWCHAR output[kOdbcBufferSize];
+ SQLSMALLINT output_length;
+ ASSERT_EQ(SQL_SUCCESS,
+ arrow::flight::sql::odbc::SQLDriverConnect(
+ driver_conn_, nullptr, wide_connect_string.data(),
+ static_cast<SQLSMALLINT>(wide_connect_string.size()), output,
+ kOdbcBufferSize, &output_length, SQL_DRIVER_NOPROMPT));
+ driver_connected_ = true;
+
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLAllocHandle(
+ SQL_HANDLE_STMT, driver_conn_, &driver_stmt_));
+ }
+
+ void TearDown() override {
+ if (driver_stmt_ != SQL_NULL_HSTMT) {
+ EXPECT_EQ(SQL_SUCCESS,
+ arrow::flight::sql::odbc::SQLFreeHandle(SQL_HANDLE_STMT,
driver_stmt_));
+ }
+ if (driver_connected_) {
+ EXPECT_EQ(SQL_SUCCESS,
arrow::flight::sql::odbc::SQLDisconnect(driver_conn_));
+ }
+ if (driver_conn_ != SQL_NULL_HDBC) {
+ EXPECT_EQ(SQL_SUCCESS,
+ arrow::flight::sql::odbc::SQLFreeHandle(SQL_HANDLE_DBC,
driver_conn_));
+ }
+ if (driver_env_ != SQL_NULL_HENV) {
+ EXPECT_EQ(SQL_SUCCESS,
+ arrow::flight::sql::odbc::SQLFreeHandle(SQL_HANDLE_ENV,
driver_env_));
+ }
+ FlightSQLODBCMockTestBase::TearDown();
+ }
+
+ void VerifyDriverErrorState(std::string_view expected_state) {
+ ODBC::ODBCStatement* statement = ODBC::ODBCStatement::Of(driver_stmt_);
+ ASSERT_TRUE(statement->GetDiagnostics().HasRecord(0));
+ EXPECT_EQ(expected_state, statement->GetDiagnostics().GetSQLState(0));
+ }
+
+ SQLHENV driver_env_ = SQL_NULL_HENV;
+ SQLHDBC driver_conn_ = SQL_NULL_HDBC;
+ SQLHSTMT driver_stmt_ = SQL_NULL_HSTMT;
+ bool driver_connected_ = false;
+};
+
+TEST_F(StatementDriverApiTest, TestSQLFetchWithoutCursor) {
+ ASSERT_EQ(SQL_ERROR, arrow::flight::sql::odbc::SQLFetch(driver_stmt_));
+ VerifyDriverErrorState(kErrorState24000);
+}
+
+TEST_F(StatementDriverApiTest, TestSQLGetDataWithoutCursor) {
+ SQLINTEGER value;
+ SQLLEN indicator;
+ ASSERT_EQ(SQL_ERROR,
+ arrow::flight::sql::odbc::SQLGetData(driver_stmt_, 1, SQL_C_LONG,
&value,
+ sizeof(value), &indicator));
+ VerifyDriverErrorState(kErrorState24000);
+}
+
+TEST_F(StatementDriverApiTest, TestChangingAppRowDescriptorRebindsColumns) {
+ SQLINTEGER implicit_value = -1;
+ SQLLEN implicit_indicator;
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLBindCol(
+ driver_stmt_, 1, SQL_C_LONG, &implicit_value,
+ sizeof(implicit_value), &implicit_indicator));
+
+ ASSIGN_SQLWCHAR_ARR_AND_LEN(wsql,
+ LR"(
+ SELECT 1 AS value
+ UNION ALL SELECT 2
+ UNION ALL SELECT 3
+ UNION ALL SELECT 4
+ UNION ALL SELECT 5;
+ )");
+ ASSERT_EQ(SQL_SUCCESS,
+ arrow::flight::sql::odbc::SQLExecDirect(driver_stmt_, wsql,
wsql_len));
+
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLFetch(driver_stmt_));
+ EXPECT_EQ(1, implicit_value);
+
+ SQLHDESC explicit_ard;
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLAllocHandle(
+ SQL_HANDLE_DESC, driver_conn_, &explicit_ard));
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLSetStmtAttr(
+ driver_stmt_, SQL_ATTR_APP_ROW_DESC,
explicit_ard, 0));
+ SQLINTEGER explicit_value = -1;
+ SQLLEN explicit_indicator;
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLBindCol(
+ driver_stmt_, 1, SQL_C_LONG, &explicit_value,
+ sizeof(explicit_value), &explicit_indicator));
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLFetch(driver_stmt_));
+ EXPECT_EQ(2, explicit_value);
+
+ // Restoring the implicit ARD must rebind the result set to its original
buffer.
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLSetStmtAttr(
+ driver_stmt_, SQL_ATTR_APP_ROW_DESC,
SQL_NULL_HANDLE, 0));
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLFetch(driver_stmt_));
+ EXPECT_EQ(3, implicit_value);
+ EXPECT_EQ(2, explicit_value);
+ ASSERT_EQ(SQL_SUCCESS,
+ arrow::flight::sql::odbc::SQLFreeHandle(SQL_HANDLE_DESC,
explicit_ard));
+
+ // Releasing an active explicit ARD must also restore and rebind the
implicit ARD.
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLAllocHandle(
+ SQL_HANDLE_DESC, driver_conn_, &explicit_ard));
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLSetStmtAttr(
+ driver_stmt_, SQL_ATTR_APP_ROW_DESC,
explicit_ard, 0));
+ explicit_value = -1;
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLBindCol(
+ driver_stmt_, 1, SQL_C_LONG, &explicit_value,
+ sizeof(explicit_value), &explicit_indicator));
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLFetch(driver_stmt_));
+ EXPECT_EQ(4, explicit_value);
+
+ ASSERT_EQ(SQL_SUCCESS,
+ arrow::flight::sql::odbc::SQLFreeHandle(SQL_HANDLE_DESC,
explicit_ard));
+ ASSERT_EQ(SQL_SUCCESS, arrow::flight::sql::odbc::SQLFetch(driver_stmt_));
+ EXPECT_EQ(5, implicit_value);
+ EXPECT_EQ(4, explicit_value);
+}
+
+} // namespace arrow::flight::sql::odbc
diff --git a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
index ba8b883aac4..6ae2cdbbbc3 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
@@ -1781,6 +1781,41 @@ TYPED_TEST(StatementTest, TestSQLBindColRowFetching) {
ASSERT_EQ(SQL_NO_DATA, SQLFetch(this->stmt));
}
+TYPED_TEST(StatementTest, TestChangingAppRowDescriptorRebindsColumns) {
+ SQLINTEGER implicit_value = -1;
+ SQLLEN implicit_indicator;
+ ASSERT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 1, SQL_C_LONG, &implicit_value,
+ sizeof(implicit_value),
&implicit_indicator));
+
+ ASSIGN_SQLWCHAR_ARR_AND_LEN(wsql,
+ LR"(
+ SELECT 1 AS value
+ UNION ALL SELECT 2
+ UNION ALL SELECT 3;
+ )");
+ ASSERT_EQ(SQL_SUCCESS, SQLExecDirect(this->stmt, wsql, wsql_len));
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+ EXPECT_EQ(1, implicit_value);
+
+ SQLHDESC explicit_ard;
+ ASSERT_EQ(SQL_SUCCESS, SQLAllocHandle(SQL_HANDLE_DESC, this->conn,
&explicit_ard));
+ ASSERT_EQ(SQL_SUCCESS,
+ SQLSetStmtAttr(this->stmt, SQL_ATTR_APP_ROW_DESC, explicit_ard,
0));
+ SQLINTEGER explicit_value = -1;
+ SQLLEN explicit_indicator;
+ ASSERT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 1, SQL_C_LONG, &explicit_value,
+ sizeof(explicit_value),
&explicit_indicator));
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+ EXPECT_EQ(2, explicit_value);
+
+ // Releasing an active explicit ARD restores and rebinds the implicit ARD.
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, explicit_ard));
+ ASSERT_EQ(SQL_SUCCESS, SQLFetch(this->stmt));
+ EXPECT_EQ(3, implicit_value);
+ EXPECT_EQ(2, explicit_value);
+}
+
TYPED_TEST(StatementTest, TestSQLBindColRowArraySize) {
// Set SQL_ATTR_ROW_ARRAY_SIZE to fetch 3 rows at once
@@ -1981,12 +2016,37 @@ TYPED_TEST(StatementTest, TestSQLMoreResultsNoData) {
ASSERT_EQ(SQL_SUCCESS, SQLExecDirect(this->stmt, wsql, wsql_len));
ASSERT_EQ(SQL_NO_DATA, SQLMoreResults(this->stmt));
+
+ // SQLMoreResults closes the current cursor when there is no next result.
+ ASSERT_EQ(SQL_ERROR, SQLCloseCursor(this->stmt));
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState24000);
}
TYPED_TEST(StatementTest, TestSQLMoreResultsWithoutQuery) {
ASSERT_EQ(SQL_NO_DATA, SQLMoreResults(this->stmt));
}
+TYPED_TEST(StatementTest, TestDriverManagerSQLFetchWithoutCursor) {
+ ASSERT_EQ(SQL_ERROR, SQLFetch(this->stmt));
+#ifdef __APPLE__
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1010);
+#else
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY010);
+#endif
+}
+
+TYPED_TEST(StatementTest, TestDriverManagerSQLGetDataWithoutCursor) {
+ SQLINTEGER value;
+ SQLLEN indicator;
+ ASSERT_EQ(SQL_ERROR,
+ SQLGetData(this->stmt, 1, SQL_C_LONG, &value, sizeof(value),
&indicator));
+#ifdef __APPLE__
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateS1010);
+#else
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY010);
+#endif
+}
+
TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsInputString) {
SQLWCHAR buf[1024];
SQLINTEGER buf_char_len = sizeof(buf) / GetSqlWCharSize();