consul/internal/controller/cache/index/txn.go

174 lines
3.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package index
import (
iradix "github.com/hashicorp/go-immutable-radix/v2"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
)
type txn struct {
inner *iradix.Txn[[]*pbresource.Resource]
index *Index
dirty bool
}
func (t *txn) Get(args ...any) (*pbresource.Resource, error) {
val, err := t.index.fromArgs(args...)
if err != nil {
return nil, err
}
return t.getRaw(val), nil
}
func (t *txn) getRaw(val []byte) *pbresource.Resource {
resources, found := t.inner.Get(val)
if !found || len(resources) < 1 {
return nil
}
return resources[0]
}
func (t *txn) ListIterator(args ...any) (ResourceIterator, error) {
val, err := t.index.fromArgs(args...)
if err != nil {
return nil, err
}
iter := t.inner.Root().Iterator()
iter.SeekPrefix(val)
return &resourceIterator{iter: iter}, nil
}
func (t *txn) ParentsIterator(args ...any) (ResourceIterator, error) {
val, err := t.index.fromArgs(args...)
if err != nil {
return nil, err
}
iter := t.inner.Root().PathIterator(val)
return &resourceIterator{iter: iter}, nil
}
func (t *txn) Insert(r *pbresource.Resource) error {
indexed, vals, err := t.index.fromResource(r)
if err != nil {
return err
}
if !indexed && t.index.required {
return MissingRequiredIndexError{Name: t.index.Name()}
}
for _, val := range vals {
if t.insertOne(val, r) {
t.dirty = true
}
}
return nil
}
func (t *txn) insertOne(idxVal []byte, r *pbresource.Resource) bool {
var newResources []*pbresource.Resource
existing, found := t.inner.Get(idxVal)
if found {
newResources = make([]*pbresource.Resource, 0, len(existing)+1)
found := false
for _, rsc := range existing {
if !resource.EqualID(rsc.GetId(), r.GetId()) {
newResources = append(newResources, rsc)
} else {
found = true
newResources = append(newResources, r)
}
}
if !found {
newResources = append(newResources, r)
}
} else {
newResources = []*pbresource.Resource{r}
}
t.inner.Insert(idxVal, newResources)
return true
}
func (t *txn) Delete(r *pbresource.Resource) error {
indexed, vals, err := t.index.fromResource(r)
if err != nil {
return err
}
if !indexed && t.index.required {
return MissingRequiredIndexError{Name: t.index.Name()}
}
for _, val := range vals {
if t.deleteOne(val, r) {
t.dirty = true
}
}
return nil
}
func (t *txn) deleteOne(idxVal []byte, r *pbresource.Resource) bool {
existing, found := t.inner.Get(idxVal)
if !found {
// this resource is not currently indexed
return false
}
existingIdx := -1
for idx, rsc := range existing {
if resource.EqualID(rsc.GetId(), r.GetId()) {
existingIdx = idx
break
}
}
switch {
// The index value maps to some resources but none had the same id as the one we wish
// to delete. Therefore we can leave the slice alone because the delete is a no-op
case existingIdx < 0:
return false
// We found something (existingIdex >= 0) but there is only one thing in the array. In
// this case we should remove the whole resource slice from the tree.
case len(existing) == 1:
t.inner.Delete(idxVal)
// The first slice element is the resource to delete so we can reslice safely
case existingIdx == 0:
t.inner.Insert(idxVal, existing[1:])
// The last slice element is the resource to delete so we can reslice safely
case existingIdx == len(existing)-1:
t.inner.Insert(idxVal, existing[:len(existing)-1])
// The resource to delete exists somewhere in the middle of the slice. So we must
// recreate a new backing array to maintain immutability of the data being stored
default:
newResources := make([]*pbresource.Resource, len(existing)-1)
copy(newResources[0:existingIdx], existing[0:existingIdx])
copy(newResources[existingIdx:], existing[existingIdx+1:])
t.inner.Insert(idxVal, newResources)
}
return true
}
func (t *txn) Commit() {
if t.dirty {
t.index.tree = t.inner.Commit()
}
}