github.com/tsuna/gohbase@v0.0.0-20250731002811-4ffcadfba63e/hrpc/call.go (about)

     1  // Copyright (C) 2015  The GoHBase Authors.  All rights reserved.
     2  // This file is part of GoHBase.
     3  // Use of this source code is governed by the Apache License 2.0
     4  // that can be found in the COPYING file.
     5  
     6  package hrpc
     7  
     8  import (
     9  	"context"
    10  	"encoding/binary"
    11  	"errors"
    12  	"fmt"
    13  	"unsafe"
    14  
    15  	"github.com/tsuna/gohbase/pb"
    16  	"google.golang.org/protobuf/proto"
    17  )
    18  
    19  // RegionInfo represents HBase region.
    20  type RegionInfo interface {
    21  	IsUnavailable() bool
    22  	AvailabilityChan() <-chan struct{}
    23  	MarkUnavailable() bool
    24  	MarkAvailable()
    25  	MarkDead()
    26  	Context() context.Context
    27  	String() string
    28  	ID() uint64
    29  	Name() []byte
    30  	StartKey() []byte
    31  	StopKey() []byte
    32  	Namespace() []byte
    33  	Table() []byte
    34  	SetClient(RegionClient)
    35  	Client() RegionClient
    36  }
    37  
    38  // RegionClient represents HBase region client.
    39  type RegionClient interface {
    40  	// Dial connects and bootstraps region client. Only the first caller to Dial gets to
    41  	// actually connect, other concurrent callers will block until connected or an error.
    42  	Dial(context.Context) error
    43  	Close()
    44  	Addr() string
    45  	QueueRPC(Call)
    46  	QueueBatch(context.Context, []Call)
    47  	String() string
    48  }
    49  
    50  // Call represents an HBase RPC call.
    51  type Call interface {
    52  	Table() []byte
    53  	Name() string
    54  	Key() []byte
    55  	Region() RegionInfo
    56  	SetRegion(region RegionInfo)
    57  	ToProto() proto.Message
    58  	// Returns a newly created (default-state) protobuf in which to store the
    59  	// response of this call.
    60  	NewResponse() proto.Message
    61  	ResultChan() chan RPCResult
    62  	Description() string // Used for tracing and metrics
    63  	Context() context.Context
    64  }
    65  
    66  type withOptions interface {
    67  	Options() []func(Call) error
    68  	setOptions([]func(Call) error)
    69  }
    70  
    71  // Batchable interface should be implemented by calls that can be batched into MultiRequest
    72  type Batchable interface {
    73  	// SkipBatch returns true if a call shouldn't be batched into MultiRequest and
    74  	// should be sent right away.
    75  	SkipBatch() bool
    76  
    77  	setSkipBatch(v bool)
    78  }
    79  
    80  // SkipBatch is an option for batchable requests (Get and Mutate) to tell
    81  // the client to skip batching and just send the request to Region Server
    82  // right away.
    83  func SkipBatch() func(Call) error {
    84  	return func(c Call) error {
    85  		if b, ok := c.(Batchable); ok {
    86  			b.setSkipBatch(true)
    87  			return nil
    88  		}
    89  		return errors.New("'SkipBatch' option only works with Get and Mutate requests")
    90  	}
    91  }
    92  
    93  // CanBatch returns true if this RPC can be included in a batch/multi
    94  // request.
    95  func CanBatch(c Call) bool {
    96  	b, ok := c.(Batchable)
    97  	return ok && !b.SkipBatch()
    98  }
    99  
   100  // hasQueryOptions is interface that needs to be implemented by calls
   101  // that allow to provide Families and Filters options.
   102  type hasQueryOptions interface {
   103  	setFamilies(families map[string][]string)
   104  	setFilter(filter *pb.Filter)
   105  	setTimeRangeUint64(from, to uint64)
   106  	setMaxVersions(versions uint32)
   107  	setMaxResultsPerColumnFamily(maxresults uint32)
   108  	setResultOffset(offset uint32)
   109  	setCacheBlocks(cacheBlocks bool)
   110  	setConsistency(consistency ConsistencyType)
   111  	setPriority(priority uint32)
   112  }
   113  
   114  // RPCResult is struct that will contain both the resulting message from an RPC
   115  // call, and any errors that may have occurred related to making the RPC call.
   116  type RPCResult struct {
   117  	Msg   proto.Message
   118  	Error error
   119  }
   120  
   121  type base struct {
   122  	ctx context.Context
   123  
   124  	table   []byte
   125  	key     []byte
   126  	options []func(Call) error
   127  
   128  	region   RegionInfo
   129  	resultch chan RPCResult
   130  }
   131  
   132  func (b *base) Context() context.Context {
   133  	return b.ctx
   134  }
   135  
   136  func (b *base) Region() RegionInfo {
   137  	return b.region
   138  }
   139  
   140  func (b *base) SetRegion(region RegionInfo) {
   141  	b.region = region
   142  }
   143  
   144  var RegionSpecifierRegionName = pb.RegionSpecifier_REGION_NAME.Enum()
   145  
   146  func (b *base) regionSpecifier() *pb.RegionSpecifier {
   147  	if i, ok := b.region.(interface{ RegionSpecifier() *pb.RegionSpecifier }); ok {
   148  		return i.RegionSpecifier()
   149  	}
   150  	return &pb.RegionSpecifier{
   151  		Type:  RegionSpecifierRegionName,
   152  		Value: b.region.Name(),
   153  	}
   154  }
   155  
   156  func (b *base) setOptions(options []func(Call) error) {
   157  	b.options = options
   158  }
   159  
   160  // Options returns all the options passed to this call
   161  func (b *base) Options() []func(Call) error {
   162  	return b.options
   163  }
   164  
   165  func applyOptions(call Call, options ...func(Call) error) error {
   166  	call.(withOptions).setOptions(options)
   167  	for _, option := range options {
   168  		err := option(call)
   169  		if err != nil {
   170  			return err
   171  		}
   172  	}
   173  	return nil
   174  }
   175  
   176  func (b *base) Table() []byte {
   177  	return b.table
   178  }
   179  
   180  func (b *base) Key() []byte {
   181  	return b.key
   182  }
   183  
   184  func (b *base) ResultChan() chan RPCResult {
   185  	return b.resultch
   186  }
   187  
   188  // Cell is the smallest level of granularity in returned results.
   189  // Represents a single cell in HBase (a row will have one cell for every qualifier).
   190  type Cell pb.Cell
   191  
   192  func (c *Cell) String() string {
   193  	return (*pb.Cell)(c).String()
   194  }
   195  
   196  // cellFromCellBlock deserializes a cell from a reader
   197  func cellFromCellBlock(b []byte) (*pb.Cell, uint32, error) {
   198  	if len(b) < 4 {
   199  		return nil, 0, fmt.Errorf(
   200  			"buffer is too small: expected %d, got %d", 4, len(b))
   201  	}
   202  
   203  	kvLen := binary.BigEndian.Uint32(b[0:4])
   204  	if len(b) < int(kvLen)+4 {
   205  		return nil, 0, fmt.Errorf(
   206  			"buffer is too small: expected %d, got %d", int(kvLen)+4, len(b))
   207  	}
   208  
   209  	rowKeyLen := binary.BigEndian.Uint32(b[4:8])
   210  	valueLen := binary.BigEndian.Uint32(b[8:12])
   211  	keyLen := binary.BigEndian.Uint16(b[12:14])
   212  	b = b[14:]
   213  
   214  	key := b[:keyLen]
   215  	b = b[keyLen:]
   216  
   217  	familyLen := b[0]
   218  	b = b[1:]
   219  
   220  	family := b[:familyLen]
   221  	b = b[familyLen:]
   222  
   223  	qualifierLen := rowKeyLen - uint32(keyLen) - uint32(familyLen) - 2 - 1 - 8 - 1
   224  	if 4 /*rowKeyLen*/ +4 /*valueLen*/ +2 /*keyLen*/ +
   225  		uint32(keyLen)+1 /*familyLen*/ +uint32(familyLen)+qualifierLen+
   226  		8 /*timestamp*/ +1 /*cellType*/ +valueLen != kvLen {
   227  		return nil, 0, fmt.Errorf("HBase has lied about KeyValue length: expected %d, got %d",
   228  			kvLen, 4+4+2+uint32(keyLen)+1+uint32(familyLen)+qualifierLen+8+1+valueLen)
   229  	}
   230  	qualifier := b[:qualifierLen]
   231  	b = b[qualifierLen:]
   232  
   233  	timestamp := binary.BigEndian.Uint64(b[:8])
   234  	b = b[8:]
   235  
   236  	cellType := b[0]
   237  	b = b[1:]
   238  
   239  	value := b[:valueLen]
   240  
   241  	return &pb.Cell{
   242  		Row:       key,
   243  		Family:    family,
   244  		Qualifier: qualifier,
   245  		Timestamp: &timestamp,
   246  		Value:     value,
   247  		CellType:  pb.CellType(cellType).Enum(),
   248  	}, kvLen + 4, nil
   249  }
   250  
   251  func deserializeCellBlocks(b []byte, cellsLen uint32) ([]*pb.Cell, uint32, error) {
   252  	cells := make([]*pb.Cell, cellsLen)
   253  	var readLen uint32
   254  	for i := 0; i < int(cellsLen); i++ {
   255  		c, l, err := cellFromCellBlock(b[readLen:])
   256  		if err != nil {
   257  			return nil, readLen, err
   258  		}
   259  		cells[i] = c
   260  		readLen += l
   261  	}
   262  	return cells, readLen, nil
   263  }
   264  
   265  // Result holds a slice of Cells as well as miscellaneous information about the response.
   266  type Result struct {
   267  	Cells   []*Cell
   268  	Stale   bool
   269  	Partial bool
   270  	// Exists is only set if existence_only was set in the request query.
   271  	Exists *bool
   272  }
   273  
   274  func (c *Result) String() string {
   275  	return fmt.Sprintf("cells:%v stale:%v partial:%v exists:%v",
   276  		c.Cells, c.Stale, c.Partial, c.Exists)
   277  }
   278  
   279  func extractBool(v *bool) bool {
   280  	return v != nil && *v
   281  }
   282  
   283  // ToLocalResult takes a protobuf Result type and converts it to our own
   284  // Result type in constant time.
   285  func ToLocalResult(pbr *pb.Result) *Result {
   286  	if pbr == nil {
   287  		return &Result{}
   288  	}
   289  	return &Result{
   290  		// Should all be O(1) operations.
   291  		Cells:   toLocalCells(pbr),
   292  		Stale:   extractBool(pbr.Stale),
   293  		Partial: extractBool(pbr.Partial),
   294  		Exists:  pbr.Exists,
   295  	}
   296  }
   297  
   298  func toLocalCells(pbr *pb.Result) []*Cell {
   299  	return *(*[]*Cell)(unsafe.Pointer(&pbr.Cell))
   300  }
   301  
   302  // We can now define any helper functions on Result that we want.