flydata 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ # coding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module Flydata
5
+ module Output
6
+ describe ForwarderFactory do
7
+ let(:forwarder) do
8
+ ForwarderFactory.create('tcpforwarder', 'test_tag', ['localhost:3456', 'localhost:4567'])
9
+ end
10
+ before :each do
11
+ conn = double(TCPSocket)
12
+ allow(conn).to receive(:setsockopt)
13
+ allow(conn).to receive(:write)
14
+ allow(conn).to receive(:close)
15
+ allow(TCPSocket).to receive(:new).and_return(conn)
16
+ allow(StringIO).to receive(:open)
17
+ end
18
+
19
+ describe '.create' do
20
+ context 'with nil forwarder_key' do
21
+ it 'should return TcpForwarder object' do
22
+ forwarder = ForwarderFactory.create(nil, 'test_tag', ['localhost:3456', 'localhost:4567'])
23
+ expect(forwarder.kind_of?(TcpForwarder)).to be_truthy
24
+ end
25
+ end
26
+
27
+ context 'with tcpforwarder forwarder_key' do
28
+ it 'should return TcpForwarder object' do
29
+ forwarder = ForwarderFactory.create('tcpforwarder', 'test_tag', ['localhost:3456', 'localhost:4567'])
30
+ expect(forwarder.kind_of?(TcpForwarder)).to be_truthy
31
+ end
32
+ end
33
+
34
+ context 'with sslforwarder forwarder_key' do
35
+ it 'should return SslForwarder object' do
36
+ forwarder = ForwarderFactory.create('sslforwarder', 'test_tag', ['localhost:3456', 'localhost:4567'])
37
+ expect(forwarder.kind_of?(SslForwarder)).to be_truthy
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#initialize' do
43
+ context 'servers is empty' do
44
+ it do
45
+ expect{ForwarderFactory.create('tcpforwarder', 'test_tag', [])}.to raise_error
46
+ end
47
+ end
48
+ context 'servers is nil' do
49
+ it do
50
+ expect{ForwarderFactory.create('tcpforwarder', 'test_tag', nil)}.to raise_error
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#emit' do
56
+ let(:record) { {table_name: 'test_table_name', log: '{"key":"value"}'} }
57
+ before :each do
58
+ forwarder.set_options({buffer_size_limit: ([Time.now.to_i,record].to_msgpack.bytesize * 2.5)})
59
+ end
60
+ context 'when the buffer size is less than threthold' do
61
+ it do
62
+ expect(forwarder.emit(record)).to be(false)
63
+ expect(forwarder.buffer_record_count).to be(1)
64
+ end
65
+ end
66
+ context 'when the buffer size exceeds threthold' do
67
+ it do
68
+ expect(forwarder.emit(record)).to be(false)
69
+ expect(forwarder.emit(record)).to be(false)
70
+ expect(forwarder.buffer_record_count).to be(2)
71
+ expect(forwarder.emit(record)).to be(true)
72
+ expect(forwarder.buffer_record_count).to be(0)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#pickup_server' do
78
+ context 'with only one server' do
79
+ let(:servers) { ['localhost:1111'] }
80
+ let(:forwarder) {
81
+ ForwarderFactory.create('tcpforwarder', 'test_tag', servers)
82
+ }
83
+ it 'expect to return same server' do
84
+ expect(forwarder.pickup_server).to eq('localhost:1111')
85
+ expect(forwarder.pickup_server).to eq('localhost:1111')
86
+ expect(forwarder.pickup_server).to eq('localhost:1111')
87
+ end
88
+ end
89
+ context 'with servers' do
90
+ let(:servers) { ['localhost:1111', 'localhost:2222', 'localhost:3333'] }
91
+ let(:forwarder) {
92
+ ForwarderFactory.create('tcpforwarder', 'test_tag', servers)
93
+ }
94
+ it 'expect to return same server' do
95
+ expect(forwarder.pickup_server).to eq('localhost:1111')
96
+ expect(forwarder.pickup_server).to eq('localhost:2222')
97
+ expect(forwarder.pickup_server).to eq('localhost:3333')
98
+ expect(forwarder.pickup_server).to eq('localhost:1111')
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ end
@@ -556,6 +556,11 @@ describe 'MysqlAlterTableParser' do
556
556
  })
557
557
  end
558
558
  end
559
+ shared_examples "a parser raising an error" do
560
+ it do
561
+ expect { subject }.to raise_error
562
+ end
563
+ end
559
564
  shared_examples "a parser parsing a nonbreaking query" do
560
565
  let(:action_hash) {
561
566
  {
@@ -710,23 +715,36 @@ describe 'MysqlAlterTableParser' do
710
715
  end
711
716
  end
712
717
  end
713
- shared_examples "test field identifier" do |*examples|
714
- context "without ." do
715
- it_behaves_like "test identifier", *examples do
716
- let(:field_ident) { " #{ident}" }
717
- end
718
+ shared_examples "test identifier combination without ." do |*examples|
719
+ it_behaves_like "test identifier", *examples do
720
+ let(:identifier) { " #{ident}" }
718
721
  end
719
- context "with single ." do
720
- it_behaves_like "test identifier", *examples do
721
- let(:field_ident) { " #{ident}.#{ident}"}
722
- end
722
+ end
723
+ shared_examples "test identifier combination with single ." do |*examples|
724
+ it_behaves_like "test identifier", *examples do
725
+ let(:identifier) { " #{ident}.#{ident}" }
723
726
  end
724
- context "with two ." do
725
- it_behaves_like "test identifier", *examples do
726
- let(:field_ident) { " #{ident}.#{ident}.#{ident}" }
727
- end
727
+ end
728
+ shared_examples "test identifier combination with single . starting with ." do |*examples|
729
+ it_behaves_like "test identifier", *examples do
730
+ let(:identifier) { " .#{ident}" }
731
+ end
732
+ end
733
+ shared_examples "test identifier combination with two ." do |*examples|
734
+ it_behaves_like "test identifier", *examples do
735
+ let(:identifier) { " #{ident}.#{ident}.#{ident}" }
736
+ end
737
+ end
738
+ shared_examples "test identifier combination with two . starting with ." do |*examples|
739
+ it_behaves_like "test identifier", *examples do
740
+ let(:identifier) { " .#{ident}.#{ident}" }
728
741
  end
729
742
  end
743
+ shared_examples "test field identifier" do |*examples|
744
+ it_behaves_like "test identifier combination without .", *examples
745
+ it_behaves_like "test identifier combination with single .", *examples
746
+ it_behaves_like "test identifier combination with two .", *examples
747
+ end
730
748
  shared_examples "test identifier" do |*examples|
731
749
  context "unquoted identifier" do
732
750
  it_behaves_like *examples do
@@ -739,7 +757,109 @@ describe 'MysqlAlterTableParser' do
739
757
  end
740
758
  end
741
759
  end
742
-
760
+ shared_examples "test optional to" do |*examples|
761
+ context "with to" do
762
+ it_behaves_like *examples do
763
+ let(:opt_to) {" TO "}
764
+ end
765
+ end
766
+ context "with =" do
767
+ it_behaves_like *examples do
768
+ let(:opt_to) {" = "}
769
+ end
770
+ end
771
+ context "with = without spaces" do
772
+ it_behaves_like *examples do
773
+ let(:opt_to) {"="}
774
+ end
775
+ end
776
+ context "with AS" do
777
+ it_behaves_like *examples do
778
+ let(:opt_to) {" as "}
779
+ end
780
+ end
781
+ context "with none" do
782
+ it_behaves_like *examples do
783
+ let(:opt_to) { "" }
784
+ end
785
+ end
786
+ end
787
+ shared_examples "test table identifier" do |*examples|
788
+ it_behaves_like "test identifier combination without .", *examples
789
+ it_behaves_like "test identifier combination with single .", *examples
790
+ it_behaves_like "test identifier combination with single . starting with .", *examples
791
+ end
792
+ shared_examples "test simple identifier" do |*examples|
793
+ it_behaves_like "test identifier combination without .", *examples
794
+ it_behaves_like "test identifier combination with single .", *examples
795
+ it_behaves_like "test identifier combination with two .", *examples
796
+ it_behaves_like "test identifier combination with two . starting with .", *examples
797
+ end
798
+ shared_examples "test order" do |*examples|
799
+ context "ascending" do
800
+ it_behaves_like *examples do
801
+ let(:order){" ASC"}
802
+ end
803
+ end
804
+ context "descending" do
805
+ it_behaves_like *examples do
806
+ let(:order){" desc"}
807
+ end
808
+ end
809
+ context "no order" do
810
+ it_behaves_like *examples do
811
+ let(:order){""}
812
+ end
813
+ end
814
+ end
815
+ shared_examples "test charset" do |*examples|
816
+ ["CHARACTER SET", "charset"].each do |charset|
817
+ context "charset is #{charset}" do
818
+ it_behaves_like *examples do
819
+ let(:charset){"#{charset}"}
820
+ end
821
+ end
822
+ end
823
+ end
824
+ shared_examples "test charsetname" do |*examples|
825
+ ["default","binary","utf8","latin1"].each do |charsetname|
826
+ context "charsetname is #{charsetname}" do
827
+ it_behaves_like *examples do
828
+ let(:charsetname){"#{charsetname}"}
829
+ end
830
+ end
831
+ end
832
+ end
833
+ shared_examples "test collate" do |*examples|
834
+ ["collate default", "collate utf8_general_ci", "collate latin1_bin"].each do |collate|
835
+ context "collate is #{collate}" do
836
+ it_behaves_like *examples do
837
+ let(:collate){"#{collate}"}
838
+ end
839
+ end
840
+ end
841
+ end
842
+ shared_examples "test collate with equals" do |*examples|
843
+ ["collate=default", "collate = utf8_general_ci"].each do |collate|
844
+ context "collate is #{collate}" do
845
+ it_behaves_like *examples do
846
+ let(:collate){"#{collate}"}
847
+ end
848
+ end
849
+ end
850
+ end
851
+ shared_examples "test optional default" do |*examples|
852
+ context "with default" do
853
+ it_behaves_like *examples do
854
+ let(:default){"default "}
855
+ end
856
+ end
857
+ context "without default" do
858
+ it_behaves_like *examples do
859
+ let(:default){""}
860
+ end
861
+ end
862
+ end
743
863
  context 'add index' do
744
864
  let(:action) { :add_index }
745
865
  context "with a simple add index" do
@@ -911,13 +1031,12 @@ describe 'MysqlAlterTableParser' do
911
1031
  context "enable keys" do
912
1032
  let(:alter_table_action) { "ENABLE KEYS" }
913
1033
  let(:action) { :enable_keys }
914
- let(:action_hash) {
915
- {
916
- action: action,
917
- support_level: :nonbreaking,
918
- query: alter_table_action,
919
- }
920
- }
1034
+ it_behaves_like "a parser parsing a nonbreaking query"
1035
+ end
1036
+ context "disable keys" do
1037
+ let(:alter_table_action) { "DISABLE KEYS" }
1038
+ let(:action) { :disable_keys }
1039
+ it_behaves_like "a parser parsing a nonbreaking query"
921
1040
  end
922
1041
  context "drop primary key" do
923
1042
  let(:alter_table_action) { "DROP PRIMARY KEY" }
@@ -926,16 +1045,98 @@ describe 'MysqlAlterTableParser' do
926
1045
  it_behaves_like "a parser parsing a breaking query"
927
1046
  end
928
1047
  context "drop foreign key" do
929
- let(:alter_table_action) { "DROP FOREIGN KEY#{field_ident}" }
1048
+ let(:alter_table_action) { "DROP FOREIGN KEY#{identifier}" }
930
1049
  let(:action) { :drop_foreign_key }
931
1050
 
932
1051
  it_behaves_like "test field identifier", "a parser parsing a nonbreaking query"
933
1052
  end
934
1053
  context "drop key or index" do
935
- let(:alter_table_action) { "DROP#{key_or_index}#{field_ident}" }
1054
+ let(:alter_table_action) { "DROP#{key_or_index}#{identifier}" }
936
1055
  let(:action) { "drop_#{key_or_index.strip.downcase}".to_sym }
937
1056
 
938
1057
  it_behaves_like "test key or index", "test field identifier", "a parser parsing a nonbreaking query"
939
1058
  end
1059
+ context "rename table" do
1060
+ let(:alter_table_action) { "rename#{opt_to}#{identifier}" }
1061
+ let(:action) { :rename_table }
1062
+
1063
+ it_behaves_like "test optional to", "test table identifier", "a parser parsing a breaking query"
1064
+ context "exception cases" do
1065
+ context "with as followed by quoted identifier and no space in between" do
1066
+ let(:alter_table_action) { "rename as`test_ident1`" } #strange, but valid
1067
+ it_behaves_like "a parser parsing a breaking query"
1068
+ end
1069
+ context "with to followed by quoted identifier and no space in between" do
1070
+ let(:alter_table_action) { "rename to`test_ident1`" }
1071
+ it_behaves_like "a parser parsing a breaking query"
1072
+ end
1073
+ context "no space between rename and to" do
1074
+ let(:alter_table_action) { "renameto `test_ident1`" }
1075
+ it_behaves_like "a parser raising an error"
1076
+ end
1077
+ context "no space between to and table name" do
1078
+ let(:alter_table_action) { "rename totest_ident1" }
1079
+ describe "'totest_ident1' is treated as a table name" do
1080
+ it_behaves_like "a parser parsing a breaking query"
1081
+ end
1082
+ end
1083
+ end
1084
+ end
1085
+ context "order by" do
1086
+ let(:action) { :order_table }
1087
+ context "with single column" do
1088
+ let(:alter_table_action) { "order by#{identifier}#{order}" }
1089
+ it_behaves_like "test simple identifier", "test order", "a parser parsing a breaking query"
1090
+ end
1091
+ context "with multiple columns" do
1092
+ let(:alter_table_action) { "order by#{identifier}#{order},#{identifier}#{order},#{identifier}#{order}" }
1093
+ it_behaves_like "test simple identifier", "test order", "a parser parsing a breaking query"
1094
+ end
1095
+ end
1096
+ context "convert to charset" do
1097
+ let(:action) { :convert_charset }
1098
+ context "test with collate" do
1099
+ let(:alter_table_action) { "convert to #{charset} #{charsetname} #{collate}" }
1100
+ it_behaves_like "test charset", "test charsetname", "test collate", "a parser parsing a breaking query"
1101
+ end
1102
+ context "test without collate" do
1103
+ let(:alter_table_action) { "convert to #{charset} #{charsetname}" }
1104
+ it_behaves_like "test charset", "test charsetname", "a parser parsing a breaking query"
1105
+ end
1106
+ end
1107
+ context "default charset" do
1108
+ let(:action) { :default_charset }
1109
+ context "with collate" do
1110
+ let(:alter_table_action) { "#{default}#{charset}=#{charsetname} #{collate}" }
1111
+ it_behaves_like "test optional default", "test charset", "test charsetname", "test collate", "a parser parsing a breaking query"
1112
+ it_behaves_like "test optional default", "test charset", "test charsetname", "test collate with equals", "a parser parsing a breaking query"
1113
+ end
1114
+ context "without collate" do
1115
+ let(:alter_table_action) { "#{default}#{charset} #{charsetname}" }
1116
+ it_behaves_like "test optional default", "test charset", "test charsetname", "a parser parsing a breaking query"
1117
+ end
1118
+ context "without charset" do
1119
+ #The doc at http://dev.mysql.com/doc/refman/5.6/en/alter-table.html does not indicate that
1120
+ #charset is optional. But this works (tested on mysql 5.5.38)
1121
+ let(:alter_table_action) { "#{default}#{collate}" }
1122
+ it_behaves_like "test optional default","test collate", "a parser parsing a breaking query"
1123
+ it_behaves_like "test optional default","test collate with equals", "a parser parsing a breaking query"
1124
+ end
1125
+ end
1126
+ context "force" do
1127
+ let(:alter_table_action) { "force" }
1128
+ let(:action) { :force }
1129
+ it_behaves_like "a parser parsing a nonbreaking query"
1130
+ end
1131
+ context "discard tablespace" do
1132
+ let(:alter_table_action) { "discard tablespace" }
1133
+ let(:action) { :discard_tablespace }
1134
+ it_behaves_like "a parser parsing a breaking query"
1135
+ end
1136
+ context "import tablespace" do
1137
+ let(:alter_table_action) { "import tablespace" }
1138
+ let(:action) { :import_tablespace }
1139
+ it_behaves_like "a parser parsing a breaking query"
1140
+ end
940
1141
  end
941
1142
  end
@@ -0,0 +1,900 @@
1
+ # coding: utf-8
2
+ require 'spec_helper'
3
+
4
+ module Flydata
5
+ module Parser
6
+ module Mysql
7
+ describe MysqlDumpGeneratorMasterData do
8
+ let(:stdout) { double(:stdout) }
9
+ let(:stderr) { double(:stderr) }
10
+ let(:status) { double(:status) }
11
+ let(:file_path) { File.join('/tmp', "flydata_sync_spec_mysqldump_#{Time.now.to_i}") }
12
+ let(:dump_io) { File.open(file_path, 'r', encoding: "utf-8") }
13
+ let(:default_conf) { {
14
+ 'host' => 'localhost', 'port' => 3306, 'username' => 'admin',
15
+ 'password' => 'pass', 'database' => 'dev', 'tables' => 'users,groups',
16
+ } }
17
+ let(:default_dump_generator) { MysqlDumpGeneratorMasterData.new(default_conf) }
18
+
19
+ describe '#initialize' do
20
+ context 'with password' do
21
+ subject { default_dump_generator.instance_variable_get(:@dump_cmd) }
22
+ it { is_expected.to eq('mysqldump --protocol=tcp -h localhost -P 3306 -uadmin -ppass --skip-lock-tables ' +
23
+ '--single-transaction --hex-blob --flush-logs --master-data=2 dev users groups') }
24
+ end
25
+ context 'without password' do
26
+ let (:dump_generator) do
27
+ MysqlDumpGeneratorMasterData.new(default_conf.merge({'password' => ''}))
28
+ end
29
+ subject { dump_generator.instance_variable_get(:@dump_cmd) }
30
+ it { is_expected.to eq('mysqldump --protocol=tcp -h localhost -P 3306 -uadmin --skip-lock-tables ' +
31
+ '--single-transaction --hex-blob --flush-logs --master-data=2 dev users groups') }
32
+ end
33
+ end
34
+
35
+ describe '#dump' do
36
+ context 'when exit status is not 0' do
37
+ before do
38
+ `touch #{file_path}`
39
+ expect(status).to receive(:exitstatus).and_return 1
40
+ expect(Open3).to receive(:capture3).and_return(
41
+ ['(dummy std out)', '(dummy std err)', status]
42
+ )
43
+ end
44
+ it do
45
+ expect{ default_dump_generator.dump(file_path) }.to raise_error
46
+ expect(File.exists?(file_path)).to be_falsey
47
+ end
48
+ end
49
+ context 'when exit status is 0 but no file' do
50
+ before do
51
+ expect(status).to receive(:exitstatus).and_return 0
52
+ expect(Open3).to receive(:capture3).and_return(
53
+ ['(dummy std out)', '(dummy std err)', status]
54
+ )
55
+ end
56
+ it do
57
+ expect{ default_dump_generator.dump(file_path) }.to raise_error
58
+ expect(File.exists?(file_path)).to be_falsey
59
+ end
60
+ end
61
+ context 'when exit status is 0 but file size is 0' do
62
+ before do
63
+ `touch #{file_path}`
64
+ expect(status).to receive(:exitstatus).and_return 0
65
+ expect(Open3).to receive(:capture3).and_return(
66
+ ['(dummy std out)', '(dummy std err)', status]
67
+ )
68
+ end
69
+ it do
70
+ expect{ default_dump_generator.dump(file_path) }.to raise_error
71
+ expect(File.exists?(file_path)).to be_truthy
72
+ end
73
+ end
74
+ context 'when exit status is 0' do
75
+ before do
76
+ `echo "something..." > #{file_path}`
77
+ expect(status).to receive(:exitstatus).and_return 0
78
+ expect(Open3).to receive(:capture3).and_return(
79
+ ['(dummy std out)', '(dummy std err)', status]
80
+ )
81
+ end
82
+ it do
83
+ expect(default_dump_generator.dump(file_path)).to be_truthy
84
+ expect(File.exists?(file_path)).to be_truthy
85
+ end
86
+ end
87
+ after :each do
88
+ File.delete(file_path) if File.exists?(file_path)
89
+ end
90
+ end
91
+ end
92
+
93
+ describe MysqlDumpParser do
94
+ let(:file_path) { File.join('/tmp', "flydata_sync_spec_mysqldump_parse_#{Time.now.to_i}") }
95
+ let(:dump_io) { File.open(file_path, 'r', encoding: "utf-8") }
96
+ let(:default_parser) { MysqlDumpParser.new }
97
+
98
+ def generate_dump_file(content)
99
+ File.open(file_path, 'w') {|f| f.write(content)}
100
+ end
101
+
102
+ after do
103
+ File.delete(file_path) if File.exists?(file_path)
104
+ end
105
+
106
+ describe '#parse' do
107
+ DUMP_HEADER = <<EOT
108
+ -- MySQL dump 10.13 Distrib 5.6.13, for osx10.9 (x86_64)
109
+ --
110
+ -- Host: localhost Database: sync_test
111
+ -- ------------------------------------------------------
112
+ -- Server version>--5.6.13-log
113
+
114
+ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
115
+ /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
116
+ /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
117
+ /*!40101 SET NAMES utf8 */;
118
+ /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
119
+ /*!40103 SET TIME_ZONE='+00:00' */;
120
+ /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
121
+ /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
122
+ /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
123
+ /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
124
+
125
+ --
126
+ -- Position to start replication or point-in-time recovery from
127
+ --
128
+
129
+ -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000267', MASTER_LOG_POS=120;
130
+ EOT
131
+
132
+ def index_after(content, string)
133
+ content.index(string) + string.bytesize + 1
134
+ end
135
+
136
+ let(:default_parser) { MysqlDumpParser.new }
137
+ let(:default_binlog_pos) { {binfile: 'mysql-bin.000267', pos: 120 } }
138
+ let(:dump_pos_after_binlog_pos) { index_after(DUMP_HEADER, 'MASTER_LOG_POS=120;') }
139
+
140
+ let(:create_table_block) { double('create_table_block') }
141
+ let(:insert_record_block) { double('insert_record_block') }
142
+ let(:check_point_block) { double('check_point_block') }
143
+
144
+ before do
145
+ generate_dump_file('')
146
+ end
147
+
148
+ context 'when dump does not contain binlog pos' do
149
+ before { generate_dump_file('dummy content') }
150
+ it do
151
+ expect(create_table_block).to receive(:call).never
152
+ expect(insert_record_block).to receive(:call).never
153
+ expect(check_point_block).to receive(:call).never
154
+ binlog_pos = default_parser.parse(
155
+ dump_io, create_table_block, insert_record_block, check_point_block
156
+ )
157
+ expect(binlog_pos).to be_nil
158
+ end
159
+ end
160
+
161
+ context 'when dump contains only binlog pos' do
162
+ before { generate_dump_file(<<EOT
163
+ #{DUMP_HEADER}
164
+ EOT
165
+ ) }
166
+ it do
167
+ expect(create_table_block).to receive(:call).never
168
+ expect(insert_record_block).to receive(:call).never
169
+ expect(check_point_block).to receive(:call).once
170
+ binlog_pos = default_parser.parse(
171
+ dump_io, create_table_block, insert_record_block, check_point_block
172
+ )
173
+ expect(binlog_pos).to eq(default_binlog_pos)
174
+ end
175
+ end
176
+
177
+ context 'when dump contains create table without records' do
178
+ let(:dump_content) { <<EOT
179
+ #{DUMP_HEADER}
180
+
181
+ DROP TABLE IF EXISTS `users_login`;
182
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
183
+ /*!40101 SET character_set_client = utf8 */;
184
+ CREATE TABLE `users_login` (
185
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
186
+ `login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
187
+ `create_time` datetime NOT NULL COMMENT 'create time',
188
+ `update_time` datetime NOT NULL COMMENT 'update time',
189
+ PRIMARY KEY (`user_id`)
190
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='';
191
+ /*!40101 SET character_set_client = @saved_cs_client */;
192
+
193
+ --
194
+ -- Dumping data for table `users_login`
195
+ --
196
+
197
+ LOCK TABLES `users_login` WRITE;
198
+ /*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
199
+ /*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
200
+ UNLOCK TABLES;
201
+
202
+ EOT
203
+ }
204
+ before { generate_dump_file(dump_content) }
205
+ it do
206
+ expect(create_table_block).to receive(:call) { |mysql_table|
207
+ expect(mysql_table.table_name).to eq('users_login')
208
+ }.once
209
+ expect(insert_record_block).to receive(:call).never
210
+ expect(check_point_block).to receive(:call) { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
211
+ expect(mysql_table).to be_nil
212
+ expect(last_pos).to eq(dump_pos_after_binlog_pos)
213
+ expect(binlog_pos).to eq(default_binlog_pos)
214
+ expect(state).to eq(MysqlDumpParser::State::CREATE_TABLE)
215
+ expect(substate).to be_nil
216
+ }
217
+ expect(check_point_block).to receive(:call) { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
218
+ expect(mysql_table.table_name).to eq('users_login')
219
+ expect(mysql_table.columns.keys).to eq(['user_id', 'login_count', 'create_time', 'update_time'])
220
+ expect(last_pos).to eq(index_after(dump_content, 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=\'\';'))
221
+ expect(binlog_pos).to eq(default_binlog_pos)
222
+ expect(state).to eq(MysqlDumpParser::State::INSERT_RECORD)
223
+ expect(substate).to be_nil
224
+ }
225
+ expect(check_point_block).to receive(:call) { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
226
+ expect(mysql_table.table_name).to eq('users_login')
227
+ expect(last_pos).to eq(index_after(dump_content, 'UNLOCK TABLES;'))
228
+ expect(binlog_pos).to eq(default_binlog_pos)
229
+ expect(state).to eq(MysqlDumpParser::State::CREATE_TABLE)
230
+ expect(substate).to be_nil
231
+ }
232
+ expect(check_point_block).to receive(:call).never
233
+
234
+ binlog_pos = default_parser.parse(
235
+ dump_io, create_table_block, insert_record_block, check_point_block
236
+ )
237
+ expect(binlog_pos).to eq(default_binlog_pos)
238
+ end
239
+ end
240
+
241
+ context 'when dump contains create table with multi inserts' do
242
+ let(:dump_content) { <<EOT
243
+ #{DUMP_HEADER}
244
+
245
+ DROP TABLE IF EXISTS `users_login`;
246
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
247
+ /*!40101 SET character_set_client = utf8 */;
248
+ CREATE TABLE `users_login` (
249
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
250
+ `login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
251
+ `comment` varchar(255) DEFAULT NULL COMMENT 'comment',
252
+ `create_time` datetime NOT NULL COMMENT 'create time',
253
+ `update_time` datetime NOT NULL COMMENT 'update time',
254
+ PRIMARY KEY (`user_id`)
255
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
256
+ /*!40101 SET character_set_client = @saved_cs_client */;
257
+
258
+ --
259
+ -- Dumping data for table `users_login`
260
+ --
261
+
262
+ LOCK TABLES `users_login` WRITE;
263
+ /*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
264
+ INSERT INTO `users_login` VALUES (15,46,'moodier','2001-10-02 08:18:08','1986-06-11 22:50:10'),(35,6,'missあいうえおteer','1991-10-15 19:38:07','1970-10-01 22:03:10'),(52,33,'sub\\\\field','1972-08-23 20:16:08','1974-10-10 23:28:11');
265
+ INSERT INTO `users_login` VALUES (373,31,'out\\'swearing','1979-10-07 08:10:08','2006-02-22 16:26:04'),(493,8,'schizophrenic','1979-07-06 07:34:07','1970-08-09 01:21:01');
266
+ /*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
267
+ UNLOCK TABLES;
268
+ /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
269
+
270
+ EOT
271
+ }
272
+ before { generate_dump_file(dump_content) }
273
+ it do
274
+ # create_table_block
275
+ expect(create_table_block).to receive(:call) { |mysql_table|
276
+ expect(mysql_table.table_name).to eq('users_login')
277
+ expect(mysql_table.columns.keys).to eq(['user_id', 'login_count', 'comment', 'create_time', 'update_time'])
278
+ }.once
279
+ expect(create_table_block).to receive(:call).never
280
+
281
+ # insert_record_block
282
+ [
283
+ [ %w(15 46 moodier 2001-10-02\ 08:18:08 1986-06-11\ 22:50:10),
284
+ %w(35 6 missあいうえおteer 1991-10-15\ 19:38:07 1970-10-01\ 22:03:10),
285
+ %w(52 33 sub\\field 1972-08-23\ 20:16:08 1974-10-10\ 23:28:11),],
286
+ [ %w(373 31 out'swearing 1979-10-07\ 08:10:08 2006-02-22\ 16:26:04),
287
+ %w(493 8 schizophrenic 1979-07-06\ 07:34:07 1970-08-09\ 01:21:01),]
288
+ ].each do |expected_values|
289
+ expect(insert_record_block).to receive(:call) { |mysql_table, values_set|
290
+ expect(mysql_table.table_name).to eq('users_login')
291
+ expect(values_set).to eq(expected_values)
292
+ nil
293
+ }.once
294
+ end
295
+ expect(insert_record_block).to receive(:call).never
296
+
297
+ # insert_record_block
298
+ [
299
+ {state: MysqlDumpParser::State::CREATE_TABLE},
300
+ {state: MysqlDumpParser::State::INSERT_RECORD},
301
+ {state: MysqlDumpParser::State::CREATE_TABLE,
302
+ last_pos: index_after(dump_content, 'UNLOCK TABLES;') + 10}
303
+ ].each do |expected_params|
304
+ expect(check_point_block).to receive(:call) { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
305
+ expect(mysql_table.table_name).to eq('users_login') if mysql_table
306
+ expect(state).to eq(expected_params[:state])
307
+ if expected_params[:last_pos]
308
+ expect(last_pos).to eq(expected_params[:last_pos])
309
+ end
310
+ expect(binlog_pos).to eq(default_binlog_pos)
311
+ expect(substate).to eq(expected_params[:substate])
312
+ expected_params[:result]
313
+ }.once
314
+ end
315
+ expect(check_point_block).to receive(:call).never
316
+
317
+ binlog_pos = default_parser.parse(
318
+ dump_io, create_table_block, insert_record_block, check_point_block
319
+ )
320
+ expect(binlog_pos).to eq(default_binlog_pos)
321
+ end
322
+ end
323
+
324
+ context 'when dump contains create table with records' do
325
+ let(:dump_content) { <<EOT
326
+ #{DUMP_HEADER}
327
+
328
+ DROP TABLE IF EXISTS `users_login`;
329
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
330
+ /*!40101 SET character_set_client = utf8 */;
331
+ CREATE TABLE `users_login` (
332
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
333
+ `login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
334
+ `comment` varchar(255) DEFAULT NULL COMMENT 'comment',
335
+ `create_time` datetime NOT NULL COMMENT 'create time',
336
+ `update_time` datetime NOT NULL COMMENT 'update time',
337
+ PRIMARY KEY (`user_id`)
338
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
339
+ /*!40101 SET character_set_client = @saved_cs_client */;
340
+
341
+ --
342
+ -- Dumping data for table `users_login`
343
+ --
344
+
345
+ LOCK TABLES `users_login` WRITE;
346
+ /*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
347
+ INSERT INTO `users_login` VALUES (15,46,'moodier','2001-10-02 08:18:08','1986-06-11 22:50:10'),(35,6,NULL,'1991-10-15 19:38:07','1970-10-01 22:03:10'),(52,33,'subfield','1972-08-23 20:16:08','1974-10-10 23:28:11');
348
+ /*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
349
+ UNLOCK TABLES;
350
+ /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
351
+
352
+ EOT
353
+ }
354
+ before { generate_dump_file(dump_content) }
355
+ it do
356
+ # create_table_block
357
+ expect(create_table_block).to receive(:call) { |mysql_table|
358
+ expect(mysql_table.table_name).to eq('users_login')
359
+ expect(mysql_table.columns.keys).to eq(['user_id', 'login_count', 'comment', 'create_time', 'update_time'])
360
+ }.once
361
+ expect(create_table_block).to receive(:call).never
362
+
363
+ # insert_record_block
364
+ [
365
+ [ %w(15 46 moodier 2001-10-02\ 08:18:08 1986-06-11\ 22:50:10),
366
+ ['35', '6', nil, '1991-10-15 19:38:07', '1970-10-01 22:03:10'],
367
+ %w(52 33 subfield 1972-08-23\ 20:16:08 1974-10-10\ 23:28:11),],
368
+ ].each do |expected_values|
369
+ expect(insert_record_block).to receive(:call) { |mysql_table, values_set|
370
+ expect(mysql_table.table_name).to eq('users_login')
371
+ expect(values_set).to eq(expected_values)
372
+ nil
373
+ }.once
374
+ end
375
+ expect(insert_record_block).to receive(:call).never
376
+
377
+ # insert_record_block
378
+ [
379
+ {state: MysqlDumpParser::State::CREATE_TABLE},
380
+ {state: MysqlDumpParser::State::INSERT_RECORD},
381
+ {state: MysqlDumpParser::State::CREATE_TABLE,
382
+ last_pos: index_after(dump_content, 'UNLOCK TABLES;')}
383
+ ].each do |expected_params|
384
+ expect(check_point_block).to receive(:call) { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
385
+ expect(mysql_table.table_name).to eq('users_login') if mysql_table
386
+ expect(state).to eq(expected_params[:state])
387
+ if expected_params[:last_pos]
388
+ expect(last_pos).to eq(expected_params[:last_pos])
389
+ end
390
+ expect(binlog_pos).to eq(default_binlog_pos)
391
+ expect(substate).to eq(expected_params[:substate])
392
+ expected_params[:result]
393
+ }.once
394
+ end
395
+ expect(check_point_block).to receive(:call).never
396
+
397
+ binlog_pos = default_parser.parse(
398
+ dump_io, create_table_block, insert_record_block, check_point_block
399
+ )
400
+ expect(binlog_pos).to eq(default_binlog_pos)
401
+ end
402
+ end
403
+
404
+ context 'with resume after creating table' do
405
+ let(:dump_content_head) { <<EOT
406
+ #{DUMP_HEADER}
407
+
408
+ DROP TABLE IF EXISTS `users_login`;
409
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
410
+ /*!40101 SET character_set_client = utf8 */;
411
+ CREATE TABLE `users_login` (
412
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
413
+ `login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
414
+ `comment` varchar(255) DEFAULT NULL COMMENT 'comment',
415
+ `create_time` datetime NOT NULL COMMENT 'create time',
416
+ `update_time` datetime NOT NULL COMMENT 'update time',
417
+ PRIMARY KEY (`user_id`)
418
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
419
+ /*!40101 SET character_set_client = @saved_cs_client */;
420
+
421
+ EOT
422
+ }
423
+ let(:dump_content_all) { <<EOT
424
+ #{dump_content_head}
425
+ --
426
+ -- Dumping data for table `users_login`
427
+ --
428
+
429
+ LOCK TABLES `users_login` WRITE;
430
+ /*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
431
+ INSERT INTO `users_login` VALUES (15,46,'moodier','2001-10-02 08:18:08','1986-06-11 22:50:10'),(35,6,'missteer','1991-10-15 19:38:07','1970-10-01 22:03:10'),(52,33,'subfield','1972-08-23 20:16:08','1974-10-10 23:28:11');
432
+ /*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
433
+ UNLOCK TABLES;
434
+ /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
435
+ EOT
436
+ }
437
+ before do
438
+ generate_dump_file(dump_content_head)
439
+ default_parser.parse(
440
+ dump_io,
441
+ Proc.new{},
442
+ Proc.new{
443
+ raise "Should not be called"
444
+ },
445
+ Proc.new{ |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
446
+ if mysql_table
447
+ @mysql_table = mysql_table
448
+ @option = { status: Flydata::Command::Sync::STATUS_PARSING,
449
+ table_name: mysql_table.table_name,
450
+ last_pos: last_pos,
451
+ binlog_pos: binlog_pos,
452
+ state: state,
453
+ substate: substate,
454
+ mysql_table: mysql_table }
455
+ end
456
+ }
457
+ )
458
+ expect(@option).to eq({
459
+ status: Flydata::Command::Sync::STATUS_PARSING,
460
+ table_name: 'users_login',
461
+ last_pos: index_after(dump_content_head, 'ENGINE=InnoDB DEFAULT CHARSET=utf8;'),
462
+ binlog_pos: default_binlog_pos,
463
+ state: MysqlDumpParser::State::INSERT_RECORD,
464
+ substate: nil,
465
+ mysql_table: @mysql_table
466
+ })
467
+ end
468
+ it do
469
+ generate_dump_file(dump_content_all)
470
+ parser_for_resume = MysqlDumpParser.new(@option)
471
+
472
+ # create_table_block
473
+ expect(create_table_block).to receive(:call).never
474
+
475
+ # insert_record_block
476
+ [
477
+ [ %w(15 46 moodier 2001-10-02\ 08:18:08 1986-06-11\ 22:50:10),
478
+ %w(35 6 missteer 1991-10-15\ 19:38:07 1970-10-01\ 22:03:10),
479
+ %w(52 33 subfield 1972-08-23\ 20:16:08 1974-10-10\ 23:28:11),],
480
+ ].each do |expected_values|
481
+ expect(insert_record_block).to receive(:call) { |mysql_table, values|
482
+ expect(mysql_table.table_name).to eq('users_login')
483
+ expect(values).to eq(expected_values)
484
+ nil
485
+ }.once
486
+ end
487
+ expect(insert_record_block).to receive(:call).never
488
+
489
+ # check_point_block
490
+ [
491
+ {state: MysqlDumpParser::State::CREATE_TABLE,
492
+ last_pos: index_after(dump_content_all, 'UNLOCK TABLES;')}
493
+ ].each do |expected_params|
494
+ expect(check_point_block).to receive(:call) { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
495
+ expect(mysql_table.table_name).to eq('users_login') if mysql_table
496
+ expect(state).to eq(expected_params[:state])
497
+ if expected_params[:last_pos]
498
+ expect(last_pos).to eq(expected_params[:last_pos])
499
+ end
500
+ expect(binlog_pos).to eq(default_binlog_pos)
501
+ expect(substate).to eq(expected_params[:substate])
502
+ expected_params[:result]
503
+ }.once
504
+ end
505
+ expect(check_point_block).to receive(:call).never
506
+
507
+ binlog_pos = parser_for_resume.parse(
508
+ dump_io, create_table_block, insert_record_block, check_point_block
509
+ )
510
+ expect(binlog_pos).to eq(default_binlog_pos)
511
+ end
512
+ end
513
+
514
+ context 'with resume during insert records' do
515
+ let(:dump_content) { <<EOT
516
+ #{DUMP_HEADER}
517
+
518
+ DROP TABLE IF EXISTS `users_login`;
519
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
520
+ /*!40101 SET character_set_client = utf8 */;
521
+ CREATE TABLE `users_login` (
522
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
523
+ `login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
524
+ `comment` varchar(255) DEFAULT NULL COMMENT 'comment',
525
+ `create_time` datetime NOT NULL COMMENT 'create time',
526
+ `update_time` datetime NOT NULL COMMENT 'update time',
527
+ PRIMARY KEY (`user_id`)
528
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
529
+ /*!40101 SET character_set_client = @saved_cs_client */;
530
+
531
+ --
532
+ -- Dumping data for table `users_login`
533
+ --
534
+
535
+ LOCK TABLES `users_login` WRITE;
536
+ /*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
537
+ INSERT INTO `users_login` VALUES (15,46,'moodier','2001-10-02 08:18:08','1986-06-11 22:50:10'),(35,6,'missteer','1991-10-15 19:38:07','1970-10-01 22:03:10'),(52,33,'subfield','1972-08-23 20:16:08','1974-10-10 23:28:11');
538
+ INSERT INTO `users_login` VALUES (194,11,'pandemonium','2008-01-22 22:15:10','1991-04-04 17:30:05'),(230,7,'cloudburst','2010-12-28 11:46:11','1971-06-22 13:08:01');
539
+ /*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
540
+ UNLOCK TABLES;
541
+ /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
542
+ EOT
543
+ }
544
+ before do
545
+ generate_dump_file(dump_content)
546
+
547
+ insert_record_block_for_resume = double('insert_record_block_for_resume')
548
+ expect(insert_record_block_for_resume).to receive(:call) { true }.once
549
+ expect(insert_record_block_for_resume).to receive(:call) { false }.once
550
+
551
+ default_parser.parse(
552
+ dump_io,
553
+ Proc.new{},
554
+ insert_record_block_for_resume,
555
+ Proc.new{ |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
556
+ if last_pos == index_after(dump_content, "'1972-08-23 20:16:08','1974-10-10 23:28:11');")
557
+ @mysql_table = mysql_table
558
+ @option = { status: Flydata::Command::Sync::STATUS_PARSING,
559
+ table_name: mysql_table.table_name,
560
+ last_pos: last_pos,
561
+ binlog_pos: binlog_pos,
562
+ state: state,
563
+ substate: substate,
564
+ mysql_table: mysql_table }
565
+ end
566
+ }
567
+ )
568
+ expect(@option).to eq({
569
+ status: Flydata::Command::Sync::STATUS_PARSING,
570
+ table_name: 'users_login',
571
+ last_pos: index_after(dump_content, "'1972-08-23 20:16:08','1974-10-10 23:28:11');"),
572
+ binlog_pos: default_binlog_pos,
573
+ state: MysqlDumpParser::State::INSERT_RECORD,
574
+ substate: nil,
575
+ mysql_table: @mysql_table
576
+ })
577
+ end
578
+ it do
579
+ generate_dump_file(dump_content)
580
+ parser_for_resume = MysqlDumpParser.new(@option)
581
+
582
+ # create_table_block
583
+ expect(create_table_block).to receive(:call).never
584
+
585
+ # insert_record_block
586
+ [
587
+ [ %w(194 11 pandemonium 2008-01-22\ 22:15:10 1991-04-04\ 17:30:05),
588
+ %w(230 7 cloudburst 2010-12-28\ 11:46:11 1971-06-22\ 13:08:01),],
589
+ ].each do |expected_values|
590
+ expect(insert_record_block).to receive(:call) { |mysql_table, values_set|
591
+ expect(mysql_table.table_name).to eq('users_login')
592
+ expect(values_set).to eq(expected_values)
593
+ nil
594
+ }.once
595
+ end
596
+ expect(insert_record_block).to receive(:call).never
597
+
598
+ # check_point_block
599
+ [
600
+ {state: MysqlDumpParser::State::CREATE_TABLE,
601
+ last_pos: index_after(dump_content, 'UNLOCK TABLES;')}
602
+ ].each do |expected_params|
603
+ expect(check_point_block).to receive(:call) { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
604
+ expect(mysql_table.table_name).to eq('users_login') if mysql_table
605
+ expect(state).to eq(expected_params[:state])
606
+ if expected_params[:last_pos]
607
+ expect(last_pos).to eq(expected_params[:last_pos])
608
+ end
609
+ expect(binlog_pos).to eq(default_binlog_pos)
610
+ expect(substate).to eq(expected_params[:substate])
611
+ expected_params[:result]
612
+ }.once
613
+ end
614
+ expect(check_point_block).to receive(:call).never
615
+
616
+ binlog_pos = parser_for_resume.parse(
617
+ dump_io, create_table_block, insert_record_block, check_point_block
618
+ )
619
+ expect(binlog_pos).to eq(default_binlog_pos)
620
+ end
621
+ end
622
+
623
+ context 'when dump contains contain escape characters' do
624
+ let(:dump_content) { <<EOT
625
+ #{DUMP_HEADER}
626
+
627
+ DROP TABLE IF EXISTS `users_login`;
628
+ /*!40101 SET @saved_cs_client = @@character_set_client */;
629
+ /*!40101 SET character_set_client = utf8 */;
630
+ CREATE TABLE `users_login` (
631
+ `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
632
+ `login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
633
+ `comment` varchar(255) DEFAULT NULL COMMENT 'comment',
634
+ `create_time` datetime NOT NULL COMMENT 'create time',
635
+ `update_time` datetime NOT NULL COMMENT 'update time',
636
+ PRIMARY KEY (`user_id`)
637
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
638
+ /*!40101 SET character_set_client = @saved_cs_client */;
639
+
640
+ --
641
+ -- Dumping data for table `users_login`
642
+ --
643
+
644
+ LOCK TABLES `users_login` WRITE;
645
+ /*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
646
+ INSERT INTO `users_login` VALUES (15,46,'moodier)','2001-10-02 08:18:08','1986-06-11 22:50:10'),(35,6,'miss,teer','1991-10-15 19:38:07','1970-10-01 22:03:10'),(52,33,'subfield\\'','1972-08-23 20:16:08','1974-10-10 23:28:11');
647
+ INSERT INTO `users_login` VALUES (373,31,'outs\\nwearing','1979-10-07 08:10:08','2006-02-22 16:26:04'),(493,8,'schiz\tophrenic','1979-07-06 07:34:07\\'),','1970-08-09,01:21:01,\\')');
648
+ /*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
649
+ UNLOCK TABLES;
650
+ /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
651
+
652
+ EOT
653
+ }
654
+ before { generate_dump_file(dump_content) }
655
+ it do
656
+ # create_table_block
657
+ expect(create_table_block).to receive(:call) { |mysql_table|
658
+ expect(mysql_table.table_name).to eq('users_login')
659
+ expect(mysql_table.columns.keys).to eq(['user_id', 'login_count', 'comment', 'create_time', 'update_time'])
660
+ }.once
661
+ expect(create_table_block).to receive(:call).never
662
+
663
+ # insert_record_block
664
+ [
665
+ [ %w(15 46 moodier\) 2001-10-02\ 08:18:08 1986-06-11\ 22:50:10),
666
+ %w(35 6 miss,teer 1991-10-15\ 19:38:07 1970-10-01\ 22:03:10),
667
+ %w(52 33 subfield' 1972-08-23\ 20:16:08 1974-10-10\ 23:28:11),],
668
+ [ ['373','31',"outs\nwearing",'1979-10-07 08:10:08','2006-02-22 16:26:04'],
669
+ ['493','8',"schiz\tophrenic",'1979-07-06 07:34:07\'),','1970-08-09,01:21:01,\')'] ]
670
+ ].each do |expected_values|
671
+ expect(insert_record_block).to receive(:call) { |mysql_table, values_set|
672
+ expect(mysql_table.table_name).to eq('users_login')
673
+ expect(values_set).to eq(expected_values)
674
+ nil
675
+ }.once
676
+ end
677
+ expect(insert_record_block).to receive(:call).never
678
+
679
+ # insert_record_block
680
+ [
681
+ {state: MysqlDumpParser::State::CREATE_TABLE},
682
+ {state: MysqlDumpParser::State::INSERT_RECORD},
683
+ {state: MysqlDumpParser::State::CREATE_TABLE,
684
+ last_pos: index_after(dump_content, 'UNLOCK TABLES;')}
685
+ ].each do |expected_params|
686
+ expect(check_point_block).to receive(:call) { |mysql_table, last_pos, bytesize, binlog_pos, state, substate|
687
+ expect(mysql_table.table_name).to eq('users_login') if mysql_table
688
+ expect(state).to eq(expected_params[:state])
689
+ if expected_params[:last_pos]
690
+ expect(last_pos).to eq(expected_params[:last_pos])
691
+ end
692
+ expect(binlog_pos).to eq(default_binlog_pos)
693
+ expect(substate).to eq(expected_params[:substate])
694
+ expected_params[:result]
695
+ }.once
696
+ end
697
+ expect(check_point_block).to receive(:call).never
698
+
699
+ binlog_pos = default_parser.parse(
700
+ dump_io, create_table_block, insert_record_block, check_point_block
701
+ )
702
+ expect(binlog_pos).to eq(default_binlog_pos)
703
+ end
704
+ end
705
+ end
706
+ end
707
+ describe MysqlDumpParser::InsertParser do
708
+ describe '#parse' do
709
+ let(:target_line) { '' }
710
+ let(:parser) { MysqlDumpParser::InsertParser.new }
711
+ subject { parser.parse(target_line) }
712
+
713
+ context 'when single record' do
714
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,'hogehoge','2014-04-15 13:49:14');" }
715
+ it 'should return one element array' do
716
+ expect(subject).to eq([['1','hogehoge','2014-04-15 13:49:14']])
717
+ end
718
+ end
719
+
720
+ context 'when 2 records' do
721
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,'foo','2014-04-15 13:49:14'),(2,'var','2014-04-15 14:21:05');" }
722
+ it 'should return two element array' do
723
+ expect(subject).to eq([['1','foo','2014-04-15 13:49:14'],['2','var','2014-04-15 14:21:05']])
724
+ end
725
+ end
726
+
727
+ context 'when data includes carriage return' do
728
+ let(:target_line) { %q|INSERT INTO `test_table` VALUES (1,'abcd\refgh','2014-04-15 13:49:14');| }
729
+ it 'should escape return carriage' do
730
+ expect(subject).to eq([['1',"abcd\refgh",'2014-04-15 13:49:14']])
731
+ end
732
+ end
733
+
734
+ context 'when data includes new line' do
735
+ let(:target_line) { %q|INSERT INTO `test_table` VALUES (1,'abcd\nefgh','2014-04-15 13:49:14');| }
736
+ it 'should escape new line' do
737
+ expect(subject).to eq([['1',"abcd\nefgh",'2014-04-15 13:49:14']])
738
+ end
739
+ end
740
+
741
+ context 'when data includes escaped single quotation' do
742
+ let(:target_line) { %q|INSERT INTO `test_table` VALUES (1,'abcd\',\'efgh','2014-04-15 13:49:14');| }
743
+ it 'should escape single quotation' do
744
+ expect(subject).to eq([['1',"abcd','efgh",'2014-04-15 13:49:14']])
745
+ end
746
+ end
747
+
748
+ context 'when data includes back slash' do
749
+ let(:target_line) { %q|INSERT INTO `test_table` VALUES (1,'abcd\\efgh','2014-04-15 13:49:14');| }
750
+ it 'should escape back slash' do
751
+ expect(subject).to eq([['1',"abcd\\efgh",'2014-04-15 13:49:14']])
752
+ end
753
+ end
754
+
755
+ context 'when data includes back slash with double quote' do
756
+ # \"\'\' value on insert query shoulde be "''
757
+ let(:target_line) { %q|INSERT INTO `tast_table` VALUES (1,'\"\'\'','2014-04-15 13:49:14');| }
758
+ it 'should escape back slash' do
759
+ expect(subject).to eq([['1',%q|"''| ,'2014-04-15 13:49:14']])
760
+ end
761
+ end
762
+
763
+ context 'when data includes back slash with double quote another case' do
764
+ # \"\"\"\"\"''\"'' value on insert query shoulde be """""''"''
765
+ let(:target_line) { %q|INSERT INTO `test_table` VALUES (1,'\"\"\"\"\"\'\'\"\'\'','2014-04-15 13:49:14');| }
766
+ it 'should escape back slash' do
767
+ expect(subject).to eq([['1',%q|"""""''"''|,'2014-04-15 13:49:14']])
768
+ end
769
+ end
770
+
771
+ context 'when data includes mixed escaped characters' do
772
+ let(:target_line) { %q|INSERT INTO `test_table` VALUES (1,'ab\rcd\\e\nf\',\'gh','2014-04-15 13:49:14');| }
773
+ it 'should escape all' do
774
+ expect(subject).to eq([['1',"ab\rcd\\e\nf','gh",'2014-04-15 13:49:14']])
775
+ end
776
+ end
777
+
778
+ context 'when data includes mixed escaped characters in a row' do
779
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,'ab\\\\ncd\\\\\\nefgh','2014-04-15 13:49:14');" }
780
+ it 'should escape all' do
781
+ expect(subject).to eq([['1',"ab\\ncd\\\nefgh",'2014-04-15 13:49:14']])
782
+ end
783
+ end
784
+
785
+ context 'when data end with back slash' do
786
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,'D:\\\\download\\\\','2014-04-15 13:49:14');" }
787
+ it 'should escape back slash' do
788
+ expect(subject).to eq([['1',"D:\\download\\",'2014-04-15 13:49:14']])
789
+ end
790
+ end
791
+
792
+ context 'when comma is the first character of a string' do
793
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,',9','2014-04-15 13:49:14');" }
794
+ it 'should parse the string correctly' do
795
+ expect(subject).to eq([['1',',9','2014-04-15 13:49:14']])
796
+ end
797
+ end
798
+
799
+ context 'when comma is the last character of a string' do
800
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,'9,','2014-04-15 13:49:14');" }
801
+ it 'should parse the string correctly' do
802
+ expect(subject).to eq([['1','9,','2014-04-15 13:49:14']])
803
+ end
804
+ end
805
+
806
+ context 'when a string consists of a comma' do
807
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,',','2014-04-15 13:49:14');" }
808
+ it 'should parse the string correctly' do
809
+ expect(subject).to eq([['1',',','2014-04-15 13:49:14']])
810
+ end
811
+ end
812
+
813
+ context 'when two commas are given as a string' do
814
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,',,','2014-04-15 13:49:14');" }
815
+ it 'should parse the string correctly' do
816
+ expect(subject).to eq([['1',',,','2014-04-15 13:49:14']])
817
+ end
818
+ end
819
+
820
+ context 'when an empty string value is given' do
821
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,'','2014-04-15 13:49:14');" }
822
+ it 'should parse the string correctly' do
823
+ expect(subject).to eq([['1','','2014-04-15 13:49:14']])
824
+ end
825
+ end
826
+
827
+ context 'when a value consists of a comma followed by closing bracket' do
828
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,'),ksd','2014-04-15 13:49:14');" }
829
+ it 'should parse the string correctly' do
830
+ expect(subject).to eq([['1','),ksd','2014-04-15 13:49:14']])
831
+ end
832
+ end
833
+
834
+ context 'when there is a white space before the closing bracket' do
835
+ let(:target_line) { "INSERT INTO `test_table` VALUES (1,'aa','2014-04-15 13:49:14' );" }
836
+ it 'should fail to parse. This is intentional for performance reason' do
837
+ expect{subject}.to raise_error
838
+ end
839
+ end
840
+
841
+ context 'when an integer that has leading zeros is given' do
842
+ let(:target_line) {"INSERT INTO `test_table` VALUES (1,00013);"}
843
+ it 'should remove the leading zeros' do
844
+ expect(subject).to eq([['1', '13']])
845
+ end
846
+ end
847
+
848
+ context 'when a decimal that has leading zeros is given' do
849
+ let(:target_line) {"INSERT INTO `test_table` VALUES (1,00013.40);"}
850
+ it 'should remove the leading zeros' do
851
+ expect(subject).to eq([['1', '13.40']])
852
+ end
853
+ end
854
+
855
+ context 'when a timestamp that has leading zeros is given' do
856
+ let(:target_line) {"INSERT INTO `test_table` VALUES (1,'0004-04-15 13:49:14');"}
857
+ it 'should not remove the leading zeros' do
858
+ expect(subject).to eq([['1', '0004-04-15 13:49:14']])
859
+ end
860
+ end
861
+
862
+ context 'when a string that has leading zeros is given' do
863
+ let(:target_line) {"INSERT INTO `test_table` VALUES (1,'00000aa');"}
864
+ it 'should not remove the leading zeros' do
865
+ expect(subject).to eq([['1', '00000aa']])
866
+ end
867
+ end
868
+
869
+ context 'when a string that has leading zeros, numbers and a comma in between is given' do
870
+ let(:target_line) {"INSERT INTO `test_table` VALUES (1,'0003,00033');"}
871
+ it 'should not remove the leading zeros' do
872
+ expect(subject).to eq([['1', '0003,00033']])
873
+ end
874
+ end
875
+
876
+ context 'when a string that has leading zeros, has only numbers is given' do
877
+ let(:target_line) {"INSERT INTO `test_table` VALUES (1,'00033');"}
878
+ it 'should not remove the leading zeros' do
879
+ expect(subject).to eq([['1', '00033']])
880
+ end
881
+ end
882
+
883
+ context 'when 0 is given' do
884
+ let(:target_line) {"INSERT INTO `test_table` VALUES (1,0);"}
885
+ it 'should not remove the leading zeros' do
886
+ expect(subject).to eq([['1', '0']])
887
+ end
888
+ end
889
+
890
+ context 'when 0.00 is given' do
891
+ let(:target_line) {"INSERT INTO `test_table` VALUES (1,0.00);"}
892
+ it 'should not remove the leading zeros' do
893
+ expect(subject).to eq([['1', '0.00']])
894
+ end
895
+ end
896
+ end
897
+ end
898
+ end
899
+ end
900
+ end