Skip to content

Commit 0f4fe4f

Browse files
DX-105463: [C++][Gandiva] Add TimestampIR for unit-aware timestamp[us/ns] support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent beb6caf commit 0f4fe4f

9 files changed

Lines changed: 1646 additions & 14 deletions

File tree

cpp/src/gandiva/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ set(SRC_FILES
6161
context_helper.cc
6262
decimal_ir.cc
6363
decimal_type_util.cc
64+
timestamp_ir.cc
6465
decimal_xlarge.cc
6566
engine.cc
6667
date_utils.cc

cpp/src/gandiva/engine.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107

108108
#include "gandiva/configuration.h"
109109
#include "gandiva/decimal_ir.h"
110+
#include "gandiva/timestamp_ir.h"
110111
#include "gandiva/exported_funcs.h"
111112
#include "gandiva/exported_funcs_registry.h"
112113

@@ -325,6 +326,7 @@ Status Engine::LoadFunctionIRs() {
325326
if (!functions_loaded_) {
326327
ARROW_RETURN_NOT_OK(LoadPreCompiledIR());
327328
ARROW_RETURN_NOT_OK(DecimalIR::AddFunctions(this));
329+
ARROW_RETURN_NOT_OK(TimestampIR::AddFunctions(this));
328330
ARROW_RETURN_NOT_OK(LoadExternalPreCompiledIR());
329331
functions_loaded_ = true;
330332
}

cpp/src/gandiva/function_signature.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ bool DataTypeEquals(const DataTypePtr& left, const DataTypePtr& right) {
4545
return (dleft != NULL) && (dright != NULL) &&
4646
(dleft->byte_width() == dright->byte_width());
4747
}
48+
case arrow::Type::TIMESTAMP: {
49+
// For timestamp types, the TimeUnit isn't part of the signature
50+
// (conversion is handled at codegen time by TimestampIR).
51+
// However, timezone IS significant — a function registered for
52+
// timestamp(null tz) should not match timestamp("America/New_York").
53+
auto tleft = checked_cast<arrow::TimestampType*>(left.get());
54+
auto tright = checked_cast<arrow::TimestampType*>(right.get());
55+
return tleft->timezone() == tright->timezone();
56+
}
4857
default:
4958
return left->Equals(right);
5059
}

cpp/src/gandiva/llvm_generator.cc

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
#include <utility>
2222
#include <vector>
2323

24+
#include "arrow/type.h"
2425
#include "gandiva/bitmap_accumulator.h"
2526
#include "gandiva/decimal_ir.h"
2627
#include "gandiva/dex.h"
28+
#include "gandiva/timestamp_ir.h"
2729
#include "gandiva/expr_decomposer.h"
2830
#include "gandiva/expression.h"
2931
#include "gandiva/llvm_types.h"
@@ -384,6 +386,7 @@ Status LLVMGenerator::CodeGenExprValue(DexPtr value_expr, int buffer_count,
384386
Visitor visitor(this, fn, loop_entry, arg_addrs, arg_local_bitmaps, arg_holder_ptrs,
385387
slice_offsets, arg_context_ptr, position_var);
386388
value_expr->Accept(visitor);
389+
ARROW_RETURN_NOT_OK(visitor.status());
387390
LValuePtr output_value = visitor.result();
388391

389392
// The "current" block may have changed due to code generation in the visitor.
@@ -813,7 +816,8 @@ void LLVMGenerator::Visitor::Visit(const NonNullableFuncDex& dex) {
813816
auto then_lambda = [&] {
814817
ADD_VISITOR_TRACE("fn " + function_name +
815818
" can return errors : all args valid, invoke fn");
816-
return BuildFunctionCall(native_function, arrow_return_type, &params);
819+
return BuildFunctionCall(native_function, arrow_return_type, &params,
820+
dex.func_descriptor());
817821
};
818822

819823
// else block
@@ -831,7 +835,9 @@ void LLVMGenerator::Visitor::Visit(const NonNullableFuncDex& dex) {
831835
result_ = BuildIfElse(is_valid, then_lambda, else_lambda, arrow_return_type);
832836
} else {
833837
// fast path : invoke function without computing validities.
834-
result_ = BuildFunctionCall(native_function, arrow_return_type, &params);
838+
result_ = BuildFunctionCall(native_function, arrow_return_type, &params,
839+
dex.func_descriptor());
840+
if (!status_.ok()) return;
835841
}
836842
}
837843

@@ -844,7 +850,8 @@ void LLVMGenerator::Visitor::Visit(const NullableNeverFuncDex& dex) {
844850
native_function->NeedsContext());
845851

846852
auto arrow_return_type = dex.func_descriptor()->return_type();
847-
result_ = BuildFunctionCall(native_function, arrow_return_type, &params);
853+
result_ = BuildFunctionCall(native_function, arrow_return_type, &params,
854+
dex.func_descriptor());
848855
}
849856

850857
void LLVMGenerator::Visitor::Visit(const NullableInternalFuncDex& dex) {
@@ -1084,6 +1091,9 @@ void LLVMGenerator::Visitor::VisitInExpression(const InExprDexBase<Type>& dex) {
10841091
for (auto& pair : dex.args()) {
10851092
DexPtr value_expr = pair->value_expr();
10861093
value_expr->Accept(*this);
1094+
if (!status_.ok()) {
1095+
return;
1096+
}
10871097
LValue& result_ref = *result();
10881098
params.push_back(result_ref.data());
10891099

@@ -1235,6 +1245,9 @@ LValuePtr LLVMGenerator::Visitor::BuildValueAndValidity(const ValueValidityPair&
12351245
// generate code for value
12361246
auto value_expr = pair.value_expr();
12371247
value_expr->Accept(*this);
1248+
if (!status_.ok()) {
1249+
return nullptr;
1250+
}
12381251
auto value = result()->data();
12391252
auto length = result()->length();
12401253

@@ -1246,12 +1259,44 @@ LValuePtr LLVMGenerator::Visitor::BuildValueAndValidity(const ValueValidityPair&
12461259

12471260
LValuePtr LLVMGenerator::Visitor::BuildFunctionCall(const NativeFunction* func,
12481261
DataTypePtr arrow_return_type,
1249-
std::vector<llvm::Value*>* params) {
1262+
std::vector<llvm::Value*>* params,
1263+
const FuncDescriptorPtr& descriptor) {
12501264
auto types = generator_->types();
12511265
auto arrow_return_type_id = arrow_return_type->id();
12521266
auto llvm_return_type = types->IRType(arrow_return_type_id);
12531267
DecimalIR decimalIR(generator_->engine_.get());
12541268

1269+
// Resolve the function name — may remap to a TimestampIR-built variant
1270+
// based on the actual TimeUnit from the expression tree.
1271+
std::string pc_name = func->pc_name();
1272+
if (descriptor != nullptr) {
1273+
arrow::TimeUnit::type ts_unit = arrow::TimeUnit::MILLI;
1274+
bool found_ts = false;
1275+
for (auto& param : descriptor->params()) {
1276+
if (param->id() == arrow::Type::TIMESTAMP) {
1277+
auto unit =
1278+
arrow::internal::checked_cast<const arrow::TimestampType&>(*param).unit();
1279+
if (!found_ts) {
1280+
ts_unit = unit;
1281+
found_ts = true;
1282+
} else if (unit != ts_unit) {
1283+
status_ = Status::Invalid(
1284+
"Gandiva cannot compile expression: mixed timestamp units in function '",
1285+
pc_name, "'. All timestamp arguments must have the same TimeUnit.");
1286+
return nullptr;
1287+
}
1288+
}
1289+
}
1290+
if (found_ts && ts_unit != arrow::TimeUnit::MILLI) {
1291+
std::string suffix = (ts_unit == arrow::TimeUnit::MICRO) ? "_us" : "_ns";
1292+
std::string remapped = pc_name + suffix;
1293+
ARROW_LOG(DEBUG) << "TimestampIR remap: " << pc_name << " -> " << remapped;
1294+
if (TimestampIR::IsTimestampIRFunction(remapped)) {
1295+
pc_name = remapped;
1296+
}
1297+
}
1298+
}
1299+
12551300
if (arrow_return_type_id == arrow::Type::DECIMAL) {
12561301
// For decimal fns, the output precision/scale are passed along as parameters.
12571302
//
@@ -1266,7 +1311,7 @@ LValuePtr LLVMGenerator::Visitor::BuildFunctionCall(const NativeFunction* func,
12661311
params->push_back(ret_lvalue->scale());
12671312

12681313
// Make the function call
1269-
auto out = decimalIR.CallDecimalFunction(func->pc_name(), llvm_return_type, *params);
1314+
auto out = decimalIR.CallDecimalFunction(pc_name, llvm_return_type, *params);
12701315
ret_lvalue->set_data(out);
12711316
return ret_lvalue;
12721317
} else {
@@ -1287,10 +1332,14 @@ LValuePtr LLVMGenerator::Visitor::BuildFunctionCall(const NativeFunction* func,
12871332

12881333
// Make the function call
12891334
llvm::IRBuilder<>* builder = ir_builder();
1290-
auto value =
1291-
isDecimalFunction
1292-
? decimalIR.CallDecimalFunction(func->pc_name(), llvm_return_type, *params)
1293-
: generator_->AddFunctionCall(func->pc_name(), llvm_return_type, *params);
1335+
llvm::Value* value;
1336+
if (isDecimalFunction) {
1337+
value = decimalIR.CallDecimalFunction(pc_name, llvm_return_type, *params);
1338+
} else if (auto* ir_fn = generator_->engine_->module()->getFunction(pc_name)) {
1339+
value = ir_builder()->CreateCall(ir_fn, *params);
1340+
} else {
1341+
value = generator_->AddFunctionCall(pc_name, llvm_return_type, *params);
1342+
}
12941343
auto value_len =
12951344
(result_len_ptr == nullptr)
12961345
? nullptr

cpp/src/gandiva/llvm_generator.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ class GANDIVA_EXPORT LLVMGenerator {
135135

136136
bool has_arena_allocs() { return has_arena_allocs_; }
137137

138+
const Status& status() const { return status_; }
139+
138140
private:
139141
enum BufferType { kBufferTypeValidity = 0, kBufferTypeData, kBufferTypeOffsets };
140142

@@ -158,7 +160,8 @@ class GANDIVA_EXPORT LLVMGenerator {
158160

159161
// Generate code to invoke a function call.
160162
LValuePtr BuildFunctionCall(const NativeFunction* func, DataTypePtr arrow_return_type,
161-
std::vector<llvm::Value*>* params);
163+
std::vector<llvm::Value*>* params,
164+
const FuncDescriptorPtr& descriptor = nullptr);
162165

163166
// Generate code for an if-else condition.
164167
LValuePtr BuildIfElse(llvm::Value* condition, std::function<LValuePtr()> then_func,
@@ -179,6 +182,7 @@ class GANDIVA_EXPORT LLVMGenerator {
179182

180183
LLVMGenerator* generator_;
181184
LValuePtr result_;
185+
Status status_;
182186
llvm::Function* function_;
183187
llvm::BasicBlock* entry_block_;
184188
llvm::Value* arg_addrs_;

cpp/src/gandiva/precompiled/time.cc

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -442,10 +442,17 @@ EXTRACT_MINUTE_TIME(time32)
442442

443443
EXTRACT_HOUR_TIME(time32)
444444

445-
#define DATE_TRUNC_FIXED_UNIT(NAME, TYPE, NMILLIS_IN_UNIT) \
446-
FORCE_INLINE \
447-
gdv_##TYPE NAME##_##TYPE(gdv_##TYPE millis) { \
448-
return ((millis / NMILLIS_IN_UNIT) * NMILLIS_IN_UNIT); \
445+
#define DATE_TRUNC_FIXED_UNIT(NAME, TYPE, NMILLIS_IN_UNIT) \
446+
FORCE_INLINE \
447+
gdv_##TYPE NAME##_##TYPE(gdv_##TYPE millis) { \
448+
/* Use floor division to correctly handle negative timestamps (pre-epoch). */ \
449+
/* C++ integer division truncates toward zero; we need toward negative inf. */ \
450+
gdv_##TYPE q = millis / NMILLIS_IN_UNIT; \
451+
gdv_##TYPE r = millis % NMILLIS_IN_UNIT; \
452+
if (r != 0 && (millis ^ NMILLIS_IN_UNIT) < 0) { \
453+
--q; \
454+
} \
455+
return q * NMILLIS_IN_UNIT; \
449456
}
450457

451458
#define DATE_TRUNC_WEEK(TYPE) \
@@ -927,7 +934,9 @@ const char* castVARCHAR_timestamp_int64(gdv_int64 context, gdv_timestamp in,
927934
gdv_int64 hour = extractHour_timestamp(in);
928935
gdv_int64 minute = extractMinute_timestamp(in);
929936
gdv_int64 second = extractSecond_timestamp(in);
937+
// Use non-negative remainder for sub-second millis (pre-epoch safe).
930938
gdv_int64 millis = in % MILLIS_IN_SEC;
939+
if (millis < 0) millis += MILLIS_IN_SEC;
931940

932941
static const int kTimeStampStringLen = 23;
933942
const int char_buffer_length = kTimeStampStringLen + 1; // snprintf adds \0

0 commit comments

Comments
 (0)