activerecord 8.0.3 → 8.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +427 -522
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/builder/association.rb +16 -5
  7. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  8. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  9. data/lib/active_record/associations/builder/has_one.rb +1 -1
  10. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  11. data/lib/active_record/associations/collection_proxy.rb +22 -4
  12. data/lib/active_record/associations/deprecation.rb +88 -0
  13. data/lib/active_record/associations/errors.rb +3 -0
  14. data/lib/active_record/associations/join_dependency.rb +2 -0
  15. data/lib/active_record/associations/preloader/branch.rb +1 -0
  16. data/lib/active_record/associations.rb +159 -21
  17. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  18. data/lib/active_record/attribute_methods.rb +1 -1
  19. data/lib/active_record/attributes.rb +3 -0
  20. data/lib/active_record/autosave_association.rb +1 -1
  21. data/lib/active_record/base.rb +2 -3
  22. data/lib/active_record/coders/json.rb +14 -5
  23. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
  24. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +382 -51
  27. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  28. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
  29. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  30. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  32. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  33. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
  34. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  35. data/lib/active_record/connection_adapters/abstract_adapter.rb +66 -14
  36. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  37. data/lib/active_record/connection_adapters/column.rb +17 -4
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  39. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  40. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  41. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  42. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  43. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  44. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
  45. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  46. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  47. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  48. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  50. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  51. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  52. data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -5
  53. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  54. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
  55. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  57. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
  58. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  59. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  60. data/lib/active_record/connection_adapters.rb +1 -0
  61. data/lib/active_record/core.rb +5 -4
  62. data/lib/active_record/counter_cache.rb +33 -8
  63. data/lib/active_record/database_configurations/database_config.rb +5 -1
  64. data/lib/active_record/database_configurations/hash_config.rb +50 -9
  65. data/lib/active_record/database_configurations/url_config.rb +13 -3
  66. data/lib/active_record/database_configurations.rb +7 -3
  67. data/lib/active_record/delegated_type.rb +1 -1
  68. data/lib/active_record/dynamic_matchers.rb +54 -69
  69. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  70. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  71. data/lib/active_record/encryption/scheme.rb +1 -1
  72. data/lib/active_record/enum.rb +24 -8
  73. data/lib/active_record/errors.rb +23 -7
  74. data/lib/active_record/explain_registry.rb +0 -1
  75. data/lib/active_record/filter_attribute_handler.rb +73 -0
  76. data/lib/active_record/fixtures.rb +2 -2
  77. data/lib/active_record/gem_version.rb +3 -3
  78. data/lib/active_record/inheritance.rb +1 -1
  79. data/lib/active_record/insert_all.rb +12 -7
  80. data/lib/active_record/locking/optimistic.rb +7 -0
  81. data/lib/active_record/locking/pessimistic.rb +5 -0
  82. data/lib/active_record/log_subscriber.rb +1 -5
  83. data/lib/active_record/middleware/shard_selector.rb +34 -17
  84. data/lib/active_record/migration/command_recorder.rb +14 -1
  85. data/lib/active_record/migration/compatibility.rb +34 -24
  86. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  87. data/lib/active_record/migration.rb +26 -16
  88. data/lib/active_record/model_schema.rb +10 -7
  89. data/lib/active_record/nested_attributes.rb +2 -0
  90. data/lib/active_record/persistence.rb +34 -3
  91. data/lib/active_record/query_cache.rb +22 -15
  92. data/lib/active_record/query_logs.rb +3 -7
  93. data/lib/active_record/railtie.rb +32 -3
  94. data/lib/active_record/railties/databases.rake +16 -4
  95. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  96. data/lib/active_record/railties/job_runtime.rb +10 -11
  97. data/lib/active_record/reflection.rb +42 -3
  98. data/lib/active_record/relation/batches.rb +26 -12
  99. data/lib/active_record/relation/calculations.rb +20 -9
  100. data/lib/active_record/relation/delegation.rb +0 -1
  101. data/lib/active_record/relation/finder_methods.rb +27 -11
  102. data/lib/active_record/relation/merger.rb +2 -2
  103. data/lib/active_record/relation/predicate_builder.rb +2 -2
  104. data/lib/active_record/relation/query_attribute.rb +3 -1
  105. data/lib/active_record/relation/query_methods.rb +39 -29
  106. data/lib/active_record/relation/where_clause.rb +1 -10
  107. data/lib/active_record/relation.rb +25 -13
  108. data/lib/active_record/result.rb +44 -21
  109. data/lib/active_record/sanitization.rb +2 -0
  110. data/lib/active_record/schema_dumper.rb +12 -10
  111. data/lib/active_record/scoping.rb +0 -1
  112. data/lib/active_record/signed_id.rb +43 -15
  113. data/lib/active_record/statement_cache.rb +13 -9
  114. data/lib/active_record/store.rb +44 -19
  115. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  116. data/lib/active_record/tasks/database_tasks.rb +2 -21
  117. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  118. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
  119. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  120. data/lib/active_record/test_databases.rb +10 -2
  121. data/lib/active_record/test_fixtures.rb +27 -2
  122. data/lib/active_record/testing/query_assertions.rb +8 -2
  123. data/lib/active_record/timestamp.rb +4 -2
  124. data/lib/active_record/transaction.rb +2 -5
  125. data/lib/active_record/transactions.rb +32 -10
  126. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  127. data/lib/active_record/type/internal/timezone.rb +7 -0
  128. data/lib/active_record/type/json.rb +15 -2
  129. data/lib/active_record/type/serialized.rb +11 -4
  130. data/lib/active_record/type/type_map.rb +1 -1
  131. data/lib/active_record/type_caster/connection.rb +2 -1
  132. data/lib/active_record/validations/associated.rb +1 -1
  133. data/lib/active_record.rb +65 -3
  134. data/lib/arel/alias_predication.rb +2 -0
  135. data/lib/arel/crud.rb +6 -11
  136. data/lib/arel/nodes/count.rb +2 -2
  137. data/lib/arel/nodes/function.rb +4 -10
  138. data/lib/arel/nodes/named_function.rb +2 -2
  139. data/lib/arel/nodes/node.rb +1 -1
  140. data/lib/arel/nodes.rb +0 -2
  141. data/lib/arel/select_manager.rb +7 -2
  142. data/lib/arel/visitors/dot.rb +0 -3
  143. data/lib/arel/visitors/postgresql.rb +55 -0
  144. data/lib/arel/visitors/sqlite.rb +55 -8
  145. data/lib/arel/visitors/to_sql.rb +3 -21
  146. data/lib/arel.rb +3 -1
  147. metadata +13 -9
  148. data/lib/active_record/normalization.rb +0 -163
@@ -55,12 +55,15 @@ module ActiveRecord
55
55
  # can be used to query the database repeatedly.
56
56
  def cacheable_query(klass, arel) # :nodoc:
57
57
  if prepared_statements
58
+ collector = collector()
59
+ collector.retryable = true
58
60
  sql, binds = visitor.compile(arel.ast, collector)
59
- query = klass.query(sql)
61
+ query = klass.query(sql, retryable: collector.retryable)
60
62
  else
61
63
  collector = klass.partial_query_collector
64
+ collector.retryable = true
62
65
  parts, binds = visitor.compile(arel.ast, collector)
63
- query = klass.partial_query(parts)
66
+ query = klass.partial_query(parts, retryable: collector.retryable)
64
67
  end
65
68
  [query, binds]
66
69
  end
@@ -110,8 +113,8 @@ module ActiveRecord
110
113
  query(...).map(&:first)
111
114
  end
112
115
 
113
- def query(...) # :nodoc:
114
- internal_exec_query(...).rows
116
+ def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
117
+ internal_exec_query(sql, name, allow_retry:, materialize_transactions:).rows
115
118
  end
116
119
 
117
120
  # Determines whether the SQL statement is a write query.
@@ -351,7 +354,7 @@ module ActiveRecord
351
354
  # :args: (requires_new: nil, isolation: nil, &block)
352
355
  def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
353
356
  if !requires_new && current_transaction.joinable?
354
- if isolation
357
+ if isolation && current_transaction.isolation != isolation
355
358
  raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
356
359
  end
357
360
  yield current_transaction.user_transaction
@@ -499,20 +502,6 @@ module ActiveRecord
499
502
  "DEFAULT VALUES"
500
503
  end
501
504
 
502
- # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
503
- #
504
- # The +limit+ may be anything that can evaluate to a string via #to_s. It
505
- # should look like an integer, or an Arel SQL literal.
506
- #
507
- # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
508
- def sanitize_limit(limit)
509
- if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
510
- limit
511
- else
512
- Integer(limit)
513
- end
514
- end
515
-
516
505
  # Fixture value is quoted by Arel, however scalar values
517
506
  # are not quotable. In this case we want to convert
518
507
  # the column value to YAML.
@@ -547,13 +536,24 @@ module ActiveRecord
547
536
  cast_result(internal_execute(...))
548
537
  end
549
538
 
539
+ def default_insert_value(column) # :nodoc:
540
+ DEFAULT_INSERT_VALUE
541
+ end
542
+
550
543
  private
544
+ DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
545
+ private_constant :DEFAULT_INSERT_VALUE
546
+
551
547
  # Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
552
548
  def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
553
549
  type_casted_binds = type_casted_binds(binds)
554
- log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
550
+ log(sql, name, binds, type_casted_binds, async: async, allow_retry: allow_retry) do |notification_payload|
555
551
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
556
- perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
552
+ result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
553
+ perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
554
+ end
555
+ handle_warnings(result, sql)
556
+ result
557
557
  end
558
558
  end
559
559
  end
@@ -562,6 +562,9 @@ module ActiveRecord
562
562
  raise NotImplementedError
563
563
  end
564
564
 
565
+ def handle_warnings(raw_result, sql)
566
+ end
567
+
565
568
  # Receive a native adapter result object and returns an ActiveRecord::Result object.
566
569
  def cast_result(raw_result)
567
570
  raise NotImplementedError
@@ -597,13 +600,6 @@ module ActiveRecord
597
600
  end
598
601
  end
599
602
 
600
- DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
601
- private_constant :DEFAULT_INSERT_VALUE
602
-
603
- def default_insert_value(column)
604
- DEFAULT_INSERT_VALUE
605
- end
606
-
607
603
  def build_fixture_sql(fixtures, table_name)
608
604
  columns = schema_cache.columns_hash(table_name).reject { |_, column| supports_virtual_columns? && column.virtual? }
609
605
 
@@ -617,8 +613,8 @@ module ActiveRecord
617
613
 
618
614
  columns.map do |name, column|
619
615
  if fixture.key?(name)
620
- type = lookup_cast_type_from_column(column)
621
- with_yaml_fallback(type.serialize(fixture[name]))
616
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
617
+ with_yaml_fallback(column.fetch_cast_type(self).serialize(fixture[name]))
622
618
  else
623
619
  default_insert_value(column)
624
620
  end
@@ -31,6 +31,18 @@ module ActiveRecord
31
31
  end
32
32
  end
33
33
 
34
+ # This is the actual query cache store.
35
+ #
36
+ # It has an internal hash whose keys are either SQL strings, or arrays of
37
+ # two elements [SQL string, binds], if there are binds. The hash values
38
+ # are their corresponding ActiveRecord::Result objects.
39
+ #
40
+ # Keeping the hash size under max size is achieved with LRU eviction.
41
+ #
42
+ # The store gets passed a version object, which is shared among the query
43
+ # cache stores of a given connection pool (see ConnectionPoolConfiguration
44
+ # down below). The version value may be externally changed as a way to
45
+ # signal cache invalidation, that is why all methods have a guard for it.
34
46
  class Store # :nodoc:
35
47
  attr_accessor :enabled, :dirties
36
48
  alias_method :enabled?, :enabled
@@ -94,6 +106,12 @@ module ActiveRecord
94
106
  end
95
107
  end
96
108
 
109
+ # Each connection pool has one of these registries. They map execution
110
+ # contexts to query cache stores.
111
+ #
112
+ # The keys of the internal map are threads or fibers (whatever
113
+ # ActiveSupport::IsolatedExecutionState.context returns), and their
114
+ # associated values are their respective query cache stores.
97
115
  class QueryCacheRegistry # :nodoc:
98
116
  def initialize
99
117
  @mutex = Mutex.new
@@ -191,26 +209,15 @@ module ActiveRecord
191
209
  end
192
210
  end
193
211
 
212
+ attr_accessor :query_cache
213
+
194
214
  def initialize(*)
195
215
  super
196
216
  @query_cache = nil
197
217
  end
198
218
 
199
- attr_writer :query_cache
200
-
201
- def query_cache
202
- if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
203
- # With transactional tests, if the connection is pinned, any thread
204
- # other than the one that pinned the connection need to go through the
205
- # query cache pool, so each thread get a different cache.
206
- pool.query_cache
207
- else
208
- @query_cache
209
- end
210
- end
211
-
212
219
  def query_cache_enabled
213
- query_cache&.enabled?
220
+ @query_cache&.enabled?
214
221
  end
215
222
 
216
223
  # Enable the query cache within the block.
@@ -249,8 +256,8 @@ module ActiveRecord
249
256
 
250
257
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
251
258
  # Such queries should not be cached.
252
- if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
253
- sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
259
+ if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
260
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
254
261
 
255
262
  if async
256
263
  result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
@@ -273,7 +280,7 @@ module ActiveRecord
273
280
 
274
281
  result = nil
275
282
  @lock.synchronize do
276
- result = query_cache[key]
283
+ result = @query_cache[key]
277
284
  end
278
285
 
279
286
  if result
@@ -292,7 +299,7 @@ module ActiveRecord
292
299
  hit = true
293
300
 
294
301
  @lock.synchronize do
295
- result = query_cache.compute_if_absent(key) do
302
+ result = @query_cache.compute_if_absent(key) do
296
303
  hit = false
297
304
  yield
298
305
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/big_decimal/conversions"
4
- require "active_support/multibyte/chars"
5
4
 
6
5
  module ActiveRecord
7
6
  module ConnectionAdapters # :nodoc:
@@ -84,7 +83,8 @@ module ActiveRecord
84
83
  when Type::Time::Value then "'#{quoted_time(value)}'"
85
84
  when Date, Time then "'#{quoted_date(value)}'"
86
85
  when Class then "'#{value}'"
87
- else raise TypeError, "can't quote #{value.class.name}"
86
+ else
87
+ raise TypeError, "can't quote #{value.class.name}"
88
88
  end
89
89
  end
90
90
 
@@ -93,7 +93,7 @@ module ActiveRecord
93
93
  # to a String.
94
94
  def type_cast(value)
95
95
  case value
96
- when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
96
+ when Symbol, Type::Binary::Data, ActiveSupport::Multibyte::Chars
97
97
  value.to_s
98
98
  when true then unquoted_true
99
99
  when false then unquoted_false
@@ -102,7 +102,8 @@ module ActiveRecord
102
102
  when nil, Numeric, String then value
103
103
  when Type::Time::Value then quoted_time(value)
104
104
  when Date, Time then quoted_date(value)
105
- else raise TypeError, "can't cast #{value.class.name}"
105
+ else
106
+ raise TypeError, "can't cast #{value.class.name}"
106
107
  end
107
108
  end
108
109
 
@@ -113,19 +114,6 @@ module ActiveRecord
113
114
  value
114
115
  end
115
116
 
116
- # If you are having to call this function, you are likely doing something
117
- # wrong. The column does not have sufficient type information if the user
118
- # provided a custom type on the class level either explicitly (via
119
- # Attributes::ClassMethods#attribute) or implicitly (via
120
- # AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+).
121
- # In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to
122
- # represent the type doesn't sufficiently reflect the differences
123
- # (varchar vs binary) for example. The type used to get this primitive
124
- # should have been provided before reaching the connection adapter.
125
- def lookup_cast_type_from_column(column) # :nodoc:
126
- lookup_cast_type(column.sql_type)
127
- end
128
-
129
117
  # Quotes a string, escaping any ' (single quote) and \ (backslash)
130
118
  # characters.
131
119
  def quote_string(s)
@@ -158,7 +146,9 @@ module ActiveRecord
158
146
  if value.is_a?(Proc)
159
147
  value.call
160
148
  else
161
- value = lookup_cast_type(column.sql_type).serialize(value)
149
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
150
+ cast_type = column.fetch_cast_type(self)
151
+ value = cast_type.serialize(value)
162
152
  quote(value)
163
153
  end
164
154
  end
@@ -208,10 +198,10 @@ module ActiveRecord
208
198
  end
209
199
 
210
200
  def sanitize_as_sql_comment(value) # :nodoc:
211
- # Sanitize a string to appear within a SQL comment
201
+ # Sanitize a string to appear within an SQL comment
212
202
  # For compatibility, this also surrounding "/*+", "/*", and "*/"
213
203
  # charcacters, possibly with single surrounding space.
214
- # Then follows that by replacing any internal "*/" or "/ *" with
204
+ # Then follows that by replacing any internal "*/" or "/*" with
215
205
  # "* /" or "/ *"
216
206
  comment = value.to_s.dup
217
207
  comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
@@ -220,6 +210,11 @@ module ActiveRecord
220
210
  comment
221
211
  end
222
212
 
213
+ def lookup_cast_type(sql_type) # :nodoc:
214
+ # TODO: Make this method private after we release 8.1.
215
+ type_map.lookup(sql_type)
216
+ end
217
+
223
218
  private
224
219
  def type_casted_binds(binds)
225
220
  binds&.map do |value|
@@ -230,10 +225,6 @@ module ActiveRecord
230
225
  end
231
226
  end
232
227
  end
233
-
234
- def lookup_cast_type(sql_type)
235
- type_map.lookup(sql_type)
236
- end
237
228
  end
238
229
  end
239
230
  end
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  :options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
18
18
  :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
19
19
  :supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?,
20
- :supports_nulls_not_distinct?,
20
+ :supports_nulls_not_distinct?, :lookup_cast_type,
21
21
  to: :@conn, private: true
22
22
 
23
23
  private
@@ -148,7 +148,7 @@ module ActiveRecord
148
148
  end
149
149
 
150
150
  def add_column_options!(sql, options)
151
- sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
151
+ sql << " DEFAULT #{quote_default_expression_for_column_definition(options[:default], options[:column])}" if options_include_default?(options)
152
152
  # must explicitly check for :null to allow change_column to work on migrations
153
153
  if options[:null] == false
154
154
  sql << " NOT NULL"
@@ -162,6 +162,11 @@ module ActiveRecord
162
162
  sql
163
163
  end
164
164
 
165
+ def quote_default_expression_for_column_definition(default, column_definition)
166
+ column_definition.cast_type = lookup_cast_type(column_definition.sql_type)
167
+ quote_default_expression(default, column_definition)
168
+ end
169
+
165
170
  def to_sql(sql)
166
171
  sql = sql.to_sql if sql.respond_to?(:to_sql)
167
172
  sql
@@ -75,7 +75,7 @@ module ActiveRecord
75
75
  # are typically created by methods in TableDefinition, and added to the
76
76
  # +columns+ attribute of said TableDefinition object, in order to be used
77
77
  # for generating a number of table creation or table changing SQL statements.
78
- ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
78
+ ColumnDefinition = Struct.new(:name, :type, :options, :sql_type, :cast_type) do # :nodoc:
79
79
  self::OPTION_NAMES = [
80
80
  :limit,
81
81
  :precision,
@@ -108,6 +108,10 @@ module ActiveRecord
108
108
  def aliased_types(name, fallback)
109
109
  "timestamp" == name ? :datetime : fallback
110
110
  end
111
+
112
+ def fetch_cast_type(connection)
113
+ cast_type
114
+ end
111
115
  end
112
116
 
113
117
  AddColumnDefinition = Struct.new(:column) # :nodoc:
@@ -303,44 +307,32 @@ module ActiveRecord
303
307
  module ColumnMethods
304
308
  extend ActiveSupport::Concern
305
309
 
306
- # Appends a primary key definition to the table definition.
307
- # Can be called multiple times, but this is probably not a good idea.
308
- def primary_key(name, type = :primary_key, **options)
309
- column(name, type, **options.merge(primary_key: true))
310
- end
311
-
312
- ##
313
- # :method: column
314
- # :call-seq: column(name, type, **options)
315
- #
316
- # Appends a column or columns of a specified type.
317
- #
318
- # t.string(:goat)
319
- # t.string(:goat, :sheep)
320
- #
321
- # See TableDefinition#column
322
-
323
- included do
324
- define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
325
- :float, :integer, :json, :string, :text, :time, :timestamp, :virtual
326
-
327
- alias :blob :binary
328
- alias :numeric :decimal
329
- end
330
-
331
310
  class_methods do
332
- def define_column_methods(*column_types) # :nodoc:
333
- column_types.each do |column_type|
334
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
311
+ private
312
+ def define_column_methods(*column_types) # :nodoc:
313
+ column_types.each do |column_type|
314
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
335
315
  def #{column_type}(*names, **options)
336
316
  raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
337
317
  names.each { |name| column(name, :#{column_type}, **options) }
338
318
  end
339
- RUBY
319
+ RUBY
320
+ end
340
321
  end
341
- end
342
- private :define_column_methods
343
322
  end
323
+ extend ClassMethods
324
+
325
+ # Appends a primary key definition to the table definition.
326
+ # Can be called multiple times, but this is probably not a good idea.
327
+ def primary_key(name, type = :primary_key, **options)
328
+ column(name, type, **options, primary_key: true)
329
+ end
330
+
331
+ define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
332
+ :float, :integer, :json, :string, :text, :time, :timestamp, :virtual
333
+
334
+ alias :blob :binary
335
+ alias :numeric :decimal
344
336
  end
345
337
 
346
338
  # = Active Record Connection Adapters \Table \Definition
@@ -351,7 +343,7 @@ module ActiveRecord
351
343
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
352
344
  # is actually of this type:
353
345
  #
354
- # class SomeMigration < ActiveRecord::Migration[8.0]
346
+ # class SomeMigration < ActiveRecord::Migration[8.1]
355
347
  # def up
356
348
  # create_table :foo do |t|
357
349
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -765,7 +757,7 @@ module ActiveRecord
765
757
  # end
766
758
  #
767
759
  # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
768
- def index_exists?(column_name, **options)
760
+ def index_exists?(column_name = nil, **options)
769
761
  @base.index_exists?(name, column_name, **options)
770
762
  end
771
763
 
@@ -85,7 +85,8 @@ module ActiveRecord
85
85
 
86
86
  def schema_default(column)
87
87
  return unless column.has_default?
88
- type = @connection.lookup_cast_type_from_column(column)
88
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
89
+ type = column.fetch_cast_type(@connection)
89
90
  default = type.deserialize(column.default)
90
91
  if default.nil?
91
92
  schema_expression(column)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/string/access"
4
+ require "active_support/core_ext/string/filters"
4
5
  require "openssl"
5
6
 
6
7
  module ActiveRecord
@@ -99,7 +100,7 @@ module ActiveRecord
99
100
  # # Check a valid index exists (PostgreSQL only)
100
101
  # index_exists?(:suppliers, :company_id, valid: true)
101
102
  #
102
- def index_exists?(table_name, column_name, **options)
103
+ def index_exists?(table_name, column_name = nil, **options)
103
104
  indexes(table_name).any? { |i| i.defined_for?(column_name, **options) }
104
105
  end
105
106
 
@@ -357,11 +358,13 @@ module ActiveRecord
357
358
  # # Creates a table called 'music_artists_records' with no id.
358
359
  # create_join_table('music_artists', 'music_records')
359
360
  #
361
+ # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference]
362
+ # for details of the options you can use in +column_options+. +column_options+
363
+ # will be applied to both columns.
364
+ #
360
365
  # You can pass an +options+ hash which can include the following keys:
361
366
  # [<tt>:table_name</tt>]
362
367
  # Sets the table name, overriding the default.
363
- # [<tt>:column_options</tt>]
364
- # Any extra options you want appended to the columns definition.
365
368
  # [<tt>:options</tt>]
366
369
  # Any extra options you want appended to the table definition.
367
370
  # [<tt>:temporary</tt>]
@@ -378,6 +381,19 @@ module ActiveRecord
378
381
  # t.index :category_id
379
382
  # end
380
383
  #
384
+ # ====== Add foreign keys with delete cascade
385
+ #
386
+ # create_join_table(:assemblies, :parts, column_options: { foreign_key: { on_delete: :cascade } })
387
+ #
388
+ # generates:
389
+ #
390
+ # CREATE TABLE assemblies_parts (
391
+ # assembly_id bigint NOT NULL,
392
+ # part_id bigint NOT NULL,
393
+ # CONSTRAINT fk_rails_0d8a572d89 FOREIGN KEY ("assembly_id") REFERENCES "assemblies" ("id") ON DELETE CASCADE,
394
+ # CONSTRAINT fk_rails_ec7b48402b FOREIGN KEY ("part_id") REFERENCES "parts" ("id") ON DELETE CASCADE
395
+ # )
396
+ #
381
397
  # ====== Add a backend specific option to the generated SQL (MySQL)
382
398
  #
383
399
  # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
@@ -915,6 +931,19 @@ module ActiveRecord
915
931
  # Concurrently adding an index is not supported in a transaction.
916
932
  #
917
933
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
934
+ #
935
+ # ====== Creating an index that is not used by queries
936
+ #
937
+ # add_index(:developers, :name, enabled: false)
938
+ #
939
+ # generates:
940
+ #
941
+ # CREATE INDEX index_developers_on_name ON developers (name) INVISIBLE -- MySQL
942
+ #
943
+ # CREATE INDEX index_developers_on_name ON developers (name) IGNORED -- MariaDB
944
+ #
945
+ # Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
946
+ #
918
947
  def add_index(table_name, column_name, **options)
919
948
  create_index = build_create_index_definition(table_name, column_name, **options)
920
949
  execute schema_creation.accept(create_index)
@@ -1175,9 +1204,10 @@ module ActiveRecord
1175
1204
  # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1176
1205
  def add_foreign_key(from_table, to_table, **options)
1177
1206
  return unless use_foreign_keys?
1178
- return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1179
1207
 
1180
1208
  options = foreign_key_options(from_table, to_table, options)
1209
+ return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column, :primary_key))
1210
+
1181
1211
  at = create_alter_table from_table
1182
1212
  at.add_foreign_key to_table, options
1183
1213
 
@@ -1216,7 +1246,7 @@ module ActiveRecord
1216
1246
  # The name of the table that contains the referenced primary key.
1217
1247
  def remove_foreign_key(from_table, to_table = nil, **options)
1218
1248
  return unless use_foreign_keys?
1219
- return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
1249
+ return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table, **options.slice(:column))
1220
1250
 
1221
1251
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1222
1252
 
@@ -1355,7 +1385,7 @@ module ActiveRecord
1355
1385
  execute schema_creation.accept(at)
1356
1386
  end
1357
1387
 
1358
- def dump_schema_information # :nodoc:
1388
+ def dump_schema_versions # :nodoc:
1359
1389
  versions = pool.schema_migration.versions
1360
1390
  insert_versions_sql(versions) if versions.any?
1361
1391
  end
@@ -1477,7 +1507,7 @@ module ActiveRecord
1477
1507
  end
1478
1508
 
1479
1509
  def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1480
- options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct)
1510
+ options.assert_valid_keys(valid_index_options)
1481
1511
 
1482
1512
  column_names = index_column_names(column_name)
1483
1513
 
@@ -1486,7 +1516,7 @@ module ActiveRecord
1486
1516
 
1487
1517
  validate_index_length!(table_name, index_name, internal)
1488
1518
 
1489
- index = IndexDefinition.new(
1519
+ index = create_index_definition(
1490
1520
  table_name, index_name,
1491
1521
  options[:unique],
1492
1522
  column_names,
@@ -1541,6 +1571,20 @@ module ActiveRecord
1541
1571
  raise NotImplementedError, "#{self.class} does not support changing column comments"
1542
1572
  end
1543
1573
 
1574
+ # Enables an index to be used by queries.
1575
+ #
1576
+ # enable_index(:users, :email)
1577
+ def enable_index(table_name, index_name)
1578
+ raise NotImplementedError, "#{self.class} does not support enabling indexes"
1579
+ end
1580
+
1581
+ # Prevents an index from being used by queries.
1582
+ #
1583
+ # disable_index(:users, :email)
1584
+ def disable_index(table_name, index_name)
1585
+ raise NotImplementedError, "#{self.class} does not support disabling indexes"
1586
+ end
1587
+
1544
1588
  def create_schema_dumper(options) # :nodoc:
1545
1589
  SchemaDumper.create(self, options)
1546
1590
  end
@@ -1607,7 +1651,7 @@ module ActiveRecord
1607
1651
  name = "idx_on_#{Array(column) * '_'}"
1608
1652
 
1609
1653
  short_limit = max_index_name_size - hashed_identifier.bytesize
1610
- short_name = name.mb_chars.limit(short_limit).to_s
1654
+ short_name = name.truncate_bytes(short_limit, omission: nil)
1611
1655
 
1612
1656
  "#{short_name}#{hashed_identifier}"
1613
1657
  end
@@ -1629,6 +1673,10 @@ module ActiveRecord
1629
1673
  end
1630
1674
  end
1631
1675
 
1676
+ def valid_index_options
1677
+ [:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct]
1678
+ end
1679
+
1632
1680
  def options_for_index_columns(options)
1633
1681
  if options.is_a?(Hash)
1634
1682
  options.symbolize_keys
@@ -1705,6 +1753,10 @@ module ActiveRecord
1705
1753
  TableDefinition.new(self, name, **options)
1706
1754
  end
1707
1755
 
1756
+ def create_index_definition(table_name, name, unique, columns, **options)
1757
+ IndexDefinition.new(table_name, name, unique, columns, **options)
1758
+ end
1759
+
1708
1760
  def create_alter_table(name)
1709
1761
  AlterTable.new create_table_definition(name)
1710
1762
  end
@@ -1767,7 +1819,20 @@ module ActiveRecord
1767
1819
 
1768
1820
  def foreign_key_for(from_table, **options)
1769
1821
  return unless use_foreign_keys?
1770
- foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
1822
+
1823
+ keys = foreign_keys(from_table)
1824
+
1825
+ if options[:_skip_column_match]
1826
+ return keys.find { |fk| fk.defined_for?(**options) }
1827
+ end
1828
+
1829
+ if options[:column].nil?
1830
+ default_column = foreign_key_column_for(options[:to_table], "id")
1831
+ matches = keys.select { |fk| fk.column == default_column }
1832
+ keys = matches if matches.any?
1833
+ end
1834
+
1835
+ keys.find { |fk| fk.defined_for?(**options) }
1771
1836
  end
1772
1837
 
1773
1838
  def foreign_key_for!(from_table, to_table: nil, **options)
@@ -1810,13 +1875,19 @@ module ActiveRecord
1810
1875
 
1811
1876
  def validate_index_length!(table_name, new_name, internal = false)
1812
1877
  if new_name.length > index_name_length
1813
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
1878
+ raise ArgumentError, <<~MSG.squish
1879
+ Index name '#{new_name}' on table '#{table_name}' is too long (#{new_name.length} characters); the limit
1880
+ is #{index_name_length} characters
1881
+ MSG
1814
1882
  end
1815
1883
  end
1816
1884
 
1817
1885
  def validate_table_length!(table_name)
1818
1886
  if table_name.length > table_name_length
1819
- raise ArgumentError, "Table name '#{table_name}' is too long; the limit is #{table_name_length} characters"
1887
+ raise ArgumentError, <<~MSG.squish
1888
+ Table name '#{table_name}' is too long (#{table_name.length} characters); the limit is
1889
+ #{table_name_length} characters
1890
+ MSG
1820
1891
  end
1821
1892
  end
1822
1893
 
@@ -1878,16 +1949,8 @@ module ActiveRecord
1878
1949
  end
1879
1950
 
1880
1951
  def insert_versions_sql(versions)
1881
- sm_table = quote_table_name(pool.schema_migration.table_name)
1882
-
1883
- if versions.is_a?(Array)
1884
- sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1885
- sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
1886
- sql << ";"
1887
- sql
1888
- else
1889
- "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
1890
- end
1952
+ versions_formatter = ActiveRecord.schema_versions_formatter.new(self)
1953
+ versions_formatter.format(versions)
1891
1954
  end
1892
1955
 
1893
1956
  def data_source_sql(name = nil, type: nil)