Skip to content

Commit

Permalink
move keyPath to its own file and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
superfell committed Feb 2, 2021
1 parent 1e523cf commit 8aeeebb
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 44 deletions.
46 changes: 2 additions & 44 deletions art.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,47 +210,6 @@ type node interface {
stats(s *Stats)
}

// keyPath contains up to 23 bytes of key that has been compressed into a node for path compression/lazy expansion.
// Compressed paths longer than 23 bytes will need intermediate node4s to extend the path. Unless you have very long
// sparse keys its unlikely that a compressed path will need more than 23 bytes.
type keyPath struct {
key [23]byte
len byte
}

func (k *keyPath) asSlice() []byte {
return k.key[:k.len]
}

func (k *keyPath) assign(p []byte) {
if len(p) > len(k.key) {
panic("Tried to assign a compressed path longer than supported")
}
copy(k.key[:], p)
k.len = byte(len(p))
}

func (k *keyPath) trimPathStart(amount int) {
copy(k.key[:], k.key[amount:k.len])
k.len -= byte(amount)
}

func (k *keyPath) canExtendBy(additional byte) bool {
return k.len+additional <= byte(len(k.key))
}

func (k *keyPath) prependPath(prefix []byte, extra ...byte) {
additionalLen := len(prefix) + len(extra)
if int(k.len)+additionalLen > len(k.key) {
panic("Attempt to extend path outside of supported size.")
}
// [prefix] [extra] [existing path]
copy(k.key[additionalLen:], k.key[:k.len])
copy(k.key[:], prefix)
copy(k.key[len(prefix):], extra)
k.len += byte(additionalLen)
}

type nodeHeader struct {
// number of populated children in this node
childCount int16
Expand Down Expand Up @@ -281,10 +240,9 @@ func splitNodePath(key []byte, n node) (remainingKey []byte, out node) {
return key[prefixLen:], n
}

func writePath(p []byte, w writer) {
func writePath(p []byte, w io.Writer) {
for _, k := range p {
w.WriteByte(' ')
fmt.Fprintf(w, "0x%02X", k)
fmt.Fprintf(w, " 0x%02X", k)
}
}

Expand Down
50 changes: 50 additions & 0 deletions keypath.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package art

import "strings"

// keyPath contains up to 23 bytes of key that has been compressed into a node for path compression/lazy expansion.
// Compressed paths longer than 23 bytes will need intermediate node4s to extend the path. Unless you have very long
// sparse keys its unlikely that a compressed path will need more than 23 bytes.
type keyPath struct {
key [23]byte
len byte
}

func (k *keyPath) asSlice() []byte {
return k.key[:k.len]
}

func (k *keyPath) assign(p []byte) {
if len(p) > len(k.key) {
panic("Tried to assign a compressed path longer than supported")
}
copy(k.key[:], p)
k.len = byte(len(p))
}

func (k *keyPath) trimPathStart(amount int) {
copy(k.key[:], k.key[amount:k.len])
k.len -= byte(amount)
}

func (k *keyPath) canExtendBy(additional byte) bool {
return k.len+additional <= byte(len(k.key))
}

func (k *keyPath) prependPath(prefix []byte, extra ...byte) {
additionalLen := len(prefix) + len(extra)
if int(k.len)+additionalLen > len(k.key) {
panic("Attempt to extend path outside of supported size.")
}
// [prefix] [extra] [existing path]
copy(k.key[additionalLen:], k.key[:k.len])
copy(k.key[:], prefix)
copy(k.key[len(prefix):], extra)
k.len += byte(additionalLen)
}

func (k *keyPath) String() string {
b := strings.Builder{}
writePath(k.asSlice(), &b)
return b.String()
}
76 changes: 76 additions & 0 deletions keypath_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package art

import (
"bytes"
"testing"
)

func Test_KeyPathAssign(t *testing.T) {
p := keyPath{}
k := []byte{4, 5, 7, 8, 1, 10}
p.assign(k)
if !bytes.Equal(k, p.asSlice()) {
t.Errorf("Assigned path %v, but got different path %v back", k, p)
}
}

func Test_KeyPathTrim(t *testing.T) {
p := keyPath{}
k := []byte{1, 2, 3, 4, 5}
p.assign(k)
p.trimPathStart(2)
exp := []byte{3, 4, 5}
if !bytes.Equal(exp, p.asSlice()) {
t.Errorf("Trimmed path expected to be %v but was %b", exp, p)
}
}

func Test_KeyPathCanExtendBy(t *testing.T) {
p := keyPath{}
k := []byte{1, 2, 3}
p.assign(k)
if !p.canExtendBy(20) {
t.Errorf("a 3 byte path should be extendable by another 20")
}
if p.canExtendBy(21) {
t.Errorf("a 3 byte path shouldn't be extendable by another 21")
}
}

func Test_KeyPathString(t *testing.T) {
p := keyPath{}
k := []byte{1, 0x42, 0, 0xFF}
p.assign(k)
exp := " 0x01 0x42 0x00 0xFF"
if exp != p.String() {
t.Errorf("Expecting '%s' for String() but got '%s'", exp, p.String())
}
}

type prependCase struct {
base string
prefix string
extra string
expected string
}

func Test_KeyPathPrepend(t *testing.T) {
cases := []prependCase{
{"qqq", "ppp", "x", "pppxqqq"},
{"qqq", "ppp", "", "pppqqq"},
{"qqq", "", "", "qqq"},
{"", "", "", ""},
{"", "", "x", "x"},
{"", "xx", "y", "xxy"},
}
for _, c := range cases {
t.Run(c.expected, func(t *testing.T) {
p := keyPath{}
p.assign([]byte(c.base))
p.prependPath([]byte(c.prefix), []byte(c.extra)...)
if !bytes.Equal([]byte(c.expected), p.asSlice()) {
t.Errorf("Expecting final path to be %s but was %s", c.expected, string(p.asSlice()))
}
})
}
}

0 comments on commit 8aeeebb

Please sign in to comment.