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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +1 -1
- data/ext/duckdb/aggregate_function.c +40 -78
- data/ext/duckdb/appender.c +39 -126
- data/ext/duckdb/arrow_array_stream.c +10 -2
- data/ext/duckdb/connection.c +17 -54
- data/ext/duckdb/conveter.c +39 -76
- data/ext/duckdb/expression.c +1 -4
- data/ext/duckdb/function_executor.c +214 -248
- data/ext/duckdb/function_executor.h +10 -0
- data/ext/duckdb/function_vector.c +26 -0
- data/ext/duckdb/function_vector.h +18 -0
- data/ext/duckdb/logical_type.c +88 -170
- data/ext/duckdb/pending_result.c +53 -14
- data/ext/duckdb/prepared_statement.c +37 -138
- data/ext/duckdb/result.c +160 -162
- data/ext/duckdb/result.h +2 -0
- data/ext/duckdb/scalar_function.c +9 -69
- data/ext/duckdb/table_function.c +33 -82
- data/ext/duckdb/table_function_bind_info.c +2 -12
- data/ext/duckdb/util.c +53 -2
- data/ext/duckdb/value.c +46 -20
- data/ext/duckdb/value.h +1 -0
- data/lib/duckdb/appender.rb +7 -19
- data/lib/duckdb/config.rb +2 -2
- data/lib/duckdb/connection.rb +19 -17
- data/lib/duckdb/database.rb +1 -18
- data/lib/duckdb/interval.rb +2 -28
- data/lib/duckdb/pending_result.rb +7 -0
- data/lib/duckdb/prepared_statement.rb +5 -0
- data/lib/duckdb/result.rb +0 -29
- data/lib/duckdb/scalar_function_set.rb +1 -2
- data/lib/duckdb/table_description.rb +3 -3
- data/lib/duckdb/table_name_parser.rb +6 -36
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +0 -14
- metadata +1 -1
data/ext/duckdb/table_function.c
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
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
|
|
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 (
|
|
475
|
-
duckdb_init_set_init_data(info,
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
duckdb_destroy_value(¶m_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
|
-
|
|
118
|
-
|
|
119
|
-
duckdb_destroy_value(¶m_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
|
|
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
|
-
|
|
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
|
-
|
|
561
|
-
VALUE result;
|
|
569
|
+
struct vector_ref_arg arg;
|
|
562
570
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
702
|
-
|
|
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
|
data/lib/duckdb/appender.rb
CHANGED
|
@@ -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)
|
|
11
|
-
#
|
|
12
|
-
#
|
|
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,
|
|
30
|
-
if table
|
|
31
|
-
|
|
32
|
-
|
|
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(
|
|
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(
|
|
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)')
|
data/lib/duckdb/connection.rb
CHANGED
|
@@ -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
|
|
144
|
-
#
|
|
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
|
data/lib/duckdb/database.rb
CHANGED
|
@@ -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,
|
|
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
|
data/lib/duckdb/interval.rb
CHANGED
|
@@ -103,12 +103,10 @@ module DuckDB
|
|
|
103
103
|
|
|
104
104
|
def matched_to_i(matched) # :nodoc:
|
|
105
105
|
sign = to_sign(matched)
|
|
106
|
-
sec =
|
|
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
|
|