rdbi 0.9.0 → 0.9.1
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.
- data/README.rdoc +18 -1
- data/Rakefile +38 -0
- data/VERSION +1 -1
- data/lib/rdbi.rb +23 -32
- data/lib/rdbi/cursor.rb +6 -1
- data/lib/rdbi/database.rb +62 -82
- data/lib/rdbi/pool.rb +14 -3
- data/lib/rdbi/result.rb +140 -30
- data/lib/rdbi/statement.rb +72 -44
- data/lib/rdbi/types.rb +20 -19
- data/perf/bench.rb +58 -0
- data/perf/profile.rb +123 -0
- data/rdbi.gemspec +37 -37
- data/test/test_database.rb +3 -2
- data/test/test_pool.rb +7 -1
- data/test/test_rdbi.rb +0 -17
- data/test/test_result.rb +38 -3
- data/test/test_statement.rb +0 -1
- metadata +7 -16
- data/.gitignore +0 -33
data/lib/rdbi/result.rb
CHANGED
@@ -35,7 +35,6 @@
|
|
35
35
|
# RDBI::Result#fetch or more explicitly with the RDBI::Result#as call.
|
36
36
|
#
|
37
37
|
class RDBI::Result
|
38
|
-
extend MethLab
|
39
38
|
include Enumerable
|
40
39
|
|
41
40
|
# The RDBI::Schema structure associated with this result.
|
@@ -59,19 +58,23 @@ class RDBI::Result
|
|
59
58
|
# The binds used in the statement that yielded this Result.
|
60
59
|
attr_reader :binds
|
61
60
|
|
62
|
-
#
|
63
|
-
|
61
|
+
# See RDBI::Statement#rewindable_result.
|
62
|
+
attr_reader :rewindable_result
|
64
63
|
|
65
|
-
##
|
66
|
-
# :attr_reader: has_data
|
67
|
-
#
|
68
|
-
# Does this result have data?
|
69
64
|
|
70
|
-
|
71
|
-
|
72
|
-
|
65
|
+
# :nodoc: FIXME async
|
66
|
+
def complete
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
alias complete? complete
|
71
|
+
|
73
72
|
# Does this result have data?
|
74
|
-
|
73
|
+
def has_data
|
74
|
+
@data.size > 0
|
75
|
+
end
|
76
|
+
|
77
|
+
alias has_data? has_data
|
75
78
|
|
76
79
|
#
|
77
80
|
# Creates a new RDBI::Result. Please refer to RDBI::Statement#new_execution
|
@@ -86,10 +89,11 @@ class RDBI::Result
|
|
86
89
|
@sth = sth
|
87
90
|
@binds = binds
|
88
91
|
@type_hash = type_hash
|
89
|
-
@mutex = Mutex.new
|
90
92
|
@driver = RDBI::Result::Driver::Array
|
91
93
|
@fetch_handle = nil
|
92
|
-
|
94
|
+
|
95
|
+
configure_rewindable
|
96
|
+
configure_driver(@driver)
|
93
97
|
end
|
94
98
|
|
95
99
|
#
|
@@ -106,6 +110,8 @@ class RDBI::Result
|
|
106
110
|
@schema = res.instance_variable_get(:@schema)
|
107
111
|
@result_count = res.instance_variable_get(:@result_count)
|
108
112
|
@affected_count = res.instance_variable_get(:@affected_count)
|
113
|
+
|
114
|
+
configure_rewindable
|
109
115
|
end
|
110
116
|
|
111
117
|
#
|
@@ -125,7 +131,15 @@ class RDBI::Result
|
|
125
131
|
end
|
126
132
|
|
127
133
|
#
|
128
|
-
#
|
134
|
+
# Is this result empty?
|
135
|
+
#
|
136
|
+
def empty?
|
137
|
+
@data.empty?
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Coerce the underlying result to an array, fetching all values. Same as
|
142
|
+
# setting RDBI::Result#rewindable_result.
|
129
143
|
#
|
130
144
|
def coerce_to_array
|
131
145
|
@data.coerce_to_array
|
@@ -153,13 +167,18 @@ class RDBI::Result
|
|
153
167
|
#
|
154
168
|
# Any additional arguments will be passed to the driver's constructor.
|
155
169
|
#
|
170
|
+
# This will force a rewind even if +rewindable_result+ is false.
|
171
|
+
#
|
156
172
|
def as(driver_klass, *args)
|
157
173
|
|
158
174
|
driver_klass = RDBI::Util.class_from_class_or_symbol(driver_klass, RDBI::Result::Driver)
|
159
175
|
|
176
|
+
rr = @data.rewindable_result
|
177
|
+
@data.rewindable_result = true
|
160
178
|
@data.rewind
|
179
|
+
@data.rewindable_result = rr
|
161
180
|
@driver = driver_klass
|
162
|
-
@
|
181
|
+
configure_driver(@driver, *args)
|
163
182
|
end
|
164
183
|
|
165
184
|
#
|
@@ -213,6 +232,20 @@ class RDBI::Result
|
|
213
232
|
@fetch_handle.fetch(row_count)
|
214
233
|
end
|
215
234
|
|
235
|
+
#
|
236
|
+
# returns the first result in the set.
|
237
|
+
#
|
238
|
+
def first
|
239
|
+
@data.first
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
# returns the last result in the set.
|
244
|
+
#
|
245
|
+
def last
|
246
|
+
@data.last
|
247
|
+
end
|
248
|
+
|
216
249
|
alias read fetch
|
217
250
|
|
218
251
|
#
|
@@ -235,7 +268,6 @@ class RDBI::Result
|
|
235
268
|
else
|
236
269
|
@data.fetch(row_count)
|
237
270
|
end
|
238
|
-
RDBI::Util.deep_copy(final_res)
|
239
271
|
end
|
240
272
|
|
241
273
|
#
|
@@ -251,6 +283,19 @@ class RDBI::Result
|
|
251
283
|
@binds = nil
|
252
284
|
@schema = nil
|
253
285
|
end
|
286
|
+
|
287
|
+
protected
|
288
|
+
|
289
|
+
def configure_driver(driver_klass, *args)
|
290
|
+
@fetch_handle = driver_klass.new(self, *args)
|
291
|
+
end
|
292
|
+
|
293
|
+
def configure_rewindable
|
294
|
+
@rewindable_result = @sth.rewindable_result
|
295
|
+
if self.rewindable_result
|
296
|
+
@data.coerce_to_array
|
297
|
+
end
|
298
|
+
end
|
254
299
|
end
|
255
300
|
|
256
301
|
#
|
@@ -289,7 +334,6 @@ class RDBI::Result::Driver
|
|
289
334
|
#
|
290
335
|
def initialize(result, *args)
|
291
336
|
@result = result
|
292
|
-
@result.rewind
|
293
337
|
end
|
294
338
|
|
295
339
|
#
|
@@ -297,26 +341,48 @@ class RDBI::Result::Driver
|
|
297
341
|
# type converted array.
|
298
342
|
#
|
299
343
|
def fetch(row_count)
|
300
|
-
|
301
|
-
|
344
|
+
rows = RDBI::Util.format_results(row_count, (@result.raw_fetch(row_count) || []))
|
345
|
+
|
346
|
+
if rows.nil?
|
347
|
+
return rows
|
348
|
+
elsif [:first, :last].include?(row_count)
|
349
|
+
return convert_row(rows)
|
350
|
+
else
|
351
|
+
result = []
|
352
|
+
rows.each do |row|
|
353
|
+
result << convert_row(row)
|
354
|
+
end
|
302
355
|
end
|
356
|
+
return result
|
357
|
+
end
|
358
|
+
|
359
|
+
#
|
360
|
+
# Returns the first result in the set.
|
361
|
+
#
|
362
|
+
def first
|
363
|
+
return fetch(:first)
|
364
|
+
end
|
303
365
|
|
304
|
-
|
366
|
+
#
|
367
|
+
# Returns the last result in the set.
|
368
|
+
#
|
369
|
+
# +Warning+: Depending on your database and drivers, calling this could have
|
370
|
+
# serious memory and performance implications!
|
371
|
+
#
|
372
|
+
def last
|
373
|
+
return fetch(:last)
|
305
374
|
end
|
306
375
|
|
307
|
-
|
308
|
-
|
376
|
+
protected
|
377
|
+
|
309
378
|
def convert_row(row)
|
310
|
-
|
311
|
-
|
312
|
-
|
379
|
+
return [] if row.nil?
|
380
|
+
|
381
|
+
row.each_with_index do |x, i|
|
382
|
+
row[i] = RDBI::Type::Out.convert(x, @result.schema.columns[i], @result.type_hash)
|
313
383
|
end
|
314
|
-
return newrow
|
315
|
-
end
|
316
384
|
|
317
|
-
|
318
|
-
def convert_item(item, column)
|
319
|
-
RDBI::Type::Out.convert(item, column, @result.type_hash)
|
385
|
+
return row
|
320
386
|
end
|
321
387
|
end
|
322
388
|
|
@@ -399,4 +465,48 @@ class RDBI::Result::Driver::Struct < RDBI::Result::Driver
|
|
399
465
|
end
|
400
466
|
end
|
401
467
|
|
468
|
+
#
|
469
|
+
# Yields YAML representations of rows, each as an array keyed by the column
|
470
|
+
# name (as symbol, not string).
|
471
|
+
#
|
472
|
+
# For example, a table:
|
473
|
+
#
|
474
|
+
# create table foo (i integer, x varchar);
|
475
|
+
# insert into foo (i, x) values (1, "bar");
|
476
|
+
# insert into foo (i, x) values (2, "foo");
|
477
|
+
# insert into foo (i, x) values (3, "quux");
|
478
|
+
#
|
479
|
+
# With a query as such:
|
480
|
+
#
|
481
|
+
# dbh.execute("select * from foo").as(:YAML).fetch(:all)
|
482
|
+
#
|
483
|
+
# Will yield:
|
484
|
+
#
|
485
|
+
# ---
|
486
|
+
# - :i: 1
|
487
|
+
# :x: bar
|
488
|
+
# - :i: 2
|
489
|
+
# :x: foo
|
490
|
+
# - :i: 3
|
491
|
+
# :x: quux
|
492
|
+
#
|
493
|
+
class RDBI::Result::Driver::YAML < RDBI::Result::Driver
|
494
|
+
def initialize(result, *args)
|
495
|
+
super
|
496
|
+
RDBI::Util.optional_require('yaml')
|
497
|
+
end
|
498
|
+
|
499
|
+
def fetch(row_count)
|
500
|
+
column_names = @result.schema.columns.map(&:name)
|
501
|
+
|
502
|
+
if [:first, :last].include?(row_count)
|
503
|
+
Hash[column_names.zip(@result.raw_fetch(row_count)[0])].to_yaml
|
504
|
+
else
|
505
|
+
@result.raw_fetch(row_count).collect do |row|
|
506
|
+
Hash[column_names.zip(row)]
|
507
|
+
end.to_yaml
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
402
512
|
# vim: syntax=ruby ts=2 et sw=2 sts=2
|
data/lib/rdbi/statement.rb
CHANGED
@@ -83,14 +83,10 @@
|
|
83
83
|
# may be possible.
|
84
84
|
#
|
85
85
|
class RDBI::Statement
|
86
|
-
extend MethLab
|
87
|
-
|
88
86
|
# the RDBI::Database handle that created this statement.
|
89
87
|
attr_reader :dbh
|
90
88
|
# The query this statement was created for.
|
91
89
|
attr_reader :query
|
92
|
-
# A mutex for locked operations. Basically a cached copy of Mutex.new.
|
93
|
-
attr_reader :mutex
|
94
90
|
# The input type map provided during statement creation -- used for binding.
|
95
91
|
attr_reader :input_type_map
|
96
92
|
|
@@ -98,68 +94,81 @@ class RDBI::Statement
|
|
98
94
|
# :attr_reader: last_result
|
99
95
|
#
|
100
96
|
# The last RDBI::Result this statement yielded.
|
101
|
-
|
97
|
+
attr_accessor :last_result
|
102
98
|
|
103
99
|
##
|
104
|
-
# :attr_reader:
|
100
|
+
# :attr_reader: rewindable_result
|
105
101
|
#
|
106
|
-
#
|
102
|
+
# Allows the user to request a fully rewindable result, allowing the use of
|
103
|
+
# fetching the last item, direct indexing, and rewinding.
|
104
|
+
#
|
105
|
+
# This can be a huge performance impact and thus should be used with great
|
106
|
+
# caution.
|
107
|
+
#
|
108
|
+
# Cascades from RDBI::Database#rewindable_result and through
|
109
|
+
# RDBI::Result#rewindable_result.
|
110
|
+
#
|
111
|
+
attr_accessor :rewindable_result
|
107
112
|
|
108
113
|
##
|
109
|
-
# :attr_reader: finished
|
114
|
+
# :attr_reader: finished
|
110
115
|
#
|
111
116
|
# Has this statement been finished?
|
112
117
|
|
113
|
-
|
118
|
+
# Has this statement been finished?
|
119
|
+
|
120
|
+
attr_reader :finished
|
121
|
+
alias finished? finished
|
114
122
|
|
115
|
-
##
|
116
|
-
# :attr_reader: driver
|
117
|
-
#
|
118
123
|
# The RDBI::Driver object that this statement belongs to.
|
124
|
+
def driver
|
125
|
+
dbh.driver
|
126
|
+
end
|
119
127
|
|
120
|
-
|
128
|
+
class << self
|
129
|
+
def input_type_map
|
130
|
+
@input_type_map ||= RDBI::Type.create_type_hash(RDBI::Type::In)
|
131
|
+
end
|
132
|
+
end
|
121
133
|
|
122
134
|
#
|
123
135
|
# Initialize a statement handle, given a text query and the RDBI::Database
|
124
136
|
# handle that created it.
|
125
137
|
#
|
126
138
|
def initialize(query, dbh)
|
127
|
-
@query
|
128
|
-
@dbh
|
129
|
-
@
|
130
|
-
@
|
131
|
-
@input_type_map = RDBI::Type.create_type_hash(RDBI::Type::In)
|
139
|
+
@query = query
|
140
|
+
@dbh = dbh
|
141
|
+
@finished = false
|
142
|
+
@input_type_map = self.class.input_type_map
|
132
143
|
|
133
|
-
|
144
|
+
self.rewindable_result = dbh.rewindable_result
|
145
|
+
@dbh.open_statements[self.object_id] = self
|
146
|
+
end
|
147
|
+
|
148
|
+
def prep_finalizer(&block)
|
149
|
+
if block
|
150
|
+
@finish_block = block
|
151
|
+
ObjectSpace.define_finalizer(self, lambda do |x|
|
152
|
+
block.call
|
153
|
+
end)
|
154
|
+
end
|
134
155
|
end
|
135
156
|
|
136
157
|
#
|
137
158
|
# Execute the statement with the supplied binds.
|
138
159
|
#
|
139
160
|
def execute(*binds)
|
140
|
-
|
141
|
-
|
142
|
-
# XXX if we ever support some kind of hash type, this'll get ugly.
|
143
|
-
hashes, binds = binds.partition { |x| x.kind_of?(Hash) }
|
161
|
+
binds = pre_execute(*binds)
|
144
162
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
hash.each do |key, value|
|
150
|
-
newhash[key] = RDBI::Type::In.convert(value, @input_type_map)
|
151
|
-
end
|
152
|
-
|
153
|
-
newhash
|
154
|
-
end
|
155
|
-
end
|
163
|
+
cursor, schema, type_map = new_execution(*binds)
|
164
|
+
cursor.rewindable_result = self.rewindable_result
|
165
|
+
self.last_result = RDBI::Result.new(self, binds, cursor, schema, type_map)
|
166
|
+
end
|
156
167
|
|
157
|
-
|
168
|
+
def execute_modification(*binds)
|
169
|
+
binds = pre_execute(*binds)
|
158
170
|
|
159
|
-
|
160
|
-
exec_args = *new_execution(*binds)
|
161
|
-
self.last_result = RDBI::Result.new(self, binds, *exec_args)
|
162
|
-
end
|
171
|
+
return new_modification(*binds)
|
163
172
|
end
|
164
173
|
|
165
174
|
#
|
@@ -170,10 +179,9 @@ class RDBI::Statement
|
|
170
179
|
# 'super' as their last statement.
|
171
180
|
#
|
172
181
|
def finish
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
end
|
182
|
+
@finish_block.call if @finish_block
|
183
|
+
@dbh.open_statements.delete(self.object_id)
|
184
|
+
@finished = true
|
177
185
|
end
|
178
186
|
|
179
187
|
##
|
@@ -196,9 +204,29 @@ class RDBI::Statement
|
|
196
204
|
# to this call) to RDBI::Result.new.
|
197
205
|
#
|
198
206
|
|
199
|
-
|
207
|
+
def new_execution
|
208
|
+
raise NoMethodError, "this method is not implemented in this driver"
|
209
|
+
end
|
210
|
+
|
211
|
+
def new_modification(*binds)
|
200
212
|
raise NoMethodError, "this method is not implemented in this driver"
|
201
213
|
end
|
214
|
+
|
215
|
+
protected
|
216
|
+
|
217
|
+
def pre_execute(*binds)
|
218
|
+
raise StandardError, "you may not execute a finished handle" if @finished
|
219
|
+
|
220
|
+
if binds[0].kind_of?(Hash)
|
221
|
+
binds[0].each do |key, value|
|
222
|
+
binds[0][key] = RDBI::Type::In.convert(value, @input_type_map)
|
223
|
+
end
|
224
|
+
else
|
225
|
+
binds.collect! { |x| RDBI::Type::In.convert(x, @input_type_map) }
|
226
|
+
end
|
227
|
+
|
228
|
+
return binds
|
229
|
+
end
|
202
230
|
end
|
203
231
|
|
204
232
|
# vim: syntax=ruby ts=2 et sw=2 sts=2
|
data/lib/rdbi/types.rb
CHANGED
@@ -115,16 +115,17 @@ module RDBI::Type
|
|
115
115
|
|
116
116
|
# Shorthand for creating a new +TypeLib::FilterList+.
|
117
117
|
def self.filterlist(*ary)
|
118
|
-
|
118
|
+
[Filters::NULL, *ary]
|
119
119
|
end
|
120
120
|
|
121
121
|
# Shorthand to duplicate the +DEFAULTS+ hash from a module. Most frequently
|
122
122
|
# used to get a copy of the RDBI::Type::In and RDBI::Type::Out type maps.
|
123
123
|
def self.create_type_hash(klass)
|
124
124
|
hash = { }
|
125
|
-
klass::DEFAULTS
|
125
|
+
orig = klass::DEFAULTS
|
126
|
+
orig.keys.each do |key|
|
126
127
|
flist = filterlist()
|
127
|
-
|
128
|
+
orig[key].each do |filter|
|
128
129
|
flist << filter
|
129
130
|
end
|
130
131
|
hash[key] = flist
|
@@ -140,12 +141,12 @@ module RDBI::Type
|
|
140
141
|
#
|
141
142
|
module Out
|
142
143
|
DEFAULTS = {
|
143
|
-
:integer =>
|
144
|
-
:decimal =>
|
145
|
-
:datetime =>
|
146
|
-
:timestamp =>
|
147
|
-
:boolean =>
|
148
|
-
:default =>
|
144
|
+
:integer => [Filters::STR_TO_INT],
|
145
|
+
:decimal => [Filters::STR_TO_DEC],
|
146
|
+
:datetime => [TypeLib::Canned.build_strptime_filter(DEFAULT_STRFTIME_FILTER)],
|
147
|
+
:timestamp => [TypeLib::Canned.build_strptime_filter(DEFAULT_STRFTIME_FILTER)],
|
148
|
+
:boolean => [Filters::TO_BOOLEAN],
|
149
|
+
:default => []
|
149
150
|
}
|
150
151
|
|
151
152
|
#
|
@@ -159,7 +160,7 @@ module RDBI::Type
|
|
159
160
|
fl = type_hash[:default]
|
160
161
|
end
|
161
162
|
|
162
|
-
|
163
|
+
TypeLib.execute_filterlist(fl, obj)
|
163
164
|
end
|
164
165
|
end
|
165
166
|
|
@@ -171,14 +172,14 @@ module RDBI::Type
|
|
171
172
|
#
|
172
173
|
module In
|
173
174
|
DEFAULTS = {
|
174
|
-
Integer =>
|
175
|
-
Fixnum =>
|
176
|
-
Float =>
|
177
|
-
BigDecimal =>
|
178
|
-
DateTime =>
|
179
|
-
TrueClass =>
|
180
|
-
FalseClass =>
|
181
|
-
:default =>
|
175
|
+
Integer => [Filters::FROM_INTEGER],
|
176
|
+
Fixnum => [Filters::FROM_INTEGER],
|
177
|
+
Float => [Filters::FROM_NUMERIC],
|
178
|
+
BigDecimal => [Filters::FROM_DECIMAL],
|
179
|
+
DateTime => [Filters::FROM_DATETIME],
|
180
|
+
TrueClass => [Filters::FROM_BOOLEAN],
|
181
|
+
FalseClass => [Filters::FROM_BOOLEAN],
|
182
|
+
:default => []
|
182
183
|
}
|
183
184
|
|
184
185
|
#
|
@@ -192,7 +193,7 @@ module RDBI::Type
|
|
192
193
|
fl = type_hash[:default]
|
193
194
|
end
|
194
195
|
|
195
|
-
|
196
|
+
TypeLib.execute_filterlist(fl, obj)
|
196
197
|
end
|
197
198
|
end
|
198
199
|
end
|