td 0.10.4 → 0.10.5

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.
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