td 0.7.3 → 0.7.4

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,10 @@
1
1
 
2
+ == 2011-08-18 version 0.7.4
3
+
4
+ * Added set-schema subcommand
5
+ * Added describe-table subcommand
6
+
7
+
2
8
  == 2011-08-15 version 0.7.3
3
9
 
4
10
  * show-jobs: removed --from and --around options
@@ -36,12 +36,43 @@ class API
36
36
  unless name =~ /^([a-z0-9_]+)$/
37
37
  raise "Name must consist only of alphabets, numbers, '_'."
38
38
  end
39
+ name
39
40
  end
40
41
 
41
42
  def self.validate_table_name(name)
42
43
  validate_database_name(name)
43
44
  end
44
45
 
46
+ def self.validate_column_name(name)
47
+ name = name.to_s
48
+ if name.empty?
49
+ raise "Empty column name is not allowed"
50
+ end
51
+ if 32 < name.length
52
+ raise "Column name must be to 32 characters, got #{name.length} characters."
53
+ end
54
+ unless name =~ /^([a-z0-9_]+)$/
55
+ raise "Column name must consist only of alphabets, numbers, '_'."
56
+ end
57
+ end
58
+
59
+ def self.normalize_type_name(name)
60
+ case name
61
+ when /int/i, /integer/i
62
+ "int"
63
+ when /long/i, /bigint/i
64
+ "long"
65
+ when /string/i
66
+ "string"
67
+ when /float/i
68
+ "float"
69
+ when /double/i
70
+ "double"
71
+ else
72
+ raise "Type name must eather of int, long, string float or double"
73
+ end
74
+ end
75
+
45
76
  ####
46
77
  ## Database API
47
78
  ##
@@ -94,19 +125,20 @@ class API
94
125
  name = m['name']
95
126
  type = (m['type'] || '?').to_sym
96
127
  count = (m['count'] || 0).to_i # TODO?
97
- result[name] = [type, count]
128
+ schema = JSON.parse(m['schema']) # FIXME?
129
+ result[name] = [type, schema, count]
98
130
  }
99
131
  return result
100
132
  end
101
133
 
102
- # => true
103
- def create_table(db, table, type)
134
+ def create_log_or_item_table(db, table, type)
104
135
  code, body, res = post("/v3/table/create/#{e db}/#{e table}/#{type}")
105
136
  if code != "200"
106
137
  raise_error("Create #{type} table failed", res)
107
138
  end
108
139
  return true
109
140
  end
141
+ private :create_log_or_item_table
110
142
 
111
143
  # => true
112
144
  def create_log_table(db, table)
@@ -118,6 +150,25 @@ class API
118
150
  create_table(db, table, :item)
119
151
  end
120
152
 
153
+ def create_table(db, table, type)
154
+ schema = schema.to_s
155
+ code, body, res = post("/v3/table/create/#{e db}/#{e table}/#{type}")
156
+ if code != "200"
157
+ raise_error("Create #{type} table failed", res)
158
+ end
159
+ return true
160
+ end
161
+ private :create_table
162
+
163
+ # => true
164
+ def update_schema(db, table, schema_json)
165
+ code, body, res = post("/v3/table/update-schema/#{e db}/#{e table}", {'schema'=>schema_json})
166
+ if code != "200"
167
+ raise_error("Create schema table failed", res)
168
+ end
169
+ return true
170
+ end
171
+
121
172
  # => type:Symbol
122
173
  def delete_table(db, table)
123
174
  code, body, res = post("/v3/table/delete/#{e db}/#{e table}")
@@ -59,18 +59,18 @@ class Client
59
59
  end
60
60
 
61
61
  # => true
62
- def create_table(db_name, table_name, type)
63
- @api.create_table(db_name, table_name, type)
62
+ def create_log_table(db_name, table_name)
63
+ @api.create_log_table(db_name, table_name)
64
64
  end
65
65
 
66
66
  # => true
67
- def create_log_table(db_name, table_name)
68
- create_table(db_name, table_name, :log)
67
+ def create_item_table(db_name, table_name)
68
+ @api.create_item_table(db_name, table_name)
69
69
  end
70
70
 
71
71
  # => true
72
- def create_item_table(db_name, table_name)
73
- create_table(db_name, table_name, :item)
72
+ def update_schema(db_name, table_name, schema)
73
+ @api.update_schema(db_name, table_name, schema.to_json)
74
74
  end
75
75
 
76
76
  # => type:Symbol
@@ -81,17 +81,17 @@ class Client
81
81
  # => [Table]
82
82
  def tables(db_name)
83
83
  m = @api.list_tables(db_name)
84
- m.map {|table_name,(type,count)|
85
- Table.new(self, db_name, table_name, type, count)
84
+ m.map {|table_name,(type,schema,count)|
85
+ schema = Schema.new.from_json(schema)
86
+ Table.new(self, db_name, table_name, type, schema, count)
86
87
  }
87
88
  end
88
89
 
89
90
  # => Table
90
91
  def table(db_name, table_name)
91
- m = @api.list_tables(db_name)
92
- m.each_pair {|name,(type,count)|
93
- if name == table_name
94
- return Table.new(self, db_name, name, type, count)
92
+ tables(db_name).each {|t|
93
+ if t.name == table_name
94
+ return t
95
95
  end
96
96
  }
97
97
  raise NotFoundError, "Table '#{db_name}.#{table_name}' does not exist"
@@ -168,16 +168,12 @@ class Database < Model
168
168
  @tables
169
169
  end
170
170
 
171
- def create_table(name, type)
172
- @client.create_table(@db_name, name, type)
173
- end
174
-
175
171
  def create_log_table(name)
176
- create_table(name, :log)
172
+ @client.create_log_table(@db_name, name)
177
173
  end
178
174
 
179
175
  def create_item_table(name)
180
- create_table(name, :item)
176
+ @client.create_item_table(@db_name, name)
181
177
  end
182
178
 
183
179
  def table(table_name)
@@ -194,28 +190,24 @@ class Database < Model
194
190
  end
195
191
 
196
192
  class Table < Model
197
- def initialize(client, db_name, table_name, type, count)
193
+ def initialize(client, db_name, table_name, type, schema, count)
198
194
  super(client)
199
195
  @db_name = db_name
200
196
  @table_name = table_name
201
197
  @type = type
198
+ @schema = schema
202
199
  @count = count
203
200
  end
204
201
 
205
- attr_reader :type, :count
202
+ attr_reader :type, :db_name, :table_name, :schema, :count
206
203
 
207
- def database_name
208
- @db_name
209
- end
204
+ alias database_name db_name
205
+ alias name table_name
210
206
 
211
207
  def database
212
208
  @client.database(@db_name)
213
209
  end
214
210
 
215
- def name
216
- @table_name
217
- end
218
-
219
211
  def identifier
220
212
  "#{@db_name}.#{@table_name}"
221
213
  end
@@ -225,6 +217,50 @@ class Table < Model
225
217
  end
226
218
  end
227
219
 
220
+ class Schema
221
+ class Field
222
+ def initialize(name, type)
223
+ @name = name
224
+ @type = type
225
+ end
226
+ attr_reader :name
227
+ attr_reader :type
228
+ end
229
+
230
+ def self.parse(cols)
231
+ fields = cols.split(',').map {|col|
232
+ name, type, *_ = col.split(':')
233
+ Field.new(name, type)
234
+ }
235
+ Schema.new(fields)
236
+ end
237
+
238
+ def initialize(fields=[])
239
+ @fields = fields
240
+ end
241
+
242
+ attr_reader :fields
243
+
244
+ def add_field(name, type)
245
+ @fields << Field.new(name, type)
246
+ end
247
+
248
+ def to_s
249
+ @fields.map {|f| "#{f.name}:#{f.type}" }.join(',')
250
+ end
251
+
252
+ def to_json(*args)
253
+ @fields.map {|f| [f.name, f.type] }.to_json(*args)
254
+ end
255
+
256
+ def from_json(obj)
257
+ @fields = obj.map {|f|
258
+ Field.new(f[0], f[1])
259
+ }
260
+ self
261
+ end
262
+ end
263
+
228
264
  class Job < Model
229
265
  def initialize(client, job_id, type, query, status=nil, url=nil, debug=nil, start_at=nil, end_at=nil, result=nil)
230
266
  super(client)
@@ -332,6 +368,5 @@ class Job < Model
332
368
  end
333
369
  end
334
370
 
335
-
336
371
  end
337
372
 
@@ -1,11 +1,22 @@
1
1
 
2
2
  module TreasureData
3
+
4
+ autoload :API, 'td/api'
5
+ autoload :Client, 'td/client'
6
+ autoload :Database, 'td/client'
7
+ autoload :Table, 'td/client'
8
+ autoload :Schema, 'td/client'
9
+ autoload :Job, 'td/client'
10
+
3
11
  module Command
4
12
  private
5
13
  def cmd_opt(name, *args)
6
14
  if args.last.to_s =~ /_$/
7
15
  multi = true
8
16
  args.push args.pop.to_s[0..-2]+'...'
17
+ elsif args.last.to_s =~ /_\?$/
18
+ multi = true
19
+ args.push args.pop.to_s[0..-3]+'...?'
9
20
  end
10
21
 
11
22
  req_args, opt_args = args.partition {|a| a.to_s !~ /\?$/ }
@@ -61,7 +72,6 @@ EOF
61
72
  unless apikey
62
73
  raise ConfigError, "Account is not configured."
63
74
  end
64
- require 'td/client'
65
75
  Client.new(apikey)
66
76
  end
67
77
 
@@ -33,25 +33,31 @@ module List
33
33
  add_list 'server', 'server-status', 'Show status of the Treasure Data server'
34
34
  add_list 'database', 'show-databases', 'Show list of databases'
35
35
  add_list 'table', 'show-tables', 'Show list of tables'
36
- add_list 'query', 'show-jobs', 'Show list of jobs'
37
36
  add_list 'database', 'create-database', 'Create a database'
38
37
  add_list 'table', 'create-log-table', 'Create a log table'
39
38
  #add_list 'table', 'create-item-table', 'Create a item table'
39
+ add_list 'table', 'set-schema', 'Set a schema on a table'
40
+ add_list 'table', 'describe-table', 'Describe information of a table'
40
41
  add_list 'database', 'drop-database', 'Delete a database'
41
42
  add_list 'table', 'drop-table', 'Delete a table'
42
43
  add_list 'query', 'query', 'Start a query'
43
44
  add_list 'query', 'job', 'Show status and result of a job'
45
+ add_list 'query', 'show-jobs', 'Show list of jobs'
44
46
  add_list 'import', 'import', 'Import files to a table'
45
47
  add_list 'list', 'version', 'Show version'
46
48
 
47
49
  add_alias 'show-dbs', 'show-databases'
48
- add_alias 'show-database', 'show-databases'
50
+ add_alias 'databases', 'show-databases'
51
+ add_alias 'dbs', 'show-databases'
49
52
  add_alias 'create-db', 'create-databases'
50
53
  add_alias 'drop-db', 'create-databases'
51
- add_alias 'show-table', 'show-tables'
54
+ add_alias 'tables', 'show-tables'
55
+ add_alias 'table', 'describe-table'
56
+ add_alias 'show-table', 'describe-table'
52
57
  add_alias 'delete-database', 'drop-database'
53
58
  add_alias 'delete-table', 'drop-table'
54
59
  add_alias 'jobs', 'show-jobs'
60
+ add_alias 'update-schema', 'set-schema'
55
61
 
56
62
  add_guess 'create-table', 'create-log-table'
57
63
  add_guess 'drop-log-table', 'drop-table'
@@ -2,13 +2,17 @@
2
2
  module TreasureData
3
3
  module Command
4
4
 
5
- def create_table_type(type, db_name, table_name)
5
+ def create_log_or_item_table(mode_log, db_name, table_name)
6
6
  client = get_client
7
7
 
8
8
  API.validate_table_name(table_name)
9
9
 
10
10
  begin
11
- client.create_table(db_name, table_name, type)
11
+ if mode_log
12
+ client.create_log_table(db_name, table_name)
13
+ else
14
+ client.create_item_table(db_name, table_name)
15
+ end
12
16
  rescue NotFoundError
13
17
  cmd_debug_error $!
14
18
  $stderr.puts "Database '#{db_name}' does not exist."
@@ -22,20 +26,20 @@ module Command
22
26
 
23
27
  $stderr.puts "Table '#{db_name}.#{table_name}' is created."
24
28
  end
25
- private :create_table_type
29
+ private :create_log_or_item_table
26
30
 
27
31
  def create_log_table
28
32
  op = cmd_opt 'create-log-table', :db_name, :table_name
29
33
  db_name, table_name = op.cmd_parse
30
34
 
31
- create_table_type(:log, db_name, table_name)
35
+ create_log_or_item_table(true, db_name, table_name)
32
36
  end
33
37
 
34
38
  def create_item_table
35
39
  op = cmd_opt 'create-item-table', :db_name, :table_name
36
40
  db_name, table_name = op.cmd_parse
37
41
 
38
- create_table_type(:item, db_name, table_name)
42
+ create_log_or_item_table(false, db_name, table_name)
39
43
  end
40
44
 
41
45
  def drop_table
@@ -56,6 +60,42 @@ module Command
56
60
  $stderr.puts "Table '#{db_name}.#{table_name}' is deleted."
57
61
  end
58
62
 
63
+ def set_schema
64
+ op = cmd_opt 'set-schema', :db_name, :table_name, :columns_?
65
+
66
+ db_name, table_name, *columns = op.cmd_parse
67
+
68
+ schema = Schema.new
69
+ columns.each {|column|
70
+ name, type = column.split(':',2)
71
+ name = name.to_s
72
+ type = type.to_s
73
+
74
+ API.validate_column_name(name)
75
+ type = API.normalize_type_name(type)
76
+
77
+ if schema.fields.find {|f| f.name == name }
78
+ $stderr.puts "Column name '#{name}' is duplicated."
79
+ exit 1
80
+ end
81
+ schema.add_field(name, type)
82
+
83
+ if name == 'v' || name == 'time'
84
+ $stderr.puts "Column name '#{name}' is reserved."
85
+ exit 1
86
+ end
87
+ }
88
+
89
+ client = get_client
90
+
91
+ find_table(client, db_name, table_name)
92
+
93
+ client.update_schema(db_name, table_name, schema)
94
+
95
+ $stderr.puts "Schema is updated on #{db_name}.#{table_name} table."
96
+ $stderr.puts "Use '#{$prog} describe-table #{db_name} #{table_name}' to confirm the schema."
97
+ end
98
+
59
99
  def show_tables
60
100
  op = cmd_opt 'show-tables', :db_name?
61
101
  db_name = op.cmd_parse
@@ -72,14 +112,17 @@ module Command
72
112
  rows = []
73
113
  dbs.each {|db|
74
114
  db.tables.each {|table|
75
- rows << {:Database => db.name, :Table => table.name, :Type => table.type.to_s, :Count => table.count.to_s}
115
+ pschema = table.schema.fields.map {|f|
116
+ "#{f.name}:#{f.type}"
117
+ }.join(', ')
118
+ rows << {:Database => db.name, :Table => table.name, :Type => table.type.to_s, :Count => table.count.to_s, :Schema=>pschema.to_s}
76
119
  }
77
120
  }
78
121
  rows = rows.sort_by {|map|
79
122
  [map[:Database], map[:Type].size, map[:Table]]
80
123
  }
81
124
 
82
- puts cmd_render_table(rows, :fields => [:Database, :Table, :Type, :Count])
125
+ puts cmd_render_table(rows, :fields => [:Database, :Table, :Type, :Count, :Schema])
83
126
 
84
127
  if rows.empty?
85
128
  if db_name
@@ -95,6 +138,24 @@ module Command
95
138
  end
96
139
  end
97
140
 
141
+ def describe_table
142
+ op = cmd_opt 'describe-table', :db_name, :table_name
143
+
144
+ db_name, table_name = op.cmd_parse
145
+
146
+ client = get_client
147
+
148
+ table = find_table(client, db_name, table_name)
149
+
150
+ puts "Name : #{table.db_name}.#{table.name}"
151
+ puts "Type : #{table.type}"
152
+ puts "Count : #{table.count}"
153
+ puts "Schema : ("
154
+ table.schema.fields.each {|f|
155
+ puts " #{f.name}:#{f.type}"
156
+ }
157
+ puts ")"
158
+ end
98
159
  end
99
160
  end
100
161
 
@@ -1,9 +1,11 @@
1
1
 
2
2
  require 'optparse'
3
+ require 'td/version'
3
4
 
4
5
  $prog = File.basename($0)
5
6
 
6
7
  op = OptionParser.new
8
+ op.version = TreasureData::VERSION
7
9
  op.banner = <<EOF
8
10
  usage: #{$prog} [options] COMMAND [args]
9
11
 
@@ -1,5 +1,5 @@
1
1
  module TreasureData
2
2
 
3
- VERSION = '0.7.2'
3
+ VERSION = '0.7.4'
4
4
 
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: td
3
3
  version: !ruby/object:Gem::Version
4
- hash: 5
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
- - 3
10
- version: 0.7.3
9
+ - 4
10
+ version: 0.7.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sadayuki Furuhashi
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-15 00:00:00 +09:00
18
+ date: 2011-08-18 00:00:00 +09:00
19
19
  default_executable: td
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency