activerecord 5.0.7.2 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +829 -2015
- data/MIT-LICENSE +3 -1
- data/README.rdoc +11 -9
- data/examples/performance.rb +31 -29
- data/examples/simple.rb +5 -3
- data/lib/active_record.rb +37 -29
- data/lib/active_record/aggregations.rb +249 -247
- data/lib/active_record/association_relation.rb +30 -18
- data/lib/active_record/associations.rb +1714 -1596
- data/lib/active_record/associations/alias_tracker.rb +36 -42
- data/lib/active_record/associations/association.rb +143 -68
- data/lib/active_record/associations/association_scope.rb +98 -94
- data/lib/active_record/associations/belongs_to_association.rb +76 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
- data/lib/active_record/associations/builder/association.rb +27 -28
- data/lib/active_record/associations/builder/belongs_to.rb +52 -60
- data/lib/active_record/associations/builder/collection_association.rb +12 -22
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +40 -62
- data/lib/active_record/associations/builder/has_many.rb +10 -2
- data/lib/active_record/associations/builder/has_one.rb +35 -2
- data/lib/active_record/associations/builder/singular_association.rb +5 -1
- data/lib/active_record/associations/collection_association.rb +104 -259
- data/lib/active_record/associations/collection_proxy.rb +169 -125
- data/lib/active_record/associations/foreign_association.rb +22 -0
- data/lib/active_record/associations/has_many_association.rb +46 -31
- data/lib/active_record/associations/has_many_through_association.rb +66 -46
- data/lib/active_record/associations/has_one_association.rb +71 -52
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency.rb +169 -180
- data/lib/active_record/associations/join_dependency/join_association.rb +53 -79
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
- data/lib/active_record/associations/preloader.rb +97 -104
- data/lib/active_record/associations/preloader/association.rb +109 -97
- data/lib/active_record/associations/preloader/through_association.rb +77 -76
- data/lib/active_record/associations/singular_association.rb +12 -45
- data/lib/active_record/associations/through_association.rb +27 -15
- data/lib/active_record/attribute_assignment.rb +55 -60
- data/lib/active_record/attribute_methods.rb +111 -141
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -9
- data/lib/active_record/attribute_methods/dirty.rb +172 -112
- data/lib/active_record/attribute_methods/primary_key.rb +88 -91
- data/lib/active_record/attribute_methods/query.rb +6 -8
- data/lib/active_record/attribute_methods/read.rb +18 -50
- data/lib/active_record/attribute_methods/serialization.rb +38 -10
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -66
- data/lib/active_record/attribute_methods/write.rb +25 -32
- data/lib/active_record/attributes.rb +69 -31
- data/lib/active_record/autosave_association.rb +102 -66
- data/lib/active_record/base.rb +16 -25
- data/lib/active_record/callbacks.rb +202 -43
- data/lib/active_record/coders/json.rb +2 -0
- data/lib/active_record/coders/yaml_column.rb +11 -12
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +661 -375
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +14 -38
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +269 -105
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +54 -35
- data/lib/active_record/connection_adapters/abstract/quoting.rb +137 -93
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +155 -113
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -162
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +591 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +229 -91
- data/lib/active_record/connection_adapters/abstract_adapter.rb +392 -244
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +457 -582
- data/lib/active_record/connection_adapters/column.rb +55 -13
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +135 -49
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +79 -49
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +66 -56
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +20 -12
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +74 -37
- data/lib/active_record/connection_adapters/pool_config.rb +63 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +39 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -101
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +26 -21
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -6
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- 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 +7 -5
- data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -30
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +98 -38
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +21 -27
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +426 -324
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +32 -23
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +418 -293
- data/lib/active_record/connection_adapters/schema_cache.rb +135 -18
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +22 -7
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +282 -290
- data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
- data/lib/active_record/connection_handling.rb +287 -45
- data/lib/active_record/core.rb +385 -181
- data/lib/active_record/counter_cache.rb +60 -28
- data/lib/active_record/database_configurations.rb +272 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +87 -87
- data/lib/active_record/enum.rb +122 -47
- data/lib/active_record/errors.rb +153 -22
- data/lib/active_record/explain.rb +13 -8
- data/lib/active_record/explain_registry.rb +3 -1
- data/lib/active_record/explain_subscriber.rb +9 -4
- data/lib/active_record/fixture_set/file.rb +20 -22
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +246 -507
- data/lib/active_record/gem_version.rb +6 -4
- data/lib/active_record/inheritance.rb +168 -95
- data/lib/active_record/insert_all.rb +208 -0
- data/lib/active_record/integration.rb +114 -25
- data/lib/active_record/internal_metadata.rb +30 -24
- data/lib/active_record/legacy_yaml_adapter.rb +11 -5
- data/lib/active_record/locking/optimistic.rb +81 -85
- data/lib/active_record/locking/pessimistic.rb +22 -6
- data/lib/active_record/log_subscriber.rb +68 -31
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/migration.rb +439 -342
- data/lib/active_record/migration/command_recorder.rb +152 -98
- data/lib/active_record/migration/compatibility.rb +229 -60
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/model_schema.rb +230 -122
- data/lib/active_record/nested_attributes.rb +213 -203
- data/lib/active_record/no_touching.rb +11 -2
- data/lib/active_record/null_relation.rb +12 -34
- data/lib/active_record/persistence.rb +471 -97
- data/lib/active_record/query_cache.rb +23 -12
- data/lib/active_record/querying.rb +43 -25
- data/lib/active_record/railtie.rb +155 -43
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +507 -195
- data/lib/active_record/readonly_attributes.rb +9 -4
- data/lib/active_record/reflection.rb +245 -269
- data/lib/active_record/relation.rb +475 -324
- data/lib/active_record/relation/batches.rb +125 -72
- data/lib/active_record/relation/batches/batch_enumerator.rb +28 -10
- data/lib/active_record/relation/calculations.rb +267 -171
- data/lib/active_record/relation/delegation.rb +73 -69
- data/lib/active_record/relation/finder_methods.rb +238 -248
- data/lib/active_record/relation/from_clause.rb +7 -9
- data/lib/active_record/relation/merger.rb +95 -77
- data/lib/active_record/relation/predicate_builder.rb +109 -110
- data/lib/active_record/relation/predicate_builder/array_handler.rb +22 -17
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +55 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/query_attribute.rb +33 -2
- data/lib/active_record/relation/query_methods.rb +654 -374
- data/lib/active_record/relation/record_fetch_warning.rb +8 -6
- data/lib/active_record/relation/spawn_methods.rb +15 -14
- data/lib/active_record/relation/where_clause.rb +171 -109
- data/lib/active_record/result.rb +88 -51
- data/lib/active_record/runtime_registry.rb +5 -3
- data/lib/active_record/sanitization.rb +73 -100
- data/lib/active_record/schema.rb +7 -14
- data/lib/active_record/schema_dumper.rb +101 -69
- data/lib/active_record/schema_migration.rb +16 -12
- data/lib/active_record/scoping.rb +20 -20
- data/lib/active_record/scoping/default.rb +92 -95
- data/lib/active_record/scoping/named.rb +39 -30
- data/lib/active_record/secure_token.rb +19 -9
- data/lib/active_record/serialization.rb +7 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +80 -29
- data/lib/active_record/store.rb +122 -42
- data/lib/active_record/suppressor.rb +6 -3
- data/lib/active_record/table_metadata.rb +51 -39
- data/lib/active_record/tasks/database_tasks.rb +332 -115
- data/lib/active_record/tasks/mysql_database_tasks.rb +66 -104
- data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -56
- data/lib/active_record/tasks/sqlite_database_tasks.rb +40 -19
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +246 -0
- data/lib/active_record/timestamp.rb +70 -38
- data/lib/active_record/touch_later.rb +26 -24
- data/lib/active_record/transactions.rb +121 -184
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type.rb +29 -17
- data/lib/active_record/type/adapter_specific_registry.rb +44 -48
- data/lib/active_record/type/date.rb +2 -0
- data/lib/active_record/type/date_time.rb +2 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
- data/lib/active_record/type/internal/timezone.rb +2 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +20 -9
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +12 -1
- data/lib/active_record/type/type_map.rb +14 -17
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type_caster.rb +4 -2
- data/lib/active_record/type_caster/connection.rb +17 -13
- data/lib/active_record/type_caster/map.rb +10 -6
- data/lib/active_record/validations.rb +8 -5
- data/lib/active_record/validations/absence.rb +2 -0
- data/lib/active_record/validations/associated.rb +4 -3
- data/lib/active_record/validations/length.rb +2 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/presence.rb +4 -2
- data/lib/active_record/validations/uniqueness.rb +52 -45
- data/lib/active_record/version.rb +3 -1
- data/lib/arel.rb +54 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +41 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -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 +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes.rb +70 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -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/delete_statement.rb +45 -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/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +72 -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/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +51 -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 +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -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 +41 -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/order_predications.rb +13 -0
- data/lib/arel/predications.rb +250 -0
- data/lib/arel/select_manager.rb +270 -0
- data/lib/arel/table.rb +118 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/visitors/dot.rb +308 -0
- data/lib/arel/visitors/mysql.rb +93 -0
- data/lib/arel/visitors/postgresql.rb +120 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +899 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record.rb +7 -5
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
- data/lib/rails/generators/active_record/migration.rb +22 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +38 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +3 -1
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +7 -5
- data/lib/rails/generators/active_record/model/model_generator.rb +41 -25
- 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 → model.rb.tt} +10 -1
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
- metadata +141 -57
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -17
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -15
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -20
- data/lib/active_record/attribute.rb +0 -213
- data/lib/active_record/attribute/user_provided_default.rb +0 -28
- data/lib/active_record/attribute_decorators.rb +0 -67
- data/lib/active_record/attribute_mutation_tracker.rb +0 -70
- data/lib/active_record/attribute_set.rb +0 -110
- data/lib/active_record/attribute_set/builder.rb +0 -132
- data/lib/active_record/collection_cache_key.rb +0 -50
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -263
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -22
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +0 -50
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -17
- data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
- data/lib/active_record/relation/where_clause_factory.rb +0 -38
- data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_record/relation/batches/batch_enumerator"
|
2
4
|
|
3
5
|
module ActiveRecord
|
4
6
|
module Batches
|
5
|
-
|
7
|
+
ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
|
6
8
|
|
7
9
|
# Looping through a collection of records from the database
|
8
10
|
# (using the Scoping::Named::ClassMethods.all method, for example)
|
@@ -34,34 +36,44 @@ module ActiveRecord
|
|
34
36
|
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
35
37
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
36
38
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
37
|
-
#
|
39
|
+
# an order is present in the relation.
|
40
|
+
# * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
|
41
|
+
#
|
42
|
+
# Limits are honored, and if present there is no requirement for the batch
|
43
|
+
# size: it can be less than, equal to, or greater than the limit.
|
38
44
|
#
|
39
|
-
#
|
40
|
-
# the same processing queue. You can make
|
41
|
-
# between id
|
42
|
-
#
|
45
|
+
# The options +start+ and +finish+ are especially useful if you want
|
46
|
+
# multiple workers dealing with the same processing queue. You can make
|
47
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
48
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
49
|
+
# option on each worker.
|
43
50
|
#
|
44
|
-
# #
|
45
|
-
# Person.find_each(
|
51
|
+
# # In worker 1, let's process until 9999 records.
|
52
|
+
# Person.find_each(finish: 9_999) do |person|
|
46
53
|
# person.party_all_night!
|
47
54
|
# end
|
48
55
|
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
56
|
+
# # In worker 2, let's process from record 10_000 and onwards.
|
57
|
+
# Person.find_each(start: 10_000) do |person|
|
58
|
+
# person.party_all_night!
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
62
|
+
# ascending on the primary key ("id ASC").
|
63
|
+
# This also means that this method only works when the primary key is
|
52
64
|
# orderable (e.g. an integer or string).
|
53
65
|
#
|
54
|
-
# NOTE:
|
55
|
-
# the
|
56
|
-
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
66
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
67
|
+
# other processes are modifying the database.
|
68
|
+
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
|
57
69
|
if block_given?
|
58
|
-
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
|
70
|
+
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do |records|
|
59
71
|
records.each { |record| yield record }
|
60
72
|
end
|
61
73
|
else
|
62
|
-
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
|
74
|
+
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
|
63
75
|
relation = self
|
64
|
-
apply_limits(relation, start, finish).size
|
76
|
+
apply_limits(relation, start, finish, order).size
|
65
77
|
end
|
66
78
|
end
|
67
79
|
end
|
@@ -89,35 +101,40 @@ module ActiveRecord
|
|
89
101
|
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
90
102
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
91
103
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
92
|
-
#
|
104
|
+
# an order is present in the relation.
|
105
|
+
# * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
|
106
|
+
#
|
107
|
+
# Limits are honored, and if present there is no requirement for the batch
|
108
|
+
# size: it can be less than, equal to, or greater than the limit.
|
93
109
|
#
|
94
|
-
#
|
95
|
-
# the same processing queue. You can make
|
96
|
-
# between id
|
97
|
-
#
|
110
|
+
# The options +start+ and +finish+ are especially useful if you want
|
111
|
+
# multiple workers dealing with the same processing queue. You can make
|
112
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
113
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
114
|
+
# option on each worker.
|
98
115
|
#
|
99
|
-
# # Let's process
|
100
|
-
# Person.find_in_batches(start:
|
116
|
+
# # Let's process from record 10_000 on.
|
117
|
+
# Person.find_in_batches(start: 10_000) do |group|
|
101
118
|
# group.each { |person| person.party_all_night! }
|
102
119
|
# end
|
103
120
|
#
|
104
|
-
# NOTE:
|
105
|
-
# ascending on the primary key ("id ASC")
|
106
|
-
#
|
121
|
+
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
122
|
+
# ascending on the primary key ("id ASC").
|
123
|
+
# This also means that this method only works when the primary key is
|
107
124
|
# orderable (e.g. an integer or string).
|
108
125
|
#
|
109
|
-
# NOTE:
|
110
|
-
# the
|
111
|
-
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
126
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
127
|
+
# other processes are modifying the database.
|
128
|
+
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, order: :asc)
|
112
129
|
relation = self
|
113
130
|
unless block_given?
|
114
|
-
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
|
115
|
-
total = apply_limits(relation, start, finish).size
|
131
|
+
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, order: order) do
|
132
|
+
total = apply_limits(relation, start, finish, order).size
|
116
133
|
(total - 1).div(batch_size) + 1
|
117
134
|
end
|
118
135
|
end
|
119
136
|
|
120
|
-
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
|
137
|
+
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, order: order) do |batch|
|
121
138
|
yield batch.to_a
|
122
139
|
end
|
123
140
|
end
|
@@ -149,17 +166,20 @@ module ActiveRecord
|
|
149
166
|
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
150
167
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
151
168
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
152
|
-
#
|
169
|
+
# an order is present in the relation.
|
170
|
+
# * <tt>:order</tt> - Specifies the primary key order (can be :asc or :desc). Defaults to :asc.
|
171
|
+
#
|
172
|
+
# Limits are honored, and if present there is no requirement for the batch
|
173
|
+
# size, it can be less than, equal, or greater than the limit.
|
153
174
|
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
# option on each worker).
|
175
|
+
# The options +start+ and +finish+ are especially useful if you want
|
176
|
+
# multiple workers dealing with the same processing queue. You can make
|
177
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
178
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
179
|
+
# option on each worker.
|
160
180
|
#
|
161
|
-
# # Let's process
|
162
|
-
# Person.in_batches(
|
181
|
+
# # Let's process from record 10_000 on.
|
182
|
+
# Person.in_batches(start: 10_000).update_all(awesome: true)
|
163
183
|
#
|
164
184
|
# An example of calling where query method on the relation:
|
165
185
|
#
|
@@ -174,36 +194,47 @@ module ActiveRecord
|
|
174
194
|
#
|
175
195
|
# Person.in_batches.each_record(&:party_all_night!)
|
176
196
|
#
|
177
|
-
# NOTE:
|
178
|
-
# ascending on the primary key ("id ASC")
|
179
|
-
#
|
180
|
-
# or
|
197
|
+
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
198
|
+
# ascending on the primary key ("id ASC").
|
199
|
+
# This also means that this method only works when the primary key is
|
200
|
+
# orderable (e.g. an integer or string).
|
181
201
|
#
|
182
|
-
# NOTE:
|
183
|
-
#
|
184
|
-
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
|
202
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
203
|
+
# other processes are modifying the database.
|
204
|
+
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, order: :asc)
|
185
205
|
relation = self
|
186
206
|
unless block_given?
|
187
207
|
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
188
208
|
end
|
189
209
|
|
190
|
-
|
191
|
-
|
210
|
+
unless [:asc, :desc].include?(order)
|
211
|
+
raise ArgumentError, ":order must be :asc or :desc, got #{order.inspect}"
|
212
|
+
end
|
213
|
+
|
214
|
+
if arel.orders.present?
|
215
|
+
act_on_ignored_order(error_on_ignore)
|
192
216
|
end
|
193
217
|
|
194
|
-
|
195
|
-
|
218
|
+
batch_limit = of
|
219
|
+
if limit_value
|
220
|
+
remaining = limit_value
|
221
|
+
batch_limit = remaining if remaining < batch_limit
|
222
|
+
end
|
223
|
+
|
224
|
+
relation = relation.reorder(batch_order(order)).limit(batch_limit)
|
225
|
+
relation = apply_limits(relation, start, finish, order)
|
226
|
+
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
|
196
227
|
batch_relation = relation
|
197
228
|
|
198
229
|
loop do
|
199
230
|
if load
|
200
231
|
records = batch_relation.records
|
201
232
|
ids = records.map(&:id)
|
202
|
-
yielded_relation =
|
233
|
+
yielded_relation = where(primary_key => ids)
|
203
234
|
yielded_relation.load_records(records)
|
204
235
|
else
|
205
236
|
ids = batch_relation.pluck(primary_key)
|
206
|
-
yielded_relation =
|
237
|
+
yielded_relation = where(primary_key => ids)
|
207
238
|
end
|
208
239
|
|
209
240
|
break if ids.empty?
|
@@ -213,31 +244,53 @@ module ActiveRecord
|
|
213
244
|
|
214
245
|
yield yielded_relation
|
215
246
|
|
216
|
-
break if ids.length <
|
217
|
-
|
247
|
+
break if ids.length < batch_limit
|
248
|
+
|
249
|
+
if limit_value
|
250
|
+
remaining -= ids.length
|
251
|
+
|
252
|
+
if remaining == 0
|
253
|
+
# Saves a useless iteration when the limit is a multiple of the
|
254
|
+
# batch size.
|
255
|
+
break
|
256
|
+
elsif remaining < batch_limit
|
257
|
+
relation = relation.limit(remaining)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
batch_relation = relation.where(
|
262
|
+
predicate_builder[primary_key, primary_key_offset, order == :desc ? :lt : :gt]
|
263
|
+
)
|
218
264
|
end
|
219
265
|
end
|
220
266
|
|
221
267
|
private
|
268
|
+
def apply_limits(relation, start, finish, order)
|
269
|
+
relation = apply_start_limit(relation, start, order) if start
|
270
|
+
relation = apply_finish_limit(relation, finish, order) if finish
|
271
|
+
relation
|
272
|
+
end
|
222
273
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
relation
|
227
|
-
end
|
274
|
+
def apply_start_limit(relation, start, order)
|
275
|
+
relation.where(predicate_builder[primary_key, start, order == :desc ? :lteq : :gteq])
|
276
|
+
end
|
228
277
|
|
229
|
-
|
230
|
-
|
231
|
-
|
278
|
+
def apply_finish_limit(relation, finish, order)
|
279
|
+
relation.where(predicate_builder[primary_key, finish, order == :desc ? :gteq : :lteq])
|
280
|
+
end
|
281
|
+
|
282
|
+
def batch_order(order)
|
283
|
+
table[primary_key].public_send(order)
|
284
|
+
end
|
232
285
|
|
233
|
-
|
234
|
-
|
286
|
+
def act_on_ignored_order(error_on_ignore)
|
287
|
+
raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
|
235
288
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
289
|
+
if raise_error
|
290
|
+
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
|
291
|
+
elsif logger
|
292
|
+
logger.warn(ORDER_IGNORE_MESSAGE)
|
293
|
+
end
|
240
294
|
end
|
241
|
-
end
|
242
295
|
end
|
243
296
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Batches
|
3
5
|
class BatchEnumerator
|
@@ -7,7 +9,7 @@ module ActiveRecord
|
|
7
9
|
@of = of
|
8
10
|
@relation = relation
|
9
11
|
@start = start
|
10
|
-
@finish
|
12
|
+
@finish = finish
|
11
13
|
end
|
12
14
|
|
13
15
|
# Looping through a collection of records from the database (using the
|
@@ -39,19 +41,35 @@ module ActiveRecord
|
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
#
|
44
|
+
# Deletes records in batches. Returns the total number of rows affected.
|
45
|
+
#
|
46
|
+
# Person.in_batches.delete_all
|
47
|
+
#
|
48
|
+
# See Relation#delete_all for details of how each batch is deleted.
|
49
|
+
def delete_all
|
50
|
+
sum(&:delete_all)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Updates records in batches. Returns the total number of rows affected.
|
43
54
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
relation.send(method, *args, &block)
|
51
|
-
end
|
55
|
+
# Person.in_batches.update_all("age = age + 1")
|
56
|
+
#
|
57
|
+
# See Relation#update_all for details of how each batch is updated.
|
58
|
+
def update_all(updates)
|
59
|
+
sum do |relation|
|
60
|
+
relation.update_all(updates)
|
52
61
|
end
|
53
62
|
end
|
54
63
|
|
64
|
+
# Destroys records in batches.
|
65
|
+
#
|
66
|
+
# Person.where("age < 10").in_batches.destroy_all
|
67
|
+
#
|
68
|
+
# See Relation#destroy_all for details of how each batch is destroyed.
|
69
|
+
def destroy_all
|
70
|
+
each(&:destroy_all)
|
71
|
+
end
|
72
|
+
|
55
73
|
# Yields an ActiveRecord::Relation object for each batch of records.
|
56
74
|
#
|
57
75
|
# Person.in_batches.each do |relation|
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
|
1
5
|
module ActiveRecord
|
2
6
|
module Calculations
|
3
7
|
# Count the records.
|
@@ -37,7 +41,15 @@ module ActiveRecord
|
|
37
41
|
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
38
42
|
# between databases. In invalid cases, an error from the database is thrown.
|
39
43
|
def count(column_name = nil)
|
40
|
-
|
44
|
+
if block_given?
|
45
|
+
unless column_name.nil?
|
46
|
+
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
47
|
+
end
|
48
|
+
|
49
|
+
super()
|
50
|
+
else
|
51
|
+
calculate(:count, column_name)
|
52
|
+
end
|
41
53
|
end
|
42
54
|
|
43
55
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
@@ -71,9 +83,16 @@ module ActiveRecord
|
|
71
83
|
# #calculate for examples with options.
|
72
84
|
#
|
73
85
|
# Person.sum(:age) # => 4562
|
74
|
-
def sum(column_name = nil
|
75
|
-
|
76
|
-
|
86
|
+
def sum(column_name = nil)
|
87
|
+
if block_given?
|
88
|
+
unless column_name.nil?
|
89
|
+
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
90
|
+
end
|
91
|
+
|
92
|
+
super()
|
93
|
+
else
|
94
|
+
calculate(:sum, column_name)
|
95
|
+
end
|
77
96
|
end
|
78
97
|
|
79
98
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
@@ -108,13 +127,17 @@ module ActiveRecord
|
|
108
127
|
# ...
|
109
128
|
# end
|
110
129
|
def calculate(operation, column_name)
|
111
|
-
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
|
112
|
-
column_name = attribute_alias(column_name)
|
113
|
-
end
|
114
|
-
|
115
130
|
if has_include?(column_name)
|
116
|
-
relation =
|
117
|
-
|
131
|
+
relation = apply_join_dependency
|
132
|
+
|
133
|
+
if operation.to_s.downcase == "count"
|
134
|
+
unless distinct_value || distinct_select?(column_name || select_for_count)
|
135
|
+
relation.distinct!
|
136
|
+
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
137
|
+
end
|
138
|
+
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
139
|
+
relation.order_values = [] if group_values.empty?
|
140
|
+
end
|
118
141
|
|
119
142
|
relation.calculate(operation, column_name)
|
120
143
|
else
|
@@ -151,29 +174,58 @@ module ActiveRecord
|
|
151
174
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
152
175
|
# # => [2, 3]
|
153
176
|
#
|
154
|
-
# Person.pluck('DATEDIFF(updated_at, created_at)')
|
177
|
+
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
155
178
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
156
179
|
# # => ['0', '27761', '173']
|
157
180
|
#
|
158
181
|
# See also #ids.
|
159
182
|
#
|
160
183
|
def pluck(*column_names)
|
161
|
-
if loaded? && (column_names
|
184
|
+
if loaded? && all_attributes?(column_names)
|
162
185
|
return records.pluck(*column_names)
|
163
186
|
end
|
164
187
|
|
165
188
|
if has_include?(column_names.first)
|
166
|
-
|
189
|
+
relation = apply_join_dependency
|
190
|
+
relation.pluck(*column_names)
|
167
191
|
else
|
192
|
+
klass.disallow_raw_sql!(column_names)
|
193
|
+
columns = arel_columns(column_names)
|
168
194
|
relation = spawn
|
169
|
-
relation.select_values =
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
195
|
+
relation.select_values = columns
|
196
|
+
result = skip_query_cache_if_necessary do
|
197
|
+
if where_clause.contradiction?
|
198
|
+
ActiveRecord::Result.new([], [])
|
199
|
+
else
|
200
|
+
klass.connection.select_all(relation.arel, nil)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
type_cast_pluck_values(result, columns)
|
174
204
|
end
|
175
205
|
end
|
176
206
|
|
207
|
+
# Pick the value(s) from the named column(s) in the current relation.
|
208
|
+
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
209
|
+
# when you have a relation that's already narrowed down to a single row.
|
210
|
+
#
|
211
|
+
# Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
|
212
|
+
# more efficient. The value is, again like with pluck, typecast by the column type.
|
213
|
+
#
|
214
|
+
# Person.where(id: 1).pick(:name)
|
215
|
+
# # SELECT people.name FROM people WHERE id = 1 LIMIT 1
|
216
|
+
# # => 'David'
|
217
|
+
#
|
218
|
+
# Person.where(id: 1).pick(:name, :email_address)
|
219
|
+
# # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
|
220
|
+
# # => [ 'David', 'david@loudthinking.com' ]
|
221
|
+
def pick(*column_names)
|
222
|
+
if loaded? && all_attributes?(column_names)
|
223
|
+
return records.pick(*column_names)
|
224
|
+
end
|
225
|
+
|
226
|
+
limit(1).pluck(*column_names).first
|
227
|
+
end
|
228
|
+
|
177
229
|
# Pluck all the ID's for the relation using the table's primary key
|
178
230
|
#
|
179
231
|
# Person.ids # SELECT people.id FROM people
|
@@ -183,203 +235,247 @@ module ActiveRecord
|
|
183
235
|
end
|
184
236
|
|
185
237
|
private
|
238
|
+
def all_attributes?(column_names)
|
239
|
+
(column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
|
240
|
+
end
|
186
241
|
|
187
|
-
|
188
|
-
|
189
|
-
|
242
|
+
def has_include?(column_name)
|
243
|
+
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
244
|
+
end
|
245
|
+
|
246
|
+
def perform_calculation(operation, column_name)
|
247
|
+
operation = operation.to_s.downcase
|
190
248
|
|
191
|
-
|
192
|
-
|
249
|
+
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
|
250
|
+
# considered distinct.
|
251
|
+
distinct = distinct_value
|
193
252
|
|
194
|
-
|
195
|
-
|
196
|
-
|
253
|
+
if operation == "count"
|
254
|
+
column_name ||= select_for_count
|
255
|
+
if column_name == :all
|
256
|
+
if !distinct
|
257
|
+
distinct = distinct_select?(select_for_count) if group_values.empty?
|
258
|
+
elsif group_values.any? || select_values.empty? && order_values.empty?
|
259
|
+
column_name = primary_key
|
260
|
+
end
|
261
|
+
elsif distinct_select?(column_name)
|
262
|
+
distinct = nil
|
263
|
+
end
|
264
|
+
end
|
197
265
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
266
|
+
if group_values.any?
|
267
|
+
execute_grouped_calculation(operation, column_name, distinct)
|
268
|
+
else
|
269
|
+
execute_simple_calculation(operation, column_name, distinct)
|
270
|
+
end
|
202
271
|
end
|
203
272
|
|
204
|
-
|
205
|
-
|
206
|
-
else
|
207
|
-
execute_simple_calculation(operation, column_name, distinct)
|
273
|
+
def distinct_select?(column_name)
|
274
|
+
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
208
275
|
end
|
209
|
-
end
|
210
276
|
|
211
|
-
|
212
|
-
|
277
|
+
def aggregate_column(column_name)
|
278
|
+
return column_name if Arel::Expressions === column_name
|
213
279
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
Arel.sql(column_name == :all ? "*" : column_name.to_s)
|
280
|
+
arel_column(column_name.to_s) do |name|
|
281
|
+
Arel.sql(column_name == :all ? "*" : name)
|
282
|
+
end
|
218
283
|
end
|
219
|
-
end
|
220
284
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
226
|
-
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
227
|
-
relation = unscope(:order)
|
285
|
+
def operation_over_aggregate_column(column, operation, distinct)
|
286
|
+
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
287
|
+
end
|
228
288
|
|
229
|
-
|
289
|
+
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
290
|
+
if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
|
291
|
+
# Shortcut when limit is zero.
|
292
|
+
return 0 if limit_value == 0
|
230
293
|
|
231
|
-
|
232
|
-
|
233
|
-
|
294
|
+
query_builder = build_count_subquery(spawn, column_name, distinct)
|
295
|
+
else
|
296
|
+
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
297
|
+
relation = unscope(:order).distinct!(false)
|
234
298
|
|
235
|
-
|
236
|
-
|
237
|
-
|
299
|
+
column = aggregate_column(column_name)
|
300
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
301
|
+
select_value.distinct = true if operation == "sum" && distinct
|
238
302
|
|
239
|
-
|
303
|
+
relation.select_values = [select_value]
|
240
304
|
|
241
|
-
|
242
|
-
select_value.distinct = true
|
305
|
+
query_builder = relation.arel
|
243
306
|
end
|
244
307
|
|
245
|
-
|
246
|
-
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
247
|
-
relation.select_values = [select_value]
|
308
|
+
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder) }
|
248
309
|
|
249
|
-
|
310
|
+
type_cast_calculated_value(result.cast_values.first, operation) do |value|
|
311
|
+
type = column.try(:type_caster) ||
|
312
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
313
|
+
type.deserialize(value)
|
314
|
+
end
|
250
315
|
end
|
251
316
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
317
|
+
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
318
|
+
group_fields = group_values
|
319
|
+
group_fields = group_fields.uniq if group_fields.size > 1
|
320
|
+
|
321
|
+
unless group_fields == group_values
|
322
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
323
|
+
`#{operation}` with group by duplicated fields does no longer affect to result in Rails 6.2.
|
324
|
+
To migrate to Rails 6.2's behavior, use `uniq!(:group)` to deduplicate group fields
|
325
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:group).#{operation}(#{column_name.inspect})`).
|
326
|
+
MSG
|
327
|
+
group_fields = group_values
|
328
|
+
end
|
258
329
|
|
259
|
-
|
260
|
-
|
330
|
+
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
331
|
+
association = klass._reflect_on_association(group_fields.first)
|
332
|
+
associated = association && association.belongs_to? # only count belongs_to associations
|
333
|
+
group_fields = Array(association.foreign_key) if associated
|
334
|
+
end
|
335
|
+
group_fields = arel_columns(group_fields)
|
261
336
|
|
262
|
-
|
263
|
-
|
337
|
+
group_aliases = group_fields.map { |field|
|
338
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
339
|
+
column_alias_for(field.to_s.downcase)
|
340
|
+
}
|
341
|
+
group_columns = group_aliases.zip(group_fields)
|
264
342
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
else
|
270
|
-
group_fields = group_attrs
|
271
|
-
end
|
272
|
-
group_fields = arel_columns(group_fields)
|
343
|
+
column = aggregate_column(column_name)
|
344
|
+
column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
|
345
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
346
|
+
select_value.as(column_alias)
|
273
347
|
|
274
|
-
|
275
|
-
|
348
|
+
select_values = [select_value]
|
349
|
+
select_values += self.select_values unless having_clause.empty?
|
276
350
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
351
|
+
select_values.concat group_columns.map { |aliaz, field|
|
352
|
+
if field.respond_to?(:as)
|
353
|
+
field.as(aliaz)
|
354
|
+
else
|
355
|
+
"#{field} AS #{aliaz}"
|
356
|
+
end
|
357
|
+
}
|
282
358
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
operation,
|
287
|
-
distinct).as(aggregate_alias)
|
288
|
-
]
|
289
|
-
select_values += self.select_values unless having_clause.empty?
|
290
|
-
|
291
|
-
select_values.concat group_columns.map { |aliaz, field|
|
292
|
-
if field.respond_to?(:as)
|
293
|
-
field.as(aliaz)
|
294
|
-
else
|
295
|
-
"#{field} AS #{aliaz}"
|
296
|
-
end
|
297
|
-
}
|
359
|
+
relation = except(:group).distinct!(false)
|
360
|
+
relation.group_values = group_fields
|
361
|
+
relation.select_values = select_values
|
298
362
|
|
299
|
-
|
300
|
-
relation.group_values = group_fields
|
301
|
-
relation.select_values = select_values
|
363
|
+
calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
|
302
364
|
|
303
|
-
|
365
|
+
if association
|
366
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
367
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
368
|
+
key_records = key_records.index_by(&:id)
|
369
|
+
end
|
304
370
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
371
|
+
key_types = group_columns.each_with_object({}) do |(aliaz, col_name), types|
|
372
|
+
types[aliaz] = type_for(col_name) do
|
373
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
374
|
+
end
|
375
|
+
end
|
310
376
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
calculated_data.column_types.fetch(aliaz) do
|
315
|
-
Type::Value.new
|
316
|
-
end
|
377
|
+
hash_rows = calculated_data.cast_values(key_types).map! do |row|
|
378
|
+
calculated_data.columns.each_with_object({}).with_index do |(col_name, hash), i|
|
379
|
+
hash[col_name] = row[i]
|
317
380
|
end
|
318
|
-
|
319
|
-
}
|
320
|
-
key = key.first if key.size == 1
|
321
|
-
key = key_records[key] if associated
|
381
|
+
end
|
322
382
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
383
|
+
type = nil
|
384
|
+
hash_rows.each_with_object({}) do |row, result|
|
385
|
+
key = group_aliases.map { |aliaz| row[aliaz] }
|
386
|
+
key = key.first if key.size == 1
|
387
|
+
key = key_records[key] if associated
|
327
388
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
# column_alias_for("count(*)") # => "count_all"
|
335
|
-
def column_alias_for(keys)
|
336
|
-
if keys.respond_to? :name
|
337
|
-
keys = "#{keys.relation.name}.#{keys.name}"
|
389
|
+
result[key] = type_cast_calculated_value(row[column_alias], operation) do |value|
|
390
|
+
type ||= column.try(:type_caster) ||
|
391
|
+
lookup_cast_type_from_join_dependencies(column_name.to_s) || Type.default_value
|
392
|
+
type.deserialize(value)
|
393
|
+
end
|
394
|
+
end
|
338
395
|
end
|
339
396
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
397
|
+
# Converts the given field to the value that the database adapter returns as
|
398
|
+
# a usable column name:
|
399
|
+
#
|
400
|
+
# column_alias_for("users.id") # => "users_id"
|
401
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
402
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
403
|
+
# column_alias_for("count(*)") # => "count_all"
|
404
|
+
def column_alias_for(field)
|
405
|
+
column_alias = +field
|
406
|
+
column_alias.gsub!(/\*/, "all")
|
407
|
+
column_alias.gsub!(/\W+/, " ")
|
408
|
+
column_alias.strip!
|
409
|
+
column_alias.gsub!(/ +/, "_")
|
410
|
+
|
411
|
+
connection.table_alias_for(column_alias)
|
412
|
+
end
|
345
413
|
|
346
|
-
|
347
|
-
|
414
|
+
def type_for(field, &block)
|
415
|
+
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
416
|
+
@klass.type_for_attribute(field_name, &block)
|
417
|
+
end
|
348
418
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
419
|
+
def lookup_cast_type_from_join_dependencies(name, join_dependencies = build_join_dependencies)
|
420
|
+
each_join_dependencies(join_dependencies) do |join|
|
421
|
+
type = join.base_klass.attribute_types.fetch(name, nil)
|
422
|
+
return type if type
|
423
|
+
end
|
424
|
+
nil
|
425
|
+
end
|
353
426
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
427
|
+
def type_cast_pluck_values(result, columns)
|
428
|
+
cast_types = if result.columns.size != columns.size
|
429
|
+
klass.attribute_types
|
430
|
+
else
|
431
|
+
join_dependencies = nil
|
432
|
+
columns.map.with_index do |column, i|
|
433
|
+
column.try(:type_caster) ||
|
434
|
+
klass.attribute_types.fetch(name = result.columns[i]) do
|
435
|
+
join_dependencies ||= build_join_dependencies
|
436
|
+
lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
|
437
|
+
result.column_types[name] || Type.default_value
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
result.cast_values(cast_types)
|
360
442
|
end
|
361
|
-
end
|
362
443
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
444
|
+
def type_cast_calculated_value(value, operation)
|
445
|
+
case operation
|
446
|
+
when "count"
|
447
|
+
value.to_i
|
448
|
+
when "sum"
|
449
|
+
yield value || 0
|
450
|
+
when "average"
|
451
|
+
value&.respond_to?(:to_d) ? value.to_d : value
|
452
|
+
else # "minimum", "maximum"
|
453
|
+
yield value
|
454
|
+
end
|
369
455
|
end
|
370
|
-
end
|
371
456
|
|
372
|
-
|
373
|
-
|
374
|
-
|
457
|
+
def select_for_count
|
458
|
+
if select_values.present?
|
459
|
+
return select_values.first if select_values.one?
|
460
|
+
select_values.join(", ")
|
461
|
+
else
|
462
|
+
:all
|
463
|
+
end
|
464
|
+
end
|
375
465
|
|
376
|
-
|
377
|
-
|
378
|
-
|
466
|
+
def build_count_subquery(relation, column_name, distinct)
|
467
|
+
if column_name == :all
|
468
|
+
column_alias = Arel.star
|
469
|
+
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
470
|
+
else
|
471
|
+
column_alias = Arel.sql("count_column")
|
472
|
+
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
473
|
+
end
|
379
474
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
475
|
+
subquery_alias = Arel.sql("subquery_for_count")
|
476
|
+
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
477
|
+
|
478
|
+
relation.build_subquery(subquery_alias, select_value)
|
479
|
+
end
|
384
480
|
end
|
385
481
|
end
|