omg-activerecord 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +355 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +219 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record/aggregations.rb +287 -0
- data/lib/active_record/association_relation.rb +50 -0
- data/lib/active_record/associations/alias_tracker.rb +90 -0
- data/lib/active_record/associations/association.rb +417 -0
- data/lib/active_record/associations/association_scope.rb +175 -0
- data/lib/active_record/associations/belongs_to_association.rb +163 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/builder/association.rb +170 -0
- data/lib/active_record/associations/builder/belongs_to.rb +160 -0
- data/lib/active_record/associations/builder/collection_association.rb +80 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
- data/lib/active_record/associations/builder/has_many.rb +23 -0
- data/lib/active_record/associations/builder/has_one.rb +61 -0
- data/lib/active_record/associations/builder/singular_association.rb +48 -0
- data/lib/active_record/associations/collection_association.rb +535 -0
- data/lib/active_record/associations/collection_proxy.rb +1163 -0
- 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 +40 -0
- data/lib/active_record/associations/has_many_association.rb +167 -0
- data/lib/active_record/associations/has_many_through_association.rb +232 -0
- data/lib/active_record/associations/has_one_association.rb +142 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/join_dependency.rb +301 -0
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +316 -0
- 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 +150 -0
- data/lib/active_record/associations/preloader.rb +135 -0
- data/lib/active_record/associations/singular_association.rb +76 -0
- data/lib/active_record/associations/through_association.rb +132 -0
- data/lib/active_record/associations.rb +1897 -0
- data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
- data/lib/active_record/attribute_assignment.rb +82 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +262 -0
- data/lib/active_record/attribute_methods/primary_key.rb +158 -0
- data/lib/active_record/attribute_methods/query.rb +50 -0
- data/lib/active_record/attribute_methods/read.rb +46 -0
- data/lib/active_record/attribute_methods/serialization.rb +232 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
- data/lib/active_record/attribute_methods/write.rb +49 -0
- data/lib/active_record/attribute_methods.rb +542 -0
- data/lib/active_record/attributes.rb +307 -0
- data/lib/active_record/autosave_association.rb +586 -0
- data/lib/active_record/base.rb +338 -0
- data/lib/active_record/callbacks.rb +452 -0
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
- data/lib/active_record/connection_adapters/column.rb +122 -0
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
- 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 +82 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- 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 +125 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +413 -0
- data/lib/active_record/core.rb +836 -0
- data/lib/active_record/counter_cache.rb +230 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
- data/lib/active_record/database_configurations/database_config.rb +104 -0
- data/lib/active_record/database_configurations/hash_config.rb +172 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/database_configurations.rb +309 -0
- data/lib/active_record/delegated_type.rb +289 -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 +121 -0
- 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 +70 -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 +184 -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 +177 -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 +159 -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 +107 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +424 -0
- data/lib/active_record/errors.rb +614 -0
- data/lib/active_record/explain.rb +63 -0
- data/lib/active_record/explain_registry.rb +37 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +89 -0
- data/lib/active_record/fixture_set/model_metadata.rb +42 -0
- data/lib/active_record/fixture_set/render_context.rb +19 -0
- data/lib/active_record/fixture_set/table_row.rb +208 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +850 -0
- data/lib/active_record/future_result.rb +182 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +366 -0
- data/lib/active_record/insert_all.rb +328 -0
- data/lib/active_record/integration.rb +209 -0
- data/lib/active_record/internal_metadata.rb +164 -0
- data/lib/active_record/legacy_yaml_adapter.rb +15 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +228 -0
- data/lib/active_record/locking/pessimistic.rb +102 -0
- data/lib/active_record/log_subscriber.rb +149 -0
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +87 -0
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +406 -0
- data/lib/active_record/migration/compatibility.rb +490 -0
- 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 +16 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +1626 -0
- data/lib/active_record/model_schema.rb +635 -0
- data/lib/active_record/nested_attributes.rb +633 -0
- data/lib/active_record/no_touching.rb +65 -0
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +968 -0
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/query_logs.rb +247 -0
- data/lib/active_record/query_logs_formatter.rb +30 -0
- data/lib/active_record/querying.rb +122 -0
- data/lib/active_record/railtie.rb +440 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +65 -0
- data/lib/active_record/railties/databases.rake +641 -0
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +66 -0
- data/lib/active_record/reflection.rb +1287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
- data/lib/active_record/relation/batches.rb +491 -0
- data/lib/active_record/relation/calculations.rb +679 -0
- data/lib/active_record/relation/delegation.rb +154 -0
- data/lib/active_record/relation/finder_methods.rb +661 -0
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +192 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
- data/lib/active_record/relation/predicate_builder.rb +181 -0
- data/lib/active_record/relation/query_attribute.rb +68 -0
- data/lib/active_record/relation/query_methods.rb +2235 -0
- data/lib/active_record/relation/record_fetch_warning.rb +52 -0
- data/lib/active_record/relation/spawn_methods.rb +78 -0
- data/lib/active_record/relation/where_clause.rb +218 -0
- data/lib/active_record/relation.rb +1495 -0
- data/lib/active_record/result.rb +249 -0
- data/lib/active_record/runtime_registry.rb +82 -0
- data/lib/active_record/sanitization.rb +254 -0
- data/lib/active_record/schema.rb +77 -0
- data/lib/active_record/schema_dumper.rb +364 -0
- data/lib/active_record/schema_migration.rb +106 -0
- data/lib/active_record/scoping/default.rb +205 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +136 -0
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +66 -0
- data/lib/active_record/serialization.rb +29 -0
- data/lib/active_record/signed_id.rb +137 -0
- data/lib/active_record/statement_cache.rb +164 -0
- data/lib/active_record/store.rb +299 -0
- data/lib/active_record/suppressor.rb +59 -0
- data/lib/active_record/table_metadata.rb +85 -0
- data/lib/active_record/tasks/database_tasks.rb +681 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +321 -0
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +177 -0
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +70 -0
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +523 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/adapter_specific_registry.rb +144 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
- data/lib/active_record/type/internal/timezone.rb +22 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +76 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +35 -0
- data/lib/active_record/type/type_map.rb +58 -0
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type.rb +83 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +65 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +45 -0
- data/lib/active_record/validations/uniqueness.rb +295 -0
- data/lib/active_record/validations.rb +101 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/active_record.rb +616 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +33 -0
- data/lib/arel/collectors/bind.rb +31 -0
- data/lib/arel/collectors/composite.rb +46 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +48 -0
- data/lib/arel/delete_manager.rb +32 -0
- data/lib/arel/errors.rb +19 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +53 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +48 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +125 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +44 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +45 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/nary.rb +39 -0
- data/lib/arel/nodes/node.rb +161 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +32 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +35 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +46 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +75 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +260 -0
- data/lib/arel/select_manager.rb +276 -0
- data/lib/arel/table.rb +121 -0
- data/lib/arel/tree_manager.rb +65 -0
- data/lib/arel/update_manager.rb +49 -0
- data/lib/arel/visitors/dot.rb +299 -0
- data/lib/arel/visitors/mysql.rb +111 -0
- data/lib/arel/visitors/postgresql.rb +99 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +1033 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +73 -0
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
- data/lib/rails/generators/active_record/migration.rb +54 -0
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
- 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 +22 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- 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
- data/lib/rails/generators/active_record.rb +19 -0
- metadata +505 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
# = Active Record \Calculations
|
|
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
|
+
|
|
49
|
+
# Count the records.
|
|
50
|
+
#
|
|
51
|
+
# Person.count
|
|
52
|
+
# # => the total count of all people
|
|
53
|
+
#
|
|
54
|
+
# Person.count(:age)
|
|
55
|
+
# # => returns the total count of all people whose age is present in database
|
|
56
|
+
#
|
|
57
|
+
# Person.count(:all)
|
|
58
|
+
# # => performs a COUNT(*) (:all is an alias for '*')
|
|
59
|
+
#
|
|
60
|
+
# Person.distinct.count(:age)
|
|
61
|
+
# # => counts the number of different age values
|
|
62
|
+
#
|
|
63
|
+
# If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
|
64
|
+
# it returns a Hash whose keys represent the aggregated column,
|
|
65
|
+
# and the values are the respective amounts:
|
|
66
|
+
#
|
|
67
|
+
# Person.group(:city).count
|
|
68
|
+
# # => { 'Rome' => 5, 'Paris' => 3 }
|
|
69
|
+
#
|
|
70
|
+
# If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
|
71
|
+
# keys are an array containing the individual values of each column and the value
|
|
72
|
+
# of each key would be the #count.
|
|
73
|
+
#
|
|
74
|
+
# Article.group(:status, :category).count
|
|
75
|
+
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
|
76
|
+
#
|
|
77
|
+
# If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
|
78
|
+
#
|
|
79
|
+
# Person.select(:age).count
|
|
80
|
+
# # => counts the number of different age values
|
|
81
|
+
#
|
|
82
|
+
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
|
83
|
+
# between databases. In invalid cases, an error from the database is thrown.
|
|
84
|
+
#
|
|
85
|
+
# When given a block, loads all records in the relation, if the relation
|
|
86
|
+
# hasn't been loaded yet. Calls the block with each record in the relation.
|
|
87
|
+
# Returns the number of records for which the block returns a truthy value.
|
|
88
|
+
#
|
|
89
|
+
# Person.count { |person| person.age > 21 }
|
|
90
|
+
# # => counts the number of people older that 21
|
|
91
|
+
#
|
|
92
|
+
# Note: If there are a lot of records in the relation, loading all records
|
|
93
|
+
# could result in performance issues.
|
|
94
|
+
def count(column_name = nil)
|
|
95
|
+
if block_given?
|
|
96
|
+
unless column_name.nil?
|
|
97
|
+
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
super()
|
|
101
|
+
else
|
|
102
|
+
calculate(:count, column_name)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
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
|
+
|
|
112
|
+
# Calculates the average value on a given column. Returns +nil+ if there's
|
|
113
|
+
# no row. See #calculate for examples with options.
|
|
114
|
+
#
|
|
115
|
+
# Person.average(:age) # => 35.8
|
|
116
|
+
def average(column_name)
|
|
117
|
+
calculate(:average, column_name)
|
|
118
|
+
end
|
|
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
|
+
|
|
126
|
+
# Calculates the minimum value on a given column. The value is returned
|
|
127
|
+
# with the same data type of the column, or +nil+ if there's no row. See
|
|
128
|
+
# #calculate for examples with options.
|
|
129
|
+
#
|
|
130
|
+
# Person.minimum(:age) # => 7
|
|
131
|
+
def minimum(column_name)
|
|
132
|
+
calculate(:minimum, column_name)
|
|
133
|
+
end
|
|
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
|
+
|
|
141
|
+
# Calculates the maximum value on a given column. The value is returned
|
|
142
|
+
# with the same data type of the column, or +nil+ if there's no row. See
|
|
143
|
+
# #calculate for examples with options.
|
|
144
|
+
#
|
|
145
|
+
# Person.maximum(:age) # => 93
|
|
146
|
+
def maximum(column_name)
|
|
147
|
+
calculate(:maximum, column_name)
|
|
148
|
+
end
|
|
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
|
+
|
|
156
|
+
# Calculates the sum of values on a given column. The value is returned
|
|
157
|
+
# with the same data type of the column, +0+ if there's no row. See
|
|
158
|
+
# #calculate for examples with options.
|
|
159
|
+
#
|
|
160
|
+
# Person.sum(:age) # => 4562
|
|
161
|
+
#
|
|
162
|
+
# When given a block, loads all records in the relation, if the relation
|
|
163
|
+
# hasn't been loaded yet. Calls the block with each record in the relation.
|
|
164
|
+
# Returns the sum of +initial_value_or_column+ and the block return
|
|
165
|
+
# values:
|
|
166
|
+
#
|
|
167
|
+
# Person.sum { |person| person.age } # => 4562
|
|
168
|
+
# Person.sum(1000) { |person| person.age } # => 5562
|
|
169
|
+
#
|
|
170
|
+
# Note: If there are a lot of records in the relation, loading all records
|
|
171
|
+
# could result in performance issues.
|
|
172
|
+
def sum(initial_value_or_column = 0, &block)
|
|
173
|
+
if block_given?
|
|
174
|
+
map(&block).sum(initial_value_or_column)
|
|
175
|
+
else
|
|
176
|
+
calculate(:sum, initial_value_or_column)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Same as #sum, but performs the query asynchronously and returns an
|
|
181
|
+
# ActiveRecord::Promise.
|
|
182
|
+
def async_sum(identity_or_column = nil)
|
|
183
|
+
async.sum(identity_or_column)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
|
187
|
+
# #minimum, and #maximum have been added as shortcuts.
|
|
188
|
+
#
|
|
189
|
+
# Person.calculate(:count, :all) # The same as Person.count
|
|
190
|
+
# Person.average(:age) # SELECT AVG(age) FROM people...
|
|
191
|
+
#
|
|
192
|
+
# # Selects the minimum age for any family without any minors
|
|
193
|
+
# Person.group(:last_name).having("min(age) > 17").minimum(:age)
|
|
194
|
+
#
|
|
195
|
+
# Person.sum("2 * age")
|
|
196
|
+
#
|
|
197
|
+
# There are two basic forms of output:
|
|
198
|
+
#
|
|
199
|
+
# * Single aggregate value: The single value is type cast to Integer for COUNT, Float
|
|
200
|
+
# for AVG, and the given column's type for everything else.
|
|
201
|
+
#
|
|
202
|
+
# * Grouped values: This returns an ordered hash of the values and groups them. It
|
|
203
|
+
# takes either a column name, or the name of a belongs_to association.
|
|
204
|
+
#
|
|
205
|
+
# values = Person.group('last_name').maximum(:age)
|
|
206
|
+
# puts values["Drake"]
|
|
207
|
+
# # => 43
|
|
208
|
+
#
|
|
209
|
+
# drake = Family.find_by(last_name: 'Drake')
|
|
210
|
+
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
|
|
211
|
+
# puts values[drake]
|
|
212
|
+
# # => 43
|
|
213
|
+
#
|
|
214
|
+
# values.each do |family, max_age|
|
|
215
|
+
# ...
|
|
216
|
+
# end
|
|
217
|
+
def calculate(operation, column_name)
|
|
218
|
+
operation = operation.to_s.downcase
|
|
219
|
+
|
|
220
|
+
if @none
|
|
221
|
+
case operation
|
|
222
|
+
when "count", "sum"
|
|
223
|
+
result = group_values.any? ? Hash.new : 0
|
|
224
|
+
return @async ? Promise::Complete.new(result) : result
|
|
225
|
+
when "average", "minimum", "maximum"
|
|
226
|
+
result = group_values.any? ? Hash.new : nil
|
|
227
|
+
return @async ? Promise::Complete.new(result) : result
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
if has_include?(column_name)
|
|
232
|
+
relation = apply_join_dependency
|
|
233
|
+
|
|
234
|
+
if operation == "count"
|
|
235
|
+
unless distinct_value || distinct_select?(column_name || select_for_count)
|
|
236
|
+
relation.distinct!
|
|
237
|
+
relation.select_values = Array(model.primary_key || table[Arel.star])
|
|
238
|
+
end
|
|
239
|
+
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
|
240
|
+
relation.order_values = [] if group_values.empty?
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
relation.calculate(operation, column_name)
|
|
244
|
+
else
|
|
245
|
+
perform_calculation(operation, column_name)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Use #pluck as a shortcut to select one or more attributes without
|
|
250
|
+
# loading an entire record object per row.
|
|
251
|
+
#
|
|
252
|
+
# Person.pluck(:name)
|
|
253
|
+
#
|
|
254
|
+
# instead of
|
|
255
|
+
#
|
|
256
|
+
# Person.all.map(&:name)
|
|
257
|
+
#
|
|
258
|
+
# Pluck returns an Array of attribute values type-casted to match
|
|
259
|
+
# the plucked column names, if they can be deduced. Plucking an SQL fragment
|
|
260
|
+
# returns String values by default.
|
|
261
|
+
#
|
|
262
|
+
# Person.pluck(:name)
|
|
263
|
+
# # SELECT people.name FROM people
|
|
264
|
+
# # => ['David', 'Jeremy', 'Jose']
|
|
265
|
+
#
|
|
266
|
+
# Person.pluck(:id, :name)
|
|
267
|
+
# # SELECT people.id, people.name FROM people
|
|
268
|
+
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
|
|
269
|
+
#
|
|
270
|
+
# Person.distinct.pluck(:role)
|
|
271
|
+
# # SELECT DISTINCT role FROM people
|
|
272
|
+
# # => ['admin', 'member', 'guest']
|
|
273
|
+
#
|
|
274
|
+
# Person.where(age: 21).limit(5).pluck(:id)
|
|
275
|
+
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
|
276
|
+
# # => [2, 3]
|
|
277
|
+
#
|
|
278
|
+
# Comment.joins(:person).pluck(:id, person: [:id])
|
|
279
|
+
# # SELECT comments.id, people.id FROM comments INNER JOIN people on comments.person_id = people.id
|
|
280
|
+
# # => [[1, 2], [2, 2]]
|
|
281
|
+
#
|
|
282
|
+
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
|
283
|
+
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
|
284
|
+
# # => ['0', '27761', '173']
|
|
285
|
+
#
|
|
286
|
+
# See also #ids.
|
|
287
|
+
def pluck(*column_names)
|
|
288
|
+
if @none
|
|
289
|
+
if @async
|
|
290
|
+
return Promise::Complete.new([])
|
|
291
|
+
else
|
|
292
|
+
return []
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
if loaded? && all_attributes?(column_names)
|
|
297
|
+
result = records.pluck(*column_names)
|
|
298
|
+
if @async
|
|
299
|
+
return Promise::Complete.new(result)
|
|
300
|
+
else
|
|
301
|
+
return result
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
if has_include?(column_names.first)
|
|
306
|
+
relation = apply_join_dependency
|
|
307
|
+
relation.pluck(*column_names)
|
|
308
|
+
else
|
|
309
|
+
model.disallow_raw_sql!(flattened_args(column_names))
|
|
310
|
+
columns = arel_columns(column_names)
|
|
311
|
+
relation = spawn
|
|
312
|
+
relation.select_values = columns
|
|
313
|
+
result = skip_query_cache_if_necessary do
|
|
314
|
+
if where_clause.contradiction?
|
|
315
|
+
ActiveRecord::Result.empty(async: @async)
|
|
316
|
+
else
|
|
317
|
+
model.with_connection do |c|
|
|
318
|
+
c.select_all(relation.arel, "#{model.name} Pluck", async: @async)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
result.then do |result|
|
|
323
|
+
type_cast_pluck_values(result, columns)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Same as #pluck, but performs the query asynchronously and returns an
|
|
329
|
+
# ActiveRecord::Promise.
|
|
330
|
+
def async_pluck(*column_names)
|
|
331
|
+
async.pluck(*column_names)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Pick the value(s) from the named column(s) in the current relation.
|
|
335
|
+
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
|
336
|
+
# when you have a relation that's already narrowed down to a single row.
|
|
337
|
+
#
|
|
338
|
+
# Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
|
|
339
|
+
# more efficient. The value is, again like with pluck, typecast by the column type.
|
|
340
|
+
#
|
|
341
|
+
# Person.where(id: 1).pick(:name)
|
|
342
|
+
# # SELECT people.name FROM people WHERE id = 1 LIMIT 1
|
|
343
|
+
# # => 'David'
|
|
344
|
+
#
|
|
345
|
+
# Person.where(id: 1).pick(:name, :email_address)
|
|
346
|
+
# # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
|
|
347
|
+
# # => [ 'David', 'david@loudthinking.com' ]
|
|
348
|
+
def pick(*column_names)
|
|
349
|
+
if loaded? && all_attributes?(column_names)
|
|
350
|
+
result = records.pick(*column_names)
|
|
351
|
+
return @async ? Promise::Complete.new(result) : result
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
limit(1).pluck(*column_names).then(&:first)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# Same as #pick, but performs the query asynchronously and returns an
|
|
358
|
+
# ActiveRecord::Promise.
|
|
359
|
+
def async_pick(*column_names)
|
|
360
|
+
async.pick(*column_names)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Returns the base model's ID's for the relation using the table's primary key
|
|
364
|
+
#
|
|
365
|
+
# Person.ids # SELECT people.id FROM people
|
|
366
|
+
# Person.joins(:company).ids # SELECT people.id FROM people INNER JOIN companies ON companies.id = people.company_id
|
|
367
|
+
def ids
|
|
368
|
+
primary_key_array = Array(primary_key)
|
|
369
|
+
|
|
370
|
+
if loaded?
|
|
371
|
+
result = records.map do |record|
|
|
372
|
+
if primary_key_array.one?
|
|
373
|
+
record._read_attribute(primary_key_array.first)
|
|
374
|
+
else
|
|
375
|
+
primary_key_array.map { |column| record._read_attribute(column) }
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
return @async ? Promise::Complete.new(result) : result
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
if has_include?(primary_key)
|
|
382
|
+
relation = apply_join_dependency.group(*primary_key_array)
|
|
383
|
+
return relation.ids
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
columns = arel_columns(primary_key_array)
|
|
387
|
+
relation = spawn
|
|
388
|
+
relation.select_values = columns
|
|
389
|
+
|
|
390
|
+
result = if relation.where_clause.contradiction?
|
|
391
|
+
ActiveRecord::Result.empty
|
|
392
|
+
else
|
|
393
|
+
skip_query_cache_if_necessary do
|
|
394
|
+
model.with_connection do |c|
|
|
395
|
+
c.select_all(relation, "#{model.name} Ids", async: @async)
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
result.then { |result| type_cast_pluck_values(result, columns) }
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Same as #ids, but performs the query asynchronously and returns an
|
|
404
|
+
# ActiveRecord::Promise.
|
|
405
|
+
def async_ids
|
|
406
|
+
async.ids
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
private
|
|
410
|
+
def all_attributes?(column_names)
|
|
411
|
+
(column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def has_include?(column_name)
|
|
415
|
+
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def perform_calculation(operation, column_name)
|
|
419
|
+
operation = operation.to_s.downcase
|
|
420
|
+
|
|
421
|
+
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
|
|
422
|
+
# considered distinct.
|
|
423
|
+
distinct = distinct_value
|
|
424
|
+
|
|
425
|
+
if operation == "count"
|
|
426
|
+
column_name ||= select_for_count
|
|
427
|
+
if column_name == :all
|
|
428
|
+
if !distinct
|
|
429
|
+
distinct = distinct_select?(select_for_count) if group_values.empty?
|
|
430
|
+
elsif group_values.any? || select_values.empty? && order_values.empty?
|
|
431
|
+
column_name = primary_key
|
|
432
|
+
end
|
|
433
|
+
elsif distinct_select?(column_name)
|
|
434
|
+
distinct = nil
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
if group_values.any?
|
|
439
|
+
execute_grouped_calculation(operation, column_name, distinct)
|
|
440
|
+
else
|
|
441
|
+
execute_simple_calculation(operation, column_name, distinct)
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def distinct_select?(column_name)
|
|
446
|
+
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def aggregate_column(column_name)
|
|
450
|
+
return column_name if Arel::Expressions === column_name
|
|
451
|
+
|
|
452
|
+
arel_column(column_name.to_s) do |name|
|
|
453
|
+
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def operation_over_aggregate_column(column, operation, distinct)
|
|
458
|
+
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def execute_simple_calculation(operation, column_name, distinct) # :nodoc:
|
|
462
|
+
if build_count_subquery?(operation, column_name, distinct)
|
|
463
|
+
# Shortcut when limit is zero.
|
|
464
|
+
return 0 if limit_value == 0
|
|
465
|
+
|
|
466
|
+
relation = self
|
|
467
|
+
query_builder = build_count_subquery(spawn, column_name, distinct)
|
|
468
|
+
else
|
|
469
|
+
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
470
|
+
relation = unscope(:order).distinct!(false)
|
|
471
|
+
|
|
472
|
+
column = aggregate_column(column_name)
|
|
473
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
474
|
+
select_value.distinct = true if operation == "sum" && distinct
|
|
475
|
+
|
|
476
|
+
relation.select_values = [select_value]
|
|
477
|
+
|
|
478
|
+
query_builder = relation.arel
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
query_result = if relation.where_clause.contradiction?
|
|
482
|
+
ActiveRecord::Result.empty
|
|
483
|
+
else
|
|
484
|
+
skip_query_cache_if_necessary do
|
|
485
|
+
model.with_connection do |c|
|
|
486
|
+
c.select_all(query_builder, "#{model.name} #{operation.capitalize}", async: @async)
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
query_result.then do |result|
|
|
492
|
+
if operation != "count"
|
|
493
|
+
type = column.try(:type_caster) ||
|
|
494
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
495
|
+
type = type.subtype if Enum::EnumType === type
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
type_cast_calculated_value(result.cast_values.first, operation, type)
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
|
|
503
|
+
group_fields = group_values
|
|
504
|
+
group_fields = group_fields.uniq if group_fields.size > 1
|
|
505
|
+
|
|
506
|
+
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
|
507
|
+
association = model._reflect_on_association(group_fields.first)
|
|
508
|
+
associated = association && association.belongs_to? # only count belongs_to associations
|
|
509
|
+
group_fields = Array(association.foreign_key) if associated
|
|
510
|
+
end
|
|
511
|
+
group_fields = arel_columns(group_fields)
|
|
512
|
+
|
|
513
|
+
model.with_connection do |connection|
|
|
514
|
+
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
515
|
+
|
|
516
|
+
group_aliases = group_fields.map { |field|
|
|
517
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
|
518
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
|
519
|
+
}
|
|
520
|
+
group_columns = group_aliases.zip(group_fields)
|
|
521
|
+
|
|
522
|
+
column = aggregate_column(column_name)
|
|
523
|
+
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
524
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
525
|
+
select_value.as(model.adapter_class.quote_column_name(column_alias))
|
|
526
|
+
|
|
527
|
+
select_values = [select_value]
|
|
528
|
+
select_values += self.select_values unless having_clause.empty?
|
|
529
|
+
|
|
530
|
+
select_values.concat group_columns.map { |aliaz, field|
|
|
531
|
+
aliaz = model.adapter_class.quote_column_name(aliaz)
|
|
532
|
+
if field.respond_to?(:as)
|
|
533
|
+
field.as(aliaz)
|
|
534
|
+
else
|
|
535
|
+
"#{field} AS #{aliaz}"
|
|
536
|
+
end
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
relation = except(:group).distinct!(false)
|
|
540
|
+
relation.group_values = group_fields
|
|
541
|
+
relation.select_values = select_values
|
|
542
|
+
|
|
543
|
+
result = skip_query_cache_if_necessary do
|
|
544
|
+
connection.select_all(relation.arel, "#{model.name} #{operation.capitalize}", async: @async)
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
result.then do |calculated_data|
|
|
548
|
+
if association
|
|
549
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
|
550
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
|
551
|
+
key_records = key_records.index_by(&:id)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
|
555
|
+
types[aliaz] = col_name.try(:type_caster) ||
|
|
556
|
+
type_for(col_name) do
|
|
557
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
|
562
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
|
563
|
+
hash[col_name] = row[i]
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
if operation != "count"
|
|
568
|
+
type = column.try(:type_caster) ||
|
|
569
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
|
570
|
+
type = type.subtype if Enum::EnumType === type
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
hash_rows.each_with_object({}) do |row, result|
|
|
574
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
|
575
|
+
key = key.first if key.size == 1
|
|
576
|
+
key = key_records[key] if associated
|
|
577
|
+
|
|
578
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation, type)
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
def type_for(field, &block)
|
|
585
|
+
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
|
586
|
+
model.type_for_attribute(field_name, &block)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
|
|
590
|
+
each_join_dependencies(join_dependencies) do |join|
|
|
591
|
+
type = join.base_klass.attribute_types.fetch(name, nil)
|
|
592
|
+
return type if type
|
|
593
|
+
end
|
|
594
|
+
nil
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def type_cast_pluck_values(result, columns)
|
|
598
|
+
cast_types = if result.columns.size != columns.size
|
|
599
|
+
model.attribute_types
|
|
600
|
+
else
|
|
601
|
+
join_dependencies = nil
|
|
602
|
+
columns.map.with_index do |column, i|
|
|
603
|
+
column.try(:type_caster) ||
|
|
604
|
+
model.attribute_types.fetch(name = result.columns[i]) do
|
|
605
|
+
join_dependencies ||= build_join_dependencies
|
|
606
|
+
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
|
607
|
+
result.column_types[i] || Type.default_value
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
result.cast_values(cast_types)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def type_cast_calculated_value(value, operation, type)
|
|
615
|
+
case operation
|
|
616
|
+
when "count"
|
|
617
|
+
value.to_i
|
|
618
|
+
when "sum"
|
|
619
|
+
type.deserialize(value || 0)
|
|
620
|
+
when "average"
|
|
621
|
+
case type.type
|
|
622
|
+
when :integer, :decimal
|
|
623
|
+
value&.to_d
|
|
624
|
+
else
|
|
625
|
+
type.deserialize(value)
|
|
626
|
+
end
|
|
627
|
+
else # "minimum", "maximum"
|
|
628
|
+
type.deserialize(value)
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def select_for_count
|
|
633
|
+
if select_values.present?
|
|
634
|
+
return select_values.first if select_values.one?
|
|
635
|
+
|
|
636
|
+
adapter_class = model.adapter_class
|
|
637
|
+
select_values.map do |field|
|
|
638
|
+
column = if Arel.arel_node?(field)
|
|
639
|
+
field
|
|
640
|
+
else
|
|
641
|
+
arel_column(field.to_s) do |attr_name|
|
|
642
|
+
Arel.sql(attr_name)
|
|
643
|
+
end
|
|
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(", ")
|
|
652
|
+
else
|
|
653
|
+
:all
|
|
654
|
+
end
|
|
655
|
+
end
|
|
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
|
+
|
|
664
|
+
def build_count_subquery(relation, column_name, distinct)
|
|
665
|
+
if column_name == :all
|
|
666
|
+
column_alias = Arel.star
|
|
667
|
+
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
668
|
+
else
|
|
669
|
+
column_alias = Arel.sql("count_column")
|
|
670
|
+
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
674
|
+
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
|
675
|
+
|
|
676
|
+
relation.build_subquery(subquery_alias, select_value)
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
end
|