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 3868ba95eb GH-47717: [C++][FlightRPC] ODBC close cursor (#48043)
3868ba95eb is described below
commit 3868ba95eb0bfc3b70f4d72f7a1e3b3d017d6231
Author: Alina (Xi) Li <[email protected]>
AuthorDate: Thu Dec 4 16:08:48 2025 -0800
GH-47717: [C++][FlightRPC] ODBC close cursor (#48043)
### Rationale for this change
Implement support for explicitly closing cursor in ODBC. Cursors are
implicitly closed when ODBC disconnects, and this implementation allows BI
tools to close a cursor by passing its associated statement handle.
### What changes are included in this PR?
- SQLCloseCursor & Tests
- Fix close cursor state code to be 24000
### Are these changes tested?
Tested on local MSVC
### Are there any user-facing changes?
N/A
* GitHub Issue: #47717
Authored-by: Alina (Xi) Li <[email protected]>
Signed-off-by: David Li <[email protected]>
---
cpp/src/arrow/flight/sql/odbc/odbc_api.cc | 12 +++++++++--
.../flight/sql/odbc/odbc_impl/odbc_statement.cc | 2 +-
.../flight/sql/odbc/odbc_impl/odbc_statement.h | 10 +++-------
.../arrow/flight/sql/odbc/tests/statement_test.cc | 23 ++++++++++++++++++++++
4 files changed, 37 insertions(+), 10 deletions(-)
diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
index 76d0024680..c4604f8aa2 100644
--- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
+++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc
@@ -1092,8 +1092,16 @@ SQLRETURN SQLBindCol(SQLHSTMT stmt, SQLUSMALLINT
record_number, SQLSMALLINT c_ty
SQLRETURN SQLCloseCursor(SQLHSTMT stmt) {
ARROW_LOG(DEBUG) << "SQLCloseCursor called with stmt: " << stmt;
- // GH-47717 TODO: Implement SQLCloseCursor
- return SQL_INVALID_HANDLE;
+
+ using ODBC::ODBCStatement;
+ return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
+ ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
+
+ // Close cursor with suppressErrors set to false
+ statement->CloseCursor(false);
+
+ return SQL_SUCCESS;
+ });
}
SQLRETURN SQLGetData(SQLHSTMT stmt, SQLUSMALLINT record_number, SQLSMALLINT
c_type,
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 8234017e7f..aa490816b7 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
@@ -693,7 +693,7 @@ void ODBCStatement::RevertAppDescriptor(bool isApd) {
void ODBCStatement::CloseCursor(bool suppress_errors) {
if (!suppress_errors && !current_result_) {
- throw DriverException("Invalid cursor state", "28000");
+ throw DriverException("Invalid cursor state", "24000");
}
if (current_result_) {
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 8e128db1bd..2409c17d29 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
@@ -80,15 +80,11 @@ class ODBCStatement : public ODBCHandle<ODBCStatement> {
bool GetData(SQLSMALLINT record_number, SQLSMALLINT c_type, SQLPOINTER
data_ptr,
SQLLEN buffer_length, SQLLEN* indicator_ptr);
- /**
- * @brief Closes the cursor. This does _not_ un-prepare the statement or
change
- * bindings.
- */
+ /// \brief Closes the cursor. This does _not_ un-prepare the statement or
change
+ /// bindings.
void CloseCursor(bool suppress_errors);
- /**
- * @brief Releases this statement from memory.
- */
+ /// \brief Releases this statement from memory.
void ReleaseStatement();
void GetTables(const std::string* catalog, const std::string* schema,
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 a83855c218..4dbe87ddbb 100644
--- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
+++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc
@@ -217,4 +217,27 @@ TYPED_TEST(StatementTest,
TestSQLNativeSqlReturnsErrorOnBadInputs) {
VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, kErrorStateHY090);
}
+TYPED_TEST(StatementTest, TestSQLCloseCursor) {
+ 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, SQLCloseCursor(this->stmt));
+}
+
+TYPED_TEST(StatementTest, TestSQLFreeStmtSQLCloseWithoutCursor) {
+ // Verify SQLFreeStmt(SQL_CLOSE) does not throw error with invalid cursor
+
+ ASSERT_EQ(SQL_SUCCESS, SQLFreeStmt(this->stmt, SQL_CLOSE));
+}
+
+TYPED_TEST(StatementTest, TestSQLCloseCursorWithoutCursor) {
+ ASSERT_EQ(SQL_ERROR, SQLCloseCursor(this->stmt));
+
+ // Verify invalid cursor error state is returned
+ VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState24000);
+}
+
} // namespace arrow::flight::sql::odbc