HBASE-23232 Remove rsgroup profile from pom.xml of hbase-assembly (#779)
[hbase.git] / hbase-shell / src / main / ruby / hbase / table.rb
blobc5d19a4e2aeebfd42c6a12578aca0b5fd8329283
3 # Licensed to the Apache Software Foundation (ASF) under one
4 # or more contributor license agreements.  See the NOTICE file
5 # distributed with this work for additional information
6 # regarding copyright ownership.  The ASF licenses this file
7 # to you under the Apache License, Version 2.0 (the
8 # "License"); you may not use this file except in compliance
9 # with the License.  You may obtain a copy of the License at
11 #     http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
20 include Java
22 java_import org.apache.hadoop.hbase.util.Bytes
23 java_import org.apache.hadoop.hbase.client.RegionReplicaUtil
25 # Wrapper for org.apache.hadoop.hbase.client.Table
27 module Hbase
28   # rubocop:disable Metrics/ClassLength
29   class Table
30     include HBaseConstants
31     @@thread_pool = nil
33     # Add the command 'name' to table s.t. the shell command also called via 'name'
34     # and has an internal method also called 'name'.
35     #
36     # e.g. name = scan, adds table.scan which calls Scan.scan
37     def self.add_shell_command(name)
38       add_command(name, name, name)
39     end
41     # add a named command to the table instance
42     #
43     # name - name of the command that should added to the table
44     #    (eg. sending 'scan' here would allow you to do table.scan)
45     # shell_command - name of the command in the shell
46     # internal_method_name - name of the method in the shell command to forward the call
47     def self.add_command(name, shell_command, internal_method_name)
48       method = name.to_sym
49       class_eval do
50         define_method method do |*args|
51           @shell.internal_command(shell_command, internal_method_name, self, *args)
52         end
53       end
54     end
56     # General help for the table
57     # class level so we can call it from anywhere
58     def self.help
59       <<-EOF
60 Help for table-reference commands.
62 You can either create a table via 'create' and then manipulate the table via commands like 'put', 'get', etc.
63 See the standard help information for how to use each of these commands.
65 However, as of 0.96, you can also get a reference to a table, on which you can invoke commands.
66 For instance, you can get create a table and keep around a reference to it via:
68    hbase> t = create 't', 'cf'
70 Or, if you have already created the table, you can get a reference to it:
72    hbase> t = get_table 't'
74 You can do things like call 'put' on the table:
76   hbase> t.put 'r', 'cf:q', 'v'
78 which puts a row 'r' with column family 'cf', qualifier 'q' and value 'v' into table t.
80 To read the data out, you can scan the table:
82   hbase> t.scan
84 which will read all the rows in table 't'.
86 Essentially, any command that takes a table name can also be done via table reference.
87 Other commands include things like: get, delete, deleteall,
88 get_all_columns, get_counter, count, incr. These functions, along with
89 the standard JRuby object methods are also available via tab completion.
91 For more information on how to use each of these commands, you can also just type:
93    hbase> t.help 'scan'
95 which will output more information on how to use that command.
97 You can also do general admin actions directly on a table; things like enable, disable,
98 flush and drop just by typing:
100    hbase> t.enable
101    hbase> t.flush
102    hbase> t.disable
103    hbase> t.drop
105 Note that after dropping a table, your reference to it becomes useless and further usage
106 is undefined (and not recommended).
108     end
110     #---------------------------------------------------------------------------------------------
112     # let external objects read the underlying table object
113     attr_reader :table
114     # let external objects read the table name
115     attr_reader :name
117     def initialize(table, shell)
118       @table = table
119       @name = @table.getName.getNameAsString
120       @shell = shell
121       @converters = {}
122     end
124     def close
125       @table.close
126     end
128     # Note the below methods are prefixed with '_' to hide them from the average user, as
129     # they will be much less likely to tab complete to the 'dangerous' internal method
130     #----------------------------------------------------------------------------------------------
132     # Put a cell 'value' at specified table/row/column
133     def _put_internal(row, column, value, timestamp = nil, args = {})
134       p = org.apache.hadoop.hbase.client.Put.new(row.to_s.to_java_bytes)
135       family, qualifier = parse_column_name(column)
136       if args.any?
137         attributes = args[ATTRIBUTES]
138         set_attributes(p, attributes) if attributes
139         visibility = args[VISIBILITY]
140         set_cell_visibility(p, visibility) if visibility
141         ttl = args[TTL]
142         set_op_ttl(p, ttl) if ttl
143       end
144       # Case where attributes are specified without timestamp
145       if timestamp.is_a?(Hash)
146         timestamp.each do |k, v|
147           if k == 'ATTRIBUTES'
148             set_attributes(p, v)
149           elsif k == 'VISIBILITY'
150             set_cell_visibility(p, v)
151           elsif k == 'TTL'
152             set_op_ttl(p, v)
153           end
154         end
155         timestamp = nil
156       end
157       if timestamp
158         p.addColumn(family, qualifier, timestamp, value.to_s.to_java_bytes)
159       else
160         p.addColumn(family, qualifier, value.to_s.to_java_bytes)
161       end
162       @table.put(p)
163     end
165     #----------------------------------------------------------------------------------------------
166     # Create a Delete mutation
167     def _createdelete_internal(row, column = nil,
168                                timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP,
169                                args = {}, all_version = true)
170       temptimestamp = timestamp
171       if temptimestamp.is_a?(Hash)
172         timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP
173       end
174       d = org.apache.hadoop.hbase.client.Delete.new(row.to_s.to_java_bytes, timestamp)
175       if temptimestamp.is_a?(Hash)
176         temptimestamp.each do |_k, v|
177           if v.is_a?(String)
178             set_cell_visibility(d, v) if v
179           end
180         end
181       end
182       if args.any?
183         visibility = args[VISIBILITY]
184         set_cell_visibility(d, visibility) if visibility
185       end
186       if column && all_version
187         family, qualifier = parse_column_name(column)
188         d.addColumns(family, qualifier, timestamp)
189       elsif column && !all_version
190         family, qualifier = parse_column_name(column)
191         d.addColumn(family, qualifier, timestamp)
192       end
193       d
194     end
196     #----------------------------------------------------------------------------------------------
197     # Delete rows using prefix
198     def _deleterows_internal(row, column = nil,
199                              timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP,
200                              args = {}, all_version = true)
201       cache = row['CACHE'] ? row['CACHE'] : 100
202       prefix = row['ROWPREFIXFILTER']
204       # create scan to get table names using prefix
205       scan = org.apache.hadoop.hbase.client.Scan.new
206       scan.setRowPrefixFilter(prefix.to_java_bytes)
207       # Run the scanner to get all rowkeys
208       scanner = @table.getScanner(scan)
209       # Create a list to store all deletes
210       list = java.util.ArrayList.new
211       # Iterate results
212       iter = scanner.iterator
213       while iter.hasNext
214         row = iter.next
215         key = org.apache.hadoop.hbase.util.Bytes.toStringBinary(row.getRow)
216         d = _createdelete_internal(key, column, timestamp, args, all_version)
217         list.add(d)
218         if list.size >= cache
219           @table.delete(list)
220           list.clear
221         end
222       end
223       @table.delete(list)
224     end
226     #----------------------------------------------------------------------------------------------
227     # Delete a cell
228     def _delete_internal(row, column,
229                          timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP,
230                          args = {}, all_version = false)
231       _deleteall_internal(row, column, timestamp, args, all_version)
232     end
234     #----------------------------------------------------------------------------------------------
235     # Delete a row
236     def _deleteall_internal(row, column = nil,
237                             timestamp = org.apache.hadoop.hbase.HConstants::LATEST_TIMESTAMP,
238                             args = {}, all_version = true)
239       # delete operation doesn't need read permission. Retaining the read check for
240       # meta table as a part of HBASE-5837.
241       if is_meta_table?
242         raise ArgumentError, 'Row Not Found' if _get_internal(row).nil?
243       end
244       if row.is_a?(Hash)
245         _deleterows_internal(row, column, timestamp, args, all_version)
246       else
247         d = _createdelete_internal(row, column, timestamp, args, all_version)
248         @table.delete(d)
249       end
250     end
252     #----------------------------------------------------------------------------------------------
253     # Increment a counter atomically
254     # rubocop:disable Metrics/AbcSize, CyclomaticComplexity, MethodLength
255     def _incr_internal(row, column, value = nil, args = {})
256       value = 1 if value.is_a?(Hash)
257       value ||= 1
258       incr = org.apache.hadoop.hbase.client.Increment.new(row.to_s.to_java_bytes)
259       family, qualifier = parse_column_name(column)
260       if args.any?
261         attributes = args[ATTRIBUTES]
262         visibility = args[VISIBILITY]
263         set_attributes(incr, attributes) if attributes
264         set_cell_visibility(incr, visibility) if visibility
265         ttl = args[TTL]
266         set_op_ttl(incr, ttl) if ttl
267       end
268       incr.addColumn(family, qualifier, value)
269       result = @table.increment(incr)
270       return nil if result.isEmpty
272       # Fetch cell value
273       cell = result.listCells[0]
274       org.apache.hadoop.hbase.util.Bytes.toLong(cell.getValueArray,
275                                                 cell.getValueOffset, cell.getValueLength)
276     end
278     #----------------------------------------------------------------------------------------------
279     # appends the value atomically
280     def _append_internal(row, column, value, args = {})
281       append = org.apache.hadoop.hbase.client.Append.new(row.to_s.to_java_bytes)
282       family, qualifier = parse_column_name(column)
283       if args.any?
284         attributes = args[ATTRIBUTES]
285         visibility = args[VISIBILITY]
286         set_attributes(append, attributes) if attributes
287         set_cell_visibility(append, visibility) if visibility
288         ttl = args[TTL]
289         set_op_ttl(append, ttl) if ttl
290       end
291       append.addColumn(family, qualifier, value.to_s.to_java_bytes)
292       result = @table.append(append)
293       return nil if result.isEmpty
295       # Fetch cell value
296       cell = result.listCells[0]
297       org.apache.hadoop.hbase.util.Bytes.toStringBinary(cell.getValueArray,
298                                                         cell.getValueOffset, cell.getValueLength)
299     end
300     # rubocop:enable Metrics/AbcSize, CyclomaticComplexity, MethodLength
302     #----------------------------------------------------------------------------------------------
303     # Count rows in a table
304     def _count_internal(interval = 1000, scan = nil)
305       raise(ArgumentError, 'Scan argument should be org.apache.hadoop.hbase.client.Scan') \
306         unless scan.nil? || scan.is_a?(org.apache.hadoop.hbase.client.Scan)
307       # We can safely set scanner caching with the first key only filter
309       if scan.nil?
310         scan = org.apache.hadoop.hbase.client.Scan.new
311         scan.setCacheBlocks(false)
312         scan.setCaching(10)
313         scan.setFilter(org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter.new)
314       else
315         scan.setCacheBlocks(false)
316         filter = scan.getFilter
317         firstKeyOnlyFilter = org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter.new
318         if filter.nil?
319           scan.setFilter(firstKeyOnlyFilter)
320         else
321           firstKeyOnlyFilter.setReversed(filter.isReversed)
322           scan.setFilter(org.apache.hadoop.hbase.filter.FilterList.new(filter, firstKeyOnlyFilter))
323         end
324       end
326       # Run the scanner
327       scanner = @table.getScanner(scan)
328       count = 0
329       iter = scanner.iterator
331       # Iterate results
332       while iter.hasNext
333         row = iter.next
334         count += 1
335         next unless block_given? && count % interval == 0
336         # Allow command modules to visualize counting process
337         yield(count,
338               org.apache.hadoop.hbase.util.Bytes.toStringBinary(row.getRow))
339       end
341       scanner.close
342       # Return the counter
343       count
344     end
346     #----------------------------------------------------------------------------------------------
347     # Get from table
348     def _get_internal(row, *args)
349       get = org.apache.hadoop.hbase.client.Get.new(row.to_s.to_java_bytes)
350       maxlength = -1
351       count = 0
352       @converters.clear
354       # Normalize args
355       args = args.first if args.first.is_a?(Hash)
356       if args.is_a?(String) || args.is_a?(Array)
357         columns = [args].flatten.compact
358         args = { COLUMNS => columns }
359       end
361       #
362       # Parse arguments
363       #
364       unless args.is_a?(Hash)
365         raise ArgumentError, "Failed parse of #{args.inspect}, #{args.class}"
366       end
368       # Get maxlength parameter if passed
369       maxlength = args.delete(MAXLENGTH) if args[MAXLENGTH]
370       filter = args.delete(FILTER) if args[FILTER]
371       attributes = args[ATTRIBUTES]
372       authorizations = args[AUTHORIZATIONS]
373       consistency = args.delete(CONSISTENCY) if args[CONSISTENCY]
374       replicaId = args.delete(REGION_REPLICA_ID) if args[REGION_REPLICA_ID]
375       converter = args.delete(FORMATTER) || nil
376       converter_class = args.delete(FORMATTER_CLASS) || 'org.apache.hadoop.hbase.util.Bytes'
377       unless args.empty?
378         columns = args[COLUMN] || args[COLUMNS]
379         vers = if args[VERSIONS]
380                  args[VERSIONS]
381                else
382                  1
383                end
384         if columns
385           # Normalize types, convert string to an array of strings
386           columns = [columns] if columns.is_a?(String)
388           # At this point it is either an array or some unsupported stuff
389           unless columns.is_a?(Array)
390             raise ArgumentError, "Failed parse column argument type #{args.inspect}, #{args.class}"
391           end
393           # Get each column name and add it to the filter
394           columns.each do |column|
395             family, qualifier = parse_column_name(column.to_s)
396             if qualifier
397               get.addColumn(family, qualifier)
398             else
399               get.addFamily(family)
400             end
401           end
403           # Additional params
404           get.readVersions(vers)
405           get.setTimestamp(args[TIMESTAMP]) if args[TIMESTAMP]
406           get.setTimeRange(args[TIMERANGE][0], args[TIMERANGE][1]) if args[TIMERANGE]
407         else
408           if attributes
409             set_attributes(get, attributes)
410           elsif authorizations
411             set_authorizations(get, authorizations)
412           else
413             # May have passed TIMESTAMP and row only; wants all columns from ts.
414             unless ts = args[TIMESTAMP] || tr = args[TIMERANGE]
415               raise ArgumentError, "Failed parse of #{args.inspect}, #{args.class}"
416             end
417           end
419           get.readVersions(vers)
420           # Set the timestamp/timerange
421           get.setTimestamp(ts.to_i) if args[TIMESTAMP]
422           get.setTimeRange(args[TIMERANGE][0], args[TIMERANGE][1]) if args[TIMERANGE]
423         end
424         set_attributes(get, attributes) if attributes
425         set_authorizations(get, authorizations) if authorizations
426       end
428       if filter.class == String
429         get.setFilter(
430           org.apache.hadoop.hbase.filter.ParseFilter.new.parseFilterString(filter.to_java_bytes)
431         )
432       else
433         get.setFilter(filter)
434       end
436       get.setConsistency(org.apache.hadoop.hbase.client.Consistency.valueOf(consistency)) if consistency
437       get.setReplicaId(replicaId) if replicaId
439       # Call hbase for the results
440       result = @table.get(get)
441       return nil if result.isEmpty
443       # Get stale info from results
444       is_stale = result.isStale
445       count += 1
447       # Print out results.  Result can be Cell or RowResult.
448       res = {}
449       result.listCells.each do |c|
450         family = convert_bytes_with_position(c.getFamilyArray,
451                                              c.getFamilyOffset, c.getFamilyLength, converter_class, converter)
452         qualifier = convert_bytes_with_position(c.getQualifierArray,
453                                                 c.getQualifierOffset, c.getQualifierLength, converter_class, converter)
455         column = "#{family}:#{qualifier}"
456         value = to_string(column, c, maxlength, converter_class, converter)
458         if block_given?
459           yield(column, value)
460         else
461           res[column] = value
462         end
463       end
465       # If block given, we've yielded all the results, otherwise just return them
466       (block_given? ? [count, is_stale] : res)
467     end
469     #----------------------------------------------------------------------------------------------
470     # Fetches and decodes a counter value from hbase
471     def _get_counter_internal(row, column)
472       family, qualifier = parse_column_name(column.to_s)
473       # Format get request
474       get = org.apache.hadoop.hbase.client.Get.new(row.to_s.to_java_bytes)
475       get.addColumn(family, qualifier)
476       get.readVersions(1)
478       # Call hbase
479       result = @table.get(get)
480       return nil if result.isEmpty
482       # Fetch cell value
483       cell = result.listCells[0]
484       org.apache.hadoop.hbase.util.Bytes.toLong(cell.getValueArray,
485                                                 cell.getValueOffset, cell.getValueLength)
486     end
488     def _hash_to_scan(args)
489       if args.any?
490         enablemetrics = args['ALL_METRICS'].nil? ? false : args['ALL_METRICS']
491         enablemetrics ||= !args['METRICS'].nil?
492         filter = args['FILTER']
493         startrow = args['STARTROW'] || ''
494         stoprow = args['STOPROW']
495         rowprefixfilter = args['ROWPREFIXFILTER']
496         timestamp = args['TIMESTAMP']
497         columns = args['COLUMNS'] || args['COLUMN'] || []
498         # If CACHE_BLOCKS not set, then default 'true'.
499         cache_blocks = args['CACHE_BLOCKS'].nil? ? true : args['CACHE_BLOCKS']
500         cache = args['CACHE'] || 0
501         reversed = args['REVERSED'] || false
502         versions = args['VERSIONS'] || 1
503         timerange = args[TIMERANGE]
504         raw = args['RAW'] || false
505         attributes = args[ATTRIBUTES]
506         authorizations = args[AUTHORIZATIONS]
507         consistency = args[CONSISTENCY]
508         # Normalize column names
509         columns = [columns] if columns.class == String
510         limit = args['LIMIT'] || -1
511         replica_id = args[REGION_REPLICA_ID]
512         isolation_level = args[ISOLATION_LEVEL]
513         read_type = args[READ_TYPE]
514         allow_partial_results = args[ALLOW_PARTIAL_RESULTS].nil? ? false : args[ALLOW_PARTIAL_RESULTS]
515         batch = args[BATCH] || -1
516         max_result_size = args[MAX_RESULT_SIZE] || -1
518         unless columns.is_a?(Array)
519           raise ArgumentError, 'COLUMNS must be specified as a String or an Array'
520         end
522         scan = if stoprow
523                  org.apache.hadoop.hbase.client.Scan.new(startrow.to_java_bytes, stoprow.to_java_bytes)
524                else
525                  org.apache.hadoop.hbase.client.Scan.new(startrow.to_java_bytes)
526                end
528         # This will overwrite any startrow/stoprow settings
529         scan.setRowPrefixFilter(rowprefixfilter.to_java_bytes) if rowprefixfilter
531         # Clear converters from last scan.
532         @converters.clear
534         columns.each do |c|
535           family, qualifier = parse_column_name(c.to_s)
536           if qualifier
537             scan.addColumn(family, qualifier)
538           else
539             scan.addFamily(family)
540           end
541         end
543         if filter.class == String
544           scan.setFilter(
545             org.apache.hadoop.hbase.filter.ParseFilter.new.parseFilterString(filter.to_java_bytes)
546           )
547         else
548           scan.setFilter(filter)
549         end
551         scan.setScanMetricsEnabled(enablemetrics) if enablemetrics
552         scan.setTimeStamp(timestamp) if timestamp
553         scan.setCacheBlocks(cache_blocks)
554         scan.setReversed(reversed)
555         scan.setCaching(cache) if cache > 0
556         scan.readVersions(versions) if versions > 1
557         scan.setTimeRange(timerange[0], timerange[1]) if timerange
558         scan.setRaw(raw)
559         scan.setLimit(limit) if limit > 0
560         set_attributes(scan, attributes) if attributes
561         set_authorizations(scan, authorizations) if authorizations
562         scan.setConsistency(org.apache.hadoop.hbase.client.Consistency.valueOf(consistency)) if consistency
563         scan.setReplicaId(replica_id) if replica_id
564         scan.setIsolationLevel(org.apache.hadoop.hbase.client.IsolationLevel.valueOf(isolation_level)) if isolation_level
565         scan.setReadType(org.apache.hadoop.hbase.client::Scan::ReadType.valueOf(read_type)) if read_type
566         scan.setAllowPartialResults(allow_partial_results) if allow_partial_results
567         scan.setBatch(batch) if batch > 0
568         scan.setMaxResultSize(max_result_size) if max_result_size > 0
569       else
570         scan = org.apache.hadoop.hbase.client.Scan.new
571       end
573       scan
574     end
576     def _get_scanner(args)
577       @table.getScanner(_hash_to_scan(args))
578     end
580     #----------------------------------------------------------------------------------------------
581     # Scans whole table or a range of keys and returns rows matching specific criteria
582     def _scan_internal(args = {}, scan = nil)
583       raise(ArgumentError, 'Args should be a Hash') unless args.is_a?(Hash)
584       raise(ArgumentError, 'Scan argument should be org.apache.hadoop.hbase.client.Scan') \
585         unless scan.nil? || scan.is_a?(org.apache.hadoop.hbase.client.Scan)
587       maxlength = args.delete('MAXLENGTH') || -1
588       converter = args.delete(FORMATTER) || nil
589       converter_class = args.delete(FORMATTER_CLASS) || 'org.apache.hadoop.hbase.util.Bytes'
590       count = 0
591       res = {}
593       # Start the scanner
594       scan = scan.nil? ? _hash_to_scan(args) : scan
595       scanner = @table.getScanner(scan)
596       iter = scanner.iterator
598       # Iterate results
599       while iter.hasNext
600         row = iter.next
601         key = convert_bytes(row.getRow, nil, converter)
602         is_stale |= row.isStale
604         row.listCells.each do |c|
605           family = convert_bytes_with_position(c.getFamilyArray,
606                                                c.getFamilyOffset, c.getFamilyLength, converter_class, converter)
607           qualifier = convert_bytes_with_position(c.getQualifierArray,
608                                                   c.getQualifierOffset, c.getQualifierLength, converter_class, converter)
610           column = "#{family}:#{qualifier}"
611           cell = to_string(column, c, maxlength, converter_class, converter)
613           if block_given?
614             yield(key, "column=#{column}, #{cell}")
615           else
616             res[key] ||= {}
617             res[key][column] = cell
618           end
619         end
620         # One more row processed
621         count += 1
622       end
624       scanner.close
625       (block_given? ? [count, is_stale] : res)
626     end
628     # Apply OperationAttributes to puts/scans/gets
629     def set_attributes(oprattr, attributes)
630       raise(ArgumentError, 'Attributes must be a Hash type') unless attributes.is_a?(Hash)
631       for k, v in attributes
632         v = v.to_s unless v.nil?
633         oprattr.setAttribute(k.to_s, v.to_java_bytes)
634       end
635     end
637     def set_cell_permissions(op, permissions)
638       raise(ArgumentError, 'Permissions must be a Hash type') unless permissions.is_a?(Hash)
639       map = java.util.HashMap.new
640       permissions.each do |user, perms|
641         map.put(user.to_s, org.apache.hadoop.hbase.security.access.Permission.new(
642                              perms.to_java_bytes
643         ))
644       end
645       op.setACL(map)
646     end
648     def set_cell_visibility(oprattr, visibility)
649       oprattr.setCellVisibility(
650         org.apache.hadoop.hbase.security.visibility.CellVisibility.new(
651           visibility.to_s
652         )
653       )
654     end
656     def set_authorizations(oprattr, authorizations)
657       raise(ArgumentError, 'Authorizations must be a Array type') unless authorizations.is_a?(Array)
658       auths = [authorizations].flatten.compact
659       oprattr.setAuthorizations(
660         org.apache.hadoop.hbase.security.visibility.Authorizations.new(
661           auths.to_java(:string)
662         )
663       )
664     end
666     def set_op_ttl(op, ttl)
667       op.setTTL(ttl.to_java(:long))
668     end
670     #----------------------------
671     # Add general administration utilities to the shell
672     # each of the names below adds this method name to the table
673     # by callling the corresponding method in the shell
674     # Add single method utilities to the current class
675     # Generally used for admin functions which just have one name and take the table name
676     def self.add_admin_utils(*args)
677       args.each do |method|
678         define_method method do |*method_args|
679           @shell.command(method, @name, *method_args)
680         end
681       end
682     end
684     # Add the following admin utilities to the table
685     add_admin_utils :enable, :disable, :flush, :drop, :describe, :snapshot
687     #----------------------------
688     # give the general help for the table
689     # or the named command
690     def help(command = nil)
691       # if there is a command, get the per-command help from the shell
692       if command
693         begin
694           return @shell.help_command(command)
695         rescue NoMethodError
696           puts "Command \'#{command}\' does not exist. Please see general table help."
697           return nil
698         end
699       end
700       @shell.help('table_help')
701     end
703     # Table to string
704     def to_s
705       cl = self.class
706       "#{cl} - #{@name}"
707     end
709     # Standard ruby call to get the return value for an object
710     # overriden here so we get sane semantics for printing a table on return
711     def inspect
712       to_s
713     end
715     #----------------------------------------------------------------------------------------
716     # Helper methods
718     # Returns a list of column names in the table
719     def get_all_columns
720       @table.descriptor.getColumnFamilies.map do |family|
721         "#{family.getNameAsString}:"
722       end
723     end
725     # Checks if current table is one of the 'meta' tables
726     def is_meta_table?
727       org.apache.hadoop.hbase.TableName::META_TABLE_NAME.equals(@table.getName)
728     end
730     # Returns family and (when has it) qualifier for a column name
731     def parse_column_name(column)
732       split = org.apache.hadoop.hbase.CellUtil.parseColumn(column.to_java_bytes)
733       set_converter(split) if split.length > 1
734       [split[0], split.length > 1 ? split[1] : nil]
735     end
737     # Make a String of the passed kv
738     # Intercept cells whose format we know such as the info:regioninfo in hbase:meta
739     def to_string(column, kv, maxlength = -1, converter_class = nil, converter = nil)
740       if is_meta_table?
741         if column == 'info:regioninfo' || column == 'info:splitA' || column == 'info:splitB'
742           hri = org.apache.hadoop.hbase.HRegionInfo.parseFromOrNull(kv.getValueArray,
743                                                                     kv.getValueOffset, kv.getValueLength)
744           return format('timestamp=%d, value=%s', kv.getTimestamp, hri.nil? ? '' : hri.toString)
745         end
746         if column == 'info:serverstartcode'
747           if kv.getValueLength > 0
748             str_val = org.apache.hadoop.hbase.util.Bytes.toLong(kv.getValueArray,
749                                                                 kv.getValueOffset, kv.getValueLength)
750           else
751             str_val = org.apache.hadoop.hbase.util.Bytes.toStringBinary(kv.getValueArray,
752                                                                         kv.getValueOffset, kv.getValueLength)
753           end
754           return format('timestamp=%d, value=%s', kv.getTimestamp, str_val)
755         end
756       end
758       if org.apache.hadoop.hbase.CellUtil.isDelete(kv)
759         val = "timestamp=#{kv.getTimestamp}, type=#{org.apache.hadoop.hbase.KeyValue::Type.codeToType(kv.getTypeByte)}"
760       else
761         val = "timestamp=#{kv.getTimestamp}, value=#{convert(column, kv, converter_class, converter)}"
762       end
763       maxlength != -1 ? val[0, maxlength] : val
764     end
766     def convert(column, kv, converter_class = 'org.apache.hadoop.hbase.util.Bytes', converter = 'toStringBinary')
767       # use org.apache.hadoop.hbase.util.Bytes as the default class
768       converter_class = 'org.apache.hadoop.hbase.util.Bytes' unless converter_class
769       # use org.apache.hadoop.hbase.util.Bytes::toStringBinary as the default convertor
770       converter = 'toStringBinary' unless converter
771       if @converters.key?(column)
772         # lookup the CONVERTER for certain column - "cf:qualifier"
773         matches = /c\((.+)\)\.(.+)/.match(@converters[column])
774         if matches.nil?
775           # cannot match the pattern of 'c(className).functionname'
776           # use the default klazz_name
777           converter = @converters[column]
778         else
779           klazz_name = matches[1]
780           converter = matches[2]
781         end
782       end
783       # apply the converter
784       convert_bytes(org.apache.hadoop.hbase.CellUtil.cloneValue(kv), klazz_name, converter)
785     end
787     def convert_bytes(bytes, converter_class = nil, converter_method = nil)
788       # Avoid nil
789       converter_class ||= 'org.apache.hadoop.hbase.util.Bytes'
790       converter_method ||= 'toStringBinary'
791       eval(converter_class).method(converter_method).call(bytes)
792     end
794     def convert_bytes_with_position(bytes, offset, len, converter_class, converter_method)
795       # Avoid nil
796       converter_class ||= 'org.apache.hadoop.hbase.util.Bytes'
797       converter_method ||= 'toStringBinary'
798       eval(converter_class).method(converter_method).call(bytes, offset, len)
799     end
801     # if the column spec contains CONVERTER information, to get rid of :CONVERTER info from column pair.
802     # 1. return back normal column pair as usual, i.e., "cf:qualifier[:CONVERTER]" to "cf" and "qualifier" only
803     # 2. register the CONVERTER information based on column spec - "cf:qualifier"
804     def set_converter(column)
805       family = String.from_java_bytes(column[0])
806       parts = org.apache.hadoop.hbase.CellUtil.parseColumn(column[1])
807       if parts.length > 1
808         @converters["#{family}:#{String.from_java_bytes(parts[0])}"] = String.from_java_bytes(parts[1])
809         column[1] = parts[0]
810       end
811     end
813     #----------------------------------------------------------------------------------------------
814     # Get the split points for the table
815     def _get_splits_internal
816       locator = @table.getRegionLocator
817       locator.getAllRegionLocations
818              .select { |s| RegionReplicaUtil.isDefaultReplica(s.getRegion) }
819              .map { |i| Bytes.toStringBinary(i.getRegion.getStartKey) }
820              .delete_if { |k| k == '' }
821     ensure
822       locator.close
823     end
824   end
825   # rubocop:enable Metrics/ClassLength