mirror of
https://github.com/iLoveElysia/openbilibili.git
synced 2026-09-02 07:38:04 -05:00
277 lines
6.4 KiB
Go
277 lines
6.4 KiB
Go
// Copyright (C) 2015 The GoHBase Authors. All rights reserved.
|
|
// This file is part of GoHBase.
|
|
// Use of this source code is governed by the Apache License 2.0
|
|
// that can be found in the COPYING file.
|
|
|
|
package hrpc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"unsafe"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/tsuna/gohbase/pb"
|
|
)
|
|
|
|
// RegionInfo represents HBase region.
|
|
type RegionInfo interface {
|
|
IsUnavailable() bool
|
|
AvailabilityChan() <-chan struct{}
|
|
MarkUnavailable() bool
|
|
MarkAvailable()
|
|
MarkDead()
|
|
Context() context.Context
|
|
String() string
|
|
ID() uint64
|
|
Name() []byte
|
|
StartKey() []byte
|
|
StopKey() []byte
|
|
Namespace() []byte
|
|
Table() []byte
|
|
SetClient(RegionClient)
|
|
Client() RegionClient
|
|
}
|
|
|
|
// RegionClient represents HBase region client.
|
|
type RegionClient interface {
|
|
Close()
|
|
Addr() string
|
|
QueueRPC(Call)
|
|
String() string
|
|
}
|
|
|
|
// Call represents an HBase RPC call.
|
|
type Call interface {
|
|
Table() []byte
|
|
Name() string
|
|
Key() []byte
|
|
Region() RegionInfo
|
|
SetRegion(region RegionInfo)
|
|
ToProto() proto.Message
|
|
// Returns a newly created (default-state) protobuf in which to store the
|
|
// response of this call.
|
|
NewResponse() proto.Message
|
|
ResultChan() chan RPCResult
|
|
Context() context.Context
|
|
}
|
|
|
|
type withOptions interface {
|
|
Options() []func(Call) error
|
|
setOptions([]func(Call) error)
|
|
}
|
|
|
|
// Batchable interface should be implemented by calls that can be batched into MultiRequest
|
|
type Batchable interface {
|
|
// SkipBatch returns true if a call shouldn't be batched into MultiRequest and
|
|
// should be sent right away.
|
|
SkipBatch() bool
|
|
|
|
setSkipBatch(v bool)
|
|
}
|
|
|
|
// SkipBatch is an option for batchable requests (Get and Mutate) to tell
|
|
// the client to skip batching and just send the request to Region Server
|
|
// right away.
|
|
func SkipBatch() func(Call) error {
|
|
return func(c Call) error {
|
|
if b, ok := c.(Batchable); ok {
|
|
b.setSkipBatch(true)
|
|
return nil
|
|
}
|
|
return errors.New("'SkipBatch' option only works with Get and Mutate requests")
|
|
}
|
|
}
|
|
|
|
// hasQueryOptions is interface that needs to be implemented by calls
|
|
// that allow to provide Families and Filters options.
|
|
type hasQueryOptions interface {
|
|
setFamilies(families map[string][]string)
|
|
setFilter(filter *pb.Filter)
|
|
setTimeRangeUint64(from, to uint64)
|
|
setMaxVersions(versions uint32)
|
|
setMaxResultsPerColumnFamily(maxresults uint32)
|
|
setResultOffset(offset uint32)
|
|
}
|
|
|
|
// RPCResult is struct that will contain both the resulting message from an RPC
|
|
// call, and any errors that may have occurred related to making the RPC call.
|
|
type RPCResult struct {
|
|
Msg proto.Message
|
|
Error error
|
|
}
|
|
|
|
type base struct {
|
|
ctx context.Context
|
|
table []byte
|
|
key []byte
|
|
options []func(Call) error
|
|
|
|
region RegionInfo
|
|
resultch chan RPCResult
|
|
}
|
|
|
|
func (b *base) Context() context.Context {
|
|
return b.ctx
|
|
}
|
|
|
|
func (b *base) Region() RegionInfo {
|
|
return b.region
|
|
}
|
|
|
|
func (b *base) SetRegion(region RegionInfo) {
|
|
b.region = region
|
|
}
|
|
|
|
func (b *base) regionSpecifier() *pb.RegionSpecifier {
|
|
return &pb.RegionSpecifier{
|
|
Type: pb.RegionSpecifier_REGION_NAME.Enum(),
|
|
Value: []byte(b.region.Name()),
|
|
}
|
|
}
|
|
|
|
func (b *base) setOptions(options []func(Call) error) {
|
|
b.options = options
|
|
}
|
|
|
|
// Options returns all the options passed to this call
|
|
func (b *base) Options() []func(Call) error {
|
|
return b.options
|
|
}
|
|
|
|
func applyOptions(call Call, options ...func(Call) error) error {
|
|
call.(withOptions).setOptions(options)
|
|
for _, option := range options {
|
|
err := option(call)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *base) Table() []byte {
|
|
return b.table
|
|
}
|
|
|
|
func (b *base) Key() []byte {
|
|
return b.key
|
|
}
|
|
|
|
func (b *base) ResultChan() chan RPCResult {
|
|
return b.resultch
|
|
}
|
|
|
|
// Cell is the smallest level of granularity in returned results.
|
|
// Represents a single cell in HBase (a row will have one cell for every qualifier).
|
|
type Cell pb.Cell
|
|
|
|
func (c *Cell) String() string {
|
|
return (*pb.Cell)(c).String()
|
|
}
|
|
|
|
// cellFromCellBlock deserializes a cell from a reader
|
|
func cellFromCellBlock(b []byte) (*pb.Cell, uint32, error) {
|
|
if len(b) < 4 {
|
|
return nil, 0, fmt.Errorf(
|
|
"buffer is too small: expected %d, got %d", 4, len(b))
|
|
}
|
|
|
|
kvLen := binary.BigEndian.Uint32(b[0:4])
|
|
if len(b) < int(kvLen)+4 {
|
|
return nil, 0, fmt.Errorf(
|
|
"buffer is too small: expected %d, got %d", int(kvLen)+4, len(b))
|
|
}
|
|
|
|
rowKeyLen := binary.BigEndian.Uint32(b[4:8])
|
|
valueLen := binary.BigEndian.Uint32(b[8:12])
|
|
keyLen := binary.BigEndian.Uint16(b[12:14])
|
|
b = b[14:]
|
|
|
|
key := b[:keyLen]
|
|
b = b[keyLen:]
|
|
|
|
familyLen := uint8(b[0])
|
|
b = b[1:]
|
|
|
|
family := b[:familyLen]
|
|
b = b[familyLen:]
|
|
|
|
qualifierLen := rowKeyLen - uint32(keyLen) - uint32(familyLen) - 2 - 1 - 8 - 1
|
|
if 4 /*rowKeyLen*/ +4 /*valueLen*/ +2 /*keyLen*/ +
|
|
uint32(keyLen)+1 /*familyLen*/ +uint32(familyLen)+qualifierLen+
|
|
8 /*timestamp*/ +1 /*cellType*/ +valueLen != kvLen {
|
|
return nil, 0, fmt.Errorf("HBase has lied about KeyValue length: expected %d, got %d",
|
|
kvLen, 4+4+2+uint32(keyLen)+1+uint32(familyLen)+qualifierLen+8+1+valueLen)
|
|
}
|
|
qualifier := b[:qualifierLen]
|
|
b = b[qualifierLen:]
|
|
|
|
timestamp := binary.BigEndian.Uint64(b[:8])
|
|
b = b[8:]
|
|
|
|
cellType := uint8(b[0])
|
|
b = b[1:]
|
|
|
|
value := b[:valueLen]
|
|
|
|
return &pb.Cell{
|
|
Row: key,
|
|
Family: family,
|
|
Qualifier: qualifier,
|
|
Timestamp: ×tamp,
|
|
Value: value,
|
|
CellType: pb.CellType(cellType).Enum(),
|
|
}, kvLen + 4, nil
|
|
}
|
|
|
|
func deserializeCellBlocks(b []byte, cellsLen uint32) ([]*pb.Cell, uint32, error) {
|
|
cells := make([]*pb.Cell, cellsLen)
|
|
var readLen uint32
|
|
for i := 0; i < int(cellsLen); i++ {
|
|
c, l, err := cellFromCellBlock(b[readLen:])
|
|
if err != nil {
|
|
return nil, readLen, err
|
|
}
|
|
cells[i] = c
|
|
readLen += l
|
|
}
|
|
return cells, readLen, nil
|
|
}
|
|
|
|
// Result holds a slice of Cells as well as miscellaneous information about the response.
|
|
type Result struct {
|
|
Cells []*Cell
|
|
Stale bool
|
|
Partial bool
|
|
// Exists is only set if existance_only was set in the request query.
|
|
Exists *bool
|
|
}
|
|
|
|
func extractBool(v *bool) bool {
|
|
return v != nil && *v
|
|
}
|
|
|
|
// ToLocalResult takes a protobuf Result type and converts it to our own
|
|
// Result type in constant time.
|
|
func ToLocalResult(pbr *pb.Result) *Result {
|
|
if pbr == nil {
|
|
return &Result{}
|
|
}
|
|
return &Result{
|
|
// Should all be O(1) operations.
|
|
Cells: toLocalCells(pbr),
|
|
Stale: extractBool(pbr.Stale),
|
|
Partial: extractBool(pbr.Partial),
|
|
Exists: pbr.Exists,
|
|
}
|
|
}
|
|
|
|
func toLocalCells(pbr *pb.Result) []*Cell {
|
|
return *(*[]*Cell)(unsafe.Pointer(pbr))
|
|
}
|
|
|
|
// We can now define any helper functions on Result that we want.
|