activerecord 4.2.0 → 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 +1537 -789
- 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 +16 -3
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +23 -9
- data/lib/active_record/associations/association_scope.rb +74 -102
- data/lib/active_record/associations/belongs_to_association.rb +26 -29
- 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 +12 -20
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +22 -15
- 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 +61 -33
- data/lib/active_record/associations/collection_proxy.rb +81 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +15 -45
- data/lib/active_record/associations/has_one_association.rb +13 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
- data/lib/active_record/associations/join_dependency.rb +37 -21
- data/lib/active_record/associations/preloader/association.rb +51 -53
- 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 +18 -8
- data/lib/active_record/associations/singular_association.rb +8 -8
- data/lib/active_record/associations/through_association.rb +22 -9
- data/lib/active_record/associations.rb +321 -212
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +79 -15
- 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 +6 -1
- data/lib/active_record/attribute_methods/dirty.rb +51 -81
- 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 +65 -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 +37 -15
- data/lib/active_record/attribute_set.rb +34 -3
- data/lib/active_record/attributes.rb +199 -73
- data/lib/active_record/autosave_association.rb +73 -25
- data/lib/active_record/base.rb +35 -27
- 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 +457 -181
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -59
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +246 -185
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +438 -136
- data/lib/active_record/connection_adapters/abstract/transaction.rb +53 -40
- data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -66
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +429 -335
- 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 +26 -177
- data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +11 -73
- 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 +2 -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 -13
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
- 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/type_map_initializer.rb +17 -5
- 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 +248 -154
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +258 -170
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +150 -209
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +38 -15
- data/lib/active_record/core.rb +109 -114
- data/lib/active_record/counter_cache.rb +14 -25
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +115 -79
- data/lib/active_record/errors.rb +88 -48
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +26 -5
- data/lib/active_record/fixtures.rb +84 -46
- data/lib/active_record/gem_version.rb +2 -2
- 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 +46 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +27 -25
- 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 +372 -114
- data/lib/active_record/model_schema.rb +128 -38
- data/lib/active_record/nested_attributes.rb +71 -32
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +124 -80
- data/lib/active_record/query_cache.rb +15 -18
- data/lib/active_record/querying.rb +10 -9
- data/lib/active_record/railtie.rb +28 -19
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +67 -51
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +318 -139
- 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 +167 -97
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +38 -41
- data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -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 +124 -82
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +323 -257
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -10
- 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 -115
- 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 +24 -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 +59 -42
- data/lib/active_record/tasks/mysql_database_tasks.rb +32 -26
- data/lib/active_record/tasks/postgresql_database_tasks.rb +29 -9
- 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 +159 -67
- 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 -38
- data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
- 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 +21 -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 +29 -18
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record.rb +9 -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 -6
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -7
- 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 -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 -30
- data/lib/active_record/type/decimal.rb +0 -40
- 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 -55
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -36
- data/lib/active_record/type/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 -101
@@ -1,178 +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
|
16
|
+
include MySQL::Quoting
|
17
|
+
include MySQL::ColumnDumper
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
|
23
|
-
|
24
|
-
statements = o.columns.map { |c| accept c }
|
25
|
-
statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
|
26
|
-
|
27
|
-
create_sql << "(#{statements.join(', ')}) " if statements.present?
|
28
|
-
create_sql << "#{o.options}"
|
29
|
-
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
|
30
|
-
create_sql
|
31
|
-
end
|
32
|
-
|
33
|
-
def visit_ChangeColumnDefinition(o)
|
34
|
-
column = o.column
|
35
|
-
options = o.options
|
36
|
-
sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
|
37
|
-
change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
|
38
|
-
add_column_options!(change_column_sql, options.merge(column: column))
|
39
|
-
add_column_position!(change_column_sql, options)
|
40
|
-
end
|
41
|
-
|
42
|
-
def add_column_position!(sql, options)
|
43
|
-
if options[:first]
|
44
|
-
sql << " FIRST"
|
45
|
-
elsif options[:after]
|
46
|
-
sql << " AFTER #{quote_column_name(options[:after])}"
|
47
|
-
end
|
48
|
-
sql
|
49
|
-
end
|
50
|
-
|
51
|
-
def index_in_create(table_name, column_name, options)
|
52
|
-
index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
|
53
|
-
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
|
54
|
-
end
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
|
65
|
-
@strict = strict
|
66
|
-
@collation = collation
|
67
|
-
@extra = extra
|
68
|
-
super(name, default, cast_type, sql_type, null)
|
69
|
-
assert_valid_default(default)
|
70
|
-
extract_default
|
71
|
-
end
|
72
|
-
|
73
|
-
def extract_default
|
74
|
-
if blob_or_text_column?
|
75
|
-
@default = null || strict ? nil : ''
|
76
|
-
elsif missing_default_forged_as_empty_string?(@default)
|
77
|
-
@default = nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def has_default?
|
82
|
-
return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
|
83
|
-
super
|
84
|
-
end
|
85
|
-
|
86
|
-
def blob_or_text_column?
|
87
|
-
sql_type =~ /blob/i || type == :text
|
88
|
-
end
|
89
|
-
|
90
|
-
def case_sensitive?
|
91
|
-
collation && !collation.match(/_ci$/)
|
92
|
-
end
|
93
|
-
|
94
|
-
def ==(other)
|
95
|
-
super &&
|
96
|
-
collation == other.collation &&
|
97
|
-
strict == other.strict &&
|
98
|
-
extra == other.extra
|
99
|
-
end
|
100
|
-
|
101
|
-
private
|
102
|
-
|
103
|
-
# MySQL misreports NOT NULL column default when none is given.
|
104
|
-
# We can't detect this for columns which may have a legitimate ''
|
105
|
-
# default (string) but we can for others (integer, datetime, boolean,
|
106
|
-
# and the rest).
|
107
|
-
#
|
108
|
-
# Test whether the column has default '', is not null, and is not
|
109
|
-
# a type allowing default ''.
|
110
|
-
def missing_default_forged_as_empty_string?(default)
|
111
|
-
type != :string && !null && default == ''
|
112
|
-
end
|
113
|
-
|
114
|
-
def assert_valid_default(default)
|
115
|
-
if blob_or_text_column? && default.present?
|
116
|
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
def attributes_for_hash
|
121
|
-
super + [collation, strict, extra]
|
122
|
-
end
|
27
|
+
def arel_visitor # :nodoc:
|
28
|
+
Arel::Visitors::MySQL.new(self)
|
123
29
|
end
|
124
30
|
|
125
31
|
##
|
126
32
|
# :singleton-method:
|
127
|
-
# By default, the
|
128
|
-
# as boolean. If you wish to disable this emulation
|
129
|
-
# behavior in versions 0.13.1 and earlier) you can add the following line
|
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
|
130
35
|
# to your application.rb file:
|
131
36
|
#
|
132
|
-
# ActiveRecord::ConnectionAdapters::
|
37
|
+
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
|
133
38
|
class_attribute :emulate_booleans
|
134
39
|
self.emulate_booleans = true
|
135
40
|
|
136
|
-
LOST_CONNECTION_ERROR_MESSAGES = [
|
137
|
-
"Server shutdown in progress",
|
138
|
-
"Broken pipe",
|
139
|
-
"Lost connection to MySQL server during query",
|
140
|
-
"MySQL server has gone away" ]
|
141
|
-
|
142
|
-
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
143
|
-
|
144
41
|
NATIVE_DATABASE_TYPES = {
|
145
|
-
:
|
146
|
-
:
|
147
|
-
:
|
148
|
-
:
|
149
|
-
:
|
150
|
-
:
|
151
|
-
:
|
152
|
-
:
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
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" },
|
156
54
|
}
|
157
55
|
|
158
56
|
INDEX_TYPES = [:fulltext, :spatial]
|
159
57
|
INDEX_USINGS = [:btree, :hash]
|
160
58
|
|
161
|
-
|
59
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
60
|
+
private def dealloc(stmt)
|
61
|
+
stmt[:stmt].close
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
162
65
|
def initialize(connection, logger, connection_options, config)
|
163
|
-
super(connection, logger)
|
164
|
-
@connection_options, @config = connection_options, config
|
165
|
-
@quoted_column_names, @quoted_table_names = {}, {}
|
66
|
+
super(connection, logger, config)
|
166
67
|
|
167
|
-
@
|
68
|
+
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
168
69
|
|
169
|
-
if
|
170
|
-
|
171
|
-
else
|
172
|
-
@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."
|
173
72
|
end
|
174
73
|
end
|
175
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
|
+
|
176
91
|
# Returns true, since this connection adapter supports migrations.
|
177
92
|
def supports_migrations?
|
178
93
|
true
|
@@ -186,18 +101,24 @@ module ActiveRecord
|
|
186
101
|
true
|
187
102
|
end
|
188
103
|
|
104
|
+
# Returns true, since this connection adapter supports prepared statement
|
105
|
+
# caching.
|
106
|
+
def supports_statement_cache?
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
189
110
|
# Technically MySQL allows to create indexes with the sort order syntax
|
190
111
|
# but at the moment (5.5) it doesn't yet implement them
|
191
112
|
def supports_index_sort_order?
|
192
113
|
true
|
193
114
|
end
|
194
115
|
|
195
|
-
# MySQL 4 technically support transaction isolation, but it is affected by a bug
|
196
|
-
# where the transaction level gets persisted for the whole session:
|
197
|
-
#
|
198
|
-
# http://bugs.mysql.com/bug.php?id=39170
|
199
116
|
def supports_transaction_isolation?
|
200
|
-
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
def supports_explain?
|
121
|
+
true
|
201
122
|
end
|
202
123
|
|
203
124
|
def supports_indexes_in_create?
|
@@ -209,7 +130,27 @@ module ActiveRecord
|
|
209
130
|
end
|
210
131
|
|
211
132
|
def supports_views?
|
212
|
-
|
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'
|
213
154
|
end
|
214
155
|
|
215
156
|
def native_database_types
|
@@ -228,8 +169,8 @@ module ActiveRecord
|
|
228
169
|
raise NotImplementedError
|
229
170
|
end
|
230
171
|
|
231
|
-
def new_column(
|
232
|
-
Column.new(
|
172
|
+
def new_column(*args) #:nodoc:
|
173
|
+
MySQL::Column.new(*args)
|
233
174
|
end
|
234
175
|
|
235
176
|
# Must return the MySQL error number from the exception, if the exception has an
|
@@ -238,40 +179,6 @@ module ActiveRecord
|
|
238
179
|
raise NotImplementedError
|
239
180
|
end
|
240
181
|
|
241
|
-
# QUOTING ==================================================
|
242
|
-
|
243
|
-
def _quote(value) # :nodoc:
|
244
|
-
if value.is_a?(Type::Binary::Data)
|
245
|
-
"x'#{value.hex}'"
|
246
|
-
else
|
247
|
-
super
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
def quote_column_name(name) #:nodoc:
|
252
|
-
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
253
|
-
end
|
254
|
-
|
255
|
-
def quote_table_name(name) #:nodoc:
|
256
|
-
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
257
|
-
end
|
258
|
-
|
259
|
-
def quoted_true
|
260
|
-
QUOTED_TRUE
|
261
|
-
end
|
262
|
-
|
263
|
-
def unquoted_true
|
264
|
-
1
|
265
|
-
end
|
266
|
-
|
267
|
-
def quoted_false
|
268
|
-
QUOTED_FALSE
|
269
|
-
end
|
270
|
-
|
271
|
-
def unquoted_false
|
272
|
-
0
|
273
|
-
end
|
274
|
-
|
275
182
|
# REFERENTIAL INTEGRITY ====================================
|
276
183
|
|
277
184
|
def disable_referential_integrity #:nodoc:
|
@@ -285,13 +192,25 @@ module ActiveRecord
|
|
285
192
|
end
|
286
193
|
end
|
287
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
|
+
|
288
203
|
#--
|
289
204
|
# DATABASE STATEMENTS ======================================
|
290
205
|
#++
|
291
206
|
|
292
|
-
def
|
293
|
-
|
294
|
-
|
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)
|
295
214
|
end
|
296
215
|
|
297
216
|
# Executes the SQL statement in the context of this connection.
|
@@ -299,18 +218,13 @@ module ActiveRecord
|
|
299
218
|
log(sql, name) { @connection.query(sql) }
|
300
219
|
end
|
301
220
|
|
302
|
-
#
|
303
|
-
# stuff in an abstract way without concerning ourselves about whether it
|
304
|
-
# explicitly freed or not.
|
305
|
-
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:
|
306
225
|
yield execute(sql, name)
|
307
226
|
end
|
308
227
|
|
309
|
-
def update_sql(sql, name = nil) #:nodoc:
|
310
|
-
super
|
311
|
-
@connection.affected_rows
|
312
|
-
end
|
313
|
-
|
314
228
|
def begin_db_transaction
|
315
229
|
execute "BEGIN"
|
316
230
|
end
|
@@ -324,14 +238,14 @@ module ActiveRecord
|
|
324
238
|
execute "COMMIT"
|
325
239
|
end
|
326
240
|
|
327
|
-
def
|
241
|
+
def exec_rollback_db_transaction #:nodoc:
|
328
242
|
execute "ROLLBACK"
|
329
243
|
end
|
330
244
|
|
331
245
|
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
332
246
|
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
333
247
|
# these, we must use a subquery.
|
334
|
-
def join_to_update(update, select)
|
248
|
+
def join_to_update(update, select, key) # :nodoc:
|
335
249
|
if select.limit || select.offset || select.orders.any?
|
336
250
|
super
|
337
251
|
else
|
@@ -364,9 +278,9 @@ module ActiveRecord
|
|
364
278
|
# create_database 'matt_development', charset: :big5
|
365
279
|
def create_database(name, options = {})
|
366
280
|
if options[:collation]
|
367
|
-
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])}"
|
368
282
|
else
|
369
|
-
execute "CREATE DATABASE
|
283
|
+
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
|
370
284
|
end
|
371
285
|
end
|
372
286
|
|
@@ -375,7 +289,7 @@ module ActiveRecord
|
|
375
289
|
# Example:
|
376
290
|
# drop_database('sebastian_development')
|
377
291
|
def drop_database(name) #:nodoc:
|
378
|
-
execute "DROP DATABASE IF EXISTS
|
292
|
+
execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
379
293
|
end
|
380
294
|
|
381
295
|
def current_database
|
@@ -392,33 +306,68 @@ module ActiveRecord
|
|
392
306
|
show_variable 'collation_database'
|
393
307
|
end
|
394
308
|
|
395
|
-
def tables(name = nil
|
396
|
-
|
397
|
-
|
398
|
-
|
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
|
399
315
|
|
400
|
-
|
401
|
-
|
316
|
+
if name
|
317
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
318
|
+
Passing arguments to #tables is deprecated without replacement.
|
319
|
+
MSG
|
402
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')
|
403
330
|
end
|
404
331
|
|
405
332
|
def truncate(table_name, name = nil)
|
406
333
|
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
407
334
|
end
|
408
335
|
|
409
|
-
def table_exists?(
|
410
|
-
|
411
|
-
|
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
|
412
343
|
|
413
|
-
|
414
|
-
|
344
|
+
data_source_exists?(table_name)
|
345
|
+
end
|
415
346
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
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)
|
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)}"
|
420
369
|
|
421
|
-
|
370
|
+
select_values(sql, 'SCHEMA').any?
|
422
371
|
end
|
423
372
|
|
424
373
|
# Returns an array of indexes for the given table.
|
@@ -434,7 +383,7 @@ module ActiveRecord
|
|
434
383
|
mysql_index_type = row[:Index_type].downcase.to_sym
|
435
384
|
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
|
436
385
|
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
|
437
|
-
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
|
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)
|
438
387
|
end
|
439
388
|
|
440
389
|
indexes.last.columns << row[:Column_name]
|
@@ -446,20 +395,29 @@ module ActiveRecord
|
|
446
395
|
end
|
447
396
|
|
448
397
|
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
449
|
-
def columns(table_name)
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
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
|
457
406
|
end
|
407
|
+
new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
|
458
408
|
end
|
459
409
|
end
|
460
410
|
|
461
|
-
def
|
462
|
-
|
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)
|
463
421
|
end
|
464
422
|
|
465
423
|
def bulk_change_table(table_name, operations) #:nodoc:
|
@@ -486,24 +444,43 @@ module ActiveRecord
|
|
486
444
|
rename_table_indexes(table_name, new_name)
|
487
445
|
end
|
488
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.
|
489
462
|
def drop_table(table_name, options = {})
|
490
|
-
|
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}"
|
491
465
|
end
|
492
466
|
|
493
467
|
def rename_index(table_name, old_name, new_name)
|
494
468
|
if supports_rename_index?
|
469
|
+
validate_index_length!(table_name, new_name)
|
470
|
+
|
495
471
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
|
496
472
|
else
|
497
473
|
super
|
498
474
|
end
|
499
475
|
end
|
500
476
|
|
501
|
-
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)
|
502
479
|
column = column_for(table_name, column_name)
|
503
480
|
change_column table_name, column_name, column.sql_type, :default => default
|
504
481
|
end
|
505
482
|
|
506
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
483
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
507
484
|
column = column_for(table_name, column_name)
|
508
485
|
|
509
486
|
unless null || default.nil?
|
@@ -523,11 +500,21 @@ module ActiveRecord
|
|
523
500
|
end
|
524
501
|
|
525
502
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
526
|
-
index_name, index_type, index_columns,
|
527
|
-
|
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
|
528
511
|
end
|
529
512
|
|
530
513
|
def foreign_keys(table_name)
|
514
|
+
raise ArgumentError unless table_name.present?
|
515
|
+
|
516
|
+
schema, name = extract_schema_qualified_name(table_name)
|
517
|
+
|
531
518
|
fk_info = select_all <<-SQL.strip_heredoc
|
532
519
|
SELECT fk.referenced_table_name as 'to_table'
|
533
520
|
,fk.referenced_column_name as 'primary_key'
|
@@ -535,11 +522,11 @@ module ActiveRecord
|
|
535
522
|
,fk.constraint_name as 'name'
|
536
523
|
FROM information_schema.key_column_usage fk
|
537
524
|
WHERE fk.referenced_column_name is not null
|
538
|
-
AND fk.table_schema =
|
539
|
-
AND fk.table_name =
|
525
|
+
AND fk.table_schema = #{quote(schema)}
|
526
|
+
AND fk.table_name = #{quote(name)}
|
540
527
|
SQL
|
541
528
|
|
542
|
-
create_table_info =
|
529
|
+
create_table_info = create_table_info(table_name)
|
543
530
|
|
544
531
|
fk_info.map do |row|
|
545
532
|
options = {
|
@@ -555,82 +542,92 @@ module ActiveRecord
|
|
555
542
|
end
|
556
543
|
end
|
557
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
|
+
|
558
560
|
# Maps logical Rails types to MySQL-specific data types.
|
559
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
560
|
-
case type.to_s
|
561
|
-
when 'binary'
|
562
|
-
case limit
|
563
|
-
when 0..0xfff; "varbinary(#{limit})"
|
564
|
-
when nil; "blob"
|
565
|
-
when 0x1000..0xffffffff; "blob(#{limit})"
|
566
|
-
else raise(ActiveRecordError, "No binary type has character length #{limit}")
|
567
|
-
end
|
561
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
|
562
|
+
sql = case type.to_s
|
568
563
|
when 'integer'
|
569
|
-
|
570
|
-
when 1; 'tinyint'
|
571
|
-
when 2; 'smallint'
|
572
|
-
when 3; 'mediumint'
|
573
|
-
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
574
|
-
when 5..8; 'bigint'
|
575
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
576
|
-
end
|
564
|
+
integer_to_sql(limit)
|
577
565
|
when 'text'
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
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)
|
584
574
|
end
|
585
575
|
else
|
586
|
-
super
|
576
|
+
super(type, limit, precision, scale)
|
587
577
|
end
|
578
|
+
|
579
|
+
sql << ' unsigned' if unsigned && type != :primary_key
|
580
|
+
sql
|
588
581
|
end
|
589
582
|
|
590
583
|
# SHOW VARIABLES LIKE 'name'
|
591
584
|
def show_variable(name)
|
592
|
-
|
593
|
-
|
585
|
+
select_value("SELECT @@#{name}", 'SCHEMA')
|
586
|
+
rescue ActiveRecord::StatementInvalid
|
587
|
+
nil
|
594
588
|
end
|
595
589
|
|
596
|
-
|
597
|
-
|
598
|
-
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
|
599
|
-
create_table = each_hash(result).first[:"Create Table"]
|
600
|
-
if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
|
601
|
-
keys = $1.split(",").map { |key| key.delete('`"') }
|
602
|
-
keys.length == 1 ? [keys.first, nil] : nil
|
603
|
-
else
|
604
|
-
nil
|
605
|
-
end
|
606
|
-
end
|
607
|
-
end
|
590
|
+
def primary_keys(table_name) # :nodoc:
|
591
|
+
raise ArgumentError unless table_name.present?
|
608
592
|
|
609
|
-
|
610
|
-
def primary_key(table)
|
611
|
-
pk_and_sequence = pk_and_sequence_for(table)
|
612
|
-
pk_and_sequence && pk_and_sequence.first
|
613
|
-
end
|
593
|
+
schema, name = extract_schema_qualified_name(table_name)
|
614
594
|
|
615
|
-
|
616
|
-
|
617
|
-
|
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
|
618
603
|
end
|
619
604
|
|
620
605
|
def case_sensitive_comparison(table, attribute, column, value)
|
621
|
-
if column.case_sensitive?
|
622
|
-
table[attribute].eq(
|
606
|
+
if !value.nil? && column.collation && !column.case_sensitive?
|
607
|
+
table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
|
623
608
|
else
|
624
609
|
super
|
625
610
|
end
|
626
611
|
end
|
627
612
|
|
628
|
-
def
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
613
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
614
|
+
column.case_sensitive?
|
615
|
+
end
|
616
|
+
private :can_perform_case_insensitive_comparison_for?
|
617
|
+
|
618
|
+
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
619
|
+
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
620
|
+
# distinct queries, and requires that the ORDER BY include the distinct column.
|
621
|
+
# See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
|
622
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
623
|
+
order_columns = orders.reject(&:blank?).map { |s|
|
624
|
+
# Convert Arel node to string
|
625
|
+
s = s.to_sql unless s.is_a?(String)
|
626
|
+
# Remove any ASC/DESC modifiers
|
627
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, '')
|
628
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
629
|
+
|
630
|
+
[super, *order_columns].join(', ')
|
634
631
|
end
|
635
632
|
|
636
633
|
def strict_mode?
|
@@ -658,6 +655,7 @@ module ActiveRecord
|
|
658
655
|
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
659
656
|
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
660
657
|
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
658
|
+
m.register_type %r(^json)i, MysqlJson.new
|
661
659
|
|
662
660
|
register_integer_type m, %r(^bigint)i, limit: 8
|
663
661
|
register_integer_type m, %r(^int)i, limit: 4
|
@@ -665,8 +663,7 @@ module ActiveRecord
|
|
665
663
|
register_integer_type m, %r(^smallint)i, limit: 2
|
666
664
|
register_integer_type m, %r(^tinyint)i, limit: 1
|
667
665
|
|
668
|
-
m.
|
669
|
-
m.alias_type %r(set)i, 'varchar'
|
666
|
+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
|
670
667
|
m.alias_type %r(year)i, 'integer'
|
671
668
|
m.alias_type %r(bit)i, 'binary'
|
672
669
|
|
@@ -675,11 +672,17 @@ module ActiveRecord
|
|
675
672
|
.split(',').map{|enum| enum.strip.length - 2}.max
|
676
673
|
MysqlString.new(limit: limit)
|
677
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
|
678
681
|
end
|
679
682
|
|
680
683
|
def register_integer_type(mapping, key, options) # :nodoc:
|
681
684
|
mapping.register_type(key) do |sql_type|
|
682
|
-
if /
|
685
|
+
if /\bunsigned\z/ === sql_type
|
683
686
|
Type::UnsignedInteger.new(options)
|
684
687
|
else
|
685
688
|
Type::Integer.new(options)
|
@@ -687,15 +690,16 @@ module ActiveRecord
|
|
687
690
|
end
|
688
691
|
end
|
689
692
|
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
693
|
+
def extract_precision(sql_type)
|
694
|
+
if /time/ === sql_type
|
695
|
+
super || 0
|
696
|
+
else
|
697
|
+
super
|
698
|
+
end
|
699
|
+
end
|
695
700
|
|
696
|
-
|
697
|
-
|
698
|
-
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?)
|
699
703
|
end
|
700
704
|
|
701
705
|
def add_index_length(option_strings, column_names, options = {})
|
@@ -703,7 +707,7 @@ module ActiveRecord
|
|
703
707
|
case length
|
704
708
|
when Hash
|
705
709
|
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
|
706
|
-
when
|
710
|
+
when Integer
|
707
711
|
column_names.each {|name| option_strings[name] += "(#{length})"}
|
708
712
|
end
|
709
713
|
end
|
@@ -726,18 +730,20 @@ module ActiveRecord
|
|
726
730
|
def translate_exception(exception, message)
|
727
731
|
case error_number(exception)
|
728
732
|
when 1062
|
729
|
-
RecordNotUnique.new(message
|
733
|
+
RecordNotUnique.new(message)
|
730
734
|
when 1452
|
731
|
-
InvalidForeignKey.new(message
|
735
|
+
InvalidForeignKey.new(message)
|
736
|
+
when 1406
|
737
|
+
ValueTooLong.new(message)
|
732
738
|
else
|
733
739
|
super
|
734
740
|
end
|
735
741
|
end
|
736
742
|
|
737
743
|
def add_column_sql(table_name, column_name, type, options = {})
|
738
|
-
td = create_table_definition
|
744
|
+
td = create_table_definition(table_name)
|
739
745
|
cd = td.new_column_definition(column_name, type, options)
|
740
|
-
schema_creation.
|
746
|
+
schema_creation.accept(AddColumnDefinition.new(cd))
|
741
747
|
end
|
742
748
|
|
743
749
|
def change_column_sql(table_name, column_name, type, options = {})
|
@@ -751,21 +757,23 @@ module ActiveRecord
|
|
751
757
|
options[:null] = column.null
|
752
758
|
end
|
753
759
|
|
754
|
-
|
755
|
-
|
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))
|
756
763
|
end
|
757
764
|
|
758
765
|
def rename_column_sql(table_name, column_name, new_column_name)
|
759
766
|
column = column_for(table_name, column_name)
|
760
767
|
options = {
|
761
|
-
name: new_column_name,
|
762
768
|
default: column.default,
|
763
769
|
null: column.null,
|
764
|
-
auto_increment: column.
|
770
|
+
auto_increment: column.auto_increment?
|
765
771
|
}
|
766
772
|
|
767
773
|
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
768
|
-
|
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))
|
769
777
|
end
|
770
778
|
|
771
779
|
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
@@ -777,8 +785,9 @@ module ActiveRecord
|
|
777
785
|
end
|
778
786
|
|
779
787
|
def add_index_sql(table_name, column_name, options = {})
|
780
|
-
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
781
|
-
|
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}"
|
782
791
|
end
|
783
792
|
|
784
793
|
def remove_index_sql(table_name, options = {})
|
@@ -796,39 +805,57 @@ module ActiveRecord
|
|
796
805
|
|
797
806
|
private
|
798
807
|
|
799
|
-
|
800
|
-
|
801
|
-
|
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]
|
802
813
|
|
803
|
-
|
804
|
-
|
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')
|
805
821
|
end
|
806
822
|
|
807
823
|
def supports_rename_index?
|
808
|
-
mariadb? ? false :
|
824
|
+
mariadb? ? false : version >= '5.7.6'
|
809
825
|
end
|
810
826
|
|
811
827
|
def configure_connection
|
812
828
|
variables = @config.fetch(:variables, {}).stringify_keys
|
813
829
|
|
814
|
-
# By default, MySQL 'where id is null' selects the last inserted id.
|
815
|
-
# 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.
|
816
831
|
variables['sql_auto_is_null'] = 0
|
817
832
|
|
818
833
|
# Increase timeout so the server doesn't disconnect us.
|
819
834
|
wait_timeout = @config[:wait_timeout]
|
820
|
-
wait_timeout = 2147483 unless wait_timeout.is_a?(
|
835
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
821
836
|
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
|
822
837
|
|
838
|
+
defaults = [':default', :default].to_set
|
839
|
+
|
823
840
|
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
824
|
-
# http://dev.mysql.com/doc/refman/5.
|
841
|
+
# http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
|
825
842
|
# If the user has provided another value for sql_mode, don't replace it.
|
826
|
-
|
827
|
-
|
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')"
|
828
854
|
end
|
855
|
+
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
|
829
856
|
|
830
857
|
# NAMES does not have an equals sign, see
|
831
|
-
# http://dev.mysql.com/doc/refman/5.
|
858
|
+
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
|
832
859
|
# (trailing comma because variable_assignments will always have content)
|
833
860
|
if @config[:encoding]
|
834
861
|
encoding = "NAMES #{@config[:encoding]}"
|
@@ -838,7 +865,7 @@ module ActiveRecord
|
|
838
865
|
|
839
866
|
# Gather up all of the SET variables...
|
840
867
|
variable_assignments = variables.map do |k, v|
|
841
|
-
if v
|
868
|
+
if defaults.include?(v)
|
842
869
|
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
843
870
|
elsif !v.nil?
|
844
871
|
"@@SESSION.#{k} = #{quote(v)}"
|
@@ -847,7 +874,13 @@ module ActiveRecord
|
|
847
874
|
end.compact.join(', ')
|
848
875
|
|
849
876
|
# ...and send them all in one query
|
850
|
-
@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
|
851
884
|
end
|
852
885
|
|
853
886
|
def extract_foreign_key_action(structure, name, action) # :nodoc:
|
@@ -859,11 +892,68 @@ module ActiveRecord
|
|
859
892
|
end
|
860
893
|
end
|
861
894
|
|
895
|
+
def create_table_info_cache # :nodoc:
|
896
|
+
@create_table_info_cache ||= {}
|
897
|
+
end
|
898
|
+
|
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)
|
949
|
+
end
|
950
|
+
end
|
951
|
+
|
862
952
|
class MysqlString < Type::String # :nodoc:
|
863
|
-
def
|
953
|
+
def serialize(value)
|
864
954
|
case value
|
865
|
-
when true then
|
866
|
-
when false then
|
955
|
+
when true then MySQL::Quoting::QUOTED_TRUE
|
956
|
+
when false then MySQL::Quoting::QUOTED_FALSE
|
867
957
|
else super
|
868
958
|
end
|
869
959
|
end
|
@@ -872,12 +962,16 @@ module ActiveRecord
|
|
872
962
|
|
873
963
|
def cast_value(value)
|
874
964
|
case value
|
875
|
-
when true then
|
876
|
-
when false then
|
965
|
+
when true then MySQL::Quoting::QUOTED_TRUE
|
966
|
+
when false then MySQL::Quoting::QUOTED_FALSE
|
877
967
|
else super
|
878
968
|
end
|
879
969
|
end
|
880
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)
|
881
975
|
end
|
882
976
|
end
|
883
977
|
end
|