rdbi 0.9.1 → 1.1.0

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