activerecord 5.0.7.2 → 6.0.3.4
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.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +708 -2040
- data/MIT-LICENSE +3 -1
- data/README.rdoc +9 -7
- data/examples/performance.rb +31 -29
- data/examples/simple.rb +5 -3
- data/lib/active_record.rb +37 -22
- data/lib/active_record/advisory_lock_base.rb +18 -0
- data/lib/active_record/aggregations.rb +249 -247
- data/lib/active_record/association_relation.rb +18 -14
- data/lib/active_record/associations.rb +1603 -1592
- data/lib/active_record/associations/alias_tracker.rb +24 -34
- data/lib/active_record/associations/association.rb +114 -55
- data/lib/active_record/associations/association_scope.rb +94 -94
- data/lib/active_record/associations/belongs_to_association.rb +58 -42
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
- data/lib/active_record/associations/builder/association.rb +18 -25
- data/lib/active_record/associations/builder/belongs_to.rb +43 -54
- data/lib/active_record/associations/builder/collection_association.rb +7 -18
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +41 -62
- data/lib/active_record/associations/builder/has_many.rb +4 -0
- data/lib/active_record/associations/builder/has_one.rb +37 -1
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +86 -254
- data/lib/active_record/associations/collection_proxy.rb +158 -122
- data/lib/active_record/associations/foreign_association.rb +9 -0
- data/lib/active_record/associations/has_many_association.rb +23 -30
- data/lib/active_record/associations/has_many_through_association.rb +58 -44
- data/lib/active_record/associations/has_one_association.rb +59 -54
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency.rb +143 -176
- data/lib/active_record/associations/join_dependency/join_association.rb +38 -87
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
- data/lib/active_record/associations/preloader.rb +90 -103
- data/lib/active_record/associations/preloader/association.rb +86 -100
- 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 +26 -14
- data/lib/active_record/attribute_assignment.rb +54 -61
- data/lib/active_record/attribute_decorators.rb +38 -17
- data/lib/active_record/attribute_methods.rb +66 -106
- data/lib/active_record/attribute_methods/before_type_cast.rb +12 -8
- data/lib/active_record/attribute_methods/dirty.rb +179 -109
- data/lib/active_record/attribute_methods/primary_key.rb +85 -92
- data/lib/active_record/attribute_methods/query.rb +4 -3
- data/lib/active_record/attribute_methods/read.rb +20 -49
- data/lib/active_record/attribute_methods/serialization.rb +29 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -66
- data/lib/active_record/attribute_methods/write.rb +34 -33
- data/lib/active_record/attributes.rb +38 -25
- data/lib/active_record/autosave_association.rb +54 -35
- data/lib/active_record/base.rb +27 -24
- data/lib/active_record/callbacks.rb +64 -35
- 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/abstract/connection_pool.rb +552 -323
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +23 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +215 -94
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +59 -35
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -75
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +33 -28
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +228 -147
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +400 -213
- data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -79
- data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -202
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +396 -562
- data/lib/active_record/connection_adapters/column.rb +41 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +172 -139
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -4
- data/lib/active_record/connection_adapters/mysql/column.rb +8 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +137 -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 +49 -45
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +58 -56
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +12 -13
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +48 -30
- data/lib/active_record/connection_adapters/postgresql/column.rb +19 -31
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -54
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +24 -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 +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +4 -2
- 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/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/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 +34 -31
- 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 +8 -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 +95 -35
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +20 -26
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -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 +378 -308
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +26 -25
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +383 -275
- data/lib/active_record/connection_adapters/schema_cache.rb +46 -12
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -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 +3 -8
- 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 +137 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +259 -266
- data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
- data/lib/active_record/connection_handling.rb +143 -40
- data/lib/active_record/core.rb +201 -163
- data/lib/active_record/counter_cache.rb +60 -28
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -87
- data/lib/active_record/enum.rb +60 -23
- data/lib/active_record/errors.rb +114 -18
- data/lib/active_record/explain.rb +4 -4
- 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 +13 -8
- data/lib/active_record/fixture_set/model_metadata.rb +33 -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 +194 -504
- data/lib/active_record/gem_version.rb +5 -3
- data/lib/active_record/inheritance.rb +150 -99
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +116 -25
- data/lib/active_record/internal_metadata.rb +16 -19
- data/lib/active_record/legacy_yaml_adapter.rb +4 -2
- data/lib/active_record/locking/optimistic.rb +77 -87
- data/lib/active_record/locking/pessimistic.rb +18 -6
- data/lib/active_record/log_subscriber.rb +48 -29
- data/lib/active_record/middleware/database_selector.rb +74 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +369 -302
- data/lib/active_record/migration/command_recorder.rb +134 -100
- data/lib/active_record/migration/compatibility.rb +174 -56
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/model_schema.rb +131 -127
- data/lib/active_record/nested_attributes.rb +213 -202
- data/lib/active_record/no_touching.rb +12 -3
- data/lib/active_record/null_relation.rb +12 -34
- data/lib/active_record/persistence.rb +446 -77
- data/lib/active_record/query_cache.rb +13 -12
- data/lib/active_record/querying.rb +37 -24
- data/lib/active_record/railtie.rb +128 -36
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- 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 +312 -177
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +214 -252
- data/lib/active_record/relation.rb +440 -318
- data/lib/active_record/relation/batches.rb +98 -52
- data/lib/active_record/relation/batches/batch_enumerator.rb +3 -1
- data/lib/active_record/relation/calculations.rb +212 -173
- data/lib/active_record/relation/delegation.rb +72 -69
- data/lib/active_record/relation/finder_methods.rb +207 -247
- data/lib/active_record/relation/from_clause.rb +6 -8
- data/lib/active_record/relation/merger.rb +78 -62
- data/lib/active_record/relation/predicate_builder.rb +83 -105
- data/lib/active_record/relation/predicate_builder/array_handler.rb +20 -14
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
- data/lib/active_record/relation/query_attribute.rb +33 -2
- data/lib/active_record/relation/query_methods.rb +476 -334
- data/lib/active_record/relation/record_fetch_warning.rb +5 -3
- data/lib/active_record/relation/spawn_methods.rb +8 -8
- data/lib/active_record/relation/where_clause.rb +111 -96
- data/lib/active_record/relation/where_clause_factory.rb +6 -11
- data/lib/active_record/result.rb +69 -40
- data/lib/active_record/runtime_registry.rb +5 -3
- data/lib/active_record/sanitization.rb +83 -99
- data/lib/active_record/schema.rb +7 -14
- data/lib/active_record/schema_dumper.rb +71 -69
- data/lib/active_record/schema_migration.rb +16 -6
- data/lib/active_record/scoping.rb +20 -20
- data/lib/active_record/scoping/default.rb +92 -95
- data/lib/active_record/scoping/named.rb +47 -27
- data/lib/active_record/secure_token.rb +4 -2
- data/lib/active_record/serialization.rb +2 -0
- data/lib/active_record/statement_cache.rb +63 -28
- data/lib/active_record/store.rb +121 -41
- data/lib/active_record/suppressor.rb +6 -3
- data/lib/active_record/table_metadata.rb +39 -18
- data/lib/active_record/tasks/database_tasks.rb +271 -81
- data/lib/active_record/tasks/mysql_database_tasks.rb +54 -91
- data/lib/active_record/tasks/postgresql_database_tasks.rb +77 -47
- data/lib/active_record/tasks/sqlite_database_tasks.rb +33 -16
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +225 -0
- data/lib/active_record/timestamp.rb +70 -36
- data/lib/active_record/touch_later.rb +8 -6
- data/lib/active_record/transactions.rb +141 -157
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type.rb +23 -18
- 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 +16 -9
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +2 -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 -12
- data/lib/active_record/type_caster/map.rb +5 -4
- data/lib/active_record/validations.rb +7 -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/presence.rb +4 -2
- data/lib/active_record/validations/uniqueness.rb +29 -42
- data/lib/active_record/version.rb +3 -1
- data/lib/arel.rb +62 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -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 +68 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -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 +18 -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 +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -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 +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -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 +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -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 +256 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/visitors/depth_first.rb +203 -0
- data/lib/arel/visitors/dot.rb +296 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +156 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +158 -0
- data/lib/arel/visitors/oracle12.rb +65 -0
- data/lib/arel/visitors/postgresql.rb +109 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +888 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors/where_sql.rb +22 -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 +17 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +1 -1
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -30
- 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 +137 -52
- 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_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/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/class_handler.rb +0 -27
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
- 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,15 +36,24 @@ 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
|
+
#
|
41
|
+
# Limits are honored, and if present there is no requirement for the batch
|
42
|
+
# size: it can be less than, equal to, or greater than the limit.
|
43
|
+
#
|
44
|
+
# The options +start+ and +finish+ are especially useful if you want
|
45
|
+
# multiple workers dealing with the same processing queue. You can make
|
46
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
47
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
48
|
+
# option on each worker.
|
38
49
|
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
50
|
+
# # In worker 1, let's process until 9999 records.
|
51
|
+
# Person.find_each(finish: 9_999) do |person|
|
52
|
+
# person.party_all_night!
|
53
|
+
# end
|
43
54
|
#
|
44
|
-
# #
|
45
|
-
# Person.find_each(start:
|
55
|
+
# # In worker 2, let's process from record 10_000 and onwards.
|
56
|
+
# Person.find_each(start: 10_000) do |person|
|
46
57
|
# person.party_all_night!
|
47
58
|
# end
|
48
59
|
#
|
@@ -51,8 +62,8 @@ module ActiveRecord
|
|
51
62
|
# work. This also means that this method only works when the primary key is
|
52
63
|
# orderable (e.g. an integer or string).
|
53
64
|
#
|
54
|
-
# NOTE:
|
55
|
-
# the
|
65
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
66
|
+
# other processes are modifying the database.
|
56
67
|
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
57
68
|
if block_given?
|
58
69
|
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
|
@@ -89,15 +100,19 @@ module ActiveRecord
|
|
89
100
|
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
90
101
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
91
102
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
92
|
-
#
|
103
|
+
# an order is present in the relation.
|
104
|
+
#
|
105
|
+
# Limits are honored, and if present there is no requirement for the batch
|
106
|
+
# size: it can be less than, equal to, or greater than the limit.
|
93
107
|
#
|
94
|
-
#
|
95
|
-
# the same processing queue. You can make
|
96
|
-
# between id
|
97
|
-
#
|
108
|
+
# The options +start+ and +finish+ are especially useful if you want
|
109
|
+
# multiple workers dealing with the same processing queue. You can make
|
110
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
111
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
112
|
+
# option on each worker.
|
98
113
|
#
|
99
|
-
# # Let's process
|
100
|
-
# Person.find_in_batches(start:
|
114
|
+
# # Let's process from record 10_000 on.
|
115
|
+
# Person.find_in_batches(start: 10_000) do |group|
|
101
116
|
# group.each { |person| person.party_all_night! }
|
102
117
|
# end
|
103
118
|
#
|
@@ -106,8 +121,8 @@ module ActiveRecord
|
|
106
121
|
# work. This also means that this method only works when the primary key is
|
107
122
|
# orderable (e.g. an integer or string).
|
108
123
|
#
|
109
|
-
# NOTE:
|
110
|
-
# the
|
124
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
125
|
+
# other processes are modifying the database.
|
111
126
|
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
|
112
127
|
relation = self
|
113
128
|
unless block_given?
|
@@ -149,17 +164,19 @@ module ActiveRecord
|
|
149
164
|
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
|
150
165
|
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
|
151
166
|
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
152
|
-
#
|
167
|
+
# an order is present in the relation.
|
153
168
|
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
# you want multiple workers dealing with the same processing queue. You can
|
157
|
-
# make worker 1 handle all the records between id 0 and 10,000 and worker 2
|
158
|
-
# handle from 10,000 and beyond (by setting the +:start+ and +:finish+
|
159
|
-
# option on each worker).
|
169
|
+
# Limits are honored, and if present there is no requirement for the batch
|
170
|
+
# size, it can be less than, equal, or greater than the limit.
|
160
171
|
#
|
161
|
-
#
|
162
|
-
#
|
172
|
+
# The options +start+ and +finish+ are especially useful if you want
|
173
|
+
# multiple workers dealing with the same processing queue. You can make
|
174
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
175
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
176
|
+
# option on each worker.
|
177
|
+
#
|
178
|
+
# # Let's process from record 10_000 on.
|
179
|
+
# Person.in_batches(start: 10_000).update_all(awesome: true)
|
163
180
|
#
|
164
181
|
# An example of calling where query method on the relation:
|
165
182
|
#
|
@@ -179,31 +196,38 @@ module ActiveRecord
|
|
179
196
|
# consistent. Therefore the primary key must be orderable, e.g. an integer
|
180
197
|
# or a string.
|
181
198
|
#
|
182
|
-
# NOTE:
|
183
|
-
#
|
199
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
200
|
+
# other processes are modifying the database.
|
184
201
|
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
|
185
202
|
relation = self
|
186
203
|
unless block_given?
|
187
204
|
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
|
188
205
|
end
|
189
206
|
|
190
|
-
if arel.orders.present?
|
191
|
-
|
207
|
+
if arel.orders.present?
|
208
|
+
act_on_ignored_order(error_on_ignore)
|
209
|
+
end
|
210
|
+
|
211
|
+
batch_limit = of
|
212
|
+
if limit_value
|
213
|
+
remaining = limit_value
|
214
|
+
batch_limit = remaining if remaining < batch_limit
|
192
215
|
end
|
193
216
|
|
194
|
-
relation = relation.reorder(batch_order).limit(
|
217
|
+
relation = relation.reorder(batch_order).limit(batch_limit)
|
195
218
|
relation = apply_limits(relation, start, finish)
|
219
|
+
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
|
196
220
|
batch_relation = relation
|
197
221
|
|
198
222
|
loop do
|
199
223
|
if load
|
200
224
|
records = batch_relation.records
|
201
225
|
ids = records.map(&:id)
|
202
|
-
yielded_relation =
|
226
|
+
yielded_relation = where(primary_key => ids)
|
203
227
|
yielded_relation.load_records(records)
|
204
228
|
else
|
205
229
|
ids = batch_relation.pluck(primary_key)
|
206
|
-
yielded_relation =
|
230
|
+
yielded_relation = where(primary_key => ids)
|
207
231
|
end
|
208
232
|
|
209
233
|
break if ids.empty?
|
@@ -213,31 +237,53 @@ module ActiveRecord
|
|
213
237
|
|
214
238
|
yield yielded_relation
|
215
239
|
|
216
|
-
break if ids.length <
|
217
|
-
|
240
|
+
break if ids.length < batch_limit
|
241
|
+
|
242
|
+
if limit_value
|
243
|
+
remaining -= ids.length
|
244
|
+
|
245
|
+
if remaining == 0
|
246
|
+
# Saves a useless iteration when the limit is a multiple of the
|
247
|
+
# batch size.
|
248
|
+
break
|
249
|
+
elsif remaining < batch_limit
|
250
|
+
relation = relation.limit(remaining)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
batch_relation = relation.where(
|
255
|
+
bind_attribute(primary_key, primary_key_offset) { |attr, bind| attr.gt(bind) }
|
256
|
+
)
|
218
257
|
end
|
219
258
|
end
|
220
259
|
|
221
260
|
private
|
261
|
+
def apply_limits(relation, start, finish)
|
262
|
+
relation = apply_start_limit(relation, start) if start
|
263
|
+
relation = apply_finish_limit(relation, finish) if finish
|
264
|
+
relation
|
265
|
+
end
|
222
266
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
relation
|
227
|
-
end
|
267
|
+
def apply_start_limit(relation, start)
|
268
|
+
relation.where(bind_attribute(primary_key, start) { |attr, bind| attr.gteq(bind) })
|
269
|
+
end
|
228
270
|
|
229
|
-
|
230
|
-
|
231
|
-
|
271
|
+
def apply_finish_limit(relation, finish)
|
272
|
+
relation.where(bind_attribute(primary_key, finish) { |attr, bind| attr.lteq(bind) })
|
273
|
+
end
|
274
|
+
|
275
|
+
def batch_order
|
276
|
+
arel_attribute(primary_key).asc
|
277
|
+
end
|
232
278
|
|
233
|
-
|
234
|
-
|
279
|
+
def act_on_ignored_order(error_on_ignore)
|
280
|
+
raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
|
235
281
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
282
|
+
if raise_error
|
283
|
+
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
|
284
|
+
elsif logger
|
285
|
+
logger.warn(ORDER_IGNORE_MESSAGE)
|
286
|
+
end
|
240
287
|
end
|
241
|
-
end
|
242
288
|
end
|
243
289
|
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
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Calculations
|
3
5
|
# Count the records.
|
@@ -37,7 +39,15 @@ module ActiveRecord
|
|
37
39
|
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
|
38
40
|
# between databases. In invalid cases, an error from the database is thrown.
|
39
41
|
def count(column_name = nil)
|
40
|
-
|
42
|
+
if block_given?
|
43
|
+
unless column_name.nil?
|
44
|
+
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
45
|
+
end
|
46
|
+
|
47
|
+
super()
|
48
|
+
else
|
49
|
+
calculate(:count, column_name)
|
50
|
+
end
|
41
51
|
end
|
42
52
|
|
43
53
|
# Calculates the average value on a given column. Returns +nil+ if there's
|
@@ -71,9 +81,16 @@ module ActiveRecord
|
|
71
81
|
# #calculate for examples with options.
|
72
82
|
#
|
73
83
|
# Person.sum(:age) # => 4562
|
74
|
-
def sum(column_name = nil
|
75
|
-
|
76
|
-
|
84
|
+
def sum(column_name = nil)
|
85
|
+
if block_given?
|
86
|
+
unless column_name.nil?
|
87
|
+
raise ArgumentError, "Column name argument is not supported when a block is passed."
|
88
|
+
end
|
89
|
+
|
90
|
+
super()
|
91
|
+
else
|
92
|
+
calculate(:sum, column_name)
|
93
|
+
end
|
77
94
|
end
|
78
95
|
|
79
96
|
# This calculates aggregate values in the given column. Methods for #count, #sum, #average,
|
@@ -108,13 +125,17 @@ module ActiveRecord
|
|
108
125
|
# ...
|
109
126
|
# end
|
110
127
|
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
128
|
if has_include?(column_name)
|
116
|
-
relation =
|
117
|
-
|
129
|
+
relation = apply_join_dependency
|
130
|
+
|
131
|
+
if operation.to_s.downcase == "count"
|
132
|
+
unless distinct_value || distinct_select?(column_name || select_for_count)
|
133
|
+
relation.distinct!
|
134
|
+
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
135
|
+
end
|
136
|
+
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
137
|
+
relation.order_values = []
|
138
|
+
end
|
118
139
|
|
119
140
|
relation.calculate(operation, column_name)
|
120
141
|
else
|
@@ -151,7 +172,7 @@ module ActiveRecord
|
|
151
172
|
# # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
|
152
173
|
# # => [2, 3]
|
153
174
|
#
|
154
|
-
# Person.pluck('DATEDIFF(updated_at, created_at)')
|
175
|
+
# Person.pluck(Arel.sql('DATEDIFF(updated_at, created_at)'))
|
155
176
|
# # SELECT DATEDIFF(updated_at, created_at) FROM people
|
156
177
|
# # => ['0', '27761', '173']
|
157
178
|
#
|
@@ -163,17 +184,35 @@ module ActiveRecord
|
|
163
184
|
end
|
164
185
|
|
165
186
|
if has_include?(column_names.first)
|
166
|
-
|
187
|
+
relation = apply_join_dependency
|
188
|
+
relation.pluck(*column_names)
|
167
189
|
else
|
190
|
+
klass.disallow_raw_sql!(column_names)
|
168
191
|
relation = spawn
|
169
|
-
relation.select_values = column_names
|
170
|
-
|
171
|
-
}
|
172
|
-
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
|
192
|
+
relation.select_values = column_names
|
193
|
+
result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
|
173
194
|
result.cast_values(klass.attribute_types)
|
174
195
|
end
|
175
196
|
end
|
176
197
|
|
198
|
+
# Pick the value(s) from the named column(s) in the current relation.
|
199
|
+
# This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
|
200
|
+
# when you have a relation that's already narrowed down to a single row.
|
201
|
+
#
|
202
|
+
# Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
|
203
|
+
# more efficient. The value is, again like with pluck, typecast by the column type.
|
204
|
+
#
|
205
|
+
# Person.where(id: 1).pick(:name)
|
206
|
+
# # SELECT people.name FROM people WHERE id = 1 LIMIT 1
|
207
|
+
# # => 'David'
|
208
|
+
#
|
209
|
+
# Person.where(id: 1).pick(:name, :email_address)
|
210
|
+
# # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
|
211
|
+
# # => [ 'David', 'david@loudthinking.com' ]
|
212
|
+
def pick(*column_names)
|
213
|
+
limit(1).pluck(*column_names).first
|
214
|
+
end
|
215
|
+
|
177
216
|
# Pluck all the ID's for the relation using the table's primary key
|
178
217
|
#
|
179
218
|
# Person.ids # SELECT people.id FROM people
|
@@ -183,203 +222,203 @@ module ActiveRecord
|
|
183
222
|
end
|
184
223
|
|
185
224
|
private
|
225
|
+
def has_include?(column_name)
|
226
|
+
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
227
|
+
end
|
186
228
|
|
187
|
-
|
188
|
-
|
189
|
-
end
|
229
|
+
def perform_calculation(operation, column_name)
|
230
|
+
operation = operation.to_s.downcase
|
190
231
|
|
191
|
-
|
192
|
-
|
232
|
+
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
|
233
|
+
# considered distinct.
|
234
|
+
distinct = distinct_value
|
193
235
|
|
194
|
-
|
195
|
-
|
196
|
-
|
236
|
+
if operation == "count"
|
237
|
+
column_name ||= select_for_count
|
238
|
+
if column_name == :all
|
239
|
+
if !distinct
|
240
|
+
distinct = distinct_select?(select_for_count) if group_values.empty?
|
241
|
+
elsif group_values.any? || select_values.empty? && order_values.empty?
|
242
|
+
column_name = primary_key
|
243
|
+
end
|
244
|
+
elsif distinct_select?(column_name)
|
245
|
+
distinct = nil
|
246
|
+
end
|
247
|
+
end
|
197
248
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
249
|
+
if group_values.any?
|
250
|
+
execute_grouped_calculation(operation, column_name, distinct)
|
251
|
+
else
|
252
|
+
execute_simple_calculation(operation, column_name, distinct)
|
253
|
+
end
|
202
254
|
end
|
203
255
|
|
204
|
-
|
205
|
-
|
206
|
-
else
|
207
|
-
execute_simple_calculation(operation, column_name, distinct)
|
256
|
+
def distinct_select?(column_name)
|
257
|
+
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
208
258
|
end
|
209
|
-
end
|
210
259
|
|
211
|
-
|
212
|
-
|
260
|
+
def aggregate_column(column_name)
|
261
|
+
return column_name if Arel::Expressions === column_name
|
213
262
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
Arel.sql(column_name == :all ? "*" : column_name.to_s)
|
263
|
+
arel_column(column_name.to_s) do |name|
|
264
|
+
Arel.sql(column_name == :all ? "*" : name)
|
265
|
+
end
|
218
266
|
end
|
219
|
-
end
|
220
267
|
|
221
|
-
|
222
|
-
|
223
|
-
|
268
|
+
def operation_over_aggregate_column(column, operation, distinct)
|
269
|
+
operation == "count" ? column.count(distinct) : column.send(operation)
|
270
|
+
end
|
224
271
|
|
225
|
-
|
226
|
-
|
227
|
-
relation = unscope(:order)
|
272
|
+
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
273
|
+
column_alias = column_name
|
228
274
|
|
229
|
-
|
275
|
+
if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
|
276
|
+
# Shortcut when limit is zero.
|
277
|
+
return 0 if limit_value == 0
|
230
278
|
|
231
|
-
|
232
|
-
|
233
|
-
|
279
|
+
query_builder = build_count_subquery(spawn, column_name, distinct)
|
280
|
+
else
|
281
|
+
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
282
|
+
relation = unscope(:order).distinct!(false)
|
234
283
|
|
235
|
-
|
236
|
-
else
|
237
|
-
column = aggregate_column(column_name)
|
284
|
+
column = aggregate_column(column_name)
|
238
285
|
|
239
|
-
|
286
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
287
|
+
if operation == "sum" && distinct
|
288
|
+
select_value.distinct = true
|
289
|
+
end
|
240
290
|
|
241
|
-
|
242
|
-
|
243
|
-
|
291
|
+
column_alias = select_value.alias
|
292
|
+
column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
|
293
|
+
relation.select_values = [select_value]
|
244
294
|
|
245
|
-
|
246
|
-
|
247
|
-
relation.select_values = [select_value]
|
295
|
+
query_builder = relation.arel
|
296
|
+
end
|
248
297
|
|
249
|
-
|
250
|
-
|
298
|
+
result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) }
|
299
|
+
row = result.first
|
300
|
+
value = row && row.values.first
|
301
|
+
type = result.column_types.fetch(column_alias) do
|
302
|
+
type_for(column_name)
|
303
|
+
end
|
251
304
|
|
252
|
-
|
253
|
-
row = result.first
|
254
|
-
value = row && row.values.first
|
255
|
-
column = result.column_types.fetch(column_alias) do
|
256
|
-
type_for(column_name)
|
305
|
+
type_cast_calculated_value(value, type, operation)
|
257
306
|
end
|
258
307
|
|
259
|
-
|
260
|
-
|
308
|
+
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
309
|
+
group_fields = group_values
|
261
310
|
|
262
|
-
|
263
|
-
|
311
|
+
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
312
|
+
association = klass._reflect_on_association(group_fields.first)
|
313
|
+
associated = association && association.belongs_to? # only count belongs_to associations
|
314
|
+
group_fields = Array(association.foreign_key) if associated
|
315
|
+
end
|
316
|
+
group_fields = arel_columns(group_fields)
|
264
317
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
318
|
+
group_aliases = group_fields.map { |field|
|
319
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
320
|
+
column_alias_for(field.to_s.downcase)
|
321
|
+
}
|
322
|
+
group_columns = group_aliases.zip(group_fields)
|
323
|
+
|
324
|
+
aggregate_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
|
325
|
+
|
326
|
+
select_values = [
|
327
|
+
operation_over_aggregate_column(
|
328
|
+
aggregate_column(column_name),
|
329
|
+
operation,
|
330
|
+
distinct).as(aggregate_alias)
|
331
|
+
]
|
332
|
+
select_values += self.select_values unless having_clause.empty?
|
333
|
+
|
334
|
+
select_values.concat group_columns.map { |aliaz, field|
|
335
|
+
if field.respond_to?(:as)
|
336
|
+
field.as(aliaz)
|
337
|
+
else
|
338
|
+
"#{field} AS #{aliaz}"
|
339
|
+
end
|
340
|
+
}
|
273
341
|
|
274
|
-
|
275
|
-
|
342
|
+
relation = except(:group).distinct!(false)
|
343
|
+
relation.group_values = group_fields
|
344
|
+
relation.select_values = select_values
|
276
345
|
|
277
|
-
|
278
|
-
aggregate_alias = 'count_all'
|
279
|
-
else
|
280
|
-
aggregate_alias = column_alias_for([operation, column_name].join(' '))
|
281
|
-
end
|
346
|
+
calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) }
|
282
347
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
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}"
|
348
|
+
if association
|
349
|
+
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
|
350
|
+
key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
|
351
|
+
key_records = Hash[key_records.map { |r| [r.id, r] }]
|
296
352
|
end
|
297
|
-
}
|
298
|
-
|
299
|
-
relation = except(:group)
|
300
|
-
relation.group_values = group_fields
|
301
|
-
relation.select_values = select_values
|
302
|
-
|
303
|
-
calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
|
304
353
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
end
|
310
|
-
|
311
|
-
Hash[calculated_data.map do |row|
|
312
|
-
key = group_columns.map { |aliaz, col_name|
|
313
|
-
column = type_for(col_name) do
|
314
|
-
calculated_data.column_types.fetch(aliaz) do
|
315
|
-
Type::Value.new
|
354
|
+
Hash[calculated_data.map do |row|
|
355
|
+
key = group_columns.map { |aliaz, col_name|
|
356
|
+
type = type_for(col_name) do
|
357
|
+
calculated_data.column_types.fetch(aliaz, Type.default_value)
|
316
358
|
end
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
end]
|
326
|
-
end
|
327
|
-
|
328
|
-
# Converts the given keys to the value that the database adapter returns as
|
329
|
-
# a usable column name:
|
330
|
-
#
|
331
|
-
# column_alias_for("users.id") # => "users_id"
|
332
|
-
# column_alias_for("sum(id)") # => "sum_id"
|
333
|
-
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
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}"
|
359
|
+
type_cast_calculated_value(row[aliaz], type)
|
360
|
+
}
|
361
|
+
key = key.first if key.size == 1
|
362
|
+
key = key_records[key] if associated
|
363
|
+
|
364
|
+
type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
|
365
|
+
[key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
|
366
|
+
end]
|
338
367
|
end
|
339
368
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
369
|
+
# Converts the given field to the value that the database adapter returns as
|
370
|
+
# a usable column name:
|
371
|
+
#
|
372
|
+
# column_alias_for("users.id") # => "users_id"
|
373
|
+
# column_alias_for("sum(id)") # => "sum_id"
|
374
|
+
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
375
|
+
# column_alias_for("count(*)") # => "count_all"
|
376
|
+
def column_alias_for(field)
|
377
|
+
column_alias = +field
|
378
|
+
column_alias.gsub!(/\*/, "all")
|
379
|
+
column_alias.gsub!(/\W+/, " ")
|
380
|
+
column_alias.strip!
|
381
|
+
column_alias.gsub!(/ +/, "_")
|
382
|
+
|
383
|
+
connection.table_alias_for(column_alias)
|
384
|
+
end
|
348
385
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
386
|
+
def type_for(field, &block)
|
387
|
+
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
388
|
+
@klass.type_for_attribute(field_name, &block)
|
389
|
+
end
|
353
390
|
|
354
|
-
|
355
|
-
|
356
|
-
when
|
357
|
-
when
|
358
|
-
when
|
391
|
+
def type_cast_calculated_value(value, type, operation = nil)
|
392
|
+
case operation
|
393
|
+
when "count" then value.to_i
|
394
|
+
when "sum" then type.deserialize(value || 0)
|
395
|
+
when "average" then value&.respond_to?(:to_d) ? value.to_d : value
|
359
396
|
else type.deserialize(value)
|
397
|
+
end
|
360
398
|
end
|
361
|
-
end
|
362
399
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
400
|
+
def select_for_count
|
401
|
+
if select_values.present?
|
402
|
+
return select_values.first if select_values.one?
|
403
|
+
select_values.join(", ")
|
404
|
+
else
|
405
|
+
:all
|
406
|
+
end
|
369
407
|
end
|
370
|
-
end
|
371
408
|
|
372
|
-
|
373
|
-
|
374
|
-
|
409
|
+
def build_count_subquery(relation, column_name, distinct)
|
410
|
+
if column_name == :all
|
411
|
+
column_alias = Arel.star
|
412
|
+
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
413
|
+
else
|
414
|
+
column_alias = Arel.sql("count_column")
|
415
|
+
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
416
|
+
end
|
375
417
|
|
376
|
-
|
377
|
-
|
378
|
-
subquery = relation.arel.as(subquery_alias)
|
418
|
+
subquery_alias = Arel.sql("subquery_for_count")
|
419
|
+
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
379
420
|
|
380
|
-
|
381
|
-
|
382
|
-
sm.project(select_value).from(subquery)
|
383
|
-
end
|
421
|
+
relation.build_subquery(subquery_alias, select_value)
|
422
|
+
end
|
384
423
|
end
|
385
424
|
end
|