activerecord 4.2.6 → 5.0.0
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 +1307 -1105
- 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 +37 -23
- 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 +50 -31
- data/lib/active_record/associations/collection_proxy.rb +69 -29
- 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 +20 -8
- 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 +20 -141
- 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 +14 -38
- data/lib/active_record/attribute_methods.rb +70 -45
- 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 -80
- 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 -9
- 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 +378 -140
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +153 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +405 -362
- 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 +25 -176
- 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 -56
- 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 +148 -203
- 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 +3 -3
- 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 +364 -109
- data/lib/active_record/model_schema.rb +128 -38
- 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 +27 -18
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +58 -45
- 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 +80 -102
- 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 -15
- 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 -17
- 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 +58 -41
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -20
- 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 -41
- 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 +7 -2
- 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 +58 -34
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/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 -50
- 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 -105
@@ -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,7 +130,27 @@ module ActiveRecord
|
|
215
130
|
end
|
216
131
|
|
217
132
|
def supports_views?
|
218
|
-
|
133
|
+
true
|
134
|
+
end
|
135
|
+
|
136
|
+
def supports_datetime_with_precision?
|
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'
|
219
154
|
end
|
220
155
|
|
221
156
|
def native_database_types
|
@@ -234,8 +169,8 @@ module ActiveRecord
|
|
234
169
|
raise NotImplementedError
|
235
170
|
end
|
236
171
|
|
237
|
-
def new_column(
|
238
|
-
Column.new(
|
172
|
+
def new_column(*args) #:nodoc:
|
173
|
+
MySQL::Column.new(*args)
|
239
174
|
end
|
240
175
|
|
241
176
|
# Must return the MySQL error number from the exception, if the exception has an
|
@@ -244,40 +179,6 @@ module ActiveRecord
|
|
244
179
|
raise NotImplementedError
|
245
180
|
end
|
246
181
|
|
247
|
-
# QUOTING ==================================================
|
248
|
-
|
249
|
-
def _quote(value) # :nodoc:
|
250
|
-
if value.is_a?(Type::Binary::Data)
|
251
|
-
"x'#{value.hex}'"
|
252
|
-
else
|
253
|
-
super
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
def quote_column_name(name) #:nodoc:
|
258
|
-
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
259
|
-
end
|
260
|
-
|
261
|
-
def quote_table_name(name) #:nodoc:
|
262
|
-
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
263
|
-
end
|
264
|
-
|
265
|
-
def quoted_true
|
266
|
-
QUOTED_TRUE
|
267
|
-
end
|
268
|
-
|
269
|
-
def unquoted_true
|
270
|
-
1
|
271
|
-
end
|
272
|
-
|
273
|
-
def quoted_false
|
274
|
-
QUOTED_FALSE
|
275
|
-
end
|
276
|
-
|
277
|
-
def unquoted_false
|
278
|
-
0
|
279
|
-
end
|
280
|
-
|
281
182
|
# REFERENTIAL INTEGRITY ====================================
|
282
183
|
|
283
184
|
def disable_referential_integrity #:nodoc:
|
@@ -291,13 +192,25 @@ module ActiveRecord
|
|
291
192
|
end
|
292
193
|
end
|
293
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
|
+
|
294
203
|
#--
|
295
204
|
# DATABASE STATEMENTS ======================================
|
296
205
|
#++
|
297
206
|
|
298
|
-
def
|
299
|
-
|
300
|
-
|
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)
|
301
214
|
end
|
302
215
|
|
303
216
|
# Executes the SQL statement in the context of this connection.
|
@@ -305,18 +218,13 @@ module ActiveRecord
|
|
305
218
|
log(sql, name) { @connection.query(sql) }
|
306
219
|
end
|
307
220
|
|
308
|
-
#
|
309
|
-
# stuff in an abstract way without concerning ourselves about whether it
|
310
|
-
# explicitly freed or not.
|
311
|
-
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:
|
312
225
|
yield execute(sql, name)
|
313
226
|
end
|
314
227
|
|
315
|
-
def update_sql(sql, name = nil) #:nodoc:
|
316
|
-
super
|
317
|
-
@connection.affected_rows
|
318
|
-
end
|
319
|
-
|
320
228
|
def begin_db_transaction
|
321
229
|
execute "BEGIN"
|
322
230
|
end
|
@@ -337,7 +245,7 @@ module ActiveRecord
|
|
337
245
|
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
338
246
|
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
339
247
|
# these, we must use a subquery.
|
340
|
-
def join_to_update(update, select)
|
248
|
+
def join_to_update(update, select, key) # :nodoc:
|
341
249
|
if select.limit || select.offset || select.orders.any?
|
342
250
|
super
|
343
251
|
else
|
@@ -370,9 +278,9 @@ module ActiveRecord
|
|
370
278
|
# create_database 'matt_development', charset: :big5
|
371
279
|
def create_database(name, options = {})
|
372
280
|
if options[:collation]
|
373
|
-
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])}"
|
374
282
|
else
|
375
|
-
execute "CREATE DATABASE
|
283
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
|
376
284
|
end
|
377
285
|
end
|
378
286
|
|
@@ -381,7 +289,7 @@ module ActiveRecord
|
|
381
289
|
# Example:
|
382
290
|
# drop_database('sebastian_development')
|
383
291
|
def drop_database(name) #:nodoc:
|
384
|
-
execute "DROP DATABASE IF EXISTS
|
292
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
385
293
|
end
|
386
294
|
|
387
295
|
def current_database
|
@@ -398,36 +306,69 @@ module ActiveRecord
|
|
398
306
|
show_variable 'collation_database'
|
399
307
|
end
|
400
308
|
|
401
|
-
def tables(name = nil
|
402
|
-
|
403
|
-
|
404
|
-
|
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
|
405
315
|
|
406
|
-
|
407
|
-
|
316
|
+
if name
|
317
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
318
|
+
Passing arguments to #tables is deprecated without replacement.
|
319
|
+
MSG
|
408
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')
|
409
330
|
end
|
410
|
-
alias data_sources tables
|
411
331
|
|
412
332
|
def truncate(table_name, name = nil)
|
413
333
|
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
414
334
|
end
|
415
335
|
|
416
|
-
def table_exists?(
|
417
|
-
|
418
|
-
|
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
|
419
343
|
|
420
|
-
|
421
|
-
|
344
|
+
data_source_exists?(table_name)
|
345
|
+
end
|
422
346
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
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
|
427
357
|
|
428
|
-
|
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 = #{quote(schema)} AND table_name = #{quote(name)}"
|
369
|
+
|
370
|
+
select_values(sql, 'SCHEMA').any?
|
429
371
|
end
|
430
|
-
alias data_source_exists? table_exists?
|
431
372
|
|
432
373
|
# Returns an array of indexes for the given table.
|
433
374
|
def indexes(table_name, name = nil) #:nodoc:
|
@@ -442,7 +383,7 @@ module ActiveRecord
|
|
442
383
|
mysql_index_type = row[:Index_type].downcase.to_sym
|
443
384
|
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
|
444
385
|
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
|
445
|
-
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)
|
446
387
|
end
|
447
388
|
|
448
389
|
indexes.last.columns << row[:Column_name]
|
@@ -454,20 +395,29 @@ module ActiveRecord
|
|
454
395
|
end
|
455
396
|
|
456
397
|
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
457
|
-
def columns(table_name)
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
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
|
465
406
|
end
|
407
|
+
new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
|
466
408
|
end
|
467
409
|
end
|
468
410
|
|
469
|
-
def
|
470
|
-
|
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)
|
471
421
|
end
|
472
422
|
|
473
423
|
def bulk_change_table(table_name, operations) #:nodoc:
|
@@ -494,8 +444,24 @@ module ActiveRecord
|
|
494
444
|
rename_table_indexes(table_name, new_name)
|
495
445
|
end
|
496
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.
|
497
462
|
def drop_table(table_name, options = {})
|
498
|
-
|
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}"
|
499
465
|
end
|
500
466
|
|
501
467
|
def rename_index(table_name, old_name, new_name)
|
@@ -508,12 +474,13 @@ module ActiveRecord
|
|
508
474
|
end
|
509
475
|
end
|
510
476
|
|
511
|
-
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)
|
512
479
|
column = column_for(table_name, column_name)
|
513
480
|
change_column table_name, column_name, column.sql_type, :default => default
|
514
481
|
end
|
515
482
|
|
516
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
483
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
517
484
|
column = column_for(table_name, column_name)
|
518
485
|
|
519
486
|
unless null || default.nil?
|
@@ -533,11 +500,21 @@ module ActiveRecord
|
|
533
500
|
end
|
534
501
|
|
535
502
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
536
|
-
index_name, index_type, index_columns,
|
537
|
-
|
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
|
538
511
|
end
|
539
512
|
|
540
513
|
def foreign_keys(table_name)
|
514
|
+
raise ArgumentError unless table_name.present?
|
515
|
+
|
516
|
+
schema, name = extract_schema_qualified_name(table_name)
|
517
|
+
|
541
518
|
fk_info = select_all <<-SQL.strip_heredoc
|
542
519
|
SELECT fk.referenced_table_name as 'to_table'
|
543
520
|
,fk.referenced_column_name as 'primary_key'
|
@@ -545,11 +522,11 @@ module ActiveRecord
|
|
545
522
|
,fk.constraint_name as 'name'
|
546
523
|
FROM information_schema.key_column_usage fk
|
547
524
|
WHERE fk.referenced_column_name is not null
|
548
|
-
AND fk.table_schema =
|
549
|
-
AND fk.table_name =
|
525
|
+
AND fk.table_schema = #{quote(schema)}
|
526
|
+
AND fk.table_name = #{quote(name)}
|
550
527
|
SQL
|
551
528
|
|
552
|
-
create_table_info =
|
529
|
+
create_table_info = create_table_info(table_name)
|
553
530
|
|
554
531
|
fk_info.map do |row|
|
555
532
|
options = {
|
@@ -565,92 +542,78 @@ module ActiveRecord
|
|
565
542
|
end
|
566
543
|
end
|
567
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
|
+
|
568
560
|
# Maps logical Rails types to MySQL-specific data types.
|
569
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
570
|
-
case type.to_s
|
571
|
-
when 'binary'
|
572
|
-
case limit
|
573
|
-
when 0..0xfff; "varbinary(#{limit})"
|
574
|
-
when nil; "blob"
|
575
|
-
when 0x1000..0xffffffff; "blob(#{limit})"
|
576
|
-
else raise(ActiveRecordError, "No binary type has character length #{limit}")
|
577
|
-
end
|
561
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
|
562
|
+
sql = case type.to_s
|
578
563
|
when 'integer'
|
579
|
-
|
580
|
-
when 1; 'tinyint'
|
581
|
-
when 2; 'smallint'
|
582
|
-
when 3; 'mediumint'
|
583
|
-
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
584
|
-
when 5..8; 'bigint'
|
585
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
586
|
-
end
|
564
|
+
integer_to_sql(limit)
|
587
565
|
when 'text'
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
return super unless precision
|
597
|
-
|
598
|
-
case precision
|
599
|
-
when 0..6; "datetime(#{precision})"
|
600
|
-
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)
|
601
574
|
end
|
602
575
|
else
|
603
|
-
super
|
576
|
+
super(type, limit, precision, scale)
|
604
577
|
end
|
578
|
+
|
579
|
+
sql << ' unsigned' if unsigned && type != :primary_key
|
580
|
+
sql
|
605
581
|
end
|
606
582
|
|
607
583
|
# SHOW VARIABLES LIKE 'name'
|
608
584
|
def show_variable(name)
|
609
|
-
|
610
|
-
variables.first['Value'] unless variables.empty?
|
585
|
+
select_value("SELECT @@#{name}", 'SCHEMA')
|
611
586
|
rescue ActiveRecord::StatementInvalid
|
612
587
|
nil
|
613
588
|
end
|
614
589
|
|
615
|
-
|
616
|
-
|
617
|
-
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
|
618
|
-
create_table = each_hash(result).first[:"Create Table"]
|
619
|
-
if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
|
620
|
-
keys = $1.split(",").map { |key| key.delete('`"') }
|
621
|
-
keys.length == 1 ? [keys.first, nil] : nil
|
622
|
-
else
|
623
|
-
nil
|
624
|
-
end
|
625
|
-
end
|
626
|
-
end
|
590
|
+
def primary_keys(table_name) # :nodoc:
|
591
|
+
raise ArgumentError unless table_name.present?
|
627
592
|
|
628
|
-
|
629
|
-
def primary_key(table)
|
630
|
-
pk_and_sequence = pk_and_sequence_for(table)
|
631
|
-
pk_and_sequence && pk_and_sequence.first
|
632
|
-
end
|
593
|
+
schema, name = extract_schema_qualified_name(table_name)
|
633
594
|
|
634
|
-
|
635
|
-
|
636
|
-
|
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
|
637
603
|
end
|
638
604
|
|
639
605
|
def case_sensitive_comparison(table, attribute, column, value)
|
640
|
-
if column.case_sensitive?
|
641
|
-
table[attribute].eq(
|
606
|
+
if !value.nil? && column.collation && !column.case_sensitive?
|
607
|
+
table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
|
642
608
|
else
|
643
609
|
super
|
644
610
|
end
|
645
611
|
end
|
646
612
|
|
647
|
-
def
|
648
|
-
|
649
|
-
super
|
650
|
-
else
|
651
|
-
table[attribute].eq(value)
|
652
|
-
end
|
613
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
614
|
+
column.case_sensitive?
|
653
615
|
end
|
616
|
+
private :can_perform_case_insensitive_comparison_for?
|
654
617
|
|
655
618
|
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
656
619
|
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
@@ -692,6 +655,7 @@ module ActiveRecord
|
|
692
655
|
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
693
656
|
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
694
657
|
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
658
|
+
m.register_type %r(^json)i, MysqlJson.new
|
695
659
|
|
696
660
|
register_integer_type m, %r(^bigint)i, limit: 8
|
697
661
|
register_integer_type m, %r(^int)i, limit: 4
|
@@ -699,26 +663,26 @@ module ActiveRecord
|
|
699
663
|
register_integer_type m, %r(^smallint)i, limit: 2
|
700
664
|
register_integer_type m, %r(^tinyint)i, limit: 1
|
701
665
|
|
702
|
-
m.
|
703
|
-
m.alias_type %r(set)i, 'varchar'
|
666
|
+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
|
704
667
|
m.alias_type %r(year)i, 'integer'
|
705
668
|
m.alias_type %r(bit)i, 'binary'
|
706
669
|
|
707
|
-
m.register_type(%r(datetime)i) do |sql_type|
|
708
|
-
precision = extract_precision(sql_type)
|
709
|
-
MysqlDateTime.new(precision: precision)
|
710
|
-
end
|
711
|
-
|
712
670
|
m.register_type(%r(enum)i) do |sql_type|
|
713
671
|
limit = sql_type[/^enum\((.+)\)/i, 1]
|
714
672
|
.split(',').map{|enum| enum.strip.length - 2}.max
|
715
673
|
MysqlString.new(limit: limit)
|
716
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
|
717
681
|
end
|
718
682
|
|
719
683
|
def register_integer_type(mapping, key, options) # :nodoc:
|
720
684
|
mapping.register_type(key) do |sql_type|
|
721
|
-
if /
|
685
|
+
if /\bunsigned\z/ === sql_type
|
722
686
|
Type::UnsignedInteger.new(options)
|
723
687
|
else
|
724
688
|
Type::Integer.new(options)
|
@@ -726,19 +690,16 @@ module ActiveRecord
|
|
726
690
|
end
|
727
691
|
end
|
728
692
|
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
737
|
-
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
|
738
700
|
|
739
|
-
|
740
|
-
|
741
|
-
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?)
|
742
703
|
end
|
743
704
|
|
744
705
|
def add_index_length(option_strings, column_names, options = {})
|
@@ -746,7 +707,7 @@ module ActiveRecord
|
|
746
707
|
case length
|
747
708
|
when Hash
|
748
709
|
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
|
749
|
-
when
|
710
|
+
when Integer
|
750
711
|
column_names.each {|name| option_strings[name] += "(#{length})"}
|
751
712
|
end
|
752
713
|
end
|
@@ -769,18 +730,20 @@ module ActiveRecord
|
|
769
730
|
def translate_exception(exception, message)
|
770
731
|
case error_number(exception)
|
771
732
|
when 1062
|
772
|
-
RecordNotUnique.new(message
|
733
|
+
RecordNotUnique.new(message)
|
773
734
|
when 1452
|
774
|
-
InvalidForeignKey.new(message
|
735
|
+
InvalidForeignKey.new(message)
|
736
|
+
when 1406
|
737
|
+
ValueTooLong.new(message)
|
775
738
|
else
|
776
739
|
super
|
777
740
|
end
|
778
741
|
end
|
779
742
|
|
780
743
|
def add_column_sql(table_name, column_name, type, options = {})
|
781
|
-
td = create_table_definition
|
744
|
+
td = create_table_definition(table_name)
|
782
745
|
cd = td.new_column_definition(column_name, type, options)
|
783
|
-
schema_creation.
|
746
|
+
schema_creation.accept(AddColumnDefinition.new(cd))
|
784
747
|
end
|
785
748
|
|
786
749
|
def change_column_sql(table_name, column_name, type, options = {})
|
@@ -794,21 +757,23 @@ module ActiveRecord
|
|
794
757
|
options[:null] = column.null
|
795
758
|
end
|
796
759
|
|
797
|
-
|
798
|
-
|
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))
|
799
763
|
end
|
800
764
|
|
801
765
|
def rename_column_sql(table_name, column_name, new_column_name)
|
802
766
|
column = column_for(table_name, column_name)
|
803
767
|
options = {
|
804
|
-
name: new_column_name,
|
805
768
|
default: column.default,
|
806
769
|
null: column.null,
|
807
|
-
auto_increment: column.
|
770
|
+
auto_increment: column.auto_increment?
|
808
771
|
}
|
809
772
|
|
810
773
|
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
811
|
-
|
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))
|
812
777
|
end
|
813
778
|
|
814
779
|
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
@@ -820,8 +785,9 @@ module ActiveRecord
|
|
820
785
|
end
|
821
786
|
|
822
787
|
def add_index_sql(table_name, column_name, options = {})
|
823
|
-
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
824
|
-
|
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}"
|
825
791
|
end
|
826
792
|
|
827
793
|
def remove_index_sql(table_name, options = {})
|
@@ -839,39 +805,57 @@ module ActiveRecord
|
|
839
805
|
|
840
806
|
private
|
841
807
|
|
842
|
-
|
843
|
-
|
844
|
-
|
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]
|
845
813
|
|
846
|
-
|
847
|
-
|
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')
|
848
821
|
end
|
849
822
|
|
850
823
|
def supports_rename_index?
|
851
|
-
mariadb? ? false :
|
824
|
+
mariadb? ? false : version >= '5.7.6'
|
852
825
|
end
|
853
826
|
|
854
827
|
def configure_connection
|
855
828
|
variables = @config.fetch(:variables, {}).stringify_keys
|
856
829
|
|
857
|
-
# By default, MySQL 'where id is null' selects the last inserted id.
|
858
|
-
# 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.
|
859
831
|
variables['sql_auto_is_null'] = 0
|
860
832
|
|
861
833
|
# Increase timeout so the server doesn't disconnect us.
|
862
834
|
wait_timeout = @config[:wait_timeout]
|
863
|
-
wait_timeout = 2147483 unless wait_timeout.is_a?(
|
835
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
864
836
|
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
|
865
837
|
|
838
|
+
defaults = [':default', :default].to_set
|
839
|
+
|
866
840
|
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
867
|
-
# http://dev.mysql.com/doc/refman/5.
|
841
|
+
# http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
|
868
842
|
# If the user has provided another value for sql_mode, don't replace it.
|
869
|
-
|
870
|
-
|
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')"
|
871
854
|
end
|
855
|
+
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
|
872
856
|
|
873
857
|
# NAMES does not have an equals sign, see
|
874
|
-
# http://dev.mysql.com/doc/refman/5.
|
858
|
+
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
|
875
859
|
# (trailing comma because variable_assignments will always have content)
|
876
860
|
if @config[:encoding]
|
877
861
|
encoding = "NAMES #{@config[:encoding]}"
|
@@ -881,7 +865,7 @@ module ActiveRecord
|
|
881
865
|
|
882
866
|
# Gather up all of the SET variables...
|
883
867
|
variable_assignments = variables.map do |k, v|
|
884
|
-
if v
|
868
|
+
if defaults.include?(v)
|
885
869
|
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
886
870
|
elsif !v.nil?
|
887
871
|
"@@SESSION.#{k} = #{quote(v)}"
|
@@ -890,7 +874,13 @@ module ActiveRecord
|
|
890
874
|
end.compact.join(', ')
|
891
875
|
|
892
876
|
# ...and send them all in one query
|
893
|
-
@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
|
894
884
|
end
|
895
885
|
|
896
886
|
def extract_foreign_key_action(structure, name, action) # :nodoc:
|
@@ -902,19 +892,68 @@ module ActiveRecord
|
|
902
892
|
end
|
903
893
|
end
|
904
894
|
|
905
|
-
|
906
|
-
|
895
|
+
def create_table_info_cache # :nodoc:
|
896
|
+
@create_table_info_cache ||= {}
|
897
|
+
end
|
907
898
|
|
908
|
-
|
909
|
-
|
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)
|
910
949
|
end
|
911
950
|
end
|
912
951
|
|
913
952
|
class MysqlString < Type::String # :nodoc:
|
914
|
-
def
|
953
|
+
def serialize(value)
|
915
954
|
case value
|
916
|
-
when true then
|
917
|
-
when false then
|
955
|
+
when true then MySQL::Quoting::QUOTED_TRUE
|
956
|
+
when false then MySQL::Quoting::QUOTED_FALSE
|
918
957
|
else super
|
919
958
|
end
|
920
959
|
end
|
@@ -923,12 +962,16 @@ module ActiveRecord
|
|
923
962
|
|
924
963
|
def cast_value(value)
|
925
964
|
case value
|
926
|
-
when true then
|
927
|
-
when false then
|
965
|
+
when true then MySQL::Quoting::QUOTED_TRUE
|
966
|
+
when false then MySQL::Quoting::QUOTED_FALSE
|
928
967
|
else super
|
929
968
|
end
|
930
969
|
end
|
931
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)
|
932
975
|
end
|
933
976
|
end
|
934
977
|
end
|