diff --git a/hbase-shell/src/main/ruby/hbase/table.rb b/hbase-shell/src/main/ruby/hbase/table.rb index 5192c63aa6d3..0a7b35a39a1e 100644 --- a/hbase-shell/src/main/ruby/hbase/table.rb +++ b/hbase-shell/src/main/ruby/hbase/table.rb @@ -132,41 +132,119 @@ def close # they will be much less likely to tab complete to the 'dangerous' internal method #---------------------------------------------------------------------------------------------- - # 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) + ## + # Method to initialize the object using the passed argument args + def _parse_args_and_init(obj, args) + attributes = args[ATTRIBUTES] + set_attributes(obj, attributes) if attributes + visibility = args[VISIBILITY] + set_cell_visibility(obj, visibility) if visibility + ttl = args[TTL] + set_op_ttl(obj, ttl) if ttl + end + + #---------------------------------------------------------------------------------------------- + + ## + # Method to add column to passed put mutation + # + # @param put [Put] The put mutation to which the column will be added + # @param column [String] The column to be added + # @param value [String] The value to be added in the column + # @param timestamp [Integer] The timestamp for the column value + def _add_column_to_put(put, column, value, timestamp) family, qualifier = parse_column_name(column) - if args.any? - attributes = args[ATTRIBUTES] - set_attributes(p, attributes) if attributes - visibility = args[VISIBILITY] - set_cell_visibility(p, visibility) if visibility - ttl = args[TTL] - set_op_ttl(p, ttl) if ttl + if timestamp + put.addColumn(family, qualifier, timestamp, value.to_s.to_java_bytes) + else + put.addColumn(family, qualifier, value.to_s.to_java_bytes) end + end + + ## + # Method to create and return a new put object, initialized with the passed argument + # + # @param row [String] The row to which the put will be added + # @param timestamp [Integer] The timestamp for the put + # @param args [Hash] Additional arguments for initializing the put + # @return [Put, Integer] The created and initialized Put object and timestamp + def _create_and_initialize_put(row, timestamp, args) + put = org.apache.hadoop.hbase.client.Put.new(row.to_s.to_java_bytes) + _parse_args_and_init(put, args) if args.any? # Case where attributes are specified without timestamp if timestamp.is_a?(Hash) timestamp.each do |k, v| if k == 'ATTRIBUTES' - set_attributes(p, v) + set_attributes(put, v) elsif k == 'VISIBILITY' - set_cell_visibility(p, v) + set_cell_visibility(put, v) elsif k == 'TTL' - set_op_ttl(p, v) + set_op_ttl(put, v) end end timestamp = nil end - if timestamp - p.addColumn(family, qualifier, timestamp, value.to_s.to_java_bytes) - else - p.addColumn(family, qualifier, value.to_s.to_java_bytes) + return put, timestamp + end + + ## + # Method to put a cell 'value' at specified table/row/column + # + # @param row [String] The row where the cell will be put + # @param column [String] The column where the cell will be put + # @param value [String] The value to be put in the cell + # @param timestamp [Integer] The timestamp for the cell value + # @param args [Hash] Additional arguments for the put + def _put_internal(row, column, value, timestamp = nil, args = {}) + p, timestamp = _create_and_initialize_put(row, timestamp, args) + _add_column_to_put(p, column, value, timestamp) + @table.put(p) + end + + ## + # Method to put cell values at specified columns for the given table/row. + # + # @param row [String] The row where the cells will be put + # @param column_value_map [Hash] A map of column names to values to be put + # @param timestamp [Integer] The timestamp for the cell values + # @param args [Hash] Additional arguments for the put + def _put_multi_column_internal(row, column_value_map, timestamp = nil, args = {}) + p, timestamp = _create_and_initialize_put(row, timestamp, args) + column_value_map.each do |column, value| + _add_column_to_put(p, column, value, timestamp) end @table.put(p) end #---------------------------------------------------------------------------------------------- - # Create a Delete mutation + + ## + # Method to add a column to the passed delete mutation. + # + # @param d [Delete] The delete mutation to which the column will be added + # @param column [String] The column to be added + # @param timestamp [Integer] The timestamp of the mutation + # @param all_version [Boolean] Flag indicating whether all versions should be deleted + def _add_column_to_delete(d, column, timestamp, all_version) + return unless column + + family, qualifier = parse_column_name(column) + + if all_version + qualifier ? d.addColumns(family, qualifier, timestamp) : d.addFamily(family, timestamp) + else + qualifier ? d.addColumn(family, qualifier, timestamp) : d.addFamilyVersion(family, timestamp) + end + end + + ## + # Method to create a delete mutation. + # + # @param row [String] The row key for the delete mutation + # @param column [String, Array] The column(s) to be deleted + # @param timestamp [Integer, Hash] The timestamp of the mutation + # @param args [Hash] Additional arguments for the mutation + # @param all_version [Boolean] Flag indicating whether all versions should be deleted def _createdelete_internal(row, column = nil, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP, args = {}, all_version = true) @@ -187,27 +265,24 @@ def _createdelete_internal(row, column = nil, set_cell_visibility(d, visibility) if visibility end if column != "" - if column && all_version - family, qualifier = parse_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) - if qualifier - d.addColumn(family, qualifier, timestamp) - else - d.addFamilyVersion(family, timestamp) - end + if column.is_a?(Array) + column.each { |column_name| _add_column_to_delete(d, column_name, timestamp, all_version) } + else + _add_column_to_delete(d, column, timestamp, all_version) end end d end + ## #---------------------------------------------------------------------------------------------- - # Delete rows using prefix + # Method to delete rows using prefix. + # + # @param row [String] The row key for the delete mutation + # @param column [String, Array] The column(s) to be deleted + # @param timestamp [Integer, Hash] The timestamp of the mutation + # @param args [Hash] Additional arguments for the mutation + # @param all_version [Boolean] Flag indicating whether all versions should be deleted def _deleterows_internal(row, column = nil, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP, args = {}, all_version = true) @@ -237,15 +312,30 @@ def _deleterows_internal(row, column = nil, end #---------------------------------------------------------------------------------------------- - # Delete a cell + + ## + # Method to delete a cell. + # + # @param row [String] The row key for the delete mutation + # @param column [String, Array] The column(s) to be deleted + # @param timestamp [Integer, Hash] The timestamp of the mutation + # @param args [Hash] Additional arguments for the mutation + # @param all_version[Boolean] Flag indicating whether all versions should be deleted def _delete_internal(row, column, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP, args = {}, all_version = false) _deleteall_internal(row, column, timestamp, args, all_version) end + ## #---------------------------------------------------------------------------------------------- - # Delete a row + # Method to delete a row. + # + # @param row [String] The row key for the delete mutation + # @param column [String, Array] The column(s) to be deleted + # @param timestamp [Integer, Hash] The timestamp of the mutation + # @param args [Hash] Additional arguments for the mutation + # @param all_version [Boolean] Flag indicating whether all versions should be deleted def _deleteall_internal(row, column = nil, timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP, args = {}, all_version = true) @@ -267,53 +357,184 @@ def _deleteall_internal(row, column = nil, end #---------------------------------------------------------------------------------------------- - # Increment a counter atomically - # rubocop:disable Metrics/AbcSize, CyclomaticComplexity, MethodLength - 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) + + ## + # Parses the value from a cell and return it + # + # @param cell [Cell] The cell from which the value is to be parsed + # @param is_long [Boolean] A flag indicating whether the value is a Long + # @return [Long, String] Returns the value from the cell as a Long if is_long is true, otherwise returns the value as a String + def _parse_value_from_cell(cell, is_long) + if is_long + org.apache.hadoop.hbase.util.Bytes.toLong(cell.getValueArray, + cell.getValueOffset, cell.getValueLength) + else + org.apache.hadoop.hbase.util.Bytes.toStringBinary(cell.getValueArray, + cell.getValueOffset, cell.getValueLength) + end + end + + ## + # Parses the passed result object and returns a map containing "family:qualifier" as key and + # "value" as value for each cell if the call originated from multi column path. + # If the call is not from multi column path, it returns the value of the first cell. + # Depending on the value of is_long, it returns value as Long or String. + # + # @param result [Object] The result to parse + # @param is_multi_column [Boolean] Specifies whether the call originated from multi column path + # @param is_long [Boolean] Specifies whether to return value as Long or String + # @return [nil, String, Long, Hash] A map of cell values if is_multi_column is true, otherwise the value of the first cell. + # If result is empty, it returns nil. + def _parse_and_get_result(result, is_multi_column, is_long) + return nil if result.isEmpty + + if is_multi_column + # Fetch cell values + value_map = {} + result.listCells.each do |cell| + family = org.apache.hadoop.hbase.util.Bytes.toStringBinary(cell.getFamilyArray, + cell.getFamilyOffset, cell.getFamilyLength) + qualifier = org.apache.hadoop.hbase.util.Bytes.toStringBinary(cell.getQualifierArray, + cell.getQualifierOffset, cell.getQualifierLength) + column = "#{family}:#{qualifier}" + value_map[column] = _parse_value_from_cell(cell, is_long) + end + value_map + else + # Fetch cell value + _parse_value_from_cell(result.listCells[0], is_long) + end + end + + #---------------------------------------------------------------------------------------------- + + ## + # Method to add the columns to 'incr' object + # + # @param incr [Object] The object to which the column is to be added + # @param column [String] The column name to be added + # @param value [Integer] The value to be added to the column, defaults to 1 + # @raise [ArgumentError] If both column family and column qualifier are not provided for 'incr' + def _add_column_to_incr(incr, column, value = 1) family, qualifier = parse_column_name(column) - if args.any? - attributes = args[ATTRIBUTES] - visibility = args[VISIBILITY] - set_attributes(incr, attributes) if attributes - set_cell_visibility(incr, visibility) if visibility - ttl = args[TTL] - set_op_ttl(incr, ttl) if ttl + if family.nil? && qualifier.nil? + raise ArgumentError, 'Failed to provide both column family and column qualifier for incr' end incr.addColumn(family, qualifier, value) + end + + # Create and return a new increment object, initialized with the passed argument + # + # @param row [String] the row to create increment for + # @param args [Hash] the arguments to initialize increment with + # @return [Increment] the created increment object + def _create_and_initialize_incr(row, args) + incr = org.apache.hadoop.hbase.client.Increment.new(row.to_s.to_java_bytes) + _parse_args_and_init(incr, args) if args.any? + incr + end + + ## + # Increment a counter atomically + # + # @param row [String] the row to increment + # @param column [String] the column to increment + # @param value [Integer, Hash] the value to increment by + # @param args [Hash] additional arguments + # @return [Long] the incremented value + def _incr_internal(row, column, value = nil, args = {}) + incr = _create_and_initialize_incr(row, args) + value = value.is_a?(Hash) || value.nil? ? 1 : value + + _add_column_to_incr(incr, column, value) + result = @table.increment(incr) - return nil if result.isEmpty + _parse_and_get_result(result, false, true) + end - # Fetch cell value - cell = result.listCells[0] - org.apache.hadoop.hbase.util.Bytes.toLong(cell.getValueArray, - cell.getValueOffset, cell.getValueLength) + ## + # Increment counters atomically at specified columns for given table/row + # + # @param row [String] the row to increment + # @param column_value [Array, Hash] the columns to increment and their values + # @param args [Hash] additional arguments + # @return [Hash] a map of incremented values + def _incr_multi_column_internal(row, column_value, args = {}) + incr = _create_and_initialize_incr(row, args) + + if column_value.is_a?(Array) + column_value.each { |column| _add_column_to_incr(incr, column) } + elsif column_value.is_a?(Hash) + column_value.each { |key, value| _add_column_to_incr(incr, key, value) } + end + + result = @table.increment(incr) + _parse_and_get_result(result, false, true) end #---------------------------------------------------------------------------------------------- - # 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) + + ## + # Method to add the columns to 'append' object + # + # @param append [Object] The object to which the column is to be added + # @param column [String] The column name to be added + # @param value [Integer] The value to be appended to the column + # @raise [ArgumentError] If both column family and column qualifier are not provided for 'append' + def _add_column_to_append(append, column, value) family, qualifier = parse_column_name(column) - if args.any? - attributes = args[ATTRIBUTES] - visibility = args[VISIBILITY] - set_attributes(append, attributes) if attributes - set_cell_visibility(append, visibility) if visibility - ttl = args[TTL] - set_op_ttl(append, ttl) if ttl + if family.nil? && qualifier.nil? + raise ArgumentError, 'Failed to provide both column family and column qualifier for append' end append.addColumn(family, qualifier, value.to_s.to_java_bytes) + end + + ## + # Create and return a new Append object, initialized with the passed argument + # + # @param row [String] The row key for the append operation + # @param args [Hash] Additional arguments for the append operation + # @return [Append] The created and initialized Append object + def _create_and_initialize_append(row, args) + append = org.apache.hadoop.hbase.client.Append.new(row.to_s.to_java_bytes) + _parse_args_and_init(append, args) if args.any? + append + end + + ## + # Append the value atomically to a specified column in a row + # + # @param row [String] The row key for the append operation + # @param column [String] The column for the append operation + # @param value [String] The value to be appended + # @param args [Hash] Additional arguments for the append operation + # @return [String, nil] The appended value or nil if the result is empty + def _append_internal(row, column, value, args = {}) + append = _create_and_initialize_append(row, args) + + _add_column_to_append(append, column, value) + result = @table.append(append) - return nil if result.isEmpty + _parse_and_get_result(result, false, false) + end + + # Append cell values to specified columns for a given table/row + # + # @param row [String] The row key for the append operation + # @param column_value_map [Hash] A map of columns and corresponding values to be appended + # @param args [Hash] Additional arguments for the append operation + # @return [Hash, nil] A map of the results or nil if the result is empty + def _append_multi_column_internal(row, column_value_map, args = {}) + append = _create_and_initialize_append(row, args) + + column_value_map.each do |k, v| + _add_column_to_append(append, k, v) + end - # Fetch cell value - cell = result.listCells[0] - org.apache.hadoop.hbase.util.Bytes.toStringBinary(cell.getValueArray, - cell.getValueOffset, cell.getValueLength) + result = @table.append(append) + _parse_and_get_result(result, false, false) end + # rubocop:enable Metrics/AbcSize, CyclomaticComplexity, MethodLength #---------------------------------------------------------------------------------------------- @@ -489,24 +710,55 @@ def _get_internal(row, *args) end #---------------------------------------------------------------------------------------------- - # Fetches and decodes a counter value from hbase + + # Creates and returns a new get for counter with the maximum version set to 1. + # + # @param row [String] The row key for the get + # @return [Get] The initialized get for the counter + # Create and return a new get for counter with max version set to 1 + def _create_and_initialize_get_for_counter(row) + get = org.apache.hadoop.hbase.client.Get.new(row.to_s.to_java_bytes) + get.readVersions(1) + get + end + + # Fetches and decodes a counter value from HBase. + # + # @param row [String] The row key to fetch the counter from + # @param column [String] The column to fetch the counter from + # @return [Integer] The fetched counter value def _get_counter_internal(row, column) + get = _create_and_initialize_get_for_counter(row) + 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) get.addColumn(family, qualifier) - get.readVersions(1) # Call hbase result = @table.get(get) - return nil if result.isEmpty + _parse_and_get_result(result, false, true) + end - # Fetch cell value - cell = result.listCells[0] - org.apache.hadoop.hbase.util.Bytes.toLong(cell.getValueArray, - cell.getValueOffset, cell.getValueLength) + # Fetches and decodes counter values for multiple columns from HBase. + # + # @param row [String] The row key to fetch the counters from + # @param column_array [Array] The array of columns to fetch the counters from + # @return [Hash] a hash with the column names as keys and the fetched counter values as values + def _get_counter_multi_column_internal(row, column_array) + get = _create_and_initialize_get_for_counter(row) + + # Add columns to get object + column_array.each do |column| + family, qualifier = parse_column_name(column) + get.addColumn(family, qualifier) + end + + # Call hbase + result = @table.get(get) + _parse_and_get_result(result, true, true) end + #---------------------------------------------------------------------------------------------- + def _hash_to_scan(args) if args.any? enablemetrics = args['ALL_METRICS'].nil? ? false : args['ALL_METRICS'] @@ -891,6 +1143,7 @@ def set_converter(column) extend Gem::Deprecate deprecate :set_converter, "4.0.0", nil, nil + #---------------------------------------------------------------------------------------------- # Get the split points for the table def _get_splits_internal diff --git a/hbase-shell/src/main/ruby/shell/commands/append.rb b/hbase-shell/src/main/ruby/shell/commands/append.rb index b469f0bda638..46b3d4ecbc22 100644 --- a/hbase-shell/src/main/ruby/shell/commands/append.rb +++ b/hbase-shell/src/main/ruby/shell/commands/append.rb @@ -32,18 +32,56 @@ def help hbase> t.append 'r1', 'c1', 'value', ATTRIBUTES=>{'mykey'=>'myvalue'} hbase> t.append 'r1', 'c1', 'value', {VISIBILITY=>'PRIVATE|SECRET'} + +Alternately, we can run the following commands for appending cell values for +multiple columns at specified table/row coordinates. + + hbase> append 't1', 'r1', {'c1'=>'value1', 'c2'=>'value2'}, {ATTRIBUTES=>{'mykey'=>'myvalue'}} + hbase> append 't1', 'r1', {'c1'=>'value1', 'c2'=>'value2'}, {VISIBILITY=>'PRIVATE|SECRET'} + +The same commands also can be run on a table reference. + + hbase> t.append 'r1', {'c1'=>'value1', 'c2'=>'value2'}, {ATTRIBUTES=>{'mykey'=>'myvalue'}} + hbase> t.append 'r1', {'c1'=>'value1', 'c2'=>'value2'}, {VISIBILITY=>'PRIVATE|SECRET'} + EOF end - def command(table_name, row, column, value, args = {}) + # Executes an append command on a specified table. + # + # @param table_name [String] The name of the table + # @param row [String] The row key + # @param column [String, Hash] The column name or a hash of column names and values + # @param value [String, Hash] The value to append or a hash of column names and values + # @param args [Hash] optional arguments + def command(table_name, row, column, value = value_omitted = {}, args = args_omitted = {}) table = table(table_name) @start_time = Time.now - append(table, row, column, value, args) + # Conditional block to omit passing optional arguments explicitly + if !value_omitted.nil? + # value field was not passed (will reach only for multi column append) + append(table, row, column) + elsif !args_omitted.nil? + # args field was not passed (must not be passed for multi column append) + append(table, row, column, value) + else + append(table, row, column, value, args) + end end - def append(table, row, column, value, args = {}) - if current_value = table._append_internal(row, column, value, args) - puts "CURRENT VALUE = #{current_value}" + def append(table, row, column, value = value_omitted = {}, args = args_omitted = {}) + if column.is_a?(Hash) + # args field must not be passed; already contained in value field + raise(ArgumentError, 'wrong number of arguments') if args_omitted.nil? + if current_values = table._append_multi_column_internal(row, column, value) + puts "CURRENT VALUES = #{current_values}" + end + else + # value field must be passed by user + raise(ArgumentError, 'wrong number of arguments') unless value_omitted.nil? + if current_value = table._append_internal(row, column, value, args) + puts "CURRENT VALUE = #{current_value}" + end end end end diff --git a/hbase-shell/src/main/ruby/shell/commands/delete.rb b/hbase-shell/src/main/ruby/shell/commands/delete.rb index 923d3498a06a..68a8043cee4e 100644 --- a/hbase-shell/src/main/ruby/shell/commands/delete.rb +++ b/hbase-shell/src/main/ruby/shell/commands/delete.rb @@ -37,6 +37,18 @@ def help hbase> t.delete 'r1', 'c1', ts1 hbase> t.delete 'r1', 'c1', ts1, {VISIBILITY=>'PRIVATE|SECRET'} + +Alternately, we can put delete cell values for multiple columns at specified table/row and +optionally timestamp coordinates. + + hbase> delete 'ns1:t1', 'r1', ['c1', 'c2'], ts1 + hbase> delete 't1', 'r1', ['c1', 'c2'], ts1 + hbase> delete 't1', 'r1', ['c1', 'c2'], ts1, {VISIBILITY=>'PRIVATE|SECRET'} + +The same command can also be run on a table reference. + + hbase> t.delete 'r1', ['c1', 'c2'], ts1 + hbase> t.delete 'r1', ['c1', 'c2'], ts1, {VISIBILITY=>'PRIVATE|SECRET'} EOF end diff --git a/hbase-shell/src/main/ruby/shell/commands/deleteall.rb b/hbase-shell/src/main/ruby/shell/commands/deleteall.rb index 56f6d5549a37..50d9bc4f517b 100644 --- a/hbase-shell/src/main/ruby/shell/commands/deleteall.rb +++ b/hbase-shell/src/main/ruby/shell/commands/deleteall.rb @@ -30,14 +30,15 @@ def help hbase> deleteall 't1', 'r1' hbase> deleteall 't1', 'r1', 'c1' hbase> deleteall 't1', 'r1', 'c1', ts1 - //'' means no specific column, will delete all cells in the row which timestamp is lower than - //the one specified in the command + # '' means no specific column, will delete all cells in the row which timestamp is lower than + # the one specified in the command hbase> deleteall 't1', 'r1', '', ts1 hbase> deleteall 't1', 'r1', 'c1', ts1, {VISIBILITY=>'PRIVATE|SECRET'} ROWPREFIXFILTER can be used to delete row ranges hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'} - hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'}, 'c1' //delete certain column family in the row ranges + # delete certain column family in the row ranges + hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'}, 'c1' hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'}, 'c1', ts1 hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'}, '', ts1 hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'}, 'c1', ts1, {VISIBILITY=>'PRIVATE|SECRET'} @@ -51,6 +52,23 @@ def help hbase> t.deleteall 'r1', 'c1', ts1, {VISIBILITY=>'PRIVATE|SECRET'} hbase> t.deleteall {ROWPREFIXFILTER => 'prefix', CACHE => 100}, 'c1', ts1, {VISIBILITY=>'PRIVATE|SECRET'} + +Alternately, we can run the following commands for deleting all cell values for +multiple columns at specified table/row coordinates. + + hbase> deleteall 't1', 'r1', ['c1', 'c2'] + hbase> deleteall 't1', 'r1', ['c1', 'c2'], ts1 + hbase> deleteall 't1', 'r1', ['c1', 'c2'], ts1, {VISIBILITY=>'PRIVATE|SECRET'} + # delete certain column families in the row ranges + hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'}, ['c1', 'c2'] + hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'}, ['c1', 'c2'], ts1 + hbase> deleteall 't1', {ROWPREFIXFILTER => 'prefix'}, ['c1', 'c2'], ts1, {VISIBILITY=>'PRIVATE|SECRET'} + +The same commands can also be run on a table reference. + + hbase> t.deleteall 'r1', ['c1', 'c2'], ts1, {VISIBILITY=>'PRIVATE|SECRET'} + hbase> t.deleteall {ROWPREFIXFILTER => 'prefix', CACHE => 100}, ['c1', 'c2'], ts1, {VISIBILITY=>'PRIVATE|SECRET'} + EOF end diff --git a/hbase-shell/src/main/ruby/shell/commands/get_counter.rb b/hbase-shell/src/main/ruby/shell/commands/get_counter.rb index 12ab4b2f2bb5..abbb68bd9f38 100644 --- a/hbase-shell/src/main/ruby/shell/commands/get_counter.rb +++ b/hbase-shell/src/main/ruby/shell/commands/get_counter.rb @@ -33,18 +33,45 @@ def help t to table 't1', the corresponding command would be: hbase> t.get_counter 'r1', 'c1' + +Alternately, we can run the following commands to get counter cell values for multiple +columns at specified table/row coordinates. + + hbase> get_counter 'ns1:t1', 'r1', ['c1', 'c2'] + hbase> get_counter 't1', 'r1', ['c1', 'c2'] + +The same commands also can be run on a table reference. + + hbase> t.get_counter 'r1', ['c1', 'c2'] + EOF end + # Gets the counter value(s) from a table for a specific row and column(s). + # If the column parameter is an array, it retrieves multiple counter values. + # If no counter is found, it prints an error message. + # + # @param table [String] the name of the table + # @param row [String] the row key + # @param column [String, Array] the column name(s) def command(table, row, column) get_counter(table(table), row, column) end def get_counter(table, row, column) - if cnt = table._get_counter_internal(row, column) - puts "COUNTER VALUE = #{cnt}" + # when column is an array, then it is a case of multi column get counter + if column.is_a?(Array) + if cnts = table._get_counter_multi_column_internal(row, column) + puts "COUNTER VALUES = #{cnts}" + else + puts 'No counters found at specified coordinates' + end else - puts 'No counter found at specified coordinates' + if cnt = table._get_counter_internal(row, column) + puts "COUNTER VALUE = #{cnt}" + else + puts 'No counter found at specified coordinates' + end end end end diff --git a/hbase-shell/src/main/ruby/shell/commands/incr.rb b/hbase-shell/src/main/ruby/shell/commands/incr.rb index f1c31210af80..a1d362f6d4ea 100644 --- a/hbase-shell/src/main/ruby/shell/commands/incr.rb +++ b/hbase-shell/src/main/ruby/shell/commands/incr.rb @@ -41,18 +41,54 @@ def help hbase> t.incr 'r1', 'c1', 1 hbase> t.incr 'r1', 'c1', 10, {ATTRIBUTES=>{'mykey'=>'myvalue'}} hbase> t.incr 'r1', 'c1', 10, {VISIBILITY=>'PRIVATE|SECRET'} + +Alternately, we can run the following commands for incrementing cell values for +multiple columns at specified table/row coordinates. + + hbase> incr 'ns1:t1', 'r1', ['c1', 'c2'] + hbase> incr 't1', 'r1', ['c1', 'c2'] + hbase> incr 't1', 'r1', {'c1'=>1, 'c2'=>1} + hbase> incr 't1', 'r1', {'c1'=>10, 'c2'=>20} + hbase> incr 't1', 'r1', {'c1'=>10, 'c2'=>20}, {ATTRIBUTES=>{'mykey'=>'myvalue'}} + hbase> incr 't1', 'r1', ['c1', 'c2'], {ATTRIBUTES=>{'mykey'=>'myvalue'}} + hbase> incr 't1', 'r1', {'c1'=>10, 'c2'=>20}, {VISIBILITY=>'PRIVATE|SECRET'} + +The same commands also can be run on a table reference. + + hbase> t.incr 'r1', ['c1', 'c2'] + hbase> t.incr 'r1', {'c1'=>1, 'c2'=>1} + hbase> t.incr 'r1', {'c1'=>10, 'c2'=>20}, {ATTRIBUTES=>{'mykey'=>'myvalue'}} + hbase> t.incr 'r1', {'c1'=>10, 'c2'=>20}, {VISIBILITY=>'PRIVATE|SECRET'} EOF end + # Methods to increment counter(s) for specified column(s) of a row for the table. + # + # @param table [String] The name of the table + # @param row [String] The row key + # @param column [String, Hash, Array] The column name or a hash or array of columns + # @param value [Integer, Hash] THe value to increment by or a hash of values + # @param args [Hash] Additional arguments def command(table, row, column, value = nil, args = {}) incr(table(table), row, column, value, args) end def incr(table, row, column, value = nil, args = {}) - if cnt = table._incr_internal(row, column, value, args) - puts "COUNTER VALUE = #{cnt}" + # when column is a hash map, then it is a case of multi column increment + if column.is_a?(Hash) || column.is_a?(Array) + raise(ArgumentError, 'wrong number of arguments') unless args == {} + value = {} if value.nil? + if cnts = table._incr_multi_column_internal(row, column, value) + puts "COUNTER VALUES = #{cnts}" + else + puts 'No counters found at specified coordinates' + end else - puts 'No counter found at specified coordinates' + if cnt = table._incr_internal(row, column, value, args) + puts "COUNTER VALUE = #{cnt}" + else + puts 'No counter found at specified coordinates' + end end end end diff --git a/hbase-shell/src/main/ruby/shell/commands/put.rb b/hbase-shell/src/main/ruby/shell/commands/put.rb index 118902a400de..be0fe38576b2 100644 --- a/hbase-shell/src/main/ruby/shell/commands/put.rb +++ b/hbase-shell/src/main/ruby/shell/commands/put.rb @@ -37,16 +37,58 @@ def help t to table 't1', the corresponding command would be: hbase> t.put 'r1', 'c1', 'value', ts1, {ATTRIBUTES=>{'mykey'=>'myvalue'}} + +Alternately, we can put cell values for multiple columns at specified table/row and +optionally timestamp coordinates. + + hbase> put 'ns1:t1', 'r1', {'c1'=>'value1', 'c2'=>'value2'} + hbase> put 't1', 'r1', {'c1'=>'value1', 'c2'=>'value2'} + hbase> put 't1', 'r1', {'c1'=>'value1', 'c2'=>'value2'}, ts1 + hbase> put 't1', 'r1', {'c1'=>'value1', 'c2'=>'value2'}, {ATTRIBUTES=>{'mykey'=>'myvalue'}} + hbase> put 't1', 'r1', {'c1'=>'value1', 'c2'=>'value2'}, ts1, {ATTRIBUTES=>{'mykey'=>'myvalue'}} + hbase> put 't1', 'r1', {'c1'=>'value1', 'c2'=>'value2'}, ts1, {VISIBILITY=>'PRIVATE|SECRET'} + +The same commands also can be run on a table reference. + + hbase> t.put 'r1', {'c1'=>'value1', 'c2'=>'value2'}, ts1, {ATTRIBUTES=>{'mykey'=>'myvalue'}} EOF end - def command(table, row, column, value, timestamp = nil, args = {}) - put table(table), row, column, value, timestamp, args + # Method to put values in specified table/row/column and optionally timestamp coordinates. + # @param table [String] The name of the table + # @param row [String] The row key + # @param column [String, Hash] The column name or a hash of column names and values + # @param value [String, Hash] The value to append or a hash of column names and values, + # optional in case of multi column put. + # @param timestamp [Integer, Hash] optional timestamp for the cell value ora hash of additional args + # @param args [Hash] optional arguments. Must be nil in case of multi column put + def command(table, row, column, value = value_omitted = {}, timestamp = nil, args = args_omitted = {}) + # Conditional block to omit passing optional arguments explicitly + if !value_omitted.nil? + # value field was not passed (will reach only for multi column put) + put table(table), row, column + elsif !args_omitted.nil? + # args field was not passed (must not be passed for multi column put) + put table(table), row, column, value, timestamp + else + put table(table), row, column, value, timestamp, args + end end - def put(table, row, column, value, timestamp = nil, args = {}) + def put(table, row, column, value = value_omitted = {}, timestamp = nil, args = args_omitted = {}) @start_time = Time.now - table._put_internal(row, column, value, timestamp, args) + # when column is a hash map, then it is a case of multi column put + if column.is_a?(Hash) + # args field must not be passed; already contained in timestamp field + raise(ArgumentError, 'wrong number of arguments') if args_omitted.nil? + value = nil unless value != {} + timestamp = {} if timestamp.nil? + table._put_multi_column_internal(row, column, value, timestamp) + else + # value field must be passed by user + raise(ArgumentError, 'wrong number of arguments') unless value_omitted.nil? + table._put_internal(row, column, value, timestamp, args) + end end end end diff --git a/hbase-shell/src/test/ruby/hbase/table_test.rb b/hbase-shell/src/test/ruby/hbase/table_test.rb old mode 100644 new mode 100755 index 8ed6f663dbd4..30d5875fdabf --- a/hbase-shell/src/test/ruby/hbase/table_test.rb +++ b/hbase-shell/src/test/ruby/hbase/table_test.rb @@ -122,6 +122,28 @@ def setup @test_table.put("111", "x:a", "5") @test_table.put("111", "x:b", "6") @test_table.put("112", "x:a", "5") + + #Insert data to test multi column delete operations + @test_table.put('201', {'x:a' => '1a', 'x:b' => '1b'}, 1220) + @test_table.put('202', {'x:a' => '2a', 'x:b' => '2b'}, 1232) + @test_table.put(203, {'x:a' => '4a', 'x:b' => '4b'}, 3234) + @test_table.put('204', {'x:a' => 5, 'x:b' => 6}, 3248) + @test_table.put('205', {'x:a' => '5a', 'x:b' => '5b'}, 3939) + + @test_table.put('206', {'x:c' => '6c0', 'x:d' => 6}) + @test_table.put('206', {'x:c' => '6c1'}) + @test_table.put('207', {'x:c' => '7c0', 'x:d' => 7}, 1233) + @test_table.put('207', {'x:c' => '7c1'}, 1238) + @test_table.put(208, {'x:c' => '8c0', 'x:d' => '8d0'}) + @test_table.put(208, {'x:c' => '8c1', 'x:d' => '8d1'}) + @test_table.put('209', {'x:c' => '9c0', 'x:d' => '9d0'}) + @test_table.put('209', {'x:c' => '9c1', 'x:a' => '9a0'}) + @test_table.put('210', {'x:c' => '10c0', 'x:d' => '10d0'}) + @test_table.put('210', {'x:a' => '10a0', 'x:b' => '10b0', 'x:c' => '10c1'}) + @test_table.put('222201', {'x:a' => 0, 'x:c' => 1, 'x:d' => 2}) + @test_table.put('222202', {'x:c' => '2c', 'x:d' => '2d'}) + @test_table.put('222203', {'x:c' => 3, 'x:d' => '4'}) + @test_table.put('222204', 'x:c' , '4') end def teardown @@ -129,6 +151,8 @@ def teardown shutdown end + #------------------------------------------------------------------------------- + define_test "put should work without timestamp" do @test_table.put("123", "x:a", "1") end @@ -149,6 +173,60 @@ def teardown @test_table.put("123", "x:a", 4, {ATTRIBUTES=>{'mykey'=>'myvalue'}}) end + define_test "put should fail if no value passed as argument" do + assert_raise(ArgumentError) do + @test_table.put('421', 'x:a') + end + end + + define_test 'multi column put should work without timestamp' do + @test_table.put('401', {'x:a' => '1', 'x:b' => '2'}) + res = @test_table._get_internal('401', 'x:a') + assert_not_nil(res) + res = @test_table._get_internal('401', 'x:b') + assert_not_nil(res) + end + + define_test 'multi column put should work with timestamp' do + @test_table.put('402', {'x:a' => '3', 'x:b' => '4'}, Time.now.to_i) + res = @test_table._get_internal('402', 'x:a') + assert_not_nil(res) + res = @test_table._get_internal('402', 'x:b') + assert_not_nil(res) + end + + define_test 'multi column put should work with integer keys' do + @test_table.put(403, {'x:a' => '5', 'x:b' => '6'}) + res = @test_table._get_internal(403, 'x:a') + assert_not_nil(res) + res = @test_table._get_internal(403, 'x:b') + assert_not_nil(res) + end + + define_test 'multi column put should work with integer values' do + @test_table.put('404', {'x:a' => 7, 'x:b' => 8}) + res = @test_table._get_internal('404', 'x:a') + assert_not_nil(res) + res = @test_table._get_internal('404', 'x:b') + assert_not_nil(res) + end + + define_test 'multi column put should work with attributes' do + @test_table.put('405', {'x:a' => '3', 'x:b' => '4'}, + {ATTRIBUTES=>{'mykey'=>'myvalue'}}) + res = @test_table._get_internal('405', 'x:a') + assert_not_nil(res) + res = @test_table._get_internal('405', 'x:b') + assert_not_nil(res) + end + + define_test 'multi column put should fail when args is passed as argument' do + assert_raise(ArgumentError) do + @test_table.put('431', {'x:a' => '3', 'x:b' => '4'}, 1111111, + {ATTRIBUTES => {'mykey' => 'myvalue'}}, {'dummy_key' => 'dummy_value'}) + end + end + #------------------------------------------------------------------------------- define_test "delete should work with string keys" do @test_table.delete('102', 'x:a', 1212) @@ -179,6 +257,37 @@ def teardown org.apache.hadoop.hbase::KeyValue::Type::DeleteFamilyVersion.getCode) end + define_test 'multi column delete should work without timestamp' do + @test_table.delete('201', ['x:a', 'x:b']) + res = @test_table._get_internal('201', ['x:a', 'x:b']) + assert_nil(res) + end + + define_test 'multi column delete should work with timestamp' do + @test_table.delete('202', ['x:a', 'x:b'], 1232) + res = @test_table._get_internal('202', ['x:a', 'x:b']) + assert_nil(res) + end + + define_test 'multi column delete should work with integer keys' do + @test_table.delete(203, ['x:a', 'x:b'], 3234) + res = @test_table._get_internal('203', ['x:a', 'x:b']) + assert_nil(res) + end + + define_test 'multi column delete should work with integer values' do + @test_table.delete('204', ['x:a', 'x:b']) + res = @test_table._get_internal('204', ['x:a', 'x:b']) + assert_nil(res) + end + + define_test 'multi column delete should work with attributes' do + @test_table.delete('205', ['x:a', 'x:b'], + {ATTRIBUTES => {'mykey' => 'myvalue'}}) + res = @test_table._get_internal('205', ['x:a', 'x:b']) + assert_nil(res) + end + #------------------------------------------------------------------------------- define_test "deleteall should work w/o columns and timestamps" do @@ -216,6 +325,56 @@ def teardown end end + define_test 'multi column deleteall should work w/o timestamps' do + @test_table.deleteall('206', ['x:c', 'x:d']) + res = @test_table._get_internal('206', ['x:c', 'x:d']) + assert_nil(res) + end + + define_test 'multi column deleteall should work with timestamps' do + @test_table.deleteall('207', ['x:c', 'x:d'], 1300) + res = @test_table._get_internal('207', ['x:c', 'x:d']) + assert_nil(res) + end + + define_test 'multi column deleteall should work with integer keys' do + @test_table.deleteall(208, ['x:c', 'x:d']) + res = @test_table._get_internal('208', ['x:c', 'x:d']) + assert_nil(res) + end + + define_test 'multi column deleteall should work with attributes' do + @test_table.deleteall('209', ['x:c', 'x:d'], + {ATTRIBUTES => {'mykey' => 'myvalue'}}) + res = @test_table._get_internal('209', ['x:c', 'x:d']) + assert_nil(res) + end + + define_test 'multi column deleteall should delete only specified columns' do + @test_table.deleteall('210', ['x:c', 'x:d']) + res = @test_table._get_internal('210', ['x:c', 'x:d']) + assert_nil(res) + res = @test_table._get_internal('210', ['x:a', 'x:b']) + assert_not_nil(res) + end + + define_test 'multi column deletall should work with row prefix' do + @test_table.deleteall({ROWPREFIXFILTER => '2222'}, ['x:c', 'x:d']) + res = @test_table._get_internal('222201', ['x:c'], 'x:d') + assert_nil(res) + res = @test_table._get_internal('222201', ['x:a']) + assert_not_nil(res) + res = @test_table._get_internal('222202') + assert_nil(res) + res = @test_table._get_internal('222203') + assert_nil(res) + res = @test_table._get_internal('222204') + assert_nil(res) + end + + + #------------------------------------------------------------------------------- + define_test "append should work with value" do @test_table.append("123", 'x:cnt2', '123') assert_equal("123123", @test_table._append_internal("123", 'x:cnt2', '123')) @@ -226,6 +385,34 @@ def teardown assert_equal('123321', @test_table._append_internal('1001', 'x', '321')) end + define_test "append should fail if no value passed as argument" do + assert_raise(ArgumentError) do + @test_table.append('441', 'x:cnt2') + end + end + + define_test 'multi column append should work with value' do + @test_table.append('401', {'x:a' => '12', 'x:b'=> '11'}) + assert_equal({'x:a' => '12123', 'x:b' => '11222'}, + @test_table._append_multi_column_internal('401', + {'x:a' => '123', 'x:b'=> '222'})) + end + + define_test 'multi column append should work with attributes' do + @test_table.append('402', {'x:a' => '123'}, + ATTRIBUTES=>{'mykey' => 'myvalue'}) + assert_equal({'x:a' => '123123', 'x:b' => '222'}, + @test_table._append_multi_column_internal('402', + {'x:a' => '123', 'x:b'=> '222'}, ATTRIBUTES => {'mykey' => 'myvalue'})) + end + + define_test 'multi column append should fail when args is passed as argument' do + assert_raise(ArgumentError) do + @test_table.append('451', {'x:a' => '3', 'x:b' => '4'}, + {ATTRIBUTES => {'mykey' => 'myvalue'}}, {'dummy_key' => 'dummy_value'}) + end + end + #------------------------------------------------------------------------------- define_test 'incr should work without qualifier' do @test_table.incr('1010', 'x', 123) @@ -234,6 +421,28 @@ def teardown assert_equal(246, @test_table._get_counter_internal('1010', 'x')) end + define_test 'multi column incr should work with value' do + @test_table.incr('501', {'x:a' => 11, 'x:b'=> 12}) + assert_equal({'x:a' => 111, 'x:b' => 13}, + @test_table._incr_multi_column_internal('501', {'x:a' => 100, 'x:b'=> 1})) + end + + define_test 'multi column incr should work w/o value' do + @test_table.incr('502', {'x:a' => 11, 'x:b'=> 12}) + assert_equal({'x:a' => 12, 'x:b' => 13}, + @test_table._incr_multi_column_internal('502', ['x:a', 'x:b'])) + end + + define_test 'multi column incr should work with attributes' do + @test_table.incr('503', {'x:a' => 123}, ATTRIBUTES => {'mykey' => 'myvalue'}) + assert_equal({'x:a' => 246, 'x:b'=> 222}, + @test_table._incr_multi_column_internal('503', {'x:a' => 123, 'x:b'=> 222}, + ATTRIBUTES => {'mykey' => 'myvalue'})) + assert_kind_of(Fixnum, @test_table._get_counter_internal('503', 'x:a')) + assert_kind_of(Fixnum, @test_table._get_counter_internal('503', 'x:b')) + end + + #------------------------------------------------------------------------------- define_test "get_counter should work with integer keys" do @test_table.incr(12345, 'x:cnt') assert_kind_of(Fixnum, @test_table._get_counter_internal(12345, 'x:cnt')) @@ -242,8 +451,31 @@ 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 'multi column get_counter should work with integer keys' do + @test_table.incr(601, ['x:cnt1', 'x:cnt2']) + assert_kind_of(Hash, @test_table._get_counter_multi_column_internal(601, + ['x:cnt1', 'x:cnt2'])) + assert_equal({'x:cnt1' => 1, 'x:cnt2' => 1}, + @test_table._get_counter_multi_column_internal(601, ['x:cnt1', 'x:cnt2'])) + end + + define_test 'multi column get_counter should return nil for non-existent counters' do + @test_table.incr('602', ['x:cnt1', 'x:cnt2']) + assert_nil(@test_table._get_counter_multi_column_internal('602', + ['x:qqqq', 'x:zzzz'])) + end + + define_test 'multi column get_counter should not return nil for one non-existent among two counters' do + @test_table.incr('603', 'x:cnt1') + assert_kind_of(Hash, @test_table._get_counter_multi_column_internal('603', + ['x:cnt1', 'x:zzz'])) + assert_equal({'x:cnt1' => 1}, + @test_table._get_counter_multi_column_internal('603', ['x:cnt1', 'x:zzz'])) + end end + #------------------------------------------------------------------------------- # Complex data management methods tests # rubocop:disable Metrics/ClassLength class TableComplexMethodsTest < Test::Unit::TestCase