diff --git a/src/main/ruby/hbase/hbase.rb b/src/main/ruby/hbase/hbase.rb index 2c37840..5ab6a72 100644 --- a/src/main/ruby/hbase/hbase.rb +++ b/src/main/ruby/hbase/hbase.rb @@ -45,8 +45,8 @@ module Hbase end # Create new one each time - def table(table, formatter) - ::Hbase::Table.new(configuration, table, formatter) + def table(table, shell) + ::Hbase::Table.new(configuration, table, shell) end def replication_admin(formatter) diff --git a/src/main/ruby/hbase/table.rb b/src/main/ruby/hbase/table.rb index 41dcf21..18d72e8 100644 --- a/src/main/ruby/hbase/table.rb +++ b/src/main/ruby/hbase/table.rb @@ -25,15 +25,45 @@ include Java module Hbase class Table include HBaseConstants + + # Add the command 'name' to table s.t. the shell command also called via 'name' + # and has an internal method also called 'name'. + # + # e.g. name = scan, adds table.scan which calls Scan.scan + def self.add_shell_command(name) + self.add_command(name, name, name) + end + + # add a named command to the table instance + # + # name - name of the command that should added to the table + # (eg. sending 'scan' here would allow you to do table.scan) + # shell_command - name of the command in the shell + # internal_method_name - name of the method in the shell command to forward the call + def self.add_command(name, shell_command, internal_method_name) + method = name.to_sym + self.class_eval do + define_method method do |*args| + @shell.internal_command(shell_command, internal_method_name, self, *args) + end + end + end + #--------------------------------------------------------------------------------------------- + + # class attributes attr_reader :table + #get the name of the table + attr_reader :name - def initialize(configuration, table_name, formatter) + def initialize(configuration, table_name, shell) @table = org.apache.hadoop.hbase.client.HTable.new(configuration, table_name) + @name = table_name + @shell = shell end #---------------------------------------------------------------------------------------------- # Put a cell 'value' at specified table/row/column - def put(row, column, value, timestamp = nil) + def put_internal(row, column, value, timestamp = nil) p = org.apache.hadoop.hbase.client.Put.new(row.to_s.to_java_bytes) family, qualifier = parse_column_name(column) if timestamp @@ -46,13 +76,13 @@ module Hbase #---------------------------------------------------------------------------------------------- # Delete a cell - def delete(row, column, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) - deleteall(row, column, timestamp) + def delete_internal(row, column, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) + deleteall_internal(row, column, timestamp) end #---------------------------------------------------------------------------------------------- # Delete a row - def deleteall(row, column = nil, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) + def deleteall_internal(row, column = nil, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) d = org.apache.hadoop.hbase.client.Delete.new(row.to_s.to_java_bytes, timestamp, nil) if column family, qualifier = parse_column_name(column) @@ -63,7 +93,7 @@ module Hbase #---------------------------------------------------------------------------------------------- # Increment a counter atomically - def incr(row, column, value = nil) + def incr_internal(row, column, value = nil) value ||= 1 family, qualifier = parse_column_name(column) @table.incrementColumnValue(row.to_s.to_java_bytes, family, qualifier, value) @@ -71,7 +101,7 @@ module Hbase #---------------------------------------------------------------------------------------------- # Count rows in a table - def count(interval = 1000, caching_rows = 10) + def count_internal(interval = 1000, caching_rows = 10) # We can safely set scanner caching with the first key only filter scan = org.apache.hadoop.hbase.client.Scan.new scan.cache_blocks = false @@ -98,7 +128,7 @@ module Hbase #---------------------------------------------------------------------------------------------- # Get from table - def get(row, *args) + def get_internal(row, *args) get = org.apache.hadoop.hbase.client.Get.new(row.to_s.to_java_bytes) maxlength = -1 @@ -188,7 +218,7 @@ module Hbase #---------------------------------------------------------------------------------------------- # Fetches and decodes a counter value from hbase - def get_counter(row, column) + def get_counter_internal(row, column) family, qualifier = parse_column_name(column.to_s) # Format get request get = org.apache.hadoop.hbase.client.Get.new(row.to_s.to_java_bytes) @@ -205,8 +235,8 @@ module Hbase end #---------------------------------------------------------------------------------------------- - # Scans whole table or a range of keys and returns rows matching specific criterias - def scan(args = {}) + # Scans whole table or a range of keys and returns rows matching specific criteria + def scan_internal(args = {}) unless args.kind_of?(Hash) raise ArgumentError, "Arguments should be a hash. Failed to parse #{args.inspect}, #{args.class}" end @@ -298,8 +328,81 @@ module Hbase return ((block_given?) ? count : res) end + #---------------------------- + # Add general administration utilities to the shell + # each of the names below adds this method name to the table + # by callling the corresponding method in the shell + # Add single method utilities to the current class + # Generally used for admin functions which just have one name and take the table name + def self.add_admin_utils(*args) + args.each do |method| + define_method method do + @shell.command(method, @name) + end + end + end + + add_admin_utils :enable, :disable, :flush, :drop + + #---------------------------- + #give the general help for the table + # or the named command + def help (command = nil) + #if there is a command, get the per-command help from the shell + if command + begin + return @shell.help_command(command) + rescue NoMethodError + puts "Command \'#{command}\' does not exist. Please see general table help." + return nil + end + end + return <<-EOF + Access an HTable. The table is accessed based on the name of the table + and the current default configuration (hbase-site.xml and hbase-default.xml). + + There are a variety of things you can do with a table. For instance, say + you have a table 't': + + hbase> t.scan + + Will get you all the rows in t. + + Similarly, to put a row into table t, assuming it was created with + the column family 'fam': + + hbase> t.put 'row', 'fam', 'value' + + Other commands include things like: get, delete, deleteall, + get_all_columns, get_counter, count, incr. These functions, along with + the standard JRuby object methods are also available via tab completion. + + For more information on how to use each of these commands, you can also just + type: + + hbase> t.help 'scan' + + which will output more information on how to use that command. + + You can also do general admin actions directly on a table; things like enable, disable, + flush and drop just by typing: + + hbase> t.enable + hbase> t.flush + hbase> t.disable + hbase> t.drop + + Note that after dropping a table, your reference to it becomes useless and further usage + is undefined (and not recommended). + + General help on this commands can also be obtained similarly to other table-centric commands. + EOF + end + #---------------------------------------------------------------------------------------- # Helper methods + #everthing below here is 'private' - can only be called from within the class context + private # Returns a list of column names in the table def get_all_columns @@ -345,6 +448,5 @@ module Hbase end (maxlength != -1) ? val[0, maxlength] : val end - end end diff --git a/src/main/ruby/shell.rb b/src/main/ruby/shell.rb index 53f3de8..7366dc2 100644 --- a/src/main/ruby/shell.rb +++ b/src/main/ruby/shell.rb @@ -80,7 +80,7 @@ module Shell end def hbase_table(name) - hbase.table(name, formatter) + hbase.table(name, self) end def hbase_replication_admin @@ -93,10 +93,15 @@ module Shell def export_commands(where) ::Shell.commands.keys.each do |cmd| + # here where is the IRB namespace + # this method just adds the call to the specified command + # which just references back to 'this' shell object + # a decently extensible way to add commands where.send :instance_eval, <<-EOF def #{cmd}(*args) - @shell.command('#{cmd}', *args) + ret = @shell.command('#{cmd}', *args) puts + return ret end EOF end @@ -106,8 +111,17 @@ module Shell ::Shell.commands[command.to_s].new(self) end + #call the method 'command' on the specified command def command(command, *args) - command_instance(command).command_safe(self.debug, *args) + internal_command(command, :command, *args) + end + + #call a specific internal method in the command instance + # command - name of the command to call + # method_name - name of the method on the command to call. Defaults to just 'command' + # args - to be passed to the named method + def internal_command(command, method_name= :command, *args) + command_instance(command).command_safe(self.debug,method_name, *args) end def print_banner @@ -234,6 +248,7 @@ Shell.load_command_group( show_filters alter_status alter_async + get_table ] ) diff --git a/src/main/ruby/shell/commands.rb b/src/main/ruby/shell/commands.rb index af6df33..c9004fa 100644 --- a/src/main/ruby/shell/commands.rb +++ b/src/main/ruby/shell/commands.rb @@ -21,14 +21,18 @@ module Shell module Commands class Command - attr_accessor :shell def initialize(shell) - self.shell = shell + @shell = shell end - def command_safe(debug, *args) - translate_hbase_exceptions(*args) { command(*args) } + #wrap an execution of cmd to catch hbase exceptions + # cmd - command name to execture + # args - arguments to pass to the command + def command_safe(debug, cmd = :command, *args) + # send is internal ruby method to call 'cmd' with *args + #(everything is a message, so this is just the formal semantics to support that idiom) + translate_hbase_exceptions(*args) { send(cmd,*args) } rescue => e puts puts "ERROR: #{e}" @@ -37,30 +41,28 @@ module Shell puts "Here is some help for this command:" puts help puts - ensure - return nil end def admin - shell.hbase_admin + @shell.hbase_admin end def table(name) - shell.hbase_table(name) + @shell.hbase_table(name) end def replication_admin - shell.hbase_replication_admin + @shell.hbase_replication_admin end def security_admin - shell.hbase_security_admin + @shell.hbase_security_admin end #---------------------------------------------------------------------- def formatter - shell.formatter + @shell.formatter end def format_simple_command @@ -70,6 +72,14 @@ module Shell formatter.footer(now) end + def format_and_return_simple_command + now = Time.now + ret = yield + formatter.header + formatter.footer(now) + return ret + end + def translate_hbase_exceptions(*args) yield rescue org.apache.hadoop.hbase.TableNotFoundException diff --git a/src/main/ruby/shell/commands/count.rb b/src/main/ruby/shell/commands/count.rb index 6596441..37b8130 100644 --- a/src/main/ruby/shell/commands/count.rb +++ b/src/main/ruby/shell/commands/count.rb @@ -39,6 +39,10 @@ EOF end def command(table, params = {}) + count(table(table), params) + end + + def count(table, params = {}) # If the second parameter is an integer, then it is the old command syntax params = { 'INTERVAL' => params } if params.kind_of?(Fixnum) @@ -51,7 +55,7 @@ EOF # Call the counter method now = Time.now formatter.header - count = table(table).count(params['INTERVAL'].to_i, params['CACHE'].to_i) do |cnt, row| + count = table.count_internal(params['INTERVAL'].to_i, params['CACHE'].to_i) do |cnt, row| formatter.row([ "Current count: #{cnt}, row: #{row}" ]) end formatter.footer(now, count) @@ -59,3 +63,6 @@ EOF end end end + +#Add the method table.count that calls count.count +::Hbase::Table.add_shell_command("count") diff --git a/src/main/ruby/shell/commands/create.rb b/src/main/ruby/shell/commands/create.rb index 14c1b0f..0845ada 100644 --- a/src/main/ruby/shell/commands/create.rb +++ b/src/main/ruby/shell/commands/create.rb @@ -38,13 +38,22 @@ Examples: hbase> # Optionally pre-split the table into NUMREGIONS, using hbase> # SPLITALGO ("HexStringSplit", "UniformSplit" or classname) hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'} + + You can also keep around a reference to the created table: + + hbase> t1 = create 't1', 'f1' + + Which gives you a reference to the table named 't1', on which you can then + call methods. EOF end def command(table, *args) format_simple_command do - admin.create(table, *args) + ret = admin.create(table, *args) end + #and then return the table you just created + table(table) end end end diff --git a/src/main/ruby/shell/commands/delete.rb b/src/main/ruby/shell/commands/delete.rb index 12bc405..83bf5d2 100644 --- a/src/main/ruby/shell/commands/delete.rb +++ b/src/main/ruby/shell/commands/delete.rb @@ -30,14 +30,21 @@ versions. To delete a cell from 't1' at row 'r1' under column 'c1' marked with the time 'ts1', do: hbase> delete 't1', 'r1', 'c1', ts1 -EOF + EOF end def command(table, row, column, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) + delete(table(table), row, column, timestamp) + end + + def delete(table, row, column, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) format_simple_command do - table(table).delete(row, column, timestamp) + table.delete_internal(row, column, timestamp) end end end end end + +#Add the method table.delete that calls delete.delete +::Hbase::Table.add_shell_command("delete") \ No newline at end of file diff --git a/src/main/ruby/shell/commands/deleteall.rb b/src/main/ruby/shell/commands/deleteall.rb index 5731b60..af71f1a 100644 --- a/src/main/ruby/shell/commands/deleteall.rb +++ b/src/main/ruby/shell/commands/deleteall.rb @@ -29,14 +29,21 @@ a column and timestamp. Examples: hbase> deleteall 't1', 'r1' hbase> deleteall 't1', 'r1', 'c1' hbase> deleteall 't1', 'r1', 'c1', ts1 -EOF + EOF end - def command(table, row, column = nil, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) + def command(table, row, column, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) + deleteall(table(table), row, column, timestamp) + end + + def deleteall(table, row, column = nil, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP) format_simple_command do - table(table).deleteall(row, column, timestamp) + table.deleteall_internal(row, column, timestamp) end end end end end + +#Add the method table.deleteall that calls deleteall.deleteall +::Hbase::Table.add_shell_command("deleteall") \ No newline at end of file diff --git a/src/main/ruby/shell/commands/get.rb b/src/main/ruby/shell/commands/get.rb index 754c3d6..88829b8 100644 --- a/src/main/ruby/shell/commands/get.rb +++ b/src/main/ruby/shell/commands/get.rb @@ -40,10 +40,14 @@ EOF end def command(table, row, *args) + get(table(table), row, *args) + end + + def get(table, row, *args) now = Time.now formatter.header(["COLUMN", "CELL"]) - table(table).get(row, *args) do |column, value| + table.get_internal(row, *args) do |column, value| formatter.row([ column, value ]) end @@ -52,3 +56,6 @@ EOF end end end + +#add get command to table +::Hbase::Table.add_shell_command('get') diff --git a/src/main/ruby/shell/commands/get_counter.rb b/src/main/ruby/shell/commands/get_counter.rb index 3cbe226..4e182d5 100644 --- a/src/main/ruby/shell/commands/get_counter.rb +++ b/src/main/ruby/shell/commands/get_counter.rb @@ -31,8 +31,11 @@ and the data should be binary encoded. Example: EOF end + def command(table, row, column, value) + get_counter(table(table), row, column, value) + end def command(table, row, column, value = nil) - if cnt = table(table).get_counter(row, column) + if cnt = table.get_counter_internal(row, column) puts "COUNTER VALUE = #{cnt}" else puts "No counter found at specified coordinates" @@ -41,3 +44,5 @@ EOF end end end + +::Hbase::Table.add_shell_command('get_counter') diff --git a/src/main/ruby/shell/commands/get_table.rb b/src/main/ruby/shell/commands/get_table.rb new file mode 100644 index 0000000..615addd --- /dev/null +++ b/src/main/ruby/shell/commands/get_table.rb @@ -0,0 +1,28 @@ +module Shell + module Commands + class GetTable < Command + def help + return <<-EOF +Get the given table name and return it as an actual object to +be manipulated by the user. See table.help for more information +on how to use the table. +Eg. + + hbase> t1 = get_table 't1' + +returns the table named 't1' as a table object. You can then do + + hbase> t1.help + +which will then print the help for that table. +EOF + end + + def command(table, *args) + format_and_return_simple_command do + table(table) + end + end + end + end +end diff --git a/src/main/ruby/shell/commands/incr.rb b/src/main/ruby/shell/commands/incr.rb index 38a2fc5..6f7bab8 100644 --- a/src/main/ruby/shell/commands/incr.rb +++ b/src/main/ruby/shell/commands/incr.rb @@ -33,10 +33,17 @@ To increment a cell value in table 't1' at row 'r1' under column EOF end - def command(table, row, column, value = nil) - cnt = table(table).incr(row, column, value) + def command(table, row, column, value) + incr(table(table), row, column, value) + end + + def incr(table, row, column, value = nil) + cnt = table.incr_internal(row, column, value) puts "COUNTER VALUE = #{cnt}" end end end end + +#add incr comamnd to Table +::Hbase::Table.add_shell_command("incr") \ No newline at end of file diff --git a/src/main/ruby/shell/commands/put.rb b/src/main/ruby/shell/commands/put.rb index dde0433..72d15db 100644 --- a/src/main/ruby/shell/commands/put.rb +++ b/src/main/ruby/shell/commands/put.rb @@ -32,10 +32,17 @@ EOF end def command(table, row, column, value, timestamp = nil) + put table(table), row, column, value, timestamp + end + + def put(table, row, column, value, timestamp = nil) format_simple_command do - table(table).put(row, column, value, timestamp) + table.put_internal(row, column, value, timestamp) end end end end end + +#Add the method table.put that calls Put.put +::Hbase::Table.add_shell_command("put") \ No newline at end of file diff --git a/src/main/ruby/shell/commands/scan.rb b/src/main/ruby/shell/commands/scan.rb index e58aaac..1ac9313 100644 --- a/src/main/ruby/shell/commands/scan.rb +++ b/src/main/ruby/shell/commands/scan.rb @@ -58,14 +58,28 @@ cells). This option cannot be combined with requesting specific COLUMNS. Disabled by default. Example: hbase> scan 't1', {RAW => true, VERSIONS => 10} + +Scan can also be used directly from a table, by first getting a reference to a table, like such: + + hbase> t = get_table 't' + hbase> t.scan + +Note in the above situation, you can still provide all the filtering, columns, options, etc as +described above. EOF end def command(table, args = {}) + scan(table(table), args) + end + + #internal command that actually does the scanning + def scan(table, args = {}) now = Time.now formatter.header(["ROW", "COLUMN+CELL"]) - count = table(table).scan(args) do |row, cells| + #actually do the scanning + count = table.scan_internal(args) do |row, cells| formatter.row([ row, cells ]) end @@ -74,3 +88,6 @@ EOF end end end + +#Add the method table.scan that calls Scan.scan +::Hbase::Table.add_shell_command("scan") diff --git a/src/test/ruby/hbase/admin_test.rb b/src/test/ruby/hbase/admin_test.rb index 0c2672b..ec4c01e 100644 --- a/src/test/ruby/hbase/admin_test.rb +++ b/src/test/ruby/hbase/admin_test.rb @@ -310,5 +310,13 @@ module Hbase assert_no_match(eval("/" + key1 + "\\$(\\d+)/"), admin.describe(@test_name)) assert_no_match(eval("/" + key2 + "/"), admin.describe(@test_name)) end + + define_test "get_table should get a real table" do + drop_test_table(@test_name) + create_test_table(@test_name) + + table = table(@test_name) + assert_not_equal(nil, table) + end end end