func (s *server) ReadModifyWriteRow(ctx context.Context, req *btspb.ReadModifyWriteRowRequest) (*btdpb.Row, error) { s.mu.Lock() tbl, ok := s.tables[req.TableName] s.mu.Unlock() if !ok { return nil, fmt.Errorf("no such table %q", req.TableName) } updates := make(map[string]cell) // copy of updated cells; keyed by full column name r := tbl.mutableRow(string(req.RowKey)) r.mu.Lock() defer r.mu.Unlock() // Assume all mutations apply to the most recent version of the cell. // TODO(dsymonds): Verify this assumption and document it in the proto. for _, rule := range req.Rules { tbl.mu.RLock() _, famOK := tbl.families[rule.FamilyName] tbl.mu.RUnlock() if !famOK { return nil, fmt.Errorf("unknown family %q", rule.FamilyName) } key := fmt.Sprintf("%s:%s", rule.FamilyName, rule.ColumnQualifier) newCell := false if len(r.cells[key]) == 0 { r.cells[key] = []cell{{ // TODO(dsymonds): should this set a timestamp? }} newCell = true } cell := &r.cells[key][0] switch rule := rule.Rule.(type) { default: return nil, fmt.Errorf("unknown RMW rule oneof %T", rule) case *btdpb.ReadModifyWriteRule_AppendValue: cell.value = append(cell.value, rule.AppendValue...) case *btdpb.ReadModifyWriteRule_IncrementAmount: var v int64 if !newCell { if len(cell.value) != 8 { return nil, fmt.Errorf("increment on non-64-bit value") } v = int64(binary.BigEndian.Uint64(cell.value)) } v += rule.IncrementAmount var val [8]byte binary.BigEndian.PutUint64(val[:], uint64(v)) cell.value = val[:] } updates[key] = *cell } res := &btdpb.Row{ Key: req.RowKey, } for col, cell := range updates { i := strings.Index(col, ":") fam, qual := col[:i], col[i+1:] var f *btdpb.Family for _, ff := range res.Families { if ff.Name == fam { f = ff break } } if f == nil { f = &btdpb.Family{Name: fam} res.Families = append(res.Families, f) } f.Columns = append(f.Columns, &btdpb.Column{ Qualifier: []byte(qual), Cells: []*btdpb.Cell{{ Value: cell.value, }}, }) } return res, nil }
func (s *server) ReadModifyWriteRow(ctx context.Context, req *btspb.ReadModifyWriteRowRequest) (*btspb.ReadModifyWriteRowResponse, error) { s.mu.Lock() tbl, ok := s.tables[req.TableName] s.mu.Unlock() if !ok { return nil, fmt.Errorf("no such table %q", req.TableName) } updates := make(map[string]cell) // copy of updated cells; keyed by full column name fs := tbl.columnFamiliesSet() r := tbl.mutableRow(string(req.RowKey)) r.mu.Lock() defer r.mu.Unlock() // Assume all mutations apply to the most recent version of the cell. // TODO(dsymonds): Verify this assumption and document it in the proto. for _, rule := range req.Rules { if !fs[rule.FamilyName] { return nil, fmt.Errorf("unknown family %q", rule.FamilyName) } key := fmt.Sprintf("%s:%s", rule.FamilyName, rule.ColumnQualifier) cells := r.cells[key] ts := newTimestamp() var newCell, prevCell cell isEmpty := len(cells) == 0 if !isEmpty { prevCell = cells[0] // ts is the max of now or the prev cell's timestamp in case the // prev cell is in the future ts = maxTimestamp(ts, prevCell.ts) } switch rule := rule.Rule.(type) { default: return nil, fmt.Errorf("unknown RMW rule oneof %T", rule) case *btdpb.ReadModifyWriteRule_AppendValue: newCell = cell{ts: ts, value: append(prevCell.value, rule.AppendValue...)} case *btdpb.ReadModifyWriteRule_IncrementAmount: var v int64 if !isEmpty { prevVal := prevCell.value if len(prevVal) != 8 { return nil, fmt.Errorf("increment on non-64-bit value") } v = int64(binary.BigEndian.Uint64(prevVal)) } v += rule.IncrementAmount var val [8]byte binary.BigEndian.PutUint64(val[:], uint64(v)) newCell = cell{ts: ts, value: val[:]} } updates[key] = newCell r.cells[key] = appendOrReplaceCell(r.cells[key], newCell) } res := &btdpb.Row{ Key: req.RowKey, } for col, cell := range updates { i := strings.Index(col, ":") fam, qual := col[:i], col[i+1:] var f *btdpb.Family for _, ff := range res.Families { if ff.Name == fam { f = ff break } } if f == nil { f = &btdpb.Family{Name: fam} res.Families = append(res.Families, f) } f.Columns = append(f.Columns, &btdpb.Column{ Qualifier: []byte(qual), Cells: []*btdpb.Cell{{ Value: cell.value, }}, }) } return &btspb.ReadModifyWriteRowResponse{Row: res}, nil }