duckdb 1.5.6.1 → 2.0.0.1

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.
@@ -22,8 +22,6 @@ static void table_function_init_callback(duckdb_init_info info);
22
22
  static VALUE table_function_execute(VALUE self);
23
23
  static void table_function_execute_callback(duckdb_function_info info, duckdb_data_chunk output);
24
24
  #ifdef HAVE_DUCKDB_H_GE_V1_5_0
25
- /* Thread detection (declared in function_executor.c); used to skip the proxy on Ruby threads. */
26
- extern int ruby_native_thread_p(void);
27
25
  static void table_function_local_init_callback(duckdb_init_info info);
28
26
  #endif
29
27
 
@@ -185,6 +183,27 @@ static VALUE table_function_add_named_parameter(VALUE self, VALUE name, VALUE lo
185
183
  return self;
186
184
  }
187
185
 
186
+ static rubyDuckDBTableFunction *table_function_get_settable_ctx(VALUE self, const char *block_msg) {
187
+ rubyDuckDBTableFunction *ctx;
188
+
189
+ if (!rb_block_given_p()) {
190
+ rb_raise(rb_eArgError, "%s", block_msg);
191
+ }
192
+
193
+ TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
194
+
195
+ if (!ctx->table_function) {
196
+ rb_raise(eDuckDBError, "Table function is destroyed");
197
+ }
198
+
199
+ return ctx;
200
+ }
201
+
202
+ static VALUE call_proc_with_one_arg(VALUE arg) {
203
+ VALUE *args = (VALUE *)arg;
204
+ return rb_funcall(args[0], rb_intern("call"), 1, args[1]);
205
+ }
206
+
188
207
  /*
189
208
  * call-seq:
190
209
  * table_function.bind { |bind_info| ... } -> self
@@ -198,17 +217,7 @@ static VALUE table_function_add_named_parameter(VALUE self, VALUE name, VALUE lo
198
217
  * end
199
218
  */
200
219
  static VALUE table_function_bind(VALUE self) {
201
- rubyDuckDBTableFunction *ctx;
202
-
203
- if (!rb_block_given_p()) {
204
- rb_raise(rb_eArgError, "block is required");
205
- }
206
-
207
- TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
208
-
209
- if (!ctx->table_function) {
210
- rb_raise(eDuckDBError, "Table function is destroyed");
211
- }
220
+ rubyDuckDBTableFunction *ctx = table_function_get_settable_ctx(self, "block is required");
212
221
 
213
222
  RB_OBJ_WRITE(self, &ctx->bind_proc, rb_block_proc());
214
223
 
@@ -219,11 +228,6 @@ static VALUE table_function_bind(VALUE self) {
219
228
  return self;
220
229
  }
221
230
 
222
- static VALUE call_bind_proc(VALUE arg) {
223
- VALUE *args = (VALUE *)arg;
224
- return rb_funcall(args[0], rb_intern("call"), 1, args[1]);
225
- }
226
-
227
231
  struct bind_dispatch_arg {
228
232
  rubyDuckDBTableFunction *ctx;
229
233
  duckdb_bind_info info;
@@ -240,7 +244,7 @@ static void execute_bind_callback_protected(void *user_data) {
240
244
  bind_info_ctx->bind_info = darg->info;
241
245
 
242
246
  VALUE call_args[2] = { darg->ctx->bind_proc, bind_info_obj };
243
- rb_protect(call_bind_proc, (VALUE)call_args, &state);
247
+ rb_protect(call_proc_with_one_arg, (VALUE)call_args, &state);
244
248
 
245
249
  if (state) {
246
250
  VALUE msg = rbduckdb_pending_error_message();
@@ -274,17 +278,7 @@ static void table_function_bind_callback(duckdb_bind_info info) {
274
278
  * end
275
279
  */
276
280
  static VALUE table_function_init(VALUE self) {
277
- rubyDuckDBTableFunction *ctx;
278
-
279
- if (!rb_block_given_p()) {
280
- rb_raise(rb_eArgError, "block is required for init");
281
- }
282
-
283
- TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
284
-
285
- if (!ctx->table_function) {
286
- rb_raise(eDuckDBError, "Table function is destroyed");
287
- }
281
+ rubyDuckDBTableFunction *ctx = table_function_get_settable_ctx(self, "block is required for init");
288
282
 
289
283
  RB_OBJ_WRITE(self, &ctx->init_proc, rb_block_proc());
290
284
  duckdb_table_function_set_init(ctx->table_function, table_function_init_callback);
@@ -294,11 +288,6 @@ static VALUE table_function_init(VALUE self) {
294
288
  return self;
295
289
  }
296
290
 
297
- static VALUE call_init_proc(VALUE args_val) {
298
- VALUE *args = (VALUE *)args_val;
299
- return rb_funcall(args[0], rb_intern("call"), 1, args[1]);
300
- }
301
-
302
291
  struct init_dispatch_arg {
303
292
  rubyDuckDBTableFunction *ctx;
304
293
  duckdb_init_info info;
@@ -315,7 +304,7 @@ static void execute_init_callback_protected(void *user_data) {
315
304
  init_info_ctx->info = darg->info;
316
305
 
317
306
  VALUE call_args[2] = { darg->ctx->init_proc, init_info_obj };
318
- rb_protect(call_init_proc, (VALUE)call_args, &state);
307
+ rb_protect(call_proc_with_one_arg, (VALUE)call_args, &state);
319
308
 
320
309
  if (state) {
321
310
  VALUE msg = rbduckdb_pending_error_message();
@@ -351,13 +340,7 @@ static void table_function_init_callback(duckdb_init_info info) {
351
340
  * end
352
341
  */
353
342
  static VALUE table_function_execute(VALUE self) {
354
- rubyDuckDBTableFunction *ctx;
355
-
356
- if (!rb_block_given_p()) {
357
- rb_raise(rb_eArgError, "block is required for execute");
358
- }
359
-
360
- TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
343
+ rubyDuckDBTableFunction *ctx = table_function_get_settable_ctx(self, "block is required for execute");
361
344
 
362
345
  RB_OBJ_WRITE(self, &ctx->execute_proc, rb_block_proc());
363
346
  duckdb_table_function_set_function(ctx->table_function, table_function_execute_callback);
@@ -431,48 +414,16 @@ static void table_function_execute_callback(duckdb_function_info info, duckdb_da
431
414
  /*
432
415
  * Per-worker init for the execute path (DuckDB >= 1.5.0).
433
416
  *
434
- * DuckDB calls this once on each worker thread that will run the execute
435
- * callback. We create a per-worker proxy (allocating its Ruby thread under the
436
- * GVL via the global executor, since this runs on a non-Ruby thread) and store
437
- * it as thread-local init data. The execute callback then dispatches through it
438
- * instead of the shared global executor, so workers run callbacks concurrently.
439
- * DuckDB invokes rbduckdb_worker_proxy_destroy when the local state is freed.
440
- */
441
- struct create_proxy_callback_arg {
442
- struct worker_proxy *proxy;
443
- };
444
-
445
- static VALUE create_proxy_callback(VALUE varg) {
446
- struct create_proxy_callback_arg *arg = (struct create_proxy_callback_arg *)varg;
447
- arg->proxy = rbduckdb_worker_proxy_create();
448
- return Qnil;
449
- }
450
-
451
- /*
452
- * rbduckdb_worker_proxy_create may raise (NoMemError, Thread.new failure).
453
- * Swallow it: the proxy stays NULL, local_init sets no state, and the execute
454
- * callback falls back to the global executor.
417
+ * Runs once on each worker thread that will execute the callback. Creates the
418
+ * worker's proxy and stores it as thread-local init data, so the execute callback
419
+ * dispatches through it rather than the shared global executor. DuckDB calls
420
+ * rbduckdb_worker_proxy_destroy when the local state is freed.
455
421
  */
456
- static void create_proxy_callback_protected(void *user_data) {
457
- int exception_state;
458
-
459
- rb_protect(create_proxy_callback, (VALUE)user_data, &exception_state);
460
- if (exception_state) {
461
- rb_set_errinfo(Qnil);
462
- }
463
- }
464
-
465
422
  static void table_function_local_init_callback(duckdb_init_info info) {
466
- struct create_proxy_callback_arg arg;
467
-
468
- /* A Ruby calling thread runs the callback inline (Case 1/2); no proxy needed. */
469
- if (ruby_native_thread_p()) return;
470
-
471
- arg.proxy = NULL;
472
- rbduckdb_function_executor_dispatch(create_proxy_callback_protected, &arg);
423
+ struct worker_proxy *proxy = rbduckdb_worker_proxy_create_for_current_thread();
473
424
 
474
- if (arg.proxy != NULL) {
475
- duckdb_init_set_init_data(info, arg.proxy, rbduckdb_worker_proxy_destroy);
425
+ if (proxy != NULL) {
426
+ duckdb_init_set_init_data(info, proxy, rbduckdb_worker_proxy_destroy);
476
427
  }
477
428
  }
478
429
  #endif
@@ -76,18 +76,13 @@ static VALUE table_function_bind_info_get_parameter(VALUE self, VALUE index) {
76
76
  rubyDuckDBBindInfo *ctx;
77
77
  idx_t idx;
78
78
  duckdb_value param_value;
79
- VALUE result;
80
79
 
81
80
  TypedData_Get_Struct(self, rubyDuckDBBindInfo, &bind_info_data_type, ctx);
82
81
 
83
82
  idx = NUM2ULL(index);
84
83
  param_value = duckdb_bind_get_parameter(ctx->bind_info, idx);
85
84
 
86
- result = rbduckdb_duckdb_value_to_ruby(param_value);
87
-
88
- duckdb_destroy_value(&param_value);
89
-
90
- return result;
85
+ return rbduckdb_duckdb_value_to_ruby_and_destroy(param_value);
91
86
  }
92
87
 
93
88
  /*
@@ -102,7 +97,6 @@ static VALUE table_function_bind_info_get_named_parameter(VALUE self, VALUE name
102
97
  rubyDuckDBBindInfo *ctx;
103
98
  const char *param_name;
104
99
  duckdb_value param_value;
105
- VALUE result;
106
100
 
107
101
  TypedData_Get_Struct(self, rubyDuckDBBindInfo, &bind_info_data_type, ctx);
108
102
 
@@ -114,11 +108,7 @@ static VALUE table_function_bind_info_get_named_parameter(VALUE self, VALUE name
114
108
  return Qnil;
115
109
  }
116
110
 
117
- result = rbduckdb_duckdb_value_to_ruby(param_value);
118
-
119
- duckdb_destroy_value(&param_value);
120
-
121
- return result;
111
+ return rbduckdb_duckdb_value_to_ruby_and_destroy(param_value);
122
112
  }
123
113
 
124
114
  /*
data/ext/duckdb/util.c CHANGED
@@ -1,5 +1,38 @@
1
1
  #include "ruby-duckdb.h"
2
2
 
3
+ #define MICROS_PER_DAY 86400000000LL
4
+
5
+ /*
6
+ * Mirrors DuckDB's Date::IsValid. duckdb_to_date throws a C++ exception
7
+ * for dates it rejects, which would abort the process.
8
+ */
9
+ static int is_valid_date(int32_t year, int32_t month, int32_t day) {
10
+ static const int32_t days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
11
+ int32_t max_day;
12
+
13
+ if (month < 1 || month > 12 || day < 1) {
14
+ return 0;
15
+ }
16
+ if (year < -5877641 || (year == -5877641 && (month < 6 || (month == 6 && day < 25)))) {
17
+ return 0;
18
+ }
19
+ if (year > 5881580 || (year == 5881580 && (month > 7 || (month == 7 && day > 10)))) {
20
+ return 0;
21
+ }
22
+ max_day = days_in_month[month];
23
+ if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) {
24
+ max_day = 29;
25
+ }
26
+ return day <= max_day;
27
+ }
28
+
29
+ static duckdb_date to_duckdb_date(duckdb_date_struct dt_struct) {
30
+ if (!is_valid_date(dt_struct.year, dt_struct.month, dt_struct.day)) {
31
+ rb_raise(rb_eRangeError, "date out of range: %d-%02d-%02d", dt_struct.year, dt_struct.month, dt_struct.day);
32
+ }
33
+ return duckdb_to_date(dt_struct);
34
+ }
35
+
3
36
  duckdb_date rbduckdb_to_duckdb_date_from_value(VALUE year, VALUE month, VALUE day) {
4
37
  duckdb_date_struct dt_struct;
5
38
 
@@ -7,7 +40,7 @@ duckdb_date rbduckdb_to_duckdb_date_from_value(VALUE year, VALUE month, VALUE da
7
40
  dt_struct.month = NUM2INT(month);
8
41
  dt_struct.day = NUM2INT(day);
9
42
 
10
- return duckdb_to_date(dt_struct);
43
+ return to_duckdb_date(dt_struct);
11
44
  }
12
45
 
13
46
  duckdb_time rbduckdb_to_duckdb_time_from_value(VALUE hour, VALUE min, VALUE sec, VALUE micros) {
@@ -47,6 +80,9 @@ duckdb_timestamp rbduckdb_to_duckdb_timestamp_from_time_value(VALUE time_obj) {
47
80
 
48
81
  duckdb_timestamp rbduckdb_to_duckdb_timestamp_from_value(VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros) {
49
82
  duckdb_timestamp_struct timestamp_st;
83
+ duckdb_timestamp result;
84
+ int64_t days;
85
+ int64_t time_micros;
50
86
 
51
87
  timestamp_st.date.year = NUM2INT(year);
52
88
  timestamp_st.date.month = NUM2INT(month);
@@ -56,7 +92,22 @@ duckdb_timestamp rbduckdb_to_duckdb_timestamp_from_value(VALUE year, VALUE month
56
92
  timestamp_st.time.sec = NUM2INT(sec);
57
93
  timestamp_st.time.micros = NUM2INT(micros);
58
94
 
59
- return duckdb_to_timestamp(timestamp_st);
95
+ /* Mirrors DuckDB's Timestamp::TryFromDatetime; duckdb_to_timestamp throws on failure. */
96
+ days = to_duckdb_date(timestamp_st.date).days;
97
+ time_micros = duckdb_to_time(timestamp_st.time).micros;
98
+ if (days > INT64_MAX / MICROS_PER_DAY || days < INT64_MIN / MICROS_PER_DAY) {
99
+ rb_raise(rb_eRangeError, "timestamp out of range");
100
+ }
101
+ result.micros = days * MICROS_PER_DAY;
102
+ if ((time_micros > 0 && result.micros > INT64_MAX - time_micros) ||
103
+ (time_micros < 0 && result.micros < INT64_MIN - time_micros)) {
104
+ rb_raise(rb_eRangeError, "timestamp out of range");
105
+ }
106
+ result.micros += time_micros;
107
+ if (!duckdb_is_finite_timestamp(result)) {
108
+ rb_raise(rb_eRangeError, "timestamp out of range");
109
+ }
110
+ return result;
60
111
  }
61
112
 
62
113
  void rbduckdb_to_duckdb_interval_from_value(duckdb_interval* interval, VALUE months, VALUE days, VALUE micros) {
data/ext/duckdb/value.c CHANGED
@@ -1,5 +1,10 @@
1
1
  #include "ruby-duckdb.h"
2
2
 
3
+ struct vector_ref_arg {
4
+ duckdb_vector vec;
5
+ duckdb_logical_type logical_type;
6
+ };
7
+
3
8
  VALUE cDuckDBValue;
4
9
 
5
10
  static void deallocate(void *);
@@ -37,6 +42,10 @@ static VALUE value_s__create_bit(VALUE klass, VALUE data);
37
42
  static VALUE value_s__create_bignum(VALUE klass, VALUE data, VALUE is_negative);
38
43
  static VALUE value_s_create_null(VALUE klass);
39
44
  static VALUE to_ruby_via_vector(duckdb_logical_type logical_type, duckdb_value val);
45
+ static VALUE convert_referenced_value(VALUE varg);
46
+ static VALUE destroy_referencing_vector(VALUE varg);
47
+ static VALUE convert_owned_value(VALUE varg);
48
+ static VALUE destroy_owned_value(VALUE varg);
40
49
  static idx_t marshal_values(VALUE ary, duckdb_value **out, volatile VALUE *guard);
41
50
  static VALUE value_s__create_list(VALUE klass, VALUE ltype, VALUE values);
42
51
  static VALUE value_s__create_array(VALUE klass, VALUE ltype, VALUE values);
@@ -557,19 +566,43 @@ rubyDuckDBValue *rbduckdb_get_struct_value(VALUE obj) {
557
566
  * the C API has no direct duckdb_get_* accessors for.
558
567
  */
559
568
  static VALUE to_ruby_via_vector(duckdb_logical_type logical_type, duckdb_value val) {
560
- duckdb_vector vec = duckdb_create_vector(logical_type, 1);
561
- VALUE result;
569
+ struct vector_ref_arg arg;
562
570
 
563
- duckdb_vector_reference_value(vec, val);
564
- result = rbduckdb_vector_value_at(vec, logical_type, 0);
565
- duckdb_destroy_vector(&vec);
566
- return result;
571
+ arg.vec = duckdb_create_vector(logical_type, 1);
572
+ arg.logical_type = logical_type;
573
+ duckdb_vector_reference_value(arg.vec, val);
574
+ return rb_ensure(convert_referenced_value, (VALUE)&arg, destroy_referencing_vector, (VALUE)&arg);
575
+ }
576
+
577
+ static VALUE convert_referenced_value(VALUE varg) {
578
+ struct vector_ref_arg *arg = (struct vector_ref_arg *)varg;
579
+
580
+ return rbduckdb_vector_value_at(arg->vec, arg->logical_type, 0);
581
+ }
582
+
583
+ static VALUE destroy_referencing_vector(VALUE varg) {
584
+ struct vector_ref_arg *arg = (struct vector_ref_arg *)varg;
585
+
586
+ duckdb_destroy_vector(&(arg->vec));
587
+ return Qnil;
588
+ }
589
+
590
+ static VALUE convert_owned_value(VALUE varg) {
591
+ return rbduckdb_duckdb_value_to_ruby(*(duckdb_value *)varg);
592
+ }
593
+
594
+ static VALUE destroy_owned_value(VALUE varg) {
595
+ duckdb_destroy_value((duckdb_value *)varg);
596
+ return Qnil;
597
+ }
598
+
599
+ VALUE rbduckdb_duckdb_value_to_ruby_and_destroy(duckdb_value val) {
600
+ return rb_ensure(convert_owned_value, (VALUE)&val, destroy_owned_value, (VALUE)&val);
567
601
  }
568
602
 
569
603
  VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
570
604
  duckdb_logical_type logical_type;
571
605
  duckdb_type type_id;
572
- duckdb_value child_value;
573
606
  VALUE result;
574
607
  VALUE elem;
575
608
  VALUE key;
@@ -676,9 +709,7 @@ VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
676
709
  size = duckdb_get_list_size(val);
677
710
  result = rb_ary_new_capa(size);
678
711
  for (i = 0; i < size; i++) {
679
- child_value = duckdb_get_list_child(val, i);
680
- elem = rbduckdb_duckdb_value_to_ruby(child_value);
681
- duckdb_destroy_value(&child_value);
712
+ elem = rbduckdb_duckdb_value_to_ruby_and_destroy(duckdb_get_list_child(val, i));
682
713
  rb_ary_push(result, elem);
683
714
  }
684
715
  break;
@@ -687,23 +718,18 @@ VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val) {
687
718
  result = rb_hash_new();
688
719
  for (i = 0; i < size; i++) {
689
720
  str = duckdb_struct_type_child_name(logical_type, i);
690
- child_value = duckdb_get_struct_child(val, i);
691
- elem = rbduckdb_duckdb_value_to_ruby(child_value);
692
- duckdb_destroy_value(&child_value);
693
- rb_hash_aset(result, ID2SYM(rb_intern(str)), elem);
721
+ key = ID2SYM(rb_intern(str));
694
722
  duckdb_free(str);
723
+ elem = rbduckdb_duckdb_value_to_ruby_and_destroy(duckdb_get_struct_child(val, i));
724
+ rb_hash_aset(result, key, elem);
695
725
  }
696
726
  break;
697
727
  case DUCKDB_TYPE_MAP:
698
728
  size = duckdb_get_map_size(val);
699
729
  result = rb_hash_new();
700
730
  for (i = 0; i < size; i++) {
701
- child_value = duckdb_get_map_key(val, i);
702
- key = rbduckdb_duckdb_value_to_ruby(child_value);
703
- duckdb_destroy_value(&child_value);
704
- child_value = duckdb_get_map_value(val, i);
705
- elem = rbduckdb_duckdb_value_to_ruby(child_value);
706
- duckdb_destroy_value(&child_value);
731
+ key = rbduckdb_duckdb_value_to_ruby_and_destroy(duckdb_get_map_key(val, i));
732
+ elem = rbduckdb_duckdb_value_to_ruby_and_destroy(duckdb_get_map_value(val, i));
707
733
  rb_hash_aset(result, key, elem);
708
734
  }
709
735
  break;
data/ext/duckdb/value.h CHANGED
@@ -10,6 +10,7 @@ typedef struct _rubyDuckDBValue rubyDuckDBValue;
10
10
  void rbduckdb_init_value(void);
11
11
  VALUE rbduckdb_value_new(duckdb_value value);
12
12
  VALUE rbduckdb_duckdb_value_to_ruby(duckdb_value val);
13
+ VALUE rbduckdb_duckdb_value_to_ruby_and_destroy(duckdb_value val);
13
14
  rubyDuckDBValue *rbduckdb_get_struct_value(VALUE obj);
14
15
 
15
16
  #endif
@@ -7,10 +7,9 @@ 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
10
+ # The +table+ argument (2nd positional argument) is used as a literal table name,
11
+ # even if it contains a dot. Use the +schema:+ and +catalog:+ keywords to qualify the table.
12
+ # Surrounding double or single quotes (<tt>'"a.b"'</tt> or <tt>"'a.b'"</tt>) are stripped.
14
13
  #
15
14
  # require 'duckdb'
16
15
  # db = DuckDB::Database.open
@@ -26,13 +25,10 @@ module DuckDB
26
25
  alias from_query create_query
27
26
  end
28
27
 
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
28
+ def initialize(con, table, schema: nil, catalog: nil)
29
+ raise DuckDB::Error, '2nd argument must be table name.' if table.nil?
30
+
31
+ initialize_with_parsed_table(con, table, schema: schema, catalog: catalog)
36
32
  end
37
33
 
38
34
  # :call-seq:
@@ -801,14 +797,6 @@ module DuckDB
801
797
 
802
798
  private
803
799
 
804
- def warn_deprecated_3arg # :nodoc:
805
- warn(
806
- 'DuckDB::Appender.new(con, schema, table) is deprecated. ' \
807
- 'Use DuckDB::Appender.new(con, table, schema: schema) instead.',
808
- category: :deprecated
809
- )
810
- end
811
-
812
800
  def initialize_with_parsed_table(con, table_name, schema:, catalog:) # :nodoc:
813
801
  table_name, schema, catalog = parse_table_name(table_name, schema, catalog)
814
802
  if catalog
data/lib/duckdb/config.rb CHANGED
@@ -6,7 +6,7 @@ module DuckDB
6
6
  # require 'duckdb'
7
7
  # config = DuckDB::Config.new
8
8
  # config['default_order'] = 'DESC'
9
- # db = DuckDB::Database.open(nil, config)
9
+ # db = DuckDB::Database.open(config: config)
10
10
  # con = db.connect
11
11
  # con.query('CREATE TABLE numbers (number INTEGER)')
12
12
  # con.query('INSERT INTO numbers VALUES (2), (1), (4), (3)')
@@ -49,7 +49,7 @@ module DuckDB
49
49
  # # config.set_config('default_order', 'DESC')
50
50
  # config['default_order'] = 'DESC'
51
51
  #
52
- # db = DuckDB::Database.open(nil, config)
52
+ # db = DuckDB::Database.open(config: config)
53
53
  # con = db.connect
54
54
  # con.query('CREATE TABLE numbers (number INTEGER)')
55
55
  # con.query('INSERT INTO numbers VALUES (2), (1), (4), (3)')
@@ -8,8 +8,6 @@ module DuckDB
8
8
  # con = db.connect
9
9
  # con.query(sql)
10
10
  class Connection
11
- include DuckDB::TableNameParser
12
-
13
11
  # executes sql with args.
14
12
  # The first argument sql must be SQL string.
15
13
  # The rest arguments are parameters of SQL string.
@@ -84,11 +82,27 @@ module DuckDB
84
82
  # pending_result.execute_task while pending_result.state == :not_ready
85
83
  # result = pending_result.execute_pending
86
84
  # result.each.first
85
+ #
86
+ # If a block is given, the block is executed with the DuckDB::PendingResult
87
+ # object, the block's value is returned, and the object is destroyed
88
+ # after the block. The yielded PendingResult must not be used after the block.
89
+ #
90
+ # con.async_query(sql, name: 'Dave', email: 'dave@example.com') do |pending|
91
+ # pending.execute_task while pending.state == :not_ready
92
+ # pending.execute_pending.each.first
93
+ # end
87
94
  def async_query(sql, *args, **kwargs)
88
- prepare(sql) do |stmt|
95
+ pending = prepare(sql) do |stmt|
89
96
  stmt.bind_args(*args, **kwargs)
90
97
  stmt.pending_prepared
91
98
  end
99
+ return pending unless block_given?
100
+
101
+ begin
102
+ yield pending
103
+ ensure
104
+ pending.destroy
105
+ end
92
106
  end
93
107
 
94
108
  # connects DuckDB database
@@ -140,8 +154,8 @@ module DuckDB
140
154
  #
141
155
  # Raises DuckDB::Error if the table (or schema/catalog) does not exist.
142
156
  #
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.
157
+ # Table name parsing (quoting) is handled by DuckDB::Appender.new.
158
+ # A table name containing a dot is used literally; use +schema:+ to qualify the table.
145
159
  #
146
160
  # require 'duckdb'
147
161
  # db = DuckDB::Database.open
@@ -164,7 +178,6 @@ module DuckDB
164
178
  # appender.append_row(4, 'Dave')
165
179
  # appender.close
166
180
  def appender(table, schema: nil, catalog: nil, &)
167
- table, schema, catalog = parse_connection_appender_table(table, schema, catalog)
168
181
  run_appender_block(Appender.new(self, table, schema: schema, catalog: catalog), &)
169
182
  end
170
183
 
@@ -466,17 +479,6 @@ module DuckDB
466
479
  appender.close
467
480
  end
468
481
 
469
- # Silently pre-parses dot-notation so Appender.new receives clean values
470
- # and does not emit a misleading "DuckDB::Appender.new" warning.
471
- # con.appender('a.b') has always split on dot — no warning needed.
472
- # Quoted table names pass through unchanged for Appender.new to handle.
473
- def parse_connection_appender_table(table, schema, catalog)
474
- return [table, schema, catalog] if quoted_table_name?(table)
475
- return [table, schema, catalog] unless table.include?('.')
476
-
477
- dot_notation_split(table, schema, catalog)
478
- end
479
-
480
482
  alias execute query
481
483
  alias async_execute async_query
482
484
  alias open connect
@@ -50,9 +50,7 @@ module DuckDB
50
50
  # con = db.connect
51
51
  # con.query('CREATE TABLE users (id INTEGER, name VARCHAR(30))')
52
52
  # end
53
- def open(path = :memory, *args, config: nil)
54
- path, config = _handle_deprecated_open_args(path, args, config)
55
-
53
+ def open(path = :memory, config: nil)
56
54
  db = new(path, config: config)
57
55
  return db unless block_given?
58
56
 
@@ -62,21 +60,6 @@ module DuckDB
62
60
  db.close
63
61
  end
64
62
  end
65
-
66
- private
67
-
68
- def _handle_deprecated_open_args(path, args, config)
69
- unless args.empty?
70
- raise TypeError, "expected DuckDB::Config, got #{args.first.class}" unless args.first.is_a?(DuckDB::Config)
71
-
72
- warn 'DuckDB::Database.open(path, config) is deprecated. ' \
73
- 'Use DuckDB::Database.open(path, config: config) instead.'
74
- config = args.first
75
- end
76
-
77
- path = :memory if path.nil?
78
- [path, config]
79
- end
80
63
  end
81
64
 
82
65
  private
@@ -103,12 +103,10 @@ module DuckDB
103
103
 
104
104
  def matched_to_i(matched) # :nodoc:
105
105
  sign = to_sign(matched)
106
- sec = to_sec(matched)
106
+ year, month, day, hour, min, sec = %i[year month day hour min sec].map { |k| matched[k].to_i }
107
107
  usec = to_usec(matched)
108
108
  usec *= -1 if sec.negative?
109
- value = [
110
- to_year(matched), to_month(matched), to_day(matched), to_hour(matched), to_min(matched), sec, usec
111
- ]
109
+ value = [year, month, day, hour, min, sec, usec]
112
110
  sign.positive? ? value : value.map { |v| v * sign }
113
111
  end
114
112
 
@@ -116,30 +114,6 @@ module DuckDB
116
114
  matched[:negativ] == '-' ? -1 : 1
117
115
  end
118
116
 
119
- def to_year(matched) # :nodoc:
120
- matched[:year].to_i
121
- end
122
-
123
- def to_month(matched) # :nodoc:
124
- matched[:month].to_i
125
- end
126
-
127
- def to_day(matched) # :nodoc:
128
- matched[:day].to_i
129
- end
130
-
131
- def to_hour(matched) # :nodoc:
132
- matched[:hour].to_i
133
- end
134
-
135
- def to_min(matched) # :nodoc:
136
- matched[:min].to_i
137
- end
138
-
139
- def to_sec(matched) # :nodoc:
140
- matched[:sec].to_i
141
- end
142
-
143
117
  def to_usec(matched) # :nodoc:
144
118
  matched[:usec].to_s.ljust(6, '0')[0, 6].to_i
145
119
  end
@@ -18,6 +18,13 @@ module DuckDB
18
18
  # pending_result.execute_task
19
19
  # end
20
20
  # result = pending_result.execute_pending
21
+ #
22
+ # Call +destroy+ to release a pending result without fetching it. On
23
+ # DuckDB >= 2.0 an unfetched pending result blocks its connection until it
24
+ # is destroyed. After +destroy+, +state+ (and +execution_finished?+) report
25
+ # :error, and +execute_task+, +execute_pending+ and +execute_check_state+
26
+ # raise DuckDB::Error. A DuckDB::Result already returned by
27
+ # +execute_pending+ stays readable.
21
28
  class PendingResult
22
29
  STATES = %i[ready not_ready error no_tasks].freeze # :nodoc:
23
30
 
@@ -332,6 +332,11 @@ module DuckDB
332
332
  decimal = _parse_deciaml(value)
333
333
  lower, upper = decimal_to_hugeint(decimal)
334
334
  width = _decimal_width(decimal)
335
+ # _bind_decimal narrows width to uint8, and DuckDB aborts on a mismatched width.
336
+ unless RANGE_DECIMAL_WIDTH.cover?(width)
337
+ raise ArgumentError, "value out of range for DECIMAL width (#{RANGE_DECIMAL_WIDTH})"
338
+ end
339
+
335
340
  _bind_decimal(index, lower, upper, width, decimal.scale)
336
341
  end
337
342