feat: initial BaraDB — multimodal database engine in Nim
- LSM-Tree storage engine with WAL, bloom filter, MemTable - BaraQL query language: lexer (80+ tokens), recursive descent parser, AST - Vector engine: HNSW + IVF-PQ indexes, 4 distance metrics - Graph engine: adjacency list, BFS/DFS, Dijkstra, PageRank - Full-Text Search: inverted index, BM25 ranking, stemming, stop words - Type system: 17 types (int/float/string/uuid/json/vector/...) - Async TCP server - 21 passing tests
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
## BaraDB — Test Suite
|
||||
import std/unittest
|
||||
import std/tables
|
||||
|
||||
import barabadb/core/types
|
||||
import barabadb/storage/bloom
|
||||
import barabadb/storage/wal
|
||||
import barabadb/storage/lsm
|
||||
import barabadb/query/lexer as lex
|
||||
import barabadb/query/ast
|
||||
import barabadb/query/parser
|
||||
import barabadb/vector/engine as vengine
|
||||
import barabadb/graph/engine as gengine
|
||||
import barabadb/fts/engine as fts
|
||||
|
||||
suite "Core Types":
|
||||
test "Value creation":
|
||||
let v = Value(kind: vkInt64, int64Val: 42)
|
||||
check v.kind == vkInt64
|
||||
check v.int64Val == 42
|
||||
|
||||
test "String value":
|
||||
let v = Value(kind: vkString, strVal: "hello")
|
||||
check v.strVal == "hello"
|
||||
|
||||
test "RecordId creation":
|
||||
let id = newRecordId()
|
||||
check uint64(id) > 0
|
||||
|
||||
suite "Bloom Filter":
|
||||
test "Basic bloom filter operations":
|
||||
var bf = newBloomFilter(1000)
|
||||
let data1 = cast[seq[byte]]("hello")
|
||||
let data2 = cast[seq[byte]]("world")
|
||||
|
||||
bf.add(data1)
|
||||
bf.add(data2)
|
||||
|
||||
check bf.contains(data1)
|
||||
check bf.contains(data2)
|
||||
|
||||
suite "Write-Ahead Log":
|
||||
test "WAL creation":
|
||||
var wal = newWriteAheadLog("/tmp/baradb_test_wal")
|
||||
check wal.entryCount == 0
|
||||
wal.close()
|
||||
|
||||
suite "LSM-Tree Storage":
|
||||
test "Put and Get":
|
||||
var db = newLSMTree("/tmp/baradb_test_lsm")
|
||||
let key = "testkey"
|
||||
let value = cast[seq[byte]]("testvalue")
|
||||
db.put(key, value)
|
||||
let (found, val) = db.get(key)
|
||||
check found
|
||||
check val == value
|
||||
db.close()
|
||||
|
||||
test "Delete":
|
||||
var db = newLSMTree("/tmp/baradb_test_lsm2")
|
||||
let key = "delkey"
|
||||
let value = cast[seq[byte]]("delval")
|
||||
db.put(key, value)
|
||||
db.delete(key)
|
||||
let (found, _) = db.get(key)
|
||||
check not found
|
||||
db.close()
|
||||
|
||||
test "Contains":
|
||||
var db = newLSMTree("/tmp/baradb_test_lsm3")
|
||||
let key = "exists"
|
||||
check not db.contains(key)
|
||||
db.put(key, cast[seq[byte]]("val"))
|
||||
check db.contains(key)
|
||||
db.close()
|
||||
|
||||
suite "BaraQL Lexer":
|
||||
test "Tokenize simple SELECT":
|
||||
let tokens = lex.tokenize("SELECT name FROM users WHERE age > 18")
|
||||
check tokens.len > 0
|
||||
check tokens[0].kind == tkSelect
|
||||
check tokens[1].kind == tkIdent
|
||||
check tokens[1].value == "name"
|
||||
check tokens[2].kind == tkFrom
|
||||
check tokens[3].kind == tkIdent
|
||||
check tokens[3].value == "users"
|
||||
|
||||
test "Tokenize string literals":
|
||||
let tokens = lex.tokenize("'hello world'")
|
||||
check tokens[0].kind == tkStringLit
|
||||
check tokens[0].value == "hello world"
|
||||
|
||||
test "Tokenize operators":
|
||||
let tokens = lex.tokenize("a + b * c")
|
||||
check tokens[0].kind == tkIdent
|
||||
check tokens[1].kind == tkPlus
|
||||
check tokens[2].kind == tkIdent
|
||||
check tokens[3].kind == tkStar
|
||||
|
||||
suite "BaraQL Parser":
|
||||
test "Parse simple SELECT":
|
||||
let ast = parse("SELECT name FROM users WHERE age > 18")
|
||||
check ast.kind == nkStatementList
|
||||
check ast.stmts.len == 1
|
||||
check ast.stmts[0].kind == nkSelect
|
||||
|
||||
test "Parse SELECT with LIMIT":
|
||||
let ast = parse("SELECT * FROM items LIMIT 10")
|
||||
check ast.stmts[0].selLimit != nil
|
||||
|
||||
suite "Vector Engine":
|
||||
test "Distance metrics":
|
||||
let a = @[1.0'f32, 0.0'f32, 0.0'f32]
|
||||
let b = @[0.0'f32, 1.0'f32, 0.0'f32]
|
||||
let c = @[1.0'f32, 0.0'f32, 0.0'f32]
|
||||
|
||||
check vengine.cosineDistance(a, b) > 0.9
|
||||
check vengine.cosineDistance(a, c) < 0.1
|
||||
check vengine.euclideanDistance(a, b) > 1.0
|
||||
check vengine.euclideanDistance(a, c) < 0.1
|
||||
|
||||
test "HNSW index insert and search":
|
||||
var idx = vengine.newHNSWIndex(3)
|
||||
vengine.insert(idx, 1, @[1.0'f32, 0.0'f32, 0.0'f32])
|
||||
vengine.insert(idx, 2, @[0.0'f32, 1.0'f32, 0.0'f32])
|
||||
vengine.insert(idx, 3, @[0.0'f32, 0.0'f32, 1.0'f32])
|
||||
|
||||
let results = vengine.search(idx, @[1.0'f32, 0.1'f32, 0.0'f32], 2)
|
||||
check results.len == 2
|
||||
|
||||
suite "Graph Engine":
|
||||
test "Add nodes and edges":
|
||||
var g = gengine.newGraph()
|
||||
let n1 = gengine.addNode(g, "Person", {"name": "Alice"}.toTable)
|
||||
let n2 = gengine.addNode(g, "Person", {"name": "Bob"}.toTable)
|
||||
let e1 = gengine.addEdge(g, n1, n2, "knows")
|
||||
|
||||
check gengine.nodeCount(g) == 2
|
||||
check gengine.edgeCount(g) == 1
|
||||
|
||||
test "BFS traversal":
|
||||
var g = gengine.newGraph()
|
||||
let n1 = gengine.addNode(g, "A")
|
||||
let n2 = gengine.addNode(g, "B")
|
||||
let n3 = gengine.addNode(g, "C")
|
||||
let n4 = gengine.addNode(g, "D")
|
||||
discard gengine.addEdge(g, n1, n2)
|
||||
discard gengine.addEdge(g, n1, n3)
|
||||
discard gengine.addEdge(g, n2, n4)
|
||||
|
||||
let traversal = gengine.bfs(g, n1)
|
||||
check traversal.len == 4
|
||||
check traversal[0] == n1
|
||||
|
||||
test "DFS traversal":
|
||||
var g = gengine.newGraph()
|
||||
let n1 = gengine.addNode(g, "A")
|
||||
let n2 = gengine.addNode(g, "B")
|
||||
let n3 = gengine.addNode(g, "C")
|
||||
discard gengine.addEdge(g, n1, n2)
|
||||
discard gengine.addEdge(g, n1, n3)
|
||||
|
||||
let traversal = gengine.dfs(g, n1)
|
||||
check traversal.len == 3
|
||||
|
||||
test "Shortest path":
|
||||
var g = gengine.newGraph()
|
||||
let n1 = gengine.addNode(g, "A")
|
||||
let n2 = gengine.addNode(g, "B")
|
||||
let n3 = gengine.addNode(g, "C")
|
||||
discard gengine.addEdge(g, n1, n2)
|
||||
discard gengine.addEdge(g, n2, n3)
|
||||
|
||||
let path = gengine.shortestPath(g, n1, n3)
|
||||
check path.len == 3
|
||||
|
||||
test "PageRank":
|
||||
var g = gengine.newGraph()
|
||||
let n1 = gengine.addNode(g, "A")
|
||||
let n2 = gengine.addNode(g, "B")
|
||||
let n3 = gengine.addNode(g, "C")
|
||||
discard gengine.addEdge(g, n1, n2)
|
||||
discard gengine.addEdge(g, n2, n3)
|
||||
discard gengine.addEdge(g, n3, n1)
|
||||
|
||||
let ranks = gengine.pageRank(g)
|
||||
check ranks.len == 3
|
||||
for nodeId, rank in ranks:
|
||||
check rank > 0.0
|
||||
|
||||
test "Dijkstra":
|
||||
var g = gengine.newGraph()
|
||||
let n1 = gengine.addNode(g, "A")
|
||||
let n2 = gengine.addNode(g, "B")
|
||||
let n3 = gengine.addNode(g, "C")
|
||||
discard gengine.addEdge(g, n1, n2, weight = 1.0)
|
||||
discard gengine.addEdge(g, n2, n3, weight = 2.0)
|
||||
discard gengine.addEdge(g, n1, n3, weight = 10.0)
|
||||
|
||||
let dists = gengine.dijkstra(g, n1)
|
||||
check dists[n1] == 0.0
|
||||
check dists[n2] == 1.0
|
||||
check dists[n3] == 3.0
|
||||
|
||||
suite "Full-Text Search":
|
||||
test "Tokenization":
|
||||
let tokens = fts.tokenize("The quick brown fox jumps over the lazy dog")
|
||||
check tokens.len > 0
|
||||
check "the" notin tokens
|
||||
|
||||
test "Inverted index operations":
|
||||
var idx = fts.newInvertedIndex()
|
||||
fts.addDocument(idx, 1, "The quick brown fox")
|
||||
fts.addDocument(idx, 2, "The lazy brown dog")
|
||||
fts.addDocument(idx, 3, "The quick red car")
|
||||
|
||||
check fts.documentCount(idx) == 3
|
||||
check fts.termCount(idx) > 0
|
||||
|
||||
test "Search results":
|
||||
var idx = fts.newInvertedIndex()
|
||||
fts.addDocument(idx, 1, "Nim programming language is fast")
|
||||
fts.addDocument(idx, 2, "Python is popular for data science")
|
||||
fts.addDocument(idx, 3, "Rust is a systems programming language")
|
||||
|
||||
let results = fts.search(idx, "programming language")
|
||||
check results.len > 0
|
||||
check results[0].score > 0
|
||||
|
||||
test "Document removal":
|
||||
var idx = fts.newInvertedIndex()
|
||||
fts.addDocument(idx, 1, "test document")
|
||||
fts.addDocument(idx, 2, "another document")
|
||||
check fts.documentCount(idx) == 2
|
||||
|
||||
fts.removeDocument(idx, 1)
|
||||
check fts.documentCount(idx) == 1
|
||||
Reference in New Issue
Block a user