activerecord 7.2.3 → 8.0.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.

Potentially problematic release.


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

Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +192 -1261
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/associations/alias_tracker.rb +4 -6
  5. data/lib/active_record/associations/association.rb +25 -5
  6. data/lib/active_record/associations/belongs_to_association.rb +2 -18
  7. data/lib/active_record/associations/builder/association.rb +7 -6
  8. data/lib/active_record/associations/collection_association.rb +4 -4
  9. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  10. data/lib/active_record/associations/has_many_through_association.rb +4 -9
  11. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  12. data/lib/active_record/associations/preloader/association.rb +2 -2
  13. data/lib/active_record/associations/singular_association.rb +8 -3
  14. data/lib/active_record/associations.rb +50 -32
  15. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  16. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +19 -24
  18. data/lib/active_record/attributes.rb +26 -37
  19. data/lib/active_record/autosave_association.rb +81 -49
  20. data/lib/active_record/base.rb +2 -2
  21. data/lib/active_record/callbacks.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  23. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  24. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -75
  26. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  27. data/lib/active_record/connection_adapters/abstract/query_cache.rb +14 -19
  28. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  29. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -6
  30. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +27 -9
  31. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  32. data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -57
  33. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -58
  34. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -15
  35. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  36. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
  37. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  38. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -16
  39. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  40. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  41. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  42. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +12 -14
  43. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +51 -9
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +44 -101
  46. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  47. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  48. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -13
  49. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  50. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -2
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +60 -22
  53. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  54. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  55. data/lib/active_record/connection_handling.rb +29 -11
  56. data/lib/active_record/core.rb +15 -60
  57. data/lib/active_record/counter_cache.rb +1 -1
  58. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -3
  59. data/lib/active_record/delegated_type.rb +18 -18
  60. data/lib/active_record/encryption/config.rb +3 -1
  61. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  62. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  63. data/lib/active_record/encryption/encryptor.rb +35 -29
  64. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  65. data/lib/active_record/encryption/scheme.rb +8 -1
  66. data/lib/active_record/enum.rb +12 -13
  67. data/lib/active_record/errors.rb +16 -8
  68. data/lib/active_record/fixture_set/table_row.rb +2 -19
  69. data/lib/active_record/fixtures.rb +0 -1
  70. data/lib/active_record/future_result.rb +14 -10
  71. data/lib/active_record/gem_version.rb +4 -4
  72. data/lib/active_record/insert_all.rb +1 -1
  73. data/lib/active_record/marshalling.rb +1 -4
  74. data/lib/active_record/migration/command_recorder.rb +22 -5
  75. data/lib/active_record/migration/compatibility.rb +5 -2
  76. data/lib/active_record/migration.rb +36 -35
  77. data/lib/active_record/model_schema.rb +1 -1
  78. data/lib/active_record/nested_attributes.rb +4 -6
  79. data/lib/active_record/persistence.rb +128 -130
  80. data/lib/active_record/query_cache.rb +5 -4
  81. data/lib/active_record/query_logs.rb +98 -44
  82. data/lib/active_record/query_logs_formatter.rb +17 -28
  83. data/lib/active_record/querying.rb +10 -10
  84. data/lib/active_record/railtie.rb +5 -6
  85. data/lib/active_record/railties/databases.rake +1 -2
  86. data/lib/active_record/reflection.rb +9 -7
  87. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  88. data/lib/active_record/relation/batches.rb +132 -72
  89. data/lib/active_record/relation/calculations.rb +55 -55
  90. data/lib/active_record/relation/delegation.rb +25 -14
  91. data/lib/active_record/relation/finder_methods.rb +31 -32
  92. data/lib/active_record/relation/merger.rb +8 -8
  93. data/lib/active_record/relation/predicate_builder/association_query_value.rb +0 -2
  94. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  95. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  96. data/lib/active_record/relation/predicate_builder.rb +5 -0
  97. data/lib/active_record/relation/query_attribute.rb +1 -1
  98. data/lib/active_record/relation/query_methods.rb +90 -91
  99. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  100. data/lib/active_record/relation/spawn_methods.rb +1 -1
  101. data/lib/active_record/relation/where_clause.rb +2 -8
  102. data/lib/active_record/relation.rb +77 -76
  103. data/lib/active_record/result.rb +68 -7
  104. data/lib/active_record/sanitization.rb +7 -6
  105. data/lib/active_record/schema_dumper.rb +16 -29
  106. data/lib/active_record/schema_migration.rb +2 -1
  107. data/lib/active_record/scoping/named.rb +5 -2
  108. data/lib/active_record/secure_token.rb +3 -3
  109. data/lib/active_record/signed_id.rb +6 -7
  110. data/lib/active_record/statement_cache.rb +12 -12
  111. data/lib/active_record/store.rb +7 -3
  112. data/lib/active_record/tasks/database_tasks.rb +24 -15
  113. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  114. data/lib/active_record/tasks/postgresql_database_tasks.rb +0 -7
  115. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  116. data/lib/active_record/test_fixtures.rb +12 -0
  117. data/lib/active_record/testing/query_assertions.rb +2 -2
  118. data/lib/active_record/token_for.rb +1 -1
  119. data/lib/active_record/transactions.rb +1 -3
  120. data/lib/active_record/validations/uniqueness.rb +8 -8
  121. data/lib/active_record.rb +16 -1
  122. data/lib/arel/collectors/bind.rb +1 -1
  123. data/lib/arel/crud.rb +0 -2
  124. data/lib/arel/delete_manager.rb +0 -5
  125. data/lib/arel/nodes/delete_statement.rb +2 -4
  126. data/lib/arel/nodes/update_statement.rb +2 -4
  127. data/lib/arel/select_manager.rb +2 -6
  128. data/lib/arel/update_manager.rb +0 -5
  129. data/lib/arel/visitors/dot.rb +0 -2
  130. data/lib/arel/visitors/sqlite.rb +0 -25
  131. data/lib/arel/visitors/to_sql.rb +1 -3
  132. metadata +14 -11
@@ -102,16 +102,16 @@ module ActiveRecord
102
102
  select_all(arel, name, binds, async: async).then(&:rows)
103
103
  end
104
104
 
105
- def query_value(sql, name = nil) # :nodoc:
106
- single_value_from_rows(query(sql, name))
105
+ def query_value(...) # :nodoc:
106
+ single_value_from_rows(query(...))
107
107
  end
108
108
 
109
- def query_values(sql, name = nil) # :nodoc:
110
- query(sql, name).map(&:first)
109
+ def query_values(...) # :nodoc:
110
+ query(...).map(&:first)
111
111
  end
112
112
 
113
- def query(sql, name = nil) # :nodoc:
114
- internal_exec_query(sql, name).rows
113
+ def query(...) # :nodoc:
114
+ internal_exec_query(...).rows
115
115
  end
116
116
 
117
117
  # Determines whether the SQL statement is a write query.
@@ -163,14 +163,14 @@ module ActiveRecord
163
163
  # +binds+ as the bind substitutes. +name+ is logged along with
164
164
  # the executed +sql+ statement.
165
165
  def exec_delete(sql, name = nil, binds = [])
166
- internal_exec_query(sql, name, binds)
166
+ affected_rows(internal_execute(sql, name, binds))
167
167
  end
168
168
 
169
169
  # Executes update +sql+ statement in the context of this connection using
170
170
  # +binds+ as the bind substitutes. +name+ is logged along with
171
171
  # the executed +sql+ statement.
172
172
  def exec_update(sql, name = nil, binds = [])
173
- internal_exec_query(sql, name, binds)
173
+ affected_rows(internal_execute(sql, name, binds))
174
174
  end
175
175
 
176
176
  def exec_insert_all(sql, name) # :nodoc:
@@ -224,11 +224,9 @@ module ActiveRecord
224
224
 
225
225
  return if table_names.empty?
226
226
 
227
- with_multi_statements do
228
- disable_referential_integrity do
229
- statements = build_truncate_statements(table_names)
230
- execute_batch(statements, "Truncate Tables")
231
- end
227
+ disable_referential_integrity do
228
+ statements = build_truncate_statements(table_names)
229
+ execute_batch(statements, "Truncate Tables")
232
230
  end
233
231
  end
234
232
 
@@ -358,7 +356,7 @@ module ActiveRecord
358
356
  end
359
357
  yield current_transaction.user_transaction
360
358
  else
361
- transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
359
+ within_new_transaction(isolation: isolation, joinable: joinable, &block)
362
360
  end
363
361
  rescue ActiveRecord::Rollback
364
362
  # rollbacks are silently swallowed
@@ -411,6 +409,14 @@ module ActiveRecord
411
409
  # Begins the transaction (and turns off auto-committing).
412
410
  def begin_db_transaction() end
413
411
 
412
+ def begin_deferred_transaction(isolation_level = nil) # :nodoc:
413
+ if isolation_level
414
+ begin_isolated_db_transaction(isolation_level)
415
+ else
416
+ begin_db_transaction
417
+ end
418
+ end
419
+
414
420
  def transaction_isolation_levels
415
421
  {
416
422
  read_uncommitted: "READ UNCOMMITTED",
@@ -427,6 +433,15 @@ module ActiveRecord
427
433
  raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
428
434
  end
429
435
 
436
+ # Hook point called after an isolated DB transaction is committed
437
+ # or rolled back.
438
+ # Most adapters don't need to implement anything because the isolation
439
+ # level is set on a per transaction basis.
440
+ # But some databases like SQLite set it on a per connection level
441
+ # and need to explicitly reset it after commit or rollback.
442
+ def reset_isolation_level
443
+ end
444
+
430
445
  # Commits the transaction (and turns on auto-committing).
431
446
  def commit_db_transaction() end
432
447
 
@@ -473,11 +488,9 @@ module ActiveRecord
473
488
  table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
474
489
  statements = table_deletes + fixture_inserts
475
490
 
476
- with_multi_statements do
477
- transaction(requires_new: true) do
478
- disable_referential_integrity do
479
- execute_batch(statements, "Fixtures Load")
480
- end
491
+ transaction(requires_new: true) do
492
+ disable_referential_integrity do
493
+ execute_batch(statements, "Fixtures Load")
481
494
  end
482
495
  end
483
496
  end
@@ -524,28 +537,64 @@ module ActiveRecord
524
537
  HIGH_PRECISION_CURRENT_TIMESTAMP
525
538
  end
526
539
 
527
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
528
- raise NotImplementedError
540
+ # Same as raw_execute but returns an ActiveRecord::Result object.
541
+ def raw_exec_query(...) # :nodoc:
542
+ cast_result(raw_execute(...))
543
+ end
544
+
545
+ # Execute a query and returns an ActiveRecord::Result
546
+ def internal_exec_query(...) # :nodoc:
547
+ cast_result(internal_execute(...))
529
548
  end
530
549
 
531
550
  private
532
- def internal_execute(sql, name = "SCHEMA", allow_retry: false, materialize_transactions: true)
533
- sql = transform_query(sql)
534
- check_if_write_query(sql)
551
+ # Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
552
+ def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
553
+ type_casted_binds = type_casted_binds(binds)
554
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
555
+ 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)
557
+ end
558
+ end
559
+ end
535
560
 
536
- mark_transaction_written_if_write(sql)
561
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
562
+ raise NotImplementedError
563
+ end
537
564
 
538
- raw_execute(sql, name, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
565
+ # Receive a native adapter result object and returns an ActiveRecord::Result object.
566
+ def cast_result(raw_result)
567
+ raise NotImplementedError
539
568
  end
540
569
 
541
- def execute_batch(statements, name = nil)
542
- statements.each do |statement|
543
- internal_execute(statement, name)
570
+ def affected_rows(raw_result)
571
+ raise NotImplementedError
572
+ end
573
+
574
+ def preprocess_query(sql)
575
+ check_if_write_query(sql)
576
+ mark_transaction_written_if_write(sql)
577
+
578
+ # We call tranformers after the write checks so we don't add extra parsing work.
579
+ # This means we assume no transformer whille change a read for a write
580
+ # but it would be insane to do such a thing.
581
+ ActiveRecord.query_transformers.each do |transformer|
582
+ sql = transformer.call(sql, self)
544
583
  end
584
+
585
+ sql
545
586
  end
546
587
 
547
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
548
- raise NotImplementedError
588
+ # Same as #internal_exec_query, but yields a native adapter result
589
+ def internal_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, &block)
590
+ sql = preprocess_query(sql)
591
+ raw_execute(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions, &block)
592
+ end
593
+
594
+ def execute_batch(statements, name = nil, **kwargs)
595
+ statements.each do |statement|
596
+ raw_execute(statement, name, **kwargs)
597
+ end
549
598
  end
550
599
 
551
600
  DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
@@ -614,10 +663,6 @@ module ActiveRecord
614
663
  end
615
664
  end
616
665
 
617
- def with_multi_statements
618
- yield
619
- end
620
-
621
666
  def combine_multi_statements(total_sql)
622
667
  total_sql.join(";\n")
623
668
  end
@@ -629,6 +674,8 @@ module ActiveRecord
629
674
  raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
630
675
  end
631
676
 
677
+ # We make sure to run query transformers on the orignal thread
678
+ sql = preprocess_query(sql)
632
679
  future_result = async.new(
633
680
  pool,
634
681
  sql,
@@ -636,19 +683,19 @@ module ActiveRecord
636
683
  binds,
637
684
  prepare: prepare,
638
685
  )
639
- if supports_concurrent_connections? && current_transaction.closed?
686
+ if supports_concurrent_connections? && !current_transaction.joinable?
640
687
  future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session)
641
688
  else
642
689
  future_result.execute!(self)
643
690
  end
644
- return future_result
645
- end
646
-
647
- result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
648
- if async
649
- FutureResult.wrap(result)
691
+ future_result
650
692
  else
651
- result
693
+ result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
694
+ if async
695
+ FutureResult.wrap(result)
696
+ else
697
+ result
698
+ end
652
699
  end
653
700
  end
654
701
 
@@ -191,26 +191,15 @@ module ActiveRecord
191
191
  end
192
192
  end
193
193
 
194
+ attr_accessor :query_cache
195
+
194
196
  def initialize(*)
195
197
  super
196
198
  @query_cache = nil
197
199
  end
198
200
 
199
- attr_writer :query_cache
200
-
201
- def query_cache
202
- if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
203
- # With transactional tests, if the connection is pinned, any thread
204
- # other than the one that pinned the connection need to go through the
205
- # query cache pool, so each thread get a different cache.
206
- pool.query_cache
207
- else
208
- @query_cache
209
- end
210
- end
211
-
212
201
  def query_cache_enabled
213
- query_cache&.enabled?
202
+ @query_cache&.enabled?
214
203
  end
215
204
 
216
205
  # Enable the query cache within the block.
@@ -249,7 +238,7 @@ module ActiveRecord
249
238
 
250
239
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
251
240
  # Such queries should not be cached.
252
- if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
241
+ if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
253
242
  sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
254
243
 
255
244
  if async
@@ -273,13 +262,13 @@ module ActiveRecord
273
262
 
274
263
  result = nil
275
264
  @lock.synchronize do
276
- result = query_cache[key]
265
+ result = @query_cache[key]
277
266
  end
278
267
 
279
268
  if result
280
269
  ActiveSupport::Notifications.instrument(
281
270
  "sql.active_record",
282
- cache_notification_info(sql, name, binds)
271
+ cache_notification_info_result(sql, name, binds, result)
283
272
  )
284
273
  end
285
274
 
@@ -292,7 +281,7 @@ module ActiveRecord
292
281
  hit = true
293
282
 
294
283
  @lock.synchronize do
295
- result = query_cache.compute_if_absent(key) do
284
+ result = @query_cache.compute_if_absent(key) do
296
285
  hit = false
297
286
  yield
298
287
  end
@@ -301,13 +290,19 @@ module ActiveRecord
301
290
  if hit
302
291
  ActiveSupport::Notifications.instrument(
303
292
  "sql.active_record",
304
- cache_notification_info(sql, name, binds)
293
+ cache_notification_info_result(sql, name, binds, result)
305
294
  )
306
295
  end
307
296
 
308
297
  result.dup
309
298
  end
310
299
 
300
+ def cache_notification_info_result(sql, name, binds, result)
301
+ payload = cache_notification_info(sql, name, binds)
302
+ payload[:row_count] = result.length
303
+ payload
304
+ end
305
+
311
306
  # Database adapters can override this method to
312
307
  # provide custom cache information.
313
308
  def cache_notification_info(sql, name, binds)
@@ -222,7 +222,7 @@ module ActiveRecord
222
222
 
223
223
  private
224
224
  def type_casted_binds(binds)
225
- binds.map do |value|
225
+ binds&.map do |value|
226
226
  if ActiveModel::Attribute === value
227
227
  type_cast(value.value_for_database)
228
228
  else
@@ -160,8 +160,6 @@ module ActiveRecord
160
160
  end
161
161
 
162
162
  def defined_for?(to_table: nil, validate: nil, **options)
163
- options = options.slice(*self.options.keys)
164
-
165
163
  (to_table.nil? || to_table.to_s == self.to_table) &&
166
164
  (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
167
165
  options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
@@ -188,8 +186,6 @@ module ActiveRecord
188
186
  end
189
187
 
190
188
  def defined_for?(name:, expression: nil, validate: nil, **options)
191
- options = options.slice(*self.options.keys)
192
-
193
189
  self.name == name.to_s &&
194
190
  (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
195
191
  options.all? { |k, v| self.options[k].to_s == v.to_s }
@@ -352,7 +348,7 @@ module ActiveRecord
352
348
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
353
349
  # is actually of this type:
354
350
  #
355
- # class SomeMigration < ActiveRecord::Migration[7.2]
351
+ # class SomeMigration < ActiveRecord::Migration[8.0]
356
352
  # def up
357
353
  # create_table :foo do |t|
358
354
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -435,7 +431,7 @@ module ActiveRecord
435
431
  #
436
432
  # == Examples
437
433
  #
438
- # # Assuming `td` is an instance of TableDefinition
434
+ # # Assuming +td+ is an instance of TableDefinition
439
435
  # td.column(:granted, :boolean, index: true)
440
436
  #
441
437
  # == Short-hand examples
@@ -186,9 +186,6 @@ module ActiveRecord
186
186
  # 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
187
  #
188
188
  # A Symbol can be used to specify the type of the generated primary key column.
189
- #
190
- # A Hash can be used to specify the generated primary key column creation options.
191
- # See {add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column] for available options.
192
189
  # [<tt>:primary_key</tt>]
193
190
  # The name of the primary key, if one is to be added automatically.
194
191
  # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
@@ -348,6 +345,15 @@ module ActiveRecord
348
345
  # # Creates a table called 'assemblies_parts' with no id.
349
346
  # create_join_table(:assemblies, :parts)
350
347
  #
348
+ # # Creates a table called 'paper_boxes_papers' with no id.
349
+ # create_join_table('papers', 'paper_boxes')
350
+ #
351
+ # A duplicate prefix is combined into a single prefix. This is useful for
352
+ # namespaced models like Music::Artist and Music::Record:
353
+ #
354
+ # # Creates a table called 'music_artists_records' with no id.
355
+ # create_join_table('music_artists', 'music_records')
356
+ #
351
357
  # You can pass an +options+ hash which can include the following keys:
352
358
  # [<tt>:table_name</tt>]
353
359
  # Sets the table name, overriding the default.
@@ -519,7 +525,7 @@ module ActiveRecord
519
525
  raise NotImplementedError, "rename_table is not implemented"
520
526
  end
521
527
 
522
- # Drops a table from the database.
528
+ # Drops a table or tables from the database.
523
529
  #
524
530
  # [<tt>:force</tt>]
525
531
  # Set to +:cascade+ to drop dependent objects as well.
@@ -530,10 +536,12 @@ module ActiveRecord
530
536
  #
531
537
  # Although this command ignores most +options+ and the block if one is given,
532
538
  # 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)}"
539
+ # 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.
540
+ def drop_table(*table_names, **options)
541
+ table_names.each do |table_name|
542
+ schema_cache.clear_data_source_cache!(table_name.to_s)
543
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
544
+ end
537
545
  end
538
546
 
539
547
  # Add a new +type+ column named +column_name+ to +table_name+.
@@ -685,7 +693,7 @@ module ActiveRecord
685
693
  #
686
694
  # If the options provided include an +if_exists+ key, it will be used to check if the
687
695
  # column does not exist. This will silently ignore the migration rather than raising
688
- # if the column was already removed.
696
+ # if the column was already used.
689
697
  #
690
698
  # remove_column(:suppliers, :qualification, if_exists: true)
691
699
  def remove_column(table_name, column_name, type = nil, **options)
@@ -847,6 +855,16 @@ module ActiveRecord
847
855
  #
848
856
  # Note: only supported by PostgreSQL.
849
857
  #
858
+ # ====== Creating an index where NULLs are treated equally
859
+ #
860
+ # add_index(:people, :last_name, nulls_not_distinct: true)
861
+ #
862
+ # generates:
863
+ #
864
+ # CREATE INDEX index_people_on_last_name ON people (last_name) NULLS NOT DISTINCT
865
+ #
866
+ # Note: only supported by PostgreSQL version 15.0.0 and greater.
867
+ #
850
868
  # ====== Creating an index with a specific method
851
869
  #
852
870
  # add_index(:developers, :name, using: 'btree')
@@ -448,10 +448,14 @@ module ActiveRecord
448
448
  # = Active Record Real \Transaction
449
449
  class RealTransaction < Transaction
450
450
  def materialize!
451
- if isolation_level
452
- connection.begin_isolated_db_transaction(isolation_level)
451
+ if joinable?
452
+ if isolation_level
453
+ connection.begin_isolated_db_transaction(isolation_level)
454
+ else
455
+ connection.begin_db_transaction
456
+ end
453
457
  else
454
- connection.begin_db_transaction
458
+ connection.begin_deferred_transaction(isolation_level)
455
459
  end
456
460
 
457
461
  super
@@ -472,13 +476,19 @@ module ActiveRecord
472
476
  end
473
477
 
474
478
  def rollback
475
- connection.rollback_db_transaction if materialized?
479
+ if materialized?
480
+ connection.rollback_db_transaction
481
+ connection.reset_isolation_level if isolation_level
482
+ end
476
483
  @state.full_rollback!
477
484
  @instrumenter.finish(:rollback) if materialized?
478
485
  end
479
486
 
480
487
  def commit
481
- connection.commit_db_transaction if materialized?
488
+ if materialized?
489
+ connection.commit_db_transaction
490
+ connection.reset_isolation_level if isolation_level
491
+ end
482
492
  @state.full_commit!
483
493
  @instrumenter.finish(:commit) if materialized?
484
494
  end