activerecord 4.2.9 → 5.2.8
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 +614 -1572
- data/MIT-LICENSE +2 -2
- data/README.rdoc +10 -11
- data/examples/performance.rb +32 -31
- data/examples/simple.rb +5 -4
- data/lib/active_record/aggregations.rb +263 -249
- data/lib/active_record/association_relation.rb +11 -6
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +77 -43
- data/lib/active_record/associations/association_scope.rb +106 -133
- data/lib/active_record/associations/belongs_to_association.rb +52 -41
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
- data/lib/active_record/associations/builder/association.rb +29 -38
- data/lib/active_record/associations/builder/belongs_to.rb +77 -30
- data/lib/active_record/associations/builder/collection_association.rb +9 -22
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
- data/lib/active_record/associations/builder/has_many.rb +6 -4
- data/lib/active_record/associations/builder/has_one.rb +13 -6
- data/lib/active_record/associations/builder/singular_association.rb +15 -11
- data/lib/active_record/associations/collection_association.rb +139 -280
- data/lib/active_record/associations/collection_proxy.rb +231 -133
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_many_association.rb +34 -89
- data/lib/active_record/associations/has_many_through_association.rb +49 -76
- data/lib/active_record/associations/has_one_association.rb +38 -24
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +40 -89
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
- data/lib/active_record/associations/join_dependency.rb +133 -159
- data/lib/active_record/associations/preloader/association.rb +85 -120
- data/lib/active_record/associations/preloader/through_association.rb +85 -74
- data/lib/active_record/associations/preloader.rb +81 -91
- data/lib/active_record/associations/singular_association.rb +27 -34
- data/lib/active_record/associations/through_association.rb +38 -18
- data/lib/active_record/associations.rb +1732 -1597
- data/lib/active_record/attribute_assignment.rb +58 -182
- data/lib/active_record/attribute_decorators.rb +39 -15
- data/lib/active_record/attribute_methods/before_type_cast.rb +10 -8
- data/lib/active_record/attribute_methods/dirty.rb +94 -135
- data/lib/active_record/attribute_methods/primary_key.rb +86 -71
- data/lib/active_record/attribute_methods/query.rb +4 -2
- data/lib/active_record/attribute_methods/read.rb +45 -63
- data/lib/active_record/attribute_methods/serialization.rb +40 -20
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
- data/lib/active_record/attribute_methods/write.rb +30 -45
- data/lib/active_record/attribute_methods.rb +166 -109
- data/lib/active_record/attributes.rb +201 -82
- data/lib/active_record/autosave_association.rb +94 -36
- data/lib/active_record/base.rb +57 -44
- data/lib/active_record/callbacks.rb +97 -57
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +24 -12
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
- data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -217
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +570 -228
- data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
- data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -593
- data/lib/active_record/connection_adapters/column.rb +50 -41
- data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +41 -188
- data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
- 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 +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
- data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -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 +55 -53
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
- 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 +23 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -284
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +432 -323
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -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 +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -308
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +40 -27
- data/lib/active_record/core.rb +178 -198
- data/lib/active_record/counter_cache.rb +79 -36
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +135 -88
- data/lib/active_record/errors.rb +179 -52
- data/lib/active_record/explain.rb +23 -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 +35 -9
- data/lib/active_record/fixtures.rb +188 -132
- data/lib/active_record/gem_version.rb +4 -2
- data/lib/active_record/inheritance.rb +148 -112
- data/lib/active_record/integration.rb +70 -28
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +21 -3
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +88 -96
- data/lib/active_record/locking/pessimistic.rb +15 -3
- data/lib/active_record/log_subscriber.rb +95 -33
- data/lib/active_record/migration/command_recorder.rb +133 -90
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +8 -6
- data/lib/active_record/migration.rb +581 -282
- data/lib/active_record/model_schema.rb +290 -111
- data/lib/active_record/nested_attributes.rb +264 -222
- data/lib/active_record/no_touching.rb +7 -1
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +347 -119
- data/lib/active_record/query_cache.rb +13 -24
- data/lib/active_record/querying.rb +19 -17
- data/lib/active_record/railtie.rb +94 -32
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +9 -3
- data/lib/active_record/railties/databases.rake +149 -156
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +414 -267
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +204 -55
- data/lib/active_record/relation/calculations.rb +256 -248
- data/lib/active_record/relation/delegation.rb +67 -60
- data/lib/active_record/relation/finder_methods.rb +288 -239
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +86 -86
- data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/predicate_builder.rb +116 -119
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +448 -393
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -13
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/relation.rb +287 -340
- data/lib/active_record/result.rb +54 -36
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +155 -124
- data/lib/active_record/schema.rb +30 -24
- data/lib/active_record/schema_dumper.rb +91 -87
- data/lib/active_record/schema_migration.rb +19 -16
- data/lib/active_record/scoping/default.rb +102 -85
- data/lib/active_record/scoping/named.rb +81 -32
- data/lib/active_record/scoping.rb +45 -26
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/statement_cache.rb +45 -35
- data/lib/active_record/store.rb +42 -36
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +134 -96
- data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
- data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
- data/lib/active_record/timestamp.rb +70 -38
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +199 -124
- data/lib/active_record/translation.rb +2 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -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 -3
- 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 +24 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +11 -16
- data/lib/active_record/type/type_map.rb +15 -17
- data/lib/active_record/type/unsigned_integer.rb +9 -7
- data/lib/active_record/type.rb +79 -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 +13 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +40 -41
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +34 -22
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -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 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -3
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
- data/lib/rails/generators/active_record/migration.rb +18 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
- data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -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 +72 -50
- 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_set/builder.rb +0 -106
- data/lib/active_record/attribute_set.rb +0 -81
- 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/infinity.rb +0 -13
- 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
@@ -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"
|
13
|
+
|
14
|
+
require "active_support/core_ext/string/strip"
|
3
15
|
|
4
16
|
module ActiveRecord
|
5
17
|
module ConnectionAdapters
|
6
18
|
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
|
19
|
+
include MySQL::Quoting
|
20
|
+
include MySQL::SchemaStatements
|
130
21
|
|
131
22
|
##
|
132
23
|
# :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
|
24
|
+
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
|
25
|
+
# as boolean. If you wish to disable this emulation you can add the following line
|
136
26
|
# to your application.rb file:
|
137
27
|
#
|
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'
|
28
|
+
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
|
29
|
+
class_attribute :emulate_booleans, default: true
|
149
30
|
|
150
31
|
NATIVE_DATABASE_TYPES = {
|
151
|
-
:
|
152
|
-
:
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
156
|
-
:
|
157
|
-
:
|
158
|
-
:
|
159
|
-
:
|
160
|
-
:
|
161
|
-
:
|
32
|
+
primary_key: "bigint auto_increment PRIMARY KEY",
|
33
|
+
string: { name: "varchar", limit: 255 },
|
34
|
+
text: { name: "text", limit: 65535 },
|
35
|
+
integer: { name: "int", limit: 4 },
|
36
|
+
float: { name: "float", limit: 24 },
|
37
|
+
decimal: { name: "decimal" },
|
38
|
+
datetime: { name: "datetime" },
|
39
|
+
timestamp: { name: "timestamp" },
|
40
|
+
time: { name: "time" },
|
41
|
+
date: { name: "date" },
|
42
|
+
binary: { name: "blob", limit: 65535 },
|
43
|
+
boolean: { name: "tinyint", limit: 1 },
|
44
|
+
json: { name: "json" },
|
162
45
|
}
|
163
46
|
|
164
|
-
|
165
|
-
|
47
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
48
|
+
private def dealloc(stmt)
|
49
|
+
stmt[: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
|
-
@connection_options, @config = connection_options, config
|
171
|
-
@quoted_column_names, @quoted_table_names = {}, {}
|
54
|
+
super(connection, logger, config)
|
172
55
|
|
173
|
-
@
|
56
|
+
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
174
57
|
|
175
|
-
if
|
176
|
-
|
177
|
-
else
|
178
|
-
@prepared_statements = false
|
58
|
+
if version < "5.1.10"
|
59
|
+
raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
|
179
60
|
end
|
180
61
|
end
|
181
62
|
|
182
|
-
|
183
|
-
|
184
|
-
true
|
63
|
+
def version #:nodoc:
|
64
|
+
@version ||= Version.new(version_string)
|
185
65
|
end
|
186
66
|
|
187
|
-
def
|
188
|
-
|
67
|
+
def mariadb? # :nodoc:
|
68
|
+
/mariadb/i.match?(full_version)
|
189
69
|
end
|
190
70
|
|
191
71
|
def supports_bulk_alter? #:nodoc:
|
192
72
|
true
|
193
73
|
end
|
194
74
|
|
195
|
-
# Technically MySQL allows to create indexes with the sort order syntax
|
196
|
-
# but at the moment (5.5) it doesn't yet implement them
|
197
75
|
def supports_index_sort_order?
|
198
|
-
|
76
|
+
!mariadb? && version >= "8.0.1"
|
199
77
|
end
|
200
78
|
|
201
|
-
# MySQL 4 technically support transaction isolation, but it is affected by a bug
|
202
|
-
# where the transaction level gets persisted for the whole session:
|
203
|
-
#
|
204
|
-
# http://bugs.mysql.com/bug.php?id=39170
|
205
79
|
def supports_transaction_isolation?
|
206
|
-
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def supports_explain?
|
84
|
+
true
|
207
85
|
end
|
208
86
|
|
209
87
|
def supports_indexes_in_create?
|
@@ -215,11 +93,35 @@ module ActiveRecord
|
|
215
93
|
end
|
216
94
|
|
217
95
|
def supports_views?
|
218
|
-
|
96
|
+
true
|
219
97
|
end
|
220
98
|
|
221
99
|
def supports_datetime_with_precision?
|
222
|
-
|
100
|
+
if mariadb?
|
101
|
+
version >= "5.3.0"
|
102
|
+
else
|
103
|
+
version >= "5.6.4"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def supports_virtual_columns?
|
108
|
+
if mariadb?
|
109
|
+
version >= "5.2.0"
|
110
|
+
else
|
111
|
+
version >= "5.7.5"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def supports_advisory_locks?
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
120
|
+
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
121
|
+
end
|
122
|
+
|
123
|
+
def release_advisory_lock(lock_name) # :nodoc:
|
124
|
+
query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
|
223
125
|
end
|
224
126
|
|
225
127
|
def native_database_types
|
@@ -227,7 +129,7 @@ module ActiveRecord
|
|
227
129
|
end
|
228
130
|
|
229
131
|
def index_algorithms
|
230
|
-
{ default:
|
132
|
+
{ default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
|
231
133
|
end
|
232
134
|
|
233
135
|
# HELPER METHODS ===========================================
|
@@ -238,54 +140,16 @@ module ActiveRecord
|
|
238
140
|
raise NotImplementedError
|
239
141
|
end
|
240
142
|
|
241
|
-
def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
|
242
|
-
Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
|
243
|
-
end
|
244
|
-
|
245
143
|
# Must return the MySQL error number from the exception, if the exception has an
|
246
144
|
# error number.
|
247
145
|
def error_number(exception) # :nodoc:
|
248
146
|
raise NotImplementedError
|
249
147
|
end
|
250
148
|
|
251
|
-
# QUOTING ==================================================
|
252
|
-
|
253
|
-
def _quote(value) # :nodoc:
|
254
|
-
if value.is_a?(Type::Binary::Data)
|
255
|
-
"x'#{value.hex}'"
|
256
|
-
else
|
257
|
-
super
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
def quote_column_name(name) #:nodoc:
|
262
|
-
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
263
|
-
end
|
264
|
-
|
265
|
-
def quote_table_name(name) #:nodoc:
|
266
|
-
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
267
|
-
end
|
268
|
-
|
269
|
-
def quoted_true
|
270
|
-
QUOTED_TRUE
|
271
|
-
end
|
272
|
-
|
273
|
-
def unquoted_true
|
274
|
-
1
|
275
|
-
end
|
276
|
-
|
277
|
-
def quoted_false
|
278
|
-
QUOTED_FALSE
|
279
|
-
end
|
280
|
-
|
281
|
-
def unquoted_false
|
282
|
-
0
|
283
|
-
end
|
284
|
-
|
285
149
|
# REFERENTIAL INTEGRITY ====================================
|
286
150
|
|
287
151
|
def disable_referential_integrity #:nodoc:
|
288
|
-
old =
|
152
|
+
old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
|
289
153
|
|
290
154
|
begin
|
291
155
|
update("SET FOREIGN_KEY_CHECKS = 0")
|
@@ -295,32 +159,43 @@ module ActiveRecord
|
|
295
159
|
end
|
296
160
|
end
|
297
161
|
|
162
|
+
# CONNECTION MANAGEMENT ====================================
|
163
|
+
|
164
|
+
# Clears the prepared statements cache.
|
165
|
+
def clear_cache!
|
166
|
+
reload_type_map
|
167
|
+
@statements.clear
|
168
|
+
end
|
169
|
+
|
298
170
|
#--
|
299
171
|
# DATABASE STATEMENTS ======================================
|
300
172
|
#++
|
301
173
|
|
302
|
-
def
|
303
|
-
|
304
|
-
|
174
|
+
def explain(arel, binds = [])
|
175
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
176
|
+
start = Time.now
|
177
|
+
result = exec_query(sql, "EXPLAIN", binds)
|
178
|
+
elapsed = Time.now - start
|
179
|
+
|
180
|
+
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
305
181
|
end
|
306
182
|
|
307
183
|
# Executes the SQL statement in the context of this connection.
|
308
184
|
def execute(sql, name = nil)
|
309
|
-
log(sql, name)
|
185
|
+
log(sql, name) do
|
186
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
187
|
+
@connection.query(sql)
|
188
|
+
end
|
189
|
+
end
|
310
190
|
end
|
311
191
|
|
312
|
-
#
|
313
|
-
# stuff in an abstract way without concerning ourselves about whether it
|
314
|
-
# explicitly freed or not.
|
315
|
-
def execute_and_free(sql, name = nil)
|
192
|
+
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
|
193
|
+
# to write stuff in an abstract way without concerning ourselves about whether it
|
194
|
+
# needs to be explicitly freed or not.
|
195
|
+
def execute_and_free(sql, name = nil) # :nodoc:
|
316
196
|
yield execute(sql, name)
|
317
197
|
end
|
318
198
|
|
319
|
-
def update_sql(sql, name = nil) #:nodoc:
|
320
|
-
super
|
321
|
-
@connection.affected_rows
|
322
|
-
end
|
323
|
-
|
324
199
|
def begin_db_transaction
|
325
200
|
execute "BEGIN"
|
326
201
|
end
|
@@ -341,7 +216,7 @@ module ActiveRecord
|
|
341
216
|
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
342
217
|
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
343
218
|
# these, we must use a subquery.
|
344
|
-
def join_to_update(update, select)
|
219
|
+
def join_to_update(update, select, key) # :nodoc:
|
345
220
|
if select.limit || select.offset || select.orders.any?
|
346
221
|
super
|
347
222
|
else
|
@@ -374,9 +249,9 @@ module ActiveRecord
|
|
374
249
|
# create_database 'matt_development', charset: :big5
|
375
250
|
def create_database(name, options = {})
|
376
251
|
if options[:collation]
|
377
|
-
execute "CREATE DATABASE
|
252
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
|
378
253
|
else
|
379
|
-
execute "CREATE DATABASE
|
254
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
|
380
255
|
end
|
381
256
|
end
|
382
257
|
|
@@ -385,99 +260,42 @@ module ActiveRecord
|
|
385
260
|
# Example:
|
386
261
|
# drop_database('sebastian_development')
|
387
262
|
def drop_database(name) #:nodoc:
|
388
|
-
execute "DROP DATABASE IF EXISTS
|
263
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
389
264
|
end
|
390
265
|
|
391
266
|
def current_database
|
392
|
-
|
267
|
+
query_value("SELECT database()", "SCHEMA")
|
393
268
|
end
|
394
269
|
|
395
270
|
# Returns the database character set.
|
396
271
|
def charset
|
397
|
-
show_variable
|
272
|
+
show_variable "character_set_database"
|
398
273
|
end
|
399
274
|
|
400
275
|
# Returns the database collation strategy.
|
401
276
|
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
|
277
|
+
show_variable "collation_database"
|
413
278
|
end
|
414
|
-
alias data_sources tables
|
415
279
|
|
416
280
|
def truncate(table_name, name = nil)
|
417
281
|
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
418
282
|
end
|
419
283
|
|
420
|
-
def
|
421
|
-
|
422
|
-
return true if tables(nil, nil, name).any?
|
423
|
-
|
424
|
-
name = name.to_s
|
425
|
-
schema, table = name.split('.', 2)
|
426
|
-
|
427
|
-
unless table # A table was provided without a schema
|
428
|
-
table = schema
|
429
|
-
schema = nil
|
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
|
284
|
+
def table_comment(table_name) # :nodoc:
|
285
|
+
scope = quoted_scope(table_name)
|
472
286
|
|
473
|
-
|
474
|
-
|
287
|
+
query_value(<<-SQL.strip_heredoc, "SCHEMA").presence
|
288
|
+
SELECT table_comment
|
289
|
+
FROM information_schema.tables
|
290
|
+
WHERE table_schema = #{scope[:schema]}
|
291
|
+
AND table_name = #{scope[:name]}
|
292
|
+
SQL
|
475
293
|
end
|
476
294
|
|
477
295
|
def bulk_change_table(table_name, operations) #:nodoc:
|
478
296
|
sqls = operations.flat_map do |command, args|
|
479
297
|
table, arguments = args.shift, args
|
480
|
-
method = :"#{command}
|
298
|
+
method = :"#{command}_for_alter"
|
481
299
|
|
482
300
|
if respond_to?(method, true)
|
483
301
|
send(method, table, *arguments)
|
@@ -489,6 +307,11 @@ module ActiveRecord
|
|
489
307
|
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
|
490
308
|
end
|
491
309
|
|
310
|
+
def change_table_comment(table_name, comment) #:nodoc:
|
311
|
+
comment = "" if comment.nil?
|
312
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
|
313
|
+
end
|
314
|
+
|
492
315
|
# Renames a table.
|
493
316
|
#
|
494
317
|
# Example:
|
@@ -498,8 +321,23 @@ module ActiveRecord
|
|
498
321
|
rename_table_indexes(table_name, new_name)
|
499
322
|
end
|
500
323
|
|
324
|
+
# Drops a table from the database.
|
325
|
+
#
|
326
|
+
# [<tt>:force</tt>]
|
327
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
328
|
+
# Defaults to false.
|
329
|
+
# [<tt>:if_exists</tt>]
|
330
|
+
# Set to +true+ to only drop the table if it exists.
|
331
|
+
# Defaults to false.
|
332
|
+
# [<tt>:temporary</tt>]
|
333
|
+
# Set to +true+ to drop temporary table.
|
334
|
+
# Defaults to false.
|
335
|
+
#
|
336
|
+
# Although this command ignores most +options+ and the block if one is given,
|
337
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
338
|
+
# In that case, +options+ and the block will be used by create_table.
|
501
339
|
def drop_table(table_name, options = {})
|
502
|
-
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
340
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
503
341
|
end
|
504
342
|
|
505
343
|
def rename_index(table_name, old_name, new_name)
|
@@ -512,149 +350,158 @@ module ActiveRecord
|
|
512
350
|
end
|
513
351
|
end
|
514
352
|
|
515
|
-
def change_column_default(table_name, column_name,
|
516
|
-
|
517
|
-
change_column table_name, column_name,
|
353
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
354
|
+
default = extract_new_default_value(default_or_changes)
|
355
|
+
change_column table_name, column_name, nil, default: default
|
518
356
|
end
|
519
357
|
|
520
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
521
|
-
column = column_for(table_name, column_name)
|
522
|
-
|
358
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
523
359
|
unless null || default.nil?
|
524
360
|
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
525
361
|
end
|
526
362
|
|
527
|
-
change_column table_name, column_name,
|
363
|
+
change_column table_name, column_name, nil, null: null
|
364
|
+
end
|
365
|
+
|
366
|
+
def change_column_comment(table_name, column_name, comment) #:nodoc:
|
367
|
+
change_column table_name, column_name, nil, comment: comment
|
528
368
|
end
|
529
369
|
|
530
370
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
531
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{
|
371
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
|
532
372
|
end
|
533
373
|
|
534
374
|
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
535
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{
|
375
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
|
536
376
|
rename_column_indexes(table_name, column_name, new_column_name)
|
537
377
|
end
|
538
378
|
|
539
379
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
540
|
-
index_name, index_type, index_columns,
|
541
|
-
|
380
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
381
|
+
sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
|
382
|
+
execute add_sql_comment!(sql, comment)
|
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.strip_heredoc, "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"])
|
421
|
+
|
422
|
+
ForeignKeyDefinition.new(table_name, row["to_table"], options)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def table_options(table_name) # :nodoc:
|
427
|
+
table_options = {}
|
428
|
+
|
429
|
+
create_table_info = create_table_info(table_name)
|
430
|
+
|
431
|
+
# strip create_definitions and partition_options
|
432
|
+
raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
|
567
433
|
|
568
|
-
|
434
|
+
# strip AUTO_INCREMENT
|
435
|
+
raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
|
436
|
+
|
437
|
+
table_options[:options] = raw_table_options
|
438
|
+
|
439
|
+
# strip COMMENT
|
440
|
+
if raw_table_options.sub!(/ COMMENT='.+'/, "")
|
441
|
+
table_options[:comment] = table_comment(table_name)
|
569
442
|
end
|
443
|
+
|
444
|
+
table_options
|
570
445
|
end
|
571
446
|
|
572
447
|
# Maps logical Rails types to MySQL-specific data types.
|
573
|
-
def type_to_sql(type, limit
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
when
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
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}")
|
448
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
|
449
|
+
sql = \
|
450
|
+
case type.to_s
|
451
|
+
when "integer"
|
452
|
+
integer_to_sql(limit)
|
453
|
+
when "text"
|
454
|
+
text_to_sql(limit)
|
455
|
+
when "blob"
|
456
|
+
binary_to_sql(limit)
|
457
|
+
when "binary"
|
458
|
+
if (0..0xfff) === limit
|
459
|
+
"varbinary(#{limit})"
|
460
|
+
else
|
461
|
+
binary_to_sql(limit)
|
462
|
+
end
|
463
|
+
else
|
464
|
+
super
|
598
465
|
end
|
599
|
-
when 'datetime'
|
600
|
-
return super unless precision
|
601
466
|
|
602
|
-
|
603
|
-
|
604
|
-
else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
|
605
|
-
end
|
606
|
-
else
|
607
|
-
super
|
608
|
-
end
|
467
|
+
sql = "#{sql} unsigned" if unsigned && type != :primary_key
|
468
|
+
sql
|
609
469
|
end
|
610
470
|
|
611
471
|
# SHOW VARIABLES LIKE 'name'
|
612
472
|
def show_variable(name)
|
613
|
-
|
614
|
-
variables.first['Value'] unless variables.empty?
|
473
|
+
query_value("SELECT @@#{name}", "SCHEMA")
|
615
474
|
rescue ActiveRecord::StatementInvalid
|
616
475
|
nil
|
617
476
|
end
|
618
477
|
|
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
|
478
|
+
def primary_keys(table_name) # :nodoc:
|
479
|
+
raise ArgumentError unless table_name.present?
|
631
480
|
|
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
|
481
|
+
scope = quoted_scope(table_name)
|
637
482
|
|
638
|
-
|
639
|
-
|
640
|
-
|
483
|
+
query_values(<<-SQL.strip_heredoc, "SCHEMA")
|
484
|
+
SELECT column_name
|
485
|
+
FROM information_schema.key_column_usage
|
486
|
+
WHERE constraint_name = 'PRIMARY'
|
487
|
+
AND table_schema = #{scope[:schema]}
|
488
|
+
AND table_name = #{scope[:name]}
|
489
|
+
ORDER BY ordinal_position
|
490
|
+
SQL
|
641
491
|
end
|
642
492
|
|
643
|
-
def case_sensitive_comparison(table, attribute, column, value)
|
644
|
-
if column.case_sensitive?
|
645
|
-
table[attribute].eq(value)
|
493
|
+
def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
|
494
|
+
if column.collation && !column.case_sensitive?
|
495
|
+
table[attribute].eq(Arel::Nodes::Bin.new(value))
|
646
496
|
else
|
647
497
|
super
|
648
498
|
end
|
649
499
|
end
|
650
500
|
|
651
|
-
def
|
652
|
-
|
653
|
-
super
|
654
|
-
else
|
655
|
-
table[attribute].eq(value)
|
656
|
-
end
|
501
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
502
|
+
column.case_sensitive?
|
657
503
|
end
|
504
|
+
private :can_perform_case_insensitive_comparison_for?
|
658
505
|
|
659
506
|
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
660
507
|
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
@@ -665,274 +512,376 @@ module ActiveRecord
|
|
665
512
|
# Convert Arel node to string
|
666
513
|
s = s.to_sql unless s.is_a?(String)
|
667
514
|
# Remove any ASC/DESC modifiers
|
668
|
-
s.gsub(/\s+(?:ASC|DESC)\b/i,
|
515
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, "")
|
669
516
|
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
670
517
|
|
671
|
-
|
518
|
+
(order_columns << super).join(", ")
|
672
519
|
end
|
673
520
|
|
674
521
|
def strict_mode?
|
675
522
|
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
|
676
523
|
end
|
677
524
|
|
678
|
-
def
|
679
|
-
|
525
|
+
def default_index_type?(index) # :nodoc:
|
526
|
+
index.using == :btree || super
|
680
527
|
end
|
681
528
|
|
682
|
-
|
529
|
+
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
530
|
+
with_multi_statements do
|
531
|
+
super { discard_remaining_results }
|
532
|
+
end
|
533
|
+
end
|
683
534
|
|
684
|
-
|
685
|
-
|
535
|
+
private
|
536
|
+
def combine_multi_statements(total_sql)
|
537
|
+
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
538
|
+
previous_packet = total_sql_chunks.last
|
539
|
+
sql << ";\n"
|
540
|
+
if max_allowed_packet_reached?(sql, previous_packet) || total_sql_chunks.empty?
|
541
|
+
total_sql_chunks << sql
|
542
|
+
else
|
543
|
+
previous_packet << sql
|
544
|
+
end
|
545
|
+
end
|
546
|
+
end
|
686
547
|
|
687
|
-
|
548
|
+
def max_allowed_packet_reached?(current_packet, previous_packet)
|
549
|
+
if current_packet.bytesize > max_allowed_packet
|
550
|
+
raise ActiveRecordError, "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
|
551
|
+
elsif previous_packet.nil?
|
552
|
+
false
|
553
|
+
else
|
554
|
+
(current_packet.bytesize + previous_packet.bytesize) > max_allowed_packet
|
555
|
+
end
|
556
|
+
end
|
688
557
|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
|
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 max_allowed_packet
|
559
|
+
bytes_margin = 2
|
560
|
+
@max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
|
561
|
+
end
|
699
562
|
|
700
|
-
|
701
|
-
|
702
|
-
register_integer_type m, %r(^mediumint)i, limit: 3
|
703
|
-
register_integer_type m, %r(^smallint)i, limit: 2
|
704
|
-
register_integer_type m, %r(^tinyint)i, limit: 1
|
563
|
+
def initialize_type_map(m = type_map)
|
564
|
+
super
|
705
565
|
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
566
|
+
register_class_with_limit m, %r(char)i, MysqlString
|
567
|
+
|
568
|
+
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
|
569
|
+
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
|
570
|
+
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
|
571
|
+
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
|
572
|
+
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
|
573
|
+
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
|
574
|
+
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
|
575
|
+
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
576
|
+
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
577
|
+
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
578
|
+
|
579
|
+
register_integer_type m, %r(^bigint)i, limit: 8
|
580
|
+
register_integer_type m, %r(^int)i, limit: 4
|
581
|
+
register_integer_type m, %r(^mediumint)i, limit: 3
|
582
|
+
register_integer_type m, %r(^smallint)i, limit: 2
|
583
|
+
register_integer_type m, %r(^tinyint)i, limit: 1
|
584
|
+
|
585
|
+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
|
586
|
+
m.alias_type %r(year)i, "integer"
|
587
|
+
m.alias_type %r(bit)i, "binary"
|
588
|
+
|
589
|
+
m.register_type(%r(enum)i) do |sql_type|
|
590
|
+
limit = sql_type[/^enum\((.+)\)/i, 1]
|
591
|
+
.split(",").map { |enum| enum.strip.length - 2 }.max
|
592
|
+
MysqlString.new(limit: limit)
|
593
|
+
end
|
710
594
|
|
711
|
-
|
712
|
-
|
713
|
-
|
595
|
+
m.register_type(%r(^set)i) do |sql_type|
|
596
|
+
limit = sql_type[/^set\((.+)\)/i, 1]
|
597
|
+
.split(",").map { |set| set.strip.length - 1 }.sum - 1
|
598
|
+
MysqlString.new(limit: limit)
|
599
|
+
end
|
714
600
|
end
|
715
601
|
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
602
|
+
def register_integer_type(mapping, key, options)
|
603
|
+
mapping.register_type(key) do |sql_type|
|
604
|
+
if /\bunsigned\b/.match?(sql_type)
|
605
|
+
Type::UnsignedInteger.new(options)
|
606
|
+
else
|
607
|
+
Type::Integer.new(options)
|
608
|
+
end
|
609
|
+
end
|
720
610
|
end
|
721
|
-
end
|
722
611
|
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
Type::UnsignedInteger.new(options)
|
612
|
+
def extract_precision(sql_type)
|
613
|
+
if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
|
614
|
+
super || 0
|
727
615
|
else
|
728
|
-
|
616
|
+
super
|
729
617
|
end
|
730
618
|
end
|
731
|
-
end
|
732
|
-
|
733
|
-
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
734
|
-
# to give it some prompting in the form of a subsubquery. Ugh!
|
735
|
-
def subquery_for(key, select)
|
736
|
-
subsubselect = select.clone
|
737
|
-
subsubselect.projections = [key]
|
738
|
-
|
739
|
-
# Materialize subquery by adding distinct
|
740
|
-
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
741
|
-
subsubselect.distinct unless select.limit || select.offset || select.orders.any?
|
742
619
|
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
620
|
+
# See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
|
621
|
+
ER_DUP_ENTRY = 1062
|
622
|
+
ER_NOT_NULL_VIOLATION = 1048
|
623
|
+
ER_DO_NOT_HAVE_DEFAULT = 1364
|
624
|
+
ER_NO_REFERENCED_ROW_2 = 1452
|
625
|
+
ER_DATA_TOO_LONG = 1406
|
626
|
+
ER_OUT_OF_RANGE = 1264
|
627
|
+
ER_LOCK_DEADLOCK = 1213
|
628
|
+
ER_CANNOT_ADD_FOREIGN = 1215
|
629
|
+
ER_CANNOT_CREATE_TABLE = 1005
|
630
|
+
ER_LOCK_WAIT_TIMEOUT = 1205
|
631
|
+
ER_QUERY_INTERRUPTED = 1317
|
632
|
+
ER_QUERY_TIMEOUT = 3024
|
633
|
+
|
634
|
+
def translate_exception(exception, message)
|
635
|
+
case error_number(exception)
|
636
|
+
when ER_DUP_ENTRY
|
637
|
+
RecordNotUnique.new(message)
|
638
|
+
when ER_NO_REFERENCED_ROW_2
|
639
|
+
InvalidForeignKey.new(message)
|
640
|
+
when ER_CANNOT_ADD_FOREIGN
|
641
|
+
mismatched_foreign_key(message)
|
642
|
+
when ER_CANNOT_CREATE_TABLE
|
643
|
+
if message.include?("errno: 150")
|
644
|
+
mismatched_foreign_key(message)
|
645
|
+
else
|
646
|
+
super
|
647
|
+
end
|
648
|
+
when ER_DATA_TOO_LONG
|
649
|
+
ValueTooLong.new(message)
|
650
|
+
when ER_OUT_OF_RANGE
|
651
|
+
RangeError.new(message)
|
652
|
+
when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
|
653
|
+
NotNullViolation.new(message)
|
654
|
+
when ER_LOCK_DEADLOCK
|
655
|
+
Deadlocked.new(message)
|
656
|
+
when ER_LOCK_WAIT_TIMEOUT
|
657
|
+
LockWaitTimeout.new(message)
|
658
|
+
when ER_QUERY_TIMEOUT
|
659
|
+
StatementTimeout.new(message)
|
660
|
+
when ER_QUERY_INTERRUPTED
|
661
|
+
QueryCanceled.new(message)
|
662
|
+
else
|
663
|
+
super
|
755
664
|
end
|
756
665
|
end
|
757
666
|
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
def quoted_columns_for_index(column_names, options = {})
|
762
|
-
option_strings = Hash[column_names.map {|name| [name, '']}]
|
667
|
+
def change_column_for_alter(table_name, column_name, type, options = {})
|
668
|
+
column = column_for(table_name, column_name)
|
669
|
+
type ||= column.sql_type
|
763
670
|
|
764
|
-
|
765
|
-
|
671
|
+
unless options.key?(:default)
|
672
|
+
options[:default] = column.default
|
673
|
+
end
|
766
674
|
|
767
|
-
|
768
|
-
|
675
|
+
unless options.key?(:null)
|
676
|
+
options[:null] = column.null
|
677
|
+
end
|
769
678
|
|
770
|
-
|
771
|
-
|
679
|
+
unless options.key?(:comment)
|
680
|
+
options[:comment] = column.comment
|
681
|
+
end
|
772
682
|
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
RecordNotUnique.new(message, exception)
|
777
|
-
when 1452
|
778
|
-
InvalidForeignKey.new(message, exception)
|
779
|
-
else
|
780
|
-
super
|
683
|
+
td = create_table_definition(table_name)
|
684
|
+
cd = td.new_column_definition(column.name, type, options)
|
685
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
781
686
|
end
|
782
|
-
end
|
783
|
-
|
784
|
-
def add_column_sql(table_name, column_name, type, options = {})
|
785
|
-
td = create_table_definition table_name, options[:temporary], options[:options]
|
786
|
-
cd = td.new_column_definition(column_name, type, options)
|
787
|
-
schema_creation.visit_AddColumn cd
|
788
|
-
end
|
789
687
|
|
790
|
-
|
791
|
-
|
688
|
+
def rename_column_for_alter(table_name, column_name, new_column_name)
|
689
|
+
column = column_for(table_name, column_name)
|
690
|
+
options = {
|
691
|
+
default: column.default,
|
692
|
+
null: column.null,
|
693
|
+
auto_increment: column.auto_increment?
|
694
|
+
}
|
792
695
|
|
793
|
-
|
794
|
-
|
696
|
+
current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
|
697
|
+
td = create_table_definition(table_name)
|
698
|
+
cd = td.new_column_definition(new_column_name, current_type, options)
|
699
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
795
700
|
end
|
796
701
|
|
797
|
-
|
798
|
-
|
702
|
+
def add_index_for_alter(table_name, column_name, options = {})
|
703
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
704
|
+
index_algorithm[0, 0] = ", " if index_algorithm.present?
|
705
|
+
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
|
799
706
|
end
|
800
707
|
|
801
|
-
options
|
802
|
-
|
803
|
-
|
708
|
+
def remove_index_for_alter(table_name, options = {})
|
709
|
+
index_name = index_name_for_remove(table_name, options)
|
710
|
+
"DROP INDEX #{quote_column_name(index_name)}"
|
711
|
+
end
|
804
712
|
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
name: new_column_name,
|
809
|
-
default: column.default,
|
810
|
-
null: column.null,
|
811
|
-
auto_increment: column.extra == "auto_increment"
|
812
|
-
}
|
713
|
+
def add_timestamps_for_alter(table_name, options = {})
|
714
|
+
[add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
|
715
|
+
end
|
813
716
|
|
814
|
-
|
815
|
-
|
816
|
-
|
717
|
+
def remove_timestamps_for_alter(table_name, options = {})
|
718
|
+
[remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
|
719
|
+
end
|
817
720
|
|
818
|
-
|
819
|
-
|
820
|
-
|
721
|
+
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
722
|
+
# to give it some prompting in the form of a subsubquery. Ugh!
|
723
|
+
def subquery_for(key, select)
|
724
|
+
subselect = select.clone
|
725
|
+
subselect.projections = [key]
|
821
726
|
|
822
|
-
|
823
|
-
|
824
|
-
|
727
|
+
# Materialize subquery by adding distinct
|
728
|
+
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
729
|
+
subselect.distinct unless select.limit || select.offset || select.orders.any?
|
825
730
|
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
end
|
830
|
-
|
831
|
-
def remove_index_sql(table_name, options = {})
|
832
|
-
index_name = index_name_for_remove(table_name, options)
|
833
|
-
"DROP INDEX #{index_name}"
|
834
|
-
end
|
731
|
+
key_name = quote_column_name(key.name)
|
732
|
+
Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
|
733
|
+
end
|
835
734
|
|
836
|
-
|
837
|
-
|
838
|
-
|
735
|
+
def supports_rename_index?
|
736
|
+
mariadb? ? false : version >= "5.7.6"
|
737
|
+
end
|
839
738
|
|
840
|
-
|
841
|
-
|
842
|
-
end
|
739
|
+
def configure_connection
|
740
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
843
741
|
|
844
|
-
|
742
|
+
# By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
|
743
|
+
variables["sql_auto_is_null"] = 0
|
845
744
|
|
846
|
-
|
847
|
-
|
848
|
-
|
745
|
+
# Increase timeout so the server doesn't disconnect us.
|
746
|
+
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
|
747
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
748
|
+
variables["wait_timeout"] = wait_timeout
|
849
749
|
|
850
|
-
|
851
|
-
full_version =~ /mariadb/i
|
852
|
-
end
|
750
|
+
defaults = [":default", :default].to_set
|
853
751
|
|
854
|
-
|
855
|
-
|
856
|
-
|
752
|
+
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
753
|
+
# https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
|
754
|
+
# If the user has provided another value for sql_mode, don't replace it.
|
755
|
+
if sql_mode = variables.delete("sql_mode")
|
756
|
+
sql_mode = quote(sql_mode)
|
757
|
+
elsif !defaults.include?(strict_mode?)
|
758
|
+
if strict_mode?
|
759
|
+
sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
|
760
|
+
else
|
761
|
+
sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
|
762
|
+
sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
|
763
|
+
sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
|
764
|
+
end
|
765
|
+
sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
|
766
|
+
end
|
767
|
+
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
|
768
|
+
|
769
|
+
# NAMES does not have an equals sign, see
|
770
|
+
# https://dev.mysql.com/doc/refman/5.7/en/set-names.html
|
771
|
+
# (trailing comma because variable_assignments will always have content)
|
772
|
+
if @config[:encoding]
|
773
|
+
encoding = "NAMES #{@config[:encoding]}".dup
|
774
|
+
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
|
775
|
+
encoding << ", "
|
776
|
+
end
|
857
777
|
|
858
|
-
|
859
|
-
|
778
|
+
# Gather up all of the SET variables...
|
779
|
+
variable_assignments = variables.map do |k, v|
|
780
|
+
if defaults.include?(v)
|
781
|
+
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
782
|
+
elsif !v.nil?
|
783
|
+
"@@SESSION.#{k} = #{quote(v)}"
|
784
|
+
end
|
785
|
+
# or else nil; compact to clear nils out
|
786
|
+
end.compact.join(", ")
|
860
787
|
|
861
|
-
|
862
|
-
|
863
|
-
|
788
|
+
# ...and send them all in one query
|
789
|
+
execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
|
790
|
+
end
|
864
791
|
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
792
|
+
def column_definitions(table_name) # :nodoc:
|
793
|
+
execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
|
794
|
+
each_hash(result)
|
795
|
+
end
|
796
|
+
end
|
869
797
|
|
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' : ''
|
798
|
+
def create_table_info(table_name) # :nodoc:
|
799
|
+
exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
|
875
800
|
end
|
876
801
|
|
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 << ", "
|
802
|
+
def arel_visitor
|
803
|
+
Arel::Visitors::MySQL.new(self)
|
884
804
|
end
|
885
805
|
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
end
|
893
|
-
# or else nil; compact to clear nils out
|
894
|
-
end.compact.join(', ')
|
806
|
+
def mismatched_foreign_key(message)
|
807
|
+
match = %r/
|
808
|
+
(?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
|
809
|
+
FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
|
810
|
+
REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
|
811
|
+
/xmi.match(message)
|
895
812
|
|
896
|
-
|
897
|
-
|
898
|
-
|
813
|
+
options = {
|
814
|
+
message: message,
|
815
|
+
}
|
899
816
|
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
817
|
+
if match
|
818
|
+
options[:table] = match[:table]
|
819
|
+
options[:foreign_key] = match[:foreign_key]
|
820
|
+
options[:target_table] = match[:target_table]
|
821
|
+
options[:primary_key] = match[:primary_key]
|
822
|
+
options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
|
905
823
|
end
|
824
|
+
|
825
|
+
MismatchedForeignKey.new(options)
|
906
826
|
end
|
907
|
-
end
|
908
827
|
|
909
|
-
|
910
|
-
|
828
|
+
def integer_to_sql(limit) # :nodoc:
|
829
|
+
case limit
|
830
|
+
when 1; "tinyint"
|
831
|
+
when 2; "smallint"
|
832
|
+
when 3; "mediumint"
|
833
|
+
when nil, 4; "int"
|
834
|
+
when 5..8; "bigint"
|
835
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.")
|
836
|
+
end
|
837
|
+
end
|
911
838
|
|
912
|
-
def
|
913
|
-
|
839
|
+
def text_to_sql(limit) # :nodoc:
|
840
|
+
case limit
|
841
|
+
when 0..0xff; "tinytext"
|
842
|
+
when nil, 0x100..0xffff; "text"
|
843
|
+
when 0x10000..0xffffff; "mediumtext"
|
844
|
+
when 0x1000000..0xffffffff; "longtext"
|
845
|
+
else raise(ActiveRecordError, "No text type has byte length #{limit}")
|
846
|
+
end
|
914
847
|
end
|
915
|
-
end
|
916
848
|
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
when
|
921
|
-
when
|
922
|
-
|
849
|
+
def binary_to_sql(limit) # :nodoc:
|
850
|
+
case limit
|
851
|
+
when 0..0xff; "tinyblob"
|
852
|
+
when nil, 0x100..0xffff; "blob"
|
853
|
+
when 0x10000..0xffffff; "mediumblob"
|
854
|
+
when 0x1000000..0xffffffff; "longblob"
|
855
|
+
else raise(ActiveRecordError, "No binary type has byte length #{limit}")
|
923
856
|
end
|
924
857
|
end
|
925
858
|
|
926
|
-
|
859
|
+
def version_string
|
860
|
+
full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
|
861
|
+
end
|
927
862
|
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
863
|
+
class MysqlString < Type::String # :nodoc:
|
864
|
+
def serialize(value)
|
865
|
+
case value
|
866
|
+
when true then "1"
|
867
|
+
when false then "0"
|
868
|
+
else super
|
869
|
+
end
|
933
870
|
end
|
871
|
+
|
872
|
+
private
|
873
|
+
|
874
|
+
def cast_value(value)
|
875
|
+
case value
|
876
|
+
when true then "1"
|
877
|
+
when false then "0"
|
878
|
+
else super
|
879
|
+
end
|
880
|
+
end
|
934
881
|
end
|
935
|
-
|
882
|
+
|
883
|
+
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
|
884
|
+
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
|
936
885
|
end
|
937
886
|
end
|
938
887
|
end
|