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.
@@ -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
- # FIXME async
63
- inline(:complete, :complete?) { true }
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
- # :attr_reader: has_data?
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
- inline(:has_data, :has_data?) { @data.size > 0 }
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
- as(@driver)
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
- # Coerce the underlying result to an array, fetching all values.
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
- @fetch_handle = driver_klass.new(self, *args)
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
- ary = (@result.raw_fetch(row_count) || []).enum_for.with_index.map do |item, i|
301
- convert_row(item)
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
- RDBI::Util.format_results(row_count, ary)
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
- # convert an entire row of data with the specified result map (see
308
- # RDBI::Type)
376
+ protected
377
+
309
378
  def convert_row(row)
310
- newrow = []
311
- (row || []).each_with_index do |x, i|
312
- newrow.push(convert_item(x, @result.schema.columns[i]))
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
- # convert a single item (row element) with the specified result map.
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
@@ -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
- attr_threaded_accessor :last_result
97
+ attr_accessor :last_result
102
98
 
103
99
  ##
104
- # :attr_reader: finished
100
+ # :attr_reader: rewindable_result
105
101
  #
106
- # Has this statement been finished?
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
- inline(:finished, :finished?) { @finished }
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
- inline(:driver) { dbh.driver }
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 = query
128
- @dbh = dbh
129
- @mutex = Mutex.new
130
- @finished = false
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
- @dbh.open_statements.push(self)
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
- raise StandardError, "you may not execute a finished handle" if @finished
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
- if hashes
146
- hashes.collect! do |hash|
147
- newhash = { }
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
- binds = (hashes || []) + binds.collect { |x| RDBI::Type::In.convert(x, @input_type_map) }
168
+ def execute_modification(*binds)
169
+ binds = pre_execute(*binds)
158
170
 
159
- mutex.synchronize do
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
- mutex.synchronize do
174
- dbh.open_statements.reject! { |x| x.object_id == self.object_id }
175
- @finished = true
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
- inline(:new_execution) do |*args|
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
@@ -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
- TypeLib::FilterList.new([Filters::NULL, *ary])
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.each do |key, value|
125
+ orig = klass::DEFAULTS
126
+ orig.keys.each do |key|
126
127
  flist = filterlist()
127
- value.each do |filter|
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 => RDBI::Type.filterlist(Filters::STR_TO_INT),
144
- :decimal => RDBI::Type.filterlist(Filters::STR_TO_DEC),
145
- :datetime => RDBI::Type.filterlist(TypeLib::Canned.build_strptime_filter(DEFAULT_STRFTIME_FILTER)),
146
- :timestamp => RDBI::Type.filterlist(TypeLib::Canned.build_strptime_filter(DEFAULT_STRFTIME_FILTER)),
147
- :boolean => RDBI::Type.filterlist(Filters::TO_BOOLEAN),
148
- :default => RDBI::Type.filterlist()
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
- fl.execute(obj)
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 => RDBI::Type.filterlist(Filters::FROM_INTEGER),
175
- Fixnum => RDBI::Type.filterlist(Filters::FROM_INTEGER),
176
- Float => RDBI::Type.filterlist(Filters::FROM_NUMERIC),
177
- BigDecimal => RDBI::Type.filterlist(Filters::FROM_DECIMAL),
178
- DateTime => RDBI::Type.filterlist(Filters::FROM_DATETIME),
179
- TrueClass => RDBI::Type.filterlist(Filters::FROM_BOOLEAN),
180
- FalseClass => RDBI::Type.filterlist(Filters::FROM_BOOLEAN),
181
- :default => RDBI::Type.filterlist()
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
- fl.execute(obj)
196
+ TypeLib.execute_filterlist(fl, obj)
196
197
  end
197
198
  end
198
199
  end