duckdb 1.5.2.0 → 1.5.3.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 +38 -0
- data/duckdb.gemspec +37 -0
- data/ext/duckdb/aggregate_function.c +62 -100
- data/ext/duckdb/aggregate_function.h +2 -2
- data/ext/duckdb/aggregate_function_set.c +86 -0
- data/ext/duckdb/aggregate_function_set.h +14 -0
- data/ext/duckdb/appender.c +121 -39
- data/ext/duckdb/appender.h +1 -1
- data/ext/duckdb/client_context.c +5 -5
- data/ext/duckdb/client_context.h +2 -2
- data/ext/duckdb/column.c +13 -13
- data/ext/duckdb/column.h +1 -1
- data/ext/duckdb/connection.c +63 -41
- data/ext/duckdb/connection.h +2 -2
- data/ext/duckdb/converter.h +1 -7
- data/ext/duckdb/conveter.c +6 -6
- data/ext/duckdb/data_chunk.c +22 -22
- data/ext/duckdb/data_chunk.h +2 -2
- data/ext/duckdb/database.c +10 -10
- data/ext/duckdb/database.h +1 -1
- data/ext/duckdb/duckdb.c +18 -17
- data/ext/duckdb/expression.c +8 -8
- data/ext/duckdb/expression.h +1 -1
- data/ext/duckdb/extconf.rb +32 -16
- data/ext/duckdb/extracted_statements.c +15 -15
- data/ext/duckdb/extracted_statements.h +1 -1
- data/ext/duckdb/instance_cache.c +10 -10
- data/ext/duckdb/instance_cache.h +1 -1
- data/ext/duckdb/logical_type.c +94 -133
- data/ext/duckdb/logical_type.h +2 -2
- data/ext/duckdb/memory_helper.c +28 -28
- data/ext/duckdb/pending_result.c +27 -27
- data/ext/duckdb/pending_result.h +2 -2
- data/ext/duckdb/prepared_statement.c +120 -103
- data/ext/duckdb/prepared_statement.h +2 -2
- data/ext/duckdb/result.c +24 -74
- data/ext/duckdb/result.h +2 -3
- data/ext/duckdb/ruby-duckdb.h +5 -0
- data/ext/duckdb/scalar_function.c +3 -3
- data/ext/duckdb/table_description.c +1 -1
- data/ext/duckdb/table_function.c +3 -3
- data/ext/duckdb/table_function_bind_info.c +1 -1
- data/ext/duckdb/value.c +62 -50
- data/ext/duckdb/value.h +2 -2
- data/ext/duckdb/vector.c +20 -20
- data/ext/duckdb/vector.h +2 -2
- data/lib/duckdb/aggregate_function.rb +208 -3
- data/lib/duckdb/aggregate_function_set.rb +29 -0
- data/lib/duckdb/appender.rb +148 -0
- data/lib/duckdb/connection.rb +86 -25
- data/lib/duckdb/converter.rb +5 -0
- data/lib/duckdb/logical_type.rb +1 -3
- data/lib/duckdb/prepared_statement.rb +19 -1
- data/lib/duckdb/result.rb +39 -2
- data/lib/duckdb/scalar_function.rb +9 -4
- data/lib/duckdb/scalar_function_set.rb +0 -1
- data/lib/duckdb/table_description.rb +7 -0
- data/lib/duckdb/table_function.rb +0 -1
- data/lib/duckdb/table_name_parser.rb +58 -0
- data/lib/duckdb/value.rb +19 -0
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +2 -0
- metadata +7 -3
- data/lib/duckdb/duckdb_native.so +0 -0
|
@@ -1,13 +1,159 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module DuckDB
|
|
4
|
-
# DuckDB::AggregateFunction
|
|
4
|
+
# DuckDB::AggregateFunction lets you register a custom aggregate function
|
|
5
|
+
# written in Ruby and call it from SQL.
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
7
|
+
# An aggregate function folds many rows into a single value. You define its
|
|
8
|
+
# behaviour with four callbacks:
|
|
9
|
+
#
|
|
10
|
+
# * +set_init+ — called once per group; returns the initial state.
|
|
11
|
+
# * +set_update+ — called once per row; receives the current state and the
|
|
12
|
+
# input value(s), returns the new state.
|
|
13
|
+
# * +set_combine+ — merges two partial states (required for parallel
|
|
14
|
+
# execution); receives source and target states, returns the
|
|
15
|
+
# merged state.
|
|
16
|
+
# * +set_finalize+ — converts the final state into the SQL result value.
|
|
17
|
+
#
|
|
18
|
+
# Only +set_init+ is required. The other three have sensible defaults:
|
|
19
|
+
# * +set_update+ defaults to +{ |state, *| state }+ (ignore inputs)
|
|
20
|
+
# * +set_combine+ defaults to +{ |s1, _s2| s1 }+ (keep source state)
|
|
21
|
+
# * +set_finalize+ defaults to +{ |x| x }+ (return state as-is)
|
|
22
|
+
#
|
|
23
|
+
# @note The default +set_combine+ keeps the source state and discards the
|
|
24
|
+
# target, which is only correct for single-threaded (single-partition)
|
|
25
|
+
# execution. If DuckDB runs the aggregate in parallel it will produce
|
|
26
|
+
# wrong results. Always supply an explicit +set_combine+ when the
|
|
27
|
+
# aggregate must be parallel-safe.
|
|
28
|
+
#
|
|
29
|
+
# == Basic example: custom SUM
|
|
30
|
+
#
|
|
31
|
+
# af = DuckDB::AggregateFunction.new
|
|
32
|
+
# af.name = 'my_sum'
|
|
33
|
+
# af.return_type = DuckDB::LogicalType::BIGINT
|
|
34
|
+
# af.add_parameter(DuckDB::LogicalType::BIGINT)
|
|
35
|
+
#
|
|
36
|
+
# af.set_init { 0 }
|
|
37
|
+
# af.set_update { |state, value| state + value }
|
|
38
|
+
# af.set_combine { |s1, s2| s1 + s2 }
|
|
39
|
+
#
|
|
40
|
+
# con.register_aggregate_function(af)
|
|
41
|
+
# con.query('SELECT my_sum(i) FROM range(100) t(i)').first.first # => 4950
|
|
42
|
+
#
|
|
43
|
+
# == Example: weighted average with Hash state
|
|
44
|
+
#
|
|
45
|
+
# af = DuckDB::AggregateFunction.new
|
|
46
|
+
# af.name = 'weighted_avg'
|
|
47
|
+
# af.return_type = DuckDB::LogicalType::DOUBLE
|
|
48
|
+
# af.add_parameter(DuckDB::LogicalType::DOUBLE) # value
|
|
49
|
+
# af.add_parameter(DuckDB::LogicalType::DOUBLE) # weight
|
|
50
|
+
#
|
|
51
|
+
# af.set_init { { sum: 0.0, weight: 0.0 } }
|
|
52
|
+
# af.set_update { |state, value, weight| { sum: state[:sum] + value * weight, weight: state[:weight] + weight } }
|
|
53
|
+
# af.set_combine { |s1, s2| { sum: s1[:sum] + s2[:sum], weight: s1[:weight] + s2[:weight] } }
|
|
54
|
+
# af.set_finalize { |state| state[:weight].zero? ? nil : state[:sum] / state[:weight] }
|
|
55
|
+
#
|
|
56
|
+
# con.register_aggregate_function(af)
|
|
8
57
|
class AggregateFunction
|
|
9
58
|
include FunctionTypeValidation
|
|
10
59
|
|
|
60
|
+
def name=(value)
|
|
61
|
+
set_name(value.to_s)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private :set_name
|
|
65
|
+
|
|
66
|
+
class << self
|
|
67
|
+
# Creates a new AggregateFunction in a single call.
|
|
68
|
+
#
|
|
69
|
+
# This is a convenience factory that builds and configures an
|
|
70
|
+
# AggregateFunction without requiring you to set each attribute
|
|
71
|
+
# separately.
|
|
72
|
+
#
|
|
73
|
+
# @param name [String, Symbol] the SQL function name
|
|
74
|
+
# @param return_type [DuckDB::LogicalType | Symbol] the SQL return type
|
|
75
|
+
# @param params [Array<DuckDB::LogicalType | Symbol>] input parameter types
|
|
76
|
+
# (empty array for a zero-argument aggregate)
|
|
77
|
+
# @param init [#call] callable that returns the initial per-group state
|
|
78
|
+
# @param update [#call] callable that folds one row into the state;
|
|
79
|
+
# receives +state, *inputs+ and must return the updated state.
|
|
80
|
+
# Default: +->( state, *) { state }+ (ignore inputs)
|
|
81
|
+
# @param combine [#call] callable that merges two partial states;
|
|
82
|
+
# receives +source_state, target_state+ and must return the merged
|
|
83
|
+
# state. Default: +->(state, _other) { state }+ (keep source only —
|
|
84
|
+
# only correct for single-threaded execution)
|
|
85
|
+
# @param finalize [#call] callable that converts the final state into the
|
|
86
|
+
# SQL result value; receives +state+ and must return a value compatible
|
|
87
|
+
# with +return_type+.
|
|
88
|
+
# Default: +->(state) { state }+ (return state as-is)
|
|
89
|
+
# @param null_handling [Boolean] when +true+, enables special NULL
|
|
90
|
+
# handling so that rows with NULL inputs are passed to +update+ as
|
|
91
|
+
# +nil+ instead of being skipped (default: +false+)
|
|
92
|
+
# @return [DuckDB::AggregateFunction] the configured aggregate function,
|
|
93
|
+
# ready to be passed to +Connection#register_aggregate_function+
|
|
94
|
+
# @raise [ArgumentError] if any of +init+, +update+, +combine+, or
|
|
95
|
+
# +finalize+ does not respond to +call+
|
|
96
|
+
#
|
|
97
|
+
# == Example: custom SUM
|
|
98
|
+
#
|
|
99
|
+
# af = DuckDB::AggregateFunction.create(
|
|
100
|
+
# name: 'my_sum',
|
|
101
|
+
# return_type: :bigint,
|
|
102
|
+
# params: [:bigint],
|
|
103
|
+
# init: -> { 0 },
|
|
104
|
+
# update: ->(state, value) { state + value },
|
|
105
|
+
# combine: ->(state, other) { state + other }
|
|
106
|
+
# )
|
|
107
|
+
# con.register_aggregate_function(af)
|
|
108
|
+
# con.query('SELECT my_sum(i) FROM range(100) t(i)').first.first # => 4950
|
|
109
|
+
#
|
|
110
|
+
# == Example: count including NULL values
|
|
111
|
+
#
|
|
112
|
+
# af = DuckDB::AggregateFunction.create(
|
|
113
|
+
# name: 'count_with_nulls',
|
|
114
|
+
# return_type: :bigint,
|
|
115
|
+
# params: [:bigint],
|
|
116
|
+
# init: -> { 0 },
|
|
117
|
+
# update: ->(state, _value) { state + 1 },
|
|
118
|
+
# combine: ->(state, other) { state + other },
|
|
119
|
+
# null_handling: true
|
|
120
|
+
# )
|
|
121
|
+
def create( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists, Metrics/AbcSize
|
|
122
|
+
name:,
|
|
123
|
+
return_type:,
|
|
124
|
+
params: [], # rubocop:disable Style/KeywordParametersOrder
|
|
125
|
+
init:,
|
|
126
|
+
update: ->(state, *_inputs) { state },
|
|
127
|
+
combine: ->(state, _other_state) { state },
|
|
128
|
+
finalize: ->(state) { state },
|
|
129
|
+
null_handling: false
|
|
130
|
+
)
|
|
131
|
+
callable!(:init, init)
|
|
132
|
+
callable!(:update, update)
|
|
133
|
+
callable!(:combine, combine)
|
|
134
|
+
callable!(:finalize, finalize)
|
|
135
|
+
|
|
136
|
+
af = AggregateFunction.new
|
|
137
|
+
af.name = name
|
|
138
|
+
af.return_type = return_type
|
|
139
|
+
params.each do |param|
|
|
140
|
+
af.add_parameter(param)
|
|
141
|
+
end
|
|
142
|
+
af.set_init { init.call }
|
|
143
|
+
af.set_update { |state, *inputs| update.call(state, *inputs) }
|
|
144
|
+
af.set_combine { |state, other_state| combine.call(state, other_state) }
|
|
145
|
+
af.set_finalize { |state| finalize.call(state) }
|
|
146
|
+
af.set_special_handling if null_handling
|
|
147
|
+
af
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def callable!(name, arg)
|
|
153
|
+
raise ArgumentError, "#{name} must respond to `call`" unless arg.respond_to?(:call)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
11
157
|
# Sets the return type for the aggregate function.
|
|
12
158
|
#
|
|
13
159
|
# @param logical_type [DuckDB::LogicalType | :logical_type_symbol] the return type
|
|
@@ -30,6 +176,65 @@ module DuckDB
|
|
|
30
176
|
_add_parameter(logical_type)
|
|
31
177
|
end
|
|
32
178
|
|
|
179
|
+
# Sets the block that initialises the per-group state.
|
|
180
|
+
# The block takes no arguments and returns the initial state value.
|
|
181
|
+
# This is the only required callback; defaults for +set_update+,
|
|
182
|
+
# +set_combine+, and +set_finalize+ are injected automatically on the
|
|
183
|
+
# first call if those methods have not been called explicitly.
|
|
184
|
+
#
|
|
185
|
+
# @note The injected default for +set_combine+ is +{ |s1, _s2| s1 }+, which
|
|
186
|
+
# is only correct for single-threaded execution. Always call +set_combine+
|
|
187
|
+
# explicitly when the aggregate must be parallel-safe.
|
|
188
|
+
#
|
|
189
|
+
# @return [DuckDB::AggregateFunction] self
|
|
190
|
+
def set_init(&)
|
|
191
|
+
unless @init_set
|
|
192
|
+
_set_update { |state, *| state } unless @update_set
|
|
193
|
+
_set_combine { |s1, _s2| s1 } unless @combine_set
|
|
194
|
+
_set_finalize { |x| x } unless @finalize_set
|
|
195
|
+
end
|
|
196
|
+
_set_init(&)
|
|
197
|
+
@init_set = true
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Sets the block that accumulates one row into the state.
|
|
201
|
+
# The block receives the current state followed by the input column
|
|
202
|
+
# value(s) for that row, and must return the updated state.
|
|
203
|
+
# Default: +{ |state, *| state }+ (ignore inputs, keep state unchanged).
|
|
204
|
+
# May be called after +set_init+ to override the injected default.
|
|
205
|
+
#
|
|
206
|
+
# @return [DuckDB::AggregateFunction] self
|
|
207
|
+
def set_update(&)
|
|
208
|
+
@update_set = true
|
|
209
|
+
_set_update(&)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Sets the block that merges two partial states during parallel execution.
|
|
213
|
+
# The block receives the source and target states and must return the
|
|
214
|
+
# merged state.
|
|
215
|
+
# May be called after +set_init+ to override the injected default.
|
|
216
|
+
#
|
|
217
|
+
# @note The default +{ |s1, _s2| s1 }+ is only correct for single-threaded
|
|
218
|
+
# execution. Supply an explicit combine block for parallel-safe aggregates.
|
|
219
|
+
#
|
|
220
|
+
# @return [DuckDB::AggregateFunction] self
|
|
221
|
+
def set_combine(&)
|
|
222
|
+
@combine_set = true
|
|
223
|
+
_set_combine(&)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Sets the block that converts the final state into the SQL result value.
|
|
227
|
+
# The block receives the accumulated state and must return a value
|
|
228
|
+
# compatible with the declared +return_type+.
|
|
229
|
+
# Default: +{ |x| x }+ (return the state as-is).
|
|
230
|
+
# May be called after +set_init+ to override the injected default.
|
|
231
|
+
#
|
|
232
|
+
# @return [DuckDB::AggregateFunction] self
|
|
233
|
+
def set_finalize(&)
|
|
234
|
+
@finalize_set = true
|
|
235
|
+
_set_finalize(&)
|
|
236
|
+
end
|
|
237
|
+
|
|
33
238
|
# Sets special NULL handling for the aggregate function.
|
|
34
239
|
# By default DuckDB skips rows with NULL input values. Calling this
|
|
35
240
|
# method disables that behaviour so the update callback is invoked even
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuckDB
|
|
4
|
+
# DuckDB::AggregateFunctionSet encapsulates DuckDB's aggregate function set,
|
|
5
|
+
# which allows registering multiple overloads of an aggregate function under one name.
|
|
6
|
+
#
|
|
7
|
+
# @note DuckDB::AggregateFunctionSet is experimental.
|
|
8
|
+
class AggregateFunctionSet
|
|
9
|
+
# @param name [String, Symbol] the function set name shared by all overloads
|
|
10
|
+
# @raise [TypeError] if name is not a String or Symbol
|
|
11
|
+
def initialize(name)
|
|
12
|
+
raise TypeError, "#{name.class} is not a String or Symbol" unless name.is_a?(String) || name.is_a?(Symbol)
|
|
13
|
+
|
|
14
|
+
_initialize(name.to_s)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @param aggregate_function [DuckDB::AggregateFunction] the overload to add
|
|
18
|
+
# @return [self]
|
|
19
|
+
# @raise [TypeError] if aggregate_function is not a DuckDB::AggregateFunction
|
|
20
|
+
# @raise [DuckDB::Error] if the overload already exists in the set
|
|
21
|
+
def add(aggregate_function)
|
|
22
|
+
unless aggregate_function.is_a?(DuckDB::AggregateFunction)
|
|
23
|
+
raise TypeError, "#{aggregate_function.class} is not a DuckDB::AggregateFunction"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
_add(aggregate_function)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/duckdb/appender.rb
CHANGED
|
@@ -7,6 +7,11 @@ require_relative 'converter'
|
|
|
7
7
|
module DuckDB
|
|
8
8
|
# The DuckDB::Appender encapsulates DuckDB Appender.
|
|
9
9
|
#
|
|
10
|
+
# The +table+ argument (2nd positional argument) supports dot-notation and quoting:
|
|
11
|
+
#
|
|
12
|
+
# - <tt>'schema.table'</tt> — interpreted as schema-qualified (deprecated; use +schema:+ instead)
|
|
13
|
+
# - <tt>'"schema.table"'</tt> or <tt>"'schema.table'"</tt> — treated as a literal table name containing a dot
|
|
14
|
+
#
|
|
10
15
|
# require 'duckdb'
|
|
11
16
|
# db = DuckDB::Database.open
|
|
12
17
|
# con = db.connect
|
|
@@ -15,11 +20,21 @@ module DuckDB
|
|
|
15
20
|
# appender.append_row(1, 'Alice')
|
|
16
21
|
class Appender
|
|
17
22
|
include DuckDB::Converter
|
|
23
|
+
include DuckDB::TableNameParser
|
|
18
24
|
|
|
19
25
|
class << self
|
|
20
26
|
alias from_query create_query
|
|
21
27
|
end
|
|
22
28
|
|
|
29
|
+
def initialize(con, table_or_schema, table = nil, schema: nil, catalog: nil)
|
|
30
|
+
if table
|
|
31
|
+
warn_deprecated_3arg
|
|
32
|
+
_initialize(con, table_or_schema, table)
|
|
33
|
+
else
|
|
34
|
+
initialize_with_parsed_table(con, table_or_schema, schema: schema, catalog: catalog)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
23
38
|
# :call-seq:
|
|
24
39
|
# appender.begin_row -> self
|
|
25
40
|
# A nop method, provided for backwards compatibility reasons.
|
|
@@ -93,6 +108,80 @@ module DuckDB
|
|
|
93
108
|
raise_appender_error('failed to close')
|
|
94
109
|
end
|
|
95
110
|
|
|
111
|
+
# :call-seq:
|
|
112
|
+
# appender.add_column(column_name) -> self
|
|
113
|
+
#
|
|
114
|
+
# Specifies a column to append to, allowing selective column insertion.
|
|
115
|
+
# Columns not added will use their default values or be computed from
|
|
116
|
+
# generated column expressions.
|
|
117
|
+
# Raises DuckDB::Error if the column does not exist in the table.
|
|
118
|
+
#
|
|
119
|
+
# require 'duckdb'
|
|
120
|
+
# db = DuckDB::Database.open
|
|
121
|
+
# con = db.connect
|
|
122
|
+
# con.query('CREATE TABLE t (id UUID PRIMARY KEY DEFAULT uuidv4(), name VARCHAR)')
|
|
123
|
+
# appender = con.appender('t')
|
|
124
|
+
# appender.add_column('name')
|
|
125
|
+
# appender
|
|
126
|
+
# .append_varchar('Alice')
|
|
127
|
+
# .end_row
|
|
128
|
+
# .flush
|
|
129
|
+
def add_column(column)
|
|
130
|
+
return self if _add_column(column)
|
|
131
|
+
|
|
132
|
+
raise_appender_error('failed to add_column')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# :call-seq:
|
|
136
|
+
# appender.clear_columns -> self
|
|
137
|
+
#
|
|
138
|
+
# Clears the list of columns previously set by #add_column, so that all
|
|
139
|
+
# columns of the table become active again. Any previously appended rows
|
|
140
|
+
# are flushed before the column list is reset; if the flush fails (e.g.
|
|
141
|
+
# a constraint violation), this method raises DuckDB::Error.
|
|
142
|
+
#
|
|
143
|
+
# require 'duckdb'
|
|
144
|
+
# db = DuckDB::Database.open
|
|
145
|
+
# con = db.connect
|
|
146
|
+
# con.query('CREATE TABLE t (id UUID PRIMARY KEY DEFAULT uuidv4(), name VARCHAR)')
|
|
147
|
+
# appender = con.appender('t')
|
|
148
|
+
# appender.add_column('name')
|
|
149
|
+
# appender
|
|
150
|
+
# .append_varchar('Alice')
|
|
151
|
+
# .end_row
|
|
152
|
+
# .flush
|
|
153
|
+
# appender.clear_columns
|
|
154
|
+
# # all table columns are active again
|
|
155
|
+
def clear_columns
|
|
156
|
+
return self if _clear_columns
|
|
157
|
+
|
|
158
|
+
raise_appender_error('failed to clear_columns')
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
if DuckDB::Appender.private_method_defined?(:_clear)
|
|
162
|
+
# :call-seq:
|
|
163
|
+
# appender.clear -> self
|
|
164
|
+
#
|
|
165
|
+
# Clears all unflushed data from the appender, discarding any appended rows
|
|
166
|
+
# that have not yet been flushed to the table.
|
|
167
|
+
#
|
|
168
|
+
# require 'duckdb'
|
|
169
|
+
# db = DuckDB::Database.open
|
|
170
|
+
# con = db.connect
|
|
171
|
+
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
|
|
172
|
+
# appender = con.appender('users')
|
|
173
|
+
# appender
|
|
174
|
+
# .append_int32(1)
|
|
175
|
+
# .append_varchar('Alice')
|
|
176
|
+
# .end_row
|
|
177
|
+
# .clear # discards the row above without flushing to the table
|
|
178
|
+
def clear
|
|
179
|
+
return self if _clear
|
|
180
|
+
|
|
181
|
+
raise_appender_error('failed to clear')
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
96
185
|
# call-seq:
|
|
97
186
|
# appender.append_bool(val) -> self
|
|
98
187
|
#
|
|
@@ -587,6 +676,20 @@ module DuckDB
|
|
|
587
676
|
|
|
588
677
|
# call-seq:
|
|
589
678
|
# appender.append_data_chunk(chunk) -> self
|
|
679
|
+
#
|
|
680
|
+
# Appends a pre-filled DuckDB::DataChunk to the appender.
|
|
681
|
+
#
|
|
682
|
+
# require 'duckdb'
|
|
683
|
+
# db = DuckDB::Database.open
|
|
684
|
+
# con = db.connect
|
|
685
|
+
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
|
|
686
|
+
# appender = con.appender('users')
|
|
687
|
+
# chunk = DuckDB::DataChunk.new([DuckDB::LogicalType::INTEGER, DuckDB::LogicalType::VARCHAR])
|
|
688
|
+
# chunk.set_value(0, 0, 1)
|
|
689
|
+
# chunk.set_value(1, 0, 'Alice')
|
|
690
|
+
# chunk.size = 1
|
|
691
|
+
# appender.append_data_chunk(chunk)
|
|
692
|
+
# appender.flush
|
|
590
693
|
def append_data_chunk(chunk)
|
|
591
694
|
raise ArgumentError, "expected DuckDB::DataChunk, got #{chunk.class}" unless chunk.is_a?(DuckDB::DataChunk)
|
|
592
695
|
|
|
@@ -595,6 +698,34 @@ module DuckDB
|
|
|
595
698
|
raise_appender_error('failed to append_data_chunk')
|
|
596
699
|
end
|
|
597
700
|
|
|
701
|
+
# call-seq:
|
|
702
|
+
# appender.append_default_to_chunk(chunk, col, row) -> self
|
|
703
|
+
#
|
|
704
|
+
# Appends the DEFAULT value for the column at +col+ and +row+ in +chunk+.
|
|
705
|
+
# If no DEFAULT is defined for the column, NULL is used.
|
|
706
|
+
# Call this before appending the chunk via #append_data_chunk.
|
|
707
|
+
#
|
|
708
|
+
# require 'duckdb'
|
|
709
|
+
# db = DuckDB::Database.open
|
|
710
|
+
# con = db.connect
|
|
711
|
+
# con.query('CREATE TABLE users (name VARCHAR, enabled BOOLEAN DEFAULT TRUE)')
|
|
712
|
+
# appender = con.appender('users')
|
|
713
|
+
# chunk = DuckDB::DataChunk.new([DuckDB::LogicalType::VARCHAR, DuckDB::LogicalType::BOOLEAN])
|
|
714
|
+
# appender.append_default_to_chunk(chunk, 1, 0) # enabled DEFAULT for row 0
|
|
715
|
+
# appender.append_default_to_chunk(chunk, 1, 1) # enabled DEFAULT for row 1
|
|
716
|
+
# chunk.set_value(0, 0, 'Alice')
|
|
717
|
+
# chunk.set_value(0, 1, 'Bob')
|
|
718
|
+
# chunk.size = 2
|
|
719
|
+
# appender.append_data_chunk(chunk)
|
|
720
|
+
# appender.flush
|
|
721
|
+
def append_default_to_chunk(chunk, col, row)
|
|
722
|
+
raise ArgumentError, "expected DuckDB::DataChunk, got #{chunk.class}" unless chunk.is_a?(DuckDB::DataChunk)
|
|
723
|
+
|
|
724
|
+
return self if _append_default_to_chunk(chunk, col, row)
|
|
725
|
+
|
|
726
|
+
raise_appender_error('failed to append_default_to_chunk')
|
|
727
|
+
end
|
|
728
|
+
|
|
598
729
|
# appends value.
|
|
599
730
|
#
|
|
600
731
|
# require 'duckdb'
|
|
@@ -648,6 +779,23 @@ module DuckDB
|
|
|
648
779
|
|
|
649
780
|
private
|
|
650
781
|
|
|
782
|
+
def warn_deprecated_3arg # :nodoc:
|
|
783
|
+
warn(
|
|
784
|
+
'DuckDB::Appender.new(con, schema, table) is deprecated. ' \
|
|
785
|
+
'Use DuckDB::Appender.new(con, table, schema: schema) instead.',
|
|
786
|
+
category: :deprecated
|
|
787
|
+
)
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
def initialize_with_parsed_table(con, table_name, schema:, catalog:) # :nodoc:
|
|
791
|
+
table_name, schema, catalog = parse_table_name(table_name, schema, catalog)
|
|
792
|
+
if catalog
|
|
793
|
+
_initialize_ext(con, catalog, schema, table_name)
|
|
794
|
+
else
|
|
795
|
+
_initialize(con, schema, table_name)
|
|
796
|
+
end
|
|
797
|
+
end
|
|
798
|
+
|
|
651
799
|
def raise_appender_error(default_message) # :nodoc:
|
|
652
800
|
message = error_message
|
|
653
801
|
raise DuckDB::Error, message || default_message
|
data/lib/duckdb/connection.rb
CHANGED
|
@@ -8,6 +8,8 @@ module DuckDB
|
|
|
8
8
|
# con = db.connect
|
|
9
9
|
# con.query(sql)
|
|
10
10
|
class Connection
|
|
11
|
+
include DuckDB::TableNameParser
|
|
12
|
+
|
|
11
13
|
# executes sql with args.
|
|
12
14
|
# The first argument sql must be SQL string.
|
|
13
15
|
# The rest arguments are parameters of SQL string.
|
|
@@ -34,7 +36,7 @@ module DuckDB
|
|
|
34
36
|
|
|
35
37
|
def query_multi_sql(sql)
|
|
36
38
|
stmts = ExtractedStatements.new(self, sql)
|
|
37
|
-
return
|
|
39
|
+
return _query_sql(sql) if stmts.size == 1
|
|
38
40
|
|
|
39
41
|
result = nil
|
|
40
42
|
stmts.each do |stmt|
|
|
@@ -127,11 +129,43 @@ module DuckDB
|
|
|
127
129
|
PreparedStatement.prepare(self, str, &)
|
|
128
130
|
end
|
|
129
131
|
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
# :call-seq:
|
|
133
|
+
# connection.appender(table, schema: nil, catalog: nil) -> DuckDB::Appender
|
|
134
|
+
# connection.appender(table, schema: nil, catalog: nil) { |appender| ... } -> self
|
|
135
|
+
#
|
|
136
|
+
# Returns a DuckDB::Appender for bulk-inserting rows into +table+.
|
|
137
|
+
# If a block is given, the appender is flushed and closed automatically after the block.
|
|
138
|
+
#
|
|
139
|
+
# +schema:+ and +catalog:+ optionally qualify the table.
|
|
140
|
+
#
|
|
141
|
+
# Raises DuckDB::Error if the table (or schema/catalog) does not exist.
|
|
142
|
+
#
|
|
143
|
+
# Table name parsing (quoting, dot-notation) is handled by DuckDB::Appender.new.
|
|
144
|
+
# See DuckDB::Appender.new for details on quoting and dot-notation.
|
|
145
|
+
#
|
|
146
|
+
# require 'duckdb'
|
|
147
|
+
# db = DuckDB::Database.open
|
|
148
|
+
# con = db.connect
|
|
149
|
+
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
|
|
150
|
+
#
|
|
151
|
+
# # block form (recommended) — flushes and closes automatically
|
|
152
|
+
# con.appender('users') do |a|
|
|
153
|
+
# a.append_row(1, 'Alice')
|
|
154
|
+
# a.append_row(2, 'Bob')
|
|
155
|
+
# end
|
|
156
|
+
#
|
|
157
|
+
# # with schema
|
|
158
|
+
# con.appender('users', schema: 'main') do |a|
|
|
159
|
+
# a.append_row(3, 'Carol')
|
|
160
|
+
# end
|
|
161
|
+
#
|
|
162
|
+
# # manual form
|
|
163
|
+
# appender = con.appender('users')
|
|
164
|
+
# appender.append_row(4, 'Dave')
|
|
165
|
+
# appender.close
|
|
166
|
+
def appender(table, schema: nil, catalog: nil, &)
|
|
167
|
+
table, schema, catalog = parse_connection_appender_table(table, schema, catalog)
|
|
168
|
+
run_appender_block(Appender.new(self, table, schema: schema, catalog: catalog), &)
|
|
135
169
|
end
|
|
136
170
|
|
|
137
171
|
if Appender.respond_to?(:create_query)
|
|
@@ -234,7 +268,7 @@ module DuckDB
|
|
|
234
268
|
# allowing DuckDB to dispatch to the correct implementation based on argument types.
|
|
235
269
|
#
|
|
236
270
|
# @param scalar_function_set [DuckDB::ScalarFunctionSet] the function set to register
|
|
237
|
-
# @return [
|
|
271
|
+
# @return [self]
|
|
238
272
|
# @raise [TypeError] if argument is not a DuckDB::ScalarFunctionSet
|
|
239
273
|
#
|
|
240
274
|
# @example Register multiple overloads under one name
|
|
@@ -251,6 +285,42 @@ module DuckDB
|
|
|
251
285
|
_register_scalar_function_set(scalar_function_set)
|
|
252
286
|
end
|
|
253
287
|
|
|
288
|
+
# Registers an aggregate function set with the connection.
|
|
289
|
+
# An aggregate function set groups multiple overloads of an aggregate function under one name,
|
|
290
|
+
# allowing DuckDB to dispatch to the correct implementation based on argument types.
|
|
291
|
+
#
|
|
292
|
+
# @param aggregate_function_set [DuckDB::AggregateFunctionSet] the function set to register
|
|
293
|
+
# @return [self]
|
|
294
|
+
# @raise [TypeError] if argument is not a DuckDB::AggregateFunctionSet
|
|
295
|
+
#
|
|
296
|
+
# @example Register multiple overloads under one name
|
|
297
|
+
# af_bigint = DuckDB::AggregateFunction.new
|
|
298
|
+
# af_bigint.name = 'my_sum'
|
|
299
|
+
# af_bigint.return_type = DuckDB::LogicalType::BIGINT
|
|
300
|
+
# af_bigint.add_parameter(DuckDB::LogicalType::BIGINT)
|
|
301
|
+
# af_bigint.set_init { 0 }
|
|
302
|
+
# af_bigint.set_update { |state, val| state + val }
|
|
303
|
+
# af_bigint.set_combine { |s1, s2| s1 + s2 }
|
|
304
|
+
#
|
|
305
|
+
# af_double = DuckDB::AggregateFunction.new
|
|
306
|
+
# af_double.name = 'my_sum'
|
|
307
|
+
# af_double.return_type = DuckDB::LogicalType::DOUBLE
|
|
308
|
+
# af_double.add_parameter(DuckDB::LogicalType::DOUBLE)
|
|
309
|
+
# af_double.set_init { 0.0 }
|
|
310
|
+
# af_double.set_update { |state, val| state + val }
|
|
311
|
+
# af_double.set_combine { |s1, s2| s1 + s2 }
|
|
312
|
+
#
|
|
313
|
+
# set = DuckDB::AggregateFunctionSet.new(:my_sum)
|
|
314
|
+
# set.add(af_bigint).add(af_double)
|
|
315
|
+
# con.register_aggregate_function_set(set)
|
|
316
|
+
def register_aggregate_function_set(aggregate_function_set)
|
|
317
|
+
unless aggregate_function_set.is_a?(AggregateFunctionSet)
|
|
318
|
+
raise TypeError, "#{aggregate_function_set.class} is not a DuckDB::AggregateFunctionSet"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
_register_aggregate_function_set(aggregate_function_set)
|
|
322
|
+
end
|
|
323
|
+
|
|
254
324
|
# Registers an aggregate function with the connection.
|
|
255
325
|
#
|
|
256
326
|
# @param aggregate_function [DuckDB::AggregateFunction] the aggregate function to register
|
|
@@ -276,7 +346,6 @@ module DuckDB
|
|
|
276
346
|
def register_table_function(table_function)
|
|
277
347
|
raise ArgumentError, 'table_function must be a TableFunction' unless table_function.is_a?(TableFunction)
|
|
278
348
|
|
|
279
|
-
check_threads
|
|
280
349
|
_register_table_function(table_function)
|
|
281
350
|
end
|
|
282
351
|
|
|
@@ -291,12 +360,10 @@ module DuckDB
|
|
|
291
360
|
# @param columns [Hash{String => DuckDB::LogicalType}, nil] optional column schema override;
|
|
292
361
|
# if omitted, the adapter determines the columns (e.g. from headers or inference)
|
|
293
362
|
# @raise [ArgumentError] if no adapter is registered for the object's class
|
|
294
|
-
# @raise [DuckDB::Error] if threads setting is not 1
|
|
295
363
|
# @return [void]
|
|
296
364
|
#
|
|
297
365
|
# @example Expose a CSV as a table
|
|
298
366
|
# require 'csv'
|
|
299
|
-
# con.execute('SET threads=1')
|
|
300
367
|
# DuckDB::TableFunction.add_table_adapter(CSV, CSVTableAdapter.new)
|
|
301
368
|
# csv = CSV.new(File.read('data.csv'), headers: true)
|
|
302
369
|
# con.expose_as_table(csv, 'csv_table')
|
|
@@ -318,18 +385,6 @@ module DuckDB
|
|
|
318
385
|
|
|
319
386
|
private
|
|
320
387
|
|
|
321
|
-
def check_threads
|
|
322
|
-
result = execute("SELECT current_setting('threads')")
|
|
323
|
-
thread_count = result.first.first.to_i
|
|
324
|
-
|
|
325
|
-
return unless thread_count > 1
|
|
326
|
-
|
|
327
|
-
raise DuckDB::Error,
|
|
328
|
-
'Functions with Ruby callbacks require single-threaded execution. ' \
|
|
329
|
-
"Current threads setting: #{thread_count}. " \
|
|
330
|
-
"Execute 'SET threads=1' before registering functions."
|
|
331
|
-
end
|
|
332
|
-
|
|
333
388
|
def run_appender_block(appender, &)
|
|
334
389
|
return appender unless block_given?
|
|
335
390
|
|
|
@@ -338,9 +393,15 @@ module DuckDB
|
|
|
338
393
|
appender.close
|
|
339
394
|
end
|
|
340
395
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
396
|
+
# Silently pre-parses dot-notation so Appender.new receives clean values
|
|
397
|
+
# and does not emit a misleading "DuckDB::Appender.new" warning.
|
|
398
|
+
# con.appender('a.b') has always split on dot — no warning needed.
|
|
399
|
+
# Quoted table names pass through unchanged for Appender.new to handle.
|
|
400
|
+
def parse_connection_appender_table(table, schema, catalog)
|
|
401
|
+
return [table, schema, catalog] if quoted_table_name?(table)
|
|
402
|
+
return [table, schema, catalog] unless table.include?('.')
|
|
403
|
+
|
|
404
|
+
dot_notation_split(table, schema, catalog)
|
|
344
405
|
end
|
|
345
406
|
|
|
346
407
|
alias execute query
|
data/lib/duckdb/converter.rb
CHANGED
|
@@ -18,6 +18,7 @@ module DuckDB
|
|
|
18
18
|
RANGE_UINT64 = 0..18_446_744_073_709_551_615
|
|
19
19
|
RANGE_HUGEINT = (-(1 << 127)..((1 << 127) - 1))
|
|
20
20
|
RANGE_UHUGEINT = (0..((1 << 128) - 1))
|
|
21
|
+
RANGE_DECIMAL_WIDTH = 1..38
|
|
21
22
|
|
|
22
23
|
HALF_HUGEINT_BIT = 64
|
|
23
24
|
HALF_HUGEINT = 1 << HALF_HUGEINT_BIT
|
|
@@ -122,6 +123,10 @@ module DuckDB
|
|
|
122
123
|
value >> HALF_HUGEINT_BIT
|
|
123
124
|
end
|
|
124
125
|
|
|
126
|
+
def _decimal_width(value)
|
|
127
|
+
value.to_s('F').gsub(/[^0-9]/, '').length
|
|
128
|
+
end
|
|
129
|
+
|
|
125
130
|
def _to_decimal_from_hugeint(width, scale, upper, lower = nil)
|
|
126
131
|
v = lower.nil? ? upper : _to_hugeint_from_vector(lower, upper)
|
|
127
132
|
_to_decimal_from_value(width, scale, v)
|
data/lib/duckdb/logical_type.rb
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module DuckDB
|
|
4
4
|
class LogicalType # rubocop:disable Metrics/ClassLength
|
|
5
|
-
RANGE_DECIMAL_WIDTH = 1..38
|
|
6
|
-
|
|
7
5
|
alias :alias get_alias
|
|
8
6
|
alias :alias= set_alias
|
|
9
7
|
|
|
@@ -171,7 +169,7 @@ module DuckDB
|
|
|
171
169
|
# decimal_type.width #=> 18
|
|
172
170
|
# decimal_type.scale #=> 3
|
|
173
171
|
def create_decimal(width, scale)
|
|
174
|
-
raise DuckDB::Error, 'width must be between 1 and 38' unless RANGE_DECIMAL_WIDTH.cover?(width)
|
|
172
|
+
raise DuckDB::Error, 'width must be between 1 and 38' unless Converter::RANGE_DECIMAL_WIDTH.cover?(width)
|
|
175
173
|
raise DuckDB::Error, "scale must be between 0 and width(#{width})" unless (0..width).cover?(scale)
|
|
176
174
|
|
|
177
175
|
_create_decimal_type(width, scale)
|