activerecord 4.2.0 → 6.1.7.1
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 +5 -5
- data/CHANGELOG.md +1221 -796
- 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 +267 -249
- data/lib/active_record/association_relation.rb +45 -7
- data/lib/active_record/associations/alias_tracker.rb +40 -43
- data/lib/active_record/associations/association.rb +172 -67
- data/lib/active_record/associations/association_scope.rb +105 -129
- data/lib/active_record/associations/belongs_to_association.rb +85 -59
- 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 -33
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -70
- 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 +168 -279
- data/lib/active_record/associations/collection_proxy.rb +263 -155
- data/lib/active_record/associations/foreign_association.rb +33 -0
- data/lib/active_record/associations/has_many_association.rb +57 -84
- data/lib/active_record/associations/has_many_through_association.rb +70 -82
- data/lib/active_record/associations/has_one_association.rb +74 -47
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +54 -73
- 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 +175 -164
- data/lib/active_record/associations/preloader/association.rb +107 -112
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/preloader.rb +99 -96
- data/lib/active_record/associations/singular_association.rb +18 -45
- data/lib/active_record/associations/through_association.rb +49 -24
- data/lib/active_record/associations.rb +1845 -1597
- data/lib/active_record/attribute_assignment.rb +59 -185
- data/lib/active_record/attribute_methods/before_type_cast.rb +20 -7
- data/lib/active_record/attribute_methods/dirty.rb +168 -138
- 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 +59 -36
- data/lib/active_record/attribute_methods/write.rb +25 -56
- data/lib/active_record/attribute_methods.rb +153 -162
- data/lib/active_record/attributes.rb +234 -70
- data/lib/active_record/autosave_association.rb +157 -69
- data/lib/active_record/base.rb +49 -50
- 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 +46 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -317
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +301 -113
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +187 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +9 -7
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +485 -253
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +909 -263
- data/lib/active_record/connection_adapters/abstract/transaction.rb +254 -92
- data/lib/active_record/connection_adapters/abstract_adapter.rb +492 -221
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +580 -608
- 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 +196 -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 +271 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +81 -199
- 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 +78 -161
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -57
- 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 +5 -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 +17 -13
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +6 -3
- 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 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
- 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 +171 -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 +499 -293
- 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 +595 -382
- data/lib/active_record/connection_adapters/schema_cache.rb +191 -29
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +146 -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 -389
- 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 +488 -243
- data/lib/active_record/counter_cache.rb +71 -50
- data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -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 +273 -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 +212 -94
- data/lib/active_record/errors.rb +225 -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 +11 -6
- 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 +273 -496
- 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 +64 -0
- data/lib/active_record/legacy_yaml_adapter.rb +52 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +103 -95
- 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 +298 -0
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/migration.rb +685 -309
- data/lib/active_record/model_schema.rb +420 -113
- data/lib/active_record/nested_attributes.rb +265 -216
- data/lib/active_record/no_touching.rb +15 -2
- data/lib/active_record/null_relation.rb +24 -38
- data/lib/active_record/persistence.rb +574 -135
- data/lib/active_record/query_cache.rb +29 -23
- data/lib/active_record/querying.rb +50 -31
- data/lib/active_record/railtie.rb +175 -54
- 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 +533 -216
- data/lib/active_record/readonly_attributes.rb +9 -4
- data/lib/active_record/reflection.rb +485 -310
- 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 +326 -244
- data/lib/active_record/relation/delegation.rb +76 -84
- data/lib/active_record/relation/finder_methods.rb +318 -256
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +99 -84
- data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -25
- 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 +139 -96
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +757 -409
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +23 -21
- data/lib/active_record/relation/where_clause.rb +239 -0
- data/lib/active_record/relation.rb +554 -342
- 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 -26
- data/lib/active_record/scoping/default.rb +96 -82
- 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 +133 -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 +366 -129
- data/lib/active_record/tasks/mysql_database_tasks.rb +68 -100
- data/lib/active_record/tasks/postgresql_database_tasks.rb +87 -39
- 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 +291 -0
- data/lib/active_record/timestamp.rb +86 -43
- data/lib/active_record/touch_later.rb +65 -0
- data/lib/active_record/transactions.rb +181 -152
- 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 -41
- data/lib/active_record/type/date_time.rb +4 -38
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +12 -5
- 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 +33 -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 +65 -48
- data/lib/active_record/validations.rb +39 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +44 -28
- 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 -10
- 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.rb +7 -5
- metadata +175 -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 -149
- data/lib/active_record/attribute_decorators.rb +0 -66
- data/lib/active_record/attribute_set/builder.rb +0 -86
- data/lib/active_record/attribute_set.rb +0 -77
- 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 -30
- data/lib/active_record/type/decimal.rb +0 -40
- 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 -55
- 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 -36
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -101
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
- /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,203 +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
|
-
class Column < ConnectionAdapters::Column # :nodoc:
|
62
|
-
attr_reader :collation, :strict, :extra
|
63
|
-
|
64
|
-
def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
|
65
|
-
@strict = strict
|
66
|
-
@collation = collation
|
67
|
-
@extra = extra
|
68
|
-
super(name, default, cast_type, sql_type, null)
|
69
|
-
assert_valid_default(default)
|
70
|
-
extract_default
|
71
|
-
end
|
72
|
-
|
73
|
-
def extract_default
|
74
|
-
if blob_or_text_column?
|
75
|
-
@default = null || strict ? nil : ''
|
76
|
-
elsif missing_default_forged_as_empty_string?(@default)
|
77
|
-
@default = nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def has_default?
|
82
|
-
return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
|
83
|
-
super
|
84
|
-
end
|
85
|
-
|
86
|
-
def blob_or_text_column?
|
87
|
-
sql_type =~ /blob/i || type == :text
|
88
|
-
end
|
89
|
-
|
90
|
-
def case_sensitive?
|
91
|
-
collation && !collation.match(/_ci$/)
|
92
|
-
end
|
93
|
-
|
94
|
-
def ==(other)
|
95
|
-
super &&
|
96
|
-
collation == other.collation &&
|
97
|
-
strict == other.strict &&
|
98
|
-
extra == other.extra
|
99
|
-
end
|
100
|
-
|
101
|
-
private
|
102
|
-
|
103
|
-
# MySQL misreports NOT NULL column default when none is given.
|
104
|
-
# We can't detect this for columns which may have a legitimate ''
|
105
|
-
# default (string) but we can for others (integer, datetime, boolean,
|
106
|
-
# and the rest).
|
107
|
-
#
|
108
|
-
# Test whether the column has default '', is not null, and is not
|
109
|
-
# a type allowing default ''.
|
110
|
-
def missing_default_forged_as_empty_string?(default)
|
111
|
-
type != :string && !null && default == ''
|
112
|
-
end
|
113
|
-
|
114
|
-
def assert_valid_default(default)
|
115
|
-
if blob_or_text_column? && default.present?
|
116
|
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def attributes_for_hash
|
121
|
-
super + [collation, strict, extra]
|
122
|
-
end
|
123
|
-
end
|
17
|
+
include MySQL::Quoting
|
18
|
+
include MySQL::SchemaStatements
|
124
19
|
|
125
20
|
##
|
126
21
|
# :singleton-method:
|
127
|
-
# By default, the
|
128
|
-
# as boolean. If you wish to disable this emulation
|
129
|
-
# 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
|
130
24
|
# to your application.rb file:
|
131
25
|
#
|
132
|
-
# ActiveRecord::ConnectionAdapters::
|
133
|
-
class_attribute :emulate_booleans
|
134
|
-
self.emulate_booleans = true
|
135
|
-
|
136
|
-
LOST_CONNECTION_ERROR_MESSAGES = [
|
137
|
-
"Server shutdown in progress",
|
138
|
-
"Broken pipe",
|
139
|
-
"Lost connection to MySQL server during query",
|
140
|
-
"MySQL server has gone away" ]
|
141
|
-
|
142
|
-
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
26
|
+
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
|
27
|
+
class_attribute :emulate_booleans, default: true
|
143
28
|
|
144
29
|
NATIVE_DATABASE_TYPES = {
|
145
|
-
:
|
146
|
-
:
|
147
|
-
:
|
148
|
-
:
|
149
|
-
:
|
150
|
-
:
|
151
|
-
:
|
152
|
-
:
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
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" },
|
156
44
|
}
|
157
45
|
|
158
|
-
|
159
|
-
|
46
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
47
|
+
private
|
48
|
+
def dealloc(stmt)
|
49
|
+
stmt.close
|
50
|
+
end
|
51
|
+
end
|
160
52
|
|
161
|
-
# FIXME: Make the first parameter more similar for the two adapters
|
162
53
|
def initialize(connection, logger, connection_options, config)
|
163
|
-
super(connection, logger)
|
164
|
-
|
165
|
-
@quoted_column_names, @quoted_table_names = {}, {}
|
54
|
+
super(connection, logger, config)
|
55
|
+
end
|
166
56
|
|
167
|
-
|
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
|
168
62
|
|
169
|
-
|
170
|
-
|
171
|
-
else
|
172
|
-
@prepared_statements = false
|
173
|
-
end
|
63
|
+
def mariadb? # :nodoc:
|
64
|
+
/mariadb/i.match?(full_version)
|
174
65
|
end
|
175
66
|
|
176
|
-
|
177
|
-
def supports_migrations?
|
67
|
+
def supports_bulk_alter?
|
178
68
|
true
|
179
69
|
end
|
180
70
|
|
181
|
-
def
|
182
|
-
|
71
|
+
def supports_index_sort_order?
|
72
|
+
!mariadb? && database_version >= "8.0.1"
|
183
73
|
end
|
184
74
|
|
185
|
-
def
|
186
|
-
|
75
|
+
def supports_expression_index?
|
76
|
+
!mariadb? && database_version >= "8.0.13"
|
187
77
|
end
|
188
78
|
|
189
|
-
|
190
|
-
# but at the moment (5.5) it doesn't yet implement them
|
191
|
-
def supports_index_sort_order?
|
79
|
+
def supports_transaction_isolation?
|
192
80
|
true
|
193
81
|
end
|
194
82
|
|
195
|
-
|
196
|
-
|
197
|
-
#
|
198
|
-
# http://bugs.mysql.com/bug.php?id=39170
|
199
|
-
def supports_transaction_isolation?
|
200
|
-
version[0] >= 5
|
83
|
+
def supports_explain?
|
84
|
+
true
|
201
85
|
end
|
202
86
|
|
203
87
|
def supports_indexes_in_create?
|
@@ -208,74 +92,90 @@ module ActiveRecord
|
|
208
92
|
true
|
209
93
|
end
|
210
94
|
|
211
|
-
def
|
212
|
-
|
95
|
+
def supports_check_constraints?
|
96
|
+
if mariadb?
|
97
|
+
database_version >= "10.2.1"
|
98
|
+
else
|
99
|
+
database_version >= "8.0.16"
|
100
|
+
end
|
213
101
|
end
|
214
102
|
|
215
|
-
def
|
216
|
-
|
103
|
+
def supports_views?
|
104
|
+
true
|
217
105
|
end
|
218
106
|
|
219
|
-
def
|
220
|
-
|
107
|
+
def supports_datetime_with_precision?
|
108
|
+
mariadb? || database_version >= "5.6.4"
|
221
109
|
end
|
222
110
|
|
223
|
-
|
111
|
+
def supports_virtual_columns?
|
112
|
+
mariadb? || database_version >= "5.7.5"
|
113
|
+
end
|
224
114
|
|
225
|
-
#
|
226
|
-
|
227
|
-
|
228
|
-
raise NotImplementedError
|
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"
|
229
118
|
end
|
230
119
|
|
231
|
-
def
|
232
|
-
|
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
|
233
126
|
end
|
234
127
|
|
235
|
-
|
236
|
-
|
237
|
-
def error_number(exception) # :nodoc:
|
238
|
-
raise NotImplementedError
|
128
|
+
def supports_advisory_locks?
|
129
|
+
true
|
239
130
|
end
|
240
131
|
|
241
|
-
|
132
|
+
def supports_insert_on_duplicate_skip?
|
133
|
+
true
|
134
|
+
end
|
242
135
|
|
243
|
-
def
|
244
|
-
|
245
|
-
"x'#{value.hex}'"
|
246
|
-
else
|
247
|
-
super
|
248
|
-
end
|
136
|
+
def supports_insert_on_duplicate_update?
|
137
|
+
true
|
249
138
|
end
|
250
139
|
|
251
|
-
def
|
252
|
-
|
140
|
+
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
141
|
+
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
253
142
|
end
|
254
143
|
|
255
|
-
def
|
256
|
-
|
144
|
+
def release_advisory_lock(lock_name) # :nodoc:
|
145
|
+
query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
|
257
146
|
end
|
258
147
|
|
259
|
-
def
|
260
|
-
|
148
|
+
def native_database_types
|
149
|
+
NATIVE_DATABASE_TYPES
|
261
150
|
end
|
262
151
|
|
263
|
-
def
|
264
|
-
|
152
|
+
def index_algorithms
|
153
|
+
{
|
154
|
+
default: "ALGORITHM = DEFAULT",
|
155
|
+
copy: "ALGORITHM = COPY",
|
156
|
+
inplace: "ALGORITHM = INPLACE",
|
157
|
+
instant: "ALGORITHM = INSTANT",
|
158
|
+
}
|
265
159
|
end
|
266
160
|
|
267
|
-
|
268
|
-
|
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
|
269
167
|
end
|
270
168
|
|
271
|
-
|
272
|
-
|
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
|
273
173
|
end
|
274
174
|
|
275
175
|
# REFERENTIAL INTEGRITY ====================================
|
276
176
|
|
277
177
|
def disable_referential_integrity #:nodoc:
|
278
|
-
old =
|
178
|
+
old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
|
279
179
|
|
280
180
|
begin
|
281
181
|
update("SET FOREIGN_KEY_CHECKS = 0")
|
@@ -285,34 +185,38 @@ module ActiveRecord
|
|
285
185
|
end
|
286
186
|
end
|
287
187
|
|
288
|
-
|
289
|
-
# DATABASE STATEMENTS ======================================
|
290
|
-
#++
|
188
|
+
# CONNECTION MANAGEMENT ====================================
|
291
189
|
|
292
|
-
def clear_cache!
|
293
|
-
super
|
190
|
+
def clear_cache! # :nodoc:
|
294
191
|
reload_type_map
|
192
|
+
super
|
295
193
|
end
|
296
194
|
|
195
|
+
#--
|
196
|
+
# DATABASE STATEMENTS ======================================
|
197
|
+
#++
|
198
|
+
|
297
199
|
# Executes the SQL statement in the context of this connection.
|
298
200
|
def execute(sql, name = nil)
|
299
|
-
|
300
|
-
|
201
|
+
materialize_transactions
|
202
|
+
mark_transaction_written_if_write(sql)
|
301
203
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
204
|
+
log(sql, name) do
|
205
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
206
|
+
@connection.query(sql)
|
207
|
+
end
|
208
|
+
end
|
307
209
|
end
|
308
210
|
|
309
|
-
|
310
|
-
|
311
|
-
|
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)
|
312
216
|
end
|
313
217
|
|
314
218
|
def begin_db_transaction
|
315
|
-
execute
|
219
|
+
execute("BEGIN", "TRANSACTION")
|
316
220
|
end
|
317
221
|
|
318
222
|
def begin_isolated_db_transaction(isolation)
|
@@ -321,26 +225,14 @@ module ActiveRecord
|
|
321
225
|
end
|
322
226
|
|
323
227
|
def commit_db_transaction #:nodoc:
|
324
|
-
execute
|
228
|
+
execute("COMMIT", "TRANSACTION")
|
325
229
|
end
|
326
230
|
|
327
|
-
def
|
328
|
-
execute
|
231
|
+
def exec_rollback_db_transaction #:nodoc:
|
232
|
+
execute("ROLLBACK", "TRANSACTION")
|
329
233
|
end
|
330
234
|
|
331
|
-
|
332
|
-
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
333
|
-
# these, we must use a subquery.
|
334
|
-
def join_to_update(update, select) #:nodoc:
|
335
|
-
if select.limit || select.offset || select.orders.any?
|
336
|
-
super
|
337
|
-
else
|
338
|
-
update.table select.source
|
339
|
-
update.wheres = select.constraints
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
def empty_insert_statement_value
|
235
|
+
def empty_insert_statement_value(primary_key = nil)
|
344
236
|
"VALUES ()"
|
345
237
|
end
|
346
238
|
|
@@ -356,7 +248,7 @@ module ActiveRecord
|
|
356
248
|
end
|
357
249
|
|
358
250
|
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
359
|
-
# Charset defaults to
|
251
|
+
# Charset defaults to utf8mb4.
|
360
252
|
#
|
361
253
|
# Example:
|
362
254
|
# create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
|
@@ -364,9 +256,13 @@ module ActiveRecord
|
|
364
256
|
# create_database 'matt_development', charset: :big5
|
365
257
|
def create_database(name, options = {})
|
366
258
|
if options[:collation]
|
367
|
-
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`"
|
368
264
|
else
|
369
|
-
|
265
|
+
raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
|
370
266
|
end
|
371
267
|
end
|
372
268
|
|
@@ -375,106 +271,38 @@ module ActiveRecord
|
|
375
271
|
# Example:
|
376
272
|
# drop_database('sebastian_development')
|
377
273
|
def drop_database(name) #:nodoc:
|
378
|
-
execute "DROP DATABASE IF EXISTS
|
274
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
379
275
|
end
|
380
276
|
|
381
277
|
def current_database
|
382
|
-
|
278
|
+
query_value("SELECT database()", "SCHEMA")
|
383
279
|
end
|
384
280
|
|
385
281
|
# Returns the database character set.
|
386
282
|
def charset
|
387
|
-
show_variable
|
283
|
+
show_variable "character_set_database"
|
388
284
|
end
|
389
285
|
|
390
286
|
# Returns the database collation strategy.
|
391
287
|
def collation
|
392
|
-
show_variable
|
393
|
-
end
|
394
|
-
|
395
|
-
def tables(name = nil, database = nil, like = nil) #:nodoc:
|
396
|
-
sql = "SHOW TABLES "
|
397
|
-
sql << "IN #{quote_table_name(database)} " if database
|
398
|
-
sql << "LIKE #{quote(like)}" if like
|
399
|
-
|
400
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
401
|
-
result.collect { |field| field.first }
|
402
|
-
end
|
403
|
-
end
|
404
|
-
|
405
|
-
def truncate(table_name, name = nil)
|
406
|
-
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
407
|
-
end
|
408
|
-
|
409
|
-
def table_exists?(name)
|
410
|
-
return false unless name.present?
|
411
|
-
return true if tables(nil, nil, name).any?
|
412
|
-
|
413
|
-
name = name.to_s
|
414
|
-
schema, table = name.split('.', 2)
|
415
|
-
|
416
|
-
unless table # A table was provided without a schema
|
417
|
-
table = schema
|
418
|
-
schema = nil
|
419
|
-
end
|
420
|
-
|
421
|
-
tables(nil, schema, table).any?
|
288
|
+
show_variable "collation_database"
|
422
289
|
end
|
423
290
|
|
424
|
-
|
425
|
-
|
426
|
-
indexes = []
|
427
|
-
current_index = nil
|
428
|
-
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
|
429
|
-
each_hash(result) do |row|
|
430
|
-
if current_index != row[:Key_name]
|
431
|
-
next if row[:Key_name] == 'PRIMARY' # skip the primary key
|
432
|
-
current_index = row[:Key_name]
|
433
|
-
|
434
|
-
mysql_index_type = row[:Index_type].downcase.to_sym
|
435
|
-
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
|
436
|
-
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
|
437
|
-
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
|
438
|
-
end
|
291
|
+
def table_comment(table_name) # :nodoc:
|
292
|
+
scope = quoted_scope(table_name)
|
439
293
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
end
|
447
|
-
|
448
|
-
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
449
|
-
def columns(table_name)#:nodoc:
|
450
|
-
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
451
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
452
|
-
each_hash(result).map do |field|
|
453
|
-
field_name = set_field_encoding(field[:Field])
|
454
|
-
sql_type = field[:Type]
|
455
|
-
cast_type = lookup_cast_type(sql_type)
|
456
|
-
new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
|
457
|
-
end
|
458
|
-
end
|
459
|
-
end
|
460
|
-
|
461
|
-
def create_table(table_name, options = {}) #:nodoc:
|
462
|
-
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
|
463
300
|
end
|
464
301
|
|
465
|
-
def
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
if respond_to?(method, true)
|
471
|
-
send(method, table, *arguments)
|
472
|
-
else
|
473
|
-
raise "Unknown method called : #{method}(#{arguments.inspect})"
|
474
|
-
end
|
475
|
-
end.join(", ")
|
476
|
-
|
477
|
-
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)}")
|
478
306
|
end
|
479
307
|
|
480
308
|
# Renames a table.
|
@@ -482,402 +310,546 @@ module ActiveRecord
|
|
482
310
|
# Example:
|
483
311
|
# rename_table('octopuses', 'octopi')
|
484
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)
|
485
315
|
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
486
316
|
rename_table_indexes(table_name, new_name)
|
487
317
|
end
|
488
318
|
|
489
|
-
|
490
|
-
|
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}"
|
491
337
|
end
|
492
338
|
|
493
339
|
def rename_index(table_name, old_name, new_name)
|
494
340
|
if supports_rename_index?
|
341
|
+
validate_index_length!(table_name, new_name)
|
342
|
+
|
495
343
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
|
496
344
|
else
|
497
345
|
super
|
498
346
|
end
|
499
347
|
end
|
500
348
|
|
501
|
-
def change_column_default(table_name, column_name,
|
502
|
-
|
503
|
-
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
|
504
352
|
end
|
505
353
|
|
506
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
507
|
-
column = column_for(table_name, column_name)
|
508
|
-
|
354
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
509
355
|
unless null || default.nil?
|
510
356
|
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
511
357
|
end
|
512
358
|
|
513
|
-
change_column table_name, column_name,
|
359
|
+
change_column table_name, column_name, nil, null: null
|
514
360
|
end
|
515
361
|
|
516
|
-
def
|
517
|
-
|
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)}")
|
518
369
|
end
|
519
370
|
|
520
371
|
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
521
|
-
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)}")
|
522
373
|
rename_column_indexes(table_name, column_name, new_column_name)
|
523
374
|
end
|
524
375
|
|
525
|
-
def add_index(table_name, column_name, options
|
526
|
-
|
527
|
-
|
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
|
528
388
|
end
|
529
389
|
|
530
390
|
def foreign_keys(table_name)
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
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]}
|
540
410
|
SQL
|
541
411
|
|
542
|
-
create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
543
|
-
|
544
412
|
fk_info.map do |row|
|
545
413
|
options = {
|
546
|
-
column: row[
|
547
|
-
name: row[
|
548
|
-
primary_key: row[
|
414
|
+
column: row["column"],
|
415
|
+
name: row["name"],
|
416
|
+
primary_key: row["primary_key"]
|
549
417
|
}
|
550
418
|
|
551
|
-
options[:on_update] = extract_foreign_key_action(
|
552
|
-
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"])
|
553
421
|
|
554
|
-
ForeignKeyDefinition.new(table_name, row[
|
422
|
+
ForeignKeyDefinition.new(table_name, row["to_table"], options)
|
555
423
|
end
|
556
424
|
end
|
557
425
|
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
when nil, 0x100..0xffff; 'text'
|
581
|
-
when 0x10000..0xffffff; 'mediumtext'
|
582
|
-
when 0x1000000..0xffffffff; 'longtext'
|
583
|
-
else raise(ActiveRecordError, "No text type has character length #{limit}")
|
426
|
+
def check_constraints(table_name)
|
427
|
+
if supports_check_constraints?
|
428
|
+
scope = quoted_scope(table_name)
|
429
|
+
|
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)
|
584
448
|
end
|
585
449
|
else
|
586
|
-
|
450
|
+
raise NotImplementedError
|
587
451
|
end
|
588
452
|
end
|
589
453
|
|
590
|
-
|
591
|
-
|
592
|
-
variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
|
593
|
-
variables.first['Value'] unless variables.empty?
|
594
|
-
end
|
454
|
+
def table_options(table_name) # :nodoc:
|
455
|
+
create_table_info = create_table_info(table_name)
|
595
456
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
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
|
606
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
|
607
481
|
end
|
608
482
|
|
609
|
-
#
|
610
|
-
def
|
611
|
-
|
612
|
-
|
483
|
+
# SHOW VARIABLES LIKE 'name'
|
484
|
+
def show_variable(name)
|
485
|
+
query_value("SELECT @@#{name}", "SCHEMA")
|
486
|
+
rescue ActiveRecord::StatementInvalid
|
487
|
+
nil
|
613
488
|
end
|
614
489
|
|
615
|
-
def
|
616
|
-
|
617
|
-
|
490
|
+
def primary_keys(table_name) # :nodoc:
|
491
|
+
raise ArgumentError unless table_name.present?
|
492
|
+
|
493
|
+
scope = quoted_scope(table_name)
|
494
|
+
|
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
|
618
503
|
end
|
619
504
|
|
620
|
-
def case_sensitive_comparison(
|
621
|
-
|
622
|
-
|
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))
|
623
510
|
else
|
624
511
|
super
|
625
512
|
end
|
626
513
|
end
|
627
514
|
|
628
|
-
def
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
515
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
516
|
+
column.case_sensitive?
|
517
|
+
end
|
518
|
+
private :can_perform_case_insensitive_comparison_for?
|
519
|
+
|
520
|
+
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
521
|
+
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
522
|
+
# distinct queries, and requires that the ORDER BY include the distinct column.
|
523
|
+
# See https://dev.mysql.com/doc/refman/en/group-by-handling.html
|
524
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
525
|
+
order_columns = orders.compact_blank.map { |s|
|
526
|
+
# Convert Arel node to string
|
527
|
+
s = visitor.compile(s) unless s.is_a?(String)
|
528
|
+
# Remove any ASC/DESC modifiers
|
529
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
530
|
+
}.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
531
|
+
|
532
|
+
(order_columns << super).join(", ")
|
634
533
|
end
|
635
534
|
|
636
535
|
def strict_mode?
|
637
536
|
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
|
638
537
|
end
|
639
538
|
|
640
|
-
def
|
641
|
-
|
539
|
+
def default_index_type?(index) # :nodoc:
|
540
|
+
index.using == :btree || super
|
642
541
|
end
|
643
542
|
|
644
|
-
|
645
|
-
|
646
|
-
def initialize_type_map(m) # :nodoc:
|
647
|
-
super
|
543
|
+
def build_insert_sql(insert) # :nodoc:
|
544
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
648
545
|
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
|
657
|
-
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
|
658
|
-
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
659
|
-
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
660
|
-
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
661
|
-
|
662
|
-
register_integer_type m, %r(^bigint)i, limit: 8
|
663
|
-
register_integer_type m, %r(^int)i, limit: 4
|
664
|
-
register_integer_type m, %r(^mediumint)i, limit: 3
|
665
|
-
register_integer_type m, %r(^smallint)i, limit: 2
|
666
|
-
register_integer_type m, %r(^tinyint)i, limit: 1
|
667
|
-
|
668
|
-
m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
|
669
|
-
m.alias_type %r(set)i, 'varchar'
|
670
|
-
m.alias_type %r(year)i, 'integer'
|
671
|
-
m.alias_type %r(bit)i, 'binary'
|
672
|
-
|
673
|
-
m.register_type(%r(enum)i) do |sql_type|
|
674
|
-
limit = sql_type[/^enum\((.+)\)/i, 1]
|
675
|
-
.split(',').map{|enum| enum.strip.length - 2}.max
|
676
|
-
MysqlString.new(limit: limit)
|
677
|
-
end
|
678
|
-
end
|
679
|
-
|
680
|
-
def register_integer_type(mapping, key, options) # :nodoc:
|
681
|
-
mapping.register_type(key) do |sql_type|
|
682
|
-
if /unsigned/i =~ sql_type
|
683
|
-
Type::UnsignedInteger.new(options)
|
684
|
-
else
|
685
|
-
Type::Integer.new(options)
|
686
|
-
end
|
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(",")
|
687
553
|
end
|
688
|
-
end
|
689
554
|
|
690
|
-
|
691
|
-
# to give it some prompting in the form of a subsubquery. Ugh!
|
692
|
-
def subquery_for(key, select)
|
693
|
-
subsubselect = select.clone
|
694
|
-
subsubselect.projections = [key]
|
695
|
-
|
696
|
-
subselect = Arel::SelectManager.new(select.engine)
|
697
|
-
subselect.project Arel.sql(key.name)
|
698
|
-
subselect.from subsubselect.as('__active_record_temp')
|
555
|
+
sql
|
699
556
|
end
|
700
557
|
|
701
|
-
def
|
702
|
-
if
|
703
|
-
|
704
|
-
when Hash
|
705
|
-
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
|
706
|
-
when Fixnum
|
707
|
-
column_names.each {|name| option_strings[name] += "(#{length})"}
|
708
|
-
end
|
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."
|
709
561
|
end
|
710
|
-
|
711
|
-
return option_strings
|
712
562
|
end
|
713
563
|
|
714
|
-
|
715
|
-
|
564
|
+
private
|
565
|
+
def initialize_type_map(m = type_map)
|
566
|
+
super
|
716
567
|
|
717
|
-
|
718
|
-
|
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
|
719
572
|
|
720
|
-
|
721
|
-
|
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
|
606
|
+
end
|
722
607
|
|
723
|
-
|
724
|
-
|
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
|
614
|
+
end
|
725
615
|
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
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)
|
672
|
+
else
|
673
|
+
super
|
674
|
+
end
|
734
675
|
end
|
735
|
-
end
|
736
676
|
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
schema_creation.visit_AddColumn cd
|
741
|
-
end
|
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
|
742
680
|
|
743
|
-
|
744
|
-
|
681
|
+
unless options.key?(:default)
|
682
|
+
options[:default] = column.default
|
683
|
+
end
|
745
684
|
|
746
|
-
|
747
|
-
|
748
|
-
|
685
|
+
unless options.key?(:null)
|
686
|
+
options[:null] = column.null
|
687
|
+
end
|
749
688
|
|
750
|
-
|
751
|
-
|
689
|
+
unless options.key?(:comment)
|
690
|
+
options[:comment] = column.comment
|
691
|
+
end
|
692
|
+
|
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))
|
752
696
|
end
|
753
697
|
|
754
|
-
|
755
|
-
|
756
|
-
end
|
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?
|
757
700
|
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
}
|
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
|
+
}
|
766
708
|
|
767
|
-
|
768
|
-
|
769
|
-
|
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))
|
713
|
+
end
|
770
714
|
|
771
|
-
|
772
|
-
|
773
|
-
|
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
|
774
718
|
|
775
|
-
|
776
|
-
|
777
|
-
end
|
719
|
+
"ADD #{schema_creation.accept(index)}#{algorithm}"
|
720
|
+
end
|
778
721
|
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
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)}"
|
725
|
+
end
|
783
726
|
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
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
|
788
734
|
|
789
|
-
|
790
|
-
|
791
|
-
|
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
|
792
742
|
|
793
|
-
|
794
|
-
|
795
|
-
end
|
743
|
+
def configure_connection
|
744
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
796
745
|
|
797
|
-
|
746
|
+
# By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
|
747
|
+
variables["sql_auto_is_null"] = 0
|
798
748
|
|
799
|
-
|
800
|
-
|
801
|
-
|
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
|
802
753
|
|
803
|
-
|
804
|
-
full_version =~ /mariadb/i
|
805
|
-
end
|
754
|
+
defaults = [":default", :default].to_set
|
806
755
|
|
807
|
-
|
808
|
-
|
809
|
-
|
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
|
810
781
|
|
811
|
-
|
812
|
-
|
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(", ")
|
813
791
|
|
814
|
-
|
815
|
-
|
816
|
-
|
792
|
+
# ...and send them all in one query
|
793
|
+
execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
|
794
|
+
end
|
817
795
|
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
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
|
822
801
|
|
823
|
-
|
824
|
-
|
825
|
-
# If the user has provided another value for sql_mode, don't replace it.
|
826
|
-
unless variables.has_key?('sql_mode')
|
827
|
-
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
|
802
|
+
def create_table_info(table_name) # :nodoc:
|
803
|
+
exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
|
828
804
|
end
|
829
805
|
|
830
|
-
|
831
|
-
|
832
|
-
# (trailing comma because variable_assignments will always have content)
|
833
|
-
if @config[:encoding]
|
834
|
-
encoding = "NAMES #{@config[:encoding]}"
|
835
|
-
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
|
836
|
-
encoding << ", "
|
806
|
+
def arel_visitor
|
807
|
+
Arel::Visitors::MySQL.new(self)
|
837
808
|
end
|
838
809
|
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
843
|
-
elsif !v.nil?
|
844
|
-
"@@SESSION.#{k} = #{quote(v)}"
|
845
|
-
end
|
846
|
-
# or else nil; compact to clear nils out
|
847
|
-
end.compact.join(', ')
|
810
|
+
def build_statement_pool
|
811
|
+
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
812
|
+
end
|
848
813
|
|
849
|
-
|
850
|
-
|
851
|
-
|
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)
|
820
|
+
|
821
|
+
options = {
|
822
|
+
message: message,
|
823
|
+
sql: sql,
|
824
|
+
binds: binds,
|
825
|
+
}
|
852
826
|
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
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])
|
858
833
|
end
|
834
|
+
|
835
|
+
MismatchedForeignKey.new(**options)
|
859
836
|
end
|
860
|
-
end
|
861
837
|
|
862
|
-
|
863
|
-
|
864
|
-
case value
|
865
|
-
when true then "1"
|
866
|
-
when false then "0"
|
867
|
-
else super
|
868
|
-
end
|
838
|
+
def version_string(full_version_string)
|
839
|
+
full_version_string.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
|
869
840
|
end
|
870
841
|
|
871
|
-
|
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:
|
872
845
|
|
873
|
-
|
874
|
-
|
875
|
-
when true then "1"
|
876
|
-
when false then "0"
|
877
|
-
else super
|
878
|
-
end
|
846
|
+
ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
|
847
|
+
Type::ImmutableString.new(true: "1", false: "0", **args)
|
879
848
|
end
|
880
|
-
|
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)
|
881
853
|
end
|
882
854
|
end
|
883
855
|
end
|