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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +169 -0
  3. data/.github/workflows/linter.yml +30 -0
  4. data/.github/workflows/test_on_macos.yml +7 -2
  5. data/.github/workflows/test_on_ubuntu.yml +7 -2
  6. data/.github/workflows/test_on_windows.yml +8 -3
  7. data/.rubocop.yml +34 -0
  8. data/CHANGELOG.md +74 -0
  9. data/Dockerfile +2 -2
  10. data/Gemfile +12 -0
  11. data/Gemfile.lock +70 -7
  12. data/Rakefile +8 -6
  13. data/bin/console +4 -3
  14. data/duckdb.gemspec +2 -6
  15. data/ext/duckdb/bind_info.c +243 -0
  16. data/ext/duckdb/bind_info.h +13 -0
  17. data/ext/duckdb/connection.c +60 -2
  18. data/ext/duckdb/connection.h +1 -0
  19. data/ext/duckdb/data_chunk.c +137 -0
  20. data/ext/duckdb/data_chunk.h +13 -0
  21. data/ext/duckdb/duckdb.c +7 -0
  22. data/ext/duckdb/function_info.c +65 -0
  23. data/ext/duckdb/function_info.h +13 -0
  24. data/ext/duckdb/init_info.c +65 -0
  25. data/ext/duckdb/init_info.h +13 -0
  26. data/ext/duckdb/logical_type.c +42 -0
  27. data/ext/duckdb/memory_helper.c +339 -0
  28. data/ext/duckdb/memory_helper.h +8 -0
  29. data/ext/duckdb/result.c +8 -9
  30. data/ext/duckdb/result.h +1 -0
  31. data/ext/duckdb/ruby-duckdb.h +7 -0
  32. data/ext/duckdb/scalar_function.c +336 -1
  33. data/ext/duckdb/scalar_function.h +2 -0
  34. data/ext/duckdb/table_function.c +403 -0
  35. data/ext/duckdb/table_function.h +16 -0
  36. data/ext/duckdb/vector.c +201 -0
  37. data/ext/duckdb/vector.h +13 -0
  38. data/lib/duckdb/appender.rb +21 -11
  39. data/lib/duckdb/bind_info.rb +32 -0
  40. data/lib/duckdb/casting.rb +35 -0
  41. data/lib/duckdb/connection.rb +123 -2
  42. data/lib/duckdb/converter.rb +32 -47
  43. data/lib/duckdb/data_chunk.rb +114 -0
  44. data/lib/duckdb/extracted_statements.rb +1 -1
  45. data/lib/duckdb/function_info.rb +21 -0
  46. data/lib/duckdb/init_info.rb +22 -0
  47. data/lib/duckdb/instance_cache.rb +0 -4
  48. data/lib/duckdb/interval.rb +1 -1
  49. data/lib/duckdb/logical_type.rb +46 -5
  50. data/lib/duckdb/prepared_statement.rb +22 -8
  51. data/lib/duckdb/result.rb +1 -0
  52. data/lib/duckdb/scalar_function.rb +119 -0
  53. data/lib/duckdb/table_function.rb +191 -0
  54. data/lib/duckdb/vector.rb +20 -0
  55. data/lib/duckdb/version.rb +1 -1
  56. data/lib/duckdb.rb +8 -0
  57. data/sample/async_query.rb +2 -0
  58. data/sample/async_query_stream.rb +2 -0
  59. data/sample/issue922.rb +54 -0
  60. data/sample/issue922_benchmark.rb +169 -0
  61. data/sample/issue930.rb +49 -0
  62. data/sample/issue930_benchmark.rb +70 -0
  63. 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
@@ -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
+ }
@@ -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
@@ -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
- case value
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
- blob?(value) ? append_blob(value) : append_varchar(value)
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