Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cpp/src/arrow/array/array_binary_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -301,14 +301,18 @@ class TestStringArray : public ::testing::Test {
auto st2 = ValidateFull(1, {0, 4}, "\xf4\x90\x80\x80");
// Single UTF8 character straddles two entries
auto st3 = ValidateFull(2, {0, 1, 2}, "\xc3\xa9");
// Null characters in the string
auto st4 = ValidateFull(1, {0, 4}, "\0\0\0\0");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this is invalid? Unicode character 0 is a valid unicode character.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it works using PyArrow:

>>> arr = pa.array(["\0"])
>>> arr
<pyarrow.lib.StringArray object at 0x7f6befb354b0>
[
  ""
]
>>> arr.to_pylist()
['\x00']
>>> arr.validate(full=True)
>>> 

@edponce edponce Mar 24, 2021

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, this case is valid. The UTF-8 standard (https://tools.ietf.org/html/rfc3629) states that characters consisting of 1-byte range from 0-7F (Section 3). But a multibyte UTF-8 character will not contain NUL as a continuation byte, the encoding does not allows it.
Valid: '\x00'
Invalid: '\xe10080'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (T::is_utf8) {
ASSERT_RAISES(Invalid, st1);
ASSERT_RAISES(Invalid, st2);
ASSERT_RAISES(Invalid, st3);
ASSERT_RAISES(Invalid, st4);
} else {
ASSERT_OK(st1);
ASSERT_OK(st2);
ASSERT_OK(st3);
ASSERT_OK(st4);
}
}

Expand Down
48 changes: 47 additions & 1 deletion cpp/src/arrow/compute/kernels/scalar_string.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ struct BinaryLength {

#ifdef ARROW_WITH_UTF8PROC

struct Utf8Length {
template <typename OutValue, typename Arg0Value = util::string_view>
static OutValue Call(KernelContext*, Arg0Value val) {
auto str = reinterpret_cast<const utf8proc_uint8_t*>(val.data());
auto strlen = static_cast<utf8proc_ssize_t>(val.size());
utf8proc_int32_t codepoint;

OutValue length = 0;
while (strlen > 0) {
auto char_width = utf8proc_iterate(str, strlen, &codepoint);
Comment thread
pitrou marked this conversation as resolved.
Outdated
// XXX check for errmsg?
str += char_width;
strlen -= char_width;
++length;
}
return length;
}
};

// Direct lookup tables for unicode properties
constexpr uint32_t kMaxCodepointLookup =
0xffff; // up to this codepoint is in a lookup table
Expand Down Expand Up @@ -1569,9 +1588,16 @@ const FunctionDoc strptime_doc(

const FunctionDoc binary_length_doc(
"Compute string lengths",
("For each string in `strings`, emit its length. Null values emit null."),
("For each string in `strings`, emit the number of bytes. Null values emit null."),
{"strings"});

#ifdef ARROW_WITH_UTF8PROC
const FunctionDoc utf8_length_doc("Compute utf8 string lengths",
("For each string in `strings`, emit the number of "
"utf8 characters. Null values emit null."),
{"strings"});
#endif // ARROW_WITH_UTF8PROC

void AddStrptime(FunctionRegistry* registry) {
auto func = std::make_shared<ScalarFunction>("strptime", Arity::Unary(), &strptime_doc);
DCHECK_OK(func->AddKernel({utf8()}, OutputType(StrptimeResolve),
Expand All @@ -1597,6 +1623,23 @@ void AddBinaryLength(FunctionRegistry* registry) {
DCHECK_OK(registry->AddFunction(std::move(func)));
}

#ifdef ARROW_WITH_UTF8PROC
void AddUtf8Length(FunctionRegistry* registry) {
auto func =
std::make_shared<ScalarFunction>("utf8_length", Arity::Unary(), &utf8_length_doc);

ArrayKernelExec exec_offset_32 =
applicator::ScalarUnaryNotNull<Int32Type, StringType, Utf8Length>::Exec;
DCHECK_OK(func->AddKernel({utf8()}, int32(), std::move(exec_offset_32)));

ArrayKernelExec exec_offset_64 =
applicator::ScalarUnaryNotNull<Int64Type, LargeStringType, Utf8Length>::Exec;
DCHECK_OK(func->AddKernel({large_utf8()}, int64(), std::move(exec_offset_64)));

DCHECK_OK(registry->AddFunction(std::move(func)));
}
#endif // ARROW_WITH_UTF8PROC

template <template <typename> class ExecFunctor>
void MakeUnaryStringBatchKernel(
std::string name, FunctionRegistry* registry, const FunctionDoc* doc,
Expand Down Expand Up @@ -1866,6 +1909,9 @@ void RegisterScalarStringAscii(FunctionRegistry* registry) {

AddSplit(registry);
AddBinaryLength(registry);
#ifdef ARROW_WITH_UTF8PROC
AddUtf8Length(registry);
#endif
AddMatchSubstring(registry);
AddStrptime(registry);
}
Expand Down
9 changes: 7 additions & 2 deletions cpp/src/arrow/compute/kernels/scalar_string_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ class TestBinaryKernels : public BaseTestStringKernels<TestType> {};
TYPED_TEST_SUITE(TestBinaryKernels, BinaryTypes);

TYPED_TEST(TestBinaryKernels, BinaryLength) {
this->CheckUnary("binary_length", R"(["aaa", null, "", "b"])", this->offset_type(),
"[3, null, 0, 1]");
this->CheckUnary("binary_length", R"(["aaa", null, "áéíóú", "", "b"])",
this->offset_type(), "[3, null, 10, 0, 1]");
}

template <typename TestType>
Expand Down Expand Up @@ -103,6 +103,11 @@ TEST(TestStringKernels, LARGE_MEMORY_TEST(Utf8Upper32bitGrowth)) {

#ifdef ARROW_WITH_UTF8PROC

TYPED_TEST(TestStringKernels, Utf8Length) {
this->CheckUnary("utf8_length", R"(["aaa", null, "áéíóú", "", "b"])",
this->offset_type(), "[3, null, 5, 0, 1]");
Comment thread
pitrou marked this conversation as resolved.
Outdated
}

TYPED_TEST(TestStringKernels, Utf8Upper) {
this->CheckUnary("utf8_upper", "[\"aAazZæÆ&\", null, \"\", \"b\"]", this->type(),
"[\"AAAZZÆÆ&\", null, \"\", \"B\"]");
Expand Down
11 changes: 8 additions & 3 deletions docs/source/cpp/compute.rst
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,11 @@ String transforms
+--------------------------+------------+-------------------------+---------------------+---------+
| binary_length | Unary | Binary- or String-like | Int32 or Int64 | \(2) |
+--------------------------+------------+-------------------------+---------------------+---------+
| utf8_lower | Unary | String-like | String-like | \(3) |
| utf8_length | Unary | String-like | Int32 or Int64 | \(3) |
+--------------------------+------------+-------------------------+---------------------+---------+
| utf8_upper | Unary | String-like | String-like | \(3) |
| utf8_lower | Unary | String-like | String-like | \(4) |
+--------------------------+------------+-------------------------+---------------------+---------+
| utf8_upper | Unary | String-like | String-like | \(4) |
+--------------------------+------------+-------------------------+---------------------+---------+


Expand All @@ -447,7 +449,10 @@ String transforms
* \(2) Output is the physical length in bytes of each input element. Output
type is Int32 for Binary / String, Int64 for LargeBinary / LargeString.

* \(3) Each UTF8-encoded character in the input is converted to lowercase or
* \(3) Output is the number of characters (not bytes) of each input element.
Output type is Int32 for String, Int64 for LargeString.

* \(4) Each UTF8-encoded character in the input is converted to lowercase or
uppercase.


Expand Down