duckdb 1.4.2.0 → 1.4.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/.github/copilot-instructions.md +169 -0
- data/.github/workflows/linter.yml +30 -0
- data/.github/workflows/test_on_macos.yml +7 -2
- data/.github/workflows/test_on_ubuntu.yml +7 -2
- data/.github/workflows/test_on_windows.yml +8 -3
- data/.rubocop.yml +34 -0
- data/CHANGELOG.md +74 -0
- data/Dockerfile +2 -2
- data/Gemfile +12 -0
- data/Gemfile.lock +70 -7
- data/Rakefile +8 -6
- data/bin/console +4 -3
- data/duckdb.gemspec +2 -6
- data/ext/duckdb/bind_info.c +243 -0
- data/ext/duckdb/bind_info.h +13 -0
- data/ext/duckdb/connection.c +60 -2
- data/ext/duckdb/connection.h +1 -0
- data/ext/duckdb/data_chunk.c +137 -0
- data/ext/duckdb/data_chunk.h +13 -0
- data/ext/duckdb/duckdb.c +7 -0
- data/ext/duckdb/function_info.c +65 -0
- data/ext/duckdb/function_info.h +13 -0
- data/ext/duckdb/init_info.c +65 -0
- data/ext/duckdb/init_info.h +13 -0
- data/ext/duckdb/logical_type.c +42 -0
- data/ext/duckdb/memory_helper.c +339 -0
- data/ext/duckdb/memory_helper.h +8 -0
- data/ext/duckdb/result.c +8 -9
- data/ext/duckdb/result.h +1 -0
- data/ext/duckdb/ruby-duckdb.h +7 -0
- data/ext/duckdb/scalar_function.c +336 -1
- data/ext/duckdb/scalar_function.h +2 -0
- data/ext/duckdb/table_function.c +403 -0
- data/ext/duckdb/table_function.h +16 -0
- data/ext/duckdb/vector.c +201 -0
- data/ext/duckdb/vector.h +13 -0
- data/lib/duckdb/appender.rb +21 -11
- data/lib/duckdb/bind_info.rb +32 -0
- data/lib/duckdb/casting.rb +35 -0
- data/lib/duckdb/connection.rb +123 -2
- data/lib/duckdb/converter.rb +32 -47
- data/lib/duckdb/data_chunk.rb +114 -0
- data/lib/duckdb/extracted_statements.rb +1 -1
- data/lib/duckdb/function_info.rb +21 -0
- data/lib/duckdb/init_info.rb +22 -0
- data/lib/duckdb/instance_cache.rb +0 -4
- data/lib/duckdb/interval.rb +1 -1
- data/lib/duckdb/logical_type.rb +46 -5
- data/lib/duckdb/prepared_statement.rb +22 -8
- data/lib/duckdb/result.rb +1 -0
- data/lib/duckdb/scalar_function.rb +119 -0
- data/lib/duckdb/table_function.rb +191 -0
- data/lib/duckdb/vector.rb +20 -0
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +8 -0
- data/sample/async_query.rb +2 -0
- data/sample/async_query_stream.rb +2 -0
- data/sample/issue922.rb +54 -0
- data/sample/issue922_benchmark.rb +169 -0
- data/sample/issue930.rb +49 -0
- data/sample/issue930_benchmark.rb +70 -0
- metadata +30 -57
|
@@ -2,24 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
VALUE cDuckDBScalarFunction;
|
|
4
4
|
|
|
5
|
+
static void mark(void *);
|
|
5
6
|
static void deallocate(void *);
|
|
6
7
|
static VALUE allocate(VALUE klass);
|
|
7
8
|
static size_t memsize(const void *p);
|
|
9
|
+
static void compact(void *);
|
|
8
10
|
static VALUE duckdb_scalar_function_initialize(VALUE self);
|
|
9
11
|
static VALUE rbduckdb_scalar_function_set_name(VALUE self, VALUE name);
|
|
12
|
+
static VALUE rbduckdb_scalar_function__set_return_type(VALUE self, VALUE logical_type);
|
|
13
|
+
static VALUE rbduckdb_scalar_function_add_parameter(VALUE self, VALUE logical_type);
|
|
14
|
+
static VALUE rbduckdb_scalar_function_set_function(VALUE self);
|
|
15
|
+
static void scalar_function_callback(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output);
|
|
16
|
+
static void vector_set_value_at(duckdb_vector vector, duckdb_logical_type element_type, idx_t index, VALUE value);
|
|
17
|
+
|
|
18
|
+
struct callback_arg {
|
|
19
|
+
rubyDuckDBScalarFunction *ctx;
|
|
20
|
+
duckdb_data_chunk input;
|
|
21
|
+
duckdb_vector output;
|
|
22
|
+
duckdb_logical_type output_type;
|
|
23
|
+
duckdb_vector *input_vectors;
|
|
24
|
+
duckdb_logical_type *input_types;
|
|
25
|
+
VALUE *args;
|
|
26
|
+
idx_t row_count;
|
|
27
|
+
idx_t col_count;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
static VALUE process_rows(VALUE arg);
|
|
31
|
+
static VALUE process_no_param_rows(VALUE arg);
|
|
32
|
+
static VALUE cleanup_callback(VALUE arg);
|
|
10
33
|
|
|
11
34
|
static const rb_data_type_t scalar_function_data_type = {
|
|
12
35
|
"DuckDB/ScalarFunction",
|
|
13
|
-
{
|
|
36
|
+
{mark, deallocate, memsize, compact},
|
|
14
37
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
15
38
|
};
|
|
16
39
|
|
|
40
|
+
static void mark(void *ctx) {
|
|
41
|
+
rubyDuckDBScalarFunction *p = (rubyDuckDBScalarFunction *)ctx;
|
|
42
|
+
rb_gc_mark(p->function_proc);
|
|
43
|
+
}
|
|
44
|
+
|
|
17
45
|
static void deallocate(void * ctx) {
|
|
18
46
|
rubyDuckDBScalarFunction *p = (rubyDuckDBScalarFunction *)ctx;
|
|
19
47
|
duckdb_destroy_scalar_function(&(p->scalar_function));
|
|
20
48
|
xfree(p);
|
|
21
49
|
}
|
|
22
50
|
|
|
51
|
+
/*
|
|
52
|
+
* GC compaction callback - updates VALUE references that may have moved during compaction.
|
|
53
|
+
* This is critical for Ruby 2.7+ where GC can move objects in memory.
|
|
54
|
+
* Without this, the function_proc VALUE could become a stale pointer after compaction,
|
|
55
|
+
* leading to crashes when DuckDB invokes the callback.
|
|
56
|
+
*/
|
|
57
|
+
static void compact(void *ctx) {
|
|
58
|
+
rubyDuckDBScalarFunction *p = (rubyDuckDBScalarFunction *)ctx;
|
|
59
|
+
if (p->function_proc != Qnil) {
|
|
60
|
+
p->function_proc = rb_gc_location(p->function_proc);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
23
64
|
static VALUE allocate(VALUE klass) {
|
|
24
65
|
rubyDuckDBScalarFunction *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBScalarFunction));
|
|
25
66
|
return TypedData_Wrap_Struct(klass, &scalar_function_data_type, ctx);
|
|
@@ -33,6 +74,7 @@ static VALUE duckdb_scalar_function_initialize(VALUE self) {
|
|
|
33
74
|
rubyDuckDBScalarFunction *p;
|
|
34
75
|
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
35
76
|
p->scalar_function = duckdb_create_scalar_function();
|
|
77
|
+
p->function_proc = Qnil;
|
|
36
78
|
return self;
|
|
37
79
|
}
|
|
38
80
|
|
|
@@ -46,6 +88,295 @@ static VALUE rbduckdb_scalar_function_set_name(VALUE self, VALUE name) {
|
|
|
46
88
|
return self;
|
|
47
89
|
}
|
|
48
90
|
|
|
91
|
+
static VALUE rbduckdb_scalar_function__set_return_type(VALUE self, VALUE logical_type) {
|
|
92
|
+
rubyDuckDBScalarFunction *p;
|
|
93
|
+
rubyDuckDBLogicalType *lt;
|
|
94
|
+
|
|
95
|
+
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
96
|
+
lt = get_struct_logical_type(logical_type);
|
|
97
|
+
|
|
98
|
+
duckdb_scalar_function_set_return_type(p->scalar_function, lt->logical_type);
|
|
99
|
+
|
|
100
|
+
return self;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static VALUE rbduckdb_scalar_function_add_parameter(VALUE self, VALUE logical_type) {
|
|
104
|
+
rubyDuckDBScalarFunction *p;
|
|
105
|
+
rubyDuckDBLogicalType *lt;
|
|
106
|
+
|
|
107
|
+
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
108
|
+
lt = get_struct_logical_type(logical_type);
|
|
109
|
+
|
|
110
|
+
duckdb_scalar_function_add_parameter(p->scalar_function, lt->logical_type);
|
|
111
|
+
|
|
112
|
+
return self;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static void scalar_function_callback(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output) {
|
|
116
|
+
rubyDuckDBScalarFunction *ctx;
|
|
117
|
+
idx_t i;
|
|
118
|
+
struct callback_arg arg;
|
|
119
|
+
|
|
120
|
+
ctx = (rubyDuckDBScalarFunction *)duckdb_scalar_function_get_extra_info(info);
|
|
121
|
+
|
|
122
|
+
if (ctx == NULL || ctx->function_proc == Qnil) {
|
|
123
|
+
// Mark all rows as NULL to avoid returning uninitialized data
|
|
124
|
+
idx_t row_count = duckdb_data_chunk_get_size(input);
|
|
125
|
+
uint64_t *validity;
|
|
126
|
+
duckdb_vector_ensure_validity_writable(output);
|
|
127
|
+
validity = duckdb_vector_get_validity(output);
|
|
128
|
+
for (i = 0; i < row_count; i++) {
|
|
129
|
+
duckdb_validity_set_row_invalid(validity, i);
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Initialize callback argument structure
|
|
135
|
+
arg.ctx = ctx;
|
|
136
|
+
arg.input = input;
|
|
137
|
+
arg.output = output;
|
|
138
|
+
arg.output_type = duckdb_vector_get_column_type(output);
|
|
139
|
+
arg.input_vectors = NULL;
|
|
140
|
+
arg.input_types = NULL;
|
|
141
|
+
arg.args = NULL;
|
|
142
|
+
arg.row_count = duckdb_data_chunk_get_size(input);
|
|
143
|
+
arg.col_count = duckdb_data_chunk_get_column_count(input);
|
|
144
|
+
|
|
145
|
+
// If no parameters, call block once and replicate result to all rows
|
|
146
|
+
if (arg.col_count == 0) {
|
|
147
|
+
rb_ensure(process_no_param_rows, (VALUE)&arg, cleanup_callback, (VALUE)&arg);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Process rows with proper cleanup on exception
|
|
152
|
+
rb_ensure(process_rows, (VALUE)&arg, cleanup_callback, (VALUE)&arg);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static VALUE process_no_param_rows(VALUE varg) {
|
|
156
|
+
struct callback_arg *arg = (struct callback_arg *)varg;
|
|
157
|
+
idx_t i;
|
|
158
|
+
VALUE result;
|
|
159
|
+
|
|
160
|
+
result = rb_funcall(arg->ctx->function_proc, rb_intern("call"), 0);
|
|
161
|
+
|
|
162
|
+
for (i = 0; i < arg->row_count; i++) {
|
|
163
|
+
vector_set_value_at(arg->output, arg->output_type, i, result);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return Qnil;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static VALUE process_rows(VALUE varg) {
|
|
170
|
+
struct callback_arg *arg = (struct callback_arg *)varg;
|
|
171
|
+
idx_t i, j;
|
|
172
|
+
VALUE result;
|
|
173
|
+
|
|
174
|
+
// Allocate arrays to hold input vectors and their types
|
|
175
|
+
arg->input_vectors = ALLOC_N(duckdb_vector, arg->col_count);
|
|
176
|
+
arg->input_types = ALLOC_N(duckdb_logical_type, arg->col_count);
|
|
177
|
+
arg->args = ALLOC_N(VALUE, arg->col_count);
|
|
178
|
+
|
|
179
|
+
// Get all input vectors and their types
|
|
180
|
+
for (j = 0; j < arg->col_count; j++) {
|
|
181
|
+
arg->input_vectors[j] = duckdb_data_chunk_get_vector(arg->input, j);
|
|
182
|
+
arg->input_types[j] = duckdb_vector_get_column_type(arg->input_vectors[j]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Process each row
|
|
186
|
+
for (i = 0; i < arg->row_count; i++) {
|
|
187
|
+
// Build arguments array for this row using vector_value_at
|
|
188
|
+
for (j = 0; j < arg->col_count; j++) {
|
|
189
|
+
arg->args[j] = rbduckdb_vector_value_at(arg->input_vectors[j], arg->input_types[j], i);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Call the Ruby block with the arguments
|
|
193
|
+
result = rb_funcallv(arg->ctx->function_proc, rb_intern("call"), arg->col_count, arg->args);
|
|
194
|
+
|
|
195
|
+
// Write result to output using helper function
|
|
196
|
+
vector_set_value_at(arg->output, arg->output_type, i, result);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return Qnil;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static VALUE cleanup_callback(VALUE varg) {
|
|
203
|
+
struct callback_arg *arg = (struct callback_arg *)varg;
|
|
204
|
+
idx_t j;
|
|
205
|
+
|
|
206
|
+
// Destroy all logical types
|
|
207
|
+
if (arg->input_types != NULL) {
|
|
208
|
+
for (j = 0; j < arg->col_count; j++) {
|
|
209
|
+
duckdb_destroy_logical_type(&arg->input_types[j]);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
duckdb_destroy_logical_type(&arg->output_type);
|
|
213
|
+
|
|
214
|
+
// Free allocated memory
|
|
215
|
+
if (arg->args != NULL) {
|
|
216
|
+
xfree(arg->args);
|
|
217
|
+
}
|
|
218
|
+
if (arg->input_types != NULL) {
|
|
219
|
+
xfree(arg->input_types);
|
|
220
|
+
}
|
|
221
|
+
if (arg->input_vectors != NULL) {
|
|
222
|
+
xfree(arg->input_vectors);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return Qnil;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static void vector_set_value_at(duckdb_vector vector, duckdb_logical_type element_type, idx_t index, VALUE value) {
|
|
229
|
+
duckdb_type type_id;
|
|
230
|
+
void* vector_data;
|
|
231
|
+
uint64_t *validity;
|
|
232
|
+
|
|
233
|
+
// Handle NULL values
|
|
234
|
+
if (value == Qnil) {
|
|
235
|
+
duckdb_vector_ensure_validity_writable(vector);
|
|
236
|
+
validity = duckdb_vector_get_validity(vector);
|
|
237
|
+
duckdb_validity_set_row_invalid(validity, index);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
type_id = duckdb_get_type_id(element_type);
|
|
242
|
+
vector_data = duckdb_vector_get_data(vector);
|
|
243
|
+
|
|
244
|
+
switch(type_id) {
|
|
245
|
+
case DUCKDB_TYPE_BOOLEAN:
|
|
246
|
+
((bool *)vector_data)[index] = RTEST(value);
|
|
247
|
+
break;
|
|
248
|
+
case DUCKDB_TYPE_TINYINT:
|
|
249
|
+
((int8_t *)vector_data)[index] = (int8_t)NUM2INT(value);
|
|
250
|
+
break;
|
|
251
|
+
case DUCKDB_TYPE_UTINYINT:
|
|
252
|
+
((uint8_t *)vector_data)[index] = (uint8_t)NUM2UINT(value);
|
|
253
|
+
break;
|
|
254
|
+
case DUCKDB_TYPE_SMALLINT:
|
|
255
|
+
((int16_t *)vector_data)[index] = (int16_t)NUM2INT(value);
|
|
256
|
+
break;
|
|
257
|
+
case DUCKDB_TYPE_USMALLINT:
|
|
258
|
+
((uint16_t *)vector_data)[index] = (uint16_t)NUM2UINT(value);
|
|
259
|
+
break;
|
|
260
|
+
case DUCKDB_TYPE_INTEGER:
|
|
261
|
+
((int32_t *)vector_data)[index] = NUM2INT(value);
|
|
262
|
+
break;
|
|
263
|
+
case DUCKDB_TYPE_UINTEGER:
|
|
264
|
+
((uint32_t *)vector_data)[index] = (uint32_t)NUM2ULL(value);
|
|
265
|
+
break;
|
|
266
|
+
case DUCKDB_TYPE_BIGINT:
|
|
267
|
+
((int64_t *)vector_data)[index] = NUM2LL(value);
|
|
268
|
+
break;
|
|
269
|
+
case DUCKDB_TYPE_UBIGINT:
|
|
270
|
+
((uint64_t *)vector_data)[index] = NUM2ULL(value);
|
|
271
|
+
break;
|
|
272
|
+
case DUCKDB_TYPE_FLOAT:
|
|
273
|
+
((float *)vector_data)[index] = (float)NUM2DBL(value);
|
|
274
|
+
break;
|
|
275
|
+
case DUCKDB_TYPE_DOUBLE:
|
|
276
|
+
((double *)vector_data)[index] = NUM2DBL(value);
|
|
277
|
+
break;
|
|
278
|
+
case DUCKDB_TYPE_VARCHAR: {
|
|
279
|
+
// VARCHAR requires special API, not direct array assignment
|
|
280
|
+
VALUE str = rb_obj_as_string(value);
|
|
281
|
+
const char *str_ptr = StringValuePtr(str);
|
|
282
|
+
idx_t str_len = RSTRING_LEN(str);
|
|
283
|
+
duckdb_vector_assign_string_element_len(vector, index, str_ptr, str_len);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case DUCKDB_TYPE_BLOB: {
|
|
287
|
+
// BLOB uses same API as VARCHAR, but expects binary data
|
|
288
|
+
VALUE str = rb_obj_as_string(value);
|
|
289
|
+
const char *str_ptr = StringValuePtr(str);
|
|
290
|
+
idx_t str_len = RSTRING_LEN(str);
|
|
291
|
+
duckdb_vector_assign_string_element_len(vector, index, str_ptr, str_len);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case DUCKDB_TYPE_TIMESTAMP: {
|
|
295
|
+
// Convert Ruby Time to DuckDB timestamp (microseconds since epoch)
|
|
296
|
+
if (!rb_obj_is_kind_of(value, rb_cTime)) {
|
|
297
|
+
rb_raise(rb_eTypeError, "Expected Time object for TIMESTAMP");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
duckdb_timestamp_struct ts_struct;
|
|
301
|
+
ts_struct.date.year = NUM2INT(rb_funcall(value, rb_intern("year"), 0));
|
|
302
|
+
ts_struct.date.month = NUM2INT(rb_funcall(value, rb_intern("month"), 0));
|
|
303
|
+
ts_struct.date.day = NUM2INT(rb_funcall(value, rb_intern("day"), 0));
|
|
304
|
+
ts_struct.time.hour = NUM2INT(rb_funcall(value, rb_intern("hour"), 0));
|
|
305
|
+
ts_struct.time.min = NUM2INT(rb_funcall(value, rb_intern("min"), 0));
|
|
306
|
+
ts_struct.time.sec = NUM2INT(rb_funcall(value, rb_intern("sec"), 0));
|
|
307
|
+
ts_struct.time.micros = NUM2INT(rb_funcall(value, rb_intern("usec"), 0));
|
|
308
|
+
|
|
309
|
+
duckdb_timestamp ts = duckdb_to_timestamp(ts_struct);
|
|
310
|
+
((duckdb_timestamp *)vector_data)[index] = ts;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
case DUCKDB_TYPE_DATE: {
|
|
314
|
+
// Convert Ruby Date to DuckDB date
|
|
315
|
+
// Ruby Date is defined in date library, check with Class name
|
|
316
|
+
VALUE date_class = rb_const_get(rb_cObject, rb_intern("Date"));
|
|
317
|
+
if (!rb_obj_is_kind_of(value, date_class)) {
|
|
318
|
+
rb_raise(rb_eTypeError, "Expected Date object for DATE");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
VALUE year = rb_funcall(value, rb_intern("year"), 0);
|
|
322
|
+
VALUE month = rb_funcall(value, rb_intern("month"), 0);
|
|
323
|
+
VALUE day = rb_funcall(value, rb_intern("day"), 0);
|
|
324
|
+
|
|
325
|
+
duckdb_date date = rbduckdb_to_duckdb_date_from_value(year, month, day);
|
|
326
|
+
((duckdb_date *)vector_data)[index] = date;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case DUCKDB_TYPE_TIME: {
|
|
330
|
+
// Convert Ruby Time to DuckDB time (time-of-day only)
|
|
331
|
+
if (!rb_obj_is_kind_of(value, rb_cTime)) {
|
|
332
|
+
rb_raise(rb_eTypeError, "Expected Time object for TIME");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
VALUE hour = rb_funcall(value, rb_intern("hour"), 0);
|
|
336
|
+
VALUE min = rb_funcall(value, rb_intern("min"), 0);
|
|
337
|
+
VALUE sec = rb_funcall(value, rb_intern("sec"), 0);
|
|
338
|
+
VALUE usec = rb_funcall(value, rb_intern("usec"), 0);
|
|
339
|
+
|
|
340
|
+
duckdb_time time = rbduckdb_to_duckdb_time_from_value(hour, min, sec, usec);
|
|
341
|
+
((duckdb_time *)vector_data)[index] = time;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
default:
|
|
345
|
+
rb_raise(rb_eArgError, "Unsupported return type for scalar function");
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
rubyDuckDBScalarFunction *get_struct_scalar_function(VALUE obj) {
|
|
351
|
+
rubyDuckDBScalarFunction *ctx;
|
|
352
|
+
TypedData_Get_Struct(obj, rubyDuckDBScalarFunction, &scalar_function_data_type, ctx);
|
|
353
|
+
return ctx;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* :nodoc: */
|
|
357
|
+
static VALUE rbduckdb_scalar_function_set_function(VALUE self) {
|
|
358
|
+
rubyDuckDBScalarFunction *p;
|
|
359
|
+
|
|
360
|
+
if (!rb_block_given_p()) {
|
|
361
|
+
rb_raise(rb_eArgError, "block is required");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
365
|
+
|
|
366
|
+
p->function_proc = rb_block_proc();
|
|
367
|
+
|
|
368
|
+
duckdb_scalar_function_set_extra_info(p->scalar_function, p, NULL);
|
|
369
|
+
duckdb_scalar_function_set_function(p->scalar_function, scalar_function_callback);
|
|
370
|
+
|
|
371
|
+
// Mark as volatile to prevent constant folding during query optimization
|
|
372
|
+
// This prevents DuckDB from evaluating the function at planning time.
|
|
373
|
+
// NOTE: Ruby scalar functions require single-threaded execution (PRAGMA threads=1)
|
|
374
|
+
// because Ruby proc callbacks cannot be safely invoked from DuckDB worker threads.
|
|
375
|
+
duckdb_scalar_function_set_volatile(p->scalar_function);
|
|
376
|
+
|
|
377
|
+
return self;
|
|
378
|
+
}
|
|
379
|
+
|
|
49
380
|
void rbduckdb_init_duckdb_scalar_function(void) {
|
|
50
381
|
#if 0
|
|
51
382
|
VALUE mDuckDB = rb_define_module("DuckDB");
|
|
@@ -54,4 +385,8 @@ void rbduckdb_init_duckdb_scalar_function(void) {
|
|
|
54
385
|
rb_define_alloc_func(cDuckDBScalarFunction, allocate);
|
|
55
386
|
rb_define_method(cDuckDBScalarFunction, "initialize", duckdb_scalar_function_initialize, 0);
|
|
56
387
|
rb_define_method(cDuckDBScalarFunction, "set_name", rbduckdb_scalar_function_set_name, 1);
|
|
388
|
+
rb_define_method(cDuckDBScalarFunction, "name=", rbduckdb_scalar_function_set_name, 1);
|
|
389
|
+
rb_define_private_method(cDuckDBScalarFunction, "_set_return_type", rbduckdb_scalar_function__set_return_type, 1);
|
|
390
|
+
rb_define_private_method(cDuckDBScalarFunction, "_add_parameter", rbduckdb_scalar_function_add_parameter, 1);
|
|
391
|
+
rb_define_method(cDuckDBScalarFunction, "set_function", rbduckdb_scalar_function_set_function, 0);
|
|
57
392
|
}
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
struct _rubyDuckDBScalarFunction {
|
|
5
5
|
duckdb_scalar_function scalar_function;
|
|
6
|
+
VALUE function_proc;
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
typedef struct _rubyDuckDBScalarFunction rubyDuckDBScalarFunction;
|
|
9
10
|
|
|
10
11
|
void rbduckdb_init_duckdb_scalar_function(void);
|
|
12
|
+
rubyDuckDBScalarFunction *get_struct_scalar_function(VALUE obj);
|
|
11
13
|
|
|
12
14
|
#endif
|
|
13
15
|
|