activerecord 4.2.11.3 → 5.0.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1281 -1204
- 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/aggregations.rb +35 -24
- data/lib/active_record/association_relation.rb +3 -3
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +11 -9
- data/lib/active_record/associations/association_scope.rb +73 -102
- 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 +14 -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 +3 -10
- data/lib/active_record/associations/collection_association.rb +49 -41
- data/lib/active_record/associations/collection_proxy.rb +67 -27
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +20 -71
- data/lib/active_record/associations/has_many_through_association.rb +8 -47
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
- data/lib/active_record/associations/join_dependency.rb +29 -19
- data/lib/active_record/associations/preloader/association.rb +46 -52
- data/lib/active_record/associations/preloader/collection_association.rb +0 -6
- 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/through_association.rb +27 -14
- data/lib/active_record/associations/preloader.rb +14 -4
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +11 -3
- data/lib/active_record/associations.rb +317 -209
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +68 -18
- data/lib/active_record/attribute_assignment.rb +19 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- 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 +2 -2
- 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_methods.rb +76 -47
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +6 -4
- data/lib/active_record/attribute_set.rb +30 -3
- data/lib/active_record/attributes.rb +199 -81
- data/lib/active_record/autosave_association.rb +49 -16
- 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 +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
- 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 -185
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
- 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 +125 -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 +29 -166
- data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- 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 +3 -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/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
- 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 +234 -148
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
- 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 +149 -192
- 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 +89 -107
- data/lib/active_record/counter_cache.rb +13 -24
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +113 -76
- data/lib/active_record/errors.rb +87 -48
- 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 +76 -40
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +4 -4
- 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 +43 -21
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration.rb +363 -133
- data/lib/active_record/model_schema.rb +129 -41
- data/lib/active_record/nested_attributes.rb +58 -29
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +121 -80
- data/lib/active_record/query_cache.rb +15 -18
- data/lib/active_record/querying.rb +10 -9
- 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 +69 -46
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +282 -115
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +139 -34
- data/lib/active_record/relation/calculations.rb +79 -108
- data/lib/active_record/relation/delegation.rb +7 -20
- data/lib/active_record/relation/finder_methods.rb +163 -81
- 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/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/predicate_builder.rb +120 -107
- 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/relation.rb +176 -116
- data/lib/active_record/result.rb +4 -3
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +95 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +62 -38
- data/lib/active_record/schema_migration.rb +11 -14
- data/lib/active_record/scoping/default.rb +23 -9
- data/lib/active_record/scoping/named.rb +49 -28
- data/lib/active_record/scoping.rb +32 -15
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +2 -4
- data/lib/active_record/statement_cache.rb +16 -14
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +57 -43
- data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
- 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 +58 -0
- data/lib/active_record/transactions.rb +138 -56
- 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 +29 -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.rb +66 -17
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- 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 +30 -29
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record.rb +8 -4
- data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
- 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/migration.rb +7 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
- 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 +60 -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)} "
|
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
|
16
|
+
include MySQL::Quoting
|
17
|
+
include MySQL::ColumnDumper
|
50
18
|
|
51
|
-
|
52
|
-
|
53
|
-
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
|
54
|
-
end
|
19
|
+
def update_table_definition(table_name, base) # :nodoc:
|
20
|
+
MySQL::Table.new(table_name, base)
|
55
21
|
end
|
56
22
|
|
57
|
-
def schema_creation
|
58
|
-
SchemaCreation.new
|
23
|
+
def schema_creation # :nodoc:
|
24
|
+
MySQL::SchemaCreation.new(self)
|
59
25
|
end
|
60
26
|
|
61
|
-
def
|
62
|
-
|
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
|
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 (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) 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(full_version.match(/^\d+\.\d+\.\d+/)[0])
|
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 = #{quote(@config[: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
|
431
343
|
|
432
|
-
|
433
|
-
|
344
|
+
data_source_exists?(table_name)
|
345
|
+
end
|
434
346
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
347
|
+
def data_source_exists?(table_name)
|
348
|
+
return false unless table_name.present?
|
349
|
+
|
350
|
+
schema, name = extract_schema_qualified_name(table_name)
|
351
|
+
|
352
|
+
sql = "SELECT table_name FROM information_schema.tables "
|
353
|
+
sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
|
354
|
+
|
355
|
+
select_values(sql, 'SCHEMA').any?
|
356
|
+
end
|
357
|
+
|
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)
|
439
366
|
|
440
|
-
tables
|
367
|
+
sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
|
368
|
+
sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(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,7 +383,7 @@ 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, [], [], nil, nil, index_type, index_using)
|
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]
|
@@ -466,20 +395,29 @@ 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] == "CURRENT_TIMESTAMP"
|
403
|
+
default, default_function = nil, field[:Default]
|
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
|
+
select_value(<<-SQL.strip_heredoc, 'SCHEMA')
|
413
|
+
SELECT table_comment
|
414
|
+
FROM information_schema.tables
|
415
|
+
WHERE table_name=#{quote(table_name)}
|
416
|
+
SQL
|
417
|
+
end
|
418
|
+
|
419
|
+
def create_table(table_name, **options) #:nodoc:
|
420
|
+
super(table_name, options: 'ENGINE=InnoDB', **options)
|
483
421
|
end
|
484
422
|
|
485
423
|
def bulk_change_table(table_name, operations) #:nodoc:
|
@@ -506,8 +444,24 @@ module ActiveRecord
|
|
506
444
|
rename_table_indexes(table_name, new_name)
|
507
445
|
end
|
508
446
|
|
447
|
+
# Drops a table from the database.
|
448
|
+
#
|
449
|
+
# [<tt>:force</tt>]
|
450
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
451
|
+
# Defaults to false.
|
452
|
+
# [<tt>:if_exists</tt>]
|
453
|
+
# Set to +true+ to only drop the table if it exists.
|
454
|
+
# Defaults to false.
|
455
|
+
# [<tt>:temporary</tt>]
|
456
|
+
# Set to +true+ to drop temporary table.
|
457
|
+
# Defaults to false.
|
458
|
+
#
|
459
|
+
# Although this command ignores most +options+ and the block if one is given,
|
460
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
461
|
+
# In that case, +options+ and the block will be used by create_table.
|
509
462
|
def drop_table(table_name, options = {})
|
510
|
-
|
463
|
+
create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
|
464
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
511
465
|
end
|
512
466
|
|
513
467
|
def rename_index(table_name, old_name, new_name)
|
@@ -520,12 +474,13 @@ module ActiveRecord
|
|
520
474
|
end
|
521
475
|
end
|
522
476
|
|
523
|
-
def change_column_default(table_name, column_name,
|
477
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
478
|
+
default = extract_new_default_value(default_or_changes)
|
524
479
|
column = column_for(table_name, column_name)
|
525
480
|
change_column table_name, column_name, column.sql_type, :default => default
|
526
481
|
end
|
527
482
|
|
528
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
483
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
529
484
|
column = column_for(table_name, column_name)
|
530
485
|
|
531
486
|
unless null || default.nil?
|
@@ -545,11 +500,21 @@ module ActiveRecord
|
|
545
500
|
end
|
546
501
|
|
547
502
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
548
|
-
index_name, index_type, index_columns,
|
549
|
-
|
503
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
504
|
+
sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
|
505
|
+
execute add_sql_comment!(sql, comment)
|
506
|
+
end
|
507
|
+
|
508
|
+
def add_sql_comment!(sql, comment) # :nodoc:
|
509
|
+
sql << " COMMENT #{quote(comment)}" if comment
|
510
|
+
sql
|
550
511
|
end
|
551
512
|
|
552
513
|
def foreign_keys(table_name)
|
514
|
+
raise ArgumentError unless table_name.present?
|
515
|
+
|
516
|
+
schema, name = extract_schema_qualified_name(table_name)
|
517
|
+
|
553
518
|
fk_info = select_all <<-SQL.strip_heredoc
|
554
519
|
SELECT fk.referenced_table_name as 'to_table'
|
555
520
|
,fk.referenced_column_name as 'primary_key'
|
@@ -557,11 +522,11 @@ module ActiveRecord
|
|
557
522
|
,fk.constraint_name as 'name'
|
558
523
|
FROM information_schema.key_column_usage fk
|
559
524
|
WHERE fk.referenced_column_name is not null
|
560
|
-
AND fk.table_schema =
|
561
|
-
AND fk.table_name =
|
525
|
+
AND fk.table_schema = #{quote(schema)}
|
526
|
+
AND fk.table_name = #{quote(name)}
|
562
527
|
SQL
|
563
528
|
|
564
|
-
create_table_info =
|
529
|
+
create_table_info = create_table_info(table_name)
|
565
530
|
|
566
531
|
fk_info.map do |row|
|
567
532
|
options = {
|
@@ -577,92 +542,78 @@ module ActiveRecord
|
|
577
542
|
end
|
578
543
|
end
|
579
544
|
|
545
|
+
def table_options(table_name)
|
546
|
+
create_table_info = create_table_info(table_name)
|
547
|
+
|
548
|
+
# strip create_definitions and partition_options
|
549
|
+
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
|
550
|
+
|
551
|
+
# strip AUTO_INCREMENT
|
552
|
+
raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
|
553
|
+
|
554
|
+
# strip COMMENT
|
555
|
+
raw_table_options.sub!(/ COMMENT='.+'/, '')
|
556
|
+
|
557
|
+
raw_table_options
|
558
|
+
end
|
559
|
+
|
580
560
|
# 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
|
561
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
|
562
|
+
sql = case type.to_s
|
590
563
|
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
|
564
|
+
integer_to_sql(limit)
|
599
565
|
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.")
|
566
|
+
text_to_sql(limit)
|
567
|
+
when 'blob'
|
568
|
+
binary_to_sql(limit)
|
569
|
+
when 'binary'
|
570
|
+
if (0..0xfff) === limit
|
571
|
+
"varbinary(#{limit})"
|
572
|
+
else
|
573
|
+
binary_to_sql(limit)
|
613
574
|
end
|
614
575
|
else
|
615
|
-
super
|
576
|
+
super(type, limit, precision, scale)
|
616
577
|
end
|
578
|
+
|
579
|
+
sql << ' unsigned' if unsigned && type != :primary_key
|
580
|
+
sql
|
617
581
|
end
|
618
582
|
|
619
583
|
# SHOW VARIABLES LIKE 'name'
|
620
584
|
def show_variable(name)
|
621
|
-
|
622
|
-
variables.first['Value'] unless variables.empty?
|
585
|
+
select_value("SELECT @@#{name}", 'SCHEMA')
|
623
586
|
rescue ActiveRecord::StatementInvalid
|
624
587
|
nil
|
625
588
|
end
|
626
589
|
|
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
|
590
|
+
def primary_keys(table_name) # :nodoc:
|
591
|
+
raise ArgumentError unless table_name.present?
|
639
592
|
|
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
|
593
|
+
schema, name = extract_schema_qualified_name(table_name)
|
645
594
|
|
646
|
-
|
647
|
-
|
648
|
-
|
595
|
+
select_values(<<-SQL.strip_heredoc, 'SCHEMA')
|
596
|
+
SELECT column_name
|
597
|
+
FROM information_schema.key_column_usage
|
598
|
+
WHERE constraint_name = 'PRIMARY'
|
599
|
+
AND table_schema = #{quote(schema)}
|
600
|
+
AND table_name = #{quote(name)}
|
601
|
+
ORDER BY ordinal_position
|
602
|
+
SQL
|
649
603
|
end
|
650
604
|
|
651
605
|
def case_sensitive_comparison(table, attribute, column, value)
|
652
|
-
if column.case_sensitive?
|
653
|
-
table[attribute].eq(
|
606
|
+
if !value.nil? && column.collation && !column.case_sensitive?
|
607
|
+
table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
|
654
608
|
else
|
655
609
|
super
|
656
610
|
end
|
657
611
|
end
|
658
612
|
|
659
|
-
def
|
660
|
-
|
661
|
-
super
|
662
|
-
else
|
663
|
-
table[attribute].eq(value)
|
664
|
-
end
|
613
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
614
|
+
column.case_sensitive?
|
665
615
|
end
|
616
|
+
private :can_perform_case_insensitive_comparison_for?
|
666
617
|
|
667
618
|
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
668
619
|
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
@@ -704,6 +655,7 @@ module ActiveRecord
|
|
704
655
|
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
705
656
|
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
706
657
|
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
658
|
+
m.register_type %r(^json)i, MysqlJson.new
|
707
659
|
|
708
660
|
register_integer_type m, %r(^bigint)i, limit: 8
|
709
661
|
register_integer_type m, %r(^int)i, limit: 4
|
@@ -711,26 +663,26 @@ module ActiveRecord
|
|
711
663
|
register_integer_type m, %r(^smallint)i, limit: 2
|
712
664
|
register_integer_type m, %r(^tinyint)i, limit: 1
|
713
665
|
|
714
|
-
m.
|
715
|
-
m.alias_type %r(set)i, 'varchar'
|
666
|
+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
|
716
667
|
m.alias_type %r(year)i, 'integer'
|
717
668
|
m.alias_type %r(bit)i, 'binary'
|
718
669
|
|
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
670
|
m.register_type(%r(enum)i) do |sql_type|
|
725
671
|
limit = sql_type[/^enum\((.+)\)/i, 1]
|
726
672
|
.split(',').map{|enum| enum.strip.length - 2}.max
|
727
673
|
MysqlString.new(limit: limit)
|
728
674
|
end
|
675
|
+
|
676
|
+
m.register_type(%r(^set)i) do |sql_type|
|
677
|
+
limit = sql_type[/^set\((.+)\)/i, 1]
|
678
|
+
.split(',').map{|set| set.strip.length - 1}.sum - 1
|
679
|
+
MysqlString.new(limit: limit)
|
680
|
+
end
|
729
681
|
end
|
730
682
|
|
731
683
|
def register_integer_type(mapping, key, options) # :nodoc:
|
732
684
|
mapping.register_type(key) do |sql_type|
|
733
|
-
if /
|
685
|
+
if /\bunsigned\z/ === sql_type
|
734
686
|
Type::UnsignedInteger.new(options)
|
735
687
|
else
|
736
688
|
Type::Integer.new(options)
|
@@ -738,19 +690,16 @@ module ActiveRecord
|
|
738
690
|
end
|
739
691
|
end
|
740
692
|
|
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?
|
693
|
+
def extract_precision(sql_type)
|
694
|
+
if /time/ === sql_type
|
695
|
+
super || 0
|
696
|
+
else
|
697
|
+
super
|
698
|
+
end
|
699
|
+
end
|
750
700
|
|
751
|
-
|
752
|
-
|
753
|
-
subselect.from subsubselect.as('__active_record_temp')
|
701
|
+
def fetch_type_metadata(sql_type, extra = "")
|
702
|
+
MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
|
754
703
|
end
|
755
704
|
|
756
705
|
def add_index_length(option_strings, column_names, options = {})
|
@@ -781,18 +730,20 @@ module ActiveRecord
|
|
781
730
|
def translate_exception(exception, message)
|
782
731
|
case error_number(exception)
|
783
732
|
when 1062
|
784
|
-
RecordNotUnique.new(message
|
733
|
+
RecordNotUnique.new(message)
|
785
734
|
when 1452
|
786
|
-
InvalidForeignKey.new(message
|
735
|
+
InvalidForeignKey.new(message)
|
736
|
+
when 1406
|
737
|
+
ValueTooLong.new(message)
|
787
738
|
else
|
788
739
|
super
|
789
740
|
end
|
790
741
|
end
|
791
742
|
|
792
743
|
def add_column_sql(table_name, column_name, type, options = {})
|
793
|
-
td = create_table_definition
|
744
|
+
td = create_table_definition(table_name)
|
794
745
|
cd = td.new_column_definition(column_name, type, options)
|
795
|
-
schema_creation.
|
746
|
+
schema_creation.accept(AddColumnDefinition.new(cd))
|
796
747
|
end
|
797
748
|
|
798
749
|
def change_column_sql(table_name, column_name, type, options = {})
|
@@ -806,21 +757,23 @@ module ActiveRecord
|
|
806
757
|
options[:null] = column.null
|
807
758
|
end
|
808
759
|
|
809
|
-
|
810
|
-
|
760
|
+
td = create_table_definition(table_name)
|
761
|
+
cd = td.new_column_definition(column.name, type, options)
|
762
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
811
763
|
end
|
812
764
|
|
813
765
|
def rename_column_sql(table_name, column_name, new_column_name)
|
814
766
|
column = column_for(table_name, column_name)
|
815
767
|
options = {
|
816
|
-
name: new_column_name,
|
817
768
|
default: column.default,
|
818
769
|
null: column.null,
|
819
|
-
auto_increment: column.
|
770
|
+
auto_increment: column.auto_increment?
|
820
771
|
}
|
821
772
|
|
822
773
|
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
823
|
-
|
774
|
+
td = create_table_definition(table_name)
|
775
|
+
cd = td.new_column_definition(new_column_name, current_type, options)
|
776
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
824
777
|
end
|
825
778
|
|
826
779
|
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
@@ -832,8 +785,9 @@ module ActiveRecord
|
|
832
785
|
end
|
833
786
|
|
834
787
|
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
|
-
|
788
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
789
|
+
index_algorithm[0, 0] = ", " if index_algorithm.present?
|
790
|
+
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
|
837
791
|
end
|
838
792
|
|
839
793
|
def remove_index_sql(table_name, options = {})
|
@@ -851,12 +805,19 @@ module ActiveRecord
|
|
851
805
|
|
852
806
|
private
|
853
807
|
|
854
|
-
|
855
|
-
|
856
|
-
|
808
|
+
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
809
|
+
# to give it some prompting in the form of a subsubquery. Ugh!
|
810
|
+
def subquery_for(key, select)
|
811
|
+
subsubselect = select.clone
|
812
|
+
subsubselect.projections = [key]
|
857
813
|
|
858
|
-
|
859
|
-
|
814
|
+
# Materialize subquery by adding distinct
|
815
|
+
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
816
|
+
subsubselect.distinct unless select.limit || select.offset || select.orders.any?
|
817
|
+
|
818
|
+
subselect = Arel::SelectManager.new(select.engine)
|
819
|
+
subselect.project Arel.sql(key.name)
|
820
|
+
subselect.from subsubselect.as('__active_record_temp')
|
860
821
|
end
|
861
822
|
|
862
823
|
def supports_rename_index?
|
@@ -866,24 +827,35 @@ module ActiveRecord
|
|
866
827
|
def configure_connection
|
867
828
|
variables = @config.fetch(:variables, {}).stringify_keys
|
868
829
|
|
869
|
-
# By default, MySQL 'where id is null' selects the last inserted id.
|
870
|
-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
830
|
+
# By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
|
871
831
|
variables['sql_auto_is_null'] = 0
|
872
832
|
|
873
833
|
# Increase timeout so the server doesn't disconnect us.
|
874
|
-
wait_timeout =
|
834
|
+
wait_timeout = @config[:wait_timeout]
|
875
835
|
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
876
|
-
variables[
|
836
|
+
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
|
837
|
+
|
838
|
+
defaults = [':default', :default].to_set
|
877
839
|
|
878
840
|
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
879
|
-
# http://dev.mysql.com/doc/refman/5.
|
841
|
+
# http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
|
880
842
|
# If the user has provided another value for sql_mode, don't replace it.
|
881
|
-
|
882
|
-
|
843
|
+
if sql_mode = variables.delete('sql_mode')
|
844
|
+
sql_mode = quote(sql_mode)
|
845
|
+
elsif !defaults.include?(strict_mode?)
|
846
|
+
if strict_mode?
|
847
|
+
sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
|
848
|
+
else
|
849
|
+
sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
|
850
|
+
sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
|
851
|
+
sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
|
852
|
+
end
|
853
|
+
sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
|
883
854
|
end
|
855
|
+
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
|
884
856
|
|
885
857
|
# NAMES does not have an equals sign, see
|
886
|
-
# http://dev.mysql.com/doc/refman/5.
|
858
|
+
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
|
887
859
|
# (trailing comma because variable_assignments will always have content)
|
888
860
|
if @config[:encoding]
|
889
861
|
encoding = "NAMES #{@config[:encoding]}"
|
@@ -893,7 +865,7 @@ module ActiveRecord
|
|
893
865
|
|
894
866
|
# Gather up all of the SET variables...
|
895
867
|
variable_assignments = variables.map do |k, v|
|
896
|
-
if v
|
868
|
+
if defaults.include?(v)
|
897
869
|
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
898
870
|
elsif !v.nil?
|
899
871
|
"@@SESSION.#{k} = #{quote(v)}"
|
@@ -902,7 +874,13 @@ module ActiveRecord
|
|
902
874
|
end.compact.join(', ')
|
903
875
|
|
904
876
|
# ...and send them all in one query
|
905
|
-
@connection.query "SET #{encoding} #{variable_assignments}"
|
877
|
+
@connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
|
878
|
+
end
|
879
|
+
|
880
|
+
def column_definitions(table_name) # :nodoc:
|
881
|
+
execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
|
882
|
+
each_hash(result)
|
883
|
+
end
|
906
884
|
end
|
907
885
|
|
908
886
|
def extract_foreign_key_action(structure, name, action) # :nodoc:
|
@@ -914,19 +892,68 @@ module ActiveRecord
|
|
914
892
|
end
|
915
893
|
end
|
916
894
|
|
917
|
-
|
918
|
-
|
895
|
+
def create_table_info_cache # :nodoc:
|
896
|
+
@create_table_info_cache ||= {}
|
897
|
+
end
|
919
898
|
|
920
|
-
|
921
|
-
|
899
|
+
def create_table_info(table_name) # :nodoc:
|
900
|
+
create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
901
|
+
end
|
902
|
+
|
903
|
+
def create_table_definition(*args) # :nodoc:
|
904
|
+
MySQL::TableDefinition.new(*args)
|
905
|
+
end
|
906
|
+
|
907
|
+
def extract_schema_qualified_name(string) # :nodoc:
|
908
|
+
schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
|
909
|
+
schema, name = @config[:database], schema unless name
|
910
|
+
[schema, name]
|
911
|
+
end
|
912
|
+
|
913
|
+
def integer_to_sql(limit) # :nodoc:
|
914
|
+
case limit
|
915
|
+
when 1; 'tinyint'
|
916
|
+
when 2; 'smallint'
|
917
|
+
when 3; 'mediumint'
|
918
|
+
when nil, 4; 'int'
|
919
|
+
when 5..8; 'bigint'
|
920
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
921
|
+
end
|
922
|
+
end
|
923
|
+
|
924
|
+
def text_to_sql(limit) # :nodoc:
|
925
|
+
case limit
|
926
|
+
when 0..0xff; 'tinytext'
|
927
|
+
when nil, 0x100..0xffff; 'text'
|
928
|
+
when 0x10000..0xffffff; 'mediumtext'
|
929
|
+
when 0x1000000..0xffffffff; 'longtext'
|
930
|
+
else raise(ActiveRecordError, "No text type has byte length #{limit}")
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
934
|
+
def binary_to_sql(limit) # :nodoc:
|
935
|
+
case limit
|
936
|
+
when 0..0xff; 'tinyblob'
|
937
|
+
when nil, 0x100..0xffff; 'blob'
|
938
|
+
when 0x10000..0xffffff; 'mediumblob'
|
939
|
+
when 0x1000000..0xffffffff; 'longblob'
|
940
|
+
else raise(ActiveRecordError, "No binary type has byte length #{limit}")
|
941
|
+
end
|
942
|
+
end
|
943
|
+
|
944
|
+
class MysqlJson < Type::Internal::AbstractJson # :nodoc:
|
945
|
+
def changed_in_place?(raw_old_value, new_value)
|
946
|
+
# Normalization is required because MySQL JSON data format includes
|
947
|
+
# the space between the elements.
|
948
|
+
super(serialize(deserialize(raw_old_value)), new_value)
|
922
949
|
end
|
923
950
|
end
|
924
951
|
|
925
952
|
class MysqlString < Type::String # :nodoc:
|
926
|
-
def
|
953
|
+
def serialize(value)
|
927
954
|
case value
|
928
|
-
when true then
|
929
|
-
when false then
|
955
|
+
when true then MySQL::Quoting::QUOTED_TRUE
|
956
|
+
when false then MySQL::Quoting::QUOTED_FALSE
|
930
957
|
else super
|
931
958
|
end
|
932
959
|
end
|
@@ -935,12 +962,16 @@ module ActiveRecord
|
|
935
962
|
|
936
963
|
def cast_value(value)
|
937
964
|
case value
|
938
|
-
when true then
|
939
|
-
when false then
|
965
|
+
when true then MySQL::Quoting::QUOTED_TRUE
|
966
|
+
when false then MySQL::Quoting::QUOTED_FALSE
|
940
967
|
else super
|
941
968
|
end
|
942
969
|
end
|
943
970
|
end
|
971
|
+
|
972
|
+
ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
|
973
|
+
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
|
974
|
+
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
|
944
975
|
end
|
945
976
|
end
|
946
977
|
end
|