Skip to content

Commit eec10a4

Browse files
committed
Fix filter in Power BI for non-Latin characters
Fixes #449 Power BI cannot process the result of its own query if it does not find a corresponding WCHAR-compatible type in `SQLGetTypeInfo`. This change adds a second definition of `String` that also represents `SQL_WCHAR`, which fixes the filter functionality in Power BI for non-Latin characters.
1 parent e8c533b commit eec10a4

File tree

2 files changed

+72
-42
lines changed

2 files changed

+72
-42
lines changed

driver/api/odbc.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,9 +1068,21 @@ SQLRETURN SQL_API EXPORTED_FUNCTION_MAYBE_W(SQLGetTypeInfo)(
10681068
std::stringstream query;
10691069
query << "SELECT * FROM (";
10701070

1071-
bool first = true;
1071+
// Power BI requires a UTF-16 compatible type to pass string-based parameters.
1072+
// However, we do not use UTF-16 internally, and therefore do not declare such a type
1073+
// in the TypeInfoCatalog. Without it, Power BI cannot bind its input parameters,
1074+
// for example, in the WHERE clause.
1075+
// To solve this problem, we suggest that Power BI use the same `String` type for both
1076+
// UTF-16 and UTF-8. This way, Power BI will attempt to bind its input parameters as
1077+
// `SQL_WVARCHAR`, and the driver will convert them to UTF-8 as needed.
1078+
// By declaring this type only here, we also avoid cluttering the TypeInfoCatalog.
1079+
std::vector<TypeInfo> types(std::cbegin(TypeInfoCatalog::Types), std::cend(TypeInfoCatalog::Types));
1080+
auto string_u16 = TypeInfoCatalog::Types[DataSourceTypeIdIndex(DataSourceTypeId::String)];
1081+
string_u16.data_type = SQL_WVARCHAR;
1082+
types.push_back(string_u16);
10721083

1073-
for (auto info : TypeInfoCatalog::Types) {
1084+
bool first = true;
1085+
for (const auto & info : types) {
10741086
if (type != SQL_ALL_TYPES && type != info.data_type)
10751087
continue;
10761088

driver/test/type_info_it.cpp

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,23 @@ TEST_F(TypeInfoTest, SQLGetTypeInfoMapping)
170170
// Check that the SQLGetTypeInfo returns a dataset with the data as expected
171171
TEST_F(TypeInfoTest, SQLGetTypeInfoResultSet)
172172
{
173+
// The same type can appear more than once in the result if it declares different SQL types. For example `String`
174+
// represent both `SQL_VARCHAR` and `SQL_WVARCHAR`. Therefore we need a special type to uniquely represent each
175+
// entry in `SQLGetTypeInfo`.
176+
struct TypeInfoEntryKey{
177+
std::string ch_type;
178+
SQLSMALLINT sql_type;
179+
180+
// This operator is needed to use this struct as a key in `std::map`
181+
bool operator<(const TypeInfoEntryKey & other) const
182+
{
183+
if (ch_type == other.ch_type)
184+
return sql_type < other.sql_type;
185+
return ch_type < other.ch_type;
186+
}
187+
};
188+
173189
struct TypeInfoEntry{
174-
SQLSMALLINT data_type;
175190
std::optional<SQLINTEGER> column_size;
176191
std::optional<std::string> literal_prefix;
177192
std::optional<std::string> literal_suffix;
@@ -192,64 +207,67 @@ TEST_F(TypeInfoTest, SQLGetTypeInfoResultSet)
192207
const std::string scale = "scale";
193208

194209
// clang-format off
195-
std::map<std::string, TypeInfoEntry> expected {
196-
197-
// ODBC pre/suffix create unsi- min/max date
198-
// type name data type size params params gned scale sql data type sub radix
199-
{"Nothing", {SQL_TYPE_NULL, 1, na, na, na, na, na, na, SQL_TYPE_NULL, na, na, }},
200-
{"Int8", {SQL_TINYINT, 4, na, na, na, false, na, na, SQL_TINYINT, na, 10, }},
201-
{"UInt8", {SQL_TINYINT, 3, na, na, na, true, na, na, SQL_TINYINT, na, 10, }},
202-
{"Int16", {SQL_SMALLINT, 6, na, na, na, false, na, na, SQL_SMALLINT, na, 10, }},
203-
{"UInt16", {SQL_SMALLINT, 5, na, na, na, true, na, na, SQL_SMALLINT, na, 10, }},
204-
{"Int32", {SQL_INTEGER, 11, na, na, na, false, na, na, SQL_INTEGER, na, 10, }},
205-
{"UInt32", {SQL_BIGINT, 10, na, na, na, true, na, na, SQL_BIGINT, na, 10, }},
206-
{"Int64", {SQL_BIGINT, 20, na, na, na, false, na, na, SQL_BIGINT, na, 10, }},
207-
{"UInt64", {SQL_BIGINT, 20, na, na, na, true, na, na, SQL_BIGINT, na, 10, }},
208-
{"Float32", {SQL_REAL, 7, na, na, na, false, na, na, SQL_REAL, na, 2, }},
209-
{"Float64", {SQL_DOUBLE, 15, na, na, na, false, na, na, SQL_DOUBLE, na, 2, }},
210-
{"Decimal", {SQL_DECIMAL, 41, na, na, pre_sc, false, 1, 76, SQL_DECIMAL, na, 10, }},
211-
{"String", {SQL_VARCHAR, max_size, "'", "'", na, na, na, na, SQL_VARCHAR, na, na, }},
212-
{"FixedString", {SQL_VARCHAR, max_size, "'", "'", length, na, na, na, SQL_VARCHAR, na, na, }},
213-
{"Date", {SQL_TYPE_DATE, 10, na, na, na, na, na, na, SQL_DATE, 1, na, }},
214-
{"DateTime64", {SQL_TYPE_TIMESTAMP, 29, na, na, scale, na, 0, 9, SQL_DATE, 3, na, }},
215-
{"DateTime", {SQL_TYPE_TIMESTAMP, 19, na, na, na, na, na, na, SQL_DATE, 3, na, }},
216-
{"UUID", {SQL_GUID, 35, na, na, na, na, na, na, SQL_GUID, na, na, }},
217-
{"Array", {SQL_VARCHAR, max_size, na, na, na, na, na, na, SQL_VARCHAR, na, na, }},
210+
std::map<TypeInfoEntryKey, TypeInfoEntry> expected {
211+
212+
// ODBC pre/suffix create unsi- min/max date
213+
// type name data type size params params gned scale sql data type sub radix
214+
{{"Nothing", SQL_TYPE_NULL }, {1, na, na, na, na, na, na, SQL_TYPE_NULL, na, na, }},
215+
{{"Int8", SQL_TINYINT }, {4, na, na, na, false, na, na, SQL_TINYINT, na, 10, }},
216+
{{"UInt8", SQL_TINYINT }, {3, na, na, na, true, na, na, SQL_TINYINT, na, 10, }},
217+
{{"Int16", SQL_SMALLINT }, {6, na, na, na, false, na, na, SQL_SMALLINT, na, 10, }},
218+
{{"UInt16", SQL_SMALLINT }, {5, na, na, na, true, na, na, SQL_SMALLINT, na, 10, }},
219+
{{"Int32", SQL_INTEGER }, {11, na, na, na, false, na, na, SQL_INTEGER, na, 10, }},
220+
{{"UInt32", SQL_BIGINT }, {10, na, na, na, true, na, na, SQL_BIGINT, na, 10, }},
221+
{{"Int64", SQL_BIGINT }, {20, na, na, na, false, na, na, SQL_BIGINT, na, 10, }},
222+
{{"UInt64", SQL_BIGINT }, {20, na, na, na, true, na, na, SQL_BIGINT, na, 10, }},
223+
{{"Float32", SQL_REAL }, {7, na, na, na, false, na, na, SQL_REAL, na, 2, }},
224+
{{"Float64", SQL_DOUBLE }, {15, na, na, na, false, na, na, SQL_DOUBLE, na, 2, }},
225+
{{"Decimal", SQL_DECIMAL }, {41, na, na, pre_sc, false, 1, 76, SQL_DECIMAL, na, 10, }},
226+
{{"String", SQL_VARCHAR }, {max_size, "'", "'", na, na, na, na, SQL_VARCHAR, na, na, }},
227+
{{"String", SQL_WVARCHAR }, {max_size, "'", "'", na, na, na, na, SQL_VARCHAR, na, na, }},
228+
{{"FixedString",SQL_VARCHAR }, {max_size, "'", "'", length, na, na, na, SQL_VARCHAR, na, na, }},
229+
{{"Date", SQL_TYPE_DATE }, {10, na, na, na, na, na, na, SQL_DATE, 1, na, }},
230+
{{"DateTime64", SQL_TYPE_TIMESTAMP}, {29, na, na, scale, na, 0, 9, SQL_DATE, 3, na, }},
231+
{{"DateTime", SQL_TYPE_TIMESTAMP}, {19, na, na, na, na, na, na, SQL_DATE, 3, na, }},
232+
{{"UUID", SQL_GUID }, {35, na, na, na, na, na, na, SQL_GUID, na, na, }},
233+
{{"Array", SQL_VARCHAR }, {max_size, na, na, na, na, na, na, SQL_VARCHAR, na, na, }},
218234
};
219235
// clang-format on
220236

221237
ODBC_CALL_ON_STMT_THROW(hstmt, SQLGetTypeInfo(hstmt, SQL_ALL_TYPES));
222238

223239
ResultSetReader reader{hstmt};
224240

225-
std::set<std::string> received_types;
241+
std::set<TypeInfoEntryKey> received_types;
226242
while (reader.fetch())
227243
{
228-
std::string type = reader.getData<std::string>("TYPE_NAME").value();
229-
SCOPED_TRACE(testing::Message() << "Failed for type " << type);
230-
EXPECT_EQ(reader.getData<SQLSMALLINT>("DATA_TYPE"), expected.at(type).data_type);
231-
EXPECT_EQ(reader.getData<SQLINTEGER>("COLUMN_SIZE"), expected.at(type).column_size);
232-
EXPECT_EQ(reader.getData<std::string>("LITERAL_PREFIX"), expected.at(type).literal_prefix);
233-
EXPECT_EQ(reader.getData<std::string>("LITERAL_SUFFIX"), expected.at(type).literal_suffix);
234-
EXPECT_EQ(reader.getData<std::string>("CREATE_PARAMS"), expected.at(type).create_params);
244+
auto ch_type = reader.getData<std::string>("TYPE_NAME").value();
245+
auto sql_type = reader.getData<SQLSMALLINT>("DATA_TYPE").value();
246+
SCOPED_TRACE(testing::Message() << "Failed for type " << ch_type);
247+
248+
EXPECT_EQ(reader.getData<SQLINTEGER>("COLUMN_SIZE"), expected.at({ch_type, sql_type}).column_size);
249+
EXPECT_EQ(reader.getData<std::string>("LITERAL_PREFIX"), expected.at({ch_type, sql_type}).literal_prefix);
250+
EXPECT_EQ(reader.getData<std::string>("LITERAL_SUFFIX"), expected.at({ch_type, sql_type}).literal_suffix);
251+
EXPECT_EQ(reader.getData<std::string>("CREATE_PARAMS"), expected.at({ch_type, sql_type}).create_params);
235252
EXPECT_EQ(reader.getData<SQLSMALLINT>("NULLABLE"), SQL_NULLABLE);
236253
EXPECT_EQ(reader.getData<SQLSMALLINT>("CASE_SENSITIVE"), SQL_TRUE);
237254
EXPECT_EQ(reader.getData<SQLSMALLINT>("SEARCHABLE"), SQL_SEARCHABLE);
238-
EXPECT_EQ(reader.getData<SQLINTEGER>("UNSIGNED_ATTRIBUTE"), expected.at(type).unsigned_attribute);
255+
EXPECT_EQ(reader.getData<SQLINTEGER>("UNSIGNED_ATTRIBUTE"),
256+
expected.at({ch_type, sql_type}).unsigned_attribute);
239257
EXPECT_EQ(reader.getData<SQLINTEGER>("FIXED_PREC_SCALE"), SQL_FALSE);
240258
EXPECT_EQ(reader.getData<SQLINTEGER>("AUTO_UNIQUE_VALUE"), std::nullopt);
241259
EXPECT_EQ(reader.getData<std::string>("LOCAL_TYPE_NAME"), std::nullopt);
242-
EXPECT_EQ(reader.getData<SQLSMALLINT>("MINIMUM_SCALE"), expected.at(type).minimum_scale);
243-
EXPECT_EQ(reader.getData<SQLSMALLINT>("MAXIMUM_SCALE"), expected.at(type).maximum_scale);
244-
EXPECT_EQ(reader.getData<SQLSMALLINT>("SQL_DATA_TYPE"), expected.at(type).sql_data_type);
245-
EXPECT_EQ(reader.getData<SQLSMALLINT>("SQL_DATETIME_SUB"), expected.at(type).sql_datetime_sub);
246-
EXPECT_EQ(reader.getData<SQLINTEGER>("NUM_PREC_RADIX"), expected.at(type).num_prec_radix);
260+
EXPECT_EQ(reader.getData<SQLSMALLINT>("MINIMUM_SCALE"), expected.at({ch_type, sql_type}).minimum_scale);
261+
EXPECT_EQ(reader.getData<SQLSMALLINT>("MAXIMUM_SCALE"), expected.at({ch_type, sql_type}).maximum_scale);
262+
EXPECT_EQ(reader.getData<SQLSMALLINT>("SQL_DATA_TYPE"), expected.at({ch_type, sql_type}).sql_data_type);
263+
EXPECT_EQ(reader.getData<SQLSMALLINT>("SQL_DATETIME_SUB"), expected.at({ch_type, sql_type}).sql_datetime_sub);
264+
EXPECT_EQ(reader.getData<SQLINTEGER>("NUM_PREC_RADIX"), expected.at({ch_type, sql_type}).num_prec_radix);
247265
EXPECT_EQ(reader.getData<SQLINTEGER>("INTERVAL_PRECISION"), std::nullopt);
248-
received_types.insert(type);
266+
received_types.insert({ch_type, sql_type});
249267
}
250268

251269
for (const auto& [type, info] : expected) {
252-
EXPECT_TRUE(received_types.contains(type)) << type << " is not in SQLGetTypeInfo result set";
270+
EXPECT_TRUE(received_types.contains(type)) << type.ch_type << " is not in SQLGetTypeInfo result set";
253271
}
254272
}
255273

0 commit comments

Comments
 (0)