activerecord 3.1.10 → 4.2.11

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.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,11 +1,13 @@
1
1
  require 'thread'
2
+ require 'thread_safe'
2
3
  require 'monitor'
3
4
  require 'set'
4
- require 'active_support/core_ext/module/synchronization'
5
+ require 'active_support/core_ext/string/filters'
5
6
 
6
7
  module ActiveRecord
7
8
  # Raised when a connection could not be obtained within the connection
8
- # acquisition timeout period.
9
+ # acquisition timeout period: because max connections in pool
10
+ # are in use.
9
11
  class ConnectionTimeoutError < ConnectionNotEstablished
10
12
  end
11
13
 
@@ -50,107 +52,203 @@ module ActiveRecord
50
52
  #
51
53
  # == Options
52
54
  #
53
- # There are two connection-pooling-related options that you can add to
55
+ # There are several connection-pooling-related options that you can add to
54
56
  # your database connection configuration:
55
57
  #
56
58
  # * +pool+: number indicating size of connection pool (default 5)
57
- # * +wait_timeout+: number of seconds to block and wait for a connection
59
+ # * +checkout_timeout+: number of seconds to block and wait for a connection
58
60
  # before giving up and raising a timeout error (default 5 seconds).
61
+ # * +reaping_frequency+: frequency in seconds to periodically run the
62
+ # Reaper, which attempts to find and recover connections from dead
63
+ # threads, which can occur if a programmer forgets to close a
64
+ # connection at the end of a thread or a thread dies unexpectedly.
65
+ # Regardless of this setting, the Reaper will be invoked before every
66
+ # blocking wait. (Default nil, which means don't schedule the Reaper).
59
67
  class ConnectionPool
60
- attr_accessor :automatic_reconnect
61
- attr_reader :spec, :connections
62
- attr_reader :columns, :columns_hash, :primary_keys, :tables
63
- attr_reader :column_defaults
64
-
65
- # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
66
- # object which describes database connection information (e.g. adapter,
67
- # host name, username, password, etc), as well as the maximum size for
68
- # this ConnectionPool.
68
+ # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
69
+ # with which it shares a Monitor. But could be a generic Queue.
69
70
  #
70
- # The default ConnectionPool maximum size is 5.
71
- def initialize(spec)
72
- @spec = spec
73
-
74
- # The cache of reserved connections mapped to threads
75
- @reserved_connections = {}
71
+ # The Queue in stdlib's 'thread' could replace this class except
72
+ # stdlib's doesn't support waiting with a timeout.
73
+ class Queue
74
+ def initialize(lock = Monitor.new)
75
+ @lock = lock
76
+ @cond = @lock.new_cond
77
+ @num_waiting = 0
78
+ @queue = []
79
+ end
76
80
 
77
- # The mutex used to synchronize pool access
78
- @connection_mutex = Monitor.new
79
- @queue = @connection_mutex.new_cond
80
- @timeout = spec.config[:wait_timeout] || 5
81
+ # Test if any threads are currently waiting on the queue.
82
+ def any_waiting?
83
+ synchronize do
84
+ @num_waiting > 0
85
+ end
86
+ end
81
87
 
82
- # default max pool size to 5
83
- @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
88
+ # Returns the number of threads currently waiting on this
89
+ # queue.
90
+ def num_waiting
91
+ synchronize do
92
+ @num_waiting
93
+ end
94
+ end
84
95
 
85
- @connections = []
86
- @checked_out = []
87
- @automatic_reconnect = true
88
- @tables = {}
89
- @visitor = nil
96
+ # Add +element+ to the queue. Never blocks.
97
+ def add(element)
98
+ synchronize do
99
+ @queue.push element
100
+ @cond.signal
101
+ end
102
+ end
90
103
 
91
- @columns = Hash.new do |h, table_name|
92
- h[table_name] = with_connection do |conn|
104
+ # If +element+ is in the queue, remove and return it, or nil.
105
+ def delete(element)
106
+ synchronize do
107
+ @queue.delete(element)
108
+ end
109
+ end
93
110
 
94
- # Fetch a list of columns
95
- conn.columns(table_name, "#{table_name} Columns").tap do |columns|
111
+ # Remove all elements from the queue.
112
+ def clear
113
+ synchronize do
114
+ @queue.clear
115
+ end
116
+ end
96
117
 
97
- # set primary key information
98
- columns.each do |column|
99
- column.primary = column.name == primary_keys[table_name]
100
- end
118
+ # Remove the head of the queue.
119
+ #
120
+ # If +timeout+ is not given, remove and return the head the
121
+ # queue if the number of available elements is strictly
122
+ # greater than the number of threads currently waiting (that
123
+ # is, don't jump ahead in line). Otherwise, return nil.
124
+ #
125
+ # If +timeout+ is given, block if it there is no element
126
+ # available, waiting up to +timeout+ seconds for an element to
127
+ # become available.
128
+ #
129
+ # Raises:
130
+ # - ConnectionTimeoutError if +timeout+ is given and no element
131
+ # becomes available after +timeout+ seconds,
132
+ def poll(timeout = nil)
133
+ synchronize do
134
+ if timeout
135
+ no_wait_poll || wait_poll(timeout)
136
+ else
137
+ no_wait_poll
101
138
  end
102
139
  end
103
140
  end
104
141
 
105
- @columns_hash = Hash.new do |h, table_name|
106
- h[table_name] = Hash[columns[table_name].map { |col|
107
- [col.name, col]
108
- }]
142
+ private
143
+
144
+ def synchronize(&block)
145
+ @lock.synchronize(&block)
109
146
  end
110
147
 
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
- }]
148
+ # Test if the queue currently contains any elements.
149
+ def any?
150
+ !@queue.empty?
115
151
  end
116
152
 
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
153
+ # A thread can remove an element from the queue without
154
+ # waiting if an only if the number of currently available
155
+ # connections is strictly greater than the number of waiting
156
+ # threads.
157
+ def can_remove_no_wait?
158
+ @queue.size > @num_waiting
121
159
  end
122
- end
123
160
 
124
- # A cached lookup for table existence.
125
- def table_exists?(name)
126
- return true if @tables.key? name
161
+ # Removes and returns the head of the queue if possible, or nil.
162
+ def remove
163
+ @queue.shift
164
+ end
127
165
 
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)
166
+ # Remove and return the head the queue if the number of
167
+ # available elements is strictly greater than the number of
168
+ # threads currently waiting. Otherwise, return nil.
169
+ def no_wait_poll
170
+ remove if can_remove_no_wait?
131
171
  end
132
172
 
133
- @tables.key? name
173
+ # Waits on the queue up to +timeout+ seconds, then removes and
174
+ # returns the head of the queue.
175
+ def wait_poll(timeout)
176
+ @num_waiting += 1
177
+
178
+ t0 = Time.now
179
+ elapsed = 0
180
+ loop do
181
+ @cond.wait(timeout - elapsed)
182
+
183
+ return remove if any?
184
+
185
+ elapsed = Time.now - t0
186
+ if elapsed >= timeout
187
+ msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
188
+ [timeout, elapsed]
189
+ raise ConnectionTimeoutError, msg
190
+ end
191
+ end
192
+ ensure
193
+ @num_waiting -= 1
194
+ end
134
195
  end
135
196
 
136
- # Clears out internal caches:
197
+ # Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
198
+ # A reaper instantiated with a nil frequency will never reap the
199
+ # connection pool.
137
200
  #
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
201
+ # Configure the frequency by setting "reaping_frequency" in your
202
+ # database yaml file.
203
+ class Reaper
204
+ attr_reader :pool, :frequency
205
+
206
+ def initialize(pool, frequency)
207
+ @pool = pool
208
+ @frequency = frequency
209
+ end
210
+
211
+ def run
212
+ return unless frequency
213
+ Thread.new(frequency, pool) { |t, p|
214
+ while true
215
+ sleep t
216
+ p.reap
217
+ end
218
+ }
219
+ end
146
220
  end
147
221
 
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
222
+ include MonitorMixin
223
+
224
+ attr_accessor :automatic_reconnect, :checkout_timeout
225
+ attr_reader :spec, :connections, :size, :reaper
226
+
227
+ # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
228
+ # object which describes database connection information (e.g. adapter,
229
+ # host name, username, password, etc), as well as the maximum size for
230
+ # this ConnectionPool.
231
+ #
232
+ # The default ConnectionPool maximum size is 5.
233
+ def initialize(spec)
234
+ super()
235
+
236
+ @spec = spec
237
+
238
+ @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
239
+ @reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
240
+ @reaper.run
241
+
242
+ # default max pool size to 5
243
+ @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
244
+
245
+ # The cache of reserved connections mapped to threads
246
+ @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
247
+
248
+ @connections = []
249
+ @automatic_reconnect = true
250
+
251
+ @available = Queue.new self
154
252
  end
155
253
 
156
254
  # Retrieve the connection associated with the current thread, or call
@@ -159,29 +257,38 @@ module ActiveRecord
159
257
  # #connection can be called any number of times; the connection is
160
258
  # held in a hash keyed by the thread id.
161
259
  def connection
162
- @reserved_connections[current_connection_id] ||= checkout
260
+ # this is correctly done double-checked locking
261
+ # (ThreadSafe::Cache's lookups have volatile semantics)
262
+ @reserved_connections[current_connection_id] || synchronize do
263
+ @reserved_connections[current_connection_id] ||= checkout
264
+ end
163
265
  end
164
266
 
165
- # Check to see if there is an active connection in this connection
166
- # pool.
267
+ # Is there an open connection that is being used for the current thread?
167
268
  def active_connection?
168
- @reserved_connections.key? current_connection_id
269
+ synchronize do
270
+ @reserved_connections.fetch(current_connection_id) {
271
+ return false
272
+ }.in_use?
273
+ end
169
274
  end
170
275
 
171
276
  # Signal that the thread is finished with the current connection.
172
277
  # #release_connection releases the connection-thread association
173
278
  # and returns the connection to the pool.
174
279
  def release_connection(with_id = current_connection_id)
175
- conn = @reserved_connections.delete(with_id)
176
- checkin conn if conn
280
+ synchronize do
281
+ conn = @reserved_connections.delete(with_id)
282
+ checkin conn if conn
283
+ end
177
284
  end
178
285
 
179
- # If a connection already exists yield it to the block. If no connection
286
+ # If a connection already exists yield it to the block. If no connection
180
287
  # exists checkout a connection, yield it to the block, and checkin the
181
288
  # connection when finished.
182
289
  def with_connection
183
290
  connection_id = current_connection_id
184
- fresh_connection = true unless @reserved_connections[connection_id]
291
+ fresh_connection = true unless active_connection?
185
292
  yield connection
186
293
  ensure
187
294
  release_connection(connection_id) if fresh_connection
@@ -189,95 +296,59 @@ module ActiveRecord
189
296
 
190
297
  # Returns true if a connection has already been opened.
191
298
  def connected?
192
- !@connections.empty?
299
+ synchronize { @connections.any? }
193
300
  end
194
301
 
195
302
  # Disconnects all connections in the pool, and clears the pool.
196
303
  def disconnect!
197
- @reserved_connections.each do |name,conn|
198
- checkin conn
199
- end
200
- @reserved_connections = {}
201
- @connections.each do |conn|
202
- conn.disconnect!
304
+ synchronize do
305
+ @reserved_connections.clear
306
+ @connections.each do |conn|
307
+ checkin conn
308
+ conn.disconnect!
309
+ end
310
+ @connections = []
311
+ @available.clear
203
312
  end
204
- @connections = []
205
313
  end
206
314
 
207
315
  # Clears the cache which maps classes.
208
316
  def clear_reloadable_connections!
209
- @reserved_connections.each do |name, conn|
210
- checkin conn
211
- end
212
- @reserved_connections = {}
213
- @connections.each do |conn|
214
- conn.disconnect! if conn.requires_reloading?
215
- end
216
- @connections.delete_if do |conn|
217
- conn.requires_reloading?
218
- end
219
- end
220
-
221
- # Verify active connections and remove and disconnect connections
222
- # associated with stale threads.
223
- def verify_active_connections! #:nodoc:
224
- clear_stale_cached_connections!
225
- @connections.each do |connection|
226
- connection.verify!
227
- end
228
- end
229
-
230
- # Return any checked-out connections back to the pool by threads that
231
- # are no longer alive.
232
- def clear_stale_cached_connections!
233
- keys = @reserved_connections.keys - Thread.list.find_all { |t|
234
- t.alive?
235
- }.map { |thread| thread.object_id }
236
- keys.each do |key|
237
- checkin @reserved_connections[key]
238
- @reserved_connections.delete(key)
317
+ synchronize do
318
+ @reserved_connections.clear
319
+ @connections.each do |conn|
320
+ checkin conn
321
+ conn.disconnect! if conn.requires_reloading?
322
+ end
323
+ @connections.delete_if do |conn|
324
+ conn.requires_reloading?
325
+ end
326
+ @available.clear
327
+ @connections.each do |conn|
328
+ @available.add conn
329
+ end
239
330
  end
240
331
  end
241
332
 
242
333
  # Check-out a database connection from the pool, indicating that you want
243
334
  # to use it. You should call #checkin when you no longer need this.
244
335
  #
245
- # This is done by either returning an existing connection, or by creating
246
- # a new connection. If the maximum number of connections for this pool has
247
- # already been reached, but the pool is empty (i.e. they're all being used),
248
- # then this method will wait until a thread has checked in a connection.
249
- # The wait time is bounded however: if no connection can be checked out
250
- # within the timeout specified for this pool, then a ConnectionTimeoutError
251
- # exception will be raised.
336
+ # This is done by either returning and leasing existing connection, or by
337
+ # creating a new connection and leasing it.
338
+ #
339
+ # If all connections are leased and the pool is at capacity (meaning the
340
+ # number of currently leased connections is greater than or equal to the
341
+ # size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
252
342
  #
253
343
  # Returns: an AbstractAdapter object.
254
344
  #
255
345
  # Raises:
256
- # - ConnectionTimeoutError: no connection can be obtained from the pool
257
- # within the timeout period.
346
+ # - ConnectionTimeoutError: no connection can be obtained from the pool.
258
347
  def checkout
259
- # Checkout an available connection
260
- @connection_mutex.synchronize do
261
- loop do
262
- conn = if @checked_out.size < @connections.size
263
- checkout_existing_connection
264
- elsif @connections.size < @size
265
- checkout_new_connection
266
- end
267
- return conn if conn
268
-
269
- @queue.wait(@timeout)
270
-
271
- if(@checked_out.size < @connections.size)
272
- next
273
- else
274
- clear_stale_cached_connections!
275
- if @size == @checked_out.size
276
- raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it."
277
- end
278
- end
279
-
280
- end
348
+ synchronize do
349
+ conn = acquire_connection
350
+ conn.lease
351
+ checkout_and_verify(conn)
281
352
  end
282
353
  end
283
354
 
@@ -287,55 +358,108 @@ module ActiveRecord
287
358
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
288
359
  # calling +checkout+ on this pool.
289
360
  def checkin(conn)
290
- @connection_mutex.synchronize do
291
- conn.run_callbacks :checkin do
292
- @checked_out.delete conn
293
- @queue.signal
361
+ synchronize do
362
+ owner = conn.owner
363
+
364
+ conn._run_checkin_callbacks do
365
+ conn.expire
294
366
  end
367
+
368
+ release conn, owner
369
+
370
+ @available.add conn
371
+ end
372
+ end
373
+
374
+ # Remove a connection from the connection pool. The connection will
375
+ # remain open and active but will no longer be managed by this pool.
376
+ def remove(conn)
377
+ synchronize do
378
+ @connections.delete conn
379
+ @available.delete conn
380
+
381
+ release conn, conn.owner
382
+
383
+ @available.add checkout_new_connection if @available.any_waiting?
295
384
  end
296
385
  end
297
386
 
298
- synchronize :clear_reloadable_connections!, :verify_active_connections!,
299
- :connected?, :disconnect!, :with => :@connection_mutex
387
+ # Recover lost connections for the pool. A lost connection can occur if
388
+ # a programmer forgets to checkin a connection at the end of a thread
389
+ # or a thread dies unexpectedly.
390
+ def reap
391
+ stale_connections = synchronize do
392
+ @connections.select do |conn|
393
+ conn.in_use? && !conn.owner.alive?
394
+ end
395
+ end
396
+
397
+ stale_connections.each do |conn|
398
+ synchronize do
399
+ if conn.active?
400
+ conn.reset!
401
+ checkin conn
402
+ else
403
+ remove conn
404
+ end
405
+ end
406
+ end
407
+ end
300
408
 
301
409
  private
302
410
 
303
- def new_connection
304
- connection = ActiveRecord::Base.send(spec.adapter_method, spec.config)
411
+ # Acquire a connection by one of 1) immediately removing one
412
+ # from the queue of available connections, 2) creating a new
413
+ # connection if the pool is not at capacity, 3) waiting on the
414
+ # queue for a connection to become available.
415
+ #
416
+ # Raises:
417
+ # - ConnectionTimeoutError if a connection could not be acquired
418
+ def acquire_connection
419
+ if conn = @available.poll
420
+ conn
421
+ elsif @connections.size < @size
422
+ checkout_new_connection
423
+ else
424
+ reap
425
+ @available.poll(@checkout_timeout)
426
+ end
427
+ end
305
428
 
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
429
+ def release(conn, owner)
430
+ thread_id = owner.object_id
312
431
 
313
- connection
432
+ if @reserved_connections[thread_id] == conn
433
+ @reserved_connections.delete thread_id
434
+ end
435
+ end
436
+
437
+ def new_connection
438
+ Base.send(spec.adapter_method, spec.config)
314
439
  end
315
440
 
316
441
  def current_connection_id #:nodoc:
317
- ActiveRecord::Base.connection_id ||= Thread.current.object_id
442
+ Base.connection_id ||= Thread.current.object_id
318
443
  end
319
444
 
320
445
  def checkout_new_connection
321
446
  raise ConnectionNotEstablished unless @automatic_reconnect
322
447
 
323
448
  c = new_connection
449
+ c.pool = self
324
450
  @connections << c
325
- checkout_and_verify(c)
326
- end
327
-
328
- def checkout_existing_connection
329
- c = (@connections - @checked_out).first
330
- checkout_and_verify(c)
451
+ c
331
452
  end
332
453
 
333
454
  def checkout_and_verify(c)
334
- c.run_callbacks :checkout do
455
+ c._run_checkout_callbacks do
335
456
  c.verify!
336
- @checked_out << c
337
457
  end
338
458
  c
459
+ rescue
460
+ remove c
461
+ c.disconnect!
462
+ raise
339
463
  end
340
464
  end
341
465
 
@@ -345,59 +469,96 @@ module ActiveRecord
345
469
  #
346
470
  # For example, suppose that you have 5 models, with the following hierarchy:
347
471
  #
348
- # |
349
- # +-- Book
350
- # | |
351
- # | +-- ScaryBook
352
- # | +-- GoodBook
353
- # +-- Author
354
- # +-- BankAccount
472
+ # class Author < ActiveRecord::Base
473
+ # end
474
+ #
475
+ # class BankAccount < ActiveRecord::Base
476
+ # end
355
477
  #
356
- # Suppose that Book is to connect to a separate database (i.e. one other
357
- # than the default database). Then Book, ScaryBook and GoodBook will all use
358
- # the same connection pool. Likewise, Author and BankAccount will use the
359
- # same connection pool. However, the connection pool used by Author/BankAccount
360
- # is not the same as the one used by Book/ScaryBook/GoodBook.
478
+ # class Book < ActiveRecord::Base
479
+ # establish_connection "library_db"
480
+ # end
361
481
  #
362
- # Normally there is only a single ConnectionHandler instance, accessible via
363
- # ActiveRecord::Base.connection_handler. Active Record models use this to
364
- # determine that connection pool that they should use.
482
+ # class ScaryBook < Book
483
+ # end
484
+ #
485
+ # class GoodBook < Book
486
+ # end
487
+ #
488
+ # And a database.yml that looked like this:
489
+ #
490
+ # development:
491
+ # database: my_application
492
+ # host: localhost
493
+ #
494
+ # library_db:
495
+ # database: library
496
+ # host: some.library.org
497
+ #
498
+ # Your primary database in the development environment is "my_application"
499
+ # but the Book model connects to a separate database called "library_db"
500
+ # (this can even be a database on a different machine).
501
+ #
502
+ # Book, ScaryBook and GoodBook will all use the same connection pool to
503
+ # "library_db" while Author, BankAccount, and any other models you create
504
+ # will use the default connection pool to "my_application".
505
+ #
506
+ # The various connection pools are managed by a single instance of
507
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
508
+ # All Active Record models use this handler to determine the connection pool that they
509
+ # should use.
365
510
  class ConnectionHandler
366
- attr_reader :connection_pools
511
+ def initialize
512
+ # These caches are keyed by klass.name, NOT klass. Keying them by klass
513
+ # alone would lead to memory leaks in development mode as all previous
514
+ # instances of the class would stay in memory.
515
+ @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
516
+ h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
517
+ end
518
+ @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
519
+ h[k] = ThreadSafe::Cache.new
520
+ end
521
+ end
367
522
 
368
- def initialize(pools = {})
369
- @connection_pools = pools
523
+ def connection_pool_list
524
+ owner_to_pool.values.compact
370
525
  end
371
526
 
372
- def establish_connection(name, spec)
373
- @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
527
+ def connection_pools
528
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
529
+ In the next release, this will return the same as `#connection_pool_list`.
530
+ (An array of pools, rather than a hash mapping specs to pools.)
531
+ MSG
532
+
533
+ Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
534
+ end
535
+
536
+ def establish_connection(owner, spec)
537
+ @class_to_pool.clear
538
+ raise RuntimeError, "Anonymous class is not allowed." unless owner.name
539
+ owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
374
540
  end
375
541
 
376
542
  # Returns true if there are any active connections among the connection
377
543
  # pools that the ConnectionHandler is managing.
378
544
  def active_connections?
379
- connection_pools.values.any? { |pool| pool.active_connection? }
545
+ connection_pool_list.any?(&:active_connection?)
380
546
  end
381
547
 
382
548
  # Returns any connections in use by the current thread back to the pool,
383
549
  # and also returns connections to the pool cached by threads that are no
384
550
  # longer alive.
385
551
  def clear_active_connections!
386
- @connection_pools.each_value {|pool| pool.release_connection }
552
+ connection_pool_list.each(&:release_connection)
387
553
  end
388
554
 
389
555
  # Clears the cache which maps classes.
390
556
  def clear_reloadable_connections!
391
- @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
557
+ connection_pool_list.each(&:clear_reloadable_connections!)
392
558
  end
393
559
 
394
560
  def clear_all_connections!
395
- @connection_pools.each_value {|pool| pool.disconnect! }
396
- end
397
-
398
- # Verify active connections.
399
- def verify_active_connections! #:nodoc:
400
- @connection_pools.each_value {|pool| pool.verify_active_connections! }
561
+ connection_pool_list.each(&:disconnect!)
401
562
  end
402
563
 
403
564
  # Locate the connection of the nearest super class. This can be an
@@ -406,7 +567,10 @@ module ActiveRecord
406
567
  # for (not necessarily the current class).
407
568
  def retrieve_connection(klass) #:nodoc:
408
569
  pool = retrieve_connection_pool(klass)
409
- (pool && pool.connection) or raise ConnectionNotEstablished
570
+ raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
571
+ conn = pool.connection
572
+ raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
573
+ conn
410
574
  end
411
575
 
412
576
  # Returns true if a connection that's accessible to this class has
@@ -420,64 +584,79 @@ module ActiveRecord
420
584
  # connection and the defined connection (if they exist). The result
421
585
  # can be used as an argument for establish_connection, for easily
422
586
  # re-establishing the connection.
423
- def remove_connection(klass)
424
- pool = @connection_pools.delete(klass.name)
425
- return nil unless pool
426
-
427
- pool.automatic_reconnect = false
428
- pool.disconnect!
429
- pool.spec.config
587
+ def remove_connection(owner)
588
+ if pool = owner_to_pool.delete(owner.name)
589
+ @class_to_pool.clear
590
+ pool.automatic_reconnect = false
591
+ pool.disconnect!
592
+ pool.spec.config
593
+ end
430
594
  end
431
595
 
596
+ # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
597
+ # This makes retrieving the connection pool O(1) once the process is warm.
598
+ # When a connection is established or removed, we invalidate the cache.
599
+ #
600
+ # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
601
+ # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
602
+ # #fetch is significantly slower than #[]. So in the nil case, no caching will
603
+ # take place, but that's ok since the nil case is not the common one that we wish
604
+ # to optimise for.
432
605
  def retrieve_connection_pool(klass)
433
- pool = @connection_pools[klass.name]
434
- return pool if pool
435
- return nil if ActiveRecord::Base == klass
436
- retrieve_connection_pool klass.superclass
437
- end
438
- end
439
-
440
- class ConnectionManagement
441
- class Proxy # :nodoc:
442
- attr_reader :body, :testing
606
+ class_to_pool[klass.name] ||= begin
607
+ until pool = pool_for(klass)
608
+ klass = klass.superclass
609
+ break unless klass <= Base
610
+ end
443
611
 
444
- def initialize(body, testing = false)
445
- @body = body
446
- @testing = testing
612
+ class_to_pool[klass.name] = pool
447
613
  end
614
+ end
448
615
 
449
- def method_missing(method_sym, *arguments, &block)
450
- @body.send(method_sym, *arguments, &block)
451
- end
616
+ private
452
617
 
453
- def respond_to?(method_sym, include_private = false)
454
- super || @body.respond_to?(method_sym)
455
- end
618
+ def owner_to_pool
619
+ @owner_to_pool[Process.pid]
620
+ end
456
621
 
457
- def each(&block)
458
- body.each(&block)
459
- end
622
+ def class_to_pool
623
+ @class_to_pool[Process.pid]
624
+ end
460
625
 
461
- def close
462
- body.close if body.respond_to?(:close)
626
+ def pool_for(owner)
627
+ owner_to_pool.fetch(owner.name) {
628
+ if ancestor_pool = pool_from_any_process_for(owner)
629
+ # A connection was established in an ancestor process that must have
630
+ # subsequently forked. We can't reuse the connection, but we can copy
631
+ # the specification and establish a new connection with it.
632
+ establish_connection owner, ancestor_pool.spec
633
+ else
634
+ owner_to_pool[owner.name] = nil
635
+ end
636
+ }
637
+ end
463
638
 
464
- # Don't return connection (and perform implicit rollback) if
465
- # this request is a part of integration test
466
- ActiveRecord::Base.clear_active_connections! unless testing
467
- end
639
+ def pool_from_any_process_for(owner)
640
+ owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[owner.name] }
641
+ owner_to_pool && owner_to_pool[owner.name]
468
642
  end
643
+ end
469
644
 
645
+ class ConnectionManagement
470
646
  def initialize(app)
471
647
  @app = app
472
648
  end
473
649
 
474
650
  def call(env)
475
- testing = env.key?('rack.test')
651
+ testing = env['rack.test']
476
652
 
477
- status, headers, body = @app.call(env)
653
+ response = @app.call(env)
654
+ response[2] = ::Rack::BodyProxy.new(response[2]) do
655
+ ActiveRecord::Base.clear_active_connections! unless testing
656
+ end
478
657
 
479
- [status, headers, Proxy.new(body, testing)]
480
- rescue
658
+ response
659
+ rescue Exception
481
660
  ActiveRecord::Base.clear_active_connections! unless testing
482
661
  raise
483
662
  end