activerecord 7.2.3 → 8.0.4

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 (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +391 -958
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -0
  5. data/lib/active_record/associations/association.rb +34 -10
  6. data/lib/active_record/associations/builder/association.rb +7 -6
  7. data/lib/active_record/associations/collection_association.rb +1 -1
  8. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  9. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  10. data/lib/active_record/associations/preloader/association.rb +2 -2
  11. data/lib/active_record/associations/singular_association.rb +8 -3
  12. data/lib/active_record/associations.rb +34 -4
  13. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  14. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  15. data/lib/active_record/attribute_methods/query.rb +34 -0
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
  17. data/lib/active_record/autosave_association.rb +69 -27
  18. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
  19. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  20. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -15
  22. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  23. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  25. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  26. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
  27. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
  28. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  29. data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
  30. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
  31. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  32. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  33. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
  34. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  36. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  37. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
  38. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  39. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  40. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  41. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  42. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
  43. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +46 -96
  46. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  47. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
  48. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  49. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  50. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
  51. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
  52. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  53. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  54. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  55. data/lib/active_record/connection_adapters.rb +0 -56
  56. data/lib/active_record/connection_handling.rb +23 -1
  57. data/lib/active_record/core.rb +29 -14
  58. data/lib/active_record/database_configurations/database_config.rb +4 -0
  59. data/lib/active_record/database_configurations/hash_config.rb +16 -2
  60. data/lib/active_record/encryption/config.rb +3 -1
  61. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  62. data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
  63. data/lib/active_record/encryption/encryptor.rb +16 -8
  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 +9 -22
  67. data/lib/active_record/errors.rb +13 -5
  68. data/lib/active_record/fixtures.rb +0 -2
  69. data/lib/active_record/future_result.rb +13 -9
  70. data/lib/active_record/gem_version.rb +3 -3
  71. data/lib/active_record/insert_all.rb +1 -1
  72. data/lib/active_record/locking/optimistic.rb +1 -1
  73. data/lib/active_record/log_subscriber.rb +5 -11
  74. data/lib/active_record/migration/command_recorder.rb +31 -11
  75. data/lib/active_record/migration/compatibility.rb +5 -2
  76. data/lib/active_record/migration.rb +38 -42
  77. data/lib/active_record/model_schema.rb +3 -4
  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_logs.rb +102 -50
  81. data/lib/active_record/query_logs_formatter.rb +17 -28
  82. data/lib/active_record/querying.rb +8 -8
  83. data/lib/active_record/railtie.rb +2 -26
  84. data/lib/active_record/railties/databases.rake +11 -35
  85. data/lib/active_record/reflection.rb +18 -21
  86. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  87. data/lib/active_record/relation/batches.rb +132 -72
  88. data/lib/active_record/relation/calculations.rb +40 -39
  89. data/lib/active_record/relation/delegation.rb +25 -14
  90. data/lib/active_record/relation/finder_methods.rb +18 -18
  91. data/lib/active_record/relation/merger.rb +8 -8
  92. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  93. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  94. data/lib/active_record/relation/predicate_builder.rb +13 -0
  95. data/lib/active_record/relation/query_methods.rb +105 -61
  96. data/lib/active_record/relation/spawn_methods.rb +7 -7
  97. data/lib/active_record/relation.rb +79 -61
  98. data/lib/active_record/result.rb +66 -4
  99. data/lib/active_record/sanitization.rb +7 -6
  100. data/lib/active_record/schema_dumper.rb +5 -0
  101. data/lib/active_record/schema_migration.rb +2 -1
  102. data/lib/active_record/scoping/named.rb +5 -2
  103. data/lib/active_record/statement_cache.rb +14 -14
  104. data/lib/active_record/store.rb +7 -3
  105. data/lib/active_record/table_metadata.rb +1 -3
  106. data/lib/active_record/tasks/database_tasks.rb +69 -60
  107. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  108. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
  109. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  110. data/lib/active_record/test_databases.rb +1 -1
  111. data/lib/active_record/test_fixtures.rb +12 -0
  112. data/lib/active_record/token_for.rb +1 -1
  113. data/lib/active_record/transactions.rb +5 -6
  114. data/lib/active_record/validations/uniqueness.rb +8 -8
  115. data/lib/active_record.rb +21 -48
  116. data/lib/arel/collectors/bind.rb +2 -2
  117. data/lib/arel/collectors/sql_string.rb +1 -1
  118. data/lib/arel/collectors/substitute_binds.rb +2 -2
  119. data/lib/arel/nodes/binary.rb +1 -1
  120. data/lib/arel/nodes/node.rb +1 -1
  121. data/lib/arel/nodes/sql_literal.rb +1 -1
  122. data/lib/arel/table.rb +3 -7
  123. metadata +9 -10
  124. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "concurrent/map"
5
4
 
6
5
  module ActiveRecord
@@ -55,19 +54,22 @@ module ActiveRecord
55
54
  # about the model. The model needs to pass a connection specification name to the handler,
56
55
  # in order to look up the correct connection pool.
57
56
  class ConnectionHandler
58
- class StringConnectionName # :nodoc:
59
- attr_reader :name
60
-
61
- def initialize(name)
57
+ class ConnectionDescriptor # :nodoc:
58
+ def initialize(name, primary = false)
62
59
  @name = name
60
+ @primary = primary
61
+ end
62
+
63
+ def name
64
+ primary_class? ? "ActiveRecord::Base" : @name
63
65
  end
64
66
 
65
67
  def primary_class?
66
- false
68
+ @primary
67
69
  end
68
70
 
69
71
  def current_preventing_writes
70
- false
72
+ ActiveRecord::Base.preventing_writes?(@name)
71
73
  end
72
74
  end
73
75
 
@@ -116,7 +118,7 @@ module ActiveRecord
116
118
  pool_config = resolve_pool_config(config, owner_name, role, shard)
117
119
  db_config = pool_config.db_config
118
120
 
119
- pool_manager = set_pool_manager(pool_config.connection_name)
121
+ pool_manager = set_pool_manager(pool_config.connection_descriptor)
120
122
 
121
123
  # If there is an existing pool with the same values as the pool_config
122
124
  # don't remove the connection. Connections should only be removed if we are
@@ -128,8 +130,8 @@ module ActiveRecord
128
130
  # Update the pool_config's connection class if it differs. This is used
129
131
  # for ensuring that ActiveRecord::Base and the primary_abstract_class use
130
132
  # the same pool. Without this granular swapping will not work correctly.
131
- if owner_name.primary_class? && (existing_pool_config.connection_class != owner_name)
132
- existing_pool_config.connection_class = owner_name
133
+ if owner_name.primary_class? && (existing_pool_config.connection_descriptor != owner_name)
134
+ existing_pool_config.connection_descriptor = owner_name
133
135
  end
134
136
 
135
137
  existing_pool_config.pool
@@ -138,7 +140,7 @@ module ActiveRecord
138
140
  pool_manager.set_pool_config(role, shard, pool_config)
139
141
 
140
142
  payload = {
141
- connection_name: pool_config.connection_name,
143
+ connection_name: pool_config.connection_descriptor.name,
142
144
  role: role,
143
145
  shard: shard,
144
146
  config: db_config.configuration_hash
@@ -166,7 +168,7 @@ module ActiveRecord
166
168
  end
167
169
  end
168
170
 
169
- # Clears the cache which maps classes.
171
+ # Clears reloadable connection caches in all connection pools.
170
172
  #
171
173
  # See ConnectionPool#clear_reloadable_connections! for details.
172
174
  def clear_reloadable_connections!(role = nil)
@@ -210,18 +212,25 @@ module ActiveRecord
210
212
  # This makes retrieving the connection pool O(1) once the process is warm.
211
213
  # When a connection is established or removed, we invalidate the cache.
212
214
  def retrieve_connection_pool(connection_name, role: ActiveRecord::Base.current_role, shard: ActiveRecord::Base.current_shard, strict: false)
213
- pool = get_pool_manager(connection_name)&.get_pool_config(role, shard)&.pool
215
+ pool_manager = get_pool_manager(connection_name)
216
+ pool = pool_manager&.get_pool_config(role, shard)&.pool
214
217
 
215
218
  if strict && !pool
216
- if shard != ActiveRecord::Base.default_shard
217
- message = "No connection pool for '#{connection_name}' found for the '#{shard}' shard."
218
- elsif role != ActiveRecord::Base.default_role
219
- message = "No connection pool for '#{connection_name}' found for the '#{role}' role."
220
- else
221
- message = "No connection pool for '#{connection_name}' found."
222
- end
219
+ selector = [
220
+ ("'#{shard}' shard" unless shard == ActiveRecord::Base.default_shard),
221
+ ("'#{role}' role" unless role == ActiveRecord::Base.default_role),
222
+ ].compact.join(" and ")
223
+
224
+ selector = [
225
+ (connection_name unless connection_name == "ActiveRecord::Base"),
226
+ selector.presence,
227
+ ].compact.join(" with ")
228
+
229
+ selector = " for #{selector}" if selector.present?
230
+
231
+ message = "No database connection defined#{selector}."
223
232
 
224
- raise ConnectionNotEstablished, message
233
+ raise ConnectionNotDefined.new(message, connection_name: connection_name, shard: shard, role: role)
225
234
  end
226
235
 
227
236
  pool
@@ -236,8 +245,8 @@ module ActiveRecord
236
245
  end
237
246
 
238
247
  # Get the existing pool manager or initialize and assign a new one.
239
- def set_pool_manager(connection_name)
240
- connection_name_to_pool_manager[connection_name] ||= PoolManager.new
248
+ def set_pool_manager(connection_descriptor)
249
+ connection_name_to_pool_manager[connection_descriptor.name] ||= PoolManager.new
241
250
  end
242
251
 
243
252
  def pool_managers
@@ -272,9 +281,9 @@ module ActiveRecord
272
281
 
273
282
  def determine_owner_name(owner_name, config)
274
283
  if owner_name.is_a?(String) || owner_name.is_a?(Symbol)
275
- StringConnectionName.new(owner_name.to_s)
284
+ ConnectionDescriptor.new(owner_name.to_s)
276
285
  elsif config.is_a?(Symbol)
277
- StringConnectionName.new(config.to_s)
286
+ ConnectionDescriptor.new(config.to_s)
278
287
  else
279
288
  owner_name
280
289
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "monitor"
5
4
 
6
5
  module ActiveRecord
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "weakref"
5
4
 
6
5
  module ActiveRecord
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "concurrent/map"
5
4
  require "monitor"
6
5
 
@@ -37,8 +36,8 @@ module ActiveRecord
37
36
  end
38
37
 
39
38
  def schema_cache; end
40
- def connection_class; end
41
39
  def query_cache; end
40
+ def connection_descriptor; end
42
41
  def checkin(_); end
43
42
  def remove(_); end
44
43
  def async_executor; end
@@ -126,7 +125,7 @@ module ActiveRecord
126
125
  else
127
126
  class WeakThreadKeyMap # :nodoc:
128
127
  # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
129
- # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
128
+ # but it currently causes GC crashes: https://github.com/byroot/rails/pull/3
130
129
  def initialize
131
130
  @map = {}
132
131
  end
@@ -325,14 +324,6 @@ module ActiveRecord
325
324
  connection_lease.sticky.nil?
326
325
  end
327
326
 
328
- def connection
329
- ActiveRecord.deprecator.warn(<<~MSG)
330
- ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
331
- and will be removed in Rails 8.0. Use #lease_connection instead.
332
- MSG
333
- lease_connection
334
- end
335
-
336
327
  def pin_connection!(lock_thread) # :nodoc:
337
328
  @pinned_connection ||= (connection_lease&.connection || checkout)
338
329
  @pinned_connections_depth += 1
@@ -377,8 +368,8 @@ module ActiveRecord
377
368
  clean
378
369
  end
379
370
 
380
- def connection_class # :nodoc:
381
- pool_config.connection_class
371
+ def connection_descriptor # :nodoc:
372
+ pool_config.connection_descriptor
382
373
  end
383
374
 
384
375
  # Returns true if there is an open connection being used for the current thread.
@@ -508,7 +499,7 @@ module ActiveRecord
508
499
  @connections.nil?
509
500
  end
510
501
 
511
- # Clears the cache which maps classes and re-connects connections that
502
+ # Clears reloadable connections from the pool and re-connects connections that
512
503
  # require reloading.
513
504
  #
514
505
  # Raises:
@@ -531,7 +522,7 @@ module ActiveRecord
531
522
  end
532
523
  end
533
524
 
534
- # Clears the cache which maps classes and re-connects connections that
525
+ # Clears reloadable connections from the pool and re-connects connections that
535
526
  # require reloading.
536
527
  #
537
528
  # The pool first tries to gain ownership of all connections. If unable to
@@ -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 original 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
 
@@ -279,7 +279,7 @@ module ActiveRecord
279
279
  if result
280
280
  ActiveSupport::Notifications.instrument(
281
281
  "sql.active_record",
282
- cache_notification_info(sql, name, binds)
282
+ cache_notification_info_result(sql, name, binds, result)
283
283
  )
284
284
  end
285
285
 
@@ -301,13 +301,19 @@ module ActiveRecord
301
301
  if hit
302
302
  ActiveSupport::Notifications.instrument(
303
303
  "sql.active_record",
304
- cache_notification_info(sql, name, binds)
304
+ cache_notification_info_result(sql, name, binds, result)
305
305
  )
306
306
  end
307
307
 
308
308
  result.dup
309
309
  end
310
310
 
311
+ def cache_notification_info_result(sql, name, binds, result)
312
+ payload = cache_notification_info(sql, name, binds)
313
+ payload[:row_count] = result.length
314
+ payload
315
+ end
316
+
311
317
  # Database adapters can override this method to
312
318
  # provide custom cache information.
313
319
  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
@@ -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
@@ -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
@@ -352,7 +351,7 @@ module ActiveRecord
352
351
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
353
352
  # is actually of this type:
354
353
  #
355
- # class SomeMigration < ActiveRecord::Migration[7.2]
354
+ # class SomeMigration < ActiveRecord::Migration[8.0]
356
355
  # def up
357
356
  # create_table :foo do |t|
358
357
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -626,6 +625,7 @@ module ActiveRecord
626
625
  attr_reader :adds
627
626
  attr_reader :foreign_key_adds, :foreign_key_drops
628
627
  attr_reader :check_constraint_adds, :check_constraint_drops
628
+ attr_reader :constraint_drops
629
629
 
630
630
  def initialize(td)
631
631
  @td = td
@@ -634,6 +634,7 @@ module ActiveRecord
634
634
  @foreign_key_drops = []
635
635
  @check_constraint_adds = []
636
636
  @check_constraint_drops = []
637
+ @constraint_drops = []
637
638
  end
638
639
 
639
640
  def name; @td.name; end
@@ -654,6 +655,10 @@ module ActiveRecord
654
655
  @check_constraint_drops << constraint_name
655
656
  end
656
657
 
658
+ def drop_constraint(constraint_name)
659
+ @constraint_drops << constraint_name
660
+ end
661
+
657
662
  def add_column(name, type, **options)
658
663
  name = name.to_s
659
664
  type = type.to_sym
@@ -348,6 +348,15 @@ module ActiveRecord
348
348
  # # Creates a table called 'assemblies_parts' with no id.
349
349
  # create_join_table(:assemblies, :parts)
350
350
  #
351
+ # # Creates a table called 'paper_boxes_papers' with no id.
352
+ # create_join_table('papers', 'paper_boxes')
353
+ #
354
+ # A duplicate prefix is combined into a single prefix. This is useful for
355
+ # namespaced models like Music::Artist and Music::Record:
356
+ #
357
+ # # Creates a table called 'music_artists_records' with no id.
358
+ # create_join_table('music_artists', 'music_records')
359
+ #
351
360
  # You can pass an +options+ hash which can include the following keys:
352
361
  # [<tt>:table_name</tt>]
353
362
  # Sets the table name, overriding the default.
@@ -519,7 +528,7 @@ module ActiveRecord
519
528
  raise NotImplementedError, "rename_table is not implemented"
520
529
  end
521
530
 
522
- # Drops a table from the database.
531
+ # Drops a table or tables from the database.
523
532
  #
524
533
  # [<tt>:force</tt>]
525
534
  # Set to +:cascade+ to drop dependent objects as well.
@@ -530,17 +539,19 @@ module ActiveRecord
530
539
  #
531
540
  # Although this command ignores most +options+ and the block if one is given,
532
541
  # 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)}"
542
+ # 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.
543
+ def drop_table(*table_names, **options)
544
+ table_names.each do |table_name|
545
+ schema_cache.clear_data_source_cache!(table_name.to_s)
546
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
547
+ end
537
548
  end
538
549
 
539
550
  # Add a new +type+ column named +column_name+ to +table_name+.
540
551
  #
541
552
  # See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column].
542
553
  #
543
- # The +type+ parameter is normally one of the migrations native types,
554
+ # The +type+ parameter is normally one of the migration's native types,
544
555
  # which is one of the following:
545
556
  # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
546
557
  # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
@@ -847,6 +858,16 @@ module ActiveRecord
847
858
  #
848
859
  # Note: only supported by PostgreSQL.
849
860
  #
861
+ # ====== Creating an index where NULLs are treated equally
862
+ #
863
+ # add_index(:people, :last_name, nulls_not_distinct: true)
864
+ #
865
+ # generates:
866
+ #
867
+ # CREATE INDEX index_people_on_last_name ON people (last_name) NULLS NOT DISTINCT
868
+ #
869
+ # Note: only supported by PostgreSQL version 15.0.0 and greater.
870
+ #
850
871
  # ====== Creating an index with a specific method
851
872
  #
852
873
  # add_index(:developers, :name, using: 'btree')
@@ -1316,7 +1337,6 @@ module ActiveRecord
1316
1337
  execute schema_creation.accept(at)
1317
1338
  end
1318
1339
 
1319
-
1320
1340
  # Checks to see if a check constraint exists on a table for a given check constraint definition.
1321
1341
  #
1322
1342
  # check_constraint_exists?(:products, name: "price_check")
@@ -1328,6 +1348,13 @@ module ActiveRecord
1328
1348
  check_constraint_for(table_name, **options).present?
1329
1349
  end
1330
1350
 
1351
+ def remove_constraint(table_name, constraint_name) # :nodoc:
1352
+ at = create_alter_table(table_name)
1353
+ at.drop_constraint(constraint_name)
1354
+
1355
+ execute schema_creation.accept(at)
1356
+ end
1357
+
1331
1358
  def dump_schema_information # :nodoc:
1332
1359
  versions = pool.schema_migration.versions
1333
1360
  insert_versions_sql(versions) if versions.any?