duckdb 1.5.5.0 → 1.5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/ext/duckdb/aggregate_function.c +298 -60
- data/ext/duckdb/aggregate_function_set.c +3 -4
- data/ext/duckdb/appender.c +33 -7
- data/ext/duckdb/arrow_array_stream.c +6 -2
- data/ext/duckdb/arrow_import.c +1 -1
- data/ext/duckdb/client_context.c +1 -1
- data/ext/duckdb/column.c +1 -1
- data/ext/duckdb/column.h +0 -1
- data/ext/duckdb/config.c +1 -1
- data/ext/duckdb/connection.c +48 -22
- data/ext/duckdb/connection.h +2 -1
- data/ext/duckdb/data_chunk.c +1 -1
- data/ext/duckdb/database.c +80 -4
- data/ext/duckdb/database.h +12 -0
- data/ext/duckdb/expression.c +1 -1
- data/ext/duckdb/extracted_statements.c +1 -1
- data/ext/duckdb/function_executor.c +166 -32
- data/ext/duckdb/function_executor.h +18 -6
- data/ext/duckdb/instance_cache.c +85 -3
- data/ext/duckdb/instance_cache.h +6 -0
- data/ext/duckdb/logical_type.c +1 -1
- data/ext/duckdb/pending_result.c +6 -1
- data/ext/duckdb/prepared_statement.c +14 -2
- data/ext/duckdb/result.c +6 -1
- data/ext/duckdb/result.h +1 -1
- data/ext/duckdb/scalar_function.c +18 -25
- data/ext/duckdb/scalar_function_bind_info.c +1 -1
- data/ext/duckdb/scalar_function_set.c +3 -4
- data/ext/duckdb/table_description.c +1 -1
- data/ext/duckdb/table_function.c +16 -21
- data/ext/duckdb/table_function_bind_info.c +29 -1
- data/ext/duckdb/table_function_bind_info.h +1 -0
- data/ext/duckdb/table_function_function_info.c +23 -1
- data/ext/duckdb/table_function_init_info.c +20 -1
- data/ext/duckdb/value.c +2 -2
- data/ext/duckdb/vector.c +1 -1
- data/lib/duckdb/appender.rb +1 -2
- data/lib/duckdb/converter/int_to_sym.rb +3 -0
- data/lib/duckdb/converter.rb +1 -2
- data/lib/duckdb/data_chunk.rb +1 -2
- data/lib/duckdb/prepared_statement.rb +1 -2
- data/lib/duckdb/table_function/function_info.rb +1 -2
- data/lib/duckdb/table_function/init_info.rb +1 -2
- data/lib/duckdb/table_function.rb +1 -2
- data/lib/duckdb/vector.rb +1 -2
- data/lib/duckdb/version.rb +1 -1
- metadata +2 -2
|
@@ -76,6 +76,52 @@ static int g_executor_started = 0;
|
|
|
76
76
|
*/
|
|
77
77
|
static VALUE g_proxy_threads = Qnil;
|
|
78
78
|
|
|
79
|
+
/*
|
|
80
|
+
* Replace embedded NUL bytes so the message can be handed to a C API.
|
|
81
|
+
* Returns str unchanged when there is nothing to replace.
|
|
82
|
+
*/
|
|
83
|
+
static VALUE message_without_nul(VALUE str) {
|
|
84
|
+
long len = RSTRING_LEN(str);
|
|
85
|
+
long i;
|
|
86
|
+
VALUE copy;
|
|
87
|
+
char *p;
|
|
88
|
+
|
|
89
|
+
if (memchr(RSTRING_PTR(str), '\0', (size_t)len) == NULL) {
|
|
90
|
+
return str;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
copy = rb_str_new(RSTRING_PTR(str), len);
|
|
94
|
+
p = RSTRING_PTR(copy);
|
|
95
|
+
for (i = 0; i < len; i++) {
|
|
96
|
+
if (p[i] == '\0') {
|
|
97
|
+
p[i] = ' ';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return copy;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static VALUE pending_error_message(VALUE errinfo) {
|
|
104
|
+
return message_without_nul(rb_obj_as_string(rb_funcall(errinfo, rb_intern("message"), 0)));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
VALUE rbduckdb_pending_error_message(void) {
|
|
108
|
+
VALUE errinfo = rb_errinfo();
|
|
109
|
+
VALUE msg;
|
|
110
|
+
int state = 0;
|
|
111
|
+
|
|
112
|
+
rb_set_errinfo(Qnil);
|
|
113
|
+
if (errinfo == Qnil) {
|
|
114
|
+
return rb_str_new_cstr("unknown error");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
msg = rb_protect(pending_error_message, errinfo, &state);
|
|
118
|
+
if (state) {
|
|
119
|
+
rb_set_errinfo(Qnil);
|
|
120
|
+
return rb_str_new_cstr("an exception was raised, but its message could not be read");
|
|
121
|
+
}
|
|
122
|
+
return msg;
|
|
123
|
+
}
|
|
124
|
+
|
|
79
125
|
/* Data passed to the executor wait function */
|
|
80
126
|
struct executor_wait_data {
|
|
81
127
|
struct callback_request *request;
|
|
@@ -130,6 +176,49 @@ static void executor_stop_func(void *data) {
|
|
|
130
176
|
#endif
|
|
131
177
|
}
|
|
132
178
|
|
|
179
|
+
/*
|
|
180
|
+
* Discard an exception a dispatched callback let escape, so that one bad
|
|
181
|
+
* callback cannot take a dispatcher thread down with it and strand every
|
|
182
|
+
* later callback. Interrupt, SystemExit and thread kill still get through.
|
|
183
|
+
*/
|
|
184
|
+
static void discard_callback_exception(int state) {
|
|
185
|
+
VALUE err = rb_errinfo();
|
|
186
|
+
|
|
187
|
+
if (err != Qnil && !rb_obj_is_kind_of(err, rb_eStandardError)) {
|
|
188
|
+
rb_jump_tag(state);
|
|
189
|
+
}
|
|
190
|
+
rb_set_errinfo(Qnil);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static VALUE request_run_body(VALUE varg) {
|
|
194
|
+
struct callback_request *req = (struct callback_request *)varg;
|
|
195
|
+
req->cb(req->user_data);
|
|
196
|
+
return Qnil;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Runs from rb_ensure: the worker waits on done_cond even if the callback raised. */
|
|
200
|
+
static VALUE request_signal_done(VALUE varg) {
|
|
201
|
+
struct callback_request *req = (struct callback_request *)varg;
|
|
202
|
+
|
|
203
|
+
#ifdef _MSC_VER
|
|
204
|
+
EnterCriticalSection(&req->done_lock);
|
|
205
|
+
req->done = 1;
|
|
206
|
+
WakeConditionVariable(&req->done_cond);
|
|
207
|
+
LeaveCriticalSection(&req->done_lock);
|
|
208
|
+
#else
|
|
209
|
+
pthread_mutex_lock(&req->done_mutex);
|
|
210
|
+
req->done = 1;
|
|
211
|
+
pthread_cond_signal(&req->done_cond);
|
|
212
|
+
pthread_mutex_unlock(&req->done_mutex);
|
|
213
|
+
#endif
|
|
214
|
+
|
|
215
|
+
return Qnil;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static VALUE request_run(VALUE varg) {
|
|
219
|
+
return rb_ensure(request_run_body, varg, request_signal_done, varg);
|
|
220
|
+
}
|
|
221
|
+
|
|
133
222
|
/* The executor thread main loop (Ruby thread) */
|
|
134
223
|
static VALUE executor_thread_func(void *data) {
|
|
135
224
|
struct executor_wait_data w;
|
|
@@ -140,23 +229,12 @@ static VALUE executor_thread_func(void *data) {
|
|
|
140
229
|
rb_thread_call_without_gvl(executor_wait_func, &w, executor_stop_func, &w);
|
|
141
230
|
|
|
142
231
|
if (w.request != NULL) {
|
|
143
|
-
|
|
232
|
+
int state = 0;
|
|
144
233
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
#ifdef _MSC_VER
|
|
150
|
-
EnterCriticalSection(&req->done_lock);
|
|
151
|
-
req->done = 1;
|
|
152
|
-
WakeConditionVariable(&req->done_cond);
|
|
153
|
-
LeaveCriticalSection(&req->done_lock);
|
|
154
|
-
#else
|
|
155
|
-
pthread_mutex_lock(&req->done_mutex);
|
|
156
|
-
req->done = 1;
|
|
157
|
-
pthread_cond_signal(&req->done_cond);
|
|
158
|
-
pthread_mutex_unlock(&req->done_mutex);
|
|
159
|
-
#endif
|
|
234
|
+
rb_protect(request_run, (VALUE)w.request, &state);
|
|
235
|
+
if (state) {
|
|
236
|
+
discard_callback_exception(state);
|
|
237
|
+
}
|
|
160
238
|
}
|
|
161
239
|
}
|
|
162
240
|
|
|
@@ -326,6 +404,37 @@ static void proxy_stop_func(void *data) {
|
|
|
326
404
|
#endif
|
|
327
405
|
}
|
|
328
406
|
|
|
407
|
+
static VALUE proxy_run_body(VALUE data) {
|
|
408
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
409
|
+
proxy->cb(proxy->user_data);
|
|
410
|
+
return Qnil;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* Runs from rb_ensure: see request_signal_done. */
|
|
414
|
+
static VALUE proxy_signal_done(VALUE data) {
|
|
415
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
416
|
+
|
|
417
|
+
#ifdef _MSC_VER
|
|
418
|
+
EnterCriticalSection(&proxy->lock);
|
|
419
|
+
proxy->has_request = 0;
|
|
420
|
+
proxy->request_done = 1;
|
|
421
|
+
WakeConditionVariable(&proxy->request_done_cond);
|
|
422
|
+
LeaveCriticalSection(&proxy->lock);
|
|
423
|
+
#else
|
|
424
|
+
pthread_mutex_lock(&proxy->lock);
|
|
425
|
+
proxy->has_request = 0;
|
|
426
|
+
proxy->request_done = 1;
|
|
427
|
+
pthread_cond_signal(&proxy->request_done_cond);
|
|
428
|
+
pthread_mutex_unlock(&proxy->lock);
|
|
429
|
+
#endif
|
|
430
|
+
|
|
431
|
+
return Qnil;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
static VALUE proxy_run(VALUE data) {
|
|
435
|
+
return rb_ensure(proxy_run_body, data, proxy_signal_done, data);
|
|
436
|
+
}
|
|
437
|
+
|
|
329
438
|
/* The proxy thread main loop. Runs as the body of rb_ensure (see below). */
|
|
330
439
|
static VALUE proxy_loop_body(VALUE data) {
|
|
331
440
|
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
@@ -337,23 +446,12 @@ static VALUE proxy_loop_body(VALUE data) {
|
|
|
337
446
|
if (proxy->stop_requested) break;
|
|
338
447
|
|
|
339
448
|
if (proxy->has_request) {
|
|
340
|
-
|
|
341
|
-
proxy->cb(proxy->user_data);
|
|
449
|
+
int state = 0;
|
|
342
450
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
proxy->request_done = 1;
|
|
348
|
-
WakeConditionVariable(&proxy->request_done_cond);
|
|
349
|
-
LeaveCriticalSection(&proxy->lock);
|
|
350
|
-
#else
|
|
351
|
-
pthread_mutex_lock(&proxy->lock);
|
|
352
|
-
proxy->has_request = 0;
|
|
353
|
-
proxy->request_done = 1;
|
|
354
|
-
pthread_cond_signal(&proxy->request_done_cond);
|
|
355
|
-
pthread_mutex_unlock(&proxy->lock);
|
|
356
|
-
#endif
|
|
451
|
+
rb_protect(proxy_run, data, &state);
|
|
452
|
+
if (state) {
|
|
453
|
+
discard_callback_exception(state);
|
|
454
|
+
}
|
|
357
455
|
}
|
|
358
456
|
}
|
|
359
457
|
|
|
@@ -565,3 +663,39 @@ void rbduckdb_function_executor_dispatch_via_proxy(rbduckdb_function_callback_t
|
|
|
565
663
|
void rbduckdb_function_executor_dispatch(rbduckdb_function_callback_t cb, void *user_data) {
|
|
566
664
|
rbduckdb_function_executor_dispatch_via_proxy(cb, user_data, NULL);
|
|
567
665
|
}
|
|
666
|
+
|
|
667
|
+
static VALUE g_function_data_registry = Qnil;
|
|
668
|
+
static unsigned long long g_function_data_next_id = 0;
|
|
669
|
+
|
|
670
|
+
static void function_data_registry_ensure(void) {
|
|
671
|
+
if (g_function_data_registry == Qnil) {
|
|
672
|
+
g_function_data_registry = rb_hash_new();
|
|
673
|
+
rb_global_variable(&g_function_data_registry);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
void *rbduckdb_function_data_register(VALUE value) {
|
|
678
|
+
unsigned long long id;
|
|
679
|
+
|
|
680
|
+
function_data_registry_ensure();
|
|
681
|
+
id = ++g_function_data_next_id;
|
|
682
|
+
rb_hash_aset(g_function_data_registry, ULL2NUM(id), value);
|
|
683
|
+
return (void *)(uintptr_t)id;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
VALUE rbduckdb_function_data_lookup(void *data) {
|
|
687
|
+
if (data == NULL || g_function_data_registry == Qnil) {
|
|
688
|
+
return Qnil;
|
|
689
|
+
}
|
|
690
|
+
return rb_hash_aref(g_function_data_registry, ULL2NUM((unsigned long long)(uintptr_t)data));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
void rbduckdb_function_data_release(void *data) {
|
|
694
|
+
if (g_function_data_registry == Qnil) return;
|
|
695
|
+
rb_hash_delete(g_function_data_registry, ULL2NUM((unsigned long long)(uintptr_t)data));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
void rbduckdb_function_data_destroy(void *data) {
|
|
699
|
+
if (data == NULL) return;
|
|
700
|
+
rbduckdb_function_executor_dispatch(rbduckdb_function_data_release, data);
|
|
701
|
+
}
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* A generic callback to be executed with the GVL held.
|
|
19
19
|
* The user_data pointer is passed through unchanged from the dispatch call.
|
|
20
20
|
*
|
|
21
|
-
* The callback
|
|
22
|
-
*
|
|
21
|
+
* The callback should still catch its own Ruby exceptions (e.g. via rb_protect)
|
|
22
|
+
* to report them to DuckDB. The dispatcher only guarantees that an exception it
|
|
23
|
+
* lets escape wakes the waiting worker instead of stranding it.
|
|
23
24
|
*/
|
|
24
25
|
typedef void (*rbduckdb_function_callback_t)(void *user_data);
|
|
25
26
|
|
|
@@ -66,10 +67,8 @@ struct worker_proxy;
|
|
|
66
67
|
* (typically by dispatching this through the global executor from a per-worker
|
|
67
68
|
* init callback, which itself runs on a non-Ruby thread).
|
|
68
69
|
*
|
|
69
|
-
* May raise (NoMemError, Thread.new failure)
|
|
70
|
-
*
|
|
71
|
-
* otherwise a raise longjmps past the executor's done-signaling and the
|
|
72
|
-
* waiting DuckDB worker blocks forever.
|
|
70
|
+
* May raise (NoMemError, Thread.new failure), so a wrapper dispatched to the
|
|
71
|
+
* executor should rb_protect this call and fall back to the global executor.
|
|
73
72
|
*/
|
|
74
73
|
struct worker_proxy *rbduckdb_worker_proxy_create(void);
|
|
75
74
|
|
|
@@ -87,4 +86,17 @@ void rbduckdb_worker_proxy_destroy(void *proxy);
|
|
|
87
86
|
*/
|
|
88
87
|
void rbduckdb_function_executor_dispatch_via_proxy(rbduckdb_function_callback_t cb, void *user_data, struct worker_proxy *proxy);
|
|
89
88
|
|
|
89
|
+
/*
|
|
90
|
+
* Take the pending Ruby exception's message as a String safe to hand to a
|
|
91
|
+
* DuckDB set_error entry point, and clear the exception. Always returns a
|
|
92
|
+
* String with no embedded NUL, so StringValueCStr on it cannot raise; #message
|
|
93
|
+
* runs under rb_protect, so neither can a hostile exception object.
|
|
94
|
+
*/
|
|
95
|
+
VALUE rbduckdb_pending_error_message(void);
|
|
96
|
+
|
|
97
|
+
void *rbduckdb_function_data_register(VALUE value);
|
|
98
|
+
VALUE rbduckdb_function_data_lookup(void *data);
|
|
99
|
+
void rbduckdb_function_data_release(void *data);
|
|
100
|
+
void rbduckdb_function_data_destroy(void *data);
|
|
101
|
+
|
|
90
102
|
#endif
|
data/ext/duckdb/instance_cache.c
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
VALUE cDuckDBInstanceCache;
|
|
4
4
|
|
|
5
5
|
static void deallocate(void * ctx);
|
|
6
|
+
static void mark(void *ctx);
|
|
7
|
+
static void compact(void *ctx);
|
|
6
8
|
static VALUE allocate(VALUE klass);
|
|
9
|
+
static VALUE memoizable_path(VALUE vpath);
|
|
10
|
+
static VALUE find_cached_wrapper(rubyDuckDBInstanceCache *ctx, VALUE vpath);
|
|
7
11
|
static size_t memsize(const void *p);
|
|
8
12
|
static VALUE instance_cache_initialize(VALUE self);
|
|
9
13
|
static VALUE instance_cache_get_or_create(int argc, VALUE *argv, VALUE self);
|
|
@@ -11,8 +15,8 @@ static VALUE instance_cache_destroy(VALUE self);
|
|
|
11
15
|
|
|
12
16
|
static const rb_data_type_t instance_cache_data_type = {
|
|
13
17
|
"DuckDB/InstanceCache",
|
|
14
|
-
{
|
|
15
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
18
|
+
{mark, deallocate, memsize, compact},
|
|
19
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
static void deallocate(void * ctx) {
|
|
@@ -24,15 +28,62 @@ static void deallocate(void * ctx) {
|
|
|
24
28
|
xfree(p);
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
static void mark(void *ctx) {
|
|
32
|
+
rubyDuckDBInstanceCache *p = (rubyDuckDBInstanceCache *)ctx;
|
|
33
|
+
|
|
34
|
+
rb_gc_mark_movable(p->wrappers);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static void compact(void *ctx) {
|
|
38
|
+
rubyDuckDBInstanceCache *p = (rubyDuckDBInstanceCache *)ctx;
|
|
39
|
+
|
|
40
|
+
p->wrappers = rb_gc_location(p->wrappers);
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
static size_t memsize(const void *p) {
|
|
28
44
|
return sizeof(rubyDuckDBInstanceCache);
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
static VALUE allocate(VALUE klass) {
|
|
32
48
|
rubyDuckDBInstanceCache *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBInstanceCache));
|
|
49
|
+
ctx->wrappers = Qnil;
|
|
33
50
|
return TypedData_Wrap_Struct(klass, &instance_cache_data_type, ctx);
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
/*
|
|
54
|
+
* A path is memoizable only if two get_or_create calls on it share one DuckDB
|
|
55
|
+
* instance. An absent, empty or ":memory:" path gets a fresh instance every
|
|
56
|
+
* time, so those wrappers must stay distinct. Returns the path or Qnil.
|
|
57
|
+
*/
|
|
58
|
+
static VALUE memoizable_path(VALUE vpath) {
|
|
59
|
+
if (NIL_P(vpath) || RSTRING_LEN(vpath) == 0) {
|
|
60
|
+
return Qnil;
|
|
61
|
+
}
|
|
62
|
+
if (rb_str_cmp(vpath, rb_str_new_cstr(":memory:")) == 0) {
|
|
63
|
+
return Qnil;
|
|
64
|
+
}
|
|
65
|
+
return vpath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* The wrapper this cache already holds for vpath, or Qnil. Entries whose
|
|
70
|
+
* wrapper has been collected are gone from the weak set already.
|
|
71
|
+
*/
|
|
72
|
+
static VALUE find_cached_wrapper(rubyDuckDBInstanceCache *ctx, VALUE vpath) {
|
|
73
|
+
VALUE wrappers = rb_funcall(ctx->wrappers, rb_intern("keys"), 0);
|
|
74
|
+
long i;
|
|
75
|
+
|
|
76
|
+
for (i = 0; i < RARRAY_LEN(wrappers); i++) {
|
|
77
|
+
VALUE wrapper = rb_ary_entry(wrappers, i);
|
|
78
|
+
rubyDuckDB *db_ctx = rbduckdb_get_struct_database(wrapper);
|
|
79
|
+
|
|
80
|
+
if (!NIL_P(db_ctx->cached_path) && rb_str_equal(db_ctx->cached_path, vpath) == Qtrue) {
|
|
81
|
+
return wrapper;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return Qnil;
|
|
85
|
+
}
|
|
86
|
+
|
|
36
87
|
static VALUE instance_cache_initialize(VALUE self) {
|
|
37
88
|
rubyDuckDBInstanceCache *ctx;
|
|
38
89
|
|
|
@@ -43,6 +94,11 @@ static VALUE instance_cache_initialize(VALUE self) {
|
|
|
43
94
|
rb_raise(eDuckDBError, "Failed to create instance cache");
|
|
44
95
|
}
|
|
45
96
|
|
|
97
|
+
RB_OBJ_WRITE(self, &ctx->wrappers,
|
|
98
|
+
rb_funcall(rb_const_get(rb_const_get(rb_cObject, rb_intern("ObjectSpace")),
|
|
99
|
+
rb_intern("WeakMap")),
|
|
100
|
+
rb_intern("new"), 0));
|
|
101
|
+
|
|
46
102
|
return self;
|
|
47
103
|
}
|
|
48
104
|
|
|
@@ -50,6 +106,8 @@ static VALUE instance_cache_initialize(VALUE self) {
|
|
|
50
106
|
static VALUE instance_cache_get_or_create(int argc, VALUE *argv, VALUE self) {
|
|
51
107
|
VALUE vpath = Qnil;
|
|
52
108
|
VALUE vconfig = Qnil;
|
|
109
|
+
VALUE memo_path;
|
|
110
|
+
VALUE obj;
|
|
53
111
|
const char *path = NULL;
|
|
54
112
|
char *error = NULL;
|
|
55
113
|
duckdb_config config = NULL;
|
|
@@ -79,7 +137,31 @@ static VALUE instance_cache_get_or_create(int argc, VALUE *argv, VALUE self) {
|
|
|
79
137
|
rb_raise(eDuckDBError, "Failed to get or create database from instance cache");
|
|
80
138
|
}
|
|
81
139
|
}
|
|
82
|
-
|
|
140
|
+
/*
|
|
141
|
+
* duckdb_get_or_create_from_cache hands back a fresh duckdb_database handle
|
|
142
|
+
* over an instance it may already have returned before. Registered functions
|
|
143
|
+
* live in that shared instance's catalog, but each wrapper retains them
|
|
144
|
+
* separately, so a second wrapper's collection frees functions the first
|
|
145
|
+
* one's connections can still resolve. One wrapper per instance is what the
|
|
146
|
+
* catalog's lifetime actually is, so reuse the wrapper we already have.
|
|
147
|
+
*/
|
|
148
|
+
memo_path = memoizable_path(vpath);
|
|
149
|
+
if (!NIL_P(memo_path)) {
|
|
150
|
+
VALUE cached = find_cached_wrapper(ctx, memo_path);
|
|
151
|
+
|
|
152
|
+
if (!NIL_P(cached)) {
|
|
153
|
+
/* Our existing wrapper keeps the instance alive; this handle is spare. */
|
|
154
|
+
duckdb_close(&db);
|
|
155
|
+
return cached;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
obj = rbduckdb_create_database_obj(db);
|
|
160
|
+
if (!NIL_P(memo_path)) {
|
|
161
|
+
rbduckdb_database_set_cache_entry(obj, memo_path, ctx->wrappers);
|
|
162
|
+
rb_funcall(ctx->wrappers, rb_intern("[]="), 2, obj, Qtrue);
|
|
163
|
+
}
|
|
164
|
+
return obj;
|
|
83
165
|
}
|
|
84
166
|
|
|
85
167
|
static VALUE instance_cache_destroy(VALUE self) {
|
data/ext/duckdb/instance_cache.h
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
struct _rubyDuckDBInstanceCache {
|
|
5
5
|
duckdb_instance_cache instance_cache;
|
|
6
|
+
/*
|
|
7
|
+
* ObjectSpace::WeakMap used as a weak set of the DuckDB::Database wrappers
|
|
8
|
+
* this cache has handed out for file paths. Weak so a wrapper nobody holds
|
|
9
|
+
* is still collected, and its DuckDB instance closed, as before.
|
|
10
|
+
*/
|
|
11
|
+
VALUE wrappers;
|
|
6
12
|
};
|
|
7
13
|
|
|
8
14
|
typedef struct _rubyDuckDBInstanceCache rubyDuckDBInstanceCache;
|
data/ext/duckdb/logical_type.c
CHANGED
|
@@ -35,7 +35,7 @@ static VALUE logical_type_initialize(VALUE self, VALUE type_id_arg);
|
|
|
35
35
|
static const rb_data_type_t logical_type_data_type = {
|
|
36
36
|
"DuckDB/LogicalType",
|
|
37
37
|
{NULL, deallocate, memsize,},
|
|
38
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
38
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
static void deallocate(void *ctx) {
|
data/ext/duckdb/pending_result.c
CHANGED
|
@@ -12,10 +12,15 @@ static VALUE pending_result_execution_finished_p(VALUE self);
|
|
|
12
12
|
static VALUE pending_result__state(VALUE self);
|
|
13
13
|
static VALUE pending_result__execute_check_state(VALUE self);
|
|
14
14
|
|
|
15
|
+
/*
|
|
16
|
+
* No RUBY_TYPED_FREE_IMMEDIATELY: deallocate can block, and must not run
|
|
17
|
+
* during GC sweep: the pending result holds a ClientContext reference; dropping the last
|
|
18
|
+
* one rolls back an open transaction and may shut the database down.
|
|
19
|
+
*/
|
|
15
20
|
static const rb_data_type_t pending_result_data_type = {
|
|
16
21
|
"DuckDB/PendingResult",
|
|
17
22
|
{NULL, deallocate, memsize,},
|
|
18
|
-
0, 0,
|
|
23
|
+
0, 0, RUBY_TYPED_WB_PROTECTED
|
|
19
24
|
};
|
|
20
25
|
|
|
21
26
|
static void deallocate(void *ctx) {
|
|
@@ -47,10 +47,15 @@ static VALUE prepared_statement__bind_uuid(VALUE self, VALUE vidx, VALUE val);
|
|
|
47
47
|
static VALUE prepared_statement__bind_decimal(VALUE self, VALUE vidx, VALUE lower, VALUE upper, VALUE width, VALUE scale);
|
|
48
48
|
static VALUE prepared_statement__bind_value(VALUE self, VALUE vidx, VALUE val);
|
|
49
49
|
|
|
50
|
+
/*
|
|
51
|
+
* No RUBY_TYPED_FREE_IMMEDIATELY: deallocate can block, and must not run
|
|
52
|
+
* during GC sweep: the statement holds a ClientContext reference; dropping the last one
|
|
53
|
+
* rolls back an open transaction and may shut the database down.
|
|
54
|
+
*/
|
|
50
55
|
static const rb_data_type_t prepared_statement_data_type = {
|
|
51
56
|
"DuckDB/PreparedStatement",
|
|
52
57
|
{NULL, deallocate, memsize,},
|
|
53
|
-
0, 0,
|
|
58
|
+
0, 0, RUBY_TYPED_WB_PROTECTED
|
|
54
59
|
};
|
|
55
60
|
|
|
56
61
|
static void destroy_prepared_statement(rubyDuckDBPreparedStatement *p) {
|
|
@@ -345,10 +350,17 @@ static VALUE prepared_statement_bind_double(VALUE self, VALUE vidx, VALUE val) {
|
|
|
345
350
|
static VALUE prepared_statement_bind_varchar(VALUE self, VALUE vidx, VALUE str) {
|
|
346
351
|
rubyDuckDBPreparedStatement *ctx;
|
|
347
352
|
idx_t idx = check_index(vidx);
|
|
353
|
+
const char *pstr;
|
|
354
|
+
long len;
|
|
348
355
|
|
|
349
356
|
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
350
357
|
|
|
351
|
-
|
|
358
|
+
/* Pass the byte length: duckdb_bind_varchar stops at the first NUL, so an
|
|
359
|
+
* embedded one stored a prefix of the String the caller had validated. */
|
|
360
|
+
pstr = StringValuePtr(str);
|
|
361
|
+
len = RSTRING_LEN(str);
|
|
362
|
+
|
|
363
|
+
if (duckdb_bind_varchar_length(ctx->prepared_statement, idx, pstr, (idx_t)len) == DuckDBError) {
|
|
352
364
|
rb_raise(eDuckDBError, "fail to bind %llu parameter", (unsigned long long)idx);
|
|
353
365
|
}
|
|
354
366
|
return self;
|
data/ext/duckdb/result.c
CHANGED
|
@@ -48,10 +48,15 @@ static VALUE vector_timestamp_tz(void* vector_data, idx_t row_idx);
|
|
|
48
48
|
static VALUE vector_uuid(void* vector_data, idx_t row_idx);
|
|
49
49
|
static VALUE vector_value(duckdb_vector vector, idx_t row_idx);
|
|
50
50
|
|
|
51
|
+
/*
|
|
52
|
+
* No RUBY_TYPED_FREE_IMMEDIATELY: deallocate can block, and must not run
|
|
53
|
+
* during GC sweep: a streaming result holds a ClientContext reference; dropping the last
|
|
54
|
+
* one rolls back an open transaction and may shut the database down.
|
|
55
|
+
*/
|
|
51
56
|
static const rb_data_type_t result_data_type = {
|
|
52
57
|
"DuckDB/Result",
|
|
53
58
|
{NULL, deallocate, memsize,},
|
|
54
|
-
0, 0,
|
|
59
|
+
0, 0, RUBY_TYPED_WB_PROTECTED
|
|
55
60
|
};
|
|
56
61
|
|
|
57
62
|
static void deallocate(void *ctx) {
|
data/ext/duckdb/result.h
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* object holds one reference, and each exported Arrow stream holds another,
|
|
7
7
|
* so the duckdb_result stays valid for consumers that outlive the Ruby
|
|
8
8
|
* objects. rbduckdb_result_unref() must not call any Ruby API: it runs from
|
|
9
|
-
* GC
|
|
9
|
+
* GC finalization (deallocate) and from Arrow stream release callbacks.
|
|
10
10
|
*/
|
|
11
11
|
struct _rubyDuckDBResult {
|
|
12
12
|
duckdb_result result;
|
|
@@ -35,7 +35,6 @@ struct callback_arg {
|
|
|
35
35
|
duckdb_logical_type output_type;
|
|
36
36
|
duckdb_vector *input_vectors;
|
|
37
37
|
duckdb_logical_type *input_types;
|
|
38
|
-
VALUE *args;
|
|
39
38
|
idx_t row_count;
|
|
40
39
|
idx_t col_count;
|
|
41
40
|
};
|
|
@@ -71,25 +70,22 @@ static void execute_callback_protected(void *user_data) {
|
|
|
71
70
|
|
|
72
71
|
rb_protect(execute_callback, (VALUE)arg, &exception_state);
|
|
73
72
|
if (exception_state) {
|
|
74
|
-
VALUE
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
duckdb_scalar_function_set_error(arg->info, StringValueCStr(msg));
|
|
78
|
-
}
|
|
79
|
-
rb_set_errinfo(Qnil);
|
|
73
|
+
VALUE msg = rbduckdb_pending_error_message();
|
|
74
|
+
duckdb_scalar_function_set_error(arg->info, StringValueCStr(msg));
|
|
75
|
+
RB_GC_GUARD(msg);
|
|
80
76
|
}
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
static const rb_data_type_t scalar_function_data_type = {
|
|
84
80
|
"DuckDB/ScalarFunction",
|
|
85
81
|
{mark, deallocate, memsize, compact},
|
|
86
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
82
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
87
83
|
};
|
|
88
84
|
|
|
89
85
|
static void mark(void *ctx) {
|
|
90
86
|
rubyDuckDBScalarFunction *p = (rubyDuckDBScalarFunction *)ctx;
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
rb_gc_mark_movable(p->function_proc);
|
|
88
|
+
rb_gc_mark_movable(p->bind_proc);
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
static void deallocate(void * ctx) {
|
|
@@ -223,7 +219,6 @@ static void scalar_function_callback(duckdb_function_info info, duckdb_data_chun
|
|
|
223
219
|
arg.output_type = duckdb_vector_get_column_type(output);
|
|
224
220
|
arg.input_vectors = NULL;
|
|
225
221
|
arg.input_types = NULL;
|
|
226
|
-
arg.args = NULL;
|
|
227
222
|
arg.row_count = duckdb_data_chunk_get_size(input);
|
|
228
223
|
arg.col_count = duckdb_data_chunk_get_column_count(input);
|
|
229
224
|
|
|
@@ -252,11 +247,13 @@ static VALUE process_rows(VALUE varg) {
|
|
|
252
247
|
struct callback_arg *arg = (struct callback_arg *)varg;
|
|
253
248
|
idx_t i, j;
|
|
254
249
|
VALUE result;
|
|
250
|
+
/* A Ruby Array, not a plain buffer: converting a later column can trigger
|
|
251
|
+
* a GC, and the earlier columns' objects must stay reachable. */
|
|
252
|
+
VALUE args = rb_ary_new_capa((long)arg->col_count);
|
|
255
253
|
|
|
256
254
|
/* Allocate arrays to hold input vectors and their types */
|
|
257
255
|
arg->input_vectors = ALLOC_N(duckdb_vector, arg->col_count);
|
|
258
256
|
arg->input_types = ALLOC_N(duckdb_logical_type, arg->col_count);
|
|
259
|
-
arg->args = ALLOC_N(VALUE, arg->col_count);
|
|
260
257
|
|
|
261
258
|
/* Get all input vectors and their types */
|
|
262
259
|
for (j = 0; j < arg->col_count; j++) {
|
|
@@ -268,16 +265,18 @@ static VALUE process_rows(VALUE varg) {
|
|
|
268
265
|
for (i = 0; i < arg->row_count; i++) {
|
|
269
266
|
/* Build arguments array for this row using vector_value_at */
|
|
270
267
|
for (j = 0; j < arg->col_count; j++) {
|
|
271
|
-
|
|
268
|
+
rb_ary_store(args, (long)j, rbduckdb_vector_value_at(arg->input_vectors[j], arg->input_types[j], i));
|
|
272
269
|
}
|
|
273
270
|
|
|
274
271
|
/* Call the Ruby block with the arguments */
|
|
275
|
-
result =
|
|
272
|
+
result = rb_apply(arg->ctx->function_proc, rb_intern("call"), args);
|
|
276
273
|
|
|
277
274
|
/* Write result to output using helper function */
|
|
278
275
|
rbduckdb_vector_set_value_at(arg->output, arg->output_type, i, result);
|
|
279
276
|
}
|
|
280
277
|
|
|
278
|
+
RB_GC_GUARD(args);
|
|
279
|
+
|
|
281
280
|
return Qnil;
|
|
282
281
|
}
|
|
283
282
|
|
|
@@ -294,9 +293,6 @@ static VALUE cleanup_callback(VALUE varg) {
|
|
|
294
293
|
duckdb_destroy_logical_type(&arg->output_type);
|
|
295
294
|
|
|
296
295
|
/* Free allocated memory */
|
|
297
|
-
if (arg->args != NULL) {
|
|
298
|
-
xfree(arg->args);
|
|
299
|
-
}
|
|
300
296
|
if (arg->input_types != NULL) {
|
|
301
297
|
xfree(arg->input_types);
|
|
302
298
|
}
|
|
@@ -375,7 +371,7 @@ static VALUE scalar_function_set_function(VALUE self) {
|
|
|
375
371
|
|
|
376
372
|
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
377
373
|
|
|
378
|
-
p->function_proc
|
|
374
|
+
RB_OBJ_WRITE(self, &p->function_proc, rb_block_proc());
|
|
379
375
|
|
|
380
376
|
duckdb_scalar_function_set_extra_info(p->scalar_function, p, NULL);
|
|
381
377
|
duckdb_scalar_function_set_function(p->scalar_function, scalar_function_callback);
|
|
@@ -402,7 +398,7 @@ static VALUE scalar_function__set_bind(VALUE self) {
|
|
|
402
398
|
|
|
403
399
|
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
404
400
|
|
|
405
|
-
p->bind_proc
|
|
401
|
+
RB_OBJ_WRITE(self, &p->bind_proc, rb_block_proc());
|
|
406
402
|
|
|
407
403
|
duckdb_scalar_function_set_extra_info(p->scalar_function, p, NULL);
|
|
408
404
|
duckdb_scalar_function_set_bind(p->scalar_function, scalar_function_bind_callback);
|
|
@@ -444,12 +440,9 @@ static void execute_bind_callback_protected(void *user_data) {
|
|
|
444
440
|
|
|
445
441
|
rb_protect(call_bind_proc, (VALUE)&arg, &exception_state);
|
|
446
442
|
if (exception_state) {
|
|
447
|
-
VALUE
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
duckdb_scalar_function_bind_set_error(darg->info, StringValueCStr(msg));
|
|
451
|
-
}
|
|
452
|
-
rb_set_errinfo(Qnil);
|
|
443
|
+
VALUE msg = rbduckdb_pending_error_message();
|
|
444
|
+
duckdb_scalar_function_bind_set_error(darg->info, StringValueCStr(msg));
|
|
445
|
+
RB_GC_GUARD(msg);
|
|
453
446
|
}
|
|
454
447
|
}
|
|
455
448
|
|
|
@@ -13,7 +13,7 @@ static VALUE scalar_function_bind_info_client_context(VALUE self);
|
|
|
13
13
|
static const rb_data_type_t scalar_function_bind_info_data_type = {
|
|
14
14
|
"DuckDB/ScalarFunction/BindInfo",
|
|
15
15
|
{NULL, deallocate, memsize,},
|
|
16
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
16
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
static void deallocate(void *ctx) {
|
|
@@ -13,12 +13,12 @@ static VALUE scalar_function_set__add(VALUE self, VALUE scalar_function);
|
|
|
13
13
|
static const rb_data_type_t scalar_function_set_data_type = {
|
|
14
14
|
"DuckDB/ScalarFunctionSet",
|
|
15
15
|
{mark, deallocate, memsize, compact},
|
|
16
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
16
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
static void mark(void *ctx) {
|
|
20
20
|
rubyDuckDBScalarFunctionSet *p = (rubyDuckDBScalarFunctionSet *)ctx;
|
|
21
|
-
|
|
21
|
+
rb_gc_mark_movable(p->functions);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
static void deallocate(void *ctx) {
|
|
@@ -35,8 +35,7 @@ static void compact(void *ctx) {
|
|
|
35
35
|
static VALUE allocate(VALUE klass) {
|
|
36
36
|
rubyDuckDBScalarFunctionSet *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBScalarFunctionSet));
|
|
37
37
|
VALUE obj = TypedData_Wrap_Struct(klass, &scalar_function_set_data_type, ctx);
|
|
38
|
-
ctx->functions
|
|
39
|
-
RB_GC_GUARD(ctx->functions);
|
|
38
|
+
RB_OBJ_WRITE(obj, &ctx->functions, rb_ary_new());
|
|
40
39
|
return obj;
|
|
41
40
|
}
|
|
42
41
|
|