duckdb 1.5.2.1 → 1.5.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/CHANGELOG.md +40 -0
- data/README.md +52 -0
- data/ext/duckdb/aggregate_function.c +1 -1
- data/ext/duckdb/aggregate_function_set.c +86 -0
- data/ext/duckdb/aggregate_function_set.h +14 -0
- data/ext/duckdb/appender.c +62 -4
- 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 -3
- 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 +14 -11
- data/ext/duckdb/error.c +1 -1
- data/ext/duckdb/error.h +1 -3
- data/ext/duckdb/extconf.rb +28 -13
- data/ext/duckdb/function_executor.c +308 -2
- data/ext/duckdb/function_executor.h +44 -0
- data/ext/duckdb/prepared_statement.c +38 -0
- data/ext/duckdb/result.c +49 -53
- data/ext/duckdb/result.h +11 -0
- data/ext/duckdb/ruby-duckdb.h +4 -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/lib/duckdb/aggregate_function.rb +7 -1
- data/lib/duckdb/aggregate_function_set.rb +29 -0
- data/lib/duckdb/appender.rb +97 -0
- data/lib/duckdb/arrow_array_stream.rb +33 -0
- data/lib/duckdb/connection.rb +139 -9
- data/lib/duckdb/prepared_statement.rb +35 -0
- 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_name_parser.rb +58 -0
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +3 -0
- metadata +11 -2
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.
|
|
@@ -543,6 +558,29 @@ module DuckDB
|
|
|
543
558
|
raise_appender_error('failed to append_uhugeint')
|
|
544
559
|
end
|
|
545
560
|
|
|
561
|
+
# :call-seq:
|
|
562
|
+
# appender.append_uuid(val) -> self
|
|
563
|
+
#
|
|
564
|
+
# Appends a UUID value to the current row in the appender.
|
|
565
|
+
# +val+ must be a String in canonical UUID format
|
|
566
|
+
# (<tt>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</tt>).
|
|
567
|
+
# Raises ArgumentError if +val+ is not a valid UUID string.
|
|
568
|
+
#
|
|
569
|
+
# require 'duckdb'
|
|
570
|
+
# db = DuckDB::Database.open
|
|
571
|
+
# con = db.connect
|
|
572
|
+
# con.query('CREATE TABLE uuids (id UUID)')
|
|
573
|
+
# appender = con.appender('uuids')
|
|
574
|
+
# appender
|
|
575
|
+
# .append_uuid('550e8400-e29b-41d4-a716-446655440000')
|
|
576
|
+
# .end_row
|
|
577
|
+
# .flush
|
|
578
|
+
def append_uuid(value)
|
|
579
|
+
return self if _append_uuid(value)
|
|
580
|
+
|
|
581
|
+
raise_appender_error('failed to append_uuid')
|
|
582
|
+
end
|
|
583
|
+
|
|
546
584
|
# call-seq:
|
|
547
585
|
# appender.append_date(val) -> self
|
|
548
586
|
#
|
|
@@ -661,6 +699,20 @@ module DuckDB
|
|
|
661
699
|
|
|
662
700
|
# call-seq:
|
|
663
701
|
# appender.append_data_chunk(chunk) -> self
|
|
702
|
+
#
|
|
703
|
+
# Appends a pre-filled DuckDB::DataChunk to the appender.
|
|
704
|
+
#
|
|
705
|
+
# require 'duckdb'
|
|
706
|
+
# db = DuckDB::Database.open
|
|
707
|
+
# con = db.connect
|
|
708
|
+
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
|
|
709
|
+
# appender = con.appender('users')
|
|
710
|
+
# chunk = DuckDB::DataChunk.new([DuckDB::LogicalType::INTEGER, DuckDB::LogicalType::VARCHAR])
|
|
711
|
+
# chunk.set_value(0, 0, 1)
|
|
712
|
+
# chunk.set_value(1, 0, 'Alice')
|
|
713
|
+
# chunk.size = 1
|
|
714
|
+
# appender.append_data_chunk(chunk)
|
|
715
|
+
# appender.flush
|
|
664
716
|
def append_data_chunk(chunk)
|
|
665
717
|
raise ArgumentError, "expected DuckDB::DataChunk, got #{chunk.class}" unless chunk.is_a?(DuckDB::DataChunk)
|
|
666
718
|
|
|
@@ -669,6 +721,34 @@ module DuckDB
|
|
|
669
721
|
raise_appender_error('failed to append_data_chunk')
|
|
670
722
|
end
|
|
671
723
|
|
|
724
|
+
# call-seq:
|
|
725
|
+
# appender.append_default_to_chunk(chunk, col, row) -> self
|
|
726
|
+
#
|
|
727
|
+
# Appends the DEFAULT value for the column at +col+ and +row+ in +chunk+.
|
|
728
|
+
# If no DEFAULT is defined for the column, NULL is used.
|
|
729
|
+
# Call this before appending the chunk via #append_data_chunk.
|
|
730
|
+
#
|
|
731
|
+
# require 'duckdb'
|
|
732
|
+
# db = DuckDB::Database.open
|
|
733
|
+
# con = db.connect
|
|
734
|
+
# con.query('CREATE TABLE users (name VARCHAR, enabled BOOLEAN DEFAULT TRUE)')
|
|
735
|
+
# appender = con.appender('users')
|
|
736
|
+
# chunk = DuckDB::DataChunk.new([DuckDB::LogicalType::VARCHAR, DuckDB::LogicalType::BOOLEAN])
|
|
737
|
+
# appender.append_default_to_chunk(chunk, 1, 0) # enabled DEFAULT for row 0
|
|
738
|
+
# appender.append_default_to_chunk(chunk, 1, 1) # enabled DEFAULT for row 1
|
|
739
|
+
# chunk.set_value(0, 0, 'Alice')
|
|
740
|
+
# chunk.set_value(0, 1, 'Bob')
|
|
741
|
+
# chunk.size = 2
|
|
742
|
+
# appender.append_data_chunk(chunk)
|
|
743
|
+
# appender.flush
|
|
744
|
+
def append_default_to_chunk(chunk, col, row)
|
|
745
|
+
raise ArgumentError, "expected DuckDB::DataChunk, got #{chunk.class}" unless chunk.is_a?(DuckDB::DataChunk)
|
|
746
|
+
|
|
747
|
+
return self if _append_default_to_chunk(chunk, col, row)
|
|
748
|
+
|
|
749
|
+
raise_appender_error('failed to append_default_to_chunk')
|
|
750
|
+
end
|
|
751
|
+
|
|
672
752
|
# appends value.
|
|
673
753
|
#
|
|
674
754
|
# require 'duckdb'
|
|
@@ -722,6 +802,23 @@ module DuckDB
|
|
|
722
802
|
|
|
723
803
|
private
|
|
724
804
|
|
|
805
|
+
def warn_deprecated_3arg # :nodoc:
|
|
806
|
+
warn(
|
|
807
|
+
'DuckDB::Appender.new(con, schema, table) is deprecated. ' \
|
|
808
|
+
'Use DuckDB::Appender.new(con, table, schema: schema) instead.',
|
|
809
|
+
category: :deprecated
|
|
810
|
+
)
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
def initialize_with_parsed_table(con, table_name, schema:, catalog:) # :nodoc:
|
|
814
|
+
table_name, schema, catalog = parse_table_name(table_name, schema, catalog)
|
|
815
|
+
if catalog
|
|
816
|
+
_initialize_ext(con, catalog, schema, table_name)
|
|
817
|
+
else
|
|
818
|
+
_initialize(con, schema, table_name)
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
|
|
725
822
|
def raise_appender_error(default_message) # :nodoc:
|
|
726
823
|
message = error_message
|
|
727
824
|
raise DuckDB::Error, message || default_message
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuckDB
|
|
4
|
+
# The ArrowArrayStream class represents an exported Arrow C stream of a
|
|
5
|
+
# query result (Arrow C Data Interface). It is created by
|
|
6
|
+
# DuckDB::Result#arrow_c_stream and cannot be instantiated directly.
|
|
7
|
+
#
|
|
8
|
+
# The object satisfies the Ruby Arrow C stream protocol: #arrow_c_stream
|
|
9
|
+
# returns self and #to_i returns the address of the underlying
|
|
10
|
+
# <tt>struct ArrowArrayStream</tt>, so it can be consumed by ruby-polars,
|
|
11
|
+
# red-arrow and other Arrow consumers:
|
|
12
|
+
#
|
|
13
|
+
# result = con.query('SELECT * FROM users')
|
|
14
|
+
#
|
|
15
|
+
# # ruby-polars
|
|
16
|
+
# df = Polars::DataFrame.new(result)
|
|
17
|
+
#
|
|
18
|
+
# # red-arrow
|
|
19
|
+
# reader = Arrow::RecordBatchReader.import(result.arrow_c_stream.to_i)
|
|
20
|
+
#
|
|
21
|
+
# The consumer takes ownership of the stream's contents; a result can be
|
|
22
|
+
# exported only once.
|
|
23
|
+
#
|
|
24
|
+
# [EXPERIMENTAL] This API is built on DuckDB's unstable Arrow C API and
|
|
25
|
+
# may change in any minor release.
|
|
26
|
+
class ArrowArrayStream
|
|
27
|
+
class << self
|
|
28
|
+
def new
|
|
29
|
+
raise DuckDB::Error, 'DuckDB::ArrowArrayStream cannot be instantiated directly. Use DuckDB::Result#arrow_c_stream.'
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
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.
|
|
@@ -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
|
|
@@ -313,8 +383,62 @@ module DuckDB
|
|
|
313
383
|
register_table_function(tf)
|
|
314
384
|
end
|
|
315
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
|
+
|
|
316
426
|
private
|
|
317
427
|
|
|
428
|
+
# Drives the Arrow stream at +address+ chunk by chunk into +table+,
|
|
429
|
+
# returning the number of rows appended.
|
|
430
|
+
def append_arrow_chunks(table, address)
|
|
431
|
+
converted_schema = _arrow_converted_schema(address)
|
|
432
|
+
rows = 0
|
|
433
|
+
appender(table) do |app|
|
|
434
|
+
while (chunk = _arrow_next_chunk(address, converted_schema))
|
|
435
|
+
rows += chunk.size
|
|
436
|
+
app.append_data_chunk(chunk)
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
rows
|
|
440
|
+
end
|
|
441
|
+
|
|
318
442
|
def run_appender_block(appender, &)
|
|
319
443
|
return appender unless block_given?
|
|
320
444
|
|
|
@@ -323,9 +447,15 @@ module DuckDB
|
|
|
323
447
|
appender.close
|
|
324
448
|
end
|
|
325
449
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
450
|
+
# Silently pre-parses dot-notation so Appender.new receives clean values
|
|
451
|
+
# and does not emit a misleading "DuckDB::Appender.new" warning.
|
|
452
|
+
# con.appender('a.b') has always split on dot — no warning needed.
|
|
453
|
+
# Quoted table names pass through unchanged for Appender.new to handle.
|
|
454
|
+
def parse_connection_appender_table(table, schema, catalog)
|
|
455
|
+
return [table, schema, catalog] if quoted_table_name?(table)
|
|
456
|
+
return [table, schema, catalog] unless table.include?('.')
|
|
457
|
+
|
|
458
|
+
dot_notation_split(table, schema, catalog)
|
|
329
459
|
end
|
|
330
460
|
|
|
331
461
|
alias execute query
|
|
@@ -193,6 +193,23 @@ module DuckDB
|
|
|
193
193
|
_bind_uhugeint(index, lower, upper)
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
+
# binds i-th parameter with a UUID value.
|
|
197
|
+
# The first argument is the index of the parameter (1-based).
|
|
198
|
+
# The second argument must be a String in canonical UUID format
|
|
199
|
+
# (<tt>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</tt>).
|
|
200
|
+
# Raises ArgumentError if the value is not a valid UUID string.
|
|
201
|
+
#
|
|
202
|
+
# require 'duckdb'
|
|
203
|
+
# db = DuckDB::Database.open
|
|
204
|
+
# con = db.connect
|
|
205
|
+
# con.query('CREATE TABLE uuids (id UUID)')
|
|
206
|
+
# stmt = DuckDB::PreparedStatement.new(con, 'INSERT INTO uuids(id) VALUES ($1)')
|
|
207
|
+
# stmt.bind_uuid(1, '550e8400-e29b-41d4-a716-446655440000')
|
|
208
|
+
# stmt.execute
|
|
209
|
+
def bind_uuid(index, value)
|
|
210
|
+
_bind_uuid(index, value)
|
|
211
|
+
end
|
|
212
|
+
|
|
196
213
|
# binds i-th parameter with SQL prepared statement.
|
|
197
214
|
# The first argument is index of parameter.
|
|
198
215
|
# The index of first parameter is 1 not 0.
|
|
@@ -250,6 +267,24 @@ module DuckDB
|
|
|
250
267
|
_bind_timestamp(index, time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
|
|
251
268
|
end
|
|
252
269
|
|
|
270
|
+
# binds i-th parameter of TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) type with SQL prepared statement.
|
|
271
|
+
# The first argument is index of parameter.
|
|
272
|
+
# The index of first parameter is 1 not 0.
|
|
273
|
+
# The second argument value is to expected time value.
|
|
274
|
+
#
|
|
275
|
+
# require 'duckdb'
|
|
276
|
+
# db = DuckDB::Database.open('duckdb_database')
|
|
277
|
+
# con = db.connect
|
|
278
|
+
# sql ='SELECT name FROM users WHERE created_at = ?'
|
|
279
|
+
# stmt = PreparedStatement.new(con, sql)
|
|
280
|
+
# stmt.bind_timestamp_tz(1, Time.now)
|
|
281
|
+
# # or you can specify timestamp string.
|
|
282
|
+
# # stmt.bind_timestamp_tz(1, '2022-02-23 07:39:45+00')
|
|
283
|
+
def bind_timestamp_tz(index, value)
|
|
284
|
+
time = _parse_time(value).utc
|
|
285
|
+
_bind_timestamp_tz(index, time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
|
|
286
|
+
end
|
|
287
|
+
|
|
253
288
|
# binds i-th parameter with SQL prepared statement.
|
|
254
289
|
# The first argument is index of parameter.
|
|
255
290
|
# The index of first parameter is 1 not 0.
|
data/lib/duckdb/result.rb
CHANGED
|
@@ -81,11 +81,48 @@ module DuckDB
|
|
|
81
81
|
# result = con.query('SELECT * FROM enums')
|
|
82
82
|
# result.enum_dictionary_values(1) # => ['sad', 'ok', 'happy', '𝘾𝝾օɭ 😎']
|
|
83
83
|
def enum_dictionary_values(col_index)
|
|
84
|
+
column = columns[col_index]
|
|
85
|
+
|
|
86
|
+
raise ArgumentError, "Invalid index: #{col_index}" if column.nil? || col_index.negative?
|
|
87
|
+
|
|
88
|
+
lt = column.logical_type
|
|
89
|
+
|
|
90
|
+
raise DuckDB::Error, "Column[#{col_index}] type is not enum" if lt.type != :enum
|
|
91
|
+
|
|
84
92
|
values = []
|
|
85
|
-
|
|
86
|
-
values <<
|
|
93
|
+
lt.dictionary_size.times do |i|
|
|
94
|
+
values << lt.dictionary_value_at(i)
|
|
87
95
|
end
|
|
88
96
|
values
|
|
89
97
|
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def _enum_dictionary_size(idx)
|
|
102
|
+
warn(":_enum_dictionary_size is deprecated. use columns[#{idx}].logical_type.dictionary_size instead.")
|
|
103
|
+
|
|
104
|
+
raise ArgumentError, "Invalid index: #{idx}" if idx.negative?
|
|
105
|
+
|
|
106
|
+
columns[idx]&.logical_type&.dictionary_size
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def _enum_dictionary_value(col_index, idx)
|
|
110
|
+
warn(":_enum_dictionary_value is deprecated.\
|
|
111
|
+
use columns[#{col_index}].logical_type.dictionary_value_at(#{idx}) instead.")
|
|
112
|
+
|
|
113
|
+
raise ArgumentError, "Invalid index: #{col_index}" if col_index.negative?
|
|
114
|
+
|
|
115
|
+
lt = columns[col_index]&.logical_type
|
|
116
|
+
|
|
117
|
+
raise DuckDB::Error, "Column[#{col_index}] type is not enum" if lt&.type != :enum
|
|
118
|
+
|
|
119
|
+
lt.dictionary_value_at(idx)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def _column_type(idx)
|
|
123
|
+
warn(":_column_type is deprecated. use columns[#{idx}].send(:_type) instead.")
|
|
124
|
+
|
|
125
|
+
columns[idx].send(:_type)
|
|
126
|
+
end
|
|
90
127
|
end
|
|
91
128
|
end
|
|
@@ -7,8 +7,7 @@ module DuckDB
|
|
|
7
7
|
class ScalarFunction
|
|
8
8
|
# Create and configure a scalar function in one call
|
|
9
9
|
#
|
|
10
|
-
# @param name [String, Symbol
|
|
11
|
-
# intended for use in a +DuckDB::ScalarFunctionSet+ (the set provides the name)
|
|
10
|
+
# @param name [String, Symbol] the function name (required)
|
|
12
11
|
# @param return_type [DuckDB::LogicalType|:logical_type_symbol] the return type
|
|
13
12
|
# @param parameter_type [DuckDB::LogicalType|:logical_type_symbol, nil] single fixed parameter type
|
|
14
13
|
# @param parameter_types [Array<DuckDB::LogicalType|:logical_type_symbol>, nil] multiple fixed parameter types
|
|
@@ -64,7 +63,7 @@ module DuckDB
|
|
|
64
63
|
# null_handling: true
|
|
65
64
|
# ) { |v| v.nil? ? 0 : v }
|
|
66
65
|
def self.create( # rubocop:disable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/ParameterLists
|
|
67
|
-
return_type:,
|
|
66
|
+
name:, return_type:, parameter_type: nil, parameter_types: nil, varargs_type: nil, null_handling: false, &
|
|
68
67
|
)
|
|
69
68
|
raise ArgumentError, 'Block required' unless block_given?
|
|
70
69
|
raise ArgumentError, 'Cannot specify both parameter_type and parameter_types' if parameter_type && parameter_types
|
|
@@ -78,7 +77,7 @@ module DuckDB
|
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
sf = new
|
|
81
|
-
sf.name = name
|
|
80
|
+
sf.name = name
|
|
82
81
|
sf.return_type = return_type
|
|
83
82
|
params.each { |type| sf.add_parameter(type) }
|
|
84
83
|
sf.varargs_type = varargs_type if varargs_type
|
|
@@ -87,6 +86,12 @@ module DuckDB
|
|
|
87
86
|
sf
|
|
88
87
|
end
|
|
89
88
|
|
|
89
|
+
def name=(value)
|
|
90
|
+
set_name(value.to_s)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private :set_name
|
|
94
|
+
|
|
90
95
|
include FunctionTypeValidation
|
|
91
96
|
|
|
92
97
|
# Adds a parameter to the scalar function.
|
|
@@ -21,11 +21,17 @@ module DuckDB
|
|
|
21
21
|
# # id: integer, default=false
|
|
22
22
|
# # name: varchar, default=true
|
|
23
23
|
class TableDescription
|
|
24
|
+
include DuckDB::TableNameParser
|
|
25
|
+
|
|
24
26
|
# Creates a new TableDescription for the given table.
|
|
25
27
|
#
|
|
26
28
|
# +con+ must be a DuckDB::Connection. +table+ is the table name (String).
|
|
27
29
|
# Optionally pass +schema:+ and/or +catalog:+ to qualify the table.
|
|
28
30
|
#
|
|
31
|
+
# The +table+ argument supports dot-notation and quoting:
|
|
32
|
+
# - <tt>'schema.table'</tt> — interpreted as schema-qualified (deprecated; use +schema:+ instead)
|
|
33
|
+
# - <tt>'"a.b"'</tt> or <tt>"'a.b'"</tt> — treated as a literal table name containing a dot
|
|
34
|
+
#
|
|
29
35
|
# Raises DuckDB::Error if the connection is invalid, the table name is nil,
|
|
30
36
|
# or the table (or schema/catalog) does not exist.
|
|
31
37
|
#
|
|
@@ -40,6 +46,7 @@ module DuckDB
|
|
|
40
46
|
raise DuckDB::Error, '1st argument must be DuckDB::Connection object.' unless con.is_a?(DuckDB::Connection)
|
|
41
47
|
raise DuckDB::Error, '2nd argument must be table name.' if table.nil?
|
|
42
48
|
|
|
49
|
+
table, schema, catalog = parse_table_name(table, schema, catalog)
|
|
43
50
|
raise DuckDB::Error, error_message unless _initialize(con, catalog, schema, table)
|
|
44
51
|
end
|
|
45
52
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DuckDB
|
|
4
|
+
# DuckDB::TableNameParser provides shared table name parsing for classes that
|
|
5
|
+
# accept a table name argument, such as DuckDB::Appender and DuckDB::TableDescription.
|
|
6
|
+
#
|
|
7
|
+
# It handles:
|
|
8
|
+
# - Dot-notation: <tt>'schema.table'</tt> is split into schema and table (deprecated).
|
|
9
|
+
# - Quoting: <tt>'"a.b"'</tt> or <tt>"'a.b'"</tt> strips the quotes and treats the name literally.
|
|
10
|
+
module TableNameParser
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Parses +table+, +schema+, and +catalog+, handling quoting and dot-notation.
|
|
14
|
+
# Returns <tt>[table, schema, catalog]</tt>.
|
|
15
|
+
def parse_table_name(table, schema, catalog)
|
|
16
|
+
if quoted_table_name?(table)
|
|
17
|
+
[unquote_table_name(table), schema, catalog]
|
|
18
|
+
elsif table.include?('.')
|
|
19
|
+
warn_dot_notation_deprecated(table)
|
|
20
|
+
dot_notation_split(table, schema, catalog)
|
|
21
|
+
else
|
|
22
|
+
[table, schema, catalog]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def quoted_table_name?(name) # :nodoc:
|
|
27
|
+
name.match?(/\A(["']).*\1\z/)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def unquote_table_name(name) # :nodoc:
|
|
31
|
+
name[1..-2]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Splits a dot-notation string into [table, schema, catalog].
|
|
35
|
+
# Explicit keyword args take precedence over dot-notation parts.
|
|
36
|
+
def dot_notation_split(table, schema, catalog) # :nodoc:
|
|
37
|
+
parts = table.split('.')
|
|
38
|
+
raise ArgumentError, "Too many dot-separated segments in '#{table}'" if parts.length > 3
|
|
39
|
+
|
|
40
|
+
case parts.length
|
|
41
|
+
when 2 then [parts[1], schema || parts[0], catalog]
|
|
42
|
+
when 3 then [parts[2], schema || parts[1], catalog || parts[0]]
|
|
43
|
+
else raise ArgumentError, "Unexpected segment count in '#{table}'"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def warn_dot_notation_deprecated(table) # :nodoc:
|
|
48
|
+
class_name = self.class.name
|
|
49
|
+
warn(
|
|
50
|
+
"Passing dot-notation '#{table}' to #{class_name}.new is deprecated. " \
|
|
51
|
+
"If '#{table}' is a schema-qualified table, use #{class_name}.new(con, table, schema: schema) instead. " \
|
|
52
|
+
"If '#{table}' is a literal table name containing a dot, " \
|
|
53
|
+
"use #{class_name}.new(con, '\"#{table}\"') instead.",
|
|
54
|
+
category: :deprecated
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/duckdb/version.rb
CHANGED
data/lib/duckdb.rb
CHANGED
|
@@ -4,10 +4,12 @@ require 'duckdb/duckdb_native'
|
|
|
4
4
|
require 'duckdb/library_version'
|
|
5
5
|
require 'duckdb/version'
|
|
6
6
|
require 'duckdb/converter'
|
|
7
|
+
require 'duckdb/table_name_parser'
|
|
7
8
|
require 'duckdb/database'
|
|
8
9
|
require 'duckdb/connection'
|
|
9
10
|
require 'duckdb/extracted_statements'
|
|
10
11
|
require 'duckdb/result'
|
|
12
|
+
require 'duckdb/arrow_array_stream'
|
|
11
13
|
require 'duckdb/prepared_statement'
|
|
12
14
|
require 'duckdb/pending_result'
|
|
13
15
|
require 'duckdb/appender'
|
|
@@ -18,6 +20,7 @@ require 'duckdb/function_type_validation'
|
|
|
18
20
|
require 'duckdb/scalar_function'
|
|
19
21
|
require 'duckdb/scalar_function_set'
|
|
20
22
|
require 'duckdb/aggregate_function'
|
|
23
|
+
require 'duckdb/aggregate_function_set'
|
|
21
24
|
require 'duckdb/expression'
|
|
22
25
|
require 'duckdb/client_context'
|
|
23
26
|
require 'duckdb/scalar_function/bind_info'
|
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
|
+
version: 1.5.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Masaki Suketa
|
|
@@ -43,8 +43,14 @@ files:
|
|
|
43
43
|
- duckdb.gemspec
|
|
44
44
|
- ext/duckdb/aggregate_function.c
|
|
45
45
|
- ext/duckdb/aggregate_function.h
|
|
46
|
+
- ext/duckdb/aggregate_function_set.c
|
|
47
|
+
- ext/duckdb/aggregate_function_set.h
|
|
46
48
|
- ext/duckdb/appender.c
|
|
47
49
|
- ext/duckdb/appender.h
|
|
50
|
+
- ext/duckdb/arrow_array_stream.c
|
|
51
|
+
- ext/duckdb/arrow_array_stream.h
|
|
52
|
+
- ext/duckdb/arrow_import.c
|
|
53
|
+
- ext/duckdb/arrow_import.h
|
|
48
54
|
- ext/duckdb/blob.c
|
|
49
55
|
- ext/duckdb/blob.h
|
|
50
56
|
- ext/duckdb/client_context.c
|
|
@@ -110,7 +116,9 @@ files:
|
|
|
110
116
|
- ext/duckdb/vector.h
|
|
111
117
|
- lib/duckdb.rb
|
|
112
118
|
- lib/duckdb/aggregate_function.rb
|
|
119
|
+
- lib/duckdb/aggregate_function_set.rb
|
|
113
120
|
- lib/duckdb/appender.rb
|
|
121
|
+
- lib/duckdb/arrow_array_stream.rb
|
|
114
122
|
- lib/duckdb/casting.rb
|
|
115
123
|
- lib/duckdb/client_context.rb
|
|
116
124
|
- lib/duckdb/column.rb
|
|
@@ -140,6 +148,7 @@ files:
|
|
|
140
148
|
- lib/duckdb/table_function/bind_info.rb
|
|
141
149
|
- lib/duckdb/table_function/function_info.rb
|
|
142
150
|
- lib/duckdb/table_function/init_info.rb
|
|
151
|
+
- lib/duckdb/table_name_parser.rb
|
|
143
152
|
- lib/duckdb/value.rb
|
|
144
153
|
- lib/duckdb/vector.rb
|
|
145
154
|
- lib/duckdb/version.rb
|
|
@@ -165,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
165
174
|
- !ruby/object:Gem::Version
|
|
166
175
|
version: '0'
|
|
167
176
|
requirements: []
|
|
168
|
-
rubygems_version: 4.0.
|
|
177
|
+
rubygems_version: 4.0.10
|
|
169
178
|
specification_version: 4
|
|
170
179
|
summary: Ruby bindings for the DuckDB database engine.
|
|
171
180
|
test_files: []
|