activerecord 6.0.0 → 7.2.3
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 +4 -4
- data/CHANGELOG.md +996 -594
- data/MIT-LICENSE +1 -1
- data/README.rdoc +34 -34
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +22 -20
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +41 -30
- data/lib/active_record/associations/association.rb +106 -41
- data/lib/active_record/associations/association_scope.rb +30 -21
- data/lib/active_record/associations/belongs_to_association.rb +69 -14
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
- data/lib/active_record/associations/builder/association.rb +39 -6
- data/lib/active_record/associations/builder/belongs_to.rb +47 -17
- data/lib/active_record/associations/builder/collection_association.rb +14 -6
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
- data/lib/active_record/associations/builder/has_many.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +13 -16
- data/lib/active_record/associations/builder/singular_association.rb +7 -3
- data/lib/active_record/associations/collection_association.rb +90 -53
- data/lib/active_record/associations/collection_proxy.rb +54 -19
- 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 +21 -1
- data/lib/active_record/associations/has_many_association.rb +41 -10
- data/lib/active_record/associations/has_many_through_association.rb +29 -12
- data/lib/active_record/associations/has_one_association.rb +33 -9
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/join_dependency.rb +97 -54
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +237 -54
- 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 +51 -17
- data/lib/active_record/associations/preloader.rb +55 -121
- data/lib/active_record/associations/singular_association.rb +16 -4
- data/lib/active_record/associations/through_association.rb +26 -15
- data/lib/active_record/associations.rb +454 -440
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +11 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +75 -34
- data/lib/active_record/attribute_methods/primary_key.rb +53 -31
- data/lib/active_record/attribute_methods/query.rb +31 -22
- data/lib/active_record/attribute_methods/read.rb +16 -17
- data/lib/active_record/attribute_methods/serialization.rb +177 -35
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
- data/lib/active_record/attribute_methods/write.rb +16 -28
- data/lib/active_record/attribute_methods.rb +227 -100
- data/lib/active_record/attributes.rb +94 -56
- data/lib/active_record/autosave_association.rb +119 -73
- data/lib/active_record/base.rb +31 -21
- data/lib/active_record/callbacks.rb +168 -55
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -25
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
- data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
- data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
- data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
- data/lib/active_record/connection_adapters/column.rb +28 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
- 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 +54 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- 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 +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
- data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +243 -115
- data/lib/active_record/core.rb +481 -199
- data/lib/active_record/counter_cache.rb +69 -32
- data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
- data/lib/active_record/database_configurations/database_config.rb +77 -10
- data/lib/active_record/database_configurations/hash_config.rb +148 -26
- data/lib/active_record/database_configurations/url_config.rb +44 -45
- data/lib/active_record/database_configurations.rb +190 -114
- data/lib/active_record/delegated_type.rb +279 -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 +5 -6
- 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 +68 -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 +175 -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 +171 -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 +157 -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 +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +224 -73
- data/lib/active_record/errors.rb +254 -36
- data/lib/active_record/explain.rb +30 -17
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +22 -15
- data/lib/active_record/fixture_set/model_metadata.rb +15 -6
- data/lib/active_record/fixture_set/render_context.rb +3 -1
- data/lib/active_record/fixture_set/table_row.rb +88 -16
- data/lib/active_record/fixture_set/table_rows.rb +4 -5
- data/lib/active_record/fixtures.rb +229 -116
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +121 -48
- data/lib/active_record/insert_all.rb +178 -29
- data/lib/active_record/integration.rb +16 -14
- data/lib/active_record/internal_metadata.rb +132 -21
- data/lib/active_record/legacy_yaml_adapter.rb +3 -36
- data/lib/active_record/locking/optimistic.rb +64 -33
- data/lib/active_record/locking/pessimistic.rb +21 -8
- data/lib/active_record/log_subscriber.rb +61 -30
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
- data/lib/active_record/middleware/database_selector.rb +25 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +160 -55
- data/lib/active_record/migration/compatibility.rb +286 -43
- 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 +1 -2
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +421 -193
- data/lib/active_record/model_schema.rb +217 -125
- data/lib/active_record/nested_attributes.rb +62 -27
- data/lib/active_record/no_touching.rb +4 -4
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +322 -319
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -15
- data/lib/active_record/query_logs.rb +193 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +54 -14
- data/lib/active_record/railtie.rb +250 -72
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +312 -197
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +45 -3
- data/lib/active_record/reflection.rb +389 -146
- data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
- data/lib/active_record/relation/batches.rb +214 -73
- data/lib/active_record/relation/calculations.rb +379 -124
- data/lib/active_record/relation/delegation.rb +36 -23
- data/lib/active_record/relation/finder_methods.rb +159 -49
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +41 -33
- data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +79 -53
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +1156 -279
- data/lib/active_record/relation/record_fetch_warning.rb +12 -11
- data/lib/active_record/relation/spawn_methods.rb +10 -9
- data/lib/active_record/relation/where_clause.rb +100 -66
- data/lib/active_record/relation.rb +829 -194
- data/lib/active_record/result.rb +76 -56
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +86 -47
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +140 -33
- data/lib/active_record/schema_migration.rb +74 -29
- data/lib/active_record/scoping/default.rb +73 -19
- data/lib/active_record/scoping/named.rb +10 -28
- data/lib/active_record/scoping.rb +65 -35
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +34 -8
- data/lib/active_record/serialization.rb +11 -4
- data/lib/active_record/signed_id.rb +138 -0
- data/lib/active_record/statement_cache.rb +26 -10
- data/lib/active_record/store.rb +19 -14
- data/lib/active_record/suppressor.rb +15 -17
- data/lib/active_record/table_metadata.rb +46 -36
- data/lib/active_record/tasks/database_tasks.rb +371 -205
- data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
- data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +189 -104
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +35 -25
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +31 -27
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +131 -99
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +33 -18
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +11 -6
- data/lib/active_record/type/time.rb +14 -0
- data/lib/active_record/type/type_map.rb +17 -21
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +7 -2
- data/lib/active_record/type_caster/connection.rb +4 -5
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -8
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +88 -18
- data/lib/active_record/validations.rb +15 -8
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +446 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +4 -8
- data/lib/arel/collectors/bind.rb +8 -1
- data/lib/arel/collectors/composite.rb +15 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/crud.rb +30 -22
- data/lib/arel/delete_manager.rb +23 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +82 -9
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +22 -10
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +14 -13
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +122 -11
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/table_alias.rb +11 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes/update_statement.rb +11 -4
- data/lib/arel/nodes.rb +10 -3
- data/lib/arel/predications.rb +31 -28
- data/lib/arel/select_manager.rb +18 -9
- data/lib/arel/table.rb +21 -10
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +25 -5
- data/lib/arel/visitors/dot.rb +94 -90
- data/lib/arel/visitors/mysql.rb +34 -6
- data/lib/arel/visitors/postgresql.rb +5 -16
- data/lib/arel/visitors/sqlite.rb +25 -1
- data/lib/arel/visitors/to_sql.rb +227 -81
- data/lib/arel/visitors/visitor.rb +2 -3
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +37 -15
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
- data/lib/rails/generators/active_record/migration.rb +9 -3
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
- 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 +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- 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
- metadata +117 -30
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/null_relation.rb +0 -68
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -204
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -157
- data/lib/arel/visitors/oracle.rb +0 -159
- data/lib/arel/visitors/oracle12.rb +0 -66
- data/lib/arel/visitors/where_sql.rb +0 -23
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Locking
|
|
5
|
-
# == What is Optimistic Locking
|
|
5
|
+
# == What is \Optimistic \Locking
|
|
6
6
|
#
|
|
7
7
|
# Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
|
|
8
8
|
# conflicts with the data. It does this by checking whether another process has made changes to a record since
|
|
9
|
-
# it was opened, an
|
|
9
|
+
# it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
|
|
10
10
|
# and the update is ignored.
|
|
11
11
|
#
|
|
12
|
-
# Check out
|
|
12
|
+
# Check out +ActiveRecord::Locking::Pessimistic+ for an alternative.
|
|
13
13
|
#
|
|
14
14
|
# == Usage
|
|
15
15
|
#
|
|
16
16
|
# Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
|
|
17
|
-
# record increments the +lock_version+
|
|
17
|
+
# record increments the integer column +lock_version+ and the locking facilities ensure that records instantiated twice
|
|
18
18
|
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
|
|
19
19
|
#
|
|
20
20
|
# p1 = Person.find(1)
|
|
@@ -56,10 +56,24 @@ module ActiveRecord
|
|
|
56
56
|
class_attribute :lock_optimistically, instance_writer: false, default: true
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
def locking_enabled?
|
|
59
|
+
def locking_enabled? # :nodoc:
|
|
60
60
|
self.class.locking_enabled?
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
def increment!(*, **) # :nodoc:
|
|
64
|
+
super.tap do
|
|
65
|
+
if locking_enabled?
|
|
66
|
+
self[self.class.locking_column] += 1
|
|
67
|
+
clear_attribute_change(self.class.locking_column)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def initialize_dup(other) # :nodoc:
|
|
73
|
+
super
|
|
74
|
+
_clear_locking_column if locking_enabled?
|
|
75
|
+
end
|
|
76
|
+
|
|
63
77
|
private
|
|
64
78
|
def _create_record(attribute_names = self.attribute_names)
|
|
65
79
|
if locking_enabled?
|
|
@@ -80,15 +94,18 @@ module ActiveRecord
|
|
|
80
94
|
|
|
81
95
|
begin
|
|
82
96
|
locking_column = self.class.locking_column
|
|
83
|
-
|
|
97
|
+
lock_attribute_was = @attributes[locking_column]
|
|
98
|
+
|
|
99
|
+
update_constraints = _query_constraints_hash
|
|
100
|
+
|
|
101
|
+
attribute_names = attribute_names.dup if attribute_names.frozen?
|
|
84
102
|
attribute_names << locking_column
|
|
85
103
|
|
|
86
104
|
self[locking_column] += 1
|
|
87
105
|
|
|
88
106
|
affected_rows = self.class._update_record(
|
|
89
107
|
attributes_with_values(attribute_names),
|
|
90
|
-
|
|
91
|
-
locking_column => previous_lock_value
|
|
108
|
+
update_constraints
|
|
92
109
|
)
|
|
93
110
|
|
|
94
111
|
if affected_rows != 1
|
|
@@ -99,28 +116,41 @@ module ActiveRecord
|
|
|
99
116
|
|
|
100
117
|
# If something went wrong, revert the locking_column value.
|
|
101
118
|
rescue Exception
|
|
102
|
-
|
|
119
|
+
@attributes[locking_column] = lock_attribute_was
|
|
103
120
|
raise
|
|
104
121
|
end
|
|
105
122
|
end
|
|
106
123
|
|
|
107
124
|
def destroy_row
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
locking_column = self.class.locking_column
|
|
125
|
+
affected_rows = super
|
|
111
126
|
|
|
112
|
-
affected_rows
|
|
113
|
-
@primary_key => id_in_database,
|
|
114
|
-
locking_column => read_attribute_before_type_cast(locking_column)
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
if affected_rows != 1
|
|
127
|
+
if locking_enabled? && affected_rows != 1
|
|
118
128
|
raise ActiveRecord::StaleObjectError.new(self, "destroy")
|
|
119
129
|
end
|
|
120
130
|
|
|
121
131
|
affected_rows
|
|
122
132
|
end
|
|
123
133
|
|
|
134
|
+
def _lock_value_for_database(locking_column)
|
|
135
|
+
if will_save_change_to_attribute?(locking_column)
|
|
136
|
+
@attributes[locking_column].value_for_database
|
|
137
|
+
else
|
|
138
|
+
@attributes[locking_column].original_value_for_database
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def _clear_locking_column
|
|
143
|
+
self[self.class.locking_column] = nil
|
|
144
|
+
clear_attribute_change(self.class.locking_column)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def _query_constraints_hash
|
|
148
|
+
return super unless locking_enabled?
|
|
149
|
+
|
|
150
|
+
locking_column = self.class.locking_column
|
|
151
|
+
super.merge(locking_column => _lock_value_for_database(locking_column))
|
|
152
|
+
end
|
|
153
|
+
|
|
124
154
|
module ClassMethods
|
|
125
155
|
DEFAULT_LOCKING_COLUMN = "lock_version"
|
|
126
156
|
|
|
@@ -138,10 +168,7 @@ module ActiveRecord
|
|
|
138
168
|
end
|
|
139
169
|
|
|
140
170
|
# The version column used for optimistic locking. Defaults to +lock_version+.
|
|
141
|
-
|
|
142
|
-
@locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
|
|
143
|
-
@locking_column
|
|
144
|
-
end
|
|
171
|
+
attr_reader :locking_column
|
|
145
172
|
|
|
146
173
|
# Reset the column used for optimistic locking back to the +lock_version+ default.
|
|
147
174
|
def reset_locking_column
|
|
@@ -156,19 +183,19 @@ module ActiveRecord
|
|
|
156
183
|
end
|
|
157
184
|
|
|
158
185
|
private
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# sub class being decorated. As such, changes to `lock_optimistically`, or
|
|
163
|
-
# `locking_column` would not be picked up.
|
|
164
|
-
def inherited(subclass)
|
|
165
|
-
subclass.class_eval do
|
|
166
|
-
is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
|
|
167
|
-
decorate_matching_attribute_types(is_lock_column, "_optimistic_locking") do |type|
|
|
168
|
-
LockingType.new(type)
|
|
169
|
-
end
|
|
186
|
+
def hook_attribute_type(name, cast_type)
|
|
187
|
+
if lock_optimistically && name == locking_column
|
|
188
|
+
cast_type = LockingType.new(cast_type)
|
|
170
189
|
end
|
|
190
|
+
|
|
191
|
+
super
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def inherited(base)
|
|
171
195
|
super
|
|
196
|
+
base.class_eval do
|
|
197
|
+
@locking_column = DEFAULT_LOCKING_COLUMN
|
|
198
|
+
end
|
|
172
199
|
end
|
|
173
200
|
end
|
|
174
201
|
end
|
|
@@ -177,6 +204,10 @@ module ActiveRecord
|
|
|
177
204
|
# `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
|
|
178
205
|
# during update record.
|
|
179
206
|
class LockingType < DelegateClass(Type::Value) # :nodoc:
|
|
207
|
+
def self.new(subtype)
|
|
208
|
+
self === subtype ? subtype : super
|
|
209
|
+
end
|
|
210
|
+
|
|
180
211
|
def deserialize(value)
|
|
181
212
|
super.to_i
|
|
182
213
|
end
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Locking
|
|
5
|
+
# = \Pessimistic \Locking
|
|
6
|
+
#
|
|
5
7
|
# Locking::Pessimistic provides support for row-level locking using
|
|
6
8
|
# SELECT ... FOR UPDATE and other lock types.
|
|
7
9
|
#
|
|
8
|
-
# Chain <tt>ActiveRecord::Base#find</tt> to
|
|
10
|
+
# Chain <tt>ActiveRecord::Base#find</tt> to ActiveRecord::QueryMethods#lock to obtain an exclusive
|
|
9
11
|
# lock on the selected rows:
|
|
10
12
|
# # select * from accounts where id=1 for update
|
|
11
13
|
# Account.lock.find(1)
|
|
@@ -53,8 +55,12 @@ module ActiveRecord
|
|
|
53
55
|
# end
|
|
54
56
|
#
|
|
55
57
|
# Database-specific information on row locking:
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
+
#
|
|
59
|
+
# [MySQL]
|
|
60
|
+
# https://dev.mysql.com/doc/refman/en/innodb-locking-reads.html
|
|
61
|
+
#
|
|
62
|
+
# [PostgreSQL]
|
|
63
|
+
# https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
|
|
58
64
|
module Pessimistic
|
|
59
65
|
# Obtain a row lock on this record. Reloads the record to obtain the requested
|
|
60
66
|
# lock. Pass an SQL locking clause to append the end of the SELECT statement
|
|
@@ -67,6 +73,7 @@ module ActiveRecord
|
|
|
67
73
|
Locking a record with unpersisted changes is not supported. Use
|
|
68
74
|
`save` to persist the changes, or `reload` to discard them
|
|
69
75
|
explicitly.
|
|
76
|
+
Changed attributes: #{changed.map(&:inspect).join(', ')}.
|
|
70
77
|
MSG
|
|
71
78
|
end
|
|
72
79
|
|
|
@@ -75,11 +82,17 @@ module ActiveRecord
|
|
|
75
82
|
self
|
|
76
83
|
end
|
|
77
84
|
|
|
78
|
-
# Wraps the passed block in a transaction,
|
|
79
|
-
# before yielding. You can pass the SQL locking clause
|
|
80
|
-
# as argument (see
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
# Wraps the passed block in a transaction, reloading the object with a
|
|
86
|
+
# lock before yielding. You can pass the SQL locking clause
|
|
87
|
+
# as an optional argument (see #lock!).
|
|
88
|
+
#
|
|
89
|
+
# You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
|
|
90
|
+
# and <tt>joinable:</tt> to the wrapping transaction (see
|
|
91
|
+
# ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction).
|
|
92
|
+
def with_lock(*args)
|
|
93
|
+
transaction_opts = args.extract_options!
|
|
94
|
+
lock = args.present? ? args.first : true
|
|
95
|
+
transaction(**transaction_opts) do
|
|
83
96
|
lock!(lock)
|
|
84
97
|
yield
|
|
85
98
|
end
|
|
@@ -6,44 +6,56 @@ module ActiveRecord
|
|
|
6
6
|
|
|
7
7
|
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.reset_runtime
|
|
18
|
-
rt, self.runtime = runtime, 0
|
|
19
|
-
rt
|
|
9
|
+
def strict_loading_violation(event)
|
|
10
|
+
debug do
|
|
11
|
+
owner = event.payload[:owner]
|
|
12
|
+
reflection = event.payload[:reflection]
|
|
13
|
+
color(reflection.strict_loading_violation_message(owner), RED)
|
|
14
|
+
end
|
|
20
15
|
end
|
|
16
|
+
subscribe_log_level :strict_loading_violation, :debug
|
|
21
17
|
|
|
22
18
|
def sql(event)
|
|
23
|
-
self.class.runtime += event.duration
|
|
24
|
-
return unless logger.debug?
|
|
25
|
-
|
|
26
19
|
payload = event.payload
|
|
27
20
|
|
|
28
21
|
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
|
29
22
|
|
|
30
|
-
name
|
|
23
|
+
name = if payload[:async]
|
|
24
|
+
"ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (db time #{event.duration.round(1)}ms)"
|
|
25
|
+
else
|
|
26
|
+
"#{payload[:name]} (#{event.duration.round(1)}ms)"
|
|
27
|
+
end
|
|
31
28
|
name = "CACHE #{name}" if payload[:cached]
|
|
32
29
|
sql = payload[:sql]
|
|
33
30
|
binds = nil
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
if payload[:binds]&.any?
|
|
36
33
|
casted_params = type_casted_binds(payload[:type_casted_binds])
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
|
|
35
|
+
binds = []
|
|
36
|
+
payload[:binds].each_with_index do |attr, i|
|
|
37
|
+
attribute_name = if attr.respond_to?(:name)
|
|
38
|
+
attr.name
|
|
39
|
+
elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
|
|
40
|
+
attr[i].name
|
|
41
|
+
else
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
filtered_params = filter(attribute_name, casted_params[i])
|
|
46
|
+
|
|
47
|
+
binds << render_bind(attr, filtered_params)
|
|
48
|
+
end
|
|
49
|
+
binds = binds.inspect
|
|
50
|
+
binds.prepend(" ")
|
|
40
51
|
end
|
|
41
52
|
|
|
42
53
|
name = colorize_payload_name(name, payload[:name])
|
|
43
|
-
sql = color(sql, sql_color(sql), true)
|
|
54
|
+
sql = color(sql, sql_color(sql), bold: true) if colorize_logging
|
|
44
55
|
|
|
45
56
|
debug " #{name} #{sql}#{binds}"
|
|
46
57
|
end
|
|
58
|
+
subscribe_log_level :sql, :debug
|
|
47
59
|
|
|
48
60
|
private
|
|
49
61
|
def type_casted_binds(casted_binds)
|
|
@@ -51,20 +63,25 @@ module ActiveRecord
|
|
|
51
63
|
end
|
|
52
64
|
|
|
53
65
|
def render_bind(attr, value)
|
|
54
|
-
|
|
66
|
+
case attr
|
|
67
|
+
when ActiveModel::Attribute
|
|
68
|
+
if attr.type.binary? && attr.value
|
|
69
|
+
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
|
|
70
|
+
end
|
|
71
|
+
when Array
|
|
55
72
|
attr = attr.first
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
else
|
|
74
|
+
attr = nil
|
|
58
75
|
end
|
|
59
76
|
|
|
60
|
-
[attr
|
|
77
|
+
[attr&.name, value]
|
|
61
78
|
end
|
|
62
79
|
|
|
63
80
|
def colorize_payload_name(name, payload_name)
|
|
64
81
|
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
|
|
65
|
-
color(name, MAGENTA, true)
|
|
82
|
+
color(name, MAGENTA, bold: true)
|
|
66
83
|
else
|
|
67
|
-
color(name, CYAN, true)
|
|
84
|
+
color(name, CYAN, bold: true)
|
|
68
85
|
end
|
|
69
86
|
end
|
|
70
87
|
|
|
@@ -96,21 +113,35 @@ module ActiveRecord
|
|
|
96
113
|
def debug(progname = nil, &block)
|
|
97
114
|
return unless super
|
|
98
115
|
|
|
99
|
-
if ActiveRecord
|
|
116
|
+
if ActiveRecord.verbose_query_logs
|
|
100
117
|
log_query_source
|
|
101
118
|
end
|
|
102
119
|
end
|
|
103
120
|
|
|
104
121
|
def log_query_source
|
|
105
|
-
source =
|
|
122
|
+
source = query_source_location
|
|
106
123
|
|
|
107
124
|
if source
|
|
108
125
|
logger.debug(" ↳ #{source}")
|
|
109
126
|
end
|
|
110
127
|
end
|
|
111
128
|
|
|
112
|
-
|
|
113
|
-
|
|
129
|
+
if Thread.respond_to?(:each_caller_location)
|
|
130
|
+
def query_source_location
|
|
131
|
+
Thread.each_caller_location do |location|
|
|
132
|
+
frame = backtrace_cleaner.clean_frame(location)
|
|
133
|
+
return frame if frame
|
|
134
|
+
end
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
else
|
|
138
|
+
def query_source_location
|
|
139
|
+
backtrace_cleaner.clean(caller(1).lazy).first
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def filter(name, value)
|
|
144
|
+
ActiveRecord::Base.inspection_filter.filter_param(name, value)
|
|
114
145
|
end
|
|
115
146
|
end
|
|
116
147
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Marshalling
|
|
5
|
+
@format_version = 6.1
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
attr_reader :format_version
|
|
9
|
+
|
|
10
|
+
def format_version=(version)
|
|
11
|
+
case version
|
|
12
|
+
when 6.1
|
|
13
|
+
Methods.remove_method(:marshal_dump) if Methods.method_defined?(:marshal_dump)
|
|
14
|
+
when 7.1
|
|
15
|
+
Methods.alias_method(:marshal_dump, :_marshal_dump_7_1)
|
|
16
|
+
else
|
|
17
|
+
raise ArgumentError, "Unknown marshalling format: #{version.inspect}"
|
|
18
|
+
end
|
|
19
|
+
@format_version = version
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module Methods
|
|
24
|
+
def _marshal_dump_7_1
|
|
25
|
+
payload = [attributes_for_database, new_record?]
|
|
26
|
+
|
|
27
|
+
cached_associations = self.class.reflect_on_all_associations.select do |reflection|
|
|
28
|
+
if association_cached?(reflection.name)
|
|
29
|
+
association = association(reflection.name)
|
|
30
|
+
association.loaded? || association.target.present?
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
unless cached_associations.empty?
|
|
35
|
+
payload << cached_associations.map do |reflection|
|
|
36
|
+
[reflection.name, association(reflection.name).target]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
payload
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def marshal_load(state)
|
|
44
|
+
attributes_from_database, new_record, associations = state
|
|
45
|
+
|
|
46
|
+
attributes = self.class.attributes_builder.build_from_database(attributes_from_database)
|
|
47
|
+
init_with_attributes(attributes, new_record)
|
|
48
|
+
|
|
49
|
+
if associations
|
|
50
|
+
associations.each do |name, target|
|
|
51
|
+
association(name).target = target
|
|
52
|
+
rescue ActiveRecord::AssociationNotFoundError
|
|
53
|
+
# the association no longer exist, we can just skip it.
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module MessagePack # :nodoc:
|
|
5
|
+
FORMAT_VERSION = 1
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def dump(input)
|
|
9
|
+
encoder = Encoder.new
|
|
10
|
+
[FORMAT_VERSION, encoder.encode(input), encoder.entries]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def load(dumped)
|
|
14
|
+
format_version, top_level, entries = dumped
|
|
15
|
+
unless format_version == FORMAT_VERSION
|
|
16
|
+
raise "Invalid format version: #{format_version.inspect}"
|
|
17
|
+
end
|
|
18
|
+
Decoder.new(entries).decode(top_level)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module Extensions
|
|
23
|
+
extend self
|
|
24
|
+
|
|
25
|
+
def install(registry)
|
|
26
|
+
registry.register_type 119, ActiveModel::Type::Binary::Data,
|
|
27
|
+
packer: :to_s,
|
|
28
|
+
unpacker: :new
|
|
29
|
+
|
|
30
|
+
registry.register_type 120, ActiveRecord::Base,
|
|
31
|
+
packer: method(:write_record),
|
|
32
|
+
unpacker: method(:read_record),
|
|
33
|
+
recursive: true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def write_record(record, packer)
|
|
37
|
+
packer.write(ActiveRecord::MessagePack.dump(record))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def read_record(unpacker)
|
|
41
|
+
ActiveRecord::MessagePack.load(unpacker.read)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Encoder
|
|
46
|
+
attr_reader :entries
|
|
47
|
+
|
|
48
|
+
def initialize
|
|
49
|
+
@entries = []
|
|
50
|
+
@refs = {}.compare_by_identity
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def encode(input)
|
|
54
|
+
if input.is_a?(Array)
|
|
55
|
+
input.map { |record| encode_record(record) }
|
|
56
|
+
elsif input
|
|
57
|
+
encode_record(input)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def encode_record(record)
|
|
62
|
+
ref = @refs[record]
|
|
63
|
+
|
|
64
|
+
if !ref
|
|
65
|
+
ref = @refs[record] = @entries.size
|
|
66
|
+
@entries << build_entry(record)
|
|
67
|
+
add_cached_associations(record, @entries.last)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
ref
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def build_entry(record)
|
|
74
|
+
[
|
|
75
|
+
ActiveSupport::MessagePack::Extensions.dump_class(record.class),
|
|
76
|
+
record.attributes_for_database,
|
|
77
|
+
record.new_record?
|
|
78
|
+
]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def add_cached_associations(record, entry)
|
|
82
|
+
record.class.normalized_reflections.each_value do |reflection|
|
|
83
|
+
if record.association_cached?(reflection.name) && record.association(reflection.name).loaded?
|
|
84
|
+
entry << reflection.name << encode(record.association(reflection.name).target)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class Decoder
|
|
91
|
+
def initialize(entries)
|
|
92
|
+
@records = entries.map { |entry| build_record(entry) }
|
|
93
|
+
@records.zip(entries) { |record, entry| resolve_cached_associations(record, entry) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def decode(ref)
|
|
97
|
+
if ref.is_a?(Array)
|
|
98
|
+
ref.map { |r| @records[r] }
|
|
99
|
+
elsif ref
|
|
100
|
+
@records[ref]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def build_record(entry)
|
|
105
|
+
class_name, attributes_hash, is_new_record, * = entry
|
|
106
|
+
klass = ActiveSupport::MessagePack::Extensions.load_class(class_name)
|
|
107
|
+
attributes = klass.attributes_builder.build_from_database(attributes_hash)
|
|
108
|
+
klass.allocate.init_with_attributes(attributes, is_new_record)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def resolve_cached_associations(record, entry)
|
|
112
|
+
i = 3 # entry == [class_name, attributes_hash, is_new_record, *associations]
|
|
113
|
+
while i < entry.length
|
|
114
|
+
begin
|
|
115
|
+
record.association(entry[i]).target = decode(entry[i + 1])
|
|
116
|
+
rescue ActiveRecord::AssociationNotFoundError
|
|
117
|
+
# The association no longer exists, so just skip it.
|
|
118
|
+
end
|
|
119
|
+
i += 2
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_record/middleware/database_selector/resolver/session"
|
|
4
|
+
require "active_support/core_ext/numeric/time"
|
|
4
5
|
|
|
5
6
|
module ActiveRecord
|
|
6
7
|
module Middleware
|
|
@@ -43,34 +44,33 @@ module ActiveRecord
|
|
|
43
44
|
write_to_primary(&blk)
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
def update_context(response)
|
|
48
|
+
context.save(response)
|
|
49
|
+
end
|
|
47
50
|
|
|
51
|
+
def reading_request?(request)
|
|
52
|
+
request.get? || request.head?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
48
56
|
def read_from_primary(&blk)
|
|
49
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
|
50
|
-
|
|
51
|
-
instrumenter.instrument("database_selector.active_record.read_from_primary") do
|
|
52
|
-
yield
|
|
53
|
-
end
|
|
54
|
-
end
|
|
57
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: true) do
|
|
58
|
+
instrumenter.instrument("database_selector.active_record.read_from_primary", &blk)
|
|
55
59
|
end
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
def read_from_replica(&blk)
|
|
59
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
|
60
|
-
instrumenter.instrument("database_selector.active_record.read_from_replica")
|
|
61
|
-
yield
|
|
62
|
-
end
|
|
63
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.reading_role, prevent_writes: true) do
|
|
64
|
+
instrumenter.instrument("database_selector.active_record.read_from_replica", &blk)
|
|
63
65
|
end
|
|
64
66
|
end
|
|
65
67
|
|
|
66
|
-
def write_to_primary
|
|
67
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
context.update_last_write_timestamp
|
|
73
|
-
end
|
|
68
|
+
def write_to_primary
|
|
69
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: false) do
|
|
70
|
+
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
|
|
71
|
+
yield
|
|
72
|
+
ensure
|
|
73
|
+
context.update_last_write_timestamp
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
end
|