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,85 @@
1
+ require 'spec_helper'
2
+ require 'flydata-core/postgresql/config'
3
+
4
+ module FlydataCore
5
+ module Postgresql
6
+ describe Config do
7
+ let(:base_conf) do
8
+ {
9
+ 'host' => 'localhost',
10
+ 'port' => 1234,
11
+ 'username' => 'testuser',
12
+ 'password' => 'password',
13
+ 'database' => 'testdb',
14
+ }
15
+ end
16
+
17
+ let(:conf) { base_conf }
18
+
19
+ describe '.build_db_opts' do
20
+ subject { described_class.build_db_opts(conf) }
21
+
22
+ context 'with basic conf' do
23
+ it { is_expected.to eq(
24
+ host: 'localhost',
25
+ port: 1234,
26
+ username: 'testuser',
27
+ password: 'password',
28
+ database: 'testdb',
29
+ user: 'testuser',
30
+ dbname: 'testdb',
31
+ ) }
32
+ end
33
+
34
+ context 'with conf having dbname' do
35
+ let(:conf) { base_conf.merge('dbname' => 'testdb2') }
36
+ it { is_expected.to eq(
37
+ host: 'localhost',
38
+ port: 1234,
39
+ username: 'testuser',
40
+ password: 'password',
41
+ database: 'testdb',
42
+ user: 'testuser',
43
+ dbname: 'testdb2',
44
+ ) }
45
+ end
46
+ end
47
+
48
+ describe '.opts_for_pg' do
49
+ subject { described_class.opts_for_pg(conf) }
50
+
51
+ context 'with basic conf' do
52
+ it { is_expected.to eq(
53
+ host: 'localhost',
54
+ port: 1234,
55
+ password: 'password',
56
+ user: 'testuser',
57
+ dbname: 'testdb',
58
+ ) }
59
+ end
60
+
61
+ context 'with conf having dbname' do
62
+ let(:conf) { base_conf.merge('dbname' => 'testdb2') }
63
+ it { is_expected.to eq(
64
+ host: 'localhost',
65
+ port: 1234,
66
+ password: 'password',
67
+ user: 'testuser',
68
+ dbname: 'testdb2',
69
+ ) }
70
+ end
71
+
72
+ context 'with conf having schema' do
73
+ let(:conf) { base_conf.merge('schema' => 'dev_schema') }
74
+ it { is_expected.to eq(
75
+ host: 'localhost',
76
+ port: 1234,
77
+ password: 'password',
78
+ user: 'testuser',
79
+ dbname: 'testdb',
80
+ ) }
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,195 @@
1
+ require 'spec_helper'
2
+ require 'flydata-core/postgresql/pg_client'
3
+
4
+ module FlydataCore
5
+ module Postgresql
6
+ describe PGClient do
7
+ let(:dbconf) do
8
+ { host: 'localhost', port: 5555, username: 'testuser',
9
+ password: 'testpassword', dbname: 'testdb' }
10
+ end
11
+ let(:subject_object) { described_class.new(dbconf) }
12
+
13
+
14
+
15
+ let(:pg_conn) { double('pg_conn') }
16
+ let(:socket) { double('socket') }
17
+
18
+ before do
19
+ allow(PG::Connection).to receive(:connect_start).and_return(pg_conn)
20
+ allow(pg_conn).to receive(:connect_poll).and_return(PG::PGRES_POLLING_OK)
21
+ allow(pg_conn).to receive(:status).and_return(PG::CONNECTION_OK)
22
+ allow(pg_conn).to receive(:socket_io).and_return(socket)
23
+ allow(IO).to receive(:select).with(nil, [socket], nil, PGClient::PG_CONNECT_TIMEOUT).and_return(true)
24
+ end
25
+
26
+ describe '#establish_connection' do
27
+ subject { subject_object.establish_connection }
28
+
29
+ context 'without errors' do
30
+ it { is_expected.to eq(pg_conn) }
31
+ it 'sets @conn' do
32
+ subject
33
+ expect(subject_object.instance_variable_get(:@conn)).to eq(pg_conn)
34
+ end
35
+ it 'reuses @conn until closes' do
36
+ subject_object.establish_connection
37
+ subject
38
+ expect(subject_object.instance_variable_get(:@conn)).to eq(pg_conn)
39
+ end
40
+ end
41
+
42
+ context 'when getting an async writing timeout error' do
43
+ before do
44
+ expect(IO).to receive(:select).with(nil, [socket], nil, PGClient::PG_CONNECT_TIMEOUT).and_return(nil)
45
+ end
46
+ it { expect{subject}.to raise_error(PG::Error, /Asynchronous.+\(WRITING\)/) }
47
+ end
48
+
49
+ context 'when getting an async reading timeout error' do
50
+ before do
51
+ expect(pg_conn).to receive(:connect_poll).and_return(PG::PGRES_POLLING_READING)
52
+ expect(IO).to receive(:select).with(nil, [socket], nil, PGClient::PG_CONNECT_TIMEOUT).and_return(true)
53
+ expect(IO).to receive(:select).with([socket], nil, nil, PGClient::PG_CONNECT_TIMEOUT).and_return(false)
54
+ end
55
+ it { expect{subject}.to raise_error(PG::Error, /Asynchronous.+\(READING\)/) }
56
+ end
57
+
58
+ context 'when getting socket error' do
59
+ before do
60
+ expect(IPSocket).to receive(:getaddress).with('localhost').and_raise(SocketError, 'getaddrinfo: nodename nor servname provided, or not known')
61
+ end
62
+ it { expect{subject}.to raise_error(PG::Error, /Connection failed: FATAL: unknown host\(localhost\)\./) }
63
+ end
64
+ end
65
+
66
+ describe '#query' do
67
+ subject { subject_object.query(query) }
68
+
69
+ context 'when query is a string' do
70
+ let(:query) { 'test query;' }
71
+ let(:result) { double('result') }
72
+
73
+ it do
74
+ expect(pg_conn).to receive(:query).with(query, []).
75
+ and_return(result)
76
+ expect(subject).to eq(result)
77
+ end
78
+ end
79
+ context 'when query has overriders' do
80
+ let(:query) { PGQuery.new(query_text, value_overriders:{"value" => -> (v) { "#{v}!" } }) }
81
+ let(:query_text) { "test_query" }
82
+ let(:result) { [{"id" => 1, "value" => "hi"},
83
+ {"id" => 2, "value" => "ho"}] }
84
+ let(:expected_values) { ["hi!", "ho!"] }
85
+ it do
86
+ expect(pg_conn).to receive(:query).with(query_text, []).
87
+ and_return(result)
88
+ subject.each_with_index do |row, i|
89
+ expect(row["value"]).to eq expected_values[i]
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'when query has binding_params' do
95
+ let(:binding_params) { ['1000', '2000'] }
96
+ let(:query) { PGQuery.new(query_text, binding_params: binding_params) }
97
+ let(:query_text) { "test_query" }
98
+ let(:result) { double('result') }
99
+ it do
100
+ expect(pg_conn).to receive(:query).with(query_text, binding_params).
101
+ and_return(result)
102
+ expect(subject).to eq(result)
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#close' do
108
+ subject { subject_object.close }
109
+
110
+ context 'when conn exists' do
111
+ before { subject_object.establish_connection }
112
+ it do
113
+ expect(pg_conn).to receive(:finish).once
114
+ subject
115
+ expect(subject_object.instance_variable_get(:@conn)).to be_nil
116
+ end
117
+ end
118
+
119
+ context 'when conn does not exist' do
120
+ it do
121
+ expect(pg_conn).to receive(:finish).never
122
+ expect{subject}.not_to raise_error
123
+ expect(subject_object.instance_variable_get(:@conn)).to be_nil
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ describe PGClient::HashValueOverrider do
130
+ let(:subject_object) { described_class.new(delegate, overriders) }
131
+
132
+ let(:delegate) { { "salute" => saluteval, "id" => idval } }
133
+ let(:idval) { 1 }
134
+ let(:saluteval) { "hello" }
135
+ let(:overriders) { {"salute" => ->(v) { "#{v}!" } } }
136
+
137
+ describe '#[]' do
138
+ subject { subject_object[key] }
139
+
140
+ context 'when no overrider for the key' do
141
+ let(:key) { "id" }
142
+ it { is_expected.to eq idval }
143
+ end
144
+
145
+ context 'when key has an overrider' do
146
+ let(:key) { "salute" }
147
+ it { is_expected.to eq "#{saluteval}!" }
148
+ end
149
+ end
150
+
151
+ describe '#values' do
152
+ subject { subject_object.values }
153
+
154
+ it { is_expected.to eq [ "#{saluteval}!", idval ] }
155
+ end
156
+
157
+ describe '#first' do
158
+ subject { subject_object.first }
159
+
160
+ it { is_expected.to eq [ "salute", "#{saluteval}!" ] }
161
+ end
162
+
163
+ describe '#kind_of?' do
164
+ subject { subject_object.kind_of?(klass) }
165
+
166
+ context 'when klass is of delegate' do
167
+ let(:klass) { delegate.class }
168
+
169
+ it { is_expected.to be_truthy }
170
+ end
171
+
172
+ context 'when klass is of self' do
173
+ let(:klass) { described_class }
174
+
175
+ it { is_expected.to be_falsey }
176
+ end
177
+ end
178
+
179
+ describe '#has_key?' do
180
+ subject { subject_object.has_key?(key) }
181
+
182
+ context 'when delegate has the key' do
183
+ let(:key) { "id" }
184
+
185
+ it { is_expected.to be_truthy }
186
+ end
187
+ context 'when delegate does not have the key' do
188
+ let(:key) { "random_stuff" }
189
+
190
+ it { is_expected.to be_falsey }
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+ require 'flydata-core/postgresql/snapshot'
3
+
4
+ module FlydataCore
5
+ module Postgresql
6
+ describe Snapshot do
7
+ let(:snapshot_text) { '10:18:11,12,15' }
8
+ let(:subject_object) { described_class.new(snapshot_text) }
9
+
10
+ describe '#initialize' do
11
+ subject { subject_object }
12
+
13
+ context 'when xip_list is empty' do
14
+ let(:snapshot_text) { '10:18:' }
15
+ it { expect(subject.xmin).to eq(10) }
16
+ it { expect(subject.xmax).to eq(18) }
17
+ it { expect(subject.xip_list).to eq([]) }
18
+ it { expect(subject.to_s).to eq('10:18:') }
19
+ end
20
+
21
+ context 'when xip_list is not empty' do
22
+ let(:snapshot_text) { '10:18:11,12,15' }
23
+ it { expect(subject.xmin).to eq(10) }
24
+ it { expect(subject.xmax).to eq(18) }
25
+ it { expect(subject.xip_list).to eq([11, 12, 15]) }
26
+ it { expect(subject.to_s).to eq('10:18:11,12,15') }
27
+ end
28
+ end
29
+
30
+ describe '#<=>' do
31
+ def new_ss(txt)
32
+ Snapshot.new(txt)
33
+ end
34
+
35
+ it { expect(new_ss("10:10:") == new_ss("10:10:")).to be(true) }
36
+ it { expect(new_ss("10:10:") == new_ss("11:11:")).to be(false) }
37
+ it { expect(new_ss("10:10:") == new_ss("9:9:")).to be(false) }
38
+ it { expect(new_ss("10:10:") == new_ss("10:11:")).to be(false) }
39
+ it { expect(new_ss("10:18:") == new_ss("10:18:10,11,12")).to be(false) }
40
+ it { expect(new_ss("10:18:10,11,12") == new_ss("10:18:10,11,12")).to be(true) }
41
+ it { expect(new_ss("10:18:10,11,12") == new_ss("10:18:11,12")).to be(false) }
42
+
43
+ it { expect(new_ss("10:10:") > new_ss("10:10:")).to be(false) }
44
+ it { expect(new_ss("10:10:") > new_ss("11:11:")).to be(false) }
45
+ it { expect(new_ss("11:11:") > new_ss("10:10:")).to be(true) }
46
+ it { expect(new_ss("10:10:") > new_ss("9:9:")).to be(true) }
47
+ it { expect(new_ss("10:10:") > new_ss("10:11:")).to be(false) }
48
+ it { expect(new_ss("10:11:") > new_ss("10:10:")).to be(true) }
49
+ it { expect(new_ss("10:18:") > new_ss("10:18:10,11,12")).to be(true) }
50
+ it { expect(new_ss("10:18:10,11,12") > new_ss("10:18:10,11,12")).to be(false) }
51
+ it { expect(new_ss("10:18:10,11,12") > new_ss("10:18:11,12")).to be(false) }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -4,21 +4,42 @@ module FlydataCore
4
4
  module Postgresql
5
5
 
6
6
  describe SourcePos do
7
- let(:subject_object) { described_class.new(snapshot_id, pk_values) }
7
+ let(:subject_object) { described_class.new(snapshot, to_snapshot, pk_values) }
8
8
 
9
- let(:snapshot_id) { "1234:1234:" }
9
+ let(:snapshot) { "1234:1234:" }
10
+ let(:to_snapshot) { nil }
10
11
  let(:pk_values) { nil }
11
12
 
13
+ describe '#initialize' do
14
+ context 'with snapshot and pk_values' do
15
+ it { expect(subject_object.snapshot).to eq(Snapshot.new(snapshot)) }
16
+ it { expect(subject_object.pk_values).to eq(pk_values) }
17
+ end
18
+
19
+ context 'with source pos object' do
20
+ let(:snapshot) { '1111:1111:' }
21
+ let(:to_snapshot) { '2222:2222:' }
22
+ let(:pk_values) { [{'id' => '1000'}] }
23
+ let(:obj) { described_class.new(snapshot, to_snapshot, pk_values) }
24
+ subject { described_class.new(obj) }
25
+
26
+ it { expect(subject.snapshot).to eq(Snapshot.new(snapshot)) }
27
+ it { expect(subject.to_snapshot).to eq(Snapshot.new(to_snapshot)) }
28
+ it { expect(subject.pk_values).to eq(pk_values) }
29
+ end
30
+ end
31
+
12
32
  describe '#to_s' do
13
33
  subject { subject_object.to_s }
14
34
 
15
35
  context 'when pk_values is nil' do
16
36
  let(:pk_values) { nil }
17
- it { is_expected.to eq %Q|#{snapshot_id}\t| }
37
+ it { is_expected.to eq %Q|#{snapshot}\t\t| }
18
38
  end
19
39
  context 'when pk_values is not nil' do
40
+ let(:to_snapshot) { '1234:1234:' }
20
41
  let(:pk_values) { { "user_id" => 1, "address_id" => 3 } }
21
- it { is_expected.to eq %Q|#{snapshot_id}\t{"user_id":1,"address_id":3}| }
42
+ it { is_expected.to eq %Q|#{snapshot}\t#{to_snapshot}\t{"user_id":1,"address_id":3}| }
22
43
  end
23
44
  end
24
45
 
@@ -26,17 +47,58 @@ describe SourcePos do
26
47
  subject { described_class.load(str) }
27
48
 
28
49
  context 'without pk_values' do
29
- let(:str) { %Q|#{snapshot_id}\t| }
30
- it { is_expected.to eq described_class.new(snapshot_id) }
50
+ let(:str) { %Q|#{snapshot}\t\t| }
51
+ it { is_expected.to eq described_class.new(snapshot) }
31
52
  end
32
53
  context 'with pk_values' do
33
- let(:str) { %Q|#{snapshot_id}\t{"user_id":1,"address_id":3}| }
54
+ let(:str) { %Q|#{snapshot}\t\t{"user_id":1,"address_id":3}| }
34
55
  it do
35
- is_expected.to eq described_class.new(snapshot_id,
56
+ is_expected.to eq described_class.new(snapshot, nil,
36
57
  "user_id" => 1, "address_id" => 3)
37
58
  end
38
59
  end
39
60
  end
61
+
62
+ describe '#<=>' do
63
+ def src_pos(snapshot, pk_values = nil)
64
+ described_class.new(snapshot, '2222:2222:', pk_values)
65
+ end
66
+ context 'when both have no pk_values' do
67
+ it { expect(src_pos('1111:1111:') == src_pos('1111:1111:')).to be(true) }
68
+ it { expect(src_pos('1111:1111:') == src_pos('1112:1112:')).to be(false) }
69
+ it { expect(src_pos('1111:1111:') < src_pos('1112:1112:')).to be(true) }
70
+ it { expect(src_pos('1111:1111:') < src_pos('1110:1110:')).to be(false) }
71
+ it { expect(src_pos('1111:1111:') > src_pos('1110:1110:')).to be(true) }
72
+ it { expect(src_pos('1111:1111:') > src_pos('1112:1112:')).to be(false) }
73
+ it { expect(src_pos('1111:1111:') > src_pos('999:999')).to be(true) }
74
+ end
75
+
76
+ context 'when one of them has pk_values' do
77
+ let(:pkv) { [{'id'=>'10'}] }
78
+ it { expect(src_pos('1111:1111:',pkv) == src_pos('1111:1111:')).to be(false) }
79
+ it { expect(src_pos('1111:1111:',pkv) == src_pos('1112:1112:')).to be(false) }
80
+ it { expect(src_pos('1111:1111:',pkv) < src_pos('1111:1111:')).to be(true) }
81
+ it { expect(src_pos('1111:1111:',pkv) < src_pos('1112:1112:')).to be(true) }
82
+ it { expect(src_pos('1111:1111:',pkv) < src_pos('1110:1110:')).to be(false) }
83
+ it { expect(src_pos('1111:1111:',pkv) > src_pos('1110:1110:')).to be(true) }
84
+ it { expect(src_pos('1111:1111:',pkv) > src_pos('1112:1112:')).to be(false) }
85
+ end
86
+
87
+ context 'when both have pk_values' do
88
+ let(:pkv1) { [{'id'=>'10'}] }
89
+ let(:pkv2) { [{'name'=>'akira'}] }
90
+ it { expect(src_pos('1111:1111:',pkv1) == src_pos('1111:1111:',pkv1)).to be(true) }
91
+ it { expect(src_pos('1111:1111:',pkv1) == src_pos('1111:1111:',pkv2)).to be(false) }
92
+ it { expect(src_pos('1111:1111:',pkv1) == src_pos('1112:1112:',pkv2)).to be(false) }
93
+ it { expect(src_pos('1111:1111:',pkv1) < src_pos('1111:1111:',pkv2)).to be(true) }
94
+ it { expect(src_pos('1111:1111:',pkv1) < src_pos('1111:1111:',pkv1)).to be(false) }
95
+ it { expect(src_pos('1111:1111:',pkv1) < src_pos('1112:1112:',pkv2)).to be(true) }
96
+ it { expect(src_pos('1111:1111:',pkv1) < src_pos('1110:1110:',pkv2)).to be(false) }
97
+ it { expect(src_pos('1111:1111:',pkv1) > src_pos('1110:1110:',pkv2)).to be(true) }
98
+ it { expect(src_pos('1111:1111:',pkv1) > src_pos('1112:1112:',pkv2)).to be(false) }
99
+ it { expect(src_pos('1111:1111:',pkv1) > src_pos('1111:1111:',pkv2)).to be(false) }
100
+ end
101
+ end
40
102
  end
41
103
 
42
104
  end
@@ -27,7 +27,7 @@ describe PostgresqlTableDef do
27
27
  information_schema_columns["data_type"] = data_type
28
28
  end
29
29
 
30
- let(:type_hash) { double('type_hash') }
30
+ let(:type_hash) { PostgresqlTableDef::TYPE_MAP_P2F[data_type] }
31
31
  let(:numeric_precision) { nil }
32
32
  let(:numeric_scale) { nil }
33
33
  let(:width_values) do
@@ -49,40 +49,101 @@ describe PostgresqlTableDef do
49
49
  information_schema_columns["numeric_scale"] = numeric_scale
50
50
  end
51
51
 
52
- let(:type_hash) { PostgresqlTableDef::TYPE_MAP_P2F[data_type] }
53
-
54
52
  context "with precision nil" do
55
53
  let(:numeric_precision) { nil }
56
54
  context "with scale nil" do
57
55
  let(:numeric_scale) { nil }
58
56
  it { is_expected.to eq "numeric" }
59
57
  end
60
- context "with scale 2" do
61
- let(:numeric_scale) { 2 }
62
- it do
63
- expect(described_class).to receive(:get_width_values).
64
- with(information_schema_columns, type_hash).and_raise("test")
65
-
66
- expect{subject}.to raise_error("test")
67
- end
68
- end
69
58
  end
70
- context "with precision 23" do
71
- let(:numeric_precision) { 23 }
72
- context "with scale nil" do
73
- let(:numeric_scale) { nil }
74
- it { is_expected.to eq "numeric(23)" }
59
+ context "with precision 1000" do
60
+ let(:numeric_precision) { 1000 }
61
+ context "with scale 0 (default value given by PostgreSQL)" do
62
+ let(:numeric_scale) { 0 }
63
+ it { is_expected.to eq "numeric(1000,0)" }
75
64
  end
76
65
  context "with scale 2" do
77
66
  let(:numeric_scale) { 2 }
78
- it { is_expected.to eq "numeric(23,2)" }
67
+ it { is_expected.to eq "numeric(1000,2)" }
79
68
  end
80
69
  end
81
70
  end
82
71
  # TODO Add data type specific specs here
72
+ context "with data type money" do
73
+ let(:data_type) { "money" }
74
+
75
+ before do
76
+ # Note that PostgreSQL's information_schema table does not have these
77
+ # values for the money type. The user of this method has to populate
78
+ # these values manually.
79
+ information_schema_columns["numeric_precision"] = numeric_precision
80
+ information_schema_columns["numeric_scale"] = numeric_scale
81
+ end
82
+
83
+ let(:numeric_precision) { 19 }
84
+
85
+ context 'when the money scale is 0' do
86
+ let(:numeric_scale) { 0 }
87
+
88
+ it { is_expected.to eq "money(19,0)" }
89
+ end
90
+ context 'when the moeny scale is 2' do
91
+ let(:numeric_scale) { 2 }
92
+
93
+ it { is_expected.to eq "money(19,2)" }
94
+ end
95
+ end
96
+ context "with time data type" do
97
+ let(:data_type) { "time" }
98
+
99
+ it { is_expected.to eq "time" }
100
+ end
101
+ context "with time without time zone data type" do
102
+ let(:data_type) { "time without time zone" }
103
+
104
+ it { is_expected.to eq "time" }
105
+ end
106
+ context "with time with time zone data type" do
107
+ let(:data_type) { "time with time zone" }
108
+
109
+ it { is_expected.to eq "timetz" }
110
+ end
111
+ context "with timestamp data type" do
112
+ let(:data_type) { "timestamp" }
113
+
114
+ it { is_expected.to eq "datetime" }
115
+ end
116
+ context "with timestamp without time zone data type" do
117
+ let(:data_type) { "timestamp without time zone" }
118
+
119
+ it { is_expected.to eq "datetime" }
120
+ end
121
+ context "with timestamp with time zone data type" do
122
+ let(:data_type) { "timestamp with time zone" }
123
+
124
+ it { is_expected.to eq "datetimetz" }
125
+ end
126
+ context "with bit data type" do
127
+ let(:data_type) { "bit" }
128
+
129
+ it { is_expected.to eq "bit" }
130
+ end
131
+ context "with bit varying data type" do
132
+ let(:data_type) { "bit varying" }
133
+
134
+ it { is_expected.to eq "varbit" }
135
+ end
136
+ context "with varbit data type" do
137
+ let(:data_type) { "varbit" }
138
+
139
+ it { is_expected.to eq "varbit" }
140
+ end
83
141
  context "with unknown data type" do
84
142
  let(:data_type) { "duck" }
85
- it { expect{subject}.to raise_error /Unknown PostgreSQL type/ }
143
+
144
+ let(:type_hash) { PostgresqlTableDef::TYPE_MAP_P2F[:default] }
145
+
146
+ it { is_expected.to eq "_unsupported" }
86
147
  end
87
148
  context "with no data type" do
88
149
  let(:data_type) { nil }