Skip to content

Commit 05388a6

Browse files
authored
Merge pull request #1078 from suketa/add-time-support-to-scalar-function
Add TIME support to scalar functions
2 parents 51db0a6 + 3224b45 commit 05388a6

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

ext/duckdb/scalar_function.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,21 @@ static void vector_set_value_at(duckdb_vector vector, duckdb_logical_type elemen
294294
((duckdb_date *)vector_data)[index] = date;
295295
break;
296296
}
297+
case DUCKDB_TYPE_TIME: {
298+
// Convert Ruby Time to DuckDB time (time-of-day only)
299+
if (!rb_obj_is_kind_of(value, rb_cTime)) {
300+
rb_raise(rb_eTypeError, "Expected Time object for TIME");
301+
}
302+
303+
VALUE hour = rb_funcall(value, rb_intern("hour"), 0);
304+
VALUE min = rb_funcall(value, rb_intern("min"), 0);
305+
VALUE sec = rb_funcall(value, rb_intern("sec"), 0);
306+
VALUE usec = rb_funcall(value, rb_intern("usec"), 0);
307+
308+
duckdb_time time = rbduckdb_to_duckdb_time_from_value(hour, min, sec, usec);
309+
((duckdb_time *)vector_data)[index] = time;
310+
break;
311+
}
297312
default:
298313
rb_raise(rb_eArgError, "Unsupported return type for scalar function");
299314
break;

lib/duckdb/scalar_function.rb

Lines changed: 4 additions & 4 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 BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, INTEGER, TIMESTAMP, and VARCHAR types.
7+
# Currently supports BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, INTEGER, TIME, TIMESTAMP, and VARCHAR types.
88
#
99
# @param logical_type [DuckDB::LogicalType] the return type
1010
# @return [DuckDB::ScalarFunction] self
@@ -13,10 +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 date double float integer timestamp varchar].include?(logical_type.type)
16+
unless %i[bigint blob boolean date double float integer time timestamp varchar].include?(logical_type.type)
1717
raise DuckDB::Error,
18-
'Only BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, INTEGER, TIMESTAMP, and VARCHAR return types are ' \
19-
'currently supported'
18+
'Only BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, INTEGER, TIME, TIMESTAMP, and VARCHAR return ' \
19+
'types are currently supported'
2020
end
2121

2222
_set_return_type(logical_type)

test/duckdb_test/scalar_function_test.rb

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ def test_return_type_setter
3737

3838
def test_return_type_setter_raises_error_for_unsupported_type
3939
sf = DuckDB::ScalarFunction.new
40-
time_type = DuckDB::LogicalType.new(14) # DUCKDB_TYPE_TIME (unsupported)
40+
interval_type = DuckDB::LogicalType.new(15) # DUCKDB_TYPE_INTERVAL (unsupported)
4141

4242
error = assert_raises(DuckDB::Error) do
43-
sf.return_type = time_type
43+
sf.return_type = interval_type
4444
end
4545

4646
assert_match(/only.*supported/i, error.message)
@@ -291,5 +291,28 @@ def test_scalar_function_date_return_type # rubocop:disable Metrics/AbcSize, Met
291291
assert_equal Date.new(2024, 1, 16), rows[0][0]
292292
assert_equal Date.new(2024, 12, 26), rows[1][0]
293293
end
294+
295+
def test_scalar_function_time_return_type # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Minitest/MultipleAssertions
296+
@con.execute('SET threads=1')
297+
@con.execute('CREATE TABLE test_table (t TIME)')
298+
@con.execute("INSERT INTO test_table VALUES ('10:30:00'), ('23:59:59')")
299+
300+
sf = DuckDB::ScalarFunction.new
301+
sf.name = 'add_one_hour'
302+
sf.add_parameter(DuckDB::LogicalType.new(14)) # TIME (type ID 14)
303+
sf.return_type = DuckDB::LogicalType.new(14) # TIME
304+
sf.set_function { |time| time + 3600 } # Add 1 hour (3600 seconds)
305+
306+
@con.register_scalar_function(sf)
307+
result = @con.execute('SELECT add_one_hour(t) FROM test_table ORDER BY t')
308+
rows = result.to_a
309+
310+
assert_equal 2, rows.size
311+
# TIME values are returned as Time objects with today's date
312+
assert_equal 11, rows[0][0].hour
313+
assert_equal 30, rows[0][0].min
314+
assert_equal 0, rows[1][0].hour
315+
assert_equal 59, rows[1][0].min
316+
end
294317
end
295318
end

0 commit comments

Comments
 (0)