td 0.10.4 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,4 +1,15 @@
1
1
 
2
+ == 2011-12-04 version 0.10.5
3
+
4
+ * Added new feature: result
5
+ * Added new feature: status
6
+ * Refined usage message
7
+ * Fixed argument length check
8
+ * help subcommand shows commandline examples
9
+ * table:tail subcommand reduced default max row number
10
+ * job:list subcommand supports --running and --error options
11
+
12
+
2
13
  == 2011-11-11 version 0.10.4
3
14
 
4
15
  * Updated dependency: td-logger-0.3.7
@@ -0,0 +1,202 @@
1
+
2
+ module TreasureData
3
+ module Command
4
+
5
+ def aggr_list(op)
6
+ op.cmd_parse
7
+
8
+ client = get_client
9
+
10
+ ass = client.aggregation_schemas
11
+
12
+ rows = []
13
+ ass.each {|as|
14
+ rows << {:Name=>as.name, :Relation=>as.relation_key}
15
+ }
16
+
17
+ puts cmd_render_table(rows, :fields => [:Name, :Relation])
18
+
19
+ if rows.empty?
20
+ $stderr.puts "There are no aggregation schemas."
21
+ $stderr.puts "Use '#{$prog} aggr:create <name>' to create a aggregation schema."
22
+ end
23
+ end
24
+
25
+ def aggr_show(op)
26
+ name = op.cmd_parse
27
+
28
+ client = get_client
29
+
30
+ begin
31
+ as = client.aggregation_schema(name)
32
+ rescue
33
+ cmd_debug_error $!
34
+ $stderr.puts "Aggregation '#{name}' does not exist."
35
+ exit 1
36
+ end
37
+
38
+ log_rows = []
39
+ as.logs.each {|las|
40
+ log_rows << {
41
+ :Table=>las.table.identifier,
42
+ :Name=>las.name,
43
+ :o1_key=>las.okeys[0].to_s,
44
+ :o2_key=>las.okeys[1].to_s,
45
+ :o3_key=>las.okeys[2].to_s,
46
+ :value_key=>las.value_key.to_s,
47
+ :count_key=>las.count_key.to_s,
48
+ :Comment=>las.comment.to_s
49
+ }
50
+ }
51
+
52
+ attr_rows = []
53
+ as.attributes.each {|aas|
54
+ params = aas.parameters.to_a.map {|k,v| "#{k}=#{v}" }.join(' ')
55
+ attr_rows << {
56
+ :Table=>aas.table.identifier,
57
+ :Name=>aas.name,
58
+ :Method=>aas.method_name,
59
+ :Parameters=>params,
60
+ :Comment=>aas.comment.to_s,
61
+ }
62
+ }
63
+
64
+ puts "Log entries:"
65
+ puts cmd_render_table(log_rows, :fields => [:Table, :Name, :o1_key, :o2_key, :o3_key, :value_key, :count_key, :Comment], :max_width=>400)
66
+
67
+ puts ''
68
+
69
+ puts "Attribute entries:"
70
+ puts cmd_render_table(attr_rows, :fields => [:Table, :Name, :Method, :Parameters, :Comment], :max_width=>400)
71
+ end
72
+
73
+ def aggr_create(op)
74
+ name, relation_key = op.cmd_parse
75
+
76
+ client = get_client
77
+
78
+ begin
79
+ client.create_aggregation_schema(name, relation_key)
80
+ rescue AlreadyExistsError
81
+ cmd_debug_error $!
82
+ $stderr.puts "Aggregation '#{name}' already exists."
83
+ exit 1
84
+ end
85
+
86
+ $stderr.puts "Aggregation schema '#{name}' is created."
87
+ end
88
+
89
+ def aggr_delete(op)
90
+ name = op.cmd_parse
91
+
92
+ client = get_client
93
+
94
+ client.delete_aggregation_schema(name)
95
+
96
+ $stderr.puts "Aggregation schema '#{name}' is deleted."
97
+ end
98
+
99
+ def aggr_add_log(op)
100
+ comment = nil
101
+ value_key = nil
102
+ count_key = nil
103
+
104
+ op.on('-m', '--comment COMMENT', 'comment of this entry') {|s|
105
+ comment = s
106
+ }
107
+ op.on('-v', '--value KEY_NAME', 'key name of value field') {|s|
108
+ value_key = s
109
+ }
110
+ op.on('-c', '--count KEY_NAME', 'key name of count field') {|s|
111
+ count_key = s
112
+ }
113
+
114
+ name, db_name, table_name, entry_name, o1_key, o2_key, o3_key = op.cmd_parse
115
+
116
+ okeys = [o1_key, o2_key, o3_key].compact
117
+
118
+ client = get_client
119
+
120
+ get_table(client, db_name, table_name)
121
+
122
+ begin
123
+ client.create_aggregation_log_entry(name, entry_name, comment, db_name, table_name, okeys, value_key, count_key)
124
+ rescue NotFoundError
125
+ cmd_debug_error $!
126
+ $stderr.puts "Aggregation schema '#{name}' does not exist."
127
+ $stderr.puts "Use '#{$prog} aggr:create #{name}' to create the aggregation schema."
128
+ exit 1
129
+ rescue AlreadyExistsError
130
+ $stderr.puts "Aggregation log entry '#{entry_name}' already exists."
131
+ exit 1
132
+ end
133
+
134
+ $stderr.puts "Aggregation log entry '#{entry_name}' is created."
135
+ end
136
+
137
+ def aggr_add_attr(op)
138
+ comment = nil
139
+
140
+ op.on('-m', '--comment COMMENT', 'comment of this entry') {|s|
141
+ comment = s
142
+ }
143
+
144
+ name, db_name, table_name, entry_name, method_name, *parameters = op.cmd_parse
145
+
146
+ params = {}
147
+ parameters.each {|pa|
148
+ k, v = pa.split('=')
149
+ params[k] = v
150
+ }
151
+
152
+ client = get_client
153
+
154
+ get_table(client, db_name, table_name)
155
+
156
+ begin
157
+ client.create_aggregation_attr_entry(name, entry_name, comment, db_name, table_name, method_name, params)
158
+ rescue NotFoundError
159
+ cmd_debug_error $!
160
+ $stderr.puts "Aggregation schema '#{name}' does not exist."
161
+ $stderr.puts "Use '#{$prog} aggr:create #{name}' to create the aggregation schema."
162
+ exit 1
163
+ rescue AlreadyExistsError
164
+ $stderr.puts "Aggregation attribute entry '#{entry_name}' already exists."
165
+ exit 1
166
+ end
167
+
168
+ $stderr.puts "Aggregation attribute entry '#{entry_name}' is created."
169
+ end
170
+
171
+ def aggr_del_log(op)
172
+ name, entry_name = op.cmd_parse
173
+
174
+ client = get_client
175
+
176
+ begin
177
+ client.delete_aggregation_log_entry(name, entry_name)
178
+ rescue NotFoundError
179
+ $stderr.puts "Aggregation log entry '#{entry_name}' does not exist."
180
+ exit 1
181
+ end
182
+
183
+ $stderr.puts "Aggregation log entry '#{entry_name}' is deleted."
184
+ end
185
+
186
+ def aggr_del_attr(op)
187
+ name, entry_name = op.cmd_parse
188
+
189
+ client = get_client
190
+
191
+ begin
192
+ client.delete_aggregation_attr_entry(name, entry_name)
193
+ rescue NotFoundError
194
+ $stderr.puts "Aggregation log entry '#{entry_name}' does not exist."
195
+ exit 1
196
+ end
197
+
198
+ $stderr.puts "Aggregation log entry '#{entry_name}' is deleted."
199
+ end
200
+
201
+ end
202
+ end
@@ -11,8 +11,8 @@ autoload :Job, 'td/client'
11
11
  module Command
12
12
 
13
13
  private
14
- def get_option(name)
15
- List.get_option(name)
14
+ def initialize
15
+ @render_indent = ''
16
16
  end
17
17
 
18
18
  def get_client
@@ -28,10 +28,10 @@ module Command
28
28
  Hirb::Helpers::Table.render(rows, *opts)
29
29
  end
30
30
 
31
- def cmd_render_tree(nodes, *opts)
32
- require 'hirb'
33
- Hirb::Helpers::Tree.render(nodes, *opts)
34
- end
31
+ #def cmd_render_tree(nodes, *opts)
32
+ # require 'hirb'
33
+ # Hirb::Helpers::Tree.render(nodes, *opts)
34
+ #end
35
35
 
36
36
  def cmd_debug_error(ex)
37
37
  if $verbose
@@ -43,6 +43,32 @@ module Command
43
43
  end
44
44
  end
45
45
 
46
+ def cmd_format_elapsed(start, finish)
47
+ if start
48
+ if !finish
49
+ finish = Time.now.utc
50
+ end
51
+ e = finish.to_i - start.to_i
52
+ elapsed = ''
53
+ if e >= 3600
54
+ elapsed << "#{e/3600}h "
55
+ e %= 3600
56
+ elapsed << "% 2dm " % (e/60)
57
+ e %= 60
58
+ elapsed << "% 2dsec" % e
59
+ elsif e >= 60
60
+ elapsed << "% 2dm " % (e/60)
61
+ e %= 60
62
+ elapsed << "% 2dsec" % e
63
+ else
64
+ elapsed << "% 2dsec" % e
65
+ end
66
+ else
67
+ elapsed = ''
68
+ end
69
+ elapsed = "% 10s" % elapsed # right aligned
70
+ end
71
+
46
72
  def get_database(client, db_name)
47
73
  begin
48
74
  return client.database(db_name)
@@ -5,14 +5,22 @@ module Command
5
5
  def help(op)
6
6
  cmd = op.cmd_parse
7
7
 
8
- op = List.get_option(cmd)
9
- unless op
8
+ usage = List.cmd_usage(cmd)
9
+ unless usage
10
10
  $stderr.puts "'#{cmd}' is not a td command. Run '#{$prog}' to show the list."
11
11
  List.show_guess(cmd)
12
12
  exit 1
13
13
  end
14
14
 
15
- puts op.message
15
+ puts usage
16
+ end
17
+
18
+ def help_all(op)
19
+ cmd = op.cmd_parse
20
+
21
+ TreasureData::Command::List.show_help(op.summary_indent)
22
+ puts ""
23
+ puts "Type '#{$prog} help COMMAND' for more information on a specific command."
16
24
  end
17
25
 
18
26
  end
@@ -222,6 +222,10 @@ module Command
222
222
  begin
223
223
  record = JSON.parse(line)
224
224
 
225
+ unless record.is_a?(Hash)
226
+ raise "record must be a Hash"
227
+ end
228
+
225
229
  time = record[@time_key]
226
230
  unless time
227
231
  raise "record doesn't have '#{@time_key}' column"
@@ -255,6 +259,10 @@ module Command
255
259
  MessagePack::Unpacker.new(file).each {|record|
256
260
  i += 1
257
261
  begin
262
+ unless record.is_a?(Hash)
263
+ raise "record must be a Hash"
264
+ end
265
+
258
266
  time = record[@time_key]
259
267
  unless time
260
268
  raise "record doesn't have '#{@time_key}' column"
@@ -5,6 +5,8 @@ module Command
5
5
  def job_list(op)
6
6
  page = 0
7
7
  skip = 0
8
+ running = false
9
+ error = false
8
10
 
9
11
  op.on('-p', '--page PAGE', 'skip N pages', Integer) {|i|
10
12
  page = i
@@ -12,6 +14,12 @@ module Command
12
14
  op.on('-s', '--skip N', 'skip N jobs', Integer) {|i|
13
15
  skip = i
14
16
  }
17
+ op.on('-R', '--running', 'show only running jobs', TrueClass) {|b|
18
+ running = b
19
+ }
20
+ op.on('-E', '--error', 'show only error jobs', TrueClass) {|b|
21
+ error = b
22
+ }
15
23
 
16
24
  max = op.cmd_parse
17
25
 
@@ -26,36 +34,14 @@ module Command
26
34
 
27
35
  rows = []
28
36
  jobs.each {|job|
37
+ next if running && !job.running?
38
+ next if error && !job.error?
29
39
  start = job.start_at
30
- finish = job.end_at
31
- if start
32
- if !finish
33
- finish = Time.now.utc
34
- end
35
- e = finish.to_i - start.to_i
36
- elapsed = ''
37
- if e >= 3600
38
- elapsed << "#{e/3600}h "
39
- e %= 3600
40
- elapsed << "% 2dm " % (e/60)
41
- e %= 60
42
- elapsed << "% 2dsec" % e
43
- elsif e >= 60
44
- elapsed << "% 2dm " % (e/60)
45
- e %= 60
46
- elapsed << "% 2dsec" % e
47
- else
48
- elapsed << "% 2dsec" % e
49
- end
50
- else
51
- elapsed = ''
52
- end
53
- elapsed = "% 10s" % elapsed # right aligned
54
-
55
- rows << {:JobID => job.job_id, :Status => job.status, :Query => job.query.to_s, :Start => (start ? start.localtime : ''), :Elapsed => elapsed}
40
+ elapsed = cmd_format_elapsed(start, job.end_at)
41
+ rows << {:JobID => job.job_id, :Status => job.status, :Query => job.query.to_s, :Start => (start ? start.localtime : ''), :Elapsed => elapsed, :Result => job.rset_name}
56
42
  }
57
43
 
58
- puts cmd_render_table(rows, :fields => [:JobID, :Status, :Start, :Elapsed, :Query])
44
+ puts cmd_render_table(rows, :fields => [:JobID, :Status, :Start, :Elapsed, :Result, :Query])
59
45
  end
60
46
 
61
47
  def job_show(op)
@@ -86,21 +72,22 @@ module Command
86
72
 
87
73
  job = client.job(job_id)
88
74
 
89
- puts "JobID : #{job.job_id}"
90
- puts "URL : #{job.url}"
91
- puts "Status : #{job.status}"
92
- puts "Query : #{job.query}"
75
+ puts "JobID : #{job.job_id}"
76
+ puts "URL : #{job.url}"
77
+ puts "Status : #{job.status}"
78
+ puts "Query : #{job.query}"
79
+ puts "Result table : #{job.rset_name}"
93
80
 
94
81
  if wait && !job.finished?
95
82
  wait_job(job)
96
83
  if job.success?
97
- puts "Result :"
84
+ puts "Result :"
98
85
  show_result(job, output, format)
99
86
  end
100
87
 
101
88
  else
102
89
  if job.success?
103
- puts "Result :"
90
+ puts "Result :"
104
91
  show_result(job, output, format)
105
92
  end
106
93
 
@@ -231,7 +218,8 @@ module Command
231
218
  # TODO limit number of rows to show
232
219
  rows << row.map {|v|
233
220
  if v.is_a?(String)
234
- v.to_s
221
+ # TODO encoding check
222
+ v.to_s.force_encoding('ASCII-8BIT')
235
223
  else
236
224
  v.to_json
237
225
  end
@@ -3,69 +3,36 @@ module TreasureData
3
3
  module Command
4
4
  module List
5
5
 
6
- class CommandOption < OptionParser
7
- def initialize(name, args, description)
6
+ class CommandParser < OptionParser
7
+ def initialize(name, req_args, opt_args, varlen, argv)
8
8
  super()
9
-
10
- @name = name
11
- @description = description.to_s
12
-
13
- if args.last.to_s =~ /_$/
14
- @varlen = true
15
- args.push args.pop.to_s[0..-2]+'...'
16
- elsif args.last.to_s =~ /_\?$/
17
- @varlen = true
18
- args.push args.pop.to_s[0..-3]+'...?'
19
- end
20
-
21
- @req_args, opt_args = args.partition {|a| a.to_s !~ /\?$/ }
22
- @opt_args = opt_args.map {|a| a.to_s[0..-2].to_sym }
23
- @args = @req_args + @opt_args
24
-
25
- @usage_args = "#{@name}"
26
- @req_args.each {|a| @usage_args << " <#{a}>" }
27
- @opt_args.each {|a| @usage_args << " [#{a}]" }
28
-
9
+ @req_args = req_args
10
+ @opt_args = opt_args
11
+ @varlen = varlen
12
+ @argv = argv
29
13
  @has_options = false
30
-
31
- self.summary_indent = " "
32
-
33
- banner = "usage:\n"
34
- banner << " $ #{File.basename($0)} #{@usage_args}\n"
35
- banner << "\n"
36
- banner << "description:\n"
37
- @description.split("\n").each {|l|
38
- banner << " #{l}\n"
39
- }
40
- self.banner = banner
41
-
42
- @message = nil
43
- @argv = nil
14
+ @message = ''
44
15
  end
45
16
 
46
- attr_accessor :message, :argv
17
+ attr_accessor :message
47
18
 
48
- def message
49
- @message || to_s
19
+ def on(*argv)
20
+ @has_options = true
21
+ super
50
22
  end
51
23
 
52
24
  def banner
53
- s = super.dup
25
+ s = @message.dup
54
26
  if @has_options
55
27
  s << "\n"
56
28
  s << "options:\n"
57
29
  end
58
- s << "\n"
59
30
  s
60
31
  end
61
32
 
62
- def usage
63
- "%-40s # %s" % [@usage_args, @description]
64
- end
65
-
66
33
  def cmd_parse(argv=@argv||ARGV)
67
34
  parse!(argv)
68
- if argv.length < @req_args.length || (!@varlen && argv.length > @argv.length)
35
+ if argv.length < @req_args.length || (!@varlen && argv.length > (@req_args.length+@opt_args.length))
69
36
  cmd_usage nil
70
37
  end
71
38
  if argv.length <= 1
@@ -78,40 +45,92 @@ module List
78
45
  end
79
46
 
80
47
  def cmd_usage(msg=nil)
81
- puts self.message
82
- if msg
83
- puts ""
84
- puts "error: #{msg}"
85
- end
48
+ puts self.to_s
49
+ puts "error: #{msg}" if msg
86
50
  exit 1
87
51
  end
52
+ end
88
53
 
89
- def group
90
- name.split(':', 2).first
54
+ class CommandOption
55
+ def initialize(name, args, description, examples)
56
+ @name = name
57
+ @args = args
58
+ @description = description.to_s
59
+ @examples = examples
60
+ @override_message = nil
91
61
  end
92
62
 
93
- def on(*argv)
94
- @has_options = true
95
- super
63
+ attr_reader :name, :args, :description, :examples
64
+ attr_accessor :override_message
65
+
66
+ def compile!
67
+ return if @usage_args
68
+
69
+ args = @args.dup
70
+ if args.last.to_s =~ /_$/
71
+ @varlen = true
72
+ args.push args.pop.to_s[0..-2]+'...'
73
+ elsif args.last.to_s =~ /_\?$/
74
+ @varlen = true
75
+ args.push args.pop.to_s[0..-3]+'...?'
76
+ end
77
+
78
+ @req_args, @opt_args = args.partition {|a| a.to_s !~ /\?$/ }
79
+ @opt_args = @opt_args.map {|a| a.to_s[0..-2].to_sym }
80
+
81
+ @usage_args = "#{@name}"
82
+ @req_args.each {|a| @usage_args << " <#{a}>" }
83
+ @opt_args.each {|a| @usage_args << " [#{a}]" }
96
84
  end
97
85
 
98
- def with_args(argv)
99
- d = dup
100
- d.argv = argv
101
- d
86
+ def create_optparse(argv)
87
+ compile!
88
+ op = CommandParser.new(@name, @req_args, @opt_args, @varlen, argv)
89
+
90
+ message = "usage:\n"
91
+ message << " $ #{File.basename($0)} #{@usage_args}\n"
92
+ unless @examples.empty?
93
+ message << "\n"
94
+ message << "example:\n"
95
+ @examples.each {|l|
96
+ message << " $ #{File.basename($0)} #{l}\n"
97
+ }
98
+ end
99
+ message << "\n"
100
+ message << "description:\n"
101
+ @description.split("\n").each {|l|
102
+ message << " #{l}\n"
103
+ }
104
+
105
+ op.message = message
106
+ op.summary_indent = " "
107
+
108
+ if msg = @override_message
109
+ (class<<op;self;end).module_eval do
110
+ define_method(:to_s) { msg }
111
+ end
112
+ end
113
+
114
+ op
115
+ end
116
+
117
+ def usage
118
+ compile!
119
+ "%-40s # %s" % [@usage_args, @description]
102
120
  end
103
121
 
104
- attr_reader :name
105
- attr_reader :description
122
+ def group
123
+ @name.split(':', 2).first
124
+ end
106
125
  end
107
126
 
108
127
  LIST = []
109
128
  COMMAND = {}
110
129
  GUESS = {}
111
- HELP_EXCLUDE = ['help', 'account']
130
+ HELP_EXCLUDE = [/^help/, /^account/, /^aggr/]
112
131
 
113
- def self.add_list(name, args, description)
114
- LIST << COMMAND[name] = CommandOption.new(name, args, description)
132
+ def self.add_list(name, args, description, *examples)
133
+ LIST << COMMAND[name] = CommandOption.new(name, args, description, examples)
115
134
  end
116
135
 
117
136
  def self.add_alias(new_cmd, old_cmd)
@@ -122,15 +141,22 @@ module List
122
141
  GUESS[wrong] = correct
123
142
  end
124
143
 
144
+ def self.cmd_usage(name)
145
+ if c = COMMAND[name]
146
+ c.create_optparse([]).cmd_usage
147
+ end
148
+ nil
149
+ end
150
+
125
151
  def self.get_method(name)
126
- if op = COMMAND[name]
127
- name = op.name
128
- group, action = op.group
152
+ if c = COMMAND[name]
153
+ name = c.name
154
+ group, action = c.group
129
155
  require 'td/command/common'
130
156
  require "td/command/#{group}"
131
- cmd = name.gsub(':', '_')
157
+ cmd = name.gsub(/[\:\-]/, '_')
132
158
  m = Object.new.extend(Command).method(cmd)
133
- return Proc.new {|args| m.call(op.with_args(args)) }
159
+ return Proc.new {|args| m.call(c.create_optparse(args)) }
134
160
  end
135
161
  nil
136
162
  end
@@ -147,74 +173,96 @@ module List
147
173
 
148
174
  def self.show_help(indent=' ')
149
175
  before_group = nil
150
- LIST.each {|op|
151
- next if HELP_EXCLUDE.include?(op.name)
152
- if before_group != op.group
153
- before_group = op.group
176
+ LIST.each {|c|
177
+ next if HELP_EXCLUDE.any? {|pattern| pattern =~ c.name }
178
+ if before_group != c.group
179
+ before_group = c.group
154
180
  puts ""
155
181
  end
156
- puts "#{indent}#{op.usage}"
182
+ puts "#{indent}#{c.usage}"
157
183
  }
158
184
  end
159
185
 
160
186
  def self.get_group(group)
161
- LIST.map {|op|
162
- op.group == group
187
+ LIST.map {|c|
188
+ c.group == group
163
189
  }
164
190
  end
165
191
 
166
192
  def self.finishup
167
193
  groups = {}
168
- LIST.each {|op|
169
- (groups[op.group] ||= []) << op
194
+ LIST.each {|c|
195
+ (groups[c.group] ||= []) << c
170
196
  }
171
197
  groups.each_pair {|group,ops|
172
- if ops.size > 1 && xop = COMMAND[group].dup
198
+ if ops.size > 1 && c = COMMAND[group]
199
+ c = c.dup
200
+
173
201
  msg = %[Additional commands, type "#{File.basename($0)} help COMMAND" for more details:\n\n]
174
- ops.each {|op|
175
- msg << %[ #{op.usage}\n]
202
+ ops.each {|c|
203
+ msg << %[ #{c.usage}\n]
176
204
  }
177
205
  msg << %[\n]
178
- xop.message = msg
179
- COMMAND[group] = xop
206
+ c.override_message = msg
207
+
208
+ COMMAND[group] = c
180
209
  end
181
210
  }
182
211
  end
183
212
 
184
- add_list 'db:list', %w[], 'Show list of tables in a database'
185
- add_list 'db:show', %w[db], 'Describe a information of a database'
186
- add_list 'db:create', %w[db], 'Create a database'
187
- add_list 'db:delete', %w[db], 'Delete a database'
213
+ add_list 'db:list', %w[], 'Show list of tables in a database', 'db:list', 'dbs'
214
+ add_list 'db:show', %w[db], 'Describe a information of a database', 'db example_db'
215
+ add_list 'db:create', %w[db], 'Create a database', 'db:create example_db'
216
+ add_list 'db:delete', %w[db], 'Delete a database', 'db:delete example_db'
217
+
218
+ add_list 'table:list', %w[db?], 'Show list of tables', 'table:list', 'table:list example_db', 'tables'
219
+ add_list 'table:show', %w[db table], 'Describe a information of a table', 'table example_db table1'
220
+ add_list 'table:create', %w[db table], 'Create a table', 'table:create example_db table1'
221
+ add_list 'table:delete', %w[db table], 'Delete a table', 'table:delete example_db table1'
222
+ add_list 'table:import', %w[db table files_], 'Parse and import files to a table', 'table:import example_db table1 --apache access.log', 'table:import example_db table1 --json -t time - < test.json'
223
+ add_list 'table:tail', %w[db table], 'Get recently imported logs', 'table:tail example_db table1', 'table:tail example_db table1 -t "2011-01-02 03:04:05" -n 30'
224
+
225
+ add_list 'result:info', %w[], 'Show information of the MySQL server', 'result:info'
226
+ add_list 'result:list', %w[], 'Show list of result tables', 'result:list', 'results'
227
+ add_list 'result:create', %w[name], 'Create a result table', 'result:create rset1'
228
+ add_list 'result:delete', %w[name], 'Delete a result table', 'result:delete rset1'
229
+ add_list 'result:connect', %w[sql?], 'Connect to the server using mysql command', 'result:connect'
230
+ #add_list 'result:get', %w[name], 'Download dump of the result table'
188
231
 
189
- add_list 'table:list', %w[db?], 'Show list of tables'
190
- add_list 'table:show', %w[db table], 'Describe a information of a table'
191
- add_list 'table:create', %w[db table], 'Create a table'
192
- add_list 'table:delete', %w[db table], 'Delete a table'
193
- add_list 'table:import', %w[db table files_], 'Parse and import files to a table'
194
- add_list 'table:tail', %w[db table], 'Get recently imported logs'
232
+ add_list 'status', %w[], 'Show schedules, jobs, tables and results', 'status', 's'
195
233
 
196
- add_list 'schema:show', %w[db table], 'Show schema of a table'
197
- add_list 'schema:set', %w[db table columns_?], 'Set new schema on a table'
198
- add_list 'schema:add', %w[db table columns_], 'Add new columns to a table'
199
- add_list 'schema:remove', %w[db table columns_], 'Remove columns from a table'
234
+ add_list 'schema:show', %w[db table], 'Show schema of a table', 'schema example_db table1'
235
+ add_list 'schema:set', %w[db table columns_?], 'Set new schema on a table', 'schema:set example_db table1 user:string size:int'
236
+ add_list 'schema:add', %w[db table columns_], 'Add new columns to a table', 'schema:add example_db table1 user:string size:int'
237
+ add_list 'schema:remove', %w[db table columns_], 'Remove columns from a table', 'schema:remove example_db table1 user size'
200
238
 
201
- add_list 'sched:list', %w[], 'Show list of schedules'
202
- add_list 'sched:create', %w[name cron sql], 'Create a schedule'
203
- add_list 'sched:delete', %w[name], 'Delete a schedule'
204
- add_list 'sched:history', %w[name max?], 'Show history of scheduled queries'
239
+ add_list 'sched:list', %w[], 'Show list of schedules', 'sched:list', 'scheds'
240
+ add_list 'sched:create', %w[name cron sql], 'Create a schedule', 'sched:create sched1 "0 * * * *" -d example_db "select count(*) from table1" -r rset1'
241
+ add_list 'sched:delete', %w[name], 'Delete a schedule', 'sched:delete sched1'
242
+ add_list 'sched:history', %w[name max?], 'Show history of scheduled queries', 'sched sched1 --page 1'
205
243
 
206
- add_list 'query', %w[sql], 'Issue a query'
244
+ add_list 'query', %w[sql], 'Issue a query', 'query -d example_db -w -r rset1 "select count(*) from table1"'
207
245
 
208
- add_list 'job:show', %w[job_id], 'Show status and result of a job'
209
- add_list 'job:list', %w[max?], 'Show list of jobs'
210
- add_list 'job:kill', %w[job_id], 'Kill or cancel a job'
246
+ add_list 'job:show', %w[job_id], 'Show status and result of a job', 'job 1461'
247
+ add_list 'job:list', %w[max?], 'Show list of jobs', 'jobs', 'jobs --page 1'
248
+ add_list 'job:kill', %w[job_id], 'Kill or cancel a job', 'job:kill 1461'
211
249
 
212
250
  add_list 'account', %w[user_name?], 'Setup a Treasure Data account'
213
251
  add_list 'apikey:show', %w[], 'Show Treasure Data API key'
214
252
  add_list 'apikey:set', %w[apikey], 'Set Treasure Data API key'
215
253
 
254
+ add_list 'aggr:list', %w[], 'Show list of aggregation schemas'
255
+ add_list 'aggr:show', %w[name], 'Describe a aggregation schema'
256
+ add_list 'aggr:create', %w[name relation_key], 'Create a aggregation schema'
257
+ add_list 'aggr:delete', %w[name], 'Delete a aggregation schema'
258
+ add_list 'aggr:add-log', %w[name db table entry_name o1_key? o2_key? o3_key?], 'Add a log aggregation entry'
259
+ add_list 'aggr:add-attr', %w[name db table entry_name method_name parameters_?], 'Add an attribute aggregation entry'
260
+ add_list 'aggr:del-log', %w[name entry_name], 'Delete a log aggregation entry'
261
+ add_list 'aggr:del-attr', %w[name entry_name], 'Delete an attribute aggregation entry'
262
+
216
263
  add_list 'server:status', %w[], 'Show status of the Treasure Data server'
217
264
 
265
+ add_list 'help:all', %w[], 'Show usage of all commands'
218
266
  add_list 'help', %w[command], 'Show usage of a command'
219
267
 
220
268
  # aliases
@@ -231,6 +279,9 @@ module List
231
279
  add_alias 'table', 'table:show'
232
280
  add_alias 'tables', 'table:list'
233
281
 
282
+ add_alias 'result', 'help' # dummy
283
+ add_alias 'results', 'result:list'
284
+
234
285
  add_alias 'schema', 'schema:show'
235
286
 
236
287
  add_alias 'schedule:list', 'sched:list'
@@ -247,7 +298,13 @@ module List
247
298
  add_alias 'jobs', 'job:list'
248
299
  add_alias 'kill', 'job:kill'
249
300
 
301
+ add_alias 'aggr', 'aggr:show'
302
+ add_alias 'aggrs', 'aggr:list'
303
+
250
304
  add_alias 'apikey', 'apikey:show'
305
+ add_alias 'server', 'server:status'
306
+
307
+ add_alias 's', 'status'
251
308
 
252
309
  # backward compatibility
253
310
  add_alias 'show-databases', 'db:list'
@@ -7,6 +7,7 @@ module Command
7
7
  wait = false
8
8
  output = nil
9
9
  format = 'tsv'
10
+ result = nil
10
11
 
11
12
  op.on('-d', '--database DB_NAME', 'use the database (required)') {|s|
12
13
  db_name = s
@@ -14,6 +15,9 @@ module Command
14
15
  op.on('-w', '--wait', 'wait for finishing the job', TrueClass) {|b|
15
16
  wait = b
16
17
  }
18
+ op.on('-r', '--result RESULT_TABLE', 'write result to the result table (use result:create command)') {|s|
19
+ result = s
20
+ }
17
21
  op.on('-o', '--output PATH', 'write result to the file') {|s|
18
22
  output = s
19
23
  }
@@ -36,7 +40,7 @@ module Command
36
40
  # local existance check
37
41
  get_database(client, db_name)
38
42
 
39
- job = client.query(db_name, sql)
43
+ job = client.query(db_name, sql, result)
40
44
 
41
45
  $stderr.puts "Job #{job.job_id} is queued."
42
46
  $stderr.puts "Use '#{$prog} job:show #{job.job_id}' to show the status."
@@ -0,0 +1,111 @@
1
+
2
+ module TreasureData
3
+ module Command
4
+
5
+ def result_info(op)
6
+ op.cmd_parse
7
+
8
+ client = get_client
9
+
10
+ info = client.result_set_info
11
+
12
+ puts "Type : #{info.type}"
13
+ puts "Host : #{info.host}"
14
+ puts "Port : #{info.port}"
15
+ puts "User : #{info.user}"
16
+ puts "Password : #{info.password}"
17
+ puts "Database : #{info.database}"
18
+ end
19
+
20
+ def result_list(op)
21
+ op.cmd_parse
22
+
23
+ client = get_client
24
+
25
+ rsets = client.result_sets
26
+
27
+ rows = []
28
+ rsets.each {|rset|
29
+ rows << {:Name => rset.name}
30
+ }
31
+ rows = rows.sort_by {|map|
32
+ map[:Name]
33
+ }
34
+
35
+ puts cmd_render_table(rows, :fields => [:Name])
36
+
37
+ if rsets.empty?
38
+ $stderr.puts "There are result tables."
39
+ $stderr.puts "Use '#{$prog} result:create <name>' to create a result table."
40
+ end
41
+ end
42
+
43
+ def result_create(op)
44
+ name = op.cmd_parse
45
+
46
+ API.validate_database_name(name)
47
+
48
+ client = get_client
49
+
50
+ begin
51
+ client.create_result_set(name)
52
+ rescue AlreadyExistsError
53
+ $stderr.puts "Result table '#{name}' already exists."
54
+ exit 1
55
+ end
56
+
57
+ $stderr.puts "Result table '#{name}' is created."
58
+ end
59
+
60
+ def result_delete(op)
61
+ name = op.cmd_parse
62
+
63
+ client = get_client
64
+
65
+ begin
66
+ client.delete_result_set(name)
67
+ rescue NotFoundError
68
+ $stderr.puts "Result table '#{name}' does not exist."
69
+ exit 1
70
+ end
71
+
72
+ $stderr.puts "Result table '#{name}' is deleted."
73
+ end
74
+
75
+ def result_connect(op)
76
+ mysql = 'mysql'
77
+
78
+ op.on('-e', '--execute MYSQL', 'mysql command') {|s|
79
+ mysql = s
80
+ }
81
+
82
+ sql = op.cmd_parse
83
+
84
+ client = get_client
85
+
86
+ info = client.result_set_info
87
+
88
+ cmd = [mysql, '-h', info.host, '-P', info.port.to_s, '-u', info.user, "--password=#{info.password}", info.database]
89
+
90
+ cmd_start = Time.now
91
+
92
+ if sql
93
+ IO.popen(cmd, "w") {|io|
94
+ io.write sql
95
+ io.close
96
+ }
97
+ else
98
+ STDERR.puts "> #{cmd.join(' ')}"
99
+ system(*cmd)
100
+ end
101
+
102
+ cmd_alive = Time.now - cmd_start
103
+ if $?.to_i != 0 && cmd_alive < 1.0
104
+ STDERR.puts "Command died within 1 second with exit code #{$?.to_i}."
105
+ STDERR.puts "Please confirm mysql command is installed."
106
+ end
107
+ end
108
+
109
+ end
110
+ end
111
+
@@ -33,10 +33,26 @@ EOF
33
33
  require 'td/command/list'
34
34
  puts op.to_s
35
35
  puts ""
36
- puts "commands:"
37
- TreasureData::Command::List.show_help(op.summary_indent)
38
- puts ""
39
- puts "Type '#{$prog} help COMMAND' for more information on a specific command."
36
+ puts <<EOF
37
+ Basic commands:
38
+
39
+ db # create/delete/list databases
40
+ table # create/delete/list/import/tail tables
41
+ query # issue a query
42
+ job # show/kill/list jobs
43
+ result # create/delete/list/connect/get MySQL result set
44
+
45
+ Additional commands:
46
+
47
+ sched # create/delete/list schedules that run a query periodically
48
+ schema # create/delete/modify schemas of tables
49
+ status # show scheds, jobs, tables and results
50
+ apikey # show/set API key
51
+ server # show status of the Treasure Data server
52
+ help # show help messages
53
+
54
+ Type 'td help COMMAND' for more information on a specific command.
55
+ EOF
40
56
  if errmsg
41
57
  puts "error: #{errmsg}"
42
58
  exit 1
@@ -11,21 +11,25 @@ module Command
11
11
 
12
12
  rows = []
13
13
  scheds.each {|sched|
14
- rows << {:Name => sched.name, :Cron => sched.cron, :Query => sched.query}
14
+ rows << {:Name => sched.name, :Cron => sched.cron, :Result => sched.rset_name, :Query => sched.query}
15
15
  }
16
16
  rows = rows.sort_by {|map|
17
17
  map[:Name]
18
18
  }
19
19
 
20
- puts cmd_render_table(rows, :fields => [:Name, :Cron, :Query])
20
+ puts cmd_render_table(rows, :fields => [:Name, :Cron, :Result, :Query])
21
21
  end
22
22
 
23
23
  def sched_create(op)
24
24
  db_name = nil
25
+ result = nil
25
26
 
26
27
  op.on('-d', '--database DB_NAME', 'use the database (required)') {|s|
27
28
  db_name = s
28
29
  }
30
+ op.on('-r', '--result RESULT_TABLE', 'write result to the result table (use result:create command)') {|s|
31
+ result = s
32
+ }
29
33
 
30
34
  name, cron, sql = op.cmd_parse
31
35
 
@@ -40,7 +44,7 @@ module Command
40
44
  get_database(client, db_name)
41
45
 
42
46
  begin
43
- first_time = client.create_schedule(name, :cron=>cron, :query=>sql, :database=>db_name)
47
+ first_time = client.create_schedule(name, :cron=>cron, :query=>sql, :database=>db_name, :result=>result)
44
48
  rescue AlreadyExistsError
45
49
  cmd_debug_error $!
46
50
  $stderr.puts "Schedule '#{name}' already exists."
@@ -99,10 +103,10 @@ module Command
99
103
 
100
104
  rows = []
101
105
  history.each {|j|
102
- rows << {:Time => j.scheduled_at.localtime, :JobID => j.job_id, :Status => j.status}
106
+ rows << {:Time => j.scheduled_at.localtime, :JobID => j.job_id, :Status => j.status, :Result=>j.rset_name}
103
107
  }
104
108
 
105
- puts cmd_render_table(rows, :fields => [:JobID, :Time, :Status])
109
+ puts cmd_render_table(rows, :fields => [:JobID, :Time, :Status, :Result])
106
110
  end
107
111
 
108
112
  end
@@ -0,0 +1,89 @@
1
+
2
+ module TreasureData
3
+ module Command
4
+
5
+ def status(op)
6
+ op.cmd_parse
7
+
8
+ client = get_client
9
+
10
+ # +----------------+
11
+ # | scheds |
12
+ # +----------------+
13
+ # +----------------+
14
+ # | jobs |
15
+ # +----------------+
16
+ # +------+ +-------+
17
+ # |tables| |results|
18
+ # +------+ +-------+
19
+
20
+ scheds = []
21
+ jobs = []
22
+ tables = []
23
+ results = []
24
+
25
+ s = client.schedules
26
+ s.each {|sched|
27
+ scheds << {:Name => sched.name, :Cron => sched.cron, :Result => sched.rset_name, :Query => sched.query}
28
+ }
29
+ scheds = scheds.sort_by {|map|
30
+ map[:Name]
31
+ }
32
+ x1, y1 = status_render(0, 0, "[Schedules]", scheds, :fields => [:Name, :Cron, :Result, :Query])
33
+
34
+ j = client.jobs(0, 4)
35
+ j.each {|job|
36
+ start = job.start_at
37
+ elapsed = cmd_format_elapsed(start, job.end_at)
38
+ jobs << {:JobID => job.job_id, :Status => job.status, :Query => job.query.to_s, :Start => (start ? start.localtime : ''), :Elapsed => elapsed, :Result => job.rset_name}
39
+ }
40
+ x2, y2 = status_render(0, 0, "[Jobs]", jobs, :fields => [:JobID, :Status, :Start, :Elapsed, :Result, :Query])
41
+
42
+ dbs = client.databases
43
+ dbs.map {|db|
44
+ db.tables.each {|table|
45
+ tables << {:Database => db.name, :Table => table.name, :Count => table.count.to_s}
46
+ }
47
+ }
48
+ x3, y3 = status_render(0, 0, "[Tables]", tables, :fields => [:Database, :Table, :Count])
49
+
50
+ r = client.result_sets
51
+ r.each {|rset|
52
+ results << {:Name => rset.name}
53
+ }
54
+ results = results.sort_by {|map|
55
+ map[:Name]
56
+ }
57
+ x4, y4 = status_render(x3+2, y3, "[Results]", results, :fields => [:Name])
58
+
59
+ (y3-y4-1).times do
60
+ print "\eD"
61
+ end
62
+ print "\eE"
63
+ end
64
+
65
+ private
66
+ def status_render(movex, movey, msg, *args)
67
+ lines = cmd_render_table(*args).split("\n")
68
+ lines.pop # remove 'N rows in set' line
69
+ lines.unshift(msg)
70
+ #lines.unshift("")
71
+
72
+ print "\e[#{movey}A" if movey > 0
73
+
74
+ max_width = 0
75
+ height = 0
76
+ lines.each {|line|
77
+ print "\e[#{movex}C" if movex > 0
78
+ puts line
79
+ width = line.length
80
+ max_width = width if max_width < width
81
+ height += 1
82
+ }
83
+
84
+ return movex+max_width, height
85
+ end
86
+
87
+ end
88
+ end
89
+
@@ -103,15 +103,34 @@ module Command
103
103
 
104
104
  def table_tail(op)
105
105
  to = nil
106
- count = 80
106
+ count = nil
107
107
 
108
108
  op.on('-t', '--to TIME', 'end time of logs to get') {|s|
109
- to = Time.parse(s).to_i
109
+ if s.to_i.to_s == s
110
+ to = s
111
+ else
112
+ to = Time.parse(s).to_i
113
+ end
110
114
  }
111
115
  op.on('-n', '--count N', 'number of logs to get', Integer) {|i|
112
116
  count = i
113
117
  }
114
118
 
119
+ if count == nil
120
+ # smart count calculation
121
+ begin
122
+ require "curses"
123
+ if Curses.stdscr.maxy - 1 <= 40
124
+ count = 5
125
+ else
126
+ count = 10
127
+ end
128
+ Curses.close_screen
129
+ rescue
130
+ count = 5
131
+ end
132
+ end
133
+
115
134
  db_name, table_name = op.cmd_parse
116
135
 
117
136
  client = get_client
@@ -1,5 +1,5 @@
1
1
  module TreasureData
2
2
 
3
- VERSION = '0.10.4'
3
+ VERSION = '0.10.5'
4
4
 
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: td
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.4
4
+ version: 0.10.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-10 00:00:00.000000000Z
12
+ date: 2011-12-04 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: msgpack
16
- requirement: &70139722729600 !ruby/object:Gem::Requirement
16
+ requirement: &70131804842780 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.4.4
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70139722729600
24
+ version_requirements: *70131804842780
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: json
27
- requirement: &70139722729020 !ruby/object:Gem::Requirement
27
+ requirement: &70131804842240 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.4.3
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70139722729020
35
+ version_requirements: *70131804842240
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: hirb
38
- requirement: &70139722728440 !ruby/object:Gem::Requirement
38
+ requirement: &70131804841700 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,21 +43,21 @@ dependencies:
43
43
  version: 0.4.5
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70139722728440
46
+ version_requirements: *70131804841700
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: td-client
49
- requirement: &70139722727900 !ruby/object:Gem::Requirement
49
+ requirement: &70131804841140 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: 0.8.3
54
+ version: 0.8.5
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70139722727900
57
+ version_requirements: *70131804841140
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: td-logger
60
- requirement: &70139722725860 !ruby/object:Gem::Requirement
60
+ requirement: &70131804840600 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: 0.3.7
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70139722725860
68
+ version_requirements: *70131804840600
69
69
  description:
70
70
  email:
71
71
  executables:
@@ -77,6 +77,7 @@ extra_rdoc_files:
77
77
  files:
78
78
  - lib/td.rb
79
79
  - lib/td/command/account.rb
80
+ - lib/td/command/aggr.rb
80
81
  - lib/td/command/apikey.rb
81
82
  - lib/td/command/common.rb
82
83
  - lib/td/command/db.rb
@@ -85,10 +86,12 @@ files:
85
86
  - lib/td/command/job.rb
86
87
  - lib/td/command/list.rb
87
88
  - lib/td/command/query.rb
89
+ - lib/td/command/result.rb
88
90
  - lib/td/command/runner.rb
89
91
  - lib/td/command/sched.rb
90
92
  - lib/td/command/schema.rb
91
93
  - lib/td/command/server.rb
94
+ - lib/td/command/status.rb
92
95
  - lib/td/command/table.rb
93
96
  - lib/td/config.rb
94
97
  - lib/td/version.rb