From af5b82ed2fbc4ea14919b518e17f4904b6954555 Mon Sep 17 00:00:00 2001 From: abdul rawoof Date: Sat, 18 Jul 2026 11:52:54 +0530 Subject: [PATCH] GH-50539: [C++][Gandiva] size varlen cast output from the formatter --- cpp/src/gandiva/gdv_function_stubs_test.cc | 29 ++++++++++ cpp/src/gandiva/gdv_string_function_stubs.cc | 61 ++++++++++---------- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/cpp/src/gandiva/gdv_function_stubs_test.cc b/cpp/src/gandiva/gdv_function_stubs_test.cc index 27ae60694b7..0128a364ea9 100644 --- a/cpp/src/gandiva/gdv_function_stubs_test.cc +++ b/cpp/src/gandiva/gdv_function_stubs_test.cc @@ -404,6 +404,35 @@ TEST(TestGdvFnStubs, TestCastVARCHARFromMilliseconds) { ctx.Reset(); } +// Dates whose formatted form is longer than "YYYY-MM-DD" must still fit in the +// block they are written to. A following allocation from the same arena would +// otherwise land inside the bytes this cast already wrote. +TEST(TestGdvFnStubs, TestCastVARCHARFromMillisecondsLongOutput) { + gandiva::ExecutionContext ctx; + uint64_t ctx_ptr = reinterpret_cast(&ctx); + int32_t out_len = 0; + int32_t other_len = 0; + + // Year 10000 needs 11 bytes. + const char* out_str = + gdv_fn_castVARCHAR_date64_int64(ctx_ptr, 253402300800000LL, 100, &out_len); + gdv_fn_castVARCHAR_int32_int64(ctx_ptr, 12345, 100, &other_len); + EXPECT_EQ(std::string(out_str, out_len), "10000-01-01"); + EXPECT_FALSE(ctx.has_error()); + + // Out-of-range dates format as a diagnostic string, far longer than a date. + out_str = gdv_fn_castVARCHAR_date64_int64(ctx_ptr, std::numeric_limits::min(), + 100, &out_len); + gdv_fn_castVARCHAR_int32_int64(ctx_ptr, 12345, 100, &other_len); + EXPECT_EQ(std::string(out_str, out_len), ""); + EXPECT_FALSE(ctx.has_error()); + + // Truncation to the requested length still applies. + out_str = gdv_fn_castVARCHAR_date64_int64(ctx_ptr, 253402300800000LL, 5, &out_len); + EXPECT_EQ(std::string(out_str, out_len), "10000"); + EXPECT_FALSE(ctx.has_error()); +} + TEST(TestGdvFnStubs, TestCastVARCHARFromFloat) { gandiva::ExecutionContext ctx; uint64_t ctx_ptr = reinterpret_cast(&ctx); diff --git a/cpp/src/gandiva/gdv_string_function_stubs.cc b/cpp/src/gandiva/gdv_string_function_stubs.cc index a0936d448ae..d9ea68b6442 100644 --- a/cpp/src/gandiva/gdv_string_function_stubs.cc +++ b/cpp/src/gandiva/gdv_string_function_stubs.cc @@ -132,41 +132,42 @@ const char* gdv_fn_regexp_extract_utf8_utf8_int32(int64_t ptr, int64_t holder_pt // Helper: invoke formatter callback, copy result to ret, handle errors. // Used by date64 and float types that rely on arrow::internal::StringFormatter. -#define GDV_FN_CAST_VARLEN_FORMATTER_SUFFIX \ - arrow::Status status = formatter(value, [&](std::string_view v) { \ - int64_t size = static_cast(v.size()); \ - *out_len = static_cast(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; \ +// The formatted length is only known inside the callback, so the arena block is +// allocated there: sizing it from a fixed upper bound guessed at the call site +// lets the copy below run past the end whenever the formatter emits more than +// that bound. +#define GDV_FN_CAST_VARLEN_FORMATTER_SUFFIX \ + char* ret = nullptr; \ + arrow::Status status = formatter(value, [&](std::string_view v) { \ + int64_t size = static_cast(v.size()); \ + *out_len = static_cast(len < size ? len : size); \ + ret = reinterpret_cast(gdv_fn_context_arena_malloc(context, *out_len)); \ + if (ret == nullptr) { \ + return arrow::Status::OutOfMemory("Could not allocate memory"); \ + } \ + memcpy(ret, v.data(), *out_len); \ + return arrow::Status::OK(); \ + }); \ + if (!status.ok() || ret == nullptr) { \ + 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; \ } -// Macro for date64 type. Output is always "YYYY-MM-DD" = 10 chars max. -#define GDV_FN_CAST_VARLEN_TYPE_FROM_DATE64(IN_TYPE, CAST_NAME, ARROW_TYPE) \ - GDV_FN_CAST_VARLEN_PREFIX(IN_TYPE, CAST_NAME) \ - constexpr int32_t max_date_str_len = 10; \ - int32_t alloc_len = \ - static_cast(len < max_date_str_len ? len : max_date_str_len); \ - GDV_FN_CAST_VARLEN_ALLOC(alloc_len) \ - arrow::internal::StringFormatter formatter; \ +// Macro for date64 type. Out-of-range values format as a diagnostic string that +// is much longer than "YYYY-MM-DD", so the output size comes from the formatter. +#define GDV_FN_CAST_VARLEN_TYPE_FROM_DATE64(IN_TYPE, CAST_NAME, ARROW_TYPE) \ + GDV_FN_CAST_VARLEN_PREFIX(IN_TYPE, CAST_NAME) \ + arrow::internal::StringFormatter formatter; \ GDV_FN_CAST_VARLEN_FORMATTER_SUFFIX // Macro for float types (float32/float64). Uses Java-compatible formatting. -// Max string: "-1.2345678901234567E-308" = 24 chars. -#define GDV_FN_CAST_VARLEN_TYPE_FROM_REAL(IN_TYPE, CAST_NAME, ARROW_TYPE) \ - GDV_FN_CAST_VARLEN_PREFIX(IN_TYPE, CAST_NAME) \ - constexpr int32_t max_real_str_len = 24; \ - int32_t alloc_len = \ - static_cast(len < max_real_str_len ? len : max_real_str_len); \ - GDV_FN_CAST_VARLEN_ALLOC(alloc_len) \ - gandiva::GdvStringFormatter formatter; \ +#define GDV_FN_CAST_VARLEN_TYPE_FROM_REAL(IN_TYPE, CAST_NAME, ARROW_TYPE) \ + GDV_FN_CAST_VARLEN_PREFIX(IN_TYPE, CAST_NAME) \ + gandiva::GdvStringFormatter formatter; \ GDV_FN_CAST_VARLEN_FORMATTER_SUFFIX // Use optimized integer macro for int32/int64, date64 macro, and real macro for floats