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 }