Skip to content

Commit 4ba23ef

Browse files
authored
SQLError implementation in macOS
* SQLError implementation for macOS Disable `SQLGetConnectOption` on macOS. Still need to check if SQLGetConnectOption is needed on macOS Excel.
1 parent 35c1bee commit 4ba23ef

File tree

5 files changed

+101
-21
lines changed

5 files changed

+101
-21
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ SQLRETURN SQL_API SQLGetDiagRec(SQLSMALLINT handle_type, SQLHANDLE handle,
8383
buffer_length, text_length_ptr);
8484
}
8585

86+
#if defined(__APPLE__)
87+
// macOS ODBC Driver Manager doesn't map SQLError to SQLGetDiagRec, so we need to
88+
// implement SQLError for macOS.
89+
// on Windows, SQLError mapping implemented by Driver Manager is preferred.
90+
SQLRETURN SQL_API SQLError(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt, SQLWCHAR* sql_state,
91+
SQLINTEGER* native_error_ptr, SQLWCHAR* message_text,
92+
SQLSMALLINT buffer_length, SQLSMALLINT* text_length_ptr) {
93+
return arrow::flight::sql::odbc::SQLError(env, conn, stmt, sql_state, native_error_ptr,
94+
message_text, buffer_length, text_length_ptr);
95+
}
96+
#endif // __APPLE__
97+
8698
SQLRETURN SQL_API SQLGetEnvAttr(SQLHENV env, SQLINTEGER attr, SQLPOINTER value_ptr,
8799
SQLINTEGER buffer_len, SQLINTEGER* str_len_ptr) {
88100
return arrow::flight::sql::odbc::SQLGetEnvAttr(env, attr, value_ptr, buffer_len,
@@ -317,9 +329,3 @@ SQLRETURN SQL_API SQLDescribeCol(SQLHSTMT stmt, SQLUSMALLINT column_number,
317329
stmt, column_number, column_name, buffer_length, name_length_ptr, data_type_ptr,
318330
column_size_ptr, decimal_digits_ptr, nullable_ptr);
319331
}
320-
321-
SQLRETURN SQL_API SQLGetConnectOption(SQLHDBC ConnectionHandle, SQLUSMALLINT Option,
322-
SQLPOINTER Value) {
323-
// TODO implement ODBC2 APIs
324-
return SQL_ERROR;
325-
}

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,56 @@ SQLRETURN SQLFreeStmt(SQLHSTMT handle, SQLUSMALLINT option) {
247247
return SQL_ERROR;
248248
}
249249

250+
#if defined(__APPLE__)
251+
SQLRETURN SQLError(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt, SQLWCHAR* sql_state,
252+
SQLINTEGER* native_error_ptr, SQLWCHAR* message_text,
253+
SQLSMALLINT buffer_length, SQLSMALLINT* text_length_ptr) {
254+
ARROW_LOG(DEBUG) << "SQLError called with env: " << env << ", conn: " << conn
255+
<< ", stmt: " << stmt
256+
<< ", sql_state: " << static_cast<const void*>(sql_state)
257+
<< ", native_error_ptr: " << static_cast<const void*>(native_error_ptr)
258+
<< ", message_text: " << static_cast<const void*>(message_text)
259+
<< ", buffer_length: " << buffer_length
260+
<< ", text_length_ptr: " << static_cast<const void*>(text_length_ptr);
261+
262+
SQLSMALLINT handle_type;
263+
SQLHANDLE handle;
264+
265+
if (env) {
266+
handle_type = SQL_HANDLE_ENV;
267+
handle = static_cast<SQLHANDLE>(env);
268+
} else if (conn) {
269+
handle_type = SQL_HANDLE_DBC;
270+
handle = static_cast<SQLHANDLE>(conn);
271+
} else if (stmt) {
272+
handle_type = SQL_HANDLE_STMT;
273+
handle = static_cast<SQLHANDLE>(stmt);
274+
} else {
275+
return static_cast<SQLRETURN>(SQL_INVALID_HANDLE);
276+
}
277+
278+
// Use the last record
279+
SQLINTEGER diag_number;
280+
SQLSMALLINT diag_number_length;
281+
282+
SQLRETURN ret = arrow::flight::sql::odbc::SQLGetDiagField(
283+
handle_type, handle, 0, SQL_DIAG_NUMBER, &diag_number, sizeof(SQLINTEGER), 0);
284+
if (ret != SQL_SUCCESS) {
285+
return ret;
286+
}
287+
288+
if (diag_number == 0) {
289+
return SQL_NO_DATA;
290+
}
291+
292+
SQLSMALLINT rec_number = static_cast<SQLSMALLINT>(diag_number);
293+
294+
return arrow::flight::sql::odbc::SQLGetDiagRec(
295+
handle_type, handle, rec_number, sql_state, native_error_ptr, message_text,
296+
buffer_length, text_length_ptr);
297+
}
298+
#endif // __APPLE__
299+
250300
inline bool IsValidStringFieldArgs(SQLPOINTER diag_info_ptr, SQLSMALLINT buffer_length,
251301
SQLSMALLINT* string_length_ptr, bool is_unicode) {
252302
const SQLSMALLINT char_size = is_unicode ? GetSqlWCharSize() : sizeof(char);
@@ -735,7 +785,6 @@ SQLRETURN SQLGetConnectAttr(SQLHDBC conn, SQLINTEGER attribute, SQLPOINTER value
735785
<< ", attribute: " << attribute << ", value_ptr: " << value_ptr
736786
<< ", buffer_length: " << buffer_length << ", string_length_ptr: "
737787
<< static_cast<const void*>(string_length_ptr);
738-
739788
using ODBC::ODBCConnection;
740789

741790
return ODBCConnection::ExecuteWithDiagnostics(conn, SQL_ERROR, [=]() {

cpp/src/arrow/flight/sql/odbc/odbc_api_internal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ namespace arrow::flight::sql::odbc {
3131
SQLHANDLE* result);
3232
[[nodiscard]] SQLRETURN SQLFreeHandle(SQLSMALLINT type, SQLHANDLE handle);
3333
[[nodiscard]] SQLRETURN SQLFreeStmt(SQLHSTMT stmt, SQLUSMALLINT option);
34+
#if defined(__APPLE__)
35+
[[nodiscard]] SQLRETURN SQLError(SQLHENV env, SQLHDBC conn, SQLHSTMT stmt,
36+
SQLWCHAR* sql_state, SQLINTEGER* native_error_ptr,
37+
SQLWCHAR* message_text, SQLSMALLINT buffer_length,
38+
SQLSMALLINT* text_length_ptr);
39+
#endif // __APPLE__
3440
[[nodiscard]] SQLRETURN SQLGetDiagField(SQLSMALLINT handle_type, SQLHANDLE handle,
3541
SQLSMALLINT rec_number,
3642
SQLSMALLINT diag_identifier,

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ using TestTypesHandle = ::testing::Types<FlightSQLOdbcEnvConnHandleMockTestBase,
4747
FlightSQLOdbcEnvConnHandleRemoteTestBase>;
4848
TYPED_TEST_SUITE(ErrorsHandleTest, TestTypesHandle);
4949

50+
using ODBC::SqlWcharToString;
51+
5052
TYPED_TEST(ErrorsHandleTest, TestSQLGetDiagFieldWForConnectFailure) {
5153
// Invalid connect string
5254
std::string connect_str = this->GetInvalidConnectionString();
@@ -119,7 +121,7 @@ TYPED_TEST(ErrorsHandleTest, TestSQLGetDiagFieldWForConnectFailure) {
119121
SQLGetDiagField(SQL_HANDLE_DBC, this->conn, RECORD_1, SQL_DIAG_SQLSTATE, sql_state,
120122
sql_state_size * GetSqlWCharSize(), &sql_state_length));
121123

122-
EXPECT_EQ(std::wstring(L"28000"), std::wstring(sql_state));
124+
EXPECT_EQ(kErrorState28000, SqlWcharToString(sql_state));
123125
}
124126

125127
TYPED_TEST(ErrorsHandleTest, DISABLED_TestSQLGetDiagFieldWForConnectFailureNTS) {
@@ -218,7 +220,7 @@ TYPED_TEST(ErrorsTest, TestSQLGetDiagFieldWForDescriptorFailureFromDriverManager
218220
SQLGetDiagField(SQL_HANDLE_DESC, descriptor, RECORD_1, SQL_DIAG_SQLSTATE, sql_state,
219221
sql_state_size * GetSqlWCharSize(), &sql_state_length));
220222

221-
EXPECT_EQ(std::wstring(L"IM001"), std::wstring(sql_state));
223+
EXPECT_EQ(kErrorStateIM001, SqlWcharToString(sql_state));
222224

223225
// Free descriptor handle
224226
EXPECT_EQ(SQL_SUCCESS, SQLFreeHandle(SQL_HANDLE_DESC, descriptor));
@@ -247,7 +249,7 @@ TYPED_TEST(ErrorsTest, TestSQLGetDiagRecForDescriptorFailureFromDriverManager) {
247249
EXPECT_EQ(0, native_error);
248250

249251
// API not implemented error from driver manager
250-
EXPECT_EQ(std::wstring(L"IM001"), std::wstring(sql_state));
252+
EXPECT_EQ(kErrorStateIM001, SqlWcharToString(sql_state));
251253

252254
EXPECT_FALSE(std::wstring(message).empty());
253255

@@ -284,7 +286,7 @@ TYPED_TEST(ErrorsHandleTest, TestSQLGetDiagRecForConnectFailure) {
284286

285287
EXPECT_EQ(200, native_error);
286288

287-
EXPECT_EQ(std::wstring(L"28000"), std::wstring(sql_state));
289+
EXPECT_EQ(kErrorState28000, SqlWcharToString(sql_state));
288290

289291
EXPECT_FALSE(std::wstring(message).empty());
290292
}
@@ -358,12 +360,12 @@ TYPED_TEST(ErrorsTest, TestSQLErrorEnvErrorFromDriverManager) {
358360
ASSERT_EQ(SQL_SUCCESS, SQLError(this->env, nullptr, nullptr, sql_state, &native_error,
359361
message, SQL_MAX_MESSAGE_LENGTH, &message_length));
360362

361-
EXPECT_GT(message_length, 50);
363+
EXPECT_GT(message_length, 40);
362364

363365
EXPECT_EQ(0, native_error);
364366

365367
// Function sequence error state from driver manager
366-
EXPECT_EQ(std::wstring(L"HY010"), std::wstring(sql_state));
368+
EXPECT_EQ(kErrorStateHY010, SqlWcharToString(sql_state));
367369

368370
EXPECT_FALSE(std::wstring(message).empty());
369371
}
@@ -390,7 +392,7 @@ TYPED_TEST(ErrorsTest, TestSQLErrorConnError) {
390392
EXPECT_EQ(100, native_error);
391393

392394
// optional feature not supported error state
393-
EXPECT_EQ(std::wstring(L"HYC00"), std::wstring(sql_state));
395+
EXPECT_EQ(kErrorStateHYC00, SqlWcharToString(sql_state));
394396

395397
EXPECT_FALSE(std::wstring(message).empty());
396398
}
@@ -420,7 +422,7 @@ TYPED_TEST(ErrorsTest, TestSQLErrorStmtError) {
420422

421423
EXPECT_EQ(100, native_error);
422424

423-
EXPECT_EQ(std::wstring(L"HY000"), std::wstring(sql_state));
425+
EXPECT_EQ(kErrorStateHY000, SqlWcharToString(sql_state));
424426

425427
EXPECT_FALSE(std::wstring(message).empty());
426428
}
@@ -457,7 +459,7 @@ TYPED_TEST(ErrorsTest, TestSQLErrorStmtWarning) {
457459
EXPECT_EQ(1000100, native_error);
458460

459461
// Verify string truncation warning is reported
460-
EXPECT_EQ(std::wstring(L"01004"), std::wstring(sql_state));
462+
EXPECT_EQ(kErrorState01004, SqlWcharToString(sql_state));
461463

462464
EXPECT_FALSE(std::wstring(message).empty());
463465
}
@@ -479,22 +481,35 @@ TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorEnvErrorFromDriverManager) {
479481
ASSERT_EQ(SQL_SUCCESS, SQLError(this->env, nullptr, nullptr, sql_state, &native_error,
480482
message, SQL_MAX_MESSAGE_LENGTH, &message_length));
481483

482-
EXPECT_GT(message_length, 50);
484+
EXPECT_GT(message_length, 40);
483485

484486
EXPECT_EQ(0, native_error);
485487

486488
// Function sequence error state from driver manager
487-
EXPECT_EQ(std::wstring(L"S1010"), std::wstring(sql_state));
489+
#ifdef _WIN32
490+
// Windows Driver Manager returns S1010
491+
EXPECT_EQ(kErrorStateS1010, SqlWcharToString(sql_state));
492+
#else
493+
// unix Driver Manager returns HY010
494+
EXPECT_EQ(kErrorStateHY010, SqlWcharToString(sql_state));
495+
#endif // _WIN32
488496

489497
EXPECT_FALSE(std::wstring(message).empty());
490498
}
491499

500+
// TODO: verify that `SQLGetConnectOption` is not required by Excel.
501+
#ifndef __APPLE__
492502
TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorConnError) {
493503
// Test ODBC 2.0 API SQLError with ODBC ver 2.
494504
// Known Windows Driver Manager (DM) behavior:
495505
// When application passes buffer length greater than SQL_MAX_MESSAGE_LENGTH (512),
496506
// DM passes 512 as buffer length to SQLError.
497507

508+
// Known macOS Driver Manager (DM) behavior:
509+
// Attempts to call SQLGetConnectOption without redirecting the API call to
510+
// SQLGetConnectAttr. SQLGetConnectOption is not implemented as it is not required by
511+
// macOS Excel.
512+
498513
// Attempt to set unsupported attribute
499514
ASSERT_EQ(SQL_ERROR,
500515
SQLGetConnectAttr(this->conn, SQL_ATTR_TXN_ISOLATION, 0, 0, nullptr));
@@ -511,10 +526,11 @@ TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorConnError) {
511526
EXPECT_EQ(100, native_error);
512527

513528
// optional feature not supported error state. Driver Manager maps state to S1C00
514-
EXPECT_EQ(std::wstring(L"S1C00"), std::wstring(sql_state));
529+
EXPECT_EQ(kErrorStateS1C00, SqlWcharToString(sql_state));
515530

516531
EXPECT_FALSE(std::wstring(message).empty());
517532
}
533+
#endif // __APPLE__
518534

519535
TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtError) {
520536
// Test ODBC 2.0 API SQLError with ODBC ver 2.
@@ -540,7 +556,7 @@ TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtError) {
540556
EXPECT_EQ(100, native_error);
541557

542558
// Driver Manager maps error state to S1000
543-
EXPECT_EQ(std::wstring(L"S1000"), std::wstring(sql_state));
559+
EXPECT_EQ(kErrorStateS1000, SqlWcharToString(sql_state));
544560

545561
EXPECT_FALSE(std::wstring(message).empty());
546562
}
@@ -577,7 +593,7 @@ TYPED_TEST(ErrorsOdbcV2Test, TestSQLErrorStmtWarning) {
577593
EXPECT_EQ(1000100, native_error);
578594

579595
// Verify string truncation warning is reported
580-
EXPECT_EQ(std::wstring(L"01004"), std::wstring(sql_state));
596+
EXPECT_EQ(kErrorState01004, SqlWcharToString(sql_state));
581597

582598
EXPECT_FALSE(std::wstring(message).empty());
583599
}

cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,13 @@ static constexpr std::string_view kErrorStateHY106 = "HY106";
236236
static constexpr std::string_view kErrorStateHY114 = "HY114";
237237
static constexpr std::string_view kErrorStateHY118 = "HY118";
238238
static constexpr std::string_view kErrorStateHYC00 = "HYC00";
239+
static constexpr std::string_view kErrorStateIM001 = "IM001";
240+
static constexpr std::string_view kErrorStateS1000 = "S1000";
239241
static constexpr std::string_view kErrorStateS1004 = "S1004";
240242
static constexpr std::string_view kErrorStateS1002 = "S1002";
241243
static constexpr std::string_view kErrorStateS1010 = "S1010";
242244
static constexpr std::string_view kErrorStateS1090 = "S1090";
245+
static constexpr std::string_view kErrorStateS1C00 = "S1C00";
243246

244247
/// Verify ODBC Error State
245248
void VerifyOdbcErrorState(SQLSMALLINT handle_type, SQLHANDLE handle,

0 commit comments

Comments
 (0)