duckdb 0.2.6.0 → 0.2.9.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,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