activerecord 8.0.2.1 → 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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -421
  3. data/README.rdoc +2 -2
  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/belongs_to_association.rb +9 -1
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_association.rb +3 -3
  13. data/lib/active_record/associations/collection_proxy.rb +22 -4
  14. data/lib/active_record/associations/deprecation.rb +88 -0
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/join_dependency.rb +2 -0
  17. data/lib/active_record/associations/preloader/branch.rb +1 -0
  18. data/lib/active_record/associations.rb +159 -21
  19. data/lib/active_record/attribute_methods/query.rb +34 -0
  20. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  21. data/lib/active_record/attributes.rb +38 -24
  22. data/lib/active_record/base.rb +0 -1
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  45. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
  46. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  47. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  48. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  49. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  51. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  52. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  53. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
  54. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  55. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
  56. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  57. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  58. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  59. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  60. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  61. data/lib/active_record/connection_adapters.rb +1 -0
  62. data/lib/active_record/connection_handling.rb +1 -1
  63. data/lib/active_record/core.rb +12 -9
  64. data/lib/active_record/counter_cache.rb +33 -8
  65. data/lib/active_record/database_configurations/database_config.rb +5 -1
  66. data/lib/active_record/database_configurations/hash_config.rb +56 -9
  67. data/lib/active_record/database_configurations/url_config.rb +13 -3
  68. data/lib/active_record/database_configurations.rb +7 -3
  69. data/lib/active_record/delegated_type.rb +2 -2
  70. data/lib/active_record/dynamic_matchers.rb +54 -69
  71. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  72. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  73. data/lib/active_record/encryption/encryptor.rb +27 -25
  74. data/lib/active_record/encryption/scheme.rb +1 -1
  75. data/lib/active_record/enum.rb +37 -20
  76. data/lib/active_record/errors.rb +20 -4
  77. data/lib/active_record/explain_registry.rb +0 -1
  78. data/lib/active_record/filter_attribute_handler.rb +73 -0
  79. data/lib/active_record/fixture_set/table_row.rb +19 -2
  80. data/lib/active_record/fixtures.rb +2 -2
  81. data/lib/active_record/gem_version.rb +3 -3
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +12 -7
  84. data/lib/active_record/locking/optimistic.rb +7 -0
  85. data/lib/active_record/locking/pessimistic.rb +5 -0
  86. data/lib/active_record/log_subscriber.rb +1 -5
  87. data/lib/active_record/middleware/shard_selector.rb +34 -17
  88. data/lib/active_record/migration/command_recorder.rb +14 -1
  89. data/lib/active_record/migration/compatibility.rb +34 -24
  90. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  91. data/lib/active_record/migration.rb +31 -21
  92. data/lib/active_record/model_schema.rb +10 -7
  93. data/lib/active_record/nested_attributes.rb +2 -0
  94. data/lib/active_record/persistence.rb +34 -3
  95. data/lib/active_record/query_cache.rb +22 -15
  96. data/lib/active_record/query_logs.rb +7 -7
  97. data/lib/active_record/querying.rb +4 -4
  98. data/lib/active_record/railtie.rb +34 -5
  99. data/lib/active_record/railties/databases.rake +23 -19
  100. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  101. data/lib/active_record/railties/job_runtime.rb +10 -11
  102. data/lib/active_record/reflection.rb +42 -3
  103. data/lib/active_record/relation/batches.rb +26 -12
  104. data/lib/active_record/relation/calculations.rb +35 -25
  105. data/lib/active_record/relation/delegation.rb +0 -1
  106. data/lib/active_record/relation/finder_methods.rb +37 -21
  107. data/lib/active_record/relation/merger.rb +2 -2
  108. data/lib/active_record/relation/predicate_builder.rb +2 -2
  109. data/lib/active_record/relation/query_attribute.rb +3 -1
  110. data/lib/active_record/relation/query_methods.rb +43 -33
  111. data/lib/active_record/relation/spawn_methods.rb +6 -6
  112. data/lib/active_record/relation/where_clause.rb +7 -10
  113. data/lib/active_record/relation.rb +37 -15
  114. data/lib/active_record/result.rb +44 -21
  115. data/lib/active_record/sanitization.rb +2 -0
  116. data/lib/active_record/schema_dumper.rb +12 -10
  117. data/lib/active_record/scoping.rb +0 -1
  118. data/lib/active_record/secure_token.rb +3 -3
  119. data/lib/active_record/signed_id.rb +46 -18
  120. data/lib/active_record/statement_cache.rb +13 -9
  121. data/lib/active_record/store.rb +44 -19
  122. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  123. data/lib/active_record/tasks/database_tasks.rb +24 -35
  124. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  125. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  126. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  127. data/lib/active_record/test_databases.rb +11 -3
  128. data/lib/active_record/test_fixtures.rb +27 -2
  129. data/lib/active_record/testing/query_assertions.rb +8 -2
  130. data/lib/active_record/timestamp.rb +4 -2
  131. data/lib/active_record/transaction.rb +2 -5
  132. data/lib/active_record/transactions.rb +34 -10
  133. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  134. data/lib/active_record/type/internal/timezone.rb +7 -0
  135. data/lib/active_record/type/json.rb +15 -2
  136. data/lib/active_record/type/serialized.rb +11 -4
  137. data/lib/active_record/type/type_map.rb +1 -1
  138. data/lib/active_record/type_caster/connection.rb +2 -1
  139. data/lib/active_record/validations/associated.rb +1 -1
  140. data/lib/active_record.rb +68 -5
  141. data/lib/arel/alias_predication.rb +2 -0
  142. data/lib/arel/crud.rb +8 -11
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/function.rb +4 -10
  147. data/lib/arel/nodes/named_function.rb +2 -2
  148. data/lib/arel/nodes/node.rb +1 -1
  149. data/lib/arel/nodes/update_statement.rb +4 -2
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +13 -4
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -3
  154. data/lib/arel/visitors/postgresql.rb +55 -0
  155. data/lib/arel/visitors/sqlite.rb +55 -8
  156. data/lib/arel/visitors/to_sql.rb +5 -21
  157. data/lib/arel.rb +3 -1
  158. metadata +13 -9
  159. 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
@@ -239,7 +257,7 @@ module ActiveRecord
239
257
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
240
258
  # Such queries should not be cached.
241
259
  if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
242
- sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
260
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
243
261
 
244
262
  if async
245
263
  result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
@@ -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
 
@@ -186,6 +187,9 @@ module ActiveRecord
186
187
  # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
187
188
  #
188
189
  # A Symbol can be used to specify the type of the generated primary key column.
190
+ #
191
+ # A Hash can be used to specify the generated primary key column creation options.
192
+ # See {add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column] for available options.
189
193
  # [<tt>:primary_key</tt>]
190
194
  # The name of the primary key, if one is to be added automatically.
191
195
  # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
@@ -354,11 +358,13 @@ module ActiveRecord
354
358
  # # Creates a table called 'music_artists_records' with no id.
355
359
  # create_join_table('music_artists', 'music_records')
356
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
+ #
357
365
  # You can pass an +options+ hash which can include the following keys:
358
366
  # [<tt>:table_name</tt>]
359
367
  # Sets the table name, overriding the default.
360
- # [<tt>:column_options</tt>]
361
- # Any extra options you want appended to the columns definition.
362
368
  # [<tt>:options</tt>]
363
369
  # Any extra options you want appended to the table definition.
364
370
  # [<tt>:temporary</tt>]
@@ -375,6 +381,19 @@ module ActiveRecord
375
381
  # t.index :category_id
376
382
  # end
377
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
+ #
378
397
  # ====== Add a backend specific option to the generated SQL (MySQL)
379
398
  #
380
399
  # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
@@ -548,7 +567,7 @@ module ActiveRecord
548
567
  #
549
568
  # See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column].
550
569
  #
551
- # The +type+ parameter is normally one of the migrations native types,
570
+ # The +type+ parameter is normally one of the migration's native types,
552
571
  # which is one of the following:
553
572
  # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
554
573
  # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
@@ -912,6 +931,19 @@ module ActiveRecord
912
931
  # Concurrently adding an index is not supported in a transaction.
913
932
  #
914
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
+ #
915
947
  def add_index(table_name, column_name, **options)
916
948
  create_index = build_create_index_definition(table_name, column_name, **options)
917
949
  execute schema_creation.accept(create_index)
@@ -1172,9 +1204,10 @@ module ActiveRecord
1172
1204
  # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1173
1205
  def add_foreign_key(from_table, to_table, **options)
1174
1206
  return unless use_foreign_keys?
1175
- return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1176
1207
 
1177
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
+
1178
1211
  at = create_alter_table from_table
1179
1212
  at.add_foreign_key to_table, options
1180
1213
 
@@ -1213,7 +1246,7 @@ module ActiveRecord
1213
1246
  # The name of the table that contains the referenced primary key.
1214
1247
  def remove_foreign_key(from_table, to_table = nil, **options)
1215
1248
  return unless use_foreign_keys?
1216
- 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))
1217
1250
 
1218
1251
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1219
1252
 
@@ -1352,7 +1385,7 @@ module ActiveRecord
1352
1385
  execute schema_creation.accept(at)
1353
1386
  end
1354
1387
 
1355
- def dump_schema_information # :nodoc:
1388
+ def dump_schema_versions # :nodoc:
1356
1389
  versions = pool.schema_migration.versions
1357
1390
  insert_versions_sql(versions) if versions.any?
1358
1391
  end
@@ -1474,7 +1507,7 @@ module ActiveRecord
1474
1507
  end
1475
1508
 
1476
1509
  def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1477
- 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)
1478
1511
 
1479
1512
  column_names = index_column_names(column_name)
1480
1513
 
@@ -1483,7 +1516,7 @@ module ActiveRecord
1483
1516
 
1484
1517
  validate_index_length!(table_name, index_name, internal)
1485
1518
 
1486
- index = IndexDefinition.new(
1519
+ index = create_index_definition(
1487
1520
  table_name, index_name,
1488
1521
  options[:unique],
1489
1522
  column_names,
@@ -1538,6 +1571,20 @@ module ActiveRecord
1538
1571
  raise NotImplementedError, "#{self.class} does not support changing column comments"
1539
1572
  end
1540
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
+
1541
1588
  def create_schema_dumper(options) # :nodoc:
1542
1589
  SchemaDumper.create(self, options)
1543
1590
  end
@@ -1604,7 +1651,7 @@ module ActiveRecord
1604
1651
  name = "idx_on_#{Array(column) * '_'}"
1605
1652
 
1606
1653
  short_limit = max_index_name_size - hashed_identifier.bytesize
1607
- short_name = name.mb_chars.limit(short_limit).to_s
1654
+ short_name = name.truncate_bytes(short_limit, omission: nil)
1608
1655
 
1609
1656
  "#{short_name}#{hashed_identifier}"
1610
1657
  end
@@ -1626,6 +1673,10 @@ module ActiveRecord
1626
1673
  end
1627
1674
  end
1628
1675
 
1676
+ def valid_index_options
1677
+ [:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct]
1678
+ end
1679
+
1629
1680
  def options_for_index_columns(options)
1630
1681
  if options.is_a?(Hash)
1631
1682
  options.symbolize_keys
@@ -1702,6 +1753,10 @@ module ActiveRecord
1702
1753
  TableDefinition.new(self, name, **options)
1703
1754
  end
1704
1755
 
1756
+ def create_index_definition(table_name, name, unique, columns, **options)
1757
+ IndexDefinition.new(table_name, name, unique, columns, **options)
1758
+ end
1759
+
1705
1760
  def create_alter_table(name)
1706
1761
  AlterTable.new create_table_definition(name)
1707
1762
  end
@@ -1764,7 +1819,20 @@ module ActiveRecord
1764
1819
 
1765
1820
  def foreign_key_for(from_table, **options)
1766
1821
  return unless use_foreign_keys?
1767
- 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) }
1768
1836
  end
1769
1837
 
1770
1838
  def foreign_key_for!(from_table, to_table: nil, **options)
@@ -1807,13 +1875,19 @@ module ActiveRecord
1807
1875
 
1808
1876
  def validate_index_length!(table_name, new_name, internal = false)
1809
1877
  if new_name.length > index_name_length
1810
- 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
1811
1882
  end
1812
1883
  end
1813
1884
 
1814
1885
  def validate_table_length!(table_name)
1815
1886
  if table_name.length > table_name_length
1816
- 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
1817
1891
  end
1818
1892
  end
1819
1893
 
@@ -1875,16 +1949,8 @@ module ActiveRecord
1875
1949
  end
1876
1950
 
1877
1951
  def insert_versions_sql(versions)
1878
- sm_table = quote_table_name(pool.schema_migration.table_name)
1879
-
1880
- if versions.is_a?(Array)
1881
- sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1882
- sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
1883
- sql << ";"
1884
- sql
1885
- else
1886
- "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
1887
- end
1952
+ versions_formatter = ActiveRecord.schema_versions_formatter.new(self)
1953
+ versions_formatter.format(versions)
1888
1954
  end
1889
1955
 
1890
1956
  def data_source_sql(name = nil, type: nil)