activerecord 4.2.11.3 → 5.0.7.2
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 +4 -4
- data/CHANGELOG.md +1638 -1132
- data/MIT-LICENSE +2 -2
- data/README.rdoc +7 -8
- data/examples/performance.rb +2 -3
- data/examples/simple.rb +0 -1
- data/lib/active_record.rb +7 -2
- data/lib/active_record/aggregations.rb +34 -21
- data/lib/active_record/association_relation.rb +7 -4
- data/lib/active_record/associations.rb +347 -218
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +22 -10
- data/lib/active_record/associations/association_scope.rb +75 -104
- data/lib/active_record/associations/belongs_to_association.rb +21 -32
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +43 -18
- data/lib/active_record/associations/builder/collection_association.rb +7 -19
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +11 -6
- data/lib/active_record/associations/builder/singular_association.rb +13 -11
- data/lib/active_record/associations/collection_association.rb +85 -69
- data/lib/active_record/associations/collection_proxy.rb +104 -46
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +21 -78
- data/lib/active_record/associations/has_many_through_association.rb +6 -47
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency.rb +38 -22
- data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +14 -4
- data/lib/active_record/associations/preloader/association.rb +52 -71
- data/lib/active_record/associations/preloader/collection_association.rb +0 -7
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +0 -1
- data/lib/active_record/associations/preloader/through_association.rb +36 -17
- data/lib/active_record/associations/singular_association.rb +13 -1
- data/lib/active_record/associations/through_association.rb +12 -4
- data/lib/active_record/attribute.rb +69 -19
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute_assignment.rb +19 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods.rb +69 -44
- data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +46 -86
- data/lib/active_record/attribute_methods/primary_key.rb +16 -3
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +31 -59
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
- data/lib/active_record/attribute_methods/write.rb +13 -37
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set.rb +32 -3
- data/lib/active_record/attribute_set/builder.rb +42 -16
- data/lib/active_record/attributes.rb +199 -81
- data/lib/active_record/autosave_association.rb +54 -17
- data/lib/active_record/base.rb +32 -23
- data/lib/active_record/callbacks.rb +39 -43
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
- data/lib/active_record/connection_adapters/column.rb +28 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
- data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +37 -14
- data/lib/active_record/core.rb +92 -108
- data/lib/active_record/counter_cache.rb +13 -24
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +116 -76
- data/lib/active_record/errors.rb +87 -48
- data/lib/active_record/explain.rb +20 -9
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +26 -5
- data/lib/active_record/fixtures.rb +77 -41
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +17 -14
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +18 -2
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +15 -15
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +48 -24
- data/lib/active_record/migration.rb +362 -111
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/model_schema.rb +270 -73
- data/lib/active_record/nested_attributes.rb +58 -29
- data/lib/active_record/no_touching.rb +4 -0
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +152 -90
- data/lib/active_record/query_cache.rb +18 -23
- data/lib/active_record/querying.rb +12 -11
- data/lib/active_record/railtie.rb +23 -16
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +52 -41
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +302 -115
- data/lib/active_record/relation.rb +187 -120
- data/lib/active_record/relation/batches.rb +141 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/calculations.rb +92 -117
- data/lib/active_record/relation/delegation.rb +8 -20
- data/lib/active_record/relation/finder_methods.rb +173 -89
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +16 -42
- data/lib/active_record/relation/predicate_builder.rb +120 -107
- data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +308 -244
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +4 -7
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/result.rb +11 -4
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +105 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +54 -37
- data/lib/active_record/schema_migration.rb +11 -14
- data/lib/active_record/scoping.rb +34 -16
- data/lib/active_record/scoping/default.rb +28 -10
- data/lib/active_record/scoping/named.rb +59 -26
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +3 -5
- data/lib/active_record/statement_cache.rb +17 -15
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +69 -0
- data/lib/active_record/tasks/database_tasks.rb +66 -49
- data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +20 -9
- data/lib/active_record/touch_later.rb +63 -0
- data/lib/active_record/transactions.rb +139 -57
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -45
- data/lib/active_record/type/date_time.rb +2 -49
- data/lib/active_record/type/internal/abstract_json.rb +33 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +15 -14
- data/lib/active_record/type/time.rb +10 -16
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +11 -12
- data/lib/active_record/validations/uniqueness.rb +33 -33
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +58 -34
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
- 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/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- 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/decimal_without_scale.rb +0 -11
- 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/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -110
@@ -1,184 +1,93 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/statement_pool'
|
3
|
+
require 'active_record/connection_adapters/mysql/column'
|
4
|
+
require 'active_record/connection_adapters/mysql/explain_pretty_printer'
|
5
|
+
require 'active_record/connection_adapters/mysql/quoting'
|
6
|
+
require 'active_record/connection_adapters/mysql/schema_creation'
|
7
|
+
require 'active_record/connection_adapters/mysql/schema_definitions'
|
8
|
+
require 'active_record/connection_adapters/mysql/schema_dumper'
|
9
|
+
require 'active_record/connection_adapters/mysql/type_metadata'
|
10
|
+
|
2
11
|
require 'active_support/core_ext/string/strip'
|
3
12
|
|
4
13
|
module ActiveRecord
|
5
14
|
module ConnectionAdapters
|
6
15
|
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)} "
|
16
|
+
include MySQL::Quoting
|
17
|
+
include MySQL::ColumnDumper
|
23
18
|
|
24
|
-
|
25
|
-
|
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
|
19
|
+
def update_table_definition(table_name, base) # :nodoc:
|
20
|
+
MySQL::Table.new(table_name, base)
|
59
21
|
end
|
60
22
|
|
61
|
-
def
|
62
|
-
|
63
|
-
spec.delete(:limit) if :boolean === column.type
|
64
|
-
spec
|
23
|
+
def schema_creation # :nodoc:
|
24
|
+
MySQL::SchemaCreation.new(self)
|
65
25
|
end
|
66
26
|
|
67
|
-
|
68
|
-
|
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
|
27
|
+
def arel_visitor # :nodoc:
|
28
|
+
Arel::Visitors::MySQL.new(self)
|
129
29
|
end
|
130
30
|
|
131
31
|
##
|
132
32
|
# :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
|
33
|
+
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
|
34
|
+
# as boolean. If you wish to disable this emulation you can add the following line
|
136
35
|
# to your application.rb file:
|
137
36
|
#
|
138
|
-
# ActiveRecord::ConnectionAdapters::
|
37
|
+
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
|
139
38
|
class_attribute :emulate_booleans
|
140
39
|
self.emulate_booleans = true
|
141
40
|
|
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'
|
149
|
-
|
150
41
|
NATIVE_DATABASE_TYPES = {
|
151
|
-
:
|
152
|
-
:
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
156
|
-
:
|
157
|
-
:
|
158
|
-
:
|
159
|
-
:
|
160
|
-
:
|
161
|
-
:
|
42
|
+
primary_key: "int auto_increment PRIMARY KEY",
|
43
|
+
string: { name: "varchar", limit: 255 },
|
44
|
+
text: { name: "text" },
|
45
|
+
integer: { name: "int", limit: 4 },
|
46
|
+
float: { name: "float" },
|
47
|
+
decimal: { name: "decimal" },
|
48
|
+
datetime: { name: "datetime" },
|
49
|
+
time: { name: "time" },
|
50
|
+
date: { name: "date" },
|
51
|
+
binary: { name: "blob" },
|
52
|
+
boolean: { name: "tinyint", limit: 1 },
|
53
|
+
json: { name: "json" },
|
162
54
|
}
|
163
55
|
|
164
56
|
INDEX_TYPES = [:fulltext, :spatial]
|
165
57
|
INDEX_USINGS = [:btree, :hash]
|
166
58
|
|
167
|
-
|
59
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
60
|
+
private def dealloc(stmt)
|
61
|
+
stmt[:stmt].close
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
168
65
|
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 = {}, {}
|
66
|
+
super(connection, logger, config)
|
172
67
|
|
173
|
-
@
|
68
|
+
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
174
69
|
|
175
|
-
if
|
176
|
-
|
177
|
-
else
|
178
|
-
@prepared_statements = false
|
70
|
+
if version < '5.0.0'
|
71
|
+
raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.0."
|
179
72
|
end
|
180
73
|
end
|
181
74
|
|
75
|
+
CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
|
76
|
+
|
77
|
+
def internal_string_options_for_primary_key # :nodoc:
|
78
|
+
super.tap { |options|
|
79
|
+
options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def version #:nodoc:
|
84
|
+
@version ||= Version.new(version_string)
|
85
|
+
end
|
86
|
+
|
87
|
+
def mariadb? # :nodoc:
|
88
|
+
full_version =~ /mariadb/i
|
89
|
+
end
|
90
|
+
|
182
91
|
# Returns true, since this connection adapter supports migrations.
|
183
92
|
def supports_migrations?
|
184
93
|
true
|
@@ -192,18 +101,24 @@ module ActiveRecord
|
|
192
101
|
true
|
193
102
|
end
|
194
103
|
|
104
|
+
# Returns true, since this connection adapter supports prepared statement
|
105
|
+
# caching.
|
106
|
+
def supports_statement_cache?
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
195
110
|
# Technically MySQL allows to create indexes with the sort order syntax
|
196
111
|
# but at the moment (5.5) it doesn't yet implement them
|
197
112
|
def supports_index_sort_order?
|
198
113
|
true
|
199
114
|
end
|
200
115
|
|
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
116
|
def supports_transaction_isolation?
|
206
|
-
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
def supports_explain?
|
121
|
+
true
|
207
122
|
end
|
208
123
|
|
209
124
|
def supports_indexes_in_create?
|
@@ -215,11 +130,27 @@ module ActiveRecord
|
|
215
130
|
end
|
216
131
|
|
217
132
|
def supports_views?
|
218
|
-
|
133
|
+
true
|
219
134
|
end
|
220
135
|
|
221
136
|
def supports_datetime_with_precision?
|
222
|
-
|
137
|
+
if mariadb?
|
138
|
+
version >= '5.3.0'
|
139
|
+
else
|
140
|
+
version >= '5.6.4'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def supports_advisory_locks?
|
145
|
+
true
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
149
|
+
select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
|
150
|
+
end
|
151
|
+
|
152
|
+
def release_advisory_lock(lock_name) # :nodoc:
|
153
|
+
select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
|
223
154
|
end
|
224
155
|
|
225
156
|
def native_database_types
|
@@ -238,8 +169,8 @@ module ActiveRecord
|
|
238
169
|
raise NotImplementedError
|
239
170
|
end
|
240
171
|
|
241
|
-
def new_column(
|
242
|
-
Column.new(
|
172
|
+
def new_column(*args) #:nodoc:
|
173
|
+
MySQL::Column.new(*args)
|
243
174
|
end
|
244
175
|
|
245
176
|
# Must return the MySQL error number from the exception, if the exception has an
|
@@ -248,48 +179,6 @@ module ActiveRecord
|
|
248
179
|
raise NotImplementedError
|
249
180
|
end
|
250
181
|
|
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
|
-
def quoted_date(value)
|
286
|
-
if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec)
|
287
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
288
|
-
else
|
289
|
-
super
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
182
|
# REFERENTIAL INTEGRITY ====================================
|
294
183
|
|
295
184
|
def disable_referential_integrity #:nodoc:
|
@@ -303,13 +192,25 @@ module ActiveRecord
|
|
303
192
|
end
|
304
193
|
end
|
305
194
|
|
195
|
+
# CONNECTION MANAGEMENT ====================================
|
196
|
+
|
197
|
+
# Clears the prepared statements cache.
|
198
|
+
def clear_cache!
|
199
|
+
reload_type_map
|
200
|
+
@statements.clear
|
201
|
+
end
|
202
|
+
|
306
203
|
#--
|
307
204
|
# DATABASE STATEMENTS ======================================
|
308
205
|
#++
|
309
206
|
|
310
|
-
def
|
311
|
-
|
312
|
-
|
207
|
+
def explain(arel, binds = [])
|
208
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
209
|
+
start = Time.now
|
210
|
+
result = exec_query(sql, 'EXPLAIN', binds)
|
211
|
+
elapsed = Time.now - start
|
212
|
+
|
213
|
+
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
313
214
|
end
|
314
215
|
|
315
216
|
# Executes the SQL statement in the context of this connection.
|
@@ -317,18 +218,13 @@ module ActiveRecord
|
|
317
218
|
log(sql, name) { @connection.query(sql) }
|
318
219
|
end
|
319
220
|
|
320
|
-
#
|
321
|
-
# stuff in an abstract way without concerning ourselves about whether it
|
322
|
-
# explicitly freed or not.
|
323
|
-
def execute_and_free(sql, name = nil)
|
221
|
+
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
|
222
|
+
# to write stuff in an abstract way without concerning ourselves about whether it
|
223
|
+
# needs to be explicitly freed or not.
|
224
|
+
def execute_and_free(sql, name = nil) # :nodoc:
|
324
225
|
yield execute(sql, name)
|
325
226
|
end
|
326
227
|
|
327
|
-
def update_sql(sql, name = nil) #:nodoc:
|
328
|
-
super
|
329
|
-
@connection.affected_rows
|
330
|
-
end
|
331
|
-
|
332
228
|
def begin_db_transaction
|
333
229
|
execute "BEGIN"
|
334
230
|
end
|
@@ -349,7 +245,7 @@ module ActiveRecord
|
|
349
245
|
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
350
246
|
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
351
247
|
# these, we must use a subquery.
|
352
|
-
def join_to_update(update, select)
|
248
|
+
def join_to_update(update, select, key) # :nodoc:
|
353
249
|
if select.limit || select.offset || select.orders.any?
|
354
250
|
super
|
355
251
|
else
|
@@ -382,9 +278,9 @@ module ActiveRecord
|
|
382
278
|
# create_database 'matt_development', charset: :big5
|
383
279
|
def create_database(name, options = {})
|
384
280
|
if options[:collation]
|
385
|
-
execute "CREATE DATABASE
|
281
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
|
386
282
|
else
|
387
|
-
execute "CREATE DATABASE
|
283
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
|
388
284
|
end
|
389
285
|
end
|
390
286
|
|
@@ -393,7 +289,7 @@ module ActiveRecord
|
|
393
289
|
# Example:
|
394
290
|
# drop_database('sebastian_development')
|
395
291
|
def drop_database(name) #:nodoc:
|
396
|
-
execute "DROP DATABASE IF EXISTS
|
292
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
397
293
|
end
|
398
294
|
|
399
295
|
def current_database
|
@@ -410,36 +306,69 @@ module ActiveRecord
|
|
410
306
|
show_variable 'collation_database'
|
411
307
|
end
|
412
308
|
|
413
|
-
def tables(name = nil
|
414
|
-
|
415
|
-
|
416
|
-
|
309
|
+
def tables(name = nil) # :nodoc:
|
310
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
311
|
+
#tables currently returns both tables and views.
|
312
|
+
This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
|
313
|
+
Use #data_sources instead.
|
314
|
+
MSG
|
417
315
|
|
418
|
-
|
419
|
-
|
316
|
+
if name
|
317
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
318
|
+
Passing arguments to #tables is deprecated without replacement.
|
319
|
+
MSG
|
420
320
|
end
|
321
|
+
|
322
|
+
data_sources
|
323
|
+
end
|
324
|
+
|
325
|
+
def data_sources
|
326
|
+
sql = "SELECT table_name FROM information_schema.tables "
|
327
|
+
sql << "WHERE table_schema = DATABASE()"
|
328
|
+
|
329
|
+
select_values(sql, 'SCHEMA')
|
421
330
|
end
|
422
|
-
alias data_sources tables
|
423
331
|
|
424
332
|
def truncate(table_name, name = nil)
|
425
333
|
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
426
334
|
end
|
427
335
|
|
428
|
-
def table_exists?(
|
429
|
-
|
430
|
-
|
336
|
+
def table_exists?(table_name)
|
337
|
+
# Update lib/active_record/internal_metadata.rb when this gets removed
|
338
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
339
|
+
#table_exists? currently checks both tables and views.
|
340
|
+
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
|
341
|
+
Use #data_source_exists? instead.
|
342
|
+
MSG
|
343
|
+
|
344
|
+
data_source_exists?(table_name)
|
345
|
+
end
|
346
|
+
|
347
|
+
def data_source_exists?(table_name)
|
348
|
+
return false unless table_name.present?
|
431
349
|
|
432
|
-
name
|
433
|
-
schema, table = name.split('.', 2)
|
350
|
+
schema, name = extract_schema_qualified_name(table_name)
|
434
351
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
352
|
+
sql = "SELECT table_name FROM information_schema.tables "
|
353
|
+
sql << "WHERE table_schema = #{schema} AND table_name = #{name}"
|
354
|
+
|
355
|
+
select_values(sql, 'SCHEMA').any?
|
356
|
+
end
|
439
357
|
|
440
|
-
|
358
|
+
def views # :nodoc:
|
359
|
+
select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
|
360
|
+
end
|
361
|
+
|
362
|
+
def view_exists?(view_name) # :nodoc:
|
363
|
+
return false unless view_name.present?
|
364
|
+
|
365
|
+
schema, name = extract_schema_qualified_name(view_name)
|
366
|
+
|
367
|
+
sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
|
368
|
+
sql << " AND table_schema = #{schema} AND table_name = #{name}"
|
369
|
+
|
370
|
+
select_values(sql, 'SCHEMA').any?
|
441
371
|
end
|
442
|
-
alias data_source_exists? table_exists?
|
443
372
|
|
444
373
|
# Returns an array of indexes for the given table.
|
445
374
|
def indexes(table_name, name = nil) #:nodoc:
|
@@ -454,11 +383,11 @@ module ActiveRecord
|
|
454
383
|
mysql_index_type = row[:Index_type].downcase.to_sym
|
455
384
|
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
|
456
385
|
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
|
457
|
-
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [],
|
386
|
+
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence)
|
458
387
|
end
|
459
388
|
|
460
389
|
indexes.last.columns << row[:Column_name]
|
461
|
-
indexes.last.lengths
|
390
|
+
indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
|
462
391
|
end
|
463
392
|
end
|
464
393
|
|
@@ -466,20 +395,32 @@ module ActiveRecord
|
|
466
395
|
end
|
467
396
|
|
468
397
|
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
469
|
-
def columns(table_name)
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
398
|
+
def columns(table_name) # :nodoc:
|
399
|
+
table_name = table_name.to_s
|
400
|
+
column_definitions(table_name).map do |field|
|
401
|
+
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
|
402
|
+
if type_metadata.type == :datetime && field[:Default] =~ /\ACURRENT_TIMESTAMP(?:\(\))?\z/i
|
403
|
+
default, default_function = nil, "CURRENT_TIMESTAMP"
|
404
|
+
else
|
405
|
+
default, default_function = field[:Default], nil
|
477
406
|
end
|
407
|
+
new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
|
478
408
|
end
|
479
409
|
end
|
480
410
|
|
481
|
-
def
|
482
|
-
|
411
|
+
def table_comment(table_name) # :nodoc:
|
412
|
+
schema, name = extract_schema_qualified_name(table_name)
|
413
|
+
|
414
|
+
select_value(<<-SQL.strip_heredoc, 'SCHEMA')
|
415
|
+
SELECT table_comment
|
416
|
+
FROM information_schema.tables
|
417
|
+
WHERE table_schema = #{schema}
|
418
|
+
AND table_name = #{name}
|
419
|
+
SQL
|
420
|
+
end
|
421
|
+
|
422
|
+
def create_table(table_name, **options) #:nodoc:
|
423
|
+
super(table_name, options: 'ENGINE=InnoDB', **options)
|
483
424
|
end
|
484
425
|
|
485
426
|
def bulk_change_table(table_name, operations) #:nodoc:
|
@@ -506,8 +447,23 @@ module ActiveRecord
|
|
506
447
|
rename_table_indexes(table_name, new_name)
|
507
448
|
end
|
508
449
|
|
450
|
+
# Drops a table from the database.
|
451
|
+
#
|
452
|
+
# [<tt>:force</tt>]
|
453
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
454
|
+
# Defaults to false.
|
455
|
+
# [<tt>:if_exists</tt>]
|
456
|
+
# Set to +true+ to only drop the table if it exists.
|
457
|
+
# Defaults to false.
|
458
|
+
# [<tt>:temporary</tt>]
|
459
|
+
# Set to +true+ to drop temporary table.
|
460
|
+
# Defaults to false.
|
461
|
+
#
|
462
|
+
# Although this command ignores most +options+ and the block if one is given,
|
463
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
464
|
+
# In that case, +options+ and the block will be used by create_table.
|
509
465
|
def drop_table(table_name, options = {})
|
510
|
-
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
466
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
511
467
|
end
|
512
468
|
|
513
469
|
def rename_index(table_name, old_name, new_name)
|
@@ -520,12 +476,13 @@ module ActiveRecord
|
|
520
476
|
end
|
521
477
|
end
|
522
478
|
|
523
|
-
def change_column_default(table_name, column_name,
|
479
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
480
|
+
default = extract_new_default_value(default_or_changes)
|
524
481
|
column = column_for(table_name, column_name)
|
525
482
|
change_column table_name, column_name, column.sql_type, :default => default
|
526
483
|
end
|
527
484
|
|
528
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
485
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
529
486
|
column = column_for(table_name, column_name)
|
530
487
|
|
531
488
|
unless null || default.nil?
|
@@ -545,24 +502,37 @@ module ActiveRecord
|
|
545
502
|
end
|
546
503
|
|
547
504
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
548
|
-
index_name, index_type, index_columns,
|
549
|
-
|
505
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
506
|
+
sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
|
507
|
+
execute add_sql_comment!(sql, comment)
|
508
|
+
end
|
509
|
+
|
510
|
+
def add_sql_comment!(sql, comment) # :nodoc:
|
511
|
+
sql << " COMMENT #{quote(comment)}" if comment.present?
|
512
|
+
sql
|
550
513
|
end
|
551
514
|
|
552
515
|
def foreign_keys(table_name)
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
516
|
+
raise ArgumentError unless table_name.present?
|
517
|
+
|
518
|
+
schema, name = extract_schema_qualified_name(table_name)
|
519
|
+
|
520
|
+
fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA')
|
521
|
+
SELECT fk.referenced_table_name AS 'to_table',
|
522
|
+
fk.referenced_column_name AS 'primary_key',
|
523
|
+
fk.column_name AS 'column',
|
524
|
+
fk.constraint_name AS 'name',
|
525
|
+
rc.update_rule AS 'on_update',
|
526
|
+
rc.delete_rule AS 'on_delete'
|
558
527
|
FROM information_schema.key_column_usage fk
|
559
|
-
|
560
|
-
|
561
|
-
|
528
|
+
JOIN information_schema.referential_constraints rc
|
529
|
+
USING (constraint_schema, constraint_name)
|
530
|
+
WHERE fk.referenced_column_name IS NOT NULL
|
531
|
+
AND fk.table_schema = #{schema}
|
532
|
+
AND fk.table_name = #{name}
|
533
|
+
AND rc.table_name = #{name}
|
562
534
|
SQL
|
563
535
|
|
564
|
-
create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
565
|
-
|
566
536
|
fk_info.map do |row|
|
567
537
|
options = {
|
568
538
|
column: row['column'],
|
@@ -570,99 +540,91 @@ module ActiveRecord
|
|
570
540
|
primary_key: row['primary_key']
|
571
541
|
}
|
572
542
|
|
573
|
-
options[:on_update] = extract_foreign_key_action(
|
574
|
-
options[:on_delete] = extract_foreign_key_action(
|
543
|
+
options[:on_update] = extract_foreign_key_action(row['on_update'])
|
544
|
+
options[:on_delete] = extract_foreign_key_action(row['on_delete'])
|
575
545
|
|
576
546
|
ForeignKeyDefinition.new(table_name, row['to_table'], options)
|
577
547
|
end
|
578
548
|
end
|
579
549
|
|
550
|
+
def table_options(table_name) # :nodoc:
|
551
|
+
table_options = {}
|
552
|
+
|
553
|
+
create_table_info = create_table_info(table_name)
|
554
|
+
|
555
|
+
# strip create_definitions and partition_options
|
556
|
+
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
|
557
|
+
|
558
|
+
# strip AUTO_INCREMENT
|
559
|
+
raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
|
560
|
+
|
561
|
+
table_options[:options] = raw_table_options
|
562
|
+
|
563
|
+
# strip COMMENT
|
564
|
+
if raw_table_options.sub!(/ COMMENT='.+'/, '')
|
565
|
+
table_options[:comment] = table_comment(table_name)
|
566
|
+
end
|
567
|
+
|
568
|
+
table_options
|
569
|
+
end
|
570
|
+
|
580
571
|
# Maps logical Rails types to MySQL-specific data types.
|
581
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
582
|
-
case type.to_s
|
583
|
-
when 'binary'
|
584
|
-
case limit
|
585
|
-
when 0..0xfff; "varbinary(#{limit})"
|
586
|
-
when nil; "blob"
|
587
|
-
when 0x1000..0xffffffff; "blob(#{limit})"
|
588
|
-
else raise(ActiveRecordError, "No binary type has character length #{limit}")
|
589
|
-
end
|
572
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
|
573
|
+
sql = case type.to_s
|
590
574
|
when 'integer'
|
591
|
-
|
592
|
-
when 1; 'tinyint'
|
593
|
-
when 2; 'smallint'
|
594
|
-
when 3; 'mediumint'
|
595
|
-
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
596
|
-
when 5..8; 'bigint'
|
597
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
598
|
-
end
|
575
|
+
integer_to_sql(limit)
|
599
576
|
when 'text'
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
return super unless precision
|
609
|
-
|
610
|
-
case precision
|
611
|
-
when 0..6; "datetime(#{precision})"
|
612
|
-
else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
|
577
|
+
text_to_sql(limit)
|
578
|
+
when 'blob'
|
579
|
+
binary_to_sql(limit)
|
580
|
+
when 'binary'
|
581
|
+
if (0..0xfff) === limit
|
582
|
+
"varbinary(#{limit})"
|
583
|
+
else
|
584
|
+
binary_to_sql(limit)
|
613
585
|
end
|
614
586
|
else
|
615
|
-
super
|
587
|
+
super(type, limit, precision, scale)
|
616
588
|
end
|
589
|
+
|
590
|
+
sql << ' unsigned' if unsigned && type != :primary_key
|
591
|
+
sql
|
617
592
|
end
|
618
593
|
|
619
594
|
# SHOW VARIABLES LIKE 'name'
|
620
595
|
def show_variable(name)
|
621
|
-
|
622
|
-
variables.first['Value'] unless variables.empty?
|
596
|
+
select_value("SELECT @@#{name}", 'SCHEMA')
|
623
597
|
rescue ActiveRecord::StatementInvalid
|
624
598
|
nil
|
625
599
|
end
|
626
600
|
|
627
|
-
|
628
|
-
|
629
|
-
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
|
630
|
-
create_table = each_hash(result).first[:"Create Table"]
|
631
|
-
if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
|
632
|
-
keys = $1.split(",").map { |key| key.delete('`"') }
|
633
|
-
keys.length == 1 ? [keys.first, nil] : nil
|
634
|
-
else
|
635
|
-
nil
|
636
|
-
end
|
637
|
-
end
|
638
|
-
end
|
601
|
+
def primary_keys(table_name) # :nodoc:
|
602
|
+
raise ArgumentError unless table_name.present?
|
639
603
|
|
640
|
-
|
641
|
-
def primary_key(table)
|
642
|
-
pk_and_sequence = pk_and_sequence_for(table)
|
643
|
-
pk_and_sequence && pk_and_sequence.first
|
644
|
-
end
|
604
|
+
schema, name = extract_schema_qualified_name(table_name)
|
645
605
|
|
646
|
-
|
647
|
-
|
648
|
-
|
606
|
+
select_values(<<-SQL.strip_heredoc, 'SCHEMA')
|
607
|
+
SELECT column_name
|
608
|
+
FROM information_schema.key_column_usage
|
609
|
+
WHERE constraint_name = 'PRIMARY'
|
610
|
+
AND table_schema = #{schema}
|
611
|
+
AND table_name = #{name}
|
612
|
+
ORDER BY ordinal_position
|
613
|
+
SQL
|
649
614
|
end
|
650
615
|
|
651
616
|
def case_sensitive_comparison(table, attribute, column, value)
|
652
|
-
if column.case_sensitive?
|
653
|
-
table[attribute].eq(
|
617
|
+
if !value.nil? && column.collation && !column.case_sensitive?
|
618
|
+
table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
|
654
619
|
else
|
655
620
|
super
|
656
621
|
end
|
657
622
|
end
|
658
623
|
|
659
|
-
def
|
660
|
-
|
661
|
-
super
|
662
|
-
else
|
663
|
-
table[attribute].eq(value)
|
664
|
-
end
|
624
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
625
|
+
column.case_sensitive?
|
665
626
|
end
|
627
|
+
private :can_perform_case_insensitive_comparison_for?
|
666
628
|
|
667
629
|
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
668
630
|
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
@@ -704,6 +666,7 @@ module ActiveRecord
|
|
704
666
|
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
705
667
|
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
706
668
|
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
669
|
+
m.register_type %r(^json)i, MysqlJson.new
|
707
670
|
|
708
671
|
register_integer_type m, %r(^bigint)i, limit: 8
|
709
672
|
register_integer_type m, %r(^int)i, limit: 4
|
@@ -711,26 +674,26 @@ module ActiveRecord
|
|
711
674
|
register_integer_type m, %r(^smallint)i, limit: 2
|
712
675
|
register_integer_type m, %r(^tinyint)i, limit: 1
|
713
676
|
|
714
|
-
m.
|
715
|
-
m.alias_type %r(set)i, 'varchar'
|
677
|
+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
|
716
678
|
m.alias_type %r(year)i, 'integer'
|
717
679
|
m.alias_type %r(bit)i, 'binary'
|
718
680
|
|
719
|
-
m.register_type(%r(datetime)i) do |sql_type|
|
720
|
-
precision = extract_precision(sql_type)
|
721
|
-
MysqlDateTime.new(precision: precision)
|
722
|
-
end
|
723
|
-
|
724
681
|
m.register_type(%r(enum)i) do |sql_type|
|
725
682
|
limit = sql_type[/^enum\((.+)\)/i, 1]
|
726
683
|
.split(',').map{|enum| enum.strip.length - 2}.max
|
727
684
|
MysqlString.new(limit: limit)
|
728
685
|
end
|
686
|
+
|
687
|
+
m.register_type(%r(^set)i) do |sql_type|
|
688
|
+
limit = sql_type[/^set\((.+)\)/i, 1]
|
689
|
+
.split(',').map{|set| set.strip.length - 1}.sum - 1
|
690
|
+
MysqlString.new(limit: limit)
|
691
|
+
end
|
729
692
|
end
|
730
693
|
|
731
694
|
def register_integer_type(mapping, key, options) # :nodoc:
|
732
695
|
mapping.register_type(key) do |sql_type|
|
733
|
-
if /
|
696
|
+
if /\bunsigned\b/ === sql_type
|
734
697
|
Type::UnsignedInteger.new(options)
|
735
698
|
else
|
736
699
|
Type::Integer.new(options)
|
@@ -738,61 +701,54 @@ module ActiveRecord
|
|
738
701
|
end
|
739
702
|
end
|
740
703
|
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
749
|
-
subsubselect.distinct unless select.limit || select.offset || select.orders.any?
|
704
|
+
def extract_precision(sql_type)
|
705
|
+
if /time/ === sql_type
|
706
|
+
super || 0
|
707
|
+
else
|
708
|
+
super
|
709
|
+
end
|
710
|
+
end
|
750
711
|
|
751
|
-
|
752
|
-
|
753
|
-
subselect.from subsubselect.as('__active_record_temp')
|
712
|
+
def fetch_type_metadata(sql_type, extra = "")
|
713
|
+
MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
|
754
714
|
end
|
755
715
|
|
756
|
-
def add_index_length(
|
757
|
-
if
|
716
|
+
def add_index_length(quoted_columns, **options)
|
717
|
+
if length = options[:length]
|
758
718
|
case length
|
759
719
|
when Hash
|
760
|
-
|
720
|
+
length = length.symbolize_keys
|
721
|
+
quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? }
|
761
722
|
when Integer
|
762
|
-
|
723
|
+
quoted_columns.each { |name, column| column << "(#{length})" }
|
763
724
|
end
|
764
725
|
end
|
765
726
|
|
766
|
-
|
727
|
+
quoted_columns
|
767
728
|
end
|
768
729
|
|
769
|
-
def
|
770
|
-
|
771
|
-
|
772
|
-
# add index length
|
773
|
-
option_strings = add_index_length(option_strings, column_names, options)
|
774
|
-
|
775
|
-
# add index sort order
|
776
|
-
option_strings = add_index_sort_order(option_strings, column_names, options)
|
777
|
-
|
778
|
-
column_names.map {|name| quote_column_name(name) + option_strings[name]}
|
730
|
+
def add_options_for_index_columns(quoted_columns, **options)
|
731
|
+
quoted_columns = add_index_length(quoted_columns, options)
|
732
|
+
super
|
779
733
|
end
|
780
734
|
|
781
735
|
def translate_exception(exception, message)
|
782
736
|
case error_number(exception)
|
783
737
|
when 1062
|
784
|
-
RecordNotUnique.new(message
|
738
|
+
RecordNotUnique.new(message)
|
785
739
|
when 1452
|
786
|
-
InvalidForeignKey.new(message
|
740
|
+
InvalidForeignKey.new(message)
|
741
|
+
when 1406
|
742
|
+
ValueTooLong.new(message)
|
787
743
|
else
|
788
744
|
super
|
789
745
|
end
|
790
746
|
end
|
791
747
|
|
792
748
|
def add_column_sql(table_name, column_name, type, options = {})
|
793
|
-
td = create_table_definition
|
749
|
+
td = create_table_definition(table_name)
|
794
750
|
cd = td.new_column_definition(column_name, type, options)
|
795
|
-
schema_creation.
|
751
|
+
schema_creation.accept(AddColumnDefinition.new(cd))
|
796
752
|
end
|
797
753
|
|
798
754
|
def change_column_sql(table_name, column_name, type, options = {})
|
@@ -806,21 +762,23 @@ module ActiveRecord
|
|
806
762
|
options[:null] = column.null
|
807
763
|
end
|
808
764
|
|
809
|
-
|
810
|
-
|
765
|
+
td = create_table_definition(table_name)
|
766
|
+
cd = td.new_column_definition(column.name, type, options)
|
767
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
811
768
|
end
|
812
769
|
|
813
770
|
def rename_column_sql(table_name, column_name, new_column_name)
|
814
771
|
column = column_for(table_name, column_name)
|
815
772
|
options = {
|
816
|
-
name: new_column_name,
|
817
773
|
default: column.default,
|
818
774
|
null: column.null,
|
819
|
-
auto_increment: column.
|
775
|
+
auto_increment: column.auto_increment?
|
820
776
|
}
|
821
777
|
|
822
778
|
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
823
|
-
|
779
|
+
td = create_table_definition(table_name)
|
780
|
+
cd = td.new_column_definition(new_column_name, current_type, options)
|
781
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
824
782
|
end
|
825
783
|
|
826
784
|
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
@@ -832,8 +790,9 @@ module ActiveRecord
|
|
832
790
|
end
|
833
791
|
|
834
792
|
def add_index_sql(table_name, column_name, options = {})
|
835
|
-
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
836
|
-
|
793
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
794
|
+
index_algorithm[0, 0] = ", " if index_algorithm.present?
|
795
|
+
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
|
837
796
|
end
|
838
797
|
|
839
798
|
def remove_index_sql(table_name, options = {})
|
@@ -851,12 +810,19 @@ module ActiveRecord
|
|
851
810
|
|
852
811
|
private
|
853
812
|
|
854
|
-
|
855
|
-
|
856
|
-
|
813
|
+
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
814
|
+
# to give it some prompting in the form of a subsubquery. Ugh!
|
815
|
+
def subquery_for(key, select)
|
816
|
+
subsubselect = select.clone
|
817
|
+
subsubselect.projections = [key]
|
857
818
|
|
858
|
-
|
859
|
-
|
819
|
+
# Materialize subquery by adding distinct
|
820
|
+
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
821
|
+
subsubselect.distinct unless select.limit || select.offset || select.orders.any?
|
822
|
+
|
823
|
+
subselect = Arel::SelectManager.new(select.engine)
|
824
|
+
subselect.project Arel.sql(key.name)
|
825
|
+
subselect.from subsubselect.as('__active_record_temp')
|
860
826
|
end
|
861
827
|
|
862
828
|
def supports_rename_index?
|
@@ -866,8 +832,7 @@ module ActiveRecord
|
|
866
832
|
def configure_connection
|
867
833
|
variables = @config.fetch(:variables, {}).stringify_keys
|
868
834
|
|
869
|
-
# By default, MySQL 'where id is null' selects the last inserted id.
|
870
|
-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
835
|
+
# By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
|
871
836
|
variables['sql_auto_is_null'] = 0
|
872
837
|
|
873
838
|
# Increase timeout so the server doesn't disconnect us.
|
@@ -875,15 +840,27 @@ module ActiveRecord
|
|
875
840
|
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
876
841
|
variables["wait_timeout"] = wait_timeout
|
877
842
|
|
843
|
+
defaults = [':default', :default].to_set
|
844
|
+
|
878
845
|
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
879
|
-
# http://dev.mysql.com/doc/refman/5.
|
846
|
+
# http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
|
880
847
|
# If the user has provided another value for sql_mode, don't replace it.
|
881
|
-
|
882
|
-
|
848
|
+
if sql_mode = variables.delete('sql_mode')
|
849
|
+
sql_mode = quote(sql_mode)
|
850
|
+
elsif !defaults.include?(strict_mode?)
|
851
|
+
if strict_mode?
|
852
|
+
sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
|
853
|
+
else
|
854
|
+
sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
|
855
|
+
sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
|
856
|
+
sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
|
857
|
+
end
|
858
|
+
sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
|
883
859
|
end
|
860
|
+
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
|
884
861
|
|
885
862
|
# NAMES does not have an equals sign, see
|
886
|
-
# http://dev.mysql.com/doc/refman/5.
|
863
|
+
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
|
887
864
|
# (trailing comma because variable_assignments will always have content)
|
888
865
|
if @config[:encoding]
|
889
866
|
encoding = "NAMES #{@config[:encoding]}"
|
@@ -893,7 +870,7 @@ module ActiveRecord
|
|
893
870
|
|
894
871
|
# Gather up all of the SET variables...
|
895
872
|
variable_assignments = variables.map do |k, v|
|
896
|
-
if v
|
873
|
+
if defaults.include?(v)
|
897
874
|
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
898
875
|
elsif !v.nil?
|
899
876
|
"@@SESSION.#{k} = #{quote(v)}"
|
@@ -902,31 +879,84 @@ module ActiveRecord
|
|
902
879
|
end.compact.join(', ')
|
903
880
|
|
904
881
|
# ...and send them all in one query
|
905
|
-
@connection.query "SET #{encoding} #{variable_assignments}"
|
882
|
+
@connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
|
906
883
|
end
|
907
884
|
|
908
|
-
def
|
909
|
-
|
910
|
-
|
911
|
-
when 'CASCADE'; :cascade
|
912
|
-
when 'SET NULL'; :nullify
|
913
|
-
end
|
885
|
+
def column_definitions(table_name) # :nodoc:
|
886
|
+
execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
|
887
|
+
each_hash(result)
|
914
888
|
end
|
915
889
|
end
|
916
890
|
|
917
|
-
|
918
|
-
|
891
|
+
def extract_foreign_key_action(specifier) # :nodoc:
|
892
|
+
case specifier
|
893
|
+
when 'CASCADE'; :cascade
|
894
|
+
when 'SET NULL'; :nullify
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
def create_table_info(table_name) # :nodoc:
|
899
|
+
select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
900
|
+
end
|
901
|
+
|
902
|
+
def create_table_definition(*args) # :nodoc:
|
903
|
+
MySQL::TableDefinition.new(*args)
|
904
|
+
end
|
919
905
|
|
920
|
-
|
921
|
-
|
906
|
+
def extract_schema_qualified_name(string) # :nodoc:
|
907
|
+
schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/).map { |s| quote(s) }
|
908
|
+
schema, name = "DATABASE()", schema unless name
|
909
|
+
[schema, name]
|
910
|
+
end
|
911
|
+
|
912
|
+
def integer_to_sql(limit) # :nodoc:
|
913
|
+
case limit
|
914
|
+
when 1; 'tinyint'
|
915
|
+
when 2; 'smallint'
|
916
|
+
when 3; 'mediumint'
|
917
|
+
when nil, 4; 'int'
|
918
|
+
when 5..8; 'bigint'
|
919
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
920
|
+
end
|
921
|
+
end
|
922
|
+
|
923
|
+
def text_to_sql(limit) # :nodoc:
|
924
|
+
case limit
|
925
|
+
when 0..0xff; 'tinytext'
|
926
|
+
when nil, 0x100..0xffff; 'text'
|
927
|
+
when 0x10000..0xffffff; 'mediumtext'
|
928
|
+
when 0x1000000..0xffffffff; 'longtext'
|
929
|
+
else raise(ActiveRecordError, "No text type has byte length #{limit}")
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
def binary_to_sql(limit) # :nodoc:
|
934
|
+
case limit
|
935
|
+
when 0..0xff; 'tinyblob'
|
936
|
+
when nil, 0x100..0xffff; 'blob'
|
937
|
+
when 0x10000..0xffffff; 'mediumblob'
|
938
|
+
when 0x1000000..0xffffffff; 'longblob'
|
939
|
+
else raise(ActiveRecordError, "No binary type has byte length #{limit}")
|
940
|
+
end
|
941
|
+
end
|
942
|
+
|
943
|
+
def version_string
|
944
|
+
full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
|
945
|
+
end
|
946
|
+
|
947
|
+
class MysqlJson < Type::Internal::AbstractJson # :nodoc:
|
948
|
+
def changed_in_place?(raw_old_value, new_value)
|
949
|
+
# Normalization is required because MySQL JSON data format includes
|
950
|
+
# the space between the elements.
|
951
|
+
super(serialize(deserialize(raw_old_value)), new_value)
|
922
952
|
end
|
923
953
|
end
|
924
954
|
|
925
955
|
class MysqlString < Type::String # :nodoc:
|
926
|
-
def
|
956
|
+
def serialize(value)
|
927
957
|
case value
|
928
|
-
when true then
|
929
|
-
when false then
|
958
|
+
when true then MySQL::Quoting::QUOTED_TRUE
|
959
|
+
when false then MySQL::Quoting::QUOTED_FALSE
|
930
960
|
else super
|
931
961
|
end
|
932
962
|
end
|
@@ -935,12 +965,16 @@ module ActiveRecord
|
|
935
965
|
|
936
966
|
def cast_value(value)
|
937
967
|
case value
|
938
|
-
when true then
|
939
|
-
when false then
|
968
|
+
when true then MySQL::Quoting::QUOTED_TRUE
|
969
|
+
when false then MySQL::Quoting::QUOTED_FALSE
|
940
970
|
else super
|
941
971
|
end
|
942
972
|
end
|
943
973
|
end
|
974
|
+
|
975
|
+
ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
|
976
|
+
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
|
977
|
+
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
|
944
978
|
end
|
945
979
|
end
|
946
980
|
end
|