duckdb 1.4.2.0 → 1.4.4.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/.github/copilot-instructions.md +169 -0
- data/.github/workflows/linter.yml +30 -0
- data/.github/workflows/test_on_macos.yml +7 -2
- data/.github/workflows/test_on_ubuntu.yml +7 -2
- data/.github/workflows/test_on_windows.yml +8 -3
- data/.rubocop.yml +34 -0
- data/CHANGELOG.md +74 -0
- data/Dockerfile +2 -2
- data/Gemfile +12 -0
- data/Gemfile.lock +70 -7
- data/Rakefile +8 -6
- data/bin/console +4 -3
- data/duckdb.gemspec +2 -6
- data/ext/duckdb/bind_info.c +243 -0
- data/ext/duckdb/bind_info.h +13 -0
- data/ext/duckdb/connection.c +60 -2
- data/ext/duckdb/connection.h +1 -0
- data/ext/duckdb/data_chunk.c +137 -0
- data/ext/duckdb/data_chunk.h +13 -0
- data/ext/duckdb/duckdb.c +7 -0
- data/ext/duckdb/function_info.c +65 -0
- data/ext/duckdb/function_info.h +13 -0
- data/ext/duckdb/init_info.c +65 -0
- data/ext/duckdb/init_info.h +13 -0
- data/ext/duckdb/logical_type.c +42 -0
- data/ext/duckdb/memory_helper.c +339 -0
- data/ext/duckdb/memory_helper.h +8 -0
- data/ext/duckdb/result.c +8 -9
- data/ext/duckdb/result.h +1 -0
- data/ext/duckdb/ruby-duckdb.h +7 -0
- data/ext/duckdb/scalar_function.c +336 -1
- data/ext/duckdb/scalar_function.h +2 -0
- data/ext/duckdb/table_function.c +403 -0
- data/ext/duckdb/table_function.h +16 -0
- data/ext/duckdb/vector.c +201 -0
- data/ext/duckdb/vector.h +13 -0
- data/lib/duckdb/appender.rb +21 -11
- data/lib/duckdb/bind_info.rb +32 -0
- data/lib/duckdb/casting.rb +35 -0
- data/lib/duckdb/connection.rb +123 -2
- data/lib/duckdb/converter.rb +32 -47
- data/lib/duckdb/data_chunk.rb +114 -0
- data/lib/duckdb/extracted_statements.rb +1 -1
- data/lib/duckdb/function_info.rb +21 -0
- data/lib/duckdb/init_info.rb +22 -0
- data/lib/duckdb/instance_cache.rb +0 -4
- data/lib/duckdb/interval.rb +1 -1
- data/lib/duckdb/logical_type.rb +46 -5
- data/lib/duckdb/prepared_statement.rb +22 -8
- data/lib/duckdb/result.rb +1 -0
- data/lib/duckdb/scalar_function.rb +119 -0
- data/lib/duckdb/table_function.rb +191 -0
- data/lib/duckdb/vector.rb +20 -0
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +8 -0
- data/sample/async_query.rb +2 -0
- data/sample/async_query_stream.rb +2 -0
- data/sample/issue922.rb +54 -0
- data/sample/issue922_benchmark.rb +169 -0
- data/sample/issue930.rb +49 -0
- data/sample/issue930_benchmark.rb +70 -0
- metadata +30 -57
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuckDB
|
|
4
|
+
#
|
|
5
|
+
# The DuckDB::BindInfo encapsulates information for the bind phase of table functions.
|
|
6
|
+
#
|
|
7
|
+
# During the bind phase, you can:
|
|
8
|
+
# - Access parameters passed to the function
|
|
9
|
+
# - Define the output schema (columns)
|
|
10
|
+
# - Set performance hints (cardinality)
|
|
11
|
+
# - Report errors
|
|
12
|
+
#
|
|
13
|
+
# Example:
|
|
14
|
+
#
|
|
15
|
+
# table_function.bind do |bind_info|
|
|
16
|
+
# # Get parameters
|
|
17
|
+
# limit = bind_info.get_parameter(0).to_i
|
|
18
|
+
#
|
|
19
|
+
# # Define output schema
|
|
20
|
+
# bind_info.add_result_column('id', DuckDB::LogicalType::BIGINT)
|
|
21
|
+
# bind_info.add_result_column('name', DuckDB::LogicalType::VARCHAR)
|
|
22
|
+
#
|
|
23
|
+
# # Set cardinality hint
|
|
24
|
+
# bind_info.set_cardinality(limit, true)
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
class BindInfo
|
|
28
|
+
def add_result_column(name, type)
|
|
29
|
+
_add_result_column(name.to_s, DuckDB::LogicalType.resolve(type))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuckDB
|
|
4
|
+
module Casting
|
|
5
|
+
def cast(value, type) # rubocop:disable Metrics/MethodLength
|
|
6
|
+
type = type.type if type.is_a?(DuckDB::LogicalType)
|
|
7
|
+
case type
|
|
8
|
+
when :integer, :bigint, :hugeint
|
|
9
|
+
Integer(value)
|
|
10
|
+
when :float, :double
|
|
11
|
+
Float(value)
|
|
12
|
+
when :varchar
|
|
13
|
+
value.to_s
|
|
14
|
+
when :timestamp
|
|
15
|
+
cast_as_timestamp(value)
|
|
16
|
+
when :date
|
|
17
|
+
cast_as_date(value)
|
|
18
|
+
else
|
|
19
|
+
raise ArgumentError, "Unsupported type: #{type} for value: #{value}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def cast_as_timestamp(value)
|
|
26
|
+
DuckDB::Converter._parse_time(value)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def cast_as_date(value)
|
|
30
|
+
DuckDB::Converter._parse_date(value)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
extend Casting
|
|
35
|
+
end
|
data/lib/duckdb/connection.rb
CHANGED
|
@@ -80,10 +80,10 @@ module DuckDB
|
|
|
80
80
|
# pending_result.execute_task while pending_result.state == :not_ready
|
|
81
81
|
# result = pending_result.execute_pending
|
|
82
82
|
# result.each.first
|
|
83
|
-
def async_query_stream(sql,
|
|
83
|
+
def async_query_stream(sql, *, **)
|
|
84
84
|
warn("`#{self.class}#{__method__}` will be deprecated. Use `#{self.class}#async_query` instead.")
|
|
85
85
|
|
|
86
|
-
async_query(sql,
|
|
86
|
+
async_query(sql, *, **)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
# connects DuckDB database
|
|
@@ -156,8 +156,129 @@ module DuckDB
|
|
|
156
156
|
end
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
+
# Registers a scalar function with the connection.
|
|
160
|
+
#
|
|
161
|
+
# Scalar functions with Ruby callbacks require single-threaded execution.
|
|
162
|
+
#
|
|
163
|
+
# @overload register_scalar_function(scalar_function)
|
|
164
|
+
# Register a pre-created ScalarFunction object.
|
|
165
|
+
# @param scalar_function [DuckDB::ScalarFunction] the scalar function to register
|
|
166
|
+
#
|
|
167
|
+
# @overload register_scalar_function(name:, return_type:, **kwargs, &block)
|
|
168
|
+
# Create and register a scalar function inline.
|
|
169
|
+
# @param name [String, Symbol] the function name
|
|
170
|
+
# @param return_type [DuckDB::LogicalType] the return type
|
|
171
|
+
# @param parameter_type [DuckDB::LogicalType, nil] single parameter type
|
|
172
|
+
# @param parameter_types [Array<DuckDB::LogicalType>, nil] multiple parameter types
|
|
173
|
+
# @yield [*args] the function implementation
|
|
174
|
+
#
|
|
175
|
+
# @raise [DuckDB::Error] if threads setting is not 1
|
|
176
|
+
# @raise [ArgumentError] if both object and keywords/block are provided
|
|
177
|
+
# @return [void]
|
|
178
|
+
#
|
|
179
|
+
# @example Register pre-created function
|
|
180
|
+
# con.execute('SET threads=1')
|
|
181
|
+
# sf = DuckDB::ScalarFunction.create(
|
|
182
|
+
# name: :triple,
|
|
183
|
+
# return_type: DuckDB::LogicalType::INTEGER,
|
|
184
|
+
# parameter_type: DuckDB::LogicalType::INTEGER
|
|
185
|
+
# ) { |v| v * 3 }
|
|
186
|
+
# con.register_scalar_function(sf)
|
|
187
|
+
#
|
|
188
|
+
# @example Register inline (single parameter)
|
|
189
|
+
# con.execute('SET threads=1')
|
|
190
|
+
# con.register_scalar_function(
|
|
191
|
+
# name: :triple,
|
|
192
|
+
# return_type: DuckDB::LogicalType::INTEGER,
|
|
193
|
+
# parameter_type: DuckDB::LogicalType::INTEGER
|
|
194
|
+
# ) { |v| v * 3 }
|
|
195
|
+
#
|
|
196
|
+
# @example Register inline (multiple parameters)
|
|
197
|
+
# con.execute('SET threads=1')
|
|
198
|
+
# con.register_scalar_function(
|
|
199
|
+
# name: :add,
|
|
200
|
+
# return_type: DuckDB::LogicalType::INTEGER,
|
|
201
|
+
# parameter_types: [DuckDB::LogicalType::INTEGER, DuckDB::LogicalType::INTEGER]
|
|
202
|
+
# ) { |a, b| a + b }
|
|
203
|
+
def register_scalar_function(scalar_function = nil, **kwargs, &)
|
|
204
|
+
# Validate: can't pass both object and inline arguments
|
|
205
|
+
if scalar_function.is_a?(ScalarFunction)
|
|
206
|
+
raise ArgumentError, 'Cannot pass both ScalarFunction object and keyword arguments' if kwargs.any?
|
|
207
|
+
|
|
208
|
+
raise ArgumentError, 'Cannot pass both ScalarFunction object and block' if block_given?
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
check_threads
|
|
212
|
+
sf = scalar_function || ScalarFunction.create(**kwargs, &)
|
|
213
|
+
_register_scalar_function(sf)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
#
|
|
217
|
+
# Registers a table function with the database connection.
|
|
218
|
+
#
|
|
219
|
+
# table_function = DuckDB::TableFunction.new
|
|
220
|
+
# table_function.name = 'my_function'
|
|
221
|
+
# table_function.bind { |bind_info| ... }
|
|
222
|
+
# table_function.execute { |func_info, output| ... }
|
|
223
|
+
# connection.register_table_function(table_function)
|
|
224
|
+
#
|
|
225
|
+
def register_table_function(table_function)
|
|
226
|
+
raise ArgumentError, 'table_function must be a TableFunction' unless table_function.is_a?(TableFunction)
|
|
227
|
+
|
|
228
|
+
check_threads
|
|
229
|
+
_register_table_function(table_function)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Exposes a Ruby object as a queryable DuckDB table function via a registered adapter.
|
|
233
|
+
#
|
|
234
|
+
# Looks up a table adapter registered for the object's class via
|
|
235
|
+
# +DuckDB::TableFunction.add_table_adapter+, then uses it to create and register
|
|
236
|
+
# a table function under the given name.
|
|
237
|
+
#
|
|
238
|
+
# @param object [Object] the Ruby object to expose as a table (e.g. a CSV instance)
|
|
239
|
+
# @param name [String] the SQL name of the table function
|
|
240
|
+
# @param columns [Hash{String => DuckDB::LogicalType}, nil] optional column schema override;
|
|
241
|
+
# if omitted, the adapter determines the columns (e.g. from headers or inference)
|
|
242
|
+
# @raise [ArgumentError] if no adapter is registered for the object's class
|
|
243
|
+
# @raise [DuckDB::Error] if threads setting is not 1
|
|
244
|
+
# @return [void]
|
|
245
|
+
#
|
|
246
|
+
# @example Expose a CSV as a table
|
|
247
|
+
# require 'csv'
|
|
248
|
+
# con.execute('SET threads=1')
|
|
249
|
+
# DuckDB::TableFunction.add_table_adapter(CSV, CSVTableAdapter.new)
|
|
250
|
+
# csv = CSV.new(File.read('data.csv'), headers: true)
|
|
251
|
+
# con.expose_as_table(csv, 'csv_table')
|
|
252
|
+
# con.query('SELECT * FROM csv_table()').to_a
|
|
253
|
+
#
|
|
254
|
+
# @example With explicit column types
|
|
255
|
+
# con.expose_as_table(csv, 'csv_table', columns: {
|
|
256
|
+
# 'id' => DuckDB::LogicalType::BIGINT,
|
|
257
|
+
# 'name' => DuckDB::LogicalType::VARCHAR
|
|
258
|
+
# })
|
|
259
|
+
#
|
|
260
|
+
def expose_as_table(object, name, columns: nil)
|
|
261
|
+
adapter = TableFunction.table_adapter_for(object.class)
|
|
262
|
+
raise ArgumentError, "No table adapter registered for #{object.class}" if adapter.nil?
|
|
263
|
+
|
|
264
|
+
tf = adapter.call(object, name, columns:)
|
|
265
|
+
register_table_function(tf)
|
|
266
|
+
end
|
|
267
|
+
|
|
159
268
|
private
|
|
160
269
|
|
|
270
|
+
def check_threads
|
|
271
|
+
result = execute("SELECT current_setting('threads')")
|
|
272
|
+
thread_count = result.first.first.to_i
|
|
273
|
+
|
|
274
|
+
return unless thread_count > 1
|
|
275
|
+
|
|
276
|
+
raise DuckDB::Error,
|
|
277
|
+
'Functions with Ruby callbacks require single-threaded execution. ' \
|
|
278
|
+
"Current threads setting: #{thread_count}. " \
|
|
279
|
+
"Execute 'SET threads=1' before registering functions."
|
|
280
|
+
end
|
|
281
|
+
|
|
161
282
|
def run_appender_block(appender, &)
|
|
162
283
|
return appender unless block_given?
|
|
163
284
|
|
data/lib/duckdb/converter.rb
CHANGED
|
@@ -28,9 +28,11 @@ module DuckDB
|
|
|
28
28
|
Date.new(year, month, day)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# rubocop:disable Metrics/ParameterLists
|
|
31
32
|
def _to_time(year, month, day, hour, minute, second, microsecond)
|
|
32
33
|
Time.local(year, month, day, hour, minute, second, microsecond)
|
|
33
34
|
end
|
|
35
|
+
# rubocop:enable Metrics/ParameterLists
|
|
34
36
|
|
|
35
37
|
def _to_time_from_duckdb_time(hour, minute, second, microsecond)
|
|
36
38
|
Time.parse(
|
|
@@ -59,46 +61,17 @@ module DuckDB
|
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def _to_time_from_duckdb_time_tz(hour, min, sec, micro, timezone)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
tzhour = timezone / 3600
|
|
69
|
-
tzmin = (timezone % 3600) / 60
|
|
70
|
-
|
|
71
|
-
Time.parse(
|
|
72
|
-
format(
|
|
73
|
-
'%<hour>02d:%<min>02d:%<sec>02d.%<micro>06d%<sign>s%<tzhour>02d:%<tzmin>02d',
|
|
74
|
-
hour: hour,
|
|
75
|
-
min: min,
|
|
76
|
-
sec: sec,
|
|
77
|
-
micro: micro,
|
|
78
|
-
sign: sign,
|
|
79
|
-
tzhour: tzhour,
|
|
80
|
-
tzmin: tzmin
|
|
81
|
-
)
|
|
82
|
-
)
|
|
64
|
+
tz_offset = format_timezone_offset(timezone)
|
|
65
|
+
time_str = format('%<hour>02d:%<min>02d:%<sec>02d.%<micro>06d%<tz>s',
|
|
66
|
+
hour: hour, min: min, sec: sec, micro: micro, tz: tz_offset)
|
|
67
|
+
Time.parse(time_str)
|
|
83
68
|
end
|
|
84
69
|
|
|
85
70
|
def _to_time_from_duckdb_timestamp_tz(bits)
|
|
86
71
|
micro = bits % 1_000_000
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Time.parse(
|
|
91
|
-
format(
|
|
92
|
-
'%<year>04d-%<mon>02d-%<day>02d %<hour>02d:%<min>02d:%<sec>02d.%<micro>06d +0000',
|
|
93
|
-
year: time.year,
|
|
94
|
-
mon: time.month,
|
|
95
|
-
day: time.day,
|
|
96
|
-
hour: time.hour,
|
|
97
|
-
min: time.min,
|
|
98
|
-
sec: time.sec,
|
|
99
|
-
micro: micro
|
|
100
|
-
)
|
|
101
|
-
)
|
|
72
|
+
time = EPOCH_UTC + (bits / 1_000_000)
|
|
73
|
+
timestamp_str = format_timestamp_with_micro(time, micro)
|
|
74
|
+
Time.parse(timestamp_str)
|
|
102
75
|
end
|
|
103
76
|
|
|
104
77
|
def _to_hugeint_from_vector(lower, upper)
|
|
@@ -134,25 +107,23 @@ module DuckDB
|
|
|
134
107
|
when Date, Time
|
|
135
108
|
value
|
|
136
109
|
else
|
|
137
|
-
|
|
138
|
-
Date.parse(value)
|
|
139
|
-
rescue StandardError => e
|
|
140
|
-
raise(ArgumentError, "Cannot parse `#{value.inspect}` to Date object. #{e.message}")
|
|
141
|
-
end
|
|
110
|
+
Date.parse(value)
|
|
142
111
|
end
|
|
112
|
+
rescue StandardError => e
|
|
113
|
+
raise(ArgumentError, "Cannot parse `#{value.inspect}` to Date object. #{e.message}")
|
|
143
114
|
end
|
|
144
115
|
|
|
145
116
|
def _parse_time(value)
|
|
146
117
|
case value
|
|
147
118
|
when Time
|
|
148
119
|
value
|
|
120
|
+
when DateTime
|
|
121
|
+
value.to_time
|
|
149
122
|
else
|
|
150
|
-
|
|
151
|
-
Time.parse(value)
|
|
152
|
-
rescue StandardError => e
|
|
153
|
-
raise(ArgumentError, "Cannot parse `#{value.inspect}` to Time object. #{e.message}")
|
|
154
|
-
end
|
|
123
|
+
Time.parse(value)
|
|
155
124
|
end
|
|
125
|
+
rescue StandardError => e
|
|
126
|
+
raise(ArgumentError, "Cannot parse `#{value.inspect}` to Time object. #{e.message}")
|
|
156
127
|
end
|
|
157
128
|
|
|
158
129
|
def _parse_deciaml(value)
|
|
@@ -172,6 +143,20 @@ module DuckDB
|
|
|
172
143
|
DuckDB::QueryProgress.new(percentage, rows_processed, total_rows_to_process).freeze
|
|
173
144
|
end
|
|
174
145
|
|
|
146
|
+
def format_timezone_offset(timezone)
|
|
147
|
+
sign = timezone.negative? ? '-' : '+'
|
|
148
|
+
offset = timezone.abs
|
|
149
|
+
tzhour = offset / 3600
|
|
150
|
+
tzmin = (offset % 3600) / 60
|
|
151
|
+
format('%<sign>s%<hour>02d:%<min>02d', sign: sign, hour: tzhour, min: tzmin)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def format_timestamp_with_micro(time, micro)
|
|
155
|
+
format('%<year>04d-%<mon>02d-%<day>02d %<hour>02d:%<min>02d:%<sec>02d.%<micro>06d +0000',
|
|
156
|
+
year: time.year, mon: time.month, day: time.day,
|
|
157
|
+
hour: time.hour, min: time.min, sec: time.sec, micro: micro)
|
|
158
|
+
end
|
|
159
|
+
|
|
175
160
|
private
|
|
176
161
|
|
|
177
162
|
def integer_to_hugeint(value)
|
|
@@ -186,7 +171,7 @@ module DuckDB
|
|
|
186
171
|
end
|
|
187
172
|
|
|
188
173
|
def decimal_to_hugeint(value)
|
|
189
|
-
integer_value = (value * (10
|
|
174
|
+
integer_value = (value * (10**value.scale)).to_i
|
|
190
175
|
integer_to_hugeint(integer_value)
|
|
191
176
|
rescue FloatDomainError => e
|
|
192
177
|
raise(ArgumentError, "The argument `#{value.inspect}` must be converted to Integer. #{e.message}")
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuckDB
|
|
4
|
+
#
|
|
5
|
+
# The DuckDB::DataChunk represents a chunk of data for table function output.
|
|
6
|
+
#
|
|
7
|
+
# During table function execution, data chunks are used to return rows.
|
|
8
|
+
#
|
|
9
|
+
# Example:
|
|
10
|
+
#
|
|
11
|
+
# done = false
|
|
12
|
+
# table_function.init { |_init_info| done = false }
|
|
13
|
+
#
|
|
14
|
+
# table_function.execute do |func_info, output|
|
|
15
|
+
# if done
|
|
16
|
+
# output.size = 0 # Signal completion
|
|
17
|
+
# else
|
|
18
|
+
# # High-level API
|
|
19
|
+
# output.set_value(0, 0, 42) # column 0, row 0, value 42
|
|
20
|
+
# output.set_value(1, 0, 'Alice') # column 1, row 0, value 'Alice'
|
|
21
|
+
# output.size = 1
|
|
22
|
+
# done = true
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
class DataChunk
|
|
27
|
+
# Most methods are defined in C extension (ext/duckdb/data_chunk.c)
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# Sets a value at the specified column and row index.
|
|
31
|
+
# Type conversion is automatic based on the column's logical type.
|
|
32
|
+
#
|
|
33
|
+
# @param col_idx [Integer] Column index (0-based)
|
|
34
|
+
# @param row_idx [Integer] Row index (0-based)
|
|
35
|
+
# @param value [Object] Value to set (Integer, Float, String, Time, Date, nil)
|
|
36
|
+
# @return [Object] The value that was set
|
|
37
|
+
#
|
|
38
|
+
# @example Set integer value
|
|
39
|
+
# output.set_value(0, 0, 42)
|
|
40
|
+
#
|
|
41
|
+
# @example Set string value
|
|
42
|
+
# output.set_value(1, 0, 'hello')
|
|
43
|
+
#
|
|
44
|
+
# @example Set NULL value
|
|
45
|
+
# output.set_value(0, 1, nil)
|
|
46
|
+
#
|
|
47
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
48
|
+
def set_value(col_idx, row_idx, value)
|
|
49
|
+
vector = get_vector(col_idx)
|
|
50
|
+
logical_type = vector.logical_type
|
|
51
|
+
type_id = logical_type.type
|
|
52
|
+
|
|
53
|
+
# Handle NULL
|
|
54
|
+
if value.nil?
|
|
55
|
+
vector.set_validity(row_idx, false)
|
|
56
|
+
return value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
case type_id
|
|
60
|
+
when :boolean
|
|
61
|
+
data = vector.get_data
|
|
62
|
+
MemoryHelper.write_boolean(data, row_idx, value)
|
|
63
|
+
when :tinyint
|
|
64
|
+
data = vector.get_data
|
|
65
|
+
MemoryHelper.write_tinyint(data, row_idx, value)
|
|
66
|
+
when :smallint
|
|
67
|
+
data = vector.get_data
|
|
68
|
+
MemoryHelper.write_smallint(data, row_idx, value)
|
|
69
|
+
when :integer
|
|
70
|
+
data = vector.get_data
|
|
71
|
+
MemoryHelper.write_integer(data, row_idx, value)
|
|
72
|
+
when :bigint
|
|
73
|
+
data = vector.get_data
|
|
74
|
+
MemoryHelper.write_bigint(data, row_idx, value)
|
|
75
|
+
when :utinyint
|
|
76
|
+
data = vector.get_data
|
|
77
|
+
MemoryHelper.write_utinyint(data, row_idx, value)
|
|
78
|
+
when :usmallint
|
|
79
|
+
data = vector.get_data
|
|
80
|
+
MemoryHelper.write_usmallint(data, row_idx, value)
|
|
81
|
+
when :uinteger
|
|
82
|
+
data = vector.get_data
|
|
83
|
+
MemoryHelper.write_uinteger(data, row_idx, value)
|
|
84
|
+
when :ubigint
|
|
85
|
+
data = vector.get_data
|
|
86
|
+
MemoryHelper.write_ubigint(data, row_idx, value)
|
|
87
|
+
when :float
|
|
88
|
+
data = vector.get_data
|
|
89
|
+
MemoryHelper.write_float(data, row_idx, value)
|
|
90
|
+
when :double
|
|
91
|
+
data = vector.get_data
|
|
92
|
+
MemoryHelper.write_double(data, row_idx, value)
|
|
93
|
+
when :varchar
|
|
94
|
+
vector.assign_string_element(row_idx, value.to_s)
|
|
95
|
+
when :blob
|
|
96
|
+
vector.assign_string_element_len(row_idx, value.to_s)
|
|
97
|
+
when :timestamp
|
|
98
|
+
data = vector.get_data
|
|
99
|
+
MemoryHelper.write_timestamp(data, row_idx, value)
|
|
100
|
+
when :timestamp_tz
|
|
101
|
+
data = vector.get_data
|
|
102
|
+
MemoryHelper.write_timestamp_tz(data, row_idx, value)
|
|
103
|
+
when :date
|
|
104
|
+
data = vector.get_data
|
|
105
|
+
MemoryHelper.write_date(data, row_idx, value)
|
|
106
|
+
else
|
|
107
|
+
raise ArgumentError, "Unsupported type for DataChunk#set_value: #{type_id} for value `#{value.inspect}`"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
value
|
|
111
|
+
end
|
|
112
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuckDB
|
|
4
|
+
#
|
|
5
|
+
# The DuckDB::FunctionInfo provides context during table function execution.
|
|
6
|
+
#
|
|
7
|
+
# It is passed to the execute callback along with the output DataChunk.
|
|
8
|
+
#
|
|
9
|
+
# Example:
|
|
10
|
+
#
|
|
11
|
+
# table_function.execute do |func_info, output|
|
|
12
|
+
# # Report errors during execution
|
|
13
|
+
# func_info.set_error('Something went wrong')
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# rubocop:disable Lint/EmptyClass
|
|
17
|
+
class FunctionInfo
|
|
18
|
+
# All methods are defined in C extension (ext/duckdb/function_info.c)
|
|
19
|
+
end
|
|
20
|
+
# rubocop:enable Lint/EmptyClass
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuckDB
|
|
4
|
+
#
|
|
5
|
+
# The DuckDB::InitInfo provides context during table function initialization.
|
|
6
|
+
#
|
|
7
|
+
# It is passed to the init callback to set up execution state.
|
|
8
|
+
#
|
|
9
|
+
# Example:
|
|
10
|
+
#
|
|
11
|
+
# table_function.init do |init_info|
|
|
12
|
+
# # Initialize execution state
|
|
13
|
+
# # Can report errors if initialization fails
|
|
14
|
+
# init_info.set_error('Initialization failed')
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# rubocop:disable Lint/EmptyClass
|
|
18
|
+
class InitInfo
|
|
19
|
+
# All methods are defined in C extension (ext/duckdb/init_info.c)
|
|
20
|
+
end
|
|
21
|
+
# rubocop:enable Lint/EmptyClass
|
|
22
|
+
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
if defined?(DuckDB::InstanceCache)
|
|
4
|
-
|
|
5
3
|
module DuckDB
|
|
6
4
|
# The DuckDB::InstanceCache is necessary if a client/program (re)opens
|
|
7
5
|
# multiple databases to the same file within the same statement.
|
|
@@ -22,5 +20,3 @@ module DuckDB
|
|
|
22
20
|
end
|
|
23
21
|
end
|
|
24
22
|
end
|
|
25
|
-
|
|
26
|
-
end
|
data/lib/duckdb/interval.rb
CHANGED
|
@@ -62,7 +62,7 @@ module DuckDB
|
|
|
62
62
|
#
|
|
63
63
|
# DuckDB::Interval.mk_interval(year: 1, month: 2, day: 3, hour: 4, min: 5, sec: 6, usec: 123456)
|
|
64
64
|
# => #<DuckDB::Interval:0x00007f9b9c0b3b60 @interval_months=14, @interval_days=3, @interval_micros=14706123456>
|
|
65
|
-
def mk_interval(year: 0, month: 0, day: 0, hour: 0, min: 0, sec: 0, usec: 0)
|
|
65
|
+
def mk_interval(year: 0, month: 0, day: 0, hour: 0, min: 0, sec: 0, usec: 0) # rubocop:disable Metrics/ParameterLists
|
|
66
66
|
Interval.new(
|
|
67
67
|
interval_months: (year * 12) + month,
|
|
68
68
|
interval_days: day,
|
data/lib/duckdb/logical_type.rb
CHANGED
|
@@ -54,6 +54,47 @@ module DuckDB
|
|
|
54
54
|
const_set(method_name.upcase, send(method_name))
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
class << self
|
|
58
|
+
def resolve(symbol)
|
|
59
|
+
return symbol if symbol.is_a?(DuckDB::LogicalType)
|
|
60
|
+
|
|
61
|
+
DuckDB::LogicalType.const_get(symbol.upcase)
|
|
62
|
+
rescue NameError
|
|
63
|
+
raise ArgumentError, "Unknown logical type symbol: #{symbol}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Creates an array logical type with the given child type and size.
|
|
67
|
+
#
|
|
68
|
+
# The +type+ argument can be a symbol or a DuckDB::LogicalType instance.
|
|
69
|
+
# The +size+ argument specifies the fixed size of the array.
|
|
70
|
+
#
|
|
71
|
+
# require 'duckdb'
|
|
72
|
+
#
|
|
73
|
+
# array_type = DuckDB::LogicalType.create_array(:integer, 3)
|
|
74
|
+
# array_type.type #=> :array
|
|
75
|
+
# array_type.child_type.type #=> :integer
|
|
76
|
+
# array_type.size #=> 3
|
|
77
|
+
def create_array(type, size)
|
|
78
|
+
_create_array_type(LogicalType.resolve(type), size)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Creates a list logical type with the given child type.
|
|
82
|
+
#
|
|
83
|
+
# The +type+ argument can be a symbol or a DuckDB::LogicalType instance.
|
|
84
|
+
#
|
|
85
|
+
# require 'duckdb'
|
|
86
|
+
#
|
|
87
|
+
# list_type = DuckDB::LogicalType.create_list(:integer)
|
|
88
|
+
# list_type.type #=> :list
|
|
89
|
+
# list_type.child_type.type #=> :integer
|
|
90
|
+
#
|
|
91
|
+
# nested_list = DuckDB::LogicalType.create_list(list_type)
|
|
92
|
+
# nested_list.child_type.type #=> :list
|
|
93
|
+
def create_list(type)
|
|
94
|
+
_create_list_type(LogicalType.resolve(type))
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
57
98
|
# returns logical type's type symbol
|
|
58
99
|
# `:unknown` means that the logical type's type is unknown/unsupported by ruby-duckdb.
|
|
59
100
|
# `:invalid` means that the logical type's type is invalid in duckdb.
|
|
@@ -104,7 +145,7 @@ module DuckDB
|
|
|
104
145
|
# names = union_logical_type.each_member_name.to_a
|
|
105
146
|
# # => ["member1", "member2"]
|
|
106
147
|
def each_member_name
|
|
107
|
-
return to_enum(__method__) {member_count} unless block_given?
|
|
148
|
+
return to_enum(__method__) { member_count } unless block_given?
|
|
108
149
|
|
|
109
150
|
member_count.times do |i|
|
|
110
151
|
yield member_name_at(i)
|
|
@@ -126,7 +167,7 @@ module DuckDB
|
|
|
126
167
|
# names = union_logical_type.each_member_type.map(&:type)
|
|
127
168
|
# # => [:varchar, :integer]
|
|
128
169
|
def each_member_type
|
|
129
|
-
return to_enum(__method__) {member_count} unless block_given?
|
|
170
|
+
return to_enum(__method__) { member_count } unless block_given?
|
|
130
171
|
|
|
131
172
|
member_count.times do |i|
|
|
132
173
|
yield member_type_at(i)
|
|
@@ -148,7 +189,7 @@ module DuckDB
|
|
|
148
189
|
# names = struct_logical_type.each_child_name.to_a
|
|
149
190
|
# # => ["child1", "child2"]
|
|
150
191
|
def each_child_name
|
|
151
|
-
return to_enum(__method__) {child_count} unless block_given?
|
|
192
|
+
return to_enum(__method__) { child_count } unless block_given?
|
|
152
193
|
|
|
153
194
|
child_count.times do |i|
|
|
154
195
|
yield child_name_at(i)
|
|
@@ -170,7 +211,7 @@ module DuckDB
|
|
|
170
211
|
# types = struct_logical_type.each_child_type.map(&:type)
|
|
171
212
|
# # => [:integer, :varchar]
|
|
172
213
|
def each_child_type
|
|
173
|
-
return to_enum(__method__) {child_count} unless block_given?
|
|
214
|
+
return to_enum(__method__) { child_count } unless block_given?
|
|
174
215
|
|
|
175
216
|
child_count.times do |i|
|
|
176
217
|
yield child_type_at(i)
|
|
@@ -192,7 +233,7 @@ module DuckDB
|
|
|
192
233
|
# values = enum_logical_type.each_value.to_a
|
|
193
234
|
# # => ["happy", "sad"]
|
|
194
235
|
def each_dictionary_value
|
|
195
|
-
return to_enum(__method__) {dictionary_size} unless block_given?
|
|
236
|
+
return to_enum(__method__) { dictionary_size } unless block_given?
|
|
196
237
|
|
|
197
238
|
dictionary_size.times do |i|
|
|
198
239
|
yield dictionary_value_at(i)
|
|
@@ -143,7 +143,8 @@ module DuckDB
|
|
|
143
143
|
def bind_uint64(index, val)
|
|
144
144
|
return _bind_uint64(index, val) if val.between?(0, 18_446_744_073_709_551_615)
|
|
145
145
|
|
|
146
|
-
raise DuckDB::Error,
|
|
146
|
+
raise DuckDB::Error,
|
|
147
|
+
"can't bind uint64(bind_uint64) to `#{val}`. The `#{val}` is out of range 0..18446744073709551615."
|
|
147
148
|
end
|
|
148
149
|
|
|
149
150
|
# binds i-th parameter with SQL prepared statement.
|
|
@@ -319,6 +320,7 @@ module DuckDB
|
|
|
319
320
|
|
|
320
321
|
private
|
|
321
322
|
|
|
323
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
322
324
|
def bind_with_index(index, value)
|
|
323
325
|
case value
|
|
324
326
|
when NilClass
|
|
@@ -326,14 +328,9 @@ module DuckDB
|
|
|
326
328
|
when Float
|
|
327
329
|
bind_double(index, value)
|
|
328
330
|
when Integer
|
|
329
|
-
|
|
330
|
-
when RANGE_INT64
|
|
331
|
-
bind_int64(index, value)
|
|
332
|
-
else
|
|
333
|
-
bind_varchar(index, value.to_s)
|
|
334
|
-
end
|
|
331
|
+
bind_integer_value(index, value)
|
|
335
332
|
when String
|
|
336
|
-
|
|
333
|
+
bind_string_value(index, value)
|
|
337
334
|
when TrueClass, FalseClass
|
|
338
335
|
bind_bool(index, value)
|
|
339
336
|
when Time
|
|
@@ -346,6 +343,23 @@ module DuckDB
|
|
|
346
343
|
raise(DuckDB::Error, "not supported type `#{value}` (#{value.class})")
|
|
347
344
|
end
|
|
348
345
|
end
|
|
346
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
347
|
+
|
|
348
|
+
def bind_integer_value(index, value)
|
|
349
|
+
if RANGE_INT64.cover?(value)
|
|
350
|
+
bind_int64(index, value)
|
|
351
|
+
else
|
|
352
|
+
bind_varchar(index, value.to_s)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def bind_string_value(index, value)
|
|
357
|
+
if blob?(value)
|
|
358
|
+
bind_blob(index, value)
|
|
359
|
+
else
|
|
360
|
+
bind_varchar(index, value)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
349
363
|
|
|
350
364
|
def bind_with_name(name, value)
|
|
351
365
|
raise DuckDB::Error, 'not supported binding with name' unless respond_to?(:bind_parameter_index)
|