activerecord 8.0.5 → 8.1.0.beta1
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 +421 -652
- 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 +0 -2
- 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 +4 -2
- data/lib/active_record/associations/preloader/batch.rb +1 -7
- 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.rb +1 -1
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -3
- 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 +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +383 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +27 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
- 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 +88 -25
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +96 -49
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- 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 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -12
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/column.rb +2 -8
- 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_dumper.rb +3 -15
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -33
- 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 +8 -12
- 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 +50 -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/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +0 -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 +14 -9
- 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 +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +15 -2
- 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 +10 -7
- 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/databases.rake +16 -4
- 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 +26 -12
- 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 +28 -12
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -3
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +40 -31
- data/lib/active_record/relation/where_clause.rb +2 -11
- data/lib/active_record/relation.rb +26 -14
- data/lib/active_record/result.rb +44 -21
- 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/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +2 -21
- 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 +10 -2
- 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 -9
- 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/predications.rb +3 -1
- 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
- metadata +14 -10
- 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
|
|
@@ -1560,11 +1604,11 @@ module ActiveRecord
|
|
|
1560
1604
|
non_combinable_operations = []
|
|
1561
1605
|
|
|
1562
1606
|
operations.each do |command, args|
|
|
1563
|
-
args.shift
|
|
1607
|
+
table, arguments = args.shift, args
|
|
1564
1608
|
method = :"#{command}_for_alter"
|
|
1565
1609
|
|
|
1566
1610
|
if respond_to?(method, true)
|
|
1567
|
-
sqls, procs = Array(send(method,
|
|
1611
|
+
sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
|
|
1568
1612
|
sql_fragments.concat(sqls)
|
|
1569
1613
|
non_combinable_operations.concat(procs)
|
|
1570
1614
|
else
|
|
@@ -1572,7 +1616,7 @@ module ActiveRecord
|
|
|
1572
1616
|
non_combinable_operations.each(&:call)
|
|
1573
1617
|
sql_fragments = []
|
|
1574
1618
|
non_combinable_operations = []
|
|
1575
|
-
send(command,
|
|
1619
|
+
send(command, table, *arguments)
|
|
1576
1620
|
end
|
|
1577
1621
|
end
|
|
1578
1622
|
|
|
@@ -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
|
|
@@ -150,6 +151,11 @@ module ActiveRecord
|
|
|
150
151
|
|
|
151
152
|
delegate :invalidate!, :invalidated?, to: :@state
|
|
152
153
|
|
|
154
|
+
# Returns the isolation level if it was explicitly set, nil otherwise
|
|
155
|
+
def isolation
|
|
156
|
+
@isolation_level
|
|
157
|
+
end
|
|
158
|
+
|
|
153
159
|
def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
|
|
154
160
|
super()
|
|
155
161
|
@connection = connection
|
|
@@ -175,11 +181,11 @@ module ActiveRecord
|
|
|
175
181
|
end
|
|
176
182
|
|
|
177
183
|
def open?
|
|
178
|
-
|
|
184
|
+
!closed?
|
|
179
185
|
end
|
|
180
186
|
|
|
181
187
|
def closed?
|
|
182
|
-
|
|
188
|
+
@state.finalized?
|
|
183
189
|
end
|
|
184
190
|
|
|
185
191
|
def add_record(record, ensure_finalize = true)
|
|
@@ -386,7 +392,7 @@ module ActiveRecord
|
|
|
386
392
|
@parent.state.add_child(@state)
|
|
387
393
|
end
|
|
388
394
|
|
|
389
|
-
delegate :materialize!, :materialized?, :restart, to: :@parent
|
|
395
|
+
delegate :materialize!, :materialized?, :restart, :isolation, to: :@parent
|
|
390
396
|
|
|
391
397
|
def rollback
|
|
392
398
|
@state.rollback!
|
|
@@ -405,6 +411,7 @@ module ActiveRecord
|
|
|
405
411
|
def initialize(connection, savepoint_name, parent_transaction, **options)
|
|
406
412
|
super(connection, **options)
|
|
407
413
|
|
|
414
|
+
@parent_transaction = parent_transaction
|
|
408
415
|
parent_transaction.state.add_child(@state)
|
|
409
416
|
|
|
410
417
|
if isolation_level
|
|
@@ -414,6 +421,11 @@ module ActiveRecord
|
|
|
414
421
|
@savepoint_name = savepoint_name
|
|
415
422
|
end
|
|
416
423
|
|
|
424
|
+
# Delegates to parent transaction's isolation level
|
|
425
|
+
def isolation
|
|
426
|
+
@parent_transaction.isolation
|
|
427
|
+
end
|
|
428
|
+
|
|
417
429
|
def materialize!
|
|
418
430
|
connection.create_savepoint(savepoint_name)
|
|
419
431
|
super
|
|
@@ -620,6 +632,7 @@ module ActiveRecord
|
|
|
620
632
|
end
|
|
621
633
|
|
|
622
634
|
def within_new_transaction(isolation: nil, joinable: true)
|
|
635
|
+
isolation ||= @connection.pool.pool_transaction_isolation_level
|
|
623
636
|
@connection.lock.synchronize do
|
|
624
637
|
transaction = begin_transaction(isolation: isolation, joinable: joinable)
|
|
625
638
|
begin
|
|
@@ -42,7 +42,7 @@ module ActiveRecord
|
|
|
42
42
|
|
|
43
43
|
attr_reader :pool
|
|
44
44
|
attr_reader :visitor, :owner, :logger, :lock
|
|
45
|
-
attr_accessor :
|
|
45
|
+
attr_accessor :allow_preconnect
|
|
46
46
|
alias :in_use? :owner
|
|
47
47
|
|
|
48
48
|
def pool=(value)
|
|
@@ -120,7 +120,7 @@ module ActiveRecord
|
|
|
120
120
|
|
|
121
121
|
# Opens a database console session.
|
|
122
122
|
def self.dbconsole(config, options = {})
|
|
123
|
-
raise NotImplementedError
|
|
123
|
+
raise NotImplementedError.new("#{self.class} should define `dbconsole` that accepts a db config and options to implement connecting to the db console")
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc:
|
|
@@ -128,6 +128,7 @@ module ActiveRecord
|
|
|
128
128
|
|
|
129
129
|
@raw_connection = nil
|
|
130
130
|
@unconfigured_connection = nil
|
|
131
|
+
@connected_since = nil
|
|
131
132
|
|
|
132
133
|
if config_or_deprecated_connection.is_a?(Hash)
|
|
133
134
|
@config = config_or_deprecated_connection.symbolize_keys
|
|
@@ -140,6 +141,7 @@ module ActiveRecord
|
|
|
140
141
|
# Soft-deprecated for now; we'll probably warn in future.
|
|
141
142
|
|
|
142
143
|
@unconfigured_connection = config_or_deprecated_connection
|
|
144
|
+
@connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
143
145
|
@logger = deprecated_logger || ActiveRecord::Base.logger
|
|
144
146
|
if deprecated_config
|
|
145
147
|
@config = (deprecated_config || {}).symbolize_keys
|
|
@@ -151,9 +153,9 @@ module ActiveRecord
|
|
|
151
153
|
end
|
|
152
154
|
|
|
153
155
|
@owner = nil
|
|
154
|
-
@pinned = false
|
|
155
156
|
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
|
|
156
157
|
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
158
|
+
@allow_preconnect = true
|
|
157
159
|
@visitor = arel_visitor
|
|
158
160
|
@statements = build_statement_pool
|
|
159
161
|
self.lock_thread = nil
|
|
@@ -171,6 +173,8 @@ module ActiveRecord
|
|
|
171
173
|
@raw_connection_dirty = false
|
|
172
174
|
@last_activity = nil
|
|
173
175
|
@verified = false
|
|
176
|
+
|
|
177
|
+
@pool_jitter = rand * max_jitter
|
|
174
178
|
end
|
|
175
179
|
|
|
176
180
|
def inspect # :nodoc:
|
|
@@ -198,6 +202,11 @@ module ActiveRecord
|
|
|
198
202
|
end
|
|
199
203
|
end
|
|
200
204
|
|
|
205
|
+
MAX_JITTER = 0.0..1.0 # :nodoc:
|
|
206
|
+
def max_jitter
|
|
207
|
+
(@config[:pool_jitter] || 0.2).to_f.clamp(MAX_JITTER)
|
|
208
|
+
end
|
|
209
|
+
|
|
201
210
|
def replica?
|
|
202
211
|
@config[:replica] || false
|
|
203
212
|
end
|
|
@@ -262,7 +271,11 @@ module ActiveRecord
|
|
|
262
271
|
end
|
|
263
272
|
|
|
264
273
|
def valid_type?(type) # :nodoc:
|
|
265
|
-
|
|
274
|
+
self.class.valid_type?(type)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def native_database_types # :nodoc:
|
|
278
|
+
self.class.native_database_types
|
|
266
279
|
end
|
|
267
280
|
|
|
268
281
|
# this method must only be called while holding connection pool's mutex
|
|
@@ -301,8 +314,12 @@ module ActiveRecord
|
|
|
301
314
|
@pool.schema_cache || (@schema_cache ||= BoundSchemaReflection.for_lone_connection(@pool.schema_reflection, self))
|
|
302
315
|
end
|
|
303
316
|
|
|
317
|
+
def pool_jitter(duration)
|
|
318
|
+
duration * (1.0 - @pool_jitter)
|
|
319
|
+
end
|
|
320
|
+
|
|
304
321
|
# this method must only be called while holding connection pool's mutex
|
|
305
|
-
def expire
|
|
322
|
+
def expire(update_idle = true) # :nodoc:
|
|
306
323
|
if in_use?
|
|
307
324
|
if @owner != ActiveSupport::IsolatedExecutionState.context
|
|
308
325
|
raise ActiveRecordError, "Cannot expire connection, " \
|
|
@@ -310,7 +327,7 @@ module ActiveRecord
|
|
|
310
327
|
"Current thread: #{ActiveSupport::IsolatedExecutionState.context}."
|
|
311
328
|
end
|
|
312
329
|
|
|
313
|
-
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
330
|
+
@idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC) if update_idle
|
|
314
331
|
@owner = nil
|
|
315
332
|
else
|
|
316
333
|
raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
|
|
@@ -343,6 +360,21 @@ module ActiveRecord
|
|
|
343
360
|
end
|
|
344
361
|
end
|
|
345
362
|
|
|
363
|
+
# Seconds since this connection was established. nil if not
|
|
364
|
+
# connected; infinity if the connection has been explicitly
|
|
365
|
+
# retired.
|
|
366
|
+
def connection_age # :nodoc:
|
|
367
|
+
if @raw_connection && @connected_since
|
|
368
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - @connected_since
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Mark the connection as needing to be retired, as if the age has
|
|
373
|
+
# exceeded the maximum allowed.
|
|
374
|
+
def force_retirement # :nodoc:
|
|
375
|
+
@connected_since &&= -Float::INFINITY
|
|
376
|
+
end
|
|
377
|
+
|
|
346
378
|
def unprepared_statement
|
|
347
379
|
cache = prepared_statements_disabled_cache.add?(object_id) if @prepared_statements
|
|
348
380
|
yield
|
|
@@ -557,6 +589,10 @@ module ActiveRecord
|
|
|
557
589
|
false
|
|
558
590
|
end
|
|
559
591
|
|
|
592
|
+
def supports_disabling_indexes?
|
|
593
|
+
false
|
|
594
|
+
end
|
|
595
|
+
|
|
560
596
|
def return_value_after_insert?(column) # :nodoc:
|
|
561
597
|
column.auto_populated?
|
|
562
598
|
end
|
|
@@ -666,33 +702,37 @@ module ActiveRecord
|
|
|
666
702
|
deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline
|
|
667
703
|
|
|
668
704
|
@lock.synchronize do
|
|
669
|
-
|
|
670
|
-
reconnect
|
|
705
|
+
@allow_preconnect = false
|
|
671
706
|
|
|
672
|
-
|
|
673
|
-
@raw_connection_dirty = false
|
|
674
|
-
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
675
|
-
@verified = true
|
|
707
|
+
reconnect
|
|
676
708
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
709
|
+
enable_lazy_transactions!
|
|
710
|
+
@raw_connection_dirty = false
|
|
711
|
+
@last_activity = @connected_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
712
|
+
@verified = true
|
|
713
|
+
@allow_preconnect = true
|
|
714
|
+
|
|
715
|
+
reset_transaction(restore: restore_transactions) do
|
|
716
|
+
clear_cache!(new_connection: true)
|
|
717
|
+
attempt_configure_connection
|
|
718
|
+
end
|
|
719
|
+
rescue => original_exception
|
|
720
|
+
translated_exception = translate_exception_class(original_exception, nil, nil)
|
|
721
|
+
retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
684
722
|
|
|
685
|
-
|
|
686
|
-
|
|
723
|
+
if !retry_deadline_exceeded && retries_available > 0
|
|
724
|
+
retries_available -= 1
|
|
687
725
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
end
|
|
726
|
+
if retryable_connection_error?(translated_exception)
|
|
727
|
+
backoff(connection_retries - retries_available)
|
|
728
|
+
retry
|
|
692
729
|
end
|
|
693
|
-
|
|
694
|
-
raise translated_exception
|
|
695
730
|
end
|
|
731
|
+
|
|
732
|
+
@last_activity = nil
|
|
733
|
+
@verified = false
|
|
734
|
+
|
|
735
|
+
raise translated_exception
|
|
696
736
|
end
|
|
697
737
|
end
|
|
698
738
|
|
|
@@ -700,11 +740,10 @@ module ActiveRecord
|
|
|
700
740
|
# method does nothing.
|
|
701
741
|
def disconnect!
|
|
702
742
|
@lock.synchronize do
|
|
703
|
-
@last_activity = nil
|
|
704
|
-
@verified = false
|
|
705
743
|
clear_cache!(new_connection: true)
|
|
706
744
|
reset_transaction
|
|
707
745
|
@raw_connection_dirty = false
|
|
746
|
+
@connected_since = nil
|
|
708
747
|
end
|
|
709
748
|
end
|
|
710
749
|
|
|
@@ -727,11 +766,9 @@ module ActiveRecord
|
|
|
727
766
|
# should call super immediately after resetting the connection (and while
|
|
728
767
|
# still holding @lock).
|
|
729
768
|
def reset!
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
configure_connection
|
|
734
|
-
end
|
|
769
|
+
clear_cache!(new_connection: true)
|
|
770
|
+
reset_transaction
|
|
771
|
+
attempt_configure_connection
|
|
735
772
|
end
|
|
736
773
|
|
|
737
774
|
# Removes the connection from the pool and disconnect it.
|
|
@@ -765,13 +802,12 @@ module ActiveRecord
|
|
|
765
802
|
unless active?
|
|
766
803
|
@lock.synchronize do
|
|
767
804
|
if @unconfigured_connection
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
end
|
|
805
|
+
@raw_connection = @unconfigured_connection
|
|
806
|
+
@unconfigured_connection = nil
|
|
807
|
+
attempt_configure_connection
|
|
808
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
809
|
+
@verified = true
|
|
810
|
+
@allow_preconnect = true
|
|
775
811
|
return
|
|
776
812
|
end
|
|
777
813
|
|
|
@@ -779,6 +815,7 @@ module ActiveRecord
|
|
|
779
815
|
end
|
|
780
816
|
end
|
|
781
817
|
|
|
818
|
+
@last_activity = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
782
819
|
@verified = true
|
|
783
820
|
end
|
|
784
821
|
|
|
@@ -792,6 +829,10 @@ module ActiveRecord
|
|
|
792
829
|
@verified = nil
|
|
793
830
|
end
|
|
794
831
|
|
|
832
|
+
def verified? # :nodoc:
|
|
833
|
+
@verified
|
|
834
|
+
end
|
|
835
|
+
|
|
795
836
|
# Provides access to the underlying database driver for this adapter. For
|
|
796
837
|
# example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
|
|
797
838
|
# and a PG::Connection object in case of PostgreSQLAdapter.
|
|
@@ -877,7 +918,7 @@ module ActiveRecord
|
|
|
877
918
|
def register_class_with_precision(mapping, key, klass, **kwargs) # :nodoc:
|
|
878
919
|
mapping.register_type(key) do |*args|
|
|
879
920
|
precision = extract_precision(args.last)
|
|
880
|
-
klass.new(precision: precision, **kwargs)
|
|
921
|
+
klass.new(precision: precision, **kwargs).freeze
|
|
881
922
|
end
|
|
882
923
|
end
|
|
883
924
|
|
|
@@ -889,6 +930,10 @@ module ActiveRecord
|
|
|
889
930
|
end
|
|
890
931
|
end
|
|
891
932
|
|
|
933
|
+
def valid_type?(type) # :nodoc:
|
|
934
|
+
!native_database_types[type].nil?
|
|
935
|
+
end
|
|
936
|
+
|
|
892
937
|
private
|
|
893
938
|
def initialize_type_map(m)
|
|
894
939
|
register_class_with_limit m, %r(boolean)i, Type::Boolean
|
|
@@ -908,7 +953,7 @@ module ActiveRecord
|
|
|
908
953
|
m.alias_type %r(number)i, "decimal"
|
|
909
954
|
m.alias_type %r(double)i, "float"
|
|
910
955
|
|
|
911
|
-
m.register_type %r(^json)i, Type::Json.new
|
|
956
|
+
m.register_type %r(^json)i, Type::Json.new.freeze
|
|
912
957
|
|
|
913
958
|
m.register_type(%r(decimal)i) do |sql_type|
|
|
914
959
|
scale = extract_scale(sql_type)
|
|
@@ -916,9 +961,9 @@ module ActiveRecord
|
|
|
916
961
|
|
|
917
962
|
if scale == 0
|
|
918
963
|
# FIXME: Remove this class as well
|
|
919
|
-
Type::DecimalWithoutScale.new(precision: precision)
|
|
964
|
+
Type::DecimalWithoutScale.new(precision: precision).freeze
|
|
920
965
|
else
|
|
921
|
-
Type::Decimal.new(precision: precision, scale: scale)
|
|
966
|
+
Type::Decimal.new(precision: precision, scale: scale).freeze
|
|
922
967
|
end
|
|
923
968
|
end
|
|
924
969
|
end
|
|
@@ -926,7 +971,7 @@ module ActiveRecord
|
|
|
926
971
|
def register_class_with_limit(mapping, key, klass)
|
|
927
972
|
mapping.register_type(key) do |*args|
|
|
928
973
|
limit = extract_limit(args.last)
|
|
929
|
-
klass.new(limit: limit)
|
|
974
|
+
klass.new(limit: limit).freeze
|
|
930
975
|
end
|
|
931
976
|
end
|
|
932
977
|
|
|
@@ -1087,7 +1132,7 @@ module ActiveRecord
|
|
|
1087
1132
|
end
|
|
1088
1133
|
|
|
1089
1134
|
def reconnect
|
|
1090
|
-
raise NotImplementedError
|
|
1135
|
+
raise NotImplementedError.new("#{self.class} should define `reconnect` to implement adapter-specific logic for reconnecting to the database")
|
|
1091
1136
|
end
|
|
1092
1137
|
|
|
1093
1138
|
# Returns a raw connection for internal use with methods that are known
|
|
@@ -1138,7 +1183,7 @@ module ActiveRecord
|
|
|
1138
1183
|
active_record_error
|
|
1139
1184
|
end
|
|
1140
1185
|
|
|
1141
|
-
def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc:
|
|
1186
|
+
def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, allow_retry: false, &block) # :doc:
|
|
1142
1187
|
instrumenter.instrument(
|
|
1143
1188
|
"sql.active_record",
|
|
1144
1189
|
sql: sql,
|
|
@@ -1146,8 +1191,10 @@ module ActiveRecord
|
|
|
1146
1191
|
binds: binds,
|
|
1147
1192
|
type_casted_binds: type_casted_binds,
|
|
1148
1193
|
async: async,
|
|
1194
|
+
allow_retry: allow_retry,
|
|
1149
1195
|
connection: self,
|
|
1150
1196
|
transaction: current_transaction.user_transaction.presence,
|
|
1197
|
+
affected_rows: 0,
|
|
1151
1198
|
row_count: 0,
|
|
1152
1199
|
&block
|
|
1153
1200
|
)
|
|
@@ -1221,7 +1268,7 @@ module ActiveRecord
|
|
|
1221
1268
|
end
|
|
1222
1269
|
|
|
1223
1270
|
def attempt_configure_connection
|
|
1224
|
-
|
|
1271
|
+
configure_connection
|
|
1225
1272
|
rescue Exception # Need to handle things such as Timeout::ExitException
|
|
1226
1273
|
disconnect!
|
|
1227
1274
|
raise
|