This is an automated email from the ASF dual-hosted git repository.
pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 374db363476 GH-50615: [C++] Reduce code generation for string kernels
(#50616)
374db363476 is described below
commit 374db36347664a38d074d26d28d81ddf1fbd7593
Author: Antoine Pitrou <[email protected]>
AuthorDate: Mon Jul 27 11:43:36 2026 +0200
GH-50615: [C++] Reduce code generation for string kernels (#50616)
### Rationale for this change
Some string kernels are generated twice for the corresponding binary and
string type (for example binary/utf8, large_binary, large_utf8). Most of the
time we can cut down on the code duplication to reduce compile times and binary
size slightly.
### What changes are included in this PR?
Only instantiate string kernels for base binary types, not the string
derived types.
This makes `libarrow_compute` somehow smaller on my system:
* before:
```console
$ size `find /build/ -name libarrow_compute.so.2600.0.0`
text data bss dec hex filename
13551567 113096 46432 13711095 d136f7
/build/build-release/relwithdebinfo/libarrow_compute.so.2600.0.0
```
* after:
```console
$ size `find /build/ -name libarrow_compute.so.2600.0.0`
text data bss dec hex filename
13363474 112128 44968 13520570 ce4eba
/build/build-release/relwithdebinfo/libarrow_compute.so.2600.0.0
```
### Are these changes tested?
Yes, including additional tests to check that utf8-ness of the input is
still propagated correctly.
### Are there any user-facing changes?
No.
* GitHub Issue: #50615
Authored-by: Antoine Pitrou <[email protected]>
Signed-off-by: Antoine Pitrou <[email protected]>
---
cpp/src/arrow/compute/kernels/codegen_internal.h | 48 +---
cpp/src/arrow/compute/kernels/scalar_compare.cc | 3 +-
cpp/src/arrow/compute/kernels/scalar_if_else.cc | 2 +-
.../arrow/compute/kernels/scalar_string_ascii.cc | 248 ++++++++++++---------
.../arrow/compute/kernels/scalar_string_internal.h | 45 ++--
.../arrow/compute/kernels/scalar_string_test.cc | 164 +++++++++++++-
.../arrow/compute/kernels/scalar_string_utf8.cc | 11 +-
cpp/src/arrow/compute/kernels/vector_array_sort.cc | 3 +-
cpp/src/arrow/compute/kernels/vector_replace.cc | 9 +-
9 files changed, 357 insertions(+), 176 deletions(-)
diff --git a/cpp/src/arrow/compute/kernels/codegen_internal.h
b/cpp/src/arrow/compute/kernels/codegen_internal.h
index 9d12d62d95f..fa89c86183f 100644
--- a/cpp/src/arrow/compute/kernels/codegen_internal.h
+++ b/cpp/src/arrow/compute/kernels/codegen_internal.h
@@ -1314,10 +1314,14 @@ KernelType
GenerateTypeAgnosticPrimitive(detail::GetTypeId get_id) {
}
}
-// similar to GenerateTypeAgnosticPrimitive, but for base variable binary types
-template <template <typename...> class Generator, typename KernelType =
ArrayKernelExec,
- typename... Args>
-KernelType GenerateTypeAgnosticVarBinaryBase(detail::GetTypeId get_id) {
+// Similar to GenerateTypeAgnosticPrimitive, but for base variable binary types
+//
+// Note that we don't offer to generate separate code for String types, because
+// the utf8-ness of a type can be retrieved and handled efficiently at runtime.
+// This helps cut down on code generation (see GH-50615).
+template <template <typename...> class Generator, typename... Args>
+auto GenerateTypeAgnosticVarBinaryBase(detail::GetTypeId get_id) {
+ using KernelType = decltype(&Generator<BinaryType, Args...>::Exec);
switch (get_id.id) {
case Type::BINARY:
case Type::STRING:
@@ -1331,24 +1335,6 @@ KernelType
GenerateTypeAgnosticVarBinaryBase(detail::GetTypeId get_id) {
}
}
-// Generate a kernel given a templated functor for binary and string types
-template <template <typename...> class Generator, typename... Args>
-ArrayKernelExec GenerateVarBinaryToVarBinary(detail::GetTypeId get_id) {
- switch (get_id.id) {
- case Type::BINARY:
- return Generator<BinaryType, Args...>::Exec;
- case Type::STRING:
- return Generator<StringType, Args...>::Exec;
- case Type::LARGE_BINARY:
- return Generator<LargeBinaryType, Args...>::Exec;
- case Type::LARGE_STRING:
- return Generator<LargeStringType, Args...>::Exec;
- default:
- ARROW_DCHECK(false);
- return nullptr;
- }
-}
-
// Generate a kernel given a templated functor for base binary types. Generates
// a single kernel for binary/string and large binary/large string. If your
kernel
// implementation needs access to the specific type at compile time, please use
@@ -1370,24 +1356,6 @@ ArrayKernelExec GenerateVarBinaryBase(detail::GetTypeId
get_id) {
}
}
-// See BaseBinary documentation
-template <template <typename...> class Generator, typename Type0, typename...
Args>
-ArrayKernelExec GenerateVarBinary(detail::GetTypeId get_id) {
- switch (get_id.id) {
- case Type::BINARY:
- return Generator<Type0, BinaryType, Args...>::Exec;
- case Type::STRING:
- return Generator<Type0, StringType, Args...>::Exec;
- case Type::LARGE_BINARY:
- return Generator<Type0, LargeBinaryType, Args...>::Exec;
- case Type::LARGE_STRING:
- return Generator<Type0, LargeStringType, Args...>::Exec;
- default:
- ARROW_DCHECK(false);
- return nullptr;
- }
-}
-
// Generate a kernel given a templated functor for binary-view types.
Generates a
// single kernel for binary/string-view.
//
diff --git a/cpp/src/arrow/compute/kernels/scalar_compare.cc
b/cpp/src/arrow/compute/kernels/scalar_compare.cc
index 3dfd66655e5..0f2c479b082 100644
--- a/cpp/src/arrow/compute/kernels/scalar_compare.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_compare.cc
@@ -825,8 +825,7 @@ std::shared_ptr<ScalarFunction>
MakeScalarMinMax(std::string name, FunctionDoc d
DCHECK_OK(func->AddKernel(std::move(kernel)));
}
for (const auto& ty : BaseBinaryTypes()) {
- auto exec =
- GenerateTypeAgnosticVarBinaryBase<BinaryScalarMinMax, ArrayKernelExec,
Op>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<BinaryScalarMinMax, Op>(ty);
ScalarKernel kernel{KernelSignature::Make({ty}, ty, /*is_varargs=*/true),
exec,
MinMaxState::Init};
kernel.null_handling = NullHandling::COMPUTED_NO_PREALLOCATE;
diff --git a/cpp/src/arrow/compute/kernels/scalar_if_else.cc
b/cpp/src/arrow/compute/kernels/scalar_if_else.cc
index 5618f24e5ce..26f880fc0c6 100644
--- a/cpp/src/arrow/compute/kernels/scalar_if_else.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_if_else.cc
@@ -1328,7 +1328,7 @@ void AddBinaryIfElseKernels(const
std::shared_ptr<IfElseFunction>& scalar_functi
const std::vector<std::shared_ptr<DataType>>&
types) {
for (auto&& type : types) {
auto exec =
- internal::GenerateTypeAgnosticVarBinaryBase<ResolveIfElseExec,
ArrayKernelExec,
+ internal::GenerateTypeAgnosticVarBinaryBase<ResolveIfElseExec,
/*AllocateMem=*/std::true_type>(
*type);
// cond array needs to be boolean always
diff --git a/cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
b/cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
index d4dda5556b3..06ec8c999c6 100644
--- a/cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_string_ascii.cc
@@ -16,6 +16,7 @@
// under the License.
#include <algorithm>
+#include <array>
#include <cctype>
#include <iterator>
#include <memory>
@@ -26,6 +27,7 @@
#include "arrow/compute/kernels/scalar_string_internal.h"
#include "arrow/compute/registry_internal.h"
#include "arrow/result.h"
+#include "arrow/status.h"
#include "arrow/util/config.h"
#include "arrow/util/logging_internal.h"
#include "arrow/util/macros.h"
@@ -71,12 +73,6 @@ RE2::Options MakeRE2Options(bool is_utf8, bool ignore_case =
false,
options.set_literal(literal);
return options;
}
-
-// Set RE2 encoding based on input type: Latin-1 for BinaryTypes and UTF-8 for
StringTypes
-template <typename T>
-RE2::Options MakeRE2Options(bool ignore_case = false, bool literal = false) {
- return MakeRE2Options(T::is_utf8, ignore_case, literal);
-}
#endif
// ----------------------------------------------------------------------
@@ -948,8 +944,8 @@ void AddAsciiStringReverse(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("binary_reverse",
Arity::Unary(),
binary_reverse_doc);
for (const auto& ty : BinaryTypes()) {
- DCHECK_OK(
- func->AddKernel({ty}, ty,
GenerateVarBinaryToVarBinary<BinaryReverse>(ty)));
+ DCHECK_OK(func->AddKernel({ty}, ty,
+
GenerateTypeAgnosticVarBinaryBase<BinaryReverse>(ty)));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
}
@@ -1376,9 +1372,9 @@ template <typename Type>
struct MatchSubstring<Type, RegexSubstringMatcher> {
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
// TODO Cache matcher across invocations (for regex compilation)
- ARROW_ASSIGN_OR_RAISE(auto matcher,
-
RegexSubstringMatcher::Make(MatchSubstringState::Get(ctx),
-
/*is_utf8=*/Type::is_utf8));
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto matcher, RegexSubstringMatcher::Make(
+ MatchSubstringState::Get(ctx),
is_utf8));
return MatchSubstringImpl<Type, RegexSubstringMatcher>::Exec(ctx, batch,
out,
matcher.get());
}
@@ -1391,9 +1387,9 @@ struct MatchSubstring<Type, PlainSubstringMatcher> {
auto options = MatchSubstringState::Get(ctx);
if (options.ignore_case) {
#ifdef ARROW_WITH_RE2
- ARROW_ASSIGN_OR_RAISE(
- auto matcher, RegexSubstringMatcher::Make(options,
/*is_utf8=*/Type::is_utf8,
- /*literal=*/true));
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto matcher, RegexSubstringMatcher::Make(options,
is_utf8,
+
/*literal=*/true));
return MatchSubstringImpl<Type, RegexSubstringMatcher>::Exec(ctx, batch,
out,
matcher.get());
#else
@@ -1414,9 +1410,9 @@ struct MatchSubstring<Type, PlainStartsWithMatcher> {
#ifdef ARROW_WITH_RE2
MatchSubstringOptions converted_options = options;
converted_options.pattern = "^" + RE2::QuoteMeta(options.pattern);
- ARROW_ASSIGN_OR_RAISE(
- auto matcher,
- RegexSubstringMatcher::Make(converted_options,
/*is_utf8=*/Type::is_utf8));
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto matcher,
+ RegexSubstringMatcher::Make(converted_options,
is_utf8));
return MatchSubstringImpl<Type, RegexSubstringMatcher>::Exec(ctx, batch,
out,
matcher.get());
#else
@@ -1437,9 +1433,9 @@ struct MatchSubstring<Type, PlainEndsWithMatcher> {
#ifdef ARROW_WITH_RE2
MatchSubstringOptions converted_options = options;
converted_options.pattern = RE2::QuoteMeta(options.pattern) + "$";
- ARROW_ASSIGN_OR_RAISE(
- auto matcher,
- RegexSubstringMatcher::Make(converted_options,
/*is_utf8=*/Type::is_utf8));
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto matcher,
+ RegexSubstringMatcher::Make(converted_options,
is_utf8));
return MatchSubstringImpl<Type, RegexSubstringMatcher>::Exec(ctx, batch,
out,
matcher.get());
#else
@@ -1500,27 +1496,50 @@ std::string MakeLikeRegex(const MatchSubstringOptions&
options) {
return like_pattern;
}
+struct MatchLikeConstants {
+ // NOTE: avoid making those constants global to avoid compiling regexes at
startup
+ RE2::Options regex_options;
+ // A LIKE pattern matching this regex can be translated into a substring
search.
+ RE2 like_pattern_is_substring_match{R"(%+([^%_]*[^\\%_])?%+)",
regex_options};
+ // A LIKE pattern matching this regex can be translated into a prefix search.
+ RE2 like_pattern_is_starts_with{R"(([^%_]*[^\\%_])?%+)", regex_options};
+ // A LIKE pattern matching this regex can be translated into a suffix search.
+ RE2 like_pattern_is_ends_with{R"(%+([^%_]*))", regex_options};
+
+ static Result<const MatchLikeConstants*> Instance(bool is_utf8) {
+ static const auto constants = MakeAll();
+ return constants[is_utf8].Map([](const auto& ptr) { return ptr.get(); });
+ }
+
+ private:
+ static Result<std::unique_ptr<MatchLikeConstants>> Make(bool is_utf8) {
+ auto constants = std::unique_ptr<MatchLikeConstants>(new
MatchLikeConstants(is_utf8));
+ RETURN_NOT_OK(RegexStatus(constants->like_pattern_is_substring_match) &
+ RegexStatus(constants->like_pattern_is_starts_with) &
+ RegexStatus(constants->like_pattern_is_ends_with));
+ return constants;
+ }
+
+ static std::array<Result<std::unique_ptr<MatchLikeConstants>>, 2> MakeAll() {
+ return {Make(false), Make(true)};
+ }
+
+ explicit MatchLikeConstants(bool is_utf8) :
regex_options(MakeRE2Options(is_utf8)) {}
+};
+
// Evaluate a SQL-like LIKE pattern by translating it to a regexp or
// substring search as appropriate. See what Apache Impala does:
//
https://github.com/apache/impala/blob/9c38568657d62b6f6d7b10aa1c721ba843374dd8/be/src/exprs/like-predicate.cc
-template <typename StringType>
+template <typename PhysicalType>
struct MatchLike {
+ static_assert(!is_string_or_string_view(PhysicalType::type_id),
+ "should only codegen on physical types");
+
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
- // NOTE: avoid making those constants global to avoid compiling regexes at
startup
- static const RE2::Options kRE2Options = MakeRE2Options<StringType>();
- // A LIKE pattern matching this regex can be translated into a substring
search.
- static const RE2 kLikePatternIsSubstringMatch(R"(%+([^%_]*[^\\%_])?%+)",
kRE2Options);
- // A LIKE pattern matching this regex can be translated into a prefix
search.
- static const RE2 kLikePatternIsStartsWith(R"(([^%_]*[^\\%_])?%+)",
kRE2Options);
- // A LIKE pattern matching this regex can be translated into a suffix
search.
- static const RE2 kLikePatternIsEndsWith(R"(%+([^%_]*))", kRE2Options);
- static bool global_checked = false;
- if (ARROW_PREDICT_FALSE(!global_checked)) {
- RETURN_NOT_OK(RegexStatus(kLikePatternIsSubstringMatch));
- RETURN_NOT_OK(RegexStatus(kLikePatternIsStartsWith));
- RETURN_NOT_OK(RegexStatus(kLikePatternIsEndsWith));
- global_checked = true;
- }
+ ARROW_ASSIGN_OR_RAISE(
+ const auto like_constants,
+ MatchLikeConstants::Instance(
+ /*is_utf8=*/is_string_or_string_view(batch[0].type()->id())));
auto original_options = MatchSubstringState::Get(ctx);
auto original_state = ctx->state();
@@ -1530,24 +1549,29 @@ struct MatchLike {
bool matched = false;
if (!original_options.ignore_case) {
if ((matched = RE2::FullMatch(original_options.pattern,
- kLikePatternIsSubstringMatch, &pattern))) {
+
like_constants->like_pattern_is_substring_match,
+ &pattern))) {
MatchSubstringOptions converted_options{pattern,
original_options.ignore_case};
MatchSubstringState converted_state(converted_options);
ctx->SetState(&converted_state);
- status = MatchSubstring<StringType, PlainSubstringMatcher>::Exec(ctx,
batch, out);
+ status =
+ MatchSubstring<PhysicalType, PlainSubstringMatcher>::Exec(ctx,
batch, out);
} else if ((matched = RE2::FullMatch(original_options.pattern,
- kLikePatternIsStartsWith,
&pattern))) {
+
like_constants->like_pattern_is_starts_with,
+ &pattern))) {
MatchSubstringOptions converted_options{pattern,
original_options.ignore_case};
MatchSubstringState converted_state(converted_options);
ctx->SetState(&converted_state);
status =
- MatchSubstring<StringType, PlainStartsWithMatcher>::Exec(ctx,
batch, out);
+ MatchSubstring<PhysicalType, PlainStartsWithMatcher>::Exec(ctx,
batch, out);
} else if ((matched = RE2::FullMatch(original_options.pattern,
- kLikePatternIsEndsWith, &pattern)))
{
+
like_constants->like_pattern_is_ends_with,
+ &pattern))) {
MatchSubstringOptions converted_options{pattern,
original_options.ignore_case};
MatchSubstringState converted_state(converted_options);
ctx->SetState(&converted_state);
- status = MatchSubstring<StringType, PlainEndsWithMatcher>::Exec(ctx,
batch, out);
+ status =
+ MatchSubstring<PhysicalType, PlainEndsWithMatcher>::Exec(ctx,
batch, out);
}
}
@@ -1556,7 +1580,7 @@ struct MatchLike {
original_options.ignore_case};
MatchSubstringState converted_state(converted_options);
ctx->SetState(&converted_state);
- status = MatchSubstring<StringType, RegexSubstringMatcher>::Exec(ctx,
batch, out);
+ status = MatchSubstring<PhysicalType, RegexSubstringMatcher>::Exec(ctx,
batch, out);
}
ctx->SetState(original_state);
return status;
@@ -1616,7 +1640,8 @@ void AddAsciiStringMatchSubstring(FunctionRegistry*
registry) {
auto func = std::make_shared<ScalarFunction>("match_substring",
Arity::Unary(),
match_substring_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<MatchSubstring,
PlainSubstringMatcher>(ty);
+ auto exec =
+ GenerateTypeAgnosticVarBinaryBase<MatchSubstring,
PlainSubstringMatcher>(ty);
DCHECK_OK(
func->AddKernel({ty}, boolean(), std::move(exec),
MatchSubstringState::Init));
}
@@ -1627,7 +1652,7 @@ void AddAsciiStringMatchSubstring(FunctionRegistry*
registry) {
std::make_shared<ScalarFunction>("starts_with", Arity::Unary(),
starts_with_doc);
for (const auto& ty : BaseBinaryTypes()) {
auto exec =
- GenerateVarBinaryToVarBinary<MatchSubstring,
PlainStartsWithMatcher>(ty);
+ GenerateTypeAgnosticVarBinaryBase<MatchSubstring,
PlainStartsWithMatcher>(ty);
DCHECK_OK(
func->AddKernel({ty}, boolean(), std::move(exec),
MatchSubstringState::Init));
}
@@ -1637,7 +1662,8 @@ void AddAsciiStringMatchSubstring(FunctionRegistry*
registry) {
auto func =
std::make_shared<ScalarFunction>("ends_with", Arity::Unary(),
ends_with_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<MatchSubstring,
PlainEndsWithMatcher>(ty);
+ auto exec =
+ GenerateTypeAgnosticVarBinaryBase<MatchSubstring,
PlainEndsWithMatcher>(ty);
DCHECK_OK(
func->AddKernel({ty}, boolean(), std::move(exec),
MatchSubstringState::Init));
}
@@ -1648,7 +1674,8 @@ void AddAsciiStringMatchSubstring(FunctionRegistry*
registry) {
auto func = std::make_shared<ScalarFunction>("match_substring_regex",
Arity::Unary(),
match_substring_regex_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<MatchSubstring,
RegexSubstringMatcher>(ty);
+ auto exec =
+ GenerateTypeAgnosticVarBinaryBase<MatchSubstring,
RegexSubstringMatcher>(ty);
DCHECK_OK(
func->AddKernel({ty}, boolean(), std::move(exec),
MatchSubstringState::Init));
}
@@ -1658,7 +1685,7 @@ void AddAsciiStringMatchSubstring(FunctionRegistry*
registry) {
auto func =
std::make_shared<ScalarFunction>("match_like", Arity::Unary(),
match_like_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<MatchLike>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<MatchLike>(ty);
DCHECK_OK(
func->AddKernel({ty}, boolean(), std::move(exec),
MatchSubstringState::Init));
}
@@ -1714,24 +1741,30 @@ struct FindSubstringRegex {
};
#endif
-template <typename InputType>
+template <typename InputPhysicalType>
struct FindSubstringExec {
- using OffsetType = typename TypeTraits<InputType>::OffsetType;
+ using OffsetType = typename TypeTraits<InputPhysicalType>::OffsetType;
+
+ static_assert(!is_string_or_string_view(InputPhysicalType::type_id),
+ "should only codegen on physical types");
+
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
const MatchSubstringOptions& options = MatchSubstringState::Get(ctx);
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
if (options.ignore_case) {
#ifdef ARROW_WITH_RE2
ARROW_ASSIGN_OR_RAISE(auto matcher,
- FindSubstringRegex::Make(options,
InputType::is_utf8, true));
- applicator::ScalarUnaryNotNullStateful<OffsetType, InputType,
FindSubstringRegex>
+ FindSubstringRegex::Make(options, is_utf8, true));
+ applicator::ScalarUnaryNotNullStateful<OffsetType, InputPhysicalType,
+ FindSubstringRegex>
kernel{std::move(matcher)};
return kernel.Exec(ctx, batch, out);
#else
return Status::NotImplemented("ignore_case requires RE2");
#endif
}
- applicator::ScalarUnaryNotNullStateful<OffsetType, InputType,
FindSubstring> kernel{
- FindSubstring(PlainSubstringMatcher(options))};
+ applicator::ScalarUnaryNotNullStateful<OffsetType, InputPhysicalType,
FindSubstring>
+ kernel{FindSubstring(PlainSubstringMatcher(options))};
return kernel.Exec(ctx, batch, out);
}
};
@@ -1744,13 +1777,19 @@ const FunctionDoc find_substring_doc(
{"strings"}, "MatchSubstringOptions", /*options_required=*/true);
#ifdef ARROW_WITH_RE2
-template <typename InputType>
+template <typename InputPhysicalType>
struct FindSubstringRegexExec {
- using OffsetType = typename TypeTraits<InputType>::OffsetType;
+ using OffsetType = typename TypeTraits<InputPhysicalType>::OffsetType;
+
+ static_assert(!is_string_or_string_view(InputPhysicalType::type_id),
+ "should only codegen on physical types");
+
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
const MatchSubstringOptions& options = MatchSubstringState::Get(ctx);
- ARROW_ASSIGN_OR_RAISE(auto matcher, FindSubstringRegex::Make(options,
false));
- applicator::ScalarUnaryNotNullStateful<OffsetType, InputType,
FindSubstringRegex>
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto matcher, FindSubstringRegex::Make(options,
is_utf8));
+ applicator::ScalarUnaryNotNullStateful<OffsetType, InputPhysicalType,
+ FindSubstringRegex>
kernel{std::move(matcher)};
return kernel.Exec(ctx, batch, out);
}
@@ -1771,7 +1810,7 @@ void AddAsciiStringFindSubstring(FunctionRegistry*
registry) {
for (const auto& ty : BaseBinaryTypes()) {
auto offset_type = offset_bit_width(ty->id()) == 64 ? int64() : int32();
DCHECK_OK(func->AddKernel({ty}, offset_type,
-
GenerateVarBinaryToVarBinary<FindSubstringExec>(ty),
+
GenerateTypeAgnosticVarBinaryBase<FindSubstringExec>(ty),
MatchSubstringState::Init));
}
DCHECK_OK(func->AddKernel({InputType(Type::FIXED_SIZE_BINARY)}, int32(),
@@ -1785,9 +1824,10 @@ void AddAsciiStringFindSubstring(FunctionRegistry*
registry) {
find_substring_regex_doc);
for (const auto& ty : BaseBinaryTypes()) {
auto offset_type = offset_bit_width(ty->id()) == 64 ? int64() : int32();
- DCHECK_OK(func->AddKernel({ty}, offset_type,
-
GenerateVarBinaryToVarBinary<FindSubstringRegexExec>(ty),
- MatchSubstringState::Init));
+ DCHECK_OK(
+ func->AddKernel({ty}, offset_type,
+
GenerateTypeAgnosticVarBinaryBase<FindSubstringRegexExec>(ty),
+ MatchSubstringState::Init));
}
DCHECK_OK(func->AddKernel({InputType(Type::FIXED_SIZE_BINARY)}, int32(),
FindSubstringRegexExec<FixedSizeBinaryType>::Exec,
@@ -1865,8 +1905,8 @@ struct CountSubstringRegexExec {
using OffsetType = typename TypeTraits<InputType>::OffsetType;
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
const MatchSubstringOptions& options = MatchSubstringState::Get(ctx);
- ARROW_ASSIGN_OR_RAISE(
- auto counter, CountSubstringRegex::Make(options,
/*is_utf8=*/InputType::is_utf8));
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto counter, CountSubstringRegex::Make(options,
is_utf8));
applicator::ScalarUnaryNotNullStateful<OffsetType, InputType,
CountSubstringRegex>
kernel{std::move(counter)};
return kernel.Exec(ctx, batch, out);
@@ -1881,9 +1921,9 @@ struct CountSubstringExec {
const MatchSubstringOptions& options = MatchSubstringState::Get(ctx);
if (options.ignore_case) {
#ifdef ARROW_WITH_RE2
- ARROW_ASSIGN_OR_RAISE(
- auto counter, CountSubstringRegex::Make(options,
/*is_utf8=*/InputType::is_utf8,
- /*literal=*/true));
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto counter, CountSubstringRegex::Make(options,
is_utf8,
+
/*literal=*/true));
applicator::ScalarUnaryNotNullStateful<OffsetType, InputType,
CountSubstringRegex>
kernel{std::move(counter)};
return kernel.Exec(ctx, batch, out);
@@ -1920,7 +1960,7 @@ void AddAsciiStringCountSubstring(FunctionRegistry*
registry) {
for (const auto& ty : BaseBinaryTypes()) {
auto offset_type = offset_bit_width(ty->id()) == 64 ? int64() : int32();
DCHECK_OK(func->AddKernel({ty}, offset_type,
-
GenerateVarBinaryToVarBinary<CountSubstringExec>(ty),
+
GenerateTypeAgnosticVarBinaryBase<CountSubstringExec>(ty),
MatchSubstringState::Init));
}
DCHECK_OK(func->AddKernel({InputType(Type::FIXED_SIZE_BINARY)}, int32(),
@@ -1934,9 +1974,10 @@ void AddAsciiStringCountSubstring(FunctionRegistry*
registry) {
count_substring_regex_doc);
for (const auto& ty : BaseBinaryTypes()) {
auto offset_type = offset_bit_width(ty->id()) == 64 ? int64() : int32();
- DCHECK_OK(func->AddKernel({ty}, offset_type,
-
GenerateVarBinaryToVarBinary<CountSubstringRegexExec>(ty),
- MatchSubstringState::Init));
+ DCHECK_OK(
+ func->AddKernel({ty}, offset_type,
+
GenerateTypeAgnosticVarBinaryBase<CountSubstringRegexExec>(ty),
+ MatchSubstringState::Init));
}
DCHECK_OK(func->AddKernel({InputType(Type::FIXED_SIZE_BINARY)}, int32(),
CountSubstringRegexExec<FixedSizeBinaryType>::Exec,
@@ -1960,12 +2001,14 @@ struct ReplaceSubstring {
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
// TODO Cache replacer across invocations (for regex compilation)
- ARROW_ASSIGN_OR_RAISE(auto replacer,
Replacer::Make(ReplaceState::Get(ctx)));
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto replacer,
Replacer::Make(ReplaceState::Get(ctx), is_utf8));
return Replace(ctx, batch, *replacer, out);
}
+ template <typename ConcreteReplacer>
static Status Replace(KernelContext* ctx, const ExecSpan& batch,
- const Replacer& replacer, ExecResult* out) {
+ const ConcreteReplacer& replacer, ExecResult* out) {
ValueDataBuilder value_data_builder(ctx->memory_pool());
OffsetBuilder offset_builder(ctx->memory_pool());
@@ -1997,7 +2040,7 @@ struct PlainSubstringReplacer {
const ReplaceSubstringOptions& options_;
static Result<std::unique_ptr<PlainSubstringReplacer>> Make(
- const ReplaceSubstringOptions& options) {
+ const ReplaceSubstringOptions& options, bool is_utf8) {
return std::make_unique<PlainSubstringReplacer>(options);
}
@@ -2039,15 +2082,14 @@ struct PlainSubstringReplacer {
};
#ifdef ARROW_WITH_RE2
-template <typename Type>
struct RegexSubstringReplacer {
const ReplaceSubstringOptions& options_;
const RE2 regex_find_;
const RE2 regex_replacement_;
static Result<std::unique_ptr<RegexSubstringReplacer>> Make(
- const ReplaceSubstringOptions& options) {
- auto replacer = std::make_unique<RegexSubstringReplacer>(options);
+ const ReplaceSubstringOptions& options, bool is_utf8) {
+ auto replacer = std::make_unique<RegexSubstringReplacer>(options, is_utf8);
RETURN_NOT_OK(RegexStatus(replacer->regex_find_));
RETURN_NOT_OK(RegexStatus(replacer->regex_replacement_));
@@ -2064,10 +2106,10 @@ struct RegexSubstringReplacer {
// Using RE2::FindAndConsume we can only find the pattern if it is a group,
therefore
// we have 2 regexes, one with () around it, one without.
- explicit RegexSubstringReplacer(const ReplaceSubstringOptions& options)
+ explicit RegexSubstringReplacer(const ReplaceSubstringOptions& options, bool
is_utf8)
: options_(options),
- regex_find_("(" + options_.pattern + ")", MakeRE2Options<Type>()),
- regex_replacement_(options_.pattern, MakeRE2Options<Type>()) {}
+ regex_find_("(" + options_.pattern + ")", MakeRE2Options(is_utf8)),
+ regex_replacement_(options_.pattern, MakeRE2Options(is_utf8)) {}
Status ReplaceString(std::string_view s, TypedBufferBuilder<uint8_t>*
builder) const {
re2::StringPiece piece(s.data(), s.length());
@@ -2138,7 +2180,7 @@ const FunctionDoc replace_substring_doc(
#ifdef ARROW_WITH_RE2
template <typename Type>
-using ReplaceSubstringRegex = ReplaceSubstring<Type,
RegexSubstringReplacer<Type>>;
+using ReplaceSubstringRegex = ReplaceSubstring<Type, RegexSubstringReplacer>;
const FunctionDoc replace_substring_regex_doc(
"Replace matching non-overlapping substrings with replacement",
@@ -2155,7 +2197,7 @@ void AddAsciiStringReplaceSubstring(FunctionRegistry*
registry) {
auto func = std::make_shared<ScalarFunction>("replace_substring",
Arity::Unary(),
replace_substring_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<ReplaceSubstringPlain>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<ReplaceSubstringPlain>(ty);
ScalarKernel kernel{{ty}, ty, std::move(exec), ReplaceState::Init};
kernel.mem_allocation = MemAllocation::NO_PREALLOCATE;
DCHECK_OK(func->AddKernel(std::move(kernel)));
@@ -2167,7 +2209,7 @@ void AddAsciiStringReplaceSubstring(FunctionRegistry*
registry) {
auto func = std::make_shared<ScalarFunction>(
"replace_substring_regex", Arity::Unary(),
replace_substring_regex_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<ReplaceSubstringRegex>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<ReplaceSubstringRegex>(ty);
ScalarKernel kernel{{ty}, ty, std::move(exec), ReplaceState::Init};
kernel.mem_allocation = MemAllocation::NO_PREALLOCATE;
DCHECK_OK(func->AddKernel(std::move(kernel)));
@@ -2289,7 +2331,8 @@ struct ExtractRegex : public ExtractRegexBase {
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
ExtractRegexOptions options = ExtractRegexState::Get(ctx);
- ARROW_ASSIGN_OR_RAISE(auto data, ExtractRegexData::Make(options,
Type::is_utf8));
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
+ ARROW_ASSIGN_OR_RAISE(auto data, ExtractRegexData::Make(options, is_utf8));
return ExtractRegex(data).Extract(ctx, batch, out);
}
@@ -2346,7 +2389,7 @@ void AddAsciiStringExtractRegex(FunctionRegistry*
registry) {
for (const auto& ty : BaseBinaryTypes()) {
ScalarKernel kernel{{ty},
out_ty,
- GenerateVarBinaryToVarBinary<ExtractRegex>(ty),
+ GenerateTypeAgnosticVarBinaryBase<ExtractRegex>(ty),
ExtractRegexState::Init};
// Null values will be computed based on regex match or not
kernel.null_handling = NullHandling::COMPUTED_NO_PREALLOCATE;
@@ -2394,8 +2437,9 @@ struct ExtractRegexSpan : ExtractRegexBase {
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
auto options = OptionsWrapper<ExtractRegexSpanOptions>::Get(ctx);
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
ARROW_ASSIGN_OR_RAISE(auto data,
- ExtractRegexSpanData::Make(options.pattern,
Type::is_utf8));
+ ExtractRegexSpanData::Make(options.pattern,
is_utf8));
return ExtractRegexSpan{data}.Extract(ctx, batch, out);
}
@@ -2486,7 +2530,7 @@ void AddAsciiStringExtractRegexSpan(FunctionRegistry*
registry) {
OutputType output_type(ResolveExtractRegexSpanOutputType);
for (const auto& type : BaseBinaryTypes()) {
ScalarKernel kernel({type}, output_type,
- GenerateVarBinaryToVarBinary<ExtractRegexSpan>(type),
+
GenerateTypeAgnosticVarBinaryBase<ExtractRegexSpan>(type),
OptionsWrapper<ExtractRegexSpanOptions>::Init);
kernel.null_handling = NullHandling::COMPUTED_NO_PREALLOCATE;
kernel.mem_allocation = MemAllocation::NO_PREALLOCATE;
@@ -2789,7 +2833,7 @@ void AddAsciiStringSlice(FunctionRegistry* registry) {
auto func =
std::make_shared<ScalarFunction>("binary_slice", Arity::Unary(),
binary_slice_doc);
for (const auto& ty : BinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<SliceBytes>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<SliceBytes>(ty);
DCHECK_OK(
func->AddKernel({ty}, ty, std::move(exec),
SliceBytesTransform::State::Init));
}
@@ -2810,7 +2854,7 @@ using SplitPatternState =
OptionsWrapper<SplitPatternOptions>;
struct SplitPatternFinder : public StringSplitFinderBase<SplitPatternOptions> {
using Options = SplitPatternOptions;
- Status PreExec(const SplitPatternOptions& options) override {
+ Status PreExec(const SplitPatternOptions& options, bool is_utf8) override {
if (options.pattern.length() == 0) {
return Status::Invalid("Empty separator");
}
@@ -2876,7 +2920,7 @@ void AddAsciiStringSplitPattern(FunctionRegistry*
registry) {
auto func = std::make_shared<ScalarFunction>("split_pattern", Arity::Unary(),
split_pattern_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<SplitPatternExec, ListType>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<SplitPatternExec,
ListType>(ty);
DCHECK_OK(
func->AddKernel({ty}, {list(ty)}, std::move(exec),
SplitPatternState::Init));
}
@@ -2947,7 +2991,7 @@ void AddAsciiStringSplitWhitespace(FunctionRegistry*
registry) {
ascii_split_whitespace_doc,
&default_options);
for (const auto& ty : StringTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<SplitWhitespaceAsciiExec,
ListType>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<SplitWhitespaceAsciiExec,
ListType>(ty);
DCHECK_OK(func->AddKernel({ty}, {list(ty)}, std::move(exec),
StringSplitState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
@@ -2957,13 +3001,12 @@ void AddAsciiStringSplitWhitespace(FunctionRegistry*
registry) {
// Split by regex
#ifdef ARROW_WITH_RE2
-template <typename Type>
struct SplitRegexFinder : public StringSplitFinderBase<SplitPatternOptions> {
using Options = SplitPatternOptions;
std::unique_ptr<RE2> regex_split;
- Status PreExec(const SplitPatternOptions& options) override {
+ Status PreExec(const SplitPatternOptions& options, bool is_utf8) override {
if (options.reverse) {
return Status::NotImplemented("Cannot split in reverse with regex");
}
@@ -2973,7 +3016,8 @@ struct SplitRegexFinder : public
StringSplitFinderBase<SplitPatternOptions> {
pattern.reserve(options.pattern.size() + 2);
pattern += options.pattern;
pattern += ')';
- regex_split = std::make_unique<RE2>(pattern, MakeRE2Options<Type>());
+ regex_split = std::make_unique<RE2>(
+ pattern, MakeRE2Options(is_utf8, /*ignore_case=*/false,
/*literal=*/false));
return RegexStatus(*regex_split);
}
@@ -3000,7 +3044,7 @@ struct SplitRegexFinder : public
StringSplitFinderBase<SplitPatternOptions> {
};
template <typename Type, typename ListType>
-using SplitRegexExec = StringSplitExec<Type, ListType, SplitRegexFinder<Type>>;
+using SplitRegexExec = StringSplitExec<Type, ListType, SplitRegexFinder>;
const FunctionDoc split_pattern_regex_doc(
"Split string according to regex pattern",
@@ -3016,7 +3060,7 @@ void AddAsciiStringSplitRegex(FunctionRegistry* registry)
{
auto func = std::make_shared<ScalarFunction>("split_pattern_regex",
Arity::Unary(),
split_pattern_regex_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<SplitRegexExec, ListType>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<SplitRegexExec,
ListType>(ty);
DCHECK_OK(
func->AddKernel({ty}, {list(ty)}, std::move(exec),
SplitPatternState::Init));
}
@@ -3418,8 +3462,7 @@ const JoinOptions* GetDefaultJoinOptions() {
template <typename ListType>
void AddBinaryJoinForListType(ScalarFunction* func) {
for (const auto& ty : BaseBinaryTypes()) {
- auto exec =
- GenerateTypeAgnosticVarBinaryBase<BinaryJoin, ArrayKernelExec,
ListType>(*ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<BinaryJoin, ListType>(*ty);
auto list_ty = std::make_shared<ListType>(ty);
DCHECK_OK(func->AddKernel({InputType(list_ty), InputType(ty)}, ty,
std::move(exec)));
}
@@ -3560,9 +3603,12 @@ struct BinaryRepeatTransform : public
StringBinaryTransformBase<Type1, Type2> {
}
};
-template <typename Type1, typename Type2>
+template <typename Type1, typename Type2,
+ typename PhysicalType1 = typename Type1::PhysicalType,
+ typename PhysicalType2 = typename Type2::PhysicalType>
using BinaryRepeat =
- StringBinaryTransformExec<Type1, Type2, BinaryRepeatTransform<Type1,
Type2>>;
+ StringBinaryTransformExec<PhysicalType1, PhysicalType2,
+ BinaryRepeatTransform<PhysicalType1,
PhysicalType2>>;
const FunctionDoc binary_repeat_doc(
"Repeat a binary string",
@@ -3573,7 +3619,7 @@ void AddAsciiStringRepeat(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarCTypeToInt64Function>(
"binary_repeat", Arity::Binary(), binary_repeat_doc);
for (const auto& ty : BaseBinaryTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<BinaryRepeat, Int64Type>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<BinaryRepeat, Int64Type>(ty);
ScalarKernel kernel{{ty, int64()}, ty, exec};
DCHECK_OK(func->AddKernel(std::move(kernel)));
}
diff --git a/cpp/src/arrow/compute/kernels/scalar_string_internal.h
b/cpp/src/arrow/compute/kernels/scalar_string_internal.h
index 664f6b4c0b0..5c621e8d53f 100644
--- a/cpp/src/arrow/compute/kernels/scalar_string_internal.h
+++ b/cpp/src/arrow/compute/kernels/scalar_string_internal.h
@@ -21,6 +21,7 @@
#include "arrow/compute/api_scalar.h"
#include "arrow/compute/kernels/common_internal.h"
+#include "arrow/type_traits.h"
namespace arrow {
namespace compute {
@@ -68,10 +69,13 @@ static int64_t GetVarBinaryValuesLength(const ArraySpan&
span) {
///
/// and returns the number of codeunits of the `output` sequence or a negative
/// value if an invalid input sequence is detected.
-template <typename Type, typename StringTransform>
+template <typename PhysicalType, typename StringTransform>
struct StringTransformExecBase {
- using offset_type = typename Type::offset_type;
- using ArrayType = typename TypeTraits<Type>::ArrayType;
+ using offset_type = typename PhysicalType::offset_type;
+ using ArrayType = typename TypeTraits<PhysicalType>::ArrayType;
+
+ static_assert(!is_string_or_string_view(PhysicalType::type_id),
+ "should only codegen on physical types");
static Status Execute(KernelContext* ctx, StringTransform* transform,
const ExecSpan& batch, ExecResult* out) {
@@ -121,9 +125,11 @@ struct StringTransformExecBase {
}
};
-template <typename Type, typename StringTransform>
-struct StringTransformExec : public StringTransformExecBase<Type,
StringTransform> {
- using StringTransformExecBase<Type, StringTransform>::Execute;
+template <typename Type, typename StringTransform,
+ typename PhysicalType = typename Type::PhysicalType>
+struct StringTransformExec
+ : public StringTransformExecBase<PhysicalType, StringTransform> {
+ using StringTransformExecBase<PhysicalType, StringTransform>::Execute;
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
StringTransform transform;
@@ -132,11 +138,12 @@ struct StringTransformExec : public
StringTransformExecBase<Type, StringTransfor
}
};
-template <typename Type, typename StringTransform>
+template <typename Type, typename StringTransform,
+ typename PhysicalType = typename Type::PhysicalType>
struct StringTransformExecWithState
- : public StringTransformExecBase<Type, StringTransform> {
+ : public StringTransformExecBase<PhysicalType, StringTransform> {
using State = typename StringTransform::State;
- using StringTransformExecBase<Type, StringTransform>::Execute;
+ using StringTransformExecBase<PhysicalType, StringTransform>::Execute;
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
StringTransform transform(State::Get(ctx));
@@ -151,7 +158,7 @@ void MakeUnaryStringBatchKernel(
MemAllocation::type mem_allocation = MemAllocation::PREALLOCATE) {
auto func = std::make_shared<ScalarFunction>(name, Arity::Unary(),
std::move(doc));
for (const auto& ty : StringTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<ExecFunctor>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<ExecFunctor>(ty);
ScalarKernel kernel{{ty}, ty, std::move(exec)};
kernel.mem_allocation = mem_allocation;
ARROW_DCHECK_OK(func->AddKernel(std::move(kernel)));
@@ -216,6 +223,9 @@ static inline FunctionDoc StringClassifyDoc(std::string
class_summary,
template <typename Type, typename Predicate>
struct StringPredicateFunctor {
+ static_assert(!is_string_or_string_view(Type::type_id),
+ "should only codegen on physical types");
+
static Status Exec(KernelContext* ctx, const ExecSpan& batch, ExecResult*
out) {
Status st = Status::OK();
EnsureUtf8LookupTablesFilled();
@@ -237,7 +247,7 @@ void AddUnaryStringPredicate(std::string name,
FunctionRegistry* registry,
FunctionDoc doc) {
auto func = std::make_shared<ScalarFunction>(name, Arity::Unary(),
std::move(doc));
for (const auto& ty : StringTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<StringPredicateFunctor,
Predicate>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<StringPredicateFunctor,
Predicate>(ty);
ARROW_DCHECK_OK(func->AddKernel({ty}, boolean(), std::move(exec)));
}
ARROW_DCHECK_OK(registry->AddFunction(std::move(func)));
@@ -281,7 +291,7 @@ struct ReplaceStringSliceTransformBase : public
StringTransformBase {
template <typename Options>
struct StringSplitFinderBase {
virtual ~StringSplitFinderBase() = default;
- virtual Status PreExec(const Options& options) { return Status::OK(); }
+ virtual Status PreExec(const Options& options, bool is_utf8) { return
Status::OK(); }
// Derived classes should also define these methods:
// static bool Find(const uint8_t* begin, const uint8_t* end,
@@ -319,8 +329,9 @@ struct StringSplitExec {
}
Status Execute(KernelContext* ctx, const ExecSpan& batch, ExecResult* out) {
+ const bool is_utf8 = is_string_or_string_view(batch[0].type()->id());
SplitFinder finder;
- RETURN_NOT_OK(finder.PreExec(options));
+ RETURN_NOT_OK(finder.PreExec(options, is_utf8));
// TODO(wesm): refactor to not require creating ArrayData
const ArrayType input(batch[0].array.ToArrayData());
@@ -347,9 +358,11 @@ struct StringSplitExec {
*list_offsets++ = static_cast<list_offset_type>(builder.length());
}
// Assign string array to list child data
- std::shared_ptr<Array> string_array;
- RETURN_NOT_OK(builder.Finish(&string_array));
- output_list->child_data.push_back(string_array->data());
+ ARROW_ASSIGN_OR_RAISE(auto physical_array, builder.Finish());
+ // We got the physical type (e.g. binary instead of utf8), need to patch it
+ auto child_data = physical_array->data()->Copy();
+ child_data->type = batch[0].type()->GetSharedPtr();
+ output_list->child_data.push_back(std::move(child_data));
return Status::OK();
}
diff --git a/cpp/src/arrow/compute/kernels/scalar_string_test.cc
b/cpp/src/arrow/compute/kernels/scalar_string_test.cc
index b279f991f6e..bf5469ccd8b 100644
--- a/cpp/src/arrow/compute/kernels/scalar_string_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_string_test.cc
@@ -30,7 +30,9 @@
#include "arrow/testing/gtest_util.h"
#include "arrow/type.h"
#include "arrow/type_fwd.h"
+#include "arrow/type_traits.h"
#include "arrow/util/config.h"
+#include "arrow/util/type_traits.h"
#include "arrow/util/value_parsing.h"
#ifdef ARROW_WITH_UTF8PROC
@@ -336,6 +338,44 @@ TYPED_TEST(TestBinaryKernels, NonUtf8Regex) {
{"letter": [1, 1], "digit": [2, 1]}])",
&options);
}
+
+ // On non-UTF8 input types, case insensitive matches should only account for
+ // ASCII letters.
+ // (this stresses that the "is_utf8" option is passed down correctly to RE2)
+ auto letters_array = this->MakeArray({"1ab2AB3", "1àb2ÀB3"});
+ {
+ MatchSubstringOptions options("(?i)(A|À)B");
+ // 5 is the byte index of "ÀB" in "1àb2ÀB3"
+ this->CheckUnary("find_substring_regex", letters_array,
this->offset_type(), "[1, 5]",
+ &options);
+ this->CheckUnary("count_substring_regex", letters_array,
this->offset_type(),
+ "[2, 1]", &options);
+ this->CheckUnary("match_substring_regex", letters_array, boolean(),
"[true, true]",
+ &options);
+ }
+ {
+ SplitPatternOptions options("(?i)(A|À)B");
+ this->CheckUnary("split_pattern_regex", letters_array, list(this->type()),
+ R"([["1", "2", "3"], ["1àb2", "3"]])", &options);
+ }
+ {
+ ReplaceSubstringOptions options("(?i)(A|À)B", "XY");
+ this->CheckUnary("replace_substring_regex", letters_array,
+ this->MakeArray({"1XY2XY3", "1àb2XY3"}), &options);
+ }
+ {
+ ExtractRegexOptions options("(?P<letters>(?i:(?:A|À)B))");
+ auto out_type = struct_({{"letters", this->type()}});
+ this->CheckUnary("extract_regex", letters_array, out_type,
+ R"([{"letters": "ab"}, {"letters": "ÀB"}])", &options);
+ }
+ {
+ ExtractRegexSpanOptions options("(?P<letters>(?i:(?:A|À)B))");
+ auto out_type = struct_({{"letters", fixed_size_list(this->offset_type(),
2)}});
+ // 5 is the byte index of "ÀB" in "1àb2ÀB3", 3 is its byte length
+ this->CheckUnary("extract_regex_span", letters_array, out_type,
+ R"([{"letters": [1, 2]}, {"letters": [5, 3]}])",
&options);
+ }
}
TYPED_TEST(TestBinaryKernels, NonUtf8WithNullRegex) {
@@ -509,6 +549,16 @@ TYPED_TEST(TestBaseBinaryKernels, FindSubstringIgnoreCase)
{
this->CheckUnary("find_substring",
R"-(["?aB)c", "acb", "c?Ab)", null, "?aBc", "AB)"])-",
this->offset_type(), "[0, -1, 1, null, -1, -1]", &options);
+ options.pattern = "?àb";
+ if (is_string_or_string_view(this->type()->id())) {
+ this->CheckUnary("find_substring",
+ R"-(["?àB)c", "àcb", "c?Àb)", null, "(?àBc", "ÀB)"])-",
+ this->offset_type(), "[0, -1, 1, null, 1, -1]", &options);
+ } else {
+ this->CheckUnary("find_substring",
+ R"-(["?àB)c", "àcb", "c?Àb)", null, "(?àBc", "ÀB)"])-",
+ this->offset_type(), "[0, -1, -1, null, 1, -1]",
&options);
+ }
}
TYPED_TEST(TestBaseBinaryKernels, FindSubstringRegex) {
@@ -597,6 +647,18 @@ TYPED_TEST(TestBaseBinaryKernels,
CountSubstringIgnoreCase) {
MatchSubstringOptions options_empty{"", /*ignore_case=*/true};
this->CheckUnary("count_substring", R"(["", null, "abc"])",
this->offset_type(),
"[1, null, 4]", &options_empty);
+
+ // case-folding is Unicode on string types, ASCII-only on binary types.
+ options.pattern = "àb";
+ if (is_string_or_string_view(this->type()->id())) {
+ this->CheckUnary("count_substring",
+ R"(["", null, "àb", "àBà", "bÀbÀ", "ÀbàbÀ", "bÀbÀcàbà"])",
+ this->offset_type(), "[0, null, 1, 1, 1, 2, 2]",
&options);
+ } else {
+ this->CheckUnary("count_substring",
+ R"(["", null, "àb", "àBà", "bÀbÀ", "ÀbàbÀ", "bÀbÀcàbà"])",
+ this->offset_type(), "[0, null, 1, 1, 0, 1, 1]",
&options);
+ }
}
TYPED_TEST(TestBaseBinaryKernels, CountSubstringRegexIgnoreCase) {
@@ -607,6 +669,17 @@ TYPED_TEST(TestBaseBinaryKernels,
CountSubstringRegexIgnoreCase) {
MatchSubstringOptions options_empty_match{"a*", /*ignore_case=*/true};
this->CheckUnary("count_substring_regex", R"(["", "bacAaAdaAaA", "c",
"AAA"])",
this->offset_type(), "[1, 7, 2, 2]", &options_empty_match);
+
+ // case-folding is Unicode on string types, ASCII-only on binary types.
+ options_as.pattern = "à+";
+ if (is_string_or_string_view(this->type()->id())) {
+ this->CheckUnary("count_substring_regex", R"(["", "bàcÀàÀdàÀàÀ", "àà",
"ÀÀÀ"])",
+ this->offset_type(), "[0, 3, 1, 1]", &options_as);
+ } else {
+ // In non-UTF8 mode, "à" is a two-character sequence, so "à+" does not
match "àà".
+ this->CheckUnary("count_substring_regex", R"(["", "bàcÀàÀdàÀàÀ", "àà",
"ÀÀÀ"])",
+ this->offset_type(), "[0, 4, 2, 0]", &options_as);
+ }
}
#else
TYPED_TEST(TestBaseBinaryKernels, CountSubstringIgnoreCase) {
@@ -1041,6 +1114,48 @@ TEST_F(TestFixedSizeBinaryKernels,
FindSubstringIgnoreCase) {
}
#endif
+#ifdef ARROW_WITH_RE2
+TYPED_TEST(TestStringKernels, Utf8Regex) {
+ // On UTF8 input types, case insensitive matches should account for all
letters.
+ // (this stresses that the "is_utf8" option is passed down correctly to RE2)
+ auto letters_array = this->MakeArray({"🙂ab2AB3", "🙂àb2ÀB3"});
+ {
+ MatchSubstringOptions options("(?i)(A|À)B");
+ // 4 is the byte index of "ab" in "🙂ab2AB3"
+ this->CheckUnary("find_substring_regex", letters_array,
this->offset_type(), "[4, 4]",
+ &options);
+ this->CheckUnary("count_substring_regex", letters_array,
this->offset_type(),
+ "[2, 2]", &options);
+ this->CheckUnary("match_substring_regex", letters_array, boolean(),
"[true, true]",
+ &options);
+ }
+ {
+ SplitPatternOptions options("(?i)(A|À)B");
+ this->CheckUnary("split_pattern_regex", letters_array, list(this->type()),
+ R"([["🙂", "2", "3"], ["🙂", "2", "3"]])", &options);
+ }
+ {
+ ReplaceSubstringOptions options("(?i)(A|À)B", "XY");
+ this->CheckUnary("replace_substring_regex", letters_array,
+ this->MakeArray({"🙂XY2XY3", "🙂XY2XY3"}), &options);
+ }
+ {
+ ExtractRegexOptions options("(?P<letters>(?i:(?:A|À)B))");
+ auto out_type = struct_({{"letters", this->type()}});
+ this->CheckUnary("extract_regex", letters_array, out_type,
+ R"([{"letters": "ab"}, {"letters": "àb"}])", &options);
+ }
+ {
+ ExtractRegexSpanOptions options("(?P<letters>(?i:(?:A|À)B))");
+ auto out_type = struct_({{"letters", fixed_size_list(this->offset_type(),
2)}});
+ // 4 is the byte index of "ab" in "🙂ab2AB3"
+ // 3 is the byte length of "àb"
+ this->CheckUnary("extract_regex_span", letters_array, out_type,
+ R"([{"letters": [4, 2]}, {"letters": [4, 3]}])",
&options);
+ }
+}
+#endif
+
TYPED_TEST(TestStringKernels, AsciiUpper) {
this->CheckUnary("ascii_upper", "[]", this->type(), "[]");
this->CheckUnary("ascii_upper", "[\"aAazZæÆ&\", null, \"\", \"bbb\"]",
this->type(),
@@ -1556,11 +1671,17 @@ TYPED_TEST(TestBaseBinaryKernels, MatchSubstring) {
}
#ifdef ARROW_WITH_RE2
-TYPED_TEST(TestStringKernels, MatchSubstringIgnoreCase) {
+TYPED_TEST(TestBaseBinaryKernels, MatchSubstringIgnoreCase) {
MatchSubstringOptions options_insensitive{"aé(", /*ignore_case=*/true};
- this->CheckUnary("match_substring", R"(["abc", "aEb", "baÉ(", "aé(", "ae(",
"Aé("])",
- boolean(), "[false, false, true, true, false, true]",
- &options_insensitive);
+ if (is_string_or_string_view(this->type()->id())) {
+ this->CheckUnary("match_substring", R"(["abc", "aEb", "baÉ(", "aé(",
"ae(", "Aé("])",
+ boolean(), "[false, false, true, true, false, true]",
+ &options_insensitive);
+ } else {
+ this->CheckUnary("match_substring", R"(["abc", "aEb", "baÉ(", "aé(",
"ae(", "Aé("])",
+ boolean(), "[false, false, false, true, false, true]",
+ &options_insensitive);
+ }
}
#else
TYPED_TEST(TestBaseBinaryKernels, MatchSubstringIgnoreCase) {
@@ -1598,6 +1719,15 @@ TYPED_TEST(TestBaseBinaryKernels,
MatchStartsWithIgnoreCase) {
boolean(), "[null, false, false, true, false, true]",
&options);
this->CheckUnary("starts_with", R"(["ABAB", "$ABAB", "ABAB$", "$AbAb",
"aBaB$"])",
boolean(), "[true, false, true, false, true]", &options);
+
+ options.pattern = "àBÀb";
+ if (is_string_or_string_view(this->type()->id())) {
+ this->CheckUnary("starts_with", R"(["ÀBÀB", "$ÀBÀB", "ÀBÀB$", "$ÀbÀb",
"àBÀB$"])",
+ boolean(), "[true, false, true, false, true]", &options);
+ } else {
+ this->CheckUnary("starts_with", R"(["ÀBÀB", "$ÀBÀB", "ÀBÀB$", "$ÀbÀb",
"àBÀB$"])",
+ boolean(), "[false, false, false, false, true]",
&options);
+ }
}
TYPED_TEST(TestBaseBinaryKernels, MatchEndsWithIgnoreCase) {
@@ -1607,6 +1737,15 @@ TYPED_TEST(TestBaseBinaryKernels,
MatchEndsWithIgnoreCase) {
boolean(), "[null, false, false, true, true, false]",
&options);
this->CheckUnary("ends_with", R"(["ABAB", "$ABAB", "ABAB$", "$AbAb",
"aBaB$"])",
boolean(), "[true, true, false, true, false]", &options);
+
+ options.pattern = "àBÀb";
+ if (is_string_or_string_view(this->type()->id())) {
+ this->CheckUnary("ends_with", R"(["ÀBÀB", "$àBÀB", "ÀBÀB$", "$ÀbÀb",
"àBÀB$"])",
+ boolean(), "[true, true, false, true, false]", &options);
+ } else {
+ this->CheckUnary("ends_with", R"(["ÀBÀB", "$àBÀB", "ÀBÀB$", "$ÀbÀb",
"àBÀB$"])",
+ boolean(), "[false, true, false, false, false]",
&options);
+ }
}
#else
TYPED_TEST(TestBaseBinaryKernels, MatchStartsWithIgnoreCase) {
@@ -1670,6 +1809,23 @@ TYPED_TEST(TestBaseBinaryKernels,
MatchSubstringRegexInvalid) {
CallFunction("match_substring_regex", {input}, &options));
}
+TYPED_TEST(TestBinaryKernels, MatchLikeIgnoreCase) {
+ // Case-folding is ASCII-only for binary types
+ MatchSubstringOptions insensitive_substring{"%e%", /*ignore_case=*/true};
+ this->CheckUnary("match_like", R"(["fooebar", "fooEbar", "é"])", boolean(),
+ "[true, true, false]", &insensitive_substring);
+ insensitive_substring.pattern = "%é%";
+ this->CheckUnary("match_like", R"(["fooébar", "fooÉbar", "e"])", boolean(),
+ "[true, false, false]", &insensitive_substring);
+
+ MatchSubstringOptions insensitive_regex{"_e%", /*ignore_case=*/true};
+ this->CheckUnary("match_like", R"(["aefoo", "aEfoo", "efoo"])", boolean(),
+ "[true, true, false]", &insensitive_regex);
+ insensitive_regex.pattern = "_é%";
+ this->CheckUnary("match_like", R"(["aéfoo", "aÉfoo", "éfoo"])", boolean(),
+ "[true, false, false]", &insensitive_regex);
+}
+
TYPED_TEST(TestStringKernels, MatchLike) {
auto inputs = R"(["foo", "bar", "foobar", "barfoo", "o", "\nfoo", "foo\n",
null])";
diff --git a/cpp/src/arrow/compute/kernels/scalar_string_utf8.cc
b/cpp/src/arrow/compute/kernels/scalar_string_utf8.cc
index fd340bba624..582d559dde2 100644
--- a/cpp/src/arrow/compute/kernels/scalar_string_utf8.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_string_utf8.cc
@@ -45,7 +45,7 @@ void MakeUnaryStringUTF8TransformKernel(std::string name,
FunctionRegistry* regi
FunctionDoc doc) {
auto func = std::make_shared<ScalarFunction>(name, Arity::Unary(),
std::move(doc));
for (const auto& ty : StringTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<Transformer>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<Transformer>(ty);
DCHECK_OK(func->AddKernel({ty}, ty, std::move(exec)));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
@@ -1144,7 +1144,7 @@ void AddUtf8StringReplaceSlice(FunctionRegistry*
registry) {
utf8_replace_slice_doc);
for (const auto& ty : StringTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<Utf8ReplaceSlice>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<Utf8ReplaceSlice>(ty);
DCHECK_OK(func->AddKernel({ty}, ty, std::move(exec),
ReplaceStringSliceTransformBase::State::Init));
}
@@ -1340,7 +1340,7 @@ void AddUtf8StringSlice(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("utf8_slice_codeunits",
Arity::Unary(),
utf8_slice_codeunits_doc);
for (const auto& ty : StringTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<SliceCodeunits>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<SliceCodeunits>(ty);
DCHECK_OK(
func->AddKernel({ty}, ty, std::move(exec),
SliceCodeunitsTransform::State::Init));
}
@@ -1355,7 +1355,8 @@ void AddUtf8StringSlice(FunctionRegistry* registry) {
struct SplitWhitespaceUtf8Finder : public StringSplitFinderBase<SplitOptions> {
using Options = SplitOptions;
- Status PreExec(const SplitOptions& options) override {
+ Status PreExec(const SplitOptions& options, bool is_utf8) override {
+ DCHECK(is_utf8);
EnsureUtf8LookupTablesFilled();
return Status::OK();
}
@@ -1426,7 +1427,7 @@ void AddUtf8StringSplitWhitespace(FunctionRegistry*
registry) {
std::make_shared<ScalarFunction>("utf8_split_whitespace", Arity::Unary(),
utf8_split_whitespace_doc,
&default_options);
for (const auto& ty : StringTypes()) {
- auto exec = GenerateVarBinaryToVarBinary<SplitWhitespaceUtf8Exec,
ListType>(ty);
+ auto exec = GenerateTypeAgnosticVarBinaryBase<SplitWhitespaceUtf8Exec,
ListType>(ty);
DCHECK_OK(func->AddKernel({ty}, {list(ty)}, std::move(exec),
StringSplitState::Init));
}
DCHECK_OK(registry->AddFunction(std::move(func)));
diff --git a/cpp/src/arrow/compute/kernels/vector_array_sort.cc
b/cpp/src/arrow/compute/kernels/vector_array_sort.cc
index ad9444037c0..1cfb11286e4 100644
--- a/cpp/src/arrow/compute/kernels/vector_array_sort.cc
+++ b/cpp/src/arrow/compute/kernels/vector_array_sort.cc
@@ -586,9 +586,8 @@ void AddArraySortingKernels(VectorKernel base,
VectorFunction* func) {
DCHECK_OK(func->AddKernel(base));
}
for (const auto& ty : BaseBinaryTypes()) {
- auto physical_type = GetPhysicalType(ty);
base.signature = KernelSignature::Make({ty}, uint64());
- base.exec = GenerateVarBinaryBase<ExecTemplate,
UInt64Type>(*physical_type);
+ base.exec = GenerateVarBinaryBase<ExecTemplate, UInt64Type>(ty);
DCHECK_OK(func->AddKernel(base));
}
base.signature = KernelSignature::Make({Type::FIXED_SIZE_BINARY}, uint64());
diff --git a/cpp/src/arrow/compute/kernels/vector_replace.cc
b/cpp/src/arrow/compute/kernels/vector_replace.cc
index ae39977aaea..6a9abfc0396 100644
--- a/cpp/src/arrow/compute/kernels/vector_replace.cc
+++ b/cpp/src/arrow/compute/kernels/vector_replace.cc
@@ -857,11 +857,10 @@ void RegisterVectorFunction(FunctionRegistry* registry,
Functor<FixedSizeBinaryType>::Exec,
ChunkedFunctor<FixedSizeBinaryType>::Exec,
registry, func.get());
for (const auto& ty : BaseBinaryTypes()) {
- AddKernel(
- ty->id(), Functor<FixedSizeBinaryType>::GetSignature(ty->id()),
- GenerateTypeAgnosticVarBinaryBase<Functor, ArrayKernelExec>(*ty),
- GenerateTypeAgnosticVarBinaryBase<ChunkedFunctor,
VectorKernel::ChunkedExec>(*ty),
- registry, func.get());
+ AddKernel(ty->id(), Functor<FixedSizeBinaryType>::GetSignature(ty->id()),
+ GenerateTypeAgnosticVarBinaryBase<Functor>(*ty),
+ GenerateTypeAgnosticVarBinaryBase<ChunkedFunctor>(*ty), registry,
+ func.get());
}
// TODO: list types
DCHECK_OK(registry->AddFunction(std::move(func)));