rdbi 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.0
1
+ 0.9.1
@@ -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
- attr_threaded_accessor :last_dbh
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 a RDBI::Pool. The 'default' pool is the ... default, but this
51
- # may be manipulated by providing :pool_name to the connection arguments.
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 instanced from your connection arguments.
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 instance from the Pool's connection arguments.
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 a RDBI::Pool. See RDBI::Pool.[].
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
@@ -17,12 +17,17 @@ class RDBI::Cursor
17
17
  #
18
18
  class NotImplementedError < Exception; end
19
19
 
20
- extend MethLab
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
  #
@@ -1,85 +1,70 @@
1
1
  #
2
- # RDBI::Database is the base class for database handles. This is the primary
3
- # method in which most users will access their database system.
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
- attr_threaded_accessor :last_statement
27
-
28
- ##
29
- # :attr: last_query
24
+ attr_accessor :last_statement
25
+
30
26
  # the last query sent, as a string.
31
- attr_threaded_accessor :last_query
27
+ attr_accessor :last_query
32
28
 
33
- ##
34
- # :attr: open_statements
35
29
  # all the open statement handles.
36
- attr_threaded_accessor :open_statements
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
- inline(:in_transaction, :in_transaction?) { @in_transaction > 0 }
33
+ def in_transaction
34
+ @in_transaction > 0
35
+ end
46
36
 
47
- # the mutex for this database handle.
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
- # :method: ping
41
+ attr_reader :connected
42
+ alias connected? connected
43
+
61
44
  # ping the database. yield an integer result on success.
62
- inline(:ping) { raise NoMethodError, "this method is not implemented in this driver" }
45
+ def ping
46
+ raise NoMethodError, "this method is not implemented in this driver"
47
+ end
63
48
 
64
- ##
65
- # :method: table_schema
66
- # query the schema for a specific table. Returns a RDBI::Schema object.
67
- inline(:table_schema) { |*args| raise NoMethodError, "this method is not implemented in this driver" }
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
- inline(:schema) { |*args| raise NoMethodError, "this method is not implemented in this driver" }
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
- inline(:rollback) { @in_transaction -= 1 unless @in_transaction == 0 }
78
-
79
- ##
80
- # :method: commit
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
- inline(:commit) { @in_transaction -= 1 unless @in_transaction == 0 }
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 = RDBI::Util.key_hash_as_symbols(args[0])
92
- @connected = true
93
- @mutex = Mutex.new
94
- @in_transaction = 0
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 a RDBI::Statement.
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
- mutex.synchronize do
176
- self.last_query = query
177
- sth = new_statement(query)
178
- yield sth if block_given?
179
- sth.finish if block_given?
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
- mutex.synchronize do
207
- self.last_query = query
208
- self.last_statement = sth = new_statement(query)
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
- sth.finish
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
- mutex.synchronize do
245
- self.last_query = query
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)
@@ -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