Skip to content

Commit e01ec17

Browse files
committed
Extract SQLFetchScroll implementation
Co-Authored-By: alinalibq <alina.li@improving.com>
1 parent 42f27ab commit e01ec17

File tree

3 files changed

+157
-2
lines changed

3 files changed

+157
-2
lines changed

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -847,8 +847,28 @@ SQLRETURN SQLFetchScroll(SQLHSTMT stmt, SQLSMALLINT fetch_orientation,
847847
ARROW_LOG(DEBUG) << "SQLFetchScroll called with stmt: " << stmt
848848
<< ", fetch_orientation: " << fetch_orientation
849849
<< ", fetch_offset: " << fetch_offset;
850-
// GH-47715 TODO: Implement SQLFetchScroll
851-
return SQL_INVALID_HANDLE;
850+
851+
using ODBC::ODBCDescriptor;
852+
using ODBC::ODBCStatement;
853+
return ODBCStatement::ExecuteWithDiagnostics(stmt, SQL_ERROR, [=]() {
854+
if (fetch_orientation != SQL_FETCH_NEXT) {
855+
throw DriverException("Optional feature not supported.", "HYC00");
856+
}
857+
// fetch_offset is ignored as only SQL_FETCH_NEXT is supported
858+
859+
ODBCStatement* statement = reinterpret_cast<ODBCStatement*>(stmt);
860+
861+
// The SQL_ATTR_ROW_ARRAY_SIZE statement attribute specifies the number of rows in the
862+
// rowset.
863+
ODBCDescriptor* ard = statement->GetARD();
864+
size_t rows = static_cast<size_t>(ard->GetArraySize());
865+
if (statement->Fetch(rows)) {
866+
return SQL_SUCCESS;
867+
} else {
868+
// Reached the end of rowset
869+
return SQL_NO_DATA;
870+
}
871+
});
852872
}
853873

854874
SQLRETURN SQLBindCol(SQLHSTMT stmt, SQLUSMALLINT record_number, SQLSMALLINT c_type,

cpp/src/arrow/flight/sql/odbc/tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ add_arrow_test(flight_sql_odbc_test
3535
odbc_test_suite.cc
3636
odbc_test_suite.h
3737
connection_test.cc
38+
statement_test.cc
3839
# Enable Protobuf cleanup after test execution
3940
# GH-46889: move protobuf_test_util to a more common location
4041
../../../../engine/substrait/protobuf_test_util.cc
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
#include "arrow/flight/sql/odbc/tests/odbc_test_suite.h"
18+
19+
#include "arrow/flight/sql/odbc/odbc_impl/platform.h"
20+
21+
#include <sql.h>
22+
#include <sqltypes.h>
23+
#include <sqlucode.h>
24+
25+
#include <limits>
26+
27+
#include <gmock/gmock.h>
28+
#include <gtest/gtest.h>
29+
30+
namespace arrow::flight::sql::odbc {
31+
32+
template <typename T>
33+
class StatementTest : public T {};
34+
35+
class StatementMockTest : public FlightSQLODBCMockTestBase {};
36+
class StatementRemoteTest : public FlightSQLODBCRemoteTestBase {};
37+
using TestTypes = ::testing::Types<StatementMockTest, StatementRemoteTest>;
38+
TYPED_TEST_SUITE(StatementTest, TestTypes);
39+
40+
TYPED_TEST(StatementTest, TestSQLFetchScrollRowFetching) {
41+
SQLLEN rows_fetched;
42+
SQLSetStmtAttr(this->stmt, SQL_ATTR_ROWS_FETCHED_PTR, &rows_fetched, 0);
43+
44+
std::wstring wsql =
45+
LR"(
46+
SELECT 1 AS small_table
47+
UNION ALL
48+
SELECT 2
49+
UNION ALL
50+
SELECT 3;
51+
)";
52+
std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
53+
54+
ASSERT_EQ(SQL_SUCCESS,
55+
SQLExecDirect(this->stmt, &sql0[0], static_cast<SQLINTEGER>(sql0.size())));
56+
57+
// Fetch row 1
58+
ASSERT_EQ(SQL_SUCCESS, SQLFetchScroll(this->stmt, SQL_FETCH_NEXT, 0));
59+
60+
SQLINTEGER val;
61+
SQLLEN buf_len = sizeof(val);
62+
SQLLEN ind;
63+
64+
ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind));
65+
// Verify 1 is returned
66+
EXPECT_EQ(1, val);
67+
// Verify 1 row is fetched
68+
EXPECT_EQ(1, rows_fetched);
69+
70+
// Fetch row 2
71+
ASSERT_EQ(SQL_SUCCESS, SQLFetchScroll(this->stmt, SQL_FETCH_NEXT, 0));
72+
73+
ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind));
74+
75+
// Verify 2 is returned
76+
EXPECT_EQ(2, val);
77+
// Verify 1 row is fetched in the last SQLFetchScroll call
78+
EXPECT_EQ(1, rows_fetched);
79+
80+
// Fetch row 3
81+
ASSERT_EQ(SQL_SUCCESS, SQLFetchScroll(this->stmt, SQL_FETCH_NEXT, 0));
82+
83+
ASSERT_EQ(SQL_SUCCESS, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, buf_len, &ind));
84+
85+
// Verify 3 is returned
86+
EXPECT_EQ(3, val);
87+
// Verify 1 row is fetched in the last SQLFetchScroll call
88+
EXPECT_EQ(1, rows_fetched);
89+
90+
// Verify result set has no more data beyond row 3
91+
ASSERT_EQ(SQL_NO_DATA, SQLFetchScroll(this->stmt, SQL_FETCH_NEXT, 0));
92+
93+
ASSERT_EQ(SQL_ERROR, SQLGetData(this->stmt, 1, SQL_C_LONG, &val, 0, &ind));
94+
// Invalid cursor state
95+
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorState24000);
96+
}
97+
98+
TYPED_TEST(StatementTest, TestSQLFetchScrollUnsupportedOrientation) {
99+
// SQL_FETCH_PRIOR is the only supported fetch orientation.
100+
101+
std::wstring wsql = L"SELECT 1;";
102+
std::vector<SQLWCHAR> sql0(wsql.begin(), wsql.end());
103+
104+
ASSERT_EQ(SQL_SUCCESS,
105+
SQLExecDirect(this->stmt, &sql0[0], static_cast<SQLINTEGER>(sql0.size())));
106+
107+
ASSERT_EQ(SQL_ERROR, SQLFetchScroll(this->stmt, SQL_FETCH_PRIOR, 0));
108+
109+
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHYC00);
110+
111+
SQLLEN fetch_offset = 1;
112+
ASSERT_EQ(SQL_ERROR, SQLFetchScroll(this->stmt, SQL_FETCH_RELATIVE, fetch_offset));
113+
114+
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHYC00);
115+
116+
ASSERT_EQ(SQL_ERROR, SQLFetchScroll(this->stmt, SQL_FETCH_ABSOLUTE, fetch_offset));
117+
118+
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHYC00);
119+
120+
ASSERT_EQ(SQL_ERROR, SQLFetchScroll(this->stmt, SQL_FETCH_FIRST, 0));
121+
122+
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHYC00);
123+
124+
ASSERT_EQ(SQL_ERROR, SQLFetchScroll(this->stmt, SQL_FETCH_LAST, 0));
125+
126+
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHYC00);
127+
128+
ASSERT_EQ(SQL_ERROR, SQLFetchScroll(this->stmt, SQL_FETCH_BOOKMARK, fetch_offset));
129+
130+
// ODBC Driver Manager returns state HY106 for SQL_FETCH_BOOKMARK
131+
VerifyOdbcErrorState(SQL_HANDLE_STMT, this->stmt, kErrorStateHY106);
132+
}
133+
134+
} // namespace arrow::flight::sql::odbc

0 commit comments

Comments
 (0)