This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch branch-2.5
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.5 by this push:
     new 3f42dc72b51 HBASE-28549 Make shell commands support column qualifiers 
with colons (#5849)
3f42dc72b51 is described below

commit 3f42dc72b5129a3bdc64965a425248c5b0c3fda0
Author: Junegunn Choi <[email protected]>
AuthorDate: Sat Jun 8 12:29:19 2024 +0900

    HBASE-28549 Make shell commands support column qualifiers with colons 
(#5849)
    
    Signed-off-by: Duo Zhang <[email protected]>
    (cherry picked from commit f136f0ace8a328f4a8489a66cb8397975b5777dd)
---
 hbase-shell/src/main/ruby/hbase/table.rb      | 50 ++++++++++++++++-----------
 hbase-shell/src/test/ruby/hbase/table_test.rb | 36 +++++++++++++++++++
 2 files changed, 65 insertions(+), 21 deletions(-)

diff --git a/hbase-shell/src/main/ruby/hbase/table.rb 
b/hbase-shell/src/main/ruby/hbase/table.rb
index fc63785d689..0461f497d0f 100644
--- a/hbase-shell/src/main/ruby/hbase/table.rb
+++ b/hbase-shell/src/main/ruby/hbase/table.rb
@@ -135,7 +135,7 @@ EOF
     # 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
@@ -188,14 +188,14 @@ EOF
       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
@@ -273,7 +273,7 @@ EOF
       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]
@@ -296,7 +296,7 @@ EOF
     # 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]
@@ -491,7 +491,7 @@ EOF
     
#----------------------------------------------------------------------------------------------
     # 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)
@@ -833,16 +833,16 @@ EOF
       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
     #
@@ -856,21 +856,29 @@ EOF
     # @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
diff --git a/hbase-shell/src/test/ruby/hbase/table_test.rb 
b/hbase-shell/src/test/ruby/hbase/table_test.rb
index 8ed6f663dbd..7b9a15fb841 100644
--- a/hbase-shell/src/test/ruby/hbase/table_test.rb
+++ b/hbase-shell/src/test/ruby/hbase/table_test.rb
@@ -242,6 +242,42 @@ module Hbase
     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

Reply via email to