duckdb 1.5.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/CLAUDE.md +4 -0
- data/README.md +52 -0
- data/duckdb.gemspec +1 -1
- data/ext/duckdb/aggregate_function.c +0 -1
- data/ext/duckdb/appender.c +17 -0
- data/ext/duckdb/arrow_array_stream.c +226 -0
- data/ext/duckdb/arrow_array_stream.h +61 -0
- data/ext/duckdb/arrow_import.c +165 -0
- data/ext/duckdb/arrow_import.h +6 -0
- data/ext/duckdb/blob.c +1 -1
- data/ext/duckdb/blob.h +1 -2
- data/ext/duckdb/config.c +1 -1
- data/ext/duckdb/config.h +1 -1
- data/ext/duckdb/connection.c +26 -4
- data/ext/duckdb/converter.h +1 -0
- data/ext/duckdb/conveter.c +39 -9
- data/ext/duckdb/data_chunk.c +10 -0
- data/ext/duckdb/data_chunk.h +1 -0
- data/ext/duckdb/duckdb.c +13 -11
- data/ext/duckdb/error.c +13 -1
- data/ext/duckdb/error.h +2 -3
- data/ext/duckdb/function_executor.c +308 -2
- data/ext/duckdb/function_executor.h +44 -0
- data/ext/duckdb/function_vector.c +14 -0
- data/ext/duckdb/prepared_statement.c +141 -5
- data/ext/duckdb/result.c +49 -3
- data/ext/duckdb/result.h +11 -0
- data/ext/duckdb/ruby-duckdb.h +3 -0
- data/ext/duckdb/scalar_function.c +97 -29
- data/ext/duckdb/scalar_function.h +2 -4
- data/ext/duckdb/scalar_function_bind_info.c +13 -13
- data/ext/duckdb/scalar_function_bind_info.h +1 -1
- data/ext/duckdb/scalar_function_set.c +9 -9
- data/ext/duckdb/scalar_function_set.h +2 -2
- data/ext/duckdb/table_description.c +19 -19
- data/ext/duckdb/table_description.h +1 -1
- data/ext/duckdb/table_function.c +94 -28
- data/ext/duckdb/table_function.h +2 -2
- data/ext/duckdb/table_function_bind_info.c +20 -20
- data/ext/duckdb/table_function_bind_info.h +2 -2
- data/ext/duckdb/table_function_function_info.c +5 -5
- data/ext/duckdb/table_function_function_info.h +2 -2
- data/ext/duckdb/table_function_init_info.c +70 -5
- data/ext/duckdb/table_function_init_info.h +2 -2
- data/ext/duckdb/util.c +8 -0
- data/ext/duckdb/util.h +1 -0
- data/ext/duckdb/value.c +526 -1
- data/lib/duckdb/appender.rb +23 -0
- data/lib/duckdb/arrow_array_stream.rb +33 -0
- data/lib/duckdb/connection.rb +73 -0
- data/lib/duckdb/converter/int_to_sym.rb +54 -1
- data/lib/duckdb/converter.rb +7 -7
- data/lib/duckdb/error.rb +26 -0
- data/lib/duckdb/function_type_validation.rb +1 -0
- data/lib/duckdb/logical_type.rb +6 -3
- data/lib/duckdb/prepared_statement.rb +32 -0
- data/lib/duckdb/scalar_function.rb +6 -6
- data/lib/duckdb/value.rb +400 -0
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +2 -0
- metadata +10 -3
data/lib/duckdb/connection.rb
CHANGED
|
@@ -383,8 +383,81 @@ module DuckDB
|
|
|
383
383
|
register_table_function(tf)
|
|
384
384
|
end
|
|
385
385
|
|
|
386
|
+
# [EXPERIMENTAL] Appends an Arrow producer into an existing table.
|
|
387
|
+
#
|
|
388
|
+
# Reads +producer+ (any object responding to +#arrow_c_stream+, such as a
|
|
389
|
+
# ruby-polars +DataFrame+ or a +DuckDB::Result+) as an Arrow C stream and
|
|
390
|
+
# appends its chunks into the existing table +table+. The producer's Arrow
|
|
391
|
+
# columns must line up with the table's columns positionally and by count.
|
|
392
|
+
# DuckDB casts compatible column types (e.g. INTEGER into a BIGINT column);
|
|
393
|
+
# a type that cannot be cast (e.g. a non-numeric VARCHAR into an INTEGER
|
|
394
|
+
# column) or a column-count mismatch raises +DuckDB::Error+.
|
|
395
|
+
#
|
|
396
|
+
# This is not transactional: a schema mismatch fails before any rows are
|
|
397
|
+
# written, but a rarer mid-stream failure can leave earlier chunks
|
|
398
|
+
# appended. Wrap the call in your own transaction for all-or-nothing.
|
|
399
|
+
#
|
|
400
|
+
# This API is built on DuckDB's unstable Arrow C API and may change in any
|
|
401
|
+
# minor release.
|
|
402
|
+
#
|
|
403
|
+
# @param table [String] the name of the existing target table
|
|
404
|
+
# @param producer [#arrow_c_stream] the Arrow producer
|
|
405
|
+
# @raise [TypeError] if +producer+ does not respond to +#arrow_c_stream+
|
|
406
|
+
# @return [Integer] the number of rows appended
|
|
407
|
+
#
|
|
408
|
+
# @example Load a Polars DataFrame into a table
|
|
409
|
+
# con.query('CREATE TABLE t (id INTEGER, name VARCHAR)')
|
|
410
|
+
# con.append_arrow('t', polars_df)
|
|
411
|
+
#
|
|
412
|
+
def append_arrow(table, producer)
|
|
413
|
+
unless producer.respond_to?(:arrow_c_stream)
|
|
414
|
+
raise TypeError, "Arrow producer must respond to #arrow_c_stream, got #{producer.class}"
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
stream = producer.arrow_c_stream # keep the producer's stream alive for the duration
|
|
418
|
+
address = stream.to_i
|
|
419
|
+
begin
|
|
420
|
+
append_arrow_chunks(table, address)
|
|
421
|
+
ensure
|
|
422
|
+
_arrow_release(address)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
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
|
+
|
|
386
445
|
private
|
|
387
446
|
|
|
447
|
+
# Drives the Arrow stream at +address+ chunk by chunk into +table+,
|
|
448
|
+
# returning the number of rows appended.
|
|
449
|
+
def append_arrow_chunks(table, address)
|
|
450
|
+
converted_schema = _arrow_converted_schema(address)
|
|
451
|
+
rows = 0
|
|
452
|
+
appender(table) do |app|
|
|
453
|
+
while (chunk = _arrow_next_chunk(address, converted_schema))
|
|
454
|
+
rows += chunk.size
|
|
455
|
+
app.append_data_chunk(chunk)
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
rows
|
|
459
|
+
end
|
|
460
|
+
|
|
388
461
|
def run_appender_block(appender, &)
|
|
389
462
|
return appender unless block_given?
|
|
390
463
|
|
|
@@ -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
|
|
data/lib/duckdb/converter.rb
CHANGED
|
@@ -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
|
|
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
|
|
93
|
-
|
|
94
|
-
nanos
|
|
95
|
-
sec
|
|
96
|
-
|
|
97
|
-
|
|
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)
|
data/lib/duckdb/error.rb
ADDED
|
@@ -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
|
data/lib/duckdb/logical_type.rb
CHANGED
|
@@ -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
|
-
|
|
51
|
-
|
|
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'
|
|
@@ -193,6 +208,23 @@ module DuckDB
|
|
|
193
208
|
_bind_uhugeint(index, lower, upper)
|
|
194
209
|
end
|
|
195
210
|
|
|
211
|
+
# binds i-th parameter with a UUID value.
|
|
212
|
+
# The first argument is the index of the parameter (1-based).
|
|
213
|
+
# The second argument must be a String in canonical UUID format
|
|
214
|
+
# (<tt>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</tt>).
|
|
215
|
+
# Raises ArgumentError if the value is not a valid UUID string.
|
|
216
|
+
#
|
|
217
|
+
# require 'duckdb'
|
|
218
|
+
# db = DuckDB::Database.open
|
|
219
|
+
# con = db.connect
|
|
220
|
+
# con.query('CREATE TABLE uuids (id UUID)')
|
|
221
|
+
# stmt = DuckDB::PreparedStatement.new(con, 'INSERT INTO uuids(id) VALUES ($1)')
|
|
222
|
+
# stmt.bind_uuid(1, '550e8400-e29b-41d4-a716-446655440000')
|
|
223
|
+
# stmt.execute
|
|
224
|
+
def bind_uuid(index, value)
|
|
225
|
+
_bind_uuid(index, value)
|
|
226
|
+
end
|
|
227
|
+
|
|
196
228
|
# binds i-th parameter with SQL prepared statement.
|
|
197
229
|
# The first argument is index of parameter.
|
|
198
230
|
# The index of first parameter is 1 not 0.
|
|
@@ -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,
|
|
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,
|
|
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,
|
|
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
|