activerecord 6.1.7.4 → 7.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1449 -1014
  3. data/README.rdoc +3 -3
  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 +14 -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 +50 -14
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +15 -7
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +138 -100
  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 +8 -6
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +19 -22
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +17 -28
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +14 -16
  47. data/lib/active_record/coders/yaml_column.rb +4 -8
  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 +52 -23
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +82 -25
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +153 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -84
  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 +45 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +4 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  68. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  70. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +19 -1
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -17
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/quoting.rb +71 -71
  83. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  86. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +40 -21
  88. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  89. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -106
  90. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +97 -32
  95. data/lib/active_record/connection_adapters.rb +6 -5
  96. data/lib/active_record/connection_handling.rb +49 -55
  97. data/lib/active_record/core.rb +123 -148
  98. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  99. data/lib/active_record/database_configurations/database_config.rb +12 -9
  100. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  101. data/lib/active_record/database_configurations/url_config.rb +2 -2
  102. data/lib/active_record/database_configurations.rb +15 -32
  103. data/lib/active_record/delegated_type.rb +53 -12
  104. data/lib/active_record/destroy_association_async_job.rb +1 -1
  105. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  106. data/lib/active_record/dynamic_matchers.rb +1 -1
  107. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  108. data/lib/active_record/encryption/cipher.rb +53 -0
  109. data/lib/active_record/encryption/config.rb +44 -0
  110. data/lib/active_record/encryption/configurable.rb +67 -0
  111. data/lib/active_record/encryption/context.rb +35 -0
  112. data/lib/active_record/encryption/contexts.rb +72 -0
  113. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  114. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  115. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  116. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  117. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  118. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  119. data/lib/active_record/encryption/encryptor.rb +155 -0
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  121. data/lib/active_record/encryption/errors.rb +15 -0
  122. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  123. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  124. data/lib/active_record/encryption/key.rb +28 -0
  125. data/lib/active_record/encryption/key_generator.rb +42 -0
  126. data/lib/active_record/encryption/key_provider.rb +46 -0
  127. data/lib/active_record/encryption/message.rb +33 -0
  128. data/lib/active_record/encryption/message_serializer.rb +90 -0
  129. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  130. data/lib/active_record/encryption/properties.rb +76 -0
  131. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  132. data/lib/active_record/encryption/scheme.rb +99 -0
  133. data/lib/active_record/encryption.rb +55 -0
  134. data/lib/active_record/enum.rb +50 -43
  135. data/lib/active_record/errors.rb +67 -4
  136. data/lib/active_record/explain_registry.rb +11 -6
  137. data/lib/active_record/explain_subscriber.rb +1 -1
  138. data/lib/active_record/fixture_set/file.rb +15 -1
  139. data/lib/active_record/fixture_set/table_row.rb +41 -6
  140. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  141. data/lib/active_record/fixtures.rb +20 -23
  142. data/lib/active_record/future_result.rb +139 -0
  143. data/lib/active_record/gem_version.rb +5 -5
  144. data/lib/active_record/inheritance.rb +55 -17
  145. data/lib/active_record/insert_all.rb +80 -14
  146. data/lib/active_record/integration.rb +4 -3
  147. data/lib/active_record/internal_metadata.rb +1 -5
  148. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  149. data/lib/active_record/locking/optimistic.rb +36 -21
  150. data/lib/active_record/locking/pessimistic.rb +10 -4
  151. data/lib/active_record/log_subscriber.rb +23 -7
  152. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  153. data/lib/active_record/middleware/database_selector.rb +18 -6
  154. data/lib/active_record/middleware/shard_selector.rb +60 -0
  155. data/lib/active_record/migration/command_recorder.rb +8 -9
  156. data/lib/active_record/migration/compatibility.rb +91 -2
  157. data/lib/active_record/migration/join_table.rb +1 -1
  158. data/lib/active_record/migration.rb +115 -84
  159. data/lib/active_record/model_schema.rb +58 -59
  160. data/lib/active_record/nested_attributes.rb +13 -12
  161. data/lib/active_record/no_touching.rb +3 -3
  162. data/lib/active_record/null_relation.rb +2 -6
  163. data/lib/active_record/persistence.rb +228 -60
  164. data/lib/active_record/query_cache.rb +2 -2
  165. data/lib/active_record/query_logs.rb +149 -0
  166. data/lib/active_record/querying.rb +16 -6
  167. data/lib/active_record/railtie.rb +136 -22
  168. data/lib/active_record/railties/controller_runtime.rb +1 -1
  169. data/lib/active_record/railties/databases.rake +78 -136
  170. data/lib/active_record/readonly_attributes.rb +11 -0
  171. data/lib/active_record/reflection.rb +80 -49
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  173. data/lib/active_record/relation/batches.rb +6 -6
  174. data/lib/active_record/relation/calculations.rb +92 -60
  175. data/lib/active_record/relation/delegation.rb +7 -7
  176. data/lib/active_record/relation/finder_methods.rb +31 -35
  177. data/lib/active_record/relation/merger.rb +20 -13
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -1
  179. data/lib/active_record/relation/predicate_builder.rb +2 -6
  180. data/lib/active_record/relation/query_attribute.rb +5 -11
  181. data/lib/active_record/relation/query_methods.rb +285 -68
  182. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  183. data/lib/active_record/relation/spawn_methods.rb +2 -2
  184. data/lib/active_record/relation/where_clause.rb +10 -19
  185. data/lib/active_record/relation.rb +189 -88
  186. data/lib/active_record/result.rb +23 -11
  187. data/lib/active_record/runtime_registry.rb +9 -13
  188. data/lib/active_record/sanitization.rb +17 -12
  189. data/lib/active_record/schema.rb +38 -23
  190. data/lib/active_record/schema_dumper.rb +29 -19
  191. data/lib/active_record/schema_migration.rb +4 -4
  192. data/lib/active_record/scoping/default.rb +60 -13
  193. data/lib/active_record/scoping/named.rb +3 -11
  194. data/lib/active_record/scoping.rb +64 -34
  195. data/lib/active_record/serialization.rb +6 -1
  196. data/lib/active_record/signed_id.rb +3 -3
  197. data/lib/active_record/store.rb +2 -2
  198. data/lib/active_record/suppressor.rb +11 -15
  199. data/lib/active_record/table_metadata.rb +5 -1
  200. data/lib/active_record/tasks/database_tasks.rb +127 -60
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  203. data/lib/active_record/test_databases.rb +1 -1
  204. data/lib/active_record/test_fixtures.rb +9 -6
  205. data/lib/active_record/timestamp.rb +3 -4
  206. data/lib/active_record/transactions.rb +9 -14
  207. data/lib/active_record/translation.rb +3 -3
  208. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  209. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  210. data/lib/active_record/type/internal/timezone.rb +2 -2
  211. data/lib/active_record/type/serialized.rb +5 -5
  212. data/lib/active_record/type/type_map.rb +17 -20
  213. data/lib/active_record/type.rb +1 -2
  214. data/lib/active_record/validations/associated.rb +4 -4
  215. data/lib/active_record/validations/presence.rb +2 -2
  216. data/lib/active_record/validations/uniqueness.rb +4 -4
  217. data/lib/active_record/version.rb +1 -1
  218. data/lib/active_record.rb +225 -27
  219. data/lib/arel/attributes/attribute.rb +0 -8
  220. data/lib/arel/crud.rb +28 -22
  221. data/lib/arel/delete_manager.rb +18 -4
  222. data/lib/arel/filter_predications.rb +9 -0
  223. data/lib/arel/insert_manager.rb +2 -3
  224. data/lib/arel/nodes/casted.rb +1 -1
  225. data/lib/arel/nodes/delete_statement.rb +12 -13
  226. data/lib/arel/nodes/filter.rb +10 -0
  227. data/lib/arel/nodes/function.rb +1 -0
  228. data/lib/arel/nodes/insert_statement.rb +2 -2
  229. data/lib/arel/nodes/select_core.rb +2 -2
  230. data/lib/arel/nodes/select_statement.rb +2 -2
  231. data/lib/arel/nodes/update_statement.rb +8 -3
  232. data/lib/arel/nodes.rb +1 -0
  233. data/lib/arel/predications.rb +11 -3
  234. data/lib/arel/select_manager.rb +10 -4
  235. data/lib/arel/table.rb +0 -1
  236. data/lib/arel/tree_manager.rb +0 -12
  237. data/lib/arel/update_manager.rb +18 -4
  238. data/lib/arel/visitors/dot.rb +80 -90
  239. data/lib/arel/visitors/mysql.rb +8 -2
  240. data/lib/arel/visitors/postgresql.rb +0 -10
  241. data/lib/arel/visitors/to_sql.rb +58 -2
  242. data/lib/arel.rb +2 -1
  243. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  244. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  245. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  246. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  247. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  248. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  249. metadata +58 -14
@@ -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
@@ -2,14 +2,14 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Locking
5
- # == What is Optimistic Locking
5
+ # == What is \Optimistic \Locking
6
6
  #
7
7
  # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
8
8
  # conflicts with the data. It does this by checking whether another process has made changes to a record since
9
- # it was opened, an <tt>ActiveRecord::StaleObjectError</tt> exception is thrown if that has occurred
9
+ # it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
10
10
  # and the update is ignored.
11
11
  #
12
- # Check out <tt>ActiveRecord::Locking::Pessimistic</tt> for an alternative.
12
+ # Check out +ActiveRecord::Locking::Pessimistic+ for an alternative.
13
13
  #
14
14
  # == Usage
15
15
  #
@@ -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
@@ -69,6 +69,11 @@ module ActiveRecord
69
69
  end
70
70
  end
71
71
 
72
+ def initialize_dup(other) # :nodoc:
73
+ super
74
+ _clear_locking_column if locking_enabled?
75
+ end
76
+
72
77
  private
73
78
  def _create_record(attribute_names = self.attribute_names)
74
79
  if locking_enabled?
@@ -90,7 +95,8 @@ module ActiveRecord
90
95
  begin
91
96
  locking_column = self.class.locking_column
92
97
  lock_attribute_was = @attributes[locking_column]
93
- lock_value_for_database = _lock_value_for_database(locking_column)
98
+
99
+ update_constraints = _query_constraints_hash
94
100
 
95
101
  attribute_names = attribute_names.dup if attribute_names.frozen?
96
102
  attribute_names << locking_column
@@ -99,8 +105,7 @@ module ActiveRecord
99
105
 
100
106
  affected_rows = self.class._update_record(
101
107
  attributes_with_values(attribute_names),
102
- @primary_key => id_in_database,
103
- locking_column => lock_value_for_database
108
+ update_constraints
104
109
  )
105
110
 
106
111
  if affected_rows != 1
@@ -117,16 +122,9 @@ module ActiveRecord
117
122
  end
118
123
 
119
124
  def destroy_row
120
- return super unless locking_enabled?
125
+ affected_rows = super
121
126
 
122
- locking_column = self.class.locking_column
123
-
124
- affected_rows = self.class._delete_record(
125
- @primary_key => id_in_database,
126
- locking_column => _lock_value_for_database(locking_column)
127
- )
128
-
129
- if affected_rows != 1
127
+ if locking_enabled? && affected_rows != 1
130
128
  raise ActiveRecord::StaleObjectError.new(self, "destroy")
131
129
  end
132
130
 
@@ -141,6 +139,18 @@ module ActiveRecord
141
139
  end
142
140
  end
143
141
 
142
+ def _clear_locking_column
143
+ self[self.class.locking_column] = nil
144
+ clear_attribute_change(self.class.locking_column)
145
+ end
146
+
147
+ def _query_constraints_hash
148
+ return super unless locking_enabled?
149
+
150
+ locking_column = self.class.locking_column
151
+ super.merge(locking_column => _lock_value_for_database(locking_column))
152
+ end
153
+
144
154
  module ClassMethods
145
155
  DEFAULT_LOCKING_COLUMN = "lock_version"
146
156
 
@@ -158,10 +168,7 @@ module ActiveRecord
158
168
  end
159
169
 
160
170
  # The version column used for optimistic locking. Defaults to +lock_version+.
161
- def locking_column
162
- @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
163
- @locking_column
164
- end
171
+ attr_reader :locking_column
165
172
 
166
173
  # Reset the column used for optimistic locking back to the +lock_version+ default.
167
174
  def reset_locking_column
@@ -181,6 +188,14 @@ module ActiveRecord
181
188
  end
182
189
  super
183
190
  end
191
+
192
+ private
193
+ def inherited(base)
194
+ super
195
+ base.class_eval do
196
+ @locking_column = DEFAULT_LOCKING_COLUMN
197
+ end
198
+ end
184
199
  end
185
200
  end
186
201
 
@@ -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 <tt>rails new my_app --minimal</tt> you will need
47
+ # to call <tt>require "active_support/core_ext/integer/time"</tt> to load
48
+ # the core extension in order to use +2.seconds+
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
@@ -12,11 +12,10 @@ module ActiveRecord
12
12
  # * add_index
13
13
  # * add_reference
14
14
  # * add_timestamps
15
- # * change_column
16
- # * change_column_default (must supply a :from and :to option)
15
+ # * change_column_default (must supply a +:from+ and +:to+ option)
17
16
  # * 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)
17
+ # * change_column_comment (must supply a +:from+ and +:to+ option)
18
+ # * change_table_comment (must supply a +:from+ and +:to+ option)
20
19
  # * create_join_table
21
20
  # * create_table
22
21
  # * disable_extension
@@ -24,7 +23,7 @@ module ActiveRecord
24
23
  # * drop_table (must supply a block)
25
24
  # * enable_extension
26
25
  # * remove_column (must supply a type)
27
- # * remove_columns (must specify at least one column name or more)
26
+ # * remove_columns (must supply a +:type+ option)
28
27
  # * remove_foreign_key (must supply a second table)
29
28
  # * remove_check_constraint
30
29
  # * remove_index
@@ -112,7 +111,7 @@ module ActiveRecord
112
111
  record(:"#{method}", args, &block) # record(:create_table, args, &block)
113
112
  end # end
114
113
  EOV
115
- ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
114
+ ruby2_keywords(method)
116
115
  end
117
116
  alias :add_belongs_to :add_reference
118
117
  alias :remove_belongs_to :remove_reference
@@ -154,9 +153,9 @@ module ActiveRecord
154
153
 
155
154
  include StraightReversions
156
155
 
157
- def invert_transaction(args)
156
+ def invert_transaction(args, &block)
158
157
  sub_recorder = CommandRecorder.new(delegate)
159
- sub_recorder.revert { yield }
158
+ sub_recorder.revert(&block)
160
159
 
161
160
  invertions_proc = proc {
162
161
  sub_recorder.replay(self)
@@ -286,7 +285,7 @@ module ActiveRecord
286
285
  super
287
286
  end
288
287
  end
289
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
288
+ ruby2_keywords(:method_missing)
290
289
  end
291
290
  end
292
291
  end