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.
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuckDB
4
+ # The exception raised by ruby-duckdb.
5
+ class Error < StandardError
6
+ # +error_type_id+ is the raw DuckDB error type id, set by the C extension when
7
+ # a query result fails; it defaults to +nil+ for all other errors.
8
+ def initialize(message = nil, error_type_id = nil)
9
+ super(message)
10
+ @error_type_id = error_type_id
11
+ end
12
+
13
+ # Returns the DuckDB error category as a Symbol (e.g. +:constraint+,
14
+ # +:catalog+, +:parser+), or +nil+ when the error did not originate from a
15
+ # DuckDB query result (e.g. internal binding failures).
16
+ #
17
+ # begin
18
+ # con.query('INSERT INTO t VALUES (1)') # duplicate primary key
19
+ # rescue DuckDB::Error => e
20
+ # e.error_type # => :constraint
21
+ # end
22
+ def error_type
23
+ @error_type_id && Converter::IntToSym.error_type_to_sym(@error_type_id)
24
+ end
25
+ end
26
+ end
@@ -23,6 +23,7 @@ module DuckDB
23
23
  timestamp_s
24
24
  timestamp_ms
25
25
  timestamp_ns
26
+ time_ns
26
27
  time_tz
27
28
  timestamp_tz
28
29
  tinyint
@@ -7,7 +7,7 @@ module DuckDB
7
7
 
8
8
  @logical_types = {}
9
9
 
10
- {
10
+ types = {
11
11
  boolean: 1,
12
12
  tinyint: 2,
13
13
  smallint: 3,
@@ -47,8 +47,11 @@ module DuckDB
47
47
  sqlnull: 36,
48
48
  string_literal: 37,
49
49
  integer_literal: 38
50
- # time_ns: 39
51
- }.each do |method_name, type_id|
50
+ }
51
+ # duckdb_create_logical_type rejects TIME_NS on DuckDB < 1.5.0.
52
+ types[:time_ns] = 39 if Gem::Version.new(DuckDB::LIBRARY_VERSION) >= Gem::Version.new('1.5.0')
53
+
54
+ types.each do |method_name, type_id|
52
55
  define_singleton_method(method_name) do
53
56
  @logical_types[type_id] ||= DuckDB::LogicalType.new(type_id)
54
57
  end
@@ -76,6 +76,21 @@ module DuckDB
76
76
  Converter::IntToSym.type_to_sym(i)
77
77
  end
78
78
 
79
+ # returns the column type of the result set of the prepared statement
80
+ # without executing it. The argument is the column index (0-based).
81
+ # Returns :invalid if the column index is out of range.
82
+ #
83
+ # require 'duckdb'
84
+ # db = DuckDB::Database.open
85
+ # con = db.connect
86
+ # con.execute('CREATE TABLE users (id INTEGER, name VARCHAR(255))')
87
+ # stmt = con.prepared_statement('SELECT * FROM users')
88
+ # stmt.column_type(0) # => :integer
89
+ def column_type(index)
90
+ i = _column_type(index)
91
+ Converter::IntToSym.type_to_sym(i)
92
+ end
93
+
79
94
  # binds all parameters with SQL prepared statement.
80
95
  #
81
96
  # require 'duckdb'
@@ -96,8 +96,8 @@ module DuckDB
96
96
 
97
97
  # Adds a parameter to the scalar function.
98
98
  # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DECIMAL, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT,
99
- # TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT,
100
- # UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
99
+ # TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT,
100
+ # UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
101
101
  # For DECIMAL, pass a DuckDB::LogicalType instance created with DuckDB::LogicalType.create_decimal(width, scale).
102
102
  #
103
103
  # @param logical_type [DuckDB::LogicalType | :logical_type_symbol] the parameter type
@@ -111,8 +111,8 @@ module DuckDB
111
111
 
112
112
  # Sets the return type for the scalar function.
113
113
  # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DECIMAL, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT,
114
- # TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT,
115
- # UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
114
+ # TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT,
115
+ # UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
116
116
  # For DECIMAL, pass a DuckDB::LogicalType instance created with DuckDB::LogicalType.create_decimal(width, scale).
117
117
  #
118
118
  # @param logical_type [DuckDB::LogicalType | :logical_type_symbol] the return type
@@ -144,8 +144,8 @@ module DuckDB
144
144
  # (e.g. a separator followed by a variable list of values).
145
145
  # The block receives fixed parameters positionally, then varargs as a splat (|fixed, *rest|).
146
146
  # Currently supports BIGINT, BLOB, BOOLEAN, DATE, DECIMAL, DOUBLE, FLOAT, HUGEINT, INTEGER, INTERVAL, SMALLINT,
147
- # TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT, UHUGEINT,
148
- # UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
147
+ # TIME, TIMESTAMP, TIMESTAMP_S, TIMESTAMP_MS, TIMESTAMP_NS, TIME_NS, TIME_TZ, TIMESTAMP_TZ, TINYINT, UBIGINT,
148
+ # UHUGEINT, UINTEGER, USMALLINT, UTINYINT, UUID, and VARCHAR types.
149
149
  # For DECIMAL, pass a DuckDB::LogicalType instance created with DuckDB::LogicalType.create_decimal(width, scale).
150
150
  #
151
151
  # @param logical_type [DuckDB::LogicalType | :logical_type_symbol] the varargs element type
data/lib/duckdb/value.rb CHANGED
@@ -238,8 +238,408 @@ module DuckDB
238
238
  _create_decimal(lower, upper, width, value.scale)
239
239
  end
240
240
 
241
+ # Creates a DuckDB::Value of DATE type.
242
+ # The argument is parsed leniently: a Date, a Time, or a String
243
+ # accepted by Date.parse, matching Appender#append_date.
244
+ #
245
+ # value = DuckDB::Value.create_date(Date.new(2026, 7, 12))
246
+ # value = DuckDB::Value.create_date('2026-07-12')
247
+ #
248
+ # @param value [Date, Time, String] the date value.
249
+ # @return [DuckDB::Value] the created Value object.
250
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Date.
251
+ def create_date(value)
252
+ date = _parse_date(value)
253
+ _create_date(date.year, date.month, date.day)
254
+ end
255
+
256
+ # Creates a DuckDB::Value of TIME type (microsecond precision).
257
+ # The argument is parsed leniently: a Time or a String accepted by
258
+ # Time.parse, matching Appender#append_time.
259
+ #
260
+ # value = DuckDB::Value.create_time(Time.now)
261
+ # value = DuckDB::Value.create_time('12:34:56.789')
262
+ #
263
+ # @param value [Time, String] the time value.
264
+ # @return [DuckDB::Value] the created Value object.
265
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Time.
266
+ def create_time(value)
267
+ time = _parse_time(value)
268
+ _create_time(time.hour, time.min, time.sec, time.usec)
269
+ end
270
+
271
+ # Creates a DuckDB::Value of TIME_NS type (nanosecond precision).
272
+ # The argument is parsed leniently: a Time or a String accepted by
273
+ # Time.parse. Nanoseconds are preserved.
274
+ #
275
+ # value = DuckDB::Value.create_time_ns(Time.now)
276
+ # value = DuckDB::Value.create_time_ns('12:34:56.123456789')
277
+ #
278
+ # @param value [Time, String] the time value.
279
+ # @return [DuckDB::Value] the created Value object.
280
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Time.
281
+ def create_time_ns(value)
282
+ time = _parse_time(value)
283
+ _create_time_ns(time.hour, time.min, time.sec, time.nsec)
284
+ end
285
+
286
+ # Creates a DuckDB::Value of TIMETZ type (time with UTC offset).
287
+ # The argument is parsed leniently: a Time (the offset is taken from
288
+ # the Time) or a String accepted by Time.parse.
289
+ #
290
+ # value = DuckDB::Value.create_time_tz(Time.now)
291
+ # value = DuckDB::Value.create_time_tz('12:34:56.789012+05:30')
292
+ #
293
+ # @param value [Time, String] the time value.
294
+ # @return [DuckDB::Value] the created Value object.
295
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Time.
296
+ def create_time_tz(value)
297
+ time = _parse_time(value)
298
+ micros = (((((time.hour * 60) + time.min) * 60) + time.sec) * 1_000_000) + time.usec
299
+ _create_time_tz(micros, time.utc_offset)
300
+ end
301
+
302
+ # Creates a DuckDB::Value of TIMESTAMP type (microsecond precision).
303
+ # The argument is parsed leniently: a Time, a Date, or a String
304
+ # accepted by Time.parse, matching Appender#append_timestamp.
305
+ #
306
+ # value = DuckDB::Value.create_timestamp(Time.now)
307
+ # value = DuckDB::Value.create_timestamp('2026-07-12 12:34:56.789')
308
+ #
309
+ # @param value [Time, Date, String] the timestamp value.
310
+ # @return [DuckDB::Value] the created Value object.
311
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Time.
312
+ def create_timestamp(value)
313
+ time = to_time(value)
314
+ _create_timestamp(time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
315
+ end
316
+
317
+ # Creates a DuckDB::Value of TIMESTAMP_S type (second precision).
318
+ # The argument is parsed leniently: a Time, a Date, or a String
319
+ # accepted by Time.parse. Sub-second input is truncated to seconds.
320
+ #
321
+ # value = DuckDB::Value.create_timestamp_s(Time.now)
322
+ # value = DuckDB::Value.create_timestamp_s('2026-07-12 12:34:56')
323
+ #
324
+ # @param value [Time, Date, String] the timestamp value.
325
+ # @return [DuckDB::Value] the created Value object.
326
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Time.
327
+ def create_timestamp_s(value)
328
+ time = to_time(value)
329
+ _create_timestamp_s(time.year, time.month, time.day, time.hour, time.min, time.sec)
330
+ end
331
+
332
+ # Creates a DuckDB::Value of TIMESTAMP_MS type (millisecond precision).
333
+ # The argument is parsed leniently: a Time, a Date, or a String
334
+ # accepted by Time.parse. Sub-millisecond input is truncated.
335
+ #
336
+ # value = DuckDB::Value.create_timestamp_ms(Time.now)
337
+ # value = DuckDB::Value.create_timestamp_ms('2026-07-12 12:34:56.789')
338
+ #
339
+ # @param value [Time, Date, String] the timestamp value.
340
+ # @return [DuckDB::Value] the created Value object.
341
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Time.
342
+ def create_timestamp_ms(value)
343
+ time = to_time(value)
344
+ _create_timestamp_ms(time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
345
+ end
346
+
347
+ # Creates a DuckDB::Value of TIMESTAMP_NS type (nanosecond precision).
348
+ # The argument is parsed leniently: a Time, a Date, or a String
349
+ # accepted by Time.parse. Nanoseconds are preserved.
350
+ #
351
+ # value = DuckDB::Value.create_timestamp_ns(Time.now)
352
+ # value = DuckDB::Value.create_timestamp_ns('2026-07-12 12:34:56.123456789')
353
+ #
354
+ # @param value [Time, Date, String] the timestamp value.
355
+ # @return [DuckDB::Value] the created Value object.
356
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Time.
357
+ def create_timestamp_ns(value)
358
+ time = to_time(value)
359
+ _create_timestamp_ns(time.year, time.month, time.day, time.hour, time.min, time.sec, time.nsec)
360
+ end
361
+
362
+ # Creates a DuckDB::Value of TIMESTAMP WITH TIME ZONE type.
363
+ # The argument is parsed leniently: a Time, a Date, or a String
364
+ # accepted by Time.parse. The instant is stored (as microseconds since
365
+ # the Unix epoch), so the input's UTC offset is honored.
366
+ #
367
+ # value = DuckDB::Value.create_timestamp_tz(Time.now)
368
+ # value = DuckDB::Value.create_timestamp_tz('2026-07-12 12:34:56.789+05:30')
369
+ #
370
+ # @param value [Time, Date, String] the timestamp value.
371
+ # @return [DuckDB::Value] the created Value object.
372
+ # @raise [ArgumentError] if +value+ cannot be parsed to a Time.
373
+ def create_timestamp_tz(value)
374
+ time = to_time(value)
375
+ _create_timestamp_tz((time.to_i * 1_000_000) + time.usec)
376
+ end
377
+
378
+ # Creates a DuckDB::Value of INTERVAL type.
379
+ # Unlike the other temporal creators, the input is strict: it must be
380
+ # a DuckDB::Interval.
381
+ #
382
+ # interval = DuckDB::Interval.new(interval_months: 14, interval_days: 3, interval_micros: 12_000_000)
383
+ # value = DuckDB::Value.create_interval(interval)
384
+ #
385
+ # @param value [DuckDB::Interval] the interval value.
386
+ # @return [DuckDB::Value] the created Value object.
387
+ # @raise [ArgumentError] if +value+ is not a DuckDB::Interval.
388
+ def create_interval(value)
389
+ check_type!(value, DuckDB::Interval)
390
+
391
+ _create_interval(value.interval_months, value.interval_days, value.interval_micros)
392
+ end
393
+
394
+ # Creates a DuckDB::Value of ENUM type.
395
+ # The first argument is the enum type: an Array of member names (as
396
+ # accepted by DuckDB::LogicalType.create_enum) or an ENUM
397
+ # DuckDB::LogicalType.
398
+ # The second argument is the member: its name (String or Symbol) or
399
+ # its 0-based index (Integer).
400
+ #
401
+ # value = DuckDB::Value.create_enum(%w[happy sad neutral], 'sad')
402
+ #
403
+ # # equivalent, with an explicit ENUM logical type:
404
+ # enum_type = DuckDB::LogicalType.create_enum('happy', 'sad', 'neutral')
405
+ # value = DuckDB::Value.create_enum(enum_type, 'sad')
406
+ #
407
+ # @param enum_type [Array<String>, DuckDB::LogicalType] the member names or ENUM logical type.
408
+ # @param member [String, Symbol, Integer] the member name or 0-based index.
409
+ # @return [DuckDB::Value] the created Value object.
410
+ # @raise [ArgumentError] if enum_type is not an Array or an ENUM
411
+ # DuckDB::LogicalType, or member is not a member of the enum.
412
+ def create_enum(enum_type, member)
413
+ enum_type = DuckDB::LogicalType.create_enum(*enum_type) if enum_type.is_a?(Array)
414
+ check_type!(enum_type, DuckDB::LogicalType)
415
+ raise ArgumentError, "expected ENUM LogicalType, got #{enum_type.type}" unless enum_type.type == :enum
416
+
417
+ _create_enum(enum_type, enum_member_index(enum_type, member))
418
+ end
419
+
420
+ # Creates a DuckDB::Value of UNION type.
421
+ # The first argument is the union type: a Hash of member name to
422
+ # member type (as accepted by DuckDB::LogicalType.create_union) or a
423
+ # UNION DuckDB::LogicalType.
424
+ # The second argument is the member tag (String or Symbol), and the
425
+ # third is the member value as a DuckDB::Value of the tagged type.
426
+ #
427
+ # value = DuckDB::Value.create_union({ num: :integer, str: :varchar }, :num, DuckDB::Value.create_int32(42))
428
+ # value.to_ruby #=> 42
429
+ #
430
+ # # equivalent, with an explicit UNION logical type:
431
+ # union_type = DuckDB::LogicalType.create_union(num: :integer, str: :varchar)
432
+ # value = DuckDB::Value.create_union(union_type, :num, DuckDB::Value.create_int32(42))
433
+ #
434
+ # @param union_type [Hash, DuckDB::LogicalType] the member spec or UNION logical type.
435
+ # @param tag [String, Symbol] the member tag.
436
+ # @param member_value [DuckDB::Value] the member value.
437
+ # @return [DuckDB::Value] the created Value object.
438
+ # @raise [ArgumentError] if union_type is not a Hash or a UNION
439
+ # DuckDB::LogicalType, tag is not a member of the union, or
440
+ # member_value is not a DuckDB::Value.
441
+ # @raise [DuckDB::Error] if member_value does not match the tagged
442
+ # member's type.
443
+ def create_union(union_type, tag, member_value)
444
+ union_type = DuckDB::LogicalType.create_union(**union_type) if union_type.is_a?(Hash)
445
+ check_type!(union_type, DuckDB::LogicalType)
446
+ raise ArgumentError, "expected UNION LogicalType, got #{union_type.type}" unless union_type.type == :union
447
+
448
+ check_type!(member_value, DuckDB::Value)
449
+ tag_index = union_type.each_member_name.find_index(tag.to_s)
450
+ raise ArgumentError, "`#{tag}` is not a member of the union" if tag_index.nil?
451
+
452
+ _create_union(union_type, tag_index, member_value)
453
+ end
454
+
455
+ # Creates a DuckDB::Value of BIT (bitstring) type.
456
+ #
457
+ # value = DuckDB::Value.create_bit('0101')
458
+ #
459
+ # @param value [String] the bitstring: '0'/'1' characters, MSB first.
460
+ # @return [DuckDB::Value] the created Value object.
461
+ # @raise [ArgumentError] if +value+ is not a non-empty String of '0'
462
+ # and '1' characters.
463
+ def create_bit(value)
464
+ check_type!(value, String)
465
+ raise ArgumentError, "expected a String of '0'/'1' characters" unless value.match?(/\A[01]+\z/)
466
+
467
+ pad = -value.length % 8
468
+ _create_bit("#{pad.chr}#{[('1' * pad) + value].pack('B*')}".b)
469
+ end
470
+
471
+ # Creates a DuckDB::Value of BIGNUM (arbitrary-precision integer)
472
+ # type.
473
+ #
474
+ # value = DuckDB::Value.create_bignum(2**200)
475
+ #
476
+ # @param value [Integer] the integer value, of arbitrary size.
477
+ # @return [DuckDB::Value] the created Value object.
478
+ # @raise [ArgumentError] if +value+ is not an Integer.
479
+ def create_bignum(value)
480
+ check_type!(value, Integer)
481
+
482
+ hex = value.abs.to_s(16)
483
+ hex = "0#{hex}" if hex.length.odd?
484
+ _create_bignum([hex].pack('H*'), value.negative?)
485
+ end
486
+
487
+ # Creates a DuckDB::Value of LIST type.
488
+ # The first argument is the element type: a Symbol (e.g. :integer) or a
489
+ # DuckDB::LogicalType.
490
+ # The second argument is an Array of DuckDB::Value elements.
491
+ #
492
+ # values = [1, 2, 3].map { |i| DuckDB::Value.create_int32(i) }
493
+ # list = DuckDB::Value.create_list(:integer, values)
494
+ #
495
+ # @param child_type [Symbol, DuckDB::LogicalType] the element logical type.
496
+ # @param values [Array<DuckDB::Value>] the list elements.
497
+ # @return [DuckDB::Value] the created Value object.
498
+ # @raise [DuckDB::Error] if child_type cannot be resolved to a
499
+ # DuckDB::LogicalType.
500
+ # @raise [ArgumentError] if values is not an Array of DuckDB::Value.
501
+ def create_list(child_type, values)
502
+ check_value_array!(values)
503
+
504
+ _create_list(DuckDB::LogicalType.resolve(child_type), values)
505
+ end
506
+
507
+ # Creates a DuckDB::Value of ARRAY type. The array size is the number
508
+ # of elements given.
509
+ # The first argument is the element type: a Symbol (e.g. :integer) or a
510
+ # DuckDB::LogicalType.
511
+ # The second argument is an Array of DuckDB::Value elements.
512
+ #
513
+ # values = [1, 2, 3].map { |i| DuckDB::Value.create_int32(i) }
514
+ # array = DuckDB::Value.create_array(:integer, values)
515
+ #
516
+ # @param child_type [Symbol, DuckDB::LogicalType] the element logical type.
517
+ # @param values [Array<DuckDB::Value>] the array elements.
518
+ # @return [DuckDB::Value] the created Value object.
519
+ # @raise [DuckDB::Error] if child_type cannot be resolved to a
520
+ # DuckDB::LogicalType.
521
+ # @raise [ArgumentError] if values is not an Array of DuckDB::Value.
522
+ def create_array(child_type, values)
523
+ check_value_array!(values)
524
+
525
+ _create_array(DuckDB::LogicalType.resolve(child_type), values)
526
+ end
527
+
528
+ # Creates a DuckDB::Value of STRUCT type.
529
+ # The first argument is the field spec: a Hash of field name to field
530
+ # type (a Symbol or a DuckDB::LogicalType, as accepted by
531
+ # DuckDB::LogicalType.create_struct) or a STRUCT DuckDB::LogicalType.
532
+ # The second argument is an Array of DuckDB::Value field values,
533
+ # positional, matching the struct's field order.
534
+ #
535
+ # values = [DuckDB::Value.create_int32(1), DuckDB::Value.create_varchar('x')]
536
+ # struct = DuckDB::Value.create_struct({ a: :integer, b: :varchar }, values)
537
+ #
538
+ # # equivalent, with an explicit STRUCT logical type:
539
+ # struct_type = DuckDB::LogicalType.create_struct(a: :integer, b: :varchar)
540
+ # struct = DuckDB::Value.create_struct(struct_type, values)
541
+ #
542
+ # @param struct_type [Hash, DuckDB::LogicalType] the field spec or STRUCT logical type.
543
+ # @param values [Array<DuckDB::Value>] the field values, in field order.
544
+ # @return [DuckDB::Value] the created Value object.
545
+ # @raise [DuckDB::Error] if a field type in the Hash cannot be resolved
546
+ # to a DuckDB::LogicalType.
547
+ # @raise [ArgumentError] if struct_type is not a Hash or a STRUCT
548
+ # DuckDB::LogicalType, values is not an Array of DuckDB::Value, or
549
+ # values.size does not match the struct's field count.
550
+ def create_struct(struct_type, values)
551
+ struct_type = DuckDB::LogicalType.create_struct(**struct_type) if struct_type.is_a?(Hash)
552
+ check_type!(struct_type, DuckDB::LogicalType)
553
+ raise ArgumentError, "expected STRUCT LogicalType, got #{struct_type.type}" unless struct_type.type == :struct
554
+
555
+ check_value_array!(values)
556
+ # The C API's duckdb_create_struct_value reads exactly child_count
557
+ # values from the buffer with no count argument, so a size mismatch
558
+ # here would cause an out-of-bounds read in C.
559
+ n = struct_type.child_count
560
+ raise ArgumentError, "expected #{n} values for STRUCT, got #{values.size}" unless values.size == n
561
+
562
+ _create_struct(struct_type, values)
563
+ end
564
+
565
+ # Creates a DuckDB::Value of MAP type.
566
+ # The first argument is the type spec: a one-pair Hash of key type to
567
+ # value type (each a Symbol or a DuckDB::LogicalType, as accepted by
568
+ # DuckDB::LogicalType.create_map) or a MAP DuckDB::LogicalType.
569
+ # The second argument is a Hash of DuckDB::Value keys to DuckDB::Value
570
+ # values.
571
+ #
572
+ # entries = {
573
+ # DuckDB::Value.create_varchar('a') => DuckDB::Value.create_int32(1),
574
+ # DuckDB::Value.create_varchar('b') => DuckDB::Value.create_int32(2)
575
+ # }
576
+ # map = DuckDB::Value.create_map({ varchar: :integer }, entries)
577
+ #
578
+ # # equivalent, with an explicit MAP logical type:
579
+ # map_type = DuckDB::LogicalType.create_map(:varchar, :integer)
580
+ # map = DuckDB::Value.create_map(map_type, entries)
581
+ #
582
+ # @param map_type [Hash, DuckDB::LogicalType] the type spec or MAP logical type.
583
+ # @param entries [Hash{DuckDB::Value => DuckDB::Value}] the map entries.
584
+ # @return [DuckDB::Value] the created Value object.
585
+ # @raise [DuckDB::Error] if a type in the Hash spec cannot be resolved
586
+ # to a DuckDB::LogicalType.
587
+ # @raise [ArgumentError] if map_type is not a one-pair Hash or a MAP
588
+ # DuckDB::LogicalType, or entries is not a Hash of DuckDB::Value to
589
+ # DuckDB::Value.
590
+ def create_map(map_type, entries)
591
+ map_type = resolve_map_type(map_type)
592
+ check_type!(entries, Hash)
593
+ check_value_array!(entries.keys)
594
+ check_value_array!(entries.values)
595
+
596
+ _create_map(map_type, entries.keys, entries.values)
597
+ end
598
+
241
599
  private
242
600
 
601
+ def to_time(value)
602
+ value.is_a?(Date) ? value.to_time : _parse_time(value)
603
+ end
604
+
605
+ def enum_member_index(enum_type, member)
606
+ case member
607
+ when Integer
608
+ check_range!(member, 0...enum_type.dictionary_size, 'ENUM index')
609
+ member
610
+ when String, Symbol
611
+ enum_member_index_by_name(enum_type, member)
612
+ else
613
+ raise ArgumentError, "expected String, Symbol or Integer, got #{member.class.name}"
614
+ end
615
+ end
616
+
617
+ def enum_member_index_by_name(enum_type, member)
618
+ index = enum_type.each_dictionary_value.find_index(member.to_s)
619
+ raise ArgumentError, "`#{member}` is not a member of the enum" if index.nil?
620
+
621
+ index
622
+ end
623
+
624
+ def resolve_map_type(map_type)
625
+ if map_type.is_a?(Hash)
626
+ unless map_type.size == 1
627
+ raise ArgumentError, "expected exactly one key type => value type pair, got #{map_type.size}"
628
+ end
629
+
630
+ map_type = DuckDB::LogicalType.create_map(*map_type.first)
631
+ end
632
+ check_type!(map_type, DuckDB::LogicalType)
633
+ raise ArgumentError, "expected MAP LogicalType, got #{map_type.type}" unless map_type.type == :map
634
+
635
+ map_type
636
+ end
637
+
638
+ def check_value_array!(values)
639
+ check_type!(values, Array)
640
+ values.each { |value| check_type!(value, DuckDB::Value) }
641
+ end
642
+
243
643
  def check_range!(value, range, type_name)
244
644
  raise ArgumentError, "value out of range for #{type_name} (#{range})" unless range.cover?(value)
245
645
  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.4.0'
6
+ VERSION = '1.5.5.0'
7
7
  end
data/lib/duckdb.rb CHANGED
@@ -4,6 +4,7 @@ require 'duckdb/duckdb_native'
4
4
  require 'duckdb/library_version'
5
5
  require 'duckdb/version'
6
6
  require 'duckdb/converter'
7
+ require 'duckdb/error'
7
8
  require 'duckdb/table_name_parser'
8
9
  require 'duckdb/database'
9
10
  require 'duckdb/connection'
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.4.0
4
+ version: 1.5.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masaki Suketa
@@ -35,6 +35,7 @@ extra_rdoc_files: []
35
35
  files:
36
36
  - ".rdoc_options"
37
37
  - CHANGELOG.md
38
+ - CLAUDE.md
38
39
  - LICENSE
39
40
  - README.md
40
41
  - Rakefile
@@ -129,6 +130,7 @@ files:
129
130
  - lib/duckdb/converter/int_to_sym.rb
130
131
  - lib/duckdb/data_chunk.rb
131
132
  - lib/duckdb/database.rb
133
+ - lib/duckdb/error.rb
132
134
  - lib/duckdb/expression.rb
133
135
  - lib/duckdb/extracted_statements.rb
134
136
  - lib/duckdb/function_type_validation.rb
@@ -167,14 +169,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
167
169
  requirements:
168
170
  - - ">="
169
171
  - !ruby/object:Gem::Version
170
- version: 3.2.0
172
+ version: 3.3.0
171
173
  required_rubygems_version: !ruby/object:Gem::Requirement
172
174
  requirements:
173
175
  - - ">="
174
176
  - !ruby/object:Gem::Version
175
177
  version: '0'
176
178
  requirements: []
177
- rubygems_version: 4.0.10
179
+ rubygems_version: 4.0.17
178
180
  specification_version: 4
179
181
  summary: Ruby bindings for the DuckDB database engine.
180
182
  test_files: []