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/README.rdoc
CHANGED
@@ -20,6 +20,24 @@ path down the rabbit hole:
|
|
20
20
|
* If you're interested in how schemas and types are dealt with, see
|
21
21
|
RDBI::Schema, RDBI::Column, and RDBI::Type.
|
22
22
|
|
23
|
+
== Need a Driver?
|
24
|
+
|
25
|
+
=== Databases:
|
26
|
+
|
27
|
+
==== Maintained by the RDBI project:
|
28
|
+
|
29
|
+
* rdbi-driver-mysql: http://github.com/RDBI/rdbi-driver-mysql
|
30
|
+
* rdbi-driver-postgresql: http://github.com/RDBI/rdbi-driver-postgresql
|
31
|
+
* rdbi-driver-sqlite3: http://github.com/RDBI/rdbi-driver-sqlite3
|
32
|
+
|
33
|
+
==== Maintained by third parties:
|
34
|
+
|
35
|
+
* rdbi-driver-odbc: https://github.com/semmons99/rdbi-driver-odbc by Shane Emmons
|
36
|
+
|
37
|
+
=== Results:
|
38
|
+
|
39
|
+
* rdbi-result-driver-json: http://github.com/RDBI/rdbi-result-driver-json
|
40
|
+
|
23
41
|
== Give me a code sample already!
|
24
42
|
|
25
43
|
# connect to an in-memory sqlite3 database:
|
@@ -51,7 +69,6 @@ compelling (or off-putting. We're pragmatists.):
|
|
51
69
|
* RDBI is, at the time of this writing, fewer than 1000 lines of code.
|
52
70
|
* RDBI is light and fast. Eventually we will show you benchmarks.
|
53
71
|
* RDBI can be tested without a database, or even a database driver.
|
54
|
-
* RDBI is almost 100% thread-safe.
|
55
72
|
* RDBI can transform your results through a driver system. Want a CSV? Use the
|
56
73
|
CSV driver, don't bother transforming it yourself. Transform to JSON or YAML
|
57
74
|
with another gem. Drivers can be independently installed, used and swapped.
|
data/Rakefile
CHANGED
@@ -106,4 +106,42 @@ task :docview => [:rerdoc] do
|
|
106
106
|
sh "open rdoc/index.html"
|
107
107
|
end
|
108
108
|
|
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
|
145
|
+
end
|
146
|
+
|
109
147
|
# vim: syntax=ruby ts=2 et sw=2 sts=2
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.1
|
data/lib/rdbi.rb
CHANGED
@@ -1,20 +1,11 @@
|
|
1
1
|
require 'epoxy'
|
2
|
-
require 'methlab'
|
3
|
-
require 'thread'
|
4
2
|
|
5
3
|
module RDBI
|
6
4
|
class << self
|
7
|
-
extend MethLab
|
8
|
-
|
9
|
-
# Every database handle allocated throughout the lifetime of the
|
10
|
-
# program. This functionality is subject to change and may be pruned
|
11
|
-
# during disconnection.
|
12
|
-
attr_reader :all_connections
|
13
|
-
|
14
5
|
#
|
15
6
|
# The last database handle allocated. This may come from pooled connections or regular ones.
|
16
7
|
#
|
17
|
-
|
8
|
+
attr_accessor :last_dbh
|
18
9
|
end
|
19
10
|
|
20
11
|
#
|
@@ -38,23 +29,20 @@ module RDBI
|
|
38
29
|
driver = klass.new(*args)
|
39
30
|
dbh = self.last_dbh = driver.new_handle
|
40
31
|
|
41
|
-
@all_connections ||= []
|
42
|
-
@all_connections.push(dbh)
|
43
|
-
|
44
32
|
yield dbh if block_given?
|
45
33
|
return dbh
|
46
34
|
end
|
47
35
|
|
48
36
|
#
|
49
37
|
# connect_cached() works similarly to connect, but yields a database handle
|
50
|
-
# copied from
|
51
|
-
# may be manipulated by
|
38
|
+
# copied from an RDBI::Pool. The 'default' pool is the ... default, but this
|
39
|
+
# may be manipulated by setting :pool_name in the connection arguments.
|
52
40
|
#
|
53
41
|
# If a pool does not exist already, it will be created and a database
|
54
|
-
# handle
|
42
|
+
# handle instantiated using your connection arguments.
|
55
43
|
#
|
56
44
|
# If a pool *already* exists, your connection arguments will be ignored and
|
57
|
-
# it will
|
45
|
+
# it will instantiate from the Pool's connection arguments.
|
58
46
|
def self.connect_cached(klass, *args)
|
59
47
|
args = args[0]
|
60
48
|
pool_name = args[:pool_name] || :default
|
@@ -74,7 +62,7 @@ module RDBI
|
|
74
62
|
end
|
75
63
|
|
76
64
|
#
|
77
|
-
# Retrieves
|
65
|
+
# Retrieves an RDBI::Pool. See RDBI::Pool.[].
|
78
66
|
def self.pool(pool_name=:default)
|
79
67
|
RDBI::Pool[pool_name]
|
80
68
|
end
|
@@ -85,18 +73,6 @@ module RDBI
|
|
85
73
|
connect(klass, *args).ping
|
86
74
|
end
|
87
75
|
|
88
|
-
#
|
89
|
-
# Reconnects all known connections. See RDBI.all_connections.
|
90
|
-
def self.reconnect_all
|
91
|
-
@all_connections.each(&:reconnect)
|
92
|
-
end
|
93
|
-
|
94
|
-
#
|
95
|
-
# Disconnects all known connections. See RDBI.all_connections.
|
96
|
-
def self.disconnect_all
|
97
|
-
@all_connections.each(&:disconnect)
|
98
|
-
end
|
99
|
-
|
100
76
|
#
|
101
77
|
# Base Error class for RDBI. Rescue this to catch all RDBI-specific errors.
|
102
78
|
#
|
@@ -169,14 +145,30 @@ module RDBI::Util
|
|
169
145
|
case row_count
|
170
146
|
when :first, :last
|
171
147
|
ary = ary[0]
|
172
|
-
return nil if ary.empty?
|
148
|
+
return nil if ary and ary.empty?
|
173
149
|
return ary
|
174
150
|
else
|
175
151
|
return ary
|
176
152
|
end
|
177
153
|
end
|
154
|
+
|
155
|
+
def self.index_binds(args, index_map)
|
156
|
+
# FIXME exception if mixed hash/indexed binds
|
157
|
+
if args[0].kind_of?(Hash)
|
158
|
+
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
|
165
|
+
else
|
166
|
+
return args
|
167
|
+
end
|
168
|
+
end
|
178
169
|
end
|
179
170
|
|
171
|
+
require 'rdbi/types'
|
180
172
|
require 'rdbi/pool'
|
181
173
|
require 'rdbi/driver'
|
182
174
|
require 'rdbi/database'
|
@@ -184,6 +176,5 @@ require 'rdbi/statement'
|
|
184
176
|
require 'rdbi/schema'
|
185
177
|
require 'rdbi/result'
|
186
178
|
require 'rdbi/cursor'
|
187
|
-
require 'rdbi/types'
|
188
179
|
|
189
180
|
# vim: syntax=ruby ts=2 et sw=2 sts=2
|
data/lib/rdbi/cursor.rb
CHANGED
@@ -17,12 +17,17 @@ class RDBI::Cursor
|
|
17
17
|
#
|
18
18
|
class NotImplementedError < Exception; end
|
19
19
|
|
20
|
-
|
20
|
+
#
|
21
|
+
# Exception which indicates that this result is not rewindable.
|
22
|
+
#
|
23
|
+
class NotRewindableError < Exception; end
|
24
|
+
|
21
25
|
include Enumerable
|
22
26
|
|
23
27
|
# underlying handle.
|
24
28
|
|
25
29
|
attr_reader :handle
|
30
|
+
attr_accessor :rewindable_result
|
26
31
|
|
27
32
|
# Default constructor. Feel free to override this.
|
28
33
|
#
|
data/lib/rdbi/database.rb
CHANGED
@@ -1,85 +1,70 @@
|
|
1
1
|
#
|
2
|
-
# RDBI::Database is the base class for database handles.
|
3
|
-
#
|
2
|
+
# RDBI::Database is the base class for database handles. Most users will
|
3
|
+
# access their database system through this class.
|
4
4
|
#
|
5
5
|
# To execute statements, look at +prepare+ and +execute+.
|
6
6
|
#
|
7
7
|
# To retrieve schema information, look at +schema+ and +table_schema+.
|
8
8
|
#
|
9
|
-
# To deal with transactions, +transaction+, +commit+, and +rollback+.
|
9
|
+
# To deal with transactions, refer to +transaction+, +commit+, and +rollback+.
|
10
10
|
class RDBI::Database
|
11
|
-
extend MethLab
|
12
|
-
|
13
11
|
# the driver class that is responsible for creating this database handle.
|
14
12
|
attr_accessor :driver
|
15
13
|
|
16
14
|
# the name of the database we're connected to, if any.
|
17
15
|
attr_accessor :database_name
|
18
16
|
|
17
|
+
# see RDBI::Statement#rewindable_result
|
18
|
+
attr_accessor :rewindable_result
|
19
|
+
|
19
20
|
# the arguments used to create the connection.
|
20
21
|
attr_reader :connect_args
|
21
22
|
|
22
|
-
##
|
23
|
-
# :attr_reader: last_statement
|
24
|
-
#
|
25
23
|
# the last statement handle allocated. affected by +prepare+ and +execute+.
|
26
|
-
|
27
|
-
|
28
|
-
##
|
29
|
-
# :attr: last_query
|
24
|
+
attr_accessor :last_statement
|
25
|
+
|
30
26
|
# the last query sent, as a string.
|
31
|
-
|
27
|
+
attr_accessor :last_query
|
32
28
|
|
33
|
-
##
|
34
|
-
# :attr: open_statements
|
35
29
|
# all the open statement handles.
|
36
|
-
|
30
|
+
attr_accessor :open_statements
|
37
31
|
|
38
|
-
##
|
39
|
-
# :attr: in_transaction
|
40
|
-
# are we currently in a transaction?
|
41
|
-
|
42
|
-
##
|
43
|
-
# :attr: in_transaction?
|
44
32
|
# are we currently in a transaction?
|
45
|
-
|
33
|
+
def in_transaction
|
34
|
+
@in_transaction > 0
|
35
|
+
end
|
46
36
|
|
47
|
-
|
48
|
-
attr_reader :mutex
|
37
|
+
alias in_transaction? in_transaction
|
49
38
|
|
50
|
-
##
|
51
|
-
# :attr: connected
|
52
|
-
# are we connected to the database?
|
53
|
-
|
54
|
-
##
|
55
|
-
# :attr_accessor: connected?
|
56
39
|
# are we connected to the database?
|
57
|
-
inline(:connected, :connected?) { @connected }
|
58
40
|
|
59
|
-
|
60
|
-
|
41
|
+
attr_reader :connected
|
42
|
+
alias connected? connected
|
43
|
+
|
61
44
|
# ping the database. yield an integer result on success.
|
62
|
-
|
45
|
+
def ping
|
46
|
+
raise NoMethodError, "this method is not implemented in this driver"
|
47
|
+
end
|
63
48
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
49
|
+
# query the schema for a specific table. Returns an RDBI::Schema object.
|
50
|
+
def table_schema(table_name)
|
51
|
+
raise NoMethodError, "this method is not implemented in this driver"
|
52
|
+
end
|
68
53
|
|
69
|
-
##
|
70
|
-
# :method: schema
|
71
54
|
# query the schema for the entire database. Returns an array of RDBI::Schema objects.
|
72
|
-
|
55
|
+
def schema
|
56
|
+
raise NoMethodError, "this method is not implemented in this driver"
|
57
|
+
end
|
73
58
|
|
74
|
-
##
|
75
|
-
# :method: rollback
|
76
59
|
# ends the outstanding transaction and rolls the affected rows back.
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
60
|
+
def rollback
|
61
|
+
@in_transaction -= 1 unless @in_transaction == 0
|
62
|
+
end
|
63
|
+
|
81
64
|
# ends the outstanding transaction and commits the result.
|
82
|
-
|
65
|
+
def commit
|
66
|
+
@in_transaction -= 1 unless @in_transaction == 0
|
67
|
+
end
|
83
68
|
|
84
69
|
#
|
85
70
|
# Create a new database handle. This is typically done by a driver and
|
@@ -88,11 +73,11 @@ class RDBI::Database
|
|
88
73
|
# args is the connection arguments the user initially supplied to
|
89
74
|
# RDBI.connect.
|
90
75
|
def initialize(*args)
|
91
|
-
@connect_args
|
92
|
-
@connected
|
93
|
-
@
|
94
|
-
@
|
95
|
-
self.open_statements
|
76
|
+
@connect_args = RDBI::Util.key_hash_as_symbols(args[0])
|
77
|
+
@connected = true
|
78
|
+
@in_transaction = 0
|
79
|
+
@rewindable_result = false
|
80
|
+
self.open_statements = { }
|
96
81
|
end
|
97
82
|
|
98
83
|
# reconnect to the database. Any outstanding connection will be terminated.
|
@@ -106,12 +91,9 @@ class RDBI::Database
|
|
106
91
|
# statement handles left open.
|
107
92
|
#
|
108
93
|
def disconnect
|
109
|
-
unless self.open_statements.empty?
|
110
|
-
warn "[RDBI] Open statements during disconnection -- automatically finishing. You should fix this."
|
111
|
-
self.open_statements.each(&:finish)
|
112
|
-
end
|
113
|
-
self.open_statements = []
|
114
94
|
@connected = false
|
95
|
+
self.open_statements.values.each { |x| x.finish if x }
|
96
|
+
self.open_statements = { }
|
115
97
|
end
|
116
98
|
|
117
99
|
#
|
@@ -157,7 +139,7 @@ class RDBI::Database
|
|
157
139
|
|
158
140
|
#
|
159
141
|
# Prepares a statement for execution. Takes a query as its only argument,
|
160
|
-
# returns
|
142
|
+
# returns an RDBI::Statement.
|
161
143
|
#
|
162
144
|
# ex:
|
163
145
|
# sth = dbh.prepare("select * from foo where item = ?")
|
@@ -172,12 +154,11 @@ class RDBI::Database
|
|
172
154
|
#
|
173
155
|
def prepare(query)
|
174
156
|
sth = nil
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
end
|
157
|
+
|
158
|
+
self.last_query = query
|
159
|
+
sth = new_statement(query)
|
160
|
+
yield sth if block_given?
|
161
|
+
sth.finish if block_given?
|
181
162
|
|
182
163
|
return self.last_statement = sth
|
183
164
|
end
|
@@ -203,23 +184,24 @@ class RDBI::Database
|
|
203
184
|
def execute(query, *binds)
|
204
185
|
res = nil
|
205
186
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
res = sth.execute(*binds)
|
210
|
-
|
211
|
-
if block_given?
|
212
|
-
yield res
|
213
|
-
else
|
214
|
-
res.coerce_to_array
|
215
|
-
end
|
187
|
+
self.last_query = query
|
188
|
+
self.last_statement = sth = new_statement(query)
|
189
|
+
res = sth.execute(*binds)
|
216
190
|
|
217
|
-
|
191
|
+
if block_given?
|
192
|
+
yield res
|
218
193
|
end
|
219
194
|
|
220
195
|
return res
|
221
196
|
end
|
222
197
|
|
198
|
+
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
|
203
|
+
end
|
204
|
+
|
223
205
|
#
|
224
206
|
# Process the query as your driver would normally, and return the result.
|
225
207
|
# Depending on the driver implementation and potentially connection
|
@@ -241,14 +223,12 @@ class RDBI::Database
|
|
241
223
|
# quoter during bind processing.
|
242
224
|
#
|
243
225
|
def preprocess_query(query, *binds)
|
244
|
-
|
245
|
-
|
246
|
-
end
|
247
|
-
|
226
|
+
self.last_query = query
|
227
|
+
|
248
228
|
ep = Epoxy.new(query)
|
249
229
|
|
250
230
|
hashes = binds.select { |x| x.kind_of?(Hash) }
|
251
|
-
binds.collect! { |x| x.kind_of?(Hash) ? nil : x }
|
231
|
+
binds.collect! { |x| x.kind_of?(Hash) ? nil : x }
|
252
232
|
total_hash = hashes.inject({}) { |x, y| x.merge(y) }
|
253
233
|
|
254
234
|
if @preprocess_quoter.respond_to?(:call)
|
data/lib/rdbi/pool.rb
CHANGED
@@ -109,6 +109,20 @@ class RDBI::Pool
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
+
#
|
113
|
+
# Asserts that all the pooled handles are connected. Returns true or false
|
114
|
+
# depending on the result of that assertion.
|
115
|
+
#
|
116
|
+
def up
|
117
|
+
alive = false
|
118
|
+
|
119
|
+
mutex.synchronize do
|
120
|
+
alive = @handles.select { |x| x.ping.nil? }.empty?
|
121
|
+
end
|
122
|
+
|
123
|
+
return alive
|
124
|
+
end
|
125
|
+
|
112
126
|
#
|
113
127
|
# Unconditionally reconnect all database handles.
|
114
128
|
def reconnect
|
@@ -216,9 +230,6 @@ class RDBI::Pool
|
|
216
230
|
# Add any ol' database handle. This is not for global consumption.
|
217
231
|
#
|
218
232
|
def add(dbh)
|
219
|
-
dbh = *MethLab.validate_array_params([RDBI::Database], [dbh])
|
220
|
-
raise dbh if dbh.kind_of?(Exception)
|
221
|
-
|
222
233
|
dbh = dbh[0] if dbh.kind_of?(Array)
|
223
234
|
|
224
235
|
mutex.synchronize do
|