flydata 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,6 +19,7 @@ class MysqlTableDef
19
19
  'fixed' => 'numeric',
20
20
  'float' => 'float4',
21
21
  'int' => 'int4',
22
+ 'integer' => 'int4',
22
23
  'longblob' => 'varbinary',
23
24
  'longtext' => 'text',
24
25
  'mediumblob' => 'varbinary',
@@ -36,6 +37,31 @@ class MysqlTableDef
36
37
  'varchar' => 'varchar',
37
38
  }
38
39
 
40
+ def self.convert_to_flydata_type(type)
41
+ TYPE_MAP_M2F.each do |mysql_type, flydata_type|
42
+ if /^#{mysql_type}\(|^#{mysql_type}$/.match(type)
43
+ ret_type = type.gsub(/^#{mysql_type}/, flydata_type)
44
+ ret_type = check_and_set_varchar_length(ret_type, mysql_type, flydata_type)
45
+ return ret_type
46
+ end
47
+ end
48
+ nil
49
+ end
50
+
51
+ # Check and set the varchar(char) size which is converted from
52
+ # length to byte size.
53
+ # On Mysql the record size of varchar(char) is a length of characters.
54
+ # ex) varchar(6) on mysql -> varchar(18) on flydata
55
+ def self.check_and_set_varchar_length(type, mysql_type, flydata_type)
56
+ return type unless %w(char varchar).include?(mysql_type)
57
+ if type =~ /\((\d+)\)/
58
+ # expect 3 byte UTF-8 character
59
+ "#{flydata_type}(#{$1.to_i * 3})"
60
+ else
61
+ raise "Invalid varchar type. It must be a bug... type:#{type}"
62
+ end
63
+ end
64
+
39
65
  def self.create(io)
40
66
  params = _create(io)
41
67
  params ? self.new(*params) : nil
@@ -74,7 +100,7 @@ class MysqlTableDef
74
100
  stripped_line = line.strip
75
101
  # `col_smallint` smallint(6) DEFAULT NULL,
76
102
  if stripped_line.start_with?('`')
77
- columns << parse_column_line(line)
103
+ columns << parse_one_column_def(line)
78
104
  # PRIMARY KEY (`id`)
79
105
  elsif stripped_line.start_with?("PRIMARY KEY")
80
106
  parse_primary_key(line, columns)
@@ -112,49 +138,56 @@ class MysqlTableDef
112
138
  tabledef
113
139
  end
114
140
 
115
- private
116
-
117
- def self.parse_column_line(line)
118
- line = line.strip
141
+ def self.parse_one_column_def(query)
142
+ line = query.strip
119
143
  line = line[0..-2] if line.end_with?(',')
120
144
  pos = 0
121
145
  cond = :column_name
122
146
  column = {}
123
147
 
124
148
  while pos < line.length
125
- #cur_char = line[pos]
126
149
  case cond
127
150
  when :column_name #`column_name` ...
128
- pos = line.index('`', 1)
129
- column[:name] = line[1..pos-1]
151
+ pos = line.index(' ', 1)
152
+ column[:name] = if line[0] == '`'
153
+ line[1..pos-2]
154
+ else
155
+ line[0..pos-1]
156
+ end
130
157
  cond = :column_type
131
158
  pos += 1
132
159
  when :column_type #... formattype(,,,) ...
133
160
  pos += 1 until line[pos] != ' '
134
161
  start_pos = pos
135
162
  pos += 1 until line[pos].nil? || line[pos] =~ /\s|\(/
163
+
164
+ # meta
136
165
  if line[pos] == '('
137
- pos = parse_column_type_values(line, pos)
166
+ #TODO: implement better parser
167
+ pos = line.index(')', pos)
138
168
  pos += 1
139
169
  end
170
+
171
+ # type
140
172
  type = line[start_pos..pos-1]
141
- TYPE_MAP_M2F.each do |mysql_type, flydata_type|
142
- type.gsub!(/\b#{mysql_type}\b/, flydata_type)
143
- end
144
- column[:type] = type
173
+ column[:type] = convert_to_flydata_type(type)
174
+
145
175
  cond = :options
146
176
  when :options
147
- column[:auto_increment] = true if line =~ /AUTO_INCREMENT/
148
- column[:not_null] = true if line =~ /NOT NULL/
149
- if /DEFAULT\s+((?:[^'\s]+\b)|(?:'(?:\\'|[^'])*'))/.match(line)
177
+ column[:auto_increment] = true if line =~ /AUTO_INCREMENT/i
178
+ column[:not_null] = true if line =~ /NOT NULL/i
179
+ if /DEFAULT\s+((?:[^'\s]+\b)|(?:'(?:\\'|[^'])*'))/i.match(line)
150
180
  val = $1
151
181
  val = val.slice(1..-1) if val.start_with?("'")
152
182
  val = val.slice(0..-2) if val.end_with?("'")
153
183
  column[:default] = val == "NULL" ? nil : val
154
184
  end
155
- if /COMMENT\s+'(((?:\\'|[^'])*))'/.match(line)
185
+ if /COMMENT\s+'(((?:\\'|[^'])*))'/i.match(line)
156
186
  column[:comment] = $1
157
187
  end
188
+ if block_given?
189
+ column = yield(column, query, pos)
190
+ end
158
191
  break
159
192
  else
160
193
  raise "Invalid condition. It must be a bug..."
@@ -163,6 +196,8 @@ class MysqlTableDef
163
196
  column
164
197
  end
165
198
 
199
+ private
200
+
166
201
  def self.parse_primary_key(line, columns)
167
202
  primary_keys = line.scan(/`(.*?)`/).collect{|item| item[0]}
168
203
  primary_keys.each do |primary_key|
@@ -173,11 +208,6 @@ class MysqlTableDef
173
208
  column[:primary_key] = true
174
209
  end
175
210
  end
176
-
177
- def self.parse_column_type_values(line, pos)
178
- #TODO: more better parse
179
- line.index(')', pos)
180
- end
181
211
  end
182
212
 
183
213
  end
@@ -27,7 +27,7 @@ class RedshiftTableDef
27
27
  'text' => {type: 'varchar(max)'},
28
28
  'time' => {type: 'timestamp'},
29
29
  'varbinary' => {type: 'varchar', use_params: true},
30
- 'varchar' => {type: 'varchar', use_params: true},
30
+ 'varchar' => {type: 'varchar', use_params: true, max_size: 65535},
31
31
  }
32
32
  def self.from_flydata_tabledef(flydata_tabledef, options = {})
33
33
  options[:flydata_ctl_table] = true unless options.has_key?(:flydata_ctl_table)
@@ -40,11 +40,14 @@ class RedshiftTableDef
40
40
  end
41
41
 
42
42
  CREATE_FLYDATA_CTL_TABLE_SQL = <<EOS
43
+ DROP TABLE flydata_ctl_columns;
43
44
  CREATE TABLE flydata_ctl_columns (
44
45
  id integer NOT NULL IDENTITY(1,1),
45
46
  table_name varchar(128) NOT NULL,
46
47
  column_name varchar(128) NOT NULL,
47
48
  src_data_type varchar(1024) NOT NULL,
49
+ revision int NOT NULL DEFAULT 1,
50
+ ordinal_position int NOT NULL,
48
51
  PRIMARY KEY(id)
49
52
  ) DISTKEY(table_name) SORTKEY(table_name);
50
53
  EOS
@@ -73,17 +76,22 @@ EOS
73
76
 
74
77
  def self.column_def_sql(column)
75
78
  type = column[:type]
76
- if type =~ /\(.*?\)/
79
+ if type =~ /\((.*?)\)/
77
80
  type = $` + $'
78
- params = $&
81
+ params = $1
79
82
  end
80
83
 
81
84
  type_info = TYPE_MAP_F2R[type]
82
85
  raise "Unsupported type '#{column[:type]}'" if type_info.nil?
83
86
 
84
- rs_type = (type_info[:use_params] && params && !params.nil?) ?
85
- type_info[:type] + "#{params}"
86
- : type_info[:type]
87
+ rs_type = if type_info[:use_params] && params && !params.nil?
88
+ if type_info[:max_size] && /\d+/.match(params) && params.to_i > type_info[:max_size]
89
+ params = type_info[:max_size]
90
+ end
91
+ type_info[:type] + "(#{params})"
92
+ else
93
+ type_info[:type]
94
+ end
87
95
  line = %Q| "#{column[:name]}" #{rs_type}|
88
96
  line += " NOT NULL" if column[:not_null]
89
97
  if (column.has_key?(:default))
@@ -127,13 +135,13 @@ EOS
127
135
 
128
136
  FLYDATA_CTL_COLUMNS_SQL = <<EOS
129
137
  DELETE FROM flydata_ctl_columns WHERE table_name = '%s';
130
- INSERT INTO flydata_ctl_columns (table_name, column_name, src_data_type) VALUES
138
+ INSERT INTO flydata_ctl_columns (table_name, column_name, src_data_type, ordinal_position) VALUES
131
139
  EOS
132
140
  def self.flydata_ctl_sql(flydata_tabledef)
133
141
  sql = FLYDATA_CTL_COLUMNS_SQL % [ flydata_tabledef[:table_name] ]
134
142
  values = []
135
- flydata_tabledef[:columns].each do |col|
136
- values << "('#{flydata_tabledef[:table_name]}', '#{col[:name]}', '#{escape(col[:type])}')"
143
+ flydata_tabledef[:columns].each.with_index(1) do |col, i|
144
+ values << "('#{flydata_tabledef[:table_name]}', '#{col[:name]}', '#{escape(col[:type])}', #{i})"
137
145
  end
138
146
  sql += values.join(",\n") + ';'
139
147
  sql
@@ -384,7 +384,7 @@ module Flydata
384
384
  end
385
385
 
386
386
  module Mysql
387
- describe MysqlDumpGenerator do
387
+ describe MysqlDumpGeneratorMasterData do
388
388
  let(:stdout) { double(:stdout) }
389
389
  let(:stderr) { double(:stderr) }
390
390
  let(:status) { double(:status) }
@@ -393,21 +393,21 @@ module Flydata
393
393
  'host' => 'localhost', 'port' => 3306, 'username' => 'admin',
394
394
  'password' => 'pass', 'database' => 'dev', 'tables' => 'users,groups',
395
395
  } }
396
- let(:default_dump_generator) { MysqlDumpGenerator.new(default_conf) }
396
+ let(:default_dump_generator) { MysqlDumpGeneratorMasterData.new(default_conf) }
397
397
 
398
398
  describe '#initialize' do
399
399
  context 'with password' do
400
400
  subject { default_dump_generator.instance_variable_get(:@dump_cmd) }
401
401
  it { should eq('mysqldump --protocol=tcp -h localhost -P 3306 -uadmin -ppass --skip-lock-tables ' +
402
- '--single-transaction --flush-logs --hex-blob --master-data=2 dev users groups') }
402
+ '--single-transaction --hex-blob --flush-logs --master-data=2 dev users groups') }
403
403
  end
404
404
  context 'without password' do
405
405
  let (:dump_generator) do
406
- MysqlDumpGenerator.new(default_conf.merge({'password' => ''}))
406
+ MysqlDumpGeneratorMasterData.new(default_conf.merge({'password' => ''}))
407
407
  end
408
408
  subject { dump_generator.instance_variable_get(:@dump_cmd) }
409
409
  it { should eq('mysqldump --protocol=tcp -h localhost -P 3306 -uadmin --skip-lock-tables ' +
410
- '--single-transaction --flush-logs --hex-blob --master-data=2 dev users groups') }
410
+ '--single-transaction --hex-blob --flush-logs --master-data=2 dev users groups') }
411
411
  end
412
412
  end
413
413
 
@@ -12,6 +12,7 @@ module Fluent
12
12
  TEST_SEQUENCE_FILE = /positions\/#{TEST_TABLE}.pos$/
13
13
  TEST_TABLES = "#{TEST_TABLE},test_table_1,test_table_2,test_table_3"
14
14
  TEST_POSITION_FILE = "test_position.log"
15
+ TEST_REVISION_FILE = "#{ENV['HOME']}/.flydata/positions/#{TEST_TABLE}.rev"
15
16
  TEST_TIMESTAMP = 1389214083
16
17
  TEST_CONFIG = <<EOT
17
18
  tag #{TEST_TAG}
@@ -66,27 +67,27 @@ EOT
66
67
  EOT
67
68
  # QUERY: create table
68
69
  TEST_EVENT_QUERY_CREATE_TABLE = <<EOT
69
- {"marker"=>0, "timestamp"=>1389309656, "type_code"=>2, "server_id"=>1, "event_length"=>121, "next_position"=>317, "flags"=>0, "event_type"=>"Query", "thread_id"=>42, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"create table test_table(id int primary key, value text)"}
70
+ {"marker"=>0, "timestamp"=>#{TEST_TIMESTAMP}, "type_code"=>2, "server_id"=>1, "event_length"=>121, "next_position"=>317, "flags"=>0, "event_type"=>"Query", "thread_id"=>42, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"create table test_table(id int primary key, value text)"}
70
71
  EOT
71
72
  # QUERY: drop table
72
73
  TEST_EVENT_QUERY_DROP_TABLE = <<EOT
73
- {"marker"=>0, "timestamp"=>1389309995, "type_code"=>2, "server_id"=>1, "event_length"=>115, "next_position"=>568, "flags"=>0, "event_type"=>"Query", "thread_id"=>42, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"DROP TABLE `test_table` /* generated by server */"}
74
+ {"marker"=>0, "timestamp"=>#{TEST_TIMESTAMP}, "type_code"=>2, "server_id"=>1, "event_length"=>115, "next_position"=>568, "flags"=>0, "event_type"=>"Query", "thread_id"=>42, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"DROP TABLE `test_table` /* generated by server */"}
74
75
  EOT
75
76
  # QUERY: alter table add column
76
77
  TEST_EVENT_QUERY_ALTER_TABLE_ADD_COLUMN = <<EOT
77
- {"marker"=>0, "timestamp"=>1389310404, "type_code"=>2, "server_id"=>1, "event_length"=>111, "next_position"=>800, "flags"=>0, "event_type"=>"Query", "thread_id"=>42, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"alter table test_table add column sum integer"}
78
+ {"marker"=>0, "timestamp"=>#{TEST_TIMESTAMP}, "type_code"=>2, "server_id"=>1, "event_length"=>111, "next_position"=>800, "flags"=>0, "event_type"=>"Query", "thread_id"=>42, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"alter table test_table add column sum integer"}
78
79
  EOT
79
80
  # QUERY: alter table drop column
80
81
  TEST_EVENT_QUERY_ALTER_TABLE_DROP_COLUMN = <<EOT
81
- {"marker"=>0, "timestamp"=>1389310533, "type_code"=>2, "server_id"=>1, "event_length"=>104, "next_position"=>904, "flags"=>0, "event_type"=>"Query", "thread_id"=>42, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"alter table test_table drop column sum"}
82
+ {"marker"=>0, "timestamp"=>#{TEST_TIMESTAMP}, "type_code"=>2, "server_id"=>1, "event_length"=>104, "next_position"=>904, "flags"=>0, "event_type"=>"Query", "thread_id"=>42, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"alter table test_table drop column sum"}
82
83
  EOT
83
84
  # QUERY: alter table modify column
84
85
  TEST_EVENT_QUERY_ALTER_TABLE_MODIFY_COLUMN = <<EOT
85
- {"marker"=>0, "timestamp"=>1389310735, "type_code"=>2, "server_id"=>1, "event_length"=>112, "next_position"=>1352, "flags"=>0, "event_type"=>"Query", "thread_id"=>48, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"alter table test_table modify column sum float"}
86
+ {"marker"=>0, "timestamp"=>#{TEST_TIMESTAMP}, "type_code"=>2, "server_id"=>1, "event_length"=>112, "next_position"=>1352, "flags"=>0, "event_type"=>"Query", "thread_id"=>48, "exec_time"=>0, "error_code"=>0, "variables"=>[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 115, 116, 100, 4, 33, 0, 33, 0, 8, 0], "db_name"=>"test_db", "query"=>"alter table test_table modify column sum float"}
86
87
  EOT
87
88
  # - 03 STOP_EVENT
88
89
  TEST_EVENT_STOP = <<EOT
89
- {"marker"=>0, "timestamp"=>1389309286, "type_code"=>3, "server_id"=>1, "event_length"=>19, "next_position"=>2929, "flags"=>0, "event_type"=>"Stop"}
90
+ {"marker"=>0, "timestamp"=>#{TEST_TIMESTAMP}, "type_code"=>3, "server_id"=>1, "event_length"=>19, "next_position"=>2929, "flags"=>0, "event_type"=>"Stop"}
90
91
  EOT
91
92
  # - 04 ROTATE_EVENT
92
93
  TEST_EVENT_ROTATE = <<EOT
@@ -137,7 +138,8 @@ EOT
137
138
  def expect_emitted_records_with_rows(event, type, table, position, binlog_file, rows)
138
139
  rows = [rows] unless rows.kind_of?(Array)
139
140
  records = rows.collect do |row|
140
- { "type"=>type, "table_name"=>table, "respect_order"=>true, "seq"=>2, "src_pos"=>"#{binlog_file}\t#{position}", "row"=>row }
141
+ { :type=>type, :table_name=>table, :respect_order=>true, :seq=>2,
142
+ :src_pos=>"#{binlog_file}\t#{position}", :table_rev=>1, :row=>row }
141
143
  end
142
144
  expect_emitted_records(event, records)
143
145
  end
@@ -152,6 +154,7 @@ EOT
152
154
  let(:update_event) { create_event(TEST_EVENT_UPDATE) }
153
155
  let(:update_two_byte_event) { create_event(TEST_EVENT_TWO_BYTE_UPDATE) }
154
156
  let(:update_three_byte_event) { create_event(TEST_EVENT_THREE_BYTE_UPDATE) }
157
+ let(:alter_table_add_column_event) { create_event(TEST_EVENT_QUERY_ALTER_TABLE_ADD_COLUMN) }
155
158
 
156
159
  let(:query_event) { create_event(TEST_EVENT_QUERY_CREATE_DATABSE) }
157
160
  let(:table_map_event) { create_event(TEST_EVENT_TABLE_MAP) }
@@ -173,6 +176,7 @@ EOT
173
176
  allow(MysqlBinlogInput::BinlogUtil).to receive(:to_hash) {|e| e}
174
177
  allow(File).to receive(:open).with(TEST_POSITION_FILE).and_return('test_position')
175
178
  allow(File).to receive(:exists?).with(TEST_POSITION_FILE).and_return(true)
179
+ allow(File).to receive(:exists?).with(TEST_REVISION_FILE).and_return(false)
176
180
  allow(File).to receive(:open).with(TEST_SEQUENCE_FILE, "r+").and_yield(table_seq_file)
177
181
  Timecop.freeze(now)
178
182
  end
@@ -185,7 +189,9 @@ EOT
185
189
 
186
190
  context 'when received rotate event' do
187
191
  before do
188
- Test.configure_plugin(plugin, TEST_CONFIG)
192
+ allow(File).to receive(:exists?).with(/binlog.pos/).and_return(false)
193
+ allow(File).to receive(:exists?).with(/\.rev$/).and_return(false)
194
+ Test.configure_plugin(plugin, TEST_CONFIG)
189
195
  plugin.event_listener(rotate_event)
190
196
  end
191
197
 
@@ -261,6 +267,38 @@ EOT
261
267
  end
262
268
  end
263
269
 
270
+ context 'when received alter table event' do
271
+ before do
272
+ allow(File).to receive(:open).with(/test_table\.rev/, 'w')
273
+ end
274
+ it do
275
+ expect(table_seq_file).to receive(:write).with(2).never
276
+ expect_no_emitted_record(alter_table_add_column_event)
277
+ end
278
+ end
279
+
280
+ #TODO: Uncomment the following test and delete the above one after supporting alter table
281
+ =begin
282
+ context 'when received alter table event' do
283
+ before do
284
+ allow(File).to receive(:open).with(/test_table\.rev/, 'w')
285
+ end
286
+ it do
287
+ expect(table_seq_file).to receive(:write).with(2).once
288
+ expect_emitted_records(alter_table_add_column_event, {
289
+ subtype: :add_column,
290
+ table_name: "test_table",
291
+ type: :alter_table,
292
+ respect_order: true,
293
+ src_pos: "mysql-bin.000048\t689",
294
+ table_rev: 2, # increment revision
295
+ seq: 2,
296
+ column: {:name=>"sum", :type=>'int4'},
297
+ })
298
+ end
299
+ end
300
+ =end
301
+
264
302
  context 'when received event with another database name' do
265
303
  it do
266
304
  event = insert_event
@@ -285,9 +323,10 @@ EOT
285
323
  end
286
324
  end
287
325
  end
288
-
326
+
289
327
  context 'when rotate event is not received' do
290
328
  before do
329
+ allow(File).to receive(:exists?).with(/binlog.pos/).and_return(false)
291
330
  Test.configure_plugin(plugin, TEST_CONFIG)
292
331
  end
293
332
 
@@ -311,7 +350,7 @@ EOT
311
350
  expect_emitted_records_with_rows(delete_event, :delete, TEST_TABLE, 5324, "",
312
351
  [{"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
313
352
  end
314
-
353
+
315
354
  it 'logs warning once when it receives consecutive events' do
316
355
  table_seq_file.should_receive(:write).exactly(10).with(2)
317
356
  expect($log).to receive(:warn).with("Binlog file name is empty. Rotate event not received!").once
@@ -322,24 +361,56 @@ EOT
322
361
  expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE, 628, "",
323
362
  [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
324
363
  expect_emitted_records_with_rows(delete_event, :delete, TEST_TABLE, 5324, "",
325
- [{"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
326
- end
364
+ [{"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
365
+ end
327
366
  end
328
367
 
329
368
  context 'when received rotate event with missing binlog file' do
330
369
  before do
370
+ allow(File).to receive(:exists?).with(/binlog.pos/).and_return(false)
331
371
  Test.configure_plugin(plugin, TEST_CONFIG)
332
372
  plugin.event_listener(rotate_event_corrupt)
333
373
  end
334
-
374
+
335
375
  it 'logs a warning and emits FET with a blank binlog file name, when it receives an insert event' do
336
376
  table_seq_file.should_receive(:write).exactly(3).with(2)
337
377
  expect($log).to receive(:warn).with("Binlog file name is empty. Rotate event not received!").once
338
378
  expect_emitted_records_with_rows(insert_event, :insert, TEST_TABLE, 628, "",
339
379
  [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
340
- end
380
+ end
381
+ end
382
+
383
+ context 'when per-table binlog file is present' do
384
+ before do
385
+ f = double('per-table-binlog-file')
386
+ allow(f).to receive(:readline).and_return("mysql-bin.000048\t1010")
387
+ allow(File).to receive(:exists?).with(/binlog.pos/).and_return(false)
388
+ allow(File).to receive(:exists?).with(/test_table.binlog.pos/).and_return(true)
389
+ allow(File).to receive(:open).with(/test_table.binlog.pos/, "r").and_return(f)
390
+ Test.configure_plugin(plugin, TEST_CONFIG)
391
+ plugin.event_listener(rotate_event)
392
+ end
393
+
394
+ it 'emits no record when it receives an event whose binlog position is less than per-table binlog position' do
395
+ expect_no_emitted_record(insert_event)
396
+ end
397
+
398
+ it 'deletes the per-table-binlog file (if there is one) as soon as a record with bigger binlog position is received' do
399
+ event = insert_event
400
+ event['next_position'] = 1250
401
+ table_seq_file.should_receive(:write).exactly(3).with(2)
402
+ expect(FileUtils).to receive(:rm).with(/test_table.binlog.pos/, :force => true)
403
+ expect_emitted_records_with_rows(event, :insert, TEST_TABLE, 1211, "mysql-bin.000048",
404
+ [{"1"=>"0SL00000001", "2"=>"foo"}, {"1"=>"0SL00000002", "2"=>"var"}, {"1"=>"0SL00000003", "2"=>"hoge"}])
405
+ end
406
+
407
+ it 'emits no record when it receives an event whose binlog position is equal to per-table binlog position' do
408
+ event = insert_event
409
+ event['next_position'] = 1049
410
+ expect_no_emitted_record(event)
411
+ end
341
412
  end
342
-
343
413
  end
344
414
  end
415
+
345
416
  end
@@ -0,0 +1,33 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ module Mysql
4
+ describe BinLogPosition do
5
+ let(:pos1) { BinLogPosition.new('mysql-bin.000064 107') }
6
+ let(:pos2) { BinLogPosition.new('mysql-bin.000064 978') }
7
+ let(:pos3) { BinLogPosition.new('mysql-bin.000065 107') }
8
+ let(:pos4) { BinLogPosition.new('mysql-bin.000064 1107') }
9
+ let(:pos5) { BinLogPosition.new('mysql-bin.000064 1107') }
10
+
11
+ it 'should respond to greater than or equal to operator' do
12
+ pos1.respond_to?('>=').should be_true
13
+ end
14
+
15
+ context 'when testing greater than or equal to operator' do
16
+ it 'should return true when it is compared with another object with smaller position value' do
17
+ (pos2 >= pos1).should be_true
18
+ end
19
+
20
+ it 'should return false when it is compared with another object with bigger binlogfile value' do
21
+ (pos2 >= pos3).should be_false
22
+ end
23
+
24
+ it 'should compare the position as an integer (and not string)' do
25
+ (pos2 >= pos4).should be_false
26
+ end
27
+
28
+ it 'should return true when it is compared with another object with equal value' do
29
+ (pos4 >= pos5).should be_true
30
+ end
31
+ end
32
+ end
33
+ end