omg-activerecord 8.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +355 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +219 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record/aggregations.rb +287 -0
- data/lib/active_record/association_relation.rb +50 -0
- data/lib/active_record/associations/alias_tracker.rb +90 -0
- data/lib/active_record/associations/association.rb +417 -0
- data/lib/active_record/associations/association_scope.rb +175 -0
- data/lib/active_record/associations/belongs_to_association.rb +163 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/builder/association.rb +170 -0
- data/lib/active_record/associations/builder/belongs_to.rb +160 -0
- data/lib/active_record/associations/builder/collection_association.rb +80 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
- data/lib/active_record/associations/builder/has_many.rb +23 -0
- data/lib/active_record/associations/builder/has_one.rb +61 -0
- data/lib/active_record/associations/builder/singular_association.rb +48 -0
- data/lib/active_record/associations/collection_association.rb +535 -0
- data/lib/active_record/associations/collection_proxy.rb +1163 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +40 -0
- data/lib/active_record/associations/has_many_association.rb +167 -0
- data/lib/active_record/associations/has_many_through_association.rb +232 -0
- data/lib/active_record/associations/has_one_association.rb +142 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/join_dependency.rb +301 -0
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +316 -0
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +150 -0
- data/lib/active_record/associations/preloader.rb +135 -0
- data/lib/active_record/associations/singular_association.rb +76 -0
- data/lib/active_record/associations/through_association.rb +132 -0
- data/lib/active_record/associations.rb +1897 -0
- data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
- data/lib/active_record/attribute_assignment.rb +82 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +262 -0
- data/lib/active_record/attribute_methods/primary_key.rb +158 -0
- data/lib/active_record/attribute_methods/query.rb +50 -0
- data/lib/active_record/attribute_methods/read.rb +46 -0
- data/lib/active_record/attribute_methods/serialization.rb +232 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
- data/lib/active_record/attribute_methods/write.rb +49 -0
- data/lib/active_record/attribute_methods.rb +542 -0
- data/lib/active_record/attributes.rb +307 -0
- data/lib/active_record/autosave_association.rb +586 -0
- data/lib/active_record/base.rb +338 -0
- data/lib/active_record/callbacks.rb +452 -0
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
- data/lib/active_record/connection_adapters/column.rb +122 -0
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +125 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +413 -0
- data/lib/active_record/core.rb +836 -0
- data/lib/active_record/counter_cache.rb +230 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
- data/lib/active_record/database_configurations/database_config.rb +104 -0
- data/lib/active_record/database_configurations/hash_config.rb +172 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/database_configurations.rb +309 -0
- data/lib/active_record/delegated_type.rb +289 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +121 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +70 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +184 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +177 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +159 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +107 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +424 -0
- data/lib/active_record/errors.rb +614 -0
- data/lib/active_record/explain.rb +63 -0
- data/lib/active_record/explain_registry.rb +37 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +89 -0
- data/lib/active_record/fixture_set/model_metadata.rb +42 -0
- data/lib/active_record/fixture_set/render_context.rb +19 -0
- data/lib/active_record/fixture_set/table_row.rb +208 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +850 -0
- data/lib/active_record/future_result.rb +182 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +366 -0
- data/lib/active_record/insert_all.rb +328 -0
- data/lib/active_record/integration.rb +209 -0
- data/lib/active_record/internal_metadata.rb +164 -0
- data/lib/active_record/legacy_yaml_adapter.rb +15 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +228 -0
- data/lib/active_record/locking/pessimistic.rb +102 -0
- data/lib/active_record/log_subscriber.rb +149 -0
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +87 -0
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +406 -0
- data/lib/active_record/migration/compatibility.rb +490 -0
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/join_table.rb +16 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +1626 -0
- data/lib/active_record/model_schema.rb +635 -0
- data/lib/active_record/nested_attributes.rb +633 -0
- data/lib/active_record/no_touching.rb +65 -0
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +968 -0
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/query_logs.rb +247 -0
- data/lib/active_record/query_logs_formatter.rb +30 -0
- data/lib/active_record/querying.rb +122 -0
- data/lib/active_record/railtie.rb +440 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +65 -0
- data/lib/active_record/railties/databases.rake +641 -0
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +66 -0
- data/lib/active_record/reflection.rb +1287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
- data/lib/active_record/relation/batches.rb +491 -0
- data/lib/active_record/relation/calculations.rb +679 -0
- data/lib/active_record/relation/delegation.rb +154 -0
- data/lib/active_record/relation/finder_methods.rb +661 -0
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +192 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
- data/lib/active_record/relation/predicate_builder.rb +181 -0
- data/lib/active_record/relation/query_attribute.rb +68 -0
- data/lib/active_record/relation/query_methods.rb +2235 -0
- data/lib/active_record/relation/record_fetch_warning.rb +52 -0
- data/lib/active_record/relation/spawn_methods.rb +78 -0
- data/lib/active_record/relation/where_clause.rb +218 -0
- data/lib/active_record/relation.rb +1495 -0
- data/lib/active_record/result.rb +249 -0
- data/lib/active_record/runtime_registry.rb +82 -0
- data/lib/active_record/sanitization.rb +254 -0
- data/lib/active_record/schema.rb +77 -0
- data/lib/active_record/schema_dumper.rb +364 -0
- data/lib/active_record/schema_migration.rb +106 -0
- data/lib/active_record/scoping/default.rb +205 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +136 -0
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +66 -0
- data/lib/active_record/serialization.rb +29 -0
- data/lib/active_record/signed_id.rb +137 -0
- data/lib/active_record/statement_cache.rb +164 -0
- data/lib/active_record/store.rb +299 -0
- data/lib/active_record/suppressor.rb +59 -0
- data/lib/active_record/table_metadata.rb +85 -0
- data/lib/active_record/tasks/database_tasks.rb +681 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +321 -0
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +177 -0
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +70 -0
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +523 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/adapter_specific_registry.rb +144 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
- data/lib/active_record/type/internal/timezone.rb +22 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +76 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +35 -0
- data/lib/active_record/type/type_map.rb +58 -0
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type.rb +83 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +65 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +45 -0
- data/lib/active_record/validations/uniqueness.rb +295 -0
- data/lib/active_record/validations.rb +101 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/active_record.rb +616 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +33 -0
- data/lib/arel/collectors/bind.rb +31 -0
- data/lib/arel/collectors/composite.rb +46 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +48 -0
- data/lib/arel/delete_manager.rb +32 -0
- data/lib/arel/errors.rb +19 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +53 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +48 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +125 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +44 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +45 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/nary.rb +39 -0
- data/lib/arel/nodes/node.rb +161 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +32 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +35 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +46 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +75 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +260 -0
- data/lib/arel/select_manager.rb +276 -0
- data/lib/arel/table.rb +121 -0
- data/lib/arel/tree_manager.rb +65 -0
- data/lib/arel/update_manager.rb +49 -0
- data/lib/arel/visitors/dot.rb +299 -0
- data/lib/arel/visitors/mysql.rb +111 -0
- data/lib/arel/visitors/postgresql.rb +99 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +1033 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +73 -0
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
- data/lib/rails/generators/active_record/migration.rb +54 -0
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- data/lib/rails/generators/active_record.rb +19 -0
- metadata +505 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Batches
|
|
5
|
+
class BatchEnumerator
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
def initialize(of: 1000, start: nil, finish: nil, relation:, cursor:, order: :asc, use_ranges: nil) # :nodoc:
|
|
9
|
+
@of = of
|
|
10
|
+
@relation = relation
|
|
11
|
+
@start = start
|
|
12
|
+
@finish = finish
|
|
13
|
+
@cursor = cursor
|
|
14
|
+
@order = order
|
|
15
|
+
@use_ranges = use_ranges
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# The primary key value from which the BatchEnumerator starts, inclusive of the value.
|
|
19
|
+
attr_reader :start
|
|
20
|
+
|
|
21
|
+
# The primary key value at which the BatchEnumerator ends, inclusive of the value.
|
|
22
|
+
attr_reader :finish
|
|
23
|
+
|
|
24
|
+
# The relation from which the BatchEnumerator yields batches.
|
|
25
|
+
attr_reader :relation
|
|
26
|
+
|
|
27
|
+
# The size of the batches yielded by the BatchEnumerator.
|
|
28
|
+
def batch_size
|
|
29
|
+
@of
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Looping through a collection of records from the database (using the
|
|
33
|
+
# +all+ method, for example) is very inefficient since it will try to
|
|
34
|
+
# instantiate all the objects at once.
|
|
35
|
+
#
|
|
36
|
+
# In that case, batch processing methods allow you to work with the
|
|
37
|
+
# records in batches, thereby greatly reducing memory consumption.
|
|
38
|
+
#
|
|
39
|
+
# Person.in_batches.each_record do |person|
|
|
40
|
+
# person.do_awesome_stuff
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# Person.where("age > 21").in_batches(of: 10).each_record do |person|
|
|
44
|
+
# person.party_all_night!
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# If you do not provide a block to #each_record, it will return an Enumerator
|
|
48
|
+
# for chaining with other methods:
|
|
49
|
+
#
|
|
50
|
+
# Person.in_batches.each_record.with_index do |person, index|
|
|
51
|
+
# person.award_trophy(index + 1)
|
|
52
|
+
# end
|
|
53
|
+
def each_record(&block)
|
|
54
|
+
return to_enum(:each_record) unless block_given?
|
|
55
|
+
|
|
56
|
+
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true, cursor: @cursor, order: @order).each do |relation|
|
|
57
|
+
relation.records.each(&block)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Deletes records in batches. Returns the total number of rows affected.
|
|
62
|
+
#
|
|
63
|
+
# Person.in_batches.delete_all
|
|
64
|
+
#
|
|
65
|
+
# See Relation#delete_all for details of how each batch is deleted.
|
|
66
|
+
def delete_all
|
|
67
|
+
sum(&:delete_all)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Updates records in batches. Returns the total number of rows affected.
|
|
71
|
+
#
|
|
72
|
+
# Person.in_batches.update_all("age = age + 1")
|
|
73
|
+
#
|
|
74
|
+
# See Relation#update_all for details of how each batch is updated.
|
|
75
|
+
def update_all(updates)
|
|
76
|
+
sum do |relation|
|
|
77
|
+
relation.update_all(updates)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Touches records in batches. Returns the total number of rows affected.
|
|
82
|
+
#
|
|
83
|
+
# Person.in_batches.touch_all
|
|
84
|
+
#
|
|
85
|
+
# See Relation#touch_all for details of how each batch is touched.
|
|
86
|
+
def touch_all(...)
|
|
87
|
+
sum do |relation|
|
|
88
|
+
relation.touch_all(...)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Destroys records in batches. Returns the total number of rows affected.
|
|
93
|
+
#
|
|
94
|
+
# Person.where("age < 10").in_batches.destroy_all
|
|
95
|
+
#
|
|
96
|
+
# See Relation#destroy_all for details of how each batch is destroyed.
|
|
97
|
+
def destroy_all
|
|
98
|
+
sum do |relation|
|
|
99
|
+
relation.destroy_all.count(&:destroyed?)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Yields an ActiveRecord::Relation object for each batch of records.
|
|
104
|
+
#
|
|
105
|
+
# Person.in_batches.each do |relation|
|
|
106
|
+
# relation.update_all(awesome: true)
|
|
107
|
+
# end
|
|
108
|
+
def each(&block)
|
|
109
|
+
enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false, cursor: @cursor, order: @order, use_ranges: @use_ranges)
|
|
110
|
+
return enum.each(&block) if block_given?
|
|
111
|
+
enum
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/relation/batches/batch_enumerator"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
# = Active Record \Batches
|
|
7
|
+
module Batches
|
|
8
|
+
ORDER_IGNORE_MESSAGE = "Scoped order is ignored, use :cursor with :order to configure custom order."
|
|
9
|
+
DEFAULT_ORDER = :asc
|
|
10
|
+
|
|
11
|
+
# Looping through a collection of records from the database
|
|
12
|
+
# (using the Scoping::Named::ClassMethods.all method, for example)
|
|
13
|
+
# is very inefficient since it will try to instantiate all the objects at once.
|
|
14
|
+
#
|
|
15
|
+
# In that case, batch processing methods allow you to work
|
|
16
|
+
# with the records in batches, thereby greatly reducing memory consumption.
|
|
17
|
+
#
|
|
18
|
+
# The #find_each method uses #find_in_batches with a batch size of 1000 (or as
|
|
19
|
+
# specified by the +:batch_size+ option).
|
|
20
|
+
#
|
|
21
|
+
# Person.find_each do |person|
|
|
22
|
+
# person.do_awesome_stuff
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# Person.where("age > 21").find_each do |person|
|
|
26
|
+
# person.party_all_night!
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# If you do not provide a block to #find_each, it will return an Enumerator
|
|
30
|
+
# for chaining with other methods:
|
|
31
|
+
#
|
|
32
|
+
# Person.find_each.with_index do |person, index|
|
|
33
|
+
# person.award_trophy(index + 1)
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# ==== Options
|
|
37
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
|
|
38
|
+
# * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
|
|
39
|
+
# * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
|
|
40
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
|
41
|
+
# an order is present in the relation.
|
|
42
|
+
# * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
|
|
43
|
+
# of column names). Defaults to primary key.
|
|
44
|
+
# * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
|
|
45
|
+
# of :asc or :desc). Defaults to +:asc+.
|
|
46
|
+
#
|
|
47
|
+
# class Order < ActiveRecord::Base
|
|
48
|
+
# self.primary_key = [:id_1, :id_2]
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# Order.find_each(order: [:asc, :desc])
|
|
52
|
+
#
|
|
53
|
+
# In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
|
|
54
|
+
#
|
|
55
|
+
# Limits are honored, and if present there is no requirement for the batch
|
|
56
|
+
# size: it can be less than, equal to, or greater than the limit.
|
|
57
|
+
#
|
|
58
|
+
# The options +start+ and +finish+ are especially useful if you want
|
|
59
|
+
# multiple workers dealing with the same processing queue. You can make
|
|
60
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
|
61
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
|
62
|
+
# option on each worker.
|
|
63
|
+
#
|
|
64
|
+
# # In worker 1, let's process until 9999 records.
|
|
65
|
+
# Person.find_each(finish: 9_999) do |person|
|
|
66
|
+
# person.party_all_night!
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# # In worker 2, let's process from record 10_000 and onwards.
|
|
70
|
+
# Person.find_each(start: 10_000) do |person|
|
|
71
|
+
# person.party_all_night!
|
|
72
|
+
# end
|
|
73
|
+
#
|
|
74
|
+
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
|
75
|
+
# ascending on the primary key ("id ASC").
|
|
76
|
+
# This also means that this method only works when the cursor column is
|
|
77
|
+
# orderable (e.g. an integer or string).
|
|
78
|
+
#
|
|
79
|
+
# NOTE: When using custom columns for batching, they should include at least one unique column
|
|
80
|
+
# (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
|
|
81
|
+
# all columns should be static (unchangeable after it was set).
|
|
82
|
+
#
|
|
83
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
|
84
|
+
# other processes are modifying the database.
|
|
85
|
+
def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, &block)
|
|
86
|
+
if block_given?
|
|
87
|
+
find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do |records|
|
|
88
|
+
records.each(&block)
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do
|
|
92
|
+
relation = self
|
|
93
|
+
cursor = Array(cursor)
|
|
94
|
+
apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Yields each batch of records that was found by the find options as
|
|
100
|
+
# an array.
|
|
101
|
+
#
|
|
102
|
+
# Person.where("age > 21").find_in_batches do |group|
|
|
103
|
+
# sleep(50) # Make sure it doesn't get too crowded in there!
|
|
104
|
+
# group.each { |person| person.party_all_night! }
|
|
105
|
+
# end
|
|
106
|
+
#
|
|
107
|
+
# If you do not provide a block to #find_in_batches, it will return an Enumerator
|
|
108
|
+
# for chaining with other methods:
|
|
109
|
+
#
|
|
110
|
+
# Person.find_in_batches.with_index do |group, batch|
|
|
111
|
+
# puts "Processing group ##{batch}"
|
|
112
|
+
# group.each(&:recover_from_last_night!)
|
|
113
|
+
# end
|
|
114
|
+
#
|
|
115
|
+
# To be yielded each record one by one, use #find_each instead.
|
|
116
|
+
#
|
|
117
|
+
# ==== Options
|
|
118
|
+
# * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
|
|
119
|
+
# * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
|
|
120
|
+
# * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
|
|
121
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
|
122
|
+
# an order is present in the relation.
|
|
123
|
+
# * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
|
|
124
|
+
# of column names). Defaults to primary key.
|
|
125
|
+
# * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
|
|
126
|
+
# of :asc or :desc). Defaults to +:asc+.
|
|
127
|
+
#
|
|
128
|
+
# class Order < ActiveRecord::Base
|
|
129
|
+
# self.primary_key = [:id_1, :id_2]
|
|
130
|
+
# end
|
|
131
|
+
#
|
|
132
|
+
# Order.find_in_batches(order: [:asc, :desc])
|
|
133
|
+
#
|
|
134
|
+
# In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
|
|
135
|
+
#
|
|
136
|
+
# Limits are honored, and if present there is no requirement for the batch
|
|
137
|
+
# size: it can be less than, equal to, or greater than the limit.
|
|
138
|
+
#
|
|
139
|
+
# The options +start+ and +finish+ are especially useful if you want
|
|
140
|
+
# multiple workers dealing with the same processing queue. You can make
|
|
141
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
|
142
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
|
143
|
+
# option on each worker.
|
|
144
|
+
#
|
|
145
|
+
# # Let's process from record 10_000 on.
|
|
146
|
+
# Person.find_in_batches(start: 10_000) do |group|
|
|
147
|
+
# group.each { |person| person.party_all_night! }
|
|
148
|
+
# end
|
|
149
|
+
#
|
|
150
|
+
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
|
151
|
+
# ascending on the primary key ("id ASC").
|
|
152
|
+
# This also means that this method only works when the cursor column is
|
|
153
|
+
# orderable (e.g. an integer or string).
|
|
154
|
+
#
|
|
155
|
+
# NOTE: When using custom columns for batching, they should include at least one unique column
|
|
156
|
+
# (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
|
|
157
|
+
# all columns should be static (unchangeable after it was set).
|
|
158
|
+
#
|
|
159
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
|
160
|
+
# other processes are modifying the database.
|
|
161
|
+
def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER)
|
|
162
|
+
relation = self
|
|
163
|
+
unless block_given?
|
|
164
|
+
return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do
|
|
165
|
+
cursor = Array(cursor)
|
|
166
|
+
total = apply_limits(relation, cursor, start, finish, build_batch_orders(cursor, order)).size
|
|
167
|
+
(total - 1).div(batch_size) + 1
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore, cursor: cursor, order: order) do |batch|
|
|
172
|
+
yield batch.to_a
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Yields ActiveRecord::Relation objects to work with a batch of records.
|
|
177
|
+
#
|
|
178
|
+
# Person.where("age > 21").in_batches do |relation|
|
|
179
|
+
# relation.delete_all
|
|
180
|
+
# sleep(10) # Throttle the delete queries
|
|
181
|
+
# end
|
|
182
|
+
#
|
|
183
|
+
# If you do not provide a block to #in_batches, it will return a
|
|
184
|
+
# BatchEnumerator which is enumerable.
|
|
185
|
+
#
|
|
186
|
+
# Person.in_batches.each_with_index do |relation, batch_index|
|
|
187
|
+
# puts "Processing relation ##{batch_index}"
|
|
188
|
+
# relation.delete_all
|
|
189
|
+
# end
|
|
190
|
+
#
|
|
191
|
+
# Examples of calling methods on the returned BatchEnumerator object:
|
|
192
|
+
#
|
|
193
|
+
# Person.in_batches.delete_all
|
|
194
|
+
# Person.in_batches.update_all(awesome: true)
|
|
195
|
+
# Person.in_batches.each_record(&:party_all_night!)
|
|
196
|
+
#
|
|
197
|
+
# ==== Options
|
|
198
|
+
# * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
|
|
199
|
+
# * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
|
|
200
|
+
# * <tt>:start</tt> - Specifies the cursor column value to start from, inclusive of the value.
|
|
201
|
+
# * <tt>:finish</tt> - Specifies the cursor column value to end at, inclusive of the value.
|
|
202
|
+
# * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
|
|
203
|
+
# an order is present in the relation.
|
|
204
|
+
# * <tt>:cursor</tt> - Specifies the column to use for batching (can be a column name or an array
|
|
205
|
+
# of column names). Defaults to primary key.
|
|
206
|
+
# * <tt>:order</tt> - Specifies the cursor column order (can be +:asc+ or +:desc+ or an array consisting
|
|
207
|
+
# of :asc or :desc). Defaults to +:asc+.
|
|
208
|
+
#
|
|
209
|
+
# class Order < ActiveRecord::Base
|
|
210
|
+
# self.primary_key = [:id_1, :id_2]
|
|
211
|
+
# end
|
|
212
|
+
#
|
|
213
|
+
# Order.in_batches(order: [:asc, :desc])
|
|
214
|
+
#
|
|
215
|
+
# In the above code, +id_1+ is sorted in ascending order and +id_2+ in descending order.
|
|
216
|
+
#
|
|
217
|
+
# * <tt>:use_ranges</tt> - Specifies whether to use range iteration (id >= x AND id <= y).
|
|
218
|
+
# It can make iterating over the whole or almost whole tables several times faster.
|
|
219
|
+
# Only whole table iterations use this style of iteration by default. You can disable this behavior by passing +false+.
|
|
220
|
+
# If you iterate over the table and the only condition is, e.g., <tt>archived_at: nil</tt> (and only a tiny fraction
|
|
221
|
+
# of the records are archived), it makes sense to opt in to this approach.
|
|
222
|
+
#
|
|
223
|
+
# Limits are honored, and if present there is no requirement for the batch
|
|
224
|
+
# size, it can be less than, equal, or greater than the limit.
|
|
225
|
+
#
|
|
226
|
+
# The options +start+ and +finish+ are especially useful if you want
|
|
227
|
+
# multiple workers dealing with the same processing queue. You can make
|
|
228
|
+
# worker 1 handle all the records between id 1 and 9999 and worker 2
|
|
229
|
+
# handle from 10000 and beyond by setting the +:start+ and +:finish+
|
|
230
|
+
# option on each worker.
|
|
231
|
+
#
|
|
232
|
+
# # Let's process from record 10_000 on.
|
|
233
|
+
# Person.in_batches(start: 10_000).update_all(awesome: true)
|
|
234
|
+
#
|
|
235
|
+
# An example of calling where query method on the relation:
|
|
236
|
+
#
|
|
237
|
+
# Person.in_batches.each do |relation|
|
|
238
|
+
# relation.update_all('age = age + 1')
|
|
239
|
+
# relation.where('age > 21').update_all(should_party: true)
|
|
240
|
+
# relation.where('age <= 21').delete_all
|
|
241
|
+
# end
|
|
242
|
+
#
|
|
243
|
+
# NOTE: If you are going to iterate through each record, you should call
|
|
244
|
+
# #each_record on the yielded BatchEnumerator:
|
|
245
|
+
#
|
|
246
|
+
# Person.in_batches.each_record(&:party_all_night!)
|
|
247
|
+
#
|
|
248
|
+
# NOTE: Order can be ascending (:asc) or descending (:desc). It is automatically set to
|
|
249
|
+
# ascending on the primary key ("id ASC").
|
|
250
|
+
# This also means that this method only works when the cursor column is
|
|
251
|
+
# orderable (e.g. an integer or string).
|
|
252
|
+
#
|
|
253
|
+
# NOTE: When using custom columns for batching, they should include at least one unique column
|
|
254
|
+
# (e.g. primary key) as a tiebreaker. Also, to reduce the likelihood of race conditions,
|
|
255
|
+
# all columns should be static (unchangeable after it was set).
|
|
256
|
+
#
|
|
257
|
+
# NOTE: By its nature, batch processing is subject to race conditions if
|
|
258
|
+
# other processes are modifying the database.
|
|
259
|
+
def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil, cursor: primary_key, order: DEFAULT_ORDER, use_ranges: nil, &block)
|
|
260
|
+
cursor = Array(cursor).map(&:to_s)
|
|
261
|
+
ensure_valid_options_for_batching!(cursor, start, finish, order)
|
|
262
|
+
|
|
263
|
+
if arel.orders.present?
|
|
264
|
+
act_on_ignored_order(error_on_ignore)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
unless block
|
|
268
|
+
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self, cursor: cursor, order: order, use_ranges: use_ranges)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
batch_limit = of
|
|
272
|
+
|
|
273
|
+
if limit_value
|
|
274
|
+
remaining = limit_value
|
|
275
|
+
batch_limit = remaining if remaining < batch_limit
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
if self.loaded?
|
|
279
|
+
batch_on_loaded_relation(
|
|
280
|
+
relation: self,
|
|
281
|
+
start: start,
|
|
282
|
+
finish: finish,
|
|
283
|
+
cursor: cursor,
|
|
284
|
+
order: order,
|
|
285
|
+
batch_limit: batch_limit,
|
|
286
|
+
&block
|
|
287
|
+
)
|
|
288
|
+
else
|
|
289
|
+
batch_on_unloaded_relation(
|
|
290
|
+
relation: self,
|
|
291
|
+
start: start,
|
|
292
|
+
finish: finish,
|
|
293
|
+
load: load,
|
|
294
|
+
cursor: cursor,
|
|
295
|
+
order: order,
|
|
296
|
+
use_ranges: use_ranges,
|
|
297
|
+
remaining: remaining,
|
|
298
|
+
batch_limit: batch_limit,
|
|
299
|
+
&block
|
|
300
|
+
)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
private
|
|
305
|
+
def ensure_valid_options_for_batching!(cursor, start, finish, order)
|
|
306
|
+
if start && Array(start).size != cursor.size
|
|
307
|
+
raise ArgumentError, ":start must contain one value per cursor column"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
if finish && Array(finish).size != cursor.size
|
|
311
|
+
raise ArgumentError, ":finish must contain one value per cursor column"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if (Array(primary_key) - cursor).any?
|
|
315
|
+
indexes = model.schema_cache.indexes(table_name)
|
|
316
|
+
unique_index = indexes.find { |index| index.unique && index.where.nil? && (Array(index.columns) - cursor).empty? }
|
|
317
|
+
|
|
318
|
+
unless unique_index
|
|
319
|
+
raise ArgumentError, ":cursor must include a primary key or other unique column(s)"
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
if (Array(order) - [:asc, :desc]).any?
|
|
324
|
+
raise ArgumentError, ":order must be :asc or :desc or an array consisting of :asc or :desc, got #{order.inspect}"
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def apply_limits(relation, cursor, start, finish, batch_orders)
|
|
329
|
+
relation = apply_start_limit(relation, cursor, start, batch_orders) if start
|
|
330
|
+
relation = apply_finish_limit(relation, cursor, finish, batch_orders) if finish
|
|
331
|
+
relation
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def apply_start_limit(relation, cursor, start, batch_orders)
|
|
335
|
+
operators = batch_orders.map do |_column, order|
|
|
336
|
+
order == :desc ? :lteq : :gteq
|
|
337
|
+
end
|
|
338
|
+
batch_condition(relation, cursor, start, operators)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def apply_finish_limit(relation, cursor, finish, batch_orders)
|
|
342
|
+
operators = batch_orders.map do |_column, order|
|
|
343
|
+
order == :desc ? :gteq : :lteq
|
|
344
|
+
end
|
|
345
|
+
batch_condition(relation, cursor, finish, operators)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def batch_condition(relation, cursor, values, operators)
|
|
349
|
+
cursor_positions = cursor.zip(Array(values), operators)
|
|
350
|
+
|
|
351
|
+
first_clause_column, first_clause_value, operator = cursor_positions.pop
|
|
352
|
+
where_clause = predicate_builder[first_clause_column, first_clause_value, operator]
|
|
353
|
+
|
|
354
|
+
cursor_positions.reverse_each do |column_name, value, operator|
|
|
355
|
+
where_clause = predicate_builder[column_name, value, operator == :lteq ? :lt : :gt].or(
|
|
356
|
+
predicate_builder[column_name, value, :eq].and(where_clause)
|
|
357
|
+
)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
relation.where(where_clause)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def build_batch_orders(cursor, order)
|
|
364
|
+
cursor.zip(Array(order)).map do |column, order_|
|
|
365
|
+
[column, order_ || DEFAULT_ORDER]
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def act_on_ignored_order(error_on_ignore)
|
|
370
|
+
raise_error = (error_on_ignore.nil? ? ActiveRecord.error_on_ignored_order : error_on_ignore)
|
|
371
|
+
|
|
372
|
+
if raise_error
|
|
373
|
+
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
|
|
374
|
+
elsif model.logger
|
|
375
|
+
model.logger.warn(ORDER_IGNORE_MESSAGE)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def batch_on_loaded_relation(relation:, start:, finish:, cursor:, order:, batch_limit:)
|
|
380
|
+
records = relation.to_a
|
|
381
|
+
order = build_batch_orders(cursor, order).map(&:second)
|
|
382
|
+
|
|
383
|
+
if start || finish
|
|
384
|
+
records = records.filter do |record|
|
|
385
|
+
values = record_cursor_values(record, cursor)
|
|
386
|
+
|
|
387
|
+
(start.nil? || compare_values_for_order(values, Array(start), order) >= 0) &&
|
|
388
|
+
(finish.nil? || compare_values_for_order(values, Array(finish), order) <= 0)
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
records.sort! do |record1, record2|
|
|
393
|
+
values1 = record_cursor_values(record1, cursor)
|
|
394
|
+
values2 = record_cursor_values(record2, cursor)
|
|
395
|
+
compare_values_for_order(values1, values2, order)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
records.each_slice(batch_limit) do |subrecords|
|
|
399
|
+
subrelation = relation.spawn
|
|
400
|
+
subrelation.load_records(subrecords)
|
|
401
|
+
|
|
402
|
+
yield subrelation
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
nil
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def record_cursor_values(record, cursor)
|
|
409
|
+
record.attributes.slice(*cursor).values
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# This is a custom implementation of `<=>` operator,
|
|
413
|
+
# which also takes into account how the collection will be ordered.
|
|
414
|
+
def compare_values_for_order(values1, values2, order)
|
|
415
|
+
values1.each_with_index do |element1, index|
|
|
416
|
+
element2 = values2[index]
|
|
417
|
+
direction = order[index]
|
|
418
|
+
comparison = element1 <=> element2
|
|
419
|
+
comparison = -comparison if direction == :desc
|
|
420
|
+
return comparison if comparison != 0
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
0
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def batch_on_unloaded_relation(relation:, start:, finish:, load:, cursor:, order:, use_ranges:, remaining:, batch_limit:)
|
|
427
|
+
batch_orders = build_batch_orders(cursor, order)
|
|
428
|
+
relation = relation.reorder(batch_orders.to_h).limit(batch_limit)
|
|
429
|
+
relation = apply_limits(relation, cursor, start, finish, batch_orders)
|
|
430
|
+
relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
|
|
431
|
+
batch_relation = relation
|
|
432
|
+
empty_scope = to_sql == model.unscoped.all.to_sql
|
|
433
|
+
|
|
434
|
+
loop do
|
|
435
|
+
if load
|
|
436
|
+
records = batch_relation.records
|
|
437
|
+
values = records.pluck(*cursor)
|
|
438
|
+
yielded_relation = where(cursor => values)
|
|
439
|
+
yielded_relation.load_records(records)
|
|
440
|
+
elsif (empty_scope && use_ranges != false) || use_ranges
|
|
441
|
+
values = batch_relation.pluck(*cursor)
|
|
442
|
+
|
|
443
|
+
finish = values.last
|
|
444
|
+
if finish
|
|
445
|
+
yielded_relation = apply_finish_limit(batch_relation, cursor, finish, batch_orders)
|
|
446
|
+
yielded_relation = yielded_relation.except(:limit, :order)
|
|
447
|
+
yielded_relation.skip_query_cache!(false)
|
|
448
|
+
end
|
|
449
|
+
else
|
|
450
|
+
values = batch_relation.pluck(*cursor)
|
|
451
|
+
yielded_relation = where(cursor => values)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
break if values.empty?
|
|
455
|
+
|
|
456
|
+
if values.flatten.any?(nil)
|
|
457
|
+
raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
|
|
458
|
+
"or some columns contain nil."
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
yield yielded_relation
|
|
462
|
+
|
|
463
|
+
break if values.length < batch_limit
|
|
464
|
+
|
|
465
|
+
if limit_value
|
|
466
|
+
remaining -= values.length
|
|
467
|
+
|
|
468
|
+
if remaining == 0
|
|
469
|
+
# Saves a useless iteration when the limit is a multiple of the
|
|
470
|
+
# batch size.
|
|
471
|
+
break
|
|
472
|
+
elsif remaining < batch_limit
|
|
473
|
+
relation = relation.limit(remaining)
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
batch_orders_copy = batch_orders.dup
|
|
478
|
+
_last_column, last_order = batch_orders_copy.pop
|
|
479
|
+
operators = batch_orders_copy.map do |_column, order|
|
|
480
|
+
order == :desc ? :lteq : :gteq
|
|
481
|
+
end
|
|
482
|
+
operators << (last_order == :desc ? :lt : :gt)
|
|
483
|
+
|
|
484
|
+
cursor_value = values.last
|
|
485
|
+
batch_relation = batch_condition(relation, cursor, cursor_value, operators)
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
nil
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
end
|