github.com/tsuna/gohbase@v0.0.0-20250731002811-4ffcadfba63e/hrpc/mutate.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  	"math"
    13  	"time"
    14  
    15  	"github.com/tsuna/gohbase/pb"
    16  	"google.golang.org/protobuf/proto"
    17  )
    18  
    19  var attributeNameTTL = "_ttl"
    20  
    21  // DurabilityType is used to set durability for Durability option
    22  type DurabilityType int32
    23  
    24  const (
    25  	// UseDefault is USER_DEFAULT
    26  	UseDefault DurabilityType = iota
    27  	// SkipWal is SKIP_WAL
    28  	SkipWal
    29  	// AsyncWal is ASYNC_WAL
    30  	AsyncWal
    31  	// SyncWal is SYNC_WAL
    32  	SyncWal
    33  	// FsyncWal is FSYNC_WAL
    34  	FsyncWal
    35  )
    36  
    37  const (
    38  	putType                 = 4
    39  	deleteType              = 8
    40  	deleteFamilyVersionType = 10
    41  	deleteColumnType        = 12
    42  	deleteFamilyType        = 14
    43  )
    44  
    45  var emptyQualifier = map[string][]byte{"": nil}
    46  
    47  // Mutate represents a mutation on HBase.
    48  type Mutate struct {
    49  	base
    50  
    51  	mutationType pb.MutationProto_MutationType //*int32
    52  
    53  	// values is a map of column families to a map of column qualifiers to bytes
    54  	values map[string]map[string][]byte
    55  
    56  	ttl              []byte
    57  	timestamp        uint64
    58  	durability       DurabilityType
    59  	deleteOneVersion bool
    60  	skipbatch        bool
    61  }
    62  
    63  // TTL sets a time-to-live for mutation queries.
    64  // The value will be in millisecond resolution.
    65  func TTL(t time.Duration) func(Call) error {
    66  	return func(o Call) error {
    67  		m, ok := o.(*Mutate)
    68  		if !ok {
    69  			return errors.New("'TTL' option can only be used with mutation queries")
    70  		}
    71  
    72  		buf := make([]byte, 8)
    73  		binary.BigEndian.PutUint64(buf, uint64(t.Nanoseconds()/1e6))
    74  		m.ttl = buf
    75  
    76  		return nil
    77  	}
    78  }
    79  
    80  // Timestamp sets timestamp for mutation queries.
    81  // The time object passed will be rounded to a millisecond resolution, as by default,
    82  // if no timestamp is provided, HBase sets it to current time in milliseconds.
    83  // In order to have custom time precision, use TimestampUint64 call option for
    84  // mutation requests and corresponding TimeRangeUint64 for retrieval requests.
    85  func Timestamp(ts time.Time) func(Call) error {
    86  	return func(o Call) error {
    87  		m, ok := o.(*Mutate)
    88  		if !ok {
    89  			return errors.New("'Timestamp' option can only be used with mutation queries")
    90  		}
    91  		m.timestamp = uint64(ts.UnixNano() / 1e6)
    92  		return nil
    93  	}
    94  }
    95  
    96  // TimestampUint64 sets timestamp for mutation queries.
    97  func TimestampUint64(ts uint64) func(Call) error {
    98  	return func(o Call) error {
    99  		m, ok := o.(*Mutate)
   100  		if !ok {
   101  			return errors.New("'TimestampUint64' option can only be used with mutation queries")
   102  		}
   103  		m.timestamp = ts
   104  		return nil
   105  	}
   106  }
   107  
   108  // Durability sets durability for mutation queries.
   109  func Durability(d DurabilityType) func(Call) error {
   110  	return func(o Call) error {
   111  		m, ok := o.(*Mutate)
   112  		if !ok {
   113  			return errors.New("'Durability' option can only be used with mutation queries")
   114  		}
   115  		if d < UseDefault || d > FsyncWal {
   116  			return errors.New("invalid durability value")
   117  		}
   118  		m.durability = d
   119  		return nil
   120  	}
   121  }
   122  
   123  // DeleteOneVersion is a delete option that can be passed in order to delete only
   124  // one latest version of the specified qualifiers. Without timestamp specified,
   125  // it will have no effect for delete specific column families request.
   126  // If a Timestamp option is passed along, only the version at that timestamp will be removed
   127  // for delete specific column families and/or qualifier request.
   128  // This option cannot be used for delete entire row request.
   129  func DeleteOneVersion() func(Call) error {
   130  	return func(o Call) error {
   131  		m, ok := o.(*Mutate)
   132  		if !ok {
   133  			return errors.New("'DeleteOneVersion' option can only be used with mutation queries")
   134  		}
   135  		m.deleteOneVersion = true
   136  		return nil
   137  	}
   138  }
   139  
   140  // baseMutate returns a Mutate struct without the mutationType filled in.
   141  func baseMutate(ctx context.Context, table, key []byte, values map[string]map[string][]byte,
   142  	options ...func(Call) error) (*Mutate, error) {
   143  	m := &Mutate{
   144  		base: base{
   145  			table:    table,
   146  			key:      key,
   147  			ctx:      ctx,
   148  			resultch: make(chan RPCResult, 1),
   149  		},
   150  		values:    values,
   151  		timestamp: MaxTimestamp,
   152  	}
   153  	err := applyOptions(m, options...)
   154  	if err != nil {
   155  		return nil, err
   156  	}
   157  	return m, nil
   158  }
   159  
   160  // NewPut creates a new Mutation request to insert the given
   161  // family-column-values in the given row key of the given table.
   162  func NewPut(ctx context.Context, table, key []byte,
   163  	values map[string]map[string][]byte, options ...func(Call) error) (*Mutate, error) {
   164  	m, err := baseMutate(ctx, table, key, values, options...)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	m.mutationType = pb.MutationProto_PUT
   169  	return m, nil
   170  }
   171  
   172  // NewPutStr is just like NewPut but takes table and key as strings.
   173  func NewPutStr(ctx context.Context, table, key string,
   174  	values map[string]map[string][]byte, options ...func(Call) error) (*Mutate, error) {
   175  	return NewPut(ctx, []byte(table), []byte(key), values, options...)
   176  }
   177  
   178  // NewDel is used to perform Delete operations on a single row.
   179  // To delete entire row, values should be nil.
   180  //
   181  // To delete specific families, qualifiers map should be nil:
   182  //
   183  //	 map[string]map[string][]byte{
   184  //			"cf1": nil,
   185  //			"cf2": nil,
   186  //	 }
   187  //
   188  // To delete specific qualifiers:
   189  //
   190  //	 map[string]map[string][]byte{
   191  //	     "cf": map[string][]byte{
   192  //				"q1": nil,
   193  //				"q2": nil,
   194  //			},
   195  //	 }
   196  //
   197  // To delete all versions before and at a timestamp, pass hrpc.Timestamp() option.
   198  // By default all versions will be removed.
   199  //
   200  // To delete only a specific version at a timestamp, pass hrpc.DeleteOneVersion() option
   201  // along with a timestamp. For delete specific qualifiers request, if timestamp is not
   202  // passed, only the latest version will be removed. For delete specific families request,
   203  // the timestamp should be passed or it will have no effect as it's an expensive
   204  // operation to perform.
   205  func NewDel(ctx context.Context, table, key []byte,
   206  	values map[string]map[string][]byte, options ...func(Call) error) (*Mutate, error) {
   207  	m, err := baseMutate(ctx, table, key, values, options...)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	if len(m.values) == 0 && m.deleteOneVersion {
   213  		return nil, errors.New(
   214  			"'DeleteOneVersion' option cannot be specified for delete entire row request")
   215  	}
   216  
   217  	m.mutationType = pb.MutationProto_DELETE
   218  	return m, nil
   219  }
   220  
   221  // NewDelStr is just like NewDel but takes table and key as strings.
   222  func NewDelStr(ctx context.Context, table, key string,
   223  	values map[string]map[string][]byte, options ...func(Call) error) (*Mutate, error) {
   224  	return NewDel(ctx, []byte(table), []byte(key), values, options...)
   225  }
   226  
   227  // NewApp creates a new Mutation request to append the given
   228  // family-column-values into the existing cells in HBase (or create them if
   229  // needed), in given row key of the given table.
   230  func NewApp(ctx context.Context, table, key []byte,
   231  	values map[string]map[string][]byte, options ...func(Call) error) (*Mutate, error) {
   232  	m, err := baseMutate(ctx, table, key, values, options...)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	m.mutationType = pb.MutationProto_APPEND
   237  	return m, nil
   238  }
   239  
   240  // NewAppStr is just like NewApp but takes table and key as strings.
   241  func NewAppStr(ctx context.Context, table, key string,
   242  	values map[string]map[string][]byte, options ...func(Call) error) (*Mutate, error) {
   243  	return NewApp(ctx, []byte(table), []byte(key), values, options...)
   244  }
   245  
   246  // NewIncSingle creates a new Mutation request that will increment the given value
   247  // by amount in HBase under the given table, key, family and qualifier.
   248  func NewIncSingle(ctx context.Context, table, key []byte, family, qualifier string,
   249  	amount int64, options ...func(Call) error) (*Mutate, error) {
   250  	buf := make([]byte, 8)
   251  	binary.BigEndian.PutUint64(buf, uint64(amount))
   252  	value := map[string]map[string][]byte{family: map[string][]byte{qualifier: buf}}
   253  	return NewInc(ctx, table, key, value, options...)
   254  }
   255  
   256  // NewIncStrSingle is just like NewIncSingle but takes table and key as strings.
   257  func NewIncStrSingle(ctx context.Context, table, key, family, qualifier string,
   258  	amount int64, options ...func(Call) error) (*Mutate, error) {
   259  	return NewIncSingle(ctx, []byte(table), []byte(key), family, qualifier, amount, options...)
   260  }
   261  
   262  // NewInc creates a new Mutation request that will increment the given values
   263  // in HBase under the given table and key.
   264  func NewInc(ctx context.Context, table, key []byte,
   265  	values map[string]map[string][]byte, options ...func(Call) error) (*Mutate, error) {
   266  	m, err := baseMutate(ctx, table, key, values, options...)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	m.mutationType = pb.MutationProto_INCREMENT
   271  	return m, nil
   272  }
   273  
   274  // NewIncStr is just like NewInc but takes table and key as strings.
   275  func NewIncStr(ctx context.Context, table, key string,
   276  	values map[string]map[string][]byte, options ...func(Call) error) (*Mutate, error) {
   277  	return NewInc(ctx, []byte(table), []byte(key), values, options...)
   278  }
   279  
   280  // Name returns the name of this RPC call.
   281  func (m *Mutate) Name() string {
   282  	return "Mutate"
   283  }
   284  
   285  // Description returns the string of type of mutation being performed
   286  func (m *Mutate) Description() string {
   287  	return pb.MutationProto_MutationType_name[int32(m.mutationType)]
   288  }
   289  
   290  // SkipBatch returns true if the Mutate request shouldn't be batched,
   291  // but should be sent to Region Server right away.
   292  func (m *Mutate) SkipBatch() bool {
   293  	return m.skipbatch
   294  }
   295  
   296  // Values returns the internal values object
   297  // which should be treated as read-only.
   298  // This would typically be used for calculations
   299  // related to metrics and workloads
   300  func (m *Mutate) Values() map[string]map[string][]byte {
   301  	return m.values
   302  }
   303  
   304  func (m *Mutate) setSkipBatch(v bool) {
   305  	m.skipbatch = v
   306  }
   307  
   308  var (
   309  	MutationProtoDeleteFamilyVersion    = pb.MutationProto_DELETE_FAMILY_VERSION.Enum()
   310  	MutationProtoDeleteFamily           = pb.MutationProto_DELETE_FAMILY.Enum()
   311  	MutationProtoDeleteOneVersion       = pb.MutationProto_DELETE_ONE_VERSION.Enum()
   312  	MutationProtoDeleteMultipleVersions = pb.MutationProto_DELETE_MULTIPLE_VERSIONS.Enum()
   313  )
   314  
   315  func (m *Mutate) valuesToProto(ts *uint64) []*pb.MutationProto_ColumnValue {
   316  	cvs := make([]*pb.MutationProto_ColumnValue, len(m.values))
   317  	i := 0
   318  	for k, v := range m.values {
   319  		// And likewise, each item in each column needs to be converted to a
   320  		// protobuf QualifierValue
   321  
   322  		// if it's a delete, figure out the type
   323  		var dt *pb.MutationProto_DeleteType
   324  		if m.mutationType == pb.MutationProto_DELETE {
   325  			if len(v) == 0 {
   326  				// delete the whole column family
   327  				if m.deleteOneVersion {
   328  					dt = MutationProtoDeleteFamilyVersion
   329  				} else {
   330  					dt = MutationProtoDeleteFamily
   331  				}
   332  				// add empty qualifier
   333  				if v == nil {
   334  					v = emptyQualifier
   335  				}
   336  			} else {
   337  				// delete specific qualifiers
   338  				if m.deleteOneVersion {
   339  					dt = MutationProtoDeleteOneVersion
   340  				} else {
   341  					dt = MutationProtoDeleteMultipleVersions
   342  				}
   343  			}
   344  		}
   345  
   346  		qvs := make([]*pb.MutationProto_ColumnValue_QualifierValue, len(v))
   347  		j := 0
   348  		for k1, v1 := range v {
   349  			qvs[j] = &pb.MutationProto_ColumnValue_QualifierValue{
   350  				Qualifier:  []byte(k1),
   351  				Value:      v1,
   352  				Timestamp:  ts,
   353  				DeleteType: dt,
   354  			}
   355  			j++
   356  		}
   357  		cvs[i] = &pb.MutationProto_ColumnValue{
   358  			Family:         []byte(k),
   359  			QualifierValue: qvs,
   360  		}
   361  		i++
   362  	}
   363  	return cvs
   364  }
   365  
   366  func cellblockLen(rowLen, familyLen, qualifierLen, valueLen int) int {
   367  	keyLength := 2 + rowLen + 1 + familyLen + qualifierLen + 8 + 1
   368  	keyValueLength := 4 + 4 + keyLength + valueLen
   369  	return 4 + keyValueLength
   370  }
   371  
   372  func appendCellblock(row []byte, family, qualifier string, value []byte, ts uint64, typ byte,
   373  	cbs []byte) []byte {
   374  	// cellblock layout:
   375  	//
   376  	// Header:
   377  	// 4 byte length of key + value
   378  	// 4 byte length of key
   379  	// 4 byte length of value
   380  	//
   381  	// Key:
   382  	// 2 byte length of row
   383  	// <row>
   384  	// 1 byte length of row family
   385  	// <family>
   386  	// <qualifier>
   387  	// 8 byte timestamp
   388  	// 1 byte type
   389  	//
   390  	// Value:
   391  	// <value>
   392  	keylength := 2 + len(row) + 1 + len(family) + len(qualifier) + 8 + 1
   393  	valuelength := len(value)
   394  
   395  	keyvaluelength := 4 + 4 + keylength + valuelength
   396  	i := len(cbs)
   397  	cbs = append(cbs, make([]byte,
   398  		cellblockLen(len(row), len(family), len(qualifier), len(value)))...)
   399  
   400  	// Header:
   401  	binary.BigEndian.PutUint32(cbs[i:], uint32(keyvaluelength))
   402  	i += 4
   403  	binary.BigEndian.PutUint32(cbs[i:], uint32(keylength))
   404  	i += 4
   405  	binary.BigEndian.PutUint32(cbs[i:], uint32(valuelength))
   406  	i += 4
   407  
   408  	// Key:
   409  	binary.BigEndian.PutUint16(cbs[i:], uint16(len(row)))
   410  	i += 2
   411  	i += copy(cbs[i:], row)
   412  	cbs[i] = byte(len(family))
   413  	i++
   414  	i += copy(cbs[i:], family)
   415  	i += copy(cbs[i:], qualifier)
   416  	binary.BigEndian.PutUint64(cbs[i:], ts)
   417  	i += 8
   418  	cbs[i] = typ
   419  	i++
   420  
   421  	// Value:
   422  	copy(cbs[i:], value)
   423  
   424  	return cbs
   425  }
   426  
   427  func (m *Mutate) valuesToCellblocks() ([]byte, int32, uint32) {
   428  	if len(m.values) == 0 {
   429  		return nil, 0, 0
   430  	}
   431  	var cbsLen int
   432  	var count int
   433  	for family, v := range m.values {
   434  		if v == nil {
   435  			v = emptyQualifier
   436  		}
   437  		count += len(v)
   438  		for k1, v1 := range v {
   439  			cbsLen += cellblockLen(len(m.key), len(family), len(k1), len(v1))
   440  		}
   441  	}
   442  	cbs := make([]byte, 0, cbsLen)
   443  
   444  	var ts uint64
   445  	if m.timestamp == MaxTimestamp {
   446  		ts = math.MaxInt64 // Java's Long.MAX_VALUE use for HBase's LATEST_TIMESTAMP
   447  	} else {
   448  		ts = m.timestamp
   449  	}
   450  	for family, v := range m.values {
   451  		// figure out mutation type
   452  		var mt byte
   453  		if m.mutationType == pb.MutationProto_DELETE {
   454  			if len(v) == 0 {
   455  				// delete the whole column family
   456  				if m.deleteOneVersion {
   457  					mt = deleteFamilyVersionType
   458  				} else {
   459  					mt = deleteFamilyType
   460  				}
   461  				// add empty qualifier
   462  				if v == nil {
   463  					v = emptyQualifier
   464  				}
   465  			} else {
   466  				// delete specific qualifiers
   467  				if m.deleteOneVersion {
   468  					mt = deleteType
   469  				} else {
   470  					mt = deleteColumnType
   471  				}
   472  			}
   473  		} else {
   474  			mt = putType
   475  		}
   476  
   477  		for k1, v1 := range v {
   478  			cbs = appendCellblock(m.key, family, k1, v1, ts, mt, cbs)
   479  		}
   480  	}
   481  	if len(cbs) != cbsLen {
   482  		panic("cellblocks len mismatch")
   483  	}
   484  	return cbs, int32(count), uint32(len(cbs))
   485  }
   486  
   487  var durabilities = []*pb.MutationProto_Durability{
   488  	pb.MutationProto_Durability(UseDefault).Enum(),
   489  	pb.MutationProto_Durability(SkipWal).Enum(),
   490  	pb.MutationProto_Durability(AsyncWal).Enum(),
   491  	pb.MutationProto_Durability(SyncWal).Enum(),
   492  	pb.MutationProto_Durability(FsyncWal).Enum(),
   493  }
   494  
   495  func (m *Mutate) toProto(isCellblocks bool, cbs [][]byte) (*pb.MutateRequest, [][]byte, uint32) {
   496  	var ts *uint64
   497  	if m.timestamp != MaxTimestamp {
   498  		ts = &m.timestamp
   499  	}
   500  
   501  	var size uint32
   502  	mProto := &pb.MutationProto{
   503  		Row:        m.key,
   504  		MutateType: &m.mutationType,
   505  		Durability: durabilities[m.durability],
   506  		Timestamp:  ts,
   507  	}
   508  
   509  	if isCellblocks {
   510  		// if cellblocks we only add associated cell count as the actual
   511  		// data will be sent after protobuf
   512  		cellblocks, count, sz := m.valuesToCellblocks()
   513  		mProto.AssociatedCellCount = &count
   514  		size = sz
   515  		if size > 0 {
   516  			cbs = append(cbs, cellblocks)
   517  		}
   518  	} else {
   519  		// otherwise, convert the values to protobuf
   520  		mProto.ColumnValue = m.valuesToProto(ts)
   521  	}
   522  
   523  	if len(m.ttl) > 0 {
   524  		mProto.Attribute = append(mProto.Attribute, &pb.NameBytesPair{
   525  			Name:  &attributeNameTTL,
   526  			Value: m.ttl,
   527  		})
   528  	}
   529  
   530  	return &pb.MutateRequest{
   531  		Region:   m.regionSpecifier(),
   532  		Mutation: mProto,
   533  	}, cbs, size
   534  }
   535  
   536  // ToProto converts this mutate RPC into a protobuf message
   537  func (m *Mutate) ToProto() proto.Message {
   538  	p, _, _ := m.toProto(false, nil)
   539  	return p
   540  }
   541  
   542  // NewResponse creates an empty protobuf message to read the response of this RPC.
   543  func (m *Mutate) NewResponse() proto.Message {
   544  	return &pb.MutateResponse{}
   545  }
   546  
   547  // DeserializeCellBlocks deserializes mutate result from cell blocks
   548  func (m *Mutate) DeserializeCellBlocks(pm proto.Message, b []byte) (uint32, error) {
   549  	resp := pm.(*pb.MutateResponse)
   550  	if resp.Result == nil {
   551  		// TODO: is this possible?
   552  		return 0, nil
   553  	}
   554  	cells, read, err := deserializeCellBlocks(b, uint32(resp.Result.GetAssociatedCellCount()))
   555  	if err != nil {
   556  		return 0, err
   557  	}
   558  	resp.Result.Cell = append(resp.Result.Cell, cells...)
   559  	return read, nil
   560  }
   561  
   562  func (m *Mutate) SerializeCellBlocks(cbs [][]byte) (proto.Message, [][]byte, uint32) {
   563  	return m.toProto(true, cbs)
   564  }
   565  
   566  func (m *Mutate) CellBlocksEnabled() bool {
   567  	// TODO: maybe have some global client option
   568  	return true
   569  }