# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 641 def primary_key(table) pk_and_sequence = pk_and_sequence_for(table) pk_and_sequence && pk_and_sequence.first end
class ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
Constants
- INDEX_TYPES
- INDEX_USINGS
- LOST_CONNECTION_ERROR_MESSAGES
- NATIVE_DATABASE_TYPES
- QUOTED_FALSE
Public Class Methods
By default, the MysqlAdapter will consider
all columns of type tinyint(1)
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 to your application.rb file:
ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 139 class_attribute :emulate_booleans
FIXME: Make the first parameter more similar for the two adapters
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 168 def initialize(connection, logger, connection_options, config) super(connection, logger) @connection_options, @config = connection_options, config @quoted_column_names, @quoted_table_names = {}, {} @visitor = Arel::Visitors::MySQL.new self if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true else @prepared_statements = false end end
Public Instance Methods
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 332 def begin_db_transaction execute "BEGIN" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 336 def begin_isolated_db_transaction(isolation) execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" begin_db_transaction end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 659 def case_insensitive_comparison(table, attribute, column, value) if column.case_sensitive? super else table[attribute].eq(value) end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 651 def case_sensitive_comparison(table, attribute, column, value) if column.case_sensitive? table[attribute].eq(value) else super end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 646 def case_sensitive_modifier(node, table_attribute) node = Arel::Nodes.build_quoted node, table_attribute Arel::Nodes::Bin.new(node) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 528 def change_column_null(table_name, column_name, null, default = nil) 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 end
Returns the database character set.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 404 def charset show_variable 'character_set_database' end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 310 def clear_cache! super reload_type_map end
Returns the database collation strategy.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 409 def collation show_variable 'collation_database' end
Create a new MySQL database with optional :charset
and
:collation
. Charset defaults to utf8.
Example:
create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin' create_database 'matt_development' create_database 'matt_development', charset: :big5
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 383 def create_database(name, options = {}) if options[:collation] execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`" else execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`" end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 399 def current_database select_value 'SELECT DATABASE() as db' end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 509 def drop_table(table_name, options = {}) execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 361 def empty_insert_statement_value "VALUES ()" end
Executes the SQL statement in the context of this connection.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 316 def execute(sql, name = nil) log(sql, name) { @connection.query(sql) } end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 552 def foreign_keys(table_name) fk_info = select_all " SELECT fk.referenced_table_name as 'to_table' ,fk.referenced_column_name as 'primary_key' ,fk.column_name as 'column' ,fk.constraint_name as 'name' FROM information_schema.key_column_usage fk WHERE fk.referenced_column_name is not null AND fk.table_schema = '#{@config[:database]}' AND fk.table_name = '#{table_name}' ".strip_heredoc create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] fk_info.map do |row| options = { column: row['column'], name: row['name'], primary_key: row['primary_key'] } options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE") options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE") ForeignKeyDefinition.new(table_name, row['to_table'], options) end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 229 def index_algorithms { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' } end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 225 def native_database_types NATIVE_DATABASE_TYPES end
Returns a table's primary key and belonging sequence.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 628 def pk_and_sequence_for(table) execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result| create_table = each_hash(result).first[:"Create Table"] if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/ keys = $1.split(",").map { |key| key.delete('`"') } keys.length == 1 ? [keys.first, nil] : nil else nil end end end
Returns just a table's primary key
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 285 def quoted_date(value) if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec) "#{super}.#{sprintf("%06d", value.usec)}" else super end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 277 def quoted_false QUOTED_FALSE end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 269 def quoted_true QUOTED_TRUE end
Drops the database specified on the name
attribute and creates
it again using the provided options
.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 369 def recreate_database(name, options = {}) drop_database(name) sql = create_database(name, options) reconnect! sql end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 513 def rename_index(table_name, old_name, new_name) if supports_rename_index? validate_index_length!(table_name, new_name) execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}" else super end end
Renames a table.
Example:
rename_table('octopuses', 'octopi')
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 504 def rename_table(table_name, new_name) execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" rename_table_indexes(table_name, new_name) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 57 def schema_creation SchemaCreation.new self end
SHOW VARIABLES LIKE 'name'
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 620 def show_variable(name) variables = select_all("select @@#{name} as 'Value'", 'SCHEMA') variables.first['Value'] unless variables.empty? rescue ActiveRecord::StatementInvalid nil end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 682 def strict_mode? self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 221 def supports_datetime_with_precision? version >= '5.6.4' end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 213 def supports_foreign_keys? true end
Technically MySQL allows to create indexes with the sort order syntax but at the moment (5.5) it doesn't yet implement them
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 197 def supports_index_sort_order? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 209 def supports_indexes_in_create? true end
Returns true, since this connection adapter supports migrations.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 183 def supports_migrations? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 187 def supports_primary_key? true end
MySQL 4 technically support transaction isolation, but it is affected by a bug where the transaction level gets persisted for the whole session:
bugs.mysql.com/bug.php?id=39170
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 205 def supports_transaction_isolation? version >= '5.0.0' end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 217 def supports_views? version >= '5.0.0' end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 428 def table_exists?(name) return false unless name.present? return true if tables(nil, nil, name).any? name = name.to_s schema, table = name.split('.', 2) unless table # A table was provided without a schema table = schema schema = nil end tables(nil, schema, table).any? end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 424 def truncate(table_name, name = nil) execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name end
Maps logical Rails types to MySQL-specific data types.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 581 def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s when 'binary' case limit when 0..0xfff; "varbinary(#{limit})" when nil; "blob" when 0x1000..0xffffffff; "blob(#{limit})" else raise(ActiveRecordError, "No binary type has character length #{limit}") end when 'integer' case limit when 1; 'tinyint' when 2; 'smallint' when 3; 'mediumint' when nil, 4, 11; 'int(11)' # compatibility with MySQL default when 5..8; 'bigint' else raise(ActiveRecordError, "No integer type has byte size #{limit}") end when 'text' case limit when 0..0xff; 'tinytext' when nil, 0x100..0xffff; 'text' when 0x10000..0xffffff; 'mediumtext' when 0x1000000..0xffffffff; 'longtext' else raise(ActiveRecordError, "No text type has character length #{limit}") end when 'datetime' return super unless precision case precision when 0..6; "datetime(#{precision})" else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.") end else super end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 281 def unquoted_false 0 end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 273 def unquoted_true 1 end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 686 def valid_type?(type) !native_database_types[type].nil? end
Protected Instance Methods
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 792 def add_column_sql(table_name, column_name, type, options = {}) td = create_table_definition table_name, options[:temporary], options[:options] cd = td.new_column_definition(column_name, type, options) schema_creation.visit_AddColumn cd end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 756 def add_index_length(option_strings, column_names, options = {}) if options.is_a?(Hash) && length = options[:length] case length when Hash column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?} when Integer column_names.each {|name| option_strings[name] += "(#{length})"} end end return option_strings end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 834 def add_index_sql(table_name, column_name, options = {}) index_name, index_type, index_columns = add_index_options(table_name, column_name, options) "ADD #{index_type} INDEX #{index_name} (#{index_columns})" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 844 def add_timestamps_sql(table_name, options = {}) [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 798 def change_column_sql(table_name, column_name, type, options = {}) column = column_for(table_name, column_name) unless options_include_default?(options) options[:default] = column.default end unless options.has_key?(:null) options[:null] = column.null end options[:name] = column.name schema_creation.accept ChangeColumnDefinition.new column, type, options end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 769 def quoted_columns_for_index(column_names, options = {}) option_strings = Hash[column_names.map {|name| [name, '']}] # add index length option_strings = add_index_length(option_strings, column_names, options) # add index sort order option_strings = add_index_sort_order(option_strings, column_names, options) column_names.map {|name| quote_column_name(name) + option_strings[name]} end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 826 def remove_column_sql(table_name, column_name, type = nil, options = {}) "DROP #{quote_column_name(column_name)}" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 830 def remove_columns_sql(table_name, *column_names) column_names.map {|column_name| remove_column_sql(table_name, column_name) } end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 839 def remove_index_sql(table_name, options = {}) index_name = index_name_for_remove(table_name, options) "DROP INDEX #{index_name}" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 848 def remove_timestamps_sql(table_name, options = {}) [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 813 def rename_column_sql(table_name, column_name, new_column_name) column = column_for(table_name, column_name) options = { name: new_column_name, default: column.default, null: column.null, auto_increment: column.extra == "auto_increment" } current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"] schema_creation.accept ChangeColumnDefinition.new column, current_type, options end
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!
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 743 def subquery_for(key, select) 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) subselect.from subsubselect.as('__active_record_temp') end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 781 def translate_exception(exception, message) case error_number(exception) when 1062 RecordNotUnique.new(message, exception) when 1452 InvalidForeignKey.new(message, exception) else super end end
Private Instance Methods
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 866 def configure_connection variables = @config.fetch(:variables, {}).stringify_keys # By default, MySQL 'where id is null' selects the last inserted id. # Turn this off. http://dev.rubyonrails.org/ticket/6778 variables['sql_auto_is_null'] = 0 # Increase timeout so the server doesn't disconnect us. wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout]) wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) variables["wait_timeout"] = wait_timeout # Make MySQL reject illegal values rather than truncating or blanking them, see # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables # If the user has provided another value for sql_mode, don't replace it. unless variables.has_key?('sql_mode') variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : '' end # NAMES does not have an equals sign, see # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430 # (trailing comma because variable_assignments will always have content) if @config[:encoding] encoding = "NAMES #{@config[:encoding]}" encoding << " COLLATE #{@config[:collation]}" if @config[:collation] encoding << ", " end # Gather up all of the SET variables... variable_assignments = variables.map do |k, v| if v == ':default' || v == :default "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default elsif !v.nil? "@@SESSION.#{k} = #{quote(v)}" end # or else nil; compact to clear nils out end.compact.join(', ') # ...and send them all in one query @connection.query "SET #{encoding} #{variable_assignments}" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 858 def mariadb? full_version =~ /mariadb/i end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 862 def supports_rename_index? mariadb? ? false : version >= '5.7.6' end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 854 def version @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0]) end