activerecord 7.1.5.2 → 8.1.2.1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -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
@@ -28,6 +28,7 @@ module ActiveRecord
28
28
  sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
29
29
  sql << o.check_constraint_adds.map { |con| visit_AddCheckConstraint con }.join(" ")
30
30
  sql << o.check_constraint_drops.map { |con| visit_DropCheckConstraint con }.join(" ")
31
+ sql << o.constraint_drops.map { |con| visit_DropConstraint con }.join(" ")
31
32
  end
32
33
 
33
34
  def visit_ColumnDefinition(o)
@@ -96,9 +97,11 @@ module ActiveRecord
96
97
  "ADD #{accept(o)}"
97
98
  end
98
99
 
99
- def visit_DropForeignKey(name)
100
+ def visit_DropConstraint(name)
100
101
  "DROP CONSTRAINT #{quote_column_name(name)}"
101
102
  end
103
+ alias :visit_DropForeignKey :visit_DropConstraint
104
+ alias :visit_DropCheckConstraint :visit_DropConstraint
102
105
 
103
106
  def visit_CreateIndexDefinition(o)
104
107
  index = o.index
@@ -127,10 +130,6 @@ module ActiveRecord
127
130
  "ADD #{accept(o)}"
128
131
  end
129
132
 
130
- def visit_DropCheckConstraint(name)
131
- "DROP CONSTRAINT #{quote_column_name(name)}"
132
- end
133
-
134
133
  def quoted_columns(o)
135
134
  String === o.columns ? o.columns : quoted_columns_for_index(o.columns, o.column_options)
136
135
  end
@@ -149,7 +148,7 @@ module ActiveRecord
149
148
  end
150
149
 
151
150
  def add_column_options!(sql, options)
152
- 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)
153
152
  # must explicitly check for :null to allow change_column to work on migrations
154
153
  if options[:null] == false
155
154
  sql << " NOT NULL"
@@ -163,6 +162,11 @@ module ActiveRecord
163
162
  sql
164
163
  end
165
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
+
166
170
  def to_sql(sql)
167
171
  sql = sql.to_sql if sql.respond_to?(:to_sql)
168
172
  sql
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module ActiveRecord
5
4
  module ConnectionAdapters # :nodoc:
6
5
  # Abstract representation of an index definition on a table. Instances of
@@ -76,7 +75,7 @@ module ActiveRecord
76
75
  # are typically created by methods in TableDefinition, and added to the
77
76
  # +columns+ attribute of said TableDefinition object, in order to be used
78
77
  # for generating a number of table creation or table changing SQL statements.
79
- ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
78
+ ColumnDefinition = Struct.new(:name, :type, :options, :sql_type, :cast_type) do # :nodoc:
80
79
  self::OPTION_NAMES = [
81
80
  :limit,
82
81
  :precision,
@@ -109,6 +108,10 @@ module ActiveRecord
109
108
  def aliased_types(name, fallback)
110
109
  "timestamp" == name ? :datetime : fallback
111
110
  end
111
+
112
+ def fetch_cast_type(connection)
113
+ cast_type
114
+ end
112
115
  end
113
116
 
114
117
  AddColumnDefinition = Struct.new(:column) # :nodoc:
@@ -160,6 +163,8 @@ module ActiveRecord
160
163
  end
161
164
 
162
165
  def defined_for?(to_table: nil, validate: nil, **options)
166
+ options = options.slice(*self.options.keys)
167
+
163
168
  (to_table.nil? || to_table.to_s == self.to_table) &&
164
169
  (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
165
170
  options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
@@ -186,6 +191,8 @@ module ActiveRecord
186
191
  end
187
192
 
188
193
  def defined_for?(name:, expression: nil, validate: nil, **options)
194
+ options = options.slice(*self.options.keys)
195
+
189
196
  self.name == name.to_s &&
190
197
  (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
191
198
  options.all? { |k, v| self.options[k].to_s == v.to_s }
@@ -300,44 +307,32 @@ module ActiveRecord
300
307
  module ColumnMethods
301
308
  extend ActiveSupport::Concern
302
309
 
303
- # Appends a primary key definition to the table definition.
304
- # Can be called multiple times, but this is probably not a good idea.
305
- def primary_key(name, type = :primary_key, **options)
306
- column(name, type, **options.merge(primary_key: true))
307
- end
308
-
309
- ##
310
- # :method: column
311
- # :call-seq: column(name, type, **options)
312
- #
313
- # Appends a column or columns of a specified type.
314
- #
315
- # t.string(:goat)
316
- # t.string(:goat, :sheep)
317
- #
318
- # See TableDefinition#column
319
-
320
- included do
321
- define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
322
- :float, :integer, :json, :string, :text, :time, :timestamp, :virtual
323
-
324
- alias :blob :binary
325
- alias :numeric :decimal
326
- end
327
-
328
310
  class_methods do
329
- def define_column_methods(*column_types) # :nodoc:
330
- column_types.each do |column_type|
331
- 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
332
315
  def #{column_type}(*names, **options)
333
316
  raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
334
317
  names.each { |name| column(name, :#{column_type}, **options) }
335
318
  end
336
- RUBY
319
+ RUBY
320
+ end
337
321
  end
338
- end
339
- private :define_column_methods
340
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
341
336
  end
342
337
 
343
338
  # = Active Record Connection Adapters \Table \Definition
@@ -348,7 +343,7 @@ module ActiveRecord
348
343
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
349
344
  # is actually of this type:
350
345
  #
351
- # class SomeMigration < ActiveRecord::Migration[7.1]
346
+ # class SomeMigration < ActiveRecord::Migration[8.1]
352
347
  # def up
353
348
  # create_table :foo do |t|
354
349
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -431,7 +426,7 @@ module ActiveRecord
431
426
  #
432
427
  # == Examples
433
428
  #
434
- # # Assuming +td+ is an instance of TableDefinition
429
+ # # Assuming `td` is an instance of TableDefinition
435
430
  # td.column(:granted, :boolean, index: true)
436
431
  #
437
432
  # == Short-hand examples
@@ -622,6 +617,7 @@ module ActiveRecord
622
617
  attr_reader :adds
623
618
  attr_reader :foreign_key_adds, :foreign_key_drops
624
619
  attr_reader :check_constraint_adds, :check_constraint_drops
620
+ attr_reader :constraint_drops
625
621
 
626
622
  def initialize(td)
627
623
  @td = td
@@ -630,6 +626,7 @@ module ActiveRecord
630
626
  @foreign_key_drops = []
631
627
  @check_constraint_adds = []
632
628
  @check_constraint_drops = []
629
+ @constraint_drops = []
633
630
  end
634
631
 
635
632
  def name; @td.name; end
@@ -650,6 +647,10 @@ module ActiveRecord
650
647
  @check_constraint_drops << constraint_name
651
648
  end
652
649
 
650
+ def drop_constraint(constraint_name)
651
+ @constraint_drops << constraint_name
652
+ end
653
+
653
654
  def add_column(name, type, **options)
654
655
  name = name.to_s
655
656
  type = type.to_sym
@@ -756,7 +757,7 @@ module ActiveRecord
756
757
  # end
757
758
  #
758
759
  # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
759
- def index_exists?(column_name, **options)
760
+ def index_exists?(column_name = nil, **options)
760
761
  @base.index_exists?(name, column_name, **options)
761
762
  end
762
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.
@@ -293,6 +297,11 @@ module ActiveRecord
293
297
  def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options, &block)
294
298
  validate_create_table_options!(options)
295
299
  validate_table_length!(table_name) unless options[:_uses_legacy_table_name]
300
+
301
+ if force && options.key?(:if_not_exists)
302
+ raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
303
+ end
304
+
296
305
  td = build_create_table_definition(table_name, id: id, primary_key: primary_key, force: force, **options, &block)
297
306
 
298
307
  if force
@@ -340,11 +349,22 @@ module ActiveRecord
340
349
  # # Creates a table called 'assemblies_parts' with no id.
341
350
  # create_join_table(:assemblies, :parts)
342
351
  #
352
+ # # Creates a table called 'paper_boxes_papers' with no id.
353
+ # create_join_table('papers', 'paper_boxes')
354
+ #
355
+ # A duplicate prefix is combined into a single prefix. This is useful for
356
+ # namespaced models like Music::Artist and Music::Record:
357
+ #
358
+ # # Creates a table called 'music_artists_records' with no id.
359
+ # create_join_table('music_artists', 'music_records')
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
+ #
343
365
  # You can pass an +options+ hash which can include the following keys:
344
366
  # [<tt>:table_name</tt>]
345
367
  # Sets the table name, overriding the default.
346
- # [<tt>:column_options</tt>]
347
- # Any extra options you want appended to the columns definition.
348
368
  # [<tt>:options</tt>]
349
369
  # Any extra options you want appended to the table definition.
350
370
  # [<tt>:temporary</tt>]
@@ -361,6 +381,19 @@ module ActiveRecord
361
381
  # t.index :category_id
362
382
  # end
363
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
+ #
364
397
  # ====== Add a backend specific option to the generated SQL (MySQL)
365
398
  #
366
399
  # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
@@ -511,7 +544,7 @@ module ActiveRecord
511
544
  raise NotImplementedError, "rename_table is not implemented"
512
545
  end
513
546
 
514
- # Drops a table from the database.
547
+ # Drops a table or tables from the database.
515
548
  #
516
549
  # [<tt>:force</tt>]
517
550
  # Set to +:cascade+ to drop dependent objects as well.
@@ -522,17 +555,19 @@ module ActiveRecord
522
555
  #
523
556
  # Although this command ignores most +options+ and the block if one is given,
524
557
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
525
- # In that case, +options+ and the block will be used by #create_table.
526
- def drop_table(table_name, **options)
527
- schema_cache.clear_data_source_cache!(table_name.to_s)
528
- execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
558
+ # In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported.
559
+ def drop_table(*table_names, **options)
560
+ table_names.each do |table_name|
561
+ schema_cache.clear_data_source_cache!(table_name.to_s)
562
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
563
+ end
529
564
  end
530
565
 
531
566
  # Add a new +type+ column named +column_name+ to +table_name+.
532
567
  #
533
568
  # See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column].
534
569
  #
535
- # The +type+ parameter is normally one of the migrations native types,
570
+ # The +type+ parameter is normally one of the migration's native types,
536
571
  # which is one of the following:
537
572
  # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
538
573
  # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
@@ -677,7 +712,7 @@ module ActiveRecord
677
712
  #
678
713
  # If the options provided include an +if_exists+ key, it will be used to check if the
679
714
  # column does not exist. This will silently ignore the migration rather than raising
680
- # if the column was already used.
715
+ # if the column was already removed.
681
716
  #
682
717
  # remove_column(:suppliers, :qualification, if_exists: true)
683
718
  def remove_column(table_name, column_name, type = nil, **options)
@@ -839,6 +874,16 @@ module ActiveRecord
839
874
  #
840
875
  # Note: only supported by PostgreSQL.
841
876
  #
877
+ # ====== Creating an index where NULLs are treated equally
878
+ #
879
+ # add_index(:people, :last_name, nulls_not_distinct: true)
880
+ #
881
+ # generates:
882
+ #
883
+ # CREATE INDEX index_people_on_last_name ON people (last_name) NULLS NOT DISTINCT
884
+ #
885
+ # Note: only supported by PostgreSQL version 15.0.0 and greater.
886
+ #
842
887
  # ====== Creating an index with a specific method
843
888
  #
844
889
  # add_index(:developers, :name, using: 'btree')
@@ -876,13 +921,29 @@ module ActiveRecord
876
921
  # ====== Creating an index with a specific algorithm
877
922
  #
878
923
  # add_index(:developers, :name, algorithm: :concurrently)
879
- # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
924
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name) -- PostgreSQL
880
925
  #
881
- # Note: only supported by PostgreSQL.
926
+ # add_index(:developers, :name, algorithm: :inplace)
927
+ # # CREATE INDEX `index_developers_on_name` ON `developers` (`name`) ALGORITHM = INPLACE -- MySQL
928
+ #
929
+ # Note: only supported by PostgreSQL and MySQL.
882
930
  #
883
931
  # Concurrently adding an index is not supported in a transaction.
884
932
  #
885
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
+ #
886
947
  def add_index(table_name, column_name, **options)
887
948
  create_index = build_create_index_definition(table_name, column_name, **options)
888
949
  execute schema_creation.accept(create_index)
@@ -1143,9 +1204,10 @@ module ActiveRecord
1143
1204
  # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1144
1205
  def add_foreign_key(from_table, to_table, **options)
1145
1206
  return unless use_foreign_keys?
1146
- return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1147
1207
 
1148
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
+
1149
1211
  at = create_alter_table from_table
1150
1212
  at.add_foreign_key to_table, options
1151
1213
 
@@ -1184,7 +1246,7 @@ module ActiveRecord
1184
1246
  # The name of the table that contains the referenced primary key.
1185
1247
  def remove_foreign_key(from_table, to_table = nil, **options)
1186
1248
  return unless use_foreign_keys?
1187
- 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))
1188
1250
 
1189
1251
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1190
1252
 
@@ -1305,7 +1367,6 @@ module ActiveRecord
1305
1367
  execute schema_creation.accept(at)
1306
1368
  end
1307
1369
 
1308
-
1309
1370
  # Checks to see if a check constraint exists on a table for a given check constraint definition.
1310
1371
  #
1311
1372
  # check_constraint_exists?(:products, name: "price_check")
@@ -1317,8 +1378,15 @@ module ActiveRecord
1317
1378
  check_constraint_for(table_name, **options).present?
1318
1379
  end
1319
1380
 
1320
- def dump_schema_information # :nodoc:
1321
- versions = schema_migration.versions
1381
+ def remove_constraint(table_name, constraint_name) # :nodoc:
1382
+ at = create_alter_table(table_name)
1383
+ at.drop_constraint(constraint_name)
1384
+
1385
+ execute schema_creation.accept(at)
1386
+ end
1387
+
1388
+ def dump_schema_versions # :nodoc:
1389
+ versions = pool.schema_migration.versions
1322
1390
  insert_versions_sql(versions) if versions.any?
1323
1391
  end
1324
1392
 
@@ -1328,8 +1396,9 @@ module ActiveRecord
1328
1396
 
1329
1397
  def assume_migrated_upto_version(version)
1330
1398
  version = version.to_i
1331
- sm_table = quote_table_name(schema_migration.table_name)
1399
+ sm_table = quote_table_name(pool.schema_migration.table_name)
1332
1400
 
1401
+ migration_context = pool.migration_context
1333
1402
  migrated = migration_context.get_all_versions
1334
1403
  versions = migration_context.migrations.map(&:version)
1335
1404
 
@@ -1438,7 +1507,7 @@ module ActiveRecord
1438
1507
  end
1439
1508
 
1440
1509
  def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1441
- 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)
1442
1511
 
1443
1512
  column_names = index_column_names(column_name)
1444
1513
 
@@ -1447,7 +1516,7 @@ module ActiveRecord
1447
1516
 
1448
1517
  validate_index_length!(table_name, index_name, internal)
1449
1518
 
1450
- index = IndexDefinition.new(
1519
+ index = create_index_definition(
1451
1520
  table_name, index_name,
1452
1521
  options[:unique],
1453
1522
  column_names,
@@ -1502,6 +1571,20 @@ module ActiveRecord
1502
1571
  raise NotImplementedError, "#{self.class} does not support changing column comments"
1503
1572
  end
1504
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
+
1505
1588
  def create_schema_dumper(options) # :nodoc:
1506
1589
  SchemaDumper.create(self, options)
1507
1590
  end
@@ -1568,7 +1651,7 @@ module ActiveRecord
1568
1651
  name = "idx_on_#{Array(column) * '_'}"
1569
1652
 
1570
1653
  short_limit = max_index_name_size - hashed_identifier.bytesize
1571
- short_name = name.mb_chars.limit(short_limit).to_s
1654
+ short_name = name.truncate_bytes(short_limit, omission: nil)
1572
1655
 
1573
1656
  "#{short_name}#{hashed_identifier}"
1574
1657
  end
@@ -1590,6 +1673,10 @@ module ActiveRecord
1590
1673
  end
1591
1674
  end
1592
1675
 
1676
+ def valid_index_options
1677
+ [:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct]
1678
+ end
1679
+
1593
1680
  def options_for_index_columns(options)
1594
1681
  if options.is_a?(Hash)
1595
1682
  options.symbolize_keys
@@ -1666,6 +1753,10 @@ module ActiveRecord
1666
1753
  TableDefinition.new(self, name, **options)
1667
1754
  end
1668
1755
 
1756
+ def create_index_definition(table_name, name, unique, columns, **options)
1757
+ IndexDefinition.new(table_name, name, unique, columns, **options)
1758
+ end
1759
+
1669
1760
  def create_alter_table(name)
1670
1761
  AlterTable.new create_table_definition(name)
1671
1762
  end
@@ -1728,7 +1819,20 @@ module ActiveRecord
1728
1819
 
1729
1820
  def foreign_key_for(from_table, **options)
1730
1821
  return unless use_foreign_keys?
1731
- 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) }
1732
1836
  end
1733
1837
 
1734
1838
  def foreign_key_for!(from_table, to_table: nil, **options)
@@ -1771,13 +1875,19 @@ module ActiveRecord
1771
1875
 
1772
1876
  def validate_index_length!(table_name, new_name, internal = false)
1773
1877
  if new_name.length > index_name_length
1774
- 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
1775
1882
  end
1776
1883
  end
1777
1884
 
1778
1885
  def validate_table_length!(table_name)
1779
1886
  if table_name.length > table_name_length
1780
- 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
1781
1891
  end
1782
1892
  end
1783
1893
 
@@ -1839,16 +1949,8 @@ module ActiveRecord
1839
1949
  end
1840
1950
 
1841
1951
  def insert_versions_sql(versions)
1842
- sm_table = quote_table_name(schema_migration.table_name)
1843
-
1844
- if versions.is_a?(Array)
1845
- sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1846
- sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
1847
- sql << ";"
1848
- sql
1849
- else
1850
- "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
1851
- end
1952
+ versions_formatter = ActiveRecord.schema_versions_formatter.new(self)
1953
+ versions_formatter.format(versions)
1852
1954
  end
1853
1955
 
1854
1956
  def data_source_sql(name = nil, type: nil)