duckdb 1.5.2.1 → 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 +29 -0
- data/ext/duckdb/aggregate_function.c +1 -0
- data/ext/duckdb/aggregate_function_set.c +86 -0
- data/ext/duckdb/aggregate_function_set.h +14 -0
- data/ext/duckdb/appender.c +45 -4
- data/ext/duckdb/connection.c +23 -0
- data/ext/duckdb/duckdb.c +1 -0
- data/ext/duckdb/extconf.rb +28 -13
- data/ext/duckdb/prepared_statement.c +17 -0
- data/ext/duckdb/result.c +0 -50
- data/ext/duckdb/ruby-duckdb.h +1 -0
- data/lib/duckdb/aggregate_function.rb +7 -1
- data/lib/duckdb/aggregate_function_set.rb +29 -0
- data/lib/duckdb/appender.rb +74 -0
- data/lib/duckdb/connection.rb +85 -9
- data/lib/duckdb/prepared_statement.rb +18 -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 +2 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4226fa5e46b5d23fec1fd817577faa2329dced7fa67cedacaa75140849c2de16
|
|
4
|
+
data.tar.gz: 931c46acf582c8d01057c7a5b81f8356d430d1ed44cfad85ff79fd16a1935664
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e41cee0179f52dab024e361a69725468a7f088a6e5314df76b1d4d5d2b216b687f075c6873a4d294026e29466e99223b79653c6391a8ddf44a574dc0ec1e633e
|
|
7
|
+
data.tar.gz: 49c1f2218424af2b13009fff358de4ace4050385323d5a6e06dee4c3b301fe8ee4855abbcbd944593b1ada48ec59eabd6af1e963aed3e70085da7f74b9257fd6
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
# Unreleased
|
|
6
6
|
|
|
7
|
+
# 1.5.3.0 - 2026-05-24
|
|
8
|
+
- bump up DuckDB 1.5.3 on CI.
|
|
9
|
+
- add `DuckDB::AggregateFunctionSet` class and `DuckDB::Connection#register_aggregate_function_set` to register multiple overloads of a custom aggregate function under one SQL name.
|
|
10
|
+
- add `DuckDB::Appender#append_default_to_chunk`.
|
|
11
|
+
- add `DuckDB::TableNameParser` module with shared table name parsing logic (quoting and dot-notation), included by `DuckDB::Appender` and `DuckDB::TableDescription`.
|
|
12
|
+
- add `DuckDB::PreparedStatement#bind_timestamp_tz` to bind a `TIMESTAMP WITH TIME ZONE` (TIMESTAMPTZ) parameter from a `Time` or timestamp string.
|
|
13
|
+
- `DuckDB::AggregateFunction#name=` and `DuckDB::ScalarFunction#name=` now accept Symbol arguments (coerced to String).
|
|
14
|
+
## Breaking Changes
|
|
15
|
+
- `DuckDB::ScalarFunction.create`: `name:` is now a required keyword argument (previously optional with `nil` default). Parameter order changed to `name:, return_type:, ...`.
|
|
16
|
+
- `DuckDB::ScalarFunctionSet#add`: no longer overrides the scalar function's name with the set's name. The individual function must have its own name set before being added to the set.
|
|
17
|
+
- `DuckDB::TableDescription.new`: the 2nd argument now parses dot-notation and quoting:
|
|
18
|
+
- `'schema.table'` is interpreted as schema-qualified (deprecated; use `schema:` keyword instead).
|
|
19
|
+
- `'"schema.table"'` or `"'schema.table'"` — quotes are stripped and the name is treated as a literal table name containing a dot.
|
|
20
|
+
- `DuckDB::Appender.new`: the 2nd argument now parses dot-notation and quoting (previously only `Connection#appender` did this):
|
|
21
|
+
- `'schema.table'` is interpreted as schema-qualified (deprecated; use `schema:` keyword instead).
|
|
22
|
+
- `'"schema.table"'` or `"'schema.table'"` — quotes are stripped and the name is treated as a literal table name containing a dot.
|
|
23
|
+
- `DuckDB::Connection#appender`: table name parsing (dot-notation, quoting) is now delegated to `DuckDB::Appender.new` internally. Behavior is unchanged — `'schema.table'` has always split on dot in `Connection#appender`. No deprecation warning is emitted.
|
|
24
|
+
- `DuckDB::Connection#appender`: table names surrounded by double or single quotes (e.g. `'"a.b"'` or `"'a.b'"`) are treated as literal table names — the quotes are stripped and no dot-splitting is performed.
|
|
25
|
+
- fix: `Connection#appender('a.b')` no longer emits a misleading deprecation warning (the behavior was not deprecated — it was already the correct behavior).
|
|
26
|
+
- add `DuckDB::Appender.new(con, table, schema: nil, catalog: nil)` keyword argument form.
|
|
27
|
+
- add `DuckDB::Connection#appender(table, schema: nil, catalog: nil)` keyword argument form.
|
|
28
|
+
- deprecate `DuckDB::Result#_column_type(i)` private method. use `columns[i].send(:_type)` instead.
|
|
29
|
+
- `DuckDB::Result#enum_dictionary_values` checks invalid column index.
|
|
30
|
+
- deprecate `DuckDB::Result#_enum_dictionary_size` private method.
|
|
31
|
+
- deprecate `DuckDB::Result#_enum_dictionary_value` private method.
|
|
32
|
+
## Deprecations
|
|
33
|
+
- deprecate `DuckDB::Appender.new(con, schema, table)` 3-positional-argument form. Use `DuckDB::Appender.new(con, table, schema: schema)` instead.
|
|
34
|
+
- deprecate passing dot-notation string (e.g. `'schema.table'`) directly to `DuckDB::Appender.new`. Use `DuckDB::Appender.new(con, table, schema: schema)` instead.
|
|
35
|
+
|
|
7
36
|
# 1.5.2.1 - 2026-04-24
|
|
8
37
|
|
|
9
38
|
- add `DuckDB::AggregateFunction.create`.
|
|
@@ -736,6 +736,7 @@ void rbduckdb_init_aggregate_function(void) {
|
|
|
736
736
|
cDuckDBAggregateFunction = rb_define_class_under(mDuckDB, "AggregateFunction", rb_cObject);
|
|
737
737
|
rb_define_alloc_func(cDuckDBAggregateFunction, allocate);
|
|
738
738
|
rb_define_method(cDuckDBAggregateFunction, "initialize", aggregate_function_initialize, 0);
|
|
739
|
+
rb_define_method(cDuckDBAggregateFunction, "set_name", aggregate_function_set_name, 1);
|
|
739
740
|
rb_define_method(cDuckDBAggregateFunction, "name=", aggregate_function_set_name, 1);
|
|
740
741
|
rb_define_private_method(cDuckDBAggregateFunction, "_set_return_type", aggregate_function__set_return_type, 1);
|
|
741
742
|
rb_define_private_method(cDuckDBAggregateFunction, "_add_parameter", aggregate_function__add_parameter, 1);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#include "ruby-duckdb.h"
|
|
2
|
+
|
|
3
|
+
VALUE cDuckDBAggregateFunctionSet;
|
|
4
|
+
|
|
5
|
+
static void mark(void *);
|
|
6
|
+
static void deallocate(void *);
|
|
7
|
+
static VALUE allocate(VALUE klass);
|
|
8
|
+
static size_t memsize(const void *p);
|
|
9
|
+
static void compact(void *);
|
|
10
|
+
static VALUE aggregate_function_set__initialize(VALUE self, VALUE name);
|
|
11
|
+
static VALUE aggregate_function_set__add(VALUE self, VALUE aggregate_function);
|
|
12
|
+
|
|
13
|
+
static const rb_data_type_t aggregate_function_set_data_type = {
|
|
14
|
+
"DuckDB/AggregateFunctionSet",
|
|
15
|
+
{mark, deallocate, memsize, compact},
|
|
16
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
static void mark(void *ctx) {
|
|
20
|
+
rubyDuckDBAggregateFunctionSet *p = (rubyDuckDBAggregateFunctionSet *)ctx;
|
|
21
|
+
rb_gc_mark(p->functions);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static void deallocate(void *ctx) {
|
|
25
|
+
rubyDuckDBAggregateFunctionSet *p = (rubyDuckDBAggregateFunctionSet *)ctx;
|
|
26
|
+
duckdb_destroy_aggregate_function_set(&(p->aggregate_function_set));
|
|
27
|
+
xfree(p);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static void compact(void *ctx) {
|
|
31
|
+
rubyDuckDBAggregateFunctionSet *p = (rubyDuckDBAggregateFunctionSet *)ctx;
|
|
32
|
+
p->functions = rb_gc_location(p->functions);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static VALUE allocate(VALUE klass) {
|
|
36
|
+
rubyDuckDBAggregateFunctionSet *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBAggregateFunctionSet));
|
|
37
|
+
VALUE obj = TypedData_Wrap_Struct(klass, &aggregate_function_set_data_type, ctx);
|
|
38
|
+
ctx->functions = rb_ary_new();
|
|
39
|
+
RB_GC_GUARD(ctx->functions);
|
|
40
|
+
return obj;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static size_t memsize(const void *p) {
|
|
44
|
+
return sizeof(rubyDuckDBAggregateFunctionSet);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
rubyDuckDBAggregateFunctionSet *rbduckdb_get_struct_aggregate_function_set(VALUE obj) {
|
|
48
|
+
rubyDuckDBAggregateFunctionSet *ctx;
|
|
49
|
+
TypedData_Get_Struct(obj, rubyDuckDBAggregateFunctionSet, &aggregate_function_set_data_type, ctx);
|
|
50
|
+
return ctx;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* :nodoc: */
|
|
54
|
+
static VALUE aggregate_function_set__initialize(VALUE self, VALUE name) {
|
|
55
|
+
rubyDuckDBAggregateFunctionSet *p;
|
|
56
|
+
|
|
57
|
+
TypedData_Get_Struct(self, rubyDuckDBAggregateFunctionSet, &aggregate_function_set_data_type, p);
|
|
58
|
+
p->aggregate_function_set = duckdb_create_aggregate_function_set(StringValueCStr(name));
|
|
59
|
+
return self;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* :nodoc: */
|
|
63
|
+
static VALUE aggregate_function_set__add(VALUE self, VALUE aggregate_function) {
|
|
64
|
+
rubyDuckDBAggregateFunctionSet *p;
|
|
65
|
+
rubyDuckDBAggregateFunction *af;
|
|
66
|
+
|
|
67
|
+
TypedData_Get_Struct(self, rubyDuckDBAggregateFunctionSet, &aggregate_function_set_data_type, p);
|
|
68
|
+
af = rbduckdb_get_struct_aggregate_function(aggregate_function);
|
|
69
|
+
|
|
70
|
+
if (duckdb_add_aggregate_function_to_set(p->aggregate_function_set, af->aggregate_function) == DuckDBError) {
|
|
71
|
+
rb_raise(eDuckDBError, "failed to add aggregate function to set (duplicate overload?)");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
rb_ary_push(p->functions, aggregate_function);
|
|
75
|
+
return self;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void rbduckdb_init_aggregate_function_set(void) {
|
|
79
|
+
#if 0
|
|
80
|
+
VALUE mDuckDB = rb_define_module("DuckDB");
|
|
81
|
+
#endif
|
|
82
|
+
cDuckDBAggregateFunctionSet = rb_define_class_under(mDuckDB, "AggregateFunctionSet", rb_cObject);
|
|
83
|
+
rb_define_alloc_func(cDuckDBAggregateFunctionSet, allocate);
|
|
84
|
+
rb_define_private_method(cDuckDBAggregateFunctionSet, "_initialize", aggregate_function_set__initialize, 1);
|
|
85
|
+
rb_define_private_method(cDuckDBAggregateFunctionSet, "_add", aggregate_function_set__add, 1);
|
|
86
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#ifndef RUBY_DUCKDB_AGGREGATE_FUNCTION_SET_H
|
|
2
|
+
#define RUBY_DUCKDB_AGGREGATE_FUNCTION_SET_H
|
|
3
|
+
|
|
4
|
+
struct _rubyDuckDBAggregateFunctionSet {
|
|
5
|
+
duckdb_aggregate_function_set aggregate_function_set;
|
|
6
|
+
VALUE functions; /* Ruby Array of AggregateFunction objects — prevents GC collection */
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
typedef struct _rubyDuckDBAggregateFunctionSet rubyDuckDBAggregateFunctionSet;
|
|
10
|
+
|
|
11
|
+
void rbduckdb_init_aggregate_function_set(void);
|
|
12
|
+
rubyDuckDBAggregateFunctionSet *rbduckdb_get_struct_aggregate_function_set(VALUE obj);
|
|
13
|
+
|
|
14
|
+
#endif
|
data/ext/duckdb/appender.c
CHANGED
|
@@ -9,7 +9,8 @@ static size_t memsize(const void *p);
|
|
|
9
9
|
|
|
10
10
|
static VALUE appender_s_create_query(VALUE klass, VALUE con, VALUE query, VALUE types, VALUE table, VALUE columns);
|
|
11
11
|
|
|
12
|
-
static VALUE
|
|
12
|
+
static VALUE appender__initialize(VALUE self, VALUE con, VALUE schema, VALUE table);
|
|
13
|
+
static VALUE appender__initialize_ext(VALUE self, VALUE con, VALUE catalog, VALUE schema, VALUE table);
|
|
13
14
|
static VALUE appender_error_message(VALUE self);
|
|
14
15
|
static VALUE appender__append_bool(VALUE self, VALUE val);
|
|
15
16
|
static VALUE appender__append_int8(VALUE self, VALUE val);
|
|
@@ -36,6 +37,7 @@ static VALUE appender__append_hugeint(VALUE self, VALUE lower, VALUE upper);
|
|
|
36
37
|
static VALUE appender__append_uhugeint(VALUE self, VALUE lower, VALUE upper);
|
|
37
38
|
static VALUE appender__append_value(VALUE self, VALUE val);
|
|
38
39
|
static VALUE appender__append_data_chunk(VALUE self, VALUE chunk);
|
|
40
|
+
static VALUE appender__append_default_to_chunk(VALUE self, VALUE chunk, VALUE col, VALUE row);
|
|
39
41
|
static VALUE appender__flush(VALUE self);
|
|
40
42
|
|
|
41
43
|
#ifdef HAVE_DUCKDB_H_GE_V1_5_0
|
|
@@ -130,14 +132,14 @@ static VALUE appender_s_create_query(VALUE klass, VALUE con, VALUE query, VALUE
|
|
|
130
132
|
return appender;
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
static VALUE
|
|
135
|
+
static VALUE appender__initialize(VALUE self, VALUE con, VALUE schema, VALUE table) {
|
|
134
136
|
|
|
135
137
|
rubyDuckDBConnection *ctxcon;
|
|
136
138
|
rubyDuckDBAppender *ctx;
|
|
137
139
|
char *pschema = 0;
|
|
138
140
|
|
|
139
141
|
if (!rb_obj_is_kind_of(con, cDuckDBConnection)) {
|
|
140
|
-
rb_raise(rb_eTypeError, "1st argument should be instance of
|
|
142
|
+
rb_raise(rb_eTypeError, "1st argument should be instance of DuckDB::Connection");
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);
|
|
@@ -153,6 +155,32 @@ static VALUE appender_initialize(VALUE self, VALUE con, VALUE schema, VALUE tabl
|
|
|
153
155
|
return self;
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
static VALUE appender__initialize_ext(VALUE self, VALUE con, VALUE catalog, VALUE schema, VALUE table) {
|
|
159
|
+
rubyDuckDBConnection *ctxcon;
|
|
160
|
+
rubyDuckDBAppender *ctx;
|
|
161
|
+
char *pcatalog = 0;
|
|
162
|
+
char *pschema = 0;
|
|
163
|
+
|
|
164
|
+
if (!rb_obj_is_kind_of(con, cDuckDBConnection)) {
|
|
165
|
+
rb_raise(rb_eTypeError, "1st argument should be instance of DuckDB::Connection");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);
|
|
169
|
+
ctxcon = rbduckdb_get_struct_connection(con);
|
|
170
|
+
|
|
171
|
+
if (catalog != Qnil) {
|
|
172
|
+
pcatalog = StringValuePtr(catalog);
|
|
173
|
+
}
|
|
174
|
+
if (schema != Qnil) {
|
|
175
|
+
pschema = StringValuePtr(schema);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (duckdb_appender_create_ext(ctxcon->con, pcatalog, pschema, StringValuePtr(table), &(ctx->appender)) == DuckDBError) {
|
|
179
|
+
rb_raise(eDuckDBError, "failed to create appender");
|
|
180
|
+
}
|
|
181
|
+
return self;
|
|
182
|
+
}
|
|
183
|
+
|
|
156
184
|
/* call-seq:
|
|
157
185
|
* appender.error_message -> String
|
|
158
186
|
*
|
|
@@ -447,6 +475,17 @@ static VALUE appender__append_data_chunk(VALUE self, VALUE chunk) {
|
|
|
447
475
|
return state_to_rbool(duckdb_append_data_chunk(ctx->appender, chunk_ctx->data_chunk));
|
|
448
476
|
}
|
|
449
477
|
|
|
478
|
+
/* :nodoc: */
|
|
479
|
+
static VALUE appender__append_default_to_chunk(VALUE self, VALUE chunk, VALUE col, VALUE row) {
|
|
480
|
+
rubyDuckDBAppender *ctx;
|
|
481
|
+
rubyDuckDBDataChunk *chunk_ctx;
|
|
482
|
+
|
|
483
|
+
TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);
|
|
484
|
+
chunk_ctx = rbduckdb_get_struct_data_chunk(chunk);
|
|
485
|
+
|
|
486
|
+
return state_to_rbool(duckdb_append_default_to_chunk(ctx->appender, chunk_ctx->data_chunk, NUM2ULL(col), NUM2ULL(row)));
|
|
487
|
+
}
|
|
488
|
+
|
|
450
489
|
/* :nodoc: */
|
|
451
490
|
static VALUE appender__flush(VALUE self) {
|
|
452
491
|
rubyDuckDBAppender *ctx;
|
|
@@ -504,7 +543,8 @@ void rbduckdb_init_appender(void) {
|
|
|
504
543
|
cDuckDBAppender = rb_define_class_under(mDuckDB, "Appender", rb_cObject);
|
|
505
544
|
rb_define_alloc_func(cDuckDBAppender, allocate);
|
|
506
545
|
rb_define_singleton_method(cDuckDBAppender, "create_query", appender_s_create_query, 5);
|
|
507
|
-
|
|
546
|
+
rb_define_private_method(cDuckDBAppender, "_initialize", appender__initialize, 3);
|
|
547
|
+
rb_define_private_method(cDuckDBAppender, "_initialize_ext", appender__initialize_ext, 4);
|
|
508
548
|
rb_define_method(cDuckDBAppender, "error_message", appender_error_message, 0);
|
|
509
549
|
rb_define_private_method(cDuckDBAppender, "_end_row", appender__end_row, 0);
|
|
510
550
|
rb_define_private_method(cDuckDBAppender, "_flush", appender__flush, 0);
|
|
@@ -540,4 +580,5 @@ void rbduckdb_init_appender(void) {
|
|
|
540
580
|
rb_define_private_method(cDuckDBAppender, "_append_uhugeint", appender__append_uhugeint, 2);
|
|
541
581
|
rb_define_private_method(cDuckDBAppender, "_append_value", appender__append_value, 1);
|
|
542
582
|
rb_define_private_method(cDuckDBAppender, "_append_data_chunk", appender__append_data_chunk, 1);
|
|
583
|
+
rb_define_private_method(cDuckDBAppender, "_append_default_to_chunk", appender__append_default_to_chunk, 3);
|
|
543
584
|
}
|
data/ext/duckdb/connection.c
CHANGED
|
@@ -15,6 +15,7 @@ static VALUE connection__register_logical_type(VALUE self, VALUE logical_type);
|
|
|
15
15
|
static VALUE connection__register_scalar_function(VALUE self, VALUE scalar_function);
|
|
16
16
|
static VALUE connection__register_scalar_function_set(VALUE self, VALUE scalar_function_set);
|
|
17
17
|
static VALUE connection__register_aggregate_function(VALUE self, VALUE aggregate_function);
|
|
18
|
+
static VALUE connection__register_aggregate_function_set(VALUE self, VALUE aggregate_function_set);
|
|
18
19
|
static VALUE connection__register_table_function(VALUE self, VALUE table_function);
|
|
19
20
|
|
|
20
21
|
static const rb_data_type_t connection_data_type = {
|
|
@@ -286,6 +287,27 @@ static VALUE connection__register_aggregate_function(VALUE self, VALUE aggregate
|
|
|
286
287
|
return self;
|
|
287
288
|
}
|
|
288
289
|
|
|
290
|
+
/* :nodoc: */
|
|
291
|
+
static VALUE connection__register_aggregate_function_set(VALUE self, VALUE aggregate_function_set) {
|
|
292
|
+
rubyDuckDBConnection *ctxcon;
|
|
293
|
+
rubyDuckDBAggregateFunctionSet *ctxafs;
|
|
294
|
+
duckdb_state state;
|
|
295
|
+
|
|
296
|
+
ctxcon = rbduckdb_get_struct_connection(self);
|
|
297
|
+
ctxafs = rbduckdb_get_struct_aggregate_function_set(aggregate_function_set);
|
|
298
|
+
|
|
299
|
+
state = duckdb_register_aggregate_function_set(ctxcon->con, ctxafs->aggregate_function_set);
|
|
300
|
+
|
|
301
|
+
if (state == DuckDBError) {
|
|
302
|
+
rb_raise(eDuckDBError, "Failed to register aggregate function set");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* Keep reference to prevent GC while connection is alive */
|
|
306
|
+
rb_ary_push(ctxcon->registered_functions, aggregate_function_set);
|
|
307
|
+
|
|
308
|
+
return self;
|
|
309
|
+
}
|
|
310
|
+
|
|
289
311
|
static VALUE connection__register_table_function(VALUE self, VALUE table_function) {
|
|
290
312
|
rubyDuckDBConnection *ctxcon;
|
|
291
313
|
rubyDuckDBTableFunction *ctxtf;
|
|
@@ -320,6 +342,7 @@ void rbduckdb_init_connection(void) {
|
|
|
320
342
|
rb_define_private_method(cDuckDBConnection, "_register_scalar_function", connection__register_scalar_function, 1);
|
|
321
343
|
rb_define_private_method(cDuckDBConnection, "_register_scalar_function_set", connection__register_scalar_function_set, 1);
|
|
322
344
|
rb_define_private_method(cDuckDBConnection, "_register_aggregate_function", connection__register_aggregate_function, 1);
|
|
345
|
+
rb_define_private_method(cDuckDBConnection, "_register_aggregate_function_set", connection__register_aggregate_function_set, 1);
|
|
323
346
|
rb_define_private_method(cDuckDBConnection, "_register_table_function", connection__register_table_function, 1);
|
|
324
347
|
rb_define_private_method(cDuckDBConnection, "_connect", connection__connect, 1);
|
|
325
348
|
rb_define_private_method(cDuckDBConnection, "_query_sql", connection__query_sql, 1);
|
data/ext/duckdb/duckdb.c
CHANGED
|
@@ -59,6 +59,7 @@ Init_duckdb_native(void) {
|
|
|
59
59
|
rbduckdb_init_duckdb_scalar_function();
|
|
60
60
|
rbduckdb_init_duckdb_scalar_function_set();
|
|
61
61
|
rbduckdb_init_aggregate_function();
|
|
62
|
+
rbduckdb_init_aggregate_function_set();
|
|
62
63
|
rbduckdb_init_expression();
|
|
63
64
|
rbduckdb_init_client_context();
|
|
64
65
|
rbduckdb_init_duckdb_scalar_function_bind_info();
|
data/ext/duckdb/extconf.rb
CHANGED
|
@@ -4,13 +4,34 @@ require 'mkmf'
|
|
|
4
4
|
|
|
5
5
|
DUCKDB_REQUIRED_VERSION = '1.4.0'
|
|
6
6
|
|
|
7
|
+
def brew_prefix(formula = nil)
|
|
8
|
+
cmd = formula ? "brew --prefix #{formula} 2>/dev/null" : 'brew --prefix 2>/dev/null'
|
|
9
|
+
prefix = `#{cmd}`.chomp
|
|
10
|
+
prefix.empty? ? nil : prefix
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
FALLBACK_PREFIXES = %w[/opt/homebrew /opt/homebrew/opt/duckdb /opt/local].freeze
|
|
14
|
+
|
|
15
|
+
def brew_dirs(subdir)
|
|
16
|
+
dirs = []
|
|
17
|
+
dirs << "#{brew_prefix('duckdb')}/#{subdir}" if brew_prefix('duckdb')
|
|
18
|
+
if (prefix = brew_prefix)
|
|
19
|
+
dirs << "#{prefix}/#{subdir}"
|
|
20
|
+
dirs << "#{prefix}/opt/duckdb/#{subdir}"
|
|
21
|
+
end
|
|
22
|
+
dirs
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def homebrew_include_dirs
|
|
26
|
+
(brew_dirs('include') + FALLBACK_PREFIXES.map { |p| "#{p}/include" }).uniq
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def homebrew_lib_dirs
|
|
30
|
+
(brew_dirs('lib') + FALLBACK_PREFIXES.map { |p| "#{p}/lib" }).uniq
|
|
31
|
+
end
|
|
32
|
+
|
|
7
33
|
def check_duckdb_header(header, version)
|
|
8
|
-
found = find_header(
|
|
9
|
-
header,
|
|
10
|
-
'/opt/homebrew/include',
|
|
11
|
-
'/opt/homebrew/opt/duckdb/include',
|
|
12
|
-
'/opt/local/include'
|
|
13
|
-
)
|
|
34
|
+
found = find_header(header, *homebrew_include_dirs)
|
|
14
35
|
return if found
|
|
15
36
|
|
|
16
37
|
msg = "#{header} is not found. Install #{header} of duckdb >= #{version}."
|
|
@@ -19,13 +40,7 @@ def check_duckdb_header(header, version)
|
|
|
19
40
|
end
|
|
20
41
|
|
|
21
42
|
def check_duckdb_library(library, func, version)
|
|
22
|
-
found = find_library(
|
|
23
|
-
library,
|
|
24
|
-
func,
|
|
25
|
-
'/opt/homebrew/lib',
|
|
26
|
-
'/opt/homebrew/opt/duckdb/lib',
|
|
27
|
-
'/opt/local/lib'
|
|
28
|
-
)
|
|
43
|
+
found = find_library(library, func, *homebrew_lib_dirs)
|
|
29
44
|
have_func(func, 'duckdb.h')
|
|
30
45
|
return if found
|
|
31
46
|
|
|
@@ -34,6 +34,7 @@ static VALUE prepared_statement__bind_uint64(VALUE self, VALUE vidx, VALUE val);
|
|
|
34
34
|
static VALUE prepared_statement__bind_date(VALUE self, VALUE vidx, VALUE year, VALUE month, VALUE day);
|
|
35
35
|
static VALUE prepared_statement__bind_time(VALUE self, VALUE vidx, VALUE hour, VALUE min, VALUE sec, VALUE micros);
|
|
36
36
|
static VALUE prepared_statement__bind_timestamp(VALUE self, VALUE vidx, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros);
|
|
37
|
+
static VALUE prepared_statement__bind_timestamp_tz(VALUE self, VALUE vidx, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros);
|
|
37
38
|
static VALUE prepared_statement__bind_interval(VALUE self, VALUE vidx, VALUE months, VALUE days, VALUE micros);
|
|
38
39
|
static VALUE prepared_statement__bind_hugeint(VALUE self, VALUE vidx, VALUE lower, VALUE upper);
|
|
39
40
|
static VALUE prepared_statement__bind_uhugeint(VALUE self, VALUE vidx, VALUE lower, VALUE upper);
|
|
@@ -471,6 +472,21 @@ static VALUE prepared_statement__bind_timestamp(VALUE self, VALUE vidx, VALUE ye
|
|
|
471
472
|
return self;
|
|
472
473
|
}
|
|
473
474
|
|
|
475
|
+
/* :nodoc: */
|
|
476
|
+
static VALUE prepared_statement__bind_timestamp_tz(VALUE self, VALUE vidx, VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros) {
|
|
477
|
+
duckdb_timestamp timestamp_tz;
|
|
478
|
+
rubyDuckDBPreparedStatement *ctx;
|
|
479
|
+
idx_t idx = check_index(vidx);
|
|
480
|
+
|
|
481
|
+
timestamp_tz = rbduckdb_to_duckdb_timestamp_from_value(year, month, day, hour, min, sec, micros);
|
|
482
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
483
|
+
|
|
484
|
+
if (duckdb_bind_timestamp_tz(ctx->prepared_statement, idx, timestamp_tz) == DuckDBError) {
|
|
485
|
+
rb_raise(eDuckDBError, "fail to bind %llu parameter", (unsigned long long)idx);
|
|
486
|
+
}
|
|
487
|
+
return self;
|
|
488
|
+
}
|
|
489
|
+
|
|
474
490
|
/* :nodoc: */
|
|
475
491
|
static VALUE prepared_statement__bind_interval(VALUE self, VALUE vidx, VALUE months, VALUE days, VALUE micros) {
|
|
476
492
|
duckdb_interval interval;
|
|
@@ -597,6 +613,7 @@ void rbduckdb_init_prepared_statement(void) {
|
|
|
597
613
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_date", prepared_statement__bind_date, 4);
|
|
598
614
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_time", prepared_statement__bind_time, 5);
|
|
599
615
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_timestamp", prepared_statement__bind_timestamp, 8);
|
|
616
|
+
rb_define_private_method(cDuckDBPreparedStatement, "_bind_timestamp_tz", prepared_statement__bind_timestamp_tz, 8);
|
|
600
617
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_interval", prepared_statement__bind_interval, 4);
|
|
601
618
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_hugeint", prepared_statement__bind_hugeint, 3);
|
|
602
619
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_uhugeint", prepared_statement__bind_uhugeint, 3);
|
data/ext/duckdb/result.c
CHANGED
|
@@ -17,12 +17,9 @@ static VALUE destroy_data_chunk(VALUE arg);
|
|
|
17
17
|
|
|
18
18
|
static VALUE result__chunk_stream(VALUE oDuckDBResult);
|
|
19
19
|
static VALUE yield_rows(VALUE arg);
|
|
20
|
-
static VALUE result__column_type(VALUE oDuckDBResult, VALUE col_idx);
|
|
21
20
|
static VALUE result__return_type(VALUE oDuckDBResult);
|
|
22
21
|
static VALUE result__statement_type(VALUE oDuckDBResult);
|
|
23
22
|
static VALUE result__enum_internal_type(VALUE oDuckDBResult, VALUE col_idx);
|
|
24
|
-
static VALUE result__enum_dictionary_size(VALUE oDuckDBResult, VALUE col_idx);
|
|
25
|
-
static VALUE result__enum_dictionary_value(VALUE oDuckDBResult, VALUE col_idx, VALUE idx);
|
|
26
23
|
|
|
27
24
|
static VALUE vector_date(void *vector_data, idx_t row_idx);
|
|
28
25
|
static VALUE vector_timestamp(void* vector_data, idx_t row_idx);
|
|
@@ -198,13 +195,6 @@ static VALUE yield_rows(VALUE arg) {
|
|
|
198
195
|
return Qnil;
|
|
199
196
|
}
|
|
200
197
|
|
|
201
|
-
/* :nodoc: */
|
|
202
|
-
static VALUE result__column_type(VALUE oDuckDBResult, VALUE col_idx) {
|
|
203
|
-
rubyDuckDBResult *ctx;
|
|
204
|
-
TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
|
|
205
|
-
return LL2NUM(duckdb_column_type(&(ctx->result), NUM2LL(col_idx)));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
198
|
/* :nodoc: */
|
|
209
199
|
static VALUE result__return_type(VALUE oDuckDBResult) {
|
|
210
200
|
rubyDuckDBResult *ctx;
|
|
@@ -234,46 +224,10 @@ static VALUE result__enum_internal_type(VALUE oDuckDBResult, VALUE col_idx) {
|
|
|
234
224
|
return type;
|
|
235
225
|
}
|
|
236
226
|
|
|
237
|
-
/* :nodoc: */
|
|
238
|
-
static VALUE result__enum_dictionary_size(VALUE oDuckDBResult, VALUE col_idx) {
|
|
239
|
-
rubyDuckDBResult *ctx;
|
|
240
|
-
VALUE size = Qnil;
|
|
241
|
-
duckdb_logical_type logical_type;
|
|
242
|
-
|
|
243
|
-
TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
|
|
244
|
-
logical_type = duckdb_column_logical_type(&(ctx->result), NUM2LL(col_idx));
|
|
245
|
-
if (logical_type) {
|
|
246
|
-
size = UINT2NUM(duckdb_enum_dictionary_size(logical_type));
|
|
247
|
-
}
|
|
248
|
-
duckdb_destroy_logical_type(&logical_type);
|
|
249
|
-
return size;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/* :nodoc: */
|
|
253
|
-
static VALUE result__enum_dictionary_value(VALUE oDuckDBResult, VALUE col_idx, VALUE idx) {
|
|
254
|
-
rubyDuckDBResult *ctx;
|
|
255
|
-
VALUE value = Qnil;
|
|
256
|
-
duckdb_logical_type logical_type;
|
|
257
|
-
char *p;
|
|
258
|
-
|
|
259
|
-
TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
|
|
260
|
-
logical_type = duckdb_column_logical_type(&(ctx->result), NUM2LL(col_idx));
|
|
261
|
-
if (logical_type) {
|
|
262
|
-
p = duckdb_enum_dictionary_value(logical_type, NUM2LL(idx));
|
|
263
|
-
if (p) {
|
|
264
|
-
value = rb_utf8_str_new_cstr(p);
|
|
265
|
-
duckdb_free(p);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
duckdb_destroy_logical_type(&logical_type);
|
|
269
|
-
return value;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
227
|
VALUE rbduckdb_create_result(void) {
|
|
273
228
|
return allocate(cDuckDBResult);
|
|
274
229
|
}
|
|
275
230
|
|
|
276
|
-
|
|
277
231
|
static VALUE vector_date(void *vector_data, idx_t row_idx) {
|
|
278
232
|
return rbduckdb_date_to_ruby(((duckdb_date *)vector_data)[row_idx]);
|
|
279
233
|
}
|
|
@@ -286,7 +240,6 @@ static VALUE vector_time(void* vector_data, idx_t row_idx) {
|
|
|
286
240
|
return rbduckdb_time_to_ruby(((duckdb_time *)vector_data)[row_idx]);
|
|
287
241
|
}
|
|
288
242
|
|
|
289
|
-
|
|
290
243
|
static VALUE vector_interval(void* vector_data, idx_t row_idx) {
|
|
291
244
|
return rbduckdb_interval_to_ruby(((duckdb_interval *)vector_data)[row_idx]);
|
|
292
245
|
}
|
|
@@ -718,11 +671,8 @@ void rbduckdb_init_result(void) {
|
|
|
718
671
|
rb_define_method(cDuckDBResult, "rows_changed", result_rows_changed, 0);
|
|
719
672
|
rb_define_method(cDuckDBResult, "columns", result_columns, 0);
|
|
720
673
|
rb_define_private_method(cDuckDBResult, "_chunk_stream", result__chunk_stream, 0);
|
|
721
|
-
rb_define_private_method(cDuckDBResult, "_column_type", result__column_type, 1);
|
|
722
674
|
rb_define_private_method(cDuckDBResult, "_return_type", result__return_type, 0);
|
|
723
675
|
rb_define_private_method(cDuckDBResult, "_statement_type", result__statement_type, 0);
|
|
724
676
|
|
|
725
677
|
rb_define_private_method(cDuckDBResult, "_enum_internal_type", result__enum_internal_type, 1);
|
|
726
|
-
rb_define_private_method(cDuckDBResult, "_enum_dictionary_size", result__enum_dictionary_size, 1);
|
|
727
|
-
rb_define_private_method(cDuckDBResult, "_enum_dictionary_value", result__enum_dictionary_value, 2);
|
|
728
678
|
}
|
data/ext/duckdb/ruby-duckdb.h
CHANGED
|
@@ -57,6 +57,12 @@ module DuckDB
|
|
|
57
57
|
class AggregateFunction
|
|
58
58
|
include FunctionTypeValidation
|
|
59
59
|
|
|
60
|
+
def name=(value)
|
|
61
|
+
set_name(value.to_s)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private :set_name
|
|
65
|
+
|
|
60
66
|
class << self
|
|
61
67
|
# Creates a new AggregateFunction in a single call.
|
|
62
68
|
#
|
|
@@ -64,7 +70,7 @@ module DuckDB
|
|
|
64
70
|
# AggregateFunction without requiring you to set each attribute
|
|
65
71
|
# separately.
|
|
66
72
|
#
|
|
67
|
-
# @param name [String] the SQL function name
|
|
73
|
+
# @param name [String, Symbol] the SQL function name
|
|
68
74
|
# @param return_type [DuckDB::LogicalType | Symbol] the SQL return type
|
|
69
75
|
# @param params [Array<DuckDB::LogicalType | Symbol>] input parameter types
|
|
70
76
|
# (empty array for a zero-argument aggregate)
|
|
@@ -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.
|
|
@@ -661,6 +676,20 @@ module DuckDB
|
|
|
661
676
|
|
|
662
677
|
# call-seq:
|
|
663
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
|
|
664
693
|
def append_data_chunk(chunk)
|
|
665
694
|
raise ArgumentError, "expected DuckDB::DataChunk, got #{chunk.class}" unless chunk.is_a?(DuckDB::DataChunk)
|
|
666
695
|
|
|
@@ -669,6 +698,34 @@ module DuckDB
|
|
|
669
698
|
raise_appender_error('failed to append_data_chunk')
|
|
670
699
|
end
|
|
671
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
|
+
|
|
672
729
|
# appends value.
|
|
673
730
|
#
|
|
674
731
|
# require 'duckdb'
|
|
@@ -722,6 +779,23 @@ module DuckDB
|
|
|
722
779
|
|
|
723
780
|
private
|
|
724
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
|
+
|
|
725
799
|
def raise_appender_error(default_message) # :nodoc:
|
|
726
800
|
message = error_message
|
|
727
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.
|
|
@@ -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
|
|
@@ -323,9 +393,15 @@ module DuckDB
|
|
|
323
393
|
appender.close
|
|
324
394
|
end
|
|
325
395
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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)
|
|
329
405
|
end
|
|
330
406
|
|
|
331
407
|
alias execute query
|
|
@@ -250,6 +250,24 @@ module DuckDB
|
|
|
250
250
|
_bind_timestamp(index, time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
+
# binds i-th parameter of TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ) type with SQL prepared statement.
|
|
254
|
+
# The first argument is index of parameter.
|
|
255
|
+
# The index of first parameter is 1 not 0.
|
|
256
|
+
# The second argument value is to expected time value.
|
|
257
|
+
#
|
|
258
|
+
# require 'duckdb'
|
|
259
|
+
# db = DuckDB::Database.open('duckdb_database')
|
|
260
|
+
# con = db.connect
|
|
261
|
+
# sql ='SELECT name FROM users WHERE created_at = ?'
|
|
262
|
+
# stmt = PreparedStatement.new(con, sql)
|
|
263
|
+
# stmt.bind_timestamp_tz(1, Time.now)
|
|
264
|
+
# # or you can specify timestamp string.
|
|
265
|
+
# # stmt.bind_timestamp_tz(1, '2022-02-23 07:39:45+00')
|
|
266
|
+
def bind_timestamp_tz(index, value)
|
|
267
|
+
time = _parse_time(value).utc
|
|
268
|
+
_bind_timestamp_tz(index, time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
|
|
269
|
+
end
|
|
270
|
+
|
|
253
271
|
# binds i-th parameter with SQL prepared statement.
|
|
254
272
|
# The first argument is index of parameter.
|
|
255
273
|
# 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,6 +4,7 @@ require 'duckdb/duckdb_native'
|
|
|
4
4
|
require 'duckdb/library_version'
|
|
5
5
|
require 'duckdb/version'
|
|
6
6
|
require 'duckdb/converter'
|
|
7
|
+
require 'duckdb/table_name_parser'
|
|
7
8
|
require 'duckdb/database'
|
|
8
9
|
require 'duckdb/connection'
|
|
9
10
|
require 'duckdb/extracted_statements'
|
|
@@ -18,6 +19,7 @@ require 'duckdb/function_type_validation'
|
|
|
18
19
|
require 'duckdb/scalar_function'
|
|
19
20
|
require 'duckdb/scalar_function_set'
|
|
20
21
|
require 'duckdb/aggregate_function'
|
|
22
|
+
require 'duckdb/aggregate_function_set'
|
|
21
23
|
require 'duckdb/expression'
|
|
22
24
|
require 'duckdb/client_context'
|
|
23
25
|
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.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Masaki Suketa
|
|
@@ -43,6 +43,8 @@ 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
|
|
48
50
|
- ext/duckdb/blob.c
|
|
@@ -110,6 +112,7 @@ files:
|
|
|
110
112
|
- ext/duckdb/vector.h
|
|
111
113
|
- lib/duckdb.rb
|
|
112
114
|
- lib/duckdb/aggregate_function.rb
|
|
115
|
+
- lib/duckdb/aggregate_function_set.rb
|
|
113
116
|
- lib/duckdb/appender.rb
|
|
114
117
|
- lib/duckdb/casting.rb
|
|
115
118
|
- lib/duckdb/client_context.rb
|
|
@@ -140,6 +143,7 @@ files:
|
|
|
140
143
|
- lib/duckdb/table_function/bind_info.rb
|
|
141
144
|
- lib/duckdb/table_function/function_info.rb
|
|
142
145
|
- lib/duckdb/table_function/init_info.rb
|
|
146
|
+
- lib/duckdb/table_name_parser.rb
|
|
143
147
|
- lib/duckdb/value.rb
|
|
144
148
|
- lib/duckdb/vector.rb
|
|
145
149
|
- lib/duckdb/version.rb
|
|
@@ -165,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
165
169
|
- !ruby/object:Gem::Version
|
|
166
170
|
version: '0'
|
|
167
171
|
requirements: []
|
|
168
|
-
rubygems_version: 4.0.
|
|
172
|
+
rubygems_version: 4.0.10
|
|
169
173
|
specification_version: 4
|
|
170
174
|
summary: Ruby bindings for the DuckDB database engine.
|
|
171
175
|
test_files: []
|