activerecord 7.2.2.1 → 8.1.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -55,12 +55,15 @@ module ActiveRecord
55
55
  # can be used to query the database repeatedly.
56
56
  def cacheable_query(klass, arel) # :nodoc:
57
57
  if prepared_statements
58
+ collector = collector()
59
+ collector.retryable = true
58
60
  sql, binds = visitor.compile(arel.ast, collector)
59
- query = klass.query(sql)
61
+ query = klass.query(sql, retryable: collector.retryable)
60
62
  else
61
63
  collector = klass.partial_query_collector
64
+ collector.retryable = true
62
65
  parts, binds = visitor.compile(arel.ast, collector)
63
- query = klass.partial_query(parts)
66
+ query = klass.partial_query(parts, retryable: collector.retryable)
64
67
  end
65
68
  [query, binds]
66
69
  end
@@ -102,16 +105,16 @@ module ActiveRecord
102
105
  select_all(arel, name, binds, async: async).then(&:rows)
103
106
  end
104
107
 
105
- def query_value(sql, name = nil) # :nodoc:
106
- single_value_from_rows(query(sql, name))
108
+ def query_value(...) # :nodoc:
109
+ single_value_from_rows(query(...))
107
110
  end
108
111
 
109
- def query_values(sql, name = nil) # :nodoc:
110
- query(sql, name).map(&:first)
112
+ def query_values(...) # :nodoc:
113
+ query(...).map(&:first)
111
114
  end
112
115
 
113
- def query(sql, name = nil) # :nodoc:
114
- internal_exec_query(sql, name).rows
116
+ def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
117
+ internal_exec_query(sql, name, allow_retry:, materialize_transactions:).rows
115
118
  end
116
119
 
117
120
  # Determines whether the SQL statement is a write query.
@@ -163,14 +166,14 @@ module ActiveRecord
163
166
  # +binds+ as the bind substitutes. +name+ is logged along with
164
167
  # the executed +sql+ statement.
165
168
  def exec_delete(sql, name = nil, binds = [])
166
- internal_exec_query(sql, name, binds)
169
+ affected_rows(internal_execute(sql, name, binds))
167
170
  end
168
171
 
169
172
  # Executes update +sql+ statement in the context of this connection using
170
173
  # +binds+ as the bind substitutes. +name+ is logged along with
171
174
  # the executed +sql+ statement.
172
175
  def exec_update(sql, name = nil, binds = [])
173
- internal_exec_query(sql, name, binds)
176
+ affected_rows(internal_execute(sql, name, binds))
174
177
  end
175
178
 
176
179
  def exec_insert_all(sql, name) # :nodoc:
@@ -224,11 +227,9 @@ module ActiveRecord
224
227
 
225
228
  return if table_names.empty?
226
229
 
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
230
+ disable_referential_integrity do
231
+ statements = build_truncate_statements(table_names)
232
+ execute_batch(statements, "Truncate Tables")
232
233
  end
233
234
  end
234
235
 
@@ -352,13 +353,29 @@ module ActiveRecord
352
353
  # isolation level.
353
354
  # :args: (requires_new: nil, isolation: nil, &block)
354
355
  def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
356
+ # If we're running inside the single, non-joinable transaction that
357
+ # ActiveRecord::TestFixtures starts around each example (depth == 1),
358
+ # an `isolation:` hint must be validated then ignored so that the
359
+ # adapter isn't asked to change the isolation level mid-transaction.
360
+ if isolation && !requires_new && open_transactions == 1 && !current_transaction.joinable?
361
+ iso = isolation.to_sym
362
+
363
+ unless transaction_isolation_levels.include?(iso)
364
+ raise ActiveRecord::TransactionIsolationError,
365
+ "invalid transaction isolation level: #{iso.inspect}"
366
+ end
367
+
368
+ current_transaction.isolation = iso
369
+ isolation = nil
370
+ end
371
+
355
372
  if !requires_new && current_transaction.joinable?
356
- if isolation
373
+ if isolation && current_transaction.isolation != isolation
357
374
  raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
358
375
  end
359
376
  yield current_transaction.user_transaction
360
377
  else
361
- transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
378
+ within_new_transaction(isolation: isolation, joinable: joinable, &block)
362
379
  end
363
380
  rescue ActiveRecord::Rollback
364
381
  # rollbacks are silently swallowed
@@ -371,10 +388,10 @@ module ActiveRecord
371
388
  :disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction,
372
389
  to: :transaction_manager
373
390
 
374
- def mark_transaction_written_if_write(sql) # :nodoc:
391
+ def mark_transaction_written # :nodoc:
375
392
  transaction = current_transaction
376
393
  if transaction.open?
377
- transaction.written ||= write_query?(sql)
394
+ transaction.written ||= true
378
395
  end
379
396
  end
380
397
 
@@ -411,13 +428,24 @@ module ActiveRecord
411
428
  # Begins the transaction (and turns off auto-committing).
412
429
  def begin_db_transaction() end
413
430
 
431
+ def begin_deferred_transaction(isolation_level = nil) # :nodoc:
432
+ if isolation_level
433
+ begin_isolated_db_transaction(isolation_level)
434
+ else
435
+ begin_db_transaction
436
+ end
437
+ end
438
+
439
+ TRANSACTION_ISOLATION_LEVELS = {
440
+ read_uncommitted: "READ UNCOMMITTED",
441
+ read_committed: "READ COMMITTED",
442
+ repeatable_read: "REPEATABLE READ",
443
+ serializable: "SERIALIZABLE"
444
+ }.freeze
445
+ private_constant :TRANSACTION_ISOLATION_LEVELS
446
+
414
447
  def transaction_isolation_levels
415
- {
416
- read_uncommitted: "READ UNCOMMITTED",
417
- read_committed: "READ COMMITTED",
418
- repeatable_read: "REPEATABLE READ",
419
- serializable: "SERIALIZABLE"
420
- }
448
+ TRANSACTION_ISOLATION_LEVELS
421
449
  end
422
450
 
423
451
  # Begins the transaction with the isolation level set. Raises an error by
@@ -427,6 +455,15 @@ module ActiveRecord
427
455
  raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
428
456
  end
429
457
 
458
+ # Hook point called after an isolated DB transaction is committed
459
+ # or rolled back.
460
+ # Most adapters don't need to implement anything because the isolation
461
+ # level is set on a per transaction basis.
462
+ # But some databases like SQLite set it on a per connection level
463
+ # and need to explicitly reset it after commit or rollback.
464
+ def reset_isolation_level
465
+ end
466
+
430
467
  # Commits the transaction (and turns on auto-committing).
431
468
  def commit_db_transaction() end
432
469
 
@@ -473,11 +510,9 @@ module ActiveRecord
473
510
  table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
474
511
  statements = table_deletes + fixture_inserts
475
512
 
476
- with_multi_statements do
477
- transaction(requires_new: true) do
478
- disable_referential_integrity do
479
- execute_batch(statements, "Fixtures Load")
480
- end
513
+ transaction(requires_new: true) do
514
+ disable_referential_integrity do
515
+ execute_batch(statements, "Fixtures Load")
481
516
  end
482
517
  end
483
518
  end
@@ -486,20 +521,6 @@ module ActiveRecord
486
521
  "DEFAULT VALUES"
487
522
  end
488
523
 
489
- # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
490
- #
491
- # The +limit+ may be anything that can evaluate to a string via #to_s. It
492
- # should look like an integer, or an Arel SQL literal.
493
- #
494
- # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
495
- def sanitize_limit(limit)
496
- if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
497
- limit
498
- else
499
- Integer(limit)
500
- end
501
- end
502
-
503
524
  # Fixture value is quoted by Arel, however scalar values
504
525
  # are not quotable. In this case we want to convert
505
526
  # the column value to YAML.
@@ -524,35 +545,78 @@ module ActiveRecord
524
545
  HIGH_PRECISION_CURRENT_TIMESTAMP
525
546
  end
526
547
 
527
- def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
528
- raise NotImplementedError
548
+ # Same as raw_execute but returns an ActiveRecord::Result object.
549
+ def raw_exec_query(...) # :nodoc:
550
+ cast_result(raw_execute(...))
551
+ end
552
+
553
+ # Execute a query and returns an ActiveRecord::Result
554
+ def internal_exec_query(...) # :nodoc:
555
+ cast_result(internal_execute(...))
556
+ end
557
+
558
+ def default_insert_value(column) # :nodoc:
559
+ DEFAULT_INSERT_VALUE
529
560
  end
530
561
 
531
562
  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)
563
+ DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
564
+ private_constant :DEFAULT_INSERT_VALUE
535
565
 
536
- mark_transaction_written_if_write(sql)
566
+ # Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
567
+ def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
568
+ type_casted_binds = type_casted_binds(binds)
569
+ log(sql, name, binds, type_casted_binds, async: async, allow_retry: allow_retry) do |notification_payload|
570
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
571
+ result = perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
572
+ handle_warnings(result, sql)
573
+ result
574
+ end
575
+ end
576
+ end
537
577
 
538
- raw_execute(sql, name, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
578
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
579
+ raise NotImplementedError
539
580
  end
540
581
 
541
- def execute_batch(statements, name = nil)
542
- statements.each do |statement|
543
- internal_execute(statement, name)
544
- end
582
+ def handle_warnings(raw_result, sql)
545
583
  end
546
584
 
547
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
585
+ # Receive a native adapter result object and returns an ActiveRecord::Result object.
586
+ def cast_result(raw_result)
548
587
  raise NotImplementedError
549
588
  end
550
589
 
551
- DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
552
- private_constant :DEFAULT_INSERT_VALUE
590
+ def affected_rows(raw_result)
591
+ raise NotImplementedError
592
+ end
593
+
594
+ def preprocess_query(sql)
595
+ if write_query?(sql)
596
+ ensure_writes_are_allowed(sql)
597
+ mark_transaction_written
598
+ end
553
599
 
554
- def default_insert_value(column)
555
- DEFAULT_INSERT_VALUE
600
+ # We call tranformers after the write checks so we don't add extra parsing work.
601
+ # This means we assume no transformer whille change a read for a write
602
+ # but it would be insane to do such a thing.
603
+ ActiveRecord.query_transformers.each do |transformer|
604
+ sql = transformer.call(sql, self)
605
+ end
606
+
607
+ sql
608
+ end
609
+
610
+ # Same as #internal_exec_query, but yields a native adapter result
611
+ def internal_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, &block)
612
+ sql = preprocess_query(sql)
613
+ raw_execute(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions, &block)
614
+ end
615
+
616
+ def execute_batch(statements, name = nil, **kwargs)
617
+ statements.each do |statement|
618
+ raw_execute(statement, name, **kwargs)
619
+ end
556
620
  end
557
621
 
558
622
  def build_fixture_sql(fixtures, table_name)
@@ -568,8 +632,8 @@ module ActiveRecord
568
632
 
569
633
  columns.map do |name, column|
570
634
  if fixture.key?(name)
571
- type = lookup_cast_type_from_column(column)
572
- with_yaml_fallback(type.serialize(fixture[name]))
635
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
636
+ with_yaml_fallback(column.fetch_cast_type(self).serialize(fixture[name]))
573
637
  else
574
638
  default_insert_value(column)
575
639
  end
@@ -614,10 +678,6 @@ module ActiveRecord
614
678
  end
615
679
  end
616
680
 
617
- def with_multi_statements
618
- yield
619
- end
620
-
621
681
  def combine_multi_statements(total_sql)
622
682
  total_sql.join(";\n")
623
683
  end
@@ -629,6 +689,8 @@ module ActiveRecord
629
689
  raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
630
690
  end
631
691
 
692
+ # We make sure to run query transformers on the original thread
693
+ sql = preprocess_query(sql)
632
694
  future_result = async.new(
633
695
  pool,
634
696
  sql,
@@ -636,19 +698,19 @@ module ActiveRecord
636
698
  binds,
637
699
  prepare: prepare,
638
700
  )
639
- if supports_concurrent_connections? && current_transaction.closed?
701
+ if supports_concurrent_connections? && !current_transaction.joinable?
640
702
  future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session)
641
703
  else
642
704
  future_result.execute!(self)
643
705
  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)
706
+ future_result
650
707
  else
651
- result
708
+ result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
709
+ if async
710
+ FutureResult.wrap(result)
711
+ else
712
+ result
713
+ end
652
714
  end
653
715
  end
654
716
 
@@ -13,8 +13,6 @@ module ActiveRecord
13
13
  dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
14
14
  :truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
15
15
  :exec_insert_all
16
-
17
- base.set_callback :checkin, :after, :unset_query_cache!
18
16
  end
19
17
 
20
18
  def dirties_query_cache(base, *method_names)
@@ -31,6 +29,18 @@ module ActiveRecord
31
29
  end
32
30
  end
33
31
 
32
+ # This is the actual query cache store.
33
+ #
34
+ # It has an internal hash whose keys are either SQL strings, or arrays of
35
+ # two elements [SQL string, binds], if there are binds. The hash values
36
+ # are their corresponding ActiveRecord::Result objects.
37
+ #
38
+ # Keeping the hash size under max size is achieved with LRU eviction.
39
+ #
40
+ # The store gets passed a version object, which is shared among the query
41
+ # cache stores of a given connection pool (see ConnectionPoolConfiguration
42
+ # down below). The version value may be externally changed as a way to
43
+ # signal cache invalidation, that is why all methods have a guard for it.
34
44
  class Store # :nodoc:
35
45
  attr_accessor :enabled, :dirties
36
46
  alias_method :enabled?, :enabled
@@ -94,6 +104,12 @@ module ActiveRecord
94
104
  end
95
105
  end
96
106
 
107
+ # Each connection pool has one of these registries. They map execution
108
+ # contexts to query cache stores.
109
+ #
110
+ # The keys of the internal map are threads or fibers (whatever
111
+ # ActiveSupport::IsolatedExecutionState.context returns), and their
112
+ # associated values are their respective query cache stores.
97
113
  class QueryCacheRegistry # :nodoc:
98
114
  def initialize
99
115
  @mutex = Mutex.new
@@ -191,15 +207,26 @@ module ActiveRecord
191
207
  end
192
208
  end
193
209
 
194
- attr_accessor :query_cache
195
-
196
210
  def initialize(*)
197
211
  super
198
212
  @query_cache = nil
199
213
  end
200
214
 
215
+ attr_writer :query_cache
216
+
217
+ def query_cache
218
+ if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
219
+ # With transactional tests, if the connection is pinned, any thread
220
+ # other than the one that pinned the connection need to go through the
221
+ # query cache pool, so each thread get a different cache.
222
+ pool.query_cache
223
+ else
224
+ @query_cache
225
+ end
226
+ end
227
+
201
228
  def query_cache_enabled
202
- @query_cache&.enabled?
229
+ query_cache&.enabled?
203
230
  end
204
231
 
205
232
  # Enable the query cache within the block.
@@ -238,8 +265,8 @@ module ActiveRecord
238
265
 
239
266
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
240
267
  # Such queries should not be cached.
241
- if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
242
- sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
268
+ if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
269
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
243
270
 
244
271
  if async
245
272
  result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
@@ -262,13 +289,13 @@ module ActiveRecord
262
289
 
263
290
  result = nil
264
291
  @lock.synchronize do
265
- result = @query_cache[key]
292
+ result = query_cache[key]
266
293
  end
267
294
 
268
295
  if result
269
296
  ActiveSupport::Notifications.instrument(
270
297
  "sql.active_record",
271
- cache_notification_info(sql, name, binds)
298
+ cache_notification_info_result(sql, name, binds, result)
272
299
  )
273
300
  end
274
301
 
@@ -281,7 +308,7 @@ module ActiveRecord
281
308
  hit = true
282
309
 
283
310
  @lock.synchronize do
284
- result = @query_cache.compute_if_absent(key) do
311
+ result = query_cache.compute_if_absent(key) do
285
312
  hit = false
286
313
  yield
287
314
  end
@@ -290,13 +317,19 @@ module ActiveRecord
290
317
  if hit
291
318
  ActiveSupport::Notifications.instrument(
292
319
  "sql.active_record",
293
- cache_notification_info(sql, name, binds)
320
+ cache_notification_info_result(sql, name, binds, result)
294
321
  )
295
322
  end
296
323
 
297
324
  result.dup
298
325
  end
299
326
 
327
+ def cache_notification_info_result(sql, name, binds, result)
328
+ payload = cache_notification_info(sql, name, binds)
329
+ payload[:row_count] = result.length
330
+ payload
331
+ end
332
+
300
333
  # Database adapters can override this method to
301
334
  # provide custom cache information.
302
335
  def cache_notification_info(sql, name, binds)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/big_decimal/conversions"
4
- require "active_support/multibyte/chars"
5
4
 
6
5
  module ActiveRecord
7
6
  module ConnectionAdapters # :nodoc:
@@ -84,7 +83,8 @@ module ActiveRecord
84
83
  when Type::Time::Value then "'#{quoted_time(value)}'"
85
84
  when Date, Time then "'#{quoted_date(value)}'"
86
85
  when Class then "'#{value}'"
87
- else raise TypeError, "can't quote #{value.class.name}"
86
+ else
87
+ raise TypeError, "can't quote #{value.class.name}"
88
88
  end
89
89
  end
90
90
 
@@ -93,7 +93,7 @@ module ActiveRecord
93
93
  # to a String.
94
94
  def type_cast(value)
95
95
  case value
96
- when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
96
+ when Symbol, Type::Binary::Data, ActiveSupport::Multibyte::Chars
97
97
  value.to_s
98
98
  when true then unquoted_true
99
99
  when false then unquoted_false
@@ -102,7 +102,8 @@ module ActiveRecord
102
102
  when nil, Numeric, String then value
103
103
  when Type::Time::Value then quoted_time(value)
104
104
  when Date, Time then quoted_date(value)
105
- else raise TypeError, "can't cast #{value.class.name}"
105
+ else
106
+ raise TypeError, "can't cast #{value.class.name}"
106
107
  end
107
108
  end
108
109
 
@@ -113,19 +114,6 @@ module ActiveRecord
113
114
  value
114
115
  end
115
116
 
116
- # If you are having to call this function, you are likely doing something
117
- # wrong. The column does not have sufficient type information if the user
118
- # provided a custom type on the class level either explicitly (via
119
- # Attributes::ClassMethods#attribute) or implicitly (via
120
- # AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+).
121
- # In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to
122
- # represent the type doesn't sufficiently reflect the differences
123
- # (varchar vs binary) for example. The type used to get this primitive
124
- # should have been provided before reaching the connection adapter.
125
- def lookup_cast_type_from_column(column) # :nodoc:
126
- lookup_cast_type(column.sql_type)
127
- end
128
-
129
117
  # Quotes a string, escaping any ' (single quote) and \ (backslash)
130
118
  # characters.
131
119
  def quote_string(s)
@@ -158,7 +146,9 @@ module ActiveRecord
158
146
  if value.is_a?(Proc)
159
147
  value.call
160
148
  else
161
- value = lookup_cast_type(column.sql_type).serialize(value)
149
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
150
+ cast_type = column.fetch_cast_type(self)
151
+ value = cast_type.serialize(value)
162
152
  quote(value)
163
153
  end
164
154
  end
@@ -208,10 +198,10 @@ module ActiveRecord
208
198
  end
209
199
 
210
200
  def sanitize_as_sql_comment(value) # :nodoc:
211
- # Sanitize a string to appear within a SQL comment
201
+ # Sanitize a string to appear within an SQL comment
212
202
  # For compatibility, this also surrounding "/*+", "/*", and "*/"
213
203
  # charcacters, possibly with single surrounding space.
214
- # Then follows that by replacing any internal "*/" or "/ *" with
204
+ # Then follows that by replacing any internal "*/" or "/*" with
215
205
  # "* /" or "/ *"
216
206
  comment = value.to_s.dup
217
207
  comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
@@ -220,9 +210,14 @@ module ActiveRecord
220
210
  comment
221
211
  end
222
212
 
213
+ def lookup_cast_type(sql_type) # :nodoc:
214
+ # TODO: Make this method private after we release 8.1.
215
+ type_map.lookup(sql_type)
216
+ end
217
+
223
218
  private
224
219
  def type_casted_binds(binds)
225
- binds.map do |value|
220
+ binds&.map do |value|
226
221
  if ActiveModel::Attribute === value
227
222
  type_cast(value.value_for_database)
228
223
  else
@@ -230,10 +225,6 @@ module ActiveRecord
230
225
  end
231
226
  end
232
227
  end
233
-
234
- def lookup_cast_type(sql_type)
235
- type_map.lookup(sql_type)
236
- end
237
228
  end
238
229
  end
239
230
  end
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  :options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
18
18
  :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
19
19
  :supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?,
20
- :supports_nulls_not_distinct?,
20
+ :supports_nulls_not_distinct?, :lookup_cast_type,
21
21
  to: :@conn, private: true
22
22
 
23
23
  private
@@ -28,6 +28,7 @@ module ActiveRecord
28
28
  sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
29
29
  sql << o.check_constraint_adds.map { |con| visit_AddCheckConstraint con }.join(" ")
30
30
  sql << o.check_constraint_drops.map { |con| visit_DropCheckConstraint con }.join(" ")
31
+ sql << o.constraint_drops.map { |con| visit_DropConstraint con }.join(" ")
31
32
  end
32
33
 
33
34
  def visit_ColumnDefinition(o)
@@ -96,9 +97,11 @@ module ActiveRecord
96
97
  "ADD #{accept(o)}"
97
98
  end
98
99
 
99
- def visit_DropForeignKey(name)
100
+ def visit_DropConstraint(name)
100
101
  "DROP CONSTRAINT #{quote_column_name(name)}"
101
102
  end
103
+ alias :visit_DropForeignKey :visit_DropConstraint
104
+ alias :visit_DropCheckConstraint :visit_DropConstraint
102
105
 
103
106
  def visit_CreateIndexDefinition(o)
104
107
  index = o.index
@@ -127,10 +130,6 @@ module ActiveRecord
127
130
  "ADD #{accept(o)}"
128
131
  end
129
132
 
130
- def visit_DropCheckConstraint(name)
131
- "DROP CONSTRAINT #{quote_column_name(name)}"
132
- end
133
-
134
133
  def quoted_columns(o)
135
134
  String === o.columns ? o.columns : quoted_columns_for_index(o.columns, o.column_options)
136
135
  end
@@ -149,7 +148,7 @@ module ActiveRecord
149
148
  end
150
149
 
151
150
  def add_column_options!(sql, options)
152
- sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
151
+ sql << " DEFAULT #{quote_default_expression_for_column_definition(options[:default], options[:column])}" if options_include_default?(options)
153
152
  # must explicitly check for :null to allow change_column to work on migrations
154
153
  if options[:null] == false
155
154
  sql << " NOT NULL"
@@ -163,6 +162,11 @@ module ActiveRecord
163
162
  sql
164
163
  end
165
164
 
165
+ def quote_default_expression_for_column_definition(default, column_definition)
166
+ column_definition.cast_type = lookup_cast_type(column_definition.sql_type)
167
+ quote_default_expression(default, column_definition)
168
+ end
169
+
166
170
  def to_sql(sql)
167
171
  sql = sql.to_sql if sql.respond_to?(:to_sql)
168
172
  sql