You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by pr...@apache.org on 2021/06/27 15:29:20 UTC
[arrow] branch master updated: ARROW-12378: [C++][Gandiva]
Implement castVARBINARY functions
This is an automated email from the ASF dual-hosted git repository.
praveenbingo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/master by this push:
new 6220ddd ARROW-12378: [C++][Gandiva] Implement castVARBINARY functions
6220ddd is described below
commit 6220ddd35aa2318792ada83dadd1679d13787fc2
Author: João Pedro <jo...@simbioseventures.com>
AuthorDate: Sun Jun 27 20:56:34 2021 +0530
ARROW-12378: [C++][Gandiva] Implement castVARBINARY functions
Implement the following functions:
- castVARBINARY(varchar, len)
- castVARBINARY(varbinary, len)
- castVARBINARY(float/double/int/bigint, bigint len)
Closes #10023 from jpedroantunes/feature/add-cast-varbinary and squashes the following commits:
51173737e <João Pedro> Add base implementation for castVARBINARY methods and the respective tests
Authored-by: João Pedro <jo...@simbioseventures.com>
Signed-off-by: Praveen <pr...@dremio.com>
---
cpp/src/gandiva/function_registry_string.cc | 24 +++
cpp/src/gandiva/gdv_function_stubs.cc | 199 ++++++++++++++++---------
cpp/src/gandiva/gdv_function_stubs.h | 8 +
cpp/src/gandiva/gdv_function_stubs_test.cc | 57 +++++++
cpp/src/gandiva/precompiled/string_ops.cc | 26 ++++
cpp/src/gandiva/precompiled/string_ops_test.cc | 48 ++++++
cpp/src/gandiva/precompiled/types.h | 8 +
7 files changed, 298 insertions(+), 72 deletions(-)
diff --git a/cpp/src/gandiva/function_registry_string.cc b/cpp/src/gandiva/function_registry_string.cc
index e8c0739..5218b1c 100644
--- a/cpp/src/gandiva/function_registry_string.cc
+++ b/cpp/src/gandiva/function_registry_string.cc
@@ -333,6 +333,30 @@ std::vector<NativeFunction> GetStringFunctionRegistry() {
kResultNullIfNull, "right_utf8_int32",
NativeFunction::kNeedsContext),
+ NativeFunction("castVARBINARY", {}, DataTypeVector{binary(), int64()}, binary(),
+ kResultNullIfNull, "castVARBINARY_binary_int64",
+ NativeFunction::kNeedsContext | NativeFunction::kCanReturnErrors),
+
+ NativeFunction("castVARBINARY", {}, DataTypeVector{utf8(), int64()}, binary(),
+ kResultNullIfNull, "castVARBINARY_utf8_int64",
+ NativeFunction::kNeedsContext | NativeFunction::kCanReturnErrors),
+
+ NativeFunction("castVARBINARY", {}, DataTypeVector{int32(), int64()}, binary(),
+ kResultNullIfNull, "gdv_fn_castVARBINARY_int32_int64",
+ NativeFunction::kNeedsContext | NativeFunction::kCanReturnErrors),
+
+ NativeFunction("castVARBINARY", {}, DataTypeVector{int64(), int64()}, binary(),
+ kResultNullIfNull, "gdv_fn_castVARBINARY_int64_int64",
+ NativeFunction::kNeedsContext | NativeFunction::kCanReturnErrors),
+
+ NativeFunction("castVARBINARY", {}, DataTypeVector{float32(), int64()}, binary(),
+ kResultNullIfNull, "gdv_fn_castVARBINARY_float32_int64",
+ NativeFunction::kNeedsContext | NativeFunction::kCanReturnErrors),
+
+ NativeFunction("castVARBINARY", {}, DataTypeVector{float64(), int64()}, binary(),
+ kResultNullIfNull, "gdv_fn_castVARBINARY_float64_int64",
+ NativeFunction::kNeedsContext | NativeFunction::kCanReturnErrors),
+
NativeFunction("split_part", {}, DataTypeVector{utf8(), utf8(), int32()}, utf8(),
kResultNullIfNull, "split_part",
NativeFunction::kNeedsContext | NativeFunction::kCanReturnErrors)};
diff --git a/cpp/src/gandiva/gdv_function_stubs.cc b/cpp/src/gandiva/gdv_function_stubs.cc
index 26b8654..38c31a8 100644
--- a/cpp/src/gandiva/gdv_function_stubs.cc
+++ b/cpp/src/gandiva/gdv_function_stubs.cc
@@ -333,83 +333,90 @@ CAST_NUMERIC_FROM_STRING(double, arrow::DoubleType, FLOAT8)
#undef CAST_NUMERIC_FROM_STRING
-#define GDV_FN_CAST_VARCHAR_INTEGER(IN_TYPE, ARROW_TYPE) \
- GANDIVA_EXPORT \
- const char* gdv_fn_castVARCHAR_##IN_TYPE##_int64(int64_t context, gdv_##IN_TYPE value, \
- int64_t len, int32_t * out_len) { \
- if (len < 0) { \
- gdv_fn_context_set_error_msg(context, "Buffer length can not be negative"); \
- *out_len = 0; \
- return ""; \
- } \
- if (len == 0) { \
- *out_len = 0; \
- return ""; \
- } \
- arrow::internal::StringFormatter<arrow::ARROW_TYPE> formatter; \
- char* ret = reinterpret_cast<char*>( \
- gdv_fn_context_arena_malloc(context, static_cast<int32_t>(len))); \
- if (ret == nullptr) { \
- gdv_fn_context_set_error_msg(context, "Could not allocate memory"); \
- *out_len = 0; \
- return ""; \
- } \
- arrow::Status status = formatter(value, [&](arrow::util::string_view v) { \
- int64_t size = static_cast<int64_t>(v.size()); \
- *out_len = static_cast<int32_t>(len < size ? len : size); \
- memcpy(ret, v.data(), *out_len); \
- return arrow::Status::OK(); \
- }); \
- if (!status.ok()) { \
- std::string err = "Could not cast " + std::to_string(value) + " to string"; \
- gdv_fn_context_set_error_msg(context, err.c_str()); \
- *out_len = 0; \
- return ""; \
- } \
- return ret; \
+#define GDV_FN_CAST_VARLEN_TYPE_FROM_INTEGER(IN_TYPE, CAST_NAME, ARROW_TYPE) \
+ GANDIVA_EXPORT \
+ const char* gdv_fn_cast##CAST_NAME##_##IN_TYPE##_int64( \
+ int64_t context, gdv_##IN_TYPE value, int64_t len, int32_t * out_len) { \
+ if (len < 0) { \
+ gdv_fn_context_set_error_msg(context, "Buffer length can not be negative"); \
+ *out_len = 0; \
+ return ""; \
+ } \
+ if (len == 0) { \
+ *out_len = 0; \
+ return ""; \
+ } \
+ arrow::internal::StringFormatter<arrow::ARROW_TYPE> formatter; \
+ char* ret = reinterpret_cast<char*>( \
+ gdv_fn_context_arena_malloc(context, static_cast<int32_t>(len))); \
+ if (ret == nullptr) { \
+ gdv_fn_context_set_error_msg(context, "Could not allocate memory"); \
+ *out_len = 0; \
+ return ""; \
+ } \
+ arrow::Status status = formatter(value, [&](arrow::util::string_view v) { \
+ int64_t size = static_cast<int64_t>(v.size()); \
+ *out_len = static_cast<int32_t>(len < size ? len : size); \
+ memcpy(ret, v.data(), *out_len); \
+ return arrow::Status::OK(); \
+ }); \
+ if (!status.ok()) { \
+ std::string err = "Could not cast " + std::to_string(value) + " to string"; \
+ gdv_fn_context_set_error_msg(context, err.c_str()); \
+ *out_len = 0; \
+ return ""; \
+ } \
+ return ret; \
}
-#define GDV_FN_CAST_VARCHAR_REAL(IN_TYPE, ARROW_TYPE) \
- GANDIVA_EXPORT \
- const char* gdv_fn_castVARCHAR_##IN_TYPE##_int64(int64_t context, gdv_##IN_TYPE value, \
- int64_t len, int32_t * out_len) { \
- if (len < 0) { \
- gdv_fn_context_set_error_msg(context, "Buffer length can not be negative"); \
- *out_len = 0; \
- return ""; \
- } \
- if (len == 0) { \
- *out_len = 0; \
- return ""; \
- } \
- gandiva::GdvStringFormatter<arrow::ARROW_TYPE> formatter; \
- char* ret = reinterpret_cast<char*>( \
- gdv_fn_context_arena_malloc(context, static_cast<int32_t>(len))); \
- if (ret == nullptr) { \
- gdv_fn_context_set_error_msg(context, "Could not allocate memory"); \
- *out_len = 0; \
- return ""; \
- } \
- arrow::Status status = formatter(value, [&](arrow::util::string_view v) { \
- int64_t size = static_cast<int64_t>(v.size()); \
- *out_len = static_cast<int32_t>(len < size ? len : size); \
- memcpy(ret, v.data(), *out_len); \
- return arrow::Status::OK(); \
- }); \
- if (!status.ok()) { \
- std::string err = "Could not cast " + std::to_string(value) + " to string"; \
- gdv_fn_context_set_error_msg(context, err.c_str()); \
- *out_len = 0; \
- return ""; \
- } \
- return ret; \
+#define GDV_FN_CAST_VARLEN_TYPE_FROM_REAL(IN_TYPE, CAST_NAME, ARROW_TYPE) \
+ GANDIVA_EXPORT \
+ const char* gdv_fn_cast##CAST_NAME##_##IN_TYPE##_int64( \
+ int64_t context, gdv_##IN_TYPE value, int64_t len, int32_t * out_len) { \
+ if (len < 0) { \
+ gdv_fn_context_set_error_msg(context, "Buffer length can not be negative"); \
+ *out_len = 0; \
+ return ""; \
+ } \
+ if (len == 0) { \
+ *out_len = 0; \
+ return ""; \
+ } \
+ gandiva::GdvStringFormatter<arrow::ARROW_TYPE> formatter; \
+ char* ret = reinterpret_cast<char*>( \
+ gdv_fn_context_arena_malloc(context, static_cast<int32_t>(len))); \
+ if (ret == nullptr) { \
+ gdv_fn_context_set_error_msg(context, "Could not allocate memory"); \
+ *out_len = 0; \
+ return ""; \
+ } \
+ arrow::Status status = formatter(value, [&](arrow::util::string_view v) { \
+ int64_t size = static_cast<int64_t>(v.size()); \
+ *out_len = static_cast<int32_t>(len < size ? len : size); \
+ memcpy(ret, v.data(), *out_len); \
+ return arrow::Status::OK(); \
+ }); \
+ if (!status.ok()) { \
+ std::string err = "Could not cast " + std::to_string(value) + " to string"; \
+ gdv_fn_context_set_error_msg(context, err.c_str()); \
+ *out_len = 0; \
+ return ""; \
+ } \
+ return ret; \
}
-GDV_FN_CAST_VARCHAR_INTEGER(int32, Int32Type)
-GDV_FN_CAST_VARCHAR_INTEGER(int64, Int64Type)
-GDV_FN_CAST_VARCHAR_REAL(float32, FloatType)
-GDV_FN_CAST_VARCHAR_REAL(float64, DoubleType)
+#define CAST_VARLEN_TYPE_FROM_NUMERIC(VARLEN_TYPE) \
+ GDV_FN_CAST_VARLEN_TYPE_FROM_INTEGER(int32, VARLEN_TYPE, Int32Type) \
+ GDV_FN_CAST_VARLEN_TYPE_FROM_INTEGER(int64, VARLEN_TYPE, Int64Type) \
+ GDV_FN_CAST_VARLEN_TYPE_FROM_REAL(float32, VARLEN_TYPE, FloatType) \
+ GDV_FN_CAST_VARLEN_TYPE_FROM_REAL(float64, VARLEN_TYPE, DoubleType)
+CAST_VARLEN_TYPE_FROM_NUMERIC(VARCHAR)
+CAST_VARLEN_TYPE_FROM_NUMERIC(VARBINARY)
+
+#undef CAST_VARLEN_TYPE_FROM_NUMERIC
+#undef GDV_FN_CAST_VARLEN_TYPE_FROM_INTEGER
+#undef GDV_FN_CAST_VARLEN_TYPE_FROM_REAL
#undef GDV_FN_CAST_VARCHAR_INTEGER
#undef GDV_FN_CAST_VARCHAR_REAL
@@ -700,6 +707,54 @@ void ExportedStubFunctions::AddMappings(Engine* engine) const {
std::vector<llvm::Type*> args;
auto types = engine->types();
+ // gdv_fn_castVARBINARY_int32
+ args = {
+ types->i64_type(), // context
+ types->i32_type(), // int32_t value
+ types->i64_type(), // int64_t out value length
+ types->i32_ptr_type() // int32_t out_length
+ };
+
+ engine->AddGlobalMappingForFunc(
+ "gdv_fn_castVARBINARY_int32_int64", types->i8_ptr_type() /*return_type*/, args,
+ reinterpret_cast<void*>(gdv_fn_castVARBINARY_int32_int64));
+
+ // gdv_fn_castVARBINARY_int64
+ args = {
+ types->i64_type(), // context
+ types->i64_type(), // int64_t value
+ types->i64_type(), // int64_t out value length
+ types->i32_ptr_type() // int32_t out_length
+ };
+
+ engine->AddGlobalMappingForFunc(
+ "gdv_fn_castVARBINARY_int64_int64", types->i8_ptr_type() /*return_type*/, args,
+ reinterpret_cast<void*>(gdv_fn_castVARBINARY_int64_int64));
+
+ // gdv_fn_castVARBINARY_float32
+ args = {
+ types->i64_type(), // context
+ types->float_type(), // float value
+ types->i64_type(), // int64_t out value length
+ types->i64_ptr_type() // int32_t out_length
+ };
+
+ engine->AddGlobalMappingForFunc(
+ "gdv_fn_castVARBINARY_float32_int64", types->i8_ptr_type() /*return_type*/, args,
+ reinterpret_cast<void*>(gdv_fn_castVARBINARY_float32_int64));
+
+ // gdv_fn_castVARBINARY_float64
+ args = {
+ types->i64_type(), // context
+ types->i64_type(), // double value
+ types->i64_type(), // int64_t out value length
+ types->i32_ptr_type() // int32_t out_length
+ };
+
+ engine->AddGlobalMappingForFunc(
+ "gdv_fn_castVARBINARY_float64_int64", types->i8_ptr_type() /*return_type*/, args,
+ reinterpret_cast<void*>(gdv_fn_castVARBINARY_float64_int64));
+
// gdv_fn_dec_from_string
args = {
types->i64_type(), // context
diff --git a/cpp/src/gandiva/gdv_function_stubs.h b/cpp/src/gandiva/gdv_function_stubs.h
index d4a127d..ee22c3f 100644
--- a/cpp/src/gandiva/gdv_function_stubs.h
+++ b/cpp/src/gandiva/gdv_function_stubs.h
@@ -72,6 +72,14 @@ int gdv_fn_time_with_zone(int* time_fields, const char* zone, int zone_len,
int64_t* ret_time);
GANDIVA_EXPORT
+const char* gdv_fn_castVARBINARY_int32_int64(int64_t context, gdv_int32 value,
+ int64_t out_len, int32_t* out_length);
+
+GANDIVA_EXPORT
+const char* gdv_fn_castVARBINARY_int64_int64(int64_t context, gdv_int64 value,
+ int64_t out_len, int32_t* out_length);
+
+GANDIVA_EXPORT
const char* gdv_fn_sha256_decimal128(int64_t context, int64_t x_high, uint64_t x_low,
int32_t x_precision, int32_t x_scale,
gdv_boolean x_isvalid, int32_t* out_length);
diff --git a/cpp/src/gandiva/gdv_function_stubs_test.cc b/cpp/src/gandiva/gdv_function_stubs_test.cc
index 6cfff5b..354a8bb 100644
--- a/cpp/src/gandiva/gdv_function_stubs_test.cc
+++ b/cpp/src/gandiva/gdv_function_stubs_test.cc
@@ -24,6 +24,63 @@
namespace gandiva {
+TEST(TestGdvFnStubs, TestCastVarbinaryNumeric) {
+ gandiva::ExecutionContext ctx;
+
+ int64_t ctx_ptr = reinterpret_cast<int64_t>(&ctx);
+ int32_t out_len = 0;
+
+ // tests for integer values as input
+ const char* out_str = gdv_fn_castVARBINARY_int32_int64(ctx_ptr, -46, 100, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "-46");
+ EXPECT_FALSE(ctx.has_error());
+
+ out_str = gdv_fn_castVARBINARY_int32_int64(ctx_ptr, 2147483647, 100, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "2147483647");
+ EXPECT_FALSE(ctx.has_error());
+
+ out_str = gdv_fn_castVARBINARY_int32_int64(ctx_ptr, -2147483647 - 1, 100, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "-2147483648");
+ EXPECT_FALSE(ctx.has_error());
+
+ out_str = gdv_fn_castVARBINARY_int32_int64(ctx_ptr, 0, 100, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "0");
+ EXPECT_FALSE(ctx.has_error());
+
+ // test with required length less than actual buffer length
+ out_str = gdv_fn_castVARBINARY_int32_int64(ctx_ptr, 34567, 3, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "345");
+ EXPECT_FALSE(ctx.has_error());
+
+ out_str = gdv_fn_castVARBINARY_int32_int64(ctx_ptr, 347, 0, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "");
+ EXPECT_FALSE(ctx.has_error());
+
+ gdv_fn_castVARBINARY_int32_int64(ctx_ptr, 347, -1, &out_len);
+ EXPECT_THAT(ctx.get_error(), ::testing::HasSubstr("Buffer length can not be negative"));
+ ctx.Reset();
+
+ // tests for big integer values as input
+ out_str =
+ gdv_fn_castVARBINARY_int64_int64(ctx_ptr, 9223372036854775807LL, 100, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "9223372036854775807");
+ EXPECT_FALSE(ctx.has_error());
+
+ out_str = gdv_fn_castVARBINARY_int64_int64(ctx_ptr, -9223372036854775807LL - 1, 100,
+ &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "-9223372036854775808");
+ EXPECT_FALSE(ctx.has_error());
+
+ out_str = gdv_fn_castVARBINARY_int64_int64(ctx_ptr, 0, 100, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "0");
+ EXPECT_FALSE(ctx.has_error());
+
+ // test with required length less than actual buffer length
+ out_str = gdv_fn_castVARBINARY_int64_int64(ctx_ptr, 12345, 3, &out_len);
+ EXPECT_EQ(std::string(out_str, out_len), "123");
+ EXPECT_FALSE(ctx.has_error());
+}
+
TEST(TestGdvFnStubs, TestCastINT) {
gandiva::ExecutionContext ctx;
diff --git a/cpp/src/gandiva/precompiled/string_ops.cc b/cpp/src/gandiva/precompiled/string_ops.cc
index 1cd566d..fe5fcf4 100644
--- a/cpp/src/gandiva/precompiled/string_ops.cc
+++ b/cpp/src/gandiva/precompiled/string_ops.cc
@@ -588,6 +588,32 @@ CAST_VARCHAR_FROM_VARLEN_TYPE(binary)
#undef CAST_VARCHAR_FROM_VARLEN_TYPE
+// Add functions for castVARBINARY
+#define CAST_VARBINARY_FROM_STRING_AND_BINARY(TYPE) \
+ GANDIVA_EXPORT \
+ const char* castVARBINARY_##TYPE##_int64(gdv_int64 context, const char* data, \
+ gdv_int32 data_len, int64_t out_len, \
+ int32_t* out_length) { \
+ int32_t len = static_cast<int32_t>(out_len); \
+ if (len < 0) { \
+ gdv_fn_context_set_error_msg(context, "Output buffer length can't be negative"); \
+ *out_length = 0; \
+ return ""; \
+ } \
+ \
+ if (len >= data_len || len == 0) { \
+ *out_length = data_len; \
+ } else { \
+ *out_length = len; \
+ } \
+ return data; \
+ }
+
+CAST_VARBINARY_FROM_STRING_AND_BINARY(utf8)
+CAST_VARBINARY_FROM_STRING_AND_BINARY(binary)
+
+#undef CAST_VARBINARY_FROM_STRING_AND_BINARY
+
#define IS_NULL(NAME, TYPE) \
FORCE_INLINE \
bool NAME##_##TYPE(gdv_##TYPE in, gdv_int32 len, gdv_boolean is_valid) { \
diff --git a/cpp/src/gandiva/precompiled/string_ops_test.cc b/cpp/src/gandiva/precompiled/string_ops_test.cc
index 2460633..3763a61 100644
--- a/cpp/src/gandiva/precompiled/string_ops_test.cc
+++ b/cpp/src/gandiva/precompiled/string_ops_test.cc
@@ -583,6 +583,54 @@ TEST(TestStringOps, TestSubstringInvalidInputs) {
EXPECT_FALSE(ctx.has_error());
}
+TEST(TestGdvFnStubs, TestCastVarbinaryUtf8) {
+ gandiva::ExecutionContext ctx;
+
+ int64_t ctx_ptr = reinterpret_cast<int64_t>(&ctx);
+ int32_t out_len = 0;
+ const char* input = "abc";
+ const char* out;
+
+ out = castVARBINARY_utf8_int64(ctx_ptr, input, 3, 0, &out_len);
+ EXPECT_EQ(std::string(out, out_len), input);
+
+ out = castVARBINARY_utf8_int64(ctx_ptr, input, 3, 1, &out_len);
+ EXPECT_EQ(std::string(out, out_len), "a");
+
+ out = castVARBINARY_utf8_int64(ctx_ptr, input, 3, 500, &out_len);
+ EXPECT_EQ(std::string(out, out_len), input);
+
+ out = castVARBINARY_utf8_int64(ctx_ptr, input, 3, -10, &out_len);
+ EXPECT_EQ(std::string(out, out_len), "");
+ EXPECT_THAT(ctx.get_error(),
+ ::testing::HasSubstr("Output buffer length can't be negative"));
+ ctx.Reset();
+}
+
+TEST(TestGdvFnStubs, TestCastVarbinaryBinary) {
+ gandiva::ExecutionContext ctx;
+
+ int64_t ctx_ptr = reinterpret_cast<int64_t>(&ctx);
+ int32_t out_len = 0;
+ const char* input = "\\x41\\x42\\x43";
+ const char* out;
+
+ out = castVARBINARY_binary_int64(ctx_ptr, input, 12, 0, &out_len);
+ EXPECT_EQ(std::string(out, out_len), input);
+
+ out = castVARBINARY_binary_int64(ctx_ptr, input, 8, 8, &out_len);
+ EXPECT_EQ(std::string(out, out_len), "\\x41\\x42");
+
+ out = castVARBINARY_binary_int64(ctx_ptr, input, 12, 500, &out_len);
+ EXPECT_EQ(std::string(out, out_len), input);
+
+ out = castVARBINARY_binary_int64(ctx_ptr, input, 12, -10, &out_len);
+ EXPECT_EQ(std::string(out, out_len), "");
+ EXPECT_THAT(ctx.get_error(),
+ ::testing::HasSubstr("Output buffer length can't be negative"));
+ ctx.Reset();
+}
+
TEST(TestStringOps, TestConcat) {
gandiva::ExecutionContext ctx;
uint64_t ctx_ptr = reinterpret_cast<gdv_int64>(&ctx);
diff --git a/cpp/src/gandiva/precompiled/types.h b/cpp/src/gandiva/precompiled/types.h
index be769dd..5bd2242 100644
--- a/cpp/src/gandiva/precompiled/types.h
+++ b/cpp/src/gandiva/precompiled/types.h
@@ -388,6 +388,14 @@ const char* castVARCHAR_utf8_int64(gdv_int64 context, const char* data,
gdv_int32 data_len, int64_t out_len,
int32_t* out_length);
+const char* castVARBINARY_utf8_int64(gdv_int64 context, const char* data,
+ gdv_int32 data_len, int64_t out_len,
+ int32_t* out_length);
+
+const char* castVARBINARY_binary_int64(gdv_int64 context, const char* data,
+ gdv_int32 data_len, int64_t out_len,
+ int32_t* out_length);
+
const char* reverse_utf8(gdv_int64 context, const char* data, gdv_int32 data_len,
int32_t* out_len);