duckdb 0.2.6.1 → 0.3.1.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 +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
|