duckdb 0.2.9.0 → 0.3.3.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.
@@ -1,303 +1,244 @@
1
1
  require 'date'
2
2
  require 'time'
3
+ require_relative './converter'
3
4
 
4
5
  module DuckDB
5
- if defined?(DuckDB::Appender)
6
- # The DuckDB::Appender encapsulates 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
+ include DuckDB::Converter
17
+
18
+ RANGE_INT16 = -32_768..32_767
19
+ RANGE_INT32 = -2_147_483_648..2_147_483_647
20
+ RANGE_INT64 = -9_223_372_036_854_775_808..9_223_372_036_854_775_807
21
+
22
+ #
23
+ # appends huge int value.
7
24
  #
8
25
  # require 'duckdb'
9
26
  # db = DuckDB::Database.open
10
27
  # con = db.connect
11
- # con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
12
- # appender = con.appender('users')
13
- # appender.append_row(1, 'Alice')
28
+ # con.query('CREATE TABLE numbers (num HUGEINT)')
29
+ # appender = con.appender('numbers')
30
+ # appender
31
+ # .begin_row
32
+ # .append_hugeint(-170_141_183_460_469_231_731_687_303_715_884_105_727)
33
+ # .end_row
14
34
  #
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)
35
+ def append_hugeint(value)
36
+ case value
37
+ when Integer
38
+ half = 1 << 64
39
+ upper = value / half
40
+ lower = value - upper * half
41
+ _append_hugeint(lower, upper)
42
+ else
43
+ raise(ArgumentError, "2nd argument `#{value}` must be Integer.")
80
44
  end
45
+ end
81
46
 
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
47
+ #
48
+ # appends date value.
49
+ #
50
+ # require 'duckdb'
51
+ # db = DuckDB::Database.open
52
+ # con = db.connect
53
+ # con.query('CREATE TABLE dates (date_value DATE)')
54
+ # appender = con.appender('dates')
55
+ # appender.begin_row
56
+ # appender.append_date(Date.today)
57
+ # # or
58
+ # # appender.append_date(Time.now)
59
+ # # appender.append_date('2021-10-10')
60
+ # appender.end_row
61
+ # appender.flush
62
+ #
63
+ def append_date(value)
64
+ date = case value
65
+ when Date, Time
66
+ value
67
+ else
68
+ begin
69
+ Date.parse(value)
70
+ rescue
71
+ raise(ArgumentError, "Cannot parse argument `#{value}` to Date.")
72
+ end
73
+ end
74
+
75
+ _append_date(date.year, date.month, date.day)
76
+ end
110
77
 
111
- _append_time(time.hour, time.min, time.sec, time.usec)
112
- end
78
+ #
79
+ # appends time value.
80
+ #
81
+ # require 'duckdb'
82
+ # db = DuckDB::Database.open
83
+ # con = db.connect
84
+ # con.query('CREATE TABLE times (time_value TIME)')
85
+ # appender = con.appender('times')
86
+ # appender.begin_row
87
+ # appender.append_time(Time.now)
88
+ # # or
89
+ # # appender.append_time('01:01:01')
90
+ # appender.end_row
91
+ # appender.flush
92
+ #
93
+ def append_time(value)
94
+ time = case value
95
+ when Time
96
+ value
97
+ else
98
+ begin
99
+ Time.parse(value)
100
+ rescue
101
+ raise(ArgumentError, "Cannot parse argument `#{value}` to Time.")
102
+ end
103
+ end
104
+
105
+ _append_time(time.hour, time.min, time.sec, time.usec)
106
+ end
113
107
 
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
108
+ #
109
+ # appends timestamp value.
110
+ #
111
+ # require 'duckdb'
112
+ # db = DuckDB::Database.open
113
+ # con = db.connect
114
+ # con.query('CREATE TABLE timestamps (timestamp_value TIMESTAMP)')
115
+ # appender = con.appender('timestamps')
116
+ # appender.begin_row
117
+ # appender.append_time(Time.now)
118
+ # # or
119
+ # # appender.append_time(Date.today)
120
+ # # appender.append_time('2021-08-01 01:01:01')
121
+ # appender.end_row
122
+ # appender.flush
123
+ #
124
+ def append_timestamp(value)
125
+ time = case value
126
+ when Time
127
+ value
128
+ when Date
129
+ value.to_time
130
+ else
131
+ begin
132
+ Time.parse(value)
133
+ rescue
134
+ raise(ArgumentError, "Cannot parse argument `#{value}` to Time or Date.")
135
+ end
136
+ end
137
+
138
+ _append_timestamp(time.year, time.month, time.day, time.hour, time.min, time.sec, time.nsec / 1000)
139
+ end
147
140
 
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)
141
+ #
142
+ # appends interval.
143
+ # The argument must be ISO8601 duration format.
144
+ # WARNING: This method is expremental.
145
+ #
146
+ # require 'duckdb'
147
+ # db = DuckDB::Database.open
148
+ # con = db.connect
149
+ # con.query('CREATE TABLE intervals (interval_value INTERVAL)')
150
+ # appender = con.appender('intervals')
151
+ # appender
152
+ # .begin_row
153
+ # .append_interval('P1Y2D') # => append 1 year 2 days interval.
154
+ # .end_row
155
+ # .flush
156
+ #
157
+ def append_interval(value)
158
+ raise ArgumentError, "Argument `#{value}` must be a string." unless value.is_a?(String)
166
159
 
167
- hash = iso8601_interval_to_hash(value)
160
+ hash = iso8601_interval_to_hash(value)
168
161
 
169
- months, days, micros = hash_to__append_interval_args(hash)
162
+ months, days, micros = hash_to__append_interval_args(hash)
170
163
 
171
- _append_interval(months, days, micros)
172
- end
164
+ _append_interval(months, days, micros)
165
+ end
173
166
 
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)
167
+ #
168
+ # appends value.
169
+ #
170
+ # require 'duckdb'
171
+ # db = DuckDB::Database.open
172
+ # con = db.connect
173
+ # con.query('CREATE TABLE users (id INTEGER, name VARCHAR)')
174
+ # appender = con.appender('users')
175
+ # appender.begin_row
176
+ # appender.append(1)
177
+ # appender.append('Alice')
178
+ # appender.end_row
179
+ #
180
+ def append(value)
181
+ case value
182
+ when NilClass
183
+ append_null
184
+ when Float
185
+ append_double(value)
186
+ when Integer
188
187
  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
188
+ when RANGE_INT16
189
+ append_int16(value)
190
+ when RANGE_INT32
191
+ append_int32(value)
192
+ when RANGE_INT64
193
+ append_int64(value)
224
194
  else
225
- raise(DuckDB::Error, "not supported type #{value} (#{value.class})")
195
+ append_hugeint(value)
226
196
  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)
197
+ when String
198
+ blob?(value) ? append_blob(value) : append_varchar(value)
199
+ when TrueClass, FalseClass
200
+ append_bool(value)
201
+ when Time
202
+ if respond_to?(:append_timestamp)
203
+ append_timestamp(value)
204
+ else
205
+ append_varchar(value.strftime('%Y-%m-%d %H:%M:%S.%N'))
245
206
  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
207
+ when Date
208
+ if respond_to?(:append_date)
209
+ append_date(value)
210
+ else
211
+ append_varchar(value.strftime('%Y-%m-%d'))
279
212
  end
280
- hash
213
+ else
214
+ raise(DuckDB::Error, "not supported type #{value} (#{value.class})")
281
215
  end
216
+ end
282
217
 
283
- def m_interval_to_hash(hash, digit, time)
284
- key = time ? 'TM' : 'M'
285
- hash[key] = digit.to_i
218
+ #
219
+ # append a row.
220
+ #
221
+ # appender.append_row(1, 'Alice')
222
+ #
223
+ # is same as:
224
+ #
225
+ # appender.begin_row
226
+ # appender.append(1)
227
+ # appender.append('Alice')
228
+ # appender.end_row
229
+ #
230
+ def append_row(*args)
231
+ begin_row
232
+ args.each do |arg|
233
+ append(arg)
286
234
  end
235
+ end_row
236
+ end
287
237
 
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
238
+ private
294
239
 
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
240
+ def blob?(value)
241
+ value.instance_of?(DuckDB::Blob) || value.encoding == Encoding::BINARY
301
242
  end
302
243
  end
303
244
  end
@@ -0,0 +1,55 @@
1
+ module DuckDB
2
+ class Column
3
+ #
4
+ # returns column type symbol
5
+ # `:unknown` means that the column type is unknown/unsupported by ruby-duckdb.
6
+ # `:invalid` means that the column type is invalid in duckdb.
7
+ #
8
+ # require 'duckdb'
9
+ # db = DuckDB::Database.open
10
+ # con = db.connect
11
+ # con.query('CREATE TABLE users (id INTEGER, name VARCHAR(30))')
12
+ #
13
+ # users = con.query('SELECT * FROM users')
14
+ # columns = users.columns
15
+ # columns.first.type #=> :integer
16
+ #
17
+ def type
18
+ types = %i[
19
+ invalid
20
+ boolean
21
+ tinyint
22
+ smallint
23
+ integer
24
+ bigint
25
+ utinyint
26
+ usmallint
27
+ uinteger
28
+ ubigint
29
+ float
30
+ double
31
+ timestamp
32
+ date
33
+ time
34
+ interval
35
+ hugeint
36
+ varchar
37
+ blob
38
+ decimal
39
+ timestamp_s
40
+ timestamp_ms
41
+ timestamp_ns
42
+ enum
43
+ list
44
+ struct
45
+ map
46
+ uuid
47
+ json
48
+ ]
49
+ index = _type
50
+ return :unknown if index >= types.size
51
+
52
+ types[index]
53
+ end
54
+ end
55
+ end
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