Skip to content

Commit 7ddfb17

Browse files
committed
Extract SQLExtendedFetch implementation
Fix comments and use `ARROW_UNUSED` Co-Authored-By: alinalibq <alina.li@improving.com>
1 parent 2571c2c commit 7ddfb17

4 files changed

Lines changed: 127 additions & 7 deletions

File tree

cpp/src/arrow/flight/sql/odbc/odbc_api.cc

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,8 +1081,33 @@ SQLRETURN SQLExtendedFetch(SQLHSTMT stmt, SQLUSMALLINT fetch_orientation,
10811081
<< ", row_count_ptr: " << static_cast<const void*>(row_count_ptr)
10821082
<< ", row_status_array: "
10831083
<< static_cast<const void*>(row_status_array);
1084-
// GH-47714 TODO: Implement SQLExtendedFetch
1085-
return SQL_INVALID_HANDLE;
1084+
1085+
using ODBC::ODBCDescriptor;
1086+
using ODBC::ODBCStatement;
1087+
return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
1088+
// Only SQL_FETCH_NEXT forward-only fetching orientation is supported,
1089+
// meaning the behavior of SQLExtendedFetch is same as SQLFetch.
1090+
if (fetch_orientation != SQL_FETCH_NEXT) {
1091+
throw DriverException("Optional feature not supported.", "HYC00");
1092+
}
1093+
// Ignore fetch_offset as it's not applicable to SQL_FETCH_NEXT
1094+
ARROW_UNUSED(fetch_offset);
1095+
1096+
ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
1097+
1098+
// The SQL_ROWSET_SIZE statement attribute specifies the number of rows in the
1099+
// rowset. Retrieve it from GetRowsetSize.
1100+
SQLULEN row_set_size = statement->GetRowsetSize();
1101+
ARROW_LOG(DEBUG) << "SQL_ROWSET_SIZE value for SQLExtendedFetch: " << row_set_size;
1102+
1103+
if (statement->Fetch(static_cast<size_t>(row_set_size), row_count_ptr,
1104+
row_status_array)) {
1105+
return SQL_SUCCESS;
1106+
} else {
1107+
// Reached the end of rowset
1108+
return SQL_NO_DATA;
1109+
}
1110+
});
10861111
}
10871112

10881113
SQLRETURN SQLFetchScroll(SQLHSTMT stmt, SQLSMALLINT fetch_orientation,

cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.cc

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,8 @@ void ODBCStatement::ExecuteDirect(const std::string& query) {
316316
is_prepared_ = false;
317317
}
318318

319-
bool ODBCStatement::Fetch(size_t rows) {
319+
bool ODBCStatement::Fetch(size_t rows, SQLULEN* row_count_ptr,
320+
SQLUSMALLINT* row_status_array) {
320321
if (has_reached_end_of_result_) {
321322
ird_->SetRowsProcessed(0);
322323
return false;
@@ -349,11 +350,24 @@ bool ODBCStatement::Fetch(size_t rows) {
349350
current_ard_->NotifyBindingsHavePropagated();
350351
}
351352

352-
size_t rows_fetched = current_result_->Move(rows, current_ard_->GetBindOffset(),
353-
current_ard_->GetBoundStructOffset(),
354-
ird_->GetArrayStatusPtr());
353+
uint16_t* array_status_ptr;
354+
if (row_status_array) {
355+
// For SQLExtendedFetch only
356+
array_status_ptr = row_status_array;
357+
} else {
358+
array_status_ptr = ird_->GetArrayStatusPtr();
359+
}
360+
361+
size_t rows_fetched =
362+
current_result_->Move(rows, current_ard_->GetBindOffset(),
363+
current_ard_->GetBoundStructOffset(), array_status_ptr);
355364
ird_->SetRowsProcessed(static_cast<SQLULEN>(rows_fetched));
356365

366+
if (row_count_ptr) {
367+
// For SQLExtendedFetch only
368+
*row_count_ptr = rows_fetched;
369+
}
370+
357371
row_number_ += rows_fetched;
358372
has_reached_end_of_result_ = rows_fetched != rows;
359373
return rows_fetched != 0;

cpp/src/arrow/flight/sql/odbc/odbc_impl/odbc_statement.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ class ODBCStatement : public ODBCHandle<ODBCStatement> {
5959
void ExecuteDirect(const std::string& query);
6060

6161
/// \brief Return true if the number of rows fetch was greater than zero.
62-
bool Fetch(size_t rows);
62+
///
63+
/// row_count_ptr and row_status_array are optional arguments, they are only needed for
64+
/// SQLExtendedFetch
65+
bool Fetch(size_t rows, SQLULEN* row_count_ptr = 0, SQLUSMALLINT* row_status_array = 0);
66+
6367
bool IsPrepared() const;
6468

6569
void GetStmtAttr(SQLINTEGER statement_attribute, SQLPOINTER output,

cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,83 @@ TYPED_TEST(StatementTest, TestSQLExecDirectIgnoreInvalidBufLen) {
11781178
EXPECT_EQ(0, timestamp_var.fraction);
11791179
}
11801180

1181+
TYPED_TEST(StatementTest, TestSQLExtendedFetchRowFetching) {
1182+
// Set SQL_ROWSET_SIZE to fetch 3 rows at once
1183+
1184+
constexpr SQLULEN rows = 3;
1185+
SQLINTEGER val[rows];
1186+
SQLLEN buf_len = sizeof(val);
1187+
SQLLEN ind[rows];
1188+
1189+
// Same variable will be used for column 1, the value of `val`
1190+
// should be updated after every SQLFetch call.
1191+
ASSERT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 1, SQL_C_LONG, val, buf_len, ind));
1192+
1193+
ASSERT_EQ(SQL_SUCCESS, SQLSetStmtAttr(this->stmt, SQL_ROWSET_SIZE,
1194+
reinterpret_cast<SQLPOINTER>(rows), 0));
1195+
1196+
std::wstring wsql =
1197+
LR"(
1198+
SELECT 1 AS small_table
1199+
UNION ALL
1200+
SELECT 2
1201+
UNION ALL
1202+
SELECT 3;
1203+
)";
1204+
std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
1205+
1206+
ASSERT_EQ(SQL_SUCCESS,
1207+
SQLExecDirect(this->stmt, &sql0[0], static_cast<SQLINTEGER>(sql0.size())));
1208+
1209+
// Fetch row 1-3.
1210+
SQLULEN row_count;
1211+
SQLUSMALLINT row_status[rows];
1212+
1213+
ASSERT_EQ(SQL_SUCCESS,
1214+
SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count, row_status));
1215+
EXPECT_EQ(3, row_count);
1216+
1217+
for (int i = 0; i < rows; i++) {
1218+
EXPECT_EQ(SQL_SUCCESS, row_status[i]);
1219+
}
1220+
1221+
// Verify 1 is returned for row 1
1222+
EXPECT_EQ(1, val[0]);
1223+
// Verify 2 is returned for row 2
1224+
EXPECT_EQ(2, val[1]);
1225+
// Verify 3 is returned for row 3
1226+
EXPECT_EQ(3, val[2]);
1227+
1228+
// Verify result set has no more data beyond row 3
1229+
SQLULEN row_count2;
1230+
SQLUSMALLINT row_status2[rows];
1231+
EXPECT_EQ(SQL_NO_DATA,
1232+
SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count2, row_status2));
1233+
}
1234+
1235+
TEST_F(StatementRemoteTest, DISABLED_TestSQLExtendedFetchQueryNullIndicator) {
1236+
// GH-47110: SQLExtendedFetch should return SQL_SUCCESS_WITH_INFO for 22002
1237+
// Limitation on mock test server prevents null from working properly, so use remote
1238+
// server instead. Mock server has type `DENSE_UNION` for null column data.
1239+
SQLINTEGER val;
1240+
1241+
ASSERT_EQ(SQL_SUCCESS, SQLBindCol(this->stmt, 1, SQL_C_LONG, &val, 0, 0));
1242+
1243+
std::wstring wsql = L"SELECT null as null_col;";
1244+
std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
1245+
1246+
ASSERT_EQ(SQL_SUCCESS,
1247+
SQLExecDirect(this->stmt, &sql0[0], static_cast<SQLINTEGER>(sql0.size())));
1248+
1249+
SQLULEN row_count1;
1250+
SQLUSMALLINT row_status1[1];
1251+
1252+
// SQLExtendedFetch should return SQL_SUCCESS_WITH_INFO for 22002 state
1253+
ASSERT_EQ(SQL_SUCCESS_WITH_INFO,
1254+
SQLExtendedFetch(this->stmt, SQL_FETCH_NEXT, 0, &row_count1, row_status1));
1255+
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState22002);
1256+
}
1257+
11811258
TYPED_TEST(StatementTest, TestSQLNativeSqlReturnsInputString) {
11821259
SQLWCHAR buf[1024];
11831260
SQLINTEGER buf_char_len = sizeof(buf) / GetSqlWCharSize();

0 commit comments

Comments
 (0)