flydata 0.6.14 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/flydata-core/Gemfile +1 -0
  4. data/flydata-core/Gemfile.lock +5 -0
  5. data/flydata-core/lib/flydata-core/errors.rb +4 -2
  6. data/flydata-core/lib/flydata-core/mysql/binlog_pos.rb +4 -0
  7. data/flydata-core/lib/flydata-core/postgresql/compatibility_checker.rb +119 -0
  8. data/flydata-core/lib/flydata-core/postgresql/config.rb +58 -0
  9. data/flydata-core/lib/flydata-core/postgresql/pg_client.rb +170 -0
  10. data/flydata-core/lib/flydata-core/postgresql/snapshot.rb +49 -0
  11. data/flydata-core/lib/flydata-core/postgresql/source_pos.rb +71 -10
  12. data/flydata-core/lib/flydata-core/table_def/mysql_table_def.rb +1 -1
  13. data/flydata-core/lib/flydata-core/table_def/postgresql_table_def.rb +76 -17
  14. data/flydata-core/lib/flydata-core/table_def/redshift_table_def.rb +59 -10
  15. data/flydata-core/spec/mysql/binlog_pos_spec.rb +10 -2
  16. data/flydata-core/spec/postgresql/compatibility_checker_spec.rb +148 -0
  17. data/flydata-core/spec/postgresql/config_spec.rb +85 -0
  18. data/flydata-core/spec/postgresql/pg_client_spec.rb +195 -0
  19. data/flydata-core/spec/postgresql/snapshot_spec.rb +55 -0
  20. data/flydata-core/spec/postgresql/source_pos_spec.rb +70 -8
  21. data/flydata-core/spec/table_def/postgresql_table_def_spec.rb +80 -19
  22. data/flydata-core/spec/table_def/redshift_table_def_spec.rb +211 -14
  23. data/flydata.gemspec +0 -0
  24. data/lib/flydata.rb +1 -0
  25. data/lib/flydata/command/sender.rb +10 -7
  26. data/lib/flydata/command/sync.rb +4 -1
  27. data/lib/flydata/fluent-plugins/flydata_plugin_ext/base.rb +1 -0
  28. data/lib/flydata/fluent-plugins/flydata_plugin_ext/fluent_log_ext.rb +73 -0
  29. data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync.rb +35 -10
  30. data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_diff_based.rb +29 -0
  31. data/lib/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_query_based.rb +26 -0
  32. data/lib/flydata/fluent-plugins/flydata_plugin_ext/preference.rb +29 -13
  33. data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +10 -18
  34. data/lib/flydata/fluent-plugins/in_postgresql_query_based_flydata.rb +64 -0
  35. data/lib/flydata/helpers.rb +1 -3
  36. data/lib/flydata/plugin_support/context.rb +14 -2
  37. data/lib/flydata/plugin_support/source_position_file.rb +35 -0
  38. data/lib/flydata/plugin_support/sync_record_emittable.rb +2 -1
  39. data/lib/flydata/query_based_sync/client.rb +101 -0
  40. data/lib/flydata/query_based_sync/record_size_estimator.rb +39 -0
  41. data/lib/flydata/query_based_sync/resource_requester.rb +70 -0
  42. data/lib/flydata/query_based_sync/response.rb +122 -0
  43. data/lib/flydata/query_based_sync/response_handler.rb +30 -0
  44. data/lib/flydata/source/sync_generate_table_ddl.rb +1 -1
  45. data/lib/flydata/source_mysql/plugin_support/binlog_record_dispatcher.rb +2 -2
  46. data/lib/flydata/source_mysql/plugin_support/binlog_record_handler.rb +3 -9
  47. data/lib/flydata/source_mysql/plugin_support/context.rb +26 -2
  48. data/lib/flydata/source_mysql/plugin_support/source_position_file.rb +14 -0
  49. data/lib/flydata/source_mysql/table_ddl.rb +3 -3
  50. data/lib/flydata/source_mysql/{plugin_support/table_meta.rb → table_meta.rb} +3 -10
  51. data/lib/flydata/source_postgresql/generate_source_dump.rb +44 -63
  52. data/lib/flydata/source_postgresql/parse_dump_and_send.rb +2 -0
  53. data/lib/flydata/source_postgresql/plugin_support/context.rb +13 -0
  54. data/lib/flydata/source_postgresql/plugin_support/source_position_file.rb +14 -0
  55. data/lib/flydata/source_postgresql/query_based_sync/client.rb +16 -0
  56. data/lib/flydata/source_postgresql/query_based_sync/diff_query_generator.rb +135 -0
  57. data/lib/flydata/source_postgresql/query_based_sync/resource_requester.rb +86 -0
  58. data/lib/flydata/source_postgresql/query_based_sync/response.rb +12 -0
  59. data/lib/flydata/source_postgresql/query_based_sync/response_handler.rb +12 -0
  60. data/lib/flydata/source_postgresql/sync_generate_table_ddl.rb +25 -79
  61. data/lib/flydata/source_postgresql/table_meta.rb +168 -0
  62. data/lib/flydata/sync_file_manager.rb +5 -5
  63. data/lib/flydata/table_meta.rb +19 -0
  64. data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_context.rb +85 -0
  65. data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_diff_based_shared_examples.rb +36 -0
  66. data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_query_based_shared_examples.rb +37 -0
  67. data/spec/flydata/fluent-plugins/flydata_plugin_ext/flydata_sync_shared_examples.rb +67 -0
  68. data/spec/flydata/fluent-plugins/in_mysql_binlog_flydata_spec.rb +119 -96
  69. data/spec/flydata/fluent-plugins/in_postgresql_query_based_flydata_spec.rb +82 -0
  70. data/spec/flydata/fluent-plugins/sync_source_plugin_context.rb +29 -0
  71. data/spec/flydata/plugin_support/context_spec.rb +37 -3
  72. data/spec/flydata/query_based_sync/client_spec.rb +79 -0
  73. data/spec/flydata/query_based_sync/query_based_sync_context.rb +116 -0
  74. data/spec/flydata/query_based_sync/record_size_estimator_spec.rb +54 -0
  75. data/spec/flydata/query_based_sync/resource_requester_spec.rb +58 -0
  76. data/spec/flydata/query_based_sync/response_handler_spec.rb +36 -0
  77. data/spec/flydata/query_based_sync/response_spec.rb +157 -0
  78. data/spec/flydata/source_mysql/plugin_support/context_spec.rb +7 -1
  79. data/spec/flydata/source_mysql/plugin_support/dml_record_handler_spec.rb +2 -15
  80. data/spec/flydata/source_mysql/plugin_support/drop_database_query_handler_spec.rb +1 -1
  81. data/spec/flydata/source_mysql/plugin_support/shared_query_handler_context.rb +12 -11
  82. data/spec/flydata/source_mysql/plugin_support/source_position_file_spec.rb +53 -0
  83. data/spec/flydata/source_mysql/plugin_support/truncate_query_handler_spec.rb +1 -1
  84. data/spec/flydata/source_mysql/table_ddl_spec.rb +5 -5
  85. data/spec/flydata/source_mysql/{plugin_support/table_meta_spec.rb → table_meta_spec.rb} +6 -7
  86. data/spec/flydata/source_postgresql/generate_source_dump_spec.rb +165 -77
  87. data/spec/flydata/source_postgresql/query_based_sync/diff_query_generator_spec.rb +213 -0
  88. data/spec/flydata/source_postgresql/query_based_sync/query_based_sync_postgresql_context.rb +76 -0
  89. data/spec/flydata/source_postgresql/query_based_sync/resource_requester_spec.rb +70 -0
  90. data/spec/flydata/source_postgresql/table_meta_spec.rb +77 -0
  91. metadata +49 -6
  92. data/lib/flydata/source_mysql/plugin_support/binlog_position_file.rb +0 -23
  93. 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: 'test_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.current_binlog_file}\t#{record['next_position'] - record['event_length']}" }
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(:current_binlog_file) { "mysql-bin.000066" }
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
- r = double('context')
25
- allow(r).to receive(:sync_fm).and_return(sync_fm)
26
- allow(r).to receive(:database).and_return(target_database)
27
- allow(r).to receive(:tables).and_return([table])
28
- allow(r).to receive(:table_meta).and_return(table_meta)
29
- allow(r).to receive(:current_binlog_file).and_return(current_binlog_file)
30
- allow(r).to receive(:omit_events).and_return({})
31
- allow(r).to receive(:table_revs).and_return({ table => table_rev})
32
- allow(r).to receive(:tag).and_return(tag)
33
- r
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: "#{current_binlog_file}\t#{next_position - event_length}",
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(:default_charset_mysql).and_return(table1_charset)
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(:default_charset_mysql).and_return(table2_charset)
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(:current_binlog_file).and_return(original_current_binlog_file)
86
- expect(context).to receive(:current_binlog_file=).with(binlog_file)
87
- expect(context).to receive(:current_binlog_file=).with(original_current_binlog_file)
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/plugin_support/table_meta'
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 '.update' do
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
- table_meta.update
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
- table_meta.update
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{table_meta.update}.to raise_error('Unsupported charset:xxxx.')
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
- it 'does expected things' do
56
- expect(File).to receive(:open).with(file_path, "w").
57
- and_return(io)
58
- expect(PGClient).to receive(:new).with(de_prefs).and_return(cli)
59
- expect(subject_object).to receive(:get_source_pos).
60
- with(cli, &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).
65
- and_yield(tabledef1, nil).and_yield(tabledef2, nil).
66
- and_return missing_tables
67
- expect(subject_object).to receive(:dump_table).
68
- with(tabledef1, source_pos, io, cli)
69
- expect(subject_object).to receive(:dump_table).
70
- with(tabledef2, source_pos, io, cli)
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(tabledef1, source_pos, io, cli)
87
- expect(subject_object).not_to receive(:dump_table).
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 'when a table has an error' do
77
+ context 'various conditions' do
95
78
  before do
96
- allow(context).to receive(:each_source_tabledef).
97
- with(tables, de_prefs).
98
- and_yield(nil, error).
99
- and_yield(tabledef2, nil).
100
- and_return missing_tables
101
- end
102
- it "does nothing for the error table. we might need to add an error
103
- handling" do
104
- expect(subject_object).not_to receive(:dump_table).
105
- with(tabledef1, source_pos, io, cli)
106
- expect(subject_object).to receive(:dump_table).
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
- subject
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 '#pk_conditions' do
122
- subject { subject_object.send(:pk_conditions, pk_columns) }
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
- context 'with a single primary key' do
125
- let(:pk_columns) { [ 'id' ] }
148
+ include_examples 'source_pos_diff_query params'
126
149
 
127
- it { is_expected.to eq %Q| AND ("id" > $1)| }
128
- end
150
+ let(:query) { double('query') }
151
+ let(:query_generator) { double('query_generator') }
129
152
 
130
- context 'with two primary keys' do
131
- let(:pk_columns) { [ 'pk1', 'pk2' ] }
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
- it { is_expected.to eq %Q| AND ("pk1" > $1 OR ("pk1" = $1 AND ("pk2" > $2)))| }
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
- it { is_expected.to eq %Q| AND ("pk1" > $1 OR ("pk1" = $1 AND ("pk2" > $2 OR ("pk2" = $2 AND ("pk3" > $3)))))| }
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