activerecord 7.1.5.2 → 8.1.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 +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- 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 +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -60,37 +60,37 @@ module ActiveRecord
|
|
|
60
60
|
# Person.distinct.count(:age)
|
|
61
61
|
# # => counts the number of different age values
|
|
62
62
|
#
|
|
63
|
-
# If
|
|
63
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
|
64
64
|
# it returns a Hash whose keys represent the aggregated column,
|
|
65
65
|
# and the values are the respective amounts:
|
|
66
66
|
#
|
|
67
67
|
# Person.group(:city).count
|
|
68
68
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
|
69
69
|
#
|
|
70
|
-
# If
|
|
70
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
|
71
71
|
# keys are an array containing the individual values of each column and the value
|
|
72
|
-
# of each key would be the
|
|
72
|
+
# of each key would be the count.
|
|
73
73
|
#
|
|
74
74
|
# Article.group(:status, :category).count
|
|
75
75
|
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
|
76
76
|
#
|
|
77
|
-
# If
|
|
77
|
+
# If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
|
78
78
|
#
|
|
79
79
|
# Person.select(:age).count
|
|
80
80
|
# # => counts the number of different age values
|
|
81
81
|
#
|
|
82
|
-
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid
|
|
82
|
+
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid +count+ expressions. The specifics differ
|
|
83
83
|
# between databases. In invalid cases, an error from the database is thrown.
|
|
84
84
|
#
|
|
85
|
-
# When given a block,
|
|
86
|
-
#
|
|
87
|
-
# Returns the number of records for which the block returns a truthy value.
|
|
85
|
+
# When given a block, calls the block with each record in the relation and
|
|
86
|
+
# returns the number of records for which the block returns a truthy value.
|
|
88
87
|
#
|
|
89
88
|
# Person.count { |person| person.age > 21 }
|
|
90
89
|
# # => counts the number of people older that 21
|
|
91
90
|
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
91
|
+
# If the relation hasn't been loaded yet, calling +count+ with a block will
|
|
92
|
+
# load all records in the relation. If there are a lot of records in the
|
|
93
|
+
# relation, loading all records could result in performance issues.
|
|
94
94
|
def count(column_name = nil)
|
|
95
95
|
if block_given?
|
|
96
96
|
unless column_name.nil?
|
|
@@ -159,16 +159,15 @@ module ActiveRecord
|
|
|
159
159
|
#
|
|
160
160
|
# Person.sum(:age) # => 4562
|
|
161
161
|
#
|
|
162
|
-
# When given a block,
|
|
163
|
-
#
|
|
164
|
-
# Returns the sum of +initial_value_or_column+ and the block return
|
|
165
|
-
# values:
|
|
162
|
+
# When given a block, calls the block with each record in the relation and
|
|
163
|
+
# returns the sum of +initial_value_or_column+ plus the block return values:
|
|
166
164
|
#
|
|
167
165
|
# Person.sum { |person| person.age } # => 4562
|
|
168
166
|
# Person.sum(1000) { |person| person.age } # => 5562
|
|
169
167
|
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
168
|
+
# If the relation hasn't been loaded yet, calling +sum+ with a block will
|
|
169
|
+
# load all records in the relation. If there are a lot of records in the
|
|
170
|
+
# relation, loading all records could result in performance issues.
|
|
172
171
|
def sum(initial_value_or_column = 0, &block)
|
|
173
172
|
if block_given?
|
|
174
173
|
map(&block).sum(initial_value_or_column)
|
|
@@ -234,7 +233,7 @@ module ActiveRecord
|
|
|
234
233
|
if operation == "count"
|
|
235
234
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
|
236
235
|
relation.distinct!
|
|
237
|
-
relation.select_values =
|
|
236
|
+
relation.select_values = Array(model.primary_key || table[Arel.star])
|
|
238
237
|
end
|
|
239
238
|
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
|
240
239
|
relation.order_values = [] if group_values.empty?
|
|
@@ -275,10 +274,23 @@ module ActiveRecord
|
|
|
275
274
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
|
276
275
|
# # => [2, 3]
|
|
277
276
|
#
|
|
277
|
+
# Comment.joins(:person).pluck(:id, person: :id)
|
|
278
|
+
# # SELECT comments.id, person.id FROM comments INNER JOIN people person ON person.id = comments.person_id
|
|
279
|
+
# # => [[1, 2], [2, 2]]
|
|
280
|
+
#
|
|
281
|
+
# Comment.joins(:person).pluck(:id, person: [:id, :name])
|
|
282
|
+
# # SELECT comments.id, person.id, person.name FROM comments INNER JOIN people person ON person.id = comments.person_id
|
|
283
|
+
# # => [[1, 2, 'David'], [2, 2, 'David']]
|
|
284
|
+
#
|
|
278
285
|
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
|
279
286
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
280
287
|
# # => ['0', '27761', '173']
|
|
281
288
|
#
|
|
289
|
+
# Be aware that #pluck ignores any previous select clauses
|
|
290
|
+
#
|
|
291
|
+
# Person.select(:name).pluck(:id)
|
|
292
|
+
# # SELECT people.id FROM people
|
|
293
|
+
#
|
|
282
294
|
# See also #ids.
|
|
283
295
|
def pluck(*column_names)
|
|
284
296
|
if @none
|
|
@@ -302,15 +314,17 @@ module ActiveRecord
|
|
|
302
314
|
relation = apply_join_dependency
|
|
303
315
|
relation.pluck(*column_names)
|
|
304
316
|
else
|
|
305
|
-
|
|
306
|
-
columns = arel_columns(column_names)
|
|
317
|
+
model.disallow_raw_sql!(flattened_args(column_names))
|
|
307
318
|
relation = spawn
|
|
319
|
+
columns = relation.arel_columns(column_names)
|
|
308
320
|
relation.select_values = columns
|
|
309
321
|
result = skip_query_cache_if_necessary do
|
|
310
|
-
if where_clause.contradiction?
|
|
322
|
+
if where_clause.contradiction? && !possible_aggregation?(column_names)
|
|
311
323
|
ActiveRecord::Result.empty(async: @async)
|
|
312
324
|
else
|
|
313
|
-
|
|
325
|
+
model.with_connection do |c|
|
|
326
|
+
c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
|
|
327
|
+
end
|
|
314
328
|
end
|
|
315
329
|
end
|
|
316
330
|
result.then do |result|
|
|
@@ -357,7 +371,7 @@ module ActiveRecord
|
|
|
357
371
|
# Returns the base model's ID's for the relation using the table's primary key
|
|
358
372
|
#
|
|
359
373
|
# Person.ids # SELECT people.id FROM people
|
|
360
|
-
# Person.joins(:
|
|
374
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
|
361
375
|
def ids
|
|
362
376
|
primary_key_array = Array(primary_key)
|
|
363
377
|
|
|
@@ -385,7 +399,9 @@ module ActiveRecord
|
|
|
385
399
|
ActiveRecord::Result.empty
|
|
386
400
|
else
|
|
387
401
|
skip_query_cache_if_necessary do
|
|
388
|
-
|
|
402
|
+
model.with_connection do |c|
|
|
403
|
+
c.select_all(relation, "#{model.name} Ids", async: @async)
|
|
404
|
+
end
|
|
389
405
|
end
|
|
390
406
|
end
|
|
391
407
|
|
|
@@ -398,9 +414,21 @@ module ActiveRecord
|
|
|
398
414
|
async.ids
|
|
399
415
|
end
|
|
400
416
|
|
|
417
|
+
protected
|
|
418
|
+
def aggregate_column(column_name)
|
|
419
|
+
case column_name
|
|
420
|
+
when Arel::Expressions
|
|
421
|
+
column_name
|
|
422
|
+
when :all
|
|
423
|
+
Arel.star
|
|
424
|
+
else
|
|
425
|
+
arel_column(column_name.to_s)
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
401
429
|
private
|
|
402
430
|
def all_attributes?(column_names)
|
|
403
|
-
(column_names.map(&:to_s) -
|
|
431
|
+
(column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
|
|
404
432
|
end
|
|
405
433
|
|
|
406
434
|
def has_include?(column_name)
|
|
@@ -438,11 +466,13 @@ module ActiveRecord
|
|
|
438
466
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
439
467
|
end
|
|
440
468
|
|
|
441
|
-
def
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
469
|
+
def possible_aggregation?(column_names)
|
|
470
|
+
column_names.all? do |column_name|
|
|
471
|
+
if column_name.is_a?(String)
|
|
472
|
+
column_name.include?("(")
|
|
473
|
+
else
|
|
474
|
+
Arel.arel_node?(column_name)
|
|
475
|
+
end
|
|
446
476
|
end
|
|
447
477
|
end
|
|
448
478
|
|
|
@@ -451,7 +481,7 @@ module ActiveRecord
|
|
|
451
481
|
end
|
|
452
482
|
|
|
453
483
|
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
|
454
|
-
if operation
|
|
484
|
+
if build_count_subquery?(operation, column_name, distinct)
|
|
455
485
|
# Shortcut when limit is zero.
|
|
456
486
|
return 0 if limit_value == 0
|
|
457
487
|
|
|
@@ -461,7 +491,7 @@ module ActiveRecord
|
|
|
461
491
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
462
492
|
relation = unscope(:order).distinct!(false)
|
|
463
493
|
|
|
464
|
-
column = aggregate_column(column_name)
|
|
494
|
+
column = relation.aggregate_column(column_name)
|
|
465
495
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
466
496
|
select_value.distinct = true if operation == "sum" && distinct
|
|
467
497
|
|
|
@@ -471,10 +501,16 @@ module ActiveRecord
|
|
|
471
501
|
end
|
|
472
502
|
|
|
473
503
|
query_result = if relation.where_clause.contradiction?
|
|
474
|
-
|
|
504
|
+
if @async
|
|
505
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
|
506
|
+
else
|
|
507
|
+
ActiveRecord::Result.empty
|
|
508
|
+
end
|
|
475
509
|
else
|
|
476
510
|
skip_query_cache_if_necessary do
|
|
477
|
-
|
|
511
|
+
model.with_connection do |c|
|
|
512
|
+
c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
|
|
513
|
+
end
|
|
478
514
|
end
|
|
479
515
|
end
|
|
480
516
|
|
|
@@ -491,84 +527,89 @@ module ActiveRecord
|
|
|
491
527
|
|
|
492
528
|
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
493
529
|
group_fields = group_values
|
|
494
|
-
group_fields = group_fields.uniq if group_fields.size > 1
|
|
495
530
|
|
|
496
531
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
|
497
|
-
association =
|
|
532
|
+
association = model._reflect_on_association(group_fields.first)
|
|
498
533
|
associated = association && association.belongs_to? # only count belongs_to associations
|
|
499
534
|
group_fields = Array(association.foreign_key) if associated
|
|
500
535
|
end
|
|
501
|
-
group_fields = arel_columns(group_fields)
|
|
502
536
|
|
|
503
|
-
|
|
537
|
+
relation = except(:group).distinct!(false)
|
|
538
|
+
group_fields = relation.arel_columns(group_fields)
|
|
504
539
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
column_alias_tracker.alias_for(field.to_s.downcase)
|
|
508
|
-
}
|
|
509
|
-
group_columns = group_aliases.zip(group_fields)
|
|
540
|
+
model.with_connection do |connection|
|
|
541
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
510
542
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
543
|
+
group_aliases = group_fields.map { |field|
|
|
544
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
|
545
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
|
546
|
+
}
|
|
547
|
+
group_columns = group_aliases.zip(group_fields)
|
|
515
548
|
|
|
516
|
-
|
|
517
|
-
|
|
549
|
+
column = relation.aggregate_column(column_name)
|
|
550
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
551
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
552
|
+
select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
518
553
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if field.respond_to?(:as)
|
|
522
|
-
field.as(aliaz)
|
|
523
|
-
else
|
|
524
|
-
"#{field} AS #{aliaz}"
|
|
525
|
-
end
|
|
526
|
-
}
|
|
554
|
+
select_values = [select_value]
|
|
555
|
+
select_values += self.select_values unless having_clause.empty?
|
|
527
556
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
|
537
|
-
key_records = key_records.index_by(&:id)
|
|
538
|
-
end
|
|
557
|
+
select_values.concat group_columns.map { |aliaz, field|
|
|
558
|
+
aliaz = model.adapter_class.quote_column_name(aliaz)
|
|
559
|
+
if field.respond_to?(:as)
|
|
560
|
+
field.as(aliaz)
|
|
561
|
+
else
|
|
562
|
+
"#{field} AS #{aliaz}"
|
|
563
|
+
end
|
|
564
|
+
}
|
|
539
565
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
566
|
+
relation.group_values = group_fields
|
|
567
|
+
relation.select_values = select_values
|
|
568
|
+
|
|
569
|
+
result = skip_query_cache_if_necessary do
|
|
570
|
+
connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
|
|
545
571
|
end
|
|
546
572
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
573
|
+
result.then do |calculated_data|
|
|
574
|
+
if association
|
|
575
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
|
576
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
|
577
|
+
key_records = key_records.index_by(&:id)
|
|
550
578
|
end
|
|
551
|
-
end
|
|
552
579
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
580
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
|
581
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
|
582
|
+
type_for(col_name) do
|
|
583
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
|
584
|
+
end
|
|
585
|
+
end
|
|
558
586
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
587
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
|
588
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
|
589
|
+
hash[col_name] = row[i]
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
if operation != "count"
|
|
594
|
+
type = column.try(:type_caster) ||
|
|
595
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
596
|
+
type = type.subtype if Enum::EnumType === type
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
hash_rows.each_with_object({}) do |row, result|
|
|
600
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
|
601
|
+
key = key.first if key.size == 1
|
|
602
|
+
key = key_records[key] if associated
|
|
563
603
|
|
|
564
|
-
|
|
604
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
|
605
|
+
end
|
|
565
606
|
end
|
|
566
607
|
end
|
|
567
608
|
end
|
|
568
609
|
|
|
569
610
|
def type_for(field, &block)
|
|
570
611
|
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
|
571
|
-
|
|
612
|
+
model.type_for_attribute(field_name, &block)
|
|
572
613
|
end
|
|
573
614
|
|
|
574
615
|
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
|
|
@@ -581,15 +622,15 @@ module ActiveRecord
|
|
|
581
622
|
|
|
582
623
|
def type_cast_pluck_values(result, columns)
|
|
583
624
|
cast_types = if result.columns.size != columns.size
|
|
584
|
-
|
|
625
|
+
model.attribute_types
|
|
585
626
|
else
|
|
586
627
|
join_dependencies = nil
|
|
587
628
|
columns.map.with_index do |column, i|
|
|
588
629
|
column.try(:type_caster) ||
|
|
589
|
-
|
|
630
|
+
model.attribute_types.fetch(name = result.columns[i]) do
|
|
590
631
|
join_dependencies ||= build_join_dependencies
|
|
591
632
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
|
592
|
-
result.column_types[
|
|
633
|
+
result.column_types[i] || Type.default_value
|
|
593
634
|
end
|
|
594
635
|
end
|
|
595
636
|
end
|
|
@@ -615,24 +656,33 @@ module ActiveRecord
|
|
|
615
656
|
end
|
|
616
657
|
|
|
617
658
|
def select_for_count
|
|
618
|
-
if select_values.
|
|
619
|
-
return select_values.first if select_values.one?
|
|
620
|
-
select_values.join(", ")
|
|
621
|
-
else
|
|
659
|
+
if select_values.empty?
|
|
622
660
|
:all
|
|
661
|
+
else
|
|
662
|
+
with_connection do |conn|
|
|
663
|
+
arel_columns(select_values).map { |column| conn.visitor.compile(column) }.join(", ")
|
|
664
|
+
end
|
|
623
665
|
end
|
|
624
666
|
end
|
|
625
667
|
|
|
668
|
+
def build_count_subquery?(operation, column_name, distinct)
|
|
669
|
+
# SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
|
|
670
|
+
# multiple columns, so we need to use subquery for this.
|
|
671
|
+
operation == "count" &&
|
|
672
|
+
(((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
|
|
673
|
+
end
|
|
674
|
+
|
|
626
675
|
def build_count_subquery(relation, column_name, distinct)
|
|
627
676
|
if column_name == :all
|
|
628
677
|
column_alias = Arel.star
|
|
629
678
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
679
|
+
relation.unscope!(:order)
|
|
630
680
|
else
|
|
631
681
|
column_alias = Arel.sql("count_column")
|
|
632
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
682
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
633
683
|
end
|
|
634
684
|
|
|
635
|
-
subquery_alias = Arel.sql("subquery_for_count")
|
|
685
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
636
686
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
637
687
|
|
|
638
688
|
relation.build_subquery(subquery_alias, select_value)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "mutex_m"
|
|
4
|
-
require "active_support/core_ext/module/delegation"
|
|
5
3
|
|
|
6
4
|
module ActiveRecord
|
|
7
5
|
module Delegation # :nodoc:
|
|
@@ -23,6 +21,9 @@ module ActiveRecord
|
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
module DelegateCache # :nodoc:
|
|
24
|
+
@delegate_base_methods = true
|
|
25
|
+
singleton_class.attr_accessor :delegate_base_methods
|
|
26
|
+
|
|
26
27
|
def relation_delegate_class(klass)
|
|
27
28
|
@relation_delegate_cache[klass]
|
|
28
29
|
end
|
|
@@ -67,23 +68,22 @@ module ActiveRecord
|
|
|
67
68
|
end
|
|
68
69
|
|
|
69
70
|
class GeneratedRelationMethods < Module # :nodoc:
|
|
70
|
-
|
|
71
|
+
MUTEX = Mutex.new
|
|
71
72
|
|
|
72
73
|
def generate_method(method)
|
|
73
|
-
synchronize do
|
|
74
|
+
MUTEX.synchronize do
|
|
74
75
|
return if method_defined?(method)
|
|
75
76
|
|
|
76
|
-
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) &&
|
|
77
|
+
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
|
77
78
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
78
79
|
def #{method}(...)
|
|
79
|
-
scoping {
|
|
80
|
+
scoping { model.#{method}(...) }
|
|
80
81
|
end
|
|
81
82
|
RUBY
|
|
82
83
|
else
|
|
83
|
-
define_method(method) do |*args, &block|
|
|
84
|
-
scoping {
|
|
84
|
+
define_method(method) do |*args, **kwargs, &block|
|
|
85
|
+
scoping { model.public_send(method, *args, **kwargs, &block) }
|
|
85
86
|
end
|
|
86
|
-
ruby2_keywords(method)
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
end
|
|
@@ -94,15 +94,15 @@ module ActiveRecord
|
|
|
94
94
|
|
|
95
95
|
# This module creates compiled delegation methods dynamically at runtime, which makes
|
|
96
96
|
# subsequent calls to that method faster by avoiding method_missing. The delegations
|
|
97
|
-
# may vary depending on the
|
|
98
|
-
# for each different
|
|
97
|
+
# may vary depending on the model of a relation, so we create a subclass of Relation
|
|
98
|
+
# for each different model, and the delegations are compiled into that subclass only.
|
|
99
99
|
|
|
100
100
|
delegate :to_xml, :encode_with, :length, :each, :join, :intersect?,
|
|
101
101
|
:[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
|
|
102
102
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
|
103
103
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
|
104
104
|
|
|
105
|
-
delegate :primary_key, :connection, :transaction, to: :
|
|
105
|
+
delegate :primary_key, :with_connection, :connection, :table_name, :transaction, :sanitize_sql_like, :unscoped, :name, to: :model
|
|
106
106
|
|
|
107
107
|
module ClassSpecificRelation # :nodoc:
|
|
108
108
|
extend ActiveSupport::Concern
|
|
@@ -114,33 +114,40 @@ module ActiveRecord
|
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
private
|
|
117
|
-
def method_missing(method,
|
|
118
|
-
if
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
def method_missing(method, ...)
|
|
118
|
+
if model.respond_to?(method)
|
|
119
|
+
if !DelegateCache.delegate_base_methods && Base.respond_to?(method)
|
|
120
|
+
# A common mistake in Active Record's own code is to call `ActiveRecord::Base`
|
|
121
|
+
# class methods on Association. It works because it's automatically delegated, but
|
|
122
|
+
# can introduce subtle bugs because it sets the global scope.
|
|
123
|
+
# We can't deprecate this behavior because gems might depend on it, however we
|
|
124
|
+
# can ban it from Active Record's own test suite to avoid regressions.
|
|
125
|
+
raise NotImplementedError, "Active Record code shouldn't rely on association delegation into ActiveRecord::Base methods"
|
|
126
|
+
elsif !Delegation.uncacheable_methods.include?(method)
|
|
127
|
+
model.generate_relation_method(method)
|
|
121
128
|
end
|
|
122
|
-
|
|
129
|
+
|
|
130
|
+
scoping { model.public_send(method, ...) }
|
|
123
131
|
else
|
|
124
132
|
super
|
|
125
133
|
end
|
|
126
134
|
end
|
|
127
|
-
ruby2_keywords(:method_missing)
|
|
128
135
|
end
|
|
129
136
|
|
|
130
137
|
module ClassMethods # :nodoc:
|
|
131
|
-
def create(
|
|
132
|
-
relation_class_for(
|
|
138
|
+
def create(model, ...)
|
|
139
|
+
relation_class_for(model).new(model, ...)
|
|
133
140
|
end
|
|
134
141
|
|
|
135
142
|
private
|
|
136
|
-
def relation_class_for(
|
|
137
|
-
|
|
143
|
+
def relation_class_for(model)
|
|
144
|
+
model.relation_delegate_class(self)
|
|
138
145
|
end
|
|
139
146
|
end
|
|
140
147
|
|
|
141
148
|
private
|
|
142
149
|
def respond_to_missing?(method, _)
|
|
143
|
-
super ||
|
|
150
|
+
super || model.respond_to?(method)
|
|
144
151
|
end
|
|
145
152
|
end
|
|
146
153
|
end
|