flydata 0.6.14 → 0.7.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.
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