module ActiveRecord::ConnectionAdapters
Constants
- CHARSETS_OF_4BYTES_MAXLEN
Public Class Methods
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 65 def initialize(connection, logger, connection_options, config) super(connection, logger, config) @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) if version < '5.0.0' raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0." end end
Public Instance Methods
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 228 def begin_db_transaction execute "BEGIN" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 232 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 615 def case_sensitive_comparison(table, attribute, column, value) if !value.nil? && column.collation && !column.case_sensitive? table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new)) else super end end
Returns the database character set.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 300 def charset show_variable 'character_set_database' end
Clears the prepared statements cache.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 198 def clear_cache! reload_type_map @statements.clear end
Returns the database collation strategy.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 305 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 279 def create_database(name, options = {}) if options[:collation] execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}" else execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}" end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 295 def current_database select_value 'SELECT DATABASE() as db' end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 347 def data_source_exists?(table_name) return false unless table_name.present? schema, name = extract_schema_qualified_name(table_name) sql = "SELECT table_name FROM information_schema.tables " sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}" select_values(sql, 'SCHEMA').any? end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 325 def data_sources sql = "SELECT table_name FROM information_schema.tables " sql << "WHERE table_schema = #{quote(@config[:database])}" select_values(sql, 'SCHEMA') end
Drops a table from the database.
:force-
Set to
:cascadeto drop dependent objects as well. Defaults to false. :if_exists-
Set to
trueto only drop the table if it exists. Defaults to false. :temporary-
Set to
trueto drop temporary table. Defaults to false.
Although this command ignores most options and the block if
one is given, 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.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 465 def drop_table(table_name, options = {}) execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 257 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 217 def execute(sql, name = nil) log(sql, name) { @connection.query(sql) } end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 207 def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" start = Time.now result = exec_query(sql, 'EXPLAIN', binds) elapsed = Time.now - start MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 515 def foreign_keys(table_name) raise ArgumentError unless table_name.present? schema, name = extract_schema_qualified_name(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', rc.update_rule AS 'on_update', rc.delete_rule AS 'on_delete' FROM information_schema.key_column_usage fk JOIN information_schema.referential_constraints rc USING (constraint_schema, constraint_name) WHERE fk.referenced_column_name IS NOT NULL AND fk.table_schema = #{quote(schema)} AND fk.table_name = #{quote(name)} ".strip_heredoc, 'SCHEMA') fk_info.map do |row| options = { column: row['column'], name: row['name'], primary_key: row['primary_key'] } options[:on_update] = extract_foreign_key_action(row['on_update']) options[:on_delete] = extract_foreign_key_action(row['on_delete']) ForeignKeyDefinition.new(table_name, row['to_table'], options) end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 160 def index_algorithms { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' } end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 156 def native_database_types NATIVE_DATABASE_TYPES 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 265 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 469 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 445 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
SHOW VARIABLES LIKE 'name'
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 594 def show_variable(name) select_value("SELECT @@#{name}", 'SCHEMA') rescue ActiveRecord::StatementInvalid nil end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 643 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 144 def supports_advisory_locks? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 136 def supports_datetime_with_precision? if mariadb? version >= '5.3.0' else version >= '5.6.4' end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 120 def supports_explain? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 128 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 112 def supports_index_sort_order? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 124 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 92 def supports_migrations? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 96 def supports_primary_key? true end
Returns true, since this connection adapter supports prepared statement caching.
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 106 def supports_statement_cache? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 116 def supports_transaction_isolation? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 132 def supports_views? true end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 336 def table_exists?(table_name) # Update lib/active_record/internal_metadata.rb when this gets removed ActiveSupport::Deprecation.warn(" #table_exists? currently checks both tables and views. This behavior is deprecated and will be changed with Rails 5.1 to only check tables. Use #data_source_exists? instead. ".squish) data_source_exists?(table_name) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 332 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 571 def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil) sql = case type.to_s when 'integer' integer_to_sql(limit) when 'text' text_to_sql(limit) when 'blob' binary_to_sql(limit) when 'binary' if (0..0xfff) === limit "varbinary(#{limit})" else binary_to_sql(limit) end else super(type, limit, precision, scale) end sql << ' unsigned' if unsigned && type != :primary_key sql end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 647 def valid_type?(type) !native_database_types[type].nil? end
Protected Instance Methods
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 747 def add_column_sql(table_name, column_name, type, options = {}) td = create_table_definition(table_name) cd = td.new_column_definition(column_name, type, options) schema_creation.accept(AddColumnDefinition.new(cd)) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 715 def add_index_length(quoted_columns, **options) if length = options[:length] case length when Hash length = length.symbolize_keys quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? } when Integer quoted_columns.each { |name, column| column << "(#{length})" } end end quoted_columns end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 791 def add_index_sql(table_name, column_name, options = {}) index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) index_algorithm[0, 0] = ", " if index_algorithm.present? "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 729 def add_options_for_index_columns(quoted_columns, **options) quoted_columns = add_index_length(quoted_columns, options) super end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 802 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 753 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 td = create_table_definition(table_name) cd = td.new_column_definition(column.name, type, options) schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 703 def extract_precision(sql_type) if /time/ === sql_type super || 0 else super end end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 711 def fetch_type_metadata(sql_type, extra = "") MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 783 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 787 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 797 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 806 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 769 def rename_column_sql(table_name, column_name, new_column_name) column = column_for(table_name, column_name) options = { default: column.default, null: column.null, auto_increment: column.auto_increment? } current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["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)) end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 734 def translate_exception(exception, message) case error_number(exception) when 1062 RecordNotUnique.new(message) when 1452 InvalidForeignKey.new(message) when 1406 ValueTooLong.new(message) else super end end
Private Instance Methods
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 623 def can_perform_case_insensitive_comparison_for?(column) column.case_sensitive? end
# File lib/active_record/connection_adapters/abstract_mysql_adapter.rb, line 831 def configure_connection variables = @config.fetch(:variables, {}).stringify_keys # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. variables['sql_auto_is_null'] = 0 # Increase timeout so the server doesn't disconnect us. wait_timeout = @config[:wait_timeout] wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout) 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 # 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) elsif !defaults.include?(strict_mode?) if strict_mode? sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" else sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" end sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" end 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 # (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 defaults.include?(v) "@@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} #{sql_mode_assignment} #{variable_assignments}" 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 814 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 827 def supports_rename_index? mariadb? ? false : version >= '5.7.6' end