activerecord 7.1.5.1 → 8.0.2

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.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
9
9
  # on any database connection adapter. For example:
10
10
  #
11
- # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
11
+ # result = ActiveRecord::Base.lease_connection.exec_query('SELECT id, title, body FROM posts')
12
12
  # result # => #<ActiveRecord::Result:0xdeadbeef>
13
13
  #
14
14
  # # Get the column names of the result:
@@ -36,6 +36,59 @@ module ActiveRecord
36
36
  class Result
37
37
  include Enumerable
38
38
 
39
+ class IndexedRow
40
+ def initialize(column_indexes, row)
41
+ @column_indexes = column_indexes
42
+ @row = row
43
+ end
44
+
45
+ def size
46
+ @column_indexes.size
47
+ end
48
+ alias_method :length, :size
49
+
50
+ def each_key(&block)
51
+ @column_indexes.each_key(&block)
52
+ end
53
+
54
+ def keys
55
+ @column_indexes.keys
56
+ end
57
+
58
+ def ==(other)
59
+ if other.is_a?(Hash)
60
+ to_hash == other
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ def key?(column)
67
+ @column_indexes.key?(column)
68
+ end
69
+
70
+ def fetch(column)
71
+ if index = @column_indexes[column]
72
+ @row[index]
73
+ elsif block_given?
74
+ yield
75
+ else
76
+ raise KeyError, "key not found: #{column.inspect}"
77
+ end
78
+ end
79
+
80
+ def [](column)
81
+ if index = @column_indexes[column]
82
+ @row[index]
83
+ end
84
+ end
85
+
86
+ def to_h
87
+ @column_indexes.transform_values { |index| @row[index] }
88
+ end
89
+ alias_method :to_hash, :to_h
90
+ end
91
+
39
92
  attr_reader :columns, :rows, :column_types
40
93
 
41
94
  def self.empty(async: false) # :nodoc:
@@ -46,11 +99,14 @@ module ActiveRecord
46
99
  end
47
100
  end
48
101
 
49
- def initialize(columns, rows, column_types = {})
50
- @columns = columns
102
+ def initialize(columns, rows, column_types = nil)
103
+ # We freeze the strings to prevent them getting duped when
104
+ # used as keys in ActiveRecord::Base's @attributes hash
105
+ @columns = columns.each(&:-@).freeze
51
106
  @rows = rows
52
107
  @hash_rows = nil
53
- @column_types = column_types
108
+ @column_types = column_types || EMPTY_HASH
109
+ @column_indexes = nil
54
110
  end
55
111
 
56
112
  # Returns true if this result set includes the column named +name+
@@ -64,7 +120,9 @@ module ActiveRecord
64
120
  end
65
121
 
66
122
  # Calls the given block once for each element in row collection, passing
67
- # row as parameter.
123
+ # row as parameter. Each row is a Hash-like, read only object.
124
+ #
125
+ # To get real hashes, use +.to_a.each+.
68
126
  #
69
127
  # Returns an +Enumerator+ if no block is given.
70
128
  def each(&block)
@@ -131,17 +189,37 @@ module ActiveRecord
131
189
  end
132
190
 
133
191
  def initialize_copy(other)
134
- @columns = columns.dup
135
- @rows = rows.dup
192
+ @rows = rows.dup
136
193
  @column_types = column_types.dup
137
194
  @hash_rows = nil
138
195
  end
139
196
 
140
197
  def freeze # :nodoc:
141
198
  hash_rows.freeze
199
+ indexed_rows.freeze
142
200
  super
143
201
  end
144
202
 
203
+ def column_indexes # :nodoc:
204
+ @column_indexes ||= begin
205
+ index = 0
206
+ hash = {}
207
+ length = columns.length
208
+ while index < length
209
+ hash[columns[index]] = index
210
+ index += 1
211
+ end
212
+ hash.freeze
213
+ end
214
+ end
215
+
216
+ def indexed_rows # :nodoc:
217
+ @indexed_rows ||= begin
218
+ columns = column_indexes
219
+ @rows.map { |row| IndexedRow.new(columns, row) }.freeze
220
+ end
221
+ end
222
+
145
223
  private
146
224
  def column_type(name, index, type_overrides)
147
225
  type_overrides.fetch(name) do
@@ -152,47 +230,18 @@ module ActiveRecord
152
230
  end
153
231
 
154
232
  def hash_rows
155
- @hash_rows ||=
156
- begin
157
- # We freeze the strings to prevent them getting duped when
158
- # used as keys in ActiveRecord::Base's @attributes hash
159
- columns = @columns.map(&:-@)
160
- length = columns.length
161
- template = nil
162
-
163
- @rows.map { |row|
164
- if template
165
- # We use transform_values to build subsequent rows from the
166
- # hash of the first row. This is faster because we avoid any
167
- # reallocs and in Ruby 2.7+ avoid hashing entirely.
168
- index = -1
169
- template.transform_values do
170
- row[index += 1]
171
- end
172
- else
173
- # In the past we used Hash[columns.zip(row)]
174
- # though elegant, the verbose way is much more efficient
175
- # both time and memory wise cause it avoids a big array allocation
176
- # this method is called a lot and needs to be micro optimised
177
- hash = {}
178
-
179
- index = 0
180
- while index < length
181
- hash[columns[index]] = row[index]
182
- index += 1
183
- end
184
-
185
- # It's possible to select the same column twice, in which case
186
- # we can't use a template
187
- template = hash if hash.length == length
188
-
189
- hash
190
- end
191
- }
192
- end
233
+ # We use transform_values to rows.
234
+ # This is faster because we avoid any reallocs and avoid hashing entirely.
235
+ @hash_rows ||= @rows.map do |row|
236
+ column_indexes.transform_values { |index| row[index] }
237
+ end
193
238
  end
194
239
 
195
- EMPTY = new([].freeze, [].freeze, {}.freeze).freeze
240
+ empty_array = [].freeze
241
+ EMPTY_HASH = {}.freeze
242
+ private_constant :EMPTY_HASH
243
+
244
+ EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
196
245
  private_constant :EMPTY
197
246
 
198
247
  EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
@@ -25,15 +25,54 @@ module ActiveRecord
25
25
  ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
26
26
  end
27
27
 
28
+ def queries_count
29
+ ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
30
+ end
31
+
32
+ def queries_count=(count)
33
+ ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
34
+ end
35
+
36
+ def cached_queries_count
37
+ ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
38
+ end
39
+
40
+ def cached_queries_count=(count)
41
+ ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
42
+ end
43
+
28
44
  def reset
45
+ reset_runtimes
46
+ reset_queries_count
47
+ reset_cached_queries_count
48
+ end
49
+
50
+ def reset_runtimes
29
51
  rt, self.sql_runtime = sql_runtime, 0.0
30
52
  self.async_sql_runtime = 0.0
31
53
  rt
32
54
  end
55
+
56
+ def reset_queries_count
57
+ qc = queries_count
58
+ self.queries_count = 0
59
+ qc
60
+ end
61
+
62
+ def reset_cached_queries_count
63
+ qc = cached_queries_count
64
+ self.cached_queries_count = 0
65
+ qc
66
+ end
33
67
  end
34
68
  end
35
69
 
36
70
  ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
71
+ unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
72
+ ActiveRecord::RuntimeRegistry.queries_count += 1
73
+ ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
74
+ end
75
+
37
76
  runtime = (finish - start) * 1_000.0
38
77
 
39
78
  if payload[:async]
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
18
18
  # # => "name='foo''bar' and group_id='4'"
19
19
  #
20
- # This method will NOT sanitize a SQL string since it won't contain
20
+ # This method will NOT sanitize an SQL string since it won't contain
21
21
  # any conditions in it and will return the string as is.
22
22
  #
23
23
  # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
@@ -52,7 +52,7 @@ module ActiveRecord
52
52
  # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
53
53
  # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
54
54
  #
55
- # This method will NOT sanitize a SQL string since it won't contain
55
+ # This method will NOT sanitize an SQL string since it won't contain
56
56
  # any conditions in it and will return the string as is.
57
57
  #
58
58
  # sanitize_sql_for_assignment("name=NULL and group_id='4'")
@@ -85,7 +85,7 @@ module ActiveRecord
85
85
  if condition.is_a?(Array) && condition.first.to_s.include?("?")
86
86
  disallow_raw_sql!(
87
87
  [condition.first],
88
- permit: connection.column_name_with_order_matcher
88
+ permit: adapter_class.column_name_with_order_matcher
89
89
  )
90
90
 
91
91
  # Ensure we aren't dealing with a subclass of String that might
@@ -105,12 +105,13 @@ module ActiveRecord
105
105
  # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
106
106
  # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
107
107
  def sanitize_sql_hash_for_assignment(attrs, table)
108
- c = connection
109
- attrs.map do |attr, value|
110
- type = type_for_attribute(attr)
111
- value = type.serialize(type.cast(value))
112
- "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
113
- end.join(", ")
108
+ with_connection do |c|
109
+ attrs.map do |attr, value|
110
+ type = type_for_attribute(attr)
111
+ value = type.serialize(type.cast(value))
112
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
113
+ end.join(", ")
114
+ end
114
115
  end
115
116
 
116
117
  # Sanitizes a +string+ so that it is safe to use within an SQL
@@ -163,17 +164,23 @@ module ActiveRecord
163
164
  def sanitize_sql_array(ary)
164
165
  statement, *values = ary
165
166
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
166
- replace_named_bind_variables(statement, values.first)
167
+ with_connection do |c|
168
+ replace_named_bind_variables(c, statement, values.first)
169
+ end
167
170
  elsif statement.include?("?")
168
- replace_bind_variables(statement, values)
171
+ with_connection do |c|
172
+ replace_bind_variables(c, statement, values)
173
+ end
169
174
  elsif statement.blank?
170
175
  statement
171
176
  else
172
- statement % values.collect { |value| connection.quote_string(value.to_s) }
177
+ with_connection do |c|
178
+ statement % values.collect { |value| c.quote_string(value.to_s) }
179
+ end
173
180
  end
174
181
  end
175
182
 
176
- def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
183
+ def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
177
184
  unexpected = nil
178
185
  args.each do |arg|
179
186
  next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
@@ -193,48 +200,47 @@ module ActiveRecord
193
200
  end
194
201
 
195
202
  private
196
- def replace_bind_variables(statement, values)
203
+ def replace_bind_variables(connection, statement, values)
197
204
  raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
198
205
  bound = values.dup
199
- c = connection
200
206
  statement.gsub(/\?/) do
201
- replace_bind_variable(bound.shift, c)
207
+ replace_bind_variable(connection, bound.shift)
202
208
  end
203
209
  end
204
210
 
205
- def replace_bind_variable(value, c = connection)
211
+ def replace_bind_variable(connection, value)
206
212
  if ActiveRecord::Relation === value
207
213
  value.to_sql
208
214
  else
209
- quote_bound_value(value, c)
215
+ quote_bound_value(connection, value)
210
216
  end
211
217
  end
212
218
 
213
- def replace_named_bind_variables(statement, bind_vars)
219
+ def replace_named_bind_variables(connection, statement, bind_vars)
214
220
  statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
215
- if $1 == ":" # skip postgresql casts
221
+ if $1 == ":" # skip PostgreSQL casts
216
222
  match # return the whole match
217
223
  elsif $1 == "\\" # escaped literal colon
218
224
  match[1..-1] # return match with escaping backlash char removed
219
225
  elsif bind_vars.include?(match = $2.to_sym)
220
- replace_bind_variable(bind_vars[match])
226
+ replace_bind_variable(connection, bind_vars[match])
221
227
  else
222
228
  raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
223
229
  end
224
230
  end
225
231
  end
226
232
 
227
- def quote_bound_value(value, c = connection)
233
+ def quote_bound_value(connection, value)
228
234
  if value.respond_to?(:map) && !value.acts_like?(:string)
229
235
  values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
230
236
  if values.empty?
231
- c.quote(c.cast_bound_value(nil))
237
+ connection.quote(connection.cast_bound_value(nil))
232
238
  else
233
- values.map! { |v| c.quote(c.cast_bound_value(v)) }.join(",")
239
+ values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
234
240
  end
235
241
  else
236
242
  value = value.id_for_database if value.respond_to?(:id_for_database)
237
- c.quote(c.cast_bound_value(value))
243
+ connection.quote(connection.cast_bound_value(value))
238
244
  end
239
245
  end
240
246
 
@@ -52,14 +52,16 @@ module ActiveRecord
52
52
  end
53
53
 
54
54
  def define(info, &block) # :nodoc:
55
- instance_eval(&block)
55
+ connection_pool.with_connection do |connection|
56
+ instance_eval(&block)
56
57
 
57
- connection.schema_migration.create_table
58
- if info[:version].present?
59
- connection.assume_migrated_upto_version(info[:version])
60
- end
58
+ connection_pool.schema_migration.create_table
59
+ if info[:version].present?
60
+ connection.assume_migrated_upto_version(info[:version])
61
+ end
61
62
 
62
- connection.internal_metadata.create_table_and_set_flags(connection.migration_context.current_environment)
63
+ connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment)
64
+ end
63
65
  end
64
66
  end
65
67
 
@@ -41,8 +41,10 @@ module ActiveRecord
41
41
  cattr_accessor :unique_ignore_pattern, default: /^uniq_rails_[0-9a-f]{10}$/
42
42
 
43
43
  class << self
44
- def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
45
- connection.create_schema_dumper(generate_options(config)).dump(stream)
44
+ def dump(pool = ActiveRecord::Base.connection_pool, stream = $stdout, config = ActiveRecord::Base)
45
+ pool.with_connection do |connection|
46
+ connection.create_schema_dumper(generate_options(config)).dump(stream)
47
+ end
46
48
  stream
47
49
  end
48
50
 
@@ -61,6 +63,7 @@ module ActiveRecord
61
63
  extensions(stream)
62
64
  types(stream)
63
65
  tables(stream)
66
+ virtual_tables(stream)
64
67
  trailer(stream)
65
68
  stream
66
69
  end
@@ -70,7 +73,7 @@ module ActiveRecord
70
73
 
71
74
  def initialize(connection, options = {})
72
75
  @connection = connection
73
- @version = connection.migration_context.current_version rescue nil
76
+ @version = connection.pool.migration_context.current_version rescue nil
74
77
  @options = options
75
78
  @ignore_tables = [
76
79
  ActiveRecord::Base.schema_migrations_table_name,
@@ -124,18 +127,31 @@ module ActiveRecord
124
127
  def schemas(stream)
125
128
  end
126
129
 
130
+ # virtual tables are only supported by SQLite
131
+ def virtual_tables(stream)
132
+ end
133
+
127
134
  def tables(stream)
128
135
  sorted_tables = @connection.tables.sort
129
136
 
130
- sorted_tables.each do |table_name|
131
- table(table_name, stream) unless ignored?(table_name)
137
+ not_ignored_tables = sorted_tables.reject { |table_name| ignored?(table_name) }
138
+
139
+ not_ignored_tables.each_with_index do |table_name, index|
140
+ table(table_name, stream)
141
+ stream.puts if index < not_ignored_tables.count - 1
132
142
  end
133
143
 
134
144
  # dump foreign keys at the end to make sure all dependent tables exist.
135
- if @connection.use_foreign_keys?
136
- sorted_tables.each do |tbl|
137
- foreign_keys(tbl, stream) unless ignored?(tbl)
145
+ if @connection.supports_foreign_keys?
146
+ foreign_keys_stream = StringIO.new
147
+ not_ignored_tables.each do |tbl|
148
+ foreign_keys(tbl, foreign_keys_stream)
138
149
  end
150
+
151
+ foreign_keys_string = foreign_keys_stream.string
152
+ stream.puts if foreign_keys_string.length > 0
153
+
154
+ stream.print foreign_keys_string
139
155
  end
140
156
  end
141
157
 
@@ -191,12 +207,16 @@ module ActiveRecord
191
207
  end
192
208
 
193
209
  indexes_in_create(table, tbl)
194
- check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
210
+ remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
195
211
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
196
212
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
197
213
 
198
214
  tbl.puts " end"
199
- tbl.puts
215
+
216
+ if remaining
217
+ tbl.puts
218
+ tbl.print remaining.string
219
+ end
200
220
 
201
221
  stream.print tbl.string
202
222
  rescue => e
@@ -262,24 +282,37 @@ module ActiveRecord
262
282
 
263
283
  def check_constraints_in_create(table, stream)
264
284
  if (check_constraints = @connection.check_constraints(table)).any?
265
- add_check_constraint_statements = check_constraints.map do |check_constraint|
266
- parts = [
267
- "t.check_constraint #{check_constraint.expression.inspect}"
268
- ]
285
+ check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
269
286
 
270
- if check_constraint.export_name_on_schema_dump?
271
- parts << "name: #{check_constraint.name.inspect}"
287
+ unless check_valid.empty?
288
+ check_constraint_statements = check_valid.map do |check|
289
+ " t.check_constraint #{check_parts(check).join(', ')}"
272
290
  end
273
291
 
274
- parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
275
-
276
- " #{parts.join(', ')}"
292
+ stream.puts check_constraint_statements.sort.join("\n")
277
293
  end
278
294
 
279
- stream.puts add_check_constraint_statements.sort.join("\n")
295
+ unless check_invalid.empty?
296
+ remaining = StringIO.new
297
+ table_name = remove_prefix_and_suffix(table).inspect
298
+
299
+ add_check_constraint_statements = check_invalid.map do |check|
300
+ " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
301
+ end
302
+
303
+ remaining.puts add_check_constraint_statements.sort.join("\n")
304
+ remaining
305
+ end
280
306
  end
281
307
  end
282
308
 
309
+ def check_parts(check)
310
+ check_parts = [ check.expression.inspect ]
311
+ check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
312
+ check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
313
+ check_parts
314
+ end
315
+
283
316
  def foreign_keys(table, stream)
284
317
  if (foreign_keys = @connection.foreign_keys(table)).any?
285
318
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
@@ -9,29 +9,36 @@ module ActiveRecord
9
9
  class NullSchemaMigration # :nodoc:
10
10
  end
11
11
 
12
- attr_reader :connection, :arel_table
12
+ attr_reader :arel_table
13
13
 
14
- def initialize(connection)
15
- @connection = connection
14
+ def initialize(pool)
15
+ @pool = pool
16
16
  @arel_table = Arel::Table.new(table_name)
17
17
  end
18
18
 
19
19
  def create_version(version)
20
20
  im = Arel::InsertManager.new(arel_table)
21
21
  im.insert(arel_table[primary_key] => version)
22
- connection.insert(im, "#{self.class} Create", primary_key, version)
22
+ @pool.with_connection do |connection|
23
+ connection.insert(im, "#{self.class} Create", primary_key, version)
24
+ end
23
25
  end
24
26
 
25
27
  def delete_version(version)
26
28
  dm = Arel::DeleteManager.new(arel_table)
27
29
  dm.wheres = [arel_table[primary_key].eq(version)]
28
30
 
29
- connection.delete(dm, "#{self.class} Destroy")
31
+ @pool.with_connection do |connection|
32
+ connection.delete(dm, "#{self.class} Destroy")
33
+ end
30
34
  end
31
35
 
32
36
  def delete_all_versions
33
- versions.each do |version|
34
- delete_version(version)
37
+ # Eagerly check in connection to avoid checking in/out many times in the called method.
38
+ @pool.with_connection do
39
+ versions.each do |version|
40
+ delete_version(version)
41
+ end
35
42
  end
36
43
  end
37
44
 
@@ -44,15 +51,19 @@ module ActiveRecord
44
51
  end
45
52
 
46
53
  def create_table
47
- unless connection.table_exists?(table_name)
48
- connection.create_table(table_name, id: false) do |t|
49
- t.string :version, **connection.internal_string_options_for_primary_key
54
+ @pool.with_connection do |connection|
55
+ unless connection.table_exists?(table_name)
56
+ connection.create_table(table_name, id: false) do |t|
57
+ t.string :version, **connection.internal_string_options_for_primary_key
58
+ end
50
59
  end
51
60
  end
52
61
  end
53
62
 
54
63
  def drop_table
55
- connection.drop_table table_name, if_exists: true
64
+ @pool.with_connection do |connection|
65
+ connection.drop_table table_name, if_exists: true
66
+ end
56
67
  end
57
68
 
58
69
  def normalize_migration_number(number)
@@ -68,7 +79,9 @@ module ActiveRecord
68
79
  sm.project(arel_table[primary_key])
69
80
  sm.order(arel_table[primary_key].asc)
70
81
 
71
- connection.select_values(sm, "#{self.class} Load")
82
+ @pool.with_connection do |connection|
83
+ connection.select_values(sm, "#{self.class} Load")
84
+ end
72
85
  end
73
86
 
74
87
  def integer_versions
@@ -79,11 +92,15 @@ module ActiveRecord
79
92
  sm = Arel::SelectManager.new(arel_table)
80
93
  sm.project(*Arel::Nodes::Count.new([Arel.star]))
81
94
 
82
- connection.select_values(sm, "#{self.class} Count").first
95
+ @pool.with_connection do |connection|
96
+ connection.select_values(sm, "#{self.class} Count").first
97
+ end
83
98
  end
84
99
 
85
100
  def table_exists?
86
- connection.data_source_exists?(table_name)
101
+ @pool.with_connection do |connection|
102
+ connection.data_source_exists?(table_name)
103
+ end
87
104
  end
88
105
  end
89
106
  end
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  scope = current_scope
24
24
 
25
25
  if scope
26
- if self == scope.klass
26
+ if self == scope.model
27
27
  scope.clone
28
28
  else
29
29
  relation.merge!(scope)
@@ -190,7 +190,11 @@ module ActiveRecord
190
190
 
191
191
  private
192
192
  def singleton_method_added(name)
193
- generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
193
+ super
194
+ # Most Kernel extends are both singleton and instance methods so
195
+ # respond_to is a fast check, but we don't want to define methods
196
+ # only on the module (ex. Module#name)
197
+ generate_relation_method(name) if Kernel.respond_to?(name) && (Kernel.method_defined?(name) || Kernel.private_method_defined?(name)) && !ActiveRecord::Relation.method_defined?(name)
194
198
  end
195
199
  end
196
200
  end