td 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -0
- data/ChangeLog +15 -0
- data/lib/td/command/common.rb +96 -26
- data/lib/td/command/connector.rb +58 -36
- data/lib/td/command/import.rb +162 -1
- data/lib/td/command/job.rb +33 -23
- data/lib/td/command/list.rb +3 -2
- data/lib/td/command/query.rb +3 -5
- data/lib/td/command/runner.rb +1 -2
- data/lib/td/command/table.rb +29 -18
- data/lib/td/compact_format_yamler.rb +41 -0
- data/lib/td/connector_config_normalizer.rb +32 -0
- data/lib/td/version.rb +1 -1
- data/spec/spec_helper.rb +18 -0
- data/spec/td/command/connector_spec.rb +153 -0
- data/spec/td/command/import_spec.rb +179 -0
- data/spec/td/command/job_spec.rb +1 -1
- data/spec/td/command/table_spec.rb +113 -0
- data/spec/td/common_spec.rb +23 -1
- data/spec/td/compact_format_yamler_spec.rb +38 -0
- data/spec/td/connector_config_normalizer_spec.rb +62 -0
- data/td.gemspec +2 -1
- metadata +25 -5
@@ -178,5 +178,184 @@ module TreasureData::Command
|
|
178
178
|
expect(stderr_io.string).to include import_name
|
179
179
|
end
|
180
180
|
end
|
181
|
+
|
182
|
+
describe '#import_config' do
|
183
|
+
include_context 'quiet_out'
|
184
|
+
|
185
|
+
let :command do
|
186
|
+
Class.new { include TreasureData::Command }.new
|
187
|
+
end
|
188
|
+
let :option do
|
189
|
+
List::CommandParser.new("import:config", args, [], nil, arguments, true)
|
190
|
+
end
|
191
|
+
let :out_file do
|
192
|
+
Tempfile.new("seed.yml").tap {|s| s.close }
|
193
|
+
end
|
194
|
+
let(:endpoint) { 'http://example.com' }
|
195
|
+
let(:apikey) { '1234ABCDEFGHIJKLMN' }
|
196
|
+
|
197
|
+
before do
|
198
|
+
TreasureData::Config.stub(:endpoint).and_return(endpoint)
|
199
|
+
TreasureData::Config.stub(:apikey).and_return(apikey)
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'unknown format' do
|
203
|
+
let(:args) { ['url'] }
|
204
|
+
let(:arguments) { ['localhost', '--format', 'msgpack', '-o', out_file.path] }
|
205
|
+
|
206
|
+
it 'exit command' do
|
207
|
+
expect {
|
208
|
+
command.import_config(option)
|
209
|
+
}.to raise_error TreasureData::Command::ParameterConfigurationError
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'support format' do
|
214
|
+
let(:td_output_config) {
|
215
|
+
{
|
216
|
+
'type' => 'td',
|
217
|
+
'endpoint' => TreasureData::Config.endpoint,
|
218
|
+
'apikey' => TreasureData::Config.apikey,
|
219
|
+
'database' => '',
|
220
|
+
'table' => '',
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
before do
|
225
|
+
command.import_config(option)
|
226
|
+
end
|
227
|
+
|
228
|
+
subject(:generated_config) { YAML.load_file(out_file.path) }
|
229
|
+
|
230
|
+
%w(csv tsv).each do |format|
|
231
|
+
context "--format #{format}" do
|
232
|
+
let(:args) { ['url'] }
|
233
|
+
context 'use path' do
|
234
|
+
let(:path_prefix) { 'path/to/prefix_' }
|
235
|
+
let(:arguments) { ["#{path_prefix}*.#{format}", '--format', format, '-o', out_file.path] }
|
236
|
+
|
237
|
+
it 'generate configuration file' do
|
238
|
+
expect(generated_config).to eq({
|
239
|
+
'in' => {
|
240
|
+
'type' => 'file',
|
241
|
+
'decorders' => [{'type' => 'gzip'}],
|
242
|
+
'path_prefix' => path_prefix,
|
243
|
+
},
|
244
|
+
'out' => td_output_config
|
245
|
+
})
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context 'use s3 scheme' do
|
250
|
+
let(:s3_access_key) { 'ABCDEFGHIJKLMN' }
|
251
|
+
let(:s3_secret_key) { '1234ABCDEFGHIJKLMN' }
|
252
|
+
let(:buckt_name) { 'my_bucket' }
|
253
|
+
let(:path_prefix) { 'path/to/prefix_' }
|
254
|
+
let(:s3_url) { "s3://#{s3_access_key}:#{s3_secret_key}@/#{buckt_name}/#{path_prefix}*.#{format}" }
|
255
|
+
let(:arguments) { [s3_url, '--format', format, '-o', out_file.path] }
|
256
|
+
|
257
|
+
it 'generate configuration file' do
|
258
|
+
expect(generated_config).to eq({
|
259
|
+
'in' => {
|
260
|
+
'type' => 's3',
|
261
|
+
'access_key_id' => s3_access_key,
|
262
|
+
'secret_access_key' => s3_secret_key,
|
263
|
+
'bucket' => buckt_name,
|
264
|
+
'path_prefix' => path_prefix,
|
265
|
+
},
|
266
|
+
'out' => {
|
267
|
+
'mode' => 'append'
|
268
|
+
}
|
269
|
+
})
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context 'format is mysql' do
|
276
|
+
let(:format) { 'mysql' }
|
277
|
+
let(:host) { 'localhost' }
|
278
|
+
let(:database) { 'database' }
|
279
|
+
let(:user) { 'my_user' }
|
280
|
+
let(:password) { 'my_password' }
|
281
|
+
let(:table) { 'my_table' }
|
282
|
+
|
283
|
+
let(:expected_config) {
|
284
|
+
{
|
285
|
+
'in' => {
|
286
|
+
'type' => 'mysql',
|
287
|
+
'host' => host,
|
288
|
+
'port' => port,
|
289
|
+
'database' => database,
|
290
|
+
'user' => user,
|
291
|
+
'password' => password,
|
292
|
+
'table' => table,
|
293
|
+
'select' => "*",
|
294
|
+
},
|
295
|
+
'out' => td_output_config
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
context 'like import:prepare arguments' do
|
300
|
+
let(:args) { ['url'] }
|
301
|
+
let(:arguments) { [table, '--db-url', mysql_url, '--db-user', user, '--db-password', password, '--format', 'mysql', '-o', out_file.path] }
|
302
|
+
|
303
|
+
context 'scheme is jdbc' do
|
304
|
+
let(:port) { 3333 }
|
305
|
+
let(:mysql_url) { "jdbc:mysql://#{host}:#{port}/#{database}" }
|
306
|
+
|
307
|
+
it 'generate configuration file' do
|
308
|
+
expect(generated_config).to eq expected_config
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
context 'scheme is mysql' do
|
313
|
+
context 'with port' do
|
314
|
+
let(:port) { 3333 }
|
315
|
+
let(:mysql_url) { "mysql://#{host}:#{port}/#{database}" }
|
316
|
+
|
317
|
+
it 'generate configuration file' do
|
318
|
+
expect(generated_config).to eq expected_config
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context 'without port' do
|
323
|
+
let(:mysql_url) { "mysql://#{host}/#{database}" }
|
324
|
+
let(:port) { 3306 }
|
325
|
+
|
326
|
+
it 'generate configuration file' do
|
327
|
+
expect(generated_config).to eq expected_config
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'like import:upload arguments' do
|
334
|
+
let(:args) { ['session', 'url'] }
|
335
|
+
let(:arguments) { ['session', table, '--db-url', mysql_url, '--db-user', user, '--db-password', password, '--format', 'mysql', '-o', out_file.path] }
|
336
|
+
let(:mysql_url) { "jdbc:mysql://#{host}/#{database}" }
|
337
|
+
let(:port) { 3306 }
|
338
|
+
|
339
|
+
it 'generate configuration file' do
|
340
|
+
expect(generated_config).to eq expected_config
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
context 'not migrate options' do
|
347
|
+
%w(--columns --column-header).each do |opt|
|
348
|
+
context "with #{opt}" do
|
349
|
+
let(:args) { ['url'] }
|
350
|
+
let(:arguments) { ["path/to/prefix_*.csv", '--format', 'csv', opt, 'col1,col2'] }
|
351
|
+
|
352
|
+
it "#{opt} is not migrate" do
|
353
|
+
expect { command.import_config(option) }.not_to raise_error
|
354
|
+
expect(stderr_io.string).to include 'not migrate. Please, edit config file after execute guess commands.'
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
181
360
|
end
|
182
361
|
end
|
data/spec/td/command/job_spec.rb
CHANGED
@@ -146,7 +146,7 @@ module TreasureData::Command
|
|
146
146
|
|
147
147
|
it 'supports json output' do
|
148
148
|
command.send(:show_result, job, file, nil, 'json', { header: true })
|
149
|
-
File.read(file.path).should == %Q([
|
149
|
+
File.read(file.path).should == %Q([{"c0":null,"c1":2.0,"v":{"key":3},"c3":null}])
|
150
150
|
end
|
151
151
|
|
152
152
|
it 'supports csv output' do
|
@@ -217,5 +217,118 @@ module TreasureData::Command
|
|
217
217
|
end
|
218
218
|
end
|
219
219
|
end
|
220
|
+
|
221
|
+
describe '#table_rename' do
|
222
|
+
include_context 'quiet_out'
|
223
|
+
|
224
|
+
let(:db_name) { 'database' }
|
225
|
+
let(:from_table_name) { 'from_table' }
|
226
|
+
let(:dest_table_name) { 'dest_table' }
|
227
|
+
let(:client) { double('client') }
|
228
|
+
let(:database) { double('database') }
|
229
|
+
let(:command) { Class.new { include TreasureData::Command }.new }
|
230
|
+
let(:option) {
|
231
|
+
List::CommandParser.new('table:rename', [], %w(db_name from_table_name dest_table_name), false, cmd_args, true)
|
232
|
+
}
|
233
|
+
|
234
|
+
before do
|
235
|
+
command.stub(:get_client) { client }
|
236
|
+
command.stub(:get_database).with(client, db_name).and_return(database)
|
237
|
+
end
|
238
|
+
|
239
|
+
context "from table isn't exists" do
|
240
|
+
let(:cmd_args) { [db_name, from_table_name, dest_table_name] }
|
241
|
+
|
242
|
+
before do
|
243
|
+
database.stub(:table).with(from_table_name).and_return { raise }
|
244
|
+
end
|
245
|
+
|
246
|
+
it "can't rename table" do
|
247
|
+
command.should_receive(:exit).with(1).and_return { raise CallSystemExitError }
|
248
|
+
|
249
|
+
expect { command.table_rename(option) }.to raise_error
|
250
|
+
expect(stderr_io.string).to include(from_table_name)
|
251
|
+
expect(stderr_io.string).not_to include(dest_table_name)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
context 'overwrite is false' do
|
256
|
+
let(:cmd_args) { [db_name, from_table_name, dest_table_name] }
|
257
|
+
|
258
|
+
before do
|
259
|
+
database.stub(:table).with(from_table_name).and_return
|
260
|
+
end
|
261
|
+
|
262
|
+
context "dest_table isn't exists" do
|
263
|
+
before do
|
264
|
+
database.stub(:table).with(dest_table_name).and_return { raise }
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'create dest table and rename table' do
|
268
|
+
client.should_receive(:create_log_table).with(db_name, dest_table_name)
|
269
|
+
client.should_receive(:swap_table).with(db_name, from_table_name, dest_table_name)
|
270
|
+
client.should_receive(:delete_table).with(db_name, from_table_name)
|
271
|
+
|
272
|
+
command.table_rename(option)
|
273
|
+
|
274
|
+
expect(stderr_io.string).to include(from_table_name)
|
275
|
+
expect(stderr_io.string).to include(dest_table_name)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
context "dest_tableis is exist" do
|
280
|
+
before do
|
281
|
+
database.stub(:table).with(dest_table_name).and_return
|
282
|
+
end
|
283
|
+
|
284
|
+
it "can't rename table" do
|
285
|
+
command.should_receive(:exit).with(1).and_return { raise CallSystemExitError }
|
286
|
+
|
287
|
+
expect { command.table_rename(option) }.to raise_error
|
288
|
+
expect(stderr_io.string).not_to include(from_table_name)
|
289
|
+
expect(stderr_io.string).to include(dest_table_name)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'overwrite is true' do
|
295
|
+
let(:cmd_args) { [db_name, from_table_name, dest_table_name, '--overwrite'] }
|
296
|
+
|
297
|
+
before do
|
298
|
+
database.stub(:table).with(from_table_name).and_return
|
299
|
+
end
|
300
|
+
|
301
|
+
context "dest_table isn't exists" do
|
302
|
+
before do
|
303
|
+
database.stub(:table).with(dest_table_name).and_return { raise }
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'create dest table and rename table' do
|
307
|
+
client.should_receive(:create_log_table).with(db_name, dest_table_name)
|
308
|
+
client.should_receive(:swap_table).with(db_name, from_table_name, dest_table_name)
|
309
|
+
client.should_receive(:delete_table).with(db_name, from_table_name)
|
310
|
+
|
311
|
+
command.table_rename(option)
|
312
|
+
expect(stderr_io.string).to include(from_table_name)
|
313
|
+
expect(stderr_io.string).to include(dest_table_name)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context "dest_tableis is exist" do
|
318
|
+
before do
|
319
|
+
database.stub(:table).with(dest_table_name).and_return
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'overwrite dest table' do
|
323
|
+
client.should_receive(:swap_table).with(db_name, from_table_name, dest_table_name)
|
324
|
+
client.should_receive(:delete_table).with(db_name, from_table_name)
|
325
|
+
|
326
|
+
command.table_rename(option)
|
327
|
+
expect(stderr_io.string).to include(from_table_name)
|
328
|
+
expect(stderr_io.string).to include(dest_table_name)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
220
333
|
end
|
221
334
|
end
|
data/spec/td/common_spec.rb
CHANGED
@@ -144,7 +144,29 @@ module TreasureData::Command
|
|
144
144
|
size_increments = 2
|
145
145
|
curr_size = 0
|
146
146
|
while (curr_size += size_increments) < size do
|
147
|
-
indicator.update(
|
147
|
+
indicator.update(curr_size)
|
148
|
+
sleep(0.05)
|
149
|
+
end
|
150
|
+
indicator.finish
|
151
|
+
end
|
152
|
+
|
153
|
+
it "shows in only current byte size if total is nil" do
|
154
|
+
indicator = TreasureData::Command::SizeBasedDownloadProgressIndicator.new("Downloading", nil)
|
155
|
+
size_increments = 2
|
156
|
+
curr_size = 0
|
157
|
+
while (curr_size += size_increments) < 200 do
|
158
|
+
indicator.update(curr_size)
|
159
|
+
sleep(0.05)
|
160
|
+
end
|
161
|
+
indicator.finish
|
162
|
+
end
|
163
|
+
|
164
|
+
it "shows in only current byte size if total is 0" do
|
165
|
+
indicator = TreasureData::Command::SizeBasedDownloadProgressIndicator.new("Downloading", 0)
|
166
|
+
size_increments = 2
|
167
|
+
curr_size = 0
|
168
|
+
while (curr_size += size_increments) < 200 do
|
169
|
+
indicator.update(curr_size)
|
148
170
|
sleep(0.05)
|
149
171
|
end
|
150
172
|
indicator.finish
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'td/compact_format_yamler'
|
3
|
+
|
4
|
+
module TreasureData
|
5
|
+
describe TreasureData::CompactFormatYamler do
|
6
|
+
describe '.dump' do
|
7
|
+
let(:data) {
|
8
|
+
{
|
9
|
+
'a' => {
|
10
|
+
'b' => {
|
11
|
+
'c' => 1,
|
12
|
+
'd' => 'e'
|
13
|
+
},
|
14
|
+
'f' => [1, 2, 3]
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
let(:comapct_format_yaml) {
|
20
|
+
<<-EOS
|
21
|
+
---
|
22
|
+
a:
|
23
|
+
b: {c: 1, d: e}
|
24
|
+
f:
|
25
|
+
- 1
|
26
|
+
- 2
|
27
|
+
- 3
|
28
|
+
EOS
|
29
|
+
}
|
30
|
+
|
31
|
+
subject { TreasureData::CompactFormatYamler.dump data }
|
32
|
+
|
33
|
+
it 'use compact format for deepest Hash' do
|
34
|
+
expect(subject).to eq comapct_format_yaml
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'td/connector_config_normalizer'
|
3
|
+
require 'td/client/api_error'
|
4
|
+
|
5
|
+
module TreasureData
|
6
|
+
describe ConnectorConfigNormalizer do
|
7
|
+
describe '#normalized_config' do
|
8
|
+
subject { TreasureData::ConnectorConfigNormalizer.new(config).normalized_config }
|
9
|
+
|
10
|
+
context 'has key :in' do
|
11
|
+
context 'without :out, :exec' do
|
12
|
+
let(:config) { {'in' => {'type' => 's3'}} }
|
13
|
+
|
14
|
+
it { expect(subject).to eq config.merge('out' => {}, 'exec' => {}, 'filters' => []) }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with :out, :exec, :filters' do
|
18
|
+
let(:config) {
|
19
|
+
{
|
20
|
+
'in' => {'type' => 's3'},
|
21
|
+
'out' => {'mode' => 'append'},
|
22
|
+
'exec' => {'guess_plugins' => ['json', 'query_string']},
|
23
|
+
'filters' => [{'type' => 'speedometer'}]
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
it { expect(subject).to eq config }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'has key :config' do
|
32
|
+
context 'with :in' do
|
33
|
+
let(:config) {
|
34
|
+
{ 'config' =>
|
35
|
+
{
|
36
|
+
'in' => {'type' => 's3'},
|
37
|
+
'out' => {'mode' => 'append'},
|
38
|
+
'exec' => {'guess_plugins' => ['json', 'query_string']},
|
39
|
+
'filters' => [{'type' => 'speedometer'}]
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
it { expect(subject).to eq config['config'] }
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'without :in' do
|
49
|
+
let(:config) { {'config' => {'type' => 's3'}} }
|
50
|
+
|
51
|
+
it { expect(subject).to eq({'in' => config['config'], 'out' => {}, 'exec' => {}, 'filters' => []}) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'does not have key :in or :config' do
|
56
|
+
let(:config) { {'type' => 's3'} }
|
57
|
+
|
58
|
+
it { expect(subject).to eq({'in' => config, 'out' => {}, 'exec' => {}, 'filters' => []}) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/td.gemspec
CHANGED
@@ -21,10 +21,11 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.add_dependency "yajl-ruby", "~> 1.1"
|
22
22
|
gem.add_dependency "hirb", ">= 0.4.5"
|
23
23
|
gem.add_dependency "parallel", "~> 0.6.1"
|
24
|
-
gem.add_dependency "td-client", "~> 0.8.
|
24
|
+
gem.add_dependency "td-client", "~> 0.8.76"
|
25
25
|
gem.add_dependency "td-logger", "~> 0.3.21"
|
26
26
|
gem.add_dependency "rubyzip", "~> 1.1.7"
|
27
27
|
gem.add_dependency "zip-zip", "~> 0.3"
|
28
|
+
gem.add_dependency "ruby-progressbar", "~> 1.7.5"
|
28
29
|
gem.add_development_dependency "rake", "~> 0.9"
|
29
30
|
gem.add_development_dependency "rspec", "~> 2.11.0"
|
30
31
|
gem.add_development_dependency "simplecov", "~> 0.10.0"
|