aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--activerecord/Rakefile14
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb639
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb125
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb176
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb9
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb28
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb34
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb12
-rw-r--r--activerecord/test/cases/query_cache_test.rb2
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb4
-rw-r--r--activerecord/test/cases/timestamp_test.rb1
-rw-r--r--activerecord/test/connections/native_mysql2/connection.rb25
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb24
20 files changed, 1110 insertions, 36 deletions
diff --git a/Gemfile b/Gemfile
index 71dc1f3e99..d594d20fac 100644
--- a/Gemfile
+++ b/Gemfile
@@ -35,6 +35,7 @@ platforms :ruby do
group :db do
gem "pg", ">= 0.9.0"
gem "mysql", ">= 2.8.1"
+ gem "mysql2", :git => 'git://github.com/brianmario/mysql2.git'
end
end
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 36cd7e3e6c..c1e90cc099 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -24,14 +24,14 @@ def run_without_aborting(*tasks)
abort "Errors running #{errors.join(', ')}" if errors.any?
end
-desc 'Run mysql, sqlite, and postgresql tests by default'
+desc 'Run mysql, mysql2, sqlite, and postgresql tests by default'
task :default => :test
-desc 'Run mysql, sqlite, and postgresql tests'
+desc 'Run mysql, mysql2, sqlite, and postgresql tests'
task :test do
tasks = defined?(JRUBY_VERSION) ?
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
- %w(test_mysql test_sqlite3 test_postgresql)
+ %w(test_mysql test_mysql2 test_sqlite3 test_postgresql)
run_without_aborting(*tasks)
end
@@ -39,15 +39,15 @@ namespace :test do
task :isolated do
tasks = defined?(JRUBY_VERSION) ?
%w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
- %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql)
+ %w(isolated_test_mysql isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql)
run_without_aborting(*tasks)
end
end
-%w( mysql postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
+%w( mysql mysql2 postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
Rake::TestTask.new("test_#{adapter}") { |t|
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
- adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
t.libs << "test" << connection_path
t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
|x| x =~ /\/adapters\//
@@ -59,7 +59,7 @@ end
task "isolated_test_#{adapter}" do
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
- adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
puts [adapter, adapter_short, connection_path].inspect
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
(Dir["test/cases/**/*_test.rb"].reject {
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
new file mode 100644
index 0000000000..568759775b
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -0,0 +1,639 @@
+# encoding: utf-8
+
+require 'mysql2' unless defined? Mysql2
+
+module ActiveRecord
+ class Base
+ def self.mysql2_connection(config)
+ config[:username] = 'root' if config[:username].nil?
+ client = Mysql2::Client.new(config.symbolize_keys)
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
+ end
+ end
+
+ module ConnectionAdapters
+ class Mysql2Column < Column
+ BOOL = "tinyint(1)"
+ def extract_default(default)
+ if sql_type =~ /blob/i || type == :text
+ if default.blank?
+ return null ? nil : ''
+ else
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ elsif missing_default_forged_as_empty_string?(default)
+ nil
+ else
+ super
+ end
+ end
+
+ def has_default?
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
+ super
+ end
+
+ # Returns the Ruby class that corresponds to the abstract data type.
+ def klass
+ case type
+ when :integer then Fixnum
+ when :float then Float
+ when :decimal then BigDecimal
+ when :datetime then Time
+ when :date then Date
+ when :timestamp then Time
+ when :time then Time
+ when :text, :string then String
+ when :binary then String
+ when :boolean then Object
+ end
+ end
+
+ def type_cast(value)
+ return nil if value.nil?
+ case type
+ when :string then value
+ when :text then value
+ when :integer then value.to_i rescue value ? 1 : 0
+ when :float then value.to_f # returns self if it's already a Float
+ when :decimal then self.class.value_to_decimal(value)
+ when :datetime, :timestamp then value.class == Time ? value : self.class.string_to_time(value)
+ when :time then value.class == Time ? value : self.class.string_to_dummy_time(value)
+ when :date then value.class == Date ? value : self.class.string_to_date(value)
+ when :binary then value
+ when :boolean then self.class.value_to_boolean(value)
+ else value
+ end
+ end
+
+ def type_cast_code(var_name)
+ case type
+ when :string then nil
+ when :text then nil
+ when :integer then "#{var_name}.to_i rescue #{var_name} ? 1 : 0"
+ when :float then "#{var_name}.to_f"
+ when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
+ when :datetime, :timestamp then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_time(#{var_name})"
+ when :time then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_dummy_time(#{var_name})"
+ when :date then "#{var_name}.class == Date ? #{var_name} : #{self.class.name}.string_to_date(#{var_name})"
+ when :binary then nil
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
+ else nil
+ end
+ end
+
+ private
+ def simplified_type(field_type)
+ return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
+ return :string if field_type =~ /enum/i or field_type =~ /set/i
+ return :integer if field_type =~ /year/i
+ return :binary if field_type =~ /bit/i
+ super
+ end
+
+ def extract_limit(sql_type)
+ case sql_type
+ when /blob|text/i
+ case sql_type
+ when /tiny/i
+ 255
+ when /medium/i
+ 16777215
+ when /long/i
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
+ else
+ super # we could return 65535 here, but we leave it undecorated by default
+ end
+ when /^bigint/i; 8
+ when /^int/i; 4
+ when /^mediumint/i; 3
+ when /^smallint/i; 2
+ when /^tinyint/i; 1
+ else
+ super
+ end
+ end
+
+ # 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
+ end
+
+ class Mysql2Adapter < AbstractAdapter
+ cattr_accessor :emulate_booleans
+ self.emulate_booleans = true
+
+ ADAPTER_NAME = 'Mysql2'
+ PRIMARY = "PRIMARY"
+
+ LOST_CONNECTION_ERROR_MESSAGES = [
+ "Server shutdown in progress",
+ "Broken pipe",
+ "Lost connection to MySQL server during query",
+ "MySQL server has gone away" ]
+
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ NATIVE_DATABASE_TYPES = {
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "int", :limit => 4 },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "time" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "tinyint", :limit => 1 }
+ }
+
+ def initialize(connection, logger, connection_options, config)
+ super(connection, logger)
+ @connection_options, @config = connection_options, config
+ @quoted_column_names, @quoted_table_names = {}, {}
+ configure_connection
+ end
+
+ def adapter_name
+ ADAPTER_NAME
+ end
+
+ def supports_migrations?
+ true
+ end
+
+ def supports_primary_key?
+ true
+ end
+
+ def supports_savepoints?
+ true
+ end
+
+ def native_database_types
+ NATIVE_DATABASE_TYPES
+ end
+
+ # QUOTING ==================================================
+
+ def quote(value, column = nil)
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
+ s = column.class.string_to_binary(value).unpack("H*")[0]
+ "x'#{s}'"
+ elsif value.kind_of?(BigDecimal)
+ value.to_s("F")
+ else
+ super
+ end
+ end
+
+ def quote_column_name(name) #:nodoc:
+ @quoted_column_names[name] ||= "`#{name}`"
+ end
+
+ def quote_table_name(name) #:nodoc:
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
+ end
+
+ def quote_string(string)
+ @connection.escape(string)
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ # REFERENTIAL INTEGRITY ====================================
+
+ def disable_referential_integrity(&block) #:nodoc:
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+
+ begin
+ update("SET FOREIGN_KEY_CHECKS = 0")
+ yield
+ ensure
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
+ end
+ end
+
+ # CONNECTION MANAGEMENT ====================================
+
+ def active?
+ return false unless @connection
+ @connection.query 'select 1'
+ true
+ rescue Mysql2::Error
+ false
+ end
+
+ def reconnect!
+ disconnect!
+ connect
+ end
+
+ # this is set to true in 2.3, but we don't want it to be
+ def requires_reloading?
+ false
+ end
+
+ def disconnect!
+ unless @connection.nil?
+ @connection.close
+ @connection = nil
+ end
+ end
+
+ def reset!
+ disconnect!
+ connect
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ # FIXME: re-enable the following once a "better" query_cache solution is in core
+ #
+ # The overrides below perform much better than the originals in AbstractAdapter
+ # because we're able to take advantage of mysql2's lazy-loading capabilities
+ #
+ # # Returns a record hash with the column names as keys and column values
+ # # as values.
+ # def select_one(sql, name = nil)
+ # result = execute(sql, name)
+ # result.each(:as => :hash) do |r|
+ # return r
+ # end
+ # end
+ #
+ # # Returns a single value from a record
+ # def select_value(sql, name = nil)
+ # result = execute(sql, name)
+ # if first = result.first
+ # first.first
+ # end
+ # end
+ #
+ # # Returns an array of the values of the first column in a select:
+ # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
+ # def select_values(sql, name = nil)
+ # execute(sql, name).map { |row| row.first }
+ # end
+
+ # Returns an array of arrays containing the field values.
+ # Order is the same as that returned by +columns+.
+ def select_rows(sql, name = nil)
+ execute(sql, name).to_a
+ end
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ if name == :skip_logging
+ @connection.query(sql)
+ else
+ log(sql, name) { @connection.query(sql) }
+ end
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message.split(":").first =~ /Packets out of order/
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ else
+ raise
+ end
+ end
+
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
+ super
+ id_value || @connection.last_id
+ end
+ alias :create :insert_sql
+
+ def update_sql(sql, name = nil)
+ super
+ @connection.affected_rows
+ end
+
+ def begin_db_transaction
+ execute "BEGIN"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def commit_db_transaction
+ execute "COMMIT"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def rollback_db_transaction
+ execute "ROLLBACK"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def add_limit_offset!(sql, options)
+ limit, offset = options[:limit], options[:offset]
+ if limit && offset
+ sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
+ elsif limit
+ sql << " LIMIT #{sanitize_limit(limit)}"
+ elsif offset
+ sql << " OFFSET #{offset.to_i}"
+ end
+ sql
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def structure_dump
+ if supports_views?
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
+ else
+ sql = "SHOW TABLES"
+ end
+
+ select_all(sql).inject("") do |structure, table|
+ table.delete('Table_type')
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
+ end
+ end
+
+ def recreate_database(name, options = {})
+ drop_database(name)
+ create_database(name, options)
+ end
+
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
+ # Charset defaults to utf8.
+ #
+ # Example:
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
+ # create_database 'matt_development'
+ # create_database 'matt_development', :charset => :big5
+ 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
+
+ def drop_database(name) #:nodoc:
+ execute "DROP DATABASE IF EXISTS `#{name}`"
+ end
+
+ def current_database
+ select_value 'SELECT DATABASE() as db'
+ end
+
+ # Returns the database character set.
+ def charset
+ show_variable 'character_set_database'
+ end
+
+ # Returns the database collation strategy.
+ def collation
+ show_variable 'collation_database'
+ end
+
+ def tables(name = nil)
+ tables = []
+ execute("SHOW TABLES", name).each do |field|
+ tables << field.first
+ end
+ tables
+ end
+
+ def drop_table(table_name, options = {})
+ super(table_name, options)
+ end
+
+ def indexes(table_name, name = nil)
+ indexes = []
+ current_index = nil
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
+ if current_index != row[:Key_name]
+ next if row[:Key_name] == PRIMARY # skip the primary key
+ current_index = row[:Key_name]
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [])
+ end
+
+ indexes.last.columns << row[:Column_name]
+ end
+ indexes
+ end
+
+ def columns(table_name, name = nil)
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
+ columns = []
+ result = execute(sql, :skip_logging)
+ result.each(:symbolize_keys => true, :as => :hash) { |field|
+ columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
+ }
+ columns
+ end
+
+ def create_table(table_name, options = {})
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ end
+
+ def rename_table(table_name, new_name)
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
+ end
+
+ def add_column(table_name, column_name, type, options = {})
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(add_column_sql, options)
+ add_column_position!(add_column_sql, options)
+ execute(add_column_sql)
+ end
+
+ def change_column_default(table_name, column_name, default)
+ column = column_for(table_name, column_name)
+ change_column table_name, column_name, column.sql_type, :default => default
+ end
+
+ 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
+
+ def change_column(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
+
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(change_column_sql, options)
+ add_column_position!(change_column_sql, options)
+ execute(change_column_sql)
+ end
+
+ def rename_column(table_name, column_name, new_column_name)
+ options = {}
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
+ options[:default] = column.default
+ options[:null] = column.null
+ else
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
+ end
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
+ add_column_options!(rename_column_sql, options)
+ execute(rename_column_sql)
+ end
+
+ # Maps logical Rails types to MySQL-specific data types.
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
+ return super unless type.to_s == '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
+ end
+
+ def add_column_position!(sql, options)
+ if options[:first]
+ sql << " FIRST"
+ elsif options[:after]
+ sql << " AFTER #{quote_column_name(options[:after])}"
+ end
+ end
+
+ def show_variable(name)
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
+ variables.first['Value'] unless variables.empty?
+ end
+
+ def pk_and_sequence_for(table)
+ keys = []
+ result = execute("describe #{quote_table_name(table)}")
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
+ keys << row[:Field] if row[:Key] == "PRI"
+ end
+ keys.length == 1 ? [keys.first, nil] : nil
+ end
+
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
+ def case_sensitive_equality_operator
+ "= BINARY"
+ end
+
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
+ where_sql
+ end
+
+ protected
+ def quoted_columns_for_index(column_names, options = {})
+ length = options[:length] if options.is_a?(Hash)
+
+ quoted_column_names = case length
+ when Hash
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
+ when Fixnum
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
+ else
+ column_names.map {|name| quote_column_name(name) }
+ end
+ end
+
+ def translate_exception(exception, message)
+ return super unless exception.respond_to?(:error_number)
+
+ case exception.error_number
+ when 1062
+ RecordNotUnique.new(message, exception)
+ when 1452
+ InvalidForeignKey.new(message, exception)
+ else
+ super
+ end
+ end
+
+ private
+ def connect
+ @connection = Mysql2::Client.new(@config)
+ configure_connection
+ end
+
+ def configure_connection
+ @connection.query_options.merge!(:as => :array)
+ encoding = @config[:encoding]
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
+
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+ end
+
+ # Returns an array of record hashes with the column names as keys and
+ # column values as values.
+ def select(sql, name = nil)
+ execute(sql, name).each(:as => :hash)
+ end
+
+ def supports_views?
+ version[0] >= 5
+ end
+
+ def version
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ end
+
+ def column_for(table_name, column_name)
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
+ raise "No such column: #{table_name}.#{column_name}"
+ end
+ column
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index ed4efdc1c0..509baacaef 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -42,7 +42,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal "DROP TABLE `people`", drop_table(:people)
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_create_mysql_database_with_encoding
assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
new file mode 100644
index 0000000000..a83399d0cd
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -0,0 +1,125 @@
+require "cases/helper"
+
+class ActiveSchemaTest < ActiveRecord::TestCase
+ def setup
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ alias_method :execute_without_stub, :execute
+ remove_method :execute
+ def execute(sql, name = nil) return sql end
+ end
+ end
+
+ def teardown
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ remove_method :execute
+ alias_method :execute, :execute_without_stub
+ end
+ end
+
+ def test_add_index
+ # add_index calls index_name_exists? which can't work since execute is stubbed
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*|
+ false
+ end
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ assert_equal expected, add_index(:people, :last_name, :length => nil)
+
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ assert_equal expected, add_index(:people, :last_name, :length => 10)
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?)
+ end
+
+ def test_drop_table
+ assert_equal "DROP TABLE `people`", drop_table(:people)
+ end
+
+ if current_adapter?(:Mysql2Adapter)
+ def test_create_mysql_database_with_encoding
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
+ end
+
+ def test_recreate_mysql_database_with_encoding
+ create_database(:luca, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
+ end
+ end
+
+ def test_add_column
+ assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
+ end
+
+ def test_add_column_with_limit
+ assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
+ end
+
+ def test_drop_table_with_specific_database
+ assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people')
+ end
+
+ def test_add_timestamps
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ end
+ ActiveRecord::Base.connection.add_timestamps :delete_me
+ assert column_present?('delete_me', 'updated_at', 'datetime')
+ assert column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
+ end
+ end
+
+ def test_remove_timestamps
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ t.timestamps
+ end
+ ActiveRecord::Base.connection.remove_timestamps :delete_me
+ assert !column_present?('delete_me', 'updated_at', 'datetime')
+ assert !column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
+ end
+ end
+
+ private
+ def with_real_execute
+ #we need to actually modify some data, so we make execute point to the original method
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ alias_method :execute_with_stub, :execute
+ remove_method :execute
+ alias_method :execute, :execute_without_stub
+ end
+ yield
+ ensure
+ #before finishing, we restore the alias to the mock-up method
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ remove_method :execute
+ alias_method :execute, :execute_with_stub
+ end
+ end
+
+
+ def method_missing(method_symbol, *arguments)
+ ActiveRecord::Base.connection.send(method_symbol, *arguments)
+ end
+
+ def column_present?(table_name, column_name, type)
+ results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
+ results.first && results.first['Type'] == type
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
new file mode 100644
index 0000000000..b973da621b
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+
+class MysqlConnectionTest < ActiveRecord::TestCase
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_no_automatic_reconnection_after_timeout
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ assert !@connection.active?
+ end
+
+ def test_successful_reconnection_after_timeout_with_manual_reconnect
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ @connection.reconnect!
+ assert @connection.active?
+ end
+
+ def test_successful_reconnection_after_timeout_with_verify
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ @connection.verify!
+ assert @connection.active?
+ end
+
+ private
+
+ def run_without_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ begin
+ yield original_connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
new file mode 100644
index 0000000000..90d8b0d923
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -0,0 +1,176 @@
+require "cases/helper"
+
+class Group < ActiveRecord::Base
+ Group.table_name = 'group'
+ belongs_to :select, :class_name => 'Select'
+ has_one :values
+end
+
+class Select < ActiveRecord::Base
+ Select.table_name = 'select'
+ has_many :groups
+end
+
+class Values < ActiveRecord::Base
+ Values.table_name = 'values'
+end
+
+class Distinct < ActiveRecord::Base
+ Distinct.table_name = 'distinct'
+ has_and_belongs_to_many :selects
+ has_many :values, :through => :groups
+end
+
+# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
+# reserved word names (ie: group, order, values, etc...)
+class MysqlReservedWordTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
+ # will fail with these table names if these test cases fail
+
+ create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int',
+ 'select'=>'id int auto_increment primary key',
+ 'values'=>'id int auto_increment primary key, group_id int',
+ 'distinct'=>'id int auto_increment primary key',
+ 'distincts_selects'=>'distinct_id int, select_id int'
+ end
+
+ def teardown
+ drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+ end
+
+ # create tables with reserved-word names and columns
+ def test_create_tables
+ assert_nothing_raised {
+ @connection.create_table :order do |t|
+ t.column :group, :string
+ end
+ }
+ end
+
+ # rename tables with reserved-word names
+ def test_rename_tables
+ assert_nothing_raised { @connection.rename_table(:group, :order) }
+ end
+
+ # alter column with a reserved-word name in a table with a reserved-word name
+ def test_change_columns
+ assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') }
+ #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
+ assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) }
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
+ end
+
+ # dump structure of table with reserved word name
+ def test_structure_dump
+ assert_nothing_raised { @connection.structure_dump }
+ end
+
+ # introspect table with reserved word name
+ def test_introspect
+ assert_nothing_raised { @connection.columns(:group) }
+ assert_nothing_raised { @connection.indexes(:group) }
+ end
+
+ #fixtures
+ self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = false
+
+ #fixtures :group
+
+ def test_fixtures
+ f = create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+
+ assert_nothing_raised {
+ f.each do |x|
+ x.delete_existing_fixtures
+ end
+ }
+
+ assert_nothing_raised {
+ f.each do |x|
+ x.insert_fixtures
+ end
+ }
+ end
+
+ #activerecord model class with reserved-word table name
+ def test_activerecord_model
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ x = nil
+ assert_nothing_raised { x = Group.new }
+ x.order = 'x'
+ assert_nothing_raised { x.save }
+ x.order = 'y'
+ assert_nothing_raised { x.save }
+ assert_nothing_raised { y = Group.find_by_order('y') }
+ assert_nothing_raised { y = Group.find(1) }
+ x = Group.find(1)
+ end
+
+ # has_one association with reserved-word table name
+ def test_has_one_associations
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ v = nil
+ assert_nothing_raised { v = Group.find(1).values }
+ assert_equal 2, v.id
+ end
+
+ # belongs_to association with reserved-word table name
+ def test_belongs_to_associations
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ gs = nil
+ assert_nothing_raised { gs = Select.find(2).groups }
+ assert_equal gs.length, 2
+ assert(gs.collect{|x| x.id}.sort == [2, 3])
+ end
+
+ # has_and_belongs_to_many with reserved-word table name
+ def test_has_and_belongs_to_many
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ s = nil
+ assert_nothing_raised { s = Distinct.find(1).selects }
+ assert_equal s.length, 2
+ assert(s.collect{|x|x.id}.sort == [1, 2])
+ end
+
+ # activerecord model introspection with reserved-word table and column names
+ def test_activerecord_introspection
+ assert_nothing_raised { Group.table_exists? }
+ assert_nothing_raised { Group.columns }
+ end
+
+ # Calculations
+ def test_calculations_work_with_reserved_words
+ assert_nothing_raised { Group.count }
+ end
+
+ def test_associations_work_with_reserved_words
+ assert_nothing_raised { Select.find(:all, :include => [:groups]) }
+ end
+
+ #the following functions were added to DRY test cases
+
+ private
+ # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
+ def create_test_fixtures(*fixture_names)
+ Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ end
+
+ # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
+ def drop_tables_directly(table_names, connection = @connection)
+ table_names.each do |name|
+ connection.execute("DROP TABLE IF EXISTS `#{name}`")
+ end
+ end
+
+ # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
+ def create_tables_directly (tables, connection = @connection)
+ tables.each do |table_name, column_properties|
+ connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 046433820d..a1ce9b1689 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -32,7 +32,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_with_primary_key_joins_on_correct_column
sql = Client.joins(:firm_with_primary_key).to_sql
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
elsif current_adapter?(:OracleAdapter)
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 6b4a1d9408..ed7d9a782c 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -109,8 +109,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
record = con.select_rows(sql).last
assert_not_nil record[2]
assert_not_nil record[3]
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2]
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3]
+ if current_adapter?(:Mysql2Adapter)
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2].to_s(:db)
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3].to_s(:db)
+ else
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2]
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3]
+ end
end
def test_should_record_timestamp_for_join_table_only_if_timestamp_should_be_recorded
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index d20b762853..2c069cd8a5 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -106,21 +106,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_read_attributes_before_type_cast_on_datetime
- developer = Developer.find(:first)
- # Oracle adapter returns Time before type cast
- unless current_adapter?(:OracleAdapter)
- assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"]
- else
- assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db)
+ unless current_adapter?(:Mysql2Adapter)
+ def test_read_attributes_before_type_cast_on_datetime
+ developer = Developer.find(:first)
+ # Oracle adapter returns Time before type cast
+ unless current_adapter?(:OracleAdapter)
+ assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"]
+ else
+ assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db)
- developer.created_at = "345643456"
- assert_equal developer.created_at_before_type_cast, "345643456"
- assert_equal developer.created_at, nil
+ developer.created_at = "345643456"
+ assert_equal developer.created_at_before_type_cast, "345643456"
+ assert_equal developer.created_at, nil
- developer.created_at = "2010-03-21T21:23:32+01:00"
- assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00"
- assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00")
+ developer.created_at = "2010-03-21T21:23:32+01:00"
+ assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00"
+ assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00")
+ end
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 56ec4ca58c..ca397d3847 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -284,7 +284,7 @@ class BasicsTest < ActiveRecord::TestCase
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_update_all_with_order_and_limit
assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 2c9d23c80f..afef31396e 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -325,7 +325,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_from_option_with_specified_index
- if Edge.connection.adapter_name == 'MySQL'
+ if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2'
assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)')
assert_equal Edge.count(:all, :conditions => 'sink_id < 5'),
Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5')
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index b5767344cd..cc6a6b44f2 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -68,6 +68,40 @@ class ColumnDefinitionTest < ActiveRecord::TestCase
end
end
+ if current_adapter?(:Mysql2Adapter)
+ def test_should_set_default_for_mysql_binary_data_types
+ binary_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "binary(1)")
+ assert_equal "a", binary_column.default
+
+ varbinary_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "varbinary(1)")
+ assert_equal "a", varbinary_column.default
+ end
+
+ def test_should_not_set_default_for_blob_and_text_data_types
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "blob")
+ end
+
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "Hello", "text")
+ end
+
+ text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text")
+ assert_equal nil, text_column.default
+
+ not_null_text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text", false)
+ assert_equal "", not_null_text_column.default
+ end
+
+ def test_has_default_should_return_false_for_blog_and_test_data_types
+ blob_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "blob")
+ assert !blob_column.has_default?
+
+ text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text")
+ assert !text_column.has_default?
+ end
+ end
+
if current_adapter?(:PostgreSQLAdapter)
def test_bigint_column_should_map_to_integer
bigint_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new('number', nil, "bigint")
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index ef29422824..0e90128907 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -39,7 +39,7 @@ class DefaultTest < ActiveRecord::TestCase
end
end
-if current_adapter?(:MysqlAdapter)
+if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional fixtures mode, this will
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 2c3fc46831..0cf3979694 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -256,7 +256,7 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_create_table_with_defaults
# MySQL doesn't allow defaults on TEXT or BLOB columns.
- mysql = current_adapter?(:MysqlAdapter)
+ mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
Person.connection.create_table :testings do |t|
t.column :one, :string, :default => "hello"
@@ -313,7 +313,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 'integer', four.sql_type
assert_equal 'bigint', eight.sql_type
assert_equal 'integer', eleven.sql_type
- elsif current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_match 'int(11)', default.sql_type
assert_match 'tinyint', one.sql_type
assert_match 'int', four.sql_type
@@ -581,7 +581,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_kind_of BigDecimal, bob.wealth
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_unabstracted_database_dependent_types
Person.delete_all
@@ -621,7 +621,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert !Person.column_methods_hash.include?(:last_name)
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def testing_table_for_positioning
Person.connection.create_table :testings, :id => false do |t|
t.column :first, :integer
@@ -1447,7 +1447,7 @@ if ActiveRecord::Base.connection.supports_migrations?
columns = Person.connection.columns(:binary_testings)
data_column = columns.detect { |c| c.name == "data" }
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_equal '', data_column.default
else
assert_nil data_column.default
@@ -1748,7 +1748,7 @@ if ActiveRecord::Base.connection.supports_migrations?
end
def integer_column
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
'int(11)'
elsif current_adapter?(:OracleAdapter)
'NUMBER(38)'
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index f0d97a00d0..594db1d0ab 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -57,7 +57,7 @@ class QueryCacheTest < ActiveRecord::TestCase
# Oracle adapter returns count() as Fixnum or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5'
+ elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' or current_adapter?(:Mysql2Adapter)
# Future versions of the sqlite3 adapter will return numeric
assert_instance_of Fixnum,
Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 1c43e3c5b5..66446b6b7e 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -93,7 +93,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_4.*}, output
assert_no_match %r{c_int_4.*:limit}, output
- elsif current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_match %r{c_int_1.*:limit => 1}, output
assert_match %r{c_int_2.*:limit => 2}, output
assert_match %r{c_int_3.*:limit => 3}, output
@@ -169,7 +169,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_schema_dump_should_not_add_default_value_for_mysql_text_field
output = standard_dump
assert_match %r{t.text\s+"body",\s+:null => false$}, output
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 06ab7aa9c7..401439994d 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -94,6 +94,7 @@ class TimestampTest < ActiveRecord::TestCase
owner.update_attribute(:updated_at, (time = 3.days.ago))
toy.touch
+ owner.reload
assert_not_equal time, owner.updated_at
ensure
diff --git a/activerecord/test/connections/native_mysql2/connection.rb b/activerecord/test/connections/native_mysql2/connection.rb
new file mode 100644
index 0000000000..c6f198b1ac
--- /dev/null
+++ b/activerecord/test/connections/native_mysql2/connection.rb
@@ -0,0 +1,25 @@
+print "Using native Mysql2\n"
+require_dependency 'models/course'
+require 'logger'
+
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost';
+# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';
+
+ActiveRecord::Base.configurations = {
+ 'arunit' => {
+ :adapter => 'mysql2',
+ :username => 'rails',
+ :encoding => 'utf8',
+ :database => 'activerecord_unittest',
+ },
+ 'arunit2' => {
+ :adapter => 'mysql2',
+ :username => 'rails',
+ :database => 'activerecord_unittest2'
+ }
+}
+
+ActiveRecord::Base.establish_connection 'arunit'
+Course.establish_connection 'arunit2'
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
new file mode 100644
index 0000000000..c78d99f4af
--- /dev/null
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -0,0 +1,24 @@
+ActiveRecord::Schema.define do
+ create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t|
+ t.binary :tiny_blob, :limit => 255
+ t.binary :normal_blob, :limit => 65535
+ t.binary :medium_blob, :limit => 16777215
+ t.binary :long_blob, :limit => 2147483647
+ t.text :tiny_text, :limit => 255
+ t.text :normal_text, :limit => 65535
+ t.text :medium_text, :limit => 16777215
+ t.text :long_text, :limit => 2147483647
+ end
+
+ ActiveRecord::Base.connection.execute <<-SQL
+DROP PROCEDURE IF EXISTS ten;
+SQL
+
+ ActiveRecord::Base.connection.execute <<-SQL
+CREATE PROCEDURE ten() SQL SECURITY INVOKER
+BEGIN
+ select 10;
+END
+SQL
+
+end