activerecord 6.1.6 → 7.0.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1314 -975
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/aggregations.rb +1 -1
  5. data/lib/active_record/association_relation.rb +0 -10
  6. data/lib/active_record/associations/association.rb +33 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -3
  8. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  10. data/lib/active_record/associations/builder/association.rb +8 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  12. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  13. data/lib/active_record/associations/builder/has_many.rb +3 -2
  14. data/lib/active_record/associations/builder/has_one.rb +2 -1
  15. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  16. data/lib/active_record/associations/collection_association.rb +19 -21
  17. data/lib/active_record/associations/collection_proxy.rb +10 -5
  18. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  19. data/lib/active_record/associations/has_many_association.rb +8 -5
  20. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  21. data/lib/active_record/associations/has_one_association.rb +10 -7
  22. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency.rb +23 -15
  24. data/lib/active_record/associations/preloader/association.rb +186 -52
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +49 -13
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +124 -95
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +49 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +14 -15
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +8 -23
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +10 -2
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -24
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +105 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +37 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +51 -51
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +37 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +208 -107
  87. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +96 -32
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +49 -55
  94. data/lib/active_record/core.rb +124 -134
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +15 -32
  100. data/lib/active_record/delegated_type.rb +53 -12
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +67 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +50 -43
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +20 -23
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +1 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +10 -4
  147. data/lib/active_record/log_subscriber.rb +23 -7
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +18 -6
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +7 -7
  152. data/lib/active_record/migration/compatibility.rb +84 -2
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +114 -83
  155. data/lib/active_record/model_schema.rb +58 -59
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +228 -60
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +138 -0
  162. data/lib/active_record/querying.rb +16 -6
  163. data/lib/active_record/railtie.rb +136 -22
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +78 -136
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +73 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +6 -6
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +276 -67
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +189 -88
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +17 -12
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +60 -13
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +6 -1
  191. data/lib/active_record/signed_id.rb +3 -3
  192. data/lib/active_record/store.rb +7 -2
  193. data/lib/active_record/suppressor.rb +11 -15
  194. data/lib/active_record/tasks/database_tasks.rb +127 -60
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  197. data/lib/active_record/test_databases.rb +1 -1
  198. data/lib/active_record/test_fixtures.rb +16 -9
  199. data/lib/active_record/timestamp.rb +3 -4
  200. data/lib/active_record/transactions.rb +9 -14
  201. data/lib/active_record/translation.rb +3 -3
  202. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  203. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  204. data/lib/active_record/type/internal/timezone.rb +2 -2
  205. data/lib/active_record/type/serialized.rb +1 -1
  206. data/lib/active_record/type/type_map.rb +17 -20
  207. data/lib/active_record/type.rb +1 -2
  208. data/lib/active_record/validations/associated.rb +4 -4
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +4 -4
  211. data/lib/active_record/version.rb +1 -1
  212. data/lib/active_record.rb +217 -27
  213. data/lib/arel/attributes/attribute.rb +0 -8
  214. data/lib/arel/crud.rb +28 -22
  215. data/lib/arel/delete_manager.rb +18 -4
  216. data/lib/arel/filter_predications.rb +9 -0
  217. data/lib/arel/insert_manager.rb +2 -3
  218. data/lib/arel/nodes/casted.rb +1 -1
  219. data/lib/arel/nodes/delete_statement.rb +12 -13
  220. data/lib/arel/nodes/filter.rb +10 -0
  221. data/lib/arel/nodes/function.rb +1 -0
  222. data/lib/arel/nodes/insert_statement.rb +2 -2
  223. data/lib/arel/nodes/select_core.rb +2 -2
  224. data/lib/arel/nodes/select_statement.rb +2 -2
  225. data/lib/arel/nodes/update_statement.rb +8 -3
  226. data/lib/arel/nodes.rb +1 -0
  227. data/lib/arel/predications.rb +11 -3
  228. data/lib/arel/select_manager.rb +10 -4
  229. data/lib/arel/table.rb +0 -1
  230. data/lib/arel/tree_manager.rb +0 -12
  231. data/lib/arel/update_manager.rb +18 -4
  232. data/lib/arel/visitors/dot.rb +80 -90
  233. data/lib/arel/visitors/mysql.rb +8 -2
  234. data/lib/arel/visitors/postgresql.rb +0 -10
  235. data/lib/arel/visitors/to_sql.rb +58 -2
  236. data/lib/arel.rb +2 -1
  237. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  238. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  239. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  240. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  241. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  242. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  243. metadata +55 -11
@@ -5,13 +5,19 @@ require "active_support/core_ext/enumerable"
5
5
  module ActiveRecord
6
6
  class InsertAll # :nodoc:
7
7
  attr_reader :model, :connection, :inserts, :keys
8
- attr_reader :on_duplicate, :returning, :unique_by
8
+ attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
9
9
 
10
- def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
10
+ def initialize(model, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
11
11
  raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
12
12
 
13
13
  @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
14
- @on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
14
+ @on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
15
+ @record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
16
+
17
+ disallow_raw_sql!(on_duplicate)
18
+ disallow_raw_sql!(returning)
19
+
20
+ configure_on_duplicate_update_logic
15
21
 
16
22
  if model.scope_attributes?
17
23
  @scope_attributes = model.scope_attributes
@@ -36,7 +42,7 @@ module ActiveRecord
36
42
  end
37
43
 
38
44
  def updatable_columns
39
- keys - readonly_columns - unique_by_columns
45
+ @updatable_columns ||= keys - readonly_columns - unique_by_columns
40
46
  end
41
47
 
42
48
  def primary_keys
@@ -56,18 +62,50 @@ module ActiveRecord
56
62
  inserts.map do |attributes|
57
63
  attributes = attributes.stringify_keys
58
64
  attributes.merge!(scope_attributes) if scope_attributes
65
+ attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
59
66
 
60
67
  verify_attributes(attributes)
61
68
 
62
- keys.map do |key|
69
+ keys_including_timestamps.map do |key|
63
70
  yield key, attributes[key]
64
71
  end
65
72
  end
66
73
  end
67
74
 
75
+ def record_timestamps?
76
+ @record_timestamps
77
+ end
78
+
79
+ # TODO: Consider remaining this method, as it only conditionally extends keys, not always
80
+ def keys_including_timestamps
81
+ @keys_including_timestamps ||= if record_timestamps?
82
+ keys + model.all_timestamp_attributes_in_model
83
+ else
84
+ keys
85
+ end
86
+ end
87
+
68
88
  private
69
89
  attr_reader :scope_attributes
70
90
 
91
+ def configure_on_duplicate_update_logic
92
+ if custom_update_sql_provided? && update_only.present?
93
+ raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
94
+ end
95
+
96
+ if update_only.present?
97
+ @updatable_columns = Array(update_only)
98
+ @on_duplicate = :update
99
+ elsif custom_update_sql_provided?
100
+ @update_sql = on_duplicate
101
+ @on_duplicate = :update
102
+ end
103
+ end
104
+
105
+ def custom_update_sql_provided?
106
+ @custom_update_sql_provided ||= Arel.arel_node?(on_duplicate)
107
+ end
108
+
71
109
  def find_unique_index_for(unique_by)
72
110
  if !connection.supports_insert_conflict_target?
73
111
  return if unique_by.nil?
@@ -126,15 +164,28 @@ module ActiveRecord
126
164
 
127
165
 
128
166
  def verify_attributes(attributes)
129
- if keys != attributes.keys.to_set
167
+ if keys_including_timestamps != attributes.keys.to_set
130
168
  raise ArgumentError, "All objects being inserted must have the same keys"
131
169
  end
132
170
  end
133
171
 
172
+ def disallow_raw_sql!(value)
173
+ return if !value.is_a?(String) || Arel.arel_node?(value)
174
+
175
+ raise ArgumentError, "Dangerous query method (method whose arguments are used as raw " \
176
+ "SQL) called: #{value}. " \
177
+ "Known-safe values can be passed " \
178
+ "by wrapping them in Arel.sql()."
179
+ end
180
+
181
+ def timestamps_for_create
182
+ model.all_timestamp_attributes_in_model.index_with(connection.high_precision_current_timestamp)
183
+ end
184
+
134
185
  class Builder # :nodoc:
135
186
  attr_reader :model
136
187
 
137
- delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
188
+ delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
138
189
 
139
190
  def initialize(insert_all)
140
191
  @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
@@ -145,9 +196,10 @@ module ActiveRecord
145
196
  end
146
197
 
147
198
  def values_list
148
- types = extract_types_from_columns_on(model.table_name, keys: keys)
199
+ types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
149
200
 
150
201
  values_list = insert_all.map_key_with_value do |key, value|
202
+ next value if Arel::Nodes::SqlLiteral === value
151
203
  connection.with_yaml_fallback(types[key].serialize(value))
152
204
  end
153
205
 
@@ -155,7 +207,13 @@ module ActiveRecord
155
207
  end
156
208
 
157
209
  def returning
158
- format_columns(insert_all.returning) if insert_all.returning
210
+ return unless insert_all.returning
211
+
212
+ if insert_all.returning.is_a?(String)
213
+ insert_all.returning
214
+ else
215
+ format_columns(insert_all.returning)
216
+ end
159
217
  end
160
218
 
161
219
  def conflict_target
@@ -173,22 +231,30 @@ module ActiveRecord
173
231
  end
174
232
 
175
233
  def touch_model_timestamps_unless(&block)
176
- model.send(:timestamp_attributes_for_update_in_model).map do |column_name|
234
+ return "" unless update_duplicates? && record_timestamps?
235
+
236
+ model.timestamp_attributes_for_update_in_model.filter_map do |column_name|
177
237
  if touch_timestamp_attribute?(column_name)
178
- "#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE CURRENT_TIMESTAMP END),"
238
+ "#{column_name}=(CASE WHEN (#{updatable_columns.map(&block).join(" AND ")}) THEN #{model.quoted_table_name}.#{column_name} ELSE #{connection.high_precision_current_timestamp} END),"
179
239
  end
180
- end.compact.join
240
+ end.join
181
241
  end
182
242
 
243
+ def raw_update_sql
244
+ insert_all.update_sql
245
+ end
246
+
247
+ alias raw_update_sql? raw_update_sql
248
+
183
249
  private
184
250
  attr_reader :connection, :insert_all
185
251
 
186
252
  def touch_timestamp_attribute?(column_name)
187
- update_duplicates? && !insert_all.updatable_columns.include?(column_name)
253
+ insert_all.updatable_columns.exclude?(column_name)
188
254
  end
189
255
 
190
256
  def columns_list
191
- format_columns(insert_all.keys)
257
+ format_columns(insert_all.keys_including_timestamps)
192
258
  end
193
259
 
194
260
  def extract_types_from_columns_on(table_name, keys:)
@@ -79,7 +79,7 @@ module ActiveRecord
79
79
  timestamp = max_updated_column_timestamp
80
80
 
81
81
  if timestamp
82
- timestamp = timestamp.utc.to_s(cache_timestamp_format)
82
+ timestamp = timestamp.utc.to_fs(cache_timestamp_format)
83
83
  "#{model_name.cache_key}/#{id}-#{timestamp}"
84
84
  else
85
85
  "#{model_name.cache_key}/#{id}"
@@ -101,8 +101,9 @@ module ActiveRecord
101
101
  timestamp = updated_at_before_type_cast
102
102
  if can_use_fast_cache_version?(timestamp)
103
103
  raw_timestamp_to_cache_version(timestamp)
104
+
104
105
  elsif timestamp = updated_at
105
- timestamp.utc.to_s(cache_timestamp_format)
106
+ timestamp.utc.to_fs(cache_timestamp_format)
106
107
  end
107
108
  elsif self.class.has_attribute?("updated_at")
108
109
  raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
@@ -177,7 +178,7 @@ module ActiveRecord
177
178
  def can_use_fast_cache_version?(timestamp)
178
179
  timestamp.is_a?(String) &&
179
180
  cache_timestamp_format == :usec &&
180
- default_timezone == :utc &&
181
+ ActiveRecord.default_timezone == :utc &&
181
182
  !updated_at_came_from_user?
182
183
  end
183
184
 
@@ -17,10 +17,6 @@ module ActiveRecord
17
17
  ActiveRecord::Base.connection.use_metadata_table?
18
18
  end
19
19
 
20
- def _internal?
21
- true
22
- end
23
-
24
20
  def primary_key
25
21
  "key"
26
22
  end
@@ -38,7 +34,7 @@ module ActiveRecord
38
34
  def [](key)
39
35
  return unless enabled?
40
36
 
41
- where(key: key).pluck(:value).first
37
+ where(key: key).pick(:value)
42
38
  end
43
39
 
44
40
  # Creates an internal metadata table with columns +key+ and +value+
@@ -2,50 +2,13 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module LegacyYamlAdapter # :nodoc:
5
- def self.convert(klass, coder)
5
+ def self.convert(coder)
6
6
  return coder unless coder.is_a?(Psych::Coder)
7
7
 
8
8
  case coder["active_record_yaml_version"]
9
9
  when 1, 2 then coder
10
10
  else
11
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
12
- YAML loading from legacy format older than Rails 5.0 is deprecated
13
- and will be removed in Rails 7.0.
14
- MSG
15
- if coder["attributes"].is_a?(ActiveModel::AttributeSet)
16
- Rails420.convert(klass, coder)
17
- else
18
- Rails41.convert(klass, coder)
19
- end
20
- end
21
- end
22
-
23
- module Rails420 # :nodoc:
24
- def self.convert(klass, coder)
25
- attribute_set = coder["attributes"]
26
-
27
- klass.attribute_names.each do |attr_name|
28
- attribute = attribute_set[attr_name]
29
- if attribute.type.is_a?(Delegator)
30
- type_from_klass = klass.type_for_attribute(attr_name)
31
- attribute_set[attr_name] = attribute.with_type(type_from_klass)
32
- end
33
- end
34
-
35
- coder
36
- end
37
- end
38
-
39
- module Rails41 # :nodoc:
40
- def self.convert(klass, coder)
41
- attributes = klass.attributes_builder
42
- .build_from_database(coder["attributes"])
43
- new_record = coder["attributes"][klass.primary_key].blank?
44
-
45
- {
46
- "attributes" => attributes,
47
- "new_record" => new_record,
48
- }
11
+ raise("Active Record doesn't know how to load YAML with this format.")
49
12
  end
50
13
  end
51
14
  end
@@ -56,11 +56,11 @@ module ActiveRecord
56
56
  class_attribute :lock_optimistically, instance_writer: false, default: true
57
57
  end
58
58
 
59
- def locking_enabled? #:nodoc:
59
+ def locking_enabled? # :nodoc:
60
60
  self.class.locking_enabled?
61
61
  end
62
62
 
63
- def increment!(*, **) #:nodoc:
63
+ def increment!(*, **) # :nodoc:
64
64
  super.tap do
65
65
  if locking_enabled?
66
66
  self[self.class.locking_column] += 1
@@ -90,7 +90,9 @@ module ActiveRecord
90
90
  begin
91
91
  locking_column = self.class.locking_column
92
92
  lock_attribute_was = @attributes[locking_column]
93
- lock_value_for_database = _lock_value_for_database(locking_column)
93
+
94
+ update_constraints = _primary_key_constraints_hash
95
+ update_constraints[locking_column] = _lock_value_for_database(locking_column)
94
96
 
95
97
  attribute_names = attribute_names.dup if attribute_names.frozen?
96
98
  attribute_names << locking_column
@@ -99,8 +101,7 @@ module ActiveRecord
99
101
 
100
102
  affected_rows = self.class._update_record(
101
103
  attributes_with_values(attribute_names),
102
- @primary_key => id_in_database,
103
- locking_column => lock_value_for_database
104
+ update_constraints
104
105
  )
105
106
 
106
107
  if affected_rows != 1
@@ -121,10 +122,10 @@ module ActiveRecord
121
122
 
122
123
  locking_column = self.class.locking_column
123
124
 
124
- affected_rows = self.class._delete_record(
125
- @primary_key => id_in_database,
126
- locking_column => _lock_value_for_database(locking_column)
127
- )
125
+ delete_constraints = _primary_key_constraints_hash
126
+ delete_constraints[locking_column] = _lock_value_for_database(locking_column)
127
+
128
+ affected_rows = self.class._delete_record(delete_constraints)
128
129
 
129
130
  if affected_rows != 1
130
131
  raise ActiveRecord::StaleObjectError.new(self, "destroy")
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  # Locking::Pessimistic provides support for row-level locking using
6
6
  # SELECT ... FOR UPDATE and other lock types.
7
7
  #
8
- # Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
8
+ # Chain <tt>ActiveRecord::Base#find</tt> to ActiveRecord::QueryMethods#lock to obtain an exclusive
9
9
  # lock on the selected rows:
10
10
  # # select * from accounts where id=1 for update
11
11
  # Account.lock.find(1)
@@ -81,9 +81,15 @@ module ActiveRecord
81
81
 
82
82
  # Wraps the passed block in a transaction, locking the object
83
83
  # before yielding. You can pass the SQL locking clause
84
- # as argument (see <tt>lock!</tt>).
85
- def with_lock(lock = true)
86
- transaction do
84
+ # as an optional argument (see #lock!).
85
+ #
86
+ # You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
87
+ # and <tt>joinable:</tt> to the wrapping transaction (see
88
+ # ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction).
89
+ def with_lock(*args)
90
+ transaction_opts = args.extract_options!
91
+ lock = args.present? ? args.first : true
92
+ transaction(**transaction_opts) do
87
93
  lock!(lock)
88
94
  yield
89
95
  end
@@ -22,10 +22,8 @@ module ActiveRecord
22
22
  def strict_loading_violation(event)
23
23
  debug do
24
24
  owner = event.payload[:owner]
25
- association = event.payload[:reflection].klass
26
- name = event.payload[:reflection].name
27
-
28
- color("Strict loading violation: #{owner} is marked for strict loading. The #{association} association named :#{name} cannot be lazily loaded.", RED)
25
+ reflection = event.payload[:reflection]
26
+ color(reflection.strict_loading_violation_message(owner), RED)
29
27
  end
30
28
  end
31
29
 
@@ -37,7 +35,11 @@ module ActiveRecord
37
35
 
38
36
  return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
39
37
 
40
- name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
38
+ name = if payload[:async]
39
+ "ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (db time #{event.duration.round(1)}ms)"
40
+ else
41
+ "#{payload[:name]} (#{event.duration.round(1)}ms)"
42
+ end
41
43
  name = "CACHE #{name}" if payload[:cached]
42
44
  sql = payload[:sql]
43
45
  binds = nil
@@ -47,7 +49,17 @@ module ActiveRecord
47
49
 
48
50
  binds = []
49
51
  payload[:binds].each_with_index do |attr, i|
50
- binds << render_bind(attr, casted_params[i])
52
+ attribute_name = if attr.respond_to?(:name)
53
+ attr.name
54
+ elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
55
+ attr[i].name
56
+ else
57
+ nil
58
+ end
59
+
60
+ filtered_params = filter(attribute_name, casted_params[i])
61
+
62
+ binds << render_bind(attr, filtered_params)
51
63
  end
52
64
  binds = binds.inspect
53
65
  binds.prepend(" ")
@@ -115,7 +127,7 @@ module ActiveRecord
115
127
  def debug(progname = nil, &block)
116
128
  return unless super
117
129
 
118
- if ActiveRecord::Base.verbose_query_logs
130
+ if ActiveRecord.verbose_query_logs
119
131
  log_query_source
120
132
  end
121
133
  end
@@ -131,6 +143,10 @@ module ActiveRecord
131
143
  def extract_query_source_location(locations)
132
144
  backtrace_cleaner.clean(locations.lazy).first
133
145
  end
146
+
147
+ def filter(name, value)
148
+ ActiveRecord::Base.inspection_filter.filter_param(name, value)
149
+ end
134
150
  end
135
151
  end
136
152
 
@@ -50,23 +50,19 @@ module ActiveRecord
50
50
 
51
51
  private
52
52
  def read_from_primary(&blk)
53
- ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
54
- instrumenter.instrument("database_selector.active_record.read_from_primary") do
55
- yield
56
- end
53
+ ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: true) do
54
+ instrumenter.instrument("database_selector.active_record.read_from_primary", &blk)
57
55
  end
58
56
  end
59
57
 
60
58
  def read_from_replica(&blk)
61
- ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do
62
- instrumenter.instrument("database_selector.active_record.read_from_replica") do
63
- yield
64
- end
59
+ ActiveRecord::Base.connected_to(role: ActiveRecord.reading_role, prevent_writes: true) do
60
+ instrumenter.instrument("database_selector.active_record.read_from_replica", &blk)
65
61
  end
66
62
  end
67
63
 
68
- def write_to_primary(&blk)
69
- ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
64
+ def write_to_primary
65
+ ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: false) do
70
66
  instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
71
67
  yield
72
68
  ensure
@@ -19,14 +19,22 @@ module ActiveRecord
19
19
  # that informs the application when to read from a primary or read from a
20
20
  # replica.
21
21
  #
22
- # To use the DatabaseSelector in your application with default settings add
23
- # the following options to your environment config:
22
+ # To use the DatabaseSelector in your application with default settings,
23
+ # run the provided generator.
24
24
  #
25
- # config.active_record.database_selector = { delay: 2.seconds }
26
- # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
27
- # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
25
+ # bin/rails g active_record:multi_db
26
+ #
27
+ # This will create a file named +config/initializers/multi_db.rb+ with the
28
+ # following contents:
28
29
  #
29
- # New applications will include these lines commented out in the production.rb.
30
+ # Rails.application.configure do
31
+ # config.active_record.database_selector = { delay: 2.seconds }
32
+ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
33
+ # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
34
+ # end
35
+ #
36
+ # Alternatively you can set the options in your environment config or
37
+ # any other config file loaded on boot.
30
38
  #
31
39
  # The default behavior can be changed by setting the config options to a
32
40
  # custom class:
@@ -34,6 +42,10 @@ module ActiveRecord
34
42
  # config.active_record.database_selector = { delay: 2.seconds }
35
43
  # config.active_record.database_resolver = MyResolver
36
44
  # config.active_record.database_resolver_context = MyResolver::MySession
45
+ #
46
+ # Note: If you are using `rails new my_app --minimal` you will need to call
47
+ # `require "active_support/core_ext/integer/time"` to load the libraries
48
+ # for +Time+.
37
49
  class DatabaseSelector
38
50
  def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
39
51
  @app = app
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Middleware
5
+ # The ShardSelector Middleware provides a framework for automatically
6
+ # swapping shards. Rails provides a basic framework to determine which
7
+ # shard to switch to and allows for applications to write custom strategies
8
+ # for swapping if needed.
9
+ #
10
+ # The ShardSelector takes a set of options (currently only +lock+ is supported)
11
+ # that can be used by the middleware to alter behavior. +lock+ is
12
+ # true by default and will prohibit the request from switching shards once
13
+ # inside the block. If +lock+ is false, then shard swapping will be allowed.
14
+ # For tenant based sharding, +lock+ should always be true to prevent application
15
+ # code from mistakenly switching between tenants.
16
+ #
17
+ # Options can be set in the config:
18
+ #
19
+ # config.active_record.shard_selector = { lock: true }
20
+ #
21
+ # Applications must also provide the code for the resolver as it depends on application
22
+ # specific models. An example resolver would look like this:
23
+ #
24
+ # config.active_record.shard_resolver = ->(request) {
25
+ # subdomain = request.subdomain
26
+ # tenant = Tenant.find_by_subdomain!(subdomain)
27
+ # tenant.shard
28
+ # }
29
+ class ShardSelector
30
+ def initialize(app, resolver, options = {})
31
+ @app = app
32
+ @resolver = resolver
33
+ @options = options
34
+ end
35
+
36
+ attr_reader :resolver, :options
37
+
38
+ def call(env)
39
+ request = ActionDispatch::Request.new(env)
40
+
41
+ shard = selected_shard(request)
42
+
43
+ set_shard(shard) do
44
+ @app.call(env)
45
+ end
46
+ end
47
+
48
+ private
49
+ def selected_shard(request)
50
+ resolver.call(request)
51
+ end
52
+
53
+ def set_shard(shard, &block)
54
+ ActiveRecord::Base.connected_to(shard: shard.to_sym) do
55
+ ActiveRecord::Base.prohibit_shard_swapping(options.fetch(:lock, true), &block)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -13,10 +13,10 @@ module ActiveRecord
13
13
  # * add_reference
14
14
  # * add_timestamps
15
15
  # * change_column
16
- # * change_column_default (must supply a :from and :to option)
16
+ # * change_column_default (must supply a +:from+ and +:to+ option)
17
17
  # * change_column_null
18
- # * change_column_comment (must supply a :from and :to option)
19
- # * change_table_comment (must supply a :from and :to option)
18
+ # * change_column_comment (must supply a +:from+ and +:to+ option)
19
+ # * change_table_comment (must supply a +:from+ and +:to+ option)
20
20
  # * create_join_table
21
21
  # * create_table
22
22
  # * disable_extension
@@ -112,7 +112,7 @@ module ActiveRecord
112
112
  record(:"#{method}", args, &block) # record(:create_table, args, &block)
113
113
  end # end
114
114
  EOV
115
- ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
115
+ ruby2_keywords(method)
116
116
  end
117
117
  alias :add_belongs_to :add_reference
118
118
  alias :remove_belongs_to :remove_reference
@@ -154,9 +154,9 @@ module ActiveRecord
154
154
 
155
155
  include StraightReversions
156
156
 
157
- def invert_transaction(args)
157
+ def invert_transaction(args, &block)
158
158
  sub_recorder = CommandRecorder.new(delegate)
159
- sub_recorder.revert { yield }
159
+ sub_recorder.revert(&block)
160
160
 
161
161
  invertions_proc = proc {
162
162
  sub_recorder.replay(self)
@@ -286,7 +286,7 @@ module ActiveRecord
286
286
  super
287
287
  end
288
288
  end
289
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
289
+ ruby2_keywords(:method_missing)
290
290
  end
291
291
  end
292
292
  end