flydata 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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