activerecord 7.1.6 → 7.2.3
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 +839 -2248
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +15 -8
- 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/belongs_to.rb +1 -0
- 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/collection_association.rb +16 -8
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +59 -292
- 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 +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +5 -25
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods.rb +51 -60
- data/lib/active_record/attributes.rb +93 -68
- data/lib/active_record/autosave_association.rb +25 -32
- data/lib/active_record/base.rb +4 -5
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +294 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +46 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +19 -18
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +30 -8
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +36 -26
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +133 -78
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +68 -49
- data/lib/active_record/core.rb +112 -44
- data/lib/active_record/counter_cache.rb +19 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +42 -18
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +25 -5
- data/lib/active_record/encryption/encryptor.rb +35 -19
- 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/enum.rb +31 -13
- data/lib/active_record/errors.rb +49 -23
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -3
- 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 +87 -77
- data/lib/active_record/model_schema.rb +31 -68
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +19 -0
- data/lib/active_record/querying.rb +25 -13
- data/lib/active_record/railtie.rb +39 -57
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -44
- data/lib/active_record/reflection.rb +98 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +127 -89
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +26 -12
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +238 -65
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +15 -21
- data/lib/active_record/relation.rb +508 -74
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +48 -20
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +27 -7
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +69 -41
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +8 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +86 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +73 -15
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +3 -1
- 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/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +7 -3
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +31 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +16 -10
|
@@ -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(klass.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,6 +274,10 @@ 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, people.id FROM comments INNER JOIN people on comments.person_id = people.id
|
|
279
|
+
# # => [[1, 2], [2, 2]]
|
|
280
|
+
#
|
|
278
281
|
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
|
279
282
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
280
283
|
# # => ['0', '27761', '173']
|
|
@@ -302,15 +305,17 @@ module ActiveRecord
|
|
|
302
305
|
relation = apply_join_dependency
|
|
303
306
|
relation.pluck(*column_names)
|
|
304
307
|
else
|
|
305
|
-
klass.disallow_raw_sql!(column_names
|
|
306
|
-
columns = arel_columns(column_names)
|
|
308
|
+
klass.disallow_raw_sql!(flattened_args(column_names))
|
|
307
309
|
relation = spawn
|
|
310
|
+
columns = relation.arel_columns(column_names)
|
|
308
311
|
relation.select_values = columns
|
|
309
312
|
result = skip_query_cache_if_necessary do
|
|
310
313
|
if where_clause.contradiction?
|
|
311
314
|
ActiveRecord::Result.empty(async: @async)
|
|
312
315
|
else
|
|
313
|
-
klass.
|
|
316
|
+
klass.with_connection do |c|
|
|
317
|
+
c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
|
318
|
+
end
|
|
314
319
|
end
|
|
315
320
|
end
|
|
316
321
|
result.then do |result|
|
|
@@ -357,7 +362,7 @@ module ActiveRecord
|
|
|
357
362
|
# Returns the base model's ID's for the relation using the table's primary key
|
|
358
363
|
#
|
|
359
364
|
# Person.ids # SELECT people.id FROM people
|
|
360
|
-
# Person.joins(:
|
|
365
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
|
361
366
|
def ids
|
|
362
367
|
primary_key_array = Array(primary_key)
|
|
363
368
|
|
|
@@ -385,7 +390,9 @@ module ActiveRecord
|
|
|
385
390
|
ActiveRecord::Result.empty
|
|
386
391
|
else
|
|
387
392
|
skip_query_cache_if_necessary do
|
|
388
|
-
klass.
|
|
393
|
+
klass.with_connection do |c|
|
|
394
|
+
c.select_all(relation, "#{klass.name} Ids", async: @async)
|
|
395
|
+
end
|
|
389
396
|
end
|
|
390
397
|
end
|
|
391
398
|
|
|
@@ -398,6 +405,15 @@ module ActiveRecord
|
|
|
398
405
|
async.ids
|
|
399
406
|
end
|
|
400
407
|
|
|
408
|
+
protected
|
|
409
|
+
def aggregate_column(column_name)
|
|
410
|
+
return column_name if Arel::Expressions === column_name
|
|
411
|
+
|
|
412
|
+
arel_column(column_name.to_s) do |name|
|
|
413
|
+
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
401
417
|
private
|
|
402
418
|
def all_attributes?(column_names)
|
|
403
419
|
(column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
|
|
@@ -438,20 +454,12 @@ module ActiveRecord
|
|
|
438
454
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
439
455
|
end
|
|
440
456
|
|
|
441
|
-
def aggregate_column(column_name)
|
|
442
|
-
return column_name if Arel::Expressions === column_name
|
|
443
|
-
|
|
444
|
-
arel_column(column_name.to_s) do |name|
|
|
445
|
-
Arel.sql(column_name == :all ? "*" : name)
|
|
446
|
-
end
|
|
447
|
-
end
|
|
448
|
-
|
|
449
457
|
def operation_over_aggregate_column(column, operation, distinct)
|
|
450
458
|
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
|
451
459
|
end
|
|
452
460
|
|
|
453
461
|
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
|
454
|
-
if operation
|
|
462
|
+
if build_count_subquery?(operation, column_name, distinct)
|
|
455
463
|
# Shortcut when limit is zero.
|
|
456
464
|
return 0 if limit_value == 0
|
|
457
465
|
|
|
@@ -461,7 +469,7 @@ module ActiveRecord
|
|
|
461
469
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
462
470
|
relation = unscope(:order).distinct!(false)
|
|
463
471
|
|
|
464
|
-
column = aggregate_column(column_name)
|
|
472
|
+
column = relation.aggregate_column(column_name)
|
|
465
473
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
466
474
|
select_value.distinct = true if operation == "sum" && distinct
|
|
467
475
|
|
|
@@ -471,10 +479,16 @@ module ActiveRecord
|
|
|
471
479
|
end
|
|
472
480
|
|
|
473
481
|
query_result = if relation.where_clause.contradiction?
|
|
474
|
-
|
|
482
|
+
if @async
|
|
483
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
|
484
|
+
else
|
|
485
|
+
ActiveRecord::Result.empty
|
|
486
|
+
end
|
|
475
487
|
else
|
|
476
488
|
skip_query_cache_if_necessary do
|
|
477
|
-
@klass.
|
|
489
|
+
@klass.with_connection do |c|
|
|
490
|
+
c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
|
491
|
+
end
|
|
478
492
|
end
|
|
479
493
|
end
|
|
480
494
|
|
|
@@ -498,70 +512,76 @@ module ActiveRecord
|
|
|
498
512
|
associated = association && association.belongs_to? # only count belongs_to associations
|
|
499
513
|
group_fields = Array(association.foreign_key) if associated
|
|
500
514
|
end
|
|
501
|
-
group_fields = arel_columns(group_fields)
|
|
502
515
|
|
|
503
|
-
|
|
516
|
+
relation = except(:group).distinct!(false)
|
|
517
|
+
group_fields = relation.arel_columns(group_fields)
|
|
504
518
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
column_alias_tracker.alias_for(field.to_s.downcase)
|
|
508
|
-
}
|
|
509
|
-
group_columns = group_aliases.zip(group_fields)
|
|
519
|
+
@klass.with_connection do |connection|
|
|
520
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
510
521
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
522
|
+
group_aliases = group_fields.map { |field|
|
|
523
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
|
524
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
|
525
|
+
}
|
|
526
|
+
group_columns = group_aliases.zip(group_fields)
|
|
515
527
|
|
|
516
|
-
|
|
517
|
-
|
|
528
|
+
column = relation.aggregate_column(column_name)
|
|
529
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
530
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
531
|
+
select_value.as(adapter_class.quote_column_name(column_alias))
|
|
518
532
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
if field.respond_to?(:as)
|
|
522
|
-
field.as(aliaz)
|
|
523
|
-
else
|
|
524
|
-
"#{field} AS #{aliaz}"
|
|
525
|
-
end
|
|
526
|
-
}
|
|
533
|
+
select_values = [select_value]
|
|
534
|
+
select_values += self.select_values unless having_clause.empty?
|
|
527
535
|
|
|
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
|
|
536
|
+
select_values.concat group_columns.map { |aliaz, field|
|
|
537
|
+
aliaz = adapter_class.quote_column_name(aliaz)
|
|
538
|
+
if field.respond_to?(:as)
|
|
539
|
+
field.as(aliaz)
|
|
540
|
+
else
|
|
541
|
+
"#{field} AS #{aliaz}"
|
|
542
|
+
end
|
|
543
|
+
}
|
|
539
544
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
+
relation.group_values = group_fields
|
|
546
|
+
relation.select_values = select_values
|
|
547
|
+
|
|
548
|
+
result = skip_query_cache_if_necessary do
|
|
549
|
+
connection.select_all(relation.arel, "#{@klass.name} #{operation.capitalize}", async: @async)
|
|
545
550
|
end
|
|
546
551
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
552
|
+
result.then do |calculated_data|
|
|
553
|
+
if association
|
|
554
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
|
555
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
|
556
|
+
key_records = key_records.index_by(&:id)
|
|
550
557
|
end
|
|
551
|
-
end
|
|
552
558
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
559
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
|
560
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
|
561
|
+
type_for(col_name) do
|
|
562
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
|
563
|
+
end
|
|
564
|
+
end
|
|
558
565
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
566
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
|
567
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
|
568
|
+
hash[col_name] = row[i]
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
if operation != "count"
|
|
573
|
+
type = column.try(:type_caster) ||
|
|
574
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
575
|
+
type = type.subtype if Enum::EnumType === type
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
hash_rows.each_with_object({}) do |row, result|
|
|
579
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
|
580
|
+
key = key.first if key.size == 1
|
|
581
|
+
key = key_records[key] if associated
|
|
563
582
|
|
|
564
|
-
|
|
583
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
|
584
|
+
end
|
|
565
585
|
end
|
|
566
586
|
end
|
|
567
587
|
end
|
|
@@ -589,7 +609,7 @@ module ActiveRecord
|
|
|
589
609
|
klass.attribute_types.fetch(name = result.columns[i]) do
|
|
590
610
|
join_dependencies ||= build_join_dependencies
|
|
591
611
|
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
|
592
|
-
result.column_types[
|
|
612
|
+
result.column_types[i] || Type.default_value
|
|
593
613
|
end
|
|
594
614
|
end
|
|
595
615
|
end
|
|
@@ -617,22 +637,40 @@ module ActiveRecord
|
|
|
617
637
|
def select_for_count
|
|
618
638
|
if select_values.present?
|
|
619
639
|
return select_values.first if select_values.one?
|
|
620
|
-
|
|
640
|
+
|
|
641
|
+
select_values.map do |field|
|
|
642
|
+
column = arel_column(field.to_s) do |attr_name|
|
|
643
|
+
Arel.sql(attr_name)
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
if column.is_a?(Arel::Nodes::SqlLiteral)
|
|
647
|
+
column
|
|
648
|
+
else
|
|
649
|
+
"#{adapter_class.quote_table_name(column.relation.name)}.#{adapter_class.quote_column_name(column.name)}"
|
|
650
|
+
end
|
|
651
|
+
end.join(", ")
|
|
621
652
|
else
|
|
622
653
|
:all
|
|
623
654
|
end
|
|
624
655
|
end
|
|
625
656
|
|
|
657
|
+
def build_count_subquery?(operation, column_name, distinct)
|
|
658
|
+
# SQLite and older MySQL does not support `COUNT DISTINCT` with `*` or
|
|
659
|
+
# multiple columns, so we need to use subquery for this.
|
|
660
|
+
operation == "count" &&
|
|
661
|
+
(((column_name == :all || select_values.many?) && distinct) || has_limit_or_offset?)
|
|
662
|
+
end
|
|
663
|
+
|
|
626
664
|
def build_count_subquery(relation, column_name, distinct)
|
|
627
665
|
if column_name == :all
|
|
628
666
|
column_alias = Arel.star
|
|
629
667
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
630
668
|
else
|
|
631
669
|
column_alias = Arel.sql("count_column")
|
|
632
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
670
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
633
671
|
end
|
|
634
672
|
|
|
635
|
-
subquery_alias = Arel.sql("subquery_for_count")
|
|
673
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
636
674
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
637
675
|
|
|
638
676
|
relation.build_subquery(subquery_alias, select_value)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "mutex_m"
|
|
4
3
|
require "active_support/core_ext/module/delegation"
|
|
5
4
|
|
|
6
5
|
module ActiveRecord
|
|
@@ -67,23 +66,22 @@ module ActiveRecord
|
|
|
67
66
|
end
|
|
68
67
|
|
|
69
68
|
class GeneratedRelationMethods < Module # :nodoc:
|
|
70
|
-
|
|
69
|
+
MUTEX = Mutex.new
|
|
71
70
|
|
|
72
71
|
def generate_method(method)
|
|
73
|
-
synchronize do
|
|
72
|
+
MUTEX.synchronize do
|
|
74
73
|
return if method_defined?(method)
|
|
75
74
|
|
|
76
|
-
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) &&
|
|
75
|
+
if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) && !::ActiveSupport::Delegation::RESERVED_METHOD_NAMES.include?(method.to_s)
|
|
77
76
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
78
77
|
def #{method}(...)
|
|
79
78
|
scoping { klass.#{method}(...) }
|
|
80
79
|
end
|
|
81
80
|
RUBY
|
|
82
81
|
else
|
|
83
|
-
define_method(method) do |*args, &block|
|
|
84
|
-
scoping { klass.public_send(method, *args, &block) }
|
|
82
|
+
define_method(method) do |*args, **kwargs, &block|
|
|
83
|
+
scoping { klass.public_send(method, *args, **kwargs, &block) }
|
|
85
84
|
end
|
|
86
|
-
ruby2_keywords(method)
|
|
87
85
|
end
|
|
88
86
|
end
|
|
89
87
|
end
|
|
@@ -102,7 +100,7 @@ module ActiveRecord
|
|
|
102
100
|
:to_sentence, :to_fs, :to_formatted_s, :as_json,
|
|
103
101
|
:shuffle, :split, :slice, :index, :rindex, to: :records
|
|
104
102
|
|
|
105
|
-
delegate :primary_key, :connection, :transaction, to: :klass
|
|
103
|
+
delegate :primary_key, :lease_connection, :connection, :with_connection, :transaction, to: :klass
|
|
106
104
|
|
|
107
105
|
module ClassSpecificRelation # :nodoc:
|
|
108
106
|
extend ActiveSupport::Concern
|
|
@@ -114,17 +112,16 @@ module ActiveRecord
|
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
private
|
|
117
|
-
def method_missing(method,
|
|
115
|
+
def method_missing(method, ...)
|
|
118
116
|
if @klass.respond_to?(method)
|
|
119
117
|
unless Delegation.uncacheable_methods.include?(method)
|
|
120
118
|
@klass.generate_relation_method(method)
|
|
121
119
|
end
|
|
122
|
-
scoping { @klass.public_send(method,
|
|
120
|
+
scoping { @klass.public_send(method, ...) }
|
|
123
121
|
else
|
|
124
122
|
super
|
|
125
123
|
end
|
|
126
124
|
end
|
|
127
|
-
ruby2_keywords(:method_missing)
|
|
128
125
|
end
|
|
129
126
|
|
|
130
127
|
module ClassMethods # :nodoc:
|
|
@@ -24,22 +24,22 @@ module ActiveRecord
|
|
|
24
24
|
# TravelRoute.primary_key = [:origin, :destination]
|
|
25
25
|
#
|
|
26
26
|
# TravelRoute.find(["Ottawa", "London"])
|
|
27
|
-
# => #<TravelRoute origin: "Ottawa", destination: "London">
|
|
27
|
+
# # => #<TravelRoute origin: "Ottawa", destination: "London">
|
|
28
28
|
#
|
|
29
29
|
# TravelRoute.find([["Paris", "Montreal"]])
|
|
30
|
-
# => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
|
30
|
+
# # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
|
31
31
|
#
|
|
32
32
|
# TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
|
|
33
|
-
# => [
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
33
|
+
# # => [
|
|
34
|
+
# # #<TravelRoute origin: "New York", destination: "Las Vegas">,
|
|
35
|
+
# # #<TravelRoute origin: "New York", destination: "Portland">
|
|
36
|
+
# # ]
|
|
37
37
|
#
|
|
38
38
|
# TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
|
|
39
|
-
# => [
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
39
|
+
# # => [
|
|
40
|
+
# # #<TravelRoute origin: "Berlin", destination: "London">,
|
|
41
|
+
# # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
|
|
42
|
+
# # ]
|
|
43
43
|
#
|
|
44
44
|
# NOTE: The returned records are in the same order as the ids you provide.
|
|
45
45
|
# If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
|
|
@@ -87,6 +87,14 @@ module ActiveRecord
|
|
|
87
87
|
#
|
|
88
88
|
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
|
|
89
89
|
# # returns an Array of the required fields.
|
|
90
|
+
#
|
|
91
|
+
# ==== Edge Cases
|
|
92
|
+
#
|
|
93
|
+
# Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
|
|
94
|
+
# Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
|
|
95
|
+
# Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
|
|
96
|
+
# Person.find([]) # returns an empty array if the argument is an empty array.
|
|
97
|
+
# Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
|
|
90
98
|
def find(*args)
|
|
91
99
|
return super if block_given?
|
|
92
100
|
find_with_ids(*args)
|
|
@@ -366,7 +374,11 @@ module ActiveRecord
|
|
|
366
374
|
relation = construct_relation_for_exists(conditions)
|
|
367
375
|
return false if relation.where_clause.contradiction?
|
|
368
376
|
|
|
369
|
-
skip_query_cache_if_necessary
|
|
377
|
+
skip_query_cache_if_necessary do
|
|
378
|
+
with_connection do |c|
|
|
379
|
+
c.select_rows(relation.arel, "#{name} Exists?").size == 1
|
|
380
|
+
end
|
|
381
|
+
end
|
|
370
382
|
end
|
|
371
383
|
|
|
372
384
|
# Returns true if the relation contains the given record or false otherwise.
|
|
@@ -460,7 +472,9 @@ module ActiveRecord
|
|
|
460
472
|
)
|
|
461
473
|
)
|
|
462
474
|
relation = skip_query_cache_if_necessary do
|
|
463
|
-
klass.
|
|
475
|
+
klass.with_connection do |c|
|
|
476
|
+
c.distinct_relation_for_primary_key(relation)
|
|
477
|
+
end
|
|
464
478
|
end
|
|
465
479
|
end
|
|
466
480
|
|
|
@@ -7,16 +7,15 @@ module ActiveRecord
|
|
|
7
7
|
class HashMerger # :nodoc:
|
|
8
8
|
attr_reader :relation, :hash
|
|
9
9
|
|
|
10
|
-
def initialize(relation, hash
|
|
10
|
+
def initialize(relation, hash)
|
|
11
11
|
hash.assert_valid_keys(*Relation::VALUE_METHODS)
|
|
12
12
|
|
|
13
13
|
@relation = relation
|
|
14
14
|
@hash = hash
|
|
15
|
-
@rewhere = rewhere
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def merge
|
|
19
|
-
Merger.new(relation, other
|
|
18
|
+
Merger.new(relation, other).merge
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
# Applying values to a relation has some side effects. E.g.
|
|
@@ -44,11 +43,10 @@ module ActiveRecord
|
|
|
44
43
|
class Merger # :nodoc:
|
|
45
44
|
attr_reader :relation, :values, :other
|
|
46
45
|
|
|
47
|
-
def initialize(relation, other
|
|
46
|
+
def initialize(relation, other)
|
|
48
47
|
@relation = relation
|
|
49
48
|
@values = other.values
|
|
50
49
|
@other = other
|
|
51
|
-
@rewhere = rewhere
|
|
52
50
|
end
|
|
53
51
|
|
|
54
52
|
NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
|
|
@@ -178,7 +176,7 @@ module ActiveRecord
|
|
|
178
176
|
def merge_clauses
|
|
179
177
|
relation.from_clause = other.from_clause if replace_from_clause?
|
|
180
178
|
|
|
181
|
-
where_clause = relation.where_clause.merge(other.where_clause
|
|
179
|
+
where_clause = relation.where_clause.merge(other.where_clause)
|
|
182
180
|
relation.where_clause = where_clause unless where_clause.empty?
|
|
183
181
|
|
|
184
182
|
having_clause = relation.having_clause.merge(other.having_clause)
|
|
@@ -13,7 +13,7 @@ module ActiveRecord
|
|
|
13
13
|
return attribute.in([]) if value.empty?
|
|
14
14
|
|
|
15
15
|
values = value.map { |x| x.is_a?(Base) ? x.id : x }
|
|
16
|
-
nils = values.
|
|
16
|
+
nils = values.compact!
|
|
17
17
|
ranges = values.extract! { |v| v.is_a?(Range) }
|
|
18
18
|
|
|
19
19
|
values_predicate =
|
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
|
23
23
|
else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
if nils
|
|
27
27
|
values_predicate = values_predicate.or(attribute.eq(nil))
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -57,9 +57,17 @@ module ActiveRecord
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def convert_to_id(value)
|
|
60
|
-
|
|
60
|
+
if primary_key.is_a?(Array)
|
|
61
|
+
primary_key.map do |attribute|
|
|
62
|
+
next nil if value.nil?
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
if attribute == "id"
|
|
65
|
+
value.id_value
|
|
66
|
+
else
|
|
67
|
+
value.public_send(attribute)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
elsif value.respond_to?(primary_key)
|
|
63
71
|
value.public_send(primary_key)
|
|
64
72
|
else
|
|
65
73
|
value
|
|
@@ -28,9 +28,9 @@ module ActiveRecord
|
|
|
28
28
|
def self.references(attributes)
|
|
29
29
|
attributes.each_with_object([]) do |(key, value), result|
|
|
30
30
|
if value.is_a?(Hash)
|
|
31
|
-
result << Arel.sql(key)
|
|
31
|
+
result << Arel.sql(key, retryable: true)
|
|
32
32
|
elsif (idx = key.rindex("."))
|
|
33
|
-
result << Arel.sql(key[0, idx])
|
|
33
|
+
result << Arel.sql(key[0, idx], retryable: true)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -142,7 +142,7 @@ module ActiveRecord
|
|
|
142
142
|
queries.first
|
|
143
143
|
else
|
|
144
144
|
queries.map! { |query| query.reduce(&:and) }
|
|
145
|
-
queries =
|
|
145
|
+
queries = Arel::Nodes::Or.new(queries)
|
|
146
146
|
Arel::Nodes::Grouping.new(queries)
|
|
147
147
|
end
|
|
148
148
|
end
|
|
@@ -35,7 +35,7 @@ module ActiveRecord
|
|
|
35
35
|
def nil?
|
|
36
36
|
unless value_before_type_cast.is_a?(StatementCache::Substitute)
|
|
37
37
|
value_before_type_cast.nil? ||
|
|
38
|
-
type.respond_to?(:subtype) && serializable? && value_for_database.nil?
|
|
38
|
+
(type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|