duckdb 0.2.6.0 → 0.2.9.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/workflows/test_on_macos.yml +27 -3
- data/.github/workflows/test_on_ubuntu.yml +17 -36
- data/.github/workflows/test_on_windows.yml +43 -0
- data/CHANGELOG.md +42 -0
- data/CONTRIBUTION.md +24 -0
- data/Gemfile.lock +3 -3
- data/README.md +84 -4
- data/duckdb.gemspec +2 -2
- data/ext/duckdb/appender.c +139 -3
- data/ext/duckdb/config.c +80 -0
- data/ext/duckdb/config.h +18 -0
- data/ext/duckdb/database.c +48 -2
- data/ext/duckdb/duckdb.c +6 -0
- data/ext/duckdb/extconf.rb +15 -0
- data/ext/duckdb/prepared_statement.c +26 -3
- data/ext/duckdb/result.c +67 -33
- data/ext/duckdb/ruby-duckdb.h +12 -0
- data/lib/duckdb/appender.rb +303 -0
- data/lib/duckdb/config.rb +65 -0
- data/lib/duckdb/connection.rb +21 -0
- data/lib/duckdb/database.rb +15 -2
- data/lib/duckdb/prepared_statement.rb +22 -4
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +2 -0
- metadata +13 -8
- data/.travis.yml +0 -18
data/ext/duckdb/database.c
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
VALUE cDuckDBDatabase;
|
4
4
|
|
5
|
+
static void close_database(rubyDuckDB *p);
|
6
|
+
static void deallocate(void * ctx);
|
7
|
+
static VALUE allocate(VALUE klass);
|
8
|
+
static VALUE duckdb_database_s_open(int argc, VALUE *argv, VALUE cDuckDBDatabase);
|
9
|
+
static VALUE duckdb_database_s_open_ext(int argc, VALUE *argv, VALUE cDuckDBDatabase);
|
10
|
+
|
5
11
|
static void close_database(rubyDuckDB *p)
|
6
12
|
{
|
7
13
|
duckdb_close(&(p->db));
|
@@ -25,6 +31,7 @@ static VALUE duckdb_database_s_open(int argc, VALUE *argv, VALUE cDuckDBDatabase
|
|
25
31
|
{
|
26
32
|
rubyDuckDB *ctx;
|
27
33
|
VALUE obj;
|
34
|
+
|
28
35
|
char *pfile = NULL;
|
29
36
|
VALUE file = Qnil;
|
30
37
|
|
@@ -36,13 +43,49 @@ static VALUE duckdb_database_s_open(int argc, VALUE *argv, VALUE cDuckDBDatabase
|
|
36
43
|
|
37
44
|
obj = allocate(cDuckDBDatabase);
|
38
45
|
Data_Get_Struct(obj, rubyDuckDB, ctx);
|
39
|
-
if (duckdb_open(pfile, &(ctx->db)) == DuckDBError)
|
40
|
-
{
|
46
|
+
if (duckdb_open(pfile, &(ctx->db)) == DuckDBError) {
|
41
47
|
rb_raise(eDuckDBError, "Failed to open database"); /* FIXME */
|
42
48
|
}
|
43
49
|
return obj;
|
44
50
|
}
|
45
51
|
|
52
|
+
#ifdef HAVE_DUCKDB_OPEN_EXT
|
53
|
+
static VALUE duckdb_database_s_open_ext(int argc, VALUE *argv, VALUE cDuckDBDatabase)
|
54
|
+
{
|
55
|
+
rubyDuckDB *ctx;
|
56
|
+
VALUE obj;
|
57
|
+
rubyDuckDBConfig *ctx_config;
|
58
|
+
char *perror;
|
59
|
+
|
60
|
+
char *pfile = NULL;
|
61
|
+
VALUE file = Qnil;
|
62
|
+
VALUE config = Qnil;
|
63
|
+
|
64
|
+
rb_scan_args(argc, argv, "02", &file, &config);
|
65
|
+
|
66
|
+
if (!NIL_P(file)) {
|
67
|
+
pfile = StringValuePtr(file);
|
68
|
+
}
|
69
|
+
|
70
|
+
obj = allocate(cDuckDBDatabase);
|
71
|
+
Data_Get_Struct(obj, rubyDuckDB, ctx);
|
72
|
+
if (!NIL_P(config)) {
|
73
|
+
if (!rb_obj_is_kind_of(config, cDuckDBConfig)) {
|
74
|
+
rb_raise(rb_eTypeError, "The second argument must be DuckDB::Config object.");
|
75
|
+
}
|
76
|
+
Data_Get_Struct(config, rubyDuckDBConfig, ctx_config);
|
77
|
+
if (duckdb_open_ext(pfile, &(ctx->db), ctx_config->config, &perror) == DuckDBError) {
|
78
|
+
rb_raise(eDuckDBError, "Failed to open database %s", perror);
|
79
|
+
}
|
80
|
+
} else {
|
81
|
+
if (duckdb_open(pfile, &(ctx->db)) == DuckDBError) {
|
82
|
+
rb_raise(eDuckDBError, "Failed to open database"); /* FIXME */
|
83
|
+
}
|
84
|
+
}
|
85
|
+
return obj;
|
86
|
+
}
|
87
|
+
#endif /* HAVE_DUCKDB_OPEN_EXT */
|
88
|
+
|
46
89
|
static VALUE duckdb_database_connect(VALUE self)
|
47
90
|
{
|
48
91
|
return create_connection(self);
|
@@ -67,6 +110,9 @@ void init_duckdb_database(void)
|
|
67
110
|
cDuckDBDatabase = rb_define_class_under(mDuckDB, "Database", rb_cObject);
|
68
111
|
rb_define_alloc_func(cDuckDBDatabase, allocate);
|
69
112
|
rb_define_singleton_method(cDuckDBDatabase, "_open", duckdb_database_s_open, -1);
|
113
|
+
#ifdef HAVE_DUCKDB_OPEN_EXT
|
114
|
+
rb_define_singleton_method(cDuckDBDatabase, "_open_ext", duckdb_database_s_open_ext, -1);
|
115
|
+
#endif
|
70
116
|
rb_define_private_method(cDuckDBDatabase, "_connect", duckdb_database_connect, 0);
|
71
117
|
rb_define_method(cDuckDBDatabase, "close", duckdb_database_close, 0);
|
72
118
|
}
|
data/ext/duckdb/duckdb.c
CHANGED
data/ext/duckdb/extconf.rb
CHANGED
@@ -2,8 +2,23 @@ require 'mkmf'
|
|
2
2
|
|
3
3
|
dir_config('duckdb')
|
4
4
|
if have_library('duckdb')
|
5
|
+
if have_func('duckdb_nparams(NULL)', 'duckdb.h')
|
6
|
+
$defs << '-DHAVE_DUCKDB_NPARAMS_029'
|
7
|
+
elsif have_func('duckdb_nparams(NULL, NULL)', 'duckdb.h')
|
8
|
+
$defs << '-DHAVE_DUCKDB_NPARAMS_028'
|
9
|
+
end
|
10
|
+
|
5
11
|
have_func('duckdb_value_blob', 'duckdb.h')
|
6
12
|
have_func('duckdb_bind_blob', 'duckdb.h')
|
7
13
|
have_func('duckdb_appender_create', 'duckdb.h')
|
14
|
+
have_func('duckdb_free', 'duckdb.h')
|
15
|
+
have_func('duckdb_create_config', 'duckdb.h')
|
16
|
+
have_func('duckdb_open_ext', 'duckdb.h')
|
17
|
+
have_func('duckdb_prepare_error', 'duckdb.h')
|
18
|
+
have_func('duckdb_append_date', 'duckdb.h')
|
19
|
+
have_func('duckdb_append_interval', 'duckdb.h')
|
20
|
+
have_func('duckdb_append_time', 'duckdb.h')
|
21
|
+
have_func('duckdb_append_timestamp', 'duckdb.h')
|
22
|
+
have_func('duckdb_append_hugeint', 'duckdb.h')
|
8
23
|
create_makefile('duckdb/duckdb_native')
|
9
24
|
end
|
@@ -29,8 +29,13 @@ static VALUE duckdb_prepared_statement_initialize(VALUE self, VALUE con, VALUE q
|
|
29
29
|
Data_Get_Struct(con, rubyDuckDBConnection, ctxcon);
|
30
30
|
|
31
31
|
if (duckdb_prepare(ctxcon->con, StringValuePtr(query), &(ctx->prepared_statement)) == DuckDBError) {
|
32
|
+
#ifdef HAVE_DUCKDB_PREPARE_ERROR
|
33
|
+
const char *error = duckdb_prepare_error(ctx->prepared_statement);
|
34
|
+
rb_raise(eDuckDBError, "%s", error);
|
35
|
+
#else
|
32
36
|
/* TODO: include query parameter information in error message. */
|
33
37
|
rb_raise(eDuckDBError, "failed to prepare statement");
|
38
|
+
#endif
|
34
39
|
}
|
35
40
|
return self;
|
36
41
|
}
|
@@ -39,11 +44,14 @@ static VALUE duckdb_prepared_statement_nparams(VALUE self)
|
|
39
44
|
{
|
40
45
|
rubyDuckDBPreparedStatement *ctx;
|
41
46
|
Data_Get_Struct(self, rubyDuckDBPreparedStatement, ctx);
|
42
|
-
|
47
|
+
#ifdef HAVE_DUCKDB_NPARAMS_029
|
48
|
+
return rb_int2big(duckdb_nparams(ctx->prepared_statement));
|
49
|
+
#else
|
43
50
|
if (duckdb_nparams(ctx->prepared_statement, &(ctx->nparams)) == DuckDBError) {
|
44
51
|
rb_raise(eDuckDBError, "failed to get number of parameters");
|
45
52
|
}
|
46
53
|
return rb_int2big(ctx->nparams);
|
54
|
+
#endif
|
47
55
|
}
|
48
56
|
|
49
57
|
|
@@ -70,7 +78,7 @@ static idx_t check_index(VALUE vidx)
|
|
70
78
|
return idx;
|
71
79
|
}
|
72
80
|
|
73
|
-
static VALUE
|
81
|
+
static VALUE duckdb_prepared_statement_bind_bool(VALUE self, VALUE vidx, VALUE val)
|
74
82
|
{
|
75
83
|
rubyDuckDBPreparedStatement *ctx;
|
76
84
|
idx_t idx = check_index(vidx);
|
@@ -86,6 +94,20 @@ static VALUE duckdb_prepared_statement_bind_boolean(VALUE self, VALUE vidx, VALU
|
|
86
94
|
return self;
|
87
95
|
}
|
88
96
|
|
97
|
+
static VALUE duckdb_prepared_statement_bind_int8(VALUE self, VALUE vidx, VALUE val)
|
98
|
+
{
|
99
|
+
rubyDuckDBPreparedStatement *ctx;
|
100
|
+
idx_t idx = check_index(vidx);
|
101
|
+
int8_t i8val = (int8_t)NUM2INT(val);
|
102
|
+
|
103
|
+
Data_Get_Struct(self, rubyDuckDBPreparedStatement, ctx);
|
104
|
+
|
105
|
+
if (duckdb_bind_int8(ctx->prepared_statement, idx, i8val) == DuckDBError) {
|
106
|
+
rb_raise(eDuckDBError, "fail to bind %llu parameter", (unsigned long long)idx);
|
107
|
+
}
|
108
|
+
return self;
|
109
|
+
}
|
110
|
+
|
89
111
|
static VALUE duckdb_prepared_statement_bind_int16(VALUE self, VALUE vidx, VALUE val)
|
90
112
|
{
|
91
113
|
rubyDuckDBPreparedStatement *ctx;
|
@@ -203,7 +225,8 @@ void init_duckdb_prepared_statement(void)
|
|
203
225
|
rb_define_method(cDuckDBPreparedStatement, "initialize", duckdb_prepared_statement_initialize, 2);
|
204
226
|
rb_define_method(cDuckDBPreparedStatement, "execute", duckdb_prepared_statement_execute, 0);
|
205
227
|
rb_define_method(cDuckDBPreparedStatement, "nparams", duckdb_prepared_statement_nparams, 0);
|
206
|
-
rb_define_method(cDuckDBPreparedStatement, "
|
228
|
+
rb_define_method(cDuckDBPreparedStatement, "bind_bool", duckdb_prepared_statement_bind_bool, 2);
|
229
|
+
rb_define_method(cDuckDBPreparedStatement, "bind_int8", duckdb_prepared_statement_bind_int8, 2);
|
207
230
|
rb_define_method(cDuckDBPreparedStatement, "bind_int16", duckdb_prepared_statement_bind_int16, 2);
|
208
231
|
rb_define_method(cDuckDBPreparedStatement, "bind_int32", duckdb_prepared_statement_bind_int32, 2);
|
209
232
|
rb_define_method(cDuckDBPreparedStatement, "bind_int64", duckdb_prepared_statement_bind_int64, 2);
|
data/ext/duckdb/result.c
CHANGED
@@ -2,66 +2,67 @@
|
|
2
2
|
|
3
3
|
static VALUE cDuckDBResult;
|
4
4
|
|
5
|
-
static void deallocate(void *ctx)
|
6
|
-
{
|
5
|
+
static void deallocate(void *ctx) {
|
7
6
|
rubyDuckDBResult *p = (rubyDuckDBResult *)ctx;
|
8
7
|
|
9
8
|
duckdb_destroy_result(&(p->result));
|
10
9
|
xfree(p);
|
11
10
|
}
|
12
11
|
|
13
|
-
static VALUE allocate(VALUE klass)
|
14
|
-
{
|
12
|
+
static VALUE allocate(VALUE klass) {
|
15
13
|
rubyDuckDBResult *ctx = xcalloc((size_t)1, sizeof(rubyDuckDBResult));
|
16
14
|
return Data_Wrap_Struct(klass, NULL, deallocate, ctx);
|
17
15
|
}
|
18
16
|
|
19
|
-
static VALUE to_ruby_obj_boolean(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
20
|
-
{
|
17
|
+
static VALUE to_ruby_obj_boolean(duckdb_result *result, idx_t col_idx, idx_t row_idx) {
|
21
18
|
bool bval = duckdb_value_boolean(result, col_idx, row_idx);
|
22
19
|
return bval ? Qtrue : Qnil;
|
23
20
|
}
|
24
21
|
|
25
|
-
static VALUE to_ruby_obj_smallint(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
26
|
-
{
|
22
|
+
static VALUE to_ruby_obj_smallint(duckdb_result *result, idx_t col_idx, idx_t row_idx) {
|
27
23
|
int16_t i16val = duckdb_value_int16(result, col_idx, row_idx);
|
28
24
|
return INT2FIX(i16val);
|
29
25
|
}
|
30
26
|
|
31
|
-
static VALUE to_ruby_obj_integer(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
32
|
-
{
|
27
|
+
static VALUE to_ruby_obj_integer(duckdb_result *result, idx_t col_idx, idx_t row_idx) {
|
33
28
|
int32_t i32val = duckdb_value_int32(result, col_idx, row_idx);
|
34
29
|
return INT2NUM(i32val);
|
35
30
|
}
|
36
31
|
|
37
|
-
static VALUE to_ruby_obj_bigint(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
38
|
-
{
|
32
|
+
static VALUE to_ruby_obj_bigint(duckdb_result *result, idx_t col_idx, idx_t row_idx) {
|
39
33
|
int64_t i64val = duckdb_value_int64(result, col_idx, row_idx);
|
40
34
|
return rb_int2big(i64val);
|
41
35
|
}
|
42
36
|
|
43
|
-
static VALUE to_ruby_obj_float(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
44
|
-
{
|
37
|
+
static VALUE to_ruby_obj_float(duckdb_result *result, idx_t col_idx, idx_t row_idx) {
|
45
38
|
float fval = duckdb_value_float(result, col_idx, row_idx);
|
46
39
|
return DBL2NUM(fval);
|
47
40
|
}
|
48
41
|
|
49
|
-
static VALUE to_ruby_obj_double(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
50
|
-
{
|
42
|
+
static VALUE to_ruby_obj_double(duckdb_result *result, idx_t col_idx, idx_t row_idx) {
|
51
43
|
double dval = duckdb_value_double(result, col_idx, row_idx);
|
52
44
|
return DBL2NUM(dval);
|
53
45
|
}
|
54
46
|
|
55
47
|
#ifdef HAVE_DUCKDB_VALUE_BLOB
|
56
|
-
static VALUE to_ruby_obj_string_from_blob(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
57
|
-
|
48
|
+
static VALUE to_ruby_obj_string_from_blob(duckdb_result *result, idx_t col_idx, idx_t row_idx) {
|
49
|
+
VALUE str;
|
58
50
|
duckdb_blob bval = duckdb_value_blob(result, col_idx, row_idx);
|
59
|
-
|
51
|
+
str = rb_str_new(bval.data, bval.size);
|
52
|
+
|
53
|
+
if (bval.data) {
|
54
|
+
#ifdef HAVE_DUCKDB_FREE
|
55
|
+
duckdb_free(bval.data);
|
56
|
+
#else
|
57
|
+
free(bval.data);
|
58
|
+
#endif
|
59
|
+
}
|
60
|
+
|
61
|
+
return str;
|
60
62
|
}
|
61
63
|
#endif /* HAVE_DUCKDB_VALUE_BLOB */
|
62
64
|
|
63
|
-
static VALUE to_ruby_obj(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
64
|
-
{
|
65
|
+
static VALUE to_ruby_obj(duckdb_result *result, idx_t col_idx, idx_t row_idx) {
|
65
66
|
char *p;
|
66
67
|
VALUE obj = Qnil;
|
67
68
|
if (result->columns[col_idx].nullmask[row_idx]) {
|
@@ -86,14 +87,22 @@ static VALUE to_ruby_obj(duckdb_result *result, idx_t col_idx, idx_t row_idx)
|
|
86
87
|
#endif /* HAVE_DUCKDB_VALUE_BLOB */
|
87
88
|
default:
|
88
89
|
p = duckdb_value_varchar(result, col_idx, row_idx);
|
89
|
-
|
90
|
-
|
90
|
+
if (p) {
|
91
|
+
obj = rb_str_new2(p);
|
92
|
+
#ifdef HAVE_DUCKDB_FREE
|
93
|
+
duckdb_free(p);
|
94
|
+
#else
|
95
|
+
free(p);
|
96
|
+
#endif /* HAVE_DUCKDB_FREE */
|
97
|
+
if (result->columns[col_idx].type == DUCKDB_TYPE_HUGEINT) {
|
98
|
+
obj = rb_funcall(obj, rb_intern("to_i"), 0);
|
99
|
+
}
|
100
|
+
}
|
91
101
|
}
|
92
102
|
return obj;
|
93
103
|
}
|
94
104
|
|
95
|
-
static VALUE row_array(rubyDuckDBResult *ctx, idx_t row_idx)
|
96
|
-
{
|
105
|
+
static VALUE row_array(rubyDuckDBResult *ctx, idx_t row_idx) {
|
97
106
|
idx_t col_idx;
|
98
107
|
VALUE ary = rb_ary_new2(ctx->result.column_count);
|
99
108
|
for(col_idx = 0; col_idx < ctx->result.column_count; col_idx++) {
|
@@ -102,16 +111,14 @@ static VALUE row_array(rubyDuckDBResult *ctx, idx_t row_idx)
|
|
102
111
|
return ary;
|
103
112
|
}
|
104
113
|
|
105
|
-
static VALUE duckdb_result_row_size(VALUE oDuckDBResult, VALUE args, VALUE obj)
|
106
|
-
{
|
114
|
+
static VALUE duckdb_result_row_size(VALUE oDuckDBResult, VALUE args, VALUE obj) {
|
107
115
|
rubyDuckDBResult *ctx;
|
108
116
|
Data_Get_Struct(oDuckDBResult, rubyDuckDBResult, ctx);
|
109
117
|
|
110
118
|
return LONG2FIX(ctx->result.row_count);
|
111
119
|
}
|
112
120
|
|
113
|
-
static VALUE duckdb_result_each(VALUE oDuckDBResult)
|
114
|
-
{
|
121
|
+
static VALUE duckdb_result_each(VALUE oDuckDBResult) {
|
115
122
|
rubyDuckDBResult *ctx;
|
116
123
|
idx_t row_idx = 0;
|
117
124
|
|
@@ -124,15 +131,42 @@ static VALUE duckdb_result_each(VALUE oDuckDBResult)
|
|
124
131
|
return oDuckDBResult;
|
125
132
|
}
|
126
133
|
|
127
|
-
|
128
|
-
|
134
|
+
/*
|
135
|
+
* call-seq:
|
136
|
+
* result.rows_changed -> integer
|
137
|
+
*
|
138
|
+
* Returns the count of rows changed.
|
139
|
+
*
|
140
|
+
* DuckDB::Database.open do |db|
|
141
|
+
* db.connect do |con|
|
142
|
+
* r = con.query('CREATE TABLE t2 (id INT)')
|
143
|
+
* r.rows_changed # => 0
|
144
|
+
* r = con.query('INSERT INTO t2 VALUES (1), (2), (3)')
|
145
|
+
* r.rows_changed # => 3
|
146
|
+
* r = con.query('UPDATE t2 SET id = id + 1 WHERE id > 1')
|
147
|
+
* r.rows_changed # => 2
|
148
|
+
* r = con.query('DELETE FROM t2 WHERE id = 0')
|
149
|
+
* r.rows_changed # => 0
|
150
|
+
* r = con.query('DELETE FROM t2 WHERE id = 4')
|
151
|
+
* r.rows_changed # => 1
|
152
|
+
* end
|
153
|
+
* end
|
154
|
+
*
|
155
|
+
*/
|
156
|
+
static VALUE duckdb_result_rows_changed(VALUE oDuckDBResult) {
|
157
|
+
rubyDuckDBResult *ctx;
|
158
|
+
Data_Get_Struct(oDuckDBResult, rubyDuckDBResult, ctx);
|
159
|
+
return LL2NUM(ctx->result.rows_changed);
|
160
|
+
}
|
161
|
+
|
162
|
+
VALUE create_result(void) {
|
129
163
|
return allocate(cDuckDBResult);
|
130
164
|
}
|
131
165
|
|
132
|
-
void init_duckdb_result(void)
|
133
|
-
{
|
166
|
+
void init_duckdb_result(void) {
|
134
167
|
cDuckDBResult = rb_define_class_under(mDuckDB, "Result", rb_cObject);
|
135
168
|
rb_define_alloc_func(cDuckDBResult, allocate);
|
136
169
|
|
137
170
|
rb_define_method(cDuckDBResult, "each", duckdb_result_each, 0);
|
171
|
+
rb_define_method(cDuckDBResult, "rows_changed", duckdb_result_rows_changed, 0);
|
138
172
|
}
|
data/ext/duckdb/ruby-duckdb.h
CHANGED
@@ -21,6 +21,12 @@
|
|
21
21
|
|
22
22
|
#endif /* HAVE_DUCKDB_APPENDER_CREATE */
|
23
23
|
|
24
|
+
#ifdef HAVE_DUCKDB_CREATE_CONFIG
|
25
|
+
|
26
|
+
#include "./config.h"
|
27
|
+
|
28
|
+
#endif /* HAVE_DUCKDB_CREATE_CONFIG */
|
29
|
+
|
24
30
|
extern VALUE mDuckDB;
|
25
31
|
extern VALUE cDuckDBDatabase;
|
26
32
|
extern VALUE cDuckDBConnection;
|
@@ -31,6 +37,12 @@ extern VALUE cDuckDBBlob;
|
|
31
37
|
|
32
38
|
#endif /* HAVE_DUCKDB_VALUE_BLOB */
|
33
39
|
|
40
|
+
#ifdef HAVE_DUCKDB_CREATE_CONFIG
|
41
|
+
|
42
|
+
extern VALUE cDuckDBConfig;
|
43
|
+
|
44
|
+
#endif /* HAVE_DUCKDB_CREATE_CONFIG */
|
45
|
+
|
34
46
|
extern VALUE eDuckDBError;
|
35
47
|
|
36
48
|
#endif
|
@@ -0,0 +1,303 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module DuckDB
|
5
|
+
if defined?(DuckDB::Appender)
|
6
|
+
# The DuckDB::Appender encapsulates DuckDB Appender.
|
7
|
+
#
|
8
|
+
# require 'duckdb'
|
9
|
+
# db = DuckDB::Database.open
|
10
|
+
# con = db.connect
|
11
|
+
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
|
12
|
+
# appender = con.appender('users')
|
13
|
+
# appender.append_row(1, 'Alice')
|
14
|
+
#
|
15
|
+
class Appender
|
16
|
+
RANGE_INT16 = -32_768..32_767
|
17
|
+
RANGE_INT32 = -2_147_483_648..2_147_483_647
|
18
|
+
RANGE_INT64 = -9_223_372_036_854_775_808..9_223_372_036_854_775_807
|
19
|
+
|
20
|
+
#
|
21
|
+
# appends huge int value.
|
22
|
+
#
|
23
|
+
# require 'duckdb'
|
24
|
+
# db = DuckDB::Database.open
|
25
|
+
# con = db.connect
|
26
|
+
# con.query('CREATE TABLE numbers (num HUGEINT)')
|
27
|
+
# appender = con.appender('numbers')
|
28
|
+
# appender
|
29
|
+
# .begin_row
|
30
|
+
# .append_hugeint(-170_141_183_460_469_231_731_687_303_715_884_105_727)
|
31
|
+
# .end_row
|
32
|
+
#
|
33
|
+
def append_hugeint(value)
|
34
|
+
case value
|
35
|
+
when Integer
|
36
|
+
if respond_to?(:_append_hugeint, true)
|
37
|
+
half = 1 << 64
|
38
|
+
upper = value / half
|
39
|
+
lower = value - upper * half
|
40
|
+
_append_hugeint(lower, upper)
|
41
|
+
else
|
42
|
+
append_varchar(value.to_s)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# appends date value.
|
51
|
+
#
|
52
|
+
# require 'duckdb'
|
53
|
+
# db = DuckDB::Database.open
|
54
|
+
# con = db.connect
|
55
|
+
# con.query('CREATE TABLE dates (date_value DATE)')
|
56
|
+
# appender = con.appender('dates')
|
57
|
+
# appender.begin_row
|
58
|
+
# appender.append_date(Date.today)
|
59
|
+
# # or
|
60
|
+
# # appender.append_date(Time.now)
|
61
|
+
# # appender.append_date('2021-10-10')
|
62
|
+
# appender.end_row
|
63
|
+
# appender.flush
|
64
|
+
#
|
65
|
+
def append_date(value)
|
66
|
+
case value
|
67
|
+
when Date, Time
|
68
|
+
date = value
|
69
|
+
when String
|
70
|
+
begin
|
71
|
+
date = Date.parse(value)
|
72
|
+
rescue
|
73
|
+
raise(ArgumentError, "Cannot parse argument `#{value}` to Date.")
|
74
|
+
end
|
75
|
+
else
|
76
|
+
raise(ArgumentError, "Argument `#{value}` must be Date, Time or String.")
|
77
|
+
end
|
78
|
+
|
79
|
+
_append_date(date.year, date.month, date.day)
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# appends time value.
|
84
|
+
#
|
85
|
+
# require 'duckdb'
|
86
|
+
# db = DuckDB::Database.open
|
87
|
+
# con = db.connect
|
88
|
+
# con.query('CREATE TABLE times (time_value TIME)')
|
89
|
+
# appender = con.appender('times')
|
90
|
+
# appender.begin_row
|
91
|
+
# appender.append_time(Time.now)
|
92
|
+
# # or
|
93
|
+
# # appender.append_time('01:01:01')
|
94
|
+
# appender.end_row
|
95
|
+
# appender.flush
|
96
|
+
#
|
97
|
+
def append_time(value)
|
98
|
+
case value
|
99
|
+
when Time
|
100
|
+
time = value
|
101
|
+
when String
|
102
|
+
begin
|
103
|
+
time = Time.parse(value)
|
104
|
+
rescue
|
105
|
+
raise(ArgumentError, "Cannot parse argument `#{value}` to Time.")
|
106
|
+
end
|
107
|
+
else
|
108
|
+
raise(ArgumentError, "Argument `#{value}` must be Time or String.")
|
109
|
+
end
|
110
|
+
|
111
|
+
_append_time(time.hour, time.min, time.sec, time.usec)
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# appends timestamp value.
|
116
|
+
#
|
117
|
+
# require 'duckdb'
|
118
|
+
# db = DuckDB::Database.open
|
119
|
+
# con = db.connect
|
120
|
+
# con.query('CREATE TABLE timestamps (timestamp_value TIMESTAMP)')
|
121
|
+
# appender = con.appender('timestamps')
|
122
|
+
# appender.begin_row
|
123
|
+
# appender.append_time(Time.now)
|
124
|
+
# # or
|
125
|
+
# # appender.append_time(Date.today)
|
126
|
+
# # appender.append_time('2021-08-01 01:01:01')
|
127
|
+
# appender.end_row
|
128
|
+
# appender.flush
|
129
|
+
#
|
130
|
+
def append_timestamp(value)
|
131
|
+
case value
|
132
|
+
when Time
|
133
|
+
time = value
|
134
|
+
when Date
|
135
|
+
time = value.to_time
|
136
|
+
when String
|
137
|
+
begin
|
138
|
+
time = Time.parse(value)
|
139
|
+
rescue
|
140
|
+
raise(ArgumentError, "Cannot parse argument `#{value.class} #{value}` to Time.")
|
141
|
+
end
|
142
|
+
else
|
143
|
+
raise(ArgumentError, "Argument `#{value.class} #{value}` must be Time or Date or String.")
|
144
|
+
end
|
145
|
+
_append_timestamp(time.year, time.month, time.day, time.hour, time.min, time.sec, time.nsec / 1000)
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# appends interval.
|
150
|
+
# The argument must be ISO8601 duration format.
|
151
|
+
# WARNING: This method is expremental.
|
152
|
+
#
|
153
|
+
# require 'duckdb'
|
154
|
+
# db = DuckDB::Database.open
|
155
|
+
# con = db.connect
|
156
|
+
# con.query('CREATE TABLE intervals (interval_value INTERVAL)')
|
157
|
+
# appender = con.appender('intervals')
|
158
|
+
# appender
|
159
|
+
# .begin_row
|
160
|
+
# .append_interval('P1Y2D') # => append 1 year 2 days interval.
|
161
|
+
# .end_row
|
162
|
+
# .flush
|
163
|
+
#
|
164
|
+
def append_interval(value)
|
165
|
+
raise ArgumentError, "Argument `#{value}` must be a string." unless value.is_a?(String)
|
166
|
+
|
167
|
+
hash = iso8601_interval_to_hash(value)
|
168
|
+
|
169
|
+
months, days, micros = hash_to__append_interval_args(hash)
|
170
|
+
|
171
|
+
_append_interval(months, days, micros)
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# appends value.
|
176
|
+
#
|
177
|
+
# require 'duckdb'
|
178
|
+
# db = DuckDB::Database.open
|
179
|
+
# con = db.connect
|
180
|
+
# con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
|
181
|
+
# appender = con.appender('users')
|
182
|
+
# appender.begin_row
|
183
|
+
# appender.append(1)
|
184
|
+
# appender.append('Alice')
|
185
|
+
# appender.end_row
|
186
|
+
#
|
187
|
+
def append(value)
|
188
|
+
case value
|
189
|
+
when NilClass
|
190
|
+
append_null
|
191
|
+
when Float
|
192
|
+
append_double(value)
|
193
|
+
when Integer
|
194
|
+
case value
|
195
|
+
when RANGE_INT16
|
196
|
+
append_int16(value)
|
197
|
+
when RANGE_INT32
|
198
|
+
append_int32(value)
|
199
|
+
when RANGE_INT64
|
200
|
+
append_int64(value)
|
201
|
+
else
|
202
|
+
append_hugeint(value)
|
203
|
+
end
|
204
|
+
when String
|
205
|
+
if defined?(DuckDB::Blob)
|
206
|
+
blob?(value) ? append_blob(value) : append_varchar(value)
|
207
|
+
else
|
208
|
+
append_varchar(value)
|
209
|
+
end
|
210
|
+
when TrueClass, FalseClass
|
211
|
+
append_bool(value)
|
212
|
+
when Time
|
213
|
+
if respond_to?(:append_timestamp)
|
214
|
+
append_timestamp(value)
|
215
|
+
else
|
216
|
+
append_varchar(value.strftime('%Y-%m-%d %H:%M:%S.%N'))
|
217
|
+
end
|
218
|
+
when Date
|
219
|
+
if respond_to?(:append_date)
|
220
|
+
append_date(value)
|
221
|
+
else
|
222
|
+
append_varchar(value.strftime('%Y-%m-%d'))
|
223
|
+
end
|
224
|
+
else
|
225
|
+
raise(DuckDB::Error, "not supported type #{value} (#{value.class})")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# append a row.
|
231
|
+
#
|
232
|
+
# appender.append_row(1, 'Alice')
|
233
|
+
#
|
234
|
+
# is same as:
|
235
|
+
#
|
236
|
+
# appender.begin_row
|
237
|
+
# appender.append(1)
|
238
|
+
# appender.append('Alice')
|
239
|
+
# appender.end_row
|
240
|
+
#
|
241
|
+
def append_row(*args)
|
242
|
+
begin_row
|
243
|
+
args.each do |arg|
|
244
|
+
append(arg)
|
245
|
+
end
|
246
|
+
end_row
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
def blob?(value)
|
252
|
+
value.instance_of?(DuckDB::Blob) || value.encoding == Encoding::BINARY
|
253
|
+
end
|
254
|
+
|
255
|
+
def iso8601_interval_to_hash(value)
|
256
|
+
digit = ''
|
257
|
+
time = false
|
258
|
+
hash = {}
|
259
|
+
hash.default = 0
|
260
|
+
|
261
|
+
value.each_char do |c|
|
262
|
+
if '-0123456789.'.include?(c)
|
263
|
+
digit += c
|
264
|
+
elsif c == 'T'
|
265
|
+
time = true
|
266
|
+
digit = ''
|
267
|
+
elsif c == 'M'
|
268
|
+
m_interval_to_hash(hash, digit, time)
|
269
|
+
digit = ''
|
270
|
+
elsif c == 'S'
|
271
|
+
s_interval_to_hash(hash, digit)
|
272
|
+
digit = ''
|
273
|
+
elsif 'YDH'.include?(c)
|
274
|
+
hash[c] = digit.to_i
|
275
|
+
digit = ''
|
276
|
+
elsif c != 'P'
|
277
|
+
raise ArgumentError, "The argument `#{value}` can't be parse."
|
278
|
+
end
|
279
|
+
end
|
280
|
+
hash
|
281
|
+
end
|
282
|
+
|
283
|
+
def m_interval_to_hash(hash, digit, time)
|
284
|
+
key = time ? 'TM' : 'M'
|
285
|
+
hash[key] = digit.to_i
|
286
|
+
end
|
287
|
+
|
288
|
+
def s_interval_to_hash(hash, digit)
|
289
|
+
sec, msec = digit.split('.')
|
290
|
+
hash['S'] = sec.to_i
|
291
|
+
hash['MS'] = "#{msec}000000"[0, 6].to_i
|
292
|
+
hash['MS'] *= -1 if hash['S'].negative?
|
293
|
+
end
|
294
|
+
|
295
|
+
def hash_to__append_interval_args(hash)
|
296
|
+
months = hash['Y'] * 12 + hash['M']
|
297
|
+
days = hash['D']
|
298
|
+
micros = (hash['H'] * 3600 + hash['TM'] * 60 + hash['S']) * 1_000_000 + hash['MS']
|
299
|
+
[months, days, micros]
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|