rdbi 0.9.1 → 1.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 54de058484d15e39b1555c43dd208f2ae7d061ba
4
+ data.tar.gz: 96fd6fa385652f31d50f12cf04f2ee1b7ad4e03e
5
+ SHA512:
6
+ metadata.gz: 25751397efafcd45de071d2ed98f5669c4bf59b8070e9d64393ddc84fdd67d0e97aecbe6bc53811839dfc82f224e5dfcadefedacdc926287a10319d8b5ab06f6
7
+ data.tar.gz: eecff757dba45e463dab45b88fb553625350937c6f4847af0280651f5b128ab4e7b50b842258f22318c2c6e575bf4d4f373c2d7667694f27cde43f3ffe201b22
File without changes
@@ -0,0 +1,13 @@
1
+ === 2013-03-16 / 1.1.0
2
+ * Bump version
3
+ * [deprecation] Statement#prep_finalizer considered harmful
4
+ Internal API: driver authors, do not use. RDBI::Util offers upon_finalize!
5
+ but this may not be needed as RDBI's own finalizations now will finish()
6
+ driver sth and result set data objects.
7
+ * [bugfix] issue#33 (memory leak) addressed with new finalization and weakened
8
+ sth objects held by Result objects.
9
+ * [bugfix] Result no longer finish()es Statement (issue#34)
10
+
11
+ === Prehistory / 2011-01-30
12
+
13
+ * First use of Hoe.
@@ -0,0 +1,28 @@
1
+ .gemtest
2
+ History.txt
3
+ LICENSE
4
+ Manifest.txt
5
+ README.txt
6
+ Rakefile
7
+ docs/external-api.pdf
8
+ docs/external-api.texi
9
+ lib/rdbi.rb
10
+ lib/rdbi/cursor.rb
11
+ lib/rdbi/database.rb
12
+ lib/rdbi/driver.rb
13
+ lib/rdbi/pool.rb
14
+ lib/rdbi/result.rb
15
+ lib/rdbi/schema.rb
16
+ lib/rdbi/statement.rb
17
+ lib/rdbi/types.rb
18
+ perf/bench.rb
19
+ perf/profile.rb
20
+ rdbi.gemspec
21
+ test/helper.rb
22
+ test/test_database.rb
23
+ test/test_pool.rb
24
+ test/test_rdbi.rb
25
+ test/test_result.rb
26
+ test/test_statement.rb
27
+ test/test_types.rb
28
+ test/test_util.rb
@@ -44,22 +44,22 @@ path down the rabbit hole:
44
44
  dbh = RDBI.connect(:SQLite3, :database => ":memory:")
45
45
 
46
46
  # execute this CREATE TABLE statement:
47
- dbh.execute("create table foo (bar integer, baz varchar)")
47
+ dbh.execute("create table tbl (string varchar(32), number integer)")
48
48
 
49
49
  # prepare an insert statement for execution with two placeholders:
50
- dbh.prepare("insert into foo (bar, baz) values (?, ?)") do |sth|
50
+ dbh.prepare("insert into tbl (string, number) values (?, ?)") do |sth|
51
51
 
52
- # and execute it with bound variables:
53
- sth.execute(1, "foo")
54
- sth.execute(2, "bar")
55
- sth.execute(3, "quux")
52
+ # and execute it three times with bound variables:
53
+ sth.execute("foo", -37)
54
+ sth.execute("bar", 127)
55
+ sth.execute("quux", 1024)
56
56
  end
57
57
 
58
58
  # get a result handle from a select statement:
59
- result = dbh.execute("select * from foo")
59
+ result = dbh.execute("select * from tbl")
60
60
 
61
61
  # and fetch the first row
62
- result.fetch(:first) # [1, "foo"]
62
+ result.fetch(:first) # ["foo", -37]
63
63
 
64
64
  == What +is+ RDBI all about, anyway?
65
65
 
@@ -154,7 +154,7 @@ Aaaaaand here are some things RDBI won't do:
154
154
  == Show me even more awesome!
155
155
 
156
156
  # retrieve cached handles 5 times -- handles will be yielded twice if there
157
- # is a smaller Pool size:
157
+ # is a smaller Pool size:
158
158
  5.times do
159
159
  RDBI.connect_cached(:SQLite3, :database => ":memory:")
160
160
  end
data/Rakefile CHANGED
@@ -1,147 +1,53 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- version = (File.exist?('VERSION') ? File.read('VERSION') : "").chomp
5
-
6
- begin
7
- require 'jeweler'
8
- Jeweler::Tasks.new do |gem|
9
- gem.name = "rdbi"
10
- gem.summary = %Q{RDBI provides sane query-level database access with low magic.}
11
- gem.description = %Q{RDBI is a rearchitecture of the Ruby/DBI project by its maintainer and others. It intends to fully supplant Ruby/DBI in the future for similar database access needs.}
12
- gem.email = "erik@hollensbe.org"
13
- gem.homepage = "http://github.com/RDBI/rdbi"
14
- gem.authors = ["Erik Hollensbe"]
15
-
16
- gem.add_development_dependency 'rdbi-driver-mock'
17
- gem.add_development_dependency 'test-unit'
18
- gem.add_development_dependency 'rdoc'
19
- ## for now, install hanna from here: http://github.com/erikh/hanna
20
- #gem.add_development_dependency 'hanna'
21
- gem.add_development_dependency 'fastercsv'
1
+ # -*- ruby -*-
22
2
 
23
- gem.add_dependency 'methlab', '>= 0.0.9'
24
- gem.add_dependency 'epoxy', '>= 0.3.1'
25
- gem.add_dependency 'typelib'
26
-
27
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
28
- end
29
- Jeweler::GemcutterTasks.new
30
- rescue LoadError
31
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
32
- end
33
-
34
- begin
35
- gem 'test-unit'
36
- require 'rake/testtask'
37
- Rake::TestTask.new(:test) do |test|
38
- test.libs << 'lib' << 'test'
39
- test.pattern = 'test/**/test_*.rb'
40
- test.verbose = true
41
- end
42
- rescue LoadError
43
- task :test do
44
- abort "test-unit gem is not available. In order to run test-unit, you must: sudo gem install test-unit"
45
- end
46
- end
3
+ require 'rubygems'
4
+ require 'hoe'
47
5
 
6
+ Hoe.plugins.delete :rubyforge
7
+ Hoe.plugin :git
8
+ Hoe.plugin :rcov
9
+ Hoe.plugin :roodi
10
+ Hoe.plugin :reek
48
11
 
49
- begin
50
- require 'rcov/rcovtask'
51
- Rcov::RcovTask.new do |test|
52
- test.libs << 'test'
53
- test.pattern = 'test/**/test_*.rb'
54
- test.verbose = true
55
- end
56
- rescue LoadError
57
- task :rcov do
58
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
59
- end
60
- end
12
+ spec = Hoe.spec 'rdbi' do
13
+ developer 'Erik Hollensbe', 'erik@hollensbe.org'
61
14
 
62
- task :test => :check_dependencies
15
+ self.rubyforge_name = nil
63
16
 
64
- begin
65
- require 'roodi'
66
- require 'roodi_task'
67
- RoodiTask.new do |t|
68
- t.verbose = false
69
- end
70
- rescue LoadError
71
- task :roodi do
72
- abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
73
- end
74
- end
17
+ self.description = <<-EOF
18
+ RDBI is a database interface built out of small parts. A micro framework for
19
+ databases, RDBI works with and extends libraries like 'typelib' and 'epoxy'
20
+ to provide type conversion and binding facilities. Via a driver/adapter
21
+ system it provides database access. RDBI itself provides pooling and other
22
+ enhanced database features.
23
+ EOF
75
24
 
76
- task :default => :test
25
+ self.summary = 'RDBI is a database interface built out of small parts.'
26
+ self.urls = %w[http://github.com/rdbi/rdbi]
77
27
 
78
- begin
79
- require 'hanna'
80
- require 'rdoc/task'
81
- RDoc::Task.new do |rdoc|
82
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
28
+ require_ruby_version ">= 1.8.7"
83
29
 
84
- rdoc.options.push '-f', 'hanna'
85
- rdoc.main = 'README.rdoc'
86
- rdoc.rdoc_dir = 'rdoc'
87
- rdoc.title = "RDBI #{version} Documentation"
88
- rdoc.rdoc_files.include('README*')
89
- rdoc.rdoc_files.include('lib/**/*.rb')
90
- end
91
- rescue LoadError => e
92
- rdoc_missing = lambda do
93
- abort "What, were you born in a barn? Install rdoc and hanna at http://github.com/raggi/hanna ."
94
- end
95
- task :rdoc, &rdoc_missing
96
- task :clobber_rdoc, &rdoc_missing
97
- end
30
+ extra_dev_deps << ['rdbi-driver-mock']
31
+ extra_dev_deps << ['test-unit']
32
+ extra_dev_deps << ['rdoc']
33
+ extra_dev_deps << ['hanna-nouveau']
34
+ extra_dev_deps << ['fastercsv']
35
+ extra_dev_deps << ['hoe-roodi']
36
+ extra_dev_deps << ['hoe-reek']
37
+ extra_dev_deps << ['minitest']
98
38
 
99
- task :to_blog => [:clobber_rdoc, :rdoc] do
100
- sh "rm -fr $git/blog/content/docs/rdbi && mv doc $git/blog/content/docs/rdbi"
101
- end
39
+ extra_deps << ['epoxy', '>= 0.3.1']
40
+ extra_deps << ['typelib']
102
41
 
103
- task :install => [:test, :build]
42
+ # waiting on a patch from hoe for this
43
+ #self.extra_rdoc_args = lambda do |rd|
44
+ #rd.generator = "hanna"
45
+ #end
104
46
 
105
- task :docview => [:rerdoc] do
106
- sh "open rdoc/index.html"
47
+ desc "install a gem without sudo"
107
48
  end
108
49
 
109
- namespace :perf do
110
- namespace :profile do
111
- task :prep => [:install]
112
-
113
- task :prepared_insert => [:prep] do
114
- sh "ruby -I lib perf/profile.rb prepared_insert"
115
- end
116
-
117
- task :insert => [:prep] do
118
- sh "ruby -I lib perf/profile.rb insert"
119
- end
120
-
121
- task :raw_select => [:prep] do
122
- sh "ruby -I lib perf/profile.rb raw_select"
123
- end
124
-
125
- task :res_select => [:prep] do
126
- sh "ruby -I lib perf/profile.rb res_select"
127
- end
128
-
129
- task :single_fetch => [:prep] do
130
- sh "ruby -I lib perf/profile.rb single_fetch"
131
- end
132
-
133
- task :unprepared_raw_select => [:prep] do
134
- sh "ruby -I lib perf/profile.rb unprepared_raw_select"
135
- end
136
-
137
- task :unprepared_res_select => [:prep] do
138
- sh "ruby -I lib perf/profile.rb unprepared_res_select"
139
- end
140
-
141
- task :unprepared_single_fetch => [:prep] do
142
- sh "ruby -I lib perf/profile.rb unprepared_single_fetch"
143
- end
144
- end
50
+ task :install => [:gem] do
51
+ sh "gem install pkg/#{spec.name}-#{spec.version}.gem"
145
52
  end
146
-
147
- # vim: syntax=ruby ts=2 et sw=2 sts=2
53
+ # vim: syntax=ruby
@@ -1,6 +1,9 @@
1
1
  require 'epoxy'
2
2
 
3
3
  module RDBI
4
+
5
+ VERSION = '1.1.0'
6
+
4
7
  class << self
5
8
  #
6
9
  # The last database handle allocated. This may come from pooled connections or regular ones.
@@ -21,7 +24,8 @@ module RDBI
21
24
  #
22
25
  # connect() returns an instance of RDBI::Database. In the instance a block
23
26
  # is provided, it will be called upon connection success, with the
24
- # RDBI::Database object provided in as the first argument.
27
+ # RDBI::Database object provided in as the first argument, and the
28
+ # connection will be automatically disconnected at the end of the block.
25
29
  def self.connect(klass, *args)
26
30
 
27
31
  klass = RDBI::Util.class_from_class_or_symbol(klass, self::Driver)
@@ -29,8 +33,13 @@ module RDBI
29
33
  driver = klass.new(*args)
30
34
  dbh = self.last_dbh = driver.new_handle
31
35
 
32
- yield dbh if block_given?
33
- return dbh
36
+ return dbh unless block_given?
37
+
38
+ begin
39
+ yield dbh
40
+ ensure
41
+ dbh.disconnect rescue nil
42
+ end
34
43
  end
35
44
 
36
45
  #
@@ -43,14 +52,17 @@ module RDBI
43
52
  #
44
53
  # If a pool *already* exists, your connection arguments will be ignored and
45
54
  # it will instantiate from the Pool's connection arguments.
55
+ #
56
+ # If a block is provided, the connection is *not* disconnected at the end
57
+ # of the block.
46
58
  def self.connect_cached(klass, *args)
47
59
  args = args[0]
48
60
  pool_name = args[:pool_name] || :default
49
61
 
50
62
  dbh = nil
51
63
 
52
- if RDBI::Pool[pool_name]
53
- dbh = RDBI::Pool[pool_name].get_dbh
64
+ if pool = RDBI::Pool[pool_name]
65
+ dbh = pool.get_dbh
54
66
  else
55
67
  dbh = RDBI::Pool.new(pool_name, [klass, args]).get_dbh
56
68
  end
@@ -134,39 +146,39 @@ module RDBI::Util
134
146
  Marshal.load(Marshal.dump(obj))
135
147
  end
136
148
 
137
- #
138
- # Takes an array and appropriate boxes/deboxes it based on what was
139
- # requested.
140
- #
141
- #--
142
- # FIXME this is a really poorly performing way of doing this.
143
- #++
144
- def self.format_results(row_count, ary)
145
- case row_count
146
- when :first, :last
147
- ary = ary[0]
148
- return nil if ary and ary.empty?
149
- return ary
150
- else
151
- return ary
152
- end
153
- end
154
-
155
149
  def self.index_binds(args, index_map)
156
150
  # FIXME exception if mixed hash/indexed binds
157
- if args[0].kind_of?(Hash)
151
+
152
+ if args.empty? or !args.find { |x| x.kind_of?(Hash) }
153
+ return args
154
+ end
155
+
156
+ if args.kind_of?(Hash)
158
157
  binds = []
159
- args[0].keys.each do |key|
160
- if index = index_map.index(key)
161
- binds.insert(index, args[0][key])
162
- end
163
- end
164
- return binds
158
+ hash = args
165
159
  else
166
- return args
160
+ hashes, binds = args.partition { |x| x.kind_of?(Hash) }
161
+ hash = hashes.inject({ }, :merge)
162
+ end
163
+
164
+ hash.each do |key, value|
165
+ # XXX yes, we want to *assign* here.
166
+ if index = index_map.index(key)
167
+ binds.insert(index, value)
168
+ end
167
169
  end
170
+ return binds
168
171
  end
169
- end
172
+
173
+ def self.upon_finalize!(what, o, meth, *args)
174
+ ObjectSpace.define_finalizer(what, make_fini_proc(o, meth, *args))
175
+ end
176
+
177
+ def self.make_fini_proc(obj, meth, *args)
178
+ proc { |object_id| obj.__send__(meth.to_sym, *args) rescue nil }
179
+ end
180
+
181
+ end # -- module RDBI::Util
170
182
 
171
183
  require 'rdbi/types'
172
184
  require 'rdbi/pool'
@@ -22,7 +22,7 @@ class RDBI::Database
22
22
 
23
23
  # the last statement handle allocated. affected by +prepare+ and +execute+.
24
24
  attr_accessor :last_statement
25
-
25
+
26
26
  # the last query sent, as a string.
27
27
  attr_accessor :last_query
28
28
 
@@ -77,6 +77,7 @@ class RDBI::Database
77
77
  @connected = true
78
78
  @in_transaction = 0
79
79
  @rewindable_result = false
80
+ @preprocess_quoter = nil
80
81
  self.open_statements = { }
81
82
  end
82
83
 
@@ -92,6 +93,7 @@ class RDBI::Database
92
93
  #
93
94
  def disconnect
94
95
  @connected = false
96
+ # FIXME - this is not serving a useful purpose nor public API
95
97
  self.open_statements.values.each { |x| x.finish if x }
96
98
  self.open_statements = { }
97
99
  end
@@ -153,53 +155,85 @@ class RDBI::Database
153
155
  # end
154
156
  #
155
157
  def prepare(query)
156
- sth = nil
158
+ sth = new_statement(query)
157
159
 
158
160
  self.last_query = query
159
- sth = new_statement(query)
160
- yield sth if block_given?
161
- sth.finish if block_given?
161
+ self.open_statements[sth.object_id] = self.last_statement = ::WeakRef.new(sth)
162
+
163
+ return sth unless block_given?
162
164
 
163
- return self.last_statement = sth
165
+ begin
166
+ yield sth
167
+ ensure
168
+ sth.finish rescue nil
169
+ end
164
170
  end
165
171
 
166
172
  #
167
- # Prepares and executes a statement. Takes a string query and an optional
173
+ # Prepares and executes a statement. Takes a string query and an optional
168
174
  # number of variable type binds.
169
175
  #
170
176
  # ex:
171
177
  # res = dbh.execute("select * from foo where item = ?", "an item")
172
178
  # ary = res.to_a
173
179
  #
174
- # You can also use a block form which will finish the statement and yield the
175
- # result handle:
180
+ # If invoked with a block, the result handle will be yielded and the handle
181
+ # and statement will be finished at the end of the block. Use this form
182
+ # when the result handle is not needed outside the block:
183
+ #
176
184
  # dbh.execute("select * from foo where item = ?", "an item") do |res|
177
185
  # res.as(:Struct).fetch(:all).each do |struct|
178
186
  # p struct.item
179
187
  # end
180
188
  # end
181
189
  #
182
- # Which will be considerably more performant under some database drivers.
190
+ # Block invocation may be considerably more efficient under some database
191
+ # drivers.
192
+ #
193
+ # For DDL and other statements which return no rows, see
194
+ # #execute_modification.
183
195
  #
184
196
  def execute(query, *binds)
185
197
  res = nil
186
198
 
187
- self.last_query = query
188
- self.last_statement = sth = new_statement(query)
189
- res = sth.execute(*binds)
199
+ sth = prepare(query)
200
+ begin
201
+ res = sth.execute(*binds)
202
+ rescue Exception => e
203
+ sth.finish rescue nil
204
+ raise e
205
+ end
190
206
 
191
- if block_given?
192
- yield res
207
+ unless block_given?
208
+ RDBI::Util.upon_finalize!(res, sth, :finish)
209
+ return res
193
210
  end
194
211
 
195
- return res
212
+ begin
213
+ yield res
214
+ ensure
215
+ res.finish rescue nil
216
+ sth.finish rescue nil
217
+ end
196
218
  end
197
219
 
220
+ #
221
+ # Prepare, execute and finish a statement, returning the number of rows
222
+ # affected (number of rows INSERTed, DELETEd, etc.).
223
+ #
224
+ # Effectively equivalent to
225
+ #
226
+ # dbh.execute(sql_stmt) do |res|
227
+ # res.affected_count
228
+ # end
229
+ #
230
+ # but likely more efficient. See also RDBI::Statement#execute_modification
231
+ #
198
232
  def execute_modification(query, *binds)
199
- self.last_statement = sth = new_statement(query)
200
- rows = sth.execute_modification(*binds)
201
- sth.finish
202
- return rows
233
+ sth = prepare(query)
234
+ sth.execute_modification(*binds)
235
+ ensure
236
+ sth.finish rescue nil
203
237
  end
204
238
 
205
239
  #
@@ -237,6 +271,13 @@ class RDBI::Database
237
271
  ep.quote(total_hash) { |x| %Q{'#{(total_hash[x] || binds[x]).to_s.gsub(/'/, "''")}'} }
238
272
  end
239
273
  end
274
+
275
+ #
276
+ # Quote a single item using a consistent quoting method.
277
+ #
278
+ def quote(item)
279
+ "\'#{item.to_s}\'"
280
+ end
240
281
  end
241
282
 
242
283
  # vim: syntax=ruby ts=2 et sw=2 sts=2