duckdb 1.5.3.0 → 1.5.4.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 +11 -0
- data/README.md +52 -0
- data/ext/duckdb/aggregate_function.c +0 -1
- data/ext/duckdb/appender.c +17 -0
- data/ext/duckdb/arrow_array_stream.c +226 -0
- data/ext/duckdb/arrow_array_stream.h +61 -0
- data/ext/duckdb/arrow_import.c +165 -0
- data/ext/duckdb/arrow_import.h +6 -0
- data/ext/duckdb/blob.c +1 -1
- data/ext/duckdb/blob.h +1 -2
- data/ext/duckdb/config.c +1 -1
- data/ext/duckdb/config.h +1 -1
- data/ext/duckdb/connection.c +3 -3
- data/ext/duckdb/converter.h +1 -0
- data/ext/duckdb/conveter.c +39 -9
- data/ext/duckdb/data_chunk.c +10 -0
- data/ext/duckdb/data_chunk.h +1 -0
- data/ext/duckdb/duckdb.c +13 -11
- data/ext/duckdb/error.c +1 -1
- data/ext/duckdb/error.h +1 -3
- data/ext/duckdb/function_executor.c +308 -2
- data/ext/duckdb/function_executor.h +44 -0
- data/ext/duckdb/prepared_statement.c +21 -0
- data/ext/duckdb/result.c +49 -3
- data/ext/duckdb/result.h +11 -0
- data/ext/duckdb/ruby-duckdb.h +3 -0
- data/ext/duckdb/scalar_function.c +97 -29
- data/ext/duckdb/scalar_function.h +2 -4
- data/ext/duckdb/scalar_function_bind_info.c +13 -13
- data/ext/duckdb/scalar_function_bind_info.h +1 -1
- data/ext/duckdb/scalar_function_set.c +9 -9
- data/ext/duckdb/scalar_function_set.h +2 -2
- data/ext/duckdb/table_description.c +19 -19
- data/ext/duckdb/table_description.h +1 -1
- data/ext/duckdb/table_function.c +94 -28
- data/ext/duckdb/table_function.h +2 -2
- data/ext/duckdb/table_function_bind_info.c +20 -20
- data/ext/duckdb/table_function_bind_info.h +2 -2
- data/ext/duckdb/table_function_function_info.c +5 -5
- data/ext/duckdb/table_function_function_info.h +2 -2
- data/ext/duckdb/table_function_init_info.c +70 -5
- data/ext/duckdb/table_function_init_info.h +2 -2
- data/lib/duckdb/appender.rb +23 -0
- data/lib/duckdb/arrow_array_stream.rb +33 -0
- data/lib/duckdb/connection.rb +54 -0
- data/lib/duckdb/prepared_statement.rb +17 -0
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +1 -0
- metadata +6 -1
data/ext/duckdb/conveter.c
CHANGED
|
@@ -153,22 +153,21 @@ static int hex_nibble(unsigned char c)
|
|
|
153
153
|
|
|
154
154
|
/*
|
|
155
155
|
* Parse a canonical UUID string ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") into
|
|
156
|
-
*
|
|
156
|
+
* two uint64_t halves. Returns true on success, false on invalid input.
|
|
157
157
|
*
|
|
158
158
|
* Iterates the 36-character string, skipping the four dash positions. The 32
|
|
159
|
-
* hex nibbles are accumulated directly into
|
|
160
|
-
* no bignum arithmetic or intermediate allocation.
|
|
161
|
-
* to hi before storing in upper, matching DuckDB's internal UUID representation.
|
|
159
|
+
* hex nibbles are accumulated directly into hi (upper 64 bits) and lo (lower
|
|
160
|
+
* 64 bits) with no bignum arithmetic or intermediate allocation.
|
|
162
161
|
*/
|
|
163
|
-
static bool
|
|
162
|
+
static bool parse_uuid_string(const char *str, long len, uint64_t *hi, uint64_t *lo)
|
|
164
163
|
{
|
|
165
|
-
// Expected format: 8-4-4-4-12 = 36 characters with dashes at fixed positions
|
|
166
164
|
if (len != 36 ||
|
|
167
165
|
str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-') {
|
|
168
166
|
return false;
|
|
169
167
|
}
|
|
170
168
|
|
|
171
|
-
|
|
169
|
+
*hi = 0;
|
|
170
|
+
*lo = 0;
|
|
172
171
|
int nibble_idx = 0;
|
|
173
172
|
|
|
174
173
|
for (int string_idx = 0; string_idx < 36; string_idx++) {
|
|
@@ -176,13 +175,27 @@ static bool uuid_str_to_hugeint(const char *str, long len, duckdb_hugeint *out)
|
|
|
176
175
|
int nib = hex_nibble((unsigned char)str[string_idx]);
|
|
177
176
|
if (nib < 0) return false;
|
|
178
177
|
if (nibble_idx < 16) {
|
|
179
|
-
hi = (hi << 4) | (uint64_t)nib;
|
|
178
|
+
*hi = (*hi << 4) | (uint64_t)nib;
|
|
180
179
|
} else {
|
|
181
|
-
lo = (lo << 4) | (uint64_t)nib;
|
|
180
|
+
*lo = (*lo << 4) | (uint64_t)nib;
|
|
182
181
|
}
|
|
183
182
|
nibble_idx++;
|
|
184
183
|
}
|
|
185
184
|
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* Parse a canonical UUID string ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") into
|
|
190
|
+
* a duckdb_hugeint. Returns true on success, false on invalid input.
|
|
191
|
+
*
|
|
192
|
+
* The sign-bit flip is applied to hi before storing in upper, matching
|
|
193
|
+
* DuckDB's internal UUID representation.
|
|
194
|
+
*/
|
|
195
|
+
static bool uuid_str_to_hugeint(const char *str, long len, duckdb_hugeint *out)
|
|
196
|
+
{
|
|
197
|
+
uint64_t hi, lo;
|
|
198
|
+
if (!parse_uuid_string(str, len, &hi, &lo)) return false;
|
|
186
199
|
// Apply the sign-bit flip to match DuckDB's internal UUID representation
|
|
187
200
|
out->upper = (int64_t)(hi ^ DUCKDB_UUID_SIGN_BIT);
|
|
188
201
|
out->lower = lo;
|
|
@@ -203,6 +216,23 @@ void rbduckdb_uuid_str_to_hugeint(VALUE uuid_str, duckdb_hugeint *out)
|
|
|
203
216
|
}
|
|
204
217
|
}
|
|
205
218
|
|
|
219
|
+
/*
|
|
220
|
+
* Ruby-callable wrapper: parse a UUID string VALUE into a duckdb_uhugeint
|
|
221
|
+
* (no sign-bit flip), raising ArgumentError on invalid input.
|
|
222
|
+
*/
|
|
223
|
+
void rbduckdb_uuid_str_to_uhugeint(VALUE uuid_str, duckdb_uhugeint *out)
|
|
224
|
+
{
|
|
225
|
+
StringValue(uuid_str);
|
|
226
|
+
const char *str = RSTRING_PTR(uuid_str);
|
|
227
|
+
long len = RSTRING_LEN(uuid_str);
|
|
228
|
+
uint64_t hi, lo;
|
|
229
|
+
if (!parse_uuid_string(str, len, &hi, &lo)) {
|
|
230
|
+
rb_raise(rb_eArgError, "Invalid UUID format: %"PRIsVALUE, uuid_str);
|
|
231
|
+
}
|
|
232
|
+
out->upper = hi;
|
|
233
|
+
out->lower = lo;
|
|
234
|
+
}
|
|
235
|
+
|
|
206
236
|
VALUE rbduckdb_interval_to_ruby(duckdb_interval i) {
|
|
207
237
|
return rb_funcall(mDuckDBConverter, id__to_interval_from_vector, 3,
|
|
208
238
|
INT2NUM(i.months),
|
data/ext/duckdb/data_chunk.c
CHANGED
|
@@ -44,6 +44,16 @@ rubyDuckDBDataChunk *rbduckdb_get_struct_data_chunk(VALUE obj) {
|
|
|
44
44
|
return ctx;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
VALUE rbduckdb_create_data_chunk(duckdb_data_chunk chunk, bool owned) {
|
|
48
|
+
VALUE obj = allocate(cDuckDBDataChunk);
|
|
49
|
+
rubyDuckDBDataChunk *ctx;
|
|
50
|
+
|
|
51
|
+
TypedData_Get_Struct(obj, rubyDuckDBDataChunk, &data_chunk_data_type, ctx);
|
|
52
|
+
ctx->data_chunk = chunk;
|
|
53
|
+
ctx->owned = owned;
|
|
54
|
+
return obj;
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
static VALUE data_chunk_initialize(int argc, VALUE *argv, VALUE self) {
|
|
48
58
|
rubyDuckDBDataChunk *ctx;
|
|
49
59
|
VALUE logical_types;
|
data/ext/duckdb/data_chunk.h
CHANGED
|
@@ -9,6 +9,7 @@ struct _rubyDuckDBDataChunk {
|
|
|
9
9
|
typedef struct _rubyDuckDBDataChunk rubyDuckDBDataChunk;
|
|
10
10
|
|
|
11
11
|
rubyDuckDBDataChunk *rbduckdb_get_struct_data_chunk(VALUE obj);
|
|
12
|
+
VALUE rbduckdb_create_data_chunk(duckdb_data_chunk chunk, bool owned);
|
|
12
13
|
void rbduckdb_init_data_chunk(void);
|
|
13
14
|
|
|
14
15
|
#endif
|
data/ext/duckdb/duckdb.c
CHANGED
|
@@ -41,7 +41,7 @@ Init_duckdb_native(void) {
|
|
|
41
41
|
rb_define_singleton_method(mDuckDB, "library_version", duckdb_s_library_version, 0);
|
|
42
42
|
rb_define_singleton_method(mDuckDB, "vector_size", duckdb_s_vector_size, 0);
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
rbduckdb_init_error();
|
|
45
45
|
rbduckdb_init_database();
|
|
46
46
|
rbduckdb_init_connection();
|
|
47
47
|
rbduckdb_init_result();
|
|
@@ -49,28 +49,30 @@ Init_duckdb_native(void) {
|
|
|
49
49
|
rbduckdb_init_logical_type();
|
|
50
50
|
rbduckdb_init_prepared_statement();
|
|
51
51
|
rbduckdb_init_pending_result();
|
|
52
|
-
|
|
52
|
+
rbduckdb_init_blob();
|
|
53
53
|
rbduckdb_init_appender();
|
|
54
|
-
|
|
54
|
+
rbduckdb_init_config();
|
|
55
55
|
rbduckdb_init_converter();
|
|
56
56
|
rbduckdb_init_extracted_statements();
|
|
57
57
|
rbduckdb_init_instance_cache();
|
|
58
58
|
rbduckdb_init_value();
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
rbduckdb_init_scalar_function();
|
|
60
|
+
rbduckdb_init_scalar_function_set();
|
|
61
61
|
rbduckdb_init_aggregate_function();
|
|
62
62
|
rbduckdb_init_aggregate_function_set();
|
|
63
63
|
rbduckdb_init_expression();
|
|
64
64
|
rbduckdb_init_client_context();
|
|
65
|
-
|
|
65
|
+
rbduckdb_init_scalar_function_bind_info();
|
|
66
66
|
rbduckdb_init_vector();
|
|
67
67
|
rbduckdb_init_data_chunk();
|
|
68
68
|
rbduckdb_init_memory_helper();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
rbduckdb_init_table_function();
|
|
70
|
+
rbduckdb_init_table_function_bind_info();
|
|
71
|
+
rbduckdb_init_table_function_init_info();
|
|
72
|
+
rbduckdb_init_table_function_function_info();
|
|
73
73
|
#ifdef HAVE_DUCKDB_H_GE_V1_5_0
|
|
74
|
-
|
|
74
|
+
rbduckdb_init_table_description();
|
|
75
75
|
#endif
|
|
76
|
+
rbduckdb_init_arrow_array_stream();
|
|
77
|
+
rbduckdb_init_arrow_import();
|
|
76
78
|
}
|
data/ext/duckdb/error.c
CHANGED
data/ext/duckdb/error.h
CHANGED
|
@@ -68,6 +68,14 @@ static struct callback_request *g_request_list = NULL;
|
|
|
68
68
|
static VALUE g_executor_thread = Qnil;
|
|
69
69
|
static int g_executor_started = 0;
|
|
70
70
|
|
|
71
|
+
/*
|
|
72
|
+
* GC-protection array holding every live per-worker proxy Ruby thread.
|
|
73
|
+
* Proxies are created from non-Ruby init hooks (via the global executor) and
|
|
74
|
+
* are not reachable from any marked object, so without this array the GC could
|
|
75
|
+
* collect a proxy thread while DuckDB still dispatches callbacks to it.
|
|
76
|
+
*/
|
|
77
|
+
static VALUE g_proxy_threads = Qnil;
|
|
78
|
+
|
|
71
79
|
/* Data passed to the executor wait function */
|
|
72
80
|
struct executor_wait_data {
|
|
73
81
|
struct callback_request *request;
|
|
@@ -166,6 +174,11 @@ void rbduckdb_function_executor_ensure_started(void) {
|
|
|
166
174
|
}
|
|
167
175
|
#endif
|
|
168
176
|
|
|
177
|
+
if (g_proxy_threads == Qnil) {
|
|
178
|
+
g_proxy_threads = rb_ary_new();
|
|
179
|
+
rb_global_variable(&g_proxy_threads);
|
|
180
|
+
}
|
|
181
|
+
|
|
169
182
|
g_executor_thread = rb_thread_create(executor_thread_func, NULL);
|
|
170
183
|
rb_global_variable(&g_executor_thread);
|
|
171
184
|
g_executor_started = 1;
|
|
@@ -242,7 +255,293 @@ static void *callback_with_gvl(void *data) {
|
|
|
242
255
|
return NULL;
|
|
243
256
|
}
|
|
244
257
|
|
|
245
|
-
|
|
258
|
+
/*
|
|
259
|
+
* ============================================================================
|
|
260
|
+
* Per-worker proxy thread
|
|
261
|
+
* ============================================================================
|
|
262
|
+
*
|
|
263
|
+
* One dedicated Ruby thread per DuckDB worker thread. Same hand-off protocol as
|
|
264
|
+
* the global executor (mutex + condvars), but private to a single worker so
|
|
265
|
+
* that callbacks from different workers no longer serialize through one queue.
|
|
266
|
+
*
|
|
267
|
+
* Pattern follows the FFI gem's async callback dispatcher:
|
|
268
|
+
* https://github.com/ffi/ffi/blob/master/ext/ffi_c/Function.c
|
|
269
|
+
*/
|
|
270
|
+
struct worker_proxy {
|
|
271
|
+
VALUE ruby_thread;
|
|
272
|
+
volatile int stop_requested;
|
|
273
|
+
rbduckdb_function_callback_t cb;
|
|
274
|
+
void *user_data;
|
|
275
|
+
volatile int has_request;
|
|
276
|
+
volatile int request_done;
|
|
277
|
+
volatile int thread_exited;
|
|
278
|
+
#ifdef _MSC_VER
|
|
279
|
+
CRITICAL_SECTION lock;
|
|
280
|
+
CONDITION_VARIABLE request_cond;
|
|
281
|
+
CONDITION_VARIABLE request_done_cond;
|
|
282
|
+
CONDITION_VARIABLE thread_exit_cond;
|
|
283
|
+
#else
|
|
284
|
+
pthread_mutex_t lock;
|
|
285
|
+
pthread_cond_t request_cond;
|
|
286
|
+
pthread_cond_t request_done_cond;
|
|
287
|
+
pthread_cond_t thread_exit_cond;
|
|
288
|
+
#endif
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/* Runs without GVL: the proxy waits for a callback request */
|
|
292
|
+
static void *proxy_wait_func(void *data) {
|
|
293
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
294
|
+
|
|
295
|
+
#ifdef _MSC_VER
|
|
296
|
+
EnterCriticalSection(&proxy->lock);
|
|
297
|
+
while (!proxy->stop_requested && !proxy->has_request) {
|
|
298
|
+
SleepConditionVariableCS(&proxy->request_cond, &proxy->lock, INFINITE);
|
|
299
|
+
}
|
|
300
|
+
LeaveCriticalSection(&proxy->lock);
|
|
301
|
+
#else
|
|
302
|
+
pthread_mutex_lock(&proxy->lock);
|
|
303
|
+
while (!proxy->stop_requested && !proxy->has_request) {
|
|
304
|
+
pthread_cond_wait(&proxy->request_cond, &proxy->lock);
|
|
305
|
+
}
|
|
306
|
+
pthread_mutex_unlock(&proxy->lock);
|
|
307
|
+
#endif
|
|
308
|
+
|
|
309
|
+
return NULL;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Unblock function for the proxy thread (VM shutdown or Thread#kill) */
|
|
313
|
+
static void proxy_stop_func(void *data) {
|
|
314
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
315
|
+
|
|
316
|
+
#ifdef _MSC_VER
|
|
317
|
+
EnterCriticalSection(&proxy->lock);
|
|
318
|
+
proxy->stop_requested = 1;
|
|
319
|
+
WakeConditionVariable(&proxy->request_cond);
|
|
320
|
+
LeaveCriticalSection(&proxy->lock);
|
|
321
|
+
#else
|
|
322
|
+
pthread_mutex_lock(&proxy->lock);
|
|
323
|
+
proxy->stop_requested = 1;
|
|
324
|
+
pthread_cond_signal(&proxy->request_cond);
|
|
325
|
+
pthread_mutex_unlock(&proxy->lock);
|
|
326
|
+
#endif
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* The proxy thread main loop. Runs as the body of rb_ensure (see below). */
|
|
330
|
+
static VALUE proxy_loop_body(VALUE data) {
|
|
331
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
332
|
+
|
|
333
|
+
while (!proxy->stop_requested) {
|
|
334
|
+
/* Release the GVL and wait for a request */
|
|
335
|
+
rb_thread_call_without_gvl(proxy_wait_func, proxy, proxy_stop_func, proxy);
|
|
336
|
+
|
|
337
|
+
if (proxy->stop_requested) break;
|
|
338
|
+
|
|
339
|
+
if (proxy->has_request) {
|
|
340
|
+
/* Execute the callback with the GVL held */
|
|
341
|
+
proxy->cb(proxy->user_data);
|
|
342
|
+
|
|
343
|
+
/* Signal completion to the DuckDB worker thread */
|
|
344
|
+
#ifdef _MSC_VER
|
|
345
|
+
EnterCriticalSection(&proxy->lock);
|
|
346
|
+
proxy->has_request = 0;
|
|
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
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return Qnil;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/*
|
|
364
|
+
* Teardown for the proxy thread. Run via rb_ensure so it executes even if an
|
|
365
|
+
* async exception (Thread#kill, VM shutdown via rb_thread_terminate_all)
|
|
366
|
+
* unwinds proxy_loop_body. If it were skipped, thread_exited would stay 0
|
|
367
|
+
* forever and rbduckdb_worker_proxy_destroy's join would deadlock.
|
|
368
|
+
*/
|
|
369
|
+
static VALUE proxy_cleanup(VALUE data) {
|
|
370
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
371
|
+
|
|
372
|
+
/* Stop being GC-protected now that we are about to exit */
|
|
373
|
+
if (g_proxy_threads != Qnil) {
|
|
374
|
+
rb_ary_delete(g_proxy_threads, proxy->ruby_thread);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/*
|
|
378
|
+
* Signal that this thread has finished and no longer touches the proxy
|
|
379
|
+
* struct. Only after this may rbduckdb_worker_proxy_destroy free it.
|
|
380
|
+
*/
|
|
381
|
+
#ifdef _MSC_VER
|
|
382
|
+
EnterCriticalSection(&proxy->lock);
|
|
383
|
+
proxy->thread_exited = 1;
|
|
384
|
+
WakeConditionVariable(&proxy->thread_exit_cond);
|
|
385
|
+
LeaveCriticalSection(&proxy->lock);
|
|
386
|
+
#else
|
|
387
|
+
pthread_mutex_lock(&proxy->lock);
|
|
388
|
+
proxy->thread_exited = 1;
|
|
389
|
+
pthread_cond_signal(&proxy->thread_exit_cond);
|
|
390
|
+
pthread_mutex_unlock(&proxy->lock);
|
|
391
|
+
#endif
|
|
392
|
+
|
|
393
|
+
return Qnil;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* The proxy thread entry point (Ruby thread). */
|
|
397
|
+
static VALUE proxy_thread_func(void *data) {
|
|
398
|
+
return rb_ensure(proxy_loop_body, (VALUE)data, proxy_cleanup, (VALUE)data);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
struct worker_proxy *rbduckdb_worker_proxy_create(void) {
|
|
402
|
+
/*
|
|
403
|
+
* Use calloc (not xcalloc): rbduckdb_worker_proxy_destroy frees the struct
|
|
404
|
+
* from a non-Ruby thread where xfree is unsafe.
|
|
405
|
+
*/
|
|
406
|
+
struct worker_proxy *proxy = calloc(1, sizeof(struct worker_proxy));
|
|
407
|
+
if (proxy == NULL) {
|
|
408
|
+
rb_raise(rb_eNoMemError, "failed to allocate worker_proxy");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
proxy->stop_requested = 0;
|
|
412
|
+
proxy->has_request = 0;
|
|
413
|
+
proxy->request_done = 0;
|
|
414
|
+
proxy->thread_exited = 0;
|
|
415
|
+
|
|
416
|
+
#ifdef _MSC_VER
|
|
417
|
+
InitializeCriticalSection(&proxy->lock);
|
|
418
|
+
InitializeConditionVariable(&proxy->request_cond);
|
|
419
|
+
InitializeConditionVariable(&proxy->request_done_cond);
|
|
420
|
+
InitializeConditionVariable(&proxy->thread_exit_cond);
|
|
421
|
+
#else
|
|
422
|
+
pthread_mutex_init(&proxy->lock, NULL);
|
|
423
|
+
pthread_cond_init(&proxy->request_cond, NULL);
|
|
424
|
+
pthread_cond_init(&proxy->request_done_cond, NULL);
|
|
425
|
+
pthread_cond_init(&proxy->thread_exit_cond, NULL);
|
|
426
|
+
#endif
|
|
427
|
+
|
|
428
|
+
/*
|
|
429
|
+
* Lazy-init the GC-protection array so create never silently skips it (see
|
|
430
|
+
* the g_proxy_threads comment above); create runs with the GVL, so safe.
|
|
431
|
+
*/
|
|
432
|
+
if (g_proxy_threads == Qnil) {
|
|
433
|
+
g_proxy_threads = rb_ary_new();
|
|
434
|
+
rb_global_variable(&g_proxy_threads);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
proxy->ruby_thread = rb_thread_create(proxy_thread_func, proxy);
|
|
438
|
+
rb_ary_push(g_proxy_threads, proxy->ruby_thread);
|
|
439
|
+
|
|
440
|
+
return proxy;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/*
|
|
444
|
+
* Hand a callback to a proxy and block until it completes.
|
|
445
|
+
* Called from the DuckDB worker thread (non-Ruby thread) that owns this proxy.
|
|
446
|
+
*/
|
|
447
|
+
static void dispatch_callback_to_proxy(struct worker_proxy *proxy, rbduckdb_function_callback_t cb, void *user_data) {
|
|
448
|
+
#ifdef _MSC_VER
|
|
449
|
+
EnterCriticalSection(&proxy->lock);
|
|
450
|
+
proxy->cb = cb;
|
|
451
|
+
proxy->user_data = user_data;
|
|
452
|
+
proxy->request_done = 0;
|
|
453
|
+
proxy->has_request = 1;
|
|
454
|
+
WakeConditionVariable(&proxy->request_cond);
|
|
455
|
+
LeaveCriticalSection(&proxy->lock);
|
|
456
|
+
|
|
457
|
+
EnterCriticalSection(&proxy->lock);
|
|
458
|
+
while (!proxy->request_done) {
|
|
459
|
+
SleepConditionVariableCS(&proxy->request_done_cond, &proxy->lock, INFINITE);
|
|
460
|
+
}
|
|
461
|
+
LeaveCriticalSection(&proxy->lock);
|
|
462
|
+
#else
|
|
463
|
+
pthread_mutex_lock(&proxy->lock);
|
|
464
|
+
proxy->cb = cb;
|
|
465
|
+
proxy->user_data = user_data;
|
|
466
|
+
proxy->request_done = 0;
|
|
467
|
+
proxy->has_request = 1;
|
|
468
|
+
pthread_cond_signal(&proxy->request_cond);
|
|
469
|
+
pthread_mutex_unlock(&proxy->lock);
|
|
470
|
+
|
|
471
|
+
pthread_mutex_lock(&proxy->lock);
|
|
472
|
+
while (!proxy->request_done) {
|
|
473
|
+
pthread_cond_wait(&proxy->request_done_cond, &proxy->lock);
|
|
474
|
+
}
|
|
475
|
+
pthread_mutex_unlock(&proxy->lock);
|
|
476
|
+
#endif
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* Blocks until the proxy thread has fully exited. Runs without the GVL. */
|
|
480
|
+
static void *proxy_join_func(void *data) {
|
|
481
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
482
|
+
|
|
483
|
+
#ifdef _MSC_VER
|
|
484
|
+
EnterCriticalSection(&proxy->lock);
|
|
485
|
+
while (!proxy->thread_exited) {
|
|
486
|
+
SleepConditionVariableCS(&proxy->thread_exit_cond, &proxy->lock, INFINITE);
|
|
487
|
+
}
|
|
488
|
+
LeaveCriticalSection(&proxy->lock);
|
|
489
|
+
#else
|
|
490
|
+
pthread_mutex_lock(&proxy->lock);
|
|
491
|
+
while (!proxy->thread_exited) {
|
|
492
|
+
pthread_cond_wait(&proxy->thread_exit_cond, &proxy->lock);
|
|
493
|
+
}
|
|
494
|
+
pthread_mutex_unlock(&proxy->lock);
|
|
495
|
+
#endif
|
|
496
|
+
|
|
497
|
+
return NULL;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
void rbduckdb_worker_proxy_destroy(void *data) {
|
|
501
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
502
|
+
if (proxy == NULL) return;
|
|
503
|
+
|
|
504
|
+
/* Ask the proxy thread to stop. */
|
|
505
|
+
#ifdef _MSC_VER
|
|
506
|
+
EnterCriticalSection(&proxy->lock);
|
|
507
|
+
proxy->stop_requested = 1;
|
|
508
|
+
WakeConditionVariable(&proxy->request_cond);
|
|
509
|
+
LeaveCriticalSection(&proxy->lock);
|
|
510
|
+
#else
|
|
511
|
+
pthread_mutex_lock(&proxy->lock);
|
|
512
|
+
proxy->stop_requested = 1;
|
|
513
|
+
pthread_cond_signal(&proxy->request_cond);
|
|
514
|
+
pthread_mutex_unlock(&proxy->lock);
|
|
515
|
+
#endif
|
|
516
|
+
|
|
517
|
+
/*
|
|
518
|
+
* Wait until the proxy thread has fully exited. Before exiting it runs Ruby
|
|
519
|
+
* code (removing itself from the GC-protection array), which needs the GVL.
|
|
520
|
+
* DuckDB may invoke this destructor either from a worker thread (no GVL) or
|
|
521
|
+
* — depending on when it tears down the local state — from a Ruby thread
|
|
522
|
+
* that holds the GVL. In the latter case we must release the GVL while
|
|
523
|
+
* waiting, or the proxy thread could never acquire it and we would deadlock.
|
|
524
|
+
*/
|
|
525
|
+
if (ruby_native_thread_p() && ruby_thread_has_gvl_p()) {
|
|
526
|
+
rb_thread_call_without_gvl(proxy_join_func, proxy, NULL, NULL);
|
|
527
|
+
} else {
|
|
528
|
+
proxy_join_func(proxy);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* The proxy thread is gone; tear down OS primitives and free the struct. */
|
|
532
|
+
#ifdef _MSC_VER
|
|
533
|
+
DeleteCriticalSection(&proxy->lock);
|
|
534
|
+
#else
|
|
535
|
+
pthread_cond_destroy(&proxy->thread_exit_cond);
|
|
536
|
+
pthread_cond_destroy(&proxy->request_done_cond);
|
|
537
|
+
pthread_cond_destroy(&proxy->request_cond);
|
|
538
|
+
pthread_mutex_destroy(&proxy->lock);
|
|
539
|
+
#endif
|
|
540
|
+
|
|
541
|
+
free(proxy);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
void rbduckdb_function_executor_dispatch_via_proxy(rbduckdb_function_callback_t cb, void *user_data, struct worker_proxy *proxy) {
|
|
246
545
|
if (ruby_native_thread_p()) {
|
|
247
546
|
if (ruby_thread_has_gvl_p()) {
|
|
248
547
|
/* Case 1: Ruby thread with GVL - call directly */
|
|
@@ -254,8 +553,15 @@ void rbduckdb_function_executor_dispatch(rbduckdb_function_callback_t cb, void *
|
|
|
254
553
|
arg.user_data = user_data;
|
|
255
554
|
rb_thread_call_with_gvl(callback_with_gvl, &arg);
|
|
256
555
|
}
|
|
556
|
+
} else if (proxy != NULL) {
|
|
557
|
+
/* Case 3a: Non-Ruby thread with a per-worker proxy */
|
|
558
|
+
dispatch_callback_to_proxy(proxy, cb, user_data);
|
|
257
559
|
} else {
|
|
258
|
-
/* Case
|
|
560
|
+
/* Case 3b: Non-Ruby thread - dispatch to the global executor */
|
|
259
561
|
dispatch_callback_to_executor(cb, user_data);
|
|
260
562
|
}
|
|
261
563
|
}
|
|
564
|
+
|
|
565
|
+
void rbduckdb_function_executor_dispatch(rbduckdb_function_callback_t cb, void *user_data) {
|
|
566
|
+
rbduckdb_function_executor_dispatch_via_proxy(cb, user_data, NULL);
|
|
567
|
+
}
|
|
@@ -43,4 +43,48 @@ void rbduckdb_function_executor_ensure_started(void);
|
|
|
43
43
|
*/
|
|
44
44
|
void rbduckdb_function_executor_dispatch(rbduckdb_function_callback_t cb, void *user_data);
|
|
45
45
|
|
|
46
|
+
/*
|
|
47
|
+
* ============================================================================
|
|
48
|
+
* Per-worker proxy threads (DuckDB >= 1.5.0)
|
|
49
|
+
* ============================================================================
|
|
50
|
+
*
|
|
51
|
+
* The global executor above serializes every non-Ruby-thread callback through
|
|
52
|
+
* a single Ruby thread. A per-worker proxy instead gives each DuckDB worker
|
|
53
|
+
* thread its own dedicated Ruby thread, so callbacks from different workers can
|
|
54
|
+
* run concurrently — they compete for the GVL in round-robin fashion, which
|
|
55
|
+
* helps when callbacks release the GVL (e.g. on I/O).
|
|
56
|
+
*
|
|
57
|
+
* Proxies are created lazily from DuckDB's per-worker init hook and stored in
|
|
58
|
+
* DuckDB's thread-local state; the global executor remains the fallback.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/* Opaque per-worker proxy handle. */
|
|
62
|
+
struct worker_proxy;
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* Create a per-worker proxy thread. Must be called with the GVL held
|
|
66
|
+
* (typically by dispatching this through the global executor from a per-worker
|
|
67
|
+
* init callback, which itself runs on a non-Ruby thread).
|
|
68
|
+
*
|
|
69
|
+
* May raise (NoMemError, Thread.new failure). The executor runs callbacks
|
|
70
|
+
* unprotected, so a wrapper dispatched to it must rb_protect this call —
|
|
71
|
+
* otherwise a raise longjmps past the executor's done-signaling and the
|
|
72
|
+
* waiting DuckDB worker blocks forever.
|
|
73
|
+
*/
|
|
74
|
+
struct worker_proxy *rbduckdb_worker_proxy_create(void);
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* Destroy a per-worker proxy. The signature matches duckdb_delete_callback_t so
|
|
78
|
+
* it can be handed directly to DuckDB. Safe to call from a non-Ruby thread: it
|
|
79
|
+
* touches only OS primitives and frees memory allocated with calloc.
|
|
80
|
+
*/
|
|
81
|
+
void rbduckdb_worker_proxy_destroy(void *proxy);
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
* Like rbduckdb_function_executor_dispatch, but on the non-Ruby-thread path
|
|
85
|
+
* (Case 3) it routes through the given per-worker proxy when non-NULL, falling
|
|
86
|
+
* back to the global executor when NULL. Cases 1 and 2 are unchanged.
|
|
87
|
+
*/
|
|
88
|
+
void rbduckdb_function_executor_dispatch_via_proxy(rbduckdb_function_callback_t cb, void *user_data, struct worker_proxy *proxy);
|
|
89
|
+
|
|
46
90
|
#endif
|
|
@@ -38,6 +38,7 @@ static VALUE prepared_statement__bind_timestamp_tz(VALUE self, VALUE vidx, VALUE
|
|
|
38
38
|
static VALUE prepared_statement__bind_interval(VALUE self, VALUE vidx, VALUE months, VALUE days, VALUE micros);
|
|
39
39
|
static VALUE prepared_statement__bind_hugeint(VALUE self, VALUE vidx, VALUE lower, VALUE upper);
|
|
40
40
|
static VALUE prepared_statement__bind_uhugeint(VALUE self, VALUE vidx, VALUE lower, VALUE upper);
|
|
41
|
+
static VALUE prepared_statement__bind_uuid(VALUE self, VALUE vidx, VALUE val);
|
|
41
42
|
static VALUE prepared_statement__bind_decimal(VALUE self, VALUE vidx, VALUE lower, VALUE upper, VALUE width, VALUE scale);
|
|
42
43
|
static VALUE prepared_statement__bind_value(VALUE self, VALUE vidx, VALUE val);
|
|
43
44
|
|
|
@@ -558,6 +559,25 @@ static VALUE prepared_statement__bind_decimal(VALUE self, VALUE vidx, VALUE lowe
|
|
|
558
559
|
return self;
|
|
559
560
|
}
|
|
560
561
|
|
|
562
|
+
/* :nodoc: */
|
|
563
|
+
static VALUE prepared_statement__bind_uuid(VALUE self, VALUE vidx, VALUE val) {
|
|
564
|
+
rubyDuckDBPreparedStatement *ctx;
|
|
565
|
+
duckdb_uhugeint uhugeint;
|
|
566
|
+
duckdb_value uuid_val;
|
|
567
|
+
duckdb_state state;
|
|
568
|
+
idx_t idx = check_index(vidx);
|
|
569
|
+
|
|
570
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
571
|
+
rbduckdb_uuid_str_to_uhugeint(val, &uhugeint);
|
|
572
|
+
uuid_val = duckdb_create_uuid(uhugeint);
|
|
573
|
+
state = duckdb_bind_value(ctx->prepared_statement, idx, uuid_val);
|
|
574
|
+
duckdb_destroy_value(&uuid_val);
|
|
575
|
+
if (state == DuckDBError) {
|
|
576
|
+
rb_raise(eDuckDBError, "fail to bind %llu parameter", (unsigned long long)idx);
|
|
577
|
+
}
|
|
578
|
+
return self;
|
|
579
|
+
}
|
|
580
|
+
|
|
561
581
|
/* :nodoc: */
|
|
562
582
|
static VALUE prepared_statement__bind_value(VALUE self, VALUE vidx, VALUE val) {
|
|
563
583
|
rubyDuckDBPreparedStatement *ctx;
|
|
@@ -617,6 +637,7 @@ void rbduckdb_init_prepared_statement(void) {
|
|
|
617
637
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_interval", prepared_statement__bind_interval, 4);
|
|
618
638
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_hugeint", prepared_statement__bind_hugeint, 3);
|
|
619
639
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_uhugeint", prepared_statement__bind_uhugeint, 3);
|
|
640
|
+
rb_define_private_method(cDuckDBPreparedStatement, "_bind_uuid", prepared_statement__bind_uuid, 2);
|
|
620
641
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_decimal", prepared_statement__bind_decimal, 5);
|
|
621
642
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_value", prepared_statement__bind_value, 2);
|
|
622
643
|
}
|