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.
@@ -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
- static VALUE duckdb_database_connect(VALUE self)
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
@@ -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
 
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,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
- obj = rb_str_new2(p);
90
- free(p);
91
- if (result->columns[col_idx].type == DUCKDB_TYPE_HUGEINT) {
92
- obj = rb_funcall(obj, rb_intern("to_i"), 0);
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
- VALUE create_result(void)
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
  }
@@ -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
@@ -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 = (-32_768..32_767).freeze
16
- RANGE_INT32 = (-2_147_483_648..2_147_483_647).freeze
17
- RANGE_INT64 = (-9_223_372_036_854_775_808..9_223_372_036_854_775_807).freeze
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
- append_varchar(value.to_s)
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
- rb_raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
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
- append_varchar(value.strftime('%Y-%m-%d %H:%M:%S.%N'))
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
- append_varchar(value.strftime('%Y-%m-%d'))
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
- rb_raise(DuckDB::Error, "not supported type #{value} (value.class)")
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