flydata 0.0.5.6 → 0.1.0
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.
- data/Gemfile +8 -0
- data/Gemfile.lock +36 -1
- data/VERSION +1 -1
- data/bin/fdmysqldump +59 -0
- data/flydata.gemspec +49 -5
- data/lib/flydata.rb +3 -1
- data/lib/flydata/api/data_entry.rb +4 -0
- data/lib/flydata/api/redshift_cluster.rb +15 -0
- data/lib/flydata/cli.rb +1 -0
- data/lib/flydata/command/base.rb +8 -2
- data/lib/flydata/command/conf.rb +48 -0
- data/lib/flydata/command/encrypt.rb +18 -0
- data/lib/flydata/command/sender.rb +10 -3
- data/lib/flydata/command/setlogdel.rb +1 -1
- data/lib/flydata/command/setup.rb +26 -3
- data/lib/flydata/command/sync.rb +962 -0
- data/lib/flydata/command/version.rb +10 -0
- data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +305 -0
- data/lib/flydata/fluent-plugins/out_forward_ssl.rb +91 -0
- data/lib/flydata/fluent-plugins/preference.rb +92 -0
- data/lib/flydata/helpers.rb +13 -1
- data/lib/flydata/preference/data_entry_preference.rb +98 -0
- data/lib/flydata/sync_file_manager.rb +120 -0
- data/lib/flydata/table_def.rb +2 -0
- data/lib/flydata/table_def/mysql_table_def.rb +128 -0
- data/lib/flydata/table_def/redshift_table_def.rb +144 -0
- data/lib/flydata/util/encryptor.rb +53 -0
- data/spec/fluent_plugins_spec_helper.rb +19 -0
- data/spec/flydata/command/sender_spec.rb +3 -29
- data/spec/flydata/command/sync_spec.rb +1049 -0
- data/spec/flydata/fluent-plugins/in_mysql_binlog_flydata_spec.rb +204 -0
- data/spec/flydata/util/encryptor_spec.rb +96 -0
- data/spec/spec_helper.rb +1 -0
- data/tmpl/redshift_mysql_data_entry.conf.tmpl +11 -0
- metadata +153 -4
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Flydata
|
4
|
+
module Util
|
5
|
+
class Encryptor
|
6
|
+
SALT = 'ae8k9h10'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def encrypt(text, key)
|
10
|
+
validate_presence(text: text, key: key)
|
11
|
+
cipher = build_cipher(:encrypt, key)
|
12
|
+
Base64.encode64(cipher.update(text) + cipher.final)
|
13
|
+
end
|
14
|
+
|
15
|
+
def decrypt(text, key, param_name = nil)
|
16
|
+
decrypt!(text, key, param_name)
|
17
|
+
rescue => e
|
18
|
+
text
|
19
|
+
end
|
20
|
+
|
21
|
+
def decrypt!(text, key, param_name = nil)
|
22
|
+
validate_presence(text: text, key: key)
|
23
|
+
cipher = build_cipher(:decrypt, key)
|
24
|
+
cipher.update(Base64.decode64(text)) + cipher.final
|
25
|
+
rescue OpenSSL::Cipher::CipherError => e
|
26
|
+
raise "Failed to decrypt '#{param_name}' parameter. error:'#{e.to_s}'"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validate_presence(names)
|
32
|
+
names.each do |name, value|
|
33
|
+
raise ArgumentError.new("#{name} is required.") if value.nil? or value.empty?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_cipher(type, key)
|
38
|
+
cipher = OpenSSL::Cipher::Cipher.new('AES-128-CBC')
|
39
|
+
case type
|
40
|
+
when :encrypt
|
41
|
+
cipher.encrypt
|
42
|
+
when :decrypt
|
43
|
+
cipher.decrypt
|
44
|
+
else
|
45
|
+
raise "Invalid type: #{type}"
|
46
|
+
end
|
47
|
+
cipher.pkcs5_keyivgen(key, SALT)
|
48
|
+
cipher
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib', 'flydata', 'fluent-plugins'))
|
2
|
+
require 'rspec'
|
3
|
+
require 'fluent/test'
|
4
|
+
|
5
|
+
module Fluent
|
6
|
+
module Test
|
7
|
+
def self.configure_plugin(plugin, str)
|
8
|
+
plugin = plugin.new if plugin.kind_of?(Class)
|
9
|
+
if str.is_a?(Fluent::Config::Element)
|
10
|
+
plugin.instance_variable_set(:@config, str)
|
11
|
+
else
|
12
|
+
plugin.instance_variable_set(:@config,
|
13
|
+
Config.parse(str, "(test)"))
|
14
|
+
end
|
15
|
+
plugin.configure(plugin.instance_variable_get(:@config))
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -3,35 +3,9 @@ require 'spec_helper'
|
|
3
3
|
module Flydata
|
4
4
|
module Command
|
5
5
|
describe Sender do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
describe '#start' do
|
10
|
-
it 'starts fluentd' do
|
11
|
-
api_client = double('api_client')
|
12
|
-
api_client.stub_chain(:data_port, :get, :[]).and_return 'active'
|
13
|
-
ApiClient.stub(:new).and_return api_client
|
14
|
-
|
15
|
-
@sender.start
|
16
|
-
end
|
17
|
-
end
|
18
|
-
describe '#stop' do
|
19
|
-
it 'stops fluentd' do
|
20
|
-
@sender.stop
|
21
|
-
end
|
22
|
-
end
|
23
|
-
describe '#restart' do
|
24
|
-
before :all do
|
25
|
-
@sender = Sender.new
|
26
|
-
@sender.start
|
27
|
-
end
|
28
|
-
it 'restart fluentd' do
|
29
|
-
@sender.restart
|
30
|
-
end
|
31
|
-
after :all do
|
32
|
-
@sender.stop
|
33
|
-
end
|
34
|
-
end
|
6
|
+
pending '#start'
|
7
|
+
pending '#stop'
|
8
|
+
pending '#restart'
|
35
9
|
end
|
36
10
|
end
|
37
11
|
end
|
@@ -0,0 +1,1049 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Flydata
|
6
|
+
module Command
|
7
|
+
describe Sync do
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module FileUtil
|
12
|
+
describe SyncFileManager do
|
13
|
+
let(:default_mysqldump_dir) do
|
14
|
+
File.join('/tmp', "sync_dump_#{Time.now.to_i}")
|
15
|
+
end
|
16
|
+
let(:default_data_entry) do
|
17
|
+
{"id"=>93,
|
18
|
+
"name"=>"flydata_sync_mysql",
|
19
|
+
"data_port_id"=>52,
|
20
|
+
"display_name"=>"flydata_sync_mysql",
|
21
|
+
"enabled"=>true,
|
22
|
+
"heroku_log_type"=>nil,
|
23
|
+
"heroku_resource_id"=>nil,
|
24
|
+
"log_deletion"=>nil,
|
25
|
+
"log_file_delimiter"=>nil,
|
26
|
+
"log_file_type"=>nil,
|
27
|
+
"log_path"=>nil,
|
28
|
+
"redshift_schema_name"=>"",
|
29
|
+
"redshift_table_name"=>nil,
|
30
|
+
"created_at"=>"2014-01-22T18:58:43Z",
|
31
|
+
"updated_at"=>"2014-01-30T02:42:26Z",
|
32
|
+
"type"=>"RedshiftMysqlDataEntry",
|
33
|
+
"tag_name"=>"flydata.a458c641_dp52.flydata_mysql",
|
34
|
+
"tag_name_dev"=>"flydata.a458c641_dp52.flydata_mysql.dev",
|
35
|
+
"data_port_key"=>"a458c641",
|
36
|
+
"mysql_data_entry_preference" =>
|
37
|
+
{ "host"=>"localhost", "port"=>3306, "username"=>"masashi",
|
38
|
+
"password"=>"welcome", "database"=>"sync_test", "tables"=>nil,
|
39
|
+
"mysqldump_dir"=>default_mysqldump_dir, "forwarder" => "tcpforwarder",
|
40
|
+
"data_servers"=>"localhost:9905" }
|
41
|
+
}
|
42
|
+
end
|
43
|
+
let(:default_sfm) { SyncFileManager.new(default_data_entry) }
|
44
|
+
|
45
|
+
let (:status) { 'PARSING' }
|
46
|
+
let (:table_name) { 'test_table' }
|
47
|
+
let (:last_pos) { 9999 }
|
48
|
+
let (:binlog_pos) { {binfile: 'mysqlbin.00001', pos: 111} }
|
49
|
+
let (:state) { 'CREATE_TABLE' }
|
50
|
+
let (:substate) { nil }
|
51
|
+
|
52
|
+
after :each do
|
53
|
+
if Dir.exists?(default_mysqldump_dir)
|
54
|
+
Dir.delete(default_mysqldump_dir) rescue nil
|
55
|
+
end
|
56
|
+
if File.exists?(default_mysqldump_dir)
|
57
|
+
File.delete(default_mysqldump_dir) rescue nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#dump_file_path' do
|
62
|
+
context 'when mysqldump_dir param is nil' do
|
63
|
+
before do
|
64
|
+
default_data_entry['mysql_data_entry_preference'].delete('mysqldump_dir')
|
65
|
+
end
|
66
|
+
it do
|
67
|
+
expect(default_sfm.dump_file_path).to eq(
|
68
|
+
File.join(ENV['HOME'], '.flydata', 'dump', 'flydata_sync_mysql.dump'))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
context 'when file exists' do
|
72
|
+
before { `touch #{default_mysqldump_dir}`}
|
73
|
+
it do
|
74
|
+
expect{default_sfm.dump_file_path}.to raise_error
|
75
|
+
end
|
76
|
+
end
|
77
|
+
context 'when directory exists' do
|
78
|
+
before { `mkdir -p #{default_mysqldump_dir}`}
|
79
|
+
it do
|
80
|
+
expect(FileUtils).to receive(:mkdir_p).with(default_mysqldump_dir).never
|
81
|
+
expect(default_sfm.dump_file_path).to eq(
|
82
|
+
File.join(default_mysqldump_dir, 'flydata_sync_mysql.dump'))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
context 'when directory or file does not exist' do
|
86
|
+
it do
|
87
|
+
expect(FileUtils).to receive(:mkdir_p).with(default_mysqldump_dir).once
|
88
|
+
expect(default_sfm.dump_file_path).to eq(
|
89
|
+
File.join(default_mysqldump_dir, 'flydata_sync_mysql.dump'))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
context 'when file name includes "~"' do
|
93
|
+
let(:default_mysqldump_dir) { "~/tmp/dump/sync_spec_#{Time.now.to_i}" }
|
94
|
+
it do
|
95
|
+
expected_dir = File.join(ENV['HOME'], default_mysqldump_dir[1..-1])
|
96
|
+
expect(FileUtils).to receive(:mkdir_p).with(expected_dir).once
|
97
|
+
expect(default_sfm.dump_file_path).to eq(
|
98
|
+
File.join(expected_dir, 'flydata_sync_mysql.dump'))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#dump_pos_path' do
|
104
|
+
it { expect(default_sfm.dump_pos_path).to eq(
|
105
|
+
File.join(default_mysqldump_dir, 'flydata_sync_mysql.dump.pos')) }
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#save_dump_pos' do
|
109
|
+
context 'without mysql marshal data' do
|
110
|
+
it do
|
111
|
+
expect{default_sfm.save_dump_pos(
|
112
|
+
status, table_name, last_pos, binlog_pos, state, substate)}.not_to raise_error
|
113
|
+
expect(default_sfm.load_dump_pos).to eq({
|
114
|
+
status: status, table_name: table_name, last_pos: last_pos,
|
115
|
+
binlog_pos: binlog_pos, state: state, substate: substate,
|
116
|
+
mysql_table: nil
|
117
|
+
})
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#load_dump_pos' do
|
123
|
+
let (:mysql_table) do
|
124
|
+
Flydata::Mysql::MysqlTable.new(
|
125
|
+
table_name, { 'id' => { format_type: 'int' }, 'value' => { format_type: 'text' } }
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'with mysql marshal data' do
|
130
|
+
before do
|
131
|
+
default_sfm.save_mysql_table_marshal_dump(mysql_table)
|
132
|
+
default_sfm.save_dump_pos(status, table_name, last_pos, binlog_pos, state, substate)
|
133
|
+
end
|
134
|
+
it do
|
135
|
+
ret = default_sfm.load_dump_pos
|
136
|
+
mt = ret.delete(:mysql_table)
|
137
|
+
expect(ret).to eq({
|
138
|
+
status: status, table_name: table_name, last_pos: last_pos,
|
139
|
+
binlog_pos: binlog_pos, state: state, substate: substate,
|
140
|
+
})
|
141
|
+
expect(mt.table_name).to eq(table_name)
|
142
|
+
expect(mt.columns).to eq({
|
143
|
+
'id' => { format_type: 'int' },
|
144
|
+
'value' => { format_type: 'text' },
|
145
|
+
})
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '#save_binlog' do
|
151
|
+
let(:binfile) { 'mysqlbinlog.000001' }
|
152
|
+
let(:pos) { 107 }
|
153
|
+
let(:binlog_pos) { {binfile: binfile, pos: pos} }
|
154
|
+
it do
|
155
|
+
default_sfm.save_binlog(binlog_pos)
|
156
|
+
expect(`cat #{default_sfm.binlog_path}`).to eq("#{binfile}\t#{pos}")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#binlog_path' do
|
161
|
+
it { expect(default_sfm.binlog_path).to eq("#{FLYDATA_HOME}/flydata_sync_mysql.binlog.pos") }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
module Output
|
167
|
+
describe ForwarderFactory do
|
168
|
+
let(:forwarder) do
|
169
|
+
ForwarderFactory.create('tcpforwarder', 'test_tag', ['localhost:3456', 'localhost:4567'])
|
170
|
+
end
|
171
|
+
before :each do
|
172
|
+
conn = double(TCPSocket)
|
173
|
+
allow(conn).to receive(:setsockopt)
|
174
|
+
allow(conn).to receive(:write)
|
175
|
+
allow(conn).to receive(:close)
|
176
|
+
allow(TCPSocket).to receive(:new).and_return(conn)
|
177
|
+
allow(StringIO).to receive(:open)
|
178
|
+
end
|
179
|
+
|
180
|
+
describe '.create' do
|
181
|
+
context 'with nil forwarder_key' do
|
182
|
+
it 'should return TcpForwarder object' do
|
183
|
+
forwarder = ForwarderFactory.create(nil, 'test_tag', ['localhost:3456', 'localhost:4567'])
|
184
|
+
expect(forwarder.kind_of?(TcpForwarder)).to be_true
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'with tcpforwarder forwarder_key' do
|
189
|
+
it 'should return TcpForwarder object' do
|
190
|
+
forwarder = ForwarderFactory.create('tcpforwarder', 'test_tag', ['localhost:3456', 'localhost:4567'])
|
191
|
+
expect(forwarder.kind_of?(TcpForwarder)).to be_true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'with sslforwarder forwarder_key' do
|
196
|
+
it 'should return SslForwarder object' do
|
197
|
+
forwarder = ForwarderFactory.create('sslforwarder', 'test_tag', ['localhost:3456', 'localhost:4567'])
|
198
|
+
expect(forwarder.kind_of?(SslForwarder)).to be_true
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe '#initialize' do
|
204
|
+
context 'servers is empty' do
|
205
|
+
it do
|
206
|
+
expect{ForwarderFactory.create('tcpforwarder', 'test_tag', [])}.to raise_error
|
207
|
+
end
|
208
|
+
end
|
209
|
+
context 'servers is nil' do
|
210
|
+
it do
|
211
|
+
expect{ForwarderFactory.create('tcpforwarder', 'test_tag', nil)}.to raise_error
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe '#emit' do
|
217
|
+
let(:record) { {table_name: 'test_table_name', log: '{"key":"value"}'} }
|
218
|
+
before :each do
|
219
|
+
forwarder.set_options({buffer_size_limit: ([Time.now.to_i,record].to_msgpack.bytesize * 2.5)})
|
220
|
+
end
|
221
|
+
context 'when the buffer size is less than threthold' do
|
222
|
+
it do
|
223
|
+
expect(forwarder.emit(record)).to be(false)
|
224
|
+
expect(forwarder.buffer_record_count).to be(1)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
context 'when the buffer size exceeds threthold' do
|
228
|
+
it do
|
229
|
+
expect(forwarder.emit(record)).to be(false)
|
230
|
+
expect(forwarder.emit(record)).to be(false)
|
231
|
+
expect(forwarder.buffer_record_count).to be(2)
|
232
|
+
expect(forwarder.emit(record)).to be(true)
|
233
|
+
expect(forwarder.buffer_record_count).to be(0)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe '#pickup_server' do
|
239
|
+
context 'with only one server' do
|
240
|
+
let(:servers) { ['localhost:1111'] }
|
241
|
+
let(:forwarder) {
|
242
|
+
ForwarderFactory.create('tcpforwarder', 'test_tag', servers)
|
243
|
+
}
|
244
|
+
it 'expect to return same server' do
|
245
|
+
expect(forwarder.pickup_server).to eq('localhost:1111')
|
246
|
+
expect(forwarder.pickup_server).to eq('localhost:1111')
|
247
|
+
expect(forwarder.pickup_server).to eq('localhost:1111')
|
248
|
+
end
|
249
|
+
end
|
250
|
+
context 'with servers' do
|
251
|
+
let(:servers) { ['localhost:1111', 'localhost:2222', 'localhost:3333'] }
|
252
|
+
let(:forwarder) {
|
253
|
+
ForwarderFactory.create('tcpforwarder', 'test_tag', servers)
|
254
|
+
}
|
255
|
+
it 'expect to return same server' do
|
256
|
+
expect(forwarder.pickup_server).to eq('localhost:1111')
|
257
|
+
expect(forwarder.pickup_server).to eq('localhost:2222')
|
258
|
+
expect(forwarder.pickup_server).to eq('localhost:3333')
|
259
|
+
expect(forwarder.pickup_server).to eq('localhost:1111')
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
module Mysql
|
267
|
+
describe MysqlDumpGenerator do
|
268
|
+
let(:stdout) { double(:stdout) }
|
269
|
+
let(:stderr) { double(:stderr) }
|
270
|
+
let(:status) { double(:status) }
|
271
|
+
let(:file_path) { File.join('/tmp', "flydata_sync_spec_mysqldump_#{Time.now.to_i}") }
|
272
|
+
let(:default_conf) { {
|
273
|
+
'host' => 'localhost', 'port' => 3306, 'username' => 'admin',
|
274
|
+
'password' => 'pass', 'database' => 'dev', 'tables' => 'users,groups',
|
275
|
+
} }
|
276
|
+
let(:default_dump_generator) { MysqlDumpGenerator.new(default_conf) }
|
277
|
+
|
278
|
+
describe '#initialize' do
|
279
|
+
context 'with password' do
|
280
|
+
subject { default_dump_generator.instance_variable_get(:@dump_cmd) }
|
281
|
+
it { should eq('mysqldump -h localhost -P 3306 -uadmin -ppass --skip-lock-tables ' +
|
282
|
+
'--single-transaction --flush-logs --hex-blob --master-data=2 dev users groups') }
|
283
|
+
end
|
284
|
+
context 'without password' do
|
285
|
+
let (:dump_generator) do
|
286
|
+
MysqlDumpGenerator.new(default_conf.merge({'password' => ''}))
|
287
|
+
end
|
288
|
+
subject { dump_generator.instance_variable_get(:@dump_cmd) }
|
289
|
+
it { should eq('mysqldump -h localhost -P 3306 -uadmin --skip-lock-tables ' +
|
290
|
+
'--single-transaction --flush-logs --hex-blob --master-data=2 dev users groups') }
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
describe '#dump' do
|
295
|
+
context 'when exit status is not 0' do
|
296
|
+
before do
|
297
|
+
`touch #{file_path}`
|
298
|
+
expect(status).to receive(:exitstatus).and_return 1
|
299
|
+
expect(Open3).to receive(:capture3).and_return(
|
300
|
+
['(dummy std out)', '(dummy std err)', status]
|
301
|
+
)
|
302
|
+
end
|
303
|
+
it do
|
304
|
+
expect{ default_dump_generator.dump(file_path) }.to raise_error
|
305
|
+
expect(File.exists?(file_path)).to be_false
|
306
|
+
end
|
307
|
+
end
|
308
|
+
context 'when exit status is 0 but no file' do
|
309
|
+
before do
|
310
|
+
expect(status).to receive(:exitstatus).and_return 0
|
311
|
+
expect(Open3).to receive(:capture3).and_return(
|
312
|
+
['(dummy std out)', '(dummy std err)', status]
|
313
|
+
)
|
314
|
+
end
|
315
|
+
it do
|
316
|
+
expect{ default_dump_generator.dump(file_path) }.to raise_error
|
317
|
+
expect(File.exists?(file_path)).to be_false
|
318
|
+
end
|
319
|
+
end
|
320
|
+
context 'when exit status is 0 but file size is 0' do
|
321
|
+
before do
|
322
|
+
`touch #{file_path}`
|
323
|
+
expect(status).to receive(:exitstatus).and_return 0
|
324
|
+
expect(Open3).to receive(:capture3).and_return(
|
325
|
+
['(dummy std out)', '(dummy std err)', status]
|
326
|
+
)
|
327
|
+
end
|
328
|
+
it do
|
329
|
+
expect{ default_dump_generator.dump(file_path) }.to raise_error
|
330
|
+
expect(File.exists?(file_path)).to be_true
|
331
|
+
end
|
332
|
+
end
|
333
|
+
context 'when exit status is 0' do
|
334
|
+
before do
|
335
|
+
`echo "something..." > #{file_path}`
|
336
|
+
expect(status).to receive(:exitstatus).and_return 0
|
337
|
+
expect(Open3).to receive(:capture3).and_return(
|
338
|
+
['(dummy std out)', '(dummy std err)', status]
|
339
|
+
)
|
340
|
+
end
|
341
|
+
it do
|
342
|
+
expect(default_dump_generator.dump(file_path)).to be_true
|
343
|
+
expect(File.exists?(file_path)).to be_true
|
344
|
+
end
|
345
|
+
end
|
346
|
+
after :each do
|
347
|
+
File.delete(file_path) if File.exists?(file_path)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
describe MysqlDumpParser do
|
353
|
+
let(:file_path) { File.join('/tmp', "flydata_sync_spec_mysqldump_parse_#{Time.now.to_i}") }
|
354
|
+
let(:default_parser) { MysqlDumpParser.new(file_path) }
|
355
|
+
|
356
|
+
def generate_dump_file(content)
|
357
|
+
File.open(file_path, 'w') {|f| f.write(content)}
|
358
|
+
end
|
359
|
+
|
360
|
+
after do
|
361
|
+
File.delete(file_path) if File.exists?(file_path)
|
362
|
+
end
|
363
|
+
|
364
|
+
describe '#initialize' do
|
365
|
+
context 'when file does not exist' do
|
366
|
+
it do
|
367
|
+
expect{ MysqlDumpParser.new(file_path) }.to raise_error
|
368
|
+
end
|
369
|
+
end
|
370
|
+
context 'when file exists' do
|
371
|
+
before { generate_dump_file('') }
|
372
|
+
it do
|
373
|
+
expect(MysqlDumpParser.new(file_path)).to be_an_instance_of(MysqlDumpParser)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
describe '#parse' do
|
379
|
+
DUMP_HEADER = <<EOT
|
380
|
+
-- MySQL dump 10.13 Distrib 5.6.13, for osx10.9 (x86_64)
|
381
|
+
--
|
382
|
+
-- Host: localhost Database: sync_test
|
383
|
+
-- ------------------------------------------------------
|
384
|
+
-- Server version>--5.6.13-log
|
385
|
+
|
386
|
+
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
387
|
+
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
388
|
+
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
389
|
+
/*!40101 SET NAMES utf8 */;
|
390
|
+
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
391
|
+
/*!40103 SET TIME_ZONE='+00:00' */;
|
392
|
+
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
393
|
+
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
394
|
+
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
395
|
+
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
396
|
+
|
397
|
+
--
|
398
|
+
-- Position to start replication or point-in-time recovery from
|
399
|
+
--
|
400
|
+
|
401
|
+
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000267', MASTER_LOG_POS=120;
|
402
|
+
EOT
|
403
|
+
|
404
|
+
def index_after(content, string)
|
405
|
+
content.index(string) + string.bytesize + 1
|
406
|
+
end
|
407
|
+
|
408
|
+
let(:default_parser) { MysqlDumpParser.new(file_path) }
|
409
|
+
let(:default_binlog_pos) { {binfile: 'mysql-bin.000267', pos: 120 } }
|
410
|
+
let(:dump_pos_after_binlog_pos) { index_after(DUMP_HEADER, 'MASTER_LOG_POS=120;') }
|
411
|
+
|
412
|
+
let(:create_table_block) { double('create_table_block') }
|
413
|
+
let(:insert_record_block) { double('insert_record_block') }
|
414
|
+
let(:check_point_block) { double('check_point_block') }
|
415
|
+
|
416
|
+
before do
|
417
|
+
generate_dump_file('')
|
418
|
+
end
|
419
|
+
|
420
|
+
context 'when dump does not contain binlog pos' do
|
421
|
+
before { generate_dump_file('dummy content') }
|
422
|
+
it do
|
423
|
+
expect(create_table_block).to receive(:call).never
|
424
|
+
expect(insert_record_block).to receive(:call).never
|
425
|
+
expect(check_point_block).to receive(:call).never
|
426
|
+
binlog_pos = default_parser.parse(
|
427
|
+
create_table_block, insert_record_block, check_point_block
|
428
|
+
)
|
429
|
+
expect(binlog_pos).to be_nil
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
context 'when dump contains only binlog pos' do
|
434
|
+
before { generate_dump_file(<<EOT
|
435
|
+
#{DUMP_HEADER}
|
436
|
+
EOT
|
437
|
+
) }
|
438
|
+
it do
|
439
|
+
expect(create_table_block).to receive(:call).never
|
440
|
+
expect(insert_record_block).to receive(:call).never
|
441
|
+
expect(check_point_block).to receive(:call).once
|
442
|
+
binlog_pos = default_parser.parse(
|
443
|
+
create_table_block, insert_record_block, check_point_block
|
444
|
+
)
|
445
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
context 'when dump contains create table without records' do
|
450
|
+
let(:dump_content) { <<EOT
|
451
|
+
#{DUMP_HEADER}
|
452
|
+
|
453
|
+
DROP TABLE IF EXISTS `users_login`;
|
454
|
+
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
455
|
+
/*!40101 SET character_set_client = utf8 */;
|
456
|
+
CREATE TABLE `users_login` (
|
457
|
+
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
|
458
|
+
`login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
|
459
|
+
`create_time` datetime NOT NULL COMMENT 'create time',
|
460
|
+
`update_time` datetime NOT NULL COMMENT 'update time',
|
461
|
+
PRIMARY KEY (`user_id`)
|
462
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='';
|
463
|
+
/*!40101 SET character_set_client = @saved_cs_client */;
|
464
|
+
|
465
|
+
--
|
466
|
+
-- Dumping data for table `users_login`
|
467
|
+
--
|
468
|
+
|
469
|
+
LOCK TABLES `users_login` WRITE;
|
470
|
+
/*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
|
471
|
+
/*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
|
472
|
+
UNLOCK TABLES;
|
473
|
+
|
474
|
+
EOT
|
475
|
+
}
|
476
|
+
before { generate_dump_file(dump_content) }
|
477
|
+
it do
|
478
|
+
expect(create_table_block).to receive(:call) { |mysql_table|
|
479
|
+
expect(mysql_table.table_name).to eq('users_login')
|
480
|
+
}.once
|
481
|
+
expect(insert_record_block).to receive(:call).never
|
482
|
+
expect(check_point_block).to receive(:call) { |mysql_table, last_pos, binlog_pos, state, substate|
|
483
|
+
expect(mysql_table).to be_nil
|
484
|
+
expect(last_pos).to eq(dump_pos_after_binlog_pos)
|
485
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
486
|
+
expect(state).to eq(Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE)
|
487
|
+
expect(substate).to be_nil
|
488
|
+
}
|
489
|
+
expect(check_point_block).to receive(:call) { |mysql_table, last_pos, binlog_pos, state, substate|
|
490
|
+
expect(mysql_table.table_name).to eq('users_login')
|
491
|
+
expect(mysql_table.columns.keys).to eq(['user_id', 'login_count', 'create_time', 'update_time'])
|
492
|
+
expect(last_pos).to eq(index_after(dump_content, 'ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=\'\';'))
|
493
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
494
|
+
expect(state).to eq(Flydata::Mysql::MysqlDumpParser::State::INSERT_RECORD)
|
495
|
+
expect(substate).to be_nil
|
496
|
+
}
|
497
|
+
expect(check_point_block).to receive(:call) { |mysql_table, last_pos, binlog_pos, state, substate|
|
498
|
+
expect(mysql_table.table_name).to eq('users_login')
|
499
|
+
expect(last_pos).to eq(index_after(dump_content, 'UNLOCK TABLES;'))
|
500
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
501
|
+
expect(state).to eq(Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE)
|
502
|
+
expect(substate).to be_nil
|
503
|
+
}
|
504
|
+
expect(check_point_block).to receive(:call).never
|
505
|
+
|
506
|
+
binlog_pos = default_parser.parse(
|
507
|
+
create_table_block, insert_record_block, check_point_block
|
508
|
+
)
|
509
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
context 'when dump contains create table with multi inserts' do
|
514
|
+
let(:dump_content) { <<EOT
|
515
|
+
#{DUMP_HEADER}
|
516
|
+
|
517
|
+
DROP TABLE IF EXISTS `users_login`;
|
518
|
+
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
519
|
+
/*!40101 SET character_set_client = utf8 */;
|
520
|
+
CREATE TABLE `users_login` (
|
521
|
+
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
|
522
|
+
`login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
|
523
|
+
`comment` varchar(255) DEFAULT NULL COMMENT 'comment',
|
524
|
+
`create_time` datetime NOT NULL COMMENT 'create time',
|
525
|
+
`update_time` datetime NOT NULL COMMENT 'update time',
|
526
|
+
PRIMARY KEY (`user_id`)
|
527
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
528
|
+
/*!40101 SET character_set_client = @saved_cs_client */;
|
529
|
+
|
530
|
+
--
|
531
|
+
-- Dumping data for table `users_login`
|
532
|
+
--
|
533
|
+
|
534
|
+
LOCK TABLES `users_login` WRITE;
|
535
|
+
/*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
|
536
|
+
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');
|
537
|
+
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');
|
538
|
+
/*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
|
539
|
+
UNLOCK TABLES;
|
540
|
+
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
541
|
+
|
542
|
+
EOT
|
543
|
+
}
|
544
|
+
before { generate_dump_file(dump_content) }
|
545
|
+
it do
|
546
|
+
# create_table_block
|
547
|
+
expect(create_table_block).to receive(:call) { |mysql_table|
|
548
|
+
expect(mysql_table.table_name).to eq('users_login')
|
549
|
+
expect(mysql_table.columns.keys).to eq(['user_id', 'login_count', 'comment', 'create_time', 'update_time'])
|
550
|
+
}.once
|
551
|
+
expect(create_table_block).to receive(:call).never
|
552
|
+
|
553
|
+
# insert_record_block
|
554
|
+
[
|
555
|
+
[ %w(15 46 moodier 2001-10-02\ 08:18:08 1986-06-11\ 22:50:10),
|
556
|
+
%w(35 6 missあいうえおteer 1991-10-15\ 19:38:07 1970-10-01\ 22:03:10),
|
557
|
+
%w(52 33 sub\\field 1972-08-23\ 20:16:08 1974-10-10\ 23:28:11),],
|
558
|
+
[ %w(373 31 out'swearing 1979-10-07\ 08:10:08 2006-02-22\ 16:26:04),
|
559
|
+
%w(493 8 schizophrenic 1979-07-06\ 07:34:07 1970-08-09\ 01:21:01),]
|
560
|
+
].each do |expected_values|
|
561
|
+
expect(insert_record_block).to receive(:call) { |mysql_table, values_set|
|
562
|
+
expect(mysql_table.table_name).to eq('users_login')
|
563
|
+
expect(values_set).to eq(expected_values)
|
564
|
+
nil
|
565
|
+
}.once
|
566
|
+
end
|
567
|
+
expect(insert_record_block).to receive(:call).never
|
568
|
+
|
569
|
+
# insert_record_block
|
570
|
+
[
|
571
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE},
|
572
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::INSERT_RECORD},
|
573
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE,
|
574
|
+
last_pos: index_after(dump_content, 'UNLOCK TABLES;') + 10}
|
575
|
+
].each do |expected_params|
|
576
|
+
expect(check_point_block).to receive(:call) { |mysql_table, last_pos, binlog_pos, state, substate|
|
577
|
+
expect(mysql_table.table_name).to eq('users_login') if mysql_table
|
578
|
+
expect(state).to eq(expected_params[:state])
|
579
|
+
if expected_params[:last_pos]
|
580
|
+
expect(last_pos).to eq(expected_params[:last_pos])
|
581
|
+
end
|
582
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
583
|
+
expect(substate).to eq(expected_params[:substate])
|
584
|
+
expected_params[:result]
|
585
|
+
}.once
|
586
|
+
end
|
587
|
+
expect(check_point_block).to receive(:call).never
|
588
|
+
|
589
|
+
binlog_pos = default_parser.parse(
|
590
|
+
create_table_block, insert_record_block, check_point_block
|
591
|
+
)
|
592
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
context 'when dump contains create table with records' do
|
597
|
+
let(:dump_content) { <<EOT
|
598
|
+
#{DUMP_HEADER}
|
599
|
+
|
600
|
+
DROP TABLE IF EXISTS `users_login`;
|
601
|
+
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
602
|
+
/*!40101 SET character_set_client = utf8 */;
|
603
|
+
CREATE TABLE `users_login` (
|
604
|
+
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
|
605
|
+
`login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
|
606
|
+
`comment` varchar(255) DEFAULT NULL COMMENT 'comment',
|
607
|
+
`create_time` datetime NOT NULL COMMENT 'create time',
|
608
|
+
`update_time` datetime NOT NULL COMMENT 'update time',
|
609
|
+
PRIMARY KEY (`user_id`)
|
610
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
611
|
+
/*!40101 SET character_set_client = @saved_cs_client */;
|
612
|
+
|
613
|
+
--
|
614
|
+
-- Dumping data for table `users_login`
|
615
|
+
--
|
616
|
+
|
617
|
+
LOCK TABLES `users_login` WRITE;
|
618
|
+
/*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
|
619
|
+
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');
|
620
|
+
/*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
|
621
|
+
UNLOCK TABLES;
|
622
|
+
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
623
|
+
|
624
|
+
EOT
|
625
|
+
}
|
626
|
+
before { generate_dump_file(dump_content) }
|
627
|
+
it do
|
628
|
+
# create_table_block
|
629
|
+
expect(create_table_block).to receive(:call) { |mysql_table|
|
630
|
+
expect(mysql_table.table_name).to eq('users_login')
|
631
|
+
expect(mysql_table.columns.keys).to eq(['user_id', 'login_count', 'comment', 'create_time', 'update_time'])
|
632
|
+
}.once
|
633
|
+
expect(create_table_block).to receive(:call).never
|
634
|
+
|
635
|
+
# insert_record_block
|
636
|
+
[
|
637
|
+
[ %w(15 46 moodier 2001-10-02\ 08:18:08 1986-06-11\ 22:50:10),
|
638
|
+
['35', '6', nil, '1991-10-15 19:38:07', '1970-10-01 22:03:10'],
|
639
|
+
%w(52 33 subfield 1972-08-23\ 20:16:08 1974-10-10\ 23:28:11),],
|
640
|
+
].each do |expected_values|
|
641
|
+
expect(insert_record_block).to receive(:call) { |mysql_table, values_set|
|
642
|
+
expect(mysql_table.table_name).to eq('users_login')
|
643
|
+
expect(values_set).to eq(expected_values)
|
644
|
+
nil
|
645
|
+
}.once
|
646
|
+
end
|
647
|
+
expect(insert_record_block).to receive(:call).never
|
648
|
+
|
649
|
+
# insert_record_block
|
650
|
+
[
|
651
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE},
|
652
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::INSERT_RECORD},
|
653
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE,
|
654
|
+
last_pos: index_after(dump_content, 'UNLOCK TABLES;')}
|
655
|
+
].each do |expected_params|
|
656
|
+
expect(check_point_block).to receive(:call) { |mysql_table, last_pos, binlog_pos, state, substate|
|
657
|
+
expect(mysql_table.table_name).to eq('users_login') if mysql_table
|
658
|
+
expect(state).to eq(expected_params[:state])
|
659
|
+
if expected_params[:last_pos]
|
660
|
+
expect(last_pos).to eq(expected_params[:last_pos])
|
661
|
+
end
|
662
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
663
|
+
expect(substate).to eq(expected_params[:substate])
|
664
|
+
expected_params[:result]
|
665
|
+
}.once
|
666
|
+
end
|
667
|
+
expect(check_point_block).to receive(:call).never
|
668
|
+
|
669
|
+
binlog_pos = default_parser.parse(
|
670
|
+
create_table_block, insert_record_block, check_point_block
|
671
|
+
)
|
672
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
context 'with resume after creating table' do
|
677
|
+
let(:dump_content_head) { <<EOT
|
678
|
+
#{DUMP_HEADER}
|
679
|
+
|
680
|
+
DROP TABLE IF EXISTS `users_login`;
|
681
|
+
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
682
|
+
/*!40101 SET character_set_client = utf8 */;
|
683
|
+
CREATE TABLE `users_login` (
|
684
|
+
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
|
685
|
+
`login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
|
686
|
+
`comment` varchar(255) DEFAULT NULL COMMENT 'comment',
|
687
|
+
`create_time` datetime NOT NULL COMMENT 'create time',
|
688
|
+
`update_time` datetime NOT NULL COMMENT 'update time',
|
689
|
+
PRIMARY KEY (`user_id`)
|
690
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
691
|
+
/*!40101 SET character_set_client = @saved_cs_client */;
|
692
|
+
|
693
|
+
EOT
|
694
|
+
}
|
695
|
+
let(:dump_content_all) { <<EOT
|
696
|
+
#{dump_content_head}
|
697
|
+
--
|
698
|
+
-- Dumping data for table `users_login`
|
699
|
+
--
|
700
|
+
|
701
|
+
LOCK TABLES `users_login` WRITE;
|
702
|
+
/*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
|
703
|
+
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');
|
704
|
+
/*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
|
705
|
+
UNLOCK TABLES;
|
706
|
+
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
707
|
+
EOT
|
708
|
+
}
|
709
|
+
before do
|
710
|
+
generate_dump_file(dump_content_head)
|
711
|
+
default_parser.parse(
|
712
|
+
Proc.new{},
|
713
|
+
Proc.new{
|
714
|
+
raise "Should not be called"
|
715
|
+
},
|
716
|
+
Proc.new{ |mysql_table, last_pos, binlog_pos, state, substate|
|
717
|
+
if mysql_table
|
718
|
+
@mysql_table = mysql_table
|
719
|
+
@option = { status: Flydata::Command::Sync::STATUS_PARSING,
|
720
|
+
table_name: mysql_table.table_name,
|
721
|
+
last_pos: last_pos,
|
722
|
+
binlog_pos: binlog_pos,
|
723
|
+
state: state,
|
724
|
+
substate: substate,
|
725
|
+
mysql_table: mysql_table }
|
726
|
+
end
|
727
|
+
}
|
728
|
+
)
|
729
|
+
expect(@option).to eq({
|
730
|
+
status: Flydata::Command::Sync::STATUS_PARSING,
|
731
|
+
table_name: 'users_login',
|
732
|
+
last_pos: index_after(dump_content_head, 'ENGINE=InnoDB DEFAULT CHARSET=utf8;'),
|
733
|
+
binlog_pos: default_binlog_pos,
|
734
|
+
state: Flydata::Mysql::MysqlDumpParser::State::INSERT_RECORD,
|
735
|
+
substate: nil,
|
736
|
+
mysql_table: @mysql_table
|
737
|
+
})
|
738
|
+
end
|
739
|
+
it do
|
740
|
+
generate_dump_file(dump_content_all)
|
741
|
+
parser_for_resume = MysqlDumpParser.new(file_path, @option)
|
742
|
+
|
743
|
+
# create_table_block
|
744
|
+
expect(create_table_block).to receive(:call).never
|
745
|
+
|
746
|
+
# insert_record_block
|
747
|
+
[
|
748
|
+
[ %w(15 46 moodier 2001-10-02\ 08:18:08 1986-06-11\ 22:50:10),
|
749
|
+
%w(35 6 missteer 1991-10-15\ 19:38:07 1970-10-01\ 22:03:10),
|
750
|
+
%w(52 33 subfield 1972-08-23\ 20:16:08 1974-10-10\ 23:28:11),],
|
751
|
+
].each do |expected_values|
|
752
|
+
expect(insert_record_block).to receive(:call) { |mysql_table, values|
|
753
|
+
expect(mysql_table.table_name).to eq('users_login')
|
754
|
+
expect(values).to eq(expected_values)
|
755
|
+
nil
|
756
|
+
}.once
|
757
|
+
end
|
758
|
+
expect(insert_record_block).to receive(:call).never
|
759
|
+
|
760
|
+
# check_point_block
|
761
|
+
[
|
762
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE,
|
763
|
+
last_pos: index_after(dump_content_all, 'UNLOCK TABLES;')}
|
764
|
+
].each do |expected_params|
|
765
|
+
expect(check_point_block).to receive(:call) { |mysql_table, last_pos, binlog_pos, state, substate|
|
766
|
+
expect(mysql_table.table_name).to eq('users_login') if mysql_table
|
767
|
+
expect(state).to eq(expected_params[:state])
|
768
|
+
if expected_params[:last_pos]
|
769
|
+
expect(last_pos).to eq(expected_params[:last_pos])
|
770
|
+
end
|
771
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
772
|
+
expect(substate).to eq(expected_params[:substate])
|
773
|
+
expected_params[:result]
|
774
|
+
}.once
|
775
|
+
end
|
776
|
+
expect(check_point_block).to receive(:call).never
|
777
|
+
|
778
|
+
binlog_pos = parser_for_resume.parse(
|
779
|
+
create_table_block, insert_record_block, check_point_block
|
780
|
+
)
|
781
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
context 'with resume during insert records' do
|
786
|
+
let(:dump_content) { <<EOT
|
787
|
+
#{DUMP_HEADER}
|
788
|
+
|
789
|
+
DROP TABLE IF EXISTS `users_login`;
|
790
|
+
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
791
|
+
/*!40101 SET character_set_client = utf8 */;
|
792
|
+
CREATE TABLE `users_login` (
|
793
|
+
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
|
794
|
+
`login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
|
795
|
+
`comment` varchar(255) DEFAULT NULL COMMENT 'comment',
|
796
|
+
`create_time` datetime NOT NULL COMMENT 'create time',
|
797
|
+
`update_time` datetime NOT NULL COMMENT 'update time',
|
798
|
+
PRIMARY KEY (`user_id`)
|
799
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
800
|
+
/*!40101 SET character_set_client = @saved_cs_client */;
|
801
|
+
|
802
|
+
--
|
803
|
+
-- Dumping data for table `users_login`
|
804
|
+
--
|
805
|
+
|
806
|
+
LOCK TABLES `users_login` WRITE;
|
807
|
+
/*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
|
808
|
+
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');
|
809
|
+
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');
|
810
|
+
/*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
|
811
|
+
UNLOCK TABLES;
|
812
|
+
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
813
|
+
EOT
|
814
|
+
}
|
815
|
+
before do
|
816
|
+
generate_dump_file(dump_content)
|
817
|
+
|
818
|
+
insert_record_block_for_resume = double('insert_record_block_for_resume')
|
819
|
+
expect(insert_record_block_for_resume).to receive(:call) { true }.once
|
820
|
+
expect(insert_record_block_for_resume).to receive(:call) { false }.once
|
821
|
+
|
822
|
+
default_parser.parse(
|
823
|
+
Proc.new{},
|
824
|
+
insert_record_block_for_resume,
|
825
|
+
Proc.new{ |mysql_table, last_pos, binlog_pos, state, substate|
|
826
|
+
if last_pos == index_after(dump_content, "'1972-08-23 20:16:08','1974-10-10 23:28:11');")
|
827
|
+
@mysql_table = mysql_table
|
828
|
+
@option = { status: Flydata::Command::Sync::STATUS_PARSING,
|
829
|
+
table_name: mysql_table.table_name,
|
830
|
+
last_pos: last_pos,
|
831
|
+
binlog_pos: binlog_pos,
|
832
|
+
state: state,
|
833
|
+
substate: substate,
|
834
|
+
mysql_table: mysql_table }
|
835
|
+
end
|
836
|
+
}
|
837
|
+
)
|
838
|
+
expect(@option).to eq({
|
839
|
+
status: Flydata::Command::Sync::STATUS_PARSING,
|
840
|
+
table_name: 'users_login',
|
841
|
+
last_pos: index_after(dump_content, "'1972-08-23 20:16:08','1974-10-10 23:28:11');"),
|
842
|
+
binlog_pos: default_binlog_pos,
|
843
|
+
state: Flydata::Mysql::MysqlDumpParser::State::INSERT_RECORD,
|
844
|
+
substate: nil,
|
845
|
+
mysql_table: @mysql_table
|
846
|
+
})
|
847
|
+
end
|
848
|
+
it do
|
849
|
+
generate_dump_file(dump_content)
|
850
|
+
parser_for_resume = MysqlDumpParser.new(file_path, @option)
|
851
|
+
|
852
|
+
# create_table_block
|
853
|
+
expect(create_table_block).to receive(:call).never
|
854
|
+
|
855
|
+
# insert_record_block
|
856
|
+
[
|
857
|
+
[ %w(194 11 pandemonium 2008-01-22\ 22:15:10 1991-04-04\ 17:30:05),
|
858
|
+
%w(230 7 cloudburst 2010-12-28\ 11:46:11 1971-06-22\ 13:08:01),],
|
859
|
+
].each do |expected_values|
|
860
|
+
expect(insert_record_block).to receive(:call) { |mysql_table, values_set|
|
861
|
+
expect(mysql_table.table_name).to eq('users_login')
|
862
|
+
expect(values_set).to eq(expected_values)
|
863
|
+
nil
|
864
|
+
}.once
|
865
|
+
end
|
866
|
+
expect(insert_record_block).to receive(:call).never
|
867
|
+
|
868
|
+
# check_point_block
|
869
|
+
[
|
870
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE,
|
871
|
+
last_pos: index_after(dump_content, 'UNLOCK TABLES;')}
|
872
|
+
].each do |expected_params|
|
873
|
+
expect(check_point_block).to receive(:call) { |mysql_table, last_pos, binlog_pos, state, substate|
|
874
|
+
expect(mysql_table.table_name).to eq('users_login') if mysql_table
|
875
|
+
expect(state).to eq(expected_params[:state])
|
876
|
+
if expected_params[:last_pos]
|
877
|
+
expect(last_pos).to eq(expected_params[:last_pos])
|
878
|
+
end
|
879
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
880
|
+
expect(substate).to eq(expected_params[:substate])
|
881
|
+
expected_params[:result]
|
882
|
+
}.once
|
883
|
+
end
|
884
|
+
expect(check_point_block).to receive(:call).never
|
885
|
+
|
886
|
+
binlog_pos = parser_for_resume.parse(
|
887
|
+
create_table_block, insert_record_block, check_point_block
|
888
|
+
)
|
889
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
context 'when dump contains contain escape characters' do
|
894
|
+
let(:dump_content) { <<EOT
|
895
|
+
#{DUMP_HEADER}
|
896
|
+
|
897
|
+
DROP TABLE IF EXISTS `users_login`;
|
898
|
+
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
899
|
+
/*!40101 SET character_set_client = utf8 */;
|
900
|
+
CREATE TABLE `users_login` (
|
901
|
+
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'users id',
|
902
|
+
`login_count` int(10) unsigned DEFAULT '0' COMMENT 'login count',
|
903
|
+
`comment` varchar(255) DEFAULT NULL COMMENT 'comment',
|
904
|
+
`create_time` datetime NOT NULL COMMENT 'create time',
|
905
|
+
`update_time` datetime NOT NULL COMMENT 'update time',
|
906
|
+
PRIMARY KEY (`user_id`)
|
907
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
908
|
+
/*!40101 SET character_set_client = @saved_cs_client */;
|
909
|
+
|
910
|
+
--
|
911
|
+
-- Dumping data for table `users_login`
|
912
|
+
--
|
913
|
+
|
914
|
+
LOCK TABLES `users_login` WRITE;
|
915
|
+
/*!40000 ALTER TABLE `users_login` DISABLE KEYS */;
|
916
|
+
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');
|
917
|
+
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,\\')');
|
918
|
+
/*!40000 ALTER TABLE `users_login` ENABLE KEYS */;
|
919
|
+
UNLOCK TABLES;
|
920
|
+
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
921
|
+
|
922
|
+
EOT
|
923
|
+
}
|
924
|
+
before { generate_dump_file(dump_content) }
|
925
|
+
it do
|
926
|
+
# create_table_block
|
927
|
+
expect(create_table_block).to receive(:call) { |mysql_table|
|
928
|
+
expect(mysql_table.table_name).to eq('users_login')
|
929
|
+
expect(mysql_table.columns.keys).to eq(['user_id', 'login_count', 'comment', 'create_time', 'update_time'])
|
930
|
+
}.once
|
931
|
+
expect(create_table_block).to receive(:call).never
|
932
|
+
|
933
|
+
# insert_record_block
|
934
|
+
[
|
935
|
+
[ %w(15 46 moodier\) 2001-10-02\ 08:18:08 1986-06-11\ 22:50:10),
|
936
|
+
%w(35 6 miss,teer 1991-10-15\ 19:38:07 1970-10-01\ 22:03:10),
|
937
|
+
%w(52 33 subfield' 1972-08-23\ 20:16:08 1974-10-10\ 23:28:11),],
|
938
|
+
[ ['373','31',"outs\nwearing",'1979-10-07 08:10:08','2006-02-22 16:26:04'],
|
939
|
+
['493','8',"schiz\tophrenic",'1979-07-06 07:34:07\'),','1970-08-09,01:21:01,\')'] ]
|
940
|
+
].each do |expected_values|
|
941
|
+
expect(insert_record_block).to receive(:call) { |mysql_table, values_set|
|
942
|
+
expect(mysql_table.table_name).to eq('users_login')
|
943
|
+
expect(values_set).to eq(expected_values)
|
944
|
+
nil
|
945
|
+
}.once
|
946
|
+
end
|
947
|
+
expect(insert_record_block).to receive(:call).never
|
948
|
+
|
949
|
+
# insert_record_block
|
950
|
+
[
|
951
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE},
|
952
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::INSERT_RECORD},
|
953
|
+
{state: Flydata::Mysql::MysqlDumpParser::State::CREATE_TABLE,
|
954
|
+
last_pos: index_after(dump_content, 'UNLOCK TABLES;')}
|
955
|
+
].each do |expected_params|
|
956
|
+
expect(check_point_block).to receive(:call) { |mysql_table, last_pos, binlog_pos, state, substate|
|
957
|
+
expect(mysql_table.table_name).to eq('users_login') if mysql_table
|
958
|
+
expect(state).to eq(expected_params[:state])
|
959
|
+
if expected_params[:last_pos]
|
960
|
+
expect(last_pos).to eq(expected_params[:last_pos])
|
961
|
+
end
|
962
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
963
|
+
expect(substate).to eq(expected_params[:substate])
|
964
|
+
expected_params[:result]
|
965
|
+
}.once
|
966
|
+
end
|
967
|
+
expect(check_point_block).to receive(:call).never
|
968
|
+
|
969
|
+
binlog_pos = default_parser.parse(
|
970
|
+
create_table_block, insert_record_block, check_point_block
|
971
|
+
)
|
972
|
+
expect(binlog_pos).to eq(default_binlog_pos)
|
973
|
+
end
|
974
|
+
end
|
975
|
+
end
|
976
|
+
end
|
977
|
+
describe MysqlDumpParser::InsertParser do
|
978
|
+
describe '#parse' do
|
979
|
+
let(:target_line) { '' }
|
980
|
+
let(:parser) { MysqlDumpParser::InsertParser.new(StringIO.new(target_line)) }
|
981
|
+
subject { parser.parse }
|
982
|
+
|
983
|
+
context 'when single record' do
|
984
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'hogehoge','2014-04-15 13:49:14');" }
|
985
|
+
it 'should return one element array' do
|
986
|
+
expect(subject).to eq([['1','hogehoge','2014-04-15 13:49:14']])
|
987
|
+
end
|
988
|
+
end
|
989
|
+
|
990
|
+
context 'when 2 records' do
|
991
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'foo','2014-04-15 13:49:14'),(2,'var','2014-04-15 14:21:05');" }
|
992
|
+
it 'should return two element array' do
|
993
|
+
expect(subject).to eq([['1','foo','2014-04-15 13:49:14'],['2','var','2014-04-15 14:21:05']])
|
994
|
+
end
|
995
|
+
end
|
996
|
+
|
997
|
+
context 'when data includes carriage return' do
|
998
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'abcd\\refgh','2014-04-15 13:49:14');" }
|
999
|
+
it 'should escape return carriage' do
|
1000
|
+
expect(subject).to eq([['1',"abcd\refgh",'2014-04-15 13:49:14']])
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
context 'when data includes new line' do
|
1005
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'abcd\\nefgh','2014-04-15 13:49:14');" }
|
1006
|
+
it 'should escape new line' do
|
1007
|
+
expect(subject).to eq([['1',"abcd\nefgh",'2014-04-15 13:49:14']])
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
context 'when data includes escaped single quotation' do
|
1012
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'abcd\\',\\'efgh','2014-04-15 13:49:14');" }
|
1013
|
+
it 'should escape single quotation' do
|
1014
|
+
expect(subject).to eq([['1',"abcd','efgh",'2014-04-15 13:49:14']])
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
context 'when data includes back slash' do
|
1019
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'abcd\\\\efgh','2014-04-15 13:49:14');" }
|
1020
|
+
it 'should escape back slash' do
|
1021
|
+
expect(subject).to eq([['1',"abcd\\efgh",'2014-04-15 13:49:14']])
|
1022
|
+
end
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
context 'when data includes mixed escaped characters' do
|
1026
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'ab\\rcd\\\\e\\nf\\',\\'gh','2014-04-15 13:49:14');" }
|
1027
|
+
it 'should escape all' do
|
1028
|
+
expect(subject).to eq([['1',"ab\rcd\\e\nf','gh",'2014-04-15 13:49:14']])
|
1029
|
+
end
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
context 'when data includes mixed escaped characters in a row' do
|
1033
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'ab\\\\ncd\\\\\\nefgh','2014-04-15 13:49:14');" }
|
1034
|
+
it 'should escape all' do
|
1035
|
+
expect(subject).to eq([['1',"ab\\ncd\\\nefgh",'2014-04-15 13:49:14']])
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
context 'when data end with back slash' do
|
1040
|
+
let(:target_line) { "INSERT INTO `test_table` VALUES (1,'D:\\\\download\\\\','2014-04-15 13:49:14');" }
|
1041
|
+
it 'should escape back slash' do
|
1042
|
+
expect(subject).to eq([['1',"D:\\download\\",'2014-04-15 13:49:14']])
|
1043
|
+
end
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
end
|