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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c7c57e0282ec2213b1efb3844445804e5fbbc09e03496f2ba71c1bd24152843
4
- data.tar.gz: 9057fa45fafeba0dc2110562f98cd49eecf53ec0f0a48a989756742525c82b4a
3
+ metadata.gz: 4226fa5e46b5d23fec1fd817577faa2329dced7fa67cedacaa75140849c2de16
4
+ data.tar.gz: 931c46acf582c8d01057c7a5b81f8356d430d1ed44cfad85ff79fd16a1935664
5
5
  SHA512:
6
- metadata.gz: 78bbd166f587491085267791ffca949f2b310597f802424b7e0525c8c4f35f345c2362b17b33401bee374601d5f52746d10ac21f60ea8344bd3bf08e28d7f4b5
7
- data.tar.gz: 6fc88b5e289ef3451691f57cb404c390b9deabbafddf81e8aedd300121276a9df62b0492559f2602eb4aeaa57ed2a83cc98ccfbef3a728771c34a565a022f78a
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
@@ -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 appender_initialize(VALUE klass, VALUE con, VALUE schema, VALUE table);
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 appender_initialize(VALUE self, VALUE con, VALUE schema, VALUE table) {
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 DackDB::Connection");
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
- rb_define_method(cDuckDBAppender, "initialize", appender_initialize, 3);
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
  }
@@ -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();
@@ -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
  }
@@ -38,6 +38,7 @@
38
38
  #include "./scalar_function.h"
39
39
  #include "./scalar_function_set.h"
40
40
  #include "./aggregate_function.h"
41
+ #include "./aggregate_function_set.h"
41
42
  #include "./expression.h"
42
43
  #include "./client_context.h"
43
44
  #include "./scalar_function_bind_info.h"
@@ -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
@@ -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
@@ -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
- # returns Appender object.
131
- # The first argument is table name
132
- def appender(table, &)
133
- appender = create_appender(table)
134
- run_appender_block(appender, &)
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 [void]
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
- def create_appender(table)
327
- t1, t2 = table.split('.')
328
- t2 ? Appender.new(self, t1, t2) : Appender.new(self, t2, t1)
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
- _enum_dictionary_size(col_index).times do |i|
86
- values << _enum_dictionary_value(col_index, i)
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, nil] the function name; use +nil+ when creating overloads
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:, name: nil, parameter_type: nil, parameter_types: nil, varargs_type: nil, null_handling: false, &
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.to_s if 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.
@@ -24,7 +24,6 @@ module DuckDB
24
24
  raise TypeError, "#{scalar_function.class} is not a DuckDB::ScalarFunction"
25
25
  end
26
26
 
27
- scalar_function.name = @name
28
27
  _add(scalar_function)
29
28
  end
30
29
  end
@@ -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
@@ -3,5 +3,5 @@
3
3
  module DuckDB
4
4
  # The version string of ruby-duckdb.
5
5
  # Currently, ruby-duckdb is NOT semantic versioning.
6
- VERSION = '1.5.2.1'
6
+ VERSION = '1.5.3.0'
7
7
  end
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.2.1
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.6
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: []