Skip to content

Commit 4c7401b

Browse files
authored
Merge pull request #1076 from suketa/add-timestamp-support-to-scalar-function
Add TIMESTAMP support to scalar functions
2 parents 64ed580 + 0779c74 commit 4c7401b

File tree

3 files changed

+43
-3
lines changed

3 files changed

+43
-3
lines changed

ext/duckdb/scalar_function.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,25 @@ static void vector_set_value_at(duckdb_vector vector, duckdb_logical_type elemen
259259
duckdb_vector_assign_string_element_len(vector, index, str_ptr, str_len);
260260
break;
261261
}
262+
case DUCKDB_TYPE_TIMESTAMP: {
263+
// Convert Ruby Time to DuckDB timestamp (microseconds since epoch)
264+
if (!rb_obj_is_kind_of(value, rb_cTime)) {
265+
rb_raise(rb_eTypeError, "Expected Time object for TIMESTAMP");
266+
}
267+
268+
duckdb_timestamp_struct ts_struct;
269+
ts_struct.date.year = NUM2INT(rb_funcall(value, rb_intern("year"), 0));
270+
ts_struct.date.month = NUM2INT(rb_funcall(value, rb_intern("month"), 0));
271+
ts_struct.date.day = NUM2INT(rb_funcall(value, rb_intern("day"), 0));
272+
ts_struct.time.hour = NUM2INT(rb_funcall(value, rb_intern("hour"), 0));
273+
ts_struct.time.min = NUM2INT(rb_funcall(value, rb_intern("min"), 0));
274+
ts_struct.time.sec = NUM2INT(rb_funcall(value, rb_intern("sec"), 0));
275+
ts_struct.time.micros = NUM2INT(rb_funcall(value, rb_intern("usec"), 0));
276+
277+
duckdb_timestamp ts = duckdb_to_timestamp(ts_struct);
278+
((duckdb_timestamp *)vector_data)[index] = ts;
279+
break;
280+
}
262281
default:
263282
rb_raise(rb_eArgError, "Unsupported return type for scalar function");
264283
break;

lib/duckdb/scalar_function.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module DuckDB
44
# DuckDB::ScalarFunction encapsulates DuckDB's scalar function
55
class ScalarFunction
66
# Sets the return type for the scalar function.
7-
# Currently supports BOOLEAN, INTEGER, BIGINT, FLOAT, DOUBLE, VARCHAR, and BLOB types.
7+
# Currently supports BIGINT, BLOB, BOOLEAN, DOUBLE, FLOAT, INTEGER, TIMESTAMP, and VARCHAR types.
88
#
99
# @param logical_type [DuckDB::LogicalType] the return type
1010
# @return [DuckDB::ScalarFunction] self
@@ -13,9 +13,10 @@ def return_type=(logical_type)
1313
raise DuckDB::Error, 'logical_type must be a DuckDB::LogicalType' unless logical_type.is_a?(DuckDB::LogicalType)
1414

1515
# Check if the type is supported
16-
unless %i[bigint blob boolean double float integer varchar].include?(logical_type.type)
16+
unless %i[bigint blob boolean double float integer timestamp varchar].include?(logical_type.type)
1717
raise DuckDB::Error,
18-
'Only BIGINT, BLOB, BOOLEAN, DOUBLE, FLOAT, INTEGER, and VARCHAR return types are currently supported'
18+
'Only BIGINT, BLOB, BOOLEAN, DOUBLE, FLOAT, INTEGER, TIMESTAMP, and VARCHAR return types are ' \
19+
'currently supported'
1920
end
2021

2122
_set_return_type(logical_type)

test/duckdb_test/scalar_function_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,5 +251,25 @@ def test_scalar_function_blob_return_type # rubocop:disable Metrics/AbcSize, Met
251251
assert_equal "\xFF\x00\x01\x02\x03".b, rows[0][0]
252252
assert_equal "\xFF\x00\xAA\xBB\xCC".b, rows[1][0]
253253
end
254+
255+
def test_scalar_function_timestamp_return_type # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
256+
@con.execute('SET threads=1')
257+
@con.execute('CREATE TABLE test_table (ts TIMESTAMP)')
258+
@con.execute("INSERT INTO test_table VALUES ('2024-01-15 10:30:00'), ('2024-12-25 23:59:59')")
259+
260+
sf = DuckDB::ScalarFunction.new
261+
sf.name = 'add_one_hour'
262+
sf.add_parameter(DuckDB::LogicalType.new(12)) # TIMESTAMP (type ID 12)
263+
sf.return_type = DuckDB::LogicalType.new(12) # TIMESTAMP
264+
sf.set_function { |ts| ts + 3600 } # Add 1 hour (3600 seconds)
265+
266+
@con.register_scalar_function(sf)
267+
result = @con.execute('SELECT add_one_hour(ts) FROM test_table ORDER BY ts')
268+
rows = result.to_a
269+
270+
assert_equal 2, rows.size
271+
assert_equal Time.new(2024, 1, 15, 11, 30, 0), rows[0][0]
272+
assert_equal Time.new(2024, 12, 26, 0, 59, 59), rows[1][0]
273+
end
254274
end
255275
end

0 commit comments

Comments
 (0)