activerecord 4.2.9 → 6.1.4.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +964 -1382
- data/MIT-LICENSE +4 -2
- data/README.rdoc +15 -14
- data/examples/performance.rb +33 -32
- data/examples/simple.rb +5 -4
- data/lib/active_record/aggregations.rb +266 -251
- data/lib/active_record/association_relation.rb +40 -15
- data/lib/active_record/associations/alias_tracker.rb +40 -43
- data/lib/active_record/associations/association.rb +162 -69
- data/lib/active_record/associations/association_scope.rb +105 -130
- data/lib/active_record/associations/belongs_to_association.rb +83 -65
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
- data/lib/active_record/associations/builder/association.rb +57 -43
- data/lib/active_record/associations/builder/belongs_to.rb +74 -57
- data/lib/active_record/associations/builder/collection_association.rb +15 -37
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +49 -66
- data/lib/active_record/associations/builder/has_many.rb +13 -5
- data/lib/active_record/associations/builder/has_one.rb +44 -6
- data/lib/active_record/associations/builder/singular_association.rb +16 -10
- data/lib/active_record/associations/collection_association.rb +148 -287
- data/lib/active_record/associations/collection_proxy.rb +252 -150
- data/lib/active_record/associations/foreign_association.rb +23 -1
- data/lib/active_record/associations/has_many_association.rb +56 -98
- data/lib/active_record/associations/has_many_through_association.rb +68 -89
- data/lib/active_record/associations/has_one_association.rb +73 -47
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +54 -81
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
- data/lib/active_record/associations/join_dependency.rb +174 -169
- data/lib/active_record/associations/preloader/association.rb +108 -115
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/preloader.rb +97 -94
- data/lib/active_record/associations/singular_association.rb +18 -39
- data/lib/active_record/associations/through_association.rb +39 -19
- data/lib/active_record/associations.rb +1845 -1598
- data/lib/active_record/attribute_assignment.rb +59 -185
- data/lib/active_record/attribute_methods/before_type_cast.rb +18 -10
- data/lib/active_record/attribute_methods/dirty.rb +168 -148
- data/lib/active_record/attribute_methods/primary_key.rb +93 -83
- data/lib/active_record/attribute_methods/query.rb +8 -10
- data/lib/active_record/attribute_methods/read.rb +19 -79
- data/lib/active_record/attribute_methods/serialization.rb +49 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +55 -36
- data/lib/active_record/attribute_methods/write.rb +24 -55
- data/lib/active_record/attribute_methods.rb +149 -154
- data/lib/active_record/attributes.rb +234 -78
- data/lib/active_record/autosave_association.rb +133 -60
- data/lib/active_record/base.rb +46 -46
- data/lib/active_record/callbacks.rb +234 -79
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +34 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -323
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +292 -124
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +177 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +8 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +473 -255
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +869 -286
- data/lib/active_record/connection_adapters/abstract/transaction.rb +257 -91
- data/lib/active_record/connection_adapters/abstract_adapter.rb +483 -230
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +557 -640
- data/lib/active_record/connection_adapters/column.rb +67 -40
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +194 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +96 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +103 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +91 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +80 -192
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -160
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -58
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -19
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -20
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
- data/lib/active_record/connection_adapters/postgresql/oid/{infinity.rb → oid.rb} +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +32 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +70 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +25 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +145 -48
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +496 -298
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +588 -375
- data/lib/active_record/connection_adapters/schema_cache.rb +167 -29
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +102 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +322 -373
- data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
- data/lib/active_record/connection_adapters.rb +52 -0
- data/lib/active_record/connection_handling.rb +314 -41
- data/lib/active_record/core.rb +458 -241
- data/lib/active_record/counter_cache.rb +70 -49
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/database_configurations.rb +272 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +87 -106
- data/lib/active_record/enum.rb +211 -92
- data/lib/active_record/errors.rb +224 -54
- data/lib/active_record/explain.rb +27 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +33 -14
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +275 -500
- data/lib/active_record/gem_version.rb +6 -4
- data/lib/active_record/inheritance.rb +175 -110
- data/lib/active_record/insert_all.rb +212 -0
- data/lib/active_record/integration.rb +121 -29
- data/lib/active_record/internal_metadata.rb +62 -0
- data/lib/active_record/legacy_yaml_adapter.rb +27 -5
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +98 -92
- data/lib/active_record/locking/pessimistic.rb +22 -6
- data/lib/active_record/log_subscriber.rb +93 -31
- 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 +77 -0
- data/lib/active_record/migration/command_recorder.rb +185 -90
- data/lib/active_record/migration/compatibility.rb +295 -0
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/migration.rb +673 -325
- data/lib/active_record/model_schema.rb +418 -113
- data/lib/active_record/nested_attributes.rb +263 -224
- data/lib/active_record/no_touching.rb +15 -2
- data/lib/active_record/null_relation.rb +24 -38
- data/lib/active_record/persistence.rb +572 -136
- data/lib/active_record/query_cache.rb +29 -23
- data/lib/active_record/querying.rb +50 -31
- data/lib/active_record/railtie.rb +170 -51
- data/lib/active_record/railties/console_sandbox.rb +3 -3
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +523 -199
- data/lib/active_record/readonly_attributes.rb +9 -4
- data/lib/active_record/reflection.rb +454 -291
- data/lib/active_record/relation/batches/batch_enumerator.rb +85 -0
- data/lib/active_record/relation/batches.rb +217 -59
- data/lib/active_record/relation/calculations.rb +324 -249
- data/lib/active_record/relation/delegation.rb +76 -84
- data/lib/active_record/relation/finder_methods.rb +316 -242
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +95 -103
- data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -26
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -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 +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/predicate_builder.rb +136 -122
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +757 -413
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +18 -20
- data/lib/active_record/relation/where_clause.rb +239 -0
- data/lib/active_record/relation.rb +554 -343
- data/lib/active_record/result.rb +91 -47
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +134 -122
- data/lib/active_record/schema.rb +21 -24
- data/lib/active_record/schema_dumper.rb +141 -92
- data/lib/active_record/schema_migration.rb +24 -23
- data/lib/active_record/scoping/default.rb +96 -83
- data/lib/active_record/scoping/named.rb +78 -36
- data/lib/active_record/scoping.rb +45 -27
- data/lib/active_record/secure_token.rb +48 -0
- data/lib/active_record/serialization.rb +8 -6
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +89 -36
- data/lib/active_record/store.rb +128 -43
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +81 -0
- data/lib/active_record/tasks/database_tasks.rb +364 -130
- data/lib/active_record/tasks/mysql_database_tasks.rb +67 -113
- data/lib/active_record/tasks/postgresql_database_tasks.rb +86 -49
- data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -19
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +287 -0
- data/lib/active_record/timestamp.rb +86 -43
- data/lib/active_record/touch_later.rb +65 -0
- data/lib/active_record/transactions.rb +182 -163
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type/adapter_specific_registry.rb +126 -0
- data/lib/active_record/type/date.rb +4 -45
- data/lib/active_record/type/date_time.rb +4 -49
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +27 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +21 -16
- data/lib/active_record/type/type_map.rb +16 -19
- data/lib/active_record/type/unsigned_integer.rb +9 -8
- data/lib/active_record/type.rb +84 -23
- 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 +12 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +63 -56
- data/lib/active_record/validations.rb +39 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +42 -29
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +41 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -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 +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -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/delete_statement.rb +45 -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/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +76 -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/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +51 -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 +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -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 +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +70 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +250 -0
- data/lib/arel/select_manager.rb +270 -0
- data/lib/arel/table.rb +118 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/dot.rb +308 -0
- data/lib/arel/visitors/mysql.rb +93 -0
- data/lib/arel/visitors/postgresql.rb +120 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +899 -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 +54 -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 +43 -37
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +26 -0
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +13 -4
- data/lib/rails/generators/active_record/migration.rb +35 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +55 -22
- 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 → module.rb.tt} +0 -0
- data/lib/rails/generators/active_record.rb +7 -5
- metadata +172 -65
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute.rb +0 -163
- data/lib/active_record/attribute_decorators.rb +0 -66
- data/lib/active_record/attribute_set/builder.rb +0 -106
- data/lib/active_record/attribute_set.rb +0 -81
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -110
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,209 +1,87 @@
|
|
1
|
-
|
2
|
-
|
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/explain_pretty_printer"
|
7
|
+
require "active_record/connection_adapters/mysql/quoting"
|
8
|
+
require "active_record/connection_adapters/mysql/schema_creation"
|
9
|
+
require "active_record/connection_adapters/mysql/schema_definitions"
|
10
|
+
require "active_record/connection_adapters/mysql/schema_dumper"
|
11
|
+
require "active_record/connection_adapters/mysql/schema_statements"
|
12
|
+
require "active_record/connection_adapters/mysql/type_metadata"
|
3
13
|
|
4
14
|
module ActiveRecord
|
5
15
|
module ConnectionAdapters
|
6
16
|
class AbstractMysqlAdapter < AbstractAdapter
|
7
|
-
include
|
8
|
-
|
9
|
-
class SchemaCreation < AbstractAdapter::SchemaCreation
|
10
|
-
def visit_AddColumn(o)
|
11
|
-
add_column_position!(super, column_options(o))
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def visit_DropForeignKey(name)
|
17
|
-
"DROP FOREIGN KEY #{name}"
|
18
|
-
end
|
19
|
-
|
20
|
-
def visit_TableDefinition(o)
|
21
|
-
name = o.name
|
22
|
-
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
|
23
|
-
|
24
|
-
statements = o.columns.map { |c| accept c }
|
25
|
-
statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
|
26
|
-
|
27
|
-
create_sql << "(#{statements.join(', ')}) " if statements.present?
|
28
|
-
create_sql << "#{o.options}"
|
29
|
-
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
|
30
|
-
create_sql
|
31
|
-
end
|
32
|
-
|
33
|
-
def visit_ChangeColumnDefinition(o)
|
34
|
-
column = o.column
|
35
|
-
options = o.options
|
36
|
-
sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
|
37
|
-
change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
|
38
|
-
add_column_options!(change_column_sql, options.merge(column: column))
|
39
|
-
add_column_position!(change_column_sql, options)
|
40
|
-
end
|
41
|
-
|
42
|
-
def add_column_position!(sql, options)
|
43
|
-
if options[:first]
|
44
|
-
sql << " FIRST"
|
45
|
-
elsif options[:after]
|
46
|
-
sql << " AFTER #{quote_column_name(options[:after])}"
|
47
|
-
end
|
48
|
-
sql
|
49
|
-
end
|
50
|
-
|
51
|
-
def index_in_create(table_name, column_name, options)
|
52
|
-
index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
|
53
|
-
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def schema_creation
|
58
|
-
SchemaCreation.new self
|
59
|
-
end
|
60
|
-
|
61
|
-
def prepare_column_options(column, types) # :nodoc:
|
62
|
-
spec = super
|
63
|
-
spec.delete(:limit) if :boolean === column.type
|
64
|
-
spec
|
65
|
-
end
|
66
|
-
|
67
|
-
class Column < ConnectionAdapters::Column # :nodoc:
|
68
|
-
attr_reader :collation, :strict, :extra
|
69
|
-
|
70
|
-
def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
|
71
|
-
@strict = strict
|
72
|
-
@collation = collation
|
73
|
-
@extra = extra
|
74
|
-
super(name, default, cast_type, sql_type, null)
|
75
|
-
assert_valid_default(default)
|
76
|
-
extract_default
|
77
|
-
end
|
78
|
-
|
79
|
-
def extract_default
|
80
|
-
if blob_or_text_column?
|
81
|
-
@default = null || strict ? nil : ''
|
82
|
-
elsif missing_default_forged_as_empty_string?(@default)
|
83
|
-
@default = nil
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def has_default?
|
88
|
-
return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
|
89
|
-
super
|
90
|
-
end
|
91
|
-
|
92
|
-
def blob_or_text_column?
|
93
|
-
sql_type =~ /blob/i || type == :text
|
94
|
-
end
|
95
|
-
|
96
|
-
def case_sensitive?
|
97
|
-
collation && !collation.match(/_ci$/)
|
98
|
-
end
|
99
|
-
|
100
|
-
def ==(other)
|
101
|
-
super &&
|
102
|
-
collation == other.collation &&
|
103
|
-
strict == other.strict &&
|
104
|
-
extra == other.extra
|
105
|
-
end
|
106
|
-
|
107
|
-
private
|
108
|
-
|
109
|
-
# MySQL misreports NOT NULL column default when none is given.
|
110
|
-
# We can't detect this for columns which may have a legitimate ''
|
111
|
-
# default (string) but we can for others (integer, datetime, boolean,
|
112
|
-
# and the rest).
|
113
|
-
#
|
114
|
-
# Test whether the column has default '', is not null, and is not
|
115
|
-
# a type allowing default ''.
|
116
|
-
def missing_default_forged_as_empty_string?(default)
|
117
|
-
type != :string && !null && default == ''
|
118
|
-
end
|
119
|
-
|
120
|
-
def assert_valid_default(default)
|
121
|
-
if blob_or_text_column? && default.present?
|
122
|
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def attributes_for_hash
|
127
|
-
super + [collation, strict, extra]
|
128
|
-
end
|
129
|
-
end
|
17
|
+
include MySQL::Quoting
|
18
|
+
include MySQL::SchemaStatements
|
130
19
|
|
131
20
|
##
|
132
21
|
# :singleton-method:
|
133
|
-
# By default, the
|
134
|
-
# as boolean. If you wish to disable this emulation
|
135
|
-
# behavior in versions 0.13.1 and earlier) you can add the following line
|
22
|
+
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
|
23
|
+
# as boolean. If you wish to disable this emulation you can add the following line
|
136
24
|
# to your application.rb file:
|
137
25
|
#
|
138
|
-
# ActiveRecord::ConnectionAdapters::
|
139
|
-
class_attribute :emulate_booleans
|
140
|
-
self.emulate_booleans = true
|
141
|
-
|
142
|
-
LOST_CONNECTION_ERROR_MESSAGES = [
|
143
|
-
"Server shutdown in progress",
|
144
|
-
"Broken pipe",
|
145
|
-
"Lost connection to MySQL server during query",
|
146
|
-
"MySQL server has gone away" ]
|
147
|
-
|
148
|
-
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
26
|
+
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
|
27
|
+
class_attribute :emulate_booleans, default: true
|
149
28
|
|
150
29
|
NATIVE_DATABASE_TYPES = {
|
151
|
-
:
|
152
|
-
:
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
156
|
-
:
|
157
|
-
:
|
158
|
-
:
|
159
|
-
:
|
160
|
-
:
|
161
|
-
:
|
30
|
+
primary_key: "bigint auto_increment PRIMARY KEY",
|
31
|
+
string: { name: "varchar", limit: 255 },
|
32
|
+
text: { name: "text" },
|
33
|
+
integer: { name: "int", limit: 4 },
|
34
|
+
float: { name: "float", limit: 24 },
|
35
|
+
decimal: { name: "decimal" },
|
36
|
+
datetime: { name: "datetime" },
|
37
|
+
timestamp: { name: "timestamp" },
|
38
|
+
time: { name: "time" },
|
39
|
+
date: { name: "date" },
|
40
|
+
binary: { name: "blob" },
|
41
|
+
blob: { name: "blob" },
|
42
|
+
boolean: { name: "tinyint", limit: 1 },
|
43
|
+
json: { name: "json" },
|
162
44
|
}
|
163
45
|
|
164
|
-
|
165
|
-
|
46
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
47
|
+
private
|
48
|
+
def dealloc(stmt)
|
49
|
+
stmt.close
|
50
|
+
end
|
51
|
+
end
|
166
52
|
|
167
|
-
# FIXME: Make the first parameter more similar for the two adapters
|
168
53
|
def initialize(connection, logger, connection_options, config)
|
169
|
-
super(connection, logger)
|
170
|
-
|
171
|
-
@quoted_column_names, @quoted_table_names = {}, {}
|
54
|
+
super(connection, logger, config)
|
55
|
+
end
|
172
56
|
|
173
|
-
|
57
|
+
def get_database_version #:nodoc:
|
58
|
+
full_version_string = get_full_version
|
59
|
+
version_string = version_string(full_version_string)
|
60
|
+
Version.new(version_string, full_version_string)
|
61
|
+
end
|
174
62
|
|
175
|
-
|
176
|
-
|
177
|
-
else
|
178
|
-
@prepared_statements = false
|
179
|
-
end
|
63
|
+
def mariadb? # :nodoc:
|
64
|
+
/mariadb/i.match?(full_version)
|
180
65
|
end
|
181
66
|
|
182
|
-
|
183
|
-
def supports_migrations?
|
67
|
+
def supports_bulk_alter?
|
184
68
|
true
|
185
69
|
end
|
186
70
|
|
187
|
-
def
|
188
|
-
|
71
|
+
def supports_index_sort_order?
|
72
|
+
!mariadb? && database_version >= "8.0.1"
|
189
73
|
end
|
190
74
|
|
191
|
-
def
|
192
|
-
|
75
|
+
def supports_expression_index?
|
76
|
+
!mariadb? && database_version >= "8.0.13"
|
193
77
|
end
|
194
78
|
|
195
|
-
|
196
|
-
# but at the moment (5.5) it doesn't yet implement them
|
197
|
-
def supports_index_sort_order?
|
79
|
+
def supports_transaction_isolation?
|
198
80
|
true
|
199
81
|
end
|
200
82
|
|
201
|
-
|
202
|
-
|
203
|
-
#
|
204
|
-
# http://bugs.mysql.com/bug.php?id=39170
|
205
|
-
def supports_transaction_isolation?
|
206
|
-
version >= '5.0.0'
|
83
|
+
def supports_explain?
|
84
|
+
true
|
207
85
|
end
|
208
86
|
|
209
87
|
def supports_indexes_in_create?
|
@@ -214,78 +92,90 @@ module ActiveRecord
|
|
214
92
|
true
|
215
93
|
end
|
216
94
|
|
95
|
+
def supports_check_constraints?
|
96
|
+
if mariadb?
|
97
|
+
database_version >= "10.2.1"
|
98
|
+
else
|
99
|
+
database_version >= "8.0.16"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
217
103
|
def supports_views?
|
218
|
-
|
104
|
+
true
|
219
105
|
end
|
220
106
|
|
221
107
|
def supports_datetime_with_precision?
|
222
|
-
|
108
|
+
mariadb? || database_version >= "5.6.4"
|
223
109
|
end
|
224
110
|
|
225
|
-
def
|
226
|
-
|
111
|
+
def supports_virtual_columns?
|
112
|
+
mariadb? || database_version >= "5.7.5"
|
227
113
|
end
|
228
114
|
|
229
|
-
|
230
|
-
|
115
|
+
# See https://dev.mysql.com/doc/refman/en/optimizer-hints.html for more details.
|
116
|
+
def supports_optimizer_hints?
|
117
|
+
!mariadb? && database_version >= "5.7.7"
|
231
118
|
end
|
232
119
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
120
|
+
def supports_common_table_expressions?
|
121
|
+
if mariadb?
|
122
|
+
database_version >= "10.2.1"
|
123
|
+
else
|
124
|
+
database_version >= "8.0.1"
|
125
|
+
end
|
239
126
|
end
|
240
127
|
|
241
|
-
def
|
242
|
-
|
128
|
+
def supports_advisory_locks?
|
129
|
+
true
|
243
130
|
end
|
244
131
|
|
245
|
-
|
246
|
-
|
247
|
-
def error_number(exception) # :nodoc:
|
248
|
-
raise NotImplementedError
|
132
|
+
def supports_insert_on_duplicate_skip?
|
133
|
+
true
|
249
134
|
end
|
250
135
|
|
251
|
-
|
252
|
-
|
253
|
-
def _quote(value) # :nodoc:
|
254
|
-
if value.is_a?(Type::Binary::Data)
|
255
|
-
"x'#{value.hex}'"
|
256
|
-
else
|
257
|
-
super
|
258
|
-
end
|
136
|
+
def supports_insert_on_duplicate_update?
|
137
|
+
true
|
259
138
|
end
|
260
139
|
|
261
|
-
def
|
262
|
-
|
140
|
+
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
141
|
+
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
263
142
|
end
|
264
143
|
|
265
|
-
def
|
266
|
-
|
144
|
+
def release_advisory_lock(lock_name) # :nodoc:
|
145
|
+
query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
|
267
146
|
end
|
268
147
|
|
269
|
-
def
|
270
|
-
|
148
|
+
def native_database_types
|
149
|
+
NATIVE_DATABASE_TYPES
|
271
150
|
end
|
272
151
|
|
273
|
-
def
|
274
|
-
|
152
|
+
def index_algorithms
|
153
|
+
{
|
154
|
+
default: "ALGORITHM = DEFAULT",
|
155
|
+
copy: "ALGORITHM = COPY",
|
156
|
+
inplace: "ALGORITHM = INPLACE",
|
157
|
+
instant: "ALGORITHM = INSTANT",
|
158
|
+
}
|
275
159
|
end
|
276
160
|
|
277
|
-
|
278
|
-
|
161
|
+
# HELPER METHODS ===========================================
|
162
|
+
|
163
|
+
# The two drivers have slightly different ways of yielding hashes of results, so
|
164
|
+
# this method must be implemented to provide a uniform interface.
|
165
|
+
def each_hash(result) # :nodoc:
|
166
|
+
raise NotImplementedError
|
279
167
|
end
|
280
168
|
|
281
|
-
|
282
|
-
|
169
|
+
# Must return the MySQL error number from the exception, if the exception has an
|
170
|
+
# error number.
|
171
|
+
def error_number(exception) # :nodoc:
|
172
|
+
raise NotImplementedError
|
283
173
|
end
|
284
174
|
|
285
175
|
# REFERENTIAL INTEGRITY ====================================
|
286
176
|
|
287
177
|
def disable_referential_integrity #:nodoc:
|
288
|
-
old =
|
178
|
+
old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
|
289
179
|
|
290
180
|
begin
|
291
181
|
update("SET FOREIGN_KEY_CHECKS = 0")
|
@@ -295,34 +185,38 @@ module ActiveRecord
|
|
295
185
|
end
|
296
186
|
end
|
297
187
|
|
298
|
-
|
299
|
-
# DATABASE STATEMENTS ======================================
|
300
|
-
#++
|
188
|
+
# CONNECTION MANAGEMENT ====================================
|
301
189
|
|
302
|
-
def clear_cache!
|
303
|
-
super
|
190
|
+
def clear_cache! # :nodoc:
|
304
191
|
reload_type_map
|
192
|
+
super
|
305
193
|
end
|
306
194
|
|
195
|
+
#--
|
196
|
+
# DATABASE STATEMENTS ======================================
|
197
|
+
#++
|
198
|
+
|
307
199
|
# Executes the SQL statement in the context of this connection.
|
308
200
|
def execute(sql, name = nil)
|
309
|
-
|
310
|
-
|
201
|
+
materialize_transactions
|
202
|
+
mark_transaction_written_if_write(sql)
|
311
203
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
204
|
+
log(sql, name) do
|
205
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
206
|
+
@connection.query(sql)
|
207
|
+
end
|
208
|
+
end
|
317
209
|
end
|
318
210
|
|
319
|
-
|
320
|
-
|
321
|
-
|
211
|
+
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
|
212
|
+
# to write stuff in an abstract way without concerning ourselves about whether it
|
213
|
+
# needs to be explicitly freed or not.
|
214
|
+
def execute_and_free(sql, name = nil) # :nodoc:
|
215
|
+
yield execute(sql, name)
|
322
216
|
end
|
323
217
|
|
324
218
|
def begin_db_transaction
|
325
|
-
execute
|
219
|
+
execute("BEGIN", "TRANSACTION")
|
326
220
|
end
|
327
221
|
|
328
222
|
def begin_isolated_db_transaction(isolation)
|
@@ -331,26 +225,14 @@ module ActiveRecord
|
|
331
225
|
end
|
332
226
|
|
333
227
|
def commit_db_transaction #:nodoc:
|
334
|
-
execute
|
228
|
+
execute("COMMIT", "TRANSACTION")
|
335
229
|
end
|
336
230
|
|
337
231
|
def exec_rollback_db_transaction #:nodoc:
|
338
|
-
execute
|
339
|
-
end
|
340
|
-
|
341
|
-
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
342
|
-
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
343
|
-
# these, we must use a subquery.
|
344
|
-
def join_to_update(update, select) #:nodoc:
|
345
|
-
if select.limit || select.offset || select.orders.any?
|
346
|
-
super
|
347
|
-
else
|
348
|
-
update.table select.source
|
349
|
-
update.wheres = select.constraints
|
350
|
-
end
|
232
|
+
execute("ROLLBACK", "TRANSACTION")
|
351
233
|
end
|
352
234
|
|
353
|
-
def empty_insert_statement_value
|
235
|
+
def empty_insert_statement_value(primary_key = nil)
|
354
236
|
"VALUES ()"
|
355
237
|
end
|
356
238
|
|
@@ -366,7 +248,7 @@ module ActiveRecord
|
|
366
248
|
end
|
367
249
|
|
368
250
|
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
369
|
-
# Charset defaults to
|
251
|
+
# Charset defaults to utf8mb4.
|
370
252
|
#
|
371
253
|
# Example:
|
372
254
|
# create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
|
@@ -374,9 +256,13 @@ module ActiveRecord
|
|
374
256
|
# create_database 'matt_development', charset: :big5
|
375
257
|
def create_database(name, options = {})
|
376
258
|
if options[:collation]
|
377
|
-
execute "CREATE DATABASE
|
259
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
|
260
|
+
elsif options[:charset]
|
261
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
|
262
|
+
elsif row_format_dynamic_by_default?
|
263
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
|
378
264
|
else
|
379
|
-
|
265
|
+
raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
|
380
266
|
end
|
381
267
|
end
|
382
268
|
|
@@ -385,108 +271,38 @@ module ActiveRecord
|
|
385
271
|
# Example:
|
386
272
|
# drop_database('sebastian_development')
|
387
273
|
def drop_database(name) #:nodoc:
|
388
|
-
execute "DROP DATABASE IF EXISTS
|
274
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
389
275
|
end
|
390
276
|
|
391
277
|
def current_database
|
392
|
-
|
278
|
+
query_value("SELECT database()", "SCHEMA")
|
393
279
|
end
|
394
280
|
|
395
281
|
# Returns the database character set.
|
396
282
|
def charset
|
397
|
-
show_variable
|
283
|
+
show_variable "character_set_database"
|
398
284
|
end
|
399
285
|
|
400
286
|
# Returns the database collation strategy.
|
401
287
|
def collation
|
402
|
-
show_variable
|
403
|
-
end
|
404
|
-
|
405
|
-
def tables(name = nil, database = nil, like = nil) #:nodoc:
|
406
|
-
sql = "SHOW TABLES "
|
407
|
-
sql << "IN #{quote_table_name(database)} " if database
|
408
|
-
sql << "LIKE #{quote(like)}" if like
|
409
|
-
|
410
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
411
|
-
result.collect { |field| field.first }
|
412
|
-
end
|
413
|
-
end
|
414
|
-
alias data_sources tables
|
415
|
-
|
416
|
-
def truncate(table_name, name = nil)
|
417
|
-
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
288
|
+
show_variable "collation_database"
|
418
289
|
end
|
419
290
|
|
420
|
-
def
|
421
|
-
|
422
|
-
return true if tables(nil, nil, name).any?
|
291
|
+
def table_comment(table_name) # :nodoc:
|
292
|
+
scope = quoted_scope(table_name)
|
423
293
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
end
|
431
|
-
|
432
|
-
tables(nil, schema, table).any?
|
433
|
-
end
|
434
|
-
alias data_source_exists? table_exists?
|
435
|
-
|
436
|
-
# Returns an array of indexes for the given table.
|
437
|
-
def indexes(table_name, name = nil) #:nodoc:
|
438
|
-
indexes = []
|
439
|
-
current_index = nil
|
440
|
-
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
|
441
|
-
each_hash(result) do |row|
|
442
|
-
if current_index != row[:Key_name]
|
443
|
-
next if row[:Key_name] == 'PRIMARY' # skip the primary key
|
444
|
-
current_index = row[:Key_name]
|
445
|
-
|
446
|
-
mysql_index_type = row[:Index_type].downcase.to_sym
|
447
|
-
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
|
448
|
-
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
|
449
|
-
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
|
450
|
-
end
|
451
|
-
|
452
|
-
indexes.last.columns << row[:Column_name]
|
453
|
-
indexes.last.lengths << row[:Sub_part]
|
454
|
-
end
|
455
|
-
end
|
456
|
-
|
457
|
-
indexes
|
458
|
-
end
|
459
|
-
|
460
|
-
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
461
|
-
def columns(table_name)#:nodoc:
|
462
|
-
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
463
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
464
|
-
each_hash(result).map do |field|
|
465
|
-
field_name = set_field_encoding(field[:Field])
|
466
|
-
sql_type = field[:Type]
|
467
|
-
cast_type = lookup_cast_type(sql_type)
|
468
|
-
new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
|
469
|
-
end
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
|
-
def create_table(table_name, options = {}) #:nodoc:
|
474
|
-
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
294
|
+
query_value(<<~SQL, "SCHEMA").presence
|
295
|
+
SELECT table_comment
|
296
|
+
FROM information_schema.tables
|
297
|
+
WHERE table_schema = #{scope[:schema]}
|
298
|
+
AND table_name = #{scope[:name]}
|
299
|
+
SQL
|
475
300
|
end
|
476
301
|
|
477
|
-
def
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
if respond_to?(method, true)
|
483
|
-
send(method, table, *arguments)
|
484
|
-
else
|
485
|
-
raise "Unknown method called : #{method}(#{arguments.inspect})"
|
486
|
-
end
|
487
|
-
end.join(", ")
|
488
|
-
|
489
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
|
302
|
+
def change_table_comment(table_name, comment_or_changes) # :nodoc:
|
303
|
+
comment = extract_new_comment_value(comment_or_changes)
|
304
|
+
comment = "" if comment.nil?
|
305
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
|
490
306
|
end
|
491
307
|
|
492
308
|
# Renames a table.
|
@@ -494,12 +310,30 @@ module ActiveRecord
|
|
494
310
|
# Example:
|
495
311
|
# rename_table('octopuses', 'octopi')
|
496
312
|
def rename_table(table_name, new_name)
|
313
|
+
schema_cache.clear_data_source_cache!(table_name.to_s)
|
314
|
+
schema_cache.clear_data_source_cache!(new_name.to_s)
|
497
315
|
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
498
316
|
rename_table_indexes(table_name, new_name)
|
499
317
|
end
|
500
318
|
|
501
|
-
|
502
|
-
|
319
|
+
# Drops a table from the database.
|
320
|
+
#
|
321
|
+
# [<tt>:force</tt>]
|
322
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
323
|
+
# Defaults to false.
|
324
|
+
# [<tt>:if_exists</tt>]
|
325
|
+
# Set to +true+ to only drop the table if it exists.
|
326
|
+
# Defaults to false.
|
327
|
+
# [<tt>:temporary</tt>]
|
328
|
+
# Set to +true+ to drop temporary table.
|
329
|
+
# Defaults to false.
|
330
|
+
#
|
331
|
+
# Although this command ignores most +options+ and the block if one is given,
|
332
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
333
|
+
# In that case, +options+ and the block will be used by create_table.
|
334
|
+
def drop_table(table_name, **options)
|
335
|
+
schema_cache.clear_data_source_cache!(table_name.to_s)
|
336
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
503
337
|
end
|
504
338
|
|
505
339
|
def rename_index(table_name, old_name, new_name)
|
@@ -512,427 +346,510 @@ module ActiveRecord
|
|
512
346
|
end
|
513
347
|
end
|
514
348
|
|
515
|
-
def change_column_default(table_name, column_name,
|
516
|
-
|
517
|
-
change_column table_name, column_name,
|
349
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
350
|
+
default = extract_new_default_value(default_or_changes)
|
351
|
+
change_column table_name, column_name, nil, default: default
|
518
352
|
end
|
519
353
|
|
520
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
521
|
-
column = column_for(table_name, column_name)
|
522
|
-
|
354
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
523
355
|
unless null || default.nil?
|
524
356
|
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
525
357
|
end
|
526
358
|
|
527
|
-
change_column table_name, column_name,
|
359
|
+
change_column table_name, column_name, nil, null: null
|
528
360
|
end
|
529
361
|
|
530
|
-
def
|
531
|
-
|
362
|
+
def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
|
363
|
+
comment = extract_new_comment_value(comment_or_changes)
|
364
|
+
change_column table_name, column_name, nil, comment: comment
|
365
|
+
end
|
366
|
+
|
367
|
+
def change_column(table_name, column_name, type, **options) #:nodoc:
|
368
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}")
|
532
369
|
end
|
533
370
|
|
534
371
|
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
535
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{
|
372
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
|
536
373
|
rename_column_indexes(table_name, column_name, new_column_name)
|
537
374
|
end
|
538
375
|
|
539
|
-
def add_index(table_name, column_name, options
|
540
|
-
|
541
|
-
|
376
|
+
def add_index(table_name, column_name, **options) #:nodoc:
|
377
|
+
index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
|
378
|
+
|
379
|
+
return if if_not_exists && index_exists?(table_name, column_name, name: index.name)
|
380
|
+
|
381
|
+
create_index = CreateIndexDefinition.new(index, algorithm)
|
382
|
+
execute schema_creation.accept(create_index)
|
383
|
+
end
|
384
|
+
|
385
|
+
def add_sql_comment!(sql, comment) # :nodoc:
|
386
|
+
sql << " COMMENT #{quote(comment)}" if comment.present?
|
387
|
+
sql
|
542
388
|
end
|
543
389
|
|
544
390
|
def foreign_keys(table_name)
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
391
|
+
raise ArgumentError unless table_name.present?
|
392
|
+
|
393
|
+
scope = quoted_scope(table_name)
|
394
|
+
|
395
|
+
fk_info = exec_query(<<~SQL, "SCHEMA")
|
396
|
+
SELECT fk.referenced_table_name AS 'to_table',
|
397
|
+
fk.referenced_column_name AS 'primary_key',
|
398
|
+
fk.column_name AS 'column',
|
399
|
+
fk.constraint_name AS 'name',
|
400
|
+
rc.update_rule AS 'on_update',
|
401
|
+
rc.delete_rule AS 'on_delete'
|
402
|
+
FROM information_schema.referential_constraints rc
|
403
|
+
JOIN information_schema.key_column_usage fk
|
404
|
+
USING (constraint_schema, constraint_name)
|
405
|
+
WHERE fk.referenced_column_name IS NOT NULL
|
406
|
+
AND fk.table_schema = #{scope[:schema]}
|
407
|
+
AND fk.table_name = #{scope[:name]}
|
408
|
+
AND rc.constraint_schema = #{scope[:schema]}
|
409
|
+
AND rc.table_name = #{scope[:name]}
|
554
410
|
SQL
|
555
411
|
|
556
|
-
create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
557
|
-
|
558
412
|
fk_info.map do |row|
|
559
413
|
options = {
|
560
|
-
column: row[
|
561
|
-
name: row[
|
562
|
-
primary_key: row[
|
414
|
+
column: row["column"],
|
415
|
+
name: row["name"],
|
416
|
+
primary_key: row["primary_key"]
|
563
417
|
}
|
564
418
|
|
565
|
-
options[:on_update] = extract_foreign_key_action(
|
566
|
-
options[:on_delete] = extract_foreign_key_action(
|
419
|
+
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
420
|
+
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
567
421
|
|
568
|
-
ForeignKeyDefinition.new(table_name, row[
|
422
|
+
ForeignKeyDefinition.new(table_name, row["to_table"], options)
|
569
423
|
end
|
570
424
|
end
|
571
425
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
when 'binary'
|
576
|
-
case limit
|
577
|
-
when 0..0xfff; "varbinary(#{limit})"
|
578
|
-
when nil; "blob"
|
579
|
-
when 0x1000..0xffffffff; "blob(#{limit})"
|
580
|
-
else raise(ActiveRecordError, "No binary type has character length #{limit}")
|
581
|
-
end
|
582
|
-
when 'integer'
|
583
|
-
case limit
|
584
|
-
when 1; 'tinyint'
|
585
|
-
when 2; 'smallint'
|
586
|
-
when 3; 'mediumint'
|
587
|
-
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
588
|
-
when 5..8; 'bigint'
|
589
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
590
|
-
end
|
591
|
-
when 'text'
|
592
|
-
case limit
|
593
|
-
when 0..0xff; 'tinytext'
|
594
|
-
when nil, 0x100..0xffff; 'text'
|
595
|
-
when 0x10000..0xffffff; 'mediumtext'
|
596
|
-
when 0x1000000..0xffffffff; 'longtext'
|
597
|
-
else raise(ActiveRecordError, "No text type has character length #{limit}")
|
598
|
-
end
|
599
|
-
when 'datetime'
|
600
|
-
return super unless precision
|
426
|
+
def check_constraints(table_name)
|
427
|
+
if supports_check_constraints?
|
428
|
+
scope = quoted_scope(table_name)
|
601
429
|
|
602
|
-
|
603
|
-
|
604
|
-
|
430
|
+
chk_info = exec_query(<<~SQL, "SCHEMA")
|
431
|
+
SELECT cc.constraint_name AS 'name',
|
432
|
+
cc.check_clause AS 'expression'
|
433
|
+
FROM information_schema.check_constraints cc
|
434
|
+
JOIN information_schema.table_constraints tc
|
435
|
+
USING (constraint_schema, constraint_name)
|
436
|
+
WHERE tc.table_schema = #{scope[:schema]}
|
437
|
+
AND tc.table_name = #{scope[:name]}
|
438
|
+
AND cc.constraint_schema = #{scope[:schema]}
|
439
|
+
SQL
|
440
|
+
|
441
|
+
chk_info.map do |row|
|
442
|
+
options = {
|
443
|
+
name: row["name"]
|
444
|
+
}
|
445
|
+
expression = row["expression"]
|
446
|
+
expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
|
447
|
+
CheckConstraintDefinition.new(table_name, expression, options)
|
605
448
|
end
|
606
449
|
else
|
607
|
-
|
450
|
+
raise NotImplementedError
|
608
451
|
end
|
609
452
|
end
|
610
453
|
|
454
|
+
def table_options(table_name) # :nodoc:
|
455
|
+
create_table_info = create_table_info(table_name)
|
456
|
+
|
457
|
+
# strip create_definitions and partition_options
|
458
|
+
# Be aware that `create_table_info` might not include any table options due to `NO_TABLE_OPTIONS` sql mode.
|
459
|
+
raw_table_options = create_table_info.sub(/\A.*\n\) ?/m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
|
460
|
+
|
461
|
+
return if raw_table_options.empty?
|
462
|
+
|
463
|
+
table_options = {}
|
464
|
+
|
465
|
+
if / DEFAULT CHARSET=(?<charset>\w+)(?: COLLATE=(?<collation>\w+))?/ =~ raw_table_options
|
466
|
+
raw_table_options = $` + $' # before part + after part
|
467
|
+
table_options[:charset] = charset
|
468
|
+
table_options[:collation] = collation if collation
|
469
|
+
end
|
470
|
+
|
471
|
+
# strip AUTO_INCREMENT
|
472
|
+
raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
|
473
|
+
|
474
|
+
# strip COMMENT
|
475
|
+
if raw_table_options.sub!(/ COMMENT='.+'/, "")
|
476
|
+
table_options[:comment] = table_comment(table_name)
|
477
|
+
end
|
478
|
+
|
479
|
+
table_options[:options] = raw_table_options unless raw_table_options == "ENGINE=InnoDB"
|
480
|
+
table_options
|
481
|
+
end
|
482
|
+
|
611
483
|
# SHOW VARIABLES LIKE 'name'
|
612
484
|
def show_variable(name)
|
613
|
-
|
614
|
-
variables.first['Value'] unless variables.empty?
|
485
|
+
query_value("SELECT @@#{name}", "SCHEMA")
|
615
486
|
rescue ActiveRecord::StatementInvalid
|
616
487
|
nil
|
617
488
|
end
|
618
489
|
|
619
|
-
|
620
|
-
|
621
|
-
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
|
622
|
-
create_table = each_hash(result).first[:"Create Table"]
|
623
|
-
if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
|
624
|
-
keys = $1.split(",").map { |key| key.delete('`"') }
|
625
|
-
keys.length == 1 ? [keys.first, nil] : nil
|
626
|
-
else
|
627
|
-
nil
|
628
|
-
end
|
629
|
-
end
|
630
|
-
end
|
490
|
+
def primary_keys(table_name) # :nodoc:
|
491
|
+
raise ArgumentError unless table_name.present?
|
631
492
|
|
632
|
-
|
633
|
-
def primary_key(table)
|
634
|
-
pk_and_sequence = pk_and_sequence_for(table)
|
635
|
-
pk_and_sequence && pk_and_sequence.first
|
636
|
-
end
|
493
|
+
scope = quoted_scope(table_name)
|
637
494
|
|
638
|
-
|
639
|
-
|
640
|
-
|
495
|
+
query_values(<<~SQL, "SCHEMA")
|
496
|
+
SELECT column_name
|
497
|
+
FROM information_schema.statistics
|
498
|
+
WHERE index_name = 'PRIMARY'
|
499
|
+
AND table_schema = #{scope[:schema]}
|
500
|
+
AND table_name = #{scope[:name]}
|
501
|
+
ORDER BY seq_in_index
|
502
|
+
SQL
|
641
503
|
end
|
642
504
|
|
643
|
-
def case_sensitive_comparison(
|
644
|
-
|
645
|
-
|
505
|
+
def case_sensitive_comparison(attribute, value) # :nodoc:
|
506
|
+
column = column_for_attribute(attribute)
|
507
|
+
|
508
|
+
if column.collation && !column.case_sensitive?
|
509
|
+
attribute.eq(Arel::Nodes::Bin.new(value))
|
646
510
|
else
|
647
511
|
super
|
648
512
|
end
|
649
513
|
end
|
650
514
|
|
651
|
-
def
|
652
|
-
|
653
|
-
super
|
654
|
-
else
|
655
|
-
table[attribute].eq(value)
|
656
|
-
end
|
515
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
516
|
+
column.case_sensitive?
|
657
517
|
end
|
518
|
+
private :can_perform_case_insensitive_comparison_for?
|
658
519
|
|
659
520
|
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
660
521
|
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
661
522
|
# distinct queries, and requires that the ORDER BY include the distinct column.
|
662
|
-
# See https://dev.mysql.com/doc/refman/
|
523
|
+
# See https://dev.mysql.com/doc/refman/en/group-by-handling.html
|
663
524
|
def columns_for_distinct(columns, orders) # :nodoc:
|
664
|
-
order_columns = orders.
|
525
|
+
order_columns = orders.compact_blank.map { |s|
|
665
526
|
# Convert Arel node to string
|
666
|
-
s = s
|
527
|
+
s = visitor.compile(s) unless s.is_a?(String)
|
667
528
|
# Remove any ASC/DESC modifiers
|
668
|
-
s.gsub(/\s+(?:ASC|DESC)\b/i,
|
669
|
-
}.
|
529
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
530
|
+
}.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
670
531
|
|
671
|
-
|
532
|
+
(order_columns << super).join(", ")
|
672
533
|
end
|
673
534
|
|
674
535
|
def strict_mode?
|
675
536
|
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
|
676
537
|
end
|
677
538
|
|
678
|
-
def
|
679
|
-
|
539
|
+
def default_index_type?(index) # :nodoc:
|
540
|
+
index.using == :btree || super
|
680
541
|
end
|
681
542
|
|
682
|
-
|
543
|
+
def build_insert_sql(insert) # :nodoc:
|
544
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
683
545
|
|
684
|
-
|
685
|
-
|
546
|
+
if insert.skip_duplicates?
|
547
|
+
no_op_column = quote_column_name(insert.keys.first)
|
548
|
+
sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
|
549
|
+
elsif insert.update_duplicates?
|
550
|
+
sql << " ON DUPLICATE KEY UPDATE "
|
551
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
|
552
|
+
sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
|
553
|
+
end
|
686
554
|
|
687
|
-
|
555
|
+
sql
|
556
|
+
end
|
688
557
|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
|
695
|
-
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
|
696
|
-
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
697
|
-
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
698
|
-
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
558
|
+
def check_version # :nodoc:
|
559
|
+
if database_version < "5.5.8"
|
560
|
+
raise "Your version of MySQL (#{database_version}) is too old. Active Record supports MySQL >= 5.5.8."
|
561
|
+
end
|
562
|
+
end
|
699
563
|
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
register_integer_type m, %r(^smallint)i, limit: 2
|
704
|
-
register_integer_type m, %r(^tinyint)i, limit: 1
|
564
|
+
private
|
565
|
+
def initialize_type_map(m = type_map)
|
566
|
+
super
|
705
567
|
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
568
|
+
m.register_type(%r(char)i) do |sql_type|
|
569
|
+
limit = extract_limit(sql_type)
|
570
|
+
Type.lookup(:string, adapter: :mysql2, limit: limit)
|
571
|
+
end
|
710
572
|
|
711
|
-
|
712
|
-
|
713
|
-
|
573
|
+
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
|
574
|
+
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
|
575
|
+
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
|
576
|
+
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
|
577
|
+
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
|
578
|
+
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
|
579
|
+
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
|
580
|
+
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
581
|
+
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
582
|
+
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
583
|
+
|
584
|
+
register_integer_type m, %r(^bigint)i, limit: 8
|
585
|
+
register_integer_type m, %r(^int)i, limit: 4
|
586
|
+
register_integer_type m, %r(^mediumint)i, limit: 3
|
587
|
+
register_integer_type m, %r(^smallint)i, limit: 2
|
588
|
+
register_integer_type m, %r(^tinyint)i, limit: 1
|
589
|
+
|
590
|
+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
|
591
|
+
m.alias_type %r(year)i, "integer"
|
592
|
+
m.alias_type %r(bit)i, "binary"
|
593
|
+
|
594
|
+
m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
|
595
|
+
m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
|
596
|
+
end
|
597
|
+
|
598
|
+
def register_integer_type(mapping, key, **options)
|
599
|
+
mapping.register_type(key) do |sql_type|
|
600
|
+
if /\bunsigned\b/.match?(sql_type)
|
601
|
+
Type::UnsignedInteger.new(**options)
|
602
|
+
else
|
603
|
+
Type::Integer.new(**options)
|
604
|
+
end
|
605
|
+
end
|
714
606
|
end
|
715
607
|
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
608
|
+
def extract_precision(sql_type)
|
609
|
+
if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
|
610
|
+
super || 0
|
611
|
+
else
|
612
|
+
super
|
613
|
+
end
|
720
614
|
end
|
721
|
-
end
|
722
615
|
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
616
|
+
# See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html
|
617
|
+
ER_DB_CREATE_EXISTS = 1007
|
618
|
+
ER_FILSORT_ABORT = 1028
|
619
|
+
ER_DUP_ENTRY = 1062
|
620
|
+
ER_NOT_NULL_VIOLATION = 1048
|
621
|
+
ER_NO_REFERENCED_ROW = 1216
|
622
|
+
ER_ROW_IS_REFERENCED = 1217
|
623
|
+
ER_DO_NOT_HAVE_DEFAULT = 1364
|
624
|
+
ER_ROW_IS_REFERENCED_2 = 1451
|
625
|
+
ER_NO_REFERENCED_ROW_2 = 1452
|
626
|
+
ER_DATA_TOO_LONG = 1406
|
627
|
+
ER_OUT_OF_RANGE = 1264
|
628
|
+
ER_LOCK_DEADLOCK = 1213
|
629
|
+
ER_CANNOT_ADD_FOREIGN = 1215
|
630
|
+
ER_CANNOT_CREATE_TABLE = 1005
|
631
|
+
ER_LOCK_WAIT_TIMEOUT = 1205
|
632
|
+
ER_QUERY_INTERRUPTED = 1317
|
633
|
+
ER_QUERY_TIMEOUT = 3024
|
634
|
+
ER_FK_INCOMPATIBLE_COLUMNS = 3780
|
635
|
+
|
636
|
+
def translate_exception(exception, message:, sql:, binds:)
|
637
|
+
case error_number(exception)
|
638
|
+
when nil
|
639
|
+
if exception.message.match?(/MySQL client is not connected/i)
|
640
|
+
ConnectionNotEstablished.new(exception)
|
641
|
+
else
|
642
|
+
super
|
643
|
+
end
|
644
|
+
when ER_DB_CREATE_EXISTS
|
645
|
+
DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
|
646
|
+
when ER_DUP_ENTRY
|
647
|
+
RecordNotUnique.new(message, sql: sql, binds: binds)
|
648
|
+
when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
|
649
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
650
|
+
when ER_CANNOT_ADD_FOREIGN, ER_FK_INCOMPATIBLE_COLUMNS
|
651
|
+
mismatched_foreign_key(message, sql: sql, binds: binds)
|
652
|
+
when ER_CANNOT_CREATE_TABLE
|
653
|
+
if message.include?("errno: 150")
|
654
|
+
mismatched_foreign_key(message, sql: sql, binds: binds)
|
655
|
+
else
|
656
|
+
super
|
657
|
+
end
|
658
|
+
when ER_DATA_TOO_LONG
|
659
|
+
ValueTooLong.new(message, sql: sql, binds: binds)
|
660
|
+
when ER_OUT_OF_RANGE
|
661
|
+
RangeError.new(message, sql: sql, binds: binds)
|
662
|
+
when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
|
663
|
+
NotNullViolation.new(message, sql: sql, binds: binds)
|
664
|
+
when ER_LOCK_DEADLOCK
|
665
|
+
Deadlocked.new(message, sql: sql, binds: binds)
|
666
|
+
when ER_LOCK_WAIT_TIMEOUT
|
667
|
+
LockWaitTimeout.new(message, sql: sql, binds: binds)
|
668
|
+
when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
|
669
|
+
StatementTimeout.new(message, sql: sql, binds: binds)
|
670
|
+
when ER_QUERY_INTERRUPTED
|
671
|
+
QueryCanceled.new(message, sql: sql, binds: binds)
|
727
672
|
else
|
728
|
-
|
673
|
+
super
|
729
674
|
end
|
730
675
|
end
|
731
|
-
end
|
732
676
|
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
subsubselect = select.clone
|
737
|
-
subsubselect.projections = [key]
|
677
|
+
def change_column_for_alter(table_name, column_name, type, **options)
|
678
|
+
column = column_for(table_name, column_name)
|
679
|
+
type ||= column.sql_type
|
738
680
|
|
739
|
-
|
740
|
-
|
741
|
-
subsubselect.distinct unless select.limit || select.offset || select.orders.any?
|
742
|
-
|
743
|
-
subselect = Arel::SelectManager.new(select.engine)
|
744
|
-
subselect.project Arel.sql(key.name)
|
745
|
-
subselect.from subsubselect.as('__active_record_temp')
|
746
|
-
end
|
747
|
-
|
748
|
-
def add_index_length(option_strings, column_names, options = {})
|
749
|
-
if options.is_a?(Hash) && length = options[:length]
|
750
|
-
case length
|
751
|
-
when Hash
|
752
|
-
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
|
753
|
-
when Integer
|
754
|
-
column_names.each {|name| option_strings[name] += "(#{length})"}
|
681
|
+
unless options.key?(:default)
|
682
|
+
options[:default] = column.default
|
755
683
|
end
|
756
|
-
end
|
757
684
|
|
758
|
-
|
759
|
-
|
685
|
+
unless options.key?(:null)
|
686
|
+
options[:null] = column.null
|
687
|
+
end
|
760
688
|
|
761
|
-
|
762
|
-
|
689
|
+
unless options.key?(:comment)
|
690
|
+
options[:comment] = column.comment
|
691
|
+
end
|
763
692
|
|
764
|
-
|
765
|
-
|
693
|
+
td = create_table_definition(table_name)
|
694
|
+
cd = td.new_column_definition(column.name, type, **options)
|
695
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
696
|
+
end
|
766
697
|
|
767
|
-
|
768
|
-
|
698
|
+
def rename_column_for_alter(table_name, column_name, new_column_name)
|
699
|
+
return rename_column_sql(table_name, column_name, new_column_name) if supports_rename_column?
|
769
700
|
|
770
|
-
|
771
|
-
|
701
|
+
column = column_for(table_name, column_name)
|
702
|
+
options = {
|
703
|
+
default: column.default,
|
704
|
+
null: column.null,
|
705
|
+
auto_increment: column.auto_increment?,
|
706
|
+
comment: column.comment
|
707
|
+
}
|
772
708
|
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
when 1452
|
778
|
-
InvalidForeignKey.new(message, exception)
|
779
|
-
else
|
780
|
-
super
|
709
|
+
current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
|
710
|
+
td = create_table_definition(table_name)
|
711
|
+
cd = td.new_column_definition(new_column_name, current_type, **options)
|
712
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
781
713
|
end
|
782
|
-
end
|
783
714
|
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
schema_creation.visit_AddColumn cd
|
788
|
-
end
|
715
|
+
def add_index_for_alter(table_name, column_name, **options)
|
716
|
+
index, algorithm, _ = add_index_options(table_name, column_name, **options)
|
717
|
+
algorithm = ", #{algorithm}" if algorithm
|
789
718
|
|
790
|
-
|
791
|
-
column = column_for(table_name, column_name)
|
792
|
-
|
793
|
-
unless options_include_default?(options)
|
794
|
-
options[:default] = column.default
|
719
|
+
"ADD #{schema_creation.accept(index)}#{algorithm}"
|
795
720
|
end
|
796
721
|
|
797
|
-
|
798
|
-
|
722
|
+
def remove_index_for_alter(table_name, column_name = nil, **options)
|
723
|
+
index_name = index_name_for_remove(table_name, column_name, options)
|
724
|
+
"DROP INDEX #{quote_column_name(index_name)}"
|
799
725
|
end
|
800
726
|
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
name: new_column_name,
|
809
|
-
default: column.default,
|
810
|
-
null: column.null,
|
811
|
-
auto_increment: column.extra == "auto_increment"
|
812
|
-
}
|
813
|
-
|
814
|
-
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
815
|
-
schema_creation.accept ChangeColumnDefinition.new column, current_type, options
|
816
|
-
end
|
817
|
-
|
818
|
-
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
819
|
-
"DROP #{quote_column_name(column_name)}"
|
820
|
-
end
|
821
|
-
|
822
|
-
def remove_columns_sql(table_name, *column_names)
|
823
|
-
column_names.map {|column_name| remove_column_sql(table_name, column_name) }
|
824
|
-
end
|
825
|
-
|
826
|
-
def add_index_sql(table_name, column_name, options = {})
|
827
|
-
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
828
|
-
"ADD #{index_type} INDEX #{index_name} (#{index_columns})"
|
829
|
-
end
|
727
|
+
def supports_rename_index?
|
728
|
+
if mariadb?
|
729
|
+
database_version >= "10.5.2"
|
730
|
+
else
|
731
|
+
database_version >= "5.7.6"
|
732
|
+
end
|
733
|
+
end
|
830
734
|
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
735
|
+
def supports_rename_column?
|
736
|
+
if mariadb?
|
737
|
+
database_version >= "10.5.2"
|
738
|
+
else
|
739
|
+
database_version >= "8.0.3"
|
740
|
+
end
|
741
|
+
end
|
835
742
|
|
836
|
-
|
837
|
-
|
838
|
-
end
|
743
|
+
def configure_connection
|
744
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
839
745
|
|
840
|
-
|
841
|
-
|
842
|
-
end
|
746
|
+
# By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
|
747
|
+
variables["sql_auto_is_null"] = 0
|
843
748
|
|
844
|
-
|
749
|
+
# Increase timeout so the server doesn't disconnect us.
|
750
|
+
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
|
751
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
752
|
+
variables["wait_timeout"] = wait_timeout
|
845
753
|
|
846
|
-
|
847
|
-
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
|
848
|
-
end
|
754
|
+
defaults = [":default", :default].to_set
|
849
755
|
|
850
|
-
|
851
|
-
|
852
|
-
|
756
|
+
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
757
|
+
# https://dev.mysql.com/doc/refman/en/sql-mode.html#sqlmode_strict_all_tables
|
758
|
+
# If the user has provided another value for sql_mode, don't replace it.
|
759
|
+
if sql_mode = variables.delete("sql_mode")
|
760
|
+
sql_mode = quote(sql_mode)
|
761
|
+
elsif !defaults.include?(strict_mode?)
|
762
|
+
if strict_mode?
|
763
|
+
sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
|
764
|
+
else
|
765
|
+
sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
|
766
|
+
sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
|
767
|
+
sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
|
768
|
+
end
|
769
|
+
sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
|
770
|
+
end
|
771
|
+
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
|
772
|
+
|
773
|
+
# NAMES does not have an equals sign, see
|
774
|
+
# https://dev.mysql.com/doc/refman/en/set-names.html
|
775
|
+
# (trailing comma because variable_assignments will always have content)
|
776
|
+
if @config[:encoding]
|
777
|
+
encoding = +"NAMES #{@config[:encoding]}"
|
778
|
+
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
|
779
|
+
encoding << ", "
|
780
|
+
end
|
853
781
|
|
854
|
-
|
855
|
-
|
856
|
-
|
782
|
+
# Gather up all of the SET variables...
|
783
|
+
variable_assignments = variables.map do |k, v|
|
784
|
+
if defaults.include?(v)
|
785
|
+
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
786
|
+
elsif !v.nil?
|
787
|
+
"@@SESSION.#{k} = #{quote(v)}"
|
788
|
+
end
|
789
|
+
# or else nil; compact to clear nils out
|
790
|
+
end.compact.join(", ")
|
857
791
|
|
858
|
-
|
859
|
-
|
792
|
+
# ...and send them all in one query
|
793
|
+
execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
|
794
|
+
end
|
860
795
|
|
861
|
-
|
862
|
-
|
863
|
-
|
796
|
+
def column_definitions(table_name) # :nodoc:
|
797
|
+
execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
|
798
|
+
each_hash(result)
|
799
|
+
end
|
800
|
+
end
|
864
801
|
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
variables["wait_timeout"] = wait_timeout
|
802
|
+
def create_table_info(table_name) # :nodoc:
|
803
|
+
exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
|
804
|
+
end
|
869
805
|
|
870
|
-
|
871
|
-
|
872
|
-
# If the user has provided another value for sql_mode, don't replace it.
|
873
|
-
unless variables.has_key?('sql_mode')
|
874
|
-
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
|
806
|
+
def arel_visitor
|
807
|
+
Arel::Visitors::MySQL.new(self)
|
875
808
|
end
|
876
809
|
|
877
|
-
|
878
|
-
|
879
|
-
# (trailing comma because variable_assignments will always have content)
|
880
|
-
if @config[:encoding]
|
881
|
-
encoding = "NAMES #{@config[:encoding]}"
|
882
|
-
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
|
883
|
-
encoding << ", "
|
810
|
+
def build_statement_pool
|
811
|
+
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
884
812
|
end
|
885
813
|
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
end
|
893
|
-
# or else nil; compact to clear nils out
|
894
|
-
end.compact.join(', ')
|
814
|
+
def mismatched_foreign_key(message, sql:, binds:)
|
815
|
+
match = %r/
|
816
|
+
(?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
|
817
|
+
FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
|
818
|
+
REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
|
819
|
+
/xmi.match(sql)
|
895
820
|
|
896
|
-
|
897
|
-
|
898
|
-
|
821
|
+
options = {
|
822
|
+
message: message,
|
823
|
+
sql: sql,
|
824
|
+
binds: binds,
|
825
|
+
}
|
899
826
|
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
827
|
+
if match
|
828
|
+
options[:table] = match[:table]
|
829
|
+
options[:foreign_key] = match[:foreign_key]
|
830
|
+
options[:target_table] = match[:target_table]
|
831
|
+
options[:primary_key] = match[:primary_key]
|
832
|
+
options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
|
905
833
|
end
|
906
|
-
end
|
907
|
-
end
|
908
834
|
|
909
|
-
|
910
|
-
private
|
911
|
-
|
912
|
-
def has_precision?
|
913
|
-
precision || 0
|
835
|
+
MismatchedForeignKey.new(**options)
|
914
836
|
end
|
915
|
-
end
|
916
837
|
|
917
|
-
|
918
|
-
|
919
|
-
case value
|
920
|
-
when true then "1"
|
921
|
-
when false then "0"
|
922
|
-
else super
|
923
|
-
end
|
838
|
+
def version_string(full_version_string)
|
839
|
+
full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
|
924
840
|
end
|
925
841
|
|
926
|
-
|
842
|
+
# Alias MysqlString to work Mashal.load(File.read("legacy_record.dump")).
|
843
|
+
# TODO: Remove the constant alias once Rails 6.1 has released.
|
844
|
+
MysqlString = Type::String # :nodoc:
|
927
845
|
|
928
|
-
|
929
|
-
|
930
|
-
when true then "1"
|
931
|
-
when false then "0"
|
932
|
-
else super
|
933
|
-
end
|
846
|
+
ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
|
847
|
+
Type::ImmutableString.new(true: "1", false: "0", **args)
|
934
848
|
end
|
935
|
-
|
849
|
+
ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
|
850
|
+
Type::String.new(true: "1", false: "0", **args)
|
851
|
+
end
|
852
|
+
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
|
936
853
|
end
|
937
854
|
end
|
938
855
|
end
|