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,7 +3,49 @@
|
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
|
4
4
|
|
|
5
5
|
module ActiveRecord
|
|
6
|
+
# = Active Record \Calculations
|
|
6
7
|
module Calculations
|
|
8
|
+
class ColumnAliasTracker # :nodoc:
|
|
9
|
+
def initialize(connection)
|
|
10
|
+
@connection = connection
|
|
11
|
+
@aliases = Hash.new(0)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def alias_for(field)
|
|
15
|
+
aliased_name = column_alias_for(field)
|
|
16
|
+
|
|
17
|
+
if @aliases[aliased_name] == 0
|
|
18
|
+
@aliases[aliased_name] = 1
|
|
19
|
+
aliased_name
|
|
20
|
+
else
|
|
21
|
+
# Update the count
|
|
22
|
+
count = @aliases[aliased_name] += 1
|
|
23
|
+
"#{truncate(aliased_name)}_#{count}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
# Converts the given field to the value that the database adapter returns as
|
|
29
|
+
# a usable column name:
|
|
30
|
+
#
|
|
31
|
+
# column_alias_for("users.id") # => "users_id"
|
|
32
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
|
33
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
|
34
|
+
# column_alias_for("count(*)") # => "count_all"
|
|
35
|
+
def column_alias_for(field)
|
|
36
|
+
column_alias = +field
|
|
37
|
+
column_alias.gsub!(/\*/, "all")
|
|
38
|
+
column_alias.gsub!(/\W+/, " ")
|
|
39
|
+
column_alias.strip!
|
|
40
|
+
column_alias.gsub!(/ +/, "_")
|
|
41
|
+
@connection.table_alias_for(column_alias)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def truncate(name)
|
|
45
|
+
name.slice(0, @connection.table_alias_length - 2)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
7
49
|
# Count the records.
|
|
8
50
|
#
|
|
9
51
|
# Person.count
|
|
@@ -30,8 +72,7 @@ module ActiveRecord
|
|
|
30
72
|
# of each key would be the #count.
|
|
31
73
|
#
|
|
32
74
|
# Article.group(:status, :category).count
|
|
33
|
-
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
|
34
|
-
# # ["published", "business"]=>0, ["published", "technology"]=>2}
|
|
75
|
+
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
|
35
76
|
#
|
|
36
77
|
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
|
37
78
|
#
|
|
@@ -40,6 +81,16 @@ module ActiveRecord
|
|
|
40
81
|
#
|
|
41
82
|
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
|
42
83
|
# between databases. In invalid cases, an error from the database is thrown.
|
|
84
|
+
#
|
|
85
|
+
# When given a block, loads all records in the relation, if the relation
|
|
86
|
+
# hasn't been loaded yet. Calls the block with each record in the relation.
|
|
87
|
+
# Returns the number of records for which the block returns a truthy value.
|
|
88
|
+
#
|
|
89
|
+
# Person.count { |person| person.age > 21 }
|
|
90
|
+
# # => counts the number of people older that 21
|
|
91
|
+
#
|
|
92
|
+
# Note: If there are a lot of records in the relation, loading all records
|
|
93
|
+
# could result in performance issues.
|
|
43
94
|
def count(column_name = nil)
|
|
44
95
|
if block_given?
|
|
45
96
|
unless column_name.nil?
|
|
@@ -52,6 +103,12 @@ module ActiveRecord
|
|
|
52
103
|
end
|
|
53
104
|
end
|
|
54
105
|
|
|
106
|
+
# Same as #count, but performs the query asynchronously and returns an
|
|
107
|
+
# ActiveRecord::Promise.
|
|
108
|
+
def async_count(column_name = nil)
|
|
109
|
+
async.count(column_name)
|
|
110
|
+
end
|
|
111
|
+
|
|
55
112
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
|
56
113
|
# no row. See #calculate for examples with options.
|
|
57
114
|
#
|
|
@@ -60,6 +117,12 @@ module ActiveRecord
|
|
|
60
117
|
calculate(:average, column_name)
|
|
61
118
|
end
|
|
62
119
|
|
|
120
|
+
# Same as #average, but performs the query asynchronously and returns an
|
|
121
|
+
# ActiveRecord::Promise.
|
|
122
|
+
def async_average(column_name)
|
|
123
|
+
async.average(column_name)
|
|
124
|
+
end
|
|
125
|
+
|
|
63
126
|
# Calculates the minimum value on a given column. The value is returned
|
|
64
127
|
# with the same data type of the column, or +nil+ if there's no row. See
|
|
65
128
|
# #calculate for examples with options.
|
|
@@ -69,6 +132,12 @@ module ActiveRecord
|
|
|
69
132
|
calculate(:minimum, column_name)
|
|
70
133
|
end
|
|
71
134
|
|
|
135
|
+
# Same as #minimum, but performs the query asynchronously and returns an
|
|
136
|
+
# ActiveRecord::Promise.
|
|
137
|
+
def async_minimum(column_name)
|
|
138
|
+
async.minimum(column_name)
|
|
139
|
+
end
|
|
140
|
+
|
|
72
141
|
# Calculates the maximum value on a given column. The value is returned
|
|
73
142
|
# with the same data type of the column, or +nil+ if there's no row. See
|
|
74
143
|
# #calculate for examples with options.
|
|
@@ -78,32 +147,42 @@ module ActiveRecord
|
|
|
78
147
|
calculate(:maximum, column_name)
|
|
79
148
|
end
|
|
80
149
|
|
|
150
|
+
# Same as #maximum, but performs the query asynchronously and returns an
|
|
151
|
+
# ActiveRecord::Promise.
|
|
152
|
+
def async_maximum(column_name)
|
|
153
|
+
async.maximum(column_name)
|
|
154
|
+
end
|
|
155
|
+
|
|
81
156
|
# Calculates the sum of values on a given column. The value is returned
|
|
82
157
|
# with the same data type of the column, +0+ if there's no row. See
|
|
83
158
|
# #calculate for examples with options.
|
|
84
159
|
#
|
|
85
160
|
# Person.sum(:age) # => 4562
|
|
86
|
-
|
|
161
|
+
#
|
|
162
|
+
# When given a block, loads all records in the relation, if the relation
|
|
163
|
+
# hasn't been loaded yet. Calls the block with each record in the relation.
|
|
164
|
+
# Returns the sum of +initial_value_or_column+ and the block return
|
|
165
|
+
# values:
|
|
166
|
+
#
|
|
167
|
+
# Person.sum { |person| person.age } # => 4562
|
|
168
|
+
# Person.sum(1000) { |person| person.age } # => 5562
|
|
169
|
+
#
|
|
170
|
+
# Note: If there are a lot of records in the relation, loading all records
|
|
171
|
+
# could result in performance issues.
|
|
172
|
+
def sum(initial_value_or_column = 0, &block)
|
|
87
173
|
if block_given?
|
|
88
|
-
|
|
89
|
-
if identity_or_column.nil? && (values.first.is_a?(Numeric) || values.first(1) == [])
|
|
90
|
-
identity_or_column = 0
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
if identity_or_column.nil?
|
|
94
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
95
|
-
Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
|
|
96
|
-
Sum of non-numeric elements requires an initial argument.
|
|
97
|
-
MSG
|
|
98
|
-
values.inject(:+) || 0
|
|
99
|
-
else
|
|
100
|
-
values.sum(identity_or_column)
|
|
101
|
-
end
|
|
174
|
+
map(&block).sum(initial_value_or_column)
|
|
102
175
|
else
|
|
103
|
-
calculate(:sum,
|
|
176
|
+
calculate(:sum, initial_value_or_column)
|
|
104
177
|
end
|
|
105
178
|
end
|
|
106
179
|
|
|
180
|
+
# Same as #sum, but performs the query asynchronously and returns an
|
|
181
|
+
# ActiveRecord::Promise.
|
|
182
|
+
def async_sum(identity_or_column = nil)
|
|
183
|
+
async.sum(identity_or_column)
|
|
184
|
+
end
|
|
185
|
+
|
|
107
186
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
|
108
187
|
# #minimum, and #maximum have been added as shortcuts.
|
|
109
188
|
#
|
|
@@ -136,13 +215,26 @@ module ActiveRecord
|
|
|
136
215
|
# ...
|
|
137
216
|
# end
|
|
138
217
|
def calculate(operation, column_name)
|
|
218
|
+
operation = operation.to_s.downcase
|
|
219
|
+
|
|
220
|
+
if @none
|
|
221
|
+
case operation
|
|
222
|
+
when "count", "sum"
|
|
223
|
+
result = group_values.any? ? Hash.new : 0
|
|
224
|
+
return @async ? Promise::Complete.new(result) : result
|
|
225
|
+
when "average", "minimum", "maximum"
|
|
226
|
+
result = group_values.any? ? Hash.new : nil
|
|
227
|
+
return @async ? Promise::Complete.new(result) : result
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
139
231
|
if has_include?(column_name)
|
|
140
232
|
relation = apply_join_dependency
|
|
141
233
|
|
|
142
|
-
if operation
|
|
234
|
+
if operation == "count"
|
|
143
235
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
|
144
236
|
relation.distinct!
|
|
145
|
-
relation.select_values =
|
|
237
|
+
relation.select_values = Array(klass.primary_key || table[Arel.star])
|
|
146
238
|
end
|
|
147
239
|
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
|
148
240
|
relation.order_values = [] if group_values.empty?
|
|
@@ -155,7 +247,7 @@ module ActiveRecord
|
|
|
155
247
|
end
|
|
156
248
|
|
|
157
249
|
# Use #pluck as a shortcut to select one or more attributes without
|
|
158
|
-
# loading
|
|
250
|
+
# loading an entire record object per row.
|
|
159
251
|
#
|
|
160
252
|
# Person.pluck(:name)
|
|
161
253
|
#
|
|
@@ -183,36 +275,62 @@ module ActiveRecord
|
|
|
183
275
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
|
184
276
|
# # => [2, 3]
|
|
185
277
|
#
|
|
278
|
+
# Comment.joins(:person).pluck(:id, person: [:id])
|
|
279
|
+
# # SELECT comments.id, people.id FROM comments INNER JOIN people on comments.person_id = people.id
|
|
280
|
+
# # => [[1, 2], [2, 2]]
|
|
281
|
+
#
|
|
186
282
|
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
|
187
283
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
188
284
|
# # => ['0', '27761', '173']
|
|
189
285
|
#
|
|
190
286
|
# See also #ids.
|
|
191
|
-
#
|
|
192
287
|
def pluck(*column_names)
|
|
288
|
+
if @none
|
|
289
|
+
if @async
|
|
290
|
+
return Promise::Complete.new([])
|
|
291
|
+
else
|
|
292
|
+
return []
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
193
296
|
if loaded? && all_attributes?(column_names)
|
|
194
|
-
|
|
297
|
+
result = records.pluck(*column_names)
|
|
298
|
+
if @async
|
|
299
|
+
return Promise::Complete.new(result)
|
|
300
|
+
else
|
|
301
|
+
return result
|
|
302
|
+
end
|
|
195
303
|
end
|
|
196
304
|
|
|
197
305
|
if has_include?(column_names.first)
|
|
198
306
|
relation = apply_join_dependency
|
|
199
307
|
relation.pluck(*column_names)
|
|
200
308
|
else
|
|
201
|
-
klass.disallow_raw_sql!(column_names)
|
|
309
|
+
klass.disallow_raw_sql!(flattened_args(column_names))
|
|
202
310
|
columns = arel_columns(column_names)
|
|
203
311
|
relation = spawn
|
|
204
312
|
relation.select_values = columns
|
|
205
313
|
result = skip_query_cache_if_necessary do
|
|
206
314
|
if where_clause.contradiction?
|
|
207
|
-
ActiveRecord::Result.empty
|
|
315
|
+
ActiveRecord::Result.empty(async: @async)
|
|
208
316
|
else
|
|
209
|
-
klass.
|
|
317
|
+
klass.with_connection do |c|
|
|
318
|
+
c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
|
319
|
+
end
|
|
210
320
|
end
|
|
211
321
|
end
|
|
212
|
-
|
|
322
|
+
result.then do |result|
|
|
323
|
+
type_cast_pluck_values(result, columns)
|
|
324
|
+
end
|
|
213
325
|
end
|
|
214
326
|
end
|
|
215
327
|
|
|
328
|
+
# Same as #pluck, but performs the query asynchronously and returns an
|
|
329
|
+
# ActiveRecord::Promise.
|
|
330
|
+
def async_pluck(*column_names)
|
|
331
|
+
async.pluck(*column_names)
|
|
332
|
+
end
|
|
333
|
+
|
|
216
334
|
# Pick the value(s) from the named column(s) in the current relation.
|
|
217
335
|
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
|
218
336
|
# when you have a relation that's already narrowed down to a single row.
|
|
@@ -229,18 +347,63 @@ module ActiveRecord
|
|
|
229
347
|
# # => [ 'David', 'david@loudthinking.com' ]
|
|
230
348
|
def pick(*column_names)
|
|
231
349
|
if loaded? && all_attributes?(column_names)
|
|
232
|
-
|
|
350
|
+
result = records.pick(*column_names)
|
|
351
|
+
return @async ? Promise::Complete.new(result) : result
|
|
233
352
|
end
|
|
234
353
|
|
|
235
|
-
limit(1).pluck(*column_names).first
|
|
354
|
+
limit(1).pluck(*column_names).then(&:first)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Same as #pick, but performs the query asynchronously and returns an
|
|
358
|
+
# ActiveRecord::Promise.
|
|
359
|
+
def async_pick(*column_names)
|
|
360
|
+
async.pick(*column_names)
|
|
236
361
|
end
|
|
237
362
|
|
|
238
|
-
#
|
|
363
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
|
239
364
|
#
|
|
240
365
|
# Person.ids # SELECT people.id FROM people
|
|
241
|
-
# Person.joins(:
|
|
366
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
|
242
367
|
def ids
|
|
243
|
-
|
|
368
|
+
primary_key_array = Array(primary_key)
|
|
369
|
+
|
|
370
|
+
if loaded?
|
|
371
|
+
result = records.map do |record|
|
|
372
|
+
if primary_key_array.one?
|
|
373
|
+
record._read_attribute(primary_key_array.first)
|
|
374
|
+
else
|
|
375
|
+
primary_key_array.map { |column| record._read_attribute(column) }
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
return @async ? Promise::Complete.new(result) : result
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
if has_include?(primary_key)
|
|
382
|
+
relation = apply_join_dependency.group(*primary_key_array)
|
|
383
|
+
return relation.ids
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
columns = arel_columns(primary_key_array)
|
|
387
|
+
relation = spawn
|
|
388
|
+
relation.select_values = columns
|
|
389
|
+
|
|
390
|
+
result = if relation.where_clause.contradiction?
|
|
391
|
+
ActiveRecord::Result.empty
|
|
392
|
+
else
|
|
393
|
+
skip_query_cache_if_necessary do
|
|
394
|
+
klass.with_connection do |c|
|
|
395
|
+
c.select_all(relation, "#{klass.name} Ids", async: @async)
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
result.then { |result| type_cast_pluck_values(result, columns) }
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Same as #ids, but performs the query asynchronously and returns an
|
|
404
|
+
# ActiveRecord::Promise.
|
|
405
|
+
def async_ids
|
|
406
|
+
async.ids
|
|
244
407
|
end
|
|
245
408
|
|
|
246
409
|
private
|
|
@@ -287,7 +450,7 @@ module ActiveRecord
|
|
|
287
450
|
return column_name if Arel::Expressions === column_name
|
|
288
451
|
|
|
289
452
|
arel_column(column_name.to_s) do |name|
|
|
290
|
-
|
|
453
|
+
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
|
291
454
|
end
|
|
292
455
|
end
|
|
293
456
|
|
|
@@ -296,10 +459,11 @@ module ActiveRecord
|
|
|
296
459
|
end
|
|
297
460
|
|
|
298
461
|
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
|
299
|
-
if operation
|
|
462
|
+
if build_count_subquery?(operation, column_name, distinct)
|
|
300
463
|
# Shortcut when limit is zero.
|
|
301
464
|
return 0 if limit_value == 0
|
|
302
465
|
|
|
466
|
+
relation = self
|
|
303
467
|
query_builder = build_count_subquery(spawn, column_name, distinct)
|
|
304
468
|
else
|
|
305
469
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
@@ -314,15 +478,25 @@ module ActiveRecord
|
|
|
314
478
|
query_builder = relation.arel
|
|
315
479
|
end
|
|
316
480
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
481
|
+
query_result = if relation.where_clause.contradiction?
|
|
482
|
+
ActiveRecord::Result.empty
|
|
483
|
+
else
|
|
484
|
+
skip_query_cache_if_necessary do
|
|
485
|
+
@klass.with_connection do |c|
|
|
486
|
+
c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
|
487
|
+
end
|
|
488
|
+
end
|
|
323
489
|
end
|
|
324
490
|
|
|
325
|
-
|
|
491
|
+
query_result.then do |result|
|
|
492
|
+
if operation != "count"
|
|
493
|
+
type = column.try(:type_caster) ||
|
|
494
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
495
|
+
type = type.subtype if Enum::EnumType === type
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
type_cast_calculated_value(result.cast_values.first, operation, type)
|
|
499
|
+
end
|
|
326
500
|
end
|
|
327
501
|
|
|
328
502
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
@@ -336,82 +510,75 @@ module ActiveRecord
|
|
|
336
510
|
end
|
|
337
511
|
group_fields = arel_columns(group_fields)
|
|
338
512
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
column_alias_for(field.to_s.downcase)
|
|
342
|
-
}
|
|
343
|
-
group_columns = group_aliases.zip(group_fields)
|
|
513
|
+
@klass.with_connection do |connection|
|
|
514
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
344
515
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
516
|
+
group_aliases = group_fields.map { |field|
|
|
517
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
|
518
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
|
519
|
+
}
|
|
520
|
+
group_columns = group_aliases.zip(group_fields)
|
|
349
521
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if field.respond_to?(:as)
|
|
355
|
-
field.as(aliaz)
|
|
356
|
-
else
|
|
357
|
-
"#{field} AS #{aliaz}"
|
|
358
|
-
end
|
|
359
|
-
}
|
|
522
|
+
column = aggregate_column(column_name)
|
|
523
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
524
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
525
|
+
select_value.as(adapter_class.quote_column_name(column_alias))
|
|
360
526
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
relation.select_values = select_values
|
|
527
|
+
select_values = [select_value]
|
|
528
|
+
select_values += self.select_values unless having_clause.empty?
|
|
364
529
|
|
|
365
|
-
|
|
530
|
+
select_values.concat group_columns.map { |aliaz, field|
|
|
531
|
+
aliaz = adapter_class.quote_column_name(aliaz)
|
|
532
|
+
if field.respond_to?(:as)
|
|
533
|
+
field.as(aliaz)
|
|
534
|
+
else
|
|
535
|
+
"#{field} AS #{aliaz}"
|
|
536
|
+
end
|
|
537
|
+
}
|
|
366
538
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
key_records = key_records.index_by(&:id)
|
|
371
|
-
end
|
|
539
|
+
relation = except(:group).distinct!(false)
|
|
540
|
+
relation.group_values = group_fields
|
|
541
|
+
relation.select_values = select_values
|
|
372
542
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
|
543
|
+
result = skip_query_cache_if_necessary do
|
|
544
|
+
connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
|
|
376
545
|
end
|
|
377
|
-
end
|
|
378
546
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
547
|
+
result.then do |calculated_data|
|
|
548
|
+
if association
|
|
549
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
|
550
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
|
551
|
+
key_records = key_records.index_by(&:id)
|
|
552
|
+
end
|
|
384
553
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
554
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
|
555
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
|
556
|
+
type_for(col_name) do
|
|
557
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
|
558
|
+
end
|
|
559
|
+
end
|
|
390
560
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
561
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
|
562
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
|
563
|
+
hash[col_name] = row[i]
|
|
564
|
+
end
|
|
565
|
+
end
|
|
395
566
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
567
|
+
if operation != "count"
|
|
568
|
+
type = column.try(:type_caster) ||
|
|
569
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
570
|
+
type = type.subtype if Enum::EnumType === type
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
hash_rows.each_with_object({}) do |row, result|
|
|
574
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
|
575
|
+
key = key.first if key.size == 1
|
|
576
|
+
key = key_records[key] if associated
|
|
399
577
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
# column_alias_for("sum(id)") # => "sum_id"
|
|
405
|
-
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
|
406
|
-
# column_alias_for("count(*)") # => "count_all"
|
|
407
|
-
def column_alias_for(field)
|
|
408
|
-
column_alias = +field
|
|
409
|
-
column_alias.gsub!(/\*/, "all")
|
|
410
|
-
column_alias.gsub!(/\W+/, " ")
|
|
411
|
-
column_alias.strip!
|
|
412
|
-
column_alias.gsub!(/ +/, "_")
|
|
413
|
-
|
|
414
|
-
connection.table_alias_for(column_alias)
|
|
578
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
end
|
|
415
582
|
end
|
|
416
583
|
|
|
417
584
|
def type_for(field, &block)
|
|
@@ -437,7 +604,7 @@ module ActiveRecord
|
|
|
437
604
|
klass.attribute_types.fetch(name = result.columns[i]) do
|
|
438
605
|
join_dependencies ||= build_join_dependencies
|
|
439
606
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
|
440
|
-
result.column_types[
|
|
607
|
+
result.column_types[i] || Type.default_value
|
|
441
608
|
end
|
|
442
609
|
end
|
|
443
610
|
end
|
|
@@ -465,12 +632,30 @@ module ActiveRecord
|
|
|
465
632
|
def select_for_count
|
|
466
633
|
if select_values.present?
|
|
467
634
|
return select_values.first if select_values.one?
|
|
468
|
-
|
|
635
|
+
|
|
636
|
+
select_values.map do |field|
|
|
637
|
+
column = arel_column(field.to_s) do |attr_name|
|
|
638
|
+
Arel.sql(attr_name)
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
if column.is_a?(Arel::Nodes::SqlLiteral)
|
|
642
|
+
column
|
|
643
|
+
else
|
|
644
|
+
"#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
|
|
645
|
+
end
|
|
646
|
+
end.join(", ")
|
|
469
647
|
else
|
|
470
648
|
:all
|
|
471
649
|
end
|
|
472
650
|
end
|
|
473
651
|
|
|
652
|
+
def build_count_subquery?(operation, column_name, distinct)
|
|
653
|
+
# SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
|
|
654
|
+
# multiple columns, so we need to use subquery for this.
|
|
655
|
+
operation == "count" &&
|
|
656
|
+
(((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
|
|
657
|
+
end
|
|
658
|
+
|
|
474
659
|
def build_count_subquery(relation, column_name, distinct)
|
|
475
660
|
if column_name == :all
|
|
476
661
|
column_alias = Arel.star
|
|
@@ -480,7 +665,7 @@ module ActiveRecord
|
|
|
480
665
|
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
481
666
|
end
|
|
482
667
|
|
|
483
|
-
subquery_alias = Arel.sql("subquery_for_count")
|
|
668
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
484
669
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
485
670
|
|
|
486
671
|
relation.build_subquery(subquery_alias, select_value)
|