duckdb 2.0.0.0 → 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.
@@ -18,11 +18,6 @@ static VALUE scalar_function__set_bind(VALUE self);
18
18
  static void scalar_function_callback(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output);
19
19
  static void scalar_function_bind_callback(duckdb_bind_info info);
20
20
  #ifdef HAVE_DUCKDB_H_GE_V1_5_0
21
- /*
22
- * Thread detection functions (available since Ruby 2.3).
23
- * Used to skip the proxy on Ruby threads.
24
- */
25
- extern int ruby_native_thread_p(void);
26
21
  static void scalar_function_init_callback(duckdb_init_info info);
27
22
  #endif
28
23
 
@@ -251,15 +246,8 @@ static VALUE process_rows(VALUE varg) {
251
246
  * a GC, and the earlier columns' objects must stay reachable. */
252
247
  VALUE args = rb_ary_new_capa((long)arg->col_count);
253
248
 
254
- /* Allocate arrays to hold input vectors and their types */
255
- arg->input_vectors = ALLOC_N(duckdb_vector, arg->col_count);
256
- arg->input_types = ALLOC_N(duckdb_logical_type, arg->col_count);
257
-
258
249
  /* Get all input vectors and their types */
259
- for (j = 0; j < arg->col_count; j++) {
260
- arg->input_vectors[j] = duckdb_data_chunk_get_vector(arg->input, j);
261
- arg->input_types[j] = duckdb_vector_get_column_type(arg->input_vectors[j]);
262
- }
250
+ rbduckdb_input_columns_load(arg->input, arg->col_count, &arg->input_vectors, &arg->input_types);
263
251
 
264
252
  /* Process each row */
265
253
  for (i = 0; i < arg->row_count; i++) {
@@ -282,24 +270,10 @@ static VALUE process_rows(VALUE varg) {
282
270
 
283
271
  static VALUE cleanup_callback(VALUE varg) {
284
272
  struct callback_arg *arg = (struct callback_arg *)varg;
285
- idx_t j;
286
273
 
287
- /* Destroy all logical types */
288
- if (arg->input_types != NULL) {
289
- for (j = 0; j < arg->col_count; j++) {
290
- duckdb_destroy_logical_type(&arg->input_types[j]);
291
- }
292
- }
274
+ rbduckdb_input_columns_free(arg->input_vectors, arg->input_types, arg->col_count);
293
275
  duckdb_destroy_logical_type(&arg->output_type);
294
276
 
295
- /* Free allocated memory */
296
- if (arg->input_types != NULL) {
297
- xfree(arg->input_types);
298
- }
299
- if (arg->input_vectors != NULL) {
300
- xfree(arg->input_vectors);
301
- }
302
-
303
277
  return Qnil;
304
278
  }
305
279
 
@@ -307,50 +281,16 @@ static VALUE cleanup_callback(VALUE varg) {
307
281
  /*
308
282
  * Per-worker init for the execute path (DuckDB >= 1.5.0).
309
283
  *
310
- * DuckDB calls this once on each worker thread that will run the execute
311
- * callback. We create a per-worker proxy (allocating its Ruby thread under the
312
- * GVL via the global executor, since this runs on a non-Ruby thread) and store
313
- * it as per-worker state. The execute callback then dispatches through it
314
- * instead of the shared global executor, so workers run callbacks concurrently.
315
- * DuckDB invokes rbduckdb_worker_proxy_destroy when the state is freed.
284
+ * Runs once on each worker thread that will execute the callback. Creates the
285
+ * worker's proxy and stores it as per-worker state, so the execute callback
286
+ * dispatches through it rather than the shared global executor. DuckDB calls
287
+ * rbduckdb_worker_proxy_destroy when the state is freed.
316
288
  */
317
- struct create_proxy_callback_arg {
318
- struct worker_proxy *proxy;
319
- };
320
-
321
- static VALUE create_proxy_callback(VALUE varg) {
322
- struct create_proxy_callback_arg *arg = (struct create_proxy_callback_arg *)varg;
323
- arg->proxy = rbduckdb_worker_proxy_create();
324
- return Qnil;
325
- }
326
-
327
- /*
328
- * rbduckdb_worker_proxy_create may raise (NoMemError, Thread.new failure),
329
- * and the executor runs callbacks unprotected — a raise would longjmp past
330
- * its done-signaling and block the waiting DuckDB worker forever. Swallow
331
- * the exception instead: the proxy stays NULL, init sets no state, and the
332
- * execute callback falls back to the global executor.
333
- */
334
- static void create_proxy_callback_protected(void *user_data) {
335
- int exception_state;
336
-
337
- rb_protect(create_proxy_callback, (VALUE)user_data, &exception_state);
338
- if (exception_state) {
339
- rb_set_errinfo(Qnil);
340
- }
341
- }
342
-
343
289
  static void scalar_function_init_callback(duckdb_init_info info) {
344
- struct create_proxy_callback_arg arg;
345
-
346
- /* A Ruby calling thread runs the callback inline (Case 1/2); no proxy needed. */
347
- if (ruby_native_thread_p()) return;
348
-
349
- arg.proxy = NULL;
350
- rbduckdb_function_executor_dispatch(create_proxy_callback_protected, &arg);
290
+ struct worker_proxy *proxy = rbduckdb_worker_proxy_create_for_current_thread();
351
291
 
352
- if (arg.proxy != NULL) {
353
- duckdb_scalar_function_init_set_state(info, arg.proxy, rbduckdb_worker_proxy_destroy);
292
+ if (proxy != NULL) {
293
+ duckdb_scalar_function_init_set_state(info, proxy, rbduckdb_worker_proxy_destroy);
354
294
  }
355
295
  }
356
296
  #endif
@@ -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
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) {
@@ -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
@@ -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
 
@@ -11,8 +11,7 @@ module DuckDB
11
11
  def initialize(name)
12
12
  raise TypeError, "#{name.class} is not a String or Symbol" unless name.is_a?(String) || name.is_a?(Symbol)
13
13
 
14
- @name = name.to_s
15
- _initialize(@name)
14
+ _initialize(name.to_s)
16
15
  end
17
16
 
18
17
  # @param scalar_function [DuckDB::ScalarFunction] the overload to add
@@ -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 = '2.0.0.0'
6
+ VERSION = '2.0.0.1'
7
7
  end
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: 2.0.0.0
4
+ version: 2.0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masaki Suketa