activerecord 7.0.8.1 → 7.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (234) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1780 -1437
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +19 -13
  15. data/lib/active_record/associations/collection_proxy.rb +15 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +6 -6
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +319 -217
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +21 -8
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +6 -6
  34. data/lib/active_record/attribute_methods.rb +145 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +59 -10
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +296 -127
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  74. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  77. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  78. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  79. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +362 -61
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  82. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  83. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  84. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  85. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  86. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  87. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  88. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -81
  89. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  90. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  91. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  92. data/lib/active_record/connection_adapters.rb +3 -1
  93. data/lib/active_record/connection_handling.rb +72 -95
  94. data/lib/active_record/core.rb +180 -153
  95. data/lib/active_record/counter_cache.rb +52 -27
  96. data/lib/active_record/database_configurations/database_config.rb +9 -3
  97. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  98. data/lib/active_record/database_configurations/url_config.rb +17 -11
  99. data/lib/active_record/database_configurations.rb +86 -33
  100. data/lib/active_record/delegated_type.rb +15 -10
  101. data/lib/active_record/deprecator.rb +7 -0
  102. data/lib/active_record/destroy_association_async_job.rb +3 -1
  103. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  105. data/lib/active_record/encryption/config.rb +25 -1
  106. data/lib/active_record/encryption/configurable.rb +12 -19
  107. data/lib/active_record/encryption/context.rb +10 -3
  108. data/lib/active_record/encryption/contexts.rb +5 -1
  109. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  110. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  111. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  113. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  114. data/lib/active_record/encryption/key_generator.rb +12 -1
  115. data/lib/active_record/encryption/message_serializer.rb +2 -0
  116. data/lib/active_record/encryption/properties.rb +3 -3
  117. data/lib/active_record/encryption/scheme.rb +22 -21
  118. data/lib/active_record/encryption.rb +1 -0
  119. data/lib/active_record/enum.rb +112 -28
  120. data/lib/active_record/errors.rb +112 -18
  121. data/lib/active_record/explain.rb +23 -3
  122. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  123. data/lib/active_record/fixture_set/render_context.rb +2 -0
  124. data/lib/active_record/fixture_set/table_row.rb +29 -8
  125. data/lib/active_record/fixtures.rb +135 -71
  126. data/lib/active_record/future_result.rb +40 -5
  127. data/lib/active_record/gem_version.rb +4 -4
  128. data/lib/active_record/inheritance.rb +30 -16
  129. data/lib/active_record/insert_all.rb +57 -10
  130. data/lib/active_record/integration.rb +8 -8
  131. data/lib/active_record/internal_metadata.rb +120 -30
  132. data/lib/active_record/locking/optimistic.rb +1 -1
  133. data/lib/active_record/locking/pessimistic.rb +5 -2
  134. data/lib/active_record/log_subscriber.rb +29 -12
  135. data/lib/active_record/marshalling.rb +56 -0
  136. data/lib/active_record/message_pack.rb +124 -0
  137. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  138. data/lib/active_record/middleware/database_selector.rb +6 -8
  139. data/lib/active_record/middleware/shard_selector.rb +3 -1
  140. data/lib/active_record/migration/command_recorder.rb +104 -5
  141. data/lib/active_record/migration/compatibility.rb +145 -5
  142. data/lib/active_record/migration/default_strategy.rb +23 -0
  143. data/lib/active_record/migration/execution_strategy.rb +19 -0
  144. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  145. data/lib/active_record/migration.rb +219 -111
  146. data/lib/active_record/model_schema.rb +68 -44
  147. data/lib/active_record/nested_attributes.rb +24 -6
  148. data/lib/active_record/normalization.rb +167 -0
  149. data/lib/active_record/persistence.rb +188 -37
  150. data/lib/active_record/promise.rb +84 -0
  151. data/lib/active_record/query_cache.rb +4 -22
  152. data/lib/active_record/query_logs.rb +77 -52
  153. data/lib/active_record/query_logs_formatter.rb +41 -0
  154. data/lib/active_record/querying.rb +15 -2
  155. data/lib/active_record/railtie.rb +105 -46
  156. data/lib/active_record/railties/controller_runtime.rb +12 -6
  157. data/lib/active_record/railties/databases.rake +144 -150
  158. data/lib/active_record/railties/job_runtime.rb +23 -0
  159. data/lib/active_record/readonly_attributes.rb +32 -5
  160. data/lib/active_record/reflection.rb +181 -45
  161. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  162. data/lib/active_record/relation/batches.rb +190 -61
  163. data/lib/active_record/relation/calculations.rb +187 -63
  164. data/lib/active_record/relation/delegation.rb +23 -9
  165. data/lib/active_record/relation/finder_methods.rb +77 -16
  166. data/lib/active_record/relation/merger.rb +2 -0
  167. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  168. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  169. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  170. data/lib/active_record/relation/predicate_builder.rb +26 -14
  171. data/lib/active_record/relation/query_attribute.rb +2 -1
  172. data/lib/active_record/relation/query_methods.rb +371 -68
  173. data/lib/active_record/relation/spawn_methods.rb +18 -1
  174. data/lib/active_record/relation.rb +103 -37
  175. data/lib/active_record/result.rb +19 -5
  176. data/lib/active_record/runtime_registry.rb +24 -1
  177. data/lib/active_record/sanitization.rb +51 -11
  178. data/lib/active_record/schema.rb +2 -3
  179. data/lib/active_record/schema_dumper.rb +46 -7
  180. data/lib/active_record/schema_migration.rb +68 -33
  181. data/lib/active_record/scoping/default.rb +15 -5
  182. data/lib/active_record/scoping/named.rb +2 -2
  183. data/lib/active_record/scoping.rb +2 -1
  184. data/lib/active_record/secure_password.rb +60 -0
  185. data/lib/active_record/secure_token.rb +21 -3
  186. data/lib/active_record/signed_id.rb +7 -5
  187. data/lib/active_record/store.rb +8 -8
  188. data/lib/active_record/suppressor.rb +3 -1
  189. data/lib/active_record/table_metadata.rb +10 -1
  190. data/lib/active_record/tasks/database_tasks.rb +142 -109
  191. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  192. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  193. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  194. data/lib/active_record/test_fixtures.rb +114 -96
  195. data/lib/active_record/timestamp.rb +30 -16
  196. data/lib/active_record/token_for.rb +113 -0
  197. data/lib/active_record/touch_later.rb +11 -6
  198. data/lib/active_record/transactions.rb +36 -10
  199. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  200. data/lib/active_record/type/internal/timezone.rb +7 -2
  201. data/lib/active_record/type/time.rb +4 -0
  202. data/lib/active_record/validations/absence.rb +1 -1
  203. data/lib/active_record/validations/numericality.rb +5 -4
  204. data/lib/active_record/validations/presence.rb +5 -28
  205. data/lib/active_record/validations/uniqueness.rb +47 -2
  206. data/lib/active_record/validations.rb +8 -4
  207. data/lib/active_record/version.rb +1 -1
  208. data/lib/active_record.rb +122 -17
  209. data/lib/arel/errors.rb +10 -0
  210. data/lib/arel/factory_methods.rb +4 -0
  211. data/lib/arel/nodes/binary.rb +6 -1
  212. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  213. data/lib/arel/nodes/cte.rb +36 -0
  214. data/lib/arel/nodes/fragments.rb +35 -0
  215. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  216. data/lib/arel/nodes/leading_join.rb +8 -0
  217. data/lib/arel/nodes/node.rb +111 -2
  218. data/lib/arel/nodes/sql_literal.rb +6 -0
  219. data/lib/arel/nodes/table_alias.rb +4 -0
  220. data/lib/arel/nodes.rb +4 -0
  221. data/lib/arel/predications.rb +2 -0
  222. data/lib/arel/table.rb +9 -5
  223. data/lib/arel/tree_manager.rb +5 -1
  224. data/lib/arel/visitors/mysql.rb +8 -1
  225. data/lib/arel/visitors/to_sql.rb +83 -18
  226. data/lib/arel/visitors/visitor.rb +2 -2
  227. data/lib/arel.rb +16 -2
  228. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  229. data/lib/rails/generators/active_record/migration.rb +3 -1
  230. data/lib/rails/generators/active_record/model/USAGE +113 -0
  231. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  232. metadata +48 -12
  233. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  234. data/lib/active_record/null_relation.rb +0 -63
@@ -5,10 +5,13 @@ require "concurrent/map"
5
5
  module ActiveRecord
6
6
  module ConnectionAdapters # :nodoc:
7
7
  module QueryCache
8
+ DEFAULT_SIZE = 100 # :nodoc:
9
+
8
10
  class << self
9
11
  def included(base) # :nodoc:
10
- dirties_query_cache base, :create, :insert, :update, :delete, :truncate, :truncate_tables,
11
- :rollback_to_savepoint, :rollback_db_transaction, :exec_insert_all
12
+ dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
13
+ :truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
14
+ :exec_insert_all
12
15
 
13
16
  base.set_callback :checkout, :after, :configure_query_cache!
14
17
  base.set_callback :checkin, :after, :disable_query_cache!
@@ -17,7 +20,7 @@ module ActiveRecord
17
20
  def dirties_query_cache(base, *method_names)
18
21
  method_names.each do |method_name|
19
22
  base.class_eval <<-end_code, __FILE__, __LINE__ + 1
20
- def #{method_name}(*)
23
+ def #{method_name}(...)
21
24
  ActiveRecord::Base.clear_query_caches_for_current_thread
22
25
  super
23
26
  end
@@ -51,8 +54,9 @@ module ActiveRecord
51
54
 
52
55
  def initialize(*)
53
56
  super
54
- @query_cache = Hash.new { |h, sql| h[sql] = {} }
57
+ @query_cache = {}
55
58
  @query_cache_enabled = false
59
+ @query_cache_max_size = nil
56
60
  end
57
61
 
58
62
  # Enable the query cache within the block.
@@ -93,7 +97,7 @@ module ActiveRecord
93
97
  end
94
98
  end
95
99
 
96
- def select_all(arel, name = nil, binds = [], preparable: nil, async: false)
100
+ def select_all(arel, name = nil, binds = [], preparable: nil, async: false) # :nodoc:
97
101
  arel = arel_from_relation(arel)
98
102
 
99
103
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
@@ -102,7 +106,8 @@ module ActiveRecord
102
106
  sql, binds, preparable = to_sql_and_binds(arel, binds, preparable)
103
107
 
104
108
  if async
105
- lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
109
+ result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async)
110
+ FutureResult.wrap(result)
106
111
  else
107
112
  cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable, async: async) }
108
113
  end
@@ -113,31 +118,52 @@ module ActiveRecord
113
118
 
114
119
  private
115
120
  def lookup_sql_cache(sql, name, binds)
121
+ key = binds.empty? ? sql : [sql, binds]
122
+ hit = false
123
+ result = nil
124
+
116
125
  @lock.synchronize do
117
- if @query_cache[sql].key?(binds)
118
- ActiveSupport::Notifications.instrument(
119
- "sql.active_record",
120
- cache_notification_info(sql, name, binds)
121
- )
122
- @query_cache[sql][binds]
126
+ if (result = @query_cache.delete(key))
127
+ hit = true
128
+ @query_cache[key] = result
123
129
  end
124
130
  end
131
+
132
+ if hit
133
+ ActiveSupport::Notifications.instrument(
134
+ "sql.active_record",
135
+ cache_notification_info(sql, name, binds)
136
+ )
137
+
138
+ result
139
+ end
125
140
  end
126
141
 
127
142
  def cache_sql(sql, name, binds)
143
+ key = binds.empty? ? sql : [sql, binds]
144
+ result = nil
145
+ hit = false
146
+
128
147
  @lock.synchronize do
129
- result =
130
- if @query_cache[sql].key?(binds)
131
- ActiveSupport::Notifications.instrument(
132
- "sql.active_record",
133
- cache_notification_info(sql, name, binds)
134
- )
135
- @query_cache[sql][binds]
136
- else
137
- @query_cache[sql][binds] = yield
148
+ if (result = @query_cache.delete(key))
149
+ hit = true
150
+ @query_cache[key] = result
151
+ else
152
+ result = @query_cache[key] = yield
153
+ if @query_cache_max_size && @query_cache.size > @query_cache_max_size
154
+ @query_cache.shift
138
155
  end
139
- result.dup
156
+ end
140
157
  end
158
+
159
+ if hit
160
+ ActiveSupport::Notifications.instrument(
161
+ "sql.active_record",
162
+ cache_notification_info(sql, name, binds)
163
+ )
164
+ end
165
+
166
+ result.dup
141
167
  end
142
168
 
143
169
  # Database adapters can override this method to
@@ -154,7 +180,20 @@ module ActiveRecord
154
180
  end
155
181
 
156
182
  def configure_query_cache!
157
- enable_query_cache! if pool.query_cache_enabled
183
+ case query_cache = pool.db_config.query_cache
184
+ when 0, false
185
+ return
186
+ when Integer
187
+ @query_cache_max_size = query_cache
188
+ when nil
189
+ @query_cache_max_size = DEFAULT_SIZE
190
+ else
191
+ @query_cache_max_size = nil # no limit
192
+ end
193
+
194
+ if pool.query_cache_enabled
195
+ enable_query_cache!
196
+ end
158
197
  end
159
198
  end
160
199
  end
@@ -5,6 +5,7 @@ require "active_support/multibyte/chars"
5
5
 
6
6
  module ActiveRecord
7
7
  module ConnectionAdapters # :nodoc:
8
+ # = Active Record Connection Adapters \Quoting
8
9
  module Quoting
9
10
  # Quotes the column value to help prevent
10
11
  # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection].
@@ -17,11 +18,14 @@ module ActiveRecord
17
18
  when nil then "NULL"
18
19
  # BigDecimals need to be put in a non-normalized form and quoted.
19
20
  when BigDecimal then value.to_s("F")
20
- when Numeric, ActiveSupport::Duration then value.to_s
21
+ when Numeric then value.to_s
21
22
  when Type::Binary::Data then quoted_binary(value)
22
23
  when Type::Time::Value then "'#{quoted_time(value)}'"
23
24
  when Date, Time then "'#{quoted_date(value)}'"
24
25
  when Class then "'#{value}'"
26
+ when ActiveSupport::Duration
27
+ warn_quote_duration_deprecated
28
+ value.to_s
25
29
  else raise TypeError, "can't quote #{value.class.name}"
26
30
  end
27
31
  end
@@ -47,8 +51,23 @@ module ActiveRecord
47
51
  # Quote a value to be used as a bound parameter of unknown type. For example,
48
52
  # MySQL might perform dangerous castings when comparing a string to a number,
49
53
  # so this method will cast numbers to string.
54
+ #
55
+ # Deprecated: Consider `Arel.sql("... ? ...", value)` or
56
+ # +sanitize_sql+ instead.
50
57
  def quote_bound_value(value)
51
- quote(value)
58
+ ActiveRecord.deprecator.warn(<<~MSG.squish)
59
+ #quote_bound_value is deprecated and will be removed in Rails 7.2.
60
+ Consider Arel.sql(".. ? ..", value) or #sanitize_sql instead.
61
+ MSG
62
+
63
+ quote(cast_bound_value(value))
64
+ end
65
+
66
+ # Cast a value to be used as a bound parameter of unknown type. For example,
67
+ # MySQL might perform dangerous castings when comparing a string to a number,
68
+ # so this method will cast numbers to string.
69
+ def cast_bound_value(value) # :nodoc:
70
+ value
52
71
  end
53
72
 
54
73
  # If you are having to call this function, you are likely doing something
@@ -83,7 +102,7 @@ module ActiveRecord
83
102
  # Override to return the quoted table name for assignment. Defaults to
84
103
  # table quoting.
85
104
  #
86
- # This works for mysql2 where table.column can be used to
105
+ # This works for MySQL where table.column can be used to
87
106
  # resolve ambiguity.
88
107
  #
89
108
  # We override this in the sqlite3 and postgresql adapters to use only
@@ -121,7 +140,7 @@ module ActiveRecord
121
140
  # if the value is a Time responding to usec.
122
141
  def quoted_date(value)
123
142
  if value.acts_like?(:time)
124
- if ActiveRecord.default_timezone == :utc
143
+ if default_timezone == :utc
125
144
  value = value.getutc if !value.utc?
126
145
  else
127
146
  value = value.getlocal
@@ -176,7 +195,7 @@ module ActiveRecord
176
195
  (
177
196
  (?:
178
197
  # table_name.column_name | function(one or no argument)
179
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
198
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
180
199
  )
181
200
  (?:(?:\s+AS)?\s+\w+)?
182
201
  )
@@ -200,7 +219,7 @@ module ActiveRecord
200
219
  (
201
220
  (?:
202
221
  # table_name.column_name | function(one or no argument)
203
- ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
222
+ ((?:\w+\.)?\w+ | \w+\((?:|\g<2>)\))
204
223
  )
205
224
  (?:\s+ASC|\s+DESC)?
206
225
  (?:\s+NULLS\s+(?:FIRST|LAST))?
@@ -225,6 +244,22 @@ module ActiveRecord
225
244
  def lookup_cast_type(sql_type)
226
245
  type_map.lookup(sql_type)
227
246
  end
247
+
248
+ def warn_quote_duration_deprecated
249
+ ActiveRecord.deprecator.warn(<<~MSG)
250
+ Using ActiveSupport::Duration as an interpolated bind parameter in a SQL
251
+ string template is deprecated. To avoid this warning, you should explicitly
252
+ convert the duration to a more specific database type. For example, if you
253
+ want to use a duration as an integer number of seconds:
254
+ ```
255
+ Record.where("duration = ?", 1.hour.to_i)
256
+ ```
257
+ If you want to use a duration as an ISO 8601 string:
258
+ ```
259
+ Record.where("duration = ?", 1.hour.iso8601)
260
+ ```
261
+ MSG
262
+ end
228
263
  end
229
264
  end
230
265
  end
@@ -2,21 +2,22 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
+ # = Active Record Connection Adapters \Savepoints
5
6
  module Savepoints
6
7
  def current_savepoint_name
7
8
  current_transaction.savepoint_name
8
9
  end
9
10
 
10
11
  def create_savepoint(name = current_savepoint_name)
11
- execute("SAVEPOINT #{name}", "TRANSACTION")
12
+ internal_execute("SAVEPOINT #{name}", "TRANSACTION")
12
13
  end
13
14
 
14
15
  def exec_rollback_to_savepoint(name = current_savepoint_name)
15
- execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
16
+ internal_execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
16
17
  end
17
18
 
18
19
  def release_savepoint(name = current_savepoint_name)
19
- execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
20
+ internal_execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
20
21
  end
21
22
  end
22
23
  end
@@ -14,8 +14,10 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
17
- :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?,
17
+ :options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
18
18
  :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
19
+ :supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?,
20
+ :supports_nulls_not_distinct?,
19
21
  to: :@conn, private: true
20
22
 
21
23
  private
@@ -51,7 +53,7 @@ module ActiveRecord
51
53
  statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
52
54
  end
53
55
 
54
- if supports_foreign_keys?
56
+ if use_foreign_keys?
55
57
  statements.concat(o.foreign_keys.map { |fk| accept fk })
56
58
  end
57
59
 
@@ -59,6 +61,14 @@ module ActiveRecord
59
61
  statements.concat(o.check_constraints.map { |chk| accept chk })
60
62
  end
61
63
 
64
+ if supports_exclusion_constraints?
65
+ statements.concat(o.exclusion_constraints.map { |exc| accept exc })
66
+ end
67
+
68
+ if supports_unique_constraints?
69
+ statements.concat(o.unique_constraints.map { |exc| accept exc })
70
+ end
71
+
62
72
  create_sql << "(#{statements.join(', ')})" if statements.present?
63
73
  add_table_options!(create_sql, o)
64
74
  create_sql << " AS #{to_sql(o.as)}" if o.as
@@ -70,10 +80,12 @@ module ActiveRecord
70
80
  end
71
81
 
72
82
  def visit_ForeignKeyDefinition(o)
83
+ quoted_columns = Array(o.column).map { |c| quote_column_name(c) }
84
+ quoted_primary_keys = Array(o.primary_key).map { |c| quote_column_name(c) }
73
85
  sql = +<<~SQL
74
86
  CONSTRAINT #{quote_column_name(o.name)}
75
- FOREIGN KEY (#{quote_column_name(o.column)})
76
- REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
87
+ FOREIGN KEY (#{quoted_columns.join(", ")})
88
+ REFERENCES #{quote_table_name(o.to_table)} (#{quoted_primary_keys.join(", ")})
77
89
  SQL
78
90
  sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
79
91
  sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
@@ -100,6 +112,8 @@ module ActiveRecord
100
112
  sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
101
113
  sql << "USING #{index.using}" if supports_index_using? && index.using
102
114
  sql << "(#{quoted_columns(index)})"
115
+ sql << "INCLUDE (#{quoted_include_columns(index.include)})" if supports_index_include? && index.include
116
+ sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && index.nulls_not_distinct
103
117
  sql << "WHERE #{index.where}" if supports_partial_index? && index.where
104
118
 
105
119
  sql.join(" ")
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+
3
4
  module ActiveRecord
4
5
  module ConnectionAdapters # :nodoc:
5
6
  # Abstract representation of an index definition on a table. Instances of
6
7
  # this type are typically created and returned by methods in database
7
8
  # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
8
9
  class IndexDefinition # :nodoc:
9
- attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment
10
+ attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :include, :nulls_not_distinct, :comment, :valid
10
11
 
11
12
  def initialize(
12
13
  table, name,
@@ -18,7 +19,10 @@ module ActiveRecord
18
19
  where: nil,
19
20
  type: nil,
20
21
  using: nil,
21
- comment: nil
22
+ include: nil,
23
+ nulls_not_distinct: nil,
24
+ comment: nil,
25
+ valid: true
22
26
  )
23
27
  @table = table
24
28
  @name = name
@@ -30,7 +34,14 @@ module ActiveRecord
30
34
  @where = where
31
35
  @type = type
32
36
  @using = using
37
+ @include = include
38
+ @nulls_not_distinct = nulls_not_distinct
33
39
  @comment = comment
40
+ @valid = valid
41
+ end
42
+
43
+ def valid?
44
+ @valid
34
45
  end
35
46
 
36
47
  def column_options
@@ -41,6 +52,16 @@ module ActiveRecord
41
52
  }
42
53
  end
43
54
 
55
+ def defined_for?(columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, **options)
56
+ columns = options[:column] if columns.blank?
57
+ (columns.nil? || Array(self.columns) == Array(columns).map(&:to_s)) &&
58
+ (name.nil? || self.name == name.to_s) &&
59
+ (unique.nil? || self.unique == unique) &&
60
+ (valid.nil? || self.valid == valid) &&
61
+ (include.nil? || Array(self.include) == Array(include).map(&:to_s)) &&
62
+ (nulls_not_distinct.nil? || self.nulls_not_distinct == nulls_not_distinct)
63
+ end
64
+
44
65
  private
45
66
  def concise_options(options)
46
67
  if columns.size == options.size && options.values.uniq.size == 1
@@ -56,11 +77,24 @@ module ActiveRecord
56
77
  # +columns+ attribute of said TableDefinition object, in order to be used
57
78
  # for generating a number of table creation or table changing SQL statements.
58
79
  ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
80
+ self::OPTION_NAMES = [
81
+ :limit,
82
+ :precision,
83
+ :scale,
84
+ :default,
85
+ :null,
86
+ :collation,
87
+ :comment,
88
+ :primary_key,
89
+ :if_exists,
90
+ :if_not_exists
91
+ ]
92
+
59
93
  def primary_key?
60
94
  options[:primary_key]
61
95
  end
62
96
 
63
- [:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name|
97
+ (self::OPTION_NAMES - [:primary_key]).each do |option_name|
64
98
  module_eval <<-CODE, __FILE__, __LINE__ + 1
65
99
  def #{option_name}
66
100
  options[:#{option_name}]
@@ -81,6 +115,8 @@ module ActiveRecord
81
115
 
82
116
  ChangeColumnDefinition = Struct.new(:column, :name) # :nodoc:
83
117
 
118
+ ChangeColumnDefaultDefinition = Struct.new(:column, :default) # :nodoc:
119
+
84
120
  CreateIndexDefinition = Struct.new(:index, :algorithm, :if_not_exists) # :nodoc:
85
121
 
86
122
  PrimaryKeyDefinition = Struct.new(:name) # :nodoc:
@@ -125,8 +161,8 @@ module ActiveRecord
125
161
 
126
162
  def defined_for?(to_table: nil, validate: nil, **options)
127
163
  (to_table.nil? || to_table.to_s == self.to_table) &&
128
- (validate.nil? || validate == options.fetch(:validate, validate)) &&
129
- options.all? { |k, v| self.options[k].to_s == v.to_s }
164
+ (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
165
+ options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
130
166
  end
131
167
 
132
168
  private
@@ -148,6 +184,12 @@ module ActiveRecord
148
184
  def export_name_on_schema_dump?
149
185
  !ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name
150
186
  end
187
+
188
+ def defined_for?(name:, expression: nil, validate: nil, **options)
189
+ self.name == name.to_s &&
190
+ (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
191
+ options.all? { |k, v| self.options[k].to_s == v.to_s }
192
+ end
151
193
  end
152
194
 
153
195
  class ReferenceDefinition # :nodoc:
@@ -171,6 +213,20 @@ module ActiveRecord
171
213
  end
172
214
  end
173
215
 
216
+ def add(table_name, connection)
217
+ columns.each do |name, type, options|
218
+ connection.add_column(table_name, name, type, **options)
219
+ end
220
+
221
+ if index
222
+ connection.add_index(table_name, column_names, **index_options(table_name))
223
+ end
224
+
225
+ if foreign_key
226
+ connection.add_foreign_key(table_name, foreign_table_name, **foreign_key_options)
227
+ end
228
+ end
229
+
174
230
  def add_to(table)
175
231
  columns.each do |name, type, options|
176
232
  table.column(name, type, **options)
@@ -192,8 +248,12 @@ module ActiveRecord
192
248
  value.is_a?(Hash) ? value : {}
193
249
  end
194
250
 
251
+ def conditional_options
252
+ options.slice(:if_exists, :if_not_exists)
253
+ end
254
+
195
255
  def polymorphic_options
196
- as_options(polymorphic).merge(options.slice(:null, :first, :after))
256
+ as_options(polymorphic).merge(conditional_options).merge(options.slice(:null, :first, :after))
197
257
  end
198
258
 
199
259
  def polymorphic_index_name(table_name)
@@ -201,7 +261,7 @@ module ActiveRecord
201
261
  end
202
262
 
203
263
  def index_options(table_name)
204
- index_options = as_options(index)
264
+ index_options = as_options(index).merge(conditional_options)
205
265
 
206
266
  # legacy reference index names are used on versions 6.0 and earlier
207
267
  return index_options if options[:_uses_legacy_reference_index_name]
@@ -211,7 +271,7 @@ module ActiveRecord
211
271
  end
212
272
 
213
273
  def foreign_key_options
214
- as_options(foreign_key).merge(column: column_name)
274
+ as_options(foreign_key).merge(column: column_name, **conditional_options)
215
275
  end
216
276
 
217
277
  def columns
@@ -280,13 +340,15 @@ module ActiveRecord
280
340
  end
281
341
  end
282
342
 
343
+ # = Active Record Connection Adapters \Table \Definition
344
+ #
283
345
  # Represents the schema of an SQL table in an abstract way. This class
284
346
  # provides methods for manipulating the schema representation.
285
347
  #
286
348
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
287
349
  # is actually of this type:
288
350
  #
289
- # class SomeMigration < ActiveRecord::Migration[7.0]
351
+ # class SomeMigration < ActiveRecord::Migration[7.1]
290
352
  # def up
291
353
  # create_table :foo do |t|
292
354
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -327,6 +389,23 @@ module ActiveRecord
327
389
  @comment = comment
328
390
  end
329
391
 
392
+ def set_primary_key(table_name, id, primary_key, **options)
393
+ if id && !as
394
+ pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
395
+
396
+ if id.is_a?(Hash)
397
+ options.merge!(id.except(:type))
398
+ id = id.fetch(:type, :primary_key)
399
+ end
400
+
401
+ if pk.is_a?(Array)
402
+ primary_keys(pk)
403
+ else
404
+ primary_key(pk, id, **options)
405
+ end
406
+ end
407
+ end
408
+
330
409
  def primary_keys(name = nil) # :nodoc:
331
410
  @primary_keys = PrimaryKeyDefinition.new(name) if name
332
411
  @primary_keys
@@ -504,7 +583,15 @@ module ActiveRecord
504
583
  end
505
584
 
506
585
  private
586
+ def valid_column_definition_options
587
+ @conn.valid_column_definition_options
588
+ end
589
+
507
590
  def create_column_definition(name, type, options)
591
+ unless options[:_skip_validate_options]
592
+ options.except(:_uses_legacy_reference_index_name, :_skip_validate_options).assert_valid_keys(valid_column_definition_options)
593
+ end
594
+
508
595
  ColumnDefinition.new(name, type, options)
509
596
  end
510
597
 
@@ -523,9 +610,9 @@ module ActiveRecord
523
610
  def raise_on_duplicate_column(name)
524
611
  if @columns_hash[name]
525
612
  if @columns_hash[name].primary_key?
526
- raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
613
+ raise ArgumentError, "you can't redefine the primary key column '#{name}' on '#{@name}'. To define a custom primary key, pass { id: false } to create_table."
527
614
  else
528
- raise ArgumentError, "you can't define an already defined column '#{name}'."
615
+ raise ArgumentError, "you can't define an already defined column '#{name}' on '#{@name}'."
529
616
  end
530
617
  end
531
618
  end
@@ -570,6 +657,8 @@ module ActiveRecord
570
657
  end
571
658
  end
572
659
 
660
+ # = Active Record Connection Adapters \Table
661
+ #
573
662
  # Represents an SQL table in an abstract way for updating a table.
574
663
  # Also see TableDefinition and {connection.create_table}[rdoc-ref:SchemaStatements#create_table]
575
664
  #
@@ -630,6 +719,7 @@ module ActiveRecord
630
719
  #
631
720
  # See TableDefinition#column for details of the options you can use.
632
721
  def column(column_name, type, index: nil, **options)
722
+ raise_on_if_exist_options(options)
633
723
  @base.add_column(name, column_name, type, **options)
634
724
  if index
635
725
  index_options = index.is_a?(Hash) ? index : {}
@@ -655,6 +745,7 @@ module ActiveRecord
655
745
  #
656
746
  # See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use.
657
747
  def index(column_name, **options)
748
+ raise_on_if_exist_options(options)
658
749
  @base.add_index(name, column_name, **options)
659
750
  end
660
751
 
@@ -684,6 +775,7 @@ module ActiveRecord
684
775
  #
685
776
  # See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
686
777
  def timestamps(**options)
778
+ raise_on_if_exist_options(options)
687
779
  @base.add_timestamps(name, **options)
688
780
  end
689
781
 
@@ -694,6 +786,7 @@ module ActiveRecord
694
786
  #
695
787
  # See TableDefinition#column for details of the options you can use.
696
788
  def change(column_name, type, **options)
789
+ raise_on_if_exist_options(options)
697
790
  @base.change_column(name, column_name, type, **options)
698
791
  end
699
792
 
@@ -725,6 +818,7 @@ module ActiveRecord
725
818
  #
726
819
  # See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns]
727
820
  def remove(*column_names, **options)
821
+ raise_on_if_exist_options(options)
728
822
  @base.remove_columns(name, *column_names, **options)
729
823
  end
730
824
 
@@ -737,6 +831,7 @@ module ActiveRecord
737
831
  #
738
832
  # See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index]
739
833
  def remove_index(column_name = nil, **options)
834
+ raise_on_if_exist_options(options)
740
835
  @base.remove_index(name, column_name, **options)
741
836
  end
742
837
 
@@ -765,6 +860,7 @@ module ActiveRecord
765
860
  #
766
861
  # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
767
862
  def references(*args, **options)
863
+ raise_on_if_exist_options(options)
768
864
  args.each do |ref_name|
769
865
  @base.add_reference(name, ref_name, **options)
770
866
  end
@@ -778,6 +874,7 @@ module ActiveRecord
778
874
  #
779
875
  # See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference]
780
876
  def remove_references(*args, **options)
877
+ raise_on_if_exist_options(options)
781
878
  args.each do |ref_name|
782
879
  @base.remove_reference(name, ref_name, **options)
783
880
  end
@@ -791,6 +888,7 @@ module ActiveRecord
791
888
  #
792
889
  # See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key]
793
890
  def foreign_key(*args, **options)
891
+ raise_on_if_exist_options(options)
794
892
  @base.add_foreign_key(name, *args, **options)
795
893
  end
796
894
 
@@ -801,6 +899,7 @@ module ActiveRecord
801
899
  #
802
900
  # See {connection.remove_foreign_key}[rdoc-ref:SchemaStatements#remove_foreign_key]
803
901
  def remove_foreign_key(*args, **options)
902
+ raise_on_if_exist_options(options)
804
903
  @base.remove_foreign_key(name, *args, **options)
805
904
  end
806
905
 
@@ -830,6 +929,33 @@ module ActiveRecord
830
929
  def remove_check_constraint(*args, **options)
831
930
  @base.remove_check_constraint(name, *args, **options)
832
931
  end
932
+
933
+ # Checks if a check_constraint exists on a table.
934
+ #
935
+ # unless t.check_constraint_exists?(name: "price_check")
936
+ # t.check_constraint("price > 0", name: "price_check")
937
+ # end
938
+ #
939
+ # See {connection.check_constraint_exists?}[rdoc-ref:SchemaStatements#check_constraint_exists?]
940
+ def check_constraint_exists?(*args, **options)
941
+ @base.check_constraint_exists?(name, *args, **options)
942
+ end
943
+
944
+ private
945
+ def raise_on_if_exist_options(options)
946
+ unrecognized_option = options.keys.find do |key|
947
+ key == :if_exists || key == :if_not_exists
948
+ end
949
+ if unrecognized_option
950
+ conditional = unrecognized_option == :if_exists ? "if" : "unless"
951
+ message = <<~TXT
952
+ Option #{unrecognized_option} will be ignored. If you are calling an expression like
953
+ `t.column(.., #{unrecognized_option}: true)` from inside a change_table block, try a
954
+ conditional clause instead, as in `t.column(..) #{conditional} t.column_exists?(..)`
955
+ TXT
956
+ raise ArgumentError.new(message)
957
+ end
958
+ end
833
959
  end
834
960
  end
835
961
  end