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.
@@ -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