rdbi 0.9.0 → 0.9.1

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