flydata 0.6.14 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/flydata-core/Gemfile +1 -0
- data/flydata-core/Gemfile.lock +5 -0
- data/flydata-core/lib/flydata-core/errors.rb +4 -2
- data/flydata-core/lib/flydata-core/mysql/binlog_pos.rb +4 -0
- data/flydata-core/lib/flydata-core/postgresql/compatibility_checker.rb +119 -0
- data/flydata-core/lib/flydata-core/postgresql/config.rb +58 -0
- data/flydata-core/lib/flydata-core/postgresql/pg_client.rb +170 -0
- data/flydata-core/lib/flydata-core/postgresql/snapshot.rb +49 -0
- data/flydata-core/lib/flydata-core/postgresql/source_pos.rb +71 -10
- data/flydata-core/lib/flydata-core/table_def/mysql_table_def.rb +1 -1
- data/flydata-core/lib/flydata-core/table_def/postgresql_table_def.rb +76 -17
- data/flydata-core/lib/flydata-core/table_def/redshift_table_def.rb +59 -10
- data/flydata-core/spec/mysql/binlog_pos_spec.rb +10 -2
- data/flydata-core/spec/postgresql/compatibility_checker_spec.rb +148 -0
- data/flydata-core/spec/postgresql/config_spec.rb +85 -0
- data/flydata-core/spec/postgresql/pg_client_spec.rb +195 -0
- data/flydata-core/spec/postgresql/snapshot_spec.rb +55 -0
- data/flydata-core/spec/postgresql/source_pos_spec.rb +70 -8
- data/flydata-core/spec/table_def/postgresql_table_def_spec.rb +80 -19
- data/flydata-core/spec/table_def/redshift_table_def_spec.rb +211 -14
- data/flydata.gemspec +0 -0
- data/lib/flydata.rb +1 -0
- data/lib/flydata/command/sender.rb +10 -7
- data/lib/flydata/command/sync.rb +4 -1
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/base.rb +1 -0
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/fluent_log_ext.rb +73 -0
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync.rb +35 -10
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_diff_based.rb +29 -0
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_query_based.rb +26 -0
- data/lib/flydata/fluent-plugins/flydata_plugin_ext/preference.rb +29 -13
- data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +10 -18
- data/lib/flydata/fluent-plugins/in_postgresql_query_based_flydata.rb +64 -0
- data/lib/flydata/helpers.rb +1 -3
- data/lib/flydata/plugin_support/context.rb +14 -2
- data/lib/flydata/plugin_support/source_position_file.rb +35 -0
- data/lib/flydata/plugin_support/sync_record_emittable.rb +2 -1
- data/lib/flydata/query_based_sync/client.rb +101 -0
- data/lib/flydata/query_based_sync/record_size_estimator.rb +39 -0
- data/lib/flydata/query_based_sync/resource_requester.rb +70 -0
- data/lib/flydata/query_based_sync/response.rb +122 -0
- data/lib/flydata/query_based_sync/response_handler.rb +30 -0
- data/lib/flydata/source/sync_generate_table_ddl.rb +1 -1
- data/lib/flydata/source_mysql/plugin_support/binlog_record_dispatcher.rb +2 -2
- data/lib/flydata/source_mysql/plugin_support/binlog_record_handler.rb +3 -9
- data/lib/flydata/source_mysql/plugin_support/context.rb +26 -2
- data/lib/flydata/source_mysql/plugin_support/source_position_file.rb +14 -0
- data/lib/flydata/source_mysql/table_ddl.rb +3 -3
- data/lib/flydata/source_mysql/{plugin_support/table_meta.rb → table_meta.rb} +3 -10
- data/lib/flydata/source_postgresql/generate_source_dump.rb +44 -63
- data/lib/flydata/source_postgresql/parse_dump_and_send.rb +2 -0
- data/lib/flydata/source_postgresql/plugin_support/context.rb +13 -0
- data/lib/flydata/source_postgresql/plugin_support/source_position_file.rb +14 -0
- data/lib/flydata/source_postgresql/query_based_sync/client.rb +16 -0
- data/lib/flydata/source_postgresql/query_based_sync/diff_query_generator.rb +135 -0
- data/lib/flydata/source_postgresql/query_based_sync/resource_requester.rb +86 -0
- data/lib/flydata/source_postgresql/query_based_sync/response.rb +12 -0
- data/lib/flydata/source_postgresql/query_based_sync/response_handler.rb +12 -0
- data/lib/flydata/source_postgresql/sync_generate_table_ddl.rb +25 -79
- data/lib/flydata/source_postgresql/table_meta.rb +168 -0
- data/lib/flydata/sync_file_manager.rb +5 -5
- data/lib/flydata/table_meta.rb +19 -0
- data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_context.rb +85 -0
- data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_diff_based_shared_examples.rb +36 -0
- data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_query_based_shared_examples.rb +37 -0
- data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_shared_examples.rb +67 -0
- data/spec/flydata/fluent-plugins/in_mysql_binlog_flydata_spec.rb +119 -96
- data/spec/flydata/fluent-plugins/in_postgresql_query_based_flydata_spec.rb +82 -0
- data/spec/flydata/fluent-plugins/sync_source_plugin_context.rb +29 -0
- data/spec/flydata/plugin_support/context_spec.rb +37 -3
- data/spec/flydata/query_based_sync/client_spec.rb +79 -0
- data/spec/flydata/query_based_sync/query_based_sync_context.rb +116 -0
- data/spec/flydata/query_based_sync/record_size_estimator_spec.rb +54 -0
- data/spec/flydata/query_based_sync/resource_requester_spec.rb +58 -0
- data/spec/flydata/query_based_sync/response_handler_spec.rb +36 -0
- data/spec/flydata/query_based_sync/response_spec.rb +157 -0
- data/spec/flydata/source_mysql/plugin_support/context_spec.rb +7 -1
- data/spec/flydata/source_mysql/plugin_support/dml_record_handler_spec.rb +2 -15
- data/spec/flydata/source_mysql/plugin_support/drop_database_query_handler_spec.rb +1 -1
- data/spec/flydata/source_mysql/plugin_support/shared_query_handler_context.rb +12 -11
- data/spec/flydata/source_mysql/plugin_support/source_position_file_spec.rb +53 -0
- data/spec/flydata/source_mysql/plugin_support/truncate_query_handler_spec.rb +1 -1
- data/spec/flydata/source_mysql/table_ddl_spec.rb +5 -5
- data/spec/flydata/source_mysql/{plugin_support/table_meta_spec.rb → table_meta_spec.rb} +6 -7
- data/spec/flydata/source_postgresql/generate_source_dump_spec.rb +165 -77
- data/spec/flydata/source_postgresql/query_based_sync/diff_query_generator_spec.rb +213 -0
- data/spec/flydata/source_postgresql/query_based_sync/query_based_sync_postgresql_context.rb +76 -0
- data/spec/flydata/source_postgresql/query_based_sync/resource_requester_spec.rb +70 -0
- data/spec/flydata/source_postgresql/table_meta_spec.rb +77 -0
- metadata +49 -6
- data/lib/flydata/source_mysql/plugin_support/binlog_position_file.rb +0 -23
- data/lib/flydata/source_postgresql/pg_client.rb +0 -43
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'flydata/query_based_sync/query_based_sync_context'
|
3
|
+
|
4
|
+
|
5
|
+
module Flydata
|
6
|
+
module QueryBasedSync
|
7
|
+
|
8
|
+
describe ResponseHandler do
|
9
|
+
include_context 'query based sync context'
|
10
|
+
|
11
|
+
let(:subject_object) { described_class.new(context) }
|
12
|
+
|
13
|
+
|
14
|
+
describe '#handle' do
|
15
|
+
subject { subject_object.handle(response) }
|
16
|
+
|
17
|
+
let(:records_for_emitting) {[
|
18
|
+
{row: {'1'=>1, '2'=>'a'}},
|
19
|
+
{row: {'1'=>2, '2'=>'b'}},
|
20
|
+
{row: {'1'=>3, '2'=>'c'}},
|
21
|
+
]}
|
22
|
+
|
23
|
+
it do
|
24
|
+
expect(subject_object).to receive(:emit_sync_records).with(records_for_emitting,
|
25
|
+
type: :update,
|
26
|
+
src_pos: response.new_source_pos.to_s,
|
27
|
+
table: 'table_1')
|
28
|
+
expect(table_1_src_pos_file).to receive(:save).with(response.new_source_pos)
|
29
|
+
subject
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'flydata/query_based_sync/query_based_sync_context'
|
3
|
+
|
4
|
+
|
5
|
+
module Flydata
|
6
|
+
module QueryBasedSync
|
7
|
+
|
8
|
+
describe Response do
|
9
|
+
include_context 'query based sync context'
|
10
|
+
|
11
|
+
let(:subject_object) { DummyResponse.new(context, 'table_1', records, query_cond) }
|
12
|
+
|
13
|
+
describe '#initialize' do
|
14
|
+
before do
|
15
|
+
subject_object
|
16
|
+
end
|
17
|
+
|
18
|
+
it { expect(subject_object.instance_variable_get(:@table_meta)).to eq(table_meta_1) }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#set_new_source_pos' do
|
22
|
+
subject { subject_object.set_new_source_pos(required_pk_values) }
|
23
|
+
|
24
|
+
context 'when require_pk_values is false' do
|
25
|
+
let(:required_pk_values) { false }
|
26
|
+
|
27
|
+
it 'sets current snapshot to the new source pos' do
|
28
|
+
expect(context.source_pos_class).to receive(:new).with(current_snapshot)
|
29
|
+
subject
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when require_pk_values is true' do
|
34
|
+
let(:required_pk_values) { true }
|
35
|
+
|
36
|
+
it 'sets old snapshot and current snapshot with primary key values' do
|
37
|
+
expect(context.source_pos_class).to receive(:new).
|
38
|
+
with(previous_snapshot, current_snapshot, [{'id'=>3}])
|
39
|
+
subject
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#record_count' do
|
45
|
+
subject { subject_object.record_count }
|
46
|
+
|
47
|
+
context 'when records is nil' do
|
48
|
+
before do
|
49
|
+
subject_object.instance_variable_set(:@records, nil)
|
50
|
+
end
|
51
|
+
it { is_expected.to eq(0) }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when records is not nil' do
|
55
|
+
it { is_expected.to eq(3) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#empty?' do
|
60
|
+
subject { subject_object.empty? }
|
61
|
+
|
62
|
+
context 'when records is nil' do
|
63
|
+
before do
|
64
|
+
subject_object.instance_variable_set(:@records, nil)
|
65
|
+
end
|
66
|
+
it { is_expected.to be(true) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when records is not nil but empty array' do
|
70
|
+
before do
|
71
|
+
subject_object.instance_variable_set(:@records, [])
|
72
|
+
end
|
73
|
+
it { is_expected.to be(true) }
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when records is not nil and not empty' do
|
77
|
+
it { is_expected.to be(false) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '.create_responses' do
|
82
|
+
let(:raw_result) {[
|
83
|
+
{'id'=>1, 'value'=>'a'},
|
84
|
+
{'id'=>2, 'value'=>'b'},
|
85
|
+
{'id'=>3, 'value'=>'c'},
|
86
|
+
]}
|
87
|
+
subject { DummyResponse.create_responses(context, 'table_1', raw_result, query_cond) }
|
88
|
+
|
89
|
+
context 'when raw_result is empty' do
|
90
|
+
let(:raw_result) { [] }
|
91
|
+
|
92
|
+
it 'returns a response having empty records' do
|
93
|
+
expect(subject.first.records).to eq([])
|
94
|
+
expect(subject.size).to eq(1)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'when raw_result have records' do
|
99
|
+
context 'when not execeeding the emit_chunk_limit' do
|
100
|
+
it 'returns a response having records' do
|
101
|
+
expect(subject.first.records).to eq([
|
102
|
+
{'1'=>1, '2'=>'a'},
|
103
|
+
{'1'=>2, '2'=>'b'},
|
104
|
+
{'1'=>3, '2'=>'c'},
|
105
|
+
])
|
106
|
+
expect(subject.size).to eq(1)
|
107
|
+
expect(subject.first.new_source_pos.args).to eq([
|
108
|
+
current_snapshot
|
109
|
+
])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'when execeeding the emit_chunk_limit' do
|
114
|
+
before do
|
115
|
+
context.params[:emit_chunk_limit] = 380
|
116
|
+
end
|
117
|
+
it 'returns responses with splitted records' do
|
118
|
+
expect(subject.size).to eq(2)
|
119
|
+
expect(subject[0].records).to eq([
|
120
|
+
{'1'=>1, '2'=>'a'},
|
121
|
+
{'1'=>2, '2'=>'b'},
|
122
|
+
])
|
123
|
+
expect(subject[0].new_source_pos.args).to eq([
|
124
|
+
previous_snapshot, current_snapshot, [{'id'=>2}]
|
125
|
+
])
|
126
|
+
expect(subject[1].records).to eq([
|
127
|
+
{'1'=>3, '2'=>'c'},
|
128
|
+
])
|
129
|
+
expect(subject[1].new_source_pos.args).to eq([
|
130
|
+
current_snapshot
|
131
|
+
])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'when the number of records is same as the max_num_rows_per_query' do
|
136
|
+
before do
|
137
|
+
table_meta_1[:max_num_rows_per_query] = 3
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'returns a response with a new source pos having pk_values' do
|
141
|
+
expect(subject.first.records).to eq([
|
142
|
+
{'1'=>1, '2'=>'a'},
|
143
|
+
{'1'=>2, '2'=>'b'},
|
144
|
+
{'1'=>3, '2'=>'c'},
|
145
|
+
])
|
146
|
+
expect(subject.size).to eq(1)
|
147
|
+
expect(subject.first.new_source_pos.args).to eq([
|
148
|
+
previous_snapshot, current_snapshot, [{'id'=>3}],
|
149
|
+
])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -7,11 +7,17 @@ describe ::Flydata::SourceMysql::PluginSupport::Context do
|
|
7
7
|
describe '#initialize' do
|
8
8
|
subject { subject_object }
|
9
9
|
|
10
|
+
let(:sync_fm) { double('sync_fm') }
|
11
|
+
|
12
|
+
before do
|
13
|
+
allow(sync_fm).to receive(:get_table_source_raw_pos).and_return('-')
|
14
|
+
end
|
15
|
+
|
10
16
|
context 'when no missing params' do
|
11
17
|
let(:params) { {
|
12
18
|
tables: %w(table_a table_b table_c),
|
13
19
|
tag: 'test_tag',
|
14
|
-
sync_fm:
|
20
|
+
sync_fm: sync_fm,
|
15
21
|
omit_events: {'table_c' => %i(delete truncate_table)},
|
16
22
|
table_revs: {'table_a' => 1, 'table_b' => 2, 'table_c' => 3},
|
17
23
|
database: 'test_db',
|
@@ -2,27 +2,14 @@
|
|
2
2
|
|
3
3
|
require 'fluent_plugins_spec_helper'
|
4
4
|
require 'flydata/source_mysql/plugin_support/dml_record_handler'
|
5
|
+
require 'flydata/source_mysql/plugin_support/shared_query_handler_context'
|
5
6
|
|
6
7
|
module Flydata::SourceMysql::PluginSupport
|
7
8
|
|
8
9
|
describe DmlRecordHandler do
|
10
|
+
include_context "query handler context"
|
9
11
|
let(:subject_object) { described_class.new(context) }
|
10
12
|
|
11
|
-
let(:table_meta) { double('table_meta') }
|
12
|
-
let(:tables) { ['items', 'orders'] }
|
13
|
-
let(:sync_fm) {
|
14
|
-
sfm = double('sync_fm')
|
15
|
-
allow(sfm).to receive(:get_table_source_raw_pos).and_return nil
|
16
|
-
sfm
|
17
|
-
}
|
18
|
-
let(:context) {
|
19
|
-
c = double('context')
|
20
|
-
allow(c).to receive(:table_meta).and_return(table_meta)
|
21
|
-
allow(c).to receive(:tables).and_return(tables)
|
22
|
-
allow(c).to receive(:sync_fm).and_return(sync_fm)
|
23
|
-
c
|
24
|
-
}
|
25
|
-
|
26
13
|
describe '#encode_row_value' do
|
27
14
|
subject { subject_object.send(:encode_row_value, value) }
|
28
15
|
|
@@ -19,7 +19,7 @@ module Flydata::SourceMysql::PluginSupport
|
|
19
19
|
shared_examples "test different dbs" do
|
20
20
|
context "for the target db" do
|
21
21
|
let(:database) { target_database }
|
22
|
-
let(:binlog_pos) { "#{context.
|
22
|
+
let(:binlog_pos) { "#{context.cur_src_pos_file}\t#{record['next_position'] - record['event_length']}" }
|
23
23
|
|
24
24
|
it "shows a error message" do
|
25
25
|
expect($log).to receive(:error).with("DROP DATABASE detected. A full re-sync is required to provide sync consistency. - db_name:'#{record["db_name"]}' query:'#{record["query"]}' normalized query:'#{record["normalized_query"]}' binlog_pos:'#{binlog_pos}'")
|
@@ -16,21 +16,22 @@ module Flydata::SourceMysql::PluginSupport
|
|
16
16
|
r
|
17
17
|
end
|
18
18
|
let(:table_meta) { double'table_meta' }
|
19
|
-
let(:
|
19
|
+
let(:cur_src_pos_file) { "mysql-bin.000066" }
|
20
20
|
let(:tag) { "some_tag" }
|
21
21
|
let(:table_rev) { 1 }
|
22
22
|
let(:flydata_record_version) { 2 }
|
23
23
|
let(:context) do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
24
|
+
require 'flydata/source_mysql/plugin_support/context'
|
25
|
+
Flydata::SourceMysql::PluginSupport::Context.new(
|
26
|
+
sync_fm: sync_fm,
|
27
|
+
database: target_database,
|
28
|
+
tables: [table],
|
29
|
+
table_meta: table_meta,
|
30
|
+
cur_src_pos_file: cur_src_pos_file,
|
31
|
+
omit_events: {},
|
32
|
+
table_revs: {table => table_rev},
|
33
|
+
tag: tag,
|
34
|
+
)
|
34
35
|
end
|
35
36
|
let(:query) { "a_query" }
|
36
37
|
let(:normalized_query) { 'a_query' }
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'fluent_plugins_spec_helper'
|
2
|
+
require 'flydata/source_mysql/plugin_support/source_position_file'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Flydata::SourceMysql::PluginSupport
|
6
|
+
describe SourcePositionFile do
|
7
|
+
let(:binlog_file) { "mysqlbin-00001" }
|
8
|
+
let(:binlog_pos) { "4" }
|
9
|
+
let(:source_pos_content) { "#{binlog_file}\t#{binlog_pos}" }
|
10
|
+
|
11
|
+
let(:pos_file) { Tempfile.new('source_pos') }
|
12
|
+
let(:file_path) { pos_file.path }
|
13
|
+
let(:subject_object) { described_class.new(file_path) }
|
14
|
+
|
15
|
+
before { File.write(file_path, source_pos_content) }
|
16
|
+
after { FileUtils.rm(file_path) if File.exists?(file_path) }
|
17
|
+
|
18
|
+
describe '#exists?' do
|
19
|
+
subject { subject_object.exists? }
|
20
|
+
context 'when the file exists' do
|
21
|
+
it { is_expected.to eq(true) }
|
22
|
+
end
|
23
|
+
context 'when the file does not exist' do
|
24
|
+
before { FileUtils.rm(file_path) }
|
25
|
+
it { is_expected.to eq(false) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#read' do
|
30
|
+
subject { subject_object.read }
|
31
|
+
it { is_expected.to eq(source_pos_content) }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#pos' do
|
35
|
+
subject { subject_object.pos }
|
36
|
+
it { is_expected.to be_kind_of(FlydataCore::Mysql::BinlogPos) }
|
37
|
+
it { expect(subject.to_s).to eq(source_pos_content) }
|
38
|
+
context 'when the file is missing' do
|
39
|
+
before { FileUtils.rm(file_path) }
|
40
|
+
it { is_expected.to be_nil }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#save' do
|
45
|
+
subject { subject_object.save("mysql-bin.000002", 120)}
|
46
|
+
it { is_expected.to be_kind_of(FlydataCore::Mysql::BinlogPos) }
|
47
|
+
it do
|
48
|
+
subject
|
49
|
+
expect(subject_object.read).to eq("mysql-bin.000002\t120")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -22,7 +22,7 @@ module Flydata::SourceMysql::PluginSupport
|
|
22
22
|
query: truncate_query,
|
23
23
|
type: :truncate_table,
|
24
24
|
respect_order: true,
|
25
|
-
src_pos: "#{
|
25
|
+
src_pos: "#{cur_src_pos_file}\t#{next_position - event_length}",
|
26
26
|
table_rev: table_rev,
|
27
27
|
seq: seq,
|
28
28
|
v: flydata_record_version
|
@@ -56,10 +56,10 @@ describe TableDdl do
|
|
56
56
|
allow($log).to receive(:error)
|
57
57
|
allow($log).to receive(:trace)
|
58
58
|
|
59
|
-
allow(mysql_tabledef1).to receive(:
|
59
|
+
allow(mysql_tabledef1).to receive(:default_source_charset).and_return(table1_charset)
|
60
60
|
allow(mysql_tabledef1).to receive(:table_name).and_return(table_name1)
|
61
61
|
allow(mysql_tabledef1).to receive(:column_def).and_return(column_def1)
|
62
|
-
allow(mysql_tabledef2).to receive(:
|
62
|
+
allow(mysql_tabledef2).to receive(:default_source_charset).and_return(table2_charset)
|
63
63
|
allow(mysql_tabledef2).to receive(:table_name).and_return(table_name2)
|
64
64
|
allow(mysql_tabledef2).to receive(:column_def).and_return(column_def2)
|
65
65
|
end
|
@@ -82,9 +82,9 @@ describe TableDdl do
|
|
82
82
|
matcher = table_names.inject(matcher) {|m, tn| m.and_yield(mysql_tabledef[tn], nil) }
|
83
83
|
expect(FlydataCore::Mysql::CommandGenerator).to matcher
|
84
84
|
expect(File).to receive(:open).with(position_file).and_return(master_binlog_position)
|
85
|
-
expect(context).to receive(:
|
86
|
-
expect(context).to receive(:
|
87
|
-
expect(context).to receive(:
|
85
|
+
expect(context).to receive(:cur_src_pos_file).and_return(original_current_binlog_file)
|
86
|
+
expect(context).to receive(:cur_src_pos_file=).with(binlog_file)
|
87
|
+
expect(context).to receive(:cur_src_pos_file=).with(original_current_binlog_file)
|
88
88
|
expect(sync_fm).to receive(:save_generated_ddl).with(table_names, target_version)
|
89
89
|
|
90
90
|
expect{|b| subject_object.send(*subject_params, &b) }.to yield_successive_args(*expected_alter_table_charset_events)
|
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'fluent_plugins_spec_helper'
|
2
|
-
require 'flydata/source_mysql/
|
2
|
+
require 'flydata/source_mysql/table_meta'
|
3
3
|
|
4
4
|
module Flydata::SourceMysql
|
5
|
-
module PluginSupport
|
6
5
|
describe TableMeta do
|
7
6
|
let(:db_opts) { { host: 'test-host', port: 3306, username: 'test-user', password: 'test-pswd', database: 'test-db' } }
|
8
7
|
let(:database) { 'test-db' }
|
@@ -19,7 +18,8 @@ module PluginSupport
|
|
19
18
|
allow(Mysql2::Client).to receive(:new).and_return(conn)
|
20
19
|
end
|
21
20
|
|
22
|
-
describe '.
|
21
|
+
describe '.reload' do
|
22
|
+
subject { table_meta.reload }
|
23
23
|
let(:query_result) {[]}
|
24
24
|
before do
|
25
25
|
allow(conn).to receive(:query).and_return(query_result)
|
@@ -31,7 +31,7 @@ module PluginSupport
|
|
31
31
|
{'table_name' => 'a_table', 'character_set_name' => utf8 }
|
32
32
|
]}
|
33
33
|
it 'set nil for encoding' do
|
34
|
-
|
34
|
+
subject
|
35
35
|
expect(table_meta['a_table'][:encoding]).to eq Encoding::UTF_8
|
36
36
|
expect(table_meta['a_table'][:mysql_charset]).to eq utf8
|
37
37
|
end
|
@@ -43,7 +43,7 @@ module PluginSupport
|
|
43
43
|
{'table_name' => 'b_table', 'character_set_name' => cp932 }
|
44
44
|
]}
|
45
45
|
it 'set ruby encoding encoding' do
|
46
|
-
|
46
|
+
subject
|
47
47
|
expect(table_meta['a_table'][:encoding]).to eq Encoding::ISO_8859_1
|
48
48
|
expect(table_meta['a_table'][:mysql_charset]).to eq latin1
|
49
49
|
expect(table_meta['b_table'][:encoding]).to eq Encoding::CP932
|
@@ -57,10 +57,9 @@ module PluginSupport
|
|
57
57
|
{'table_name' => 'a_table', 'character_set_name' => 'xxxx' }
|
58
58
|
]}
|
59
59
|
it 'raise an error' do
|
60
|
-
expect{
|
60
|
+
expect{subject}.to raise_error('Unsupported charset:xxxx.')
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
66
|
-
end
|
@@ -17,6 +17,22 @@ describe GenerateSourceDump do
|
|
17
17
|
}
|
18
18
|
let(:dp) { double('dp') }
|
19
19
|
let(:options) { double('options') }
|
20
|
+
let(:cli) { double('cli') }
|
21
|
+
let(:table_meta) { double('table_meta') }
|
22
|
+
let(:io) { double('io') }
|
23
|
+
let(:source_pos) { double('source_pos') }
|
24
|
+
let(:snapshot) { double('snapshot') }
|
25
|
+
let(:tabledef) { double('tabledef') }
|
26
|
+
|
27
|
+
let(:de_prefs_with_table_meta) { de_prefs.merge(table_meta: table_meta) }
|
28
|
+
|
29
|
+
shared_examples 'source_pos_diff_query params' do
|
30
|
+
let(:table) { double('table') }
|
31
|
+
let(:schema) { double('schema') }
|
32
|
+
let(:num_rows) { double('num_rows') }
|
33
|
+
let(:pk_columns) { [ 'id' ] }
|
34
|
+
let(:last_pks) { nil }
|
35
|
+
end
|
20
36
|
|
21
37
|
describe '#dump' do
|
22
38
|
subject { subject_object.dump(tables, file_path, &src_pos_callback) }
|
@@ -26,87 +42,93 @@ describe GenerateSourceDump do
|
|
26
42
|
|
27
43
|
context 'when file_path is given' do
|
28
44
|
let(:file_path) { double('file_path') }
|
29
|
-
before do
|
30
|
-
allow(File).to receive(:open).with(file_path, "w").
|
31
|
-
and_return(io)
|
32
|
-
allow(PGClient).to receive(:new).with(de_prefs).and_return(cli)
|
33
|
-
allow(subject_object).to receive(:get_source_pos).
|
34
|
-
with(cli, &src_pos_callback).and_return(source_pos)
|
35
|
-
allow(source).to receive(:sync_generate_table_ddl).with(dp, nil).
|
36
|
-
and_return context
|
37
|
-
allow(subject_object).to receive(:dump_table).
|
38
|
-
with(tabledef1, source_pos, io, cli)
|
39
|
-
allow(subject_object).to receive(:dump_table).
|
40
|
-
with(tabledef2, source_pos, io, cli)
|
41
|
-
end
|
42
|
-
let(:io) { double('io') }
|
43
|
-
before do
|
44
|
-
allow(io).to receive(:close)
|
45
|
-
end
|
46
|
-
|
47
|
-
let(:cli) { double('cli') }
|
48
|
-
let(:source_pos) { double('source_pos') }
|
49
45
|
let(:context) { double('context') }
|
50
|
-
let(:tabledef1) { double('tabledef1') }
|
51
46
|
let(:tabledef2) { double('tabledef2') }
|
52
47
|
let(:missing_tables) { [] }
|
53
48
|
let(:error) { double('error') }
|
54
49
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
subject
|
73
|
-
end
|
74
|
-
|
75
|
-
context 'when a table is missing' do
|
76
|
-
let(:missing_tables) { [ "test2" ] }
|
77
|
-
before do
|
78
|
-
allow(context).to receive(:each_source_tabledef).
|
79
|
-
with(tables, de_prefs).
|
80
|
-
and_yield(tabledef1, nil).
|
50
|
+
context 'an ordinary case' do
|
51
|
+
it 'does expected things' do
|
52
|
+
expect(File).to receive(:open).with(file_path, "w").
|
53
|
+
and_return(io)
|
54
|
+
expect(FlydataCore::Postgresql::PGClient).to receive(:new).with(de_prefs).and_return(cli)
|
55
|
+
allow(cli).to receive(:close)
|
56
|
+
expect(Flydata::SourcePostgresql::TableMeta).to receive(:new).with(de_prefs, tables).and_return(table_meta)
|
57
|
+
expect(table_meta).to receive(:reload).with(cli).once
|
58
|
+
expect(table_meta).to receive(:current_snapshot).and_return(snapshot)
|
59
|
+
expect(subject_object).to receive(:get_source_pos).
|
60
|
+
with(snapshot, &src_pos_callback).and_return(source_pos)
|
61
|
+
expect(source).to receive(:sync_generate_table_ddl).with(dp, nil).
|
62
|
+
and_return context
|
63
|
+
expect(context).to receive(:each_source_tabledef).
|
64
|
+
with(tables, de_prefs_with_table_meta).
|
65
|
+
and_yield(tabledef, nil).and_yield(tabledef2, nil).
|
81
66
|
and_return missing_tables
|
82
|
-
end
|
83
|
-
it "does nothing for the missing table. we might need to add an error
|
84
|
-
handling" do
|
85
67
|
expect(subject_object).to receive(:dump_table).
|
86
|
-
with(
|
87
|
-
expect(subject_object).
|
68
|
+
with(tabledef, source_pos, io, cli)
|
69
|
+
expect(subject_object).to receive(:dump_table).
|
88
70
|
with(tabledef2, source_pos, io, cli)
|
71
|
+
expect(io).to receive(:close)
|
89
72
|
|
90
73
|
subject
|
91
74
|
end
|
92
75
|
end
|
93
76
|
|
94
|
-
context '
|
77
|
+
context 'various conditions' do
|
95
78
|
before do
|
96
|
-
allow(
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
79
|
+
allow(File).to receive(:open).with(file_path, "w").
|
80
|
+
and_return(io)
|
81
|
+
allow(FlydataCore::Postgresql::PGClient).to receive(:new).with(de_prefs).and_return(cli)
|
82
|
+
allow(cli).to receive(:close)
|
83
|
+
expect(Flydata::SourcePostgresql::TableMeta).to receive(:new).with(de_prefs, tables).and_return(table_meta)
|
84
|
+
expect(table_meta).to receive(:reload).with(cli).once
|
85
|
+
expect(table_meta).to receive(:current_snapshot).and_return(snapshot)
|
86
|
+
expect(subject_object).to receive(:get_source_pos).
|
87
|
+
with(snapshot, &src_pos_callback).and_return(source_pos)
|
88
|
+
allow(source).to receive(:sync_generate_table_ddl).with(dp, nil).
|
89
|
+
and_return context
|
90
|
+
allow(subject_object).to receive(:dump_table).
|
91
|
+
with(tabledef, source_pos, io, cli)
|
92
|
+
allow(subject_object).to receive(:dump_table).
|
107
93
|
with(tabledef2, source_pos, io, cli)
|
94
|
+
allow(io).to receive(:close)
|
95
|
+
end
|
96
|
+
context 'when a table is missing' do
|
97
|
+
let(:missing_tables) { [ "test2" ] }
|
98
|
+
before do
|
99
|
+
allow(context).to receive(:each_source_tabledef).
|
100
|
+
with(tables, de_prefs_with_table_meta).
|
101
|
+
and_yield(tabledef, nil).
|
102
|
+
and_return missing_tables
|
103
|
+
end
|
104
|
+
it "does nothing for the missing table. we might need to add an error
|
105
|
+
handling" do
|
106
|
+
expect(subject_object).to receive(:dump_table).
|
107
|
+
with(tabledef, source_pos, io, cli)
|
108
|
+
expect(subject_object).not_to receive(:dump_table).
|
109
|
+
with(tabledef2, source_pos, io, cli)
|
110
|
+
|
111
|
+
subject
|
112
|
+
end
|
113
|
+
end
|
108
114
|
|
109
|
-
|
115
|
+
context 'when a table has an error' do
|
116
|
+
before do
|
117
|
+
allow(context).to receive(:each_source_tabledef).
|
118
|
+
with(tables, de_prefs_with_table_meta).
|
119
|
+
and_yield(nil, error).
|
120
|
+
and_yield(tabledef2, nil).
|
121
|
+
and_return missing_tables
|
122
|
+
end
|
123
|
+
it "does nothing for the error table. we might need to add an error
|
124
|
+
handling" do
|
125
|
+
expect(subject_object).not_to receive(:dump_table).
|
126
|
+
with(tabledef, source_pos, io, cli)
|
127
|
+
expect(subject_object).to receive(:dump_table).
|
128
|
+
with(tabledef2, source_pos, io, cli)
|
129
|
+
|
130
|
+
subject
|
131
|
+
end
|
110
132
|
end
|
111
133
|
end
|
112
134
|
end
|
@@ -118,24 +140,90 @@ describe GenerateSourceDump do
|
|
118
140
|
end
|
119
141
|
end
|
120
142
|
|
121
|
-
describe '#
|
122
|
-
subject { subject_object.send(:
|
143
|
+
describe '#dump_table_chunk' do
|
144
|
+
let(:subject) { subject_object.send(:dump_table_chunk, table, schema, source_pos,
|
145
|
+
num_rows, tabledef, pk_columns,
|
146
|
+
last_pks, io, cli) }
|
123
147
|
|
124
|
-
|
125
|
-
let(:pk_columns) { [ 'id' ] }
|
148
|
+
include_examples 'source_pos_diff_query params'
|
126
149
|
|
127
|
-
|
128
|
-
|
150
|
+
let(:query) { double('query') }
|
151
|
+
let(:query_generator) { double('query_generator') }
|
129
152
|
|
130
|
-
|
131
|
-
|
153
|
+
before do
|
154
|
+
allow(tabledef).to receive(:columns).and_return(coldefs)
|
155
|
+
allow(source_pos).to receive(:snapshot).and_return(snapshot)
|
156
|
+
allow(query_generator).to receive(:build_query).and_return(query)
|
157
|
+
end
|
158
|
+
let(:coldefs) { [
|
159
|
+
{ column: 'id', type: 'int4' },
|
160
|
+
{ column: 'url', type: 'varchar' }
|
161
|
+
] }
|
162
|
+
let(:res) { [
|
163
|
+
{ "id" => 10, "url" => "http://foobar.com/", },
|
164
|
+
{ "id" => last_pk, "url" => "http://nowhere.org/", },
|
165
|
+
] }
|
166
|
+
let(:last_pk) { 11 }
|
167
|
+
|
168
|
+
let(:expected_columns) { [ 'id', 'url' ] }
|
169
|
+
let(:expected_types) { [ 'int4', 'varchar' ] }
|
170
|
+
let(:expected_query_params) { [] }
|
171
|
+
|
172
|
+
context 'in an ordinary case' do
|
173
|
+
it 'does what it has to do' do
|
174
|
+
expect(Flydata::SourcePostgresql::QueryBasedSync::DiffQueryGenerator).to receive(:new).with(
|
175
|
+
table, schema,
|
176
|
+
columns: coldefs,
|
177
|
+
to_sid: source_pos.snapshot,
|
178
|
+
pk_columns: pk_columns,
|
179
|
+
last_pks: last_pks,
|
180
|
+
limit: num_rows,
|
181
|
+
).and_return(query_generator)
|
182
|
+
expect(cli).to receive(:query).
|
183
|
+
with(query).
|
184
|
+
and_return(res)
|
185
|
+
res.each do |row|
|
186
|
+
expect(subject_object).to receive(:dump_row).
|
187
|
+
with(row, expected_columns, io)
|
188
|
+
end
|
132
189
|
|
133
|
-
|
190
|
+
expect(subject).to eq([res.size, [ last_pk ] ])
|
191
|
+
end
|
134
192
|
end
|
135
|
-
context 'with three primary keys' do
|
136
|
-
let(:pk_columns) { [ 'pk1', 'pk2', 'pk3' ] }
|
137
193
|
|
138
|
-
|
194
|
+
context 'various cases' do
|
195
|
+
before do
|
196
|
+
allow(Flydata::SourcePostgresql::QueryBasedSync::DiffQueryGenerator).to receive(:new).with(
|
197
|
+
table, schema,
|
198
|
+
columns: coldefs,
|
199
|
+
to_sid: source_pos.snapshot,
|
200
|
+
pk_columns: pk_columns,
|
201
|
+
last_pks: last_pks,
|
202
|
+
limit: num_rows,
|
203
|
+
).and_return(query_generator)
|
204
|
+
allow(cli).to receive(:query).
|
205
|
+
with(query).
|
206
|
+
and_return(res)
|
207
|
+
res.each do |row|
|
208
|
+
allow(subject_object).to receive(:dump_row).
|
209
|
+
with(row, expected_columns, io)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'when last_pks is given' do
|
214
|
+
let(:last_pks) { double('last_pks') }
|
215
|
+
let(:expected_query_params) { last_pks }
|
216
|
+
it 'calls `cli.query` with `last_pks`' do
|
217
|
+
is_expected.to eq([res.size, [ last_pk ]])
|
218
|
+
end
|
219
|
+
end
|
220
|
+
context 'when query returned no result' do
|
221
|
+
let(:res) { [] }
|
222
|
+
it 'does not call #dump_row and returns nil results' do
|
223
|
+
expect(subject_object).not_to receive(:dump_row)
|
224
|
+
is_expected.to eq([0, nil])
|
225
|
+
end
|
226
|
+
end
|
139
227
|
end
|
140
228
|
end
|
141
229
|
end
|