duckdb 1.5.1.0 → 1.5.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c55213e9e388de53b04f29987c7714975ab5c1a8fa10a40ae378189507dae752
4
- data.tar.gz: a96e6256ce85583fd395d936994d2a871727af8f7ba11fadc40a9d9ca79c15fa
3
+ metadata.gz: '0982b8680fcd79b493773dbbf39fd3f366fe11ccc952a8bfb4be0a140657e8fe'
4
+ data.tar.gz: 34cc46de733b8bc215197e5ceab3eb3022b0067ac34376b828ded5106d4b1261
5
5
  SHA512:
6
- metadata.gz: 67c2d5de633c5999ca07e3deb7c98a091b4eaedb70eacce2d2f7c34b9e8795b012d4415e197d5996ca80eb9c526dc28e86ced8f1d977cc040fcfd8c59a9a3ad2
7
- data.tar.gz: 38de479e2069a0b4dd1fa8684b2174d6f42c15375d54269f56983b530e2b7ea43150284b8825879cdceca1fe959f1c05b8043bb1c2202b30456c63eac8dbc6ab
6
+ metadata.gz: 2b3043a60bbb99bd449f709befb3b84d1036d1e0643c9ed3a93eb50e88df48962095caa537f23881bfc9434a035b240230c8d81004b1d863742916baa6540902
7
+ data.tar.gz: 34ff46b0100769147684a4092005838ba418ab1907fc5b0690bd37f883007b0004e4838a5fdfd04ca276d157d91d39523aba74eced99133f46a8039d52b3c036
@@ -20,7 +20,7 @@ jobs:
20
20
  timeout-minutes: 120
21
21
  strategy:
22
22
  matrix:
23
- ruby: ['3.2.9', '3.3.10', '3.4.9', '4.0.2', 'head']
23
+ ruby: ['3.2.11', '3.3.11', '3.4.9', '4.0.2', 'head']
24
24
  duckdb: ['1.4.4', '1.5.1']
25
25
 
26
26
  steps:
@@ -20,7 +20,7 @@ jobs:
20
20
  timeout-minutes: 120
21
21
  strategy:
22
22
  matrix:
23
- ruby: ['3.2.9', '3.3.10', '3.4.9', '4.0.2', 'head']
23
+ ruby: ['3.2.11', '3.3.11', '3.4.9', '4.0.2', 'head']
24
24
  duckdb: ['1.4.4', '1.5.1']
25
25
 
26
26
  services:
data/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  # Unreleased
6
6
 
7
+ # 1.5.1.1 - 2026-04-04
8
+
9
+ - fix `DuckDB::ScalarFunction` to allow `HUGEINT` and `UHUGEINT` as `return_type` and parameter type (the C extension's vector write path was missing those cases).
10
+ - add `DuckDB::ScalarFunctionSet` to register multiple overloads of a scalar function under one name (wraps `duckdb_scalar_function_set`).
11
+ - add `DuckDB::ScalarFunctionSet#add` to add a `DuckDB::ScalarFunction` overload to the set (wraps `duckdb_add_scalar_function_to_set`).
12
+ - add `DuckDB::Connection#register_scalar_function_set` to register a `DuckDB::ScalarFunctionSet` with the connection (wraps `duckdb_register_scalar_function_set`).
13
+ - `DuckDB::ScalarFunction.create` now accepts `name: nil` (optional) to allow creating nameless functions for use inside a `ScalarFunctionSet`.
14
+ - add `DuckDB::LogicalType.create_struct` to create a struct logical type.
15
+ - add `DuckDB::LogicalType.create_enum` to create an enum logical type.
16
+ - add `DuckDB::LogicalType.create_decimal` to create a decimal logical type.
17
+
7
18
  # 1.5.1.0 - 2026-03-29
8
19
 
9
20
  - add `DuckDB::ScalarFunction#varargs_type=` to register a scalar function that accepts a variable number of arguments of a given type (wraps `duckdb_scalar_function_set_varargs`).
@@ -17,6 +28,7 @@ All notable changes to this project will be documented in this file.
17
28
  - add `DuckDB::ScalarFunction::BindInfo#get_argument(index)` to return the expression at the given argument index as a `DuckDB::Expression` object (wraps `duckdb_scalar_function_bind_get_argument`). Raises `ArgumentError` for out-of-range index.
18
29
  - add `DuckDB::Expression#foldable?` to check whether an expression can be folded to a constant at query planning time (wraps `duckdb_expression_is_foldable`). Returns `true` for literals and constant arithmetic, `false` for column references and non-deterministic functions.
19
30
  - add `DuckDB::LogicalType.create_map` to create a map logical type.
31
+ - add `DuckDB::LogicalType.create_union` to create a union logical type.
20
32
  - bump duckdb to 1.5.1 on CI
21
33
  - add `DuckDB::ScalarFunction::BindInfo#client_context` to return the client context of the bind phase as a `DuckDB::ClientContext` object (wraps `duckdb_scalar_function_get_client_context`).
22
34
  - add `DuckDB::ClientContext#connection_id` to return the connection id of the client context (wraps `duckdb_client_context_get_connection_id`).
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- duckdb (1.5.1.0)
4
+ duckdb (1.5.1.1)
5
5
  bigdecimal (>= 3.1.4)
6
6
 
7
7
  GEM
@@ -14,10 +14,10 @@ GEM
14
14
  json (2.19.3)
15
15
  language_server-protocol (3.17.0.5)
16
16
  lint_roller (1.1.0)
17
- minitest (6.0.2)
17
+ minitest (6.0.3)
18
18
  drb (~> 2.0)
19
19
  prism (~> 1.5)
20
- parallel (1.27.0)
20
+ parallel (1.28.0)
21
21
  parser (3.3.11.1)
22
22
  ast (~> 2.4.1)
23
23
  racc
@@ -12,6 +12,7 @@ static VALUE duckdb_connection_query_progress(VALUE self);
12
12
  static VALUE duckdb_connection_connect(VALUE self, VALUE oDuckDBDatabase);
13
13
  static VALUE duckdb_connection_query_sql(VALUE self, VALUE str);
14
14
  static VALUE duckdb_connection_register_scalar_function(VALUE self, VALUE scalar_function);
15
+ static VALUE duckdb_connection_register_scalar_function_set(VALUE self, VALUE scalar_function_set);
15
16
  static VALUE duckdb_connection_register_table_function(VALUE self, VALUE table_function);
16
17
 
17
18
  static const rb_data_type_t connection_data_type = {
@@ -220,6 +221,27 @@ static VALUE duckdb_connection_register_scalar_function(VALUE self, VALUE scalar
220
221
  return self;
221
222
  }
222
223
 
224
+ /* :nodoc: */
225
+ static VALUE duckdb_connection_register_scalar_function_set(VALUE self, VALUE scalar_function_set) {
226
+ rubyDuckDBConnection *ctxcon;
227
+ rubyDuckDBScalarFunctionSet *ctxsfs;
228
+ duckdb_state state;
229
+
230
+ ctxcon = get_struct_connection(self);
231
+ ctxsfs = get_struct_scalar_function_set(scalar_function_set);
232
+
233
+ state = duckdb_register_scalar_function_set(ctxcon->con, ctxsfs->scalar_function_set);
234
+
235
+ if (state == DuckDBError) {
236
+ rb_raise(eDuckDBError, "Failed to register scalar function set");
237
+ }
238
+
239
+ /* Keep reference to prevent GC while connection is alive */
240
+ rb_ary_push(ctxcon->registered_functions, scalar_function_set);
241
+
242
+ return self;
243
+ }
244
+
223
245
  static VALUE duckdb_connection_register_table_function(VALUE self, VALUE table_function) {
224
246
  rubyDuckDBConnection *ctxcon;
225
247
  rubyDuckDBTableFunction *ctxtf;
@@ -251,6 +273,7 @@ void rbduckdb_init_duckdb_connection(void) {
251
273
  rb_define_method(cDuckDBConnection, "interrupt", duckdb_connection_interrupt, 0);
252
274
  rb_define_method(cDuckDBConnection, "query_progress", duckdb_connection_query_progress, 0);
253
275
  rb_define_private_method(cDuckDBConnection, "_register_scalar_function", duckdb_connection_register_scalar_function, 1);
276
+ rb_define_private_method(cDuckDBConnection, "_register_scalar_function_set", duckdb_connection_register_scalar_function_set, 1);
254
277
  rb_define_private_method(cDuckDBConnection, "_register_table_function", duckdb_connection_register_table_function, 1);
255
278
  rb_define_private_method(cDuckDBConnection, "_connect", duckdb_connection_connect, 1);
256
279
  /* TODO: query_sql => _query_sql */
@@ -8,6 +8,7 @@ extern ID id__to_interval_from_vector;
8
8
  extern ID id__to_hugeint_from_vector;
9
9
  extern ID id__to_decimal_from_hugeint;
10
10
  extern ID id__to_uuid_from_vector;
11
+ extern ID id__to_uuid_from_uhugeint;
11
12
  extern ID id__to_time_from_duckdb_timestamp_s;
12
13
  extern ID id__to_time_from_duckdb_timestamp_ms;
13
14
  extern ID id__to_time_from_duckdb_timestamp_ns;
@@ -15,6 +16,11 @@ extern ID id__to_time_from_duckdb_time_tz;
15
16
  extern ID id__to_time_from_duckdb_timestamp_tz;
16
17
  extern ID id__to_infinity;
17
18
 
19
+ VALUE rbduckdb_uuid_to_ruby(duckdb_hugeint h);
20
+ VALUE rbduckdb_uuid_uhugeint_to_ruby(duckdb_uhugeint h);
21
+ VALUE rbduckdb_interval_to_ruby(duckdb_interval i);
22
+ VALUE rbduckdb_hugeint_to_ruby(duckdb_hugeint h);
23
+ VALUE rbduckdb_uhugeint_to_ruby(duckdb_uhugeint h);
18
24
  VALUE rbduckdb_timestamp_s_to_ruby(duckdb_timestamp_s ts);
19
25
  VALUE rbduckdb_timestamp_ms_to_ruby(duckdb_timestamp_ms ts);
20
26
  VALUE rbduckdb_timestamp_ns_to_ruby(duckdb_timestamp_ns ts);
@@ -9,6 +9,7 @@ ID id__to_interval_from_vector;
9
9
  ID id__to_hugeint_from_vector;
10
10
  ID id__to_decimal_from_hugeint;
11
11
  ID id__to_uuid_from_vector;
12
+ ID id__to_uuid_from_uhugeint;
12
13
  ID id__to_time_from_duckdb_timestamp_s;
13
14
  ID id__to_time_from_duckdb_timestamp_ms;
14
15
  ID id__to_time_from_duckdb_timestamp_ns;
@@ -61,6 +62,42 @@ VALUE infinite_timestamp_ns_value(duckdb_timestamp_ns timestamp_ns) {
61
62
  return Qnil;
62
63
  }
63
64
 
65
+ VALUE rbduckdb_uuid_to_ruby(duckdb_hugeint h) {
66
+ return rb_funcall(mDuckDBConverter, id__to_uuid_from_vector, 2,
67
+ ULL2NUM(h.lower),
68
+ LL2NUM(h.upper)
69
+ );
70
+ }
71
+
72
+ VALUE rbduckdb_uuid_uhugeint_to_ruby(duckdb_uhugeint h) {
73
+ return rb_funcall(mDuckDBConverter, id__to_uuid_from_uhugeint, 2,
74
+ ULL2NUM(h.lower),
75
+ ULL2NUM(h.upper)
76
+ );
77
+ }
78
+
79
+ VALUE rbduckdb_interval_to_ruby(duckdb_interval i) {
80
+ return rb_funcall(mDuckDBConverter, id__to_interval_from_vector, 3,
81
+ INT2NUM(i.months),
82
+ INT2NUM(i.days),
83
+ LL2NUM(i.micros)
84
+ );
85
+ }
86
+
87
+ VALUE rbduckdb_hugeint_to_ruby(duckdb_hugeint h) {
88
+ return rb_funcall(mDuckDBConverter, id__to_hugeint_from_vector, 2,
89
+ ULL2NUM(h.lower),
90
+ LL2NUM(h.upper)
91
+ );
92
+ }
93
+
94
+ VALUE rbduckdb_uhugeint_to_ruby(duckdb_uhugeint h) {
95
+ return rb_funcall(mDuckDBConverter, id__to_hugeint_from_vector, 2,
96
+ ULL2NUM(h.lower),
97
+ ULL2NUM(h.upper)
98
+ );
99
+ }
100
+
64
101
  VALUE rbduckdb_timestamp_s_to_ruby(duckdb_timestamp_s ts) {
65
102
  VALUE obj = infinite_timestamp_s_value(ts);
66
103
  if (obj != Qnil) {
@@ -160,6 +197,7 @@ void rbduckdb_init_duckdb_converter(void) {
160
197
  id__to_hugeint_from_vector = rb_intern("_to_hugeint_from_vector");
161
198
  id__to_decimal_from_hugeint = rb_intern("_to_decimal_from_hugeint");
162
199
  id__to_uuid_from_vector = rb_intern("_to_uuid_from_vector");
200
+ id__to_uuid_from_uhugeint = rb_intern("_to_uuid_from_uhugeint");
163
201
  id__to_time_from_duckdb_timestamp_s = rb_intern("_to_time_from_duckdb_timestamp_s");
164
202
  id__to_time_from_duckdb_timestamp_ms = rb_intern("_to_time_from_duckdb_timestamp_ms");
165
203
  id__to_time_from_duckdb_timestamp_ns = rb_intern("_to_time_from_duckdb_timestamp_ns");
data/ext/duckdb/duckdb.c CHANGED
@@ -57,6 +57,7 @@ Init_duckdb_native(void) {
57
57
  rbduckdb_init_duckdb_instance_cache();
58
58
  rbduckdb_init_duckdb_value_impl();
59
59
  rbduckdb_init_duckdb_scalar_function();
60
+ rbduckdb_init_duckdb_scalar_function_set();
60
61
  rbduckdb_init_duckdb_expression();
61
62
  rbduckdb_init_duckdb_client_context();
62
63
  rbduckdb_init_duckdb_scalar_function_bind_info();
@@ -26,6 +26,8 @@ static VALUE duckdb_logical_type__set_alias(VALUE self, VALUE aname);
26
26
  static VALUE duckdb_logical_type_s_create_array_type(VALUE klass, VALUE child, VALUE array_size);
27
27
  static VALUE duckdb_logical_type_s_create_list_type(VALUE klass, VALUE child);
28
28
  static VALUE duckdb_logical_type_s_create_map_type(VALUE klass, VALUE key, VALUE value);
29
+ static VALUE duckdb_logical_type_s_create_union_type(VALUE klass, VALUE members);
30
+ static VALUE duckdb_logical_type_s_create_struct_type(VALUE klass, VALUE members);
29
31
  static VALUE initialize(VALUE self, VALUE type_id_arg);
30
32
 
31
33
  static const rb_data_type_t logical_type_data_type = {
@@ -490,6 +492,143 @@ static VALUE duckdb_logical_type_s_create_map_type(VALUE klass, VALUE key, VALUE
490
492
  return rbduckdb_create_logical_type(new_type);
491
493
  }
492
494
 
495
+ /*
496
+ * call-seq:
497
+ * DuckDB::LogicalType._create_union_type(members) -> DuckDB::LogicalType
498
+ *
499
+ * Return a union logical type from the given member hash.
500
+ */
501
+ static VALUE duckdb_logical_type_s_create_union_type(VALUE klass, VALUE members) {
502
+ idx_t member_size = RHASH_SIZE(members);
503
+ duckdb_logical_type *member_types = NULL;
504
+ const char **member_names = NULL;
505
+ duckdb_logical_type new_type;
506
+ VALUE keys;
507
+
508
+ if (member_size == 0) {
509
+ rb_raise(rb_eArgError, "members hash must not be empty");
510
+ }
511
+
512
+ member_types = (duckdb_logical_type *)xcalloc(member_size, sizeof(duckdb_logical_type));
513
+ member_names = (const char **)xcalloc(member_size, sizeof(const char *));
514
+
515
+ keys = rb_funcall(members, rb_intern("keys"), 0);
516
+
517
+ for (idx_t i = 0; i < member_size; i++) {
518
+ VALUE key = rb_ary_entry(keys, (long)i);
519
+ VALUE val = rb_hash_aref(members, key);
520
+ rubyDuckDBLogicalType *type_ctx = get_struct_logical_type(val);
521
+
522
+ member_names[i] = rb_id2name(SYM2ID(key));
523
+ member_types[i] = type_ctx->logical_type;
524
+ }
525
+
526
+ new_type = duckdb_create_union_type(member_types, member_names, member_size);
527
+
528
+ xfree(member_types);
529
+ xfree(member_names);
530
+
531
+ if (!new_type) {
532
+ rb_raise(eDuckDBError, "Failed to create union type");
533
+ }
534
+
535
+ return rbduckdb_create_logical_type(new_type);
536
+ }
537
+
538
+ /*
539
+ * call-seq:
540
+ * DuckDB::LogicalType._create_struct_type(members) -> DuckDB::LogicalType
541
+ *
542
+ * Return a struct logical type from the given member hash.
543
+ */
544
+ static VALUE duckdb_logical_type_s_create_struct_type(VALUE klass, VALUE members) {
545
+ idx_t member_size = RHASH_SIZE(members);
546
+ duckdb_logical_type *member_types = NULL;
547
+ const char **member_names = NULL;
548
+ duckdb_logical_type new_type;
549
+ VALUE keys;
550
+
551
+ if (member_size == 0) {
552
+ rb_raise(rb_eArgError, "members hash must not be empty");
553
+ }
554
+
555
+ member_types = (duckdb_logical_type *)xcalloc(member_size, sizeof(duckdb_logical_type));
556
+ member_names = (const char **)xcalloc(member_size, sizeof(const char *));
557
+
558
+ keys = rb_funcall(members, rb_intern("keys"), 0);
559
+
560
+ for (idx_t i = 0; i < member_size; i++) {
561
+ VALUE key = rb_ary_entry(keys, (long)i);
562
+ VALUE val = rb_hash_aref(members, key);
563
+ rubyDuckDBLogicalType *type_ctx = get_struct_logical_type(val);
564
+
565
+ member_names[i] = rb_id2name(SYM2ID(key));
566
+ member_types[i] = type_ctx->logical_type;
567
+ }
568
+
569
+ new_type = duckdb_create_struct_type(member_types, member_names, member_size);
570
+
571
+ xfree(member_types);
572
+ xfree(member_names);
573
+
574
+ if (!new_type) {
575
+ rb_raise(eDuckDBError, "Failed to create struct type");
576
+ }
577
+
578
+ return rbduckdb_create_logical_type(new_type);
579
+ }
580
+
581
+ /*
582
+ * call-seq:
583
+ * DuckDB::LogicalType._create_enum_type(members) -> DuckDB::LogicalType
584
+ *
585
+ * Return an enum logical type from the given array of strings.
586
+ */
587
+ static VALUE duckdb_logical_type_s_create_enum_type(VALUE klass, VALUE members) {
588
+ idx_t member_size = RARRAY_LEN(members);
589
+ const char **member_names = NULL;
590
+ duckdb_logical_type new_type;
591
+
592
+ if (member_size == 0) {
593
+ rb_raise(rb_eArgError, "members must not be empty");
594
+ }
595
+
596
+ member_names = (const char **)xcalloc(member_size, sizeof(const char *));
597
+
598
+ for (idx_t i = 0; i < member_size; i++) {
599
+ VALUE val = rb_ary_entry(members, (long)i);
600
+ member_names[i] = StringValueCStr(val);
601
+ }
602
+
603
+ new_type = duckdb_create_enum_type(member_names, member_size);
604
+
605
+ xfree(member_names);
606
+
607
+ if (!new_type) {
608
+ rb_raise(eDuckDBError, "Failed to create enum type");
609
+ }
610
+
611
+ return rbduckdb_create_logical_type(new_type);
612
+ }
613
+
614
+ /*
615
+ * call-seq:
616
+ * DuckDB::LogicalType._create_decimal_type(width, scale) -> DuckDB::LogicalType
617
+ *
618
+ * Return a decimal logical type with the given width and scale.
619
+ */
620
+ static VALUE duckdb_logical_type_s_create_decimal_type(VALUE klass, VALUE width, VALUE scale) {
621
+ duckdb_logical_type new_type;
622
+
623
+ new_type = duckdb_create_decimal_type((uint8_t)NUM2UINT(width), (uint8_t)NUM2UINT(scale));
624
+
625
+ if (!new_type) {
626
+ rb_raise(eDuckDBError, "Failed to create decimal type");
627
+ }
628
+
629
+ return rbduckdb_create_logical_type(new_type);
630
+ }
631
+
493
632
  VALUE rbduckdb_create_logical_type(duckdb_logical_type logical_type) {
494
633
  VALUE obj;
495
634
  rubyDuckDBLogicalType *ctx;
@@ -534,6 +673,14 @@ void rbduckdb_init_duckdb_logical_type(void) {
534
673
  duckdb_logical_type_s_create_list_type, 1);
535
674
  rb_define_private_method(rb_singleton_class(cDuckDBLogicalType), "_create_map_type",
536
675
  duckdb_logical_type_s_create_map_type, 2);
676
+ rb_define_private_method(rb_singleton_class(cDuckDBLogicalType), "_create_union_type",
677
+ duckdb_logical_type_s_create_union_type, 1);
678
+ rb_define_private_method(rb_singleton_class(cDuckDBLogicalType), "_create_struct_type",
679
+ duckdb_logical_type_s_create_struct_type, 1);
680
+ rb_define_private_method(rb_singleton_class(cDuckDBLogicalType), "_create_enum_type",
681
+ duckdb_logical_type_s_create_enum_type, 1);
682
+ rb_define_private_method(rb_singleton_class(cDuckDBLogicalType), "_create_decimal_type",
683
+ duckdb_logical_type_s_create_decimal_type, 2);
537
684
 
538
685
  rb_define_method(cDuckDBLogicalType, "initialize", initialize, 1);
539
686
  }
@@ -251,22 +251,14 @@ static VALUE rbduckdb_memory_helper_write_timestamp(VALUE self, VALUE ptr, VALUE
251
251
  (void)self;
252
252
 
253
253
  if (!rb_obj_is_kind_of(value, rb_cTime)) {
254
- rb_raise(rb_eTypeError, "Expected Time object for TIMESTAMP");
254
+ rb_raise(rb_eTypeError, "Expected Time object");
255
255
  }
256
256
 
257
257
  data = (duckdb_timestamp *)NUM2ULL(ptr);
258
258
  idx = (idx_t)NUM2ULL(index);
259
259
 
260
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
- );
261
+ data[idx] = rbduckdb_to_duckdb_timestamp_from_time_value(local_time);
270
262
 
271
263
  return Qnil;
272
264
  }
data/ext/duckdb/result.c CHANGED
@@ -287,12 +287,7 @@ static VALUE vector_time(void* vector_data, idx_t row_idx) {
287
287
 
288
288
 
289
289
  static VALUE vector_interval(void* vector_data, idx_t row_idx) {
290
- duckdb_interval data = ((duckdb_interval *)vector_data)[row_idx];
291
- return rb_funcall(mDuckDBConverter, id__to_interval_from_vector, 3,
292
- INT2NUM(data.months),
293
- INT2NUM(data.days),
294
- LL2NUM(data.micros)
295
- );
290
+ return rbduckdb_interval_to_ruby(((duckdb_interval *)vector_data)[row_idx]);
296
291
  }
297
292
 
298
293
  static VALUE vector_blob(void* vector_data, idx_t row_idx) {
@@ -314,19 +309,11 @@ static VALUE vector_varchar(void* vector_data, idx_t row_idx) {
314
309
  }
315
310
 
316
311
  static VALUE vector_hugeint(void* vector_data, idx_t row_idx) {
317
- duckdb_hugeint hugeint = ((duckdb_hugeint *)vector_data)[row_idx];
318
- return rb_funcall(mDuckDBConverter, id__to_hugeint_from_vector, 2,
319
- ULL2NUM(hugeint.lower),
320
- LL2NUM(hugeint.upper)
321
- );
312
+ return rbduckdb_hugeint_to_ruby(((duckdb_hugeint *)vector_data)[row_idx]);
322
313
  }
323
314
 
324
315
  static VALUE vector_uhugeint(void* vector_data, idx_t row_idx) {
325
- duckdb_uhugeint uhugeint = ((duckdb_uhugeint *)vector_data)[row_idx];
326
- return rb_funcall(mDuckDBConverter, id__to_hugeint_from_vector, 2,
327
- ULL2NUM(uhugeint.lower),
328
- ULL2NUM(uhugeint.upper)
329
- );
316
+ return rbduckdb_uhugeint_to_ruby(((duckdb_uhugeint *)vector_data)[row_idx]);
330
317
  }
331
318
 
332
319
  static VALUE vector_decimal(duckdb_logical_type ty, void* vector_data, idx_t row_idx) {
@@ -696,11 +683,7 @@ static VALUE vector_timestamp_tz(void* vector_data, idx_t row_idx) {
696
683
  }
697
684
 
698
685
  static VALUE vector_uuid(void* vector_data, idx_t row_idx) {
699
- duckdb_hugeint hugeint = ((duckdb_hugeint *)vector_data)[row_idx];
700
- return rb_funcall(mDuckDBConverter, id__to_uuid_from_vector, 2,
701
- ULL2NUM(hugeint.lower),
702
- LL2NUM(hugeint.upper)
703
- );
686
+ return rbduckdb_uuid_to_ruby(((duckdb_hugeint *)vector_data)[row_idx]);
704
687
  }
705
688
 
706
689
  static VALUE vector_value(duckdb_vector vector, idx_t row_idx) {
@@ -30,6 +30,7 @@
30
30
  #include "./instance_cache.h"
31
31
  #include "./value_impl.h"
32
32
  #include "./scalar_function.h"
33
+ #include "./scalar_function_set.h"
33
34
  #include "./expression.h"
34
35
  #include "./client_context.h"
35
36
  #include "./scalar_function_bind_info.h"
@@ -589,6 +589,20 @@ static void vector_set_value_at(duckdb_vector vector, duckdb_logical_type elemen
589
589
  case DUCKDB_TYPE_UBIGINT:
590
590
  ((uint64_t *)vector_data)[index] = NUM2ULL(value);
591
591
  break;
592
+ case DUCKDB_TYPE_HUGEINT: {
593
+ duckdb_hugeint hugeint;
594
+ hugeint.lower = NUM2ULL(rb_funcall(mDuckDBConverter, rb_intern("_hugeint_lower"), 1, value));
595
+ hugeint.upper = NUM2LL(rb_funcall(mDuckDBConverter, rb_intern("_hugeint_upper"), 1, value));
596
+ ((duckdb_hugeint *)vector_data)[index] = hugeint;
597
+ break;
598
+ }
599
+ case DUCKDB_TYPE_UHUGEINT: {
600
+ duckdb_uhugeint uhugeint;
601
+ uhugeint.lower = NUM2ULL(rb_funcall(mDuckDBConverter, rb_intern("_hugeint_lower"), 1, value));
602
+ uhugeint.upper = NUM2ULL(rb_funcall(mDuckDBConverter, rb_intern("_hugeint_upper"), 1, value));
603
+ ((duckdb_uhugeint *)vector_data)[index] = uhugeint;
604
+ break;
605
+ }
592
606
  case DUCKDB_TYPE_FLOAT:
593
607
  ((float *)vector_data)[index] = (float)NUM2DBL(value);
594
608
  break;
@@ -612,21 +626,33 @@ static void vector_set_value_at(duckdb_vector vector, duckdb_logical_type elemen
612
626
  break;
613
627
  }
614
628
  case DUCKDB_TYPE_TIMESTAMP: {
615
- /* Convert Ruby Time to DuckDB timestamp (microseconds since epoch) */
616
- if (!rb_obj_is_kind_of(value, rb_cTime)) {
617
- rb_raise(rb_eTypeError, "Expected Time object for TIMESTAMP");
618
- }
619
-
620
- duckdb_timestamp_struct ts_struct;
621
- ts_struct.date.year = NUM2INT(rb_funcall(value, rb_intern("year"), 0));
622
- ts_struct.date.month = NUM2INT(rb_funcall(value, rb_intern("month"), 0));
623
- ts_struct.date.day = NUM2INT(rb_funcall(value, rb_intern("day"), 0));
624
- ts_struct.time.hour = NUM2INT(rb_funcall(value, rb_intern("hour"), 0));
625
- ts_struct.time.min = NUM2INT(rb_funcall(value, rb_intern("min"), 0));
626
- ts_struct.time.sec = NUM2INT(rb_funcall(value, rb_intern("sec"), 0));
627
- ts_struct.time.micros = NUM2INT(rb_funcall(value, rb_intern("usec"), 0));
628
-
629
- duckdb_timestamp ts = duckdb_to_timestamp(ts_struct);
629
+ duckdb_timestamp ts = rbduckdb_to_duckdb_timestamp_from_time_value(value);
630
+ ((duckdb_timestamp *)vector_data)[index] = ts;
631
+ break;
632
+ }
633
+ case DUCKDB_TYPE_TIMESTAMP_S: {
634
+ duckdb_timestamp ts = rbduckdb_to_duckdb_timestamp_from_time_value(value);
635
+ duckdb_timestamp_s ts_s;
636
+ ts_s.seconds = ts.micros / 1000000;
637
+ ((duckdb_timestamp_s *)vector_data)[index] = ts_s;
638
+ break;
639
+ }
640
+ case DUCKDB_TYPE_TIMESTAMP_MS: {
641
+ duckdb_timestamp ts = rbduckdb_to_duckdb_timestamp_from_time_value(value);
642
+ duckdb_timestamp_ms ts_ms;
643
+ ts_ms.millis = ts.micros / 1000;
644
+ ((duckdb_timestamp_ms *)vector_data)[index] = ts_ms;
645
+ break;
646
+ }
647
+ case DUCKDB_TYPE_TIMESTAMP_NS: {
648
+ duckdb_timestamp ts = rbduckdb_to_duckdb_timestamp_from_time_value(value);
649
+ duckdb_timestamp_ns ts_ns;
650
+ ts_ns.nanos = ts.micros * 1000;
651
+ ((duckdb_timestamp_ns *)vector_data)[index] = ts_ns;
652
+ break;
653
+ }
654
+ case DUCKDB_TYPE_TIMESTAMP_TZ: {
655
+ duckdb_timestamp ts = rbduckdb_to_duckdb_timestamp_from_time_value(value);
630
656
  ((duckdb_timestamp *)vector_data)[index] = ts;
631
657
  break;
632
658
  }
@@ -660,6 +686,25 @@ static void vector_set_value_at(duckdb_vector vector, duckdb_logical_type elemen
660
686
  ((duckdb_time *)vector_data)[index] = time;
661
687
  break;
662
688
  }
689
+ case DUCKDB_TYPE_TIME_TZ: {
690
+ if (!rb_obj_is_kind_of(value, rb_cTime)) {
691
+ rb_raise(rb_eTypeError, "Expected Time object for TIME_TZ");
692
+ }
693
+
694
+ VALUE hour = rb_funcall(value, rb_intern("hour"), 0);
695
+ VALUE min = rb_funcall(value, rb_intern("min"), 0);
696
+ VALUE sec = rb_funcall(value, rb_intern("sec"), 0);
697
+ VALUE usec = rb_funcall(value, rb_intern("usec"), 0);
698
+ VALUE utc_offset = rb_funcall(value, rb_intern("utc_offset"), 0);
699
+
700
+ duckdb_time t = rbduckdb_to_duckdb_time_from_value(hour, min, sec, usec);
701
+ int64_t micros = t.micros;
702
+ int32_t offset = NUM2INT(utc_offset);
703
+
704
+ duckdb_time_tz time_tz = duckdb_create_time_tz(micros, offset);
705
+ ((duckdb_time_tz *)vector_data)[index] = time_tz;
706
+ break;
707
+ }
663
708
  default:
664
709
  rb_raise(rb_eArgError, "Unsupported return type for scalar function");
665
710
  break;
@@ -0,0 +1,86 @@
1
+ #include "ruby-duckdb.h"
2
+
3
+ VALUE cDuckDBScalarFunctionSet;
4
+
5
+ static void mark(void *);
6
+ static void deallocate(void *);
7
+ static VALUE allocate(VALUE klass);
8
+ static size_t memsize(const void *p);
9
+ static void compact(void *);
10
+ static VALUE rbduckdb_scalar_function_set__initialize(VALUE self, VALUE name);
11
+ static VALUE rbduckdb_scalar_function_set__add(VALUE self, VALUE scalar_function);
12
+
13
+ static const rb_data_type_t scalar_function_set_data_type = {
14
+ "DuckDB/ScalarFunctionSet",
15
+ {mark, deallocate, memsize, compact},
16
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
17
+ };
18
+
19
+ static void mark(void *ctx) {
20
+ rubyDuckDBScalarFunctionSet *p = (rubyDuckDBScalarFunctionSet *)ctx;
21
+ rb_gc_mark(p->functions);
22
+ }
23
+
24
+ static void deallocate(void *ctx) {
25
+ rubyDuckDBScalarFunctionSet *p = (rubyDuckDBScalarFunctionSet *)ctx;
26
+ duckdb_destroy_scalar_function_set(&(p->scalar_function_set));
27
+ xfree(p);
28
+ }
29
+
30
+ static void compact(void *ctx) {
31
+ rubyDuckDBScalarFunctionSet *p = (rubyDuckDBScalarFunctionSet *)ctx;
32
+ p->functions = rb_gc_location(p->functions);
33
+ }
34
+
35
+ static VALUE allocate(VALUE klass) {
36
+ rubyDuckDBScalarFunctionSet *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBScalarFunctionSet));
37
+ VALUE obj = TypedData_Wrap_Struct(klass, &scalar_function_set_data_type, ctx);
38
+ ctx->functions = rb_ary_new();
39
+ RB_GC_GUARD(ctx->functions);
40
+ return obj;
41
+ }
42
+
43
+ static size_t memsize(const void *p) {
44
+ return sizeof(rubyDuckDBScalarFunctionSet);
45
+ }
46
+
47
+ rubyDuckDBScalarFunctionSet *get_struct_scalar_function_set(VALUE obj) {
48
+ rubyDuckDBScalarFunctionSet *ctx;
49
+ TypedData_Get_Struct(obj, rubyDuckDBScalarFunctionSet, &scalar_function_set_data_type, ctx);
50
+ return ctx;
51
+ }
52
+
53
+ /* :nodoc: */
54
+ static VALUE rbduckdb_scalar_function_set__initialize(VALUE self, VALUE name) {
55
+ rubyDuckDBScalarFunctionSet *p;
56
+
57
+ TypedData_Get_Struct(self, rubyDuckDBScalarFunctionSet, &scalar_function_set_data_type, p);
58
+ p->scalar_function_set = duckdb_create_scalar_function_set(StringValueCStr(name));
59
+ return self;
60
+ }
61
+
62
+ /* :nodoc: */
63
+ static VALUE rbduckdb_scalar_function_set__add(VALUE self, VALUE scalar_function) {
64
+ rubyDuckDBScalarFunctionSet *p;
65
+ rubyDuckDBScalarFunction *sf;
66
+
67
+ TypedData_Get_Struct(self, rubyDuckDBScalarFunctionSet, &scalar_function_set_data_type, p);
68
+ sf = get_struct_scalar_function(scalar_function);
69
+
70
+ if (duckdb_add_scalar_function_to_set(p->scalar_function_set, sf->scalar_function) == DuckDBError) {
71
+ rb_raise(eDuckDBError, "failed to add scalar function to set (duplicate overload?)");
72
+ }
73
+
74
+ rb_ary_push(p->functions, scalar_function);
75
+ return self;
76
+ }
77
+
78
+ void rbduckdb_init_duckdb_scalar_function_set(void) {
79
+ #if 0
80
+ VALUE mDuckDB = rb_define_module("DuckDB");
81
+ #endif
82
+ cDuckDBScalarFunctionSet = rb_define_class_under(mDuckDB, "ScalarFunctionSet", rb_cObject);
83
+ rb_define_alloc_func(cDuckDBScalarFunctionSet, allocate);
84
+ rb_define_private_method(cDuckDBScalarFunctionSet, "_initialize", rbduckdb_scalar_function_set__initialize, 1);
85
+ rb_define_private_method(cDuckDBScalarFunctionSet, "_add", rbduckdb_scalar_function_set__add, 1);
86
+ }
@@ -0,0 +1,14 @@
1
+ #ifndef RUBY_DUCKDB_SCALAR_FUNCTION_SET_H
2
+ #define RUBY_DUCKDB_SCALAR_FUNCTION_SET_H
3
+
4
+ struct _rubyDuckDBScalarFunctionSet {
5
+ duckdb_scalar_function_set scalar_function_set;
6
+ VALUE functions; /* Ruby Array of ScalarFunction objects — prevents GC collection */
7
+ };
8
+
9
+ typedef struct _rubyDuckDBScalarFunctionSet rubyDuckDBScalarFunctionSet;
10
+
11
+ void rbduckdb_init_duckdb_scalar_function_set(void);
12
+ rubyDuckDBScalarFunctionSet *get_struct_scalar_function_set(VALUE obj);
13
+
14
+ #endif
data/ext/duckdb/util.c CHANGED
@@ -21,6 +21,22 @@ duckdb_time rbduckdb_to_duckdb_time_from_value(VALUE hour, VALUE min, VALUE sec,
21
21
  return duckdb_to_time(time_st);
22
22
  }
23
23
 
24
+ duckdb_timestamp rbduckdb_to_duckdb_timestamp_from_time_value(VALUE time_obj) {
25
+ if (!rb_obj_is_kind_of(time_obj, rb_cTime)) {
26
+ rb_raise(rb_eTypeError, "Expected Time object");
27
+ }
28
+
29
+ return rbduckdb_to_duckdb_timestamp_from_value(
30
+ rb_funcall(time_obj, rb_intern("year"), 0),
31
+ rb_funcall(time_obj, rb_intern("month"), 0),
32
+ rb_funcall(time_obj, rb_intern("day"), 0),
33
+ rb_funcall(time_obj, rb_intern("hour"), 0),
34
+ rb_funcall(time_obj, rb_intern("min"), 0),
35
+ rb_funcall(time_obj, rb_intern("sec"), 0),
36
+ rb_funcall(time_obj, rb_intern("usec"), 0)
37
+ );
38
+ }
39
+
24
40
  duckdb_timestamp rbduckdb_to_duckdb_timestamp_from_value(VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros) {
25
41
  duckdb_timestamp_struct timestamp_st;
26
42
 
data/ext/duckdb/util.h CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  duckdb_date rbduckdb_to_duckdb_date_from_value(VALUE year, VALUE month, VALUE day);
5
5
  duckdb_time rbduckdb_to_duckdb_time_from_value(VALUE hour, VALUE min, VALUE sec, VALUE micros);
6
+ duckdb_timestamp rbduckdb_to_duckdb_timestamp_from_time_value(VALUE time_obj);
6
7
  duckdb_timestamp rbduckdb_to_duckdb_timestamp_from_value(VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros);
7
8
  void rbduckdb_to_duckdb_interval_from_value(duckdb_interval* interval, VALUE months, VALUE days, VALUE micros);
8
9
 
@@ -61,6 +61,12 @@ VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
61
61
  case DUCKDB_TYPE_BIGINT:
62
62
  result = LL2NUM(duckdb_get_int64(val));
63
63
  break;
64
+ case DUCKDB_TYPE_HUGEINT:
65
+ result = rbduckdb_hugeint_to_ruby(duckdb_get_hugeint(val));
66
+ break;
67
+ case DUCKDB_TYPE_UHUGEINT:
68
+ result = rbduckdb_uhugeint_to_ruby(duckdb_get_uhugeint(val));
69
+ break;
64
70
  case DUCKDB_TYPE_UTINYINT:
65
71
  result = INT2FIX(duckdb_get_uint8(val));
66
72
  break;
@@ -88,6 +94,9 @@ VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
88
94
  case DUCKDB_TYPE_TIME:
89
95
  result = rbduckdb_time_to_ruby(duckdb_get_time(val));
90
96
  break;
97
+ case DUCKDB_TYPE_INTERVAL:
98
+ result = rbduckdb_interval_to_ruby(duckdb_get_interval(val));
99
+ break;
91
100
  case DUCKDB_TYPE_TIMESTAMP_S:
92
101
  result = rbduckdb_timestamp_s_to_ruby(duckdb_get_timestamp_s(val));
93
102
  break;
@@ -108,6 +117,9 @@ VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
108
117
  result = rb_str_new_cstr(str);
109
118
  duckdb_free(str);
110
119
  break;
120
+ case DUCKDB_TYPE_UUID:
121
+ result = rbduckdb_uuid_uhugeint_to_ruby(duckdb_get_uuid(val));
122
+ break;
111
123
  default:
112
124
  result = Qnil;
113
125
  break;
@@ -187,6 +187,28 @@ module DuckDB
187
187
  _register_scalar_function(sf)
188
188
  end
189
189
 
190
+ # Registers a scalar function set with the connection.
191
+ # A scalar function set groups multiple overloads of a function under one name,
192
+ # allowing DuckDB to dispatch to the correct implementation based on argument types.
193
+ #
194
+ # @param scalar_function_set [DuckDB::ScalarFunctionSet] the function set to register
195
+ # @return [void]
196
+ # @raise [TypeError] if argument is not a DuckDB::ScalarFunctionSet
197
+ #
198
+ # @example Register multiple overloads under one name
199
+ # add_int = DuckDB::ScalarFunction.create(return_type: :integer, parameter_types: %i[integer integer]) { |a, b| a + b }
200
+ # add_dbl = DuckDB::ScalarFunction.create(return_type: :double, parameter_types: %i[double double]) { |a, b| a + b }
201
+ # set = DuckDB::ScalarFunctionSet.new(:add)
202
+ # set.add(add_int).add(add_dbl)
203
+ # con.register_scalar_function_set(set)
204
+ def register_scalar_function_set(scalar_function_set)
205
+ unless scalar_function_set.is_a?(ScalarFunctionSet)
206
+ raise TypeError, "#{scalar_function_set.class} is not a DuckDB::ScalarFunctionSet"
207
+ end
208
+
209
+ _register_scalar_function_set(scalar_function_set)
210
+ end
211
+
190
212
  #
191
213
  # Registers a table function with the database connection.
192
214
  #
@@ -10,6 +10,7 @@ module DuckDB
10
10
  module Converter # :nodoc: all
11
11
  HALF_HUGEINT_BIT = 64
12
12
  HALF_HUGEINT = 1 << HALF_HUGEINT_BIT
13
+ LOWER_HUGEINT_MASK = HALF_HUGEINT - 1
13
14
  FLIP_HUGEINT = 1 << 63
14
15
  EPOCH = Time.local(1970, 1, 1)
15
16
  EPOCH_UTC = Time.utc(1970, 1, 1)
@@ -93,16 +94,21 @@ module DuckDB
93
94
  (upper << HALF_HUGEINT_BIT) + lower
94
95
  end
95
96
 
97
+ def _hugeint_lower(value)
98
+ value & LOWER_HUGEINT_MASK
99
+ end
100
+
101
+ def _hugeint_upper(value)
102
+ value >> HALF_HUGEINT_BIT
103
+ end
104
+
96
105
  def _to_decimal_from_hugeint(width, scale, upper, lower = nil)
97
106
  v = lower.nil? ? upper : _to_hugeint_from_vector(lower, upper)
98
107
  _to_decimal_from_value(width, scale, v)
99
108
  end
100
109
 
101
110
  def _to_decimal_from_value(_width, scale, value)
102
- v = value.to_s
103
- v = v.rjust(scale + 1, '0') if v.length < scale
104
- v[-scale, 0] = '.' if scale.positive?
105
- BigDecimal(v)
111
+ BigDecimal("#{value}e-#{scale}")
106
112
  end
107
113
 
108
114
  def _to_interval_from_vector(months, days, micros)
@@ -117,6 +123,11 @@ module DuckDB
117
123
  "#{str[0, 8]}-#{str[8, 4]}-#{str[12, 4]}-#{str[16, 4]}-#{str[20, 12]}"
118
124
  end
119
125
 
126
+ def _to_uuid_from_uhugeint(lower, upper)
127
+ str = _to_hugeint_from_vector(lower, upper).to_s(16).rjust(32, '0')
128
+ "#{str[0, 8]}-#{str[8, 4]}-#{str[12, 4]}-#{str[16, 4]}-#{str[20, 12]}"
129
+ end
130
+
120
131
  def _parse_date(value)
121
132
  case value
122
133
  when Date, Time
@@ -177,9 +188,7 @@ module DuckDB
177
188
  def integer_to_hugeint(value)
178
189
  case value
179
190
  when Integer
180
- upper = value >> HALF_HUGEINT_BIT
181
- lower = value - (upper << HALF_HUGEINT_BIT)
182
- [lower, upper]
191
+ [_hugeint_lower(value), _hugeint_upper(value)]
183
192
  else
184
193
  raise(ArgumentError, "The argument `#{value.inspect}` must be Integer.")
185
194
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module DuckDB
4
4
  class LogicalType # rubocop:disable Metrics/ClassLength
5
+ RANGE_DECIMAL_WIDTH = 1..38
6
+
5
7
  alias :alias get_alias
6
8
  alias :alias= set_alias
7
9
 
@@ -38,6 +40,7 @@ module DuckDB
38
40
  # array: 33,
39
41
  # uuid: 27,
40
42
  # union: 28,
43
+ uuid: 27,
41
44
  bit: 29,
42
45
  time_tz: 30,
43
46
  timestamp_tz: 31,
@@ -111,6 +114,69 @@ module DuckDB
111
114
  _create_map_type(LogicalType.resolve(key_type), LogicalType.resolve(value_type))
112
115
  end
113
116
 
117
+ # Creates a union logical type with the given member names and types.
118
+ #
119
+ # The keyword arguments map member names to types. Each type can be
120
+ # a symbol or a DuckDB::LogicalType instance.
121
+ #
122
+ # require 'duckdb'
123
+ #
124
+ # union_type = DuckDB::LogicalType.create_union(num: :integer, str: :varchar)
125
+ # union_type.type #=> :union
126
+ # union_type.member_count #=> 2
127
+ # union_type.member_name_at(0) #=> "num"
128
+ # union_type.member_type_at(0).type #=> :integer
129
+ def create_union(**members)
130
+ resolved = members.transform_values { |v| LogicalType.resolve(v) }
131
+ _create_union_type(resolved)
132
+ end
133
+
134
+ # Creates a struct logical type with the given member names and types.
135
+ #
136
+ # The keyword arguments map member names to types. Each type can be
137
+ # a symbol or a DuckDB::LogicalType instance.
138
+ #
139
+ # require 'duckdb'
140
+ #
141
+ # struct_type = DuckDB::LogicalType.create_struct(name: :varchar, age: :integer)
142
+ # struct_type.type #=> :struct
143
+ # struct_type.child_count #=> 2
144
+ # struct_type.child_name_at(0) #=> "name"
145
+ # struct_type.child_type_at(0).type #=> :varchar
146
+ def create_struct(**members)
147
+ resolved = members.transform_values { |v| LogicalType.resolve(v) }
148
+ _create_struct_type(resolved)
149
+ end
150
+
151
+ # Creates an enum logical type with the given members.
152
+ #
153
+ # Each member must be a String representing an enum member.
154
+ #
155
+ # require 'duckdb'
156
+ #
157
+ # enum_type = DuckDB::LogicalType.create_enum('happy', 'sad', 'neutral')
158
+ # enum_type.type #=> :enum
159
+ # enum_type.dictionary_size #=> 3
160
+ # enum_type.dictionary_value_at(0) #=> "happy"
161
+ def create_enum(*members)
162
+ _create_enum_type(members.map(&:to_s))
163
+ end
164
+
165
+ # Creates a decimal logical type with the given width and scale.
166
+ #
167
+ # require 'duckdb'
168
+ #
169
+ # decimal_type = DuckDB::LogicalType.create_decimal(18, 3)
170
+ # decimal_type.type #=> :decimal
171
+ # decimal_type.width #=> 18
172
+ # decimal_type.scale #=> 3
173
+ def create_decimal(width, scale)
174
+ raise DuckDB::Error, 'width must be between 1 and 38' unless RANGE_DECIMAL_WIDTH.cover?(width)
175
+ raise DuckDB::Error, "scale must be between 0 and width(#{width})" unless (0..width).cover?(scale)
176
+
177
+ _create_decimal_type(width, scale)
178
+ end
179
+
114
180
  private
115
181
 
116
182
  def raise_resolve_error(symbol)
@@ -7,7 +7,8 @@ module DuckDB
7
7
  class ScalarFunction
8
8
  # Create and configure a scalar function in one call
9
9
  #
10
- # @param name [String, Symbol] the function name
10
+ # @param name [String, Symbol, nil] the function name; use +nil+ when creating overloads
11
+ # intended for use in a +DuckDB::ScalarFunctionSet+ (the set provides the name)
11
12
  # @param return_type [DuckDB::LogicalType|:logical_type_symbol] the return type
12
13
  # @param parameter_type [DuckDB::LogicalType|:logical_type_symbol, nil] single fixed parameter type
13
14
  # @param parameter_types [Array<DuckDB::LogicalType|:logical_type_symbol>, nil] multiple fixed parameter types
@@ -63,7 +64,7 @@ module DuckDB
63
64
  # null_handling: true
64
65
  # ) { |v| v.nil? ? 0 : v }
65
66
  def self.create( # rubocop:disable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/ParameterLists
66
- name:, return_type:, parameter_type: nil, parameter_types: nil, varargs_type: nil, null_handling: false, &
67
+ return_type:, name: nil, parameter_type: nil, parameter_types: nil, varargs_type: nil, null_handling: false, &
67
68
  )
68
69
  raise ArgumentError, 'Block required' unless block_given?
69
70
  raise ArgumentError, 'Cannot specify both parameter_type and parameter_types' if parameter_type && parameter_types
@@ -77,7 +78,7 @@ module DuckDB
77
78
  end
78
79
 
79
80
  sf = new
80
- sf.name = name.to_s
81
+ sf.name = name.to_s if name
81
82
  sf.return_type = return_type
82
83
  params.each { |type| sf.add_parameter(type) }
83
84
  sf.varargs_type = varargs_type if varargs_type
@@ -95,7 +96,9 @@ module DuckDB
95
96
  date
96
97
  double
97
98
  float
99
+ hugeint
98
100
  integer
101
+ interval
99
102
  smallint
100
103
  time
101
104
  timestamp
@@ -106,18 +109,20 @@ module DuckDB
106
109
  timestamp_tz
107
110
  tinyint
108
111
  ubigint
112
+ uhugeint
109
113
  uinteger
110
114
  usmallint
111
115
  utinyint
116
+ uuid
112
117
  varchar
113
118
  ].freeze
114
119
 
115
120
  private_constant :SUPPORTED_TYPES
116
121
 
117
122
  # Adds a parameter to the scalar function.
118
- # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, INTEGER, SMALLINT, TIME, TIMESTAMP,
119
- # TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UINTEGER, USMALLINT, UTINYINT,
120
- # and VARCHAR types.
123
+ # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT, TIME,
124
+ # TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT,
125
+ # UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
121
126
  #
122
127
  # @param logical_type [DuckDB::LogicalType | :logical_type_symbol] the parameter type
123
128
  # @return [DuckDB::ScalarFunction] self
@@ -129,9 +134,9 @@ module DuckDB
129
134
  end
130
135
 
131
136
  # Sets the return type for the scalar function.
132
- # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, INTEGER, SMALLINT, TIME, TIMESTAMP,
133
- # TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UINTEGER, USMALLINT, UTINYINT,
134
- # and VARCHAR types.
137
+ # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT, TIME,
138
+ # TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT,
139
+ # UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
135
140
  #
136
141
  # @param logical_type [DuckDB::LogicalType | :logical_type_symbol] the return type
137
142
  # @return [DuckDB::ScalarFunction] self
@@ -161,9 +166,9 @@ module DuckDB
161
166
  # given type. Can be combined with add_parameter to add fixed leading parameters
162
167
  # (e.g. a separator followed by a variable list of values).
163
168
  # The block receives fixed parameters positionally, then varargs as a splat (|fixed, *rest|).
164
- # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, INTEGER, SMALLINT, TIME, TIMESTAMP,
165
- # TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UINTEGER, USMALLINT, UTINYINT,
166
- # and VARCHAR types.
169
+ # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT, TIME,
170
+ # TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT,
171
+ # UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
167
172
  #
168
173
  # @param logical_type [DuckDB::LogicalType | :logical_type_symbol] the varargs element type
169
174
  # @return [DuckDB::ScalarFunction] self
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuckDB
4
+ # DuckDB::ScalarFunctionSet encapsulates DuckDB's scalar function set,
5
+ # which allows registering multiple overloads of a scalar function under one name.
6
+ #
7
+ # @note DuckDB::ScalarFunctionSet is experimental.
8
+ class ScalarFunctionSet
9
+ # @param name [String, Symbol] the function set name shared by all overloads
10
+ # @raise [TypeError] if name is not a String or Symbol
11
+ def initialize(name)
12
+ raise TypeError, "#{name.class} is not a String or Symbol" unless name.is_a?(String) || name.is_a?(Symbol)
13
+
14
+ @name = name.to_s
15
+ _initialize(@name)
16
+ end
17
+
18
+ # @param scalar_function [DuckDB::ScalarFunction] the overload to add
19
+ # @return [self]
20
+ # @raise [TypeError] if scalar_function is not a DuckDB::ScalarFunction
21
+ # @raise [DuckDB::Error] if the overload already exists in the set
22
+ def add(scalar_function)
23
+ unless scalar_function.is_a?(DuckDB::ScalarFunction)
24
+ raise TypeError, "#{scalar_function.class} is not a DuckDB::ScalarFunction"
25
+ end
26
+
27
+ scalar_function.name = @name
28
+ _add(scalar_function)
29
+ end
30
+ end
31
+ end
@@ -3,5 +3,5 @@
3
3
  module DuckDB
4
4
  # The version string of ruby-duckdb.
5
5
  # Currently, ruby-duckdb is NOT semantic versioning.
6
- VERSION = '1.5.1.0'
6
+ VERSION = '1.5.1.1'
7
7
  end
data/lib/duckdb.rb CHANGED
@@ -15,6 +15,7 @@ require 'duckdb/config'
15
15
  require 'duckdb/column'
16
16
  require 'duckdb/logical_type'
17
17
  require 'duckdb/scalar_function'
18
+ require 'duckdb/scalar_function_set'
18
19
  require 'duckdb/expression'
19
20
  require 'duckdb/client_context'
20
21
  require 'duckdb/scalar_function/bind_info'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: duckdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1.0
4
+ version: 1.5.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masaki Suketa
@@ -101,6 +101,8 @@ files:
101
101
  - ext/duckdb/scalar_function.h
102
102
  - ext/duckdb/scalar_function_bind_info.c
103
103
  - ext/duckdb/scalar_function_bind_info.h
104
+ - ext/duckdb/scalar_function_set.c
105
+ - ext/duckdb/scalar_function_set.h
104
106
  - ext/duckdb/table_function.c
105
107
  - ext/duckdb/table_function.h
106
108
  - ext/duckdb/table_function_bind_info.c
@@ -139,6 +141,7 @@ files:
139
141
  - lib/duckdb/result.rb
140
142
  - lib/duckdb/scalar_function.rb
141
143
  - lib/duckdb/scalar_function/bind_info.rb
144
+ - lib/duckdb/scalar_function_set.rb
142
145
  - lib/duckdb/table_function.rb
143
146
  - lib/duckdb/table_function/bind_info.rb
144
147
  - lib/duckdb/table_function/function_info.rb