Skip to content

Commit a8f18e1

Browse files
authored
Merge pull request #1124 from suketa/memory_helper_support_timestamp
Support TIMESTAMP column writing Ruby Time object in DataChunk#set_value
2 parents e891455 + 522e735 commit a8f18e1

File tree

5 files changed

+81
-1
lines changed

5 files changed

+81
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
All notable changes to this project will be documented in this file.
44

55
# Unreleased
6+
- support TIMESTAMP column writing Ruby Time object in `DuckDB::DataChunk#set_value`.
7+
- add `DuckDB::MemoryHelper.write_timestamp` method to write a Ruby Time object as a DuckDB timestamp to raw memory.
68
- add `DuckDB::Connection#expose_as_table` to expose a Ruby object as a queryable DuckDB table function via a registered adapter.
79
- add `DuckDB::TableFunction.add_table_adapter` to register a table adapter for a Ruby class.
810
- add `DuckDB::TableFunction.table_adapter_for` to look up a registered table adapter by class.

ext/duckdb/memory_helper.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,43 @@ static VALUE rbduckdb_memory_helper_write_float(VALUE self, VALUE ptr, VALUE ind
234234
return Qnil;
235235
}
236236

237+
/*
238+
* call-seq:
239+
* DuckDB::MemoryHelper.write_timestamp(ptr, index, value) -> nil
240+
*
241+
* Writes a DuckDB timestamp to raw memory.
242+
* +value+ must be a Ruby Time object.
243+
*
244+
* ptr = vector.get_data
245+
* DuckDB::MemoryHelper.write_timestamp(ptr, 0, Time.new(2024, 3, 15, 10, 30, 45))
246+
*/
247+
static VALUE rbduckdb_memory_helper_write_timestamp(VALUE self, VALUE ptr, VALUE index, VALUE value) {
248+
duckdb_timestamp *data;
249+
idx_t idx;
250+
251+
(void)self;
252+
253+
if (!rb_obj_is_kind_of(value, rb_cTime)) {
254+
rb_raise(rb_eTypeError, "Expected Time object for TIMESTAMP");
255+
}
256+
257+
data = (duckdb_timestamp *)NUM2ULL(ptr);
258+
idx = (idx_t)NUM2ULL(index);
259+
260+
VALUE local_time = rb_funcall(value, rb_intern("getlocal"), 0);
261+
data[idx] = rbduckdb_to_duckdb_timestamp_from_value(
262+
rb_funcall(local_time, rb_intern("year"), 0),
263+
rb_funcall(local_time, rb_intern("month"), 0),
264+
rb_funcall(local_time, rb_intern("day"), 0),
265+
rb_funcall(local_time, rb_intern("hour"), 0),
266+
rb_funcall(local_time, rb_intern("min"), 0),
267+
rb_funcall(local_time, rb_intern("sec"), 0),
268+
rb_funcall(local_time, rb_intern("usec"), 0)
269+
);
270+
271+
return Qnil;
272+
}
273+
237274
void rbduckdb_init_memory_helper(void) {
238275
mDuckDBMemoryHelper = rb_define_module_under(mDuckDB, "MemoryHelper");
239276

@@ -248,4 +285,5 @@ void rbduckdb_init_memory_helper(void) {
248285
rb_define_singleton_method(mDuckDBMemoryHelper, "write_uinteger", rbduckdb_memory_helper_write_uinteger, 3);
249286
rb_define_singleton_method(mDuckDBMemoryHelper, "write_ubigint", rbduckdb_memory_helper_write_ubigint, 3);
250287
rb_define_singleton_method(mDuckDBMemoryHelper, "write_float", rbduckdb_memory_helper_write_float, 3);
288+
rb_define_singleton_method(mDuckDBMemoryHelper, "write_timestamp", rbduckdb_memory_helper_write_timestamp, 3);
251289
}

lib/duckdb/data_chunk.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def set_value(col_idx, row_idx, value)
9494
vector.assign_string_element(row_idx, value.to_s)
9595
when :blob
9696
vector.assign_string_element_len(row_idx, value.to_s)
97+
when :timestamp
98+
data = vector.get_data
99+
MemoryHelper.write_timestamp(data, row_idx, value)
97100
else
98101
raise ArgumentError, "Unsupported type for DataChunk#set_value: #{type_id}"
99102
end

sample/issue922.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def infer_columns(data_frame)
3535

3636
DuckDB::TableFunction.add_table_adapter(Polars::DataFrame, PolarsDataFrameTableAdapter.new)
3737

38-
df = polars::dataframe.new(
38+
df = Polars::DataFrame.new(
3939
{
4040
a: [1, 2, 3],
4141
b: %w[one two three]

test/duckdb_test/data_chunk_test.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,5 +358,42 @@ def test_data_chunk_set_value_multiple_columns
358358
assert_in_delta 87.3, row1[2], 0.001
359359
end
360360
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Minitest/MultipleAssertions
361+
362+
# Test 12: DataChunk#set_value with TIMESTAMP
363+
def test_data_chunk_set_value_timestamp # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
364+
@conn.execute('SET threads=1')
365+
366+
done = false
367+
table_function = DuckDB::TableFunction.new
368+
table_function.name = 'test_set_value_timestamp'
369+
370+
table_function.bind do |bind_info|
371+
bind_info.add_result_column('ts', DuckDB::LogicalType::TIMESTAMP)
372+
end
373+
374+
table_function.init { |_init_info| done = false }
375+
376+
time1 = Time.new(2024, 3, 15, 10, 30, 45, '+00:00')
377+
time2 = Time.new(2000, 1, 1, 0, 0, 0, '+00:00')
378+
379+
table_function.execute do |_func_info, output|
380+
if done
381+
output.size = 0
382+
else
383+
output.set_value(0, 0, time1)
384+
output.set_value(0, 1, time2)
385+
output.size = 2
386+
done = true
387+
end
388+
end
389+
390+
@conn.register_table_function(table_function)
391+
result = @conn.query('SELECT * FROM test_set_value_timestamp()')
392+
rows = result.each.to_a
393+
394+
assert_equal 2, rows.length
395+
assert_equal time1.utc, rows[0].first.utc
396+
assert_equal time2.utc, rows[1].first.utc
397+
end
361398
end
362399
end

0 commit comments

Comments
 (0)