diff --git a/cpp/src/arrow/flight/sql/odbc/entry_points.cc b/cpp/src/arrow/flight/sql/odbc/entry_points.cc index 6ddd2481e9b1..38edc86ddff7 100644 --- a/cpp/src/arrow/flight/sql/odbc/entry_points.cc +++ b/cpp/src/arrow/flight/sql/odbc/entry_points.cc @@ -254,13 +254,8 @@ SQLRETURN SQL_API SQLNativeSql(SQLHDBC connectionHandle, SQLWCHAR* inStatementTe SQLINTEGER inStatementTextLength, SQLWCHAR* outStatementText, SQLINTEGER bufferLength, SQLINTEGER* outStatementTextLength) { - LOG_DEBUG( - "SQLNativeSqlW called with connectionHandle: {}, inStatementText: {}, " - "inStatementTextLength: " - "{}, outStatementText: {}, bufferLength: {}, outStatementTextLength: {}", - connectionHandle, fmt::ptr(inStatementText), inStatementTextLength, - fmt::ptr(outStatementText), bufferLength, fmt::ptr(outStatementTextLength)); - return SQL_ERROR; + return arrow::SQLNativeSql(connectionHandle, inStatementText, inStatementTextLength, + outStatementText, bufferLength, outStatementTextLength); } SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT stmt, SQLSMALLINT* columnCountPtr) { diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc index f5cac728c092..e87e713caa88 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.cc +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.cc @@ -1273,4 +1273,33 @@ SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT recordNumber, return SQL_SUCCESS; }); } + +SQLRETURN SQLNativeSql(SQLHDBC connectionHandle, SQLWCHAR* inStatementText, + SQLINTEGER inStatementTextLength, SQLWCHAR* outStatementText, + SQLINTEGER bufferLength, SQLINTEGER* outStatementTextLength) { + LOG_DEBUG( + "SQLNativeSqlW called with connectionHandle: {}, inStatementText: {}, " + "inStatementTextLength: {}, outStatementText: {}, bufferLength: {}, " + "outStatementTextLength: {}", + connectionHandle, fmt::ptr(inStatementText), inStatementTextLength, + fmt::ptr(outStatementText), bufferLength, fmt::ptr(outStatementTextLength)); + + using driver::odbcabstraction::Diagnostics; + using ODBC::GetAttributeSQLWCHAR; + using ODBC::ODBCConnection; + using ODBC::SqlWcharToString; + + return ODBCConnection::ExecuteWithDiagnostics(connectionHandle, SQL_ERROR, [=]() { + const bool isLengthInBytes = false; + + ODBCConnection* connection = reinterpret_cast(connectionHandle); + Diagnostics& diagnostics = connection->GetDiagnostics(); + + std::string inStatementStr = SqlWcharToString(inStatementText, inStatementTextLength); + + return GetAttributeSQLWCHAR(inStatementStr, isLengthInBytes, outStatementText, + bufferLength, outStatementTextLength, diagnostics); + }); +} + } // namespace arrow diff --git a/cpp/src/arrow/flight/sql/odbc/odbc_api.h b/cpp/src/arrow/flight/sql/odbc/odbc_api.h index 05f8d1a74f84..d9181ff329b6 100644 --- a/cpp/src/arrow/flight/sql/odbc/odbc_api.h +++ b/cpp/src/arrow/flight/sql/odbc/odbc_api.h @@ -91,4 +91,7 @@ SQLRETURN SQLColAttribute(SQLHSTMT stmt, SQLUSMALLINT recordNumber, SQLUSMALLINT fieldIdentifier, SQLPOINTER characterAttributePtr, SQLSMALLINT bufferLength, SQLSMALLINT* outputLength, SQLLEN* numericAttributePtr); +SQLRETURN SQLNativeSql(SQLHDBC connectionHandle, SQLWCHAR* inStatementText, + SQLINTEGER inStatementTextLength, SQLWCHAR* outStatementText, + SQLINTEGER bufferLength, SQLINTEGER* outStatementTextLength); } // namespace arrow diff --git a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h index ffaa33c3a10b..0ce4bc4144d0 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h +++ b/cpp/src/arrow/flight/sql/odbc/tests/odbc_test_suite.h @@ -172,8 +172,10 @@ static constexpr std::string_view error_state_22002 = "22002"; static constexpr std::string_view error_state_24000 = "24000"; static constexpr std::string_view error_state_28000 = "28000"; static constexpr std::string_view error_state_HY000 = "HY000"; +static constexpr std::string_view error_state_HY009 = "HY009"; static constexpr std::string_view error_state_HY010 = "HY010"; static constexpr std::string_view error_state_HY024 = "HY024"; +static constexpr std::string_view error_state_HY090 = "HY090"; static constexpr std::string_view error_state_HY092 = "HY092"; static constexpr std::string_view error_state_HYC00 = "HYC00"; static constexpr std::string_view error_state_HY106 = "HY106"; 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 79d58b64bc85..45e4a29f9fa3 100644 --- a/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc +++ b/cpp/src/arrow/flight/sql/odbc/tests/statement_test.cc @@ -2265,4 +2265,139 @@ TYPED_TEST(FlightSQLODBCTestBase, TestSQLMoreResultsInvalidFunctionSequence) { this->disconnect(); } + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsInputString) { + this->connect(); + + SQLWCHAR buf[1024]; + constexpr SQLINTEGER bufCharLen = sizeof(buf) / ODBC::GetSqlWCharSize(); + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + std::wstring expectedString = std::wstring(inputStr); + + SQLRETURN ret = + SQLNativeSql(this->conn, inputStr, inputCharLen, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(outputCharLen, inputCharLen); + + // returned length is in characters + std::wstring returnedString(buf, buf + outputCharLen); + + EXPECT_EQ(returnedString, expectedString); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsNTSInputString) { + this->connect(); + + SQLWCHAR buf[1024]; + constexpr SQLINTEGER bufCharLen = sizeof(buf) / ODBC::GetSqlWCharSize(); + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + std::wstring expectedString = std::wstring(inputStr); + + SQLRETURN ret = + SQLNativeSql(this->conn, inputStr, SQL_NTS, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(outputCharLen, inputCharLen); + + // returned length is in characters + std::wstring returnedString(buf, buf + outputCharLen); + + EXPECT_EQ(returnedString, expectedString); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsInputStringLength) { + this->connect(); + + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + std::wstring expectedString = std::wstring(inputStr); + + SQLRETURN ret = + SQLNativeSql(this->conn, inputStr, inputCharLen, nullptr, 0, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(outputCharLen, inputCharLen); + + ret = SQLNativeSql(this->conn, inputStr, SQL_NTS, nullptr, 0, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS); + + EXPECT_EQ(outputCharLen, inputCharLen); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsTruncatedString) { + this->connect(); + + const SQLINTEGER smallBufSizeInChar = 11; + SQLWCHAR smallBuf[smallBufSizeInChar]; + constexpr SQLINTEGER smallBufCharLen = sizeof(smallBuf) / ODBC::GetSqlWCharSize(); + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + + // Create expected return string based on buf size + SQLWCHAR expectedStringBuf[smallBufSizeInChar]; + wcsncpy(expectedStringBuf, inputStr, 10); + expectedStringBuf[10] = L'\0'; + std::wstring expectedString(expectedStringBuf, expectedStringBuf + smallBufSizeInChar); + + SQLRETURN ret = SQLNativeSql(this->conn, inputStr, inputCharLen, smallBuf, + smallBufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_SUCCESS_WITH_INFO); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_01004); + + // Returned text length represents full string char length regardless of truncation + EXPECT_EQ(outputCharLen, inputCharLen); + + std::wstring returnedString(smallBuf, smallBuf + smallBufCharLen); + + EXPECT_EQ(returnedString, expectedString); + + this->disconnect(); +} + +TYPED_TEST(FlightSQLODBCTestBase, TestSQLNativeSqlReturnsErrorOnBadInputs) { + this->connect(); + + SQLWCHAR buf[1024]; + constexpr SQLINTEGER bufCharLen = sizeof(buf) / ODBC::GetSqlWCharSize(); + SQLWCHAR inputStr[] = L"SELECT * FROM mytable WHERE id == 1"; + SQLINTEGER inputCharLen = static_cast(wcslen(inputStr)); + SQLINTEGER outputCharLen = 0; + + SQLRETURN ret = + SQLNativeSql(this->conn, nullptr, inputCharLen, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY009); + + ret = SQLNativeSql(this->conn, nullptr, SQL_NTS, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY009); + + ret = SQLNativeSql(this->conn, inputStr, -100, buf, bufCharLen, &outputCharLen); + + EXPECT_EQ(ret, SQL_ERROR); + VerifyOdbcErrorState(SQL_HANDLE_DBC, this->conn, error_state_HY090); + + this->disconnect(); +} + } // namespace arrow::flight::sql::odbc