duckdb 1.5.4.0 → 1.5.5.0

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.
data/ext/duckdb/value.c CHANGED
@@ -21,7 +21,34 @@ static VALUE value_s__create_blob(VALUE klass, VALUE str);
21
21
  static VALUE value_s__create_hugeint(VALUE klass, VALUE lower, VALUE upper);
22
22
  static VALUE value_s__create_uhugeint(VALUE klass, VALUE lower, VALUE upper);
23
23
  static VALUE value_s__create_decimal(VALUE klass, VALUE lower, VALUE upper, VALUE width, VALUE scale);
24
+ static VALUE value_s__create_date(VALUE klass, VALUE year, VALUE month, VALUE day);
25
+ static VALUE value_s__create_time(VALUE klass, VALUE hour, VALUE min, VALUE sec, VALUE micros);
26
+ static VALUE value_s__create_time_ns(VALUE klass, VALUE hour, VALUE min, VALUE sec, VALUE nanos);
27
+ static VALUE value_s__create_time_tz(VALUE klass, VALUE micros, VALUE offset);
28
+ static VALUE value_s__create_timestamp(VALUE klass, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros);
29
+ static VALUE value_s__create_timestamp_s(VALUE klass, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec);
30
+ static VALUE value_s__create_timestamp_ms(VALUE klass, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros);
31
+ static VALUE value_s__create_timestamp_ns(VALUE klass, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE nanos);
32
+ static VALUE value_s__create_timestamp_tz(VALUE klass, VALUE micros);
33
+ static VALUE value_s__create_interval(VALUE klass, VALUE months, VALUE days, VALUE micros);
34
+ static VALUE value_s__create_enum(VALUE klass, VALUE ltype, VALUE index);
35
+ static VALUE value_s__create_union(VALUE klass, VALUE ltype, VALUE tag_index, VALUE member);
36
+ static VALUE value_s__create_bit(VALUE klass, VALUE data);
37
+ static VALUE value_s__create_bignum(VALUE klass, VALUE data, VALUE is_negative);
24
38
  static VALUE value_s_create_null(VALUE klass);
39
+ static VALUE to_ruby_via_vector(duckdb_logical_type logical_type, duckdb_value val);
40
+ static idx_t marshal_values(VALUE ary, duckdb_value **out, volatile VALUE *guard);
41
+ static VALUE value_s__create_list(VALUE klass, VALUE ltype, VALUE values);
42
+ static VALUE value_s__create_array(VALUE klass, VALUE ltype, VALUE values);
43
+ static VALUE value_s__create_struct(VALUE klass, VALUE ltype, VALUE values);
44
+ static VALUE value_s__create_map(VALUE klass, VALUE ltype, VALUE keys, VALUE values);
45
+ static VALUE value_list_size(VALUE self);
46
+ static VALUE value_list_child(VALUE self, VALUE vidx);
47
+ static VALUE value_struct_child(VALUE self, VALUE vidx);
48
+ static VALUE value_map_size(VALUE self);
49
+ static VALUE value_map_key(VALUE self, VALUE vidx);
50
+ static VALUE value_map_value(VALUE self, VALUE vidx);
51
+ static VALUE value_to_ruby(VALUE self);
25
52
 
26
53
  static const rb_data_type_t value_data_type = {
27
54
  "DuckDB/Value",
@@ -140,6 +167,136 @@ static VALUE value_s__create_decimal(VALUE klass, VALUE lower, VALUE upper, VALU
140
167
  return rbduckdb_value_new(value);
141
168
  }
142
169
 
170
+ /* :nodoc: */
171
+ static VALUE value_s__create_date(VALUE klass, VALUE year, VALUE month, VALUE day) {
172
+ duckdb_value value = duckdb_create_date(rbduckdb_to_duckdb_date_from_value(year, month, day));
173
+ return rbduckdb_value_new(value);
174
+ }
175
+
176
+ /* :nodoc: */
177
+ static VALUE value_s__create_time(VALUE klass, VALUE hour, VALUE min, VALUE sec, VALUE micros) {
178
+ duckdb_value value = duckdb_create_time(rbduckdb_to_duckdb_time_from_value(hour, min, sec, micros));
179
+ return rbduckdb_value_new(value);
180
+ }
181
+
182
+ /* :nodoc: */
183
+ static VALUE value_s__create_time_ns(VALUE klass, VALUE hour, VALUE min, VALUE sec, VALUE nanos) {
184
+ duckdb_time_ns time_ns = rbduckdb_to_duckdb_time_ns_from_value(hour, min, sec, nanos);
185
+
186
+ return rbduckdb_value_new(duckdb_create_time_ns(time_ns));
187
+ }
188
+
189
+ /* :nodoc: */
190
+ static VALUE value_s__create_time_tz(VALUE klass, VALUE micros, VALUE offset) {
191
+ duckdb_time_tz time_tz = duckdb_create_time_tz(NUM2LL(micros), NUM2INT(offset));
192
+ return rbduckdb_value_new(duckdb_create_time_tz_value(time_tz));
193
+ }
194
+
195
+ /* :nodoc: */
196
+ static VALUE value_s__create_timestamp(VALUE klass, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros) {
197
+ duckdb_timestamp timestamp = rbduckdb_to_duckdb_timestamp_from_value(year, month, day, hour, min, sec, micros);
198
+ return rbduckdb_value_new(duckdb_create_timestamp(timestamp));
199
+ }
200
+
201
+ /* :nodoc: */
202
+ static VALUE value_s__create_timestamp_s(VALUE klass, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec) {
203
+ duckdb_timestamp_s ts;
204
+
205
+ ts.seconds = rbduckdb_to_duckdb_timestamp_from_value(year, month, day, hour, min, sec, INT2FIX(0)).micros / 1000000;
206
+ return rbduckdb_value_new(duckdb_create_timestamp_s(ts));
207
+ }
208
+
209
+ /* :nodoc: */
210
+ static VALUE value_s__create_timestamp_ms(VALUE klass, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros) {
211
+ duckdb_timestamp_ms ts;
212
+
213
+ ts.millis = rbduckdb_to_duckdb_timestamp_from_value(year, month, day, hour, min, sec, micros).micros / 1000;
214
+ return rbduckdb_value_new(duckdb_create_timestamp_ms(ts));
215
+ }
216
+
217
+ /* :nodoc: */
218
+ static VALUE value_s__create_timestamp_ns(VALUE klass, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE nanos) {
219
+ duckdb_timestamp_ns ts;
220
+
221
+ ts.nanos = rbduckdb_to_duckdb_timestamp_from_value(year, month, day, hour, min, sec, INT2FIX(0)).micros * 1000 + NUM2LL(nanos);
222
+ return rbduckdb_value_new(duckdb_create_timestamp_ns(ts));
223
+ }
224
+
225
+ /* :nodoc: */
226
+ static VALUE value_s__create_timestamp_tz(VALUE klass, VALUE micros) {
227
+ duckdb_timestamp ts;
228
+
229
+ ts.micros = NUM2LL(micros);
230
+ return rbduckdb_value_new(duckdb_create_timestamp_tz(ts));
231
+ }
232
+
233
+ /* :nodoc: */
234
+ static VALUE value_s__create_interval(VALUE klass, VALUE months, VALUE days, VALUE micros) {
235
+ duckdb_interval interval;
236
+
237
+ rbduckdb_to_duckdb_interval_from_value(&interval, months, days, micros);
238
+ return rbduckdb_value_new(duckdb_create_interval(interval));
239
+ }
240
+
241
+ /* :nodoc: */
242
+ static VALUE value_s__create_enum(VALUE klass, VALUE ltype, VALUE index) {
243
+ duckdb_logical_type type = rbduckdb_get_struct_logical_type(ltype)->logical_type;
244
+ duckdb_value value = duckdb_create_enum_value(type, NUM2ULL(index));
245
+
246
+ if (value == NULL) {
247
+ rb_raise(eDuckDBError, "failed to create ENUM value");
248
+ }
249
+ return rbduckdb_value_new(value);
250
+ }
251
+
252
+ /* :nodoc: */
253
+ static VALUE value_s__create_union(VALUE klass, VALUE ltype, VALUE tag_index, VALUE member) {
254
+ duckdb_logical_type type = rbduckdb_get_struct_logical_type(ltype)->logical_type;
255
+ duckdb_value value = duckdb_create_union_value(type, (idx_t)NUM2ULL(tag_index), rbduckdb_get_struct_value(member)->value);
256
+
257
+ if (value == NULL) {
258
+ rb_raise(eDuckDBError, "failed to create UNION value (mismatched member type?)");
259
+ }
260
+ return rbduckdb_value_new(value);
261
+ }
262
+
263
+ /*
264
+ * :nodoc:
265
+ * data is the raw DuckDB BIT blob built in Ruby: one byte holding the
266
+ * number of padding bits, then the bits MSB-first with the padding bits
267
+ * of the first data byte set to 1.
268
+ */
269
+ static VALUE value_s__create_bit(VALUE klass, VALUE data) {
270
+ duckdb_bit bit;
271
+ duckdb_value value;
272
+
273
+ bit.data = (uint8_t *)StringValuePtr(data);
274
+ bit.size = RSTRING_LEN(data);
275
+ value = duckdb_create_bit(bit);
276
+ if (value == NULL) {
277
+ rb_raise(eDuckDBError, "failed to create BIT value");
278
+ }
279
+ return rbduckdb_value_new(value);
280
+ }
281
+
282
+ /*
283
+ * :nodoc:
284
+ * data is the magnitude in big-endian bytes, built in Ruby.
285
+ */
286
+ static VALUE value_s__create_bignum(VALUE klass, VALUE data, VALUE is_negative) {
287
+ duckdb_bignum bignum;
288
+ duckdb_value value;
289
+
290
+ bignum.data = (uint8_t *)StringValuePtr(data);
291
+ bignum.size = RSTRING_LEN(data);
292
+ bignum.is_negative = RTEST(is_negative);
293
+ value = duckdb_create_bignum(bignum);
294
+ if (value == NULL) {
295
+ rb_raise(eDuckDBError, "failed to create BIGNUM value");
296
+ }
297
+ return rbduckdb_value_new(value);
298
+ }
299
+
143
300
  /*
144
301
  * call-seq:
145
302
  * DuckDB::Value.create_null -> DuckDB::Value
@@ -154,6 +311,231 @@ static VALUE value_s_create_null(VALUE klass) {
154
311
  return rbduckdb_value_new(value);
155
312
  }
156
313
 
314
+ /*
315
+ * Fills *out with the unwrapped duckdb_values of a Ruby Array of
316
+ * DuckDB::Value objects and returns the array length. The buffer is
317
+ * an ALLOCV tmpbuf; the caller must call ALLOCV_END(*guard) after use.
318
+ * The buffer is non-NULL even for an empty array because
319
+ * duckdb_create_*_value functions reject a NULL values pointer.
320
+ *
321
+ * This always allocates via rb_alloc_tmp_buffer2 (heap-backed)
322
+ * rather than the ALLOCV_N macro, because that macro's small-size fast
323
+ * path uses alloca() in *this* function's stack frame: the returned
324
+ * pointer would dangle once marshal_values returns, and a second call
325
+ * (e.g. keys then values for MAP) would silently reuse and clobber the
326
+ * same stack slot as the first call's "buffer".
327
+ */
328
+ static idx_t marshal_values(VALUE ary, duckdb_value **out, volatile VALUE *guard) {
329
+ idx_t n = (idx_t)RARRAY_LEN(ary);
330
+ idx_t count = n == 0 ? 1 : n;
331
+ duckdb_value *buf = (duckdb_value *)rb_alloc_tmp_buffer2(guard, (long)count, sizeof(duckdb_value));
332
+ idx_t i;
333
+
334
+ for (i = 0; i < n; i++) {
335
+ buf[i] = rbduckdb_get_struct_value(RARRAY_AREF(ary, i))->value;
336
+ }
337
+ *out = buf;
338
+ return n;
339
+ }
340
+
341
+ /* :nodoc: */
342
+ static VALUE value_s__create_list(VALUE klass, VALUE ltype, VALUE values) {
343
+ duckdb_logical_type type = rbduckdb_get_struct_logical_type(ltype)->logical_type;
344
+ duckdb_value *buf;
345
+ volatile VALUE guard;
346
+ idx_t n = marshal_values(values, &buf, &guard);
347
+ duckdb_value value = duckdb_create_list_value(type, buf, n);
348
+
349
+ RB_GC_GUARD(values);
350
+ ALLOCV_END(guard);
351
+ if (value == NULL) {
352
+ rb_raise(eDuckDBError, "failed to create LIST value");
353
+ }
354
+ return rbduckdb_value_new(value);
355
+ }
356
+
357
+ /* :nodoc: */
358
+ static VALUE value_s__create_array(VALUE klass, VALUE ltype, VALUE values) {
359
+ duckdb_logical_type type = rbduckdb_get_struct_logical_type(ltype)->logical_type;
360
+ duckdb_value *buf;
361
+ volatile VALUE guard;
362
+ idx_t n = marshal_values(values, &buf, &guard);
363
+ duckdb_value value = duckdb_create_array_value(type, buf, n);
364
+
365
+ RB_GC_GUARD(values);
366
+ ALLOCV_END(guard);
367
+ if (value == NULL) {
368
+ rb_raise(eDuckDBError, "failed to create ARRAY value");
369
+ }
370
+ return rbduckdb_value_new(value);
371
+ }
372
+
373
+ /* :nodoc: */
374
+ static VALUE value_s__create_struct(VALUE klass, VALUE ltype, VALUE values) {
375
+ duckdb_logical_type type = rbduckdb_get_struct_logical_type(ltype)->logical_type;
376
+ duckdb_value *buf;
377
+ volatile VALUE guard;
378
+ idx_t n = marshal_values(values, &buf, &guard);
379
+ duckdb_value value = duckdb_create_struct_value(type, buf);
380
+
381
+ (void)n;
382
+ RB_GC_GUARD(values);
383
+ ALLOCV_END(guard);
384
+ if (value == NULL) {
385
+ rb_raise(eDuckDBError, "failed to create STRUCT value");
386
+ }
387
+ return rbduckdb_value_new(value);
388
+ }
389
+
390
+ /* :nodoc: */
391
+ static VALUE value_s__create_map(VALUE klass, VALUE ltype, VALUE keys, VALUE values) {
392
+ duckdb_logical_type type = rbduckdb_get_struct_logical_type(ltype)->logical_type;
393
+ duckdb_value *kbuf;
394
+ duckdb_value *vbuf;
395
+ volatile VALUE kguard;
396
+ volatile VALUE vguard;
397
+ idx_t n = marshal_values(keys, &kbuf, &kguard);
398
+ duckdb_value value;
399
+
400
+ marshal_values(values, &vbuf, &vguard); /* same length, validated in Ruby */
401
+ value = duckdb_create_map_value(type, kbuf, vbuf, n);
402
+ RB_GC_GUARD(keys);
403
+ RB_GC_GUARD(values);
404
+ ALLOCV_END(kguard);
405
+ ALLOCV_END(vguard);
406
+ if (value == NULL) {
407
+ rb_raise(eDuckDBError, "failed to create MAP value");
408
+ }
409
+ return rbduckdb_value_new(value);
410
+ }
411
+
412
+ /*
413
+ * call-seq:
414
+ * value.list_size -> Integer
415
+ *
416
+ * Returns the number of elements of a LIST value.
417
+ *
418
+ * require 'duckdb'
419
+ * child_type = DuckDB::LogicalType.resolve(:integer)
420
+ * values = [1, 2, 3].map { |i| DuckDB::Value.create_int32(i) }
421
+ * list = DuckDB::Value.create_list(child_type, values)
422
+ * list.list_size # => 3
423
+ */
424
+ static VALUE value_list_size(VALUE self) {
425
+ return ULL2NUM(duckdb_get_list_size(rbduckdb_get_struct_value(self)->value));
426
+ }
427
+
428
+ /*
429
+ * call-seq:
430
+ * value.list_child(index) -> DuckDB::Value
431
+ *
432
+ * Returns the element at the specified index (0-based) of a LIST value
433
+ * as a DuckDB::Value.
434
+ * Raises IndexError if the index is out of range or the value is not a LIST.
435
+ *
436
+ * require 'duckdb'
437
+ * child_type = DuckDB::LogicalType.resolve(:integer)
438
+ * values = [1, 2, 3].map { |i| DuckDB::Value.create_int32(i) }
439
+ * list = DuckDB::Value.create_list(child_type, values)
440
+ * list.list_child(0) # => DuckDB::Value
441
+ */
442
+ static VALUE value_list_child(VALUE self, VALUE vidx) {
443
+ duckdb_value child = duckdb_get_list_child(rbduckdb_get_struct_value(self)->value, (idx_t)NUM2ULL(vidx));
444
+
445
+ if (child == NULL) {
446
+ rb_raise(rb_eIndexError, "list index out of range (or the value is not a LIST)");
447
+ }
448
+ return rbduckdb_value_new(child);
449
+ }
450
+
451
+ /*
452
+ * call-seq:
453
+ * value.struct_child(index) -> DuckDB::Value
454
+ *
455
+ * Returns the field at the specified index (0-based) of a STRUCT value
456
+ * as a DuckDB::Value.
457
+ * Raises IndexError if the index is out of range or the value is not a STRUCT.
458
+ *
459
+ * require 'duckdb'
460
+ * struct_type = DuckDB::LogicalType.create_struct(a: :integer, b: :varchar)
461
+ * values = [DuckDB::Value.create_int32(1), DuckDB::Value.create_varchar('x')]
462
+ * struct = DuckDB::Value.create_struct(struct_type, values)
463
+ * struct.struct_child(0) # => DuckDB::Value
464
+ */
465
+ static VALUE value_struct_child(VALUE self, VALUE vidx) {
466
+ duckdb_value child = duckdb_get_struct_child(rbduckdb_get_struct_value(self)->value, (idx_t)NUM2ULL(vidx));
467
+
468
+ if (child == NULL) {
469
+ rb_raise(rb_eIndexError, "struct index out of range (or the value is not a STRUCT)");
470
+ }
471
+ return rbduckdb_value_new(child);
472
+ }
473
+
474
+ /*
475
+ * call-seq:
476
+ * value.map_size -> Integer
477
+ *
478
+ * Returns the number of entries of a MAP value.
479
+ *
480
+ * require 'duckdb'
481
+ * map_type = DuckDB::LogicalType.create_map(:varchar, :integer)
482
+ * keys = [DuckDB::Value.create_varchar('a')]
483
+ * values = [DuckDB::Value.create_int32(1)]
484
+ * map = DuckDB::Value.create_map(map_type, keys, values)
485
+ * map.map_size # => 1
486
+ */
487
+ static VALUE value_map_size(VALUE self) {
488
+ return ULL2NUM(duckdb_get_map_size(rbduckdb_get_struct_value(self)->value));
489
+ }
490
+
491
+ /*
492
+ * call-seq:
493
+ * value.map_key(index) -> DuckDB::Value
494
+ *
495
+ * Returns the key at the specified index (0-based) of a MAP value
496
+ * as a DuckDB::Value.
497
+ * Raises IndexError if the index is out of range or the value is not a MAP.
498
+ *
499
+ * require 'duckdb'
500
+ * map_type = DuckDB::LogicalType.create_map(:varchar, :integer)
501
+ * keys = [DuckDB::Value.create_varchar('a')]
502
+ * values = [DuckDB::Value.create_int32(1)]
503
+ * map = DuckDB::Value.create_map(map_type, keys, values)
504
+ * map.map_key(0) # => DuckDB::Value
505
+ */
506
+ static VALUE value_map_key(VALUE self, VALUE vidx) {
507
+ duckdb_value child = duckdb_get_map_key(rbduckdb_get_struct_value(self)->value, (idx_t)NUM2ULL(vidx));
508
+
509
+ if (child == NULL) {
510
+ rb_raise(rb_eIndexError, "map index out of range (or the value is not a MAP)");
511
+ }
512
+ return rbduckdb_value_new(child);
513
+ }
514
+
515
+ /*
516
+ * call-seq:
517
+ * value.map_value(index) -> DuckDB::Value
518
+ *
519
+ * Returns the value at the specified index (0-based) of a MAP value
520
+ * as a DuckDB::Value.
521
+ * Raises IndexError if the index is out of range or the value is not a MAP.
522
+ *
523
+ * require 'duckdb'
524
+ * map_type = DuckDB::LogicalType.create_map(:varchar, :integer)
525
+ * keys = [DuckDB::Value.create_varchar('a')]
526
+ * values = [DuckDB::Value.create_int32(1)]
527
+ * map = DuckDB::Value.create_map(map_type, keys, values)
528
+ * map.map_value(0) # => DuckDB::Value
529
+ */
530
+ static VALUE value_map_value(VALUE self, VALUE vidx) {
531
+ duckdb_value child = duckdb_get_map_value(rbduckdb_get_struct_value(self)->value, (idx_t)NUM2ULL(vidx));
532
+
533
+ if (child == NULL) {
534
+ rb_raise(rb_eIndexError, "map index out of range (or the value is not a MAP)");
535
+ }
536
+ return rbduckdb_value_new(child);
537
+ }
538
+
157
539
  VALUE rbduckdb_value_new(duckdb_value value) {
158
540
  rubyDuckDBValue *ctx;
159
541
  VALUE obj = allocate(cDuckDBValue);
@@ -168,12 +550,38 @@ rubyDuckDBValue *rbduckdb_get_struct_value(VALUE obj) {
168
550
  return ctx;
169
551
  }
170
552
 
553
+ /*
554
+ * Converts a duckdb_value to Ruby by referencing it into a one-element
555
+ * vector and reusing the vector read path, so Value#to_ruby returns the
556
+ * same Ruby object a query result produces for that type. Used for types
557
+ * the C API has no direct duckdb_get_* accessors for.
558
+ */
559
+ static VALUE to_ruby_via_vector(duckdb_logical_type logical_type, duckdb_value val) {
560
+ duckdb_vector vec = duckdb_create_vector(logical_type, 1);
561
+ VALUE result;
562
+
563
+ duckdb_vector_reference_value(vec, val);
564
+ result = rbduckdb_vector_value_at(vec, logical_type, 0);
565
+ duckdb_destroy_vector(&vec);
566
+ return result;
567
+ }
568
+
171
569
  VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
172
570
  duckdb_logical_type logical_type;
173
571
  duckdb_type type_id;
572
+ duckdb_value child_value;
174
573
  VALUE result;
574
+ VALUE elem;
575
+ VALUE key;
175
576
  char *str;
577
+ idx_t i;
578
+ idx_t size;
579
+
580
+ if (duckdb_is_null_value(val)) {
581
+ return Qnil;
582
+ }
176
583
 
584
+ /* logical_type from duckdb_get_value_type is borrowed and must not be destroyed */
177
585
  logical_type = duckdb_get_value_type(val);
178
586
  type_id = duckdb_get_type_id(logical_type);
179
587
 
@@ -255,6 +663,73 @@ VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
255
663
  case DUCKDB_TYPE_UUID:
256
664
  result = rbduckdb_uuid_uhugeint_to_ruby(duckdb_get_uuid(val));
257
665
  break;
666
+ case DUCKDB_TYPE_ENUM:
667
+ str = duckdb_enum_dictionary_value(logical_type, duckdb_get_enum_value(val));
668
+ if (str == NULL) {
669
+ result = Qnil;
670
+ break;
671
+ }
672
+ result = rb_utf8_str_new_cstr(str);
673
+ duckdb_free(str);
674
+ break;
675
+ case DUCKDB_TYPE_LIST:
676
+ size = duckdb_get_list_size(val);
677
+ result = rb_ary_new_capa(size);
678
+ for (i = 0; i < size; i++) {
679
+ child_value = duckdb_get_list_child(val, i);
680
+ elem = rbduckdb_duckdb_value_to_ruby(child_value);
681
+ duckdb_destroy_value(&child_value);
682
+ rb_ary_push(result, elem);
683
+ }
684
+ break;
685
+ case DUCKDB_TYPE_STRUCT:
686
+ size = duckdb_struct_type_child_count(logical_type);
687
+ result = rb_hash_new();
688
+ for (i = 0; i < size; i++) {
689
+ str = duckdb_struct_type_child_name(logical_type, i);
690
+ child_value = duckdb_get_struct_child(val, i);
691
+ elem = rbduckdb_duckdb_value_to_ruby(child_value);
692
+ duckdb_destroy_value(&child_value);
693
+ rb_hash_aset(result, ID2SYM(rb_intern(str)), elem);
694
+ duckdb_free(str);
695
+ }
696
+ break;
697
+ case DUCKDB_TYPE_MAP:
698
+ size = duckdb_get_map_size(val);
699
+ result = rb_hash_new();
700
+ for (i = 0; i < size; i++) {
701
+ child_value = duckdb_get_map_key(val, i);
702
+ key = rbduckdb_duckdb_value_to_ruby(child_value);
703
+ duckdb_destroy_value(&child_value);
704
+ child_value = duckdb_get_map_value(val, i);
705
+ elem = rbduckdb_duckdb_value_to_ruby(child_value);
706
+ duckdb_destroy_value(&child_value);
707
+ rb_hash_aset(result, key, elem);
708
+ }
709
+ break;
710
+ case DUCKDB_TYPE_ARRAY:
711
+ case DUCKDB_TYPE_UNION:
712
+ case DUCKDB_TYPE_BIT:
713
+ case DUCKDB_TYPE_DECIMAL:
714
+ result = to_ruby_via_vector(logical_type, val);
715
+ break;
716
+ case DUCKDB_TYPE_BLOB: {
717
+ duckdb_blob blob = duckdb_get_blob(val);
718
+
719
+ result = rb_str_new((const char *)blob.data, blob.size);
720
+ duckdb_free(blob.data);
721
+ break;
722
+ }
723
+ case DUCKDB_TYPE_BIGNUM: {
724
+ duckdb_bignum bignum = duckdb_get_bignum(val);
725
+
726
+ result = rb_integer_unpack(bignum.data, bignum.size, 1, 0, INTEGER_PACK_BIG_ENDIAN);
727
+ duckdb_free(bignum.data);
728
+ if (bignum.is_negative) {
729
+ result = rb_funcall(result, rb_intern("-@"), 0);
730
+ }
731
+ break;
732
+ }
258
733
  default:
259
734
  result = Qnil;
260
735
  break;
@@ -263,6 +738,31 @@ VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
263
738
  return result;
264
739
  }
265
740
 
741
+ /*
742
+ * call-seq:
743
+ * value.to_ruby -> Object
744
+ *
745
+ * Converts the DuckDB::Value to a Ruby object. Scalar types are converted
746
+ * to their natural Ruby classes. LIST and ARRAY values are converted to
747
+ * Array recursively. STRUCT and MAP values are converted to Hash
748
+ * recursively (STRUCT keys are Symbols; MAP keys keep their natural Ruby
749
+ * type). ENUM values are converted to the member String. UNION values
750
+ * are converted to the member's Ruby value. BIT values are converted to
751
+ * a String of '0'/'1' characters. BIGNUM values are converted to
752
+ * Integer. BLOB values are converted to a BINARY-encoded String.
753
+ * DECIMAL values are converted to BigDecimal. NULL is converted to nil.
754
+ * Returns nil for unsupported types.
755
+ *
756
+ * require 'duckdb'
757
+ * child_type = DuckDB::LogicalType.resolve(:integer)
758
+ * values = [1, 2, 3].map { |i| DuckDB::Value.create_int32(i) }
759
+ * list = DuckDB::Value.create_list(child_type, values)
760
+ * list.to_ruby # => [1, 2, 3]
761
+ */
762
+ static VALUE value_to_ruby(VALUE self) {
763
+ return rbduckdb_duckdb_value_to_ruby(rbduckdb_get_struct_value(self)->value);
764
+ }
765
+
266
766
  void rbduckdb_init_value(void) {
267
767
  #if 0
268
768
  VALUE mDuckDB = rb_define_module("DuckDB");
@@ -286,6 +786,31 @@ void rbduckdb_init_value(void) {
286
786
  rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_hugeint", value_s__create_hugeint, 2);
287
787
  rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_uhugeint", value_s__create_uhugeint, 2);
288
788
  rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_decimal", value_s__create_decimal, 4);
789
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_date", value_s__create_date, 3);
790
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_time", value_s__create_time, 4);
791
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_time_ns", value_s__create_time_ns, 4);
792
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_time_tz", value_s__create_time_tz, 2);
793
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_timestamp", value_s__create_timestamp, 7);
794
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_timestamp_s", value_s__create_timestamp_s, 6);
795
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_timestamp_ms", value_s__create_timestamp_ms, 7);
796
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_timestamp_ns", value_s__create_timestamp_ns, 7);
797
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_timestamp_tz", value_s__create_timestamp_tz, 1);
798
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_interval", value_s__create_interval, 3);
799
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_enum", value_s__create_enum, 2);
800
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_union", value_s__create_union, 3);
801
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_bit", value_s__create_bit, 1);
802
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_bignum", value_s__create_bignum, 2);
803
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_list", value_s__create_list, 2);
804
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_array", value_s__create_array, 2);
805
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_struct", value_s__create_struct, 2);
806
+ rb_define_private_method(rb_singleton_class(cDuckDBValue), "_create_map", value_s__create_map, 3);
289
807
  rb_define_singleton_method(cDuckDBValue, "create_null", value_s_create_null, 0);
290
- }
291
808
 
809
+ rb_define_method(cDuckDBValue, "list_size", value_list_size, 0);
810
+ rb_define_method(cDuckDBValue, "list_child", value_list_child, 1);
811
+ rb_define_method(cDuckDBValue, "struct_child", value_struct_child, 1);
812
+ rb_define_method(cDuckDBValue, "map_size", value_map_size, 0);
813
+ rb_define_method(cDuckDBValue, "map_key", value_map_key, 1);
814
+ rb_define_method(cDuckDBValue, "map_value", value_map_value, 1);
815
+ rb_define_method(cDuckDBValue, "to_ruby", value_to_ruby, 0);
816
+ }
@@ -423,6 +423,25 @@ module DuckDB
423
423
  end
424
424
  end
425
425
 
426
+ # Returns the names of the tables referenced by the given SQL query,
427
+ # without executing it.
428
+ #
429
+ # @param query [String] the SQL query to inspect
430
+ # @param qualified [Boolean] if true, returns each table reference as
431
+ # written in the query, keeping any catalog/schema qualification;
432
+ # if false (default), bare table names only
433
+ # @return [Array<String>] the referenced table names, in unspecified order
434
+ # @raise [DuckDB::Error] if the query cannot be parsed
435
+ #
436
+ # @example
437
+ # con.table_names('SELECT * FROM users u JOIN orders o ON u.id = o.user_id').sort
438
+ # #=> ["orders", "users"]
439
+ # con.table_names('SELECT * FROM memory.main.users', qualified: true)
440
+ # #=> ["memory.main.users"]
441
+ def table_names(query, qualified: false)
442
+ _get_table_names(query, qualified).to_ruby
443
+ end
444
+
426
445
  private
427
446
 
428
447
  # Drives the Arrow stream at +address+ chunk by chunk into +table+,
@@ -73,11 +73,64 @@ module DuckDB
73
73
  35 => :bignum,
74
74
  36 => :sqlnull,
75
75
  37 => :string_literal,
76
- 38 => :integer_literal
76
+ 38 => :integer_literal,
77
+ 39 => :time_ns
77
78
  }.freeze
78
79
 
80
+ ERROR_TYPES = %i[
81
+ invalid
82
+ out_of_range
83
+ conversion
84
+ unknown_type
85
+ decimal
86
+ mismatch_type
87
+ divide_by_zero
88
+ object_size
89
+ invalid_type
90
+ serialization
91
+ transaction
92
+ not_implemented
93
+ expression
94
+ catalog
95
+ parser
96
+ planner
97
+ scheduler
98
+ executor
99
+ constraint
100
+ index
101
+ stat
102
+ connection
103
+ syntax
104
+ settings
105
+ binder
106
+ network
107
+ optimizer
108
+ null_pointer
109
+ io
110
+ interrupt
111
+ fatal
112
+ internal
113
+ invalid_input
114
+ out_of_memory
115
+ permission
116
+ parameter_not_resolved
117
+ parameter_not_allowed
118
+ dependency
119
+ http
120
+ missing_extension
121
+ autoload
122
+ sequence
123
+ invalid_configuration
124
+ ].freeze
125
+
79
126
  module_function
80
127
 
128
+ def error_type_to_sym(val) # :nodoc:
129
+ raise DuckDB::Error, "Unknown error type: #{val}" if val >= ERROR_TYPES.size
130
+
131
+ ERROR_TYPES[val]
132
+ end
133
+
81
134
  def statement_type_to_sym(val) # :nodoc:
82
135
  raise DuckDB::Error, "Unknown statement type: #{val}" if val >= STATEMENT_TYPES.size
83
136
 
@@ -83,18 +83,18 @@ module DuckDB
83
83
 
84
84
  def _to_time_from_duckdb_timestamp_ns(time)
85
85
  _to_time_from_duckdb_timestamp_s(time / 1_000_000_000).then do |tm|
86
- _to_time(tm.year, tm.month, tm.day, tm.hour, tm.min, tm.sec, time % 1_000_000_000 / 1000)
86
+ _to_time(tm.year, tm.month, tm.day, tm.hour, tm.min, tm.sec, Rational(time % 1_000_000_000, 1000))
87
87
  end
88
88
  end
89
89
 
90
90
  def _to_time_from_duckdb_time_ns(nanos)
91
91
  hour = nanos / 3_600_000_000_000
92
- nanos %= 3_600_000_000_000
93
- min = nanos / 60_000_000_000
94
- nanos %= 60_000_000_000
95
- sec = nanos / 1_000_000_000
96
- microsecond = (nanos % 1_000_000_000) / 1_000
97
- _to_time_from_duckdb_time(hour, min, sec, microsecond)
92
+ min = nanos % 3_600_000_000_000 / 60_000_000_000
93
+ sec = nanos % 60_000_000_000 / 1_000_000_000
94
+ nsec = nanos % 1_000_000_000
95
+ return Time.utc(1970, 1, 1, hour, min, sec, Rational(nsec, 1_000)) if default_timezone_utc?
96
+
97
+ Time.parse(format('%<hour>02d:%<min>02d:%<sec>02d.%<nsec>09d', hour: hour, min: min, sec: sec, nsec: nsec))
98
98
  end
99
99
 
100
100
  def _to_time_from_duckdb_time_tz(hour, min, sec, micro, timezone)