duckdb 0.2.6.0 → 0.2.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -24,4 +24,10 @@ Init_duckdb_native(void)
24
24
  init_duckdb_appender();
25
25
 
26
26
  #endif /* HAVE_DUCKDB_APPENDER_CREATE */
27
+
28
+ #ifdef HAVE_DUCKDB_CREATE_CONFIG
29
+
30
+ init_duckdb_config();
31
+
32
+ #endif /* HAVE_DUCKDB_CREATE_CONFIG */
27
33
  }
@@ -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 duckdb_prepared_statement_bind_boolean(VALUE self, VALUE vidx, VALUE val)
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, "bind_boolean", duckdb_prepared_statement_bind_boolean, 2);
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
- return rb_str_new(bval.data, bval.size);
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
- obj = rb_str_new2(p);
90
- free(p);
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
- VALUE create_result(void)
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
  }
@@ -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