Skip to content

Commit fc5fd48

Browse files
alinaliBQrscales
andauthored
GH-47726 : [C++][FlightRPC] ODBC Unicode Support (#47771)
### Rationale for this change Enable ODBC unicode support, so ODBC can handle wide characters. ### What changes are included in this PR? - Enable ODBC unicode support by setting `-DUNICODE` to 1 - DSN is read and retrieved in unicode format, and then converted to regular strings for driver - Fix a string reading bug in `GetAttributeSQLWCHAR` ### Are these changes tested? Build is tested locally ### Are there any user-facing changes? No * GitHub Issue: #47726 Lead-authored-by: Alina (Xi) Li <[email protected]> Co-authored-by: Alina (Xi) Li <[email protected]> Co-authored-by: rscales <[email protected]> Signed-off-by: David Li <[email protected]>
1 parent 541cba8 commit fc5fd48

19 files changed

+358
-224
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ add_arrow_lib(arrow_flight_sql_odbc
5555
DEPENDENCIES
5656
arrow_flight_sql
5757
DEFINITIONS
58-
FMT_HEADER_ONLY
58+
UNICODE
5959
SHARED_LINK_FLAGS
6060
${ARROW_VERSION_SCRIPT_FLAGS} # Defined in cpp/arrow/CMakeLists.txt
6161
SHARED_LINK_LIBS

cpp/src/arrow/flight/sql/odbc/odbc.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818
LIBRARY arrow_flight_sql_odbc
1919
EXPORTS
20-
; TODO: update to ConfigDSNW when driver has unicode implemented
21-
; ConfigDSN
20+
; GH-46574 TODO enable DSN window
21+
; ConfigDSNW
2222
SQLAllocConnect
2323
SQLAllocEnv
2424
SQLAllocHandle

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,16 @@ add_library(arrow_odbc_spi_impl
3939
blocking_queue.h
4040
calendar_utils.cc
4141
calendar_utils.h
42+
config/configuration.cc
43+
config/configuration.h
44+
config/connection_string_parser.cc
45+
config/connection_string_parser.h
4246
diagnostics.cc
4347
diagnostics.h
48+
error_codes.h
4449
encoding.cc
4550
encoding.h
4651
encoding_utils.h
47-
error_codes.h
4852
exceptions.cc
4953
exceptions.h
5054
flight_sql_auth_method.cc
@@ -106,26 +110,21 @@ add_library(arrow_odbc_spi_impl
106110
type_utilities.h
107111
util.cc
108112
util.h)
113+
target_compile_definitions(arrow_odbc_spi_impl PUBLIC UNICODE)
109114
target_include_directories(arrow_odbc_spi_impl PUBLIC include include/odbc_impl)
110115
target_include_directories(arrow_odbc_spi_impl PUBLIC ${CMAKE_CURRENT_LIST_DIR})
111116

112117
if(WIN32)
113118
target_sources(arrow_odbc_spi_impl
114-
PRIVATE config/configuration.cc
115-
config/configuration.h
116-
config/connection_string_parser.cc
117-
config/connection_string_parser.h
118-
ui/add_property_window.cc
119+
PRIVATE ui/add_property_window.cc
119120
ui/add_property_window.h
120121
ui/custom_window.cc
121122
ui/custom_window.h
122123
ui/dsn_configuration_window.cc
123124
ui/dsn_configuration_window.h
124125
ui/window.cc
125126
ui/window.h
126-
win_system_dsn.cc
127-
system_dsn.cc
128-
system_dsn.h)
127+
system_dsn.cc)
129128
endif()
130129

131130
target_link_libraries(arrow_odbc_spi_impl PUBLIC arrow_flight_sql_shared

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,21 @@ template <typename O>
8383
inline SQLRETURN GetAttributeSQLWCHAR(const std::string& attribute_value,
8484
bool is_length_in_bytes, SQLPOINTER output,
8585
O output_size, O* output_len_ptr) {
86-
size_t result = ConvertToSqlWChar(
86+
size_t length = ConvertToSqlWChar(
8787
attribute_value, reinterpret_cast<SQLWCHAR*>(output),
8888
is_length_in_bytes ? output_size : output_size * GetSqlWCharSize());
8989

90+
if (!is_length_in_bytes) {
91+
length = length / GetSqlWCharSize();
92+
}
93+
9094
if (output_len_ptr) {
91-
*output_len_ptr =
92-
static_cast<O>(is_length_in_bytes ? result : result / GetSqlWCharSize());
95+
*output_len_ptr = static_cast<O>(length);
9396
}
9497

9598
if (output &&
9699
output_size <
97-
static_cast<O>(result + (is_length_in_bytes ? GetSqlWCharSize() : 1))) {
100+
static_cast<O>(length + (is_length_in_bytes ? GetSqlWCharSize() : 1))) {
98101
return SQL_SUCCESS_WITH_INFO;
99102
}
100103
return SQL_SUCCESS;

cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.cc

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
#include "arrow/flight/sql/odbc/odbc_impl/config/configuration.h"
1919
#include "arrow/flight/sql/odbc/odbc_impl/flight_sql_connection.h"
20+
#include "arrow/flight/sql/odbc/odbc_impl/util.h"
21+
#include "arrow/result.h"
22+
#include "arrow/util/utf8.h"
2023

2124
#include <odbcinst.h>
2225
#include <boost/range/adaptor/map.hpp>
@@ -26,7 +29,6 @@
2629

2730
namespace arrow::flight::sql::odbc {
2831
namespace config {
29-
3032
static const char DEFAULT_DSN[] = "Apache Arrow Flight SQL";
3133
static const char DEFAULT_ENABLE_ENCRYPTION[] = TRUE_STR;
3234
static const char DEFAULT_USE_CERT_STORE[] = TRUE_STR;
@@ -35,23 +37,27 @@ static const char DEFAULT_DISABLE_CERT_VERIFICATION[] = FALSE_STR;
3537
namespace {
3638
std::string ReadDsnString(const std::string& dsn, const std::string_view& key,
3739
const std::string& dflt = "") {
38-
#define BUFFER_SIZE (1024)
39-
std::vector<char> buf(BUFFER_SIZE);
40+
CONVERT_WIDE_STR(const std::wstring wdsn, dsn);
41+
CONVERT_WIDE_STR(const std::wstring wkey, key);
42+
CONVERT_WIDE_STR(const std::wstring wdflt, dflt);
4043

41-
std::string key_str = std::string(key);
44+
#define BUFFER_SIZE (1024)
45+
std::vector<wchar_t> buf(BUFFER_SIZE);
4246
int ret =
43-
SQLGetPrivateProfileString(dsn.c_str(), key_str.c_str(), dflt.c_str(), buf.data(),
44-
static_cast<int>(buf.size()), "ODBC.INI");
47+
SQLGetPrivateProfileString(wdsn.c_str(), wkey.c_str(), wdflt.c_str(), buf.data(),
48+
static_cast<int>(buf.size()), L"ODBC.INI");
4549

4650
if (ret > BUFFER_SIZE) {
4751
// If there wasn't enough space, try again with the right size buffer.
4852
buf.resize(ret + 1);
4953
ret =
50-
SQLGetPrivateProfileString(dsn.c_str(), key_str.c_str(), dflt.c_str(), buf.data(),
51-
static_cast<int>(buf.size()), "ODBC.INI");
54+
SQLGetPrivateProfileString(wdsn.c_str(), wkey.c_str(), wdflt.c_str(), buf.data(),
55+
static_cast<int>(buf.size()), L"ODBC.INI");
5256
}
5357

54-
return std::string(buf.data(), ret);
58+
std::wstring wresult = std::wstring(buf.data(), ret);
59+
CONVERT_UTF8_STR(const std::string result, wresult);
60+
return result;
5561
}
5662

5763
void RemoveAllKnownKeys(std::vector<std::string>& keys) {
@@ -68,28 +74,32 @@ void RemoveAllKnownKeys(std::vector<std::string>& keys) {
6874
}
6975

7076
std::vector<std::string> ReadAllKeys(const std::string& dsn) {
71-
std::vector<char> buf(BUFFER_SIZE);
77+
CONVERT_WIDE_STR(const std::wstring wdsn, dsn);
78+
79+
std::vector<wchar_t> buf(BUFFER_SIZE);
7280

73-
int ret = SQLGetPrivateProfileString(dsn.c_str(), NULL, "", buf.data(),
74-
static_cast<int>(buf.size()), "ODBC.INI");
81+
int ret = SQLGetPrivateProfileString(wdsn.c_str(), NULL, L"", buf.data(),
82+
static_cast<int>(buf.size()), L"ODBC.INI");
7583

7684
if (ret > BUFFER_SIZE) {
7785
// If there wasn't enough space, try again with the right size buffer.
7886
buf.resize(ret + 1);
79-
ret = SQLGetPrivateProfileString(dsn.c_str(), NULL, "", buf.data(),
80-
static_cast<int>(buf.size()), "ODBC.INI");
87+
ret = SQLGetPrivateProfileString(wdsn.c_str(), NULL, L"", buf.data(),
88+
static_cast<int>(buf.size()), L"ODBC.INI");
8189
}
8290

8391
// When you pass NULL to SQLGetPrivateProfileString it gives back a \0 delimited list of
8492
// all the keys. The below loop simply tokenizes all the keys and places them into a
8593
// vector.
8694
std::vector<std::string> keys;
87-
char* begin = buf.data();
95+
wchar_t* begin = buf.data();
8896
while (begin && *begin != '\0') {
89-
char* cur;
97+
wchar_t* cur;
9098
for (cur = begin; *cur != '\0'; ++cur) {
9199
}
92-
keys.emplace_back(begin, cur);
100+
101+
CONVERT_UTF8_STR(const std::string key, std::wstring(begin, cur));
102+
keys.emplace_back(key);
93103
begin = ++cur;
94104
}
95105
return keys;
@@ -153,6 +163,11 @@ const std::string& Configuration::Get(const std::string_view& key) const {
153163
return itr->second;
154164
}
155165

166+
void Configuration::Set(const std::string_view& key, const std::wstring& wvalue) {
167+
CONVERT_UTF8_STR(const std::string value, wvalue);
168+
Set(key, value);
169+
}
170+
156171
void Configuration::Set(const std::string_view& key, const std::string& value) {
157172
const std::string copy = boost::trim_copy(value);
158173
if (!copy.empty()) {

cpp/src/arrow/flight/sql/odbc/odbc_impl/config/configuration.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class Configuration {
5959
void Clear();
6060
bool IsSet(const std::string_view& key) const;
6161
const std::string& Get(const std::string_view& key) const;
62+
void Set(const std::string_view& key, const std::wstring& wvalue);
6263
void Set(const std::string_view& key, const std::string& value);
6364

6465
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ namespace {
7171

7272
#if _WIN32 || _WIN64
7373
constexpr auto SYSTEM_TRUST_STORE_DEFAULT = true;
74-
constexpr auto STORES = {"CA", "MY", "ROOT", "SPC"};
74+
constexpr auto STORES = {L"CA", L"MY", L"ROOT", L"SPC"};
7575

7676
inline std::string GetCerts() {
7777
std::string certs;

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

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717

1818
#include "arrow/flight/sql/odbc/odbc_impl/odbc_connection.h"
1919

20+
#include "arrow/result.h"
21+
#include "arrow/util/utf8.h"
22+
2023
#include "arrow/flight/sql/odbc/odbc_impl/attribute_utils.h"
2124
#include "arrow/flight/sql/odbc/odbc_impl/exceptions.h"
2225
#include "arrow/flight/sql/odbc/odbc_impl/odbc_descriptor.h"
2326
#include "arrow/flight/sql/odbc/odbc_impl/odbc_environment.h"
2427
#include "arrow/flight/sql/odbc/odbc_impl/odbc_statement.h"
2528
#include "arrow/flight/sql/odbc/odbc_impl/spi/connection.h"
2629
#include "arrow/flight/sql/odbc/odbc_impl/spi/statement.h"
30+
#include "arrow/flight/sql/odbc/odbc_impl/util.h"
2731

2832
#include <odbcinst.h>
2933
#include <sql.h>
@@ -56,41 +60,43 @@ const boost::xpressive::sregex CONNECTION_STR_REGEX(
5660
void loadPropertiesFromDSN(const std::string& dsn,
5761
Connection::ConnPropertyMap& properties) {
5862
const size_t BUFFER_SIZE = 1024 * 10;
59-
std::vector<char> outputBuffer;
60-
outputBuffer.resize(BUFFER_SIZE, '\0');
63+
std::vector<wchar_t> output_buffer;
64+
output_buffer.resize(BUFFER_SIZE, '\0');
6165
SQLSetConfigMode(ODBC_BOTH_DSN);
6266

63-
SQLGetPrivateProfileString(dsn.c_str(), NULL, "", &outputBuffer[0], BUFFER_SIZE,
64-
"odbc.ini");
67+
CONVERT_WIDE_STR(const std::wstring wdsn, dsn);
68+
69+
SQLGetPrivateProfileString(wdsn.c_str(), NULL, L"", &output_buffer[0], BUFFER_SIZE,
70+
L"odbc.ini");
6571

6672
// The output buffer holds the list of keys in a series of NUL-terminated strings.
6773
// The series is terminated with an empty string (eg a NUL-terminator terminating the
6874
// last key followed by a NUL terminator after).
69-
std::vector<std::string_view> keys;
75+
std::vector<std::wstring_view> keys;
7076
size_t pos = 0;
7177
while (pos < BUFFER_SIZE) {
72-
std::string key(&outputBuffer[pos]);
73-
if (key.empty()) {
78+
std::wstring wkey(&output_buffer[pos]);
79+
if (wkey.empty()) {
7480
break;
7581
}
76-
size_t len = key.size();
82+
size_t len = wkey.size();
7783

7884
// Skip over Driver or DSN keys.
79-
if (!boost::iequals(key, "DSN") && !boost::iequals(key, "Driver")) {
80-
keys.emplace_back(std::move(key));
85+
if (!boost::iequals(wkey, L"DSN") && !boost::iequals(wkey, L"Driver")) {
86+
keys.emplace_back(std::move(wkey));
8187
}
8288
pos += len + 1;
8389
}
8490

85-
for (auto& key : keys) {
86-
outputBuffer.clear();
87-
outputBuffer.resize(BUFFER_SIZE, '\0');
88-
89-
std::string key_str = std::string(key);
90-
SQLGetPrivateProfileString(dsn.c_str(), key_str.c_str(), "", &outputBuffer[0],
91-
BUFFER_SIZE, "odbc.ini");
91+
for (auto& wkey : keys) {
92+
output_buffer.clear();
93+
output_buffer.resize(BUFFER_SIZE, '\0');
94+
SQLGetPrivateProfileString(wdsn.c_str(), wkey.data(), L"", &output_buffer[0],
95+
BUFFER_SIZE, L"odbc.ini");
9296

93-
std::string value = std::string(&outputBuffer[0]);
97+
std::wstring wvalue = std::wstring(&output_buffer[0]);
98+
CONVERT_UTF8_STR(const std::string value, wvalue);
99+
CONVERT_UTF8_STR(const std::string key, std::wstring(wkey));
94100
auto propIter = properties.find(key);
95101
if (propIter == properties.end()) {
96102
properties.emplace(std::make_pair(std::move(key), std::move(value)));

0 commit comments

Comments
 (0)