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.rb126
1 files changed, 69 insertions, 57 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 c742799aab..ca651ef390 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_record/connection_adapters/abstract_adapter"
require "active_record/connection_adapters/statement_pool"
require "active_record/connection_adapters/mysql/column"
@@ -15,13 +17,8 @@ module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
include MySQL::Quoting
- include MySQL::ColumnDumper
include MySQL::SchemaStatements
- def update_table_definition(table_name, base) # :nodoc:
- MySQL::Table.new(table_name, base)
- end
-
##
# :singleton-method:
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
@@ -29,15 +26,14 @@ module ActiveRecord
# to your application.rb file:
#
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
- class_attribute :emulate_booleans
- self.emulate_booleans = true
+ class_attribute :emulate_booleans, default: true
NATIVE_DATABASE_TYPES = {
primary_key: "bigint auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
text: { name: "text", limit: 65535 },
integer: { name: "int", limit: 4 },
- float: { name: "float" },
+ float: { name: "float", limit: 24 },
decimal: { name: "decimal" },
datetime: { name: "datetime" },
timestamp: { name: "timestamp" },
@@ -60,12 +56,12 @@ module ActiveRecord
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
if version < "5.1.10"
- raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.1.10."
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
end
end
def version #:nodoc:
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
+ @version ||= Version.new(version_string)
end
def mariadb? # :nodoc:
@@ -76,12 +72,6 @@ module ActiveRecord
true
end
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
def supports_index_sort_order?
!mariadb? && version >= "8.0.1"
end
@@ -127,11 +117,11 @@ module ActiveRecord
end
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
- select_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
+ query_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
end
def release_advisory_lock(lock_name) # :nodoc:
- select_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
end
def native_database_types
@@ -139,7 +129,7 @@ module ActiveRecord
end
def index_algorithms
- { default: "ALGORITHM = DEFAULT", copy: "ALGORITHM = COPY", inplace: "ALGORITHM = INPLACE" }
+ { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
end
# HELPER METHODS ===========================================
@@ -159,7 +149,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
begin
update("SET FOREIGN_KEY_CHECKS = 0")
@@ -274,7 +264,7 @@ module ActiveRecord
end
def current_database
- select_value "SELECT DATABASE() as db"
+ query_value("SELECT database()", "SCHEMA")
end
# Returns the database character set.
@@ -294,7 +284,7 @@ module ActiveRecord
def table_comment(table_name) # :nodoc:
scope = quoted_scope(table_name)
- select_value(<<-SQL.strip_heredoc, "SCHEMA")
+ query_value(<<-SQL.strip_heredoc, "SCHEMA").presence
SELECT table_comment
FROM information_schema.tables
WHERE table_schema = #{scope[:schema]}
@@ -321,6 +311,11 @@ module ActiveRecord
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
end
+ def change_table_comment(table_name, comment) #:nodoc:
+ comment = "" if comment.nil?
+ execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
+ end
+
# Renames a table.
#
# Example:
@@ -361,18 +356,19 @@ module ActiveRecord
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
default = extract_new_default_value(default_or_changes)
- column = column_for(table_name, column_name)
- change_column table_name, column_name, column.sql_type, default: default
+ change_column table_name, column_name, nil, default: default
end
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
- column = column_for(table_name, column_name)
-
unless null || default.nil?
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
- change_column table_name, column_name, column.sql_type, null: null
+ change_column table_name, column_name, nil, null: null
+ end
+
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
+ change_column table_name, column_name, nil, comment: comment
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
@@ -386,7 +382,7 @@ module ActiveRecord
def add_index(table_name, column_name, options = {}) #:nodoc:
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
- sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
execute add_sql_comment!(sql, comment)
end
@@ -400,7 +396,7 @@ module ActiveRecord
scope = quoted_scope(table_name)
- fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA")
+ fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
SELECT fk.referenced_table_name AS 'to_table',
fk.referenced_column_name AS 'primary_key',
fk.column_name AS 'column',
@@ -471,13 +467,13 @@ module ActiveRecord
super
end
- sql << " unsigned" if unsigned && type != :primary_key
+ sql = "#{sql} unsigned" if unsigned && type != :primary_key
sql
end
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- select_value("SELECT @@#{name}", "SCHEMA")
+ query_value("SELECT @@#{name}", "SCHEMA")
rescue ActiveRecord::StatementInvalid
nil
end
@@ -487,7 +483,7 @@ module ActiveRecord
scope = quoted_scope(table_name)
- select_values(<<-SQL.strip_heredoc, "SCHEMA")
+ query_values(<<-SQL.strip_heredoc, "SCHEMA")
SELECT column_name
FROM information_schema.key_column_usage
WHERE constraint_name = 'PRIMARY'
@@ -533,9 +529,26 @@ module ActiveRecord
index.using == :btree || super
end
+ def insert_fixtures(*)
+ without_sql_mode("NO_AUTO_VALUE_ON_ZERO") { super }
+ end
+
private
- def initialize_type_map(m)
+ def without_sql_mode(mode)
+ result = execute("SELECT @@SESSION.sql_mode")
+ current_mode = result.first[0]
+ return yield unless current_mode.include?(mode)
+
+ sql_mode = "REPLACE(@@sql_mode, '#{mode}', '')"
+ execute("SET @@SESSION.sql_mode = #{sql_mode}")
+ yield
+ ensure
+ sql_mode = "CONCAT(@@sql_mode, ',#{mode}')"
+ execute("SET @@SESSION.sql_mode = #{sql_mode}")
+ end
+
+ def initialize_type_map(m = type_map)
super
register_class_with_limit m, %r(char)i, MysqlString
@@ -550,7 +563,6 @@ module ActiveRecord
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
m.register_type %r(^float)i, Type::Float.new(limit: 24)
m.register_type %r(^double)i, Type::Float.new(limit: 53)
- m.register_type %r(^json)i, MysqlJson.new
register_integer_type m, %r(^bigint)i, limit: 8
register_integer_type m, %r(^int)i, limit: 4
@@ -622,6 +634,8 @@ module ActiveRecord
ER_LOCK_DEADLOCK = 1213
ER_CANNOT_ADD_FOREIGN = 1215
ER_CANNOT_CREATE_TABLE = 1005
+ ER_LOCK_WAIT_TIMEOUT = 1205
+ ER_QUERY_TIMEOUT = 3024
def translate_exception(exception, message)
case error_number(exception)
@@ -645,6 +659,10 @@ module ActiveRecord
NotNullViolation.new(message)
when ER_LOCK_DEADLOCK
Deadlocked.new(message)
+ when ER_LOCK_WAIT_TIMEOUT
+ TransactionTimeout.new(message)
+ when ER_QUERY_TIMEOUT
+ StatementTimeout.new(message)
else
super
end
@@ -658,6 +676,7 @@ module ActiveRecord
def change_column_sql(table_name, column_name, type, options = {})
column = column_for(table_name, column_name)
+ type ||= column.sql_type
unless options.key?(:default)
options[:default] = column.default
@@ -684,7 +703,7 @@ module ActiveRecord
auto_increment: column.auto_increment?
}
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", "SCHEMA")["Type"]
+ current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
td = create_table_definition(table_name)
cd = td.new_column_definition(new_column_name, current_type, options)
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -706,7 +725,7 @@ module ActiveRecord
def remove_index_sql(table_name, options = {})
index_name = index_name_for_remove(table_name, options)
- "DROP INDEX #{index_name}"
+ "DROP INDEX #{quote_column_name(index_name)}"
end
def add_timestamps_sql(table_name, options = {})
@@ -720,16 +739,14 @@ module ActiveRecord
# MySQL is too stupid to create a temporary table for use subquery, so we have
# to give it some prompting in the form of a subsubquery. Ugh!
def subquery_for(key, select)
- subsubselect = select.clone
- subsubselect.projections = [key]
+ subselect = select.clone
+ subselect.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.distinct unless select.limit || select.offset || select.orders.any?
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(key.name)
- subselect.from subsubselect.as("__active_record_temp")
+ Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key.name))
end
def supports_rename_index?
@@ -750,7 +767,7 @@ module ActiveRecord
defaults = [":default", :default].to_set
# Make MySQL reject illegal values rather than truncating or blanking them, see
- # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
+ # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
# If the user has provided another value for sql_mode, don't replace it.
if sql_mode = variables.delete("sql_mode")
sql_mode = quote(sql_mode)
@@ -767,10 +784,10 @@ module ActiveRecord
sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
# NAMES does not have an equals sign, see
- # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
+ # https://dev.mysql.com/doc/refman/5.7/en/set-names.html
# (trailing comma because variable_assignments will always have content)
if @config[:encoding]
- encoding = "NAMES #{@config[:encoding]}"
+ encoding = "NAMES #{@config[:encoding]}".dup
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
encoding << ", "
end
@@ -796,7 +813,7 @@ module ActiveRecord
end
def create_table_info(table_name) # :nodoc:
- select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
end
def arel_visitor
@@ -846,19 +863,15 @@ module ActiveRecord
end
end
- class MysqlJson < Type::Internal::AbstractJson # :nodoc:
- def changed_in_place?(raw_old_value, new_value)
- # Normalization is required because MySQL JSON data format includes
- # the space between the elements.
- super(serialize(deserialize(raw_old_value)), new_value)
- end
+ def version_string
+ full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
end
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
- when true then MySQL::Quoting::QUOTED_TRUE
- when false then MySQL::Quoting::QUOTED_FALSE
+ when true then "1"
+ when false then "0"
else super
end
end
@@ -867,14 +880,13 @@ module ActiveRecord
def cast_value(value)
case value
- when true then MySQL::Quoting::QUOTED_TRUE
- when false then MySQL::Quoting::QUOTED_FALSE
+ when true then "1"
+ when false then "0"
else super
end
end
end
- ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
end