HBASE-487 Replace hql w/ a hbase-friendly jirb or jython shell; First cut at DDL and Admin implementations; create, drop, list, etc.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/hbase/trunk@666965 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
545a21579a
commit
85f562ebea
|
@ -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
|
||||
|
||||
|
|
@ -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
|
183
bin/hirb.rb
183
bin/hirb.rb
|
@ -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 @@ import org.apache.hadoop.hbase.io.BatchUpdate
|
|||
# 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 @@ def delete(table_name, row_key, *args)
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue