duckdb 1.5.3.0 → 1.5.5.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 +47 -0
- data/CLAUDE.md +4 -0
- data/README.md +52 -0
- data/duckdb.gemspec +1 -1
- data/ext/duckdb/aggregate_function.c +0 -1
- data/ext/duckdb/appender.c +17 -0
- data/ext/duckdb/arrow_array_stream.c +226 -0
- data/ext/duckdb/arrow_array_stream.h +61 -0
- data/ext/duckdb/arrow_import.c +165 -0
- data/ext/duckdb/arrow_import.h +6 -0
- data/ext/duckdb/blob.c +1 -1
- data/ext/duckdb/blob.h +1 -2
- data/ext/duckdb/config.c +1 -1
- data/ext/duckdb/config.h +1 -1
- data/ext/duckdb/connection.c +26 -4
- data/ext/duckdb/converter.h +1 -0
- data/ext/duckdb/conveter.c +39 -9
- data/ext/duckdb/data_chunk.c +10 -0
- data/ext/duckdb/data_chunk.h +1 -0
- data/ext/duckdb/duckdb.c +13 -11
- data/ext/duckdb/error.c +13 -1
- data/ext/duckdb/error.h +2 -3
- data/ext/duckdb/function_executor.c +308 -2
- data/ext/duckdb/function_executor.h +44 -0
- data/ext/duckdb/function_vector.c +14 -0
- data/ext/duckdb/prepared_statement.c +141 -5
- data/ext/duckdb/result.c +49 -3
- data/ext/duckdb/result.h +11 -0
- data/ext/duckdb/ruby-duckdb.h +3 -0
- data/ext/duckdb/scalar_function.c +97 -29
- data/ext/duckdb/scalar_function.h +2 -4
- data/ext/duckdb/scalar_function_bind_info.c +13 -13
- data/ext/duckdb/scalar_function_bind_info.h +1 -1
- data/ext/duckdb/scalar_function_set.c +9 -9
- data/ext/duckdb/scalar_function_set.h +2 -2
- data/ext/duckdb/table_description.c +19 -19
- data/ext/duckdb/table_description.h +1 -1
- data/ext/duckdb/table_function.c +94 -28
- data/ext/duckdb/table_function.h +2 -2
- data/ext/duckdb/table_function_bind_info.c +20 -20
- data/ext/duckdb/table_function_bind_info.h +2 -2
- data/ext/duckdb/table_function_function_info.c +5 -5
- data/ext/duckdb/table_function_function_info.h +2 -2
- data/ext/duckdb/table_function_init_info.c +70 -5
- data/ext/duckdb/table_function_init_info.h +2 -2
- data/ext/duckdb/util.c +8 -0
- data/ext/duckdb/util.h +1 -0
- data/ext/duckdb/value.c +526 -1
- data/lib/duckdb/appender.rb +23 -0
- data/lib/duckdb/arrow_array_stream.rb +33 -0
- data/lib/duckdb/connection.rb +73 -0
- data/lib/duckdb/converter/int_to_sym.rb +54 -1
- data/lib/duckdb/converter.rb +7 -7
- data/lib/duckdb/error.rb +26 -0
- data/lib/duckdb/function_type_validation.rb +1 -0
- data/lib/duckdb/logical_type.rb +6 -3
- data/lib/duckdb/prepared_statement.rb +32 -0
- data/lib/duckdb/scalar_function.rb +6 -6
- data/lib/duckdb/value.rb +400 -0
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +2 -0
- metadata +10 -3
|
@@ -137,6 +137,20 @@ void rbduckdb_vector_set_value_at(duckdb_vector vector, duckdb_logical_type elem
|
|
|
137
137
|
((duckdb_time *)vector_data)[index] = time;
|
|
138
138
|
break;
|
|
139
139
|
}
|
|
140
|
+
case DUCKDB_TYPE_TIME_NS: {
|
|
141
|
+
if (!rb_obj_is_kind_of(value, rb_cTime)) {
|
|
142
|
+
rb_raise(rb_eTypeError, "Expected Time object for TIME_NS");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
VALUE hour = rb_funcall(value, rb_intern("hour"), 0);
|
|
146
|
+
VALUE min = rb_funcall(value, rb_intern("min"), 0);
|
|
147
|
+
VALUE sec = rb_funcall(value, rb_intern("sec"), 0);
|
|
148
|
+
VALUE nsec = rb_funcall(value, rb_intern("nsec"), 0);
|
|
149
|
+
|
|
150
|
+
duckdb_time_ns time_ns = rbduckdb_to_duckdb_time_ns_from_value(hour, min, sec, nsec);
|
|
151
|
+
((duckdb_time_ns *)vector_data)[index] = time_ns;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
140
154
|
case DUCKDB_TYPE_TIME_TZ: {
|
|
141
155
|
if (!rb_obj_is_kind_of(value, rb_cTime)) {
|
|
142
156
|
rb_raise(rb_eTypeError, "Expected Time object for TIME_TZ");
|
|
@@ -14,6 +14,7 @@ static idx_t check_index(VALUE vidx);
|
|
|
14
14
|
|
|
15
15
|
static VALUE prepared_statement_bind_parameter_index(VALUE self, VALUE name);
|
|
16
16
|
static VALUE prepared_statement_parameter_name(VALUE self, VALUE vidx);
|
|
17
|
+
static VALUE prepared_statement_param_logical_type(VALUE self, VALUE vidx);
|
|
17
18
|
static VALUE prepared_statement_clear_bindings(VALUE self);
|
|
18
19
|
static VALUE prepared_statement_bind_bool(VALUE self, VALUE vidx, VALUE val);
|
|
19
20
|
static VALUE prepared_statement_bind_int8(VALUE self, VALUE vidx, VALUE val);
|
|
@@ -27,6 +28,10 @@ static VALUE prepared_statement_bind_blob(VALUE self, VALUE vidx, VALUE blob);
|
|
|
27
28
|
static VALUE prepared_statement_bind_null(VALUE self, VALUE vidx);
|
|
28
29
|
static VALUE prepared_statement__statement_type(VALUE self);
|
|
29
30
|
static VALUE prepared_statement__param_type(VALUE self, VALUE vidx);
|
|
31
|
+
static VALUE prepared_statement_column_count(VALUE self);
|
|
32
|
+
static VALUE prepared_statement_column_name(VALUE self, VALUE vidx);
|
|
33
|
+
static VALUE prepared_statement__column_type(VALUE self, VALUE vidx);
|
|
34
|
+
static VALUE prepared_statement_column_logical_type(VALUE self, VALUE vidx);
|
|
30
35
|
static VALUE prepared_statement__bind_uint8(VALUE self, VALUE vidx, VALUE val);
|
|
31
36
|
static VALUE prepared_statement__bind_uint16(VALUE self, VALUE vidx, VALUE val);
|
|
32
37
|
static VALUE prepared_statement__bind_uint32(VALUE self, VALUE vidx, VALUE val);
|
|
@@ -38,6 +43,7 @@ static VALUE prepared_statement__bind_timestamp_tz(VALUE self, VALUE vidx, VALUE
|
|
|
38
43
|
static VALUE prepared_statement__bind_interval(VALUE self, VALUE vidx, VALUE months, VALUE days, VALUE micros);
|
|
39
44
|
static VALUE prepared_statement__bind_hugeint(VALUE self, VALUE vidx, VALUE lower, VALUE upper);
|
|
40
45
|
static VALUE prepared_statement__bind_uhugeint(VALUE self, VALUE vidx, VALUE lower, VALUE upper);
|
|
46
|
+
static VALUE prepared_statement__bind_uuid(VALUE self, VALUE vidx, VALUE val);
|
|
41
47
|
static VALUE prepared_statement__bind_decimal(VALUE self, VALUE vidx, VALUE lower, VALUE upper, VALUE width, VALUE scale);
|
|
42
48
|
static VALUE prepared_statement__bind_value(VALUE self, VALUE vidx, VALUE val);
|
|
43
49
|
|
|
@@ -128,7 +134,6 @@ static VALUE prepared_statement_execute(VALUE self) {
|
|
|
128
134
|
rubyDuckDBResult *ctxr;
|
|
129
135
|
const char *error;
|
|
130
136
|
VALUE result = rbduckdb_create_result();
|
|
131
|
-
VALUE msg;
|
|
132
137
|
|
|
133
138
|
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
134
139
|
ctxr = rbduckdb_get_struct_result(result);
|
|
@@ -145,11 +150,10 @@ static VALUE prepared_statement_execute(VALUE self) {
|
|
|
145
150
|
if (state == DuckDBError) {
|
|
146
151
|
error = duckdb_prepare_error(args.prepared_statement);
|
|
147
152
|
if (error == NULL) {
|
|
148
|
-
error
|
|
153
|
+
/* error originates from the result, which carries an error type */
|
|
154
|
+
rbduckdb_raise_result_error(args.out_result);
|
|
149
155
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
rb_raise(eDuckDBError, "%s", StringValuePtr(msg));
|
|
156
|
+
rb_raise(eDuckDBError, "%s", error);
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
return result;
|
|
@@ -205,6 +209,28 @@ static VALUE prepared_statement_parameter_name(VALUE self, VALUE vidx) {
|
|
|
205
209
|
return vname;
|
|
206
210
|
}
|
|
207
211
|
|
|
212
|
+
/*
|
|
213
|
+
* call-seq:
|
|
214
|
+
* prepared_statement.param_logical_type(param_index) -> DuckDB::LogicalType
|
|
215
|
+
*
|
|
216
|
+
* Returns the logical type of the parameter at the given (1-based) index.
|
|
217
|
+
* This provides richer type information than #param_type, e.g. decimal
|
|
218
|
+
* width/scale, nested types.
|
|
219
|
+
*/
|
|
220
|
+
static VALUE prepared_statement_param_logical_type(VALUE self, VALUE vidx) {
|
|
221
|
+
rubyDuckDBPreparedStatement *ctx;
|
|
222
|
+
duckdb_logical_type logical_type;
|
|
223
|
+
idx_t idx = check_index(vidx);
|
|
224
|
+
|
|
225
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
226
|
+
|
|
227
|
+
logical_type = duckdb_param_logical_type(ctx->prepared_statement, idx);
|
|
228
|
+
if (logical_type == NULL) {
|
|
229
|
+
rb_raise(eDuckDBError, "fail to get logical type of the parameter at %llu. parameter index is out of range.", (unsigned long long)idx);
|
|
230
|
+
}
|
|
231
|
+
return rbduckdb_create_logical_type(logical_type);
|
|
232
|
+
}
|
|
233
|
+
|
|
208
234
|
/*
|
|
209
235
|
* call-seq:
|
|
210
236
|
* prepared_statement.clear_bindings -> DuckDB::PreparedStatement
|
|
@@ -359,6 +385,91 @@ static VALUE prepared_statement__statement_type(VALUE self) {
|
|
|
359
385
|
return INT2FIX(duckdb_prepared_statement_type(ctx->prepared_statement));
|
|
360
386
|
}
|
|
361
387
|
|
|
388
|
+
/*
|
|
389
|
+
* call-seq:
|
|
390
|
+
* prepared_statement.column_count -> Integer
|
|
391
|
+
*
|
|
392
|
+
* Returns the number of columns in the result set of the prepared statement
|
|
393
|
+
* without executing it.
|
|
394
|
+
*
|
|
395
|
+
* require 'duckdb'
|
|
396
|
+
* db = DuckDB::Database.open
|
|
397
|
+
* con = db.connect
|
|
398
|
+
* stmt = con.prepared_statement('SELECT 1 AS a, 2 AS b')
|
|
399
|
+
* stmt.column_count # => 2
|
|
400
|
+
*/
|
|
401
|
+
static VALUE prepared_statement_column_count(VALUE self) {
|
|
402
|
+
rubyDuckDBPreparedStatement *ctx;
|
|
403
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
404
|
+
return ULL2NUM(duckdb_prepared_statement_column_count(ctx->prepared_statement));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/*
|
|
408
|
+
* call-seq:
|
|
409
|
+
* prepared_statement.column_name(col_index) -> String
|
|
410
|
+
*
|
|
411
|
+
* Returns the name of the column at the specified index (0-based) of the
|
|
412
|
+
* result set of the prepared statement without executing it.
|
|
413
|
+
* Raises DuckDB::Error if the column index is out of range.
|
|
414
|
+
*
|
|
415
|
+
* require 'duckdb'
|
|
416
|
+
* db = DuckDB::Database.open
|
|
417
|
+
* con = db.connect
|
|
418
|
+
* stmt = con.prepared_statement('SELECT 1 AS a, 2 AS b')
|
|
419
|
+
* stmt.column_name(1) # => 'b'
|
|
420
|
+
*/
|
|
421
|
+
static VALUE prepared_statement_column_name(VALUE self, VALUE vidx) {
|
|
422
|
+
rubyDuckDBPreparedStatement *ctx;
|
|
423
|
+
VALUE vname;
|
|
424
|
+
const char *name;
|
|
425
|
+
idx_t idx = NUM2ULL(vidx);
|
|
426
|
+
|
|
427
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
428
|
+
|
|
429
|
+
name = duckdb_prepared_statement_column_name(ctx->prepared_statement, idx);
|
|
430
|
+
if (name == NULL) {
|
|
431
|
+
rb_raise(eDuckDBError, "fail to get column name at %llu. column index is out of range.", (unsigned long long)idx);
|
|
432
|
+
}
|
|
433
|
+
vname = rb_str_new2(name);
|
|
434
|
+
duckdb_free((void *)name);
|
|
435
|
+
return vname;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* :nodoc: */
|
|
439
|
+
static VALUE prepared_statement__column_type(VALUE self, VALUE vidx) {
|
|
440
|
+
rubyDuckDBPreparedStatement *ctx;
|
|
441
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
442
|
+
return INT2FIX(duckdb_prepared_statement_column_type(ctx->prepared_statement, NUM2ULL(vidx)));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/*
|
|
446
|
+
* call-seq:
|
|
447
|
+
* prepared_statement.column_logical_type(col_index) -> DuckDB::LogicalType
|
|
448
|
+
*
|
|
449
|
+
* Returns the logical type of the column at the specified index (0-based) of
|
|
450
|
+
* the result set of the prepared statement without executing it.
|
|
451
|
+
* Raises DuckDB::Error if the column index is out of range.
|
|
452
|
+
*
|
|
453
|
+
* require 'duckdb'
|
|
454
|
+
* db = DuckDB::Database.open
|
|
455
|
+
* con = db.connect
|
|
456
|
+
* stmt = con.prepared_statement('SELECT 1.5::DECIMAL(9, 4) AS a')
|
|
457
|
+
* stmt.column_logical_type(0).type # => :decimal
|
|
458
|
+
*/
|
|
459
|
+
static VALUE prepared_statement_column_logical_type(VALUE self, VALUE vidx) {
|
|
460
|
+
rubyDuckDBPreparedStatement *ctx;
|
|
461
|
+
duckdb_logical_type logical_type;
|
|
462
|
+
idx_t idx = NUM2ULL(vidx);
|
|
463
|
+
|
|
464
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
465
|
+
|
|
466
|
+
logical_type = duckdb_prepared_statement_column_logical_type(ctx->prepared_statement, idx);
|
|
467
|
+
if (logical_type == NULL) {
|
|
468
|
+
rb_raise(eDuckDBError, "fail to get column logical type at %llu. column index is out of range.", (unsigned long long)idx);
|
|
469
|
+
}
|
|
470
|
+
return rbduckdb_create_logical_type(logical_type);
|
|
471
|
+
}
|
|
472
|
+
|
|
362
473
|
/* :nodoc: */
|
|
363
474
|
static VALUE prepared_statement__param_type(VALUE self, VALUE vidx) {
|
|
364
475
|
rubyDuckDBPreparedStatement *ctx;
|
|
@@ -558,6 +669,25 @@ static VALUE prepared_statement__bind_decimal(VALUE self, VALUE vidx, VALUE lowe
|
|
|
558
669
|
return self;
|
|
559
670
|
}
|
|
560
671
|
|
|
672
|
+
/* :nodoc: */
|
|
673
|
+
static VALUE prepared_statement__bind_uuid(VALUE self, VALUE vidx, VALUE val) {
|
|
674
|
+
rubyDuckDBPreparedStatement *ctx;
|
|
675
|
+
duckdb_uhugeint uhugeint;
|
|
676
|
+
duckdb_value uuid_val;
|
|
677
|
+
duckdb_state state;
|
|
678
|
+
idx_t idx = check_index(vidx);
|
|
679
|
+
|
|
680
|
+
TypedData_Get_Struct(self, rubyDuckDBPreparedStatement, &prepared_statement_data_type, ctx);
|
|
681
|
+
rbduckdb_uuid_str_to_uhugeint(val, &uhugeint);
|
|
682
|
+
uuid_val = duckdb_create_uuid(uhugeint);
|
|
683
|
+
state = duckdb_bind_value(ctx->prepared_statement, idx, uuid_val);
|
|
684
|
+
duckdb_destroy_value(&uuid_val);
|
|
685
|
+
if (state == DuckDBError) {
|
|
686
|
+
rb_raise(eDuckDBError, "fail to bind %llu parameter", (unsigned long long)idx);
|
|
687
|
+
}
|
|
688
|
+
return self;
|
|
689
|
+
}
|
|
690
|
+
|
|
561
691
|
/* :nodoc: */
|
|
562
692
|
static VALUE prepared_statement__bind_value(VALUE self, VALUE vidx, VALUE val) {
|
|
563
693
|
rubyDuckDBPreparedStatement *ctx;
|
|
@@ -593,7 +723,11 @@ void rbduckdb_init_prepared_statement(void) {
|
|
|
593
723
|
rb_define_method(cDuckDBPreparedStatement, "nparams", prepared_statement_nparams, 0);
|
|
594
724
|
rb_define_method(cDuckDBPreparedStatement, "bind_parameter_index", prepared_statement_bind_parameter_index, 1);
|
|
595
725
|
rb_define_method(cDuckDBPreparedStatement, "parameter_name", prepared_statement_parameter_name, 1);
|
|
726
|
+
rb_define_method(cDuckDBPreparedStatement, "param_logical_type", prepared_statement_param_logical_type, 1);
|
|
596
727
|
rb_define_method(cDuckDBPreparedStatement, "clear_bindings", prepared_statement_clear_bindings, 0);
|
|
728
|
+
rb_define_method(cDuckDBPreparedStatement, "column_count", prepared_statement_column_count, 0);
|
|
729
|
+
rb_define_method(cDuckDBPreparedStatement, "column_name", prepared_statement_column_name, 1);
|
|
730
|
+
rb_define_method(cDuckDBPreparedStatement, "column_logical_type", prepared_statement_column_logical_type, 1);
|
|
597
731
|
rb_define_method(cDuckDBPreparedStatement, "bind_bool", prepared_statement_bind_bool, 2);
|
|
598
732
|
rb_define_method(cDuckDBPreparedStatement, "bind_int8", prepared_statement_bind_int8, 2);
|
|
599
733
|
rb_define_method(cDuckDBPreparedStatement, "bind_int16", prepared_statement_bind_int16, 2);
|
|
@@ -610,6 +744,7 @@ void rbduckdb_init_prepared_statement(void) {
|
|
|
610
744
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_uint64", prepared_statement__bind_uint64, 2);
|
|
611
745
|
rb_define_private_method(cDuckDBPreparedStatement, "_statement_type", prepared_statement__statement_type, 0);
|
|
612
746
|
rb_define_private_method(cDuckDBPreparedStatement, "_param_type", prepared_statement__param_type, 1);
|
|
747
|
+
rb_define_private_method(cDuckDBPreparedStatement, "_column_type", prepared_statement__column_type, 1);
|
|
613
748
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_date", prepared_statement__bind_date, 4);
|
|
614
749
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_time", prepared_statement__bind_time, 5);
|
|
615
750
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_timestamp", prepared_statement__bind_timestamp, 8);
|
|
@@ -617,6 +752,7 @@ void rbduckdb_init_prepared_statement(void) {
|
|
|
617
752
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_interval", prepared_statement__bind_interval, 4);
|
|
618
753
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_hugeint", prepared_statement__bind_hugeint, 3);
|
|
619
754
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_uhugeint", prepared_statement__bind_uhugeint, 3);
|
|
755
|
+
rb_define_private_method(cDuckDBPreparedStatement, "_bind_uuid", prepared_statement__bind_uuid, 2);
|
|
620
756
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_decimal", prepared_statement__bind_decimal, 5);
|
|
621
757
|
rb_define_private_method(cDuckDBPreparedStatement, "_bind_value", prepared_statement__bind_value, 2);
|
|
622
758
|
}
|
data/ext/duckdb/result.c
CHANGED
|
@@ -16,6 +16,7 @@ static VALUE result_columns(VALUE oDuckDBResult);
|
|
|
16
16
|
static VALUE destroy_data_chunk(VALUE arg);
|
|
17
17
|
|
|
18
18
|
static VALUE result__chunk_stream(VALUE oDuckDBResult);
|
|
19
|
+
static VALUE result_arrow_c_stream(VALUE oDuckDBResult);
|
|
19
20
|
static VALUE yield_rows(VALUE arg);
|
|
20
21
|
static VALUE result__return_type(VALUE oDuckDBResult);
|
|
21
22
|
static VALUE result__statement_type(VALUE oDuckDBResult);
|
|
@@ -56,15 +57,30 @@ static const rb_data_type_t result_data_type = {
|
|
|
56
57
|
static void deallocate(void *ctx) {
|
|
57
58
|
rubyDuckDBResult *p = (rubyDuckDBResult *)ctx;
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
xfree(p);
|
|
60
|
+
rbduckdb_result_unref(p);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
static VALUE allocate(VALUE klass) {
|
|
64
|
-
rubyDuckDBResult *ctx =
|
|
64
|
+
rubyDuckDBResult *ctx = calloc((size_t)1, sizeof(rubyDuckDBResult));
|
|
65
|
+
|
|
66
|
+
if (ctx == NULL) {
|
|
67
|
+
rb_raise(rb_eNoMemError, "failed to allocate DuckDB::Result");
|
|
68
|
+
}
|
|
69
|
+
ctx->refcount = 1;
|
|
65
70
|
return TypedData_Wrap_Struct(klass, &result_data_type, ctx);
|
|
66
71
|
}
|
|
67
72
|
|
|
73
|
+
void rbduckdb_result_ref(rubyDuckDBResult *ctx) {
|
|
74
|
+
RUBY_ATOMIC_FETCH_ADD(ctx->refcount, 1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void rbduckdb_result_unref(rubyDuckDBResult *ctx) {
|
|
78
|
+
if (RUBY_ATOMIC_FETCH_SUB(ctx->refcount, 1) == 1) {
|
|
79
|
+
duckdb_destroy_result(&(ctx->result));
|
|
80
|
+
free(ctx);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
static size_t memsize(const void *p) {
|
|
69
85
|
return sizeof(rubyDuckDBResult);
|
|
70
86
|
}
|
|
@@ -647,6 +663,35 @@ static VALUE vector_uuid(void* vector_data, idx_t row_idx) {
|
|
|
647
663
|
return rbduckdb_uuid_to_ruby(((duckdb_hugeint *)vector_data)[row_idx]);
|
|
648
664
|
}
|
|
649
665
|
|
|
666
|
+
/*
|
|
667
|
+
* call-seq:
|
|
668
|
+
* result.arrow_c_stream -> DuckDB::ArrowArrayStream
|
|
669
|
+
*
|
|
670
|
+
* [EXPERIMENTAL] Exports the result as an Arrow C stream
|
|
671
|
+
* (Arrow C Data Interface). The returned stream object satisfies the
|
|
672
|
+
* Ruby Arrow C stream protocol, so it can be consumed directly by
|
|
673
|
+
* ruby-polars, red-arrow and other Arrow consumers:
|
|
674
|
+
*
|
|
675
|
+
* result = con.query('SELECT * FROM users')
|
|
676
|
+
* df = Polars::DataFrame.new(result)
|
|
677
|
+
*
|
|
678
|
+
* The stream consumes the result's chunks; a result can be exported
|
|
679
|
+
* only once. This API is built on DuckDB's unstable Arrow C API and
|
|
680
|
+
* may change in any minor release.
|
|
681
|
+
*/
|
|
682
|
+
static VALUE result_arrow_c_stream(VALUE oDuckDBResult) {
|
|
683
|
+
rubyDuckDBResult *ctx;
|
|
684
|
+
VALUE stream;
|
|
685
|
+
|
|
686
|
+
TypedData_Get_Struct(oDuckDBResult, rubyDuckDBResult, &result_data_type, ctx);
|
|
687
|
+
if (ctx->arrow_exported) {
|
|
688
|
+
rb_raise(eDuckDBError, "result is already exported as an Arrow stream");
|
|
689
|
+
}
|
|
690
|
+
stream = rbduckdb_create_arrow_array_stream(oDuckDBResult);
|
|
691
|
+
ctx->arrow_exported = true;
|
|
692
|
+
return stream;
|
|
693
|
+
}
|
|
694
|
+
|
|
650
695
|
static VALUE vector_value(duckdb_vector vector, idx_t row_idx) {
|
|
651
696
|
duckdb_logical_type ty;
|
|
652
697
|
VALUE obj = Qnil;
|
|
@@ -671,6 +716,7 @@ void rbduckdb_init_result(void) {
|
|
|
671
716
|
rb_define_method(cDuckDBResult, "rows_changed", result_rows_changed, 0);
|
|
672
717
|
rb_define_method(cDuckDBResult, "columns", result_columns, 0);
|
|
673
718
|
rb_define_private_method(cDuckDBResult, "_chunk_stream", result__chunk_stream, 0);
|
|
719
|
+
rb_define_method(cDuckDBResult, "arrow_c_stream", result_arrow_c_stream, 0);
|
|
674
720
|
rb_define_private_method(cDuckDBResult, "_return_type", result__return_type, 0);
|
|
675
721
|
rb_define_private_method(cDuckDBResult, "_statement_type", result__statement_type, 0);
|
|
676
722
|
|
data/ext/duckdb/result.h
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
#ifndef RUBY_DUCKDB_RESULT_H
|
|
2
2
|
#define RUBY_DUCKDB_RESULT_H
|
|
3
3
|
|
|
4
|
+
/*
|
|
5
|
+
* Allocated with plain calloc/free and reference-counted: the Ruby Result
|
|
6
|
+
* object holds one reference, and each exported Arrow stream holds another,
|
|
7
|
+
* so the duckdb_result stays valid for consumers that outlive the Ruby
|
|
8
|
+
* objects. rbduckdb_result_unref() must not call any Ruby API: it runs from
|
|
9
|
+
* GC sweep (deallocate) and from Arrow stream release callbacks.
|
|
10
|
+
*/
|
|
4
11
|
struct _rubyDuckDBResult {
|
|
5
12
|
duckdb_result result;
|
|
13
|
+
bool arrow_exported;
|
|
14
|
+
rb_atomic_t refcount;
|
|
6
15
|
};
|
|
7
16
|
|
|
8
17
|
typedef struct _rubyDuckDBResult rubyDuckDBResult;
|
|
9
18
|
|
|
10
19
|
rubyDuckDBResult *rbduckdb_get_struct_result(VALUE obj);
|
|
20
|
+
void rbduckdb_result_ref(rubyDuckDBResult *ctx);
|
|
21
|
+
void rbduckdb_result_unref(rubyDuckDBResult *ctx);
|
|
11
22
|
void rbduckdb_init_result(void);
|
|
12
23
|
VALUE rbduckdb_create_result(void);
|
|
13
24
|
VALUE rbduckdb_vector_value_at(duckdb_vector vector, duckdb_logical_type element_type, idx_t index);
|
data/ext/duckdb/ruby-duckdb.h
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
#include "ruby.h"
|
|
8
8
|
#include "ruby/thread.h"
|
|
9
|
+
#include "ruby/atomic.h"
|
|
9
10
|
#include <duckdb.h>
|
|
10
11
|
|
|
11
12
|
#ifdef HAVE_DUCKDB_UNSAFE_VECTOR_ASSIGN_STRING_ELEMENT_LEN
|
|
@@ -50,6 +51,8 @@
|
|
|
50
51
|
#include "./memory_helper.h"
|
|
51
52
|
#include "./table_function.h"
|
|
52
53
|
#include "./table_description.h"
|
|
54
|
+
#include "./arrow_array_stream.h"
|
|
55
|
+
#include "./arrow_import.h"
|
|
53
56
|
|
|
54
57
|
extern VALUE mDuckDB;
|
|
55
58
|
extern VALUE cDuckDBDatabase;
|
|
@@ -7,16 +7,24 @@ static void deallocate(void *);
|
|
|
7
7
|
static VALUE allocate(VALUE klass);
|
|
8
8
|
static size_t memsize(const void *p);
|
|
9
9
|
static void compact(void *);
|
|
10
|
-
static VALUE
|
|
11
|
-
static VALUE
|
|
12
|
-
static VALUE
|
|
13
|
-
static VALUE
|
|
14
|
-
static VALUE
|
|
15
|
-
static VALUE
|
|
16
|
-
static VALUE
|
|
17
|
-
static VALUE
|
|
10
|
+
static VALUE scalar_function_initialize(VALUE self);
|
|
11
|
+
static VALUE scalar_function_set_name(VALUE self, VALUE name);
|
|
12
|
+
static VALUE scalar_function__set_return_type(VALUE self, VALUE logical_type);
|
|
13
|
+
static VALUE scalar_function__set_varargs(VALUE self, VALUE logical_type);
|
|
14
|
+
static VALUE scalar_function__add_parameter(VALUE self, VALUE logical_type);
|
|
15
|
+
static VALUE scalar_function__set_special_handling(VALUE self);
|
|
16
|
+
static VALUE scalar_function_set_function(VALUE self);
|
|
17
|
+
static VALUE scalar_function__set_bind(VALUE self);
|
|
18
18
|
static void scalar_function_callback(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output);
|
|
19
19
|
static void scalar_function_bind_callback(duckdb_bind_info info);
|
|
20
|
+
#ifdef HAVE_DUCKDB_H_GE_V1_5_0
|
|
21
|
+
/*
|
|
22
|
+
* Thread detection functions (available since Ruby 2.3).
|
|
23
|
+
* Used to skip the proxy on Ruby threads.
|
|
24
|
+
*/
|
|
25
|
+
extern int ruby_native_thread_p(void);
|
|
26
|
+
static void scalar_function_init_callback(duckdb_init_info info);
|
|
27
|
+
#endif
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
struct callback_arg {
|
|
@@ -115,7 +123,7 @@ static size_t memsize(const void *p) {
|
|
|
115
123
|
return sizeof(rubyDuckDBScalarFunction);
|
|
116
124
|
}
|
|
117
125
|
|
|
118
|
-
static VALUE
|
|
126
|
+
static VALUE scalar_function_initialize(VALUE self) {
|
|
119
127
|
rubyDuckDBScalarFunction *p;
|
|
120
128
|
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
121
129
|
p->scalar_function = duckdb_create_scalar_function();
|
|
@@ -124,7 +132,7 @@ static VALUE duckdb_scalar_function_initialize(VALUE self) {
|
|
|
124
132
|
return self;
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
static VALUE
|
|
135
|
+
static VALUE scalar_function_set_name(VALUE self, VALUE name) {
|
|
128
136
|
rubyDuckDBScalarFunction *p;
|
|
129
137
|
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
130
138
|
|
|
@@ -134,7 +142,7 @@ static VALUE rbduckdb_scalar_function_set_name(VALUE self, VALUE name) {
|
|
|
134
142
|
return self;
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
static VALUE
|
|
145
|
+
static VALUE scalar_function__set_return_type(VALUE self, VALUE logical_type) {
|
|
138
146
|
rubyDuckDBScalarFunction *p;
|
|
139
147
|
rubyDuckDBLogicalType *lt;
|
|
140
148
|
|
|
@@ -146,7 +154,7 @@ static VALUE rbduckdb_scalar_function__set_return_type(VALUE self, VALUE logical
|
|
|
146
154
|
return self;
|
|
147
155
|
}
|
|
148
156
|
|
|
149
|
-
static VALUE
|
|
157
|
+
static VALUE scalar_function__set_varargs(VALUE self, VALUE logical_type) {
|
|
150
158
|
rubyDuckDBScalarFunction *p;
|
|
151
159
|
rubyDuckDBLogicalType *lt;
|
|
152
160
|
|
|
@@ -158,7 +166,7 @@ static VALUE rbduckdb_scalar_function__set_varargs(VALUE self, VALUE logical_typ
|
|
|
158
166
|
return self;
|
|
159
167
|
}
|
|
160
168
|
|
|
161
|
-
static VALUE
|
|
169
|
+
static VALUE scalar_function__set_special_handling(VALUE self) {
|
|
162
170
|
rubyDuckDBScalarFunction *p;
|
|
163
171
|
|
|
164
172
|
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
@@ -167,7 +175,7 @@ static VALUE rbduckdb_scalar_function__set_special_handling(VALUE self) {
|
|
|
167
175
|
return self;
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
static VALUE
|
|
178
|
+
static VALUE scalar_function__add_parameter(VALUE self, VALUE logical_type) {
|
|
171
179
|
rubyDuckDBScalarFunction *p;
|
|
172
180
|
rubyDuckDBLogicalType *lt;
|
|
173
181
|
|
|
@@ -191,6 +199,7 @@ static void scalar_function_callback(duckdb_function_info info, duckdb_data_chun
|
|
|
191
199
|
rubyDuckDBScalarFunction *ctx;
|
|
192
200
|
idx_t i;
|
|
193
201
|
struct callback_arg arg;
|
|
202
|
+
struct worker_proxy *proxy = NULL;
|
|
194
203
|
|
|
195
204
|
ctx = (rubyDuckDBScalarFunction *)duckdb_scalar_function_get_extra_info(info);
|
|
196
205
|
|
|
@@ -218,7 +227,11 @@ static void scalar_function_callback(duckdb_function_info info, duckdb_data_chun
|
|
|
218
227
|
arg.row_count = duckdb_data_chunk_get_size(input);
|
|
219
228
|
arg.col_count = duckdb_data_chunk_get_column_count(input);
|
|
220
229
|
|
|
221
|
-
|
|
230
|
+
#ifdef HAVE_DUCKDB_H_GE_V1_5_0
|
|
231
|
+
/* On DuckDB >= 1.5.0 each worker thread carries its own proxy (see init). */
|
|
232
|
+
proxy = (struct worker_proxy *)duckdb_scalar_function_get_state(info);
|
|
233
|
+
#endif
|
|
234
|
+
rbduckdb_function_executor_dispatch_via_proxy(execute_callback_protected, &arg, proxy);
|
|
222
235
|
}
|
|
223
236
|
|
|
224
237
|
static VALUE process_no_param_rows(VALUE varg) {
|
|
@@ -259,7 +272,7 @@ static VALUE process_rows(VALUE varg) {
|
|
|
259
272
|
}
|
|
260
273
|
|
|
261
274
|
/* Call the Ruby block with the arguments */
|
|
262
|
-
result = rb_funcallv(arg->ctx->function_proc, rb_intern("call"), arg->col_count, arg->args);
|
|
275
|
+
result = rb_funcallv(arg->ctx->function_proc, rb_intern("call"), (int)arg->col_count, arg->args);
|
|
263
276
|
|
|
264
277
|
/* Write result to output using helper function */
|
|
265
278
|
rbduckdb_vector_set_value_at(arg->output, arg->output_type, i, result);
|
|
@@ -294,14 +307,66 @@ static VALUE cleanup_callback(VALUE varg) {
|
|
|
294
307
|
return Qnil;
|
|
295
308
|
}
|
|
296
309
|
|
|
297
|
-
|
|
310
|
+
#ifdef HAVE_DUCKDB_H_GE_V1_5_0
|
|
311
|
+
/*
|
|
312
|
+
* Per-worker init for the execute path (DuckDB >= 1.5.0).
|
|
313
|
+
*
|
|
314
|
+
* DuckDB calls this once on each worker thread that will run the execute
|
|
315
|
+
* callback. We create a per-worker proxy (allocating its Ruby thread under the
|
|
316
|
+
* GVL via the global executor, since this runs on a non-Ruby thread) and store
|
|
317
|
+
* it as per-worker state. The execute callback then dispatches through it
|
|
318
|
+
* instead of the shared global executor, so workers run callbacks concurrently.
|
|
319
|
+
* DuckDB invokes rbduckdb_worker_proxy_destroy when the state is freed.
|
|
320
|
+
*/
|
|
321
|
+
struct create_proxy_callback_arg {
|
|
322
|
+
struct worker_proxy *proxy;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
static VALUE create_proxy_callback(VALUE varg) {
|
|
326
|
+
struct create_proxy_callback_arg *arg = (struct create_proxy_callback_arg *)varg;
|
|
327
|
+
arg->proxy = rbduckdb_worker_proxy_create();
|
|
328
|
+
return Qnil;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/*
|
|
332
|
+
* rbduckdb_worker_proxy_create may raise (NoMemError, Thread.new failure),
|
|
333
|
+
* and the executor runs callbacks unprotected — a raise would longjmp past
|
|
334
|
+
* its done-signaling and block the waiting DuckDB worker forever. Swallow
|
|
335
|
+
* the exception instead: the proxy stays NULL, init sets no state, and the
|
|
336
|
+
* execute callback falls back to the global executor.
|
|
337
|
+
*/
|
|
338
|
+
static void create_proxy_callback_protected(void *user_data) {
|
|
339
|
+
int exception_state;
|
|
340
|
+
|
|
341
|
+
rb_protect(create_proxy_callback, (VALUE)user_data, &exception_state);
|
|
342
|
+
if (exception_state) {
|
|
343
|
+
rb_set_errinfo(Qnil);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
static void scalar_function_init_callback(duckdb_init_info info) {
|
|
348
|
+
struct create_proxy_callback_arg arg;
|
|
349
|
+
|
|
350
|
+
/* A Ruby calling thread runs the callback inline (Case 1/2); no proxy needed. */
|
|
351
|
+
if (ruby_native_thread_p()) return;
|
|
352
|
+
|
|
353
|
+
arg.proxy = NULL;
|
|
354
|
+
rbduckdb_function_executor_dispatch(create_proxy_callback_protected, &arg);
|
|
355
|
+
|
|
356
|
+
if (arg.proxy != NULL) {
|
|
357
|
+
duckdb_scalar_function_init_set_state(info, arg.proxy, rbduckdb_worker_proxy_destroy);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
#endif
|
|
361
|
+
|
|
362
|
+
rubyDuckDBScalarFunction *rbduckdb_get_struct_scalar_function(VALUE obj) {
|
|
298
363
|
rubyDuckDBScalarFunction *ctx;
|
|
299
364
|
TypedData_Get_Struct(obj, rubyDuckDBScalarFunction, &scalar_function_data_type, ctx);
|
|
300
365
|
return ctx;
|
|
301
366
|
}
|
|
302
367
|
|
|
303
368
|
/* :nodoc: */
|
|
304
|
-
static VALUE
|
|
369
|
+
static VALUE scalar_function_set_function(VALUE self) {
|
|
305
370
|
rubyDuckDBScalarFunction *p;
|
|
306
371
|
|
|
307
372
|
if (!rb_block_given_p()) {
|
|
@@ -314,6 +379,10 @@ static VALUE rbduckdb_scalar_function_set_function(VALUE self) {
|
|
|
314
379
|
|
|
315
380
|
duckdb_scalar_function_set_extra_info(p->scalar_function, p, NULL);
|
|
316
381
|
duckdb_scalar_function_set_function(p->scalar_function, scalar_function_callback);
|
|
382
|
+
#ifdef HAVE_DUCKDB_H_GE_V1_5_0
|
|
383
|
+
/* Per-worker proxy threads for the execute path (DuckDB >= 1.5.0). */
|
|
384
|
+
duckdb_scalar_function_set_init(p->scalar_function, scalar_function_init_callback);
|
|
385
|
+
#endif
|
|
317
386
|
|
|
318
387
|
/*
|
|
319
388
|
* Mark as volatile to prevent constant folding during query optimization.
|
|
@@ -328,7 +397,7 @@ static VALUE rbduckdb_scalar_function_set_function(VALUE self) {
|
|
|
328
397
|
}
|
|
329
398
|
|
|
330
399
|
/* :nodoc: */
|
|
331
|
-
static VALUE
|
|
400
|
+
static VALUE scalar_function__set_bind(VALUE self) {
|
|
332
401
|
rubyDuckDBScalarFunction *p;
|
|
333
402
|
|
|
334
403
|
TypedData_Get_Struct(self, rubyDuckDBScalarFunction, &scalar_function_data_type, p);
|
|
@@ -406,19 +475,18 @@ static void scalar_function_bind_callback(duckdb_bind_info info) {
|
|
|
406
475
|
}
|
|
407
476
|
|
|
408
477
|
|
|
409
|
-
void
|
|
478
|
+
void rbduckdb_init_scalar_function(void) {
|
|
410
479
|
#if 0
|
|
411
480
|
VALUE mDuckDB = rb_define_module("DuckDB");
|
|
412
481
|
#endif
|
|
413
482
|
cDuckDBScalarFunction = rb_define_class_under(mDuckDB, "ScalarFunction", rb_cObject);
|
|
414
483
|
rb_define_alloc_func(cDuckDBScalarFunction, allocate);
|
|
415
|
-
rb_define_method(cDuckDBScalarFunction, "initialize",
|
|
416
|
-
rb_define_method(cDuckDBScalarFunction, "set_name",
|
|
417
|
-
|
|
418
|
-
rb_define_private_method(cDuckDBScalarFunction, "
|
|
419
|
-
rb_define_private_method(cDuckDBScalarFunction, "
|
|
420
|
-
rb_define_private_method(cDuckDBScalarFunction, "
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
rb_define_private_method(cDuckDBScalarFunction, "_set_bind", rbduckdb_scalar_function__set_bind, 0);
|
|
484
|
+
rb_define_method(cDuckDBScalarFunction, "initialize", scalar_function_initialize, 0);
|
|
485
|
+
rb_define_method(cDuckDBScalarFunction, "set_name", scalar_function_set_name, 1);
|
|
486
|
+
rb_define_private_method(cDuckDBScalarFunction, "_set_return_type", scalar_function__set_return_type, 1);
|
|
487
|
+
rb_define_private_method(cDuckDBScalarFunction, "_set_varargs", scalar_function__set_varargs, 1);
|
|
488
|
+
rb_define_private_method(cDuckDBScalarFunction, "_set_special_handling", scalar_function__set_special_handling, 0);
|
|
489
|
+
rb_define_private_method(cDuckDBScalarFunction, "_add_parameter", scalar_function__add_parameter, 1);
|
|
490
|
+
rb_define_method(cDuckDBScalarFunction, "set_function", scalar_function_set_function, 0);
|
|
491
|
+
rb_define_private_method(cDuckDBScalarFunction, "_set_bind", scalar_function__set_bind, 0);
|
|
424
492
|
}
|
|
@@ -9,9 +9,7 @@ struct _rubyDuckDBScalarFunction {
|
|
|
9
9
|
|
|
10
10
|
typedef struct _rubyDuckDBScalarFunction rubyDuckDBScalarFunction;
|
|
11
11
|
|
|
12
|
-
void
|
|
13
|
-
rubyDuckDBScalarFunction *
|
|
12
|
+
void rbduckdb_init_scalar_function(void);
|
|
13
|
+
rubyDuckDBScalarFunction *rbduckdb_get_struct_scalar_function(VALUE obj);
|
|
14
14
|
|
|
15
15
|
#endif
|
|
16
|
-
|
|
17
|
-
|