Skip to content

Commit

Permalink
HBASE-28549 Make shell commands support column qualifiers with colons (
Browse files Browse the repository at this point in the history
…#5849)

Signed-off-by: Duo Zhang <zhangduo@apache.org>
  • Loading branch information
junegunn authored Jun 8, 2024
1 parent 092ce0d commit f136f0a
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 21 deletions.
50 changes: 29 additions & 21 deletions hbase-shell/src/main/ruby/hbase/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def close
# Put a cell 'value' at specified table/row/column
def _put_internal(row, column, value, timestamp = nil, args = {})
p = org.apache.hadoop.hbase.client.Put.new(row.to_s.to_java_bytes)
family, qualifier = parse_column_name(column)
family, qualifier = split_column_name(column)
if args.any?
attributes = args[ATTRIBUTES]
set_attributes(p, attributes) if attributes
Expand Down Expand Up @@ -188,14 +188,14 @@ def _createdelete_internal(row, column = nil,
end
if column != ""
if column && all_version
family, qualifier = parse_column_name(column)
family, qualifier = split_column_name(column)
if qualifier
d.addColumns(family, qualifier, timestamp)
else
d.addFamily(family, timestamp)
end
elsif column && !all_version
family, qualifier = parse_column_name(column)
family, qualifier = split_column_name(column)
if qualifier
d.addColumn(family, qualifier, timestamp)
else
Expand Down Expand Up @@ -273,7 +273,7 @@ def _incr_internal(row, column, value = nil, args = {})
value = 1 if value.is_a?(Hash)
value ||= 1
incr = org.apache.hadoop.hbase.client.Increment.new(row.to_s.to_java_bytes)
family, qualifier = parse_column_name(column)
family, qualifier = split_column_name(column)
if args.any?
attributes = args[ATTRIBUTES]
visibility = args[VISIBILITY]
Expand All @@ -296,7 +296,7 @@ def _incr_internal(row, column, value = nil, args = {})
# appends the value atomically
def _append_internal(row, column, value, args = {})
append = org.apache.hadoop.hbase.client.Append.new(row.to_s.to_java_bytes)
family, qualifier = parse_column_name(column)
family, qualifier = split_column_name(column)
if args.any?
attributes = args[ATTRIBUTES]
visibility = args[VISIBILITY]
Expand Down Expand Up @@ -491,7 +491,7 @@ def _get_internal(row, *args)
#----------------------------------------------------------------------------------------------
# Fetches and decodes a counter value from hbase
def _get_counter_internal(row, column)
family, qualifier = parse_column_name(column.to_s)
family, qualifier = split_column_name(column.to_s)
# Format get request
get = org.apache.hadoop.hbase.client.Get.new(row.to_s.to_java_bytes)
get.addColumn(family, qualifier)
Expand Down Expand Up @@ -833,16 +833,16 @@ def convert_bytes(bytes, converter_class = nil, converter_method = nil)
eval(converter_class).method(converter_method).call(bytes)
end

def convert_bytes_with_position(bytes, offset, len, converter_class, converter_method)
# Avoid nil
converter_class ||= 'org.apache.hadoop.hbase.util.Bytes'
converter_method ||= 'toStringBinary'
eval(converter_class).method(converter_method).call(bytes, offset, len)
end

# store the information designating what part of a column should be printed, and how
ColumnFormatSpec = Struct.new(:family, :qualifier, :converter)

# Use this instead of parse_column_name if the name cannot contain a converter
private def split_column_name(column)
# NOTE: We use 'take(2)' instead of 'to_a' to avoid type coercion of the nested byte arrays.
# https://github.com/jruby/jruby/blob/9.3.13.0/core/src/main/java/org/jruby/java/proxies/ArrayJavaProxy.java#L484-L488
org.apache.hadoop.hbase.CellUtil.parseColumn(column.to_java_bytes).take(2)
end

##
# Parse the column specification for formatting used by shell commands like :scan
#
Expand All @@ -856,21 +856,29 @@ def convert_bytes_with_position(bytes, offset, len, converter_class, converter_m
# @param [String] column
# @return [ColumnFormatSpec] family, qualifier, and converter as Java bytes
private def parse_column_format_spec(column)
split = org.apache.hadoop.hbase.CellUtil.parseColumn(column.to_java_bytes)
family = split[0]
qualifier = nil
converter = nil
if split.length > 1
parts = org.apache.hadoop.hbase.CellUtil.parseColumn(split[1])
qualifier = parts[0]
if parts.length > 1
converter = parts[1]
family, qualifier = split_column_name(column)
if qualifier
delim = org.apache.hadoop.hbase.KeyValue.getDelimiterInReverse(
qualifier, 0, qualifier.length, org.apache.hadoop.hbase.KeyValue::COLUMN_FAMILY_DELIMITER
)
if delim >= 0
prefix, suffix = qualifier[0...delim], qualifier[delim+1..-1]
if converter?(suffix.to_s)
qualifier = prefix
converter = suffix
end
end
end

ColumnFormatSpec.new(family, qualifier, converter)
end

# Check if the expression can be a converter
private def converter?(expr)
expr =~ /^c\(.+\)\..+/ || Bytes.respond_to?(expr)
end

private def set_column_converter(family, qualifier, converter)
@converters["#{String.from_java_bytes(family)}:#{String.from_java_bytes(qualifier)}"] = String.from_java_bytes(converter)
end
Expand Down
36 changes: 36 additions & 0 deletions hbase-shell/src/test/ruby/hbase/table_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,42 @@ def teardown
define_test "get_counter should return nil for non-existent counters" do
assert_nil(@test_table._get_counter_internal(12345, 'x:qqqq'))
end

define_test "should work with qualifiers with colons" do
rowkey = "123"

# Two columns with multiple colons in their qualifiers with the same prefix
col1 = "x:foo:bar:c1"
col2 = "x:foo:bar:c2"

# Make sure that no data is present
@test_table.deleteall(rowkey)

# Put two columns with colons in their qualifiers
@test_table.put(rowkey, col1, org.apache.hadoop.hbase.util.Bytes.toBytes(1))
@test_table.put(rowkey, col2, org.apache.hadoop.hbase.util.Bytes.toBytes(2))
assert_equal(2, @test_table._get_internal(rowkey).length)

# Increment the second column by 10 => 2 + 10 => 12
@test_table.incr(rowkey, col2, 10)
assert_equal(12, @test_table._get_counter_internal(rowkey, col2))

# Check the counter value using toLong converter
%w[:toLong :c(org.apache.hadoop.hbase.util.Bytes).toLong].each do |suffix|
res = @test_table._get_internal(rowkey, { COLUMNS => [col2 + suffix] })
assert_not_nil(res)
assert_kind_of(Hash, res)
assert_not_nil(/value=12/.match(res[col2]))
end

# Delete the first column
@test_table.delete(rowkey, col1)
assert_equal(1, @test_table._get_internal(rowkey).length)

# Append twice to the deleted column
@test_table.append(rowkey, col1, '123')
assert_equal("123123", @test_table._append_internal(rowkey, col1, '123'))
end
end

# Complex data management methods tests
Expand Down

0 comments on commit f136f0a

Please sign in to comment.