activerecord 7.0.0 → 7.2.1
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 +515 -1268
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +28 -17
- data/lib/active_record/associations/collection_proxy.rb +36 -13
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +28 -18
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +18 -14
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +2 -4
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +378 -491
- data/lib/active_record/attribute_assignment.rb +1 -13
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +153 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -40
- data/lib/active_record/attributes.rb +63 -48
- data/lib/active_record/autosave_association.rb +70 -38
- data/lib/active_record/base.rb +12 -8
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +297 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +215 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
- data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +98 -106
- data/lib/active_record/core.rb +220 -177
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +88 -35
- data/lib/active_record/delegated_type.rb +40 -11
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +47 -25
- data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
- data/lib/active_record/encryption/encryptor.rb +25 -10
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +23 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +131 -27
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +169 -99
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +13 -10
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +39 -24
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +28 -27
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +110 -13
- data/lib/active_record/migration/compatibility.rb +174 -64
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +292 -125
- data/lib/active_record/model_schema.rb +113 -112
- data/lib/active_record/nested_attributes.rb +35 -9
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +177 -345
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +102 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +34 -9
- data/lib/active_record/railtie.rb +153 -100
- data/lib/active_record/railties/controller_runtime.rb +24 -10
- data/lib/active_record/railties/databases.rake +148 -152
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +278 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +293 -108
- data/lib/active_record/relation/delegation.rb +31 -20
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +625 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +602 -96
- data/lib/active_record/result.rb +55 -52
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +76 -30
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +82 -30
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +3 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +29 -8
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +191 -121
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +174 -152
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +109 -27
- data/lib/active_record/translation.rb +1 -3
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -7
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +12 -6
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +63 -14
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +266 -30
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +59 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
|
@@ -3,20 +3,70 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
# = Active Record \Relation
|
|
5
5
|
class Relation
|
|
6
|
+
class ExplainProxy # :nodoc:
|
|
7
|
+
def initialize(relation, options)
|
|
8
|
+
@relation = relation
|
|
9
|
+
@options = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def inspect
|
|
13
|
+
exec_explain { @relation.send(:exec_queries) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def average(column_name)
|
|
17
|
+
exec_explain { @relation.average(column_name) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def count(column_name = nil)
|
|
21
|
+
exec_explain { @relation.count(column_name) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def first(limit = nil)
|
|
25
|
+
exec_explain { @relation.first(limit) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def last(limit = nil)
|
|
29
|
+
exec_explain { @relation.last(limit) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def maximum(column_name)
|
|
33
|
+
exec_explain { @relation.maximum(column_name) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def minimum(column_name)
|
|
37
|
+
exec_explain { @relation.minimum(column_name) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def pluck(*column_names)
|
|
41
|
+
exec_explain { @relation.pluck(*column_names) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sum(identity_or_column = nil)
|
|
45
|
+
exec_explain { @relation.sum(identity_or_column) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def exec_explain(&block)
|
|
50
|
+
@relation.exec_explain(@relation.collecting_queries_for_explain { block.call }, @options)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
6
54
|
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
|
|
7
55
|
:order, :joins, :left_outer_joins, :references,
|
|
8
|
-
:extending, :unscope, :optimizer_hints, :annotate
|
|
56
|
+
:extending, :unscope, :optimizer_hints, :annotate,
|
|
57
|
+
:with]
|
|
9
58
|
|
|
10
59
|
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :strict_loading,
|
|
11
60
|
:reverse_order, :distinct, :create_with, :skip_query_cache]
|
|
12
61
|
|
|
13
62
|
CLAUSE_METHODS = [:where, :having, :from]
|
|
14
|
-
INVALID_METHODS_FOR_DELETE_ALL = [:distinct]
|
|
63
|
+
INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :with, :with_recursive]
|
|
15
64
|
|
|
16
65
|
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
|
|
17
66
|
|
|
18
67
|
include Enumerable
|
|
19
68
|
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
|
|
69
|
+
include SignedId::RelationMethods, TokenFor::RelationMethods
|
|
20
70
|
|
|
21
71
|
attr_reader :table, :klass, :loaded, :predicate_builder
|
|
22
72
|
attr_accessor :skip_preloading_value
|
|
@@ -33,6 +83,8 @@ module ActiveRecord
|
|
|
33
83
|
@delegate_to_klass = false
|
|
34
84
|
@future_result = nil
|
|
35
85
|
@records = nil
|
|
86
|
+
@async = false
|
|
87
|
+
@none = false
|
|
36
88
|
end
|
|
37
89
|
|
|
38
90
|
def initialize_copy(other)
|
|
@@ -43,7 +95,7 @@ module ActiveRecord
|
|
|
43
95
|
def bind_attribute(name, value) # :nodoc:
|
|
44
96
|
if reflection = klass._reflect_on_association(name)
|
|
45
97
|
name = reflection.foreign_key
|
|
46
|
-
value = value.read_attribute(reflection.
|
|
98
|
+
value = value.read_attribute(reflection.association_primary_key) unless value.nil?
|
|
47
99
|
end
|
|
48
100
|
|
|
49
101
|
attr = table[name]
|
|
@@ -159,21 +211,25 @@ module ActiveRecord
|
|
|
159
211
|
# failed due to validation errors it won't be persisted, you get what
|
|
160
212
|
# #create returns in such situation.
|
|
161
213
|
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
214
|
+
# If creation failed because of a unique constraint, this method will
|
|
215
|
+
# assume it encountered a race condition and will try finding the record
|
|
216
|
+
# once more. If somehow the second find still does not find a record
|
|
217
|
+
# because a concurrent DELETE happened, it will then raise an
|
|
218
|
+
# ActiveRecord::RecordNotFound exception.
|
|
166
219
|
#
|
|
167
|
-
#
|
|
220
|
+
# Please note <b>this method is not atomic</b>, it runs first a SELECT,
|
|
221
|
+
# and if there are no results an INSERT is attempted. So if the table
|
|
222
|
+
# doesn't have a relevant unique constraint it could be the case that
|
|
223
|
+
# you end up with two or more similar records.
|
|
168
224
|
def find_or_create_by(attributes, &block)
|
|
169
|
-
find_by(attributes) ||
|
|
225
|
+
find_by(attributes) || create_or_find_by(attributes, &block)
|
|
170
226
|
end
|
|
171
227
|
|
|
172
228
|
# Like #find_or_create_by, but calls
|
|
173
229
|
# {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
|
|
174
230
|
# is raised if the created record is invalid.
|
|
175
231
|
def find_or_create_by!(attributes, &block)
|
|
176
|
-
find_by(attributes) ||
|
|
232
|
+
find_by(attributes) || create_or_find_by!(attributes, &block)
|
|
177
233
|
end
|
|
178
234
|
|
|
179
235
|
# Attempts to create a record with the given attributes in a table that has a unique database constraint
|
|
@@ -181,16 +237,15 @@ module ActiveRecord
|
|
|
181
237
|
# unique constraints, the exception such an insertion would normally raise is caught,
|
|
182
238
|
# and the existing record with those attributes is found using #find_by!.
|
|
183
239
|
#
|
|
184
|
-
# This is similar to #find_or_create_by, but
|
|
185
|
-
#
|
|
186
|
-
# if none is found.
|
|
240
|
+
# This is similar to #find_or_create_by, but tries to create the record first. As such it is
|
|
241
|
+
# better suited for cases where the record is most likely not to exist yet.
|
|
187
242
|
#
|
|
188
243
|
# There are several drawbacks to #create_or_find_by, though:
|
|
189
244
|
#
|
|
190
245
|
# * The underlying table must have the relevant columns defined with unique database constraints.
|
|
191
246
|
# * A unique constraint violation may be triggered by only one, or at least less than all,
|
|
192
247
|
# of the given attributes. This means that the subsequent #find_by! may fail to find a
|
|
193
|
-
# matching record, which will then raise an
|
|
248
|
+
# matching record, which will then raise an ActiveRecord::RecordNotFound exception,
|
|
194
249
|
# rather than a record with the given attributes.
|
|
195
250
|
# * While we avoid the race condition between SELECT -> INSERT from #find_or_create_by,
|
|
196
251
|
# we actually have another race condition between INSERT -> SELECT, which can be triggered
|
|
@@ -199,26 +254,40 @@ module ActiveRecord
|
|
|
199
254
|
# * It relies on exception handling to handle control flow, which may be marginally slower.
|
|
200
255
|
# * The primary key may auto-increment on each create, even if it fails. This can accelerate
|
|
201
256
|
# the problem of running out of integers, if the underlying table is still stuck on a primary
|
|
202
|
-
# key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
|
|
257
|
+
# key of type int (note: All \Rails apps since 5.1+ have defaulted to bigint, which is not liable
|
|
203
258
|
# to this problem).
|
|
259
|
+
# * Columns with unique database constraints should not have uniqueness validations defined,
|
|
260
|
+
# otherwise #create will fail due to validation errors and #find_by will never be called.
|
|
204
261
|
#
|
|
205
262
|
# This method will return a record if all given attributes are covered by unique constraints
|
|
206
263
|
# (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
|
|
207
264
|
# and failed due to validation errors it won't be persisted, you get what #create returns in
|
|
208
265
|
# such situation.
|
|
209
266
|
def create_or_find_by(attributes, &block)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
267
|
+
with_connection do |connection|
|
|
268
|
+
transaction(requires_new: true) { create(attributes, &block) }
|
|
269
|
+
rescue ActiveRecord::RecordNotUnique
|
|
270
|
+
if connection.transaction_open?
|
|
271
|
+
where(attributes).lock.find_by!(attributes)
|
|
272
|
+
else
|
|
273
|
+
find_by!(attributes)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
213
276
|
end
|
|
214
277
|
|
|
215
278
|
# Like #create_or_find_by, but calls
|
|
216
279
|
# {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
|
|
217
280
|
# is raised if the created record is invalid.
|
|
218
281
|
def create_or_find_by!(attributes, &block)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
282
|
+
with_connection do |connection|
|
|
283
|
+
transaction(requires_new: true) { create!(attributes, &block) }
|
|
284
|
+
rescue ActiveRecord::RecordNotUnique
|
|
285
|
+
if connection.transaction_open?
|
|
286
|
+
where(attributes).lock.find_by!(attributes)
|
|
287
|
+
else
|
|
288
|
+
find_by!(attributes)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
222
291
|
end
|
|
223
292
|
|
|
224
293
|
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
|
|
@@ -231,13 +300,30 @@ module ActiveRecord
|
|
|
231
300
|
# returns the result as a string. The string is formatted imitating the
|
|
232
301
|
# ones printed by the database shell.
|
|
233
302
|
#
|
|
303
|
+
# User.all.explain
|
|
304
|
+
# # EXPLAIN SELECT `users`.* FROM `users`
|
|
305
|
+
# # ...
|
|
306
|
+
#
|
|
234
307
|
# Note that this method actually runs the queries, since the results of some
|
|
235
308
|
# are needed by the next ones when eager loading is going on.
|
|
236
309
|
#
|
|
310
|
+
# To run EXPLAIN on queries created by +first+, +pluck+ and +count+, call
|
|
311
|
+
# these methods on +explain+:
|
|
312
|
+
#
|
|
313
|
+
# User.all.explain.count
|
|
314
|
+
# # EXPLAIN SELECT COUNT(*) FROM `users`
|
|
315
|
+
# # ...
|
|
316
|
+
#
|
|
317
|
+
# The column name can be passed if required:
|
|
318
|
+
#
|
|
319
|
+
# User.all.explain.maximum(:id)
|
|
320
|
+
# # EXPLAIN SELECT MAX(`users`.`id`) FROM `users`
|
|
321
|
+
# # ...
|
|
322
|
+
#
|
|
237
323
|
# Please see further details in the
|
|
238
324
|
# {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
|
|
239
|
-
def explain
|
|
240
|
-
|
|
325
|
+
def explain(*options)
|
|
326
|
+
ExplainProxy.new(self, options)
|
|
241
327
|
end
|
|
242
328
|
|
|
243
329
|
# Converts relation objects to Array.
|
|
@@ -267,6 +353,8 @@ module ActiveRecord
|
|
|
267
353
|
|
|
268
354
|
# Returns true if there are no records.
|
|
269
355
|
def empty?
|
|
356
|
+
return true if @none
|
|
357
|
+
|
|
270
358
|
if loaded?
|
|
271
359
|
records.empty?
|
|
272
360
|
else
|
|
@@ -275,26 +363,49 @@ module ActiveRecord
|
|
|
275
363
|
end
|
|
276
364
|
|
|
277
365
|
# Returns true if there are no records.
|
|
278
|
-
|
|
279
|
-
|
|
366
|
+
#
|
|
367
|
+
# When a pattern argument is given, this method checks whether elements in
|
|
368
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
|
369
|
+
#
|
|
370
|
+
# posts.none?(Comment) # => true or false
|
|
371
|
+
def none?(*args)
|
|
372
|
+
return true if @none
|
|
373
|
+
|
|
374
|
+
return super if args.present? || block_given?
|
|
280
375
|
empty?
|
|
281
376
|
end
|
|
282
377
|
|
|
283
378
|
# Returns true if there are any records.
|
|
284
|
-
|
|
285
|
-
|
|
379
|
+
#
|
|
380
|
+
# When a pattern argument is given, this method checks whether elements in
|
|
381
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
|
382
|
+
#
|
|
383
|
+
# posts.any?(Post) # => true or false
|
|
384
|
+
def any?(*args)
|
|
385
|
+
return false if @none
|
|
386
|
+
|
|
387
|
+
return super if args.present? || block_given?
|
|
286
388
|
!empty?
|
|
287
389
|
end
|
|
288
390
|
|
|
289
391
|
# Returns true if there is exactly one record.
|
|
290
|
-
|
|
291
|
-
|
|
392
|
+
#
|
|
393
|
+
# When a pattern argument is given, this method checks whether elements in
|
|
394
|
+
# the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
|
|
395
|
+
#
|
|
396
|
+
# posts.one?(Post) # => true or false
|
|
397
|
+
def one?(*args)
|
|
398
|
+
return false if @none
|
|
399
|
+
|
|
400
|
+
return super if args.present? || block_given?
|
|
292
401
|
return records.one? if loaded?
|
|
293
402
|
limited_count == 1
|
|
294
403
|
end
|
|
295
404
|
|
|
296
405
|
# Returns true if there is more than one record.
|
|
297
406
|
def many?
|
|
407
|
+
return false if @none
|
|
408
|
+
|
|
298
409
|
return super if block_given?
|
|
299
410
|
return records.many? if loaded?
|
|
300
411
|
limited_count > 1
|
|
@@ -307,7 +418,7 @@ module ActiveRecord
|
|
|
307
418
|
# # => "products/query-1850ab3d302391b85b8693e941286659"
|
|
308
419
|
#
|
|
309
420
|
# If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
|
|
310
|
-
# in Rails 6.0 and earlier, the cache key will also include a version.
|
|
421
|
+
# in \Rails 6.0 and earlier, the cache key will also include a version.
|
|
311
422
|
#
|
|
312
423
|
# ActiveRecord::Base.collection_cache_versioning = false
|
|
313
424
|
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
|
@@ -362,33 +473,35 @@ module ActiveRecord
|
|
|
362
473
|
else
|
|
363
474
|
collection = eager_loading? ? apply_join_dependency : self
|
|
364
475
|
|
|
365
|
-
|
|
366
|
-
|
|
476
|
+
with_connection do |c|
|
|
477
|
+
column = c.visitor.compile(table[timestamp_column])
|
|
478
|
+
select_values = "COUNT(*) AS #{adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
|
|
367
479
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
480
|
+
if collection.has_limit_or_offset?
|
|
481
|
+
query = collection.select("#{column} AS collection_cache_key_timestamp")
|
|
482
|
+
query._select!(table[Arel.star]) if distinct_value && collection.select_values.empty?
|
|
483
|
+
subquery_alias = "subquery_for_cache_key"
|
|
484
|
+
subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
|
|
485
|
+
arel = query.build_subquery(subquery_alias, select_values % subquery_column)
|
|
486
|
+
else
|
|
487
|
+
query = collection.unscope(:order)
|
|
488
|
+
query.select_values = [select_values % column]
|
|
489
|
+
arel = query.arel
|
|
490
|
+
end
|
|
379
491
|
|
|
380
|
-
|
|
492
|
+
size, timestamp = c.select_rows(arel, nil).first
|
|
381
493
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
494
|
+
if size
|
|
495
|
+
column_type = klass.type_for_attribute(timestamp_column)
|
|
496
|
+
timestamp = column_type.deserialize(timestamp)
|
|
497
|
+
else
|
|
498
|
+
size = 0
|
|
499
|
+
end
|
|
387
500
|
end
|
|
388
501
|
end
|
|
389
502
|
|
|
390
503
|
if timestamp
|
|
391
|
-
"#{size}-#{timestamp.utc.
|
|
504
|
+
"#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
|
|
392
505
|
else
|
|
393
506
|
"#{size}"
|
|
394
507
|
end
|
|
@@ -409,7 +522,7 @@ module ActiveRecord
|
|
|
409
522
|
# Comment.where(post_id: 1).scoping do
|
|
410
523
|
# Comment.first
|
|
411
524
|
# end
|
|
412
|
-
# #
|
|
525
|
+
# # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
|
|
413
526
|
#
|
|
414
527
|
# If <tt>all_queries: true</tt> is passed, scoping will apply to all queries
|
|
415
528
|
# for the relation including +update+ and +delete+ on instances.
|
|
@@ -429,10 +542,10 @@ module ActiveRecord
|
|
|
429
542
|
end
|
|
430
543
|
end
|
|
431
544
|
|
|
432
|
-
def _exec_scope(
|
|
545
|
+
def _exec_scope(...) # :nodoc:
|
|
433
546
|
@delegate_to_klass = true
|
|
434
547
|
registry = klass.scope_registry
|
|
435
|
-
_scoping(nil, registry) { instance_exec(
|
|
548
|
+
_scoping(nil, registry) { instance_exec(...) || self }
|
|
436
549
|
ensure
|
|
437
550
|
@delegate_to_klass = false
|
|
438
551
|
end
|
|
@@ -446,7 +559,8 @@ module ActiveRecord
|
|
|
446
559
|
#
|
|
447
560
|
# ==== Parameters
|
|
448
561
|
#
|
|
449
|
-
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
|
|
562
|
+
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement. Any strings provided will
|
|
563
|
+
# be type cast, unless you use +Arel.sql+. (Don't pass user-provided values to +Arel.sql+.)
|
|
450
564
|
#
|
|
451
565
|
# ==== Examples
|
|
452
566
|
#
|
|
@@ -461,9 +575,14 @@ module ActiveRecord
|
|
|
461
575
|
#
|
|
462
576
|
# # Update all invoices and set the number column to its id value.
|
|
463
577
|
# Invoice.update_all('number = id')
|
|
578
|
+
#
|
|
579
|
+
# # Update all books with 'Rails' in their title
|
|
580
|
+
# Book.where('title LIKE ?', '%Rails%').update_all(title: Arel.sql("title + ' - volume 1'"))
|
|
464
581
|
def update_all(updates)
|
|
465
582
|
raise ArgumentError, "Empty list of attributes to change" if updates.blank?
|
|
466
583
|
|
|
584
|
+
return 0 if @none
|
|
585
|
+
|
|
467
586
|
if updates.is_a?(Hash)
|
|
468
587
|
if klass.locking_enabled? &&
|
|
469
588
|
!updates.key?(klass.locking_column) &&
|
|
@@ -476,13 +595,20 @@ module ActiveRecord
|
|
|
476
595
|
values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
|
|
477
596
|
end
|
|
478
597
|
|
|
479
|
-
|
|
480
|
-
|
|
598
|
+
klass.with_connection do |c|
|
|
599
|
+
arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
|
|
600
|
+
arel.source.left = table
|
|
481
601
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
602
|
+
group_values_arel_columns = arel_columns(group_values.uniq)
|
|
603
|
+
having_clause_ast = having_clause.ast unless having_clause.empty?
|
|
604
|
+
key = if klass.composite_primary_key?
|
|
605
|
+
primary_key.map { |pk| table[pk] }
|
|
606
|
+
else
|
|
607
|
+
table[primary_key]
|
|
608
|
+
end
|
|
609
|
+
stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
|
|
610
|
+
c.update(stmt, "#{klass} Update All").tap { reset }
|
|
611
|
+
end
|
|
486
612
|
end
|
|
487
613
|
|
|
488
614
|
def update(id = :all, attributes) # :nodoc:
|
|
@@ -501,6 +627,283 @@ module ActiveRecord
|
|
|
501
627
|
end
|
|
502
628
|
end
|
|
503
629
|
|
|
630
|
+
|
|
631
|
+
# Inserts a single record into the database in a single SQL INSERT
|
|
632
|
+
# statement. It does not instantiate any models nor does it trigger
|
|
633
|
+
# Active Record callbacks or validations. Though passed values
|
|
634
|
+
# go through Active Record's type casting and serialization.
|
|
635
|
+
#
|
|
636
|
+
# See #insert_all for documentation.
|
|
637
|
+
def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
|
|
638
|
+
insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Inserts multiple records into the database in a single SQL INSERT
|
|
642
|
+
# statement. It does not instantiate any models nor does it trigger
|
|
643
|
+
# Active Record callbacks or validations. Though passed values
|
|
644
|
+
# go through Active Record's type casting and serialization.
|
|
645
|
+
#
|
|
646
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
|
647
|
+
# the attributes for a single row and must have the same keys.
|
|
648
|
+
#
|
|
649
|
+
# Rows are considered to be unique by every unique index on the table. Any
|
|
650
|
+
# duplicate rows are skipped.
|
|
651
|
+
# Override with <tt>:unique_by</tt> (see below).
|
|
652
|
+
#
|
|
653
|
+
# Returns an ActiveRecord::Result with its contents based on
|
|
654
|
+
# <tt>:returning</tt> (see below).
|
|
655
|
+
#
|
|
656
|
+
# ==== Options
|
|
657
|
+
#
|
|
658
|
+
# [:returning]
|
|
659
|
+
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
|
|
660
|
+
# inserted records, which by default is the primary key.
|
|
661
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
662
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
663
|
+
# clause entirely.
|
|
664
|
+
#
|
|
665
|
+
# You can also pass an SQL string if you need more control on the return values
|
|
666
|
+
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
|
667
|
+
#
|
|
668
|
+
# [:unique_by]
|
|
669
|
+
# (PostgreSQL and SQLite only) By default rows are considered to be unique
|
|
670
|
+
# by every unique index on the table. Any duplicate rows are skipped.
|
|
671
|
+
#
|
|
672
|
+
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
|
|
673
|
+
#
|
|
674
|
+
# Consider a Book model where no duplicate ISBNs make sense, but if any
|
|
675
|
+
# row has an existing id, or is not unique by another unique index,
|
|
676
|
+
# ActiveRecord::RecordNotUnique is raised.
|
|
677
|
+
#
|
|
678
|
+
# Unique indexes can be identified by columns or name:
|
|
679
|
+
#
|
|
680
|
+
# unique_by: :isbn
|
|
681
|
+
# unique_by: %i[ author_id name ]
|
|
682
|
+
# unique_by: :index_books_on_isbn
|
|
683
|
+
#
|
|
684
|
+
# [:record_timestamps]
|
|
685
|
+
# By default, automatic setting of timestamp columns is controlled by
|
|
686
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
|
687
|
+
# behavior.
|
|
688
|
+
#
|
|
689
|
+
# To override this and force automatic setting of timestamp columns one
|
|
690
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
|
691
|
+
#
|
|
692
|
+
# record_timestamps: true # Always set timestamps automatically
|
|
693
|
+
# record_timestamps: false # Never set timestamps automatically
|
|
694
|
+
#
|
|
695
|
+
# Because it relies on the index information from the database
|
|
696
|
+
# <tt>:unique_by</tt> is recommended to be paired with
|
|
697
|
+
# Active Record's schema_cache.
|
|
698
|
+
#
|
|
699
|
+
# ==== Example
|
|
700
|
+
#
|
|
701
|
+
# # Insert records and skip inserting any duplicates.
|
|
702
|
+
# # Here "Eloquent Ruby" is skipped because its id is not unique.
|
|
703
|
+
#
|
|
704
|
+
# Book.insert_all([
|
|
705
|
+
# { id: 1, title: "Rework", author: "David" },
|
|
706
|
+
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
|
707
|
+
# ])
|
|
708
|
+
#
|
|
709
|
+
# # insert_all works on chained scopes, and you can use create_with
|
|
710
|
+
# # to set default attributes for all inserted records.
|
|
711
|
+
#
|
|
712
|
+
# author.books.create_with(created_at: Time.now).insert_all([
|
|
713
|
+
# { id: 1, title: "Rework" },
|
|
714
|
+
# { id: 2, title: "Eloquent Ruby" }
|
|
715
|
+
# ])
|
|
716
|
+
def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
|
|
717
|
+
InsertAll.execute(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
# Inserts a single record into the database in a single SQL INSERT
|
|
721
|
+
# statement. It does not instantiate any models nor does it trigger
|
|
722
|
+
# Active Record callbacks or validations. Though passed values
|
|
723
|
+
# go through Active Record's type casting and serialization.
|
|
724
|
+
#
|
|
725
|
+
# See #insert_all! for more.
|
|
726
|
+
def insert!(attributes, returning: nil, record_timestamps: nil)
|
|
727
|
+
insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps)
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
# Inserts multiple records into the database in a single SQL INSERT
|
|
731
|
+
# statement. It does not instantiate any models nor does it trigger
|
|
732
|
+
# Active Record callbacks or validations. Though passed values
|
|
733
|
+
# go through Active Record's type casting and serialization.
|
|
734
|
+
#
|
|
735
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
|
736
|
+
# the attributes for a single row and must have the same keys.
|
|
737
|
+
#
|
|
738
|
+
# Raises ActiveRecord::RecordNotUnique if any rows violate a
|
|
739
|
+
# unique index on the table. In that case, no rows are inserted.
|
|
740
|
+
#
|
|
741
|
+
# To skip duplicate rows, see #insert_all. To replace them, see #upsert_all.
|
|
742
|
+
#
|
|
743
|
+
# Returns an ActiveRecord::Result with its contents based on
|
|
744
|
+
# <tt>:returning</tt> (see below).
|
|
745
|
+
#
|
|
746
|
+
# ==== Options
|
|
747
|
+
#
|
|
748
|
+
# [:returning]
|
|
749
|
+
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
|
|
750
|
+
# inserted records, which by default is the primary key.
|
|
751
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
752
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
753
|
+
# clause entirely.
|
|
754
|
+
#
|
|
755
|
+
# You can also pass an SQL string if you need more control on the return values
|
|
756
|
+
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
|
757
|
+
#
|
|
758
|
+
# [:record_timestamps]
|
|
759
|
+
# By default, automatic setting of timestamp columns is controlled by
|
|
760
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
|
761
|
+
# behavior.
|
|
762
|
+
#
|
|
763
|
+
# To override this and force automatic setting of timestamp columns one
|
|
764
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
|
765
|
+
#
|
|
766
|
+
# record_timestamps: true # Always set timestamps automatically
|
|
767
|
+
# record_timestamps: false # Never set timestamps automatically
|
|
768
|
+
#
|
|
769
|
+
# ==== Examples
|
|
770
|
+
#
|
|
771
|
+
# # Insert multiple records
|
|
772
|
+
# Book.insert_all!([
|
|
773
|
+
# { title: "Rework", author: "David" },
|
|
774
|
+
# { title: "Eloquent Ruby", author: "Russ" }
|
|
775
|
+
# ])
|
|
776
|
+
#
|
|
777
|
+
# # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
|
|
778
|
+
# # does not have a unique id.
|
|
779
|
+
# Book.insert_all!([
|
|
780
|
+
# { id: 1, title: "Rework", author: "David" },
|
|
781
|
+
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
|
782
|
+
# ])
|
|
783
|
+
def insert_all!(attributes, returning: nil, record_timestamps: nil)
|
|
784
|
+
InsertAll.execute(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps)
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
# Updates or inserts (upserts) a single record into the database in a
|
|
788
|
+
# single SQL INSERT statement. It does not instantiate any models nor does
|
|
789
|
+
# it trigger Active Record callbacks or validations. Though passed values
|
|
790
|
+
# go through Active Record's type casting and serialization.
|
|
791
|
+
#
|
|
792
|
+
# See #upsert_all for documentation.
|
|
793
|
+
def upsert(attributes, **kwargs)
|
|
794
|
+
upsert_all([ attributes ], **kwargs)
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
# Updates or inserts (upserts) multiple records into the database in a
|
|
798
|
+
# single SQL INSERT statement. It does not instantiate any models nor does
|
|
799
|
+
# it trigger Active Record callbacks or validations. Though passed values
|
|
800
|
+
# go through Active Record's type casting and serialization.
|
|
801
|
+
#
|
|
802
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
|
803
|
+
# the attributes for a single row and must have the same keys.
|
|
804
|
+
#
|
|
805
|
+
# Returns an ActiveRecord::Result with its contents based on
|
|
806
|
+
# <tt>:returning</tt> (see below).
|
|
807
|
+
#
|
|
808
|
+
# By default, +upsert_all+ will update all the columns that can be updated when
|
|
809
|
+
# there is a conflict. These are all the columns except primary keys, read-only
|
|
810
|
+
# columns, and columns covered by the optional +unique_by+.
|
|
811
|
+
#
|
|
812
|
+
# ==== Options
|
|
813
|
+
#
|
|
814
|
+
# [:returning]
|
|
815
|
+
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
|
|
816
|
+
# inserted records, which by default is the primary key.
|
|
817
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
818
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
819
|
+
# clause entirely.
|
|
820
|
+
#
|
|
821
|
+
# You can also pass an SQL string if you need more control on the return values
|
|
822
|
+
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
|
823
|
+
#
|
|
824
|
+
# [:unique_by]
|
|
825
|
+
# (PostgreSQL and SQLite only) By default rows are considered to be unique
|
|
826
|
+
# by every unique index on the table. Any duplicate rows are skipped.
|
|
827
|
+
#
|
|
828
|
+
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
|
|
829
|
+
#
|
|
830
|
+
# Consider a Book model where no duplicate ISBNs make sense, but if any
|
|
831
|
+
# row has an existing id, or is not unique by another unique index,
|
|
832
|
+
# ActiveRecord::RecordNotUnique is raised.
|
|
833
|
+
#
|
|
834
|
+
# Unique indexes can be identified by columns or name:
|
|
835
|
+
#
|
|
836
|
+
# unique_by: :isbn
|
|
837
|
+
# unique_by: %i[ author_id name ]
|
|
838
|
+
# unique_by: :index_books_on_isbn
|
|
839
|
+
#
|
|
840
|
+
# Because it relies on the index information from the database
|
|
841
|
+
# <tt>:unique_by</tt> is recommended to be paired with
|
|
842
|
+
# Active Record's schema_cache.
|
|
843
|
+
#
|
|
844
|
+
# [:on_duplicate]
|
|
845
|
+
# Configure the SQL update sentence that will be used in case of conflict.
|
|
846
|
+
#
|
|
847
|
+
# NOTE: If you use this option you must provide all the columns you want to update
|
|
848
|
+
# by yourself.
|
|
849
|
+
#
|
|
850
|
+
# Example:
|
|
851
|
+
#
|
|
852
|
+
# Commodity.upsert_all(
|
|
853
|
+
# [
|
|
854
|
+
# { id: 2, name: "Copper", price: 4.84 },
|
|
855
|
+
# { id: 4, name: "Gold", price: 1380.87 },
|
|
856
|
+
# { id: 6, name: "Aluminium", price: 0.35 }
|
|
857
|
+
# ],
|
|
858
|
+
# on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)")
|
|
859
|
+
# )
|
|
860
|
+
#
|
|
861
|
+
# See the related +:update_only+ option. Both options can't be used at the same time.
|
|
862
|
+
#
|
|
863
|
+
# [:update_only]
|
|
864
|
+
# Provide a list of column names that will be updated in case of conflict. If not provided,
|
|
865
|
+
# +upsert_all+ will update all the columns that can be updated. These are all the columns
|
|
866
|
+
# except primary keys, read-only columns, and columns covered by the optional +unique_by+
|
|
867
|
+
#
|
|
868
|
+
# Example:
|
|
869
|
+
#
|
|
870
|
+
# Commodity.upsert_all(
|
|
871
|
+
# [
|
|
872
|
+
# { id: 2, name: "Copper", price: 4.84 },
|
|
873
|
+
# { id: 4, name: "Gold", price: 1380.87 },
|
|
874
|
+
# { id: 6, name: "Aluminium", price: 0.35 }
|
|
875
|
+
# ],
|
|
876
|
+
# update_only: [:price] # Only prices will be updated
|
|
877
|
+
# )
|
|
878
|
+
#
|
|
879
|
+
# See the related +:on_duplicate+ option. Both options can't be used at the same time.
|
|
880
|
+
#
|
|
881
|
+
# [:record_timestamps]
|
|
882
|
+
# By default, automatic setting of timestamp columns is controlled by
|
|
883
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
|
884
|
+
# behavior.
|
|
885
|
+
#
|
|
886
|
+
# To override this and force automatic setting of timestamp columns one
|
|
887
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
|
888
|
+
#
|
|
889
|
+
# record_timestamps: true # Always set timestamps automatically
|
|
890
|
+
# record_timestamps: false # Never set timestamps automatically
|
|
891
|
+
#
|
|
892
|
+
# ==== Examples
|
|
893
|
+
#
|
|
894
|
+
# # Inserts multiple records, performing an upsert when records have duplicate ISBNs.
|
|
895
|
+
# # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
|
|
896
|
+
#
|
|
897
|
+
# Book.upsert_all([
|
|
898
|
+
# { title: "Rework", author: "David", isbn: "1" },
|
|
899
|
+
# { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
|
|
900
|
+
# ], unique_by: :isbn)
|
|
901
|
+
#
|
|
902
|
+
# Book.find_by(isbn: "1").title # => "Eloquent Ruby"
|
|
903
|
+
def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
|
904
|
+
InsertAll.execute(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
|
905
|
+
end
|
|
906
|
+
|
|
504
907
|
# Updates the counters of the records in the current relation.
|
|
505
908
|
#
|
|
506
909
|
# ==== Parameters
|
|
@@ -599,6 +1002,8 @@ module ActiveRecord
|
|
|
599
1002
|
# Post.distinct.delete_all
|
|
600
1003
|
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
|
|
601
1004
|
def delete_all
|
|
1005
|
+
return 0 if @none
|
|
1006
|
+
|
|
602
1007
|
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
|
|
603
1008
|
value = @values[method]
|
|
604
1009
|
method == :distinct ? value : value&.any?
|
|
@@ -607,14 +1012,79 @@ module ActiveRecord
|
|
|
607
1012
|
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
|
|
608
1013
|
end
|
|
609
1014
|
|
|
610
|
-
|
|
611
|
-
|
|
1015
|
+
klass.with_connection do |c|
|
|
1016
|
+
arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
|
|
1017
|
+
arel.source.left = table
|
|
612
1018
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1019
|
+
group_values_arel_columns = arel_columns(group_values.uniq)
|
|
1020
|
+
having_clause_ast = having_clause.ast unless having_clause.empty?
|
|
1021
|
+
key = if klass.composite_primary_key?
|
|
1022
|
+
primary_key.map { |pk| table[pk] }
|
|
1023
|
+
else
|
|
1024
|
+
table[primary_key]
|
|
1025
|
+
end
|
|
1026
|
+
stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
|
|
616
1027
|
|
|
617
|
-
|
|
1028
|
+
c.delete(stmt, "#{klass} Delete All").tap { reset }
|
|
1029
|
+
end
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
# Deletes the row with a primary key matching the +id+ argument, using an
|
|
1033
|
+
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
|
|
1034
|
+
# Record objects are not instantiated, so the object's callbacks are not
|
|
1035
|
+
# executed, including any <tt>:dependent</tt> association options.
|
|
1036
|
+
#
|
|
1037
|
+
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
|
1038
|
+
#
|
|
1039
|
+
# Note: Although it is often much faster than the alternative, #destroy,
|
|
1040
|
+
# skipping callbacks might bypass business logic in your application
|
|
1041
|
+
# that ensures referential integrity or performs other essential jobs.
|
|
1042
|
+
#
|
|
1043
|
+
# ==== Examples
|
|
1044
|
+
#
|
|
1045
|
+
# # Delete a single row
|
|
1046
|
+
# Todo.delete(1)
|
|
1047
|
+
#
|
|
1048
|
+
# # Delete multiple rows
|
|
1049
|
+
# Todo.delete([2,3,4])
|
|
1050
|
+
def delete(id_or_array)
|
|
1051
|
+
return 0 if id_or_array.nil? || (id_or_array.is_a?(Array) && id_or_array.empty?)
|
|
1052
|
+
|
|
1053
|
+
where(model.primary_key => id_or_array).delete_all
|
|
1054
|
+
end
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
# Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
|
|
1058
|
+
# therefore all callbacks and filters are fired off before the object is deleted. This method is
|
|
1059
|
+
# less efficient than #delete but allows cleanup methods and other actions to be run.
|
|
1060
|
+
#
|
|
1061
|
+
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
|
1062
|
+
# from the attributes, and then calls destroy on it.
|
|
1063
|
+
#
|
|
1064
|
+
# ==== Parameters
|
|
1065
|
+
#
|
|
1066
|
+
# * +id+ - This should be the id or an array of ids to be destroyed.
|
|
1067
|
+
#
|
|
1068
|
+
# ==== Examples
|
|
1069
|
+
#
|
|
1070
|
+
# # Destroy a single object
|
|
1071
|
+
# Todo.destroy(1)
|
|
1072
|
+
#
|
|
1073
|
+
# # Destroy multiple objects
|
|
1074
|
+
# todos = [1,2,3]
|
|
1075
|
+
# Todo.destroy(todos)
|
|
1076
|
+
def destroy(id)
|
|
1077
|
+
multiple_ids = if model.composite_primary_key?
|
|
1078
|
+
id.first.is_a?(Array)
|
|
1079
|
+
else
|
|
1080
|
+
id.is_a?(Array)
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
if multiple_ids
|
|
1084
|
+
find(id).each(&:destroy)
|
|
1085
|
+
else
|
|
1086
|
+
find(id).destroy
|
|
1087
|
+
end
|
|
618
1088
|
end
|
|
619
1089
|
|
|
620
1090
|
# Finds and destroys all records matching the specified conditions.
|
|
@@ -646,18 +1116,35 @@ module ActiveRecord
|
|
|
646
1116
|
# Schedule the query to be performed from a background thread pool.
|
|
647
1117
|
#
|
|
648
1118
|
# Post.where(published: true).load_async # => #<ActiveRecord::Relation>
|
|
1119
|
+
#
|
|
1120
|
+
# When the +Relation+ is iterated, if the background query wasn't executed yet,
|
|
1121
|
+
# it will be performed by the foreground thread.
|
|
1122
|
+
#
|
|
1123
|
+
# Note that {config.active_record.async_query_executor}[https://guides.rubyonrails.org/configuring.html#config-active-record-async-query-executor] must be configured
|
|
1124
|
+
# for queries to actually be executed concurrently. Otherwise it defaults to
|
|
1125
|
+
# executing them in the foreground.
|
|
1126
|
+
#
|
|
1127
|
+
# +load_async+ will also fall back to executing in the foreground in the test environment when transactional
|
|
1128
|
+
# fixtures are enabled.
|
|
1129
|
+
#
|
|
1130
|
+
# If the query was actually executed in the background, the Active Record logs will show
|
|
1131
|
+
# it by prefixing the log line with <tt>ASYNC</tt>:
|
|
1132
|
+
#
|
|
1133
|
+
# ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
|
|
649
1134
|
def load_async
|
|
650
|
-
|
|
1135
|
+
with_connection do |c|
|
|
1136
|
+
return load if !c.async_enabled?
|
|
651
1137
|
|
|
652
|
-
|
|
653
|
-
|
|
1138
|
+
unless loaded?
|
|
1139
|
+
result = exec_main_query(async: c.current_transaction.closed?)
|
|
654
1140
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1141
|
+
if result.is_a?(Array)
|
|
1142
|
+
@records = result
|
|
1143
|
+
else
|
|
1144
|
+
@future_result = result
|
|
1145
|
+
end
|
|
1146
|
+
@loaded = true
|
|
659
1147
|
end
|
|
660
|
-
@loaded = true
|
|
661
1148
|
end
|
|
662
1149
|
|
|
663
1150
|
self
|
|
@@ -697,6 +1184,7 @@ module ActiveRecord
|
|
|
697
1184
|
@to_sql = @arel = @loaded = @should_eager_load = nil
|
|
698
1185
|
@offsets = @take = nil
|
|
699
1186
|
@cache_keys = nil
|
|
1187
|
+
@cache_versions = nil
|
|
700
1188
|
@records = nil
|
|
701
1189
|
self
|
|
702
1190
|
end
|
|
@@ -704,7 +1192,7 @@ module ActiveRecord
|
|
|
704
1192
|
# Returns sql statement for the relation.
|
|
705
1193
|
#
|
|
706
1194
|
# User.where(name: 'Oscar').to_sql
|
|
707
|
-
# #
|
|
1195
|
+
# # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
|
|
708
1196
|
def to_sql
|
|
709
1197
|
@to_sql ||= if eager_loading?
|
|
710
1198
|
apply_join_dependency do |relation, join_dependency|
|
|
@@ -712,8 +1200,9 @@ module ActiveRecord
|
|
|
712
1200
|
relation.to_sql
|
|
713
1201
|
end
|
|
714
1202
|
else
|
|
715
|
-
|
|
716
|
-
|
|
1203
|
+
klass.with_connection do |conn|
|
|
1204
|
+
conn.unprepared_statement { conn.to_sql(arel) }
|
|
1205
|
+
end
|
|
717
1206
|
end
|
|
718
1207
|
end
|
|
719
1208
|
|
|
@@ -721,7 +1210,7 @@ module ActiveRecord
|
|
|
721
1210
|
#
|
|
722
1211
|
# User.where(name: 'Oscar').where_values_hash
|
|
723
1212
|
# # => {name: "Oscar"}
|
|
724
|
-
def where_values_hash(relation_table_name = klass.table_name)
|
|
1213
|
+
def where_values_hash(relation_table_name = klass.table_name) # :nodoc:
|
|
725
1214
|
where_clause.to_h(relation_table_name)
|
|
726
1215
|
end
|
|
727
1216
|
|
|
@@ -741,7 +1230,7 @@ module ActiveRecord
|
|
|
741
1230
|
# Joins that are also marked for preloading. In which case we should just eager load them.
|
|
742
1231
|
# Note that this is a naive implementation because we could have strings and symbols which
|
|
743
1232
|
# represent the same association, but that aren't matched by this. Also, we could have
|
|
744
|
-
# nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] }
|
|
1233
|
+
# nested hashes which partially match, e.g. <tt>{ a: :b } & { a: [:b, :c] }</tt>
|
|
745
1234
|
def joined_includes_values
|
|
746
1235
|
includes_values & joins_values
|
|
747
1236
|
end
|
|
@@ -758,8 +1247,13 @@ module ActiveRecord
|
|
|
758
1247
|
end
|
|
759
1248
|
end
|
|
760
1249
|
|
|
761
|
-
def pretty_print(
|
|
762
|
-
|
|
1250
|
+
def pretty_print(pp)
|
|
1251
|
+
subject = loaded? ? records : annotate("loading for pp")
|
|
1252
|
+
entries = subject.take([limit_value, 11].compact.min)
|
|
1253
|
+
|
|
1254
|
+
entries[10] = "..." if entries.size == 11
|
|
1255
|
+
|
|
1256
|
+
pp.pp(entries)
|
|
763
1257
|
end
|
|
764
1258
|
|
|
765
1259
|
# Returns true if relation is blank.
|
|
@@ -793,7 +1287,7 @@ module ActiveRecord
|
|
|
793
1287
|
end
|
|
794
1288
|
|
|
795
1289
|
def alias_tracker(joins = [], aliases = nil) # :nodoc:
|
|
796
|
-
ActiveRecord::Associations::AliasTracker.create(
|
|
1290
|
+
ActiveRecord::Associations::AliasTracker.create(connection_pool, table.name, joins, aliases)
|
|
797
1291
|
end
|
|
798
1292
|
|
|
799
1293
|
class StrictLoadingScope # :nodoc:
|
|
@@ -821,10 +1315,6 @@ module ActiveRecord
|
|
|
821
1315
|
@loaded = true
|
|
822
1316
|
end
|
|
823
1317
|
|
|
824
|
-
def null_relation? # :nodoc:
|
|
825
|
-
is_a?(NullRelation)
|
|
826
|
-
end
|
|
827
|
-
|
|
828
1318
|
private
|
|
829
1319
|
def already_in_scope?(registry)
|
|
830
1320
|
@delegate_to_klass && registry.current_scope(klass, true)
|
|
@@ -873,7 +1363,11 @@ module ActiveRecord
|
|
|
873
1363
|
def _substitute_values(values)
|
|
874
1364
|
values.map do |name, value|
|
|
875
1365
|
attr = table[name]
|
|
876
|
-
|
|
1366
|
+
if Arel.arel_node?(value)
|
|
1367
|
+
if value.is_a?(Arel::Nodes::SqlLiteral)
|
|
1368
|
+
value = Arel::Nodes::Grouping.new(value)
|
|
1369
|
+
end
|
|
1370
|
+
else
|
|
877
1371
|
type = klass.type_for_attribute(attr.name)
|
|
878
1372
|
value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
|
|
879
1373
|
end
|
|
@@ -902,28 +1396,40 @@ module ActiveRecord
|
|
|
902
1396
|
preload_associations(records) unless skip_preloading_value
|
|
903
1397
|
|
|
904
1398
|
records.each(&:readonly!) if readonly_value
|
|
905
|
-
records.each
|
|
1399
|
+
records.each { |record| record.strict_loading!(strict_loading_value) } unless strict_loading_value.nil?
|
|
906
1400
|
|
|
907
1401
|
records
|
|
908
1402
|
end
|
|
909
1403
|
end
|
|
910
1404
|
|
|
911
1405
|
def exec_main_query(async: false)
|
|
1406
|
+
if @none
|
|
1407
|
+
if async
|
|
1408
|
+
return FutureResult.wrap([])
|
|
1409
|
+
else
|
|
1410
|
+
return []
|
|
1411
|
+
end
|
|
1412
|
+
end
|
|
1413
|
+
|
|
912
1414
|
skip_query_cache_if_necessary do
|
|
913
1415
|
if where_clause.contradiction?
|
|
914
1416
|
[].freeze
|
|
915
1417
|
elsif eager_loading?
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1418
|
+
klass.with_connection do |c|
|
|
1419
|
+
apply_join_dependency do |relation, join_dependency|
|
|
1420
|
+
if relation.null_relation?
|
|
1421
|
+
[].freeze
|
|
1422
|
+
else
|
|
1423
|
+
relation = join_dependency.apply_column_aliases(relation)
|
|
1424
|
+
@_join_dependency = join_dependency
|
|
1425
|
+
c.select_all(relation.arel, "SQL", async: async)
|
|
1426
|
+
end
|
|
923
1427
|
end
|
|
924
1428
|
end
|
|
925
1429
|
else
|
|
926
|
-
klass.
|
|
1430
|
+
klass.with_connection do |c|
|
|
1431
|
+
klass._query_by_sql(c, arel, async: async)
|
|
1432
|
+
end
|
|
927
1433
|
end
|
|
928
1434
|
end
|
|
929
1435
|
end
|