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,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 }