activerecord 3.1.11 → 3.2.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.
- data/CHANGELOG.md +6294 -97
- data/README.rdoc +2 -2
- data/examples/performance.rb +55 -31
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations/association.rb +2 -42
- data/lib/active_record/associations/association_scope.rb +3 -30
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +55 -28
- data/lib/active_record/associations/collection_proxy.rb +1 -35
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +11 -8
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +3 -1
- data/lib/active_record/associations.rb +82 -69
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +72 -83
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
- data/lib/active_record/attribute_methods/write.rb +27 -5
- data/lib/active_record/attribute_methods.rb +209 -30
- data/lib/active_record/autosave_association.rb +23 -8
- data/lib/active_record/base.rb +217 -1709
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +9 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +43 -22
- data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +2 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
- data/lib/active_record/counter_cache.rb +4 -3
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +83 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +31 -76
- data/lib/active_record/identity_map.rb +4 -11
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +30 -25
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration/command_recorder.rb +8 -8
- data/lib/active_record/migration.rb +47 -30
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +51 -9
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +24 -28
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +134 -77
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +27 -6
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +6 -5
- data/lib/active_record/relation/predicate_builder.rb +12 -19
- data/lib/active_record/relation/query_methods.rb +76 -10
- data/lib/active_record/relation/spawn_methods.rb +11 -2
- data/lib/active_record/relation.rb +77 -34
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +5 -2
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +2 -44
- data/lib/active_record/session_store.rb +15 -15
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +16 -3
- data/lib/active_record/transactions.rb +5 -5
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record.rb +28 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +9 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +5 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +50 -40
- checksums.yaml +0 -7
- data/lib/active_record/named_scope.rb +0 -200
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'thread'
|
|
2
2
|
require 'monitor'
|
|
3
3
|
require 'set'
|
|
4
|
-
require 'active_support/core_ext/module/
|
|
4
|
+
require 'active_support/core_ext/module/deprecation'
|
|
5
5
|
|
|
6
6
|
module ActiveRecord
|
|
7
7
|
# Raised when a connection could not be obtained within the connection
|
|
@@ -57,10 +57,10 @@ module ActiveRecord
|
|
|
57
57
|
# * +wait_timeout+: number of seconds to block and wait for a connection
|
|
58
58
|
# before giving up and raising a timeout error (default 5 seconds).
|
|
59
59
|
class ConnectionPool
|
|
60
|
+
include MonitorMixin
|
|
61
|
+
|
|
60
62
|
attr_accessor :automatic_reconnect
|
|
61
63
|
attr_reader :spec, :connections
|
|
62
|
-
attr_reader :columns, :columns_hash, :primary_keys, :tables
|
|
63
|
-
attr_reader :column_defaults
|
|
64
64
|
|
|
65
65
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
|
66
66
|
# object which describes database connection information (e.g. adapter,
|
|
@@ -69,88 +69,21 @@ module ActiveRecord
|
|
|
69
69
|
#
|
|
70
70
|
# The default ConnectionPool maximum size is 5.
|
|
71
71
|
def initialize(spec)
|
|
72
|
+
super()
|
|
73
|
+
|
|
72
74
|
@spec = spec
|
|
73
75
|
|
|
74
76
|
# The cache of reserved connections mapped to threads
|
|
75
77
|
@reserved_connections = {}
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
@connection_mutex = Monitor.new
|
|
79
|
-
@queue = @connection_mutex.new_cond
|
|
79
|
+
@queue = new_cond
|
|
80
80
|
@timeout = spec.config[:wait_timeout] || 5
|
|
81
81
|
|
|
82
82
|
# default max pool size to 5
|
|
83
83
|
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
|
84
84
|
|
|
85
85
|
@connections = []
|
|
86
|
-
@checked_out = []
|
|
87
86
|
@automatic_reconnect = true
|
|
88
|
-
@tables = {}
|
|
89
|
-
@visitor = nil
|
|
90
|
-
|
|
91
|
-
@columns = Hash.new do |h, table_name|
|
|
92
|
-
h[table_name] = with_connection do |conn|
|
|
93
|
-
|
|
94
|
-
# Fetch a list of columns
|
|
95
|
-
conn.columns(table_name, "#{table_name} Columns").tap do |columns|
|
|
96
|
-
|
|
97
|
-
# set primary key information
|
|
98
|
-
columns.each do |column|
|
|
99
|
-
column.primary = column.name == primary_keys[table_name]
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
@columns_hash = Hash.new do |h, table_name|
|
|
106
|
-
h[table_name] = Hash[columns[table_name].map { |col|
|
|
107
|
-
[col.name, col]
|
|
108
|
-
}]
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
@column_defaults = Hash.new do |h, table_name|
|
|
112
|
-
h[table_name] = Hash[columns[table_name].map { |col|
|
|
113
|
-
[col.name, col.default]
|
|
114
|
-
}]
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
@primary_keys = Hash.new do |h, table_name|
|
|
118
|
-
h[table_name] = with_connection do |conn|
|
|
119
|
-
table_exists?(table_name) ? conn.primary_key(table_name) : 'id'
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# A cached lookup for table existence.
|
|
125
|
-
def table_exists?(name)
|
|
126
|
-
return true if @tables.key? name
|
|
127
|
-
|
|
128
|
-
with_connection do |conn|
|
|
129
|
-
conn.tables.each { |table| @tables[table] = true }
|
|
130
|
-
@tables[name] = true if !@tables.key?(name) && conn.table_exists?(name)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
@tables.key? name
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Clears out internal caches:
|
|
137
|
-
#
|
|
138
|
-
# * columns
|
|
139
|
-
# * columns_hash
|
|
140
|
-
# * tables
|
|
141
|
-
def clear_cache!
|
|
142
|
-
@columns.clear
|
|
143
|
-
@columns_hash.clear
|
|
144
|
-
@column_defaults.clear
|
|
145
|
-
@tables.clear
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Clear out internal caches for table with +table_name+.
|
|
149
|
-
def clear_table_cache!(table_name)
|
|
150
|
-
@columns.delete table_name
|
|
151
|
-
@columns_hash.delete table_name
|
|
152
|
-
@column_defaults.delete table_name
|
|
153
|
-
@primary_keys.delete table_name
|
|
154
87
|
end
|
|
155
88
|
|
|
156
89
|
# Retrieve the connection associated with the current thread, or call
|
|
@@ -165,7 +98,7 @@ module ActiveRecord
|
|
|
165
98
|
# Check to see if there is an active connection in this connection
|
|
166
99
|
# pool.
|
|
167
100
|
def active_connection?
|
|
168
|
-
|
|
101
|
+
active_connections.any?
|
|
169
102
|
end
|
|
170
103
|
|
|
171
104
|
# Signal that the thread is finished with the current connection.
|
|
@@ -176,12 +109,12 @@ module ActiveRecord
|
|
|
176
109
|
checkin conn if conn
|
|
177
110
|
end
|
|
178
111
|
|
|
179
|
-
# If a connection already exists yield it to the block.
|
|
112
|
+
# If a connection already exists yield it to the block. If no connection
|
|
180
113
|
# exists checkout a connection, yield it to the block, and checkin the
|
|
181
114
|
# connection when finished.
|
|
182
115
|
def with_connection
|
|
183
116
|
connection_id = current_connection_id
|
|
184
|
-
fresh_connection = true unless
|
|
117
|
+
fresh_connection = true unless active_connection?
|
|
185
118
|
yield connection
|
|
186
119
|
ensure
|
|
187
120
|
release_connection(connection_id) if fresh_connection
|
|
@@ -189,43 +122,73 @@ module ActiveRecord
|
|
|
189
122
|
|
|
190
123
|
# Returns true if a connection has already been opened.
|
|
191
124
|
def connected?
|
|
192
|
-
|
|
125
|
+
synchronize { @connections.any? }
|
|
193
126
|
end
|
|
194
127
|
|
|
195
128
|
# Disconnects all connections in the pool, and clears the pool.
|
|
196
129
|
def disconnect!
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
130
|
+
synchronize do
|
|
131
|
+
@reserved_connections = {}
|
|
132
|
+
@connections.each do |conn|
|
|
133
|
+
checkin conn
|
|
134
|
+
conn.disconnect!
|
|
135
|
+
end
|
|
136
|
+
@connections = []
|
|
203
137
|
end
|
|
204
|
-
@connections = []
|
|
205
138
|
end
|
|
206
139
|
|
|
207
140
|
# Clears the cache which maps classes.
|
|
208
141
|
def clear_reloadable_connections!
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
142
|
+
synchronize do
|
|
143
|
+
@reserved_connections = {}
|
|
144
|
+
@connections.each do |conn|
|
|
145
|
+
checkin conn
|
|
146
|
+
conn.disconnect! if conn.requires_reloading?
|
|
147
|
+
end
|
|
148
|
+
@connections.delete_if do |conn|
|
|
149
|
+
conn.requires_reloading?
|
|
150
|
+
end
|
|
218
151
|
end
|
|
219
152
|
end
|
|
220
153
|
|
|
221
154
|
# Verify active connections and remove and disconnect connections
|
|
222
155
|
# associated with stale threads.
|
|
223
156
|
def verify_active_connections! #:nodoc:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
connection
|
|
157
|
+
synchronize do
|
|
158
|
+
clear_stale_cached_connections!
|
|
159
|
+
@connections.each do |connection|
|
|
160
|
+
connection.verify!
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def columns
|
|
166
|
+
with_connection do |c|
|
|
167
|
+
c.schema_cache.columns
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
deprecate :columns
|
|
171
|
+
|
|
172
|
+
def columns_hash
|
|
173
|
+
with_connection do |c|
|
|
174
|
+
c.schema_cache.columns_hash
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
deprecate :columns_hash
|
|
178
|
+
|
|
179
|
+
def primary_keys
|
|
180
|
+
with_connection do |c|
|
|
181
|
+
c.schema_cache.primary_keys
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
deprecate :primary_keys
|
|
185
|
+
|
|
186
|
+
def clear_cache!
|
|
187
|
+
with_connection do |c|
|
|
188
|
+
c.schema_cache.clear!
|
|
227
189
|
end
|
|
228
190
|
end
|
|
191
|
+
deprecate :clear_cache!
|
|
229
192
|
|
|
230
193
|
# Return any checked-out connections back to the pool by threads that
|
|
231
194
|
# are no longer alive.
|
|
@@ -234,7 +197,13 @@ module ActiveRecord
|
|
|
234
197
|
t.alive?
|
|
235
198
|
}.map { |thread| thread.object_id }
|
|
236
199
|
keys.each do |key|
|
|
237
|
-
|
|
200
|
+
conn = @reserved_connections[key]
|
|
201
|
+
ActiveSupport::Deprecation.warn(<<-eowarn) if conn.in_use?
|
|
202
|
+
Database connections will not be closed automatically, please close your
|
|
203
|
+
database connection at the end of the thread by calling `close` on your
|
|
204
|
+
connection. For example: ActiveRecord::Base.connection.close
|
|
205
|
+
eowarn
|
|
206
|
+
checkin conn
|
|
238
207
|
@reserved_connections.delete(key)
|
|
239
208
|
end
|
|
240
209
|
end
|
|
@@ -257,23 +226,30 @@ module ActiveRecord
|
|
|
257
226
|
# within the timeout period.
|
|
258
227
|
def checkout
|
|
259
228
|
# Checkout an available connection
|
|
260
|
-
|
|
229
|
+
synchronize do
|
|
261
230
|
loop do
|
|
262
|
-
conn =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
231
|
+
conn = @connections.find { |c| c.lease }
|
|
232
|
+
|
|
233
|
+
unless conn
|
|
234
|
+
if @connections.size < @size
|
|
235
|
+
conn = checkout_new_connection
|
|
236
|
+
conn.lease
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
if conn
|
|
241
|
+
checkout_and_verify conn
|
|
242
|
+
return conn
|
|
243
|
+
end
|
|
268
244
|
|
|
269
245
|
@queue.wait(@timeout)
|
|
270
246
|
|
|
271
|
-
if(
|
|
247
|
+
if(active_connections.size < @connections.size)
|
|
272
248
|
next
|
|
273
249
|
else
|
|
274
250
|
clear_stale_cached_connections!
|
|
275
|
-
if @size ==
|
|
276
|
-
raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}.
|
|
251
|
+
if @size == active_connections.size
|
|
252
|
+
raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it."
|
|
277
253
|
end
|
|
278
254
|
end
|
|
279
255
|
|
|
@@ -287,30 +263,18 @@ module ActiveRecord
|
|
|
287
263
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
|
288
264
|
# calling +checkout+ on this pool.
|
|
289
265
|
def checkin(conn)
|
|
290
|
-
|
|
266
|
+
synchronize do
|
|
291
267
|
conn.run_callbacks :checkin do
|
|
292
|
-
|
|
268
|
+
conn.expire
|
|
293
269
|
@queue.signal
|
|
294
270
|
end
|
|
295
271
|
end
|
|
296
272
|
end
|
|
297
273
|
|
|
298
|
-
synchronize :clear_reloadable_connections!, :verify_active_connections!,
|
|
299
|
-
:connected?, :disconnect!, :with => :@connection_mutex
|
|
300
|
-
|
|
301
274
|
private
|
|
302
275
|
|
|
303
276
|
def new_connection
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
# TODO: This is a bit icky, and in the long term we may want to change the method
|
|
307
|
-
# signature for connections. Also, if we switch to have one visitor per
|
|
308
|
-
# connection (and therefore per thread), we can get rid of the thread-local
|
|
309
|
-
# variable in Arel::Visitors::ToSql.
|
|
310
|
-
@visitor ||= connection.class.visitor_for(self)
|
|
311
|
-
connection.visitor = @visitor
|
|
312
|
-
|
|
313
|
-
connection
|
|
277
|
+
ActiveRecord::Base.send(spec.adapter_method, spec.config)
|
|
314
278
|
end
|
|
315
279
|
|
|
316
280
|
def current_connection_id #:nodoc:
|
|
@@ -321,22 +285,21 @@ module ActiveRecord
|
|
|
321
285
|
raise ConnectionNotEstablished unless @automatic_reconnect
|
|
322
286
|
|
|
323
287
|
c = new_connection
|
|
288
|
+
c.pool = self
|
|
324
289
|
@connections << c
|
|
325
|
-
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def checkout_existing_connection
|
|
329
|
-
c = (@connections - @checked_out).first
|
|
330
|
-
checkout_and_verify(c)
|
|
290
|
+
c
|
|
331
291
|
end
|
|
332
292
|
|
|
333
293
|
def checkout_and_verify(c)
|
|
334
294
|
c.run_callbacks :checkout do
|
|
335
295
|
c.verify!
|
|
336
|
-
@checked_out << c
|
|
337
296
|
end
|
|
338
297
|
c
|
|
339
298
|
end
|
|
299
|
+
|
|
300
|
+
def active_connections
|
|
301
|
+
@connections.find_all { |c| c.in_use? }
|
|
302
|
+
end
|
|
340
303
|
end
|
|
341
304
|
|
|
342
305
|
# ConnectionHandler is a collection of ConnectionPool objects. It is used
|
|
@@ -367,10 +330,12 @@ module ActiveRecord
|
|
|
367
330
|
|
|
368
331
|
def initialize(pools = {})
|
|
369
332
|
@connection_pools = pools
|
|
333
|
+
@class_to_pool = {}
|
|
370
334
|
end
|
|
371
335
|
|
|
372
336
|
def establish_connection(name, spec)
|
|
373
|
-
@connection_pools[
|
|
337
|
+
@connection_pools[spec] ||= ConnectionAdapters::ConnectionPool.new(spec)
|
|
338
|
+
@class_to_pool[name] = @connection_pools[spec]
|
|
374
339
|
end
|
|
375
340
|
|
|
376
341
|
# Returns true if there are any active connections among the connection
|
|
@@ -421,16 +386,17 @@ module ActiveRecord
|
|
|
421
386
|
# can be used as an argument for establish_connection, for easily
|
|
422
387
|
# re-establishing the connection.
|
|
423
388
|
def remove_connection(klass)
|
|
424
|
-
pool = @
|
|
389
|
+
pool = @class_to_pool.delete(klass.name)
|
|
425
390
|
return nil unless pool
|
|
426
391
|
|
|
392
|
+
@connection_pools.delete pool.spec
|
|
427
393
|
pool.automatic_reconnect = false
|
|
428
394
|
pool.disconnect!
|
|
429
395
|
pool.spec.config
|
|
430
396
|
end
|
|
431
397
|
|
|
432
398
|
def retrieve_connection_pool(klass)
|
|
433
|
-
pool = @
|
|
399
|
+
pool = @class_to_pool[klass.name]
|
|
434
400
|
return pool if pool
|
|
435
401
|
return nil if ActiveRecord::Base == klass
|
|
436
402
|
retrieve_connection_pool klass.superclass
|
|
@@ -5,6 +5,74 @@ module ActiveRecord
|
|
|
5
5
|
def initialize (config, adapter_method)
|
|
6
6
|
@config, @adapter_method = config, adapter_method
|
|
7
7
|
end
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Builds a ConnectionSpecification from user input
|
|
11
|
+
class Resolver # :nodoc:
|
|
12
|
+
attr_reader :config, :klass, :configurations
|
|
13
|
+
|
|
14
|
+
def initialize(config, configurations)
|
|
15
|
+
@config = config
|
|
16
|
+
@configurations = configurations
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def spec
|
|
20
|
+
case config
|
|
21
|
+
when nil
|
|
22
|
+
raise AdapterNotSpecified unless defined?(Rails.env)
|
|
23
|
+
resolve_string_connection Rails.env
|
|
24
|
+
when Symbol, String
|
|
25
|
+
resolve_string_connection config.to_s
|
|
26
|
+
when Hash
|
|
27
|
+
resolve_hash_connection config
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def resolve_string_connection(spec) # :nodoc:
|
|
33
|
+
hash = configurations.fetch(spec) do |k|
|
|
34
|
+
connection_url_to_hash(k)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
|
|
38
|
+
|
|
39
|
+
resolve_hash_connection hash
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def resolve_hash_connection(spec) # :nodoc:
|
|
43
|
+
spec = spec.symbolize_keys
|
|
44
|
+
|
|
45
|
+
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
|
49
|
+
rescue LoadError => e
|
|
50
|
+
raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
adapter_method = "#{spec[:adapter]}_connection"
|
|
54
|
+
|
|
55
|
+
ConnectionSpecification.new(spec, adapter_method)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def connection_url_to_hash(url) # :nodoc:
|
|
59
|
+
config = URI.parse url
|
|
60
|
+
adapter = config.scheme
|
|
61
|
+
adapter = "postgresql" if adapter == "postgres"
|
|
62
|
+
spec = { :adapter => adapter,
|
|
63
|
+
:username => config.user,
|
|
64
|
+
:password => config.password,
|
|
65
|
+
:port => config.port,
|
|
66
|
+
:database => config.path.sub(%r{^/},""),
|
|
67
|
+
:host => config.host }
|
|
68
|
+
spec.reject!{ |_,value| !value }
|
|
69
|
+
if config.query
|
|
70
|
+
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
|
|
71
|
+
spec.merge!(options)
|
|
72
|
+
end
|
|
73
|
+
spec
|
|
74
|
+
end
|
|
75
|
+
end
|
|
8
76
|
end
|
|
9
77
|
|
|
10
78
|
##
|
|
@@ -46,39 +114,24 @@ module ActiveRecord
|
|
|
46
114
|
# "database" => "path/to/dbfile"
|
|
47
115
|
# )
|
|
48
116
|
#
|
|
117
|
+
# Or a URL:
|
|
118
|
+
#
|
|
119
|
+
# ActiveRecord::Base.establish_connection(
|
|
120
|
+
# "postgres://myuser:mypass@localhost/somedatabase"
|
|
121
|
+
# )
|
|
122
|
+
#
|
|
49
123
|
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
|
50
124
|
# may be returned on an error.
|
|
51
|
-
def self.establish_connection(spec =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
raise AdapterNotSpecified unless defined?(Rails.env)
|
|
55
|
-
establish_connection(Rails.env)
|
|
56
|
-
when ConnectionSpecification
|
|
57
|
-
self.connection_handler.establish_connection(name, spec)
|
|
58
|
-
when Symbol, String
|
|
59
|
-
if configuration = configurations[spec.to_s]
|
|
60
|
-
establish_connection(configuration)
|
|
61
|
-
else
|
|
62
|
-
raise AdapterNotSpecified, "#{spec} database is not configured"
|
|
63
|
-
end
|
|
64
|
-
else
|
|
65
|
-
spec = spec.symbolize_keys
|
|
66
|
-
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
|
125
|
+
def self.establish_connection(spec = ENV["DATABASE_URL"])
|
|
126
|
+
resolver = ConnectionSpecification::Resolver.new spec, configurations
|
|
127
|
+
spec = resolver.spec
|
|
67
128
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
rescue LoadError => e
|
|
71
|
-
raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e})"
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
adapter_method = "#{spec[:adapter]}_connection"
|
|
75
|
-
unless respond_to?(adapter_method)
|
|
76
|
-
raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
remove_connection
|
|
80
|
-
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
|
129
|
+
unless respond_to?(spec.adapter_method)
|
|
130
|
+
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
|
|
81
131
|
end
|
|
132
|
+
|
|
133
|
+
remove_connection
|
|
134
|
+
connection_handler.establish_connection name, spec
|
|
82
135
|
end
|
|
83
136
|
|
|
84
137
|
class << self
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
require 'active_support/core_ext/module/deprecation'
|
|
2
|
-
|
|
3
1
|
module ActiveRecord
|
|
4
2
|
module ConnectionAdapters # :nodoc:
|
|
5
3
|
module DatabaseStatements
|
|
6
4
|
# Converts an arel AST to SQL
|
|
7
|
-
def to_sql(arel
|
|
5
|
+
def to_sql(arel)
|
|
8
6
|
if arel.respond_to?(:ast)
|
|
9
|
-
visitor.accept(arel.ast)
|
|
10
|
-
quote(*binds.shift.reverse)
|
|
11
|
-
end
|
|
7
|
+
visitor.accept(arel.ast)
|
|
12
8
|
else
|
|
13
9
|
arel
|
|
14
10
|
end
|
|
@@ -17,7 +13,7 @@ module ActiveRecord
|
|
|
17
13
|
# Returns an array of record hashes with the column names as keys and
|
|
18
14
|
# column values as values.
|
|
19
15
|
def select_all(arel, name = nil, binds = [])
|
|
20
|
-
select(to_sql(arel
|
|
16
|
+
select(to_sql(arel), name, binds)
|
|
21
17
|
end
|
|
22
18
|
|
|
23
19
|
# Returns a record hash with the column names as keys and column values
|
|
@@ -37,7 +33,7 @@ module ActiveRecord
|
|
|
37
33
|
# Returns an array of the values of the first column in a select:
|
|
38
34
|
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
|
39
35
|
def select_values(arel, name = nil)
|
|
40
|
-
result = select_rows(to_sql(arel
|
|
36
|
+
result = select_rows(to_sql(arel), name)
|
|
41
37
|
result.map { |v| v[0] }
|
|
42
38
|
end
|
|
43
39
|
|
|
@@ -53,7 +49,7 @@ module ActiveRecord
|
|
|
53
49
|
undef_method :execute
|
|
54
50
|
|
|
55
51
|
# Executes +sql+ statement in the context of this connection using
|
|
56
|
-
# +binds+ as the bind substitutes.
|
|
52
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
|
57
53
|
# the executed +sql+ statement.
|
|
58
54
|
def exec_query(sql, name = 'SQL', binds = [])
|
|
59
55
|
end
|
|
@@ -88,19 +84,19 @@ module ActiveRecord
|
|
|
88
84
|
# If the next id was calculated in advance (as in Oracle), it should be
|
|
89
85
|
# passed in as +id_value+.
|
|
90
86
|
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
|
91
|
-
sql, binds = sql_for_insert(to_sql(arel
|
|
87
|
+
sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
|
|
92
88
|
value = exec_insert(sql, name, binds)
|
|
93
89
|
id_value || last_inserted_id(value)
|
|
94
90
|
end
|
|
95
91
|
|
|
96
92
|
# Executes the update statement and returns the number of rows affected.
|
|
97
93
|
def update(arel, name = nil, binds = [])
|
|
98
|
-
exec_update(to_sql(arel
|
|
94
|
+
exec_update(to_sql(arel), name, binds)
|
|
99
95
|
end
|
|
100
96
|
|
|
101
97
|
# Executes the delete statement and returns the number of rows affected.
|
|
102
98
|
def delete(arel, name = nil, binds = [])
|
|
103
|
-
exec_delete(to_sql(arel
|
|
99
|
+
exec_delete(to_sql(arel), name, binds)
|
|
104
100
|
end
|
|
105
101
|
|
|
106
102
|
# Checks whether there is currently no transaction active. This is done
|
|
@@ -256,38 +252,13 @@ module ActiveRecord
|
|
|
256
252
|
# done if the transaction block raises an exception or returns false.
|
|
257
253
|
def rollback_db_transaction() end
|
|
258
254
|
|
|
259
|
-
# Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL
|
|
260
|
-
# fragment that has the same semantics as LIMIT and OFFSET.
|
|
261
|
-
#
|
|
262
|
-
# +options+ must be a Hash which contains a +:limit+ option
|
|
263
|
-
# and an +:offset+ option.
|
|
264
|
-
#
|
|
265
|
-
# This method *modifies* the +sql+ parameter.
|
|
266
|
-
#
|
|
267
|
-
# This method is deprecated!! Stop using it!
|
|
268
|
-
#
|
|
269
|
-
# ===== Examples
|
|
270
|
-
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
|
271
|
-
# generates
|
|
272
|
-
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
|
|
273
|
-
def add_limit_offset!(sql, options)
|
|
274
|
-
if limit = options[:limit]
|
|
275
|
-
sql << " LIMIT #{sanitize_limit(limit)}"
|
|
276
|
-
end
|
|
277
|
-
if offset = options[:offset]
|
|
278
|
-
sql << " OFFSET #{offset.to_i}"
|
|
279
|
-
end
|
|
280
|
-
sql
|
|
281
|
-
end
|
|
282
|
-
deprecate :add_limit_offset!
|
|
283
|
-
|
|
284
255
|
def default_sequence_name(table, column)
|
|
285
256
|
nil
|
|
286
257
|
end
|
|
287
258
|
|
|
288
259
|
# Set the sequence to the max value of the table's column.
|
|
289
260
|
def reset_sequence!(table, column, sequence = nil)
|
|
290
|
-
# Do nothing by default.
|
|
261
|
+
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
|
|
291
262
|
end
|
|
292
263
|
|
|
293
264
|
# Inserts the given fixture into the table. Overridden in adapters that require
|
|
@@ -319,10 +290,10 @@ module ActiveRecord
|
|
|
319
290
|
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
|
|
320
291
|
#
|
|
321
292
|
# The +limit+ may be anything that can evaluate to a string via #to_s. It
|
|
322
|
-
# should look like an integer, or a comma-delimited list of integers, or
|
|
293
|
+
# should look like an integer, or a comma-delimited list of integers, or
|
|
323
294
|
# an Arel SQL literal.
|
|
324
295
|
#
|
|
325
|
-
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
|
|
296
|
+
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
|
|
326
297
|
# Returns the sanitized limit parameter, either as an integer, or as a
|
|
327
298
|
# string which contains a comma-delimited list of integers.
|
|
328
299
|
def sanitize_limit(limit)
|
|
@@ -370,7 +341,7 @@ module ActiveRecord
|
|
|
370
341
|
|
|
371
342
|
# Send a rollback message to all records after they have been rolled back. If rollback
|
|
372
343
|
# is false, only rollback records since the last save point.
|
|
373
|
-
def rollback_transaction_records(rollback)
|
|
344
|
+
def rollback_transaction_records(rollback)
|
|
374
345
|
if rollback
|
|
375
346
|
records = @_current_transaction_records.flatten
|
|
376
347
|
@_current_transaction_records.clear
|
|
@@ -390,7 +361,7 @@ module ActiveRecord
|
|
|
390
361
|
end
|
|
391
362
|
|
|
392
363
|
# Send a commit message to all records after they have been committed.
|
|
393
|
-
def commit_transaction_records
|
|
364
|
+
def commit_transaction_records
|
|
394
365
|
records = @_current_transaction_records.flatten
|
|
395
366
|
@_current_transaction_records.clear
|
|
396
367
|
unless records.blank?
|
|
@@ -25,19 +25,13 @@ module ActiveRecord
|
|
|
25
25
|
when true, false
|
|
26
26
|
if column && column.type == :integer
|
|
27
27
|
value ? '1' : '0'
|
|
28
|
-
elsif column && [:text, :string, :binary].include?(column.type)
|
|
29
|
-
value ? "'1'" : "'0'"
|
|
30
28
|
else
|
|
31
29
|
value ? quoted_true : quoted_false
|
|
32
30
|
end
|
|
33
31
|
# BigDecimals need to be put in a non-normalized form and quoted.
|
|
34
32
|
when nil then "NULL"
|
|
35
|
-
when
|
|
36
|
-
|
|
37
|
-
if column && ![:integer, :float, :decimal].include?(column.type)
|
|
38
|
-
value = "'#{value}'"
|
|
39
|
-
end
|
|
40
|
-
value
|
|
33
|
+
when BigDecimal then value.to_s('F')
|
|
34
|
+
when Numeric then value.to_s
|
|
41
35
|
when Date, Time then "'#{quoted_date(value)}'"
|
|
42
36
|
when Symbol then "'#{quote_string(value.to_s)}'"
|
|
43
37
|
else
|
|
@@ -108,10 +102,13 @@ module ActiveRecord
|
|
|
108
102
|
def quoted_date(value)
|
|
109
103
|
if value.acts_like?(:time)
|
|
110
104
|
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
|
|
106
|
+
if value.respond_to?(zone_conversion_method)
|
|
107
|
+
value = value.send(zone_conversion_method)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
value.to_s(:db)
|
|
115
112
|
end
|
|
116
113
|
end
|
|
117
114
|
end
|