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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +520 -514
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -0
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +0 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +66 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +2 -1
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +53 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +12 -0
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +2 -6
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +36 -10
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +15 -3
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +27 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +38 -28
- data/lib/active_record/relation/where_clause.rb +1 -8
- data/lib/active_record/relation.rb +24 -12
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +25 -34
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +14 -10
- data/lib/active_record/explain_subscriber.rb +0 -34
- 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
|
|
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(
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
189
|
+
!closed?
|
|
179
190
|
end
|
|
180
191
|
|
|
181
192
|
def closed?
|
|
182
|
-
|
|
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
|
-
|
|
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::
|
|
198
|
+
ActiveSupport::Concurrency::ThreadMonitor.new
|
|
188
199
|
when Fiber
|
|
189
|
-
|
|
200
|
+
::Monitor.new
|
|
190
201
|
else
|
|
191
202
|
ActiveSupport::Concurrency::NullLock
|
|
192
203
|
end
|
|
193
204
|
end
|
|
194
205
|
|
|
195
|
-
def
|
|
196
|
-
if preventing_writes?
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
-
|
|
787
|
-
|
|
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
|
)
|