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
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#include "ruby-duckdb.h"
|
|
2
|
+
|
|
3
|
+
static VALUE cDuckDBTableFunction;
|
|
4
|
+
extern VALUE cDuckDBBindInfo;
|
|
5
|
+
extern VALUE cDuckDBInitInfo;
|
|
6
|
+
extern VALUE cDuckDBFunctionInfo;
|
|
7
|
+
extern VALUE cDuckDBDataChunk;
|
|
8
|
+
|
|
9
|
+
static void mark(void *ctx);
|
|
10
|
+
static void deallocate(void *ctx);
|
|
11
|
+
static VALUE allocate(VALUE klass);
|
|
12
|
+
static size_t memsize(const void *p);
|
|
13
|
+
static void compact(void *ctx);
|
|
14
|
+
static VALUE duckdb_table_function_initialize(VALUE self);
|
|
15
|
+
static VALUE rbduckdb_table_function_set_name(VALUE self, VALUE name);
|
|
16
|
+
static VALUE rbduckdb_table_function_add_parameter(VALUE self, VALUE logical_type);
|
|
17
|
+
static VALUE rbduckdb_table_function_add_named_parameter(VALUE self, VALUE name, VALUE logical_type);
|
|
18
|
+
static VALUE rbduckdb_table_function_set_bind(VALUE self);
|
|
19
|
+
static void table_function_bind_callback(duckdb_bind_info info);
|
|
20
|
+
static VALUE rbduckdb_table_function_set_init(VALUE self);
|
|
21
|
+
static void table_function_init_callback(duckdb_init_info info);
|
|
22
|
+
static VALUE rbduckdb_table_function_set_execute(VALUE self);
|
|
23
|
+
static void table_function_execute_callback(duckdb_function_info info, duckdb_data_chunk output);
|
|
24
|
+
|
|
25
|
+
static const rb_data_type_t table_function_data_type = {
|
|
26
|
+
"DuckDB/TableFunction",
|
|
27
|
+
{mark, deallocate, memsize, compact},
|
|
28
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
static void mark(void *ctx) {
|
|
32
|
+
rubyDuckDBTableFunction *p = (rubyDuckDBTableFunction *)ctx;
|
|
33
|
+
rb_gc_mark(p->bind_proc);
|
|
34
|
+
rb_gc_mark(p->init_proc);
|
|
35
|
+
rb_gc_mark(p->execute_proc);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static void deallocate(void *ctx) {
|
|
39
|
+
rubyDuckDBTableFunction *p = (rubyDuckDBTableFunction *)ctx;
|
|
40
|
+
|
|
41
|
+
if (p->table_function) {
|
|
42
|
+
duckdb_destroy_table_function(&(p->table_function));
|
|
43
|
+
p->table_function = NULL;
|
|
44
|
+
}
|
|
45
|
+
xfree(p);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* GC compaction callback - updates VALUE references that may have moved during compaction.
|
|
50
|
+
* This is critical for Ruby 2.7+ where GC can move objects in memory.
|
|
51
|
+
* TableFunction has three callback procs (bind, init, execute) that all need updating.
|
|
52
|
+
* Without this, these VALUE pointers could become stale after compaction,
|
|
53
|
+
* leading to crashes when DuckDB invokes the callbacks.
|
|
54
|
+
*/
|
|
55
|
+
static void compact(void *ctx) {
|
|
56
|
+
rubyDuckDBTableFunction *p = (rubyDuckDBTableFunction *)ctx;
|
|
57
|
+
if (p->bind_proc != Qnil) {
|
|
58
|
+
p->bind_proc = rb_gc_location(p->bind_proc);
|
|
59
|
+
}
|
|
60
|
+
if (p->init_proc != Qnil) {
|
|
61
|
+
p->init_proc = rb_gc_location(p->init_proc);
|
|
62
|
+
}
|
|
63
|
+
if (p->execute_proc != Qnil) {
|
|
64
|
+
p->execute_proc = rb_gc_location(p->execute_proc);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static VALUE allocate(VALUE klass) {
|
|
69
|
+
rubyDuckDBTableFunction *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBTableFunction));
|
|
70
|
+
return TypedData_Wrap_Struct(klass, &table_function_data_type, ctx);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static size_t memsize(const void *p) {
|
|
74
|
+
return sizeof(rubyDuckDBTableFunction);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
* call-seq:
|
|
79
|
+
* DuckDB::TableFunction.new -> DuckDB::TableFunction
|
|
80
|
+
*
|
|
81
|
+
* Creates a new table function.
|
|
82
|
+
*
|
|
83
|
+
* tf = DuckDB::TableFunction.new
|
|
84
|
+
* tf.name = "my_function"
|
|
85
|
+
* # ... configure tf ...
|
|
86
|
+
*/
|
|
87
|
+
static VALUE duckdb_table_function_initialize(VALUE self) {
|
|
88
|
+
rubyDuckDBTableFunction *ctx;
|
|
89
|
+
|
|
90
|
+
TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
|
|
91
|
+
|
|
92
|
+
ctx->table_function = duckdb_create_table_function();
|
|
93
|
+
if (!ctx->table_function) {
|
|
94
|
+
rb_raise(eDuckDBError, "Failed to create table function");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
ctx->bind_proc = Qnil;
|
|
98
|
+
ctx->init_proc = Qnil;
|
|
99
|
+
ctx->execute_proc = Qnil;
|
|
100
|
+
|
|
101
|
+
// Set extra_info to the C struct pointer (safe with GC compaction)
|
|
102
|
+
// Store ctx instead of self - ctx is xmalloc'd and won't move during GC
|
|
103
|
+
duckdb_table_function_set_extra_info(ctx->table_function, ctx, NULL);
|
|
104
|
+
|
|
105
|
+
return self;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* call-seq:
|
|
110
|
+
* table_function.name = name -> name
|
|
111
|
+
*
|
|
112
|
+
* Sets the name of the table function.
|
|
113
|
+
*
|
|
114
|
+
* tf.name = "my_function"
|
|
115
|
+
*/
|
|
116
|
+
static VALUE rbduckdb_table_function_set_name(VALUE self, VALUE name) {
|
|
117
|
+
rubyDuckDBTableFunction *ctx;
|
|
118
|
+
const char *func_name;
|
|
119
|
+
|
|
120
|
+
TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
|
|
121
|
+
|
|
122
|
+
if (!ctx->table_function) {
|
|
123
|
+
rb_raise(eDuckDBError, "Table function is destroyed");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func_name = StringValueCStr(name);
|
|
127
|
+
duckdb_table_function_set_name(ctx->table_function, func_name);
|
|
128
|
+
|
|
129
|
+
return name;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
* call-seq:
|
|
134
|
+
* table_function.add_parameter(logical_type) -> self
|
|
135
|
+
*
|
|
136
|
+
* Adds a positional parameter to the table function.
|
|
137
|
+
*
|
|
138
|
+
* tf.add_parameter(DuckDB::LogicalType::BIGINT)
|
|
139
|
+
* tf.add_parameter(DuckDB::LogicalType::VARCHAR)
|
|
140
|
+
*/
|
|
141
|
+
static VALUE rbduckdb_table_function_add_parameter(VALUE self, VALUE logical_type) {
|
|
142
|
+
rubyDuckDBTableFunction *ctx;
|
|
143
|
+
rubyDuckDBLogicalType *ctx_logical_type;
|
|
144
|
+
|
|
145
|
+
TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
|
|
146
|
+
|
|
147
|
+
if (!ctx->table_function) {
|
|
148
|
+
rb_raise(eDuckDBError, "Table function is destroyed");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ctx_logical_type = get_struct_logical_type(logical_type);
|
|
152
|
+
duckdb_table_function_add_parameter(ctx->table_function, ctx_logical_type->logical_type);
|
|
153
|
+
|
|
154
|
+
return self;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* call-seq:
|
|
159
|
+
* table_function.add_named_parameter(name, logical_type) -> self
|
|
160
|
+
*
|
|
161
|
+
* Adds a named parameter to the table function.
|
|
162
|
+
*
|
|
163
|
+
* tf.add_named_parameter("limit", DuckDB::LogicalType::BIGINT)
|
|
164
|
+
*/
|
|
165
|
+
static VALUE rbduckdb_table_function_add_named_parameter(VALUE self, VALUE name, VALUE logical_type) {
|
|
166
|
+
rubyDuckDBTableFunction *ctx;
|
|
167
|
+
rubyDuckDBLogicalType *ctx_logical_type;
|
|
168
|
+
const char *param_name;
|
|
169
|
+
|
|
170
|
+
TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
|
|
171
|
+
|
|
172
|
+
if (!ctx->table_function) {
|
|
173
|
+
rb_raise(eDuckDBError, "Table function is destroyed");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
param_name = StringValueCStr(name);
|
|
177
|
+
ctx_logical_type = get_struct_logical_type(logical_type);
|
|
178
|
+
duckdb_table_function_add_named_parameter(ctx->table_function, param_name, ctx_logical_type->logical_type);
|
|
179
|
+
|
|
180
|
+
return self;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* call-seq:
|
|
185
|
+
* table_function.bind { |bind_info| ... } -> self
|
|
186
|
+
*
|
|
187
|
+
* Sets the bind callback for the table function.
|
|
188
|
+
* The callback is called when the function is used in a query.
|
|
189
|
+
*
|
|
190
|
+
* table_function.bind do |bind_info|
|
|
191
|
+
* bind_info.add_result_column('id', DuckDB::LogicalType::BIGINT)
|
|
192
|
+
* bind_info.add_result_column('name', DuckDB::LogicalType::VARCHAR)
|
|
193
|
+
* end
|
|
194
|
+
*/
|
|
195
|
+
static VALUE rbduckdb_table_function_set_bind(VALUE self) {
|
|
196
|
+
rubyDuckDBTableFunction *ctx;
|
|
197
|
+
|
|
198
|
+
if (!rb_block_given_p()) {
|
|
199
|
+
rb_raise(rb_eArgError, "block is required");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
|
|
203
|
+
|
|
204
|
+
if (!ctx->table_function) {
|
|
205
|
+
rb_raise(eDuckDBError, "Table function is destroyed");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
ctx->bind_proc = rb_block_proc();
|
|
209
|
+
|
|
210
|
+
duckdb_table_function_set_bind(ctx->table_function, table_function_bind_callback);
|
|
211
|
+
|
|
212
|
+
return self;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static VALUE call_bind_proc(VALUE arg) {
|
|
216
|
+
VALUE *args = (VALUE *)arg;
|
|
217
|
+
return rb_funcall(args[0], rb_intern("call"), 1, args[1]);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static void table_function_bind_callback(duckdb_bind_info info) {
|
|
221
|
+
rubyDuckDBTableFunction *ctx;
|
|
222
|
+
rubyDuckDBBindInfo *bind_info_ctx;
|
|
223
|
+
VALUE bind_info_obj;
|
|
224
|
+
int state = 0;
|
|
225
|
+
|
|
226
|
+
// Get the C struct pointer (safe with GC compaction)
|
|
227
|
+
ctx = (rubyDuckDBTableFunction *)duckdb_bind_get_extra_info(info);
|
|
228
|
+
if (!ctx || ctx->bind_proc == Qnil) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Create BindInfo wrapper
|
|
233
|
+
bind_info_obj = rb_class_new_instance(0, NULL, cDuckDBBindInfo);
|
|
234
|
+
bind_info_ctx = get_struct_bind_info(bind_info_obj);
|
|
235
|
+
bind_info_ctx->bind_info = info;
|
|
236
|
+
|
|
237
|
+
// Call Ruby block with exception protection
|
|
238
|
+
VALUE call_args[2] = { ctx->bind_proc, bind_info_obj };
|
|
239
|
+
rb_protect(call_bind_proc, (VALUE)call_args, &state);
|
|
240
|
+
|
|
241
|
+
if (state) {
|
|
242
|
+
VALUE err = rb_errinfo();
|
|
243
|
+
VALUE msg = rb_funcall(err, rb_intern("message"), 0);
|
|
244
|
+
duckdb_bind_set_error(info, StringValueCStr(msg));
|
|
245
|
+
rb_set_errinfo(Qnil); // Clear the error
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* call-seq:
|
|
251
|
+
* table_function.init { |init_info| ... } -> table_function
|
|
252
|
+
*
|
|
253
|
+
* Sets the init callback for the table function.
|
|
254
|
+
* The callback is invoked once during query initialization to set up execution state.
|
|
255
|
+
*
|
|
256
|
+
* table_function.init do |init_info|
|
|
257
|
+
* # Initialize execution state
|
|
258
|
+
* end
|
|
259
|
+
*/
|
|
260
|
+
static VALUE rbduckdb_table_function_set_init(VALUE self) {
|
|
261
|
+
rubyDuckDBTableFunction *ctx;
|
|
262
|
+
|
|
263
|
+
if (!rb_block_given_p()) {
|
|
264
|
+
rb_raise(rb_eArgError, "block is required for init");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
|
|
268
|
+
|
|
269
|
+
if (!ctx->table_function) {
|
|
270
|
+
rb_raise(eDuckDBError, "Table function is destroyed");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
ctx->init_proc = rb_block_proc();
|
|
274
|
+
duckdb_table_function_set_init(ctx->table_function, table_function_init_callback);
|
|
275
|
+
|
|
276
|
+
return self;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
static VALUE call_init_proc(VALUE args_val) {
|
|
280
|
+
VALUE *args = (VALUE *)args_val;
|
|
281
|
+
return rb_funcall(args[0], rb_intern("call"), 1, args[1]);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
static void table_function_init_callback(duckdb_init_info info) {
|
|
285
|
+
rubyDuckDBTableFunction *ctx;
|
|
286
|
+
VALUE init_info_obj;
|
|
287
|
+
rubyDuckDBInitInfo *init_info_ctx;
|
|
288
|
+
int state = 0;
|
|
289
|
+
|
|
290
|
+
// Get the C struct pointer (safe with GC compaction)
|
|
291
|
+
ctx = (rubyDuckDBTableFunction *)duckdb_init_get_extra_info(info);
|
|
292
|
+
if (!ctx || ctx->init_proc == Qnil) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Create InitInfo wrapper
|
|
297
|
+
init_info_obj = rb_class_new_instance(0, NULL, cDuckDBInitInfo);
|
|
298
|
+
init_info_ctx = get_struct_init_info(init_info_obj);
|
|
299
|
+
init_info_ctx->info = info;
|
|
300
|
+
|
|
301
|
+
// Call Ruby block with exception protection
|
|
302
|
+
VALUE call_args[2] = { ctx->init_proc, init_info_obj };
|
|
303
|
+
rb_protect(call_init_proc, (VALUE)call_args, &state);
|
|
304
|
+
|
|
305
|
+
if (state) {
|
|
306
|
+
VALUE err = rb_errinfo();
|
|
307
|
+
VALUE msg = rb_funcall(err, rb_intern("message"), 0);
|
|
308
|
+
duckdb_init_set_error(info, StringValueCStr(msg));
|
|
309
|
+
rb_set_errinfo(Qnil); // Clear the error
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/*
|
|
314
|
+
* call-seq:
|
|
315
|
+
* table_function.execute { |function_info, output| ... } -> table_function
|
|
316
|
+
*
|
|
317
|
+
* Sets the execute callback for the table function.
|
|
318
|
+
* The callback is invoked during query execution to generate output rows.
|
|
319
|
+
*
|
|
320
|
+
* table_function.execute do |func_info, output|
|
|
321
|
+
* output.size = 10
|
|
322
|
+
* vec = output.get_vector(0)
|
|
323
|
+
* # Write data...
|
|
324
|
+
* end
|
|
325
|
+
*/
|
|
326
|
+
static VALUE rbduckdb_table_function_set_execute(VALUE self) {
|
|
327
|
+
rubyDuckDBTableFunction *ctx;
|
|
328
|
+
|
|
329
|
+
if (!rb_block_given_p()) {
|
|
330
|
+
rb_raise(rb_eArgError, "block is required for execute");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
|
|
334
|
+
|
|
335
|
+
ctx->execute_proc = rb_block_proc();
|
|
336
|
+
duckdb_table_function_set_function(ctx->table_function, table_function_execute_callback);
|
|
337
|
+
|
|
338
|
+
return self;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
static VALUE call_execute_proc(VALUE args_val) {
|
|
342
|
+
VALUE *args = (VALUE *)args_val;
|
|
343
|
+
return rb_funcall(args[0], rb_intern("call"), 2, args[1], args[2]);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
static void table_function_execute_callback(duckdb_function_info info, duckdb_data_chunk output) {
|
|
347
|
+
rubyDuckDBTableFunction *ctx;
|
|
348
|
+
VALUE func_info_obj;
|
|
349
|
+
VALUE data_chunk_obj;
|
|
350
|
+
rubyDuckDBFunctionInfo *func_info_ctx;
|
|
351
|
+
rubyDuckDBDataChunk *data_chunk_ctx;
|
|
352
|
+
int state = 0;
|
|
353
|
+
|
|
354
|
+
// Get the C struct pointer (safe with GC compaction)
|
|
355
|
+
ctx = (rubyDuckDBTableFunction *)duckdb_function_get_extra_info(info);
|
|
356
|
+
if (!ctx || ctx->execute_proc == Qnil) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Create FunctionInfo wrapper
|
|
361
|
+
func_info_obj = rb_class_new_instance(0, NULL, cDuckDBFunctionInfo);
|
|
362
|
+
func_info_ctx = get_struct_function_info(func_info_obj);
|
|
363
|
+
func_info_ctx->info = info;
|
|
364
|
+
|
|
365
|
+
// Create DataChunk wrapper
|
|
366
|
+
data_chunk_obj = rb_class_new_instance(0, NULL, cDuckDBDataChunk);
|
|
367
|
+
data_chunk_ctx = get_struct_data_chunk(data_chunk_obj);
|
|
368
|
+
data_chunk_ctx->data_chunk = output;
|
|
369
|
+
|
|
370
|
+
// Call Ruby block with exception protection
|
|
371
|
+
VALUE call_args[3] = { ctx->execute_proc, func_info_obj, data_chunk_obj };
|
|
372
|
+
rb_protect(call_execute_proc, (VALUE)call_args, &state);
|
|
373
|
+
|
|
374
|
+
if (state) {
|
|
375
|
+
VALUE err = rb_errinfo();
|
|
376
|
+
VALUE msg = rb_funcall(err, rb_intern("message"), 0);
|
|
377
|
+
duckdb_function_set_error(info, StringValueCStr(msg));
|
|
378
|
+
rb_set_errinfo(Qnil); // Clear the error
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
rubyDuckDBTableFunction *get_struct_table_function(VALUE self) {
|
|
383
|
+
rubyDuckDBTableFunction *ctx;
|
|
384
|
+
TypedData_Get_Struct(self, rubyDuckDBTableFunction, &table_function_data_type, ctx);
|
|
385
|
+
return ctx;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
void rbduckdb_init_duckdb_table_function(void) {
|
|
389
|
+
#if 0
|
|
390
|
+
VALUE mDuckDB = rb_define_module("DuckDB");
|
|
391
|
+
#endif
|
|
392
|
+
cDuckDBTableFunction = rb_define_class_under(mDuckDB, "TableFunction", rb_cObject);
|
|
393
|
+
rb_define_alloc_func(cDuckDBTableFunction, allocate);
|
|
394
|
+
|
|
395
|
+
rb_define_method(cDuckDBTableFunction, "initialize", duckdb_table_function_initialize, 0);
|
|
396
|
+
rb_define_method(cDuckDBTableFunction, "set_name", rbduckdb_table_function_set_name, 1);
|
|
397
|
+
rb_define_method(cDuckDBTableFunction, "name=", rbduckdb_table_function_set_name, 1);
|
|
398
|
+
rb_define_method(cDuckDBTableFunction, "add_parameter", rbduckdb_table_function_add_parameter, 1);
|
|
399
|
+
rb_define_method(cDuckDBTableFunction, "add_named_parameter", rbduckdb_table_function_add_named_parameter, 2);
|
|
400
|
+
rb_define_method(cDuckDBTableFunction, "bind", rbduckdb_table_function_set_bind, 0);
|
|
401
|
+
rb_define_method(cDuckDBTableFunction, "init", rbduckdb_table_function_set_init, 0);
|
|
402
|
+
rb_define_method(cDuckDBTableFunction, "execute", rbduckdb_table_function_set_execute, 0);
|
|
403
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#ifndef RUBY_DUCKDB_TABLE_FUNCTION_H
|
|
2
|
+
#define RUBY_DUCKDB_TABLE_FUNCTION_H
|
|
3
|
+
|
|
4
|
+
struct _rubyDuckDBTableFunction {
|
|
5
|
+
duckdb_table_function table_function;
|
|
6
|
+
VALUE bind_proc;
|
|
7
|
+
VALUE init_proc;
|
|
8
|
+
VALUE execute_proc;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
typedef struct _rubyDuckDBTableFunction rubyDuckDBTableFunction;
|
|
12
|
+
|
|
13
|
+
rubyDuckDBTableFunction *get_struct_table_function(VALUE self);
|
|
14
|
+
void rbduckdb_init_duckdb_table_function(void);
|
|
15
|
+
|
|
16
|
+
#endif
|
data/ext/duckdb/vector.c
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#include "ruby-duckdb.h"
|
|
2
|
+
|
|
3
|
+
VALUE cDuckDBVector;
|
|
4
|
+
|
|
5
|
+
static void deallocate(void *ctx);
|
|
6
|
+
static VALUE allocate(VALUE klass);
|
|
7
|
+
static size_t memsize(const void *p);
|
|
8
|
+
static VALUE rbduckdb_vector_get_data(VALUE self);
|
|
9
|
+
static VALUE rbduckdb_vector_get_validity(VALUE self);
|
|
10
|
+
static VALUE rbduckdb_vector_assign_string_element(VALUE self, VALUE index, VALUE str);
|
|
11
|
+
static VALUE rbduckdb_vector_assign_string_element_len(VALUE self, VALUE index, VALUE str);
|
|
12
|
+
static VALUE rbduckdb_vector_logical_type(VALUE self);
|
|
13
|
+
static VALUE rbduckdb_vector_set_validity(VALUE self, VALUE index, VALUE valid);
|
|
14
|
+
|
|
15
|
+
static const rb_data_type_t vector_data_type = {
|
|
16
|
+
"DuckDB/Vector",
|
|
17
|
+
{NULL, deallocate, memsize,},
|
|
18
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static void deallocate(void *ctx) {
|
|
22
|
+
rubyDuckDBVector *p = (rubyDuckDBVector *)ctx;
|
|
23
|
+
xfree(p);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static VALUE allocate(VALUE klass) {
|
|
27
|
+
rubyDuckDBVector *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBVector));
|
|
28
|
+
return TypedData_Wrap_Struct(klass, &vector_data_type, ctx);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static size_t memsize(const void *p) {
|
|
32
|
+
return sizeof(rubyDuckDBVector);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
rubyDuckDBVector *get_struct_vector(VALUE obj) {
|
|
36
|
+
rubyDuckDBVector *ctx;
|
|
37
|
+
TypedData_Get_Struct(obj, rubyDuckDBVector, &vector_data_type, ctx);
|
|
38
|
+
return ctx;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
* call-seq:
|
|
43
|
+
* vector.get_data -> Integer (pointer address)
|
|
44
|
+
*
|
|
45
|
+
* Gets the raw data pointer for the vector.
|
|
46
|
+
* Returns the memory address as an integer.
|
|
47
|
+
*
|
|
48
|
+
* ptr = vector.get_data
|
|
49
|
+
*/
|
|
50
|
+
static VALUE rbduckdb_vector_get_data(VALUE self) {
|
|
51
|
+
rubyDuckDBVector *ctx;
|
|
52
|
+
void *data;
|
|
53
|
+
|
|
54
|
+
TypedData_Get_Struct(self, rubyDuckDBVector, &vector_data_type, ctx);
|
|
55
|
+
|
|
56
|
+
data = duckdb_vector_get_data(ctx->vector);
|
|
57
|
+
|
|
58
|
+
return ULL2NUM((uintptr_t)data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* call-seq:
|
|
63
|
+
* vector.get_validity -> Integer or nil (pointer address)
|
|
64
|
+
*
|
|
65
|
+
* Gets the validity mask pointer for the vector.
|
|
66
|
+
* Returns nil if all values are valid.
|
|
67
|
+
*
|
|
68
|
+
* validity = vector.get_validity
|
|
69
|
+
*/
|
|
70
|
+
static VALUE rbduckdb_vector_get_validity(VALUE self) {
|
|
71
|
+
rubyDuckDBVector *ctx;
|
|
72
|
+
uint64_t *validity;
|
|
73
|
+
|
|
74
|
+
TypedData_Get_Struct(self, rubyDuckDBVector, &vector_data_type, ctx);
|
|
75
|
+
|
|
76
|
+
validity = duckdb_vector_get_validity(ctx->vector);
|
|
77
|
+
|
|
78
|
+
if (!validity) {
|
|
79
|
+
return Qnil;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return ULL2NUM((uintptr_t)validity);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* call-seq:
|
|
87
|
+
* vector.assign_string_element(index, str) -> self
|
|
88
|
+
*
|
|
89
|
+
* Assigns a string value at the specified index.
|
|
90
|
+
*
|
|
91
|
+
* vector.assign_string_element(0, 'hello')
|
|
92
|
+
*/
|
|
93
|
+
static VALUE rbduckdb_vector_assign_string_element(VALUE self, VALUE index, VALUE str) {
|
|
94
|
+
rubyDuckDBVector *ctx;
|
|
95
|
+
idx_t idx;
|
|
96
|
+
const char *string_val;
|
|
97
|
+
|
|
98
|
+
TypedData_Get_Struct(self, rubyDuckDBVector, &vector_data_type, ctx);
|
|
99
|
+
|
|
100
|
+
idx = NUM2ULL(index);
|
|
101
|
+
string_val = StringValueCStr(str);
|
|
102
|
+
|
|
103
|
+
duckdb_vector_assign_string_element(ctx->vector, idx, string_val);
|
|
104
|
+
|
|
105
|
+
return self;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* call-seq:
|
|
110
|
+
* vector.assign_string_element_len(index, str) -> self
|
|
111
|
+
*
|
|
112
|
+
* Assigns a string/blob value at the specified index with explicit length.
|
|
113
|
+
* Supports strings containing null bytes (for BLOB columns).
|
|
114
|
+
*
|
|
115
|
+
* vector.assign_string_element_len(0, "\x00\x01\x02\x03")
|
|
116
|
+
*/
|
|
117
|
+
static VALUE rbduckdb_vector_assign_string_element_len(VALUE self, VALUE index, VALUE str) {
|
|
118
|
+
rubyDuckDBVector *ctx;
|
|
119
|
+
idx_t idx;
|
|
120
|
+
const char *string_val;
|
|
121
|
+
idx_t str_len;
|
|
122
|
+
|
|
123
|
+
TypedData_Get_Struct(self, rubyDuckDBVector, &vector_data_type, ctx);
|
|
124
|
+
|
|
125
|
+
idx = NUM2ULL(index);
|
|
126
|
+
string_val = StringValuePtr(str);
|
|
127
|
+
str_len = RSTRING_LEN(str);
|
|
128
|
+
|
|
129
|
+
duckdb_vector_assign_string_element_len(ctx->vector, idx, string_val, str_len);
|
|
130
|
+
|
|
131
|
+
return self;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* call-seq:
|
|
136
|
+
* vector.logical_type -> DuckDB::LogicalType
|
|
137
|
+
*
|
|
138
|
+
* Gets the logical type of the vector.
|
|
139
|
+
*
|
|
140
|
+
* vector = output.get_vector(0)
|
|
141
|
+
* type = vector.logical_type
|
|
142
|
+
* type.id #=> DuckDB::Type::BIGINT
|
|
143
|
+
*/
|
|
144
|
+
static VALUE rbduckdb_vector_logical_type(VALUE self) {
|
|
145
|
+
rubyDuckDBVector *ctx;
|
|
146
|
+
duckdb_logical_type logical_type;
|
|
147
|
+
|
|
148
|
+
TypedData_Get_Struct(self, rubyDuckDBVector, &vector_data_type, ctx);
|
|
149
|
+
|
|
150
|
+
logical_type = duckdb_vector_get_column_type(ctx->vector);
|
|
151
|
+
|
|
152
|
+
return rbduckdb_create_logical_type(logical_type);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/*
|
|
156
|
+
* call-seq:
|
|
157
|
+
* vector.set_validity(index, valid) -> self
|
|
158
|
+
*
|
|
159
|
+
* Sets the validity of a value at the specified index.
|
|
160
|
+
*
|
|
161
|
+
* vector.set_validity(0, false) # Mark row 0 as NULL
|
|
162
|
+
* vector.set_validity(1, true) # Mark row 1 as valid
|
|
163
|
+
*/
|
|
164
|
+
static VALUE rbduckdb_vector_set_validity(VALUE self, VALUE index, VALUE valid) {
|
|
165
|
+
rubyDuckDBVector *ctx;
|
|
166
|
+
idx_t idx;
|
|
167
|
+
uint64_t *validity;
|
|
168
|
+
|
|
169
|
+
TypedData_Get_Struct(self, rubyDuckDBVector, &vector_data_type, ctx);
|
|
170
|
+
|
|
171
|
+
idx = NUM2ULL(index);
|
|
172
|
+
|
|
173
|
+
if (RTEST(valid)) {
|
|
174
|
+
// Setting to valid - ensure validity mask exists and set bit
|
|
175
|
+
duckdb_vector_ensure_validity_writable(ctx->vector);
|
|
176
|
+
validity = duckdb_vector_get_validity(ctx->vector);
|
|
177
|
+
duckdb_validity_set_row_valid(validity, idx);
|
|
178
|
+
} else {
|
|
179
|
+
// Setting to invalid (NULL)
|
|
180
|
+
duckdb_vector_ensure_validity_writable(ctx->vector);
|
|
181
|
+
validity = duckdb_vector_get_validity(ctx->vector);
|
|
182
|
+
duckdb_validity_set_row_invalid(validity, idx);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return self;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
void rbduckdb_init_duckdb_vector(void) {
|
|
189
|
+
#if 0
|
|
190
|
+
VALUE mDuckDB = rb_define_module("DuckDB");
|
|
191
|
+
#endif
|
|
192
|
+
cDuckDBVector = rb_define_class_under(mDuckDB, "Vector", rb_cObject);
|
|
193
|
+
rb_define_alloc_func(cDuckDBVector, allocate);
|
|
194
|
+
|
|
195
|
+
rb_define_method(cDuckDBVector, "get_data", rbduckdb_vector_get_data, 0);
|
|
196
|
+
rb_define_method(cDuckDBVector, "get_validity", rbduckdb_vector_get_validity, 0);
|
|
197
|
+
rb_define_method(cDuckDBVector, "assign_string_element", rbduckdb_vector_assign_string_element, 2);
|
|
198
|
+
rb_define_method(cDuckDBVector, "assign_string_element_len", rbduckdb_vector_assign_string_element_len, 2);
|
|
199
|
+
rb_define_method(cDuckDBVector, "logical_type", rbduckdb_vector_logical_type, 0);
|
|
200
|
+
rb_define_method(cDuckDBVector, "set_validity", rbduckdb_vector_set_validity, 2);
|
|
201
|
+
}
|
data/ext/duckdb/vector.h
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#ifndef RUBY_DUCKDB_VECTOR_H
|
|
2
|
+
#define RUBY_DUCKDB_VECTOR_H
|
|
3
|
+
|
|
4
|
+
struct _rubyDuckDBVector {
|
|
5
|
+
duckdb_vector vector;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
typedef struct _rubyDuckDBVector rubyDuckDBVector;
|
|
9
|
+
|
|
10
|
+
rubyDuckDBVector *get_struct_vector(VALUE obj);
|
|
11
|
+
void rbduckdb_init_duckdb_vector(void);
|
|
12
|
+
|
|
13
|
+
#endif
|
data/lib/duckdb/appender.rb
CHANGED
|
@@ -580,6 +580,7 @@ module DuckDB
|
|
|
580
580
|
# appender.append(1)
|
|
581
581
|
# appender.append('Alice')
|
|
582
582
|
# appender.end_row
|
|
583
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
583
584
|
def append(value)
|
|
584
585
|
case value
|
|
585
586
|
when NilClass
|
|
@@ -587,18 +588,9 @@ module DuckDB
|
|
|
587
588
|
when Float
|
|
588
589
|
append_double(value)
|
|
589
590
|
when Integer
|
|
590
|
-
|
|
591
|
-
when RANGE_INT16
|
|
592
|
-
append_int16(value)
|
|
593
|
-
when RANGE_INT32
|
|
594
|
-
append_int32(value)
|
|
595
|
-
when RANGE_INT64
|
|
596
|
-
append_int64(value)
|
|
597
|
-
else
|
|
598
|
-
append_hugeint(value)
|
|
599
|
-
end
|
|
591
|
+
append_integer_value(value)
|
|
600
592
|
when String
|
|
601
|
-
|
|
593
|
+
append_string_value(value)
|
|
602
594
|
when TrueClass, FalseClass
|
|
603
595
|
append_bool(value)
|
|
604
596
|
when Time
|
|
@@ -611,6 +603,7 @@ module DuckDB
|
|
|
611
603
|
raise(DuckDB::Error, "not supported type #{value} (#{value.class})")
|
|
612
604
|
end
|
|
613
605
|
end
|
|
606
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
614
607
|
|
|
615
608
|
# append a row.
|
|
616
609
|
#
|
|
@@ -639,6 +632,23 @@ module DuckDB
|
|
|
639
632
|
value.instance_of?(DuckDB::Blob) || value.encoding == Encoding::BINARY
|
|
640
633
|
end
|
|
641
634
|
|
|
635
|
+
def append_integer_value(value) # :nodoc:
|
|
636
|
+
case value
|
|
637
|
+
when RANGE_INT16
|
|
638
|
+
append_int16(value)
|
|
639
|
+
when RANGE_INT32
|
|
640
|
+
append_int32(value)
|
|
641
|
+
when RANGE_INT64
|
|
642
|
+
append_int64(value)
|
|
643
|
+
else
|
|
644
|
+
append_hugeint(value)
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def append_string_value(value) # :nodoc:
|
|
649
|
+
blob?(value) ? append_blob(value) : append_varchar(value)
|
|
650
|
+
end
|
|
651
|
+
|
|
642
652
|
def to_time(value) # :nodoc:
|
|
643
653
|
case value
|
|
644
654
|
when Date
|