Skip to content

Commit

Permalink
feat(examples): add p/demo/avl/rotree
Browse files Browse the repository at this point in the history
Signed-off-by: moul <[email protected]>
  • Loading branch information
moul committed Dec 10, 2024
1 parent d73023e commit 630e8f3
Show file tree
Hide file tree
Showing 9 changed files with 660 additions and 35 deletions.
27 changes: 17 additions & 10 deletions examples/gno.land/p/demo/avl/index/index.gno
Original file line number Diff line number Diff line change
Expand Up @@ -130,19 +130,26 @@ func (it *IndexedTree) removeFromIndexes(primaryKey string, value interface{}) {
}
}

// Add these methods to IndexedTree to satisfy avl.TreeInterface
func (it *IndexedTree) Get(key string) (interface{}, bool) {
return it.primary.Get(key)
}

func (it *IndexedTree) Iterate(start, end string, cb func(key string, value interface{}) bool) bool {
return it.primary.Iterate(start, end, cb)
}

// Add this method to get access to an index as an avl.TreeInterface
func (it *IndexedTree) GetIndexTree(name string) avl.TreeInterface {
if idx, exists := it.indexes[name]; exists {
return idx.tree
}
return nil
}

func (it *IndexedTree) GetPrimary() avl.TreeInterface {
return it.primary
}

func (it *IndexedTree) Update(key string, oldValue interface{}, newValue interface{}) bool {
// Remove old value from indexes
it.removeFromIndexes(key, oldValue)

// Update primary tree
updated := it.primary.Set(key, newValue)

// Add new value to indexes
it.addToIndexes(key, newValue)

return updated
}
194 changes: 171 additions & 23 deletions examples/gno.land/p/demo/avl/index/index_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,183 @@ type Person struct {
Age int
}

func TestIndexedTree(t *testing.T) {
// Create a new indexed tree
it := NewIndexedTree()
type InvalidPerson struct {
ID string
}

func TestIndexedTreeComprehensive(t *testing.T) {
// Test 1: Basic operations without any indexes
t.Run("NoIndexes", func(t *testing.T) {
tree := NewIndexedTree()
p1 := &Person{ID: "1", Name: "Alice", Age: 30}

// Test Set and Get using primary tree directly
tree.Set("1", p1)
val, exists := tree.GetPrimary().Get("1")
if !exists || val.(*Person).Name != "Alice" {
t.Error("Basic Get failed without indexes")
}

// Add secondary indexes
it.AddIndex("name", func(value interface{}) string {
return value.(*Person).Name
// Test direct tree iteration
count := 0
tree.GetPrimary().Iterate("", "", func(key string, value interface{}) bool {
count++
return false
})
if count != 1 {
t.Error("Basic iteration failed")
}
})

it.AddIndex("age", func(value interface{}) string {
return strconv.Itoa(value.(*Person).Age)
// Test 2: Multiple indexes on same field
t.Run("DuplicateIndexes", func(t *testing.T) {
tree := NewIndexedTree()
tree.AddIndex("age1", func(v interface{}) string {
return strconv.Itoa(v.(*Person).Age)
})
tree.AddIndex("age2", func(v interface{}) string {
return strconv.Itoa(v.(*Person).Age)
})

p1 := &Person{ID: "1", Name: "Alice", Age: 30}
p2 := &Person{ID: "2", Name: "Bob", Age: 30}

tree.Set("1", p1)
tree.Set("2", p2)

// Both indexes should return the same results
results1 := tree.GetByIndex("age1", "30")
results2 := tree.GetByIndex("age2", "30")

if len(results1) != 2 || len(results2) != 2 {
t.Error("Duplicate indexes returned different results")
}
})

// Add some test data
person1 := &Person{ID: "1", Name: "Alice", Age: 30}
person2 := &Person{ID: "2", Name: "Bob", Age: 30}
// Test 3: Invalid extractor
t.Run("InvalidExtractor", func(t *testing.T) {
didPanic := false

func() {
defer func() {
if r := recover(); r != nil {
didPanic = true
}
}()

tree := NewIndexedTree()
tree.AddIndex("name", func(v interface{}) string {
// This should panic when trying to use an InvalidPerson
return v.(*Person).Name // Intentionally wrong type assertion
})

invalid := &InvalidPerson{ID: "1"}
tree.Set("1", invalid) // This should trigger the panic
}()

if !didPanic {
t.Error("Expected panic from invalid type")
}
})

// Test 4: Mixed usage of indexed and direct access
t.Run("MixedUsage", func(t *testing.T) {
tree := NewIndexedTree()
tree.AddIndex("age", func(v interface{}) string {
return strconv.Itoa(v.(*Person).Age)
})

p1 := &Person{ID: "1", Name: "Alice", Age: 30}

// Use Set instead of direct tree access to ensure indexes are updated
tree.Set("1", p1)

// Index should work
results := tree.GetByIndex("age", "30")
if len(results) != 1 {
t.Error("Index failed after direct tree usage")
}
})

it.Set(person1.ID, person1)
it.Set(person2.ID, person2)
// Test 5: Using index as TreeInterface
t.Run("IndexAsTreeInterface", func(t *testing.T) {
tree := NewIndexedTree()
tree.AddIndex("age", func(v interface{}) string {
return strconv.Itoa(v.(*Person).Age)
})

// Query by name index
aliceResults := it.GetByIndex("name", "Alice")
if len(aliceResults) != 1 {
t.Error("Expected 1 result for Alice")
}
p1 := &Person{ID: "1", Name: "Alice", Age: 30}
p2 := &Person{ID: "2", Name: "Bob", Age: 30}

// Query by age index
age30Results := it.GetByIndex("age", "30")
if len(age30Results) != 2 {
t.Error("Expected 2 results for age 30")
}
tree.Set("1", p1)
tree.Set("2", p2)

// Get the index as TreeInterface
ageIndex := tree.GetIndexTree("age")
if ageIndex == nil {
t.Error("Failed to get index as TreeInterface")
}

// Use the interface methods
val, exists := ageIndex.Get("30")
if !exists {
t.Error("Failed to get value through index interface")
}

// The value should be a []string of primary keys
primaryKeys := val.([]string)
if len(primaryKeys) != 2 {
t.Error("Wrong number of primary keys in index")
}
})

// Test 6: Remove operations
t.Run("RemoveOperations", func(t *testing.T) {
tree := NewIndexedTree()
tree.AddIndex("age", func(v interface{}) string {
return strconv.Itoa(v.(*Person).Age)
})

p1 := &Person{ID: "1", Name: "Alice", Age: 30}
tree.Set("1", p1)

// Remove and verify both primary and index
tree.Remove("1")

if _, exists := tree.GetPrimary().Get("1"); exists {
t.Error("Entry still exists in primary after remove")
}

results := tree.GetByIndex("age", "30")
if len(results) != 0 {
t.Error("Entry still exists in index after remove")
}
})

// Test 7: Update operations
t.Run("UpdateOperations", func(t *testing.T) {
tree := NewIndexedTree()
tree.AddIndex("age", func(v interface{}) string {
return strconv.Itoa(v.(*Person).Age)
})

p1 := &Person{ID: "1", Name: "Alice", Age: 30}
tree.Set("1", p1)

// Update age using the new Update method
p1New := &Person{ID: "1", Name: "Alice", Age: 31}
tree.Update("1", p1, p1New)

// Check old index is removed
results30 := tree.GetByIndex("age", "30")
if len(results30) != 0 {
t.Error("Old index entry still exists")
}

// Check new index is added
results31 := tree.GetByIndex("age", "31")
if len(results31) != 1 {
t.Error("New index entry not found")
}
})
}
4 changes: 2 additions & 2 deletions examples/gno.land/p/demo/avl/pager/pager.gno
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// Pager is a struct that holds the AVL tree and pagination parameters.
type Pager struct {
Tree *avl.Tree
Tree avl.ITree
PageQueryParam string
SizeQueryParam string
DefaultPageSize int
Expand All @@ -37,7 +37,7 @@ type Item struct {
}

// NewPager creates a new Pager with default values.
func NewPager(tree *avl.Tree, defaultPageSize int, reversed bool) *Pager {
func NewPager(tree avl.TreeInterface, defaultPageSize int, reversed bool) *Pager {
return &Pager{
Tree: tree,
PageQueryParam: "page",
Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/avl/rotree/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/avl/rotree
Loading

0 comments on commit 630e8f3

Please sign in to comment.