omg-activerecord 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|