activerecord 6.0.0 → 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 +996 -594
- data/MIT-LICENSE +1 -1
- data/README.rdoc +34 -34
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +22 -20
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +41 -30
- data/lib/active_record/associations/association.rb +106 -41
- data/lib/active_record/associations/association_scope.rb +30 -21
- data/lib/active_record/associations/belongs_to_association.rb +69 -14
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
- data/lib/active_record/associations/builder/association.rb +39 -6
- data/lib/active_record/associations/builder/belongs_to.rb +47 -17
- data/lib/active_record/associations/builder/collection_association.rb +14 -6
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
- data/lib/active_record/associations/builder/has_many.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +13 -16
- data/lib/active_record/associations/builder/singular_association.rb +7 -3
- data/lib/active_record/associations/collection_association.rb +90 -53
- data/lib/active_record/associations/collection_proxy.rb +54 -19
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +21 -1
- data/lib/active_record/associations/has_many_association.rb +41 -10
- data/lib/active_record/associations/has_many_through_association.rb +29 -12
- data/lib/active_record/associations/has_one_association.rb +33 -9
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/join_dependency.rb +97 -54
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +237 -54
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +51 -17
- data/lib/active_record/associations/preloader.rb +55 -121
- data/lib/active_record/associations/singular_association.rb +16 -4
- data/lib/active_record/associations/through_association.rb +26 -15
- data/lib/active_record/associations.rb +454 -440
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +11 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +75 -34
- data/lib/active_record/attribute_methods/primary_key.rb +53 -31
- data/lib/active_record/attribute_methods/query.rb +31 -22
- data/lib/active_record/attribute_methods/read.rb +16 -17
- data/lib/active_record/attribute_methods/serialization.rb +177 -35
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
- data/lib/active_record/attribute_methods/write.rb +16 -28
- data/lib/active_record/attribute_methods.rb +227 -100
- data/lib/active_record/attributes.rb +94 -56
- data/lib/active_record/autosave_association.rb +119 -73
- data/lib/active_record/base.rb +31 -21
- data/lib/active_record/callbacks.rb +168 -55
- 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 -25
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
- data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
- data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
- data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
- data/lib/active_record/connection_adapters/column.rb +28 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
- data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
- 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 +102 -24
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
- 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 +176 -0
- data/lib/active_record/connection_handling.rb +243 -115
- data/lib/active_record/core.rb +481 -199
- data/lib/active_record/counter_cache.rb +69 -32
- data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
- data/lib/active_record/database_configurations/database_config.rb +77 -10
- data/lib/active_record/database_configurations/hash_config.rb +148 -26
- data/lib/active_record/database_configurations/url_config.rb +44 -45
- data/lib/active_record/database_configurations.rb +190 -114
- data/lib/active_record/delegated_type.rb +279 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +5 -6
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +171 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +224 -73
- data/lib/active_record/errors.rb +254 -36
- data/lib/active_record/explain.rb +30 -17
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +22 -15
- data/lib/active_record/fixture_set/model_metadata.rb +15 -6
- data/lib/active_record/fixture_set/render_context.rb +3 -1
- data/lib/active_record/fixture_set/table_row.rb +88 -16
- data/lib/active_record/fixture_set/table_rows.rb +4 -5
- data/lib/active_record/fixtures.rb +229 -116
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +121 -48
- data/lib/active_record/insert_all.rb +178 -29
- data/lib/active_record/integration.rb +16 -14
- data/lib/active_record/internal_metadata.rb +132 -21
- data/lib/active_record/legacy_yaml_adapter.rb +3 -36
- data/lib/active_record/locking/optimistic.rb +64 -33
- data/lib/active_record/locking/pessimistic.rb +21 -8
- data/lib/active_record/log_subscriber.rb +61 -30
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
- data/lib/active_record/middleware/database_selector.rb +25 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +160 -55
- data/lib/active_record/migration/compatibility.rb +286 -43
- 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/join_table.rb +1 -2
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +421 -193
- data/lib/active_record/model_schema.rb +217 -125
- data/lib/active_record/nested_attributes.rb +62 -27
- data/lib/active_record/no_touching.rb +4 -4
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +322 -319
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -15
- data/lib/active_record/query_logs.rb +193 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +54 -14
- data/lib/active_record/railtie.rb +250 -72
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +312 -197
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +45 -3
- data/lib/active_record/reflection.rb +389 -146
- data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
- data/lib/active_record/relation/batches.rb +214 -73
- data/lib/active_record/relation/calculations.rb +379 -124
- data/lib/active_record/relation/delegation.rb +36 -23
- data/lib/active_record/relation/finder_methods.rb +159 -49
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +41 -33
- data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +79 -53
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +1156 -279
- data/lib/active_record/relation/record_fetch_warning.rb +12 -11
- data/lib/active_record/relation/spawn_methods.rb +10 -9
- data/lib/active_record/relation/where_clause.rb +100 -66
- data/lib/active_record/relation.rb +829 -194
- data/lib/active_record/result.rb +76 -56
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +86 -47
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +140 -33
- data/lib/active_record/schema_migration.rb +74 -29
- data/lib/active_record/scoping/default.rb +73 -19
- data/lib/active_record/scoping/named.rb +10 -28
- data/lib/active_record/scoping.rb +65 -35
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +34 -8
- data/lib/active_record/serialization.rb +11 -4
- data/lib/active_record/signed_id.rb +138 -0
- data/lib/active_record/statement_cache.rb +26 -10
- data/lib/active_record/store.rb +19 -14
- data/lib/active_record/suppressor.rb +15 -17
- data/lib/active_record/table_metadata.rb +46 -36
- data/lib/active_record/tasks/database_tasks.rb +371 -205
- data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
- data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +189 -104
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +35 -25
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +31 -27
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +131 -99
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +33 -18
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +11 -6
- data/lib/active_record/type/time.rb +14 -0
- data/lib/active_record/type/type_map.rb +17 -21
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +7 -2
- data/lib/active_record/type_caster/connection.rb +4 -5
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -8
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +88 -18
- data/lib/active_record/validations.rb +15 -8
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +446 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +4 -8
- data/lib/arel/collectors/bind.rb +8 -1
- data/lib/arel/collectors/composite.rb +15 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/crud.rb +30 -22
- data/lib/arel/delete_manager.rb +23 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +82 -9
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +22 -10
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +14 -13
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/join_source.rb +1 -1
- 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 +122 -11
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/table_alias.rb +11 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes/update_statement.rb +11 -4
- data/lib/arel/nodes.rb +10 -3
- data/lib/arel/predications.rb +31 -28
- data/lib/arel/select_manager.rb +18 -9
- data/lib/arel/table.rb +21 -10
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +25 -5
- data/lib/arel/visitors/dot.rb +94 -90
- data/lib/arel/visitors/mysql.rb +34 -6
- data/lib/arel/visitors/postgresql.rb +5 -16
- data/lib/arel/visitors/sqlite.rb +25 -1
- data/lib/arel/visitors/to_sql.rb +227 -81
- data/lib/arel/visitors/visitor.rb +2 -3
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +37 -15
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
- data/lib/rails/generators/active_record/migration.rb +9 -3
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +117 -30
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/null_relation.rb +0 -68
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -204
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -157
- data/lib/arel/visitors/oracle.rb +0 -159
- data/lib/arel/visitors/oracle12.rb +0 -66
- data/lib/arel/visitors/where_sql.rb +0 -23
|
@@ -1,7 +1,51 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
|
4
|
+
|
|
3
5
|
module ActiveRecord
|
|
6
|
+
# = Active Record \Calculations
|
|
4
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
|
+
|
|
5
49
|
# Count the records.
|
|
6
50
|
#
|
|
7
51
|
# Person.count
|
|
@@ -16,28 +60,37 @@ module ActiveRecord
|
|
|
16
60
|
# Person.distinct.count(:age)
|
|
17
61
|
# # => counts the number of different age values
|
|
18
62
|
#
|
|
19
|
-
# If
|
|
63
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
|
20
64
|
# it returns a Hash whose keys represent the aggregated column,
|
|
21
65
|
# and the values are the respective amounts:
|
|
22
66
|
#
|
|
23
67
|
# Person.group(:city).count
|
|
24
68
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
|
25
69
|
#
|
|
26
|
-
# If
|
|
70
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
|
27
71
|
# keys are an array containing the individual values of each column and the value
|
|
28
|
-
# of each key would be the
|
|
72
|
+
# of each key would be the count.
|
|
29
73
|
#
|
|
30
74
|
# Article.group(:status, :category).count
|
|
31
|
-
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4,
|
|
32
|
-
# ["published", "business"]=>0, ["published", "technology"]=>2}
|
|
75
|
+
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
|
33
76
|
#
|
|
34
|
-
# If
|
|
77
|
+
# If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
|
35
78
|
#
|
|
36
79
|
# Person.select(:age).count
|
|
37
80
|
# # => counts the number of different age values
|
|
38
81
|
#
|
|
39
|
-
# 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
|
|
40
83
|
# between databases. In invalid cases, an error from the database is thrown.
|
|
84
|
+
#
|
|
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.
|
|
87
|
+
#
|
|
88
|
+
# Person.count { |person| person.age > 21 }
|
|
89
|
+
# # => counts the number of people older that 21
|
|
90
|
+
#
|
|
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.
|
|
41
94
|
def count(column_name = nil)
|
|
42
95
|
if block_given?
|
|
43
96
|
unless column_name.nil?
|
|
@@ -50,6 +103,12 @@ module ActiveRecord
|
|
|
50
103
|
end
|
|
51
104
|
end
|
|
52
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
|
+
|
|
53
112
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
|
54
113
|
# no row. See #calculate for examples with options.
|
|
55
114
|
#
|
|
@@ -58,6 +117,12 @@ module ActiveRecord
|
|
|
58
117
|
calculate(:average, column_name)
|
|
59
118
|
end
|
|
60
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
|
+
|
|
61
126
|
# Calculates the minimum value on a given column. The value is returned
|
|
62
127
|
# with the same data type of the column, or +nil+ if there's no row. See
|
|
63
128
|
# #calculate for examples with options.
|
|
@@ -67,6 +132,12 @@ module ActiveRecord
|
|
|
67
132
|
calculate(:minimum, column_name)
|
|
68
133
|
end
|
|
69
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
|
+
|
|
70
141
|
# Calculates the maximum value on a given column. The value is returned
|
|
71
142
|
# with the same data type of the column, or +nil+ if there's no row. See
|
|
72
143
|
# #calculate for examples with options.
|
|
@@ -76,23 +147,41 @@ module ActiveRecord
|
|
|
76
147
|
calculate(:maximum, column_name)
|
|
77
148
|
end
|
|
78
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
|
+
|
|
79
156
|
# Calculates the sum of values on a given column. The value is returned
|
|
80
157
|
# with the same data type of the column, +0+ if there's no row. See
|
|
81
158
|
# #calculate for examples with options.
|
|
82
159
|
#
|
|
83
160
|
# Person.sum(:age) # => 4562
|
|
84
|
-
|
|
161
|
+
#
|
|
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:
|
|
164
|
+
#
|
|
165
|
+
# Person.sum { |person| person.age } # => 4562
|
|
166
|
+
# Person.sum(1000) { |person| person.age } # => 5562
|
|
167
|
+
#
|
|
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.
|
|
171
|
+
def sum(initial_value_or_column = 0, &block)
|
|
85
172
|
if block_given?
|
|
86
|
-
|
|
87
|
-
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
super()
|
|
173
|
+
map(&block).sum(initial_value_or_column)
|
|
91
174
|
else
|
|
92
|
-
calculate(:sum,
|
|
175
|
+
calculate(:sum, initial_value_or_column)
|
|
93
176
|
end
|
|
94
177
|
end
|
|
95
178
|
|
|
179
|
+
# Same as #sum, but performs the query asynchronously and returns an
|
|
180
|
+
# ActiveRecord::Promise.
|
|
181
|
+
def async_sum(identity_or_column = nil)
|
|
182
|
+
async.sum(identity_or_column)
|
|
183
|
+
end
|
|
184
|
+
|
|
96
185
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
|
97
186
|
# #minimum, and #maximum have been added as shortcuts.
|
|
98
187
|
#
|
|
@@ -125,16 +214,29 @@ module ActiveRecord
|
|
|
125
214
|
# ...
|
|
126
215
|
# end
|
|
127
216
|
def calculate(operation, column_name)
|
|
217
|
+
operation = operation.to_s.downcase
|
|
218
|
+
|
|
219
|
+
if @none
|
|
220
|
+
case operation
|
|
221
|
+
when "count", "sum"
|
|
222
|
+
result = group_values.any? ? Hash.new : 0
|
|
223
|
+
return @async ? Promise::Complete.new(result) : result
|
|
224
|
+
when "average", "minimum", "maximum"
|
|
225
|
+
result = group_values.any? ? Hash.new : nil
|
|
226
|
+
return @async ? Promise::Complete.new(result) : result
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
128
230
|
if has_include?(column_name)
|
|
129
231
|
relation = apply_join_dependency
|
|
130
232
|
|
|
131
|
-
if operation
|
|
233
|
+
if operation == "count"
|
|
132
234
|
unless distinct_value || distinct_select?(column_name || select_for_count)
|
|
133
235
|
relation.distinct!
|
|
134
|
-
relation.select_values =
|
|
236
|
+
relation.select_values = Array(klass.primary_key || table[Arel.star])
|
|
135
237
|
end
|
|
136
238
|
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
|
137
|
-
relation.order_values = []
|
|
239
|
+
relation.order_values = [] if group_values.empty?
|
|
138
240
|
end
|
|
139
241
|
|
|
140
242
|
relation.calculate(operation, column_name)
|
|
@@ -144,7 +246,7 @@ module ActiveRecord
|
|
|
144
246
|
end
|
|
145
247
|
|
|
146
248
|
# Use #pluck as a shortcut to select one or more attributes without
|
|
147
|
-
# loading
|
|
249
|
+
# loading an entire record object per row.
|
|
148
250
|
#
|
|
149
251
|
# Person.pluck(:name)
|
|
150
252
|
#
|
|
@@ -172,29 +274,62 @@ module ActiveRecord
|
|
|
172
274
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
|
173
275
|
# # => [2, 3]
|
|
174
276
|
#
|
|
175
|
-
#
|
|
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
|
+
#
|
|
281
|
+
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
|
176
282
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
177
283
|
# # => ['0', '27761', '173']
|
|
178
284
|
#
|
|
179
285
|
# See also #ids.
|
|
180
|
-
#
|
|
181
286
|
def pluck(*column_names)
|
|
182
|
-
if
|
|
183
|
-
|
|
287
|
+
if @none
|
|
288
|
+
if @async
|
|
289
|
+
return Promise::Complete.new([])
|
|
290
|
+
else
|
|
291
|
+
return []
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
if loaded? && all_attributes?(column_names)
|
|
296
|
+
result = records.pluck(*column_names)
|
|
297
|
+
if @async
|
|
298
|
+
return Promise::Complete.new(result)
|
|
299
|
+
else
|
|
300
|
+
return result
|
|
301
|
+
end
|
|
184
302
|
end
|
|
185
303
|
|
|
186
304
|
if has_include?(column_names.first)
|
|
187
305
|
relation = apply_join_dependency
|
|
188
306
|
relation.pluck(*column_names)
|
|
189
307
|
else
|
|
190
|
-
klass.disallow_raw_sql!(column_names)
|
|
308
|
+
klass.disallow_raw_sql!(flattened_args(column_names))
|
|
191
309
|
relation = spawn
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
result
|
|
310
|
+
columns = relation.arel_columns(column_names)
|
|
311
|
+
relation.select_values = columns
|
|
312
|
+
result = skip_query_cache_if_necessary do
|
|
313
|
+
if where_clause.contradiction?
|
|
314
|
+
ActiveRecord::Result.empty(async: @async)
|
|
315
|
+
else
|
|
316
|
+
klass.with_connection do |c|
|
|
317
|
+
c.select_all(relation.arel, "#{klass.name} Pluck", async: @async)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
result.then do |result|
|
|
322
|
+
type_cast_pluck_values(result, columns)
|
|
323
|
+
end
|
|
195
324
|
end
|
|
196
325
|
end
|
|
197
326
|
|
|
327
|
+
# Same as #pluck, but performs the query asynchronously and returns an
|
|
328
|
+
# ActiveRecord::Promise.
|
|
329
|
+
def async_pluck(*column_names)
|
|
330
|
+
async.pluck(*column_names)
|
|
331
|
+
end
|
|
332
|
+
|
|
198
333
|
# Pick the value(s) from the named column(s) in the current relation.
|
|
199
334
|
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
|
200
335
|
# when you have a relation that's already narrowed down to a single row.
|
|
@@ -210,18 +345,80 @@ module ActiveRecord
|
|
|
210
345
|
# # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
|
|
211
346
|
# # => [ 'David', 'david@loudthinking.com' ]
|
|
212
347
|
def pick(*column_names)
|
|
213
|
-
|
|
348
|
+
if loaded? && all_attributes?(column_names)
|
|
349
|
+
result = records.pick(*column_names)
|
|
350
|
+
return @async ? Promise::Complete.new(result) : result
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
limit(1).pluck(*column_names).then(&:first)
|
|
214
354
|
end
|
|
215
355
|
|
|
216
|
-
#
|
|
356
|
+
# Same as #pick, but performs the query asynchronously and returns an
|
|
357
|
+
# ActiveRecord::Promise.
|
|
358
|
+
def async_pick(*column_names)
|
|
359
|
+
async.pick(*column_names)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
|
217
363
|
#
|
|
218
364
|
# Person.ids # SELECT people.id FROM people
|
|
219
|
-
# Person.joins(:
|
|
365
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
|
220
366
|
def ids
|
|
221
|
-
|
|
367
|
+
primary_key_array = Array(primary_key)
|
|
368
|
+
|
|
369
|
+
if loaded?
|
|
370
|
+
result = records.map do |record|
|
|
371
|
+
if primary_key_array.one?
|
|
372
|
+
record._read_attribute(primary_key_array.first)
|
|
373
|
+
else
|
|
374
|
+
primary_key_array.map { |column| record._read_attribute(column) }
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
return @async ? Promise::Complete.new(result) : result
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
if has_include?(primary_key)
|
|
381
|
+
relation = apply_join_dependency.group(*primary_key_array)
|
|
382
|
+
return relation.ids
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
columns = arel_columns(primary_key_array)
|
|
386
|
+
relation = spawn
|
|
387
|
+
relation.select_values = columns
|
|
388
|
+
|
|
389
|
+
result = if relation.where_clause.contradiction?
|
|
390
|
+
ActiveRecord::Result.empty
|
|
391
|
+
else
|
|
392
|
+
skip_query_cache_if_necessary do
|
|
393
|
+
klass.with_connection do |c|
|
|
394
|
+
c.select_all(relation, "#{klass.name} Ids", async: @async)
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
result.then { |result| type_cast_pluck_values(result, columns) }
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Same as #ids, but performs the query asynchronously and returns an
|
|
403
|
+
# ActiveRecord::Promise.
|
|
404
|
+
def async_ids
|
|
405
|
+
async.ids
|
|
222
406
|
end
|
|
223
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
|
+
|
|
224
417
|
private
|
|
418
|
+
def all_attributes?(column_names)
|
|
419
|
+
(column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
|
|
420
|
+
end
|
|
421
|
+
|
|
225
422
|
def has_include?(column_name)
|
|
226
423
|
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
|
227
424
|
end
|
|
@@ -257,130 +454,136 @@ module ActiveRecord
|
|
|
257
454
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
258
455
|
end
|
|
259
456
|
|
|
260
|
-
def aggregate_column(column_name)
|
|
261
|
-
return column_name if Arel::Expressions === column_name
|
|
262
|
-
|
|
263
|
-
arel_column(column_name.to_s) do |name|
|
|
264
|
-
Arel.sql(column_name == :all ? "*" : name)
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
457
|
def operation_over_aggregate_column(column, operation, distinct)
|
|
269
|
-
operation == "count" ? column.count(distinct) : column.
|
|
458
|
+
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
|
270
459
|
end
|
|
271
460
|
|
|
272
|
-
def execute_simple_calculation(operation, column_name, distinct)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
|
|
461
|
+
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
|
462
|
+
if build_count_subquery?(operation, column_name, distinct)
|
|
276
463
|
# Shortcut when limit is zero.
|
|
277
464
|
return 0 if limit_value == 0
|
|
278
465
|
|
|
466
|
+
relation = self
|
|
279
467
|
query_builder = build_count_subquery(spawn, column_name, distinct)
|
|
280
468
|
else
|
|
281
469
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
282
470
|
relation = unscope(:order).distinct!(false)
|
|
283
471
|
|
|
284
|
-
column = aggregate_column(column_name)
|
|
285
|
-
|
|
472
|
+
column = relation.aggregate_column(column_name)
|
|
286
473
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
287
|
-
if operation == "sum" && distinct
|
|
288
|
-
select_value.distinct = true
|
|
289
|
-
end
|
|
474
|
+
select_value.distinct = true if operation == "sum" && distinct
|
|
290
475
|
|
|
291
|
-
column_alias = select_value.alias
|
|
292
|
-
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
|
293
476
|
relation.select_values = [select_value]
|
|
294
477
|
|
|
295
478
|
query_builder = relation.arel
|
|
296
479
|
end
|
|
297
480
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
481
|
+
query_result = if relation.where_clause.contradiction?
|
|
482
|
+
if @async
|
|
483
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
|
484
|
+
else
|
|
485
|
+
ActiveRecord::Result.empty
|
|
486
|
+
end
|
|
487
|
+
else
|
|
488
|
+
skip_query_cache_if_necessary do
|
|
489
|
+
@klass.with_connection do |c|
|
|
490
|
+
c.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
303
493
|
end
|
|
304
494
|
|
|
305
|
-
|
|
495
|
+
query_result.then do |result|
|
|
496
|
+
if operation != "count"
|
|
497
|
+
type = column.try(:type_caster) ||
|
|
498
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
499
|
+
type = type.subtype if Enum::EnumType === type
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
type_cast_calculated_value(result.cast_values.first, operation, type)
|
|
503
|
+
end
|
|
306
504
|
end
|
|
307
505
|
|
|
308
|
-
def execute_grouped_calculation(operation, column_name, distinct)
|
|
506
|
+
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
309
507
|
group_fields = group_values
|
|
508
|
+
group_fields = group_fields.uniq if group_fields.size > 1
|
|
310
509
|
|
|
311
510
|
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
|
312
511
|
association = klass._reflect_on_association(group_fields.first)
|
|
313
512
|
associated = association && association.belongs_to? # only count belongs_to associations
|
|
314
513
|
group_fields = Array(association.foreign_key) if associated
|
|
315
514
|
end
|
|
316
|
-
group_fields = arel_columns(group_fields)
|
|
317
|
-
|
|
318
|
-
group_aliases = group_fields.map { |field|
|
|
319
|
-
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
|
320
|
-
column_alias_for(field.to_s.downcase)
|
|
321
|
-
}
|
|
322
|
-
group_columns = group_aliases.zip(group_fields)
|
|
323
|
-
|
|
324
|
-
aggregate_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
325
|
-
|
|
326
|
-
select_values = [
|
|
327
|
-
operation_over_aggregate_column(
|
|
328
|
-
aggregate_column(column_name),
|
|
329
|
-
operation,
|
|
330
|
-
distinct).as(aggregate_alias)
|
|
331
|
-
]
|
|
332
|
-
select_values += self.select_values unless having_clause.empty?
|
|
333
|
-
|
|
334
|
-
select_values.concat group_columns.map { |aliaz, field|
|
|
335
|
-
if field.respond_to?(:as)
|
|
336
|
-
field.as(aliaz)
|
|
337
|
-
else
|
|
338
|
-
"#{field} AS #{aliaz}"
|
|
339
|
-
end
|
|
340
|
-
}
|
|
341
515
|
|
|
342
516
|
relation = except(:group).distinct!(false)
|
|
343
|
-
relation.
|
|
344
|
-
relation.select_values = select_values
|
|
517
|
+
group_fields = relation.arel_columns(group_fields)
|
|
345
518
|
|
|
346
|
-
|
|
519
|
+
@klass.with_connection do |connection|
|
|
520
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
347
521
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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)
|
|
527
|
+
|
|
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))
|
|
353
532
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
533
|
+
select_values = [select_value]
|
|
534
|
+
select_values += self.select_values unless having_clause.empty?
|
|
535
|
+
|
|
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}"
|
|
358
542
|
end
|
|
359
|
-
type_cast_calculated_value(row[aliaz], type)
|
|
360
543
|
}
|
|
361
|
-
key = key.first if key.size == 1
|
|
362
|
-
key = key_records[key] if associated
|
|
363
544
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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)
|
|
550
|
+
end
|
|
551
|
+
|
|
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)
|
|
557
|
+
end
|
|
368
558
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
|
565
|
+
|
|
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
|
|
582
|
+
|
|
583
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
end
|
|
384
587
|
end
|
|
385
588
|
|
|
386
589
|
def type_for(field, &block)
|
|
@@ -388,34 +591,86 @@ module ActiveRecord
|
|
|
388
591
|
@klass.type_for_attribute(field_name, &block)
|
|
389
592
|
end
|
|
390
593
|
|
|
391
|
-
def
|
|
594
|
+
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
|
|
595
|
+
each_join_dependencies(join_dependencies) do |join|
|
|
596
|
+
type = join.base_klass.attribute_types.fetch(name, nil)
|
|
597
|
+
return type if type
|
|
598
|
+
end
|
|
599
|
+
nil
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def type_cast_pluck_values(result, columns)
|
|
603
|
+
cast_types = if result.columns.size != columns.size
|
|
604
|
+
klass.attribute_types
|
|
605
|
+
else
|
|
606
|
+
join_dependencies = nil
|
|
607
|
+
columns.map.with_index do |column, i|
|
|
608
|
+
column.try(:type_caster) ||
|
|
609
|
+
klass.attribute_types.fetch(name = result.columns[i]) do
|
|
610
|
+
join_dependencies ||= build_join_dependencies
|
|
611
|
+
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
|
612
|
+
result.column_types[i] || Type.default_value
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
result.cast_values(cast_types)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def type_cast_calculated_value(value, operation, type)
|
|
392
620
|
case operation
|
|
393
|
-
when "count"
|
|
394
|
-
|
|
395
|
-
when "
|
|
396
|
-
|
|
621
|
+
when "count"
|
|
622
|
+
value.to_i
|
|
623
|
+
when "sum"
|
|
624
|
+
type.deserialize(value || 0)
|
|
625
|
+
when "average"
|
|
626
|
+
case type.type
|
|
627
|
+
when :integer, :decimal
|
|
628
|
+
value&.to_d
|
|
629
|
+
else
|
|
630
|
+
type.deserialize(value)
|
|
631
|
+
end
|
|
632
|
+
else # "minimum", "maximum"
|
|
633
|
+
type.deserialize(value)
|
|
397
634
|
end
|
|
398
635
|
end
|
|
399
636
|
|
|
400
637
|
def select_for_count
|
|
401
638
|
if select_values.present?
|
|
402
639
|
return select_values.first if select_values.one?
|
|
403
|
-
|
|
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(", ")
|
|
404
652
|
else
|
|
405
653
|
:all
|
|
406
654
|
end
|
|
407
655
|
end
|
|
408
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
|
+
|
|
409
664
|
def build_count_subquery(relation, column_name, distinct)
|
|
410
665
|
if column_name == :all
|
|
411
666
|
column_alias = Arel.star
|
|
412
667
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
413
668
|
else
|
|
414
669
|
column_alias = Arel.sql("count_column")
|
|
415
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
670
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
416
671
|
end
|
|
417
672
|
|
|
418
|
-
subquery_alias = Arel.sql("subquery_for_count")
|
|
673
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
419
674
|
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
420
675
|
|
|
421
676
|
relation.build_subquery(subquery_alias, select_value)
|