aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb168
1 files changed, 50 insertions, 118 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index f8c9e13392..3e84786be0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,7 +1,9 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/mysql/column'
require 'active_record/connection_adapters/mysql/schema_creation'
require 'active_record/connection_adapters/mysql/schema_definitions'
require 'active_record/connection_adapters/mysql/schema_dumper'
+require 'active_record/connection_adapters/mysql/type_metadata'
require 'active_support/core_ext/string/strip'
@@ -19,99 +21,13 @@ module ActiveRecord
MySQL::SchemaCreation.new(self)
end
- class Column < ConnectionAdapters::Column # :nodoc:
- delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
-
- def initialize(*)
- super
- assert_valid_default(default)
- extract_default
- end
-
- def extract_default
- if blob_or_text_column?
- @default = null || strict ? nil : ''
- elsif missing_default_forged_as_empty_string?(default)
- @default = nil
- end
- end
-
- def has_default?
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
- super
- end
-
- def blob_or_text_column?
- sql_type =~ /blob/i || type == :text
- end
-
- def unsigned?
- /unsigned/ === sql_type
- end
-
- def case_sensitive?
- collation && !collation.match(/_ci$/)
- end
-
- def auto_increment?
- extra == 'auto_increment'
- end
-
- private
-
- # MySQL misreports NOT NULL column default when none is given.
- # We can't detect this for columns which may have a legitimate ''
- # default (string) but we can for others (integer, datetime, boolean,
- # and the rest).
- #
- # Test whether the column has default '', is not null, and is not
- # a type allowing default ''.
- def missing_default_forged_as_empty_string?(default)
- type != :string && !null && default == ''
- end
-
- def assert_valid_default(default)
- if blob_or_text_column? && default.present?
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- end
- end
-
- class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
- attr_reader :extra, :strict
-
- def initialize(type_metadata, extra: "", strict: false)
- super(type_metadata)
- @type_metadata = type_metadata
- @extra = extra
- @strict = strict
- end
-
- def ==(other)
- other.is_a?(MysqlTypeMetadata) &&
- attributes_for_hash == other.attributes_for_hash
- end
- alias eql? ==
-
- def hash
- attributes_for_hash.hash
- end
-
- protected
-
- def attributes_for_hash
- [self.class, @type_metadata, extra, strict]
- end
- end
-
##
# :singleton-method:
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
- # as boolean. If you wish to disable this emulation (which was the default
- # behavior in versions 0.13.1 and earlier) you can add the following line
+ # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
+ # as boolean. If you wish to disable this emulation you can add the following line
# to your application.rb file:
#
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
class_attribute :emulate_booleans
self.emulate_booleans = true
@@ -248,7 +164,7 @@ module ActiveRecord
end
def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- Column.new(field, default, sql_type_metadata, null, default_function, collation)
+ MySQL::Column.new(field, default, sql_type_metadata, null, default_function, collation)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -400,18 +316,13 @@ module ActiveRecord
log(sql, name) { @connection.query(sql) }
end
- # MysqlAdapter has to free a result after using it, so we use this method to write
- # stuff in an abstract way without concerning ourselves about whether it needs to be
- # explicitly freed or not.
- def execute_and_free(sql, name = nil) #:nodoc:
+ # Mysql2Adapter doesn't have to free a result after using it, but we use this method
+ # to write stuff in an abstract way without concerning ourselves about whether it
+ # needs to be explicitly freed or not.
+ def execute_and_free(sql, name = nil) # :nodoc:
yield execute(sql, name)
end
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.affected_rows
- end
-
def begin_db_transaction
execute "BEGIN"
end
@@ -432,7 +343,7 @@ module ActiveRecord
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
# these, we must use a subquery.
- def join_to_update(update, select) #:nodoc:
+ def join_to_update(update, select, key) # :nodoc:
if select.limit || select.offset || select.orders.any?
super
else
@@ -521,6 +432,7 @@ module ActiveRecord
end
def table_exists?(table_name)
+ # Update lib/active_record/internal_metadata.rb when this gets removed
ActiveSupport::Deprecation.warn(<<-MSG.squish)
#table_exists? currently checks both tables and views.
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
@@ -583,12 +495,16 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name)#:nodoc:
+ def columns(table_name) # :nodoc:
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
+ else
+ new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
+ end
end
end
end
@@ -637,6 +553,7 @@ module ActiveRecord
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
# In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
+ create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
@@ -765,16 +682,11 @@ module ActiveRecord
SQL
end
- def case_sensitive_modifier(node, table_attribute)
- node = Arel::Nodes.build_quoted node, table_attribute
- Arel::Nodes::Bin.new(node)
- end
-
def case_sensitive_comparison(table, attribute, column, value)
- if column.case_sensitive?
- table[attribute].eq(value)
- else
+ if value.nil? || column.case_sensitive?
super
+ else
+ table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
end
end
@@ -782,10 +694,25 @@ module ActiveRecord
if column.case_sensitive?
super
else
- table[attribute].eq(value)
+ table[attribute].eq(Arel::Nodes::BindParam.new)
end
end
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
+ # distinct queries, and requires that the ORDER BY include the distinct column.
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
+ def columns_for_distinct(columns, orders) # :nodoc:
+ order_columns = orders.reject(&:blank?).map { |s|
+ # Convert Arel node to string
+ s = s.to_sql unless s.is_a?(String)
+ # Remove any ASC/DESC modifiers
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
+
+ [super, *order_columns].join(', ')
+ end
+
def strict_mode?
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
@@ -838,7 +765,7 @@ module ActiveRecord
def register_integer_type(mapping, key, options) # :nodoc:
mapping.register_type(key) do |sql_type|
- if /unsigned/i =~ sql_type
+ if /\bunsigned\z/ === sql_type
Type::UnsignedInteger.new(options)
else
Type::Integer.new(options)
@@ -855,7 +782,7 @@ module ActiveRecord
end
def fetch_type_metadata(sql_type, extra = "")
- MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
end
def add_index_length(option_strings, column_names, options = {})
@@ -965,11 +892,13 @@ module ActiveRecord
subsubselect = select.clone
subsubselect.projections = [key]
+ # Materialize subquery by adding distinct
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
+ subsubselect.distinct unless select.limit || select.offset || select.orders.any?
+
subselect = Arel::SelectManager.new(select.engine)
subselect.project Arel.sql(key.name)
- # Materialized subquery by adding distinct
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
- subselect.from subsubselect.distinct.as('__active_record_temp')
+ subselect.from subsubselect.as('__active_record_temp')
end
def mariadb?
@@ -1032,9 +961,12 @@ module ActiveRecord
end
end
+ def create_table_info_cache # :nodoc:
+ @create_table_info_cache ||= {}
+ end
+
def create_table_info(table_name) # :nodoc:
- @create_table_info_cache = {}
- @create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: