diff options
Diffstat (limited to 'activerecord/test/cases/adapters/mysql')
12 files changed, 924 insertions, 0 deletions
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb new file mode 100644 index 0000000000..7c0f11b033 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -0,0 +1,155 @@ +require "cases/helper" +require 'support/connection_helper' + +class ActiveSchemaTest < ActiveRecord::TestCase + include ConnectionHelper + + def setup + ActiveRecord::Base.connection.singleton_class.class_eval do + alias_method :execute_without_stub, :execute + def execute(sql, name = nil) return sql end + end + end + + teardown do + reset_connection + end + + def test_add_index + # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed + def (ActiveRecord::Base.connection).table_exists?(*); true; end + def (ActiveRecord::Base.connection).index_name_exists?(*); 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}) + + %w(SPATIAL FULLTEXT UNIQUE).each do |type| + expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) " + assert_equal expected, add_index(:people, :last_name, :type => type) + end + + %w(btree hash).each do |using| + expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) " + assert_equal expected, add_index(:people, :last_name, :using => using) + end + + expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) " + assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree) + + expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY" + assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy) + + assert_raise ArgumentError do + add_index(:people, :last_name, algorithm: :coyp) + end + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) " + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) + end + + def test_drop_table + assert_equal "DROP TABLE `people`", drop_table(:people) + end + + if current_adapter?(:MysqlAdapter, :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 + 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 + + def test_indexes_in_create + ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) + ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) + + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| + t.index :zip + end + + assert_equal expected, actual + end + + private + def with_real_execute + ActiveRecord::Base.connection.singleton_class.class_eval do + alias_method :execute_with_stub, :execute + remove_method :execute + alias_method :execute, :execute_without_stub + end + + yield + ensure + ActiveRecord::Base.connection.singleton_class.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/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb new file mode 100644 index 0000000000..340fc95503 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb @@ -0,0 +1,55 @@ +require "cases/helper" +require 'models/person' + +class MysqlCaseSensitivityTest < ActiveRecord::TestCase + class CollationTest < ActiveRecord::Base + end + + repair_validations(CollationTest) + + def test_columns_include_collation_different_from_table + assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation + assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation + end + + def test_case_sensitive + assert !CollationTest.columns_hash['string_ci_column'].case_sensitive? + assert CollationTest.columns_hash['string_cs_column'].case_sensitive? + end + + def test_case_insensitive_comparison_for_ci_column + CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false) + CollationTest.create!(:string_ci_column => 'A') + invalid = CollationTest.new(:string_ci_column => 'a') + queries = assert_sql { invalid.save } + ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } + assert_no_match(/lower/i, ci_uniqueness_query) + end + + def test_case_insensitive_comparison_for_cs_column + CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false) + CollationTest.create!(:string_cs_column => 'A') + invalid = CollationTest.new(:string_cs_column => 'a') + queries = assert_sql { invalid.save } + cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } + assert_match(/lower/i, cs_uniqueness_query) + end + + def test_case_sensitive_comparison_for_ci_column + CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true) + CollationTest.create!(:string_ci_column => 'A') + invalid = CollationTest.new(:string_ci_column => 'A') + queries = assert_sql { invalid.save } + ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) } + assert_match(/binary/i, ci_uniqueness_query) + end + + def test_case_sensitive_comparison_for_cs_column + CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true) + CollationTest.create!(:string_cs_column => 'A') + invalid = CollationTest.new(:string_cs_column => 'A') + queries = assert_sql { invalid.save } + cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) } + assert_no_match(/binary/i, cs_uniqueness_query) + end +end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb new file mode 100644 index 0000000000..b0759dffde --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -0,0 +1,179 @@ +require "cases/helper" +require 'support/connection_helper' +require 'support/ddl_helper' + +class MysqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper + include DdlHelper + + class Klass < ActiveRecord::Base + end + + def setup + super + @connection = ActiveRecord::Base.connection + end + + def test_mysql_reconnect_attribute_after_connection_with_reconnect_true + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true})) + assert ActiveRecord::Base.connection.raw_connection.reconnect + end + end + + unless ARTest.connection_config['arunit']['socket'] + def test_connect_with_url + run_without_connection do + ar_config = ARTest.connection_config['arunit'] + + url = "mysql://#{ar_config["username"]}@localhost/#{ar_config["database"]}" + Klass.establish_connection(url) + assert_equal ar_config['database'], Klass.connection.current_database + end + end + end + + def test_mysql_reconnect_attribute_after_connection_with_reconnect_false + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false})) + assert !ActiveRecord::Base.connection.raw_connection.reconnect + end + end + + def test_no_automatic_reconnection_after_timeout + assert @connection.active? + @connection.update('set @@wait_timeout=1') + sleep 2 + assert !@connection.active? + + # Repair all fixture connections so other tests won't break. + @fixture_connections.each do |c| + c.verify! + end + 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 + + def test_bind_value_substitute + bind_param = @connection.substitute_at('foo', 0) + assert_equal Arel.sql('?'), bind_param + end + + def test_exec_no_binds + with_example_table do + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 0, result.rows.length + assert_equal 2, result.columns.length + assert_equal %w{ id data }, result.columns + + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + + # if there are no bind parameters, it will return a string (due to + # the libmysql api) + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [['1', 'foo']], result.rows + end + end + + def test_exec_with_binds + with_example_table do + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) + + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [[1, 'foo']], result.rows + end + end + + def test_exec_typecasts_bind_vals + with_example_table do + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + column = @connection.columns('ex').find { |col| col.name == 'id' } + + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length + + assert_equal [[1, 'foo']], result.rows + end + end + + # Test that MySQL allows multiple results for stored procedures + if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS) + def test_multi_results + rows = ActiveRecord::Base.connection.select_rows('CALL ten();') + assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" + assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'" + end + end + + def test_mysql_default_in_strict_mode + result = @connection.exec_query "SELECT @@SESSION.sql_mode" + assert_equal [["STRICT_ALL_TABLES"]], result.rows + end + + def test_mysql_strict_mode_disabled + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false})) + result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode" + assert_equal [['']], result.rows + end + end + + def test_mysql_set_session_variable + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}})) + session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" + assert_equal 3, session_mode.rows.first.first.to_i + end + end + + def test_mysql_sql_mode_variable_overides_strict_mode + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) + result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' + assert_not_equal [['STRICT_ALL_TABLES']], result.rows + end + end + + def test_mysql_set_session_variable_to_default + run_without_connection do |orig_connection| + ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}})) + global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT" + session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT" + assert_equal global_mode.rows, session_mode.rows + end + end + + private + + def with_example_table(&block) + definition ||= <<-SQL + `id` int(11) auto_increment PRIMARY KEY, + `data` varchar(255) + SQL + super(@connection, 'ex', definition, &block) + end +end diff --git a/activerecord/test/cases/adapters/mysql/consistency_test.rb b/activerecord/test/cases/adapters/mysql/consistency_test.rb new file mode 100644 index 0000000000..083d533bb2 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/consistency_test.rb @@ -0,0 +1,48 @@ +require "cases/helper" + +class MysqlConsistencyTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class Consistency < ActiveRecord::Base + self.table_name = "mysql_consistency" + end + + setup do + @old_emulate_booleans = ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans + ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false + + @connection = ActiveRecord::Base.connection + @connection.create_table("mysql_consistency") do |t| + t.boolean "a_bool" + t.string "a_string" + end + Consistency.reset_column_information + Consistency.create! + end + + teardown do + ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = @old_emulate_booleans + @connection.drop_table "mysql_consistency" + end + + test "boolean columns with random value type cast to 0 when emulate_booleans is false" do + with_new = Consistency.new + with_last = Consistency.last + with_new.a_bool = 'wibble' + with_last.a_bool = 'wibble' + + assert_equal 0, with_new.a_bool + assert_equal 0, with_last.a_bool + end + + test "string columns call #to_s" do + with_new = Consistency.new + with_last = Consistency.last + thing = Object.new + with_new.a_string = thing + with_last.a_string = thing + + assert_equal thing.to_s, with_new.a_string + assert_equal thing.to_s, with_last.a_string + end +end diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb new file mode 100644 index 0000000000..f4e7a3ef0a --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/enum_test.rb @@ -0,0 +1,10 @@ +require "cases/helper" + +class MysqlEnumTest < ActiveRecord::TestCase + class EnumTest < ActiveRecord::Base + end + + def test_enum_limit + assert_equal 6, EnumTest.columns.first.limit + end +end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb new file mode 100644 index 0000000000..28106d3772 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -0,0 +1,147 @@ +# encoding: utf-8 + +require "cases/helper" +require 'support/ddl_helper' + +module ActiveRecord + module ConnectionAdapters + class MysqlAdapterTest < ActiveRecord::TestCase + include DdlHelper + + def setup + @conn = ActiveRecord::Base.connection + end + + def test_bad_connection_mysql + assert_raise ActiveRecord::NoDatabaseError do + configuration = ActiveRecord::Base.configurations['arunit'].merge(database: 'inexistent_activerecord_unittest') + connection = ActiveRecord::Base.mysql_connection(configuration) + connection.exec_query('drop table if exists ex') + end + end + + def test_valid_column + with_example_table do + column = @conn.columns('ex').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end + end + + def test_invalid_column + assert_not @conn.valid_type?(:foobar) + end + + def test_client_encoding + assert_equal Encoding::UTF_8, @conn.client_encoding + end + + def test_exec_insert_number + with_example_table do + insert(@conn, 'number' => 10) + + result = @conn.exec_query('SELECT number FROM ex WHERE number = 10') + + assert_equal 1, result.rows.length + # if there are no bind parameters, it will return a string (due to + # the libmysql api) + assert_equal '10', result.rows.last.last + end + end + + def test_exec_insert_string + with_example_table do + str = 'いただきます!' + insert(@conn, 'number' => 10, 'data' => str) + + result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10') + + value = result.rows.last.last + + # FIXME: this should probably be inside the mysql AR adapter? + value.force_encoding(@conn.client_encoding) + + # The strings in this file are utf-8, so transcode to utf-8 + value.encode!(Encoding::UTF_8) + + assert_equal str, value + end + end + + def test_tables_quoting + @conn.tables(nil, "foo-bar", nil) + flunk + rescue => e + # assertion for *quoted* database properly + assert_match(/database 'foo-bar'/, e.inspect) + end + + def test_pk_and_sequence_for + with_example_table do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex', 'id'), seq + end + end + + def test_pk_and_sequence_for_with_non_standard_primary_key + with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'code', pk + assert_equal @conn.default_sequence_name('ex', 'code'), seq + end + end + + def test_pk_and_sequence_for_with_custom_index_type_pk + with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex', 'id'), seq + end + end + + def test_tinyint_integer_typecasting + with_example_table '`status` TINYINT(4)' do + insert(@conn, { 'status' => 2 }, 'ex') + + result = @conn.exec_query('SELECT status FROM ex') + + assert_equal 2, result.column_types['status'].type_cast_from_database(result.last['status']) + end + end + + def test_supports_extensions + assert_not @conn.supports_extensions?, 'does not support extensions' + end + + def test_respond_to_enable_extension + assert @conn.respond_to?(:enable_extension) + end + + def test_respond_to_disable_extension + assert @conn.respond_to?(:disable_extension) + end + + private + def insert(ctx, data, table='ex') + binds = data.map { |name, value| + [ctx.columns(table).find { |x| x.name == name }, value] + } + columns = binds.map(&:first).map(&:name) + + sql = "INSERT INTO #{table} (#{columns.join(", ")}) + VALUES (#{(['?'] * columns.length).join(', ')})" + + ctx.exec_insert(sql, 'SQL', binds) + end + + def with_example_table(definition = nil, &block) + definition ||= <<-SQL + `id` int(11) auto_increment PRIMARY KEY, + `number` integer, + `data` varchar(255) + SQL + super(@conn, 'ex', definition, &block) + end + end + end +end diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb new file mode 100644 index 0000000000..d8a954efa8 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb @@ -0,0 +1,25 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class MysqlAdapter + class QuotingTest < ActiveRecord::TestCase + def setup + @conn = ActiveRecord::Base.connection + end + + def test_type_cast_true + c = Column.new(nil, 1, Type::Boolean.new) + assert_equal 1, @conn.type_cast(true, nil) + assert_equal 1, @conn.type_cast(true, c) + end + + def test_type_cast_false + c = Column.new(nil, 1, Type::Boolean.new) + assert_equal 0, @conn.type_cast(false, nil) + assert_equal 0, @conn.type_cast(false, c) + end + end + end + end +end diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb new file mode 100644 index 0000000000..61ae0abfd1 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -0,0 +1,153 @@ +require "cases/helper" + +class Group < ActiveRecord::Base + Group.table_name = 'group' + belongs_to :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', + 'distinct_select'=>'distinct_id int, select_id int' + end + + teardown do + drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', '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 + + # 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 + + #activerecord model class with reserved-word table name + def test_activerecord_model + create_test_fixtures :select, :distinct, :group, :values, :distinct_select + 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 { Group.find_by_order('y') } + assert_nothing_raised { Group.find(1) } + Group.find(1) + end + + # has_one association with reserved-word table name + def test_has_one_associations + create_test_fixtures :select, :distinct, :group, :values, :distinct_select + 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, :distinct_select + 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, :distinct_select + 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.all.merge!(:includes => [:groups]).to_a } + end + + #the following functions were added to DRY test cases + + private + # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path + def create_test_fixtures(*fixture_names) + ActiveRecord::FixtureSet.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/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb new file mode 100644 index 0000000000..87c5277e64 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -0,0 +1,100 @@ +require "cases/helper" +require 'models/post' +require 'models/comment' + +module ActiveRecord + module ConnectionAdapters + class MysqlSchemaTest < ActiveRecord::TestCase + fixtures :posts + + def setup + @connection = ActiveRecord::Base.connection + db = Post.connection_pool.spec.config[:database] + table = Post.table_name + @db_name = db + + @omgpost = Class.new(ActiveRecord::Base) do + self.table_name = "#{db}.#{table}" + def self.name; 'Post'; end + end + + @connection.create_table "mysql_doubles" + end + + teardown do + @connection.execute "drop table if exists mysql_doubles" + end + + class MysqlDouble < ActiveRecord::Base + self.table_name = "mysql_doubles" + end + + def test_float_limits + @connection.add_column :mysql_doubles, :float_no_limit, :float + @connection.add_column :mysql_doubles, :float_short, :float, limit: 5 + @connection.add_column :mysql_doubles, :float_long, :float, limit: 53 + + @connection.add_column :mysql_doubles, :float_23, :float, limit: 23 + @connection.add_column :mysql_doubles, :float_24, :float, limit: 24 + @connection.add_column :mysql_doubles, :float_25, :float, limit: 25 + MysqlDouble.reset_column_information + + column_no_limit = MysqlDouble.columns.find { |c| c.name == 'float_no_limit' } + column_short = MysqlDouble.columns.find { |c| c.name == 'float_short' } + column_long = MysqlDouble.columns.find { |c| c.name == 'float_long' } + + column_23 = MysqlDouble.columns.find { |c| c.name == 'float_23' } + column_24 = MysqlDouble.columns.find { |c| c.name == 'float_24' } + column_25 = MysqlDouble.columns.find { |c| c.name == 'float_25' } + + # Mysql floats are precision 0..24, Mysql doubles are precision 25..53 + assert_equal 24, column_no_limit.limit + assert_equal 24, column_short.limit + assert_equal 53, column_long.limit + + assert_equal 24, column_23.limit + assert_equal 24, column_24.limit + assert_equal 53, column_25.limit + end + + def test_schema + assert @omgpost.first + end + + def test_primary_key + assert_equal 'id', @omgpost.primary_key + end + + def test_table_exists? + name = @omgpost.table_name + assert @connection.table_exists?(name), "#{name} table should exist" + end + + def test_table_exists_wrong_schema + assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist") + end + + def test_dump_indexes + index_a_name = 'index_key_tests_on_snack' + index_b_name = 'index_key_tests_on_pizza' + index_c_name = 'index_key_tests_on_awesome' + + table = 'key_tests' + + indexes = @connection.indexes(table).sort_by {|i| i.name} + assert_equal 3,indexes.size + + index_a = indexes.select{|i| i.name == index_a_name}[0] + index_b = indexes.select{|i| i.name == index_b_name}[0] + index_c = indexes.select{|i| i.name == index_c_name}[0] + assert_equal :btree, index_a.using + assert_nil index_a.type + assert_equal :btree, index_b.using + assert_nil index_b.type + + assert_nil index_c.using + assert_equal :fulltext, index_c.type + end + end + end +end diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb new file mode 100644 index 0000000000..3ca2917ca4 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/sp_test.rb @@ -0,0 +1,15 @@ +require "cases/helper" +require 'models/topic' + +class StoredProcedureTest < ActiveRecord::TestCase + fixtures :topics + + # Test that MySQL allows multiple results for stored procedures + if Mysql.const_defined?(:CLIENT_MULTI_RESULTS) + def test_multi_results_from_find_by_sql + topics = Topic.find_by_sql 'CALL topics();' + assert_equal 1, topics.size + assert ActiveRecord::Base.connection.active?, "Bad connection use by 'MysqlAdapter.select'" + end + end +end diff --git a/activerecord/test/cases/adapters/mysql/sql_types_test.rb b/activerecord/test/cases/adapters/mysql/sql_types_test.rb new file mode 100644 index 0000000000..1ddb1b91c9 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/sql_types_test.rb @@ -0,0 +1,14 @@ +require "cases/helper" + +class SqlTypesTest < ActiveRecord::TestCase + def test_binary_types + assert_equal 'varbinary(64)', type_to_sql(:binary, 64) + assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095) + assert_equal 'blob(4096)', type_to_sql(:binary, 4096) + assert_equal 'blob', type_to_sql(:binary) + end + + def type_to_sql(*args) + ActiveRecord::Base.connection.type_to_sql(*args) + end +end diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb new file mode 100644 index 0000000000..209a0cf464 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb @@ -0,0 +1,23 @@ +require 'cases/helper' + +module ActiveRecord::ConnectionAdapters + class MysqlAdapter + class StatementPoolTest < ActiveRecord::TestCase + if Process.respond_to?(:fork) + def test_cache_is_per_pid + cache = StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] + + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } + + Process.waitpid pid + assert $?.success?, 'process should exit successfully' + end + end + end + end +end |