Skip to content

Commit

Permalink
arc: first cut of Add() and its test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
toru committed Nov 20, 2024
1 parent 5ecd7b6 commit d07b1ac
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 7 deletions.
38 changes: 31 additions & 7 deletions arc.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ var (
// ErrCorrupted is returned when a database corruption is detected.
ErrCorrupted = errors.New("database corruption detected")

// ErrDuplicateKey is returned when an insertion is attempted using a
// key that already exists in the database.
ErrDuplicateKey = errors.New("cannot insert duplicate key")

// ErrKeyNotFound is returned when the key does not exist in the index.
ErrKeyNotFound = errors.New("key not found")

Expand Down Expand Up @@ -58,13 +62,27 @@ func (a *Arc) Len() int {
return a.numRecords
}

func (a *Arc) empty() bool {
return a.root == nil && a.numRecords == 0
// Add inserts a new key-value pair in the database. It returns ErrDuplicateKey
// if the key already exists.
func (a *Arc) Add(key []byte, value []byte) error {
a.mu.Lock()
defer a.mu.Unlock()

return a.insert(key, value, false)
}

// Put inserts or updates a key-value pair in the database. It returns an error
// if the key is nil or if either the key or value exceeds size limits.
// Put inserts or updates a key-value pair in the database.
func (a *Arc) Put(key []byte, value []byte) error {
a.mu.Lock()
defer a.mu.Unlock()

return a.insert(key, value, true)
}

// insert adds a key-value pair to the database. If the key already exists and
// overwrite is true, the existing value is updated. If overwrite is false and
// the key exists, ErrDuplicateKey is returned. It returns nil on success.
func (a *Arc) insert(key []byte, value []byte, overwrite bool) error {
if key == nil {
return ErrNilKey
}
Expand All @@ -81,9 +99,6 @@ func (a *Arc) Put(key []byte, value []byte) error {
newNode.setKey(key)
newNode.setValue(value)

a.mu.Lock()
defer a.mu.Unlock()

// Empty empty: Set newNode as the root.
if a.empty() {
a.root = newNode
Expand Down Expand Up @@ -119,6 +134,10 @@ func (a *Arc) Put(key []byte, value []byte) error {
// Found exact match. Put() will overwrite the existing value.
// Do not update counters because this is an in-place update.
if prefixLen == len(current.key) && prefixLen == len(newNode.key) {
if !overwrite {
return ErrDuplicateKey
}

if !current.isRecord {
a.numRecords++
}
Expand Down Expand Up @@ -371,6 +390,11 @@ func (a *Arc) clear() {
a.numRecords = 0
}

// empty returns true if the database is empty.
func (a *Arc) empty() bool {
return a.root == nil && a.numRecords == 0
}

// splitNode splits a node based on a common prefix by creating an intermediate
// parent node. For the root node, it simply creates a new parent. For non-root
// nodes, it updates the parent-child relationships before modifying the node
Expand Down
22 changes: 22 additions & 0 deletions arc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,28 @@ func TestSplitNode(t *testing.T) {
}
}

func TestAdd(t *testing.T) {
testCases := []struct {
name string
key []byte
want error
}{
{name: "with nil key", key: nil, want: ErrNilKey},
{name: "with existing key", key: []byte("apricot"), want: ErrDuplicateKey},
{name: "with non-existing key", key: []byte("lychee"), want: nil},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
arc := basicTestTree()

if err := arc.Add(tc.key, nil); err != tc.want {
t.Errorf("unexpected error: got:%v, want:%v", err, tc.want)
}
})
}
}

func TestPut(t *testing.T) {
testCases := []struct {
name string
Expand Down

0 comments on commit d07b1ac

Please sign in to comment.