duckdb 0.2.8.0 → 0.3.2.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.
@@ -8,13 +8,9 @@
8
8
  #include "./connection.h"
9
9
  #include "./result.h"
10
10
  #include "./prepared_statement.h"
11
-
12
- #ifdef HAVE_DUCKDB_VALUE_BLOB
11
+ #include "./util.h"
13
12
 
14
13
  #include "./blob.h"
15
-
16
- #endif /* HAVE_DUCKDB_VALUE_BLOB */
17
-
18
14
  #ifdef HAVE_DUCKDB_APPENDER_CREATE
19
15
 
20
16
  #include "./appender.h"
@@ -30,19 +26,8 @@
30
26
  extern VALUE mDuckDB;
31
27
  extern VALUE cDuckDBDatabase;
32
28
  extern VALUE cDuckDBConnection;
33
-
34
- #ifdef HAVE_DUCKDB_VALUE_BLOB
35
-
36
29
  extern VALUE cDuckDBBlob;
37
-
38
- #endif /* HAVE_DUCKDB_VALUE_BLOB */
39
-
40
- #ifdef HAVE_DUCKDB_CREATE_CONFIG
41
-
42
30
  extern VALUE cDuckDBConfig;
43
-
44
- #endif /* HAVE_DUCKDB_CREATE_CONFIG */
45
-
46
31
  extern VALUE eDuckDBError;
47
32
 
48
33
  #endif
data/ext/duckdb/util.c ADDED
@@ -0,0 +1,45 @@
1
+ #include "ruby-duckdb.h"
2
+
3
+ #ifdef HAVE_DUCKDB_APPEND_DATE
4
+
5
+ duckdb_date to_duckdb_date_from_value(VALUE year, VALUE month, VALUE day) {
6
+ duckdb_date_struct dt_struct;
7
+
8
+ dt_struct.year = NUM2INT(year);
9
+ dt_struct.month = NUM2INT(month);
10
+ dt_struct.day = NUM2INT(day);
11
+
12
+ return duckdb_to_date(dt_struct);
13
+ }
14
+
15
+ duckdb_time to_duckdb_time_from_value(VALUE hour, VALUE min, VALUE sec, VALUE micros) {
16
+ duckdb_time_struct time_st;
17
+
18
+ time_st.hour = NUM2INT(hour);
19
+ time_st.min = NUM2INT(min);
20
+ time_st.sec = NUM2INT(sec);
21
+ time_st.micros = NUM2INT(micros);
22
+
23
+ return duckdb_to_time(time_st);
24
+ }
25
+
26
+ duckdb_timestamp to_duckdb_timestamp_from_value(VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros) {
27
+ duckdb_timestamp_struct timestamp_st;
28
+
29
+ timestamp_st.date.year = NUM2INT(year);
30
+ timestamp_st.date.month = NUM2INT(month);
31
+ timestamp_st.date.day = NUM2INT(day);
32
+ timestamp_st.time.hour = NUM2INT(hour);
33
+ timestamp_st.time.min = NUM2INT(min);
34
+ timestamp_st.time.sec = NUM2INT(sec);
35
+ timestamp_st.time.micros = NUM2INT(micros);
36
+
37
+ return duckdb_to_timestamp(timestamp_st);
38
+ }
39
+
40
+ void to_duckdb_interval_from_value(duckdb_interval* interval, VALUE months, VALUE days, VALUE micros) {
41
+ interval->months = NUM2INT(months);
42
+ interval->days = NUM2INT(days);
43
+ interval->micros = NUM2LL(micros);
44
+ }
45
+ #endif
data/ext/duckdb/util.h ADDED
@@ -0,0 +1,13 @@
1
+ #ifndef RUBY_DUCKDB_UTIL_H
2
+ #define RUBY_DUCKDB_UTIL_H
3
+
4
+ #ifdef HAVE_DUCKDB_APPEND_DATE
5
+
6
+ duckdb_date to_duckdb_date_from_value(VALUE year, VALUE month, VALUE day);
7
+ duckdb_time to_duckdb_time_from_value(VALUE hour, VALUE min, VALUE sec, VALUE micros);
8
+ duckdb_timestamp to_duckdb_timestamp_from_value(VALUE year, VALUE month, VALUE day, VALUE hour, VALUE min, VALUE sec, VALUE micros);
9
+ void to_duckdb_interval_from_value(duckdb_interval* interval, VALUE months, VALUE days, VALUE micros);
10
+
11
+ #endif
12
+
13
+ #endif
@@ -1,4 +1,6 @@
1
1
  require 'date'
2
+ require 'time'
3
+ require_relative './converter'
2
4
 
3
5
  module DuckDB
4
6
  if defined?(DuckDB::Appender)
@@ -12,19 +14,161 @@ module DuckDB
12
14
  # appender.append_row(1, 'Alice')
13
15
  #
14
16
  class Appender
17
+ include DuckDB::Converter
18
+
15
19
  RANGE_INT16 = -32_768..32_767
16
20
  RANGE_INT32 = -2_147_483_648..2_147_483_647
17
21
  RANGE_INT64 = -9_223_372_036_854_775_808..9_223_372_036_854_775_807
18
22
 
23
+ #
24
+ # appends huge int value.
25
+ #
26
+ # require 'duckdb'
27
+ # db = DuckDB::Database.open
28
+ # con = db.connect
29
+ # con.query('CREATE TABLE numbers (num HUGEINT)')
30
+ # appender = con.appender('numbers')
31
+ # appender
32
+ # .begin_row
33
+ # .append_hugeint(-170_141_183_460_469_231_731_687_303_715_884_105_727)
34
+ # .end_row
35
+ #
19
36
  def append_hugeint(value)
20
37
  case value
21
38
  when Integer
22
- append_varchar(value.to_s)
39
+ if respond_to?(:_append_hugeint, true)
40
+ half = 1 << 64
41
+ upper = value / half
42
+ lower = value - upper * half
43
+ _append_hugeint(lower, upper)
44
+ else
45
+ append_varchar(value.to_s)
46
+ end
23
47
  else
24
- rb_raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
48
+ raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
25
49
  end
26
50
  end
27
51
 
52
+ #
53
+ # appends date value.
54
+ #
55
+ # require 'duckdb'
56
+ # db = DuckDB::Database.open
57
+ # con = db.connect
58
+ # con.query('CREATE TABLE dates (date_value DATE)')
59
+ # appender = con.appender('dates')
60
+ # appender.begin_row
61
+ # appender.append_date(Date.today)
62
+ # # or
63
+ # # appender.append_date(Time.now)
64
+ # # appender.append_date('2021-10-10')
65
+ # appender.end_row
66
+ # appender.flush
67
+ #
68
+ def append_date(value)
69
+ date = case value
70
+ when Date, Time
71
+ value
72
+ else
73
+ begin
74
+ Date.parse(value)
75
+ rescue
76
+ raise(ArgumentError, "Cannot parse argument `#{value}` to Date.")
77
+ end
78
+ end
79
+
80
+ _append_date(date.year, date.month, date.day)
81
+ end
82
+
83
+ #
84
+ # appends time value.
85
+ #
86
+ # require 'duckdb'
87
+ # db = DuckDB::Database.open
88
+ # con = db.connect
89
+ # con.query('CREATE TABLE times (time_value TIME)')
90
+ # appender = con.appender('times')
91
+ # appender.begin_row
92
+ # appender.append_time(Time.now)
93
+ # # or
94
+ # # appender.append_time('01:01:01')
95
+ # appender.end_row
96
+ # appender.flush
97
+ #
98
+ def append_time(value)
99
+ time = case value
100
+ when Time
101
+ value
102
+ else
103
+ begin
104
+ Time.parse(value)
105
+ rescue
106
+ raise(ArgumentError, "Cannot parse argument `#{value}` to Time.")
107
+ end
108
+ end
109
+
110
+ _append_time(time.hour, time.min, time.sec, time.usec)
111
+ end
112
+
113
+ #
114
+ # appends timestamp value.
115
+ #
116
+ # require 'duckdb'
117
+ # db = DuckDB::Database.open
118
+ # con = db.connect
119
+ # con.query('CREATE TABLE timestamps (timestamp_value TIMESTAMP)')
120
+ # appender = con.appender('timestamps')
121
+ # appender.begin_row
122
+ # appender.append_time(Time.now)
123
+ # # or
124
+ # # appender.append_time(Date.today)
125
+ # # appender.append_time('2021-08-01 01:01:01')
126
+ # appender.end_row
127
+ # appender.flush
128
+ #
129
+ def append_timestamp(value)
130
+ time = case value
131
+ when Time
132
+ value
133
+ when Date
134
+ value.to_time
135
+ else
136
+ begin
137
+ Time.parse(value)
138
+ rescue
139
+ raise(ArgumentError, "Cannot parse argument `#{value}` to Time or Date.")
140
+ end
141
+ end
142
+
143
+ _append_timestamp(time.year, time.month, time.day, time.hour, time.min, time.sec, time.nsec / 1000)
144
+ end
145
+
146
+ #
147
+ # appends interval.
148
+ # The argument must be ISO8601 duration format.
149
+ # WARNING: This method is expremental.
150
+ #
151
+ # require 'duckdb'
152
+ # db = DuckDB::Database.open
153
+ # con = db.connect
154
+ # con.query('CREATE TABLE intervals (interval_value INTERVAL)')
155
+ # appender = con.appender('intervals')
156
+ # appender
157
+ # .begin_row
158
+ # .append_interval('P1Y2D') # => append 1 year 2 days interval.
159
+ # .end_row
160
+ # .flush
161
+ #
162
+ def append_interval(value)
163
+ raise ArgumentError, "Argument `#{value}` must be a string." unless value.is_a?(String)
164
+
165
+ hash = iso8601_interval_to_hash(value)
166
+
167
+ months, days, micros = hash_to__append_interval_args(hash)
168
+
169
+ _append_interval(months, days, micros)
170
+ end
171
+
28
172
  #
29
173
  # appends value.
30
174
  #
@@ -56,19 +200,23 @@ module DuckDB
56
200
  append_hugeint(value)
57
201
  end
58
202
  when String
59
- if defined?(DuckDB::Blob)
60
- blob?(value) ? append_blob(value) : append_varchar(value)
61
- else
62
- append_varchar(value)
63
- end
203
+ blob?(value) ? append_blob(value) : append_varchar(value)
64
204
  when TrueClass, FalseClass
65
205
  append_bool(value)
66
206
  when Time
67
- append_varchar(value.strftime('%Y-%m-%d %H:%M:%S.%N'))
207
+ if respond_to?(:append_timestamp)
208
+ append_timestamp(value)
209
+ else
210
+ append_varchar(value.strftime('%Y-%m-%d %H:%M:%S.%N'))
211
+ end
68
212
  when Date
69
- append_varchar(value.strftime('%Y-%m-%d'))
213
+ if respond_to?(:append_date)
214
+ append_date(value)
215
+ else
216
+ append_varchar(value.strftime('%Y-%m-%d'))
217
+ end
70
218
  else
71
- rb_raise(DuckDB::Error, "not supported type #{value} (value.class)")
219
+ raise(DuckDB::Error, "not supported type #{value} (#{value.class})")
72
220
  end
73
221
  end
74
222
 
data/lib/duckdb/config.rb CHANGED
@@ -34,10 +34,7 @@ module DuckDB
34
34
  # configs['default_order'] # => "The order type used when none is specified ([ASC] or DESC)"
35
35
  #
36
36
  def key_descriptions
37
- return @key_descriptions if @key_descriptions
38
-
39
- n = size
40
- @key_descriptions = (0...n).each_with_object({}) do |i, hash|
37
+ @key_descriptions ||= (0...size).each_with_object({}) do |i, hash|
41
38
  key, description = key_description(i)
42
39
  hash[key] = description
43
40
  end
@@ -0,0 +1,52 @@
1
+ module DuckDB
2
+ module Converter
3
+ private
4
+
5
+ def iso8601_interval_to_hash(value)
6
+ digit = ''
7
+ time = false
8
+ hash = {}
9
+ hash.default = 0
10
+
11
+ value.each_char do |c|
12
+ if '-0123456789.'.include?(c)
13
+ digit += c
14
+ elsif c == 'T'
15
+ time = true
16
+ digit = ''
17
+ elsif c == 'M'
18
+ m_interval_to_hash(hash, digit, time)
19
+ digit = ''
20
+ elsif c == 'S'
21
+ s_interval_to_hash(hash, digit)
22
+ digit = ''
23
+ elsif 'YDH'.include?(c)
24
+ hash[c] = digit.to_i
25
+ digit = ''
26
+ elsif c != 'P'
27
+ raise ArgumentError, "The argument `#{value}` can't be parse."
28
+ end
29
+ end
30
+ hash
31
+ end
32
+
33
+ def m_interval_to_hash(hash, digit, time)
34
+ key = time ? 'TM' : 'M'
35
+ hash[key] = digit.to_i
36
+ end
37
+
38
+ def s_interval_to_hash(hash, digit)
39
+ sec, msec = digit.split('.')
40
+ hash['S'] = sec.to_i
41
+ hash['MS'] = "#{msec}000000"[0, 6].to_i
42
+ hash['MS'] *= -1 if hash['S'].negative?
43
+ end
44
+
45
+ def hash_to__append_interval_args(hash)
46
+ months = hash['Y'] * 12 + hash['M']
47
+ days = hash['D']
48
+ micros = (hash['H'] * 3600 + hash['TM'] * 60 + hash['S']) * 1_000_000 + hash['MS']
49
+ [months, days, micros]
50
+ end
51
+ end
52
+ end
@@ -1,4 +1,5 @@
1
1
  require 'date'
2
+ require_relative './converter'
2
3
 
3
4
  module DuckDB
4
5
  # The DuckDB::PreparedStatement encapsulates connection with DuckDB prepared
@@ -12,6 +13,7 @@ module DuckDB
12
13
  # stmt.bind(1, 'email@example.com')
13
14
  # stmt.execute
14
15
  class PreparedStatement
16
+ include DuckDB::Converter
15
17
 
16
18
  RANGE_INT16 = -32768..32767
17
19
  RANGE_INT32 = -2147483648..2147483647
@@ -22,13 +24,119 @@ module DuckDB
22
24
  when Integer
23
25
  bind_varchar(i, value.to_s)
24
26
  else
25
- rb_raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
27
+ raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
26
28
  end
27
29
  end
28
30
 
29
31
  # binds i-th parameter with SQL prepared statement.
30
- # The first argument is index of parameter. The index of first parameter is
31
- # 1 not 0.
32
+ # The first argument is index of parameter.
33
+ # The index of first parameter is 1 not 0.
34
+ # The second argument value is to expected date.
35
+ #
36
+ # require 'duckdb'
37
+ # db = DuckDB::Database.open('duckdb_database')
38
+ # con = db.connect
39
+ # sql ='SELECT name FROM users WHERE birth_day = ?'
40
+ # stmt = PreparedStatement.new(con, sql)
41
+ # stmt.bind(1, Date.today)
42
+ # # or you can specify date string.
43
+ # # stmt.bind(1, '2021-02-23')
44
+ def bind_date(i, value)
45
+ case value
46
+ when Date, Time
47
+ date = value
48
+ else
49
+ begin
50
+ date = Date.parse(value)
51
+ rescue => e
52
+ raise(ArgumentError, "Cannot parse argument value to date. #{e.message}")
53
+ end
54
+ end
55
+
56
+ _bind_date(i, date.year, date.month, date.day)
57
+ end
58
+
59
+ # binds i-th parameter with SQL prepared statement.
60
+ # The first argument is index of parameter.
61
+ # The index of first parameter is 1 not 0.
62
+ # The second argument value is to expected time value.
63
+ #
64
+ # require 'duckdb'
65
+ # db = DuckDB::Database.open('duckdb_database')
66
+ # con = db.connect
67
+ # sql ='SELECT name FROM users WHERE birth_time = ?'
68
+ # stmt = PreparedStatement.new(con, sql)
69
+ # stmt.bind(1, Time.now)
70
+ # # or you can specify time string.
71
+ # # stmt.bind(1, '07:39:45')
72
+ def bind_time(i, value)
73
+ case value
74
+ when Time
75
+ time = value
76
+ else
77
+ begin
78
+ time = Time.parse(value)
79
+ rescue => e
80
+ raise(ArgumentError, "Cannot parse argument value to time. #{e.message}")
81
+ end
82
+ end
83
+
84
+ _bind_time(i, time.hour, time.min, time.sec, time.usec)
85
+ end
86
+
87
+ # binds i-th parameter with SQL prepared statement.
88
+ # The first argument is index of parameter.
89
+ # The index of first parameter is 1 not 0.
90
+ # The second argument value is to expected time value.
91
+ #
92
+ # require 'duckdb'
93
+ # db = DuckDB::Database.open('duckdb_database')
94
+ # con = db.connect
95
+ # sql ='SELECT name FROM users WHERE created_at = ?'
96
+ # stmt = PreparedStatement.new(con, sql)
97
+ # stmt.bind(1, Time.now)
98
+ # # or you can specify timestamp string.
99
+ # # stmt.bind(1, '2022-02-23 07:39:45')
100
+ def bind_timestamp(i, value)
101
+ case value
102
+ when Time
103
+ time = value
104
+ else
105
+ begin
106
+ time = Time.parse(value)
107
+ rescue => e
108
+ raise(ArgumentError, "Cannot parse argument value to time. #{e.message}")
109
+ end
110
+ end
111
+
112
+ _bind_timestamp(i, time.year, time.month, time.day, time.hour, time.min, time.sec, time.usec)
113
+ end
114
+
115
+ # binds i-th parameter with SQL prepared statement.
116
+ # The first argument is index of parameter.
117
+ # The index of first parameter is 1 not 0.
118
+ # The second argument value is to expected ISO8601 time interval string.
119
+ #
120
+ # require 'duckdb'
121
+ # db = DuckDB::Database.open('duckdb_database')
122
+ # con = db.connect
123
+ # sql ='SELECT value FROM intervals WHERE interval = ?'
124
+ # stmt = PreparedStatement.new(con, sql)
125
+ # stmt.bind(1, 'P1Y2D')
126
+ def bind_interval(i, value)
127
+ raise(DuckDB::Error, 'bind_interval is not available with your duckdb version. please install duckdb latest version at first') unless respond_to?(:_bind_interval, true)
128
+ raise ArgumentError, "Argument `#{value}` must be a string." unless value.is_a?(String)
129
+
130
+ hash = iso8601_interval_to_hash(value)
131
+
132
+ months, days, micros = hash_to__append_interval_args(hash)
133
+
134
+ _bind_interval(i, months, days, micros)
135
+ end
136
+
137
+ # binds i-th parameter with SQL prepared statement.
138
+ # The first argument is index of parameter.
139
+ # The index of first parameter is 1 not 0.
32
140
  # The second argument value is the value of prepared statement parameter.
33
141
  #
34
142
  # require 'duckdb'
@@ -40,7 +148,7 @@ module DuckDB
40
148
  def bind(i, value)
41
149
  case value
42
150
  when NilClass
43
- respond_to?(:bind_null) ? bind_null(i) : rb_raise(DuckDB::Error, 'This bind method does not support nil value. Re-compile ruby-duckdb with DuckDB version >= 0.1.1')
151
+ bind_null(i)
44
152
  when Float
45
153
  bind_double(i, value)
46
154
  when Integer
@@ -51,11 +159,7 @@ module DuckDB
51
159
  bind_varchar(i, value.to_s)
52
160
  end
53
161
  when String
54
- if defined?(DuckDB::Blob)
55
- blob?(value) ? bind_blob(i, value) : bind_varchar(i, value)
56
- else
57
- bind_varchar(i, value)
58
- end
162
+ blob?(value) ? bind_blob(i, value) : bind_varchar(i, value)
59
163
  when TrueClass, FalseClass
60
164
  bind_bool(i, value)
61
165
  when Time
@@ -63,7 +167,7 @@ module DuckDB
63
167
  when Date
64
168
  bind_varchar(i, value.strftime('%Y-%m-%d'))
65
169
  else
66
- rb_raise(DuckDB::Error, "not supported type #{value} (value.class)")
170
+ raise(DuckDB::Error, "not supported type `#{value}` (#{value.class})")
67
171
  end
68
172
  end
69
173
 
@@ -1,5 +1,5 @@
1
1
  module DuckDB
2
2
  # The version string of ruby-duckdb.
3
3
  # Currently, ruby-duckdb is NOT semantic versioning.
4
- VERSION = '0.2.8.0'.freeze
4
+ VERSION = '0.3.2.0'.freeze
5
5
  end
data/lib/duckdb.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'duckdb/duckdb_native'
2
2
  require 'duckdb/version'
3
+ require 'duckdb/converter'
3
4
  require 'duckdb/database'
4
5
  require 'duckdb/connection'
5
6
  require 'duckdb/result'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: duckdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8.0
4
+ version: 0.3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masaki Suketa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-04 00:00:00.000000000 Z
11
+ date: 2022-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -79,8 +79,8 @@ files:
79
79
  - ".github/workflows/test_on_ubuntu.yml"
80
80
  - ".github/workflows/test_on_windows.yml"
81
81
  - ".gitignore"
82
- - ".travis.yml"
83
82
  - CHANGELOG.md
83
+ - CONTRIBUTION.md
84
84
  - Gemfile
85
85
  - Gemfile.lock
86
86
  - LICENSE
@@ -108,10 +108,13 @@ files:
108
108
  - ext/duckdb/result.c
109
109
  - ext/duckdb/result.h
110
110
  - ext/duckdb/ruby-duckdb.h
111
+ - ext/duckdb/util.c
112
+ - ext/duckdb/util.h
111
113
  - lib/duckdb.rb
112
114
  - lib/duckdb/appender.rb
113
115
  - lib/duckdb/config.rb
114
116
  - lib/duckdb/connection.rb
117
+ - lib/duckdb/converter.rb
115
118
  - lib/duckdb/database.rb
116
119
  - lib/duckdb/prepared_statement.rb
117
120
  - lib/duckdb/result.rb
@@ -131,14 +134,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
134
  requirements:
132
135
  - - ">="
133
136
  - !ruby/object:Gem::Version
134
- version: 2.5.0
137
+ version: 2.6.0
135
138
  required_rubygems_version: !ruby/object:Gem::Requirement
136
139
  requirements:
137
140
  - - ">="
138
141
  - !ruby/object:Gem::Version
139
142
  version: '0'
140
143
  requirements: []
141
- rubygems_version: 3.2.22
144
+ rubygems_version: 3.3.7
142
145
  signing_key:
143
146
  specification_version: 4
144
147
  summary: This module is Ruby binding for DuckDB database engine.
data/.travis.yml DELETED
@@ -1,18 +0,0 @@
1
- language: ruby
2
- cache:
3
- bundler: true
4
- directories:
5
- - ${HOME}/duckdb-v0.2.8
6
- before_install:
7
- - yes | gem update --system
8
- - if [[ ! -d ${HOME}/duckdb-v0.2.8/build ]]; then cd ${HOME} && git clone -b v0.2.8 https://github.com/cwida/duckdb.git duckdb-v0.2.8 && cd duckdb-v0.2.8 && make && cd ${TRAVIS_BUILD_DIR}; fi
9
-
10
- env:
11
- - DUCKDB_VERSION=0.2.8
12
- rvm:
13
- - 2.5.8
14
- - 2.6.8
15
- - 2.7.4
16
- - 3.0.2
17
- - ruby-head
18
- script: bundle exec rake -- --with-duckdb-include=${HOME}/duckdb-v${DUCKDB_VERSION}/src/include --with-duckdb-lib=${HOME}/duckdb-v${DUCKDB_VERSION}/build/release/src/