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: ×tamp, 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.