require 'spec_helper' require 'flydata/mysql/table_ddl' module Flydata module Mysql describe TableDdl do let(:table_name1) { "items" } let(:table_name2) { "orders" } let(:table_names) { [table_name1, table_name2] } let(:table1_charset) { 'utf8' } let(:table2_charset) { 'sjis' } let(:at_charset_event_table1) { double('at_charset_event_table1') } let(:at_charset_event_table2) { double('at_charset_event_table2') } let(:at_column_charset_event_table1) { double('at_column_charset_event_table1') } let(:expected_alter_table_charset_events) { [ at_charset_event_table1, at_charset_event_table2] } let(:at_charset_query) { "ALTER TABLE `#{database_name}`.`#{at_table}` CHARACTER SET = #{charset};" } let(:query) { "ALTER TABLE `#{database_name}`.`#{event_table_name}` CHARACTER SET = #{charset};" } let(:column_query1) { "ALTER TABLE `#{database_name}`.`#{event_table_name}` CHANGE COLUMN `#{table1_varchar_column}` #{table1_varchar_column_def};" } let(:charset) { 'utf8' } let(:event_type) { 'Query' } let(:database_name) { "a_database" } let(:sync_fm) { double('sync_fm') } let(:master_binlog_position) { "#{binlog_file}\t#{binlog_position}" } let(:binlog_file) { "mysql-binlog.00123" } let(:binlog_position) { 28393 } let(:original_current_binlog_file) { double('original_current_binlog_file') } let(:mysql_opts) { { host: double('host'), port: double('port'), username: double('username'), password: double('password'), database: database_name, } } let(:position_file) { double('position_file') } let(:context) { double('context') } let(:mysql_tabledef) { { table_name1 => mysql_tabledef1, table_name2 => mysql_tabledef2, } } let(:mysql_tabledef1) { double('mysql_tabledef1') } let(:mysql_tabledef2) { double('mysql_tabledef2') } let(:column_def1) { { "id" => "`id` integer NOT NULL", table1_varchar_column => table1_varchar_column_def, } } let(:table1_varchar_column) { 'name' } let(:table1_varchar_column_def) { "`#{table1_varchar_column}` varchar(45)#{charset_option1} NULL" } let(:column_def2) { { "id" => "`id` integer NOT NULL", "address" => "`address` varchar(45) NULL" } } let(:charset_option1) { "" } let(:save) { {} } before do save[:log] = $log $log = double('$log') allow($log).to receive(:info) allow($log).to receive(:debug) allow($log).to receive(:error) allow($log).to receive(:trace) allow(mysql_tabledef1).to receive(:default_charset_mysql).and_return(table1_charset) allow(mysql_tabledef1).to receive(:table_name).and_return(table_name1) allow(mysql_tabledef1).to receive(:column_def).and_return(column_def1) allow(mysql_tabledef2).to receive(:default_charset_mysql).and_return(table2_charset) allow(mysql_tabledef2).to receive(:table_name).and_return(table_name2) allow(mysql_tabledef2).to receive(:column_def).and_return(column_def2) end after do $log = save[:log] end describe '.migrate_to_v2' do let(:subject_object) { described_class } let(:subject_params) { [:migrate_to_v2, table_names, mysql_opts, sync_fm, position_file, context] } let(:target_version) { "2" } shared_examples "create and send charset records and increment version" do let(:next_position) { binlog_position } it do matcher = receive(:each_mysql_tabledef).with(table_names, mysql_opts) matcher = table_names.inject(matcher) {|m, tn| m.and_yield(mysql_tabledef[tn], nil) } expect(FlydataCore::Mysql::CommandGenerator).to matcher expect(File).to receive(:open).with(position_file).and_return(master_binlog_position) expect(context).to receive(:current_binlog_file).and_return(original_current_binlog_file) expect(context).to receive(:current_binlog_file=).with(binlog_file) expect(context).to receive(:current_binlog_file=).with(original_current_binlog_file) expect(sync_fm).to receive(:save_generated_ddl).with(table_names, target_version) expect{|b| subject_object.send(*subject_params, &b) }.to yield_successive_args(*expected_alter_table_charset_events) end end shared_examples "does nothing" do it do expect(sync_fm).not_to receive(:save_generated_ddl) expect{|b| subject_object.send(*subject_params, &b) }.not_to yield_control end end let(:table_versions) { table_names.size.times.collect{ current_version } } before do expect(sync_fm).to receive(:load_generated_ddl).with(table_names).and_return table_versions end context "with a single table" do let(:table_names) { [table_name1] } let(:event_table_name) { table_name1 } let(:charset) { table1_charset } let(:expected_alter_table_charset_events) { [ at_charset_event_table1 ] } context "with a table whose version is 0" do let(:current_version) { nil } # generate_ddl file doesn't exist in case of version "0" before do expect(QueryEvent).to receive(:new).with(event_type, database_name, event_table_name, next_position, 0, query, Integer).and_return(at_charset_event_table1) end it_behaves_like "create and send charset records and increment version" end context "with a table whose version is 1" do let(:current_version) { "1" } before do expect(QueryEvent).to receive(:new).with(event_type, database_name, event_table_name, next_position, 0, query, Integer).and_return(at_charset_event_table1) end it_behaves_like "create and send charset records and increment version" end context "with a table whose version is 2" do let(:current_version) { "2" } it_behaves_like "does nothing" end context "with a table whose version is 3" do let(:current_version) { "3" } it_behaves_like "does nothing" end context 'with a single table who has a column with charset' do let(:charset_option1) { " CHARACTER SET 'latin5'" } let(:expected_alter_table_charset_events) { [ at_column_charset_event_table1, at_charset_event_table1 ] } context "with a table whose version is 0" do let(:current_version) { nil } # generate_ddl file doesn't exist in case of version "0" before do expect(QueryEvent).to receive(:new).with(event_type, database_name, event_table_name, next_position, 0, column_query1, Integer).and_return(at_column_charset_event_table1) expect(QueryEvent).to receive(:new).with(event_type, database_name, event_table_name, next_position, 0, query, Integer).and_return(at_charset_event_table1) end it_behaves_like "create and send charset records and increment version" end end end end end describe QueryEvent do let(:subject_object) { described_class.new(event_type, database_name, event_table_name, next_position, event_length, query, timestamp) } let(:event_type) { 'Query' } let(:database_name) { 'mydb' } let(:event_table_name) { 'mytable' } let(:next_position) { 38293 } let(:event_length) { 2093 } let(:query) { 'ALTER TABLE CHARACTER SET sjis' } let(:timestamp) { Time.now.to_i } describe "#event_type" do subject { subject_object.event_type } it { is_expected.to eq event_type } end describe "#database_name" do subject { subject_object.database_name } it { is_expected.to eq database_name} end describe "#event_table_name" do subject { subject_object.event_table_name } it { is_expected.to eq event_table_name} end describe "#next_position" do subject { subject_object.next_position } it { is_expected.to eq next_position} end describe "#event_length" do subject { subject_object.event_length } it { is_expected.to eq event_length} end describe "#query" do subject { subject_object.query } it { is_expected.to eq query} end describe "#timestamp" do subject { subject_object.timestamp } it { is_expected.to eq timestamp} end end end end