activerecord 8.0.3 → 8.1.0.rc1

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 +520 -514
  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 +66 -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 +54 -30
  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 +2 -1
  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 +3 -3
  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 +42 -3
  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 +38 -28
  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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/string/access"
4
+ require "active_support/core_ext/string/filters"
4
5
  require "openssl"
5
6
 
6
7
  module ActiveRecord
@@ -99,7 +100,7 @@ module ActiveRecord
99
100
  # # Check a valid index exists (PostgreSQL only)
100
101
  # index_exists?(:suppliers, :company_id, valid: true)
101
102
  #
102
- def index_exists?(table_name, column_name, **options)
103
+ def index_exists?(table_name, column_name = nil, **options)
103
104
  indexes(table_name).any? { |i| i.defined_for?(column_name, **options) }
104
105
  end
105
106
 
@@ -357,11 +358,13 @@ module ActiveRecord
357
358
  # # Creates a table called 'music_artists_records' with no id.
358
359
  # create_join_table('music_artists', 'music_records')
359
360
  #
361
+ # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference]
362
+ # for details of the options you can use in +column_options+. +column_options+
363
+ # will be applied to both columns.
364
+ #
360
365
  # You can pass an +options+ hash which can include the following keys:
361
366
  # [<tt>:table_name</tt>]
362
367
  # Sets the table name, overriding the default.
363
- # [<tt>:column_options</tt>]
364
- # Any extra options you want appended to the columns definition.
365
368
  # [<tt>:options</tt>]
366
369
  # Any extra options you want appended to the table definition.
367
370
  # [<tt>:temporary</tt>]
@@ -378,6 +381,19 @@ module ActiveRecord
378
381
  # t.index :category_id
379
382
  # end
380
383
  #
384
+ # ====== Add foreign keys with delete cascade
385
+ #
386
+ # create_join_table(:assemblies, :parts, column_options: { foreign_key: { on_delete: :cascade } })
387
+ #
388
+ # generates:
389
+ #
390
+ # CREATE TABLE assemblies_parts (
391
+ # assembly_id bigint NOT NULL,
392
+ # part_id bigint NOT NULL,
393
+ # CONSTRAINT fk_rails_0d8a572d89 FOREIGN KEY ("assembly_id") REFERENCES "assemblies" ("id") ON DELETE CASCADE,
394
+ # CONSTRAINT fk_rails_ec7b48402b FOREIGN KEY ("part_id") REFERENCES "parts" ("id") ON DELETE CASCADE
395
+ # )
396
+ #
381
397
  # ====== Add a backend specific option to the generated SQL (MySQL)
382
398
  #
383
399
  # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
@@ -915,6 +931,19 @@ module ActiveRecord
915
931
  # Concurrently adding an index is not supported in a transaction.
916
932
  #
917
933
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
934
+ #
935
+ # ====== Creating an index that is not used by queries
936
+ #
937
+ # add_index(:developers, :name, enabled: false)
938
+ #
939
+ # generates:
940
+ #
941
+ # CREATE INDEX index_developers_on_name ON developers (name) INVISIBLE -- MySQL
942
+ #
943
+ # CREATE INDEX index_developers_on_name ON developers (name) IGNORED -- MariaDB
944
+ #
945
+ # Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
946
+ #
918
947
  def add_index(table_name, column_name, **options)
919
948
  create_index = build_create_index_definition(table_name, column_name, **options)
920
949
  execute schema_creation.accept(create_index)
@@ -1175,9 +1204,10 @@ module ActiveRecord
1175
1204
  # +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
1176
1205
  def add_foreign_key(from_table, to_table, **options)
1177
1206
  return unless use_foreign_keys?
1178
- return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
1179
1207
 
1180
1208
  options = foreign_key_options(from_table, to_table, options)
1209
+ return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column, :primary_key))
1210
+
1181
1211
  at = create_alter_table from_table
1182
1212
  at.add_foreign_key to_table, options
1183
1213
 
@@ -1216,7 +1246,7 @@ module ActiveRecord
1216
1246
  # The name of the table that contains the referenced primary key.
1217
1247
  def remove_foreign_key(from_table, to_table = nil, **options)
1218
1248
  return unless use_foreign_keys?
1219
- return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
1249
+ return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table, **options.slice(:column))
1220
1250
 
1221
1251
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1222
1252
 
@@ -1355,7 +1385,7 @@ module ActiveRecord
1355
1385
  execute schema_creation.accept(at)
1356
1386
  end
1357
1387
 
1358
- def dump_schema_information # :nodoc:
1388
+ def dump_schema_versions # :nodoc:
1359
1389
  versions = pool.schema_migration.versions
1360
1390
  insert_versions_sql(versions) if versions.any?
1361
1391
  end
@@ -1477,7 +1507,7 @@ module ActiveRecord
1477
1507
  end
1478
1508
 
1479
1509
  def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1480
- options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct)
1510
+ options.assert_valid_keys(valid_index_options)
1481
1511
 
1482
1512
  column_names = index_column_names(column_name)
1483
1513
 
@@ -1486,7 +1516,7 @@ module ActiveRecord
1486
1516
 
1487
1517
  validate_index_length!(table_name, index_name, internal)
1488
1518
 
1489
- index = IndexDefinition.new(
1519
+ index = create_index_definition(
1490
1520
  table_name, index_name,
1491
1521
  options[:unique],
1492
1522
  column_names,
@@ -1541,6 +1571,20 @@ module ActiveRecord
1541
1571
  raise NotImplementedError, "#{self.class} does not support changing column comments"
1542
1572
  end
1543
1573
 
1574
+ # Enables an index to be used by queries.
1575
+ #
1576
+ # enable_index(:users, :email)
1577
+ def enable_index(table_name, index_name)
1578
+ raise NotImplementedError, "#{self.class} does not support enabling indexes"
1579
+ end
1580
+
1581
+ # Prevents an index from being used by queries.
1582
+ #
1583
+ # disable_index(:users, :email)
1584
+ def disable_index(table_name, index_name)
1585
+ raise NotImplementedError, "#{self.class} does not support disabling indexes"
1586
+ end
1587
+
1544
1588
  def create_schema_dumper(options) # :nodoc:
1545
1589
  SchemaDumper.create(self, options)
1546
1590
  end
@@ -1607,7 +1651,7 @@ module ActiveRecord
1607
1651
  name = "idx_on_#{Array(column) * '_'}"
1608
1652
 
1609
1653
  short_limit = max_index_name_size - hashed_identifier.bytesize
1610
- short_name = name.mb_chars.limit(short_limit).to_s
1654
+ short_name = name.truncate_bytes(short_limit, omission: nil)
1611
1655
 
1612
1656
  "#{short_name}#{hashed_identifier}"
1613
1657
  end
@@ -1629,6 +1673,10 @@ module ActiveRecord
1629
1673
  end
1630
1674
  end
1631
1675
 
1676
+ def valid_index_options
1677
+ [:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct]
1678
+ end
1679
+
1632
1680
  def options_for_index_columns(options)
1633
1681
  if options.is_a?(Hash)
1634
1682
  options.symbolize_keys
@@ -1705,6 +1753,10 @@ module ActiveRecord
1705
1753
  TableDefinition.new(self, name, **options)
1706
1754
  end
1707
1755
 
1756
+ def create_index_definition(table_name, name, unique, columns, **options)
1757
+ IndexDefinition.new(table_name, name, unique, columns, **options)
1758
+ end
1759
+
1708
1760
  def create_alter_table(name)
1709
1761
  AlterTable.new create_table_definition(name)
1710
1762
  end
@@ -1767,7 +1819,20 @@ module ActiveRecord
1767
1819
 
1768
1820
  def foreign_key_for(from_table, **options)
1769
1821
  return unless use_foreign_keys?
1770
- foreign_keys(from_table).detect { |fk| fk.defined_for?(**options) }
1822
+
1823
+ keys = foreign_keys(from_table)
1824
+
1825
+ if options[:_skip_column_match]
1826
+ return keys.find { |fk| fk.defined_for?(**options) }
1827
+ end
1828
+
1829
+ if options[:column].nil?
1830
+ default_column = foreign_key_column_for(options[:to_table], "id")
1831
+ matches = keys.select { |fk| fk.column == default_column }
1832
+ keys = matches if matches.any?
1833
+ end
1834
+
1835
+ keys.find { |fk| fk.defined_for?(**options) }
1771
1836
  end
1772
1837
 
1773
1838
  def foreign_key_for!(from_table, to_table: nil, **options)
@@ -1810,13 +1875,19 @@ module ActiveRecord
1810
1875
 
1811
1876
  def validate_index_length!(table_name, new_name, internal = false)
1812
1877
  if new_name.length > index_name_length
1813
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
1878
+ raise ArgumentError, <<~MSG.squish
1879
+ Index name '#{new_name}' on table '#{table_name}' is too long (#{new_name.length} characters); the limit
1880
+ is #{index_name_length} characters
1881
+ MSG
1814
1882
  end
1815
1883
  end
1816
1884
 
1817
1885
  def validate_table_length!(table_name)
1818
1886
  if table_name.length > table_name_length
1819
- raise ArgumentError, "Table name '#{table_name}' is too long; the limit is #{table_name_length} characters"
1887
+ raise ArgumentError, <<~MSG.squish
1888
+ Table name '#{table_name}' is too long (#{table_name.length} characters); the limit is
1889
+ #{table_name_length} characters
1890
+ MSG
1820
1891
  end
1821
1892
  end
1822
1893
 
@@ -1878,16 +1949,8 @@ module ActiveRecord
1878
1949
  end
1879
1950
 
1880
1951
  def insert_versions_sql(versions)
1881
- sm_table = quote_table_name(pool.schema_migration.table_name)
1882
-
1883
- if versions.is_a?(Array)
1884
- sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
1885
- sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
1886
- sql << ";"
1887
- sql
1888
- else
1889
- "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
1890
- end
1952
+ versions_formatter = ActiveRecord.schema_versions_formatter.new(self)
1953
+ versions_formatter.format(versions)
1891
1954
  end
1892
1955
 
1893
1956
  def data_source_sql(name = nil, type: nil)
@@ -112,6 +112,7 @@ module ActiveRecord
112
112
  def closed?; true; end
113
113
  def open?; false; end
114
114
  def joinable?; false; end
115
+ def isolation; nil; end
115
116
  def add_record(record, _ = true); end
116
117
  def restartable?; false; end
117
118
  def dirty?; false; end
@@ -123,6 +124,7 @@ module ActiveRecord
123
124
  def after_commit; yield; end
124
125
  def after_rollback; end
125
126
  def user_transaction; ActiveRecord::Transaction::NULL_TRANSACTION; end
127
+ def isolation=(_); end
126
128
  end
127
129
 
128
130
  class Transaction # :nodoc:
@@ -150,6 +152,15 @@ module ActiveRecord
150
152
 
151
153
  delegate :invalidate!, :invalidated?, to: :@state
152
154
 
155
+ # Returns the isolation level if it was explicitly set, nil otherwise
156
+ def isolation
157
+ @isolation_level
158
+ end
159
+
160
+ def isolation=(isolation) # :nodoc:
161
+ @isolation_level = isolation
162
+ end
163
+
153
164
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
154
165
  super()
155
166
  @connection = connection
@@ -175,11 +186,11 @@ module ActiveRecord
175
186
  end
176
187
 
177
188
  def open?
178
- true
189
+ !closed?
179
190
  end
180
191
 
181
192
  def closed?
182
- false
193
+ @state.finalized?
183
194
  end
184
195
 
185
196
  def add_record(record, ensure_finalize = true)
@@ -386,7 +397,7 @@ module ActiveRecord
386
397
  @parent.state.add_child(@state)
387
398
  end
388
399
 
389
- delegate :materialize!, :materialized?, :restart, to: :@parent
400
+ delegate :materialize!, :materialized?, :restart, :isolation, to: :@parent
390
401
 
391
402
  def rollback
392
403
  @state.rollback!
@@ -405,6 +416,7 @@ module ActiveRecord
405
416
  def initialize(connection, savepoint_name, parent_transaction, **options)
406
417
  super(connection, **options)
407
418
 
419
+ @parent_transaction = parent_transaction
408
420
  parent_transaction.state.add_child(@state)
409
421
 
410
422
  if isolation_level
@@ -414,6 +426,15 @@ module ActiveRecord
414
426
  @savepoint_name = savepoint_name
415
427
  end
416
428
 
429
+ # Delegates to parent transaction's isolation level
430
+ def isolation
431
+ @parent_transaction.isolation
432
+ end
433
+
434
+ def isolation=(isolation) # :nodoc:
435
+ @parent_transaction.isolation = isolation
436
+ end
437
+
417
438
  def materialize!
418
439
  connection.create_savepoint(savepoint_name)
419
440
  super
@@ -620,6 +641,7 @@ module ActiveRecord
620
641
  end
621
642
 
622
643
  def within_new_transaction(isolation: nil, joinable: true)
644
+ isolation ||= @connection.pool.pool_transaction_isolation_level
623
645
  @connection.lock.synchronize do
624
646
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
625
647
  begin
@@ -5,6 +5,7 @@ require "active_record/connection_adapters/abstract/schema_dumper"
5
5
  require "active_record/connection_adapters/abstract/schema_creation"
6
6
  require "active_support/concurrency/null_lock"
7
7
  require "active_support/concurrency/load_interlock_aware_monitor"
8
+ require "active_support/concurrency/thread_monitor"
8
9
  require "arel/collectors/bind"
9
10
  require "arel/collectors/composite"
10
11
  require "arel/collectors/sql_string"
@@ -42,6 +43,7 @@ module ActiveRecord
42
43
 
43
44
  attr_reader :pool
44
45
  attr_reader :visitor, :owner, :logger, :lock
46
+ attr_reader :allow_preconnect # :nodoc:
45
47
  attr_accessor :pinned # :nodoc:
46
48
  alias :in_use? :owner
47
49
 
@@ -51,7 +53,11 @@ module ActiveRecord
51
53
  @pool = value
52
54
  end
53
55
 
54
- set_callback :checkin, :after, :enable_lazy_transactions!
56
+ def allow_preconnect=(value) # :nodoc:
57
+ @lock.synchronize do
58
+ @allow_preconnect = value
59
+ end
60
+ end
55
61
 
56
62
  def self.type_cast_config_to_integer(config)
57
63
  if config.is_a?(Integer)
@@ -120,7 +126,7 @@ module ActiveRecord
120
126
 
121
127
  # Opens a database console session.
122
128
  def self.dbconsole(config, options = {})
123
- raise NotImplementedError
129
+ raise NotImplementedError.new("#{self.class} should define `dbconsole` that accepts a db config and options to implement connecting to the db console")
124
130
  end
125
131
 
126
132
  def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc:
@@ -128,6 +134,7 @@ module ActiveRecord
128
134
 
129
135
  @raw_connection = nil
130
136
  @unconfigured_connection = nil
137
+ @connected_since = nil
131
138
 
132
139
  if config_or_deprecated_connection.is_a?(Hash)
133
140
  @config = config_or_deprecated_connection.symbolize_keys
@@ -140,6 +147,7 @@ module ActiveRecord
140
147
  # Soft-deprecated for now; we'll probably warn in future.
141
148
 
142
149
  @unconfigured_connection = config_or_deprecated_connection
150
+ @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
143
151
  @logger = deprecated_logger || ActiveRecord::Base.logger
144
152
  if deprecated_config
145
153
  @config = (deprecated_config || {}).symbolize_keys
@@ -154,6 +162,7 @@ module ActiveRecord
154
162
  @pinned = false
155
163
  @pool = ActiveRecord::ConnectionAdapters::NullPool.new
156
164
  @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
165
+ @allow_preconnect = false
157
166
  @visitor = arel_visitor
158
167
  @statements = build_statement_pool
159
168
  self.lock_thread = nil
@@ -171,6 +180,8 @@ module ActiveRecord
171
180
  @raw_connection_dirty = false
172
181
  @last_activity = nil
173
182
  @verified = false
183
+
184
+ @pool_jitter = rand * max_jitter
174
185
  end
175
186
 
176
187
  def inspect # :nodoc:
@@ -184,20 +195,25 @@ module ActiveRecord
184
195
  @lock =
185
196
  case lock_thread
186
197
  when Thread
187
- ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
198
+ ActiveSupport::Concurrency::ThreadMonitor.new
188
199
  when Fiber
189
- ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
200
+ ::Monitor.new
190
201
  else
191
202
  ActiveSupport::Concurrency::NullLock
192
203
  end
193
204
  end
194
205
 
195
- def check_if_write_query(sql) # :nodoc:
196
- if preventing_writes? && write_query?(sql)
206
+ def ensure_writes_are_allowed(sql) # :nodoc:
207
+ if preventing_writes?
197
208
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
198
209
  end
199
210
  end
200
211
 
212
+ MAX_JITTER = 0.0..1.0 # :nodoc:
213
+ def max_jitter
214
+ (@config[:pool_jitter] || 0.2).to_f.clamp(MAX_JITTER)
215
+ end
216
+
201
217
  def replica?
202
218
  @config[:replica] || false
203
219
  end
@@ -262,7 +278,11 @@ module ActiveRecord
262
278
  end
263
279
 
264
280
  def valid_type?(type) # :nodoc:
265
- !native_database_types[type].nil?
281
+ self.class.valid_type?(type)
282
+ end
283
+
284
+ def native_database_types # :nodoc:
285
+ self.class.native_database_types
266
286
  end
267
287
 
268
288
  # this method must only be called while holding connection pool's mutex
@@ -301,8 +321,12 @@ module ActiveRecord
301
321
  @pool.schema_cache || (@schema_cache ||= BoundSchemaReflection.for_lone_connection(@pool.schema_reflection, self))
302
322
  end
303
323
 
324
+ def pool_jitter(duration)
325
+ duration * (1.0 - @pool_jitter)
326
+ end
327
+
304
328
  # this method must only be called while holding connection pool's mutex
305
- def expire
329
+ def expire(update_idle = true) # :nodoc:
306
330
  if in_use?
307
331
  if @owner != ActiveSupport::IsolatedExecutionState.context
308
332
  raise ActiveRecordError, "Cannot expire connection, " \
@@ -310,8 +334,12 @@ module ActiveRecord
310
334
  "Current thread: #{ActiveSupport::IsolatedExecutionState.context}."
311
335
  end
312
336
 
313
- @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
314
- @owner = nil
337
+ _run_checkin_callbacks do
338
+ @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) if update_idle
339
+ @owner = nil
340
+ enable_lazy_transactions!
341
+ unset_query_cache!
342
+ end
315
343
  else
316
344
  raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
317
345
  end
@@ -343,6 +371,21 @@ module ActiveRecord
343
371
  end
344
372
  end
345
373
 
374
+ # Seconds since this connection was established. nil if not
375
+ # connected; infinity if the connection has been explicitly
376
+ # retired.
377
+ def connection_age # :nodoc:
378
+ if @raw_connection && @connected_since
379
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - @connected_since
380
+ end
381
+ end
382
+
383
+ # Mark the connection as needing to be retired, as if the age has
384
+ # exceeded the maximum allowed.
385
+ def force_retirement # :nodoc:
386
+ @connected_since &&= -Float::INFINITY
387
+ end
388
+
346
389
  def unprepared_statement
347
390
  cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements
348
391
  yield
@@ -557,6 +600,10 @@ module ActiveRecord
557
600
  false
558
601
  end
559
602
 
603
+ def supports_disabling_indexes?
604
+ false
605
+ end
606
+
560
607
  def return_value_after_insert?(column) # :nodoc:
561
608
  column.auto_populated?
562
609
  end
@@ -666,12 +713,15 @@ module ActiveRecord
666
713
  deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline
667
714
 
668
715
  @lock.synchronize do
716
+ @allow_preconnect = false
717
+
669
718
  reconnect
670
719
 
671
720
  enable_lazy_transactions!
672
721
  @raw_connection_dirty = false
673
- @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
722
+ @last_activity = @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
674
723
  @verified = true
724
+ @allow_preconnect = true
675
725
 
676
726
  reset_transaction(restore: restore_transactions) do
677
727
  clear_cache!(new_connection: true)
@@ -704,6 +754,7 @@ module ActiveRecord
704
754
  clear_cache!(new_connection: true)
705
755
  reset_transaction
706
756
  @raw_connection_dirty = false
757
+ @connected_since = nil
707
758
  end
708
759
  end
709
760
 
@@ -767,6 +818,7 @@ module ActiveRecord
767
818
  attempt_configure_connection
768
819
  @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
769
820
  @verified = true
821
+ @allow_preconnect = true
770
822
  return
771
823
  end
772
824
 
@@ -774,6 +826,7 @@ module ActiveRecord
774
826
  end
775
827
  end
776
828
 
829
+ @last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
777
830
  @verified = true
778
831
  end
779
832
 
@@ -783,8 +836,15 @@ module ActiveRecord
783
836
  end
784
837
 
785
838
  def clean! # :nodoc:
786
- @raw_connection_dirty = false
787
- @verified = nil
839
+ _run_checkout_callbacks do
840
+ @raw_connection_dirty = false
841
+ @verified = nil
842
+ end
843
+ self
844
+ end
845
+
846
+ def verified? # :nodoc:
847
+ @verified
788
848
  end
789
849
 
790
850
  # Provides access to the underlying database driver for this adapter. For
@@ -872,7 +932,7 @@ module ActiveRecord
872
932
  def register_class_with_precision(mapping, key, klass, **kwargs) # :nodoc:
873
933
  mapping.register_type(key) do |*args|
874
934
  precision = extract_precision(args.last)
875
- klass.new(precision: precision, **kwargs)
935
+ klass.new(precision: precision, **kwargs).freeze
876
936
  end
877
937
  end
878
938
 
@@ -884,6 +944,10 @@ module ActiveRecord
884
944
  end
885
945
  end
886
946
 
947
+ def valid_type?(type) # :nodoc:
948
+ !native_database_types[type].nil?
949
+ end
950
+
887
951
  private
888
952
  def initialize_type_map(m)
889
953
  register_class_with_limit m, %r(boolean)i, Type::Boolean
@@ -903,7 +967,7 @@ module ActiveRecord
903
967
  m.alias_type %r(number)i, "decimal"
904
968
  m.alias_type %r(double)i, "float"
905
969
 
906
- m.register_type %r(^json)i, Type::Json.new
970
+ m.register_type %r(^json)i, Type::Json.new.freeze
907
971
 
908
972
  m.register_type(%r(decimal)i) do |sql_type|
909
973
  scale = extract_scale(sql_type)
@@ -911,9 +975,9 @@ module ActiveRecord
911
975
 
912
976
  if scale == 0
913
977
  # FIXME: Remove this class as well
914
- Type::DecimalWithoutScale.new(precision: precision)
978
+ Type::DecimalWithoutScale.new(precision: precision).freeze
915
979
  else
916
- Type::Decimal.new(precision: precision, scale: scale)
980
+ Type::Decimal.new(precision: precision, scale: scale).freeze
917
981
  end
918
982
  end
919
983
  end
@@ -921,7 +985,7 @@ module ActiveRecord
921
985
  def register_class_with_limit(mapping, key, klass)
922
986
  mapping.register_type(key) do |*args|
923
987
  limit = extract_limit(args.last)
924
- klass.new(limit: limit)
988
+ klass.new(limit: limit).freeze
925
989
  end
926
990
  end
927
991
 
@@ -1082,7 +1146,7 @@ module ActiveRecord
1082
1146
  end
1083
1147
 
1084
1148
  def reconnect
1085
- raise NotImplementedError
1149
+ raise NotImplementedError.new("#{self.class} should define `reconnect` to implement adapter-specific logic for reconnecting to the database")
1086
1150
  end
1087
1151
 
1088
1152
  # Returns a raw connection for internal use with methods that are known
@@ -1133,7 +1197,7 @@ module ActiveRecord
1133
1197
  active_record_error
1134
1198
  end
1135
1199
 
1136
- def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc:
1200
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, allow_retry: false, &block) # :doc:
1137
1201
  instrumenter.instrument(
1138
1202
  "sql.active_record",
1139
1203
  sql: sql,
@@ -1141,8 +1205,10 @@ module ActiveRecord
1141
1205
  binds: binds,
1142
1206
  type_casted_binds: type_casted_binds,
1143
1207
  async: async,
1208
+ allow_retry: allow_retry,
1144
1209
  connection: self,
1145
1210
  transaction: current_transaction.user_transaction.presence,
1211
+ affected_rows: 0,
1146
1212
  row_count: 0,
1147
1213
  &block
1148
1214
  )