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
data/ext/duckdb/appender.c
CHANGED
|
@@ -50,10 +50,20 @@ static VALUE appender__clear_columns(VALUE self);
|
|
|
50
50
|
static VALUE appender__close(VALUE self);
|
|
51
51
|
static VALUE state_to_rbool(duckdb_state state);
|
|
52
52
|
|
|
53
|
+
/*
|
|
54
|
+
* Keeps RUBY_TYPED_FREE_IMMEDIATELY although duckdb_appender_destroy flushes
|
|
55
|
+
* buffered rows (blocking I/O): when an unclosed appender is collected in the
|
|
56
|
+
* same GC as its connection, freeing it during sweep runs before the deferred
|
|
57
|
+
* Connection/Database free, so its rows still reach the table. Deferring it too
|
|
58
|
+
* would let the connection close first and lose rows. (The appender holds no
|
|
59
|
+
* reference to its connection, so if the connection is collected in an earlier
|
|
60
|
+
* GC the flush is lost anyway.) Freeing in sweep cannot shut the database down:
|
|
61
|
+
* DuckDB's BaseAppender holds only a weak_ptr<ClientContext>.
|
|
62
|
+
*/
|
|
53
63
|
static const rb_data_type_t appender_data_type = {
|
|
54
64
|
"DuckDB/Appender",
|
|
55
65
|
{NULL, deallocate, memsize,},
|
|
56
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
66
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
57
67
|
};
|
|
58
68
|
|
|
59
69
|
static void deallocate(void * ctx) {
|
|
@@ -94,6 +104,8 @@ static VALUE appender_s_create_query(VALUE klass, VALUE con, VALUE query, VALUE
|
|
|
94
104
|
idx_t column_count = 0;
|
|
95
105
|
duckdb_logical_type *type_array = NULL;
|
|
96
106
|
VALUE appender = Qnil;
|
|
107
|
+
VALUE column_name_strings = Qnil;
|
|
108
|
+
duckdb_state state;
|
|
97
109
|
|
|
98
110
|
if (!rb_obj_is_kind_of(con, cDuckDBConnection)) {
|
|
99
111
|
rb_raise(rb_eTypeError, "1st argument should be instance of DackDB::Connection");
|
|
@@ -116,17 +128,29 @@ static VALUE appender_s_create_query(VALUE klass, VALUE con, VALUE query, VALUE
|
|
|
116
128
|
if (rb_obj_is_kind_of(columns, rb_cArray) == Qfalse) {
|
|
117
129
|
rb_raise(rb_eTypeError, "4th argument should be an Array or nil");
|
|
118
130
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
131
|
+
/* DuckDB reads column_count entries from column_names, so a shorter list
|
|
132
|
+
* makes it read uninitialized stack slots as column names. */
|
|
133
|
+
if ((idx_t)RARRAY_LEN(columns) != column_count) {
|
|
134
|
+
rb_raise(rb_eArgError, "column names size (%ld) must be the same as types size (%ld)",
|
|
135
|
+
RARRAY_LEN(columns), RARRAY_LEN(types));
|
|
136
|
+
}
|
|
137
|
+
/* Keep the converted Strings alive: StringValue on a to_str object
|
|
138
|
+
* returns a String that nothing else references. */
|
|
139
|
+
column_name_strings = rb_ary_new_capa((long)column_count);
|
|
140
|
+
column_names = ALLOCA_N(const char *, (size_t)column_count);
|
|
141
|
+
for (idx_t i = 0; i < column_count; i++) {
|
|
122
142
|
VALUE col_name_val = rb_ary_entry(columns, i);
|
|
123
|
-
|
|
143
|
+
StringValue(col_name_val);
|
|
144
|
+
rb_ary_push(column_name_strings, col_name_val);
|
|
145
|
+
column_names[i] = RSTRING_PTR(col_name_val);
|
|
124
146
|
}
|
|
125
147
|
}
|
|
126
148
|
ctxcon = rbduckdb_get_struct_connection(con);
|
|
127
149
|
appender = allocate(klass);
|
|
128
150
|
TypedData_Get_Struct(appender, rubyDuckDBAppender, &appender_data_type, ctx);
|
|
129
|
-
|
|
151
|
+
state = duckdb_appender_create_query(ctxcon->con, query_str, column_count, type_array, table_name, column_names, &ctx->appender);
|
|
152
|
+
RB_GC_GUARD(column_name_strings);
|
|
153
|
+
if (state == DuckDBError) {
|
|
130
154
|
rb_raise(eDuckDBError, "failed to create appender from query");
|
|
131
155
|
}
|
|
132
156
|
|
|
@@ -333,11 +357,13 @@ static VALUE appender__append_double(VALUE self, VALUE val) {
|
|
|
333
357
|
/* :nodoc: */
|
|
334
358
|
static VALUE appender__append_varchar(VALUE self, VALUE val) {
|
|
335
359
|
rubyDuckDBAppender *ctx;
|
|
360
|
+
/* Length-aware, as duckdb_append_varchar would stop at an embedded NUL. */
|
|
336
361
|
char *pval = StringValuePtr(val);
|
|
362
|
+
long len = RSTRING_LEN(val);
|
|
337
363
|
|
|
338
364
|
TypedData_Get_Struct(self, rubyDuckDBAppender, &appender_data_type, ctx);
|
|
339
365
|
|
|
340
|
-
return state_to_rbool(
|
|
366
|
+
return state_to_rbool(duckdb_append_varchar_length(ctx->appender, pval, (idx_t)len));
|
|
341
367
|
}
|
|
342
368
|
|
|
343
369
|
/* :nodoc: */
|
|
@@ -13,7 +13,7 @@ typedef struct {
|
|
|
13
13
|
* the Ruby DuckDB::ArrowArrayStream object is gone, so this context is
|
|
14
14
|
* freed only by the stream release callback, and it holds a reference on
|
|
15
15
|
* the result struct (rbduckdb_result_ref) until then. The release callback
|
|
16
|
-
* must not call any Ruby API: it can run during GC
|
|
16
|
+
* must not call any Ruby API: it can run during GC finalization (via deallocate
|
|
17
17
|
* of an unconsumed stream) or from a non-Ruby thread.
|
|
18
18
|
*/
|
|
19
19
|
typedef struct {
|
|
@@ -34,10 +34,14 @@ static int stream_get_next(struct ArrowArrayStream *stream, struct ArrowArray *o
|
|
|
34
34
|
static const char *stream_get_last_error(struct ArrowArrayStream *stream);
|
|
35
35
|
static void stream_release(struct ArrowArrayStream *stream);
|
|
36
36
|
|
|
37
|
+
/*
|
|
38
|
+
* No RUBY_TYPED_FREE_IMMEDIATELY: deallocate can block, and must not run
|
|
39
|
+
* during GC sweep: releasing the stream may destroy a streaming result (see result.c).
|
|
40
|
+
*/
|
|
37
41
|
static const rb_data_type_t arrow_array_stream_data_type = {
|
|
38
42
|
"DuckDB/ArrowArrayStream",
|
|
39
43
|
{NULL, deallocate, memsize,},
|
|
40
|
-
0, 0,
|
|
44
|
+
0, 0, RUBY_TYPED_WB_PROTECTED
|
|
41
45
|
};
|
|
42
46
|
|
|
43
47
|
static void deallocate(void *ctx) {
|
data/ext/duckdb/arrow_import.c
CHANGED
|
@@ -26,7 +26,7 @@ static VALUE connection__arrow_release(VALUE self, VALUE address);
|
|
|
26
26
|
static const rb_data_type_t arrow_converted_schema_data_type = {
|
|
27
27
|
"DuckDB/ArrowConvertedSchema",
|
|
28
28
|
{NULL, deallocate, memsize,},
|
|
29
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
29
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
static void deallocate(void *ctx) {
|
data/ext/duckdb/client_context.c
CHANGED
|
@@ -10,7 +10,7 @@ static VALUE client_context_connection_id(VALUE self);
|
|
|
10
10
|
static const rb_data_type_t client_context_data_type = {
|
|
11
11
|
"DuckDB/ClientContext",
|
|
12
12
|
{NULL, deallocate, memsize,},
|
|
13
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
13
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
static void deallocate(void *ctx) {
|
data/ext/duckdb/column.c
CHANGED
|
@@ -12,7 +12,7 @@ static VALUE column_name(VALUE oDuckDBColumn);
|
|
|
12
12
|
static const rb_data_type_t column_data_type = {
|
|
13
13
|
"DuckDB/Column",
|
|
14
14
|
{NULL, deallocate, memsize,},
|
|
15
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
15
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
static void deallocate(void *ctx) {
|
data/ext/duckdb/column.h
CHANGED
data/ext/duckdb/config.c
CHANGED
|
@@ -13,7 +13,7 @@ static VALUE config_set_config(VALUE self, VALUE key, VALUE value);
|
|
|
13
13
|
static const rb_data_type_t config_data_type = {
|
|
14
14
|
"DuckDB/Config",
|
|
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) {
|
data/ext/duckdb/connection.c
CHANGED
|
@@ -4,6 +4,7 @@ VALUE cDuckDBConnection;
|
|
|
4
4
|
|
|
5
5
|
static void deallocate(void *ctx);
|
|
6
6
|
static void mark(void *ctx);
|
|
7
|
+
static void compact(void *ctx);
|
|
7
8
|
static VALUE allocate(VALUE klass);
|
|
8
9
|
static size_t memsize(const void *p);
|
|
9
10
|
static VALUE connection_disconnect(VALUE self);
|
|
@@ -19,10 +20,15 @@ static VALUE connection__register_aggregate_function_set(VALUE self, VALUE aggre
|
|
|
19
20
|
static VALUE connection__register_table_function(VALUE self, VALUE table_function);
|
|
20
21
|
static VALUE connection__get_table_names(VALUE self, VALUE query, VALUE qualified);
|
|
21
22
|
|
|
23
|
+
/*
|
|
24
|
+
* No RUBY_TYPED_FREE_IMMEDIATELY: deallocate can block, and must not run
|
|
25
|
+
* during GC sweep: duckdb_disconnect rolls back an open transaction and may drop the
|
|
26
|
+
* last reference to the database (checkpoint on shutdown).
|
|
27
|
+
*/
|
|
22
28
|
static const rb_data_type_t connection_data_type = {
|
|
23
29
|
"DuckDB/Connection",
|
|
24
|
-
{mark, deallocate, memsize,},
|
|
25
|
-
0, 0,
|
|
30
|
+
{mark, deallocate, memsize, compact},
|
|
31
|
+
0, 0, RUBY_TYPED_WB_PROTECTED
|
|
26
32
|
};
|
|
27
33
|
|
|
28
34
|
static void deallocate(void *ctx) {
|
|
@@ -34,16 +40,29 @@ static void deallocate(void *ctx) {
|
|
|
34
40
|
|
|
35
41
|
static void mark(void *ctx) {
|
|
36
42
|
rubyDuckDBConnection *p = (rubyDuckDBConnection *)ctx;
|
|
37
|
-
|
|
43
|
+
rb_gc_mark_movable(p->database);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static void compact(void *ctx) {
|
|
47
|
+
rubyDuckDBConnection *p = (rubyDuckDBConnection *)ctx;
|
|
48
|
+
p->database = rb_gc_location(p->database);
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
static VALUE allocate(VALUE klass) {
|
|
41
52
|
rubyDuckDBConnection *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBConnection));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
ctx->database = Qnil;
|
|
54
|
+
return TypedData_Wrap_Struct(klass, &connection_data_type, ctx);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Anchors obj to this connection's database so it outlives the connection. */
|
|
58
|
+
static void connection_retain_registered(VALUE self, VALUE obj) {
|
|
59
|
+
rubyDuckDBConnection *ctx;
|
|
60
|
+
|
|
61
|
+
TypedData_Get_Struct(self, rubyDuckDBConnection, &connection_data_type, ctx);
|
|
62
|
+
if (NIL_P(ctx->database)) {
|
|
63
|
+
rb_raise(eDuckDBError, "connection is not associated with a database");
|
|
64
|
+
}
|
|
65
|
+
rbduckdb_database_retain(ctx->database, obj);
|
|
47
66
|
}
|
|
48
67
|
|
|
49
68
|
static size_t memsize(const void *p) {
|
|
@@ -69,6 +88,7 @@ VALUE rbduckdb_create_connection(VALUE oDuckDBDatabase) {
|
|
|
69
88
|
if (duckdb_connect(ctxdb->db, &(ctxcon->con)) == DuckDBError) {
|
|
70
89
|
rb_raise(eDuckDBError, "connection error");
|
|
71
90
|
}
|
|
91
|
+
RB_OBJ_WRITE(obj, &ctxcon->database, oDuckDBDatabase);
|
|
72
92
|
|
|
73
93
|
return obj;
|
|
74
94
|
}
|
|
@@ -79,8 +99,12 @@ static VALUE connection_disconnect(VALUE self) {
|
|
|
79
99
|
TypedData_Get_Struct(self, rubyDuckDBConnection, &connection_data_type, ctx);
|
|
80
100
|
duckdb_disconnect(&(ctx->con));
|
|
81
101
|
|
|
82
|
-
/*
|
|
83
|
-
|
|
102
|
+
/*
|
|
103
|
+
* Registered functions are not released here: the catalog entry is still
|
|
104
|
+
* reachable from other connections. They are released with the database.
|
|
105
|
+
* This connection no longer needs the database, so stop retaining it.
|
|
106
|
+
*/
|
|
107
|
+
RB_OBJ_WRITE(self, &ctx->database, Qnil);
|
|
84
108
|
|
|
85
109
|
return self;
|
|
86
110
|
}
|
|
@@ -145,9 +169,11 @@ static VALUE connection__connect(VALUE self, VALUE oDuckDBDatabase) {
|
|
|
145
169
|
ctxdb = rbduckdb_get_struct_database(oDuckDBDatabase);
|
|
146
170
|
TypedData_Get_Struct(self, rubyDuckDBConnection, &connection_data_type, ctx);
|
|
147
171
|
|
|
172
|
+
/* Set only on success: a failed connect must not re-anchor registrations to the new database. */
|
|
148
173
|
if (duckdb_connect(ctxdb->db, &(ctx->con)) == DuckDBError) {
|
|
149
174
|
rb_raise(eDuckDBError, "connection error");
|
|
150
175
|
}
|
|
176
|
+
RB_OBJ_WRITE(self, &ctx->database, oDuckDBDatabase);
|
|
151
177
|
|
|
152
178
|
return self;
|
|
153
179
|
}
|
|
@@ -239,8 +265,8 @@ static VALUE connection__register_logical_type(VALUE self, VALUE logical_type) {
|
|
|
239
265
|
rb_raise(eDuckDBError, "Failed to register logical type");
|
|
240
266
|
}
|
|
241
267
|
|
|
242
|
-
/* Keep reference
|
|
243
|
-
|
|
268
|
+
/* Keep reference alive as long as the database can call it */
|
|
269
|
+
connection_retain_registered(self, logical_type);
|
|
244
270
|
|
|
245
271
|
return self;
|
|
246
272
|
}
|
|
@@ -260,8 +286,8 @@ static VALUE connection__register_scalar_function(VALUE self, VALUE scalar_funct
|
|
|
260
286
|
rb_raise(eDuckDBError, "Failed to register scalar function");
|
|
261
287
|
}
|
|
262
288
|
|
|
263
|
-
/* Keep reference
|
|
264
|
-
|
|
289
|
+
/* Keep reference alive as long as the database can call it */
|
|
290
|
+
connection_retain_registered(self, scalar_function);
|
|
265
291
|
|
|
266
292
|
return self;
|
|
267
293
|
}
|
|
@@ -281,8 +307,8 @@ static VALUE connection__register_scalar_function_set(VALUE self, VALUE scalar_f
|
|
|
281
307
|
rb_raise(eDuckDBError, "Failed to register scalar function set");
|
|
282
308
|
}
|
|
283
309
|
|
|
284
|
-
/* Keep reference
|
|
285
|
-
|
|
310
|
+
/* Keep reference alive as long as the database can call it */
|
|
311
|
+
connection_retain_registered(self, scalar_function_set);
|
|
286
312
|
|
|
287
313
|
return self;
|
|
288
314
|
}
|
|
@@ -302,8 +328,8 @@ static VALUE connection__register_aggregate_function(VALUE self, VALUE aggregate
|
|
|
302
328
|
rb_raise(eDuckDBError, "Failed to register aggregate function");
|
|
303
329
|
}
|
|
304
330
|
|
|
305
|
-
/* Keep reference
|
|
306
|
-
|
|
331
|
+
/* Keep reference alive as long as the database can call it */
|
|
332
|
+
connection_retain_registered(self, aggregate_function);
|
|
307
333
|
|
|
308
334
|
return self;
|
|
309
335
|
}
|
|
@@ -323,8 +349,8 @@ static VALUE connection__register_aggregate_function_set(VALUE self, VALUE aggre
|
|
|
323
349
|
rb_raise(eDuckDBError, "Failed to register aggregate function set");
|
|
324
350
|
}
|
|
325
351
|
|
|
326
|
-
/* Keep reference
|
|
327
|
-
|
|
352
|
+
/* Keep reference alive as long as the database can call it */
|
|
353
|
+
connection_retain_registered(self, aggregate_function_set);
|
|
328
354
|
|
|
329
355
|
return self;
|
|
330
356
|
}
|
|
@@ -343,8 +369,8 @@ static VALUE connection__register_table_function(VALUE self, VALUE table_functio
|
|
|
343
369
|
rb_raise(eDuckDBError, "Failed to register table function");
|
|
344
370
|
}
|
|
345
371
|
|
|
346
|
-
/* Keep reference
|
|
347
|
-
|
|
372
|
+
/* Keep reference alive as long as the database can call it */
|
|
373
|
+
connection_retain_registered(self, table_function);
|
|
348
374
|
|
|
349
375
|
return self;
|
|
350
376
|
}
|
data/ext/duckdb/connection.h
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
struct _rubyDuckDBConnection {
|
|
5
5
|
duckdb_connection con;
|
|
6
|
-
|
|
6
|
+
/* The DuckDB::Database this connection belongs to. Registrations are anchored there. */
|
|
7
|
+
VALUE database;
|
|
7
8
|
};
|
|
8
9
|
|
|
9
10
|
typedef struct _rubyDuckDBConnection rubyDuckDBConnection;
|
data/ext/duckdb/data_chunk.c
CHANGED
|
@@ -16,7 +16,7 @@ static VALUE data_chunk__reset(VALUE self);
|
|
|
16
16
|
static const rb_data_type_t data_chunk_data_type = {
|
|
17
17
|
"DuckDB/DataChunk",
|
|
18
18
|
{NULL, deallocate, memsize,},
|
|
19
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
19
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
static void deallocate(void *ctx) {
|
data/ext/duckdb/database.c
CHANGED
|
@@ -4,6 +4,8 @@ VALUE cDuckDBDatabase;
|
|
|
4
4
|
|
|
5
5
|
static void close_database(rubyDuckDB *p);
|
|
6
6
|
static void deallocate(void * ctx);
|
|
7
|
+
static void mark(void *ctx);
|
|
8
|
+
static void compact(void *ctx);
|
|
7
9
|
static VALUE allocate(VALUE klass);
|
|
8
10
|
static size_t memsize(const void *p);
|
|
9
11
|
static duckdb_config create_config_with_ruby_api(void);
|
|
@@ -11,16 +13,37 @@ static VALUE database__initialize(VALUE self, VALUE file, VALUE config);
|
|
|
11
13
|
static VALUE database__connect(VALUE self);
|
|
12
14
|
static VALUE database_close(VALUE self);
|
|
13
15
|
|
|
16
|
+
/*
|
|
17
|
+
* No RUBY_TYPED_FREE_IMMEDIATELY: deallocate can block, and must not run
|
|
18
|
+
* during GC sweep: duckdb_close may checkpoint to disk and joins worker threads.
|
|
19
|
+
*/
|
|
14
20
|
static const rb_data_type_t database_data_type = {
|
|
15
21
|
"DuckDB/Database",
|
|
16
|
-
{
|
|
17
|
-
0, 0,
|
|
22
|
+
{mark, deallocate, memsize, compact},
|
|
23
|
+
0, 0, RUBY_TYPED_WB_PROTECTED
|
|
18
24
|
};
|
|
19
25
|
|
|
20
26
|
static void close_database(rubyDuckDB *p) {
|
|
21
27
|
duckdb_close(&(p->db));
|
|
22
28
|
}
|
|
23
29
|
|
|
30
|
+
/*
|
|
31
|
+
* duckdb_close joins DuckDB's worker threads, and a worker tearing down a
|
|
32
|
+
* pipeline can still run a UDF callback -- the aggregate destroy callback,
|
|
33
|
+
* say, for a query that failed and left its pipeline queued. That callback is
|
|
34
|
+
* dispatched to the executor thread, which needs the GVL, so closing while
|
|
35
|
+
* holding it deadlocks: the worker waits for the executor, the executor waits
|
|
36
|
+
* for us, and we wait for the worker.
|
|
37
|
+
*
|
|
38
|
+
* The GC free function calls close_database directly and cannot do this;
|
|
39
|
+
* a free function must not release the GVL. A database reaching close via
|
|
40
|
+
* the GC with callbacks still pending would deadlock the same way.
|
|
41
|
+
*/
|
|
42
|
+
static void *close_database_without_gvl(void *ctx) {
|
|
43
|
+
close_database((rubyDuckDB *)ctx);
|
|
44
|
+
return NULL;
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
static void deallocate(void * ctx) {
|
|
25
48
|
rubyDuckDB *p = (rubyDuckDB *)ctx;
|
|
26
49
|
|
|
@@ -28,13 +51,57 @@ static void deallocate(void * ctx) {
|
|
|
28
51
|
xfree(p);
|
|
29
52
|
}
|
|
30
53
|
|
|
54
|
+
static void mark(void *ctx) {
|
|
55
|
+
rubyDuckDB *p = (rubyDuckDB *)ctx;
|
|
56
|
+
|
|
57
|
+
rb_gc_mark_movable(p->registered_functions);
|
|
58
|
+
rb_gc_mark_movable(p->cached_path);
|
|
59
|
+
rb_gc_mark_movable(p->cache_wrappers);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static void compact(void *ctx) {
|
|
63
|
+
rubyDuckDB *p = (rubyDuckDB *)ctx;
|
|
64
|
+
|
|
65
|
+
p->registered_functions = rb_gc_location(p->registered_functions);
|
|
66
|
+
p->cached_path = rb_gc_location(p->cached_path);
|
|
67
|
+
p->cache_wrappers = rb_gc_location(p->cache_wrappers);
|
|
68
|
+
}
|
|
69
|
+
|
|
31
70
|
static size_t memsize(const void *p) {
|
|
32
71
|
return sizeof(rubyDuckDB);
|
|
33
72
|
}
|
|
34
73
|
|
|
35
74
|
static VALUE allocate(VALUE klass) {
|
|
36
75
|
rubyDuckDB *ctx = xcalloc((size_t)1, sizeof(rubyDuckDB));
|
|
37
|
-
|
|
76
|
+
VALUE obj = TypedData_Wrap_Struct(klass, &database_data_type, ctx);
|
|
77
|
+
ctx->cached_path = Qnil;
|
|
78
|
+
ctx->cache_wrappers = Qnil;
|
|
79
|
+
RB_OBJ_WRITE(obj, &ctx->registered_functions, rb_ary_new());
|
|
80
|
+
return obj;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
* Keeps obj alive as long as this database is alive.
|
|
85
|
+
* DuckDB registers functions in the database-level catalog and has no
|
|
86
|
+
* unregister API, so releasing them any earlier leaves a dangling extra_info.
|
|
87
|
+
*/
|
|
88
|
+
void rbduckdb_database_retain(VALUE database, VALUE obj) {
|
|
89
|
+
rubyDuckDB *ctx;
|
|
90
|
+
|
|
91
|
+
TypedData_Get_Struct(database, rubyDuckDB, &database_data_type, ctx);
|
|
92
|
+
rb_ary_push(ctx->registered_functions, obj);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* Records that this database is the InstanceCache's wrapper for path, so the
|
|
97
|
+
* cache can find it again and #close can remove it from the cache's weak set.
|
|
98
|
+
*/
|
|
99
|
+
void rbduckdb_database_set_cache_entry(VALUE database, VALUE path, VALUE wrappers) {
|
|
100
|
+
rubyDuckDB *ctx;
|
|
101
|
+
|
|
102
|
+
TypedData_Get_Struct(database, rubyDuckDB, &database_data_type, ctx);
|
|
103
|
+
RB_OBJ_WRITE(database, &ctx->cached_path, rb_str_new_frozen(path));
|
|
104
|
+
RB_OBJ_WRITE(database, &ctx->cache_wrappers, wrappers);
|
|
38
105
|
}
|
|
39
106
|
|
|
40
107
|
rubyDuckDB *rbduckdb_get_struct_database(VALUE obj) {
|
|
@@ -119,7 +186,16 @@ static VALUE database__connect(VALUE self) {
|
|
|
119
186
|
static VALUE database_close(VALUE self) {
|
|
120
187
|
rubyDuckDB *ctx;
|
|
121
188
|
TypedData_Get_Struct(self, rubyDuckDB, &database_data_type, ctx);
|
|
122
|
-
|
|
189
|
+
/*
|
|
190
|
+
* A closed wrapper must not be handed to the next get_or_create, so drop it
|
|
191
|
+
* from the cache's set first. The next call then opens a fresh wrapper.
|
|
192
|
+
*/
|
|
193
|
+
if (!NIL_P(ctx->cache_wrappers)) {
|
|
194
|
+
rb_funcall(ctx->cache_wrappers, rb_intern("delete"), 1, self);
|
|
195
|
+
RB_OBJ_WRITE(self, &ctx->cache_wrappers, Qnil);
|
|
196
|
+
RB_OBJ_WRITE(self, &ctx->cached_path, Qnil);
|
|
197
|
+
}
|
|
198
|
+
rb_thread_call_without_gvl(close_database_without_gvl, ctx, NULL, NULL);
|
|
123
199
|
return self;
|
|
124
200
|
}
|
|
125
201
|
|
data/ext/duckdb/database.h
CHANGED
|
@@ -3,12 +3,24 @@
|
|
|
3
3
|
|
|
4
4
|
struct _rubyDuckDB {
|
|
5
5
|
duckdb_database db;
|
|
6
|
+
/* Functions and logical types registered on any connection to this database */
|
|
7
|
+
VALUE registered_functions;
|
|
8
|
+
/*
|
|
9
|
+
* Set only for a database handed out by DuckDB::InstanceCache: the path it
|
|
10
|
+
* was opened under, and the cache's weak set of live wrappers. Together they
|
|
11
|
+
* let the cache hand back this same wrapper for the same path, and let
|
|
12
|
+
* #close drop this wrapper from the set. Qnil for any other database.
|
|
13
|
+
*/
|
|
14
|
+
VALUE cached_path;
|
|
15
|
+
VALUE cache_wrappers;
|
|
6
16
|
};
|
|
7
17
|
|
|
8
18
|
typedef struct _rubyDuckDB rubyDuckDB;
|
|
9
19
|
|
|
10
20
|
rubyDuckDB *rbduckdb_get_struct_database(VALUE obj);
|
|
11
21
|
VALUE rbduckdb_create_database_obj(duckdb_database db);
|
|
22
|
+
void rbduckdb_database_retain(VALUE database, VALUE obj);
|
|
23
|
+
void rbduckdb_database_set_cache_entry(VALUE database, VALUE path, VALUE wrappers);
|
|
12
24
|
void rbduckdb_init_database(void);
|
|
13
25
|
|
|
14
26
|
#endif
|
data/ext/duckdb/expression.c
CHANGED
|
@@ -11,7 +11,7 @@ static VALUE expression__fold(VALUE self, VALUE client_context);
|
|
|
11
11
|
static const rb_data_type_t expression_data_type = {
|
|
12
12
|
"DuckDB/Expression",
|
|
13
13
|
{NULL, deallocate, memsize,},
|
|
14
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
14
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
static void deallocate(void *ctx) {
|
|
@@ -9,7 +9,7 @@ static size_t memsize(const void *p);
|
|
|
9
9
|
static const rb_data_type_t extract_statements_data_type = {
|
|
10
10
|
"DuckDB/ExtractedStatements",
|
|
11
11
|
{NULL, deallocate, memsize,},
|
|
12
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
12
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
static void deallocate(void *ctx);
|