activerecord 7.2.3 → 8.1.3

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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +612 -1055
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +35 -11
  6. data/lib/active_record/associations/builder/association.rb +23 -11
  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_association.rb +1 -1
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  17. data/lib/active_record/associations/join_dependency.rb +4 -2
  18. data/lib/active_record/associations/preloader/association.rb +2 -2
  19. data/lib/active_record/associations/preloader/batch.rb +7 -1
  20. data/lib/active_record/associations/preloader/branch.rb +1 -0
  21. data/lib/active_record/associations/singular_association.rb +8 -3
  22. data/lib/active_record/associations.rb +192 -24
  23. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  24. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  25. data/lib/active_record/attribute_methods/query.rb +34 -0
  26. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  27. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  28. data/lib/active_record/attributes.rb +3 -0
  29. data/lib/active_record/autosave_association.rb +69 -27
  30. data/lib/active_record/base.rb +1 -2
  31. data/lib/active_record/coders/json.rb +14 -5
  32. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  33. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  34. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
  36. data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
  37. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  38. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  39. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  40. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  41. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
  43. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  44. data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
  45. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
  46. data/lib/active_record/connection_adapters/column.rb +17 -4
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  49. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  57. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  58. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  61. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  62. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  63. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  64. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  67. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  72. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  73. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
  74. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
  75. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  78. data/lib/active_record/connection_adapters.rb +1 -56
  79. data/lib/active_record/connection_handling.rb +25 -2
  80. data/lib/active_record/core.rb +33 -17
  81. data/lib/active_record/counter_cache.rb +33 -8
  82. data/lib/active_record/database_configurations/database_config.rb +9 -1
  83. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  84. data/lib/active_record/database_configurations/url_config.rb +13 -3
  85. data/lib/active_record/database_configurations.rb +7 -3
  86. data/lib/active_record/delegated_type.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +54 -69
  88. data/lib/active_record/encryption/config.rb +3 -1
  89. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  90. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  91. data/lib/active_record/encryption/encryptor.rb +28 -8
  92. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  93. data/lib/active_record/encryption/scheme.rb +9 -2
  94. data/lib/active_record/enum.rb +33 -30
  95. data/lib/active_record/errors.rb +33 -9
  96. data/lib/active_record/explain.rb +1 -1
  97. data/lib/active_record/explain_registry.rb +51 -2
  98. data/lib/active_record/filter_attribute_handler.rb +73 -0
  99. data/lib/active_record/fixtures.rb +2 -4
  100. data/lib/active_record/future_result.rb +15 -9
  101. data/lib/active_record/gem_version.rb +2 -2
  102. data/lib/active_record/inheritance.rb +1 -1
  103. data/lib/active_record/insert_all.rb +14 -9
  104. data/lib/active_record/locking/optimistic.rb +8 -1
  105. data/lib/active_record/locking/pessimistic.rb +5 -0
  106. data/lib/active_record/log_subscriber.rb +3 -13
  107. data/lib/active_record/middleware/shard_selector.rb +34 -17
  108. data/lib/active_record/migration/command_recorder.rb +45 -12
  109. data/lib/active_record/migration/compatibility.rb +37 -24
  110. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  111. data/lib/active_record/migration.rb +48 -42
  112. data/lib/active_record/model_schema.rb +38 -13
  113. data/lib/active_record/nested_attributes.rb +6 -6
  114. data/lib/active_record/persistence.rb +162 -133
  115. data/lib/active_record/query_cache.rb +22 -15
  116. data/lib/active_record/query_logs.rb +100 -52
  117. data/lib/active_record/query_logs_formatter.rb +17 -28
  118. data/lib/active_record/querying.rb +8 -8
  119. data/lib/active_record/railtie.rb +35 -30
  120. data/lib/active_record/railties/controller_runtime.rb +11 -6
  121. data/lib/active_record/railties/databases.rake +26 -38
  122. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  123. data/lib/active_record/railties/job_runtime.rb +10 -11
  124. data/lib/active_record/reflection.rb +53 -21
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  126. data/lib/active_record/relation/batches.rb +147 -73
  127. data/lib/active_record/relation/calculations.rb +52 -40
  128. data/lib/active_record/relation/delegation.rb +25 -15
  129. data/lib/active_record/relation/finder_methods.rb +40 -24
  130. data/lib/active_record/relation/merger.rb +8 -8
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  132. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  133. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  135. data/lib/active_record/relation/predicate_builder.rb +22 -7
  136. data/lib/active_record/relation/query_attribute.rb +3 -1
  137. data/lib/active_record/relation/query_methods.rb +140 -86
  138. data/lib/active_record/relation/spawn_methods.rb +7 -7
  139. data/lib/active_record/relation/where_clause.rb +2 -9
  140. data/lib/active_record/relation.rb +107 -75
  141. data/lib/active_record/result.rb +109 -24
  142. data/lib/active_record/runtime_registry.rb +42 -58
  143. data/lib/active_record/sanitization.rb +9 -6
  144. data/lib/active_record/schema_dumper.rb +18 -11
  145. data/lib/active_record/schema_migration.rb +2 -1
  146. data/lib/active_record/scoping/named.rb +5 -2
  147. data/lib/active_record/scoping.rb +0 -1
  148. data/lib/active_record/signed_id.rb +43 -15
  149. data/lib/active_record/statement_cache.rb +24 -20
  150. data/lib/active_record/store.rb +51 -22
  151. data/lib/active_record/structured_event_subscriber.rb +85 -0
  152. data/lib/active_record/table_metadata.rb +6 -23
  153. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  154. data/lib/active_record/tasks/database_tasks.rb +85 -85
  155. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  156. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  157. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  158. data/lib/active_record/test_databases.rb +14 -4
  159. data/lib/active_record/test_fixtures.rb +39 -2
  160. data/lib/active_record/testing/query_assertions.rb +8 -2
  161. data/lib/active_record/timestamp.rb +4 -2
  162. data/lib/active_record/token_for.rb +1 -1
  163. data/lib/active_record/transaction.rb +2 -5
  164. data/lib/active_record/transactions.rb +37 -16
  165. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  166. data/lib/active_record/type/internal/timezone.rb +7 -0
  167. data/lib/active_record/type/json.rb +13 -2
  168. data/lib/active_record/type/serialized.rb +16 -4
  169. data/lib/active_record/type/type_map.rb +1 -1
  170. data/lib/active_record/type_caster/connection.rb +2 -1
  171. data/lib/active_record/validations/associated.rb +1 -1
  172. data/lib/active_record/validations/uniqueness.rb +8 -8
  173. data/lib/active_record.rb +84 -49
  174. data/lib/arel/alias_predication.rb +2 -0
  175. data/lib/arel/collectors/bind.rb +2 -2
  176. data/lib/arel/collectors/sql_string.rb +1 -1
  177. data/lib/arel/collectors/substitute_binds.rb +2 -2
  178. data/lib/arel/crud.rb +6 -11
  179. data/lib/arel/nodes/binary.rb +1 -1
  180. data/lib/arel/nodes/count.rb +2 -2
  181. data/lib/arel/nodes/function.rb +4 -10
  182. data/lib/arel/nodes/named_function.rb +2 -2
  183. data/lib/arel/nodes/node.rb +2 -2
  184. data/lib/arel/nodes/sql_literal.rb +1 -1
  185. data/lib/arel/nodes.rb +0 -2
  186. data/lib/arel/predications.rb +1 -3
  187. data/lib/arel/select_manager.rb +7 -2
  188. data/lib/arel/table.rb +3 -7
  189. data/lib/arel/visitors/dot.rb +0 -3
  190. data/lib/arel/visitors/postgresql.rb +55 -0
  191. data/lib/arel/visitors/sqlite.rb +55 -8
  192. data/lib/arel/visitors/to_sql.rb +3 -21
  193. data/lib/arel.rb +3 -1
  194. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  195. metadata +16 -13
  196. data/lib/active_record/explain_subscriber.rb +0 -34
  197. data/lib/active_record/normalization.rb +0 -163
  198. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -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:
@@ -304,44 +307,32 @@ module ActiveRecord
304
307
  module ColumnMethods
305
308
  extend ActiveSupport::Concern
306
309
 
307
- # Appends a primary key definition to the table definition.
308
- # Can be called multiple times, but this is probably not a good idea.
309
- def primary_key(name, type = :primary_key, **options)
310
- column(name, type, **options.merge(primary_key: true))
311
- end
312
-
313
- ##
314
- # :method: column
315
- # :call-seq: column(name, type, **options)
316
- #
317
- # Appends a column or columns of a specified type.
318
- #
319
- # t.string(:goat)
320
- # t.string(:goat, :sheep)
321
- #
322
- # See TableDefinition#column
323
-
324
- included do
325
- define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
326
- :float, :integer, :json, :string, :text, :time, :timestamp, :virtual
327
-
328
- alias :blob :binary
329
- alias :numeric :decimal
330
- end
331
-
332
310
  class_methods do
333
- def define_column_methods(*column_types) # :nodoc:
334
- column_types.each do |column_type|
335
- 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
336
315
  def #{column_type}(*names, **options)
337
316
  raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
338
317
  names.each { |name| column(name, :#{column_type}, **options) }
339
318
  end
340
- RUBY
319
+ RUBY
320
+ end
341
321
  end
342
- end
343
- private :define_column_methods
344
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
345
336
  end
346
337
 
347
338
  # = Active Record Connection Adapters \Table \Definition
@@ -352,7 +343,7 @@ module ActiveRecord
352
343
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
353
344
  # is actually of this type:
354
345
  #
355
- # class SomeMigration < ActiveRecord::Migration[7.2]
346
+ # class SomeMigration < ActiveRecord::Migration[8.1]
356
347
  # def up
357
348
  # create_table :foo do |t|
358
349
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -626,6 +617,7 @@ module ActiveRecord
626
617
  attr_reader :adds
627
618
  attr_reader :foreign_key_adds, :foreign_key_drops
628
619
  attr_reader :check_constraint_adds, :check_constraint_drops
620
+ attr_reader :constraint_drops
629
621
 
630
622
  def initialize(td)
631
623
  @td = td
@@ -634,6 +626,7 @@ module ActiveRecord
634
626
  @foreign_key_drops = []
635
627
  @check_constraint_adds = []
636
628
  @check_constraint_drops = []
629
+ @constraint_drops = []
637
630
  end
638
631
 
639
632
  def name; @td.name; end
@@ -654,6 +647,10 @@ module ActiveRecord
654
647
  @check_constraint_drops << constraint_name
655
648
  end
656
649
 
650
+ def drop_constraint(constraint_name)
651
+ @constraint_drops << constraint_name
652
+ end
653
+
657
654
  def add_column(name, type, **options)
658
655
  name = name.to_s
659
656
  type = type.to_sym
@@ -760,7 +757,7 @@ module ActiveRecord
760
757
  # end
761
758
  #
762
759
  # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
763
- def index_exists?(column_name, **options)
760
+ def index_exists?(column_name = nil, **options)
764
761
  @base.index_exists?(name, column_name, **options)
765
762
  end
766
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
 
@@ -348,11 +349,22 @@ module ActiveRecord
348
349
  # # Creates a table called 'assemblies_parts' with no id.
349
350
  # create_join_table(:assemblies, :parts)
350
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
+ #
351
365
  # You can pass an +options+ hash which can include the following keys:
352
366
  # [<tt>:table_name</tt>]
353
367
  # Sets the table name, overriding the default.
354
- # [<tt>:column_options</tt>]
355
- # Any extra options you want appended to the columns definition.
356
368
  # [<tt>:options</tt>]
357
369
  # Any extra options you want appended to the table definition.
358
370
  # [<tt>:temporary</tt>]
@@ -369,6 +381,19 @@ module ActiveRecord
369
381
  # t.index :category_id
370
382
  # end
371
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
+ #
372
397
  # ====== Add a backend specific option to the generated SQL (MySQL)
373
398
  #
374
399
  # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
@@ -519,7 +544,7 @@ module ActiveRecord
519
544
  raise NotImplementedError, "rename_table is not implemented"
520
545
  end
521
546
 
522
- # Drops a table from the database.
547
+ # Drops a table or tables from the database.
523
548
  #
524
549
  # [<tt>:force</tt>]
525
550
  # Set to +:cascade+ to drop dependent objects as well.
@@ -530,17 +555,19 @@ module ActiveRecord
530
555
  #
531
556
  # Although this command ignores most +options+ and the block if one is given,
532
557
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
533
- # In that case, +options+ and the block will be used by #create_table.
534
- def drop_table(table_name, **options)
535
- schema_cache.clear_data_source_cache!(table_name.to_s)
536
- 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
537
564
  end
538
565
 
539
566
  # Add a new +type+ column named +column_name+ to +table_name+.
540
567
  #
541
568
  # See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column].
542
569
  #
543
- # The +type+ parameter is normally one of the migrations native types,
570
+ # The +type+ parameter is normally one of the migration's native types,
544
571
  # which is one of the following:
545
572
  # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
546
573
  # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
@@ -847,6 +874,16 @@ module ActiveRecord
847
874
  #
848
875
  # Note: only supported by PostgreSQL.
849
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
+ #
850
887
  # ====== Creating an index with a specific method
851
888
  #
852
889
  # add_index(:developers, :name, using: 'btree')
@@ -894,6 +931,19 @@ module ActiveRecord
894
931
  # Concurrently adding an index is not supported in a transaction.
895
932
  #
896
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
+ #
897
947
  def add_index(table_name, column_name, **options)
898
948
  create_index = build_create_index_definition(table_name, column_name, **options)
899
949
  execute schema_creation.accept(create_index)
@@ -1154,9 +1204,10 @@ module ActiveRecord
1154
1204
  # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1155
1205
  def add_foreign_key(from_table, to_table, **options)
1156
1206
  return unless use_foreign_keys?
1157
- return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1158
1207
 
1159
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
+
1160
1211
  at = create_alter_table from_table
1161
1212
  at.add_foreign_key to_table, options
1162
1213
 
@@ -1195,7 +1246,7 @@ module ActiveRecord
1195
1246
  # The name of the table that contains the referenced primary key.
1196
1247
  def remove_foreign_key(from_table, to_table = nil, **options)
1197
1248
  return unless use_foreign_keys?
1198
- 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))
1199
1250
 
1200
1251
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1201
1252
 
@@ -1316,7 +1367,6 @@ module ActiveRecord
1316
1367
  execute schema_creation.accept(at)
1317
1368
  end
1318
1369
 
1319
-
1320
1370
  # Checks to see if a check constraint exists on a table for a given check constraint definition.
1321
1371
  #
1322
1372
  # check_constraint_exists?(:products, name: "price_check")
@@ -1328,7 +1378,14 @@ module ActiveRecord
1328
1378
  check_constraint_for(table_name, **options).present?
1329
1379
  end
1330
1380
 
1331
- def dump_schema_information # :nodoc:
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:
1332
1389
  versions = pool.schema_migration.versions
1333
1390
  insert_versions_sql(versions) if versions.any?
1334
1391
  end
@@ -1450,7 +1507,7 @@ module ActiveRecord
1450
1507
  end
1451
1508
 
1452
1509
  def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1453
- 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)
1454
1511
 
1455
1512
  column_names = index_column_names(column_name)
1456
1513
 
@@ -1459,7 +1516,7 @@ module ActiveRecord
1459
1516
 
1460
1517
  validate_index_length!(table_name, index_name, internal)
1461
1518
 
1462
- index = IndexDefinition.new(
1519
+ index = create_index_definition(
1463
1520
  table_name, index_name,
1464
1521
  options[:unique],
1465
1522
  column_names,
@@ -1514,6 +1571,20 @@ module ActiveRecord
1514
1571
  raise NotImplementedError, "#{self.class} does not support changing column comments"
1515
1572
  end
1516
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
+
1517
1588
  def create_schema_dumper(options) # :nodoc:
1518
1589
  SchemaDumper.create(self, options)
1519
1590
  end
@@ -1533,11 +1604,11 @@ module ActiveRecord
1533
1604
  non_combinable_operations = []
1534
1605
 
1535
1606
  operations.each do |command, args|
1536
- table, arguments = args.shift, args
1607
+ args.shift # remove table_name
1537
1608
  method = :"#{command}_for_alter"
1538
1609
 
1539
1610
  if respond_to?(method, true)
1540
- sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
1611
+ sqls, procs = Array(send(method, table_name, *args)).partition { |v| v.is_a?(String) }
1541
1612
  sql_fragments.concat(sqls)
1542
1613
  non_combinable_operations.concat(procs)
1543
1614
  else
@@ -1545,7 +1616,7 @@ module ActiveRecord
1545
1616
  non_combinable_operations.each(&:call)
1546
1617
  sql_fragments = []
1547
1618
  non_combinable_operations = []
1548
- send(command, table, *arguments)
1619
+ send(command, table_name, *args)
1549
1620
  end
1550
1621
  end
1551
1622
 
@@ -1580,7 +1651,7 @@ module ActiveRecord
1580
1651
  name = "idx_on_#{Array(column) * '_'}"
1581
1652
 
1582
1653
  short_limit = max_index_name_size - hashed_identifier.bytesize
1583
- short_name = name.mb_chars.limit(short_limit).to_s
1654
+ short_name = name.truncate_bytes(short_limit, omission: nil)
1584
1655
 
1585
1656
  "#{short_name}#{hashed_identifier}"
1586
1657
  end
@@ -1602,6 +1673,10 @@ module ActiveRecord
1602
1673
  end
1603
1674
  end
1604
1675
 
1676
+ def valid_index_options
1677
+ [:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct]
1678
+ end
1679
+
1605
1680
  def options_for_index_columns(options)
1606
1681
  if options.is_a?(Hash)
1607
1682
  options.symbolize_keys
@@ -1678,6 +1753,10 @@ module ActiveRecord
1678
1753
  TableDefinition.new(self, name, **options)
1679
1754
  end
1680
1755
 
1756
+ def create_index_definition(table_name, name, unique, columns, **options)
1757
+ IndexDefinition.new(table_name, name, unique, columns, **options)
1758
+ end
1759
+
1681
1760
  def create_alter_table(name)
1682
1761
  AlterTable.new create_table_definition(name)
1683
1762
  end
@@ -1740,7 +1819,20 @@ module ActiveRecord
1740
1819
 
1741
1820
  def foreign_key_for(from_table, **options)
1742
1821
  return unless use_foreign_keys?
1743
- 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) }
1744
1836
  end
1745
1837
 
1746
1838
  def foreign_key_for!(from_table, to_table: nil, **options)
@@ -1783,13 +1875,19 @@ module ActiveRecord
1783
1875
 
1784
1876
  def validate_index_length!(table_name, new_name, internal = false)
1785
1877
  if new_name.length > index_name_length
1786
- 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
1787
1882
  end
1788
1883
  end
1789
1884
 
1790
1885
  def validate_table_length!(table_name)
1791
1886
  if table_name.length > table_name_length
1792
- 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
1793
1891
  end
1794
1892
  end
1795
1893
 
@@ -1851,16 +1949,8 @@ module ActiveRecord
1851
1949
  end
1852
1950
 
1853
1951
  def insert_versions_sql(versions)
1854
- sm_table = quote_table_name(pool.schema_migration.table_name)
1855
-
1856
- if versions.is_a?(Array)
1857
- sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1858
- sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
1859
- sql << ";"
1860
- sql
1861
- else
1862
- "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
1863
- end
1952
+ versions_formatter = ActiveRecord.schema_versions_formatter.new(self)
1953
+ versions_formatter.format(versions)
1864
1954
  end
1865
1955
 
1866
1956
  def data_source_sql(name = nil, type: nil)
@@ -112,6 +112,7 @@ module ActiveRecord
112
112
  def closed?; true; end
113
113
  def open?; false; end
114
114
  def joinable?; false; end
115
+ def isolation; nil; end
115
116
  def add_record(record, _ = true); end
116
117
  def restartable?; false; end
117
118
  def dirty?; false; end
@@ -123,6 +124,7 @@ module ActiveRecord
123
124
  def after_commit; yield; end
124
125
  def after_rollback; end
125
126
  def user_transaction; ActiveRecord::Transaction::NULL_TRANSACTION; end
127
+ def isolation=(_); end
126
128
  end
127
129
 
128
130
  class Transaction # :nodoc:
@@ -150,6 +152,15 @@ module ActiveRecord
150
152
 
151
153
  delegate :invalidate!, :invalidated?, to: :@state
152
154
 
155
+ # Returns the isolation level if it was explicitly set, nil otherwise
156
+ def isolation
157
+ @isolation_level
158
+ end
159
+
160
+ def isolation=(isolation) # :nodoc:
161
+ @isolation_level = isolation
162
+ end
163
+
153
164
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
154
165
  super()
155
166
  @connection = connection
@@ -175,11 +186,11 @@ module ActiveRecord
175
186
  end
176
187
 
177
188
  def open?
178
- true
189
+ !closed?
179
190
  end
180
191
 
181
192
  def closed?
182
- false
193
+ @state.finalized?
183
194
  end
184
195
 
185
196
  def add_record(record, ensure_finalize = true)
@@ -386,7 +397,7 @@ module ActiveRecord
386
397
  @parent.state.add_child(@state)
387
398
  end
388
399
 
389
- delegate :materialize!, :materialized?, :restart, to: :@parent
400
+ delegate :materialize!, :materialized?, :restart, :isolation, to: :@parent
390
401
 
391
402
  def rollback
392
403
  @state.rollback!
@@ -405,6 +416,7 @@ module ActiveRecord
405
416
  def initialize(connection, savepoint_name, parent_transaction, **options)
406
417
  super(connection, **options)
407
418
 
419
+ @parent_transaction = parent_transaction
408
420
  parent_transaction.state.add_child(@state)
409
421
 
410
422
  if isolation_level
@@ -414,6 +426,15 @@ module ActiveRecord
414
426
  @savepoint_name = savepoint_name
415
427
  end
416
428
 
429
+ # Delegates to parent transaction's isolation level
430
+ def isolation
431
+ @parent_transaction.isolation
432
+ end
433
+
434
+ def isolation=(isolation) # :nodoc:
435
+ @parent_transaction.isolation = isolation
436
+ end
437
+
417
438
  def materialize!
418
439
  connection.create_savepoint(savepoint_name)
419
440
  super
@@ -448,10 +469,14 @@ module ActiveRecord
448
469
  # = Active Record Real \Transaction
449
470
  class RealTransaction < Transaction
450
471
  def materialize!
451
- if isolation_level
452
- connection.begin_isolated_db_transaction(isolation_level)
472
+ if joinable?
473
+ if isolation_level
474
+ connection.begin_isolated_db_transaction(isolation_level)
475
+ else
476
+ connection.begin_db_transaction
477
+ end
453
478
  else
454
- connection.begin_db_transaction
479
+ connection.begin_deferred_transaction(isolation_level)
455
480
  end
456
481
 
457
482
  super
@@ -472,13 +497,19 @@ module ActiveRecord
472
497
  end
473
498
 
474
499
  def rollback
475
- connection.rollback_db_transaction if materialized?
500
+ if materialized?
501
+ connection.rollback_db_transaction
502
+ connection.reset_isolation_level if isolation_level
503
+ end
476
504
  @state.full_rollback!
477
505
  @instrumenter.finish(:rollback) if materialized?
478
506
  end
479
507
 
480
508
  def commit
481
- connection.commit_db_transaction if materialized?
509
+ if materialized?
510
+ connection.commit_db_transaction
511
+ connection.reset_isolation_level if isolation_level
512
+ end
482
513
  @state.full_commit!
483
514
  @instrumenter.finish(:commit) if materialized?
484
515
  end
@@ -610,6 +641,7 @@ module ActiveRecord
610
641
  end
611
642
 
612
643
  def within_new_transaction(isolation: nil, joinable: true)
644
+ isolation ||= @connection.pool.pool_transaction_isolation_level
613
645
  @connection.lock.synchronize do
614
646
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
615
647
  begin