duckdb 0.2.6.1 → 0.3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test_on_macos.yml +12 -3
- data/.github/workflows/test_on_ubuntu.yml +17 -36
- data/.github/workflows/test_on_windows.yml +41 -0
- data/CHANGELOG.md +39 -0
- data/CONTRIBUTION.md +24 -0
- data/Gemfile.lock +3 -3
- data/README.md +21 -7
- data/duckdb.gemspec +2 -2
- data/ext/duckdb/appender.c +136 -0
- data/ext/duckdb/config.c +80 -0
- data/ext/duckdb/config.h +18 -0
- data/ext/duckdb/connection.c +12 -12
- data/ext/duckdb/database.c +56 -16
- data/ext/duckdb/duckdb.c +6 -0
- data/ext/duckdb/extconf.rb +15 -0
- data/ext/duckdb/prepared_statement.c +9 -1
- data/ext/duckdb/result.c +66 -35
- data/ext/duckdb/ruby-duckdb.h +12 -0
- data/lib/duckdb/appender.rb +204 -8
- data/lib/duckdb/config.rb +65 -0
- data/lib/duckdb/database.rb +15 -2
- data/lib/duckdb/prepared_statement.rb +3 -3
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +1 -0
- metadata +9 -5
- data/.travis.yml +0 -18
data/ext/duckdb/database.c
CHANGED
@@ -2,29 +2,34 @@
|
|
2
2
|
|
3
3
|
VALUE cDuckDBDatabase;
|
4
4
|
|
5
|
-
static void close_database(rubyDuckDB *p)
|
6
|
-
|
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
|
+
static VALUE duckdb_database_connect(VALUE self);
|
11
|
+
static VALUE duckdb_database_close(VALUE self);
|
12
|
+
|
13
|
+
static void close_database(rubyDuckDB *p) {
|
7
14
|
duckdb_close(&(p->db));
|
8
15
|
}
|
9
16
|
|
10
|
-
static void deallocate(void * ctx)
|
11
|
-
{
|
17
|
+
static void deallocate(void * ctx) {
|
12
18
|
rubyDuckDB *p = (rubyDuckDB *)ctx;
|
13
19
|
|
14
20
|
close_database(p);
|
15
21
|
xfree(p);
|
16
22
|
}
|
17
23
|
|
18
|
-
static VALUE allocate(VALUE klass)
|
19
|
-
{
|
24
|
+
static VALUE allocate(VALUE klass) {
|
20
25
|
rubyDuckDB *ctx = xcalloc((size_t)1, sizeof(rubyDuckDB));
|
21
26
|
return Data_Wrap_Struct(klass, NULL, deallocate, ctx);
|
22
27
|
}
|
23
28
|
|
24
|
-
static VALUE duckdb_database_s_open(int argc, VALUE *argv, VALUE cDuckDBDatabase)
|
25
|
-
{
|
29
|
+
static VALUE duckdb_database_s_open(int argc, VALUE *argv, VALUE cDuckDBDatabase) {
|
26
30
|
rubyDuckDB *ctx;
|
27
31
|
VALUE obj;
|
32
|
+
|
28
33
|
char *pfile = NULL;
|
29
34
|
VALUE file = Qnil;
|
30
35
|
|
@@ -36,15 +41,49 @@ static VALUE duckdb_database_s_open(int argc, VALUE *argv, VALUE cDuckDBDatabase
|
|
36
41
|
|
37
42
|
obj = allocate(cDuckDBDatabase);
|
38
43
|
Data_Get_Struct(obj, rubyDuckDB, ctx);
|
39
|
-
if (duckdb_open(pfile, &(ctx->db)) == DuckDBError)
|
40
|
-
{
|
44
|
+
if (duckdb_open(pfile, &(ctx->db)) == DuckDBError) {
|
41
45
|
rb_raise(eDuckDBError, "Failed to open database"); /* FIXME */
|
42
46
|
}
|
43
47
|
return obj;
|
44
48
|
}
|
45
49
|
|
46
|
-
|
47
|
-
{
|
50
|
+
#ifdef HAVE_DUCKDB_OPEN_EXT
|
51
|
+
static VALUE duckdb_database_s_open_ext(int argc, VALUE *argv, VALUE cDuckDBDatabase) {
|
52
|
+
rubyDuckDB *ctx;
|
53
|
+
VALUE obj;
|
54
|
+
rubyDuckDBConfig *ctx_config;
|
55
|
+
char *perror;
|
56
|
+
|
57
|
+
char *pfile = NULL;
|
58
|
+
VALUE file = Qnil;
|
59
|
+
VALUE config = Qnil;
|
60
|
+
|
61
|
+
rb_scan_args(argc, argv, "02", &file, &config);
|
62
|
+
|
63
|
+
if (!NIL_P(file)) {
|
64
|
+
pfile = StringValuePtr(file);
|
65
|
+
}
|
66
|
+
|
67
|
+
obj = allocate(cDuckDBDatabase);
|
68
|
+
Data_Get_Struct(obj, rubyDuckDB, ctx);
|
69
|
+
if (!NIL_P(config)) {
|
70
|
+
if (!rb_obj_is_kind_of(config, cDuckDBConfig)) {
|
71
|
+
rb_raise(rb_eTypeError, "The second argument must be DuckDB::Config object.");
|
72
|
+
}
|
73
|
+
Data_Get_Struct(config, rubyDuckDBConfig, ctx_config);
|
74
|
+
if (duckdb_open_ext(pfile, &(ctx->db), ctx_config->config, &perror) == DuckDBError) {
|
75
|
+
rb_raise(eDuckDBError, "Failed to open database %s", perror);
|
76
|
+
}
|
77
|
+
} else {
|
78
|
+
if (duckdb_open(pfile, &(ctx->db)) == DuckDBError) {
|
79
|
+
rb_raise(eDuckDBError, "Failed to open database"); /* FIXME */
|
80
|
+
}
|
81
|
+
}
|
82
|
+
return obj;
|
83
|
+
}
|
84
|
+
#endif /* HAVE_DUCKDB_OPEN_EXT */
|
85
|
+
|
86
|
+
static VALUE duckdb_database_connect(VALUE self) {
|
48
87
|
return create_connection(self);
|
49
88
|
}
|
50
89
|
|
@@ -54,19 +93,20 @@ static VALUE duckdb_database_connect(VALUE self)
|
|
54
93
|
*
|
55
94
|
* closes DuckDB database.
|
56
95
|
*/
|
57
|
-
static VALUE duckdb_database_close(VALUE self)
|
58
|
-
{
|
96
|
+
static VALUE duckdb_database_close(VALUE self) {
|
59
97
|
rubyDuckDB *ctx;
|
60
98
|
Data_Get_Struct(self, rubyDuckDB, ctx);
|
61
99
|
close_database(ctx);
|
62
100
|
return self;
|
63
101
|
}
|
64
102
|
|
65
|
-
void init_duckdb_database(void)
|
66
|
-
{
|
103
|
+
void init_duckdb_database(void) {
|
67
104
|
cDuckDBDatabase = rb_define_class_under(mDuckDB, "Database", rb_cObject);
|
68
105
|
rb_define_alloc_func(cDuckDBDatabase, allocate);
|
69
106
|
rb_define_singleton_method(cDuckDBDatabase, "_open", duckdb_database_s_open, -1);
|
107
|
+
#ifdef HAVE_DUCKDB_OPEN_EXT
|
108
|
+
rb_define_singleton_method(cDuckDBDatabase, "_open_ext", duckdb_database_s_open_ext, -1);
|
109
|
+
#endif
|
70
110
|
rb_define_private_method(cDuckDBDatabase, "_connect", duckdb_database_connect, 0);
|
71
111
|
rb_define_method(cDuckDBDatabase, "close", duckdb_database_close, 0);
|
72
112
|
}
|
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
|
|
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,17 +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
|
-
|
91
|
-
|
92
|
-
|
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
|
+
}
|
93
100
|
}
|
94
101
|
}
|
95
102
|
return obj;
|
96
103
|
}
|
97
104
|
|
98
|
-
static VALUE row_array(rubyDuckDBResult *ctx, idx_t row_idx)
|
99
|
-
{
|
105
|
+
static VALUE row_array(rubyDuckDBResult *ctx, idx_t row_idx) {
|
100
106
|
idx_t col_idx;
|
101
107
|
VALUE ary = rb_ary_new2(ctx->result.column_count);
|
102
108
|
for(col_idx = 0; col_idx < ctx->result.column_count; col_idx++) {
|
@@ -105,16 +111,14 @@ static VALUE row_array(rubyDuckDBResult *ctx, idx_t row_idx)
|
|
105
111
|
return ary;
|
106
112
|
}
|
107
113
|
|
108
|
-
static VALUE duckdb_result_row_size(VALUE oDuckDBResult, VALUE args, VALUE obj)
|
109
|
-
{
|
114
|
+
static VALUE duckdb_result_row_size(VALUE oDuckDBResult, VALUE args, VALUE obj) {
|
110
115
|
rubyDuckDBResult *ctx;
|
111
116
|
Data_Get_Struct(oDuckDBResult, rubyDuckDBResult, ctx);
|
112
117
|
|
113
118
|
return LONG2FIX(ctx->result.row_count);
|
114
119
|
}
|
115
120
|
|
116
|
-
static VALUE duckdb_result_each(VALUE oDuckDBResult)
|
117
|
-
{
|
121
|
+
static VALUE duckdb_result_each(VALUE oDuckDBResult) {
|
118
122
|
rubyDuckDBResult *ctx;
|
119
123
|
idx_t row_idx = 0;
|
120
124
|
|
@@ -127,15 +131,42 @@ static VALUE duckdb_result_each(VALUE oDuckDBResult)
|
|
127
131
|
return oDuckDBResult;
|
128
132
|
}
|
129
133
|
|
130
|
-
|
131
|
-
|
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) {
|
132
163
|
return allocate(cDuckDBResult);
|
133
164
|
}
|
134
165
|
|
135
|
-
void init_duckdb_result(void)
|
136
|
-
{
|
166
|
+
void init_duckdb_result(void) {
|
137
167
|
cDuckDBResult = rb_define_class_under(mDuckDB, "Result", rb_cObject);
|
138
168
|
rb_define_alloc_func(cDuckDBResult, allocate);
|
139
169
|
|
140
170
|
rb_define_method(cDuckDBResult, "each", duckdb_result_each, 0);
|
171
|
+
rb_define_method(cDuckDBResult, "rows_changed", duckdb_result_rows_changed, 0);
|
141
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
|
data/lib/duckdb/appender.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'time'
|
2
3
|
|
3
4
|
module DuckDB
|
4
5
|
if defined?(DuckDB::Appender)
|
@@ -12,19 +13,159 @@ module DuckDB
|
|
12
13
|
# appender.append_row(1, 'Alice')
|
13
14
|
#
|
14
15
|
class Appender
|
15
|
-
RANGE_INT16 =
|
16
|
-
RANGE_INT32 =
|
17
|
-
RANGE_INT64 =
|
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
|
18
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
|
+
#
|
19
33
|
def append_hugeint(value)
|
20
34
|
case value
|
21
35
|
when Integer
|
22
|
-
|
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
|
23
44
|
else
|
24
|
-
|
45
|
+
raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
|
25
46
|
end
|
26
47
|
end
|
27
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
|
+
date = case value
|
67
|
+
when Date, Time
|
68
|
+
value
|
69
|
+
else
|
70
|
+
begin
|
71
|
+
Date.parse(value)
|
72
|
+
rescue
|
73
|
+
raise(ArgumentError, "Cannot parse argument `#{value}` to Date.")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
_append_date(date.year, date.month, date.day)
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# appends time value.
|
82
|
+
#
|
83
|
+
# require 'duckdb'
|
84
|
+
# db = DuckDB::Database.open
|
85
|
+
# con = db.connect
|
86
|
+
# con.query('CREATE TABLE times (time_value TIME)')
|
87
|
+
# appender = con.appender('times')
|
88
|
+
# appender.begin_row
|
89
|
+
# appender.append_time(Time.now)
|
90
|
+
# # or
|
91
|
+
# # appender.append_time('01:01:01')
|
92
|
+
# appender.end_row
|
93
|
+
# appender.flush
|
94
|
+
#
|
95
|
+
def append_time(value)
|
96
|
+
time = case value
|
97
|
+
when Time
|
98
|
+
value
|
99
|
+
else
|
100
|
+
begin
|
101
|
+
Time.parse(value)
|
102
|
+
rescue
|
103
|
+
raise(ArgumentError, "Cannot parse argument `#{value}` to Time.")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
_append_time(time.hour, time.min, time.sec, time.usec)
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# appends timestamp value.
|
112
|
+
#
|
113
|
+
# require 'duckdb'
|
114
|
+
# db = DuckDB::Database.open
|
115
|
+
# con = db.connect
|
116
|
+
# con.query('CREATE TABLE timestamps (timestamp_value TIMESTAMP)')
|
117
|
+
# appender = con.appender('timestamps')
|
118
|
+
# appender.begin_row
|
119
|
+
# appender.append_time(Time.now)
|
120
|
+
# # or
|
121
|
+
# # appender.append_time(Date.today)
|
122
|
+
# # appender.append_time('2021-08-01 01:01:01')
|
123
|
+
# appender.end_row
|
124
|
+
# appender.flush
|
125
|
+
#
|
126
|
+
def append_timestamp(value)
|
127
|
+
time = case value
|
128
|
+
when Time
|
129
|
+
value
|
130
|
+
when Date
|
131
|
+
value.to_time
|
132
|
+
else
|
133
|
+
begin
|
134
|
+
Time.parse(value)
|
135
|
+
rescue
|
136
|
+
raise(ArgumentError, "Cannot parse argument `#{value}` to Time or Date.")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
_append_timestamp(time.year, time.month, time.day, time.hour, time.min, time.sec, time.nsec / 1000)
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# appends interval.
|
145
|
+
# The argument must be ISO8601 duration format.
|
146
|
+
# WARNING: This method is expremental.
|
147
|
+
#
|
148
|
+
# require 'duckdb'
|
149
|
+
# db = DuckDB::Database.open
|
150
|
+
# con = db.connect
|
151
|
+
# con.query('CREATE TABLE intervals (interval_value INTERVAL)')
|
152
|
+
# appender = con.appender('intervals')
|
153
|
+
# appender
|
154
|
+
# .begin_row
|
155
|
+
# .append_interval('P1Y2D') # => append 1 year 2 days interval.
|
156
|
+
# .end_row
|
157
|
+
# .flush
|
158
|
+
#
|
159
|
+
def append_interval(value)
|
160
|
+
raise ArgumentError, "Argument `#{value}` must be a string." unless value.is_a?(String)
|
161
|
+
|
162
|
+
hash = iso8601_interval_to_hash(value)
|
163
|
+
|
164
|
+
months, days, micros = hash_to__append_interval_args(hash)
|
165
|
+
|
166
|
+
_append_interval(months, days, micros)
|
167
|
+
end
|
168
|
+
|
28
169
|
#
|
29
170
|
# appends value.
|
30
171
|
#
|
@@ -64,11 +205,19 @@ module DuckDB
|
|
64
205
|
when TrueClass, FalseClass
|
65
206
|
append_bool(value)
|
66
207
|
when Time
|
67
|
-
|
208
|
+
if respond_to?(:append_timestamp)
|
209
|
+
append_timestamp(value)
|
210
|
+
else
|
211
|
+
append_varchar(value.strftime('%Y-%m-%d %H:%M:%S.%N'))
|
212
|
+
end
|
68
213
|
when Date
|
69
|
-
|
214
|
+
if respond_to?(:append_date)
|
215
|
+
append_date(value)
|
216
|
+
else
|
217
|
+
append_varchar(value.strftime('%Y-%m-%d'))
|
218
|
+
end
|
70
219
|
else
|
71
|
-
|
220
|
+
raise(DuckDB::Error, "not supported type #{value} (#{value.class})")
|
72
221
|
end
|
73
222
|
end
|
74
223
|
|
@@ -97,6 +246,53 @@ module DuckDB
|
|
97
246
|
def blob?(value)
|
98
247
|
value.instance_of?(DuckDB::Blob) || value.encoding == Encoding::BINARY
|
99
248
|
end
|
249
|
+
|
250
|
+
def iso8601_interval_to_hash(value)
|
251
|
+
digit = ''
|
252
|
+
time = false
|
253
|
+
hash = {}
|
254
|
+
hash.default = 0
|
255
|
+
|
256
|
+
value.each_char do |c|
|
257
|
+
if '-0123456789.'.include?(c)
|
258
|
+
digit += c
|
259
|
+
elsif c == 'T'
|
260
|
+
time = true
|
261
|
+
digit = ''
|
262
|
+
elsif c == 'M'
|
263
|
+
m_interval_to_hash(hash, digit, time)
|
264
|
+
digit = ''
|
265
|
+
elsif c == 'S'
|
266
|
+
s_interval_to_hash(hash, digit)
|
267
|
+
digit = ''
|
268
|
+
elsif 'YDH'.include?(c)
|
269
|
+
hash[c] = digit.to_i
|
270
|
+
digit = ''
|
271
|
+
elsif c != 'P'
|
272
|
+
raise ArgumentError, "The argument `#{value}` can't be parse."
|
273
|
+
end
|
274
|
+
end
|
275
|
+
hash
|
276
|
+
end
|
277
|
+
|
278
|
+
def m_interval_to_hash(hash, digit, time)
|
279
|
+
key = time ? 'TM' : 'M'
|
280
|
+
hash[key] = digit.to_i
|
281
|
+
end
|
282
|
+
|
283
|
+
def s_interval_to_hash(hash, digit)
|
284
|
+
sec, msec = digit.split('.')
|
285
|
+
hash['S'] = sec.to_i
|
286
|
+
hash['MS'] = "#{msec}000000"[0, 6].to_i
|
287
|
+
hash['MS'] *= -1 if hash['S'].negative?
|
288
|
+
end
|
289
|
+
|
290
|
+
def hash_to__append_interval_args(hash)
|
291
|
+
months = hash['Y'] * 12 + hash['M']
|
292
|
+
days = hash['D']
|
293
|
+
micros = (hash['H'] * 3600 + hash['TM'] * 60 + hash['S']) * 1_000_000 + hash['MS']
|
294
|
+
[months, days, micros]
|
295
|
+
end
|
100
296
|
end
|
101
297
|
end
|
102
298
|
end
|