diff --git a/cpp/src/gandiva/precompiled/string_ops.cc b/cpp/src/gandiva/precompiled/string_ops.cc index b7c6b518b45b..2bab598c349e 100644 --- a/cpp/src/gandiva/precompiled/string_ops.cc +++ b/cpp/src/gandiva/precompiled/string_ops.cc @@ -3125,9 +3125,14 @@ const char* soundex_utf8(gdv_int64 context, const char* in, gdv_int32 in_len, int start_idx = 0; for (int i = 0; i < in_len; ++i) { - if (isalpha(static_cast(in[i])) > 0) { + unsigned char ch = static_cast(in[i]); + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { // Retain the first letter - ret[0] = toupper(static_cast(in[i])); + if (ch >= 'a') { + ret[0] = static_cast(ch - 'a' + 'A'); + } else { + ret[0] = static_cast(ch); + } start_idx = i + 1; break; } @@ -3143,12 +3148,17 @@ const char* soundex_utf8(gdv_int64 context, const char* in, gdv_int32 in_len, soundex[0] = '\0'; // Replace consonants with digits and special letters with 0 for (int i = start_idx; i < in_len; i++) { - if (isalpha(static_cast(in[i])) > 0) { - c = toupper(static_cast(in[i])) - 65; - if (mappings[c] != soundex[si - 1]) { - soundex[si] = mappings[c]; - si++; - } + unsigned char ch = static_cast(in[i]); + if (ch >= 'A' && ch <= 'Z') { + c = ch - 'A'; + } else if (ch >= 'a' && ch <= 'z') { + c = ch - 'a'; + } else { + continue; + } + if (mappings[c] != soundex[si - 1]) { + soundex[si] = mappings[c]; + si++; } } diff --git a/cpp/src/gandiva/precompiled/string_ops_test.cc b/cpp/src/gandiva/precompiled/string_ops_test.cc index 3faeb316231c..b574885dc7cb 100644 --- a/cpp/src/gandiva/precompiled/string_ops_test.cc +++ b/cpp/src/gandiva/precompiled/string_ops_test.cc @@ -18,10 +18,12 @@ #include #include +#include #include #include #include +#include "arrow/testing/gtest_util.h" #include "gandiva/execution_context.h" #include "gandiva/precompiled/types.h" @@ -2929,6 +2931,38 @@ TEST(TestStringOps, TestFromHex) { EXPECT_EQ(out_valid, false); } +static void CheckSoundexNonAsciiBytes() { + for (int byte = 128; byte <= 255; ++byte) { + const std::string non_ascii(1, static_cast(byte)); + for (const auto& input : {non_ascii + "Robert", "R" + non_ascii + "obert"}) { + gandiva::ExecutionContext ctx; + auto ctx_ptr = reinterpret_cast(&ctx); + int32_t out_len = -1; + bool validity = false; + const auto* out = + soundex_utf8(ctx_ptr, input.data(), static_cast(input.size()), true, + &validity, &out_len); + ASSERT_TRUE(validity); + ASSERT_EQ(out_len, 4); + EXPECT_EQ(std::string(out, out_len), "R163"); + EXPECT_FALSE(ctx.has_error()); + } + } +} + +TEST(TestStringOps, TestSoundexNonAsciiBytes) { + arrow::LocaleGuard locale_guard("C"); + CheckSoundexNonAsciiBytes(); +} + +TEST(TestStringOps, TestSoundexNonAsciiLocale) { + arrow::LocaleGuard locale_guard("en_US.ISO-8859-1"); + if (std::isalpha(static_cast(0xe9)) == 0) { + GTEST_SKIP() << "Requires an ISO-8859-1 locale classifying high bytes as letters"; + } + CheckSoundexNonAsciiBytes(); +} + TEST(TestStringOps, TestSoundex) { gandiva::ExecutionContext ctx; auto ctx_ptr = reinterpret_cast(&ctx);