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.
@@ -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