activerecord 8.0.0 → 8.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +703 -248
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +18 -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_association.rb +3 -3
- 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/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/primary_key.rb +2 -1
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +23 -18
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +22 -12
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
- 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 +458 -108
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
- 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 +31 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -64
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +57 -20
- 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/quoting.rb +7 -1
- 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 +33 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -17
- 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 +22 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +82 -49
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +14 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -12
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +65 -36
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -2
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +15 -10
- data/lib/active_record/core.rb +44 -12
- data/lib/active_record/counter_cache.rb +34 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +59 -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 +19 -19
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encryptor.rb +39 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +23 -7
- 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/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +3 -3
- data/lib/active_record/gem_version.rb +2 -2
- 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 +19 -3
- 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 +31 -21
- 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 +7 -7
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +35 -6
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +24 -20
- 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 +35 -0
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +54 -38
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +42 -25
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -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 +4 -2
- data/lib/active_record/relation/query_methods.rb +43 -32
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +43 -19
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +42 -22
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +47 -18
- data/lib/active_record/statement_cache.rb +15 -11
- 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 +44 -45
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- 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 +39 -16
- 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 +71 -6
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +6 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -15
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -60,7 +60,7 @@ module ActiveRecord
|
|
|
60
60
|
:reverse_order, :distinct, :create_with, :skip_query_cache]
|
|
61
61
|
|
|
62
62
|
CLAUSE_METHODS = [:where, :having, :from]
|
|
63
|
-
|
|
63
|
+
INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL = [:distinct, :with, :with_recursive]
|
|
64
64
|
|
|
65
65
|
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
|
|
66
66
|
|
|
@@ -272,7 +272,12 @@ module ActiveRecord
|
|
|
272
272
|
# such situation.
|
|
273
273
|
def create_or_find_by(attributes, &block)
|
|
274
274
|
with_connection do |connection|
|
|
275
|
-
|
|
275
|
+
record = nil
|
|
276
|
+
transaction(requires_new: true) do
|
|
277
|
+
record = create(attributes, &block)
|
|
278
|
+
record._last_transaction_return_status || raise(ActiveRecord::Rollback)
|
|
279
|
+
end
|
|
280
|
+
record
|
|
276
281
|
rescue ActiveRecord::RecordNotUnique
|
|
277
282
|
if connection.transaction_open?
|
|
278
283
|
where(attributes).lock.find_by!(attributes)
|
|
@@ -287,7 +292,12 @@ module ActiveRecord
|
|
|
287
292
|
# is raised if the created record is invalid.
|
|
288
293
|
def create_or_find_by!(attributes, &block)
|
|
289
294
|
with_connection do |connection|
|
|
290
|
-
|
|
295
|
+
record = nil
|
|
296
|
+
transaction(requires_new: true) do
|
|
297
|
+
record = create!(attributes, &block)
|
|
298
|
+
record._last_transaction_return_status || raise(ActiveRecord::Rollback)
|
|
299
|
+
end
|
|
300
|
+
record
|
|
291
301
|
rescue ActiveRecord::RecordNotUnique
|
|
292
302
|
if connection.transaction_open?
|
|
293
303
|
where(attributes).lock.find_by!(attributes)
|
|
@@ -297,7 +307,7 @@ module ActiveRecord
|
|
|
297
307
|
end
|
|
298
308
|
end
|
|
299
309
|
|
|
300
|
-
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core
|
|
310
|
+
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
|
|
301
311
|
# instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
|
|
302
312
|
def find_or_initialize_by(attributes, &block)
|
|
303
313
|
find_by(attributes) || new(attributes, &block)
|
|
@@ -590,6 +600,18 @@ module ActiveRecord
|
|
|
590
600
|
|
|
591
601
|
return 0 if @none
|
|
592
602
|
|
|
603
|
+
invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
|
|
604
|
+
value = @values[method]
|
|
605
|
+
method == :distinct ? value : value&.any?
|
|
606
|
+
end
|
|
607
|
+
if invalid_methods.any?
|
|
608
|
+
ActiveRecord.deprecator.warn <<~MESSAGE
|
|
609
|
+
`#{invalid_methods.join(', ')}` is not supported by `update_all` and was never included in the generated query.
|
|
610
|
+
|
|
611
|
+
Calling `#{invalid_methods.join(', ')}` with `update_all` will raise an error in Rails 8.2.
|
|
612
|
+
MESSAGE
|
|
613
|
+
end
|
|
614
|
+
|
|
593
615
|
if updates.is_a?(Hash)
|
|
594
616
|
if model.locking_enabled? &&
|
|
595
617
|
!updates.key?(model.locking_column) &&
|
|
@@ -603,17 +625,15 @@ module ActiveRecord
|
|
|
603
625
|
end
|
|
604
626
|
|
|
605
627
|
model.with_connection do |c|
|
|
606
|
-
arel = eager_loading? ? apply_join_dependency.arel :
|
|
628
|
+
arel = eager_loading? ? apply_join_dependency.arel : arel()
|
|
607
629
|
arel.source.left = table
|
|
608
630
|
|
|
609
|
-
group_values_arel_columns = arel_columns(group_values.uniq)
|
|
610
|
-
having_clause_ast = having_clause.ast unless having_clause.empty?
|
|
611
631
|
key = if model.composite_primary_key?
|
|
612
632
|
primary_key.map { |pk| table[pk] }
|
|
613
633
|
else
|
|
614
634
|
table[primary_key]
|
|
615
635
|
end
|
|
616
|
-
stmt = arel.compile_update(values, key
|
|
636
|
+
stmt = arel.compile_update(values, key)
|
|
617
637
|
c.update(stmt, "#{model} Update All").tap { reset }
|
|
618
638
|
end
|
|
619
639
|
end
|
|
@@ -820,7 +840,7 @@ module ActiveRecord
|
|
|
820
840
|
#
|
|
821
841
|
# [:returning]
|
|
822
842
|
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
|
|
823
|
-
#
|
|
843
|
+
# upserted records, which by default is the primary key.
|
|
824
844
|
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
825
845
|
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
826
846
|
# clause entirely.
|
|
@@ -849,7 +869,9 @@ module ActiveRecord
|
|
|
849
869
|
# Active Record's schema_cache.
|
|
850
870
|
#
|
|
851
871
|
# [:on_duplicate]
|
|
852
|
-
# Configure the
|
|
872
|
+
# Configure the behavior that will be used in case of conflict. Use `:skip`
|
|
873
|
+
# to ignore any conflicts or provide a safe SQL fragment wrapped with
|
|
874
|
+
# `Arel.sql`.
|
|
853
875
|
#
|
|
854
876
|
# NOTE: If you use this option you must provide all the columns you want to update
|
|
855
877
|
# by yourself.
|
|
@@ -949,7 +971,7 @@ module ActiveRecord
|
|
|
949
971
|
# If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
|
950
972
|
# If no time argument is passed, the current time is used as default.
|
|
951
973
|
#
|
|
952
|
-
#
|
|
974
|
+
# ==== Examples
|
|
953
975
|
#
|
|
954
976
|
# # Touch all records
|
|
955
977
|
# Person.all.touch_all
|
|
@@ -1000,7 +1022,7 @@ module ActiveRecord
|
|
|
1000
1022
|
#
|
|
1001
1023
|
# Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
|
|
1002
1024
|
#
|
|
1003
|
-
#
|
|
1025
|
+
# This call deletes the affected posts all at once with a single DELETE statement.
|
|
1004
1026
|
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
|
|
1005
1027
|
# +after_destroy+ callbacks, use the #destroy_all method instead.
|
|
1006
1028
|
#
|
|
@@ -1011,7 +1033,7 @@ module ActiveRecord
|
|
|
1011
1033
|
def delete_all
|
|
1012
1034
|
return 0 if @none
|
|
1013
1035
|
|
|
1014
|
-
invalid_methods =
|
|
1036
|
+
invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
|
|
1015
1037
|
value = @values[method]
|
|
1016
1038
|
method == :distinct ? value : value&.any?
|
|
1017
1039
|
end
|
|
@@ -1020,17 +1042,15 @@ module ActiveRecord
|
|
|
1020
1042
|
end
|
|
1021
1043
|
|
|
1022
1044
|
model.with_connection do |c|
|
|
1023
|
-
arel = eager_loading? ? apply_join_dependency.arel :
|
|
1045
|
+
arel = eager_loading? ? apply_join_dependency.arel : arel()
|
|
1024
1046
|
arel.source.left = table
|
|
1025
1047
|
|
|
1026
|
-
group_values_arel_columns = arel_columns(group_values.uniq)
|
|
1027
|
-
having_clause_ast = having_clause.ast unless having_clause.empty?
|
|
1028
1048
|
key = if model.composite_primary_key?
|
|
1029
1049
|
primary_key.map { |pk| table[pk] }
|
|
1030
1050
|
else
|
|
1031
1051
|
table[primary_key]
|
|
1032
1052
|
end
|
|
1033
|
-
stmt = arel.compile_delete(key
|
|
1053
|
+
stmt = arel.compile_delete(key)
|
|
1034
1054
|
|
|
1035
1055
|
c.delete(stmt, "#{model} Delete All").tap { reset }
|
|
1036
1056
|
end
|
|
@@ -1395,12 +1415,16 @@ module ActiveRecord
|
|
|
1395
1415
|
|
|
1396
1416
|
def _increment_attribute(attribute, value = 1)
|
|
1397
1417
|
bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
|
|
1398
|
-
expr = table.coalesce(
|
|
1418
|
+
expr = table.coalesce(attribute, 0)
|
|
1399
1419
|
expr = value < 0 ? expr - bind : expr + bind
|
|
1400
1420
|
expr.expr
|
|
1401
1421
|
end
|
|
1402
1422
|
|
|
1403
1423
|
def exec_queries(&block)
|
|
1424
|
+
if lock_value && model.current_preventing_writes
|
|
1425
|
+
raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
|
|
1426
|
+
end
|
|
1427
|
+
|
|
1404
1428
|
skip_query_cache_if_necessary do
|
|
1405
1429
|
rows = if scheduled?
|
|
1406
1430
|
future = @future_result
|
|
@@ -1440,7 +1464,7 @@ module ActiveRecord
|
|
|
1440
1464
|
else
|
|
1441
1465
|
relation = join_dependency.apply_column_aliases(relation)
|
|
1442
1466
|
@_join_dependency = join_dependency
|
|
1443
|
-
c.select_all(relation.arel, "
|
|
1467
|
+
c.select_all(relation.arel, "#{model.name} Eager Load", async: async)
|
|
1444
1468
|
end
|
|
1445
1469
|
end
|
|
1446
1470
|
end
|
data/lib/active_record/result.rb
CHANGED
|
@@ -29,6 +29,11 @@ module ActiveRecord
|
|
|
29
29
|
# ...
|
|
30
30
|
# ]
|
|
31
31
|
#
|
|
32
|
+
# # Get the number of rows affected by the query:
|
|
33
|
+
# result = ActiveRecord::Base.lease_connection.exec_query('INSERT INTO posts (title, body) VALUES ("title_3", "body_3"), ("title_4", "body_4")')
|
|
34
|
+
# result.affected_rows
|
|
35
|
+
# # => 2
|
|
36
|
+
#
|
|
32
37
|
# # ActiveRecord::Result also includes Enumerable.
|
|
33
38
|
# result.each do |row|
|
|
34
39
|
# puts row['title'] + " " + row['body']
|
|
@@ -89,24 +94,26 @@ module ActiveRecord
|
|
|
89
94
|
alias_method :to_hash, :to_h
|
|
90
95
|
end
|
|
91
96
|
|
|
92
|
-
attr_reader :columns, :rows, :
|
|
97
|
+
attr_reader :columns, :rows, :affected_rows
|
|
93
98
|
|
|
94
|
-
def self.empty(async: false) # :nodoc:
|
|
99
|
+
def self.empty(async: false, affected_rows: nil) # :nodoc:
|
|
95
100
|
if async
|
|
96
|
-
|
|
101
|
+
FutureResult.wrap(new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows)).freeze
|
|
97
102
|
else
|
|
98
|
-
|
|
103
|
+
new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows).freeze
|
|
99
104
|
end
|
|
100
105
|
end
|
|
101
106
|
|
|
102
|
-
def initialize(columns, rows, column_types = nil)
|
|
107
|
+
def initialize(columns, rows, column_types = nil, affected_rows: nil)
|
|
103
108
|
# We freeze the strings to prevent them getting duped when
|
|
104
109
|
# used as keys in ActiveRecord::Base's @attributes hash
|
|
105
110
|
@columns = columns.each(&:-@).freeze
|
|
106
111
|
@rows = rows
|
|
107
112
|
@hash_rows = nil
|
|
108
|
-
@column_types = column_types
|
|
113
|
+
@column_types = column_types.freeze
|
|
114
|
+
@types_hash = nil
|
|
109
115
|
@column_indexes = nil
|
|
116
|
+
@affected_rows = affected_rows
|
|
110
117
|
end
|
|
111
118
|
|
|
112
119
|
# Returns true if this result set includes the column named +name+
|
|
@@ -154,6 +161,24 @@ module ActiveRecord
|
|
|
154
161
|
n ? hash_rows.last(n) : hash_rows.last
|
|
155
162
|
end
|
|
156
163
|
|
|
164
|
+
# Returns the +ActiveRecord::Type+ type of all columns.
|
|
165
|
+
# Note that not all database adapters return the result types,
|
|
166
|
+
# so the hash may be empty.
|
|
167
|
+
def column_types
|
|
168
|
+
if @column_types
|
|
169
|
+
@types_hash ||= begin
|
|
170
|
+
types = {}
|
|
171
|
+
@columns.each_with_index do |name, index|
|
|
172
|
+
type = @column_types[index] || Type.default_value
|
|
173
|
+
types[name] = types[index] = type
|
|
174
|
+
end
|
|
175
|
+
types.freeze
|
|
176
|
+
end
|
|
177
|
+
else
|
|
178
|
+
EMPTY_HASH
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
157
182
|
def result # :nodoc:
|
|
158
183
|
self
|
|
159
184
|
end
|
|
@@ -162,7 +187,7 @@ module ActiveRecord
|
|
|
162
187
|
self
|
|
163
188
|
end
|
|
164
189
|
|
|
165
|
-
def cast_values(type_overrides =
|
|
190
|
+
def cast_values(type_overrides = nil) # :nodoc:
|
|
166
191
|
if columns.one?
|
|
167
192
|
# Separated to avoid allocating an array per row
|
|
168
193
|
|
|
@@ -190,13 +215,13 @@ module ActiveRecord
|
|
|
190
215
|
|
|
191
216
|
def initialize_copy(other)
|
|
192
217
|
@rows = rows.dup
|
|
193
|
-
@column_types = column_types.dup
|
|
194
218
|
@hash_rows = nil
|
|
195
219
|
end
|
|
196
220
|
|
|
197
221
|
def freeze # :nodoc:
|
|
198
222
|
hash_rows.freeze
|
|
199
|
-
indexed_rows
|
|
223
|
+
indexed_rows
|
|
224
|
+
column_types
|
|
200
225
|
super
|
|
201
226
|
end
|
|
202
227
|
|
|
@@ -204,7 +229,7 @@ module ActiveRecord
|
|
|
204
229
|
@column_indexes ||= begin
|
|
205
230
|
index = 0
|
|
206
231
|
hash = {}
|
|
207
|
-
length
|
|
232
|
+
length = columns.length
|
|
208
233
|
while index < length
|
|
209
234
|
hash[columns[index]] = index
|
|
210
235
|
index += 1
|
|
@@ -222,10 +247,14 @@ module ActiveRecord
|
|
|
222
247
|
|
|
223
248
|
private
|
|
224
249
|
def column_type(name, index, type_overrides)
|
|
225
|
-
type_overrides
|
|
226
|
-
|
|
227
|
-
|
|
250
|
+
if type_overrides
|
|
251
|
+
type_overrides.fetch(name) do
|
|
252
|
+
column_type(name, index, nil)
|
|
228
253
|
end
|
|
254
|
+
elsif @column_types
|
|
255
|
+
@column_types[index] || Type.default_value
|
|
256
|
+
else
|
|
257
|
+
Type.default_value
|
|
229
258
|
end
|
|
230
259
|
end
|
|
231
260
|
|
|
@@ -237,14 +266,8 @@ module ActiveRecord
|
|
|
237
266
|
end
|
|
238
267
|
end
|
|
239
268
|
|
|
240
|
-
|
|
269
|
+
EMPTY_ARRAY = [].freeze
|
|
241
270
|
EMPTY_HASH = {}.freeze
|
|
242
|
-
private_constant :EMPTY_HASH
|
|
243
|
-
|
|
244
|
-
EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
|
|
245
|
-
private_constant :EMPTY
|
|
246
|
-
|
|
247
|
-
EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
|
|
248
|
-
private_constant :EMPTY_ASYNC
|
|
271
|
+
private_constant :EMPTY_ARRAY, :EMPTY_HASH
|
|
249
272
|
end
|
|
250
273
|
end
|
|
@@ -3,80 +3,64 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
# This is a thread locals registry for Active Record. For example:
|
|
5
5
|
#
|
|
6
|
-
# ActiveRecord::RuntimeRegistry.sql_runtime
|
|
6
|
+
# ActiveRecord::RuntimeRegistry.stats.sql_runtime
|
|
7
7
|
#
|
|
8
8
|
# returns the connection handler local to the current unit of execution (either thread of fiber).
|
|
9
9
|
module RuntimeRegistry # :nodoc:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
class Stats
|
|
11
|
+
attr_accessor :sql_runtime, :async_sql_runtime, :queries_count, :cached_queries_count
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@sql_runtime = 0.0
|
|
15
|
+
@async_sql_runtime = 0.0
|
|
16
|
+
@queries_count = 0
|
|
17
|
+
@cached_queries_count = 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset_runtimes
|
|
21
|
+
sql_runtime_was = @sql_runtime
|
|
22
|
+
@sql_runtime = 0.0
|
|
23
|
+
@async_sql_runtime = 0.0
|
|
24
|
+
sql_runtime_was
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
public alias_method :reset, :initialize
|
|
14
28
|
end
|
|
15
29
|
|
|
16
|
-
|
|
17
|
-
ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def async_sql_runtime
|
|
21
|
-
ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
|
|
22
|
-
end
|
|
30
|
+
extend self
|
|
23
31
|
|
|
24
|
-
def
|
|
25
|
-
|
|
32
|
+
def call(name, start, finish, id, payload)
|
|
33
|
+
record(
|
|
34
|
+
payload[:name],
|
|
35
|
+
(finish - start) * 1_000.0,
|
|
36
|
+
cached: payload[:cached],
|
|
37
|
+
async: payload[:async],
|
|
38
|
+
lock_wait: payload[:lock_wait],
|
|
39
|
+
)
|
|
26
40
|
end
|
|
27
41
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
end
|
|
42
|
+
def record(query_name, runtime, cached: false, async: false, lock_wait: nil)
|
|
43
|
+
stats = self.stats
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
unless query_name == "TRANSACTION" || query_name == "SCHEMA"
|
|
46
|
+
stats.queries_count += 1
|
|
47
|
+
stats.cached_queries_count += 1 if cached
|
|
48
|
+
end
|
|
35
49
|
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
if async
|
|
51
|
+
stats.async_sql_runtime += (runtime - lock_wait)
|
|
52
|
+
end
|
|
53
|
+
stats.sql_runtime += runtime
|
|
38
54
|
end
|
|
39
55
|
|
|
40
|
-
def
|
|
41
|
-
ActiveSupport::IsolatedExecutionState[:
|
|
56
|
+
def stats
|
|
57
|
+
ActiveSupport::IsolatedExecutionState[:active_record_runtime] ||= Stats.new
|
|
42
58
|
end
|
|
43
59
|
|
|
44
60
|
def reset
|
|
45
|
-
|
|
46
|
-
reset_queries_count
|
|
47
|
-
reset_cached_queries_count
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def reset_runtimes
|
|
51
|
-
rt, self.sql_runtime = sql_runtime, 0.0
|
|
52
|
-
self.async_sql_runtime = 0.0
|
|
53
|
-
rt
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def reset_queries_count
|
|
57
|
-
qc = queries_count
|
|
58
|
-
self.queries_count = 0
|
|
59
|
-
qc
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def reset_cached_queries_count
|
|
63
|
-
qc = cached_queries_count
|
|
64
|
-
self.cached_queries_count = 0
|
|
65
|
-
qc
|
|
61
|
+
stats.reset
|
|
66
62
|
end
|
|
67
63
|
end
|
|
68
64
|
end
|
|
69
65
|
|
|
70
|
-
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record"
|
|
71
|
-
unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
|
|
72
|
-
ActiveRecord::RuntimeRegistry.queries_count += 1
|
|
73
|
-
ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
runtime = (finish - start) * 1_000.0
|
|
77
|
-
|
|
78
|
-
if payload[:async]
|
|
79
|
-
ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
|
|
80
|
-
end
|
|
81
|
-
ActiveRecord::RuntimeRegistry.sql_runtime += runtime
|
|
82
|
-
end
|
|
66
|
+
ActiveSupport::Notifications.monotonic_subscribe("sql.active_record", ActiveRecord::RuntimeRegistry)
|
|
@@ -161,6 +161,8 @@ module ActiveRecord
|
|
|
161
161
|
#
|
|
162
162
|
# sanitize_sql_array(["role = ?", 0])
|
|
163
163
|
# # => "role = '0'"
|
|
164
|
+
#
|
|
165
|
+
# Before using this method, please consider if Arel.sql would be better for your use-case
|
|
164
166
|
def sanitize_sql_array(ary)
|
|
165
167
|
statement, *values = ary
|
|
166
168
|
if values.first.is_a?(Hash) && /:\w+/.match?(statement)
|
|
@@ -165,7 +165,7 @@ module ActiveRecord
|
|
|
165
165
|
# first dump primary key column
|
|
166
166
|
pk = @connection.primary_key(table)
|
|
167
167
|
|
|
168
|
-
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
|
168
|
+
tbl.print " create_table #{relation_name(remove_prefix_and_suffix(table)).inspect}"
|
|
169
169
|
|
|
170
170
|
case pk
|
|
171
171
|
when String
|
|
@@ -192,7 +192,7 @@ module ActiveRecord
|
|
|
192
192
|
tbl.puts ", force: :cascade do |t|"
|
|
193
193
|
|
|
194
194
|
# then dump all non-primary key columns
|
|
195
|
-
columns.each do |column|
|
|
195
|
+
columns.sort_by(&:name).each do |column|
|
|
196
196
|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
|
197
197
|
next if column.name == pk
|
|
198
198
|
|
|
@@ -207,12 +207,17 @@ module ActiveRecord
|
|
|
207
207
|
end
|
|
208
208
|
|
|
209
209
|
indexes_in_create(table, tbl)
|
|
210
|
-
check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
|
210
|
+
remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
|
211
211
|
exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
|
|
212
212
|
unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
|
|
213
213
|
|
|
214
214
|
tbl.puts " end"
|
|
215
215
|
|
|
216
|
+
if remaining
|
|
217
|
+
tbl.puts
|
|
218
|
+
tbl.print remaining.string
|
|
219
|
+
end
|
|
220
|
+
|
|
216
221
|
stream.print tbl.string
|
|
217
222
|
rescue => e
|
|
218
223
|
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
|
@@ -227,8 +232,8 @@ module ActiveRecord
|
|
|
227
232
|
def indexes(table, stream)
|
|
228
233
|
if (indexes = @connection.indexes(table)).any?
|
|
229
234
|
add_index_statements = indexes.map do |index|
|
|
230
|
-
table_name = remove_prefix_and_suffix(index.table)
|
|
231
|
-
" add_index #{([table_name] + index_parts(index)).join(', ')}"
|
|
235
|
+
table_name = remove_prefix_and_suffix(index.table)
|
|
236
|
+
" add_index #{([relation_name(table_name).inspect] + index_parts(index)).join(', ')}"
|
|
232
237
|
end
|
|
233
238
|
|
|
234
239
|
stream.puts add_index_statements.sort.join("\n")
|
|
@@ -272,35 +277,49 @@ module ActiveRecord
|
|
|
272
277
|
index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
|
|
273
278
|
index_parts << "type: #{index.type.inspect}" if index.type
|
|
274
279
|
index_parts << "comment: #{index.comment.inspect}" if index.comment
|
|
280
|
+
index_parts << "enabled: #{index.enabled.inspect}" if @connection.supports_disabling_indexes? && index.disabled?
|
|
275
281
|
index_parts
|
|
276
282
|
end
|
|
277
283
|
|
|
278
284
|
def check_constraints_in_create(table, stream)
|
|
279
285
|
if (check_constraints = @connection.check_constraints(table)).any?
|
|
280
|
-
|
|
281
|
-
parts = [
|
|
282
|
-
"t.check_constraint #{check_constraint.expression.inspect}"
|
|
283
|
-
]
|
|
286
|
+
check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
|
|
284
287
|
|
|
285
|
-
|
|
286
|
-
|
|
288
|
+
unless check_valid.empty?
|
|
289
|
+
check_constraint_statements = check_valid.map do |check|
|
|
290
|
+
" t.check_constraint #{check_parts(check).join(', ')}"
|
|
287
291
|
end
|
|
288
292
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
" #{parts.join(', ')}"
|
|
293
|
+
stream.puts check_constraint_statements.sort.join("\n")
|
|
292
294
|
end
|
|
293
295
|
|
|
294
|
-
|
|
296
|
+
unless check_invalid.empty?
|
|
297
|
+
remaining = StringIO.new
|
|
298
|
+
table_name = remove_prefix_and_suffix(table).inspect
|
|
299
|
+
|
|
300
|
+
add_check_constraint_statements = check_invalid.map do |check|
|
|
301
|
+
" add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
remaining.puts add_check_constraint_statements.sort.join("\n")
|
|
305
|
+
remaining
|
|
306
|
+
end
|
|
295
307
|
end
|
|
296
308
|
end
|
|
297
309
|
|
|
310
|
+
def check_parts(check)
|
|
311
|
+
check_parts = [ check.expression.inspect ]
|
|
312
|
+
check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
|
|
313
|
+
check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
|
|
314
|
+
check_parts
|
|
315
|
+
end
|
|
316
|
+
|
|
298
317
|
def foreign_keys(table, stream)
|
|
299
318
|
if (foreign_keys = @connection.foreign_keys(table)).any?
|
|
300
319
|
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
|
301
320
|
parts = [
|
|
302
|
-
|
|
303
|
-
remove_prefix_and_suffix(foreign_key.to_table).inspect,
|
|
321
|
+
relation_name(remove_prefix_and_suffix(foreign_key.from_table)).inspect,
|
|
322
|
+
relation_name(remove_prefix_and_suffix(foreign_key.to_table)).inspect,
|
|
304
323
|
]
|
|
305
324
|
|
|
306
325
|
if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
|
|
@@ -311,16 +330,13 @@ module ActiveRecord
|
|
|
311
330
|
parts << "primary_key: #{foreign_key.primary_key.inspect}"
|
|
312
331
|
end
|
|
313
332
|
|
|
314
|
-
if foreign_key.export_name_on_schema_dump?
|
|
315
|
-
parts << "name: #{foreign_key.name.inspect}"
|
|
316
|
-
end
|
|
317
|
-
|
|
333
|
+
parts << "name: #{foreign_key.name.inspect}" if foreign_key.export_name_on_schema_dump?
|
|
318
334
|
parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
|
|
319
335
|
parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
|
|
320
336
|
parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
|
|
321
337
|
parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
|
|
322
338
|
|
|
323
|
-
" #{parts.join(', ')}"
|
|
339
|
+
" add_foreign_key #{parts.join(', ')}"
|
|
324
340
|
end
|
|
325
341
|
|
|
326
342
|
stream.puts add_foreign_key_statements.sort.join("\n")
|
|
@@ -345,6 +361,10 @@ module ActiveRecord
|
|
|
345
361
|
end
|
|
346
362
|
end
|
|
347
363
|
|
|
364
|
+
def relation_name(name)
|
|
365
|
+
name
|
|
366
|
+
end
|
|
367
|
+
|
|
348
368
|
def remove_prefix_and_suffix(table)
|
|
349
369
|
# This method appears at the top when profiling active_record test cases run.
|
|
350
370
|
# Avoid costly calculation when there are no prefix and suffix.
|
|
@@ -30,13 +30,13 @@ module ActiveRecord
|
|
|
30
30
|
# {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
|
|
31
31
|
# You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
|
|
32
32
|
#
|
|
33
|
-
#
|
|
33
|
+
# ==== Options
|
|
34
34
|
#
|
|
35
|
-
# [
|
|
35
|
+
# [+:length+]
|
|
36
36
|
# Length of the Secure Random, with a minimum of 24 characters. It will
|
|
37
37
|
# default to 24.
|
|
38
38
|
#
|
|
39
|
-
# [
|
|
39
|
+
# [+:on+]
|
|
40
40
|
# The callback when the value is generated. When called with <tt>on:
|
|
41
41
|
# :initialize</tt>, the value is generated in an
|
|
42
42
|
# <tt>after_initialize</tt> callback, otherwise the value will be used
|