duckdb 0.2.8.0 → 0.3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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/