You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by st...@apache.org on 2008/06/12 08:00:36 UTC

svn commit: r666965 - in /hadoop/hbase/trunk/bin: Formatter.rb HBase.rb hirb.rb

Author: stack
Date: Wed Jun 11 23:00:35 2008
New Revision: 666965

URL: http://svn.apache.org/viewvc?rev=666965&view=rev
Log:
HBASE-487 Replace hql w/ a hbase-friendly jirb or jython shell; First cut at DDL and Admin implementations; create, drop, list, etc.

Added:
    hadoop/hbase/trunk/bin/Formatter.rb
    hadoop/hbase/trunk/bin/HBase.rb
Modified:
    hadoop/hbase/trunk/bin/hirb.rb

Added: hadoop/hbase/trunk/bin/Formatter.rb
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/bin/Formatter.rb?rev=666965&view=auto
==============================================================================
--- hadoop/hbase/trunk/bin/Formatter.rb (added)
+++ hadoop/hbase/trunk/bin/Formatter.rb Wed Jun 11 23:00:35 2008
@@ -0,0 +1,112 @@
+# Results formatter
+module Formatter
+  class Formatter
+    # Base abstract class for results formatting.
+    def initialize(o, w = 80)
+      raise TypeError.new("Type %s of parameter %s is not IO" % [o.class, o]) \
+        unless o.instance_of? IO
+      @out = o
+      @maxWidth = w
+      @rowCount = 0
+    end
+    
+    def header(args = [])
+      row(args) if args.length > 0
+      @rowCount = 0
+    end
+    
+    def row(args = [])
+      if not args or args.length == 0
+        # Print out nothing
+        return
+      end
+      # TODO: Look at the type.  Is it RowResult?
+      if args.length == 1
+        splits = split(@maxWidth, dump(args[0]))
+        for l in splits
+          output(@maxWidth, l)
+          puts
+        end
+      elsif args.length == 2
+        col1width = 8
+        col2width = 70
+        splits1 = split(col1width, dump(args[0]))
+        splits2 = split(col2width, dump(args[1]))
+        biggest = (splits2.length > splits1.length)? splits2.length: splits1.length
+        index = 0
+        while index < biggest
+          @out.print(" ")
+          output(col1width, splits1[index])
+          @out.print(" ")
+          output(col2width, splits2[index])
+          index += 1
+          puts
+        end
+      else
+        # Print a space to set off multi-column rows
+        print ' '
+        first = true
+        for e in args
+          @out.print " " unless first
+          first = false
+          @out.print e
+        end
+        puts
+      end
+      @rowCount += 1
+    end
+
+    def split(width, str)
+      result = []
+      index = 0
+      while index < str.length do
+        result << str.slice(index, index + width)
+        index += width
+      end
+      result
+    end
+
+    def dump(str)
+      # Remove double-quotes added by 'dump'.
+      return str.dump.slice(1, str.length)
+    end
+
+    def output(width, str)
+      # Make up a spec for printf
+      spec = "%%-%d.%ds" % [width, width]
+      @out.printf(spec % str)
+    end
+
+    def footer(startTime = nil)
+      if not startTime
+        return
+      end
+      # Only output elapsed time and row count if startTime passed
+      @out.puts("%d row(s) in %s seconds" % [@rowCount, Time.now - startTime])
+    end
+  end
+     
+
+  class Console < Formatter
+  end
+
+  class XHTMLFormatter < Formatter
+    # http://www.germane-software.com/software/rexml/doc/classes/REXML/Document.html
+    # http://www.crummy.com/writing/RubyCookbook/test_results/75942.html
+  end
+
+  class JSON < Formatter
+  end
+
+  # Do a bit of testing.
+  if $0 == __FILE__
+    formatter = Console.new(STDOUT)
+    formatter.header(['a', 'b'])
+    formatter.row(['a', 'b'])
+    formatter.row(['xxxxxxxxx xxxxxxxxxxx xxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxxxxxx'])
+    formatter.row(['yyyyyy yyyyyy yyyyy yyy', 'xxxxxxxxx xxxxxxxxxxx xxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxxxxxx  xxx xx x xx xxx xx xx xx x xx x x xxx x x xxx x x xx x x x x x x xx '])
+    formatter.footer()
+  end
+end
+
+

Added: hadoop/hbase/trunk/bin/HBase.rb
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/bin/HBase.rb?rev=666965&view=auto
==============================================================================
--- hadoop/hbase/trunk/bin/HBase.rb (added)
+++ hadoop/hbase/trunk/bin/HBase.rb Wed Jun 11 23:00:35 2008
@@ -0,0 +1,93 @@
+# HBase ruby classes
+
+module HBase
+  # Constants needed as keys creating tables, etc.
+  NAME = "NAME"
+  MAX_VERSIONS = "MAX_VERSIONS"
+  MAX_LENGTH = "MAX_LENGTH"
+  TTL = "TTL"
+  BLOOMFILTER = "BLOOMFILTER"
+  COMPRESSION_TYPE = "COMPRESSION_TYPE"
+  # TODO: Add table options here.
+
+  class Admin
+    def initialize(configuration, formatter)
+      @admin = HBaseAdmin.new(configuration)
+      @formatter = formatter
+    end
+   
+    def list
+      now = Time.now 
+      @formatter.header()
+      for t in @admin.listTables()
+        @formatter.row([t.getNameAsString()])
+      end
+      @formatter.footer(now)
+    end
+
+    def exists(tableName)
+      now = Time.now 
+      @formatter.header()
+      @formatter.row([@admin.tableExists(tableName)])
+      @formatter.footer(now)
+    end
+
+    def enable(tableName)
+      # TODO: Need an isEnabled method
+      now = Time.now 
+      @admin.enableTable(tableName)
+      @formatter.header()
+      @formatter.footer(now)
+    end
+
+    def disable(tableName)
+      # TODO: Need an isDisabled method
+      now = Time.now 
+      @admin.disableTable(tableName)
+      @formatter.header()
+      @formatter.footer(now)
+    end
+
+    def drop(tableName)
+      now = Time.now 
+      @admin.deleteTable(tableName)
+      @formatter.header()
+      @formatter.row(["Deleted %s" % tableName])
+      @formatter.footer(now)
+    end
+
+    def create(tableName, args)
+      now = Time.now 
+      # Pass table name and an array of Hashes.  Later, test the last
+      # array to see if its table options rather than column family spec.
+      raise TypeError.new("Table name must be of type String") \
+        unless tableName.instance_of? String
+      # For now presume all the rest of the args are column family
+      # hash specifications. TODO: Add table options handling.
+      htd = HTableDescriptor.new(tableName)
+      for arg in args
+        raise TypeError.new(arg.class.to_s + " of " + arg.to_s + " is not of Hash type") \
+          unless arg.instance_of? Hash
+        name = arg[NAME]
+        raise ArgumentError.new("Column family " + arg + " must have a name at least") \
+          unless name
+        # TODO: Process all other parameters for column family
+        # Check the family name for colon.  Add it if missing.
+        index = name.index(':')
+        if not index
+          # Add a colon.  If already a colon, its in the right place,
+          # or an exception will come up out of the addFamily
+          name << ':'
+        end
+        htd.addFamily(HColumnDescriptor.new(name))
+      end
+      @admin.createTable(htd)
+      @formatter.header()
+      @formatter.row(["Created %s" % tableName])
+      @formatter.footer(now)
+    end
+  end
+
+  class Table
+  end
+end

Modified: hadoop/hbase/trunk/bin/hirb.rb
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/bin/hirb.rb?rev=666965&r1=666964&r2=666965&view=diff
==============================================================================
--- hadoop/hbase/trunk/bin/hirb.rb (original)
+++ hadoop/hbase/trunk/bin/hirb.rb Wed Jun 11 23:00:35 2008
@@ -4,11 +4,11 @@
 
 # TODO: Process command-line arguments: e.g. --master= or -Dhbase.etc and --formatter
 # or read hbase shell configurations from irbrc
-# TODO: Read from environment which outputter to use (outputter should
-# be able to output to a passed Stream as well as STDIN and STDOUT)
+
 # TODO: Write a base class for formatters with ascii, xhtml, and json subclasses.
 
-# Run the java magic include and import basic HBase types.
+# Run the java magic include and import basic HBase types that will help ease
+# hbase hacking.
 include Java
 import org.apache.hadoop.hbase.HBaseConfiguration
 import org.apache.hadoop.hbase.client.HTable
@@ -21,52 +21,137 @@
 # Some goodies for hirb. Should these be left up to the user's discretion?
 require 'irb/completion'
 
+# Add the $HBASE_HOME/bin directory, the presumed location of this script,
+# to the ruby load path so I can load up my HBase ruby modules
+$LOAD_PATH.unshift File.dirname($PROGRAM_NAME)
+require 'Formatter'
+require 'HBase'
+
+# A HERE document used outputting shell command-line options.
+@cmdline_help = <<HERE
+HBase Shell command-line options:
+ format   Formatter outputting results: console | html.  Default: console.
+ master   HBase master shell should connect to: e.g --master=example:60000.
+HERE
+
+# See if there are args for us.  If any, read and then strip from ARGV
+# so they don't go through to irb.
+master = nil
+@formatter = Formatter::Console.new(STDOUT)
+found = []
+for arg in ARGV
+  if arg =~ /^--master=(.+)/i
+    master = $1
+    found.push(arg)
+  elsif arg =~ /^--format=(.+)/i
+    format = $1
+    if format =~ /^html$/i
+      @formatter = Formatter::XHTML.new(STDOUT)
+    elsif format =~ /^console$/i
+      # This is default
+    else
+      raise ArgumentError.new("Unsupported format " + arg)
+    end
+  elsif arg == '-h' || arg == '--help'
+    puts @cmdline_help
+    exit
+  end
+end
+for arg in found
+  ARGV.delete(arg)
+end
+
+# Setup the HBase module.  Create a configuration.  If a master, set it.
+@configuration = HBaseConfiguration.new()
+@configuration.set("hbase.master", master) if master
+# Do lazy create of admin.  If we are pointed at bad master, will hang
+# shell on startup trying to connect.
+@admin = nil
+
+# Promote all HBase constants to be constants of this module.
+for c in HBase.constants
+  if c == c.upcase
+    eval("%s = \"%s\"" % [c, c])
+  end
+end 
+
+# TODO: Add table options here.
+
+# General Shell Commands: help and version
 def help
-  puts 'HBase Shell Commands:'
-  puts ' version   Output HBase version'
-  puts ARGV.inspect
+  # Format is command name and then short description
+  # TODO: Can't do 'help COMMAND'.  Interpreter runs help and then the command
+  commands = {'version' => 'Output HBase version',
+    'list' => 'List all tables',
+    # The help string in the below is carefully formatted to wrap nicely in
+    # our dumb Console formatter
+    'create' => "Create table; pass a table name, a dictionary of \
+specifications per   column family, and optionally, named parameters of table \
+options.     Dictionaries are specified with curly-brackets, uppercase keys, a '=>'\
+key/value delimiter and then a value. Named parameters are like dict- \
+ionary elements with uppercase names and a '=>' delimiter.  E.g. To   \
+create a table named 'table1' with an alternate maximum region size   \
+and a single family named 'family1' with an alternate maximum cells:  \
+create 'table1' {NAME =>'family1', MAX_NUM_VERSIONS => 5}, REGION_SIZE => 12345",
+    'enable' => "Enable named table",
+    'disable' => "Disable named table",
+    'exists' => "Does named table exist",
+    }
+  @formatter.header(["HBase Shell Commands:"])
+  # TODO: Add general note that all names must be quoted and a general
+  # description of dictionary so create doesn't have to be so long.
+  for k, v in commands.sort
+    @formatter.row([k, v])
+  end
+  @formatter.footer()
 end
 
 def version
-  "Version: #{org.apache.hadoop.hbase.util.VersionInfo.getVersion()},\
+  @formatter.header()
+  @formatter.row(["Version: #{org.apache.hadoop.hbase.util.VersionInfo.getVersion()},\
  r#{org.apache.hadoop.hbase.util.VersionInfo.getRevision()},\
- #{org.apache.hadoop.hbase.util.VersionInfo.getDate()}"
-end
-
-# general
-
-def list
-  puts "Not implemented yet"
+ #{org.apache.hadoop.hbase.util.VersionInfo.getDate()}"])
+  @formatter.footer()
 end
 
 # DDL
 
 def create(table_name, *args)
-  puts "Not impemented yet"
+  @admin = HBase::Admin.new(@configuration, @formatter) unless @admin
+  @admin.create(table_name, args)
 end
 
 def drop(table_name)
-  puts "Not implemented yet"
+  @admin = HBase::Admin.new(@configuration, @formatter) unless @admin
+  @admin.drop(table_name)
 end
 
 def alter(table_name, *args)
   puts "Not implemented yet"
 end
 
-# admin
+# Administration
+
+def list
+  @admin = HBase::Admin.new(@configuration, @formatter) unless @admin
+  @admin.list()
+end
   
 def enable(table_name)
-  puts "Not implemented yet"
+  @admin = HBase::Admin.new(@configuration, @formatter) unless @admin
+  @admin.enable(table_name)
 end
 
 def disable(table_name)
-  puts "Not implemented yet"
-end
-  
-def truncate(table_name)
-  puts "Not implemented yet"
+  @admin = HBase::Admin.new(@configuration, @formatter) unless @admin
+  @admin.disable(table_name)
 end
 
+def exists(table_name)
+  @admin = HBase::Admin.new(@configuration, @formatter) unless @admin
+  @admin.exists(table_name)
+end
+  
 # CRUD
   
 def get(table_name, row_key, *args)
@@ -85,58 +170,60 @@
   puts "Not implemented yet"
 end
 
-
-
 # Output a banner message that tells users where to go for help
 # TODO: Test that we're in irb context.  For now presume it.
 # TODO: Test that we are in shell context.
 puts "HBase Shell; type 'hbase<RETURN>' for the list of supported HBase commands"
-puts version
+version
 
 require "irb"
 
-IRB::ExtendCommandBundle.instance_variable_get("@EXTEND_COMMANDS").delete_if{|x| x.first == :irb_help}
+# IRB::ExtendCommandBundle.instance_variable_get("@EXTEND_COMMANDS").delete_if{|x| x.first == :irb_help}
 
 module IRB
   module ExtendCommandBundle
-    
+    # These are attempts at blocking the complaint about :irb_help on startup.
+    # @EXTEND_COMMANDS.delete_if{|x| x[0] == :irb_help}
+    # @EXTEND_COMMANDS.each{|x| x[3][1] = OVERRIDE_ALL if x[0] == :irb_help}
+    # @EXTEND_COMMANDS.each{|x| puts x if x[0] == :irb_help}
   end
-  
+
+  class HIRB < Irb
+    # Subclass irb so can intercept methods
+
+    def output_value
+      # Suppress output if last_value is 'nil'
+      # Otherwise, when user types help, get ugly 'nil'
+      # after all output.
+      if @context.last_value
+        super
+      end
+    end
+  end
+
   def IRB.start(ap_path = nil)
     $0 = File::basename(ap_path, ".rb") if ap_path
 
     IRB.setup(ap_path)
-    
-    @CONF[:PROMPT][:HBASE] = {
-      :PROMPT_I => "hbase> ",
-    	:PROMPT_N => "hbase> ",
-    	:PROMPT_S => nil,
-    	:PROMPT_C => "?> ",
-    	:RETURN => "%s\n"
-    }
-    @CONF[:PROMPT_MODE] = :HBASE
-    
+    @CONF[:IRB_NAME]="hbase"
     
     if @CONF[:SCRIPT]
-      irb = Irb.new(nil, @CONF[:SCRIPT])
+      hirb = HIRB.new(nil, @CONF[:SCRIPT])
     else
-      irb = Irb.new
+      hirb = HIRB.new
     end
 
-    @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
-    @CONF[:MAIN_CONTEXT] = irb.context
+    @CONF[:IRB_RC].call(hirb.context) if @CONF[:IRB_RC]
+    @CONF[:MAIN_CONTEXT] = hirb.context
 
     trap("SIGINT") do
-      irb.signal_handle
+      hirb.signal_handle
     end
 
     catch(:IRB_EXIT) do
-      irb.eval_input
+      hirb.eval_input
     end
   end
 end
 
-
-# .delete_if{|x| x.first == :irb_help}.inspect
-
 IRB.start