Complete in-memory RDD implementation: - CRUD: Create, Append, GetValue, PutValue, Delete, Recall - Navigation: GoTo, GoTop, GoBottom, Skip, BOF, EOF - Index: CreateIndex (sorted slice + binary search), Seek (exact + soft) - Bulk: Pack (remove deleted), Zap (clear all) - Multi-open: shared table across work areas - Driver registered as "MEMRDD", prefix "mem:" Tests: 9 tests including 5000-record stress test Create, Append/Get/Put, Navigation, Delete/Pack, Zap, Index (string + numeric), Seek (exact + soft), Stress 5000, Multi-open Use cases: temp tables, query results, pivot, caching Performance: no disk I/O, no byte packing — pure Go slices Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
231 lines
5.8 KiB
Go
231 lines
5.8 KiB
Go
package mem
|
|
|
|
import (
|
|
"five/hbrdd"
|
|
"five/hbrt"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
func createTestTable(t *testing.T) hbrdd.Area {
|
|
t.Helper()
|
|
drv := &MemDriver{}
|
|
area, err := drv.Create(hbrdd.CreateParams{
|
|
Path: "mem:test",
|
|
Alias: "TEST",
|
|
Fields: []hbrdd.FieldInfo{
|
|
{Name: "ID", Type: 'N', Len: 5, Dec: 0},
|
|
{Name: "NAME", Type: 'C', Len: 20, Dec: 0},
|
|
{Name: "SALARY", Type: 'N', Len: 10, Dec: 2},
|
|
{Name: "ACTIVE", Type: 'L', Len: 1, Dec: 0},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return area
|
|
}
|
|
|
|
func addRecord(t *testing.T, area hbrdd.Area, id int, name string, salary float64, active bool) {
|
|
t.Helper()
|
|
area.Append()
|
|
area.PutValue(0, hbrt.MakeInt(id))
|
|
area.PutValue(1, hbrt.MakeString(name))
|
|
area.PutValue(2, hbrt.MakeDouble(salary, 10, 2))
|
|
area.PutValue(3, hbrt.MakeBool(active))
|
|
}
|
|
|
|
func TestMemRDD_Create(t *testing.T) {
|
|
area := createTestTable(t)
|
|
defer area.Close()
|
|
if area.FieldCount() != 4 { t.Errorf("fields: %d", area.FieldCount()) }
|
|
cnt, _ := area.RecCount()
|
|
if cnt != 0 { t.Errorf("count: %d", cnt) }
|
|
t.Log("Create: OK")
|
|
}
|
|
|
|
func TestMemRDD_AppendGetPut(t *testing.T) {
|
|
area := createTestTable(t)
|
|
defer area.Close()
|
|
|
|
addRecord(t, area, 1, "Charles", 15000.50, true)
|
|
addRecord(t, area, 2, "John", 8200, false)
|
|
|
|
cnt, _ := area.RecCount()
|
|
if cnt != 2 { t.Fatalf("count: %d", cnt) }
|
|
|
|
area.GoTo(1)
|
|
v, _ := area.GetValue(0)
|
|
if v.AsInt() != 1 { t.Errorf("ID: %d", v.AsInt()) }
|
|
v, _ = area.GetValue(1)
|
|
if v.AsString() != "Charles" { t.Errorf("NAME: %q", v.AsString()) }
|
|
v, _ = area.GetValue(3)
|
|
if !v.AsBool() { t.Error("ACTIVE: false") }
|
|
|
|
area.GoTo(2)
|
|
v, _ = area.GetValue(1)
|
|
if v.AsString() != "John" { t.Errorf("NAME2: %q", v.AsString()) }
|
|
t.Log("Append/Get/Put: OK")
|
|
}
|
|
|
|
func TestMemRDD_Navigation(t *testing.T) {
|
|
area := createTestTable(t)
|
|
defer area.Close()
|
|
addRecord(t, area, 1, "A", 100, true)
|
|
addRecord(t, area, 2, "B", 200, true)
|
|
addRecord(t, area, 3, "C", 300, true)
|
|
|
|
area.GoTop()
|
|
if area.RecNo() != 1 { t.Errorf("GoTop: %d", area.RecNo()) }
|
|
area.Skip(1)
|
|
if area.RecNo() != 2 { t.Errorf("Skip+1: %d", area.RecNo()) }
|
|
area.GoBottom()
|
|
if area.RecNo() != 3 { t.Errorf("GoBottom: %d", area.RecNo()) }
|
|
area.Skip(1)
|
|
if !area.EOF() { t.Error("not EOF") }
|
|
area.GoTop()
|
|
area.Skip(-1)
|
|
if !area.BOF() { t.Error("not BOF") }
|
|
t.Log("Navigation: OK")
|
|
}
|
|
|
|
func TestMemRDD_DeletePack(t *testing.T) {
|
|
area := createTestTable(t)
|
|
defer area.Close()
|
|
addRecord(t, area, 1, "Keep", 100, true)
|
|
addRecord(t, area, 2, "Del", 200, true)
|
|
addRecord(t, area, 3, "Keep2", 300, true)
|
|
|
|
area.GoTo(2)
|
|
area.Delete()
|
|
if !area.Deleted() { t.Error("not deleted") }
|
|
area.Recall()
|
|
if area.Deleted() { t.Error("recall failed") }
|
|
area.Delete()
|
|
area.Pack()
|
|
|
|
cnt, _ := area.RecCount()
|
|
if cnt != 2 { t.Errorf("pack count: %d", cnt) }
|
|
t.Log("Delete/Pack: OK")
|
|
}
|
|
|
|
func TestMemRDD_Zap(t *testing.T) {
|
|
area := createTestTable(t)
|
|
defer area.Close()
|
|
addRecord(t, area, 1, "A", 100, true)
|
|
area.Zap()
|
|
cnt, _ := area.RecCount()
|
|
if cnt != 0 { t.Errorf("zap: %d", cnt) }
|
|
t.Log("Zap: OK")
|
|
}
|
|
|
|
func TestMemRDD_Index(t *testing.T) {
|
|
area := createTestTable(t)
|
|
defer area.Close()
|
|
ma := area.(*memArea)
|
|
|
|
addRecord(t, area, 3, "Charlie", 300, true)
|
|
addRecord(t, area, 1, "Alice", 100, true)
|
|
addRecord(t, area, 2, "Bob", 200, true)
|
|
|
|
ma.CreateIndex("NAME", 1, false)
|
|
|
|
area.GoTop()
|
|
v, _ := area.GetValue(1)
|
|
if v.AsString() != "Alice" { t.Errorf("top: %q", v.AsString()) }
|
|
area.Skip(1)
|
|
v, _ = area.GetValue(1)
|
|
if v.AsString() != "Bob" { t.Errorf("skip: %q", v.AsString()) }
|
|
area.GoBottom()
|
|
v, _ = area.GetValue(1)
|
|
if v.AsString() != "Charlie" { t.Errorf("bottom: %q", v.AsString()) }
|
|
t.Log("Index: OK")
|
|
}
|
|
|
|
func TestMemRDD_Seek(t *testing.T) {
|
|
area := createTestTable(t)
|
|
defer area.Close()
|
|
ma := area.(*memArea)
|
|
|
|
addRecord(t, area, 1, "Alice", 100, true)
|
|
addRecord(t, area, 2, "Bob", 200, true)
|
|
addRecord(t, area, 3, "Charlie", 300, true)
|
|
addRecord(t, area, 4, "David", 400, true)
|
|
|
|
ma.CreateIndex("NAME", 1, false)
|
|
|
|
if !ma.Seek(hbrt.MakeString("Charlie"), false) { t.Error("exact seek failed") }
|
|
v, _ := area.GetValue(0)
|
|
if v.AsInt() != 3 { t.Errorf("seek Charlie: ID=%d", v.AsInt()) }
|
|
|
|
ma.Seek(hbrt.MakeString("Bobby"), true)
|
|
if area.EOF() { t.Error("soft seek: EOF") }
|
|
v, _ = area.GetValue(1)
|
|
if v.AsString() != "Charlie" { t.Errorf("soft: %q", v.AsString()) }
|
|
|
|
if ma.Seek(hbrt.MakeString("Zebra"), false) { t.Error("Zebra found") }
|
|
if !area.EOF() { t.Error("miss: not EOF") }
|
|
t.Log("Seek: OK")
|
|
}
|
|
|
|
func TestMemRDD_Stress5000(t *testing.T) {
|
|
area := createTestTable(t)
|
|
defer area.Close()
|
|
ma := area.(*memArea)
|
|
|
|
for i := 1; i <= 5000; i++ {
|
|
area.Append()
|
|
area.PutValue(0, hbrt.MakeInt(i))
|
|
area.PutValue(1, hbrt.MakeString(fmt.Sprintf("Name_%05d", i)))
|
|
area.PutValue(2, hbrt.MakeDouble(float64(i)*10.5, 10, 2))
|
|
area.PutValue(3, hbrt.MakeBool(i%2 == 0))
|
|
}
|
|
|
|
cnt, _ := area.RecCount()
|
|
if cnt != 5000 { t.Fatalf("count: %d", cnt) }
|
|
|
|
ma.CreateIndex("ID", 0, false)
|
|
|
|
ma.Seek(hbrt.MakeInt(2500), false)
|
|
v, _ := area.GetValue(1)
|
|
if v.AsString() != "Name_02500" { t.Errorf("seek 2500: %q", v.AsString()) }
|
|
|
|
// Full scan
|
|
area.GoTop()
|
|
n := 0
|
|
for !area.EOF() {
|
|
n++
|
|
area.Skip(1)
|
|
}
|
|
if n != 5000 { t.Errorf("scan: %d", n) }
|
|
t.Logf("Stress 5000: create+index+seek+scan OK")
|
|
}
|
|
|
|
func TestMemRDD_MultiOpen(t *testing.T) {
|
|
drv := &MemDriver{}
|
|
a1, _ := drv.Create(hbrdd.CreateParams{
|
|
Path: "mem:shared", Alias: "A",
|
|
Fields: []hbrdd.FieldInfo{{Name: "VAL", Type: 'N', Len: 5}},
|
|
})
|
|
a1.Append()
|
|
a1.PutValue(0, hbrt.MakeInt(42))
|
|
|
|
a2, err := drv.Open(hbrdd.OpenParams{Path: "mem:shared", Alias: "B"})
|
|
if err != nil { t.Fatal(err) }
|
|
|
|
a2.GoTo(1)
|
|
v, _ := a2.GetValue(0)
|
|
if v.AsInt() != 42 { t.Errorf("shared read: %d", v.AsInt()) }
|
|
|
|
a1.GoTo(1)
|
|
a1.PutValue(0, hbrt.MakeInt(99))
|
|
a2.GoTo(1)
|
|
v, _ = a2.GetValue(0)
|
|
if v.AsInt() != 99 { t.Errorf("shared write: %d", v.AsInt()) }
|
|
|
|
a1.Close()
|
|
a2.Close()
|
|
DropTable("shared")
|
|
t.Log("Multi-open: OK")
|
|
}
|