Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions btreeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The read-only mode is mainly to avoid the mutation on this field, so that we can have a fully read-only tree instance but supports IsoCopy method at the same time.

Copy link
Author

@yihuang yihuang Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative approach is to simply add a new method IsoCopyReadOnly without adding a new mode, and make it callers's responsibility to avoid modify the origin tree in place. That'll make the PR less intrusive. WDYT?

}
tr.isoid = newIsoID()
tr2 := new(BTreeG[T])
*tr2 = *tr
tr2.mu = mu
tr2.isoid = newIsoID()
tr2.readOnly = false
return tr2
}

Expand Down Expand Up @@ -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)
}
Expand Down