diff --git a/btreeg.go b/btreeg.go index 218a2db..693a62d 100644 --- a/btreeg.go +++ b/btreeg.go @@ -13,6 +13,7 @@ type BTreeG[T any] struct { locks bool copyItems bool isoCopyItems bool + readOnly bool less func(a, b T) bool empty T max int @@ -44,6 +45,8 @@ type Options struct { // NoLocks will disable locking. Otherwide a sync.RWMutex is used to // ensure all operations are safe across multiple goroutines. NoLocks bool + // ReadOnly marks the tree as read-only, any modifications will trigger panic. + ReadOnly bool } // New returns a new BTree @@ -60,9 +63,17 @@ func NewBTreeGOptions[T any](less func(a, b T) bool, opts Options) *BTreeG[T] { } tr.less = less tr.init(opts.Degree) + if opts.ReadOnly { + tr.Freeze() + } return tr } +// Freeze marks the tree as read-only. +func (tr *BTreeG[T]) Freeze() { + tr.readOnly = true +} + func (tr *BTreeG[T]) init(degree int) { if tr.min != 0 { return @@ -194,6 +205,9 @@ path_match: // SetHint sets or replace a value for a key using a path hint func (tr *BTreeG[T]) SetHint(item T, hint *PathHint) (prev T, replaced bool) { + if tr.readOnly { + panic("read-only tree") + } if tr.locks { tr.mu.Lock() prev, replaced = tr.setHint(item, hint) @@ -875,6 +889,9 @@ func (tr *BTreeG[T]) Delete(key T) (T, bool) { // deleted value. // Returns false if there was no value by that key found. func (tr *BTreeG[T]) DeleteHint(key T, hint *PathHint) (T, bool) { + if tr.readOnly { + panic("read-only tree") + } if tr.lock(true) { defer tr.unlock(true) } @@ -1195,6 +1212,9 @@ func (tr *BTreeG[T]) nodeDescend(cn **node[T], pivot T, hint *PathHint, // Load is for bulk loading pre-sorted items func (tr *BTreeG[T]) Load(item T) (T, bool) { + if tr.readOnly { + panic("read-only tree") + } if tr.lock(true) { defer tr.unlock(true) } @@ -1283,6 +1303,9 @@ func (tr *BTreeG[T]) maxMut(mut bool) (T, bool) { // PopMin removes the minimum item in tree and returns it. // Returns nil if the tree has no items. func (tr *BTreeG[T]) PopMin() (T, bool) { + if tr.readOnly { + panic("read-only tree") + } if tr.lock(true) { defer tr.unlock(true) } @@ -1324,6 +1347,9 @@ func (tr *BTreeG[T]) PopMin() (T, bool) { // PopMax removes the maximum item in tree and returns it. // Returns nil if the tree has no items. func (tr *BTreeG[T]) PopMax() (T, bool) { + if tr.readOnly { + panic("read-only tree") + } if tr.lock(true) { defer tr.unlock(true) } @@ -1397,6 +1423,9 @@ func (tr *BTreeG[T]) getAt(index int, mut bool) (T, bool) { // DeleteAt deletes the item at index. // Return nil if the tree is empty or the index is out of bounds. func (tr *BTreeG[T]) DeleteAt(index int) (T, bool) { + if tr.readOnly { + panic("read-only tree") + } if tr.lock(true) { defer tr.unlock(true) } @@ -1525,15 +1554,18 @@ func (tr *BTreeG[T]) Copy() *BTreeG[T] { func (tr *BTreeG[T]) IsoCopy() *BTreeG[T] { var mu *sync.RWMutex - if tr.lock(true) { + if tr.lock(!tr.readOnly) { mu = new(sync.RWMutex) - defer tr.unlock(true) + defer tr.unlock(!tr.readOnly) + } + if !tr.readOnly { + tr.isoid = newIsoID() } - tr.isoid = newIsoID() tr2 := new(BTreeG[T]) *tr2 = *tr tr2.mu = mu tr2.isoid = newIsoID() + tr2.readOnly = false return tr2 } @@ -1852,6 +1884,9 @@ func (tr *BTreeG[T]) nodeItems(cn **node[T], items []T, mut bool) []T { // Clear will delete all items. func (tr *BTreeG[T]) Clear() { + if tr.readOnly { + panic("read-only tree") + } if tr.lock(true) { defer tr.unlock(true) }