omg-activerecord 8.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +355 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +219 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record/aggregations.rb +287 -0
- data/lib/active_record/association_relation.rb +50 -0
- data/lib/active_record/associations/alias_tracker.rb +90 -0
- data/lib/active_record/associations/association.rb +417 -0
- data/lib/active_record/associations/association_scope.rb +175 -0
- data/lib/active_record/associations/belongs_to_association.rb +163 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/builder/association.rb +170 -0
- data/lib/active_record/associations/builder/belongs_to.rb +160 -0
- data/lib/active_record/associations/builder/collection_association.rb +80 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
- data/lib/active_record/associations/builder/has_many.rb +23 -0
- data/lib/active_record/associations/builder/has_one.rb +61 -0
- data/lib/active_record/associations/builder/singular_association.rb +48 -0
- data/lib/active_record/associations/collection_association.rb +535 -0
- data/lib/active_record/associations/collection_proxy.rb +1163 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +40 -0
- data/lib/active_record/associations/has_many_association.rb +167 -0
- data/lib/active_record/associations/has_many_through_association.rb +232 -0
- data/lib/active_record/associations/has_one_association.rb +142 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/join_dependency.rb +301 -0
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +316 -0
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +150 -0
- data/lib/active_record/associations/preloader.rb +135 -0
- data/lib/active_record/associations/singular_association.rb +76 -0
- data/lib/active_record/associations/through_association.rb +132 -0
- data/lib/active_record/associations.rb +1897 -0
- data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
- data/lib/active_record/attribute_assignment.rb +82 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +262 -0
- data/lib/active_record/attribute_methods/primary_key.rb +158 -0
- data/lib/active_record/attribute_methods/query.rb +50 -0
- data/lib/active_record/attribute_methods/read.rb +46 -0
- data/lib/active_record/attribute_methods/serialization.rb +232 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
- data/lib/active_record/attribute_methods/write.rb +49 -0
- data/lib/active_record/attribute_methods.rb +542 -0
- data/lib/active_record/attributes.rb +307 -0
- data/lib/active_record/autosave_association.rb +586 -0
- data/lib/active_record/base.rb +338 -0
- data/lib/active_record/callbacks.rb +452 -0
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
- data/lib/active_record/connection_adapters/column.rb +122 -0
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +125 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +413 -0
- data/lib/active_record/core.rb +836 -0
- data/lib/active_record/counter_cache.rb +230 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
- data/lib/active_record/database_configurations/database_config.rb +104 -0
- data/lib/active_record/database_configurations/hash_config.rb +172 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/database_configurations.rb +309 -0
- data/lib/active_record/delegated_type.rb +289 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +121 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +70 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +184 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +177 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +159 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +107 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +424 -0
- data/lib/active_record/errors.rb +614 -0
- data/lib/active_record/explain.rb +63 -0
- data/lib/active_record/explain_registry.rb +37 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +89 -0
- data/lib/active_record/fixture_set/model_metadata.rb +42 -0
- data/lib/active_record/fixture_set/render_context.rb +19 -0
- data/lib/active_record/fixture_set/table_row.rb +208 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +850 -0
- data/lib/active_record/future_result.rb +182 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +366 -0
- data/lib/active_record/insert_all.rb +328 -0
- data/lib/active_record/integration.rb +209 -0
- data/lib/active_record/internal_metadata.rb +164 -0
- data/lib/active_record/legacy_yaml_adapter.rb +15 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +228 -0
- data/lib/active_record/locking/pessimistic.rb +102 -0
- data/lib/active_record/log_subscriber.rb +149 -0
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +87 -0
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +406 -0
- data/lib/active_record/migration/compatibility.rb +490 -0
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/join_table.rb +16 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +1626 -0
- data/lib/active_record/model_schema.rb +635 -0
- data/lib/active_record/nested_attributes.rb +633 -0
- data/lib/active_record/no_touching.rb +65 -0
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +968 -0
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/query_logs.rb +247 -0
- data/lib/active_record/query_logs_formatter.rb +30 -0
- data/lib/active_record/querying.rb +122 -0
- data/lib/active_record/railtie.rb +440 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +65 -0
- data/lib/active_record/railties/databases.rake +641 -0
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +66 -0
- data/lib/active_record/reflection.rb +1287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
- data/lib/active_record/relation/batches.rb +491 -0
- data/lib/active_record/relation/calculations.rb +679 -0
- data/lib/active_record/relation/delegation.rb +154 -0
- data/lib/active_record/relation/finder_methods.rb +661 -0
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +192 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
- data/lib/active_record/relation/predicate_builder.rb +181 -0
- data/lib/active_record/relation/query_attribute.rb +68 -0
- data/lib/active_record/relation/query_methods.rb +2235 -0
- data/lib/active_record/relation/record_fetch_warning.rb +52 -0
- data/lib/active_record/relation/spawn_methods.rb +78 -0
- data/lib/active_record/relation/where_clause.rb +218 -0
- data/lib/active_record/relation.rb +1495 -0
- data/lib/active_record/result.rb +249 -0
- data/lib/active_record/runtime_registry.rb +82 -0
- data/lib/active_record/sanitization.rb +254 -0
- data/lib/active_record/schema.rb +77 -0
- data/lib/active_record/schema_dumper.rb +364 -0
- data/lib/active_record/schema_migration.rb +106 -0
- data/lib/active_record/scoping/default.rb +205 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +136 -0
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +66 -0
- data/lib/active_record/serialization.rb +29 -0
- data/lib/active_record/signed_id.rb +137 -0
- data/lib/active_record/statement_cache.rb +164 -0
- data/lib/active_record/store.rb +299 -0
- data/lib/active_record/suppressor.rb +59 -0
- data/lib/active_record/table_metadata.rb +85 -0
- data/lib/active_record/tasks/database_tasks.rb +681 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +321 -0
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +177 -0
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +70 -0
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +523 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/adapter_specific_registry.rb +144 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
- data/lib/active_record/type/internal/timezone.rb +22 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +76 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +35 -0
- data/lib/active_record/type/type_map.rb +58 -0
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type.rb +83 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +65 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +45 -0
- data/lib/active_record/validations/uniqueness.rb +295 -0
- data/lib/active_record/validations.rb +101 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/active_record.rb +616 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +33 -0
- data/lib/arel/collectors/bind.rb +31 -0
- data/lib/arel/collectors/composite.rb +46 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +48 -0
- data/lib/arel/delete_manager.rb +32 -0
- data/lib/arel/errors.rb +19 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +53 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +48 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +125 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +44 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +45 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/nary.rb +39 -0
- data/lib/arel/nodes/node.rb +161 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +32 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +35 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +46 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +75 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +260 -0
- data/lib/arel/select_manager.rb +276 -0
- data/lib/arel/table.rb +121 -0
- data/lib/arel/tree_manager.rb +65 -0
- data/lib/arel/update_manager.rb +49 -0
- data/lib/arel/visitors/dot.rb +299 -0
- data/lib/arel/visitors/mysql.rb +111 -0
- data/lib/arel/visitors/postgresql.rb +99 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +1033 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +73 -0
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
- data/lib/rails/generators/active_record/migration.rb +54 -0
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- data/lib/rails/generators/active_record.rb +19 -0
- metadata +505 -0
|
@@ -0,0 +1,1016 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/connection_adapters/abstract_adapter"
|
|
4
|
+
require "active_record/connection_adapters/statement_pool"
|
|
5
|
+
require "active_record/connection_adapters/mysql/column"
|
|
6
|
+
require "active_record/connection_adapters/mysql/database_statements"
|
|
7
|
+
require "active_record/connection_adapters/mysql/explain_pretty_printer"
|
|
8
|
+
require "active_record/connection_adapters/mysql/quoting"
|
|
9
|
+
require "active_record/connection_adapters/mysql/schema_creation"
|
|
10
|
+
require "active_record/connection_adapters/mysql/schema_definitions"
|
|
11
|
+
require "active_record/connection_adapters/mysql/schema_dumper"
|
|
12
|
+
require "active_record/connection_adapters/mysql/schema_statements"
|
|
13
|
+
require "active_record/connection_adapters/mysql/type_metadata"
|
|
14
|
+
|
|
15
|
+
module ActiveRecord
|
|
16
|
+
module ConnectionAdapters
|
|
17
|
+
class AbstractMysqlAdapter < AbstractAdapter
|
|
18
|
+
include MySQL::DatabaseStatements
|
|
19
|
+
include MySQL::Quoting
|
|
20
|
+
include MySQL::SchemaStatements
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# :singleton-method:
|
|
24
|
+
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
|
|
25
|
+
# as boolean. If you wish to disable this emulation you can add the following line
|
|
26
|
+
# to your application.rb file:
|
|
27
|
+
#
|
|
28
|
+
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
|
|
29
|
+
class_attribute :emulate_booleans, default: true
|
|
30
|
+
|
|
31
|
+
NATIVE_DATABASE_TYPES = {
|
|
32
|
+
primary_key: "bigint auto_increment PRIMARY KEY",
|
|
33
|
+
string: { name: "varchar", limit: 255 },
|
|
34
|
+
text: { name: "text" },
|
|
35
|
+
integer: { name: "int", limit: 4 },
|
|
36
|
+
bigint: { name: "bigint" },
|
|
37
|
+
float: { name: "float", limit: 24 },
|
|
38
|
+
decimal: { name: "decimal" },
|
|
39
|
+
datetime: { name: "datetime" },
|
|
40
|
+
timestamp: { name: "timestamp" },
|
|
41
|
+
time: { name: "time" },
|
|
42
|
+
date: { name: "date" },
|
|
43
|
+
binary: { name: "blob" },
|
|
44
|
+
blob: { name: "blob" },
|
|
45
|
+
boolean: { name: "tinyint", limit: 1 },
|
|
46
|
+
json: { name: "json" },
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
|
50
|
+
private
|
|
51
|
+
def dealloc(stmt)
|
|
52
|
+
stmt.close
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
def dbconsole(config, options = {})
|
|
58
|
+
mysql_config = config.configuration_hash
|
|
59
|
+
|
|
60
|
+
args = {
|
|
61
|
+
host: "--host",
|
|
62
|
+
port: "--port",
|
|
63
|
+
socket: "--socket",
|
|
64
|
+
username: "--user",
|
|
65
|
+
encoding: "--default-character-set",
|
|
66
|
+
sslca: "--ssl-ca",
|
|
67
|
+
sslcert: "--ssl-cert",
|
|
68
|
+
sslcapath: "--ssl-capath",
|
|
69
|
+
sslcipher: "--ssl-cipher",
|
|
70
|
+
sslkey: "--ssl-key",
|
|
71
|
+
ssl_mode: "--ssl-mode"
|
|
72
|
+
}.filter_map { |opt, arg| "#{arg}=#{mysql_config[opt]}" if mysql_config[opt] }
|
|
73
|
+
|
|
74
|
+
if mysql_config[:password] && options[:include_password]
|
|
75
|
+
args << "--password=#{mysql_config[:password]}"
|
|
76
|
+
elsif mysql_config[:password] && !mysql_config[:password].to_s.empty?
|
|
77
|
+
args << "-p"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
args << config.database
|
|
81
|
+
|
|
82
|
+
find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def get_database_version # :nodoc:
|
|
87
|
+
full_version_string = get_full_version
|
|
88
|
+
version_string = version_string(full_version_string)
|
|
89
|
+
Version.new(version_string, full_version_string)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def mariadb? # :nodoc:
|
|
93
|
+
/mariadb/i.match?(full_version)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def supports_bulk_alter?
|
|
97
|
+
true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def supports_index_sort_order?
|
|
101
|
+
!mariadb? && database_version >= "8.0.1"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def supports_expression_index?
|
|
105
|
+
!mariadb? && database_version >= "8.0.13"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def supports_transaction_isolation?
|
|
109
|
+
true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def supports_restart_db_transaction?
|
|
113
|
+
true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def supports_explain?
|
|
117
|
+
true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def supports_indexes_in_create?
|
|
121
|
+
true
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def supports_foreign_keys?
|
|
125
|
+
true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def supports_check_constraints?
|
|
129
|
+
if mariadb?
|
|
130
|
+
database_version >= "10.3.10" || (database_version < "10.3" && database_version >= "10.2.22")
|
|
131
|
+
else
|
|
132
|
+
database_version >= "8.0.16"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def supports_views?
|
|
137
|
+
true
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def supports_datetime_with_precision?
|
|
141
|
+
true
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def supports_virtual_columns?
|
|
145
|
+
mariadb? || database_version >= "5.7.5"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details.
|
|
149
|
+
def supports_optimizer_hints?
|
|
150
|
+
!mariadb? && database_version >= "5.7.7"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def supports_common_table_expressions?
|
|
154
|
+
if mariadb?
|
|
155
|
+
database_version >= "10.2.1"
|
|
156
|
+
else
|
|
157
|
+
database_version >= "8.0.1"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def supports_advisory_locks?
|
|
162
|
+
true
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def supports_insert_on_duplicate_skip?
|
|
166
|
+
true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def supports_insert_on_duplicate_update?
|
|
170
|
+
true
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def supports_insert_returning?
|
|
174
|
+
mariadb? && database_version >= "10.5.0"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
|
178
|
+
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def release_advisory_lock(lock_name) # :nodoc:
|
|
182
|
+
query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def native_database_types
|
|
186
|
+
NATIVE_DATABASE_TYPES
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def index_algorithms
|
|
190
|
+
{
|
|
191
|
+
default: "ALGORITHM = DEFAULT",
|
|
192
|
+
copy: "ALGORITHM = COPY",
|
|
193
|
+
inplace: "ALGORITHM = INPLACE",
|
|
194
|
+
instant: "ALGORITHM = INSTANT",
|
|
195
|
+
}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# HELPER METHODS ===========================================
|
|
199
|
+
|
|
200
|
+
# Must return the MySQL error number from the exception, if the exception has an
|
|
201
|
+
# error number.
|
|
202
|
+
def error_number(exception) # :nodoc:
|
|
203
|
+
raise NotImplementedError
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# REFERENTIAL INTEGRITY ====================================
|
|
207
|
+
|
|
208
|
+
def disable_referential_integrity # :nodoc:
|
|
209
|
+
old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
|
|
210
|
+
|
|
211
|
+
begin
|
|
212
|
+
update("SET FOREIGN_KEY_CHECKS = 0")
|
|
213
|
+
yield
|
|
214
|
+
ensure
|
|
215
|
+
update("SET FOREIGN_KEY_CHECKS = #{old}") if active?
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
#--
|
|
220
|
+
# DATABASE STATEMENTS ======================================
|
|
221
|
+
#++
|
|
222
|
+
|
|
223
|
+
def begin_db_transaction # :nodoc:
|
|
224
|
+
internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def begin_isolated_db_transaction(isolation) # :nodoc:
|
|
228
|
+
# From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session.
|
|
229
|
+
# So we don't need to implement #reset_isolation_level
|
|
230
|
+
execute_batch(
|
|
231
|
+
["SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "BEGIN"],
|
|
232
|
+
"TRANSACTION",
|
|
233
|
+
allow_retry: true,
|
|
234
|
+
materialize_transactions: false,
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def commit_db_transaction # :nodoc:
|
|
239
|
+
internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def exec_rollback_db_transaction # :nodoc:
|
|
243
|
+
internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def exec_restart_db_transaction # :nodoc:
|
|
247
|
+
internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def empty_insert_statement_value(primary_key = nil) # :nodoc:
|
|
251
|
+
"VALUES ()"
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# SCHEMA STATEMENTS ========================================
|
|
255
|
+
|
|
256
|
+
# Drops the database specified on the +name+ attribute
|
|
257
|
+
# and creates it again using the provided +options+.
|
|
258
|
+
def recreate_database(name, options = {})
|
|
259
|
+
drop_database(name)
|
|
260
|
+
sql = create_database(name, options)
|
|
261
|
+
reconnect!
|
|
262
|
+
sql
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
|
266
|
+
# Charset defaults to utf8mb4.
|
|
267
|
+
#
|
|
268
|
+
# Example:
|
|
269
|
+
# create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
|
|
270
|
+
# create_database 'matt_development'
|
|
271
|
+
# create_database 'matt_development', charset: :big5
|
|
272
|
+
def create_database(name, options = {})
|
|
273
|
+
if options[:collation]
|
|
274
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
|
|
275
|
+
elsif options[:charset]
|
|
276
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
|
|
277
|
+
elsif row_format_dynamic_by_default?
|
|
278
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
|
|
279
|
+
else
|
|
280
|
+
raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Drops a MySQL database.
|
|
285
|
+
#
|
|
286
|
+
# Example:
|
|
287
|
+
# drop_database('sebastian_development')
|
|
288
|
+
def drop_database(name) # :nodoc:
|
|
289
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def current_database
|
|
293
|
+
query_value("SELECT database()", "SCHEMA")
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Returns the database character set.
|
|
297
|
+
def charset
|
|
298
|
+
show_variable "character_set_database"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Returns the database collation strategy.
|
|
302
|
+
def collation
|
|
303
|
+
show_variable "collation_database"
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def table_comment(table_name) # :nodoc:
|
|
307
|
+
scope = quoted_scope(table_name)
|
|
308
|
+
|
|
309
|
+
query_value(<<~SQL, "SCHEMA").presence
|
|
310
|
+
SELECT table_comment
|
|
311
|
+
FROM information_schema.tables
|
|
312
|
+
WHERE table_schema = #{scope[:schema]}
|
|
313
|
+
AND table_name = #{scope[:name]}
|
|
314
|
+
SQL
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def change_table_comment(table_name, comment_or_changes) # :nodoc:
|
|
318
|
+
comment = extract_new_comment_value(comment_or_changes)
|
|
319
|
+
comment = "" if comment.nil?
|
|
320
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Renames a table.
|
|
324
|
+
#
|
|
325
|
+
# Example:
|
|
326
|
+
# rename_table('octopuses', 'octopi')
|
|
327
|
+
def rename_table(table_name, new_name, **options)
|
|
328
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
|
329
|
+
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
330
|
+
schema_cache.clear_data_source_cache!(new_name.to_s)
|
|
331
|
+
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
|
332
|
+
rename_table_indexes(table_name, new_name, **options)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Drops a table or tables from the database.
|
|
336
|
+
#
|
|
337
|
+
# [<tt>:force</tt>]
|
|
338
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
|
339
|
+
# Defaults to false.
|
|
340
|
+
# [<tt>:if_exists</tt>]
|
|
341
|
+
# Set to +true+ to only drop the table if it exists.
|
|
342
|
+
# Defaults to false.
|
|
343
|
+
# [<tt>:temporary</tt>]
|
|
344
|
+
# Set to +true+ to drop temporary table.
|
|
345
|
+
# Defaults to false.
|
|
346
|
+
#
|
|
347
|
+
# Although this command ignores most +options+ and the block if one is given,
|
|
348
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
|
349
|
+
# In that case, +options+ and the block will be used by #create_table except if you provide more than one table which is not supported.
|
|
350
|
+
def drop_table(*table_names, **options)
|
|
351
|
+
table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
|
|
352
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def rename_index(table_name, old_name, new_name)
|
|
356
|
+
if supports_rename_index?
|
|
357
|
+
validate_index_length!(table_name, new_name)
|
|
358
|
+
|
|
359
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
|
|
360
|
+
else
|
|
361
|
+
super
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
|
366
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
|
|
370
|
+
column = column_for(table_name, column_name)
|
|
371
|
+
return unless column
|
|
372
|
+
|
|
373
|
+
default = extract_new_default_value(default_or_changes)
|
|
374
|
+
ChangeColumnDefaultDefinition.new(column, default)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
|
|
378
|
+
validate_change_column_null_argument!(null)
|
|
379
|
+
|
|
380
|
+
unless null || default.nil?
|
|
381
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
change_column table_name, column_name, nil, null: null
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
|
|
388
|
+
comment = extract_new_comment_value(comment_or_changes)
|
|
389
|
+
change_column table_name, column_name, nil, comment: comment
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def change_column(table_name, column_name, type, **options) # :nodoc:
|
|
393
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Builds a ChangeColumnDefinition object.
|
|
397
|
+
#
|
|
398
|
+
# This definition object contains information about the column change that would occur
|
|
399
|
+
# if the same arguments were passed to #change_column. See #change_column for information about
|
|
400
|
+
# passing a +table_name+, +column_name+, +type+ and other options that can be passed.
|
|
401
|
+
def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
|
|
402
|
+
column = column_for(table_name, column_name)
|
|
403
|
+
type ||= column.sql_type
|
|
404
|
+
|
|
405
|
+
unless options.key?(:default)
|
|
406
|
+
options[:default] = column.default
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
unless options.key?(:null)
|
|
410
|
+
options[:null] = column.null
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
unless options.key?(:comment)
|
|
414
|
+
options[:comment] = column.comment
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
if options[:collation] == :no_collation
|
|
418
|
+
options.delete(:collation)
|
|
419
|
+
else
|
|
420
|
+
options[:collation] ||= column.collation if text_type?(type)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
unless options.key?(:auto_increment)
|
|
424
|
+
options[:auto_increment] = column.auto_increment?
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
td = create_table_definition(table_name)
|
|
428
|
+
cd = td.new_column_definition(column.name, type, **options)
|
|
429
|
+
ChangeColumnDefinition.new(cd, column.name)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def rename_column(table_name, column_name, new_column_name) # :nodoc:
|
|
433
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
|
|
434
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def add_index(table_name, column_name, **options) # :nodoc:
|
|
438
|
+
create_index = build_create_index_definition(table_name, column_name, **options)
|
|
439
|
+
return unless create_index
|
|
440
|
+
|
|
441
|
+
execute schema_creation.accept(create_index)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def build_create_index_definition(table_name, column_name, **options) # :nodoc:
|
|
445
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
|
446
|
+
|
|
447
|
+
return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
|
|
448
|
+
|
|
449
|
+
CreateIndexDefinition.new(index, algorithm)
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def add_sql_comment!(sql, comment) # :nodoc:
|
|
453
|
+
sql << " COMMENT #{quote(comment)}" if comment.present?
|
|
454
|
+
sql
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def foreign_keys(table_name)
|
|
458
|
+
raise ArgumentError unless table_name.present?
|
|
459
|
+
|
|
460
|
+
scope = quoted_scope(table_name)
|
|
461
|
+
|
|
462
|
+
# MySQL returns 1 row for each column of composite foreign keys.
|
|
463
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA")
|
|
464
|
+
SELECT fk.referenced_table_name AS 'to_table',
|
|
465
|
+
fk.referenced_column_name AS 'primary_key',
|
|
466
|
+
fk.column_name AS 'column',
|
|
467
|
+
fk.constraint_name AS 'name',
|
|
468
|
+
fk.ordinal_position AS 'position',
|
|
469
|
+
rc.update_rule AS 'on_update',
|
|
470
|
+
rc.delete_rule AS 'on_delete'
|
|
471
|
+
FROM information_schema.referential_constraints rc
|
|
472
|
+
JOIN information_schema.key_column_usage fk
|
|
473
|
+
USING (constraint_schema, constraint_name)
|
|
474
|
+
WHERE fk.referenced_column_name IS NOT NULL
|
|
475
|
+
AND fk.table_schema = #{scope[:schema]}
|
|
476
|
+
AND fk.table_name = #{scope[:name]}
|
|
477
|
+
AND rc.constraint_schema = #{scope[:schema]}
|
|
478
|
+
AND rc.table_name = #{scope[:name]}
|
|
479
|
+
SQL
|
|
480
|
+
|
|
481
|
+
grouped_fk = fk_info.group_by { |row| row["name"] }.values.each { |group| group.sort_by! { |row| row["position"] } }
|
|
482
|
+
grouped_fk.map do |group|
|
|
483
|
+
row = group.first
|
|
484
|
+
options = {
|
|
485
|
+
name: row["name"],
|
|
486
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
|
487
|
+
on_delete: extract_foreign_key_action(row["on_delete"])
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if group.one?
|
|
491
|
+
options[:column] = unquote_identifier(row["column"])
|
|
492
|
+
options[:primary_key] = row["primary_key"]
|
|
493
|
+
else
|
|
494
|
+
options[:column] = group.map { |row| unquote_identifier(row["column"]) }
|
|
495
|
+
options[:primary_key] = group.map { |row| row["primary_key"] }
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
ForeignKeyDefinition.new(table_name, unquote_identifier(row["to_table"]), options)
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def check_constraints(table_name)
|
|
503
|
+
if supports_check_constraints?
|
|
504
|
+
scope = quoted_scope(table_name)
|
|
505
|
+
|
|
506
|
+
sql = <<~SQL
|
|
507
|
+
SELECT cc.constraint_name AS 'name',
|
|
508
|
+
cc.check_clause AS 'expression'
|
|
509
|
+
FROM information_schema.check_constraints cc
|
|
510
|
+
JOIN information_schema.table_constraints tc
|
|
511
|
+
USING (constraint_schema, constraint_name)
|
|
512
|
+
WHERE tc.table_schema = #{scope[:schema]}
|
|
513
|
+
AND tc.table_name = #{scope[:name]}
|
|
514
|
+
AND cc.constraint_schema = #{scope[:schema]}
|
|
515
|
+
SQL
|
|
516
|
+
sql += " AND cc.table_name = #{scope[:name]}" if mariadb?
|
|
517
|
+
|
|
518
|
+
chk_info = internal_exec_query(sql, "SCHEMA")
|
|
519
|
+
|
|
520
|
+
chk_info.map do |row|
|
|
521
|
+
options = {
|
|
522
|
+
name: row["name"]
|
|
523
|
+
}
|
|
524
|
+
expression = row["expression"]
|
|
525
|
+
expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
|
|
526
|
+
expression = strip_whitespace_characters(expression)
|
|
527
|
+
|
|
528
|
+
unless mariadb?
|
|
529
|
+
# MySQL returns check constraints expression in an already escaped form.
|
|
530
|
+
# This leads to duplicate escaping later (e.g. when the expression is used in the SchemaDumper).
|
|
531
|
+
expression = expression.gsub("\\'", "'")
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
CheckConstraintDefinition.new(table_name, expression, options)
|
|
535
|
+
end
|
|
536
|
+
else
|
|
537
|
+
raise NotImplementedError
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def table_options(table_name) # :nodoc:
|
|
542
|
+
create_table_info = create_table_info(table_name)
|
|
543
|
+
|
|
544
|
+
# strip create_definitions and partition_options
|
|
545
|
+
# Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
|
|
546
|
+
raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
|
|
547
|
+
|
|
548
|
+
return if raw_table_options.empty?
|
|
549
|
+
|
|
550
|
+
table_options = {}
|
|
551
|
+
|
|
552
|
+
if / DEFAULT CHARSET=(?<charset>\w+)(?: COLLATE=(?<collation>\w+))?/ =~ raw_table_options
|
|
553
|
+
raw_table_options = $` + $' # before part + after part
|
|
554
|
+
table_options[:charset] = charset
|
|
555
|
+
table_options[:collation] = collation if collation
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
# strip AUTO_INCREMENT
|
|
559
|
+
raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
|
|
560
|
+
|
|
561
|
+
# strip COMMENT
|
|
562
|
+
if raw_table_options.sub!(/ COMMENT='.+'/, "")
|
|
563
|
+
table_options[:comment] = table_comment(table_name)
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
|
|
567
|
+
table_options
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# SHOW VARIABLES LIKE 'name'
|
|
571
|
+
def show_variable(name)
|
|
572
|
+
query_value("SELECT @@#{name}", "SCHEMA", materialize_transactions: false, allow_retry: true)
|
|
573
|
+
rescue ActiveRecord::StatementInvalid
|
|
574
|
+
nil
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
def primary_keys(table_name) # :nodoc:
|
|
578
|
+
raise ArgumentError unless table_name.present?
|
|
579
|
+
|
|
580
|
+
scope = quoted_scope(table_name)
|
|
581
|
+
|
|
582
|
+
query_values(<<~SQL, "SCHEMA")
|
|
583
|
+
SELECT column_name
|
|
584
|
+
FROM information_schema.statistics
|
|
585
|
+
WHERE index_name = 'PRIMARY'
|
|
586
|
+
AND table_schema = #{scope[:schema]}
|
|
587
|
+
AND table_name = #{scope[:name]}
|
|
588
|
+
ORDER BY seq_in_index
|
|
589
|
+
SQL
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
def case_sensitive_comparison(attribute, value) # :nodoc:
|
|
593
|
+
column = column_for_attribute(attribute)
|
|
594
|
+
|
|
595
|
+
if column.collation && !column.case_sensitive?
|
|
596
|
+
attribute.eq(Arel::Nodes::Bin.new(value))
|
|
597
|
+
else
|
|
598
|
+
super
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
|
603
|
+
column.case_sensitive?
|
|
604
|
+
end
|
|
605
|
+
private :can_perform_case_insensitive_comparison_for?
|
|
606
|
+
|
|
607
|
+
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
|
608
|
+
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
|
609
|
+
# distinct queries, and requires that the ORDER BY include the distinct column.
|
|
610
|
+
# See https://dev.mysql.com/doc/refman/en/group-by-handling.html
|
|
611
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
|
612
|
+
order_columns = orders.compact_blank.map { |s|
|
|
613
|
+
# Convert Arel node to string
|
|
614
|
+
s = visitor.compile(s) unless s.is_a?(String)
|
|
615
|
+
# Remove any ASC/DESC modifiers
|
|
616
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
|
617
|
+
}.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
|
618
|
+
|
|
619
|
+
(order_columns << super).join(", ")
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def strict_mode?
|
|
623
|
+
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def default_index_type?(index) # :nodoc:
|
|
627
|
+
index.using == :btree || super
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def build_insert_sql(insert) # :nodoc:
|
|
631
|
+
no_op_column = quote_column_name(insert.keys.first)
|
|
632
|
+
|
|
633
|
+
# MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
|
|
634
|
+
# then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
|
|
635
|
+
if supports_insert_raw_alias_syntax?
|
|
636
|
+
values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
|
|
637
|
+
sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
|
|
638
|
+
|
|
639
|
+
if insert.skip_duplicates?
|
|
640
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
|
|
641
|
+
elsif insert.update_duplicates?
|
|
642
|
+
if insert.raw_update_sql?
|
|
643
|
+
sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
|
|
644
|
+
else
|
|
645
|
+
sql << " ON DUPLICATE KEY UPDATE "
|
|
646
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
|
|
647
|
+
sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
else
|
|
651
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
|
652
|
+
|
|
653
|
+
if insert.skip_duplicates?
|
|
654
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
|
|
655
|
+
elsif insert.update_duplicates?
|
|
656
|
+
sql << " ON DUPLICATE KEY UPDATE "
|
|
657
|
+
if insert.raw_update_sql?
|
|
658
|
+
sql << insert.raw_update_sql
|
|
659
|
+
else
|
|
660
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
|
|
661
|
+
sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
|
667
|
+
sql
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
def check_version # :nodoc:
|
|
671
|
+
if database_version < "5.6.4"
|
|
672
|
+
raise DatabaseVersionError, "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.6.4."
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
#--
|
|
677
|
+
# QUOTING ==================================================
|
|
678
|
+
#++
|
|
679
|
+
|
|
680
|
+
# Quotes strings for use in SQL input.
|
|
681
|
+
def quote_string(string)
|
|
682
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
|
|
683
|
+
connection.escape(string)
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
class << self
|
|
688
|
+
def extended_type_map(default_timezone: nil, emulate_booleans:) # :nodoc:
|
|
689
|
+
super(default_timezone: default_timezone).tap do |m|
|
|
690
|
+
if emulate_booleans
|
|
691
|
+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
private
|
|
697
|
+
def initialize_type_map(m)
|
|
698
|
+
super
|
|
699
|
+
|
|
700
|
+
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
|
|
701
|
+
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
|
|
702
|
+
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
|
|
703
|
+
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
|
|
704
|
+
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
|
|
705
|
+
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
|
|
706
|
+
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
|
|
707
|
+
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
|
708
|
+
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
|
709
|
+
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
|
710
|
+
|
|
711
|
+
register_integer_type m, %r(^bigint)i, limit: 8
|
|
712
|
+
register_integer_type m, %r(^int)i, limit: 4
|
|
713
|
+
register_integer_type m, %r(^mediumint)i, limit: 3
|
|
714
|
+
register_integer_type m, %r(^smallint)i, limit: 2
|
|
715
|
+
register_integer_type m, %r(^tinyint)i, limit: 1
|
|
716
|
+
|
|
717
|
+
m.alias_type %r(year)i, "integer"
|
|
718
|
+
m.alias_type %r(bit)i, "binary"
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def register_integer_type(mapping, key, **options)
|
|
722
|
+
mapping.register_type(key) do |sql_type|
|
|
723
|
+
if /\bunsigned\b/.match?(sql_type)
|
|
724
|
+
Type::UnsignedInteger.new(**options)
|
|
725
|
+
else
|
|
726
|
+
Type::Integer.new(**options)
|
|
727
|
+
end
|
|
728
|
+
end
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
def extract_precision(sql_type)
|
|
732
|
+
if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
|
|
733
|
+
super || 0
|
|
734
|
+
else
|
|
735
|
+
super
|
|
736
|
+
end
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
EXTENDED_TYPE_MAPS = Concurrent::Map.new
|
|
741
|
+
EMULATE_BOOLEANS_TRUE = { emulate_booleans: true }.freeze
|
|
742
|
+
|
|
743
|
+
private
|
|
744
|
+
def strip_whitespace_characters(expression)
|
|
745
|
+
expression = expression.gsub(/\\n|\\\\/, "")
|
|
746
|
+
expression = expression.gsub(/\s{2,}/, " ")
|
|
747
|
+
expression
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
def extended_type_map_key
|
|
751
|
+
if @default_timezone
|
|
752
|
+
{ default_timezone: @default_timezone, emulate_booleans: emulate_booleans }
|
|
753
|
+
elsif emulate_booleans
|
|
754
|
+
EMULATE_BOOLEANS_TRUE
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
|
|
758
|
+
def handle_warnings(sql)
|
|
759
|
+
return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
|
|
760
|
+
|
|
761
|
+
@affected_rows_before_warnings = @raw_connection.affected_rows
|
|
762
|
+
warning_count = @raw_connection.warning_count
|
|
763
|
+
result = @raw_connection.query("SHOW WARNINGS")
|
|
764
|
+
result = [
|
|
765
|
+
["Warning", nil, "Query had warning_count=#{warning_count} but ‘SHOW WARNINGS’ did not return the warnings. Check MySQL logs or database configuration."],
|
|
766
|
+
] if result.count == 0
|
|
767
|
+
result.each do |level, code, message|
|
|
768
|
+
warning = SQLWarning.new(message, code, level, sql, @pool)
|
|
769
|
+
next if warning_ignored?(warning)
|
|
770
|
+
|
|
771
|
+
ActiveRecord.db_warnings_action.call(warning)
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def warning_ignored?(warning)
|
|
776
|
+
warning.level == "Note" || super
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
# See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
|
|
780
|
+
ER_DB_CREATE_EXISTS = 1007
|
|
781
|
+
ER_FILSORT_ABORT = 1028
|
|
782
|
+
ER_DUP_ENTRY = 1062
|
|
783
|
+
ER_SERVER_SHUTDOWN = 1053
|
|
784
|
+
ER_NOT_NULL_VIOLATION = 1048
|
|
785
|
+
ER_NO_REFERENCED_ROW = 1216
|
|
786
|
+
ER_ROW_IS_REFERENCED = 1217
|
|
787
|
+
ER_DO_NOT_HAVE_DEFAULT = 1364
|
|
788
|
+
ER_ROW_IS_REFERENCED_2 = 1451
|
|
789
|
+
ER_NO_REFERENCED_ROW_2 = 1452
|
|
790
|
+
ER_DATA_TOO_LONG = 1406
|
|
791
|
+
ER_OUT_OF_RANGE = 1264
|
|
792
|
+
ER_LOCK_DEADLOCK = 1213
|
|
793
|
+
ER_CANNOT_ADD_FOREIGN = 1215
|
|
794
|
+
ER_CANNOT_CREATE_TABLE = 1005
|
|
795
|
+
ER_LOCK_WAIT_TIMEOUT = 1205
|
|
796
|
+
ER_QUERY_INTERRUPTED = 1317
|
|
797
|
+
ER_CONNECTION_KILLED = 1927
|
|
798
|
+
CR_SERVER_GONE_ERROR = 2006
|
|
799
|
+
CR_SERVER_LOST = 2013
|
|
800
|
+
ER_QUERY_TIMEOUT = 3024
|
|
801
|
+
ER_FK_INCOMPATIBLE_COLUMNS = 3780
|
|
802
|
+
ER_CLIENT_INTERACTION_TIMEOUT = 4031
|
|
803
|
+
|
|
804
|
+
def translate_exception(exception, message:, sql:, binds:)
|
|
805
|
+
case error_number(exception)
|
|
806
|
+
when nil
|
|
807
|
+
if exception.message.match?(/MySQL client is not connected/i)
|
|
808
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
809
|
+
else
|
|
810
|
+
super
|
|
811
|
+
end
|
|
812
|
+
when ER_CONNECTION_KILLED, ER_SERVER_SHUTDOWN, CR_SERVER_GONE_ERROR, CR_SERVER_LOST, ER_CLIENT_INTERACTION_TIMEOUT
|
|
813
|
+
ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
814
|
+
when ER_DB_CREATE_EXISTS
|
|
815
|
+
DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
816
|
+
when ER_DUP_ENTRY
|
|
817
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
818
|
+
when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
|
|
819
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
820
|
+
when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
|
|
821
|
+
mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
822
|
+
when ER_CANNOT_CREATE_TABLE
|
|
823
|
+
if message.include?("errno: 150")
|
|
824
|
+
mismatched_foreign_key(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
825
|
+
else
|
|
826
|
+
super
|
|
827
|
+
end
|
|
828
|
+
when ER_DATA_TOO_LONG
|
|
829
|
+
ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
830
|
+
when ER_OUT_OF_RANGE
|
|
831
|
+
RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
832
|
+
when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
|
|
833
|
+
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
834
|
+
when ER_LOCK_DEADLOCK
|
|
835
|
+
Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
836
|
+
when ER_LOCK_WAIT_TIMEOUT
|
|
837
|
+
LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
838
|
+
when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
|
|
839
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
840
|
+
when ER_QUERY_INTERRUPTED
|
|
841
|
+
QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
842
|
+
else
|
|
843
|
+
super
|
|
844
|
+
end
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
def change_column_for_alter(table_name, column_name, type, **options)
|
|
848
|
+
cd = build_change_column_definition(table_name, column_name, type, **options)
|
|
849
|
+
schema_creation.accept(cd)
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
def rename_column_for_alter(table_name, column_name, new_column_name)
|
|
853
|
+
return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
|
|
854
|
+
|
|
855
|
+
column = column_for(table_name, column_name)
|
|
856
|
+
options = {
|
|
857
|
+
default: column.default,
|
|
858
|
+
null: column.null,
|
|
859
|
+
auto_increment: column.auto_increment?,
|
|
860
|
+
comment: column.comment
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
current_type = internal_exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
|
|
864
|
+
td = create_table_definition(table_name)
|
|
865
|
+
cd = td.new_column_definition(new_column_name, current_type, **options)
|
|
866
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
def add_index_for_alter(table_name, column_name, **options)
|
|
870
|
+
index, algorithm, _ = add_index_options(table_name, column_name, **options)
|
|
871
|
+
algorithm = ", #{algorithm}" if algorithm
|
|
872
|
+
|
|
873
|
+
"ADD #{schema_creation.accept(index)}#{algorithm}"
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
def remove_index_for_alter(table_name, column_name = nil, **options)
|
|
877
|
+
index_name = index_name_for_remove(table_name, column_name, options)
|
|
878
|
+
"DROP INDEX #{quote_column_name(index_name)}"
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
def supports_insert_raw_alias_syntax?
|
|
882
|
+
!mariadb? && database_version >= "8.0.19"
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
def supports_rename_index?
|
|
886
|
+
if mariadb?
|
|
887
|
+
database_version >= "10.5.2"
|
|
888
|
+
else
|
|
889
|
+
database_version >= "5.7.6"
|
|
890
|
+
end
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
def supports_rename_column?
|
|
894
|
+
if mariadb?
|
|
895
|
+
database_version >= "10.5.2"
|
|
896
|
+
else
|
|
897
|
+
database_version >= "8.0.3"
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
def configure_connection
|
|
902
|
+
super
|
|
903
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
|
904
|
+
|
|
905
|
+
# Increase timeout so the server doesn't disconnect us.
|
|
906
|
+
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
|
|
907
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
|
908
|
+
variables["wait_timeout"] = wait_timeout
|
|
909
|
+
|
|
910
|
+
defaults = [":default", :default].to_set
|
|
911
|
+
|
|
912
|
+
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
|
913
|
+
# https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables
|
|
914
|
+
# If the user has provided another value for sql_mode, don't replace it.
|
|
915
|
+
if sql_mode = variables.delete("sql_mode")
|
|
916
|
+
sql_mode = quote(sql_mode)
|
|
917
|
+
elsif !defaults.include?(strict_mode?)
|
|
918
|
+
if strict_mode?
|
|
919
|
+
sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
|
|
920
|
+
else
|
|
921
|
+
sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
|
|
922
|
+
sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
|
|
923
|
+
sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
|
|
924
|
+
end
|
|
925
|
+
sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
|
|
926
|
+
end
|
|
927
|
+
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
|
|
928
|
+
|
|
929
|
+
# NAMES does not have an equals sign, see
|
|
930
|
+
# https://dev.mysql.com/doc/refman/en/set-names.html
|
|
931
|
+
# (trailing comma because variable_assignments will always have content)
|
|
932
|
+
if @config[:encoding]
|
|
933
|
+
encoding = +"NAMES #{@config[:encoding]}"
|
|
934
|
+
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
|
|
935
|
+
encoding << ", "
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
# Gather up all of the SET variables...
|
|
939
|
+
variable_assignments = variables.filter_map do |k, v|
|
|
940
|
+
if defaults.include?(v)
|
|
941
|
+
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
|
942
|
+
elsif !v.nil?
|
|
943
|
+
"@@SESSION.#{k} = #{quote(v)}"
|
|
944
|
+
end
|
|
945
|
+
end.join(", ")
|
|
946
|
+
|
|
947
|
+
# ...and send them all in one query
|
|
948
|
+
raw_execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
def column_definitions(table_name) # :nodoc:
|
|
952
|
+
internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA")
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
def create_table_info(table_name) # :nodoc:
|
|
956
|
+
internal_exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
def arel_visitor
|
|
960
|
+
Arel::Visitors::MySQL.new(self)
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
def build_statement_pool
|
|
964
|
+
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
def mismatched_foreign_key_details(message:, sql:)
|
|
968
|
+
foreign_key_pat =
|
|
969
|
+
/Referencing column '(\w+)' and referenced/i =~ message ? $1 : '\w+'
|
|
970
|
+
|
|
971
|
+
match = %r/
|
|
972
|
+
(?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
|
|
973
|
+
FOREIGN\s+KEY\s*\(`?(?<foreign_key>#{foreign_key_pat})`?\)\s*
|
|
974
|
+
REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
|
|
975
|
+
/xmi.match(sql)
|
|
976
|
+
|
|
977
|
+
options = {}
|
|
978
|
+
|
|
979
|
+
if match
|
|
980
|
+
options[:table] = match[:table]
|
|
981
|
+
options[:foreign_key] = match[:foreign_key]
|
|
982
|
+
options[:target_table] = match[:target_table]
|
|
983
|
+
options[:primary_key] = match[:primary_key]
|
|
984
|
+
options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
options
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
def mismatched_foreign_key(message, sql:, binds:, connection_pool:)
|
|
991
|
+
options = {
|
|
992
|
+
message: message,
|
|
993
|
+
sql: sql,
|
|
994
|
+
binds: binds,
|
|
995
|
+
connection_pool: connection_pool
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
if sql
|
|
999
|
+
options.update mismatched_foreign_key_details(message: message, sql: sql)
|
|
1000
|
+
else
|
|
1001
|
+
options[:query_parser] = ->(sql) { mismatched_foreign_key_details(message: message, sql: sql) }
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
MismatchedForeignKey.new(**options)
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
def version_string(full_version_string)
|
|
1008
|
+
if full_version_string && matches = full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)
|
|
1009
|
+
matches[1]
|
|
1010
|
+
else
|
|
1011
|
+
raise DatabaseVersionError, "Unable to parse MySQL version from #{full_version_string.inspect}"
|
|
1012
|
+
end
|
|
1013
|
+
end
|
|
1014
|
+
end
|
|
1015
|
+
end
|
|
1016
|
+
end
|