Skip to content

Commit

Permalink
roachpb: add Key.Prevish() to find a previous key
Browse files Browse the repository at this point in the history
This patch adds `Key.Prevish()`, which returns a previous key in
lexicographical sort order. This is needed to expand a latch span
leftwards to peek at any left-adjacent range keys.

It is impossible to find the exact immediate predecessor of a key,
because it can have an infinite number of `0xff` bytes at the end, so
this returns the nearest previous key right-padded with `0xff` up to the
given length. It is still possible for an infinite number of keys to
exist between `Key` and `Key.Prevish()`, as keys have unbounded length.

Release note: None
  • Loading branch information
erikgrinaker committed Jun 4, 2022
1 parent b0b1432 commit caaa8c0
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 0 deletions.
21 changes: 21 additions & 0 deletions pkg/roachpb/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ const (
localPrefixByte = '\x01'
// LocalMaxByte is the end of the local key range.
LocalMaxByte = '\x02'
// PrevishKeyLength is a reasonable key length to use for Key.Prevish(),
// typically when peeking to the left of a known key. We want this to be as
// tight as possible, since it can e.g. be used for latch spans. However, the
// exact previous key has infinite length, so we assume that most keys are
// less than 1024 bytes, or have a fairly unique 1024-byte prefix.
PrevishKeyLength = 1024
)

var (
Expand Down Expand Up @@ -157,6 +163,21 @@ func (k Key) Next() Key {
return Key(encoding.BytesNext(k))
}

// Prevish returns a previous key in lexicographic sort order. It is impossible
// in general to find the exact immediate predecessor key, because it has an
// infinite number of 0xff bytes at the end, so this returns the nearest
// previous key right-padded with 0xff up to length bytes. An infinite number of
// keys may exist between Key and Key.Prevish(), as keys have unbounded length.
// This also implies that k.Prevish().IsPrev(k) will often be false.
//
// PrevishKeyLength can be used as a reasonable length in most situations.
//
// The method may only take a shallow copy of the Key, so both the receiver and
// the return value should be treated as immutable after.
func (k Key) Prevish(length int) Key {
return Key(encoding.BytesPrevish(k, length))
}

// IsPrev is a more efficient version of k.Next().Equal(m).
func (k Key) IsPrev(m Key) bool {
l := len(m) - 1
Expand Down
22 changes: 22 additions & 0 deletions pkg/roachpb/data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package roachpb

import (
"bytes"
"encoding/hex"
"math"
"math/rand"
"reflect"
Expand Down Expand Up @@ -221,6 +222,27 @@ func TestNextKey(t *testing.T) {
}
}

func TestPrevish(t *testing.T) {
const length = 4
testcases := []struct {
key Key
expect Key
}{
{nil, nil},
{[]byte{}, []byte{}},
{[]byte{0x00}, []byte{}},
{[]byte{0x01, 0x00}, []byte{0x01}},
{[]byte{0x01}, []byte{0x00, 0xff, 0xff, 0xff}},
{[]byte{0x01, 0x01}, []byte{0x01, 0x00, 0xff, 0xff}},
{[]byte{0xff, 0xff, 0xff, 0xff}, []byte{0xff, 0xff, 0xff, 0xfe}},
}
for _, tc := range testcases {
t.Run(hex.EncodeToString(tc.key), func(t *testing.T) {
require.Equal(t, tc.expect, tc.key.Prevish(length))
})
}
}

func TestIsPrev(t *testing.T) {
for i, tc := range []struct {
k, m Key
Expand Down
26 changes: 26 additions & 0 deletions pkg/util/encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -3215,3 +3215,29 @@ func BytesNext(b []byte) []byte {
bn[len(bn)-1] = 0
return bn
}

// BytesPrevish returns a previous byte slice in lexicographical ordering. It is
// impossible in general to find the exact previous byte slice, because it has
// an infinite number of 0xff bytes at the end, so this returns the nearest
// previous slice right-padded with 0xff up to length bytes. It may reuse the
// given slice when possible.
func BytesPrevish(b []byte, length int) []byte {
bLen := len(b)
// An empty slice has no previous slice.
if bLen == 0 {
return b
}
// If the last byte is 0, just remove it.
if b[bLen-1] == 0 {
return b[:bLen-1]
}
// Otherwise, decrement the last byte and right-pad with 0xff.
if bLen > length {
length = bLen
}
buf := make([]byte, length)
copy(buf, b)
buf[bLen-1]--
copy(buf[bLen:], bytes.Repeat([]byte{0xff}, length-bLen))
return buf
}

0 comments on commit caaa8c0

Please sign in to comment.