activerecord 8.0.3 → 8.1.0

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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +538 -512
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +2 -0
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/errors.rb +3 -0
  15. data/lib/active_record/associations/join_dependency.rb +2 -0
  16. data/lib/active_record/associations/preloader/branch.rb +1 -0
  17. data/lib/active_record/associations.rb +159 -21
  18. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  19. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  20. data/lib/active_record/attributes.rb +3 -0
  21. data/lib/active_record/autosave_association.rb +1 -1
  22. data/lib/active_record/base.rb +0 -2
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
  45. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  46. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
  47. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  48. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  50. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  51. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  52. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
  53. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
  54. data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
  55. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  56. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
  57. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  58. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  59. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  60. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  61. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  62. data/lib/active_record/connection_adapters.rb +1 -0
  63. data/lib/active_record/connection_handling.rb +14 -9
  64. data/lib/active_record/core.rb +5 -4
  65. data/lib/active_record/counter_cache.rb +33 -8
  66. data/lib/active_record/database_configurations/database_config.rb +5 -1
  67. data/lib/active_record/database_configurations/hash_config.rb +53 -9
  68. data/lib/active_record/database_configurations/url_config.rb +13 -3
  69. data/lib/active_record/database_configurations.rb +7 -3
  70. data/lib/active_record/delegated_type.rb +1 -1
  71. data/lib/active_record/dynamic_matchers.rb +54 -69
  72. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  73. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  74. data/lib/active_record/encryption/encryptor.rb +12 -0
  75. data/lib/active_record/encryption/scheme.rb +1 -1
  76. data/lib/active_record/enum.rb +24 -8
  77. data/lib/active_record/errors.rb +20 -4
  78. data/lib/active_record/explain.rb +1 -1
  79. data/lib/active_record/explain_registry.rb +51 -2
  80. data/lib/active_record/filter_attribute_handler.rb +73 -0
  81. data/lib/active_record/fixtures.rb +2 -2
  82. data/lib/active_record/gem_version.rb +2 -2
  83. data/lib/active_record/inheritance.rb +1 -1
  84. data/lib/active_record/insert_all.rb +12 -7
  85. data/lib/active_record/locking/optimistic.rb +7 -0
  86. data/lib/active_record/locking/pessimistic.rb +5 -0
  87. data/lib/active_record/log_subscriber.rb +2 -6
  88. data/lib/active_record/middleware/shard_selector.rb +34 -17
  89. data/lib/active_record/migration/command_recorder.rb +14 -1
  90. data/lib/active_record/migration/compatibility.rb +34 -24
  91. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  92. data/lib/active_record/migration.rb +26 -16
  93. data/lib/active_record/model_schema.rb +36 -10
  94. data/lib/active_record/nested_attributes.rb +2 -0
  95. data/lib/active_record/persistence.rb +34 -3
  96. data/lib/active_record/query_cache.rb +22 -15
  97. data/lib/active_record/query_logs.rb +3 -7
  98. data/lib/active_record/railtie.rb +32 -3
  99. data/lib/active_record/railties/controller_runtime.rb +11 -6
  100. data/lib/active_record/railties/databases.rake +15 -3
  101. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  102. data/lib/active_record/railties/job_runtime.rb +10 -11
  103. data/lib/active_record/reflection.rb +35 -0
  104. data/lib/active_record/relation/batches.rb +25 -11
  105. data/lib/active_record/relation/calculations.rb +20 -9
  106. data/lib/active_record/relation/delegation.rb +0 -1
  107. data/lib/active_record/relation/finder_methods.rb +27 -11
  108. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  109. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  110. data/lib/active_record/relation/predicate_builder.rb +9 -7
  111. data/lib/active_record/relation/query_attribute.rb +3 -1
  112. data/lib/active_record/relation/query_methods.rb +40 -29
  113. data/lib/active_record/relation/where_clause.rb +1 -8
  114. data/lib/active_record/relation.rb +24 -12
  115. data/lib/active_record/result.rb +44 -21
  116. data/lib/active_record/runtime_registry.rb +41 -58
  117. data/lib/active_record/sanitization.rb +2 -0
  118. data/lib/active_record/schema_dumper.rb +12 -10
  119. data/lib/active_record/scoping.rb +0 -1
  120. data/lib/active_record/signed_id.rb +43 -15
  121. data/lib/active_record/statement_cache.rb +13 -9
  122. data/lib/active_record/store.rb +44 -19
  123. data/lib/active_record/structured_event_subscriber.rb +85 -0
  124. data/lib/active_record/table_metadata.rb +5 -20
  125. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  126. data/lib/active_record/tasks/database_tasks.rb +25 -34
  127. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  128. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
  129. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  130. data/lib/active_record/test_databases.rb +14 -4
  131. data/lib/active_record/test_fixtures.rb +27 -2
  132. data/lib/active_record/testing/query_assertions.rb +8 -2
  133. data/lib/active_record/timestamp.rb +4 -2
  134. data/lib/active_record/transaction.rb +2 -5
  135. data/lib/active_record/transactions.rb +32 -10
  136. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  137. data/lib/active_record/type/internal/timezone.rb +7 -0
  138. data/lib/active_record/type/json.rb +15 -2
  139. data/lib/active_record/type/serialized.rb +11 -4
  140. data/lib/active_record/type/type_map.rb +1 -1
  141. data/lib/active_record/type_caster/connection.rb +2 -1
  142. data/lib/active_record/validations/associated.rb +1 -1
  143. data/lib/active_record.rb +65 -3
  144. data/lib/arel/alias_predication.rb +2 -0
  145. data/lib/arel/crud.rb +6 -11
  146. data/lib/arel/nodes/count.rb +2 -2
  147. data/lib/arel/nodes/function.rb +4 -10
  148. data/lib/arel/nodes/named_function.rb +2 -2
  149. data/lib/arel/nodes/node.rb +1 -1
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +7 -2
  152. data/lib/arel/visitors/dot.rb +0 -3
  153. data/lib/arel/visitors/postgresql.rb +55 -0
  154. data/lib/arel/visitors/sqlite.rb +55 -8
  155. data/lib/arel/visitors/to_sql.rb +3 -21
  156. data/lib/arel.rb +3 -1
  157. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  158. metadata +14 -10
  159. data/lib/active_record/explain_subscriber.rb +0 -34
  160. data/lib/active_record/normalization.rb +0 -163
@@ -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
@@ -110,8 +113,8 @@ module ActiveRecord
110
113
  query(...).map(&:first)
111
114
  end
112
115
 
113
- def query(...) # :nodoc:
114
- internal_exec_query(...).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.
@@ -350,8 +353,24 @@ module ActiveRecord
350
353
  # isolation level.
351
354
  # :args: (requires_new: nil, isolation: nil, &block)
352
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
+
353
372
  if !requires_new && current_transaction.joinable?
354
- if isolation
373
+ if isolation && current_transaction.isolation != isolation
355
374
  raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
356
375
  end
357
376
  yield current_transaction.user_transaction
@@ -369,10 +388,10 @@ module ActiveRecord
369
388
  :disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction,
370
389
  to: :transaction_manager
371
390
 
372
- def mark_transaction_written_if_write(sql) # :nodoc:
391
+ def mark_transaction_written # :nodoc:
373
392
  transaction = current_transaction
374
393
  if transaction.open?
375
- transaction.written ||= write_query?(sql)
394
+ transaction.written ||= true
376
395
  end
377
396
  end
378
397
 
@@ -417,13 +436,16 @@ module ActiveRecord
417
436
  end
418
437
  end
419
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
+
420
447
  def transaction_isolation_levels
421
- {
422
- read_uncommitted: "READ UNCOMMITTED",
423
- read_committed: "READ COMMITTED",
424
- repeatable_read: "REPEATABLE READ",
425
- serializable: "SERIALIZABLE"
426
- }
448
+ TRANSACTION_ISOLATION_LEVELS
427
449
  end
428
450
 
429
451
  # Begins the transaction with the isolation level set. Raises an error by
@@ -499,20 +521,6 @@ module ActiveRecord
499
521
  "DEFAULT VALUES"
500
522
  end
501
523
 
502
- # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
503
- #
504
- # The +limit+ may be anything that can evaluate to a string via #to_s. It
505
- # should look like an integer, or an Arel SQL literal.
506
- #
507
- # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
508
- def sanitize_limit(limit)
509
- if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
510
- limit
511
- else
512
- Integer(limit)
513
- end
514
- end
515
-
516
524
  # Fixture value is quoted by Arel, however scalar values
517
525
  # are not quotable. In this case we want to convert
518
526
  # the column value to YAML.
@@ -547,13 +555,22 @@ module ActiveRecord
547
555
  cast_result(internal_execute(...))
548
556
  end
549
557
 
558
+ def default_insert_value(column) # :nodoc:
559
+ DEFAULT_INSERT_VALUE
560
+ end
561
+
550
562
  private
563
+ DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
564
+ private_constant :DEFAULT_INSERT_VALUE
565
+
551
566
  # Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
552
567
  def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
553
568
  type_casted_binds = type_casted_binds(binds)
554
- log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
569
+ log(sql, name, binds, type_casted_binds, async: async, allow_retry: allow_retry) do |notification_payload|
555
570
  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)
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
557
574
  end
558
575
  end
559
576
  end
@@ -562,6 +579,9 @@ module ActiveRecord
562
579
  raise NotImplementedError
563
580
  end
564
581
 
582
+ def handle_warnings(raw_result, sql)
583
+ end
584
+
565
585
  # Receive a native adapter result object and returns an ActiveRecord::Result object.
566
586
  def cast_result(raw_result)
567
587
  raise NotImplementedError
@@ -572,8 +592,10 @@ module ActiveRecord
572
592
  end
573
593
 
574
594
  def preprocess_query(sql)
575
- check_if_write_query(sql)
576
- mark_transaction_written_if_write(sql)
595
+ if write_query?(sql)
596
+ ensure_writes_are_allowed(sql)
597
+ mark_transaction_written
598
+ end
577
599
 
578
600
  # We call tranformers after the write checks so we don't add extra parsing work.
579
601
  # This means we assume no transformer whille change a read for a write
@@ -597,13 +619,6 @@ module ActiveRecord
597
619
  end
598
620
  end
599
621
 
600
- DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
601
- private_constant :DEFAULT_INSERT_VALUE
602
-
603
- def default_insert_value(column)
604
- DEFAULT_INSERT_VALUE
605
- end
606
-
607
622
  def build_fixture_sql(fixtures, table_name)
608
623
  columns = schema_cache.columns_hash(table_name).reject { |_, column| supports_virtual_columns? && column.virtual? }
609
624
 
@@ -617,8 +632,8 @@ module ActiveRecord
617
632
 
618
633
  columns.map do |name, column|
619
634
  if fixture.key?(name)
620
- type = lookup_cast_type_from_column(column)
621
- 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]))
622
637
  else
623
638
  default_insert_value(column)
624
639
  end
@@ -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
@@ -250,7 +266,7 @@ module ActiveRecord
250
266
  # If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
251
267
  # Such queries should not be cached.
252
268
  if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
253
- sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
269
+ sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
254
270
 
255
271
  if async
256
272
  result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
@@ -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,6 +210,11 @@ 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
220
  binds&.map do |value|
@@ -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
@@ -148,7 +148,7 @@ module ActiveRecord
148
148
  end
149
149
 
150
150
  def add_column_options!(sql, options)
151
- 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)
152
152
  # must explicitly check for :null to allow change_column to work on migrations
153
153
  if options[:null] == false
154
154
  sql << " NOT NULL"
@@ -162,6 +162,11 @@ module ActiveRecord
162
162
  sql
163
163
  end
164
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
+
165
170
  def to_sql(sql)
166
171
  sql = sql.to_sql if sql.respond_to?(:to_sql)
167
172
  sql
@@ -75,7 +75,7 @@ module ActiveRecord
75
75
  # are typically created by methods in TableDefinition, and added to the
76
76
  # +columns+ attribute of said TableDefinition object, in order to be used
77
77
  # for generating a number of table creation or table changing SQL statements.
78
- ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
78
+ ColumnDefinition = Struct.new(:name, :type, :options, :sql_type, :cast_type) do # :nodoc:
79
79
  self::OPTION_NAMES = [
80
80
  :limit,
81
81
  :precision,
@@ -108,6 +108,10 @@ module ActiveRecord
108
108
  def aliased_types(name, fallback)
109
109
  "timestamp" == name ? :datetime : fallback
110
110
  end
111
+
112
+ def fetch_cast_type(connection)
113
+ cast_type
114
+ end
111
115
  end
112
116
 
113
117
  AddColumnDefinition = Struct.new(:column) # :nodoc:
@@ -303,44 +307,32 @@ module ActiveRecord
303
307
  module ColumnMethods
304
308
  extend ActiveSupport::Concern
305
309
 
306
- # Appends a primary key definition to the table definition.
307
- # Can be called multiple times, but this is probably not a good idea.
308
- def primary_key(name, type = :primary_key, **options)
309
- column(name, type, **options.merge(primary_key: true))
310
- end
311
-
312
- ##
313
- # :method: column
314
- # :call-seq: column(name, type, **options)
315
- #
316
- # Appends a column or columns of a specified type.
317
- #
318
- # t.string(:goat)
319
- # t.string(:goat, :sheep)
320
- #
321
- # See TableDefinition#column
322
-
323
- included do
324
- define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
325
- :float, :integer, :json, :string, :text, :time, :timestamp, :virtual
326
-
327
- alias :blob :binary
328
- alias :numeric :decimal
329
- end
330
-
331
310
  class_methods do
332
- def define_column_methods(*column_types) # :nodoc:
333
- column_types.each do |column_type|
334
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
311
+ private
312
+ def define_column_methods(*column_types) # :nodoc:
313
+ column_types.each do |column_type|
314
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
335
315
  def #{column_type}(*names, **options)
336
316
  raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
337
317
  names.each { |name| column(name, :#{column_type}, **options) }
338
318
  end
339
- RUBY
319
+ RUBY
320
+ end
340
321
  end
341
- end
342
- private :define_column_methods
343
322
  end
323
+ extend ClassMethods
324
+
325
+ # Appends a primary key definition to the table definition.
326
+ # Can be called multiple times, but this is probably not a good idea.
327
+ def primary_key(name, type = :primary_key, **options)
328
+ column(name, type, **options, primary_key: true)
329
+ end
330
+
331
+ define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
332
+ :float, :integer, :json, :string, :text, :time, :timestamp, :virtual
333
+
334
+ alias :blob :binary
335
+ alias :numeric :decimal
344
336
  end
345
337
 
346
338
  # = Active Record Connection Adapters \Table \Definition
@@ -351,7 +343,7 @@ module ActiveRecord
351
343
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
352
344
  # is actually of this type:
353
345
  #
354
- # class SomeMigration < ActiveRecord::Migration[8.0]
346
+ # class SomeMigration < ActiveRecord::Migration[8.1]
355
347
  # def up
356
348
  # create_table :foo do |t|
357
349
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -765,7 +757,7 @@ module ActiveRecord
765
757
  # end
766
758
  #
767
759
  # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
768
- def index_exists?(column_name, **options)
760
+ def index_exists?(column_name = nil, **options)
769
761
  @base.index_exists?(name, column_name, **options)
770
762
  end
771
763
 
@@ -85,7 +85,8 @@ module ActiveRecord
85
85
 
86
86
  def schema_default(column)
87
87
  return unless column.has_default?
88
- type = @connection.lookup_cast_type_from_column(column)
88
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
89
+ type = column.fetch_cast_type(@connection)
89
90
  default = type.deserialize(column.default)
90
91
  if default.nil?
91
92
  schema_expression(column)