Top Level Namespace

Defined Under Namespace

Modules: DynamoDB Classes: Array

Instance Method Summary collapse

Instance Method Details

#evaluate_command(driver, cmd_arg) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/ddbcli/cli/functions.rb', line 69

def evaluate_command(driver, cmd_arg)
  cmd, arg = cmd_arg.split(/\s+/, 2).map {|i| i.strip }
  arg = nil if (arg || '').strip.empty?

  r = /\A#{Regexp.compile(cmd)}/i

  commands = {
    'help' => lambda {
      print_help(:pagerize => true)
    },

    ['exit', 'quit'] => lambda {
      exit 0
    },

    'timeout' => lambda {
      case arg
      when nil
        puts driver.timeout
      when /\d+/
        driver.timeout = arg.to_i
      else
        print_error('Invalid argument')
      end
    },

    'consistent' => lambda {
      if arg
        r_arg = /\A#{Regexp.compile(arg)}/i

        if r_arg =~ 'true'
          driver.consistent = true
        elsif r_arg =~ 'false'
          driver.consistent = false
        else
          print_error('Invalid argument')
        end
      else
        puts driver.consistent
      end
    },

    'iteratable' => lambda {
      if arg
        r_arg = /\A#{Regexp.compile(arg)}/i

        if r_arg =~ 'true'
          driver.iteratable = true
        elsif r_arg =~ 'false'
          driver.iteratable = false
        else
          print_error('Invalid argument')
        end
      else
        puts driver.iteratable
      end
    },

    'retry' => lambda {
      case arg
      when nil
        puts driver.retry_num
      when /\d+/
        driver.retry_num = arg.to_i
      else
        print_error('Invalid argument')
      end
    },

    'retry_interval' => lambda {
      case arg
      when nil
        puts driver.retry_intvl
      when /\d+/
        driver.retry_intvl = arg.to_i
      else
        print_error('Invalid argument')
      end
    },

    'debug' => lambda {
      if arg
        r_arg = /\A#{Regexp.compile(arg)}/i

        if r_arg =~ 'true'
          driver.debug = true
        elsif r_arg =~ 'false'
          driver.debug = false
        else
          print_error('Invalid argument')
        end
      else
        puts driver.debug
      end
    },

    'version' => lambda {
      print_version
    }
  }

  cmd_name, cmd_proc = commands.find do |name, proc|
    if name.kind_of?(Array)
      name.any? {|i| r =~ i }
    else
      r =~ name
    end
  end

  if cmd_proc
    cmd_proc.call
  else
    print_error('Unknown command')
  end
end

#evaluate_query(driver, src, opts = {}) ⇒ Object



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/ddbcli/cli/evaluate.rb', line 1

def evaluate_query(driver, src, opts = {})
  ss = StringScanner.new(src.dup)
  buf = ''

  until ss.eos?
    if (tok = ss.scan %r{[^`'";\\/#]+}) #'
      buf << tok
    elsif (tok = ss.scan /`(?:[^`]|``)*`/)
      buf << tok
    elsif (tok = ss.scan /'(?:[^']|'')*'/) #'
      buf << tok
    elsif (tok = ss.scan /"(?:[^"]|"")*"/) #"
      buf << tok
    elsif (tok = ss.scan %r{/\*/?(?:\n|[^/]|[^*]/)*\*/})
      # nothing to do
    elsif (tok = ss.scan /--[^\r\n]*(?:\r\n|\r|\n|\Z)/)
      # nothing to do
    elsif (tok = ss.scan /#[^\r\n]*(?:\r\n|\r|\n|\Z)/)
      # nothing to do
    elsif (tok = ss.scan /(?:\\;)/)
      buf << ';' # escape of ';'
    elsif (tok = ss.scan /(?:;|\\G)/)
      src.replace(ss.rest)
      query = buf
      buf = ''

      if query.strip.empty?
        print_error('No query specified')
        next
      end

      start_time = Time.new
      out = driver.execute(query, opts.merge(:inline => (tok != '\G')))
      elapsed = Time.now - start_time

      if out.kind_of?(DynamoDB::Driver::Rownum)
        print_rownum(out, opts.merge(:time => elapsed))
      elsif out.kind_of?(String)
        puts out
      elsif out
        opts = opts.merge(:inline => (tok != '\G'), :time => elapsed)
        print_json(out, $stdout, opts)
      end
    elsif (tok = ss.scan /./)
      buf << tok # 落ち穂拾い
    end
  end

  src.replace(buf.strip)
  buf
end

#parse_optionsObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/ddbcli/cli/options.rb', line 5

def parse_options
  options = OpenStruct.new
  options.access_key_id     = ENV['AWS_ACCESS_KEY_ID']
  options.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
  options.ddb_endpoint_or_region =
    ENV['AWS_REGION'] || ENV['DDB_ENDPOINT'] || ENV['DDB_REGION'] || 'dynamodb.us-east-1.amazonaws.com'

  # default value
  options.timeout     = 60
  options.consistent  = false
  options.iteratable  = false
  options.retry_num   = 3
  options.retry_intvl = 10
  options.debug       = false

  ARGV.options do |opt|
    opt.on('-k', '--access-key=ACCESS_KEY')          {|v| options.access_key_id          = v      }
    opt.on('-s', '--secret-key=SECRET_KEY')          {|v| options.secret_access_key      = v      }
    opt.on('-r', '--region=REGION_OR_ENDPOINT')      {|v| options.ddb_endpoint_or_region = v      }

    url_opt = proc do |v|
      uri = v
      uri = "http://#{uri}" unless uri =~ %r|\A\w+://|
      uri = URI.parse(uri)
      raise URI::InvalidURIError, "invalid shceme: #{v}" unless /\Ahttps?\Z/ =~ uri.scheme
      options.ddb_endpoint_or_region = uri
    end

    opt.on('',   '--url=URL', &url_opt)
    opt.on('',   '--uri=URL (DEPRECATION)', &url_opt)

    opt.on('-e', '--eval=COMMAND')                   {|v| options.command                = v      }
    opt.on('-t', '--timeout=SECOND', Integer)        {|v| options.timeout                = v.to_i }

    opt.on('',   '--import=TABLE,JSON_FILE') {|v|
      v = v.split(/\s*,\s*/, 2)
      options.import = {:table => v[0], :file => v[1]}
    }

    opt.on('',   '--consistent-read')                {    options.consistent             = true   }
    opt.on('',   '--iteratable')                     {    options.iteratable             = true   }
    opt.on('',   '--retry=NUM', Integer)             {|v| options.retry_num              = v.to_i }
    opt.on('',   '--retry-interval=SECOND', Integer) {|v| options.retry_intvl            = v.to_i }
    opt.on('',   '--debug')                          {    options.debug                  = true   }

    opt.on('-h', '--help') {
      puts opt.help
      exit
    }

    opt.parse!

    if options.ddb_endpoint_or_region.kind_of?(URI) and not (options.access_key_id and options.secret_access_key)
      options.access_key_id = 'scott'
      options.secret_access_key = 'tiger'
      puts 'Warning: dummy auth key was set because ACCESS_KEY/SECRET_KEY is not set'
    end

    unless options.access_key_id and options.secret_access_key and options.ddb_endpoint_or_region
      puts opt.help
      exit 1
    end
  end

  options
end


1
2
3
4
5
6
# File 'lib/ddbcli/cli/functions.rb', line 1

def print_error(errmsg, opts = {})
  errmsg = errmsg.join("\n") if errmsg.kind_of?(Array)
  errmsg = errmsg.strip.split("\n").map {|i| "// #{i.strip}" }.join("\n")
  errmsg += "\n\n" unless opts[:strip]
  $stderr.puts errmsg
end


3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/ddbcli/cli/help.rb', line 3

def print_help(options = {})
  doc =<<EOS
##### Query #####

SHOW TABLES [LIMIT num] [LIKE '...']
  display a table list

SHOW TABLE STATUS [LIKE '...']
  display table statues

SHOW REGIONS
  display a region list

SHOW CREATE TABLE table_name
  display a CREATE TABLE statement

CREATE TABLE table_name (
     key_name {STRING|NUMBER|BINARY} HASH
  [, key_name {STRING|NUMBER|BINARY} RANGE]
  [, INDEX index1_name (attr1 {STRING|NUMBER|BINARY}) {ALL|KEYS_ONLY|INCLUDE (attr, ...)}
   , INDEX index2_name (attr2 {STRING|NUMBER|BINARY}) {ALL|KEYS_ONLY|INCLUDE (attr, ...)}
   , ...]
  [, GLOBAL INDEX index1_name (hash_attr1 {STRING|NUMBER|BINARY} [, range_attr1 {STRING|NUMBER|BINARY}]) {ALL|KEYS_ONLY|INCLUDE (attr, ...)} [READ = num WRITE = num]
   , GLOBAL INDEX index2_name (hash_attr2 {STRING|NUMBER|BINARY} [, range_attr2 {STRING|NUMBER|BINARY}]) {ALL|KEYS_ONLY|INCLUDE (attr, ...)} [READ = num WRITE = num]
   , ...]
) READ = num WRITE = num [STREAM = {true|false|NEW_IMAGE|OLD_IMAGE|NEW_AND_OLD_IMAGES|KEYS_ONLY}]
  create a table

CREATE TABLE table_name LIKE another_table_name [READ = num WRITE = num] [STREAM = {true|false|NEW_IMAGE|OLD_IMAGE|NEW_AND_OLD_IMAGES|KEYS_ONLY}]
  create a table like another table

DROP TABLE table_name [, table_name2, ...]
  delete tables

ALTER TABLE table_name {READ = num WRITE = num|STREAM = {true|false|NEW_IMAGE|OLD_IMAGE|NEW_AND_OLD_IMAGES|KEYS_ONLY}}
  update the provisioned throughput

ALTER TABLE table_name CHANGE GLOBAL INDEX index_name READ = num WRITE = num
  update GSI provisioned throughput

ALTER TABLE table_name ADD GLOBAL INDEX index_name (hash_attr1 {STRING|NUMBER|BINARY} [, range_attr1 {STRING|NUMBER|BINARY}]) {ALL|KEYS_ONLY|INCLUDE (attr, ...)} READ = num WRITE = num
  add GSI

ALTER TABLE table_name DROP GLOBAL INDEX index_name
  delete GSI

GET {*|attr1,attr2,...} FROM table_name WHERE key1 = '...' AND ...
  get items

INSERT INTO table_name (attr1, attr2, ...) VALUES ('val1', 'val2', ...), ('val3', 'val4', ...), ...
INSERT INTO table_name SELECT ...
INSERT INTO table_name SELECT ALL ...
  create items

UPDATE table_name {SET|ADD} attr1 = 'val1', ... WHERE key1 = '...' AND ...
UPDATE ALL table_name {SET|ADD} attr1 = 'val1', ... [WHERE attr1 = '...' AND ...] [LIMIT limit]
  update items
  ("UPDATE" can update only one record. Please use "UPDATE ALL", when you update more than one.)

UPDATE table_name DEL[ETE] attr1, ... WHERE key1 = '...' AND ...
UPDATE ALL table_name DEL[ETE] attr1, ... [WHERE attr1 = '...' AND ...] [LIMIT limit]
  update items (delete attribute)

DELETE FROM table_name WHERE key1 = '...' AND ..
DELETE ALL FROM table_name WHERE [WHERE attr1 = '...' AND ...] [ORDER {ASC|DESC}] [LIMIT limit]
  delete items
  ("DELETE" can delete only one record. Please use "DELETE ALL", when you update more than one.)

SELECT {*|attr1,attr2,...|COUNT(*)} FROM table_name [USE INDEX (index_name)] [WHERE key1 = '...' AND ...] [HAVING attr1 = '...' AND ...] [ORDER {ASC|DESC}] [LIMIT limit]
SELECT ALL {*|attr1,attr2,...|COUNT(*)} FROM table_name [USE INDEX (index_name)] [WHERE attr1 = '...' AND ...] [LIMIT limit]
SELECT segment/total_segments {*|attr1,attr2,...|COUNT(*)} FROM table_name [USE INDEX (index_name)] [WHERE attr1 = '...' AND ...] [LIMIT limit]
  query using the Query/Scan action
  see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html

DESC[RIBE] table_name
  display information about the table

USE region_or_endpoint
  change an endpoint

NEXT
  display a continuation of a result
  (NEXT statement is published after SELECT statement)


##### Type #####

String
  'London Bridge is...',  "is broken down..." ...

Number
  10, 100, 0.3 ...

Binary
  x'123456789abcd...', x"123456789abcd..." ...

Identifier
  `ABCD...` or Non-keywords

Set
  ('String', 'String', ...), (1, 2, 3, ...)

List
  ['String', (1, 2, 3), {foo: 'FOO', bar: 'BAR'}, ...]

Map
  {key1:'String', "key2":(1, 2, 3), key3: ['FOO', 'BAR'], ...}

Bool
  true, false

Null
  null


##### Operator #####

Query (SELECT)
  = | <= | < | >= | > | BEGINS_WITH | BETWEEN
  see http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditions

Scan (SELECT ALL), QueryFilter (HAVING)
  = | <> | != | <= | < | >= | > | IS NOT NULL | IS NULL | CONTAINS | NOT CONTAINS | BEGINS_WITH | IN | BETWEEN
  see http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ScanFilter,
      http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-QueryFilter


##### Pass to Ruby/Shell #####

Ryby
  query | ruby_script

  ex) SELECT ALL * FROM employees WHERE gender = 'M' | birth_date.map {|i| Time.parse(i) };
      [
        "1957-09-16 00:00:00 +0900",
        "1954-12-16 00:00:00 +0900",
        "1964-05-23 00:00:00 +0900",
        ...

Shell
  query ! shell_command

  ex) SELECT ALL * FROM employees LIMIT 10 ! sort;
      {"birth_date"=>"1957-09-16", "emp_no"=>452020,...
      {"birth_date"=>"1963-07-14", "emp_no"=>16998, ...
      {"birth_date"=>"1964-04-30", "emp_no"=>225407,...
      ...


##### Output to a file #####

Overwrite
  SELECT ALL * FROM employees > 'foo.json';

Append
  SELECT ALL * FROM employees >> 'foo.json';


##### Command #####

.help                           display this message
.quit | .exit                   exit ddbcli
.consistent      (true|false)?  display ConsistentRead parameter or changes it
.iteratable      (true|false)?  display iteratable option or changes it
                                all results are displayed if true
.debug           (true|false)?  display a debug status or changes it
.retry           NUM?           display number of times of a retry or changes it
.retry_interval  SECOND?        display a retry interval second or changes it
.timeout         SECOND?        display a timeout second or changes it
.version                        display a version

EOS

  if options[:pagerize]
    Tempfile.open("ddbcli.#{$$}.#{Time.now.to_i}") do |f|
      f.puts(doc)
      f.flush

      unless system("less #{f.path}")
        puts doc
      end
    end
  else
    puts doc
  end
end


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/ddbcli/cli/functions.rb', line 21

def print_json(data, out, opts = {})
  str = nil
  last_evaluated_key = nil

  if data.kind_of?(DynamoDB::Iteratorable)
    last_evaluated_key = data.last_evaluated_key
    data = data.data
  end

  if data.kind_of?(Array) and opts[:inline]
    str = "[\n"

    data.each_with_index do |item, i|
      str << "  #{item.to_json}"
      str << ',' if i < (data.length - 1)
      str << "\n"
    end

    str << "]"
  else
    if data.kind_of?(Array) or data.kind_of?(Hash)
      str = JSON.pretty_generate(data)
    else
      str = data.to_json
    end
  end

  str.sub!(/(?:\r\n|\r|\n)*\Z/, "\n")

  if opts[:show_rows]
   if [Array, Hash].any? {|i| data.kind_of?(i) }
      str << "// #{data.length} #{data.length > 1 ? 'rows' : 'row'} in set"
    else
      str << '// 1 row in set'
    end

    str << " (%.2f sec)" % opts[:time] if opts[:time]
    str << "\n"
  end

  if last_evaluated_key
    str << "// has more\n"
  end

  str << "\n" unless opts[:strip]
  out.puts(str)
end


8
9
10
11
12
13
14
15
# File 'lib/ddbcli/cli/functions.rb', line 8

def print_rownum(data, opts = {})
  rownum = data.to_i
  msg = "// #{rownum} #{rownum > 1 ? 'rows' : 'row'} changed"
  msg << " (%.2f sec)" % opts[:time] if opts[:time]
  msg << "\n"
  msg << "\n" unless opts[:strip]
  puts msg
end


17
18
19
# File 'lib/ddbcli/cli/functions.rb', line 17

def print_version
  puts "#{File.basename($0)} #{Version}"
end