duckdb 0.2.9.0 → 0.3.3.0

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