From ca87acb120675ac212a2b9af9e6f36aa4f29d304 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Wed, 13 May 2026 13:36:58 +0300 Subject: [PATCH] debug(ci): comment out first half of test_all.nim --- tests/test_all.nim | 1312 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1312 insertions(+) diff --git a/tests/test_all.nim b/tests/test_all.nim index 94125c3..4d13467 100644 --- a/tests/test_all.nim +++ b/tests/test_all.nim @@ -1,3 +1,1315 @@ +## BaraDB — Test Suite +import std/unittest +import std/tables +import std/strutils +import std/os +import std/asyncdispatch +import std/times +import std/random +import std/monotimes +import std/base64 + +import barabadb/core/types +import barabadb/core/mvcc +import barabadb/core/deadlock +import barabadb/core/config +import barabadb/core/server +import barabadb/core/columnar +import barabadb/core/raft +import barabadb/core/sharding +import barabadb/core/replication +import barabadb/storage/bloom +import barabadb/storage/wal +import barabadb/storage/lsm +import barabadb/storage/btree +import barabadb/storage/compaction +import barabadb/query/lexer as lex +import barabadb/query/ast +import barabadb/query/parser +import barabadb/query/ir as qir +import barabadb/query/codegen +import barabadb/query/udf +import barabadb/vector/simd +import barabadb/core/crossmodal +import barabadb/core/gossip +import barabadb/client/client +import barabadb/client/fileops +import barabadb/fts/multilang as mlang +import barabadb/protocol/zerocopy +import barabadb/query/adaptive +import barabadb/query/executor as qexec +import barabadb/core/disttxn +import barabadb/vector/engine as vengine +import barabadb/graph/cypher +import barabadb/vector/quant as vquant +import barabadb/storage/recovery +import barabadb/cli/shell +import barabadb/protocol/ssl +import barabadb/graph/engine as gengine +import barabadb/graph/community as gcomm +import barabadb/fts/engine as fts +import barabadb/protocol/wire +import barabadb/protocol/pool +import barabadb/protocol/auth +import barabadb/protocol/scram +import barabadb/protocol/ratelimit +import barabadb/schema/schema as schema + +#[ +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": + removeDir("/tmp/baradb_test_lsm") + 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": + removeDir("/tmp/baradb_test_lsm2") + 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": + removeDir("/tmp/baradb_test_lsm3") + 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 + + test "Save and load graph": + var g = gengine.newGraph() + let n1 = gengine.addNode(g, "Person", {"name": "Alice"}.toTable) + let n2 = gengine.addNode(g, "Person", {"name": "Bob"}.toTable) + let n3 = gengine.addNode(g, "City", {"name": "Sofia"}.toTable) + discard gengine.addEdge(g, n1, n2, "knows", {"since": "2020"}.toTable, 1.5) + discard gengine.addEdge(g, n2, n3, "lives_in", weight = 2.0) + + let path = "/tmp/baradb_test_graph.bin" + removeFile(path) + gengine.saveToFile(g, path) + + let g2 = gengine.loadFromFile(path) + check gengine.nodeCount(g2) == 3 + check gengine.edgeCount(g2) == 2 + check gengine.neighbors(g2, n1).len == 1 + check gengine.neighbors(g2, n1)[0] == n2 + check gengine.neighbors(g2, n2)[0] == n3 + + let loadedNode = gengine.getNode(g2, n1) + check loadedNode.label == "Person" + check loadedNode.properties["name"] == "Alice" + + let sp = gengine.shortestPath(g2, n1, n3) + check sp.len == 3 + + removeFile(path) + +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 + +suite "MVCC Transactions": + test "Begin and commit transaction": + var tm = newTxnManager() + let txn = tm.beginTxn() + check txn.state == tsActive + check tm.write(txn, "key1", cast[seq[byte]]("value1")) + check tm.commit(txn) + check txn.state == tsCommitted + + test "Read own writes": + var tm = newTxnManager() + let txn = tm.beginTxn() + discard tm.write(txn, "key1", cast[seq[byte]]("value1")) + let (found, val) = tm.read(txn, "key1") + check found + check val == cast[seq[byte]]("value1") + discard tm.commit(txn) + + test "Abort transaction": + var tm = newTxnManager() + let txn = tm.beginTxn() + discard tm.write(txn, "key1", cast[seq[byte]]("value1")) + discard tm.abortTxn(txn) + check txn.state == tsAborted + + test "Snapshot isolation — no dirty reads": + var tm = newTxnManager() + let txn1 = tm.beginTxn() + discard tm.write(txn1, "key1", cast[seq[byte]]("value1")) + + let txn2 = tm.beginTxn() + let (found, _) = tm.read(txn2, "key1") + check not found # txn2 can't see txn1's uncommitted write + + discard tm.commit(txn1) + # txn2 still can't see it (snapshot taken before commit) + let (found2, _) = tm.read(txn2, "key1") + check not found2 + discard tm.abortTxn(txn2) + + test "Committed writes visible to new transactions": + var tm = newTxnManager() + let txn1 = tm.beginTxn() + discard tm.write(txn1, "key1", cast[seq[byte]]("value1")) + discard tm.commit(txn1) + + let txn2 = tm.beginTxn() + let (found, val) = tm.read(txn2, "key1") + check found + check val == cast[seq[byte]]("value1") + discard tm.commit(txn2) + + test "Savepoint and rollback": + var tm = newTxnManager() + let txn = tm.beginTxn() + discard tm.write(txn, "key1", cast[seq[byte]]("value1")) + tm.savepoint(txn) + discard tm.write(txn, "key2", cast[seq[byte]]("value2")) + check tm.rollbackToSavepoint(txn) + let (found1, _) = tm.read(txn, "key1") + check found1 + let (found2, _) = tm.read(txn, "key2") + check not found2 + discard tm.commit(txn) + + test "Delete via xmax": + var tm = newTxnManager() + let txn1 = tm.beginTxn() + discard tm.write(txn1, "key1", cast[seq[byte]]("value1")) + discard tm.commit(txn1) + + let txn2 = tm.beginTxn() + discard tm.delete(txn2, "key1") + discard tm.commit(txn2) + + let txn3 = tm.beginTxn() + let (found, _) = tm.read(txn3, "key1") + check not found + discard tm.commit(txn3) + +suite "Deadlock Detection": + test "No deadlock without cycles": + var dd = newDeadlockDetector() + dd.addWait(1, 2) + dd.addWait(2, 3) + check not dd.hasDeadlock() + + test "Detect simple deadlock": + var dd = newDeadlockDetector() + dd.addWait(1, 2) + dd.addWait(2, 1) + check dd.hasDeadlock() + + test "Find deadlock victim": + var dd = newDeadlockDetector() + dd.addWait(1, 2) + dd.addWait(2, 3) + dd.addWait(3, 1) + let victim = dd.findDeadlockVictim() + check victim == 3 # youngest txn + + test "Remove transaction clears edges": + var dd = newDeadlockDetector() + dd.addWait(1, 2) + dd.addWait(2, 1) + dd.removeTxn(2) + check not dd.hasDeadlock() + +suite "MVCC Deadlock Detection": + test "TxnManager detects and breaks deadlock": + var tm = newTxnManager() + var t1 = tm.beginTxn() + var t2 = tm.beginTxn() + # t1 writes key "a" + check tm.write(t1, "a", @[1'u8]) + # t2 writes key "b" + check tm.write(t2, "b", @[2'u8]) + # t2 tries to write "a" — conflicts with t1 (active), adds wait edge t2->t1 + check not tm.write(t2, "a", @[3'u8]) + # t1 tries to write "b" — conflicts with t2 (active), adds wait edge t1->t2 + # This creates a cycle: t1->t2->t1 + check not tm.write(t1, "b", @[4'u8]) + # One of the transactions should have been aborted as victim + let t1Active = t1.state == tsActive + let t2Active = t2.state == tsActive + # At least one victim must be aborted + check (not t1Active) or (not t2Active) + # The survivor should be able to commit + if t1Active: + check tm.commit(t1) + if t2Active: + check tm.commit(t2) + + test "No false deadlock on sequential writes": + var tm = newTxnManager() + var t1 = tm.beginTxn() + # t1 writes key "a" + check tm.write(t1, "a", @[1'u8]) + # t1 commits + check tm.commit(t1) + # t2 begins after t1 committed + var t2 = tm.beginTxn() + # t2 writes same key — no active conflict + check tm.write(t2, "a", @[2'u8]) + check tm.commit(t2) + +suite "Wire Protocol": + test "Value serialization roundtrip": + var buf: seq[byte] = @[] + let val = WireValue(kind: fkString, strVal: "hello world") + buf.serializeValue(val) + var pos = 0 + let decoded = buf.deserializeValue(pos) + check decoded.kind == fkString + check decoded.strVal == "hello world" + + test "Int64 serialization": + var buf: seq[byte] = @[] + let val = WireValue(kind: fkInt64, int64Val: 42) + buf.serializeValue(val) + var pos = 0 + let decoded = buf.deserializeValue(pos) + check decoded.kind == fkInt64 + check decoded.int64Val == 42 + + test "Array serialization": + var buf: seq[byte] = @[] + let val = WireValue(kind: fkArray, arrayVal: @[ + WireValue(kind: fkInt32, int32Val: 1), + WireValue(kind: fkInt32, int32Val: 2), + WireValue(kind: fkInt32, int32Val: 3), + ]) + buf.serializeValue(val) + var pos = 0 + let decoded = buf.deserializeValue(pos) + check decoded.kind == fkArray + check decoded.arrayVal.len == 3 + check decoded.arrayVal[1].int32Val == 2 + + test "Vector serialization": + var buf: seq[byte] = @[] + let val = WireValue(kind: fkVector, vecVal: @[1.0'f32, 2.0'f32, 3.0'f32]) + buf.serializeValue(val) + var pos = 0 + let decoded = buf.deserializeValue(pos) + check decoded.kind == fkVector + check decoded.vecVal.len == 3 + + test "Query message creation": + let msg = makeQueryMessage(1, "SELECT * FROM users") + check msg.len > 0 + check msg[3] == byte(mkQuery) # big-endian uint32, last byte + +suite "Schema System": + test "Create type with properties": + var s = newSchema() + let person = newType("Person") + person.addProperty("name", "str", required = true) + person.addProperty("age", "int32") + s.addType("default", person) + check s.getType("Person") != nil + check s.getType("Person").properties.len == 2 + + test "Create type with links": + var s = newSchema() + let person = newType("Person") + person.addProperty("name", "str", required = true) + s.addType("default", person) + + let movie = newType("Movie") + movie.addProperty("title", "str", required = true) + movie.addLink("actors", "Person", multi = true) + s.addType("default", movie) + + check movie.links.len == 1 + check movie.links["actors"].target == "Person" + + test "Schema diff": + let s1 = newSchema() + let t1 = newType("Person") + t1.addProperty("name", "str") + s1.addType("default", t1) + + let s2 = newSchema() + let t2 = newType("Person") + t2.addProperty("name", "str") + t2.addProperty("age", "int32") + s2.addType("default", t2) + let movieT = newType("Movie") + movieT.addProperty("title", "str") + s2.addType("default", movieT) + + let d = diff(s1, s2) + check d.addedTypes.len == 1 + check d.addedTypes[0] == "Movie" + check d.modifiedTypes.len == 1 + check d.modifiedTypes[0].addedProperties.len == 1 + + test "Type validation": + let t = newType("Person") + t.addProperty("name", "str", required = true) + t.addLink("friend", "") # empty target + let errors = t.validateType() + check errors.len == 1 # empty link target + + test "Migration creation": + var s = newSchema() + let m1 = s.createMigration("initial", "CREATE TYPE Person { name: str }") + check m1.id == 1 + let m2 = s.createMigration("add age", "ALTER TYPE Person { ADD age: int32 }") + check m2.id == 2 + check m2.parentId == 1 + + test "Type to string": + let t = newType("Person") + t.addProperty("name", "str", required = true) + t.addProperty("age", "int32") + t.addLink("friend", "Person") + let s = $t + check s.find("Person") >= 0 + check s.find("name") >= 0 + check s.find("str") >= 0 + +suite "B-Tree Index": + test "Insert and get": + var btree = newBTreeIndex[string, string]() + btree.insert("key1", "value1") + btree.insert("key2", "value2") + check btree.get("key1") == @["value1"] + check btree.get("key2") == @["value2"] + check not btree.contains("nonexistent") + + test "Scan range": + var btree = newBTreeIndex[string, string]() + for i in 0..9: + btree.insert("key" & $i, "val" & $i) + let results = btree.scan("key2", "key5") + check results.len == 4 + + test "Duplicate keys": + var btree = newBTreeIndex[string, string]() + btree.insert("a", "val1") + btree.insert("a", "val2") + let vals = btree.get("a") + check vals.len == 2 + +suite "Columnar Engine": + test "Column batch operations": + var batch = newColumnBatch() + var intCol = batch.addInt64Col("age") + var strCol = batch.addStringCol("name") + intCol.appendInt64(25) + intCol.appendInt64(30) + intCol.appendInt64(35) + strCol.appendString("Alice") + strCol.appendString("Bob") + strCol.appendString("Charlie") + check batch.rowCount() == 3 + + test "Aggregate operations": + var batch = newColumnBatch() + var col = batch.addInt64Col("age") + col.appendInt64(10) + col.appendInt64(20) + col.appendInt64(30) + check col.sumInt64() == 60 + check col.avgInt64() - 20.0 < 0.001 + check col.minInt64() == 10 + check col.maxInt64() == 30 + check col.count() == 3 + + test "RLE encoding": + let data = @[1'i64, 1, 1, 2, 2, 3, 3, 3, 3] + let encoded = rleEncode(data) + let decoded = rleDecode(encoded) + check decoded == data + + test "Dictionary encoding": + let data = @["apple", "banana", "apple", "cherry", "banana"] + let encoded = dictEncode(data) + let decoded = dictDecode(encoded) + check decoded == data + check encoded.dict.len == 3 + + test "GroupBy": + var batch = newColumnBatch() + var deptCol = batch.addStringCol("department") + var salaryCol = batch.addInt64Col("salary") + deptCol.appendString("Engineering") + deptCol.appendString("Sales") + deptCol.appendString("Engineering") + salaryCol.appendInt64(100) + salaryCol.appendInt64(80) + salaryCol.appendInt64(120) + + let groups = groupBy(batch, @["department"]) + check groups.groups.len == 2 # unique departments + +suite "Type Checker & IR": + test "Literal type inference": + var tc = newTypeChecker() + let lit = IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkInt64, int64Val: 42)) + let t = tc.inferExpr(lit, initTable[string, IRType]()) + check t.name == "int64" + + test "Binary operation type inference": + var tc = newTypeChecker() + let left = IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkInt64, int64Val: 1)) + let right = IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkInt64, int64Val: 2)) + let bin = IRExpr(kind: irekBinary, binOp: irEq, binLeft: left, binRight: right) + let t = tc.inferExpr(bin, initTable[string, IRType]()) + check t.name == "bool" + + test "Aggregate type inference": + var tc = newTypeChecker() + let agg = IRExpr(kind: irekAggregate, aggOp: irCount) + let t = tc.inferExpr(agg, initTable[string, IRType]()) + check t.name == "int64" + +suite "Connection Pool": + test "Create pool and acquire connection": + var pool = newConnectionPool("127.0.0.1", 9472) + let conn = pool.acquire() + check conn != nil + check conn.host == "127.0.0.1" + check conn.port == 9472 + pool.release(conn) + + test "Pool stats": + var cfg = defaultPoolConfig() + cfg.minConnections = 1 + cfg.maxConnections = 10 + var pool = newConnectionPool("127.0.0.1", 9472, "default", cfg) + let conn1 = pool.acquire() + let (total, idle, inUse) = pool.stats() + check inUse == 1 + pool.release(conn1) + let (t2, i2, u2) = pool.stats() + check u2 == 0 + +suite "Authentication": + test "Anonymous auth": + var am = newAuthManager() + let result = am.validateCredentials(AuthCredentials(authMethod: amNone)) + check result.authenticated + check result.username == "anonymous" + + test "Token auth": + var am = newAuthManager("mysecretkey") + let token = am.createToken(JWTClaims(sub: "user1", role: "admin")) + let result = am.validateCredentials(AuthCredentials( + authMethod: amToken, payload: token)) + check result.authenticated + + test "Invalid token": + var am = newAuthManager("mysecretkey") + let result = am.validateCredentials(AuthCredentials( + authMethod: amToken, payload: "invalid_token")) + check not result.authenticated + + test "SCRAM-SHA-256 full handshake": + var am = newAuthManager() + am.registerScramUser("alice", "wonderland") + + # Step 1: ClientFirstMessage + let clientNonce = generateNonce() + let clientFirst = "n,,n=alice,r=" & clientNonce + + # Step 2: ServerFirstMessage + let serverFirst = am.startScram(clientFirst) + check serverFirst.startsWith("r=") + check serverFirst.contains("s=") + check serverFirst.contains("i=4096") + + # Parse serverFirst + var combinedNonce = "" + var saltB64 = "" + var iterCount = 0 + for part in serverFirst.split(","): + if part.startsWith("r="): combinedNonce = part[2..^1] + elif part.startsWith("s="): saltB64 = part[2..^1] + elif part.startsWith("i="): iterCount = parseInt(part[2..^1]) + check combinedNonce.len > 0 + check saltB64.len > 0 + check iterCount == 4096 + + # Step 3: Compute client proof + let salt = decode(saltB64) + let saltedPassword = pbkdf2HmacSha256("wonderland", salt, iterCount) + let clientKey = hmacSha256(saltedPassword, "Client Key") + let storedKey = sha256(clientKey) + let serverKey = hmacSha256(saltedPassword, "Server Key") + + let clientFirstBare = "n=alice,r=" & clientNonce + let clientFinalWithoutProof = "c=biws,r=" & combinedNonce + let authMessage = clientFirstBare & "," & serverFirst & "," & clientFinalWithoutProof + + let clientSignature = hmacSha256(storedKey, authMessage) + var clientProof = newSeq[byte](32) + for i in 0..<32: + clientProof[i] = clientKey[i] xor clientSignature[i] + var proofStr = newString(32) + copyMem(addr proofStr[0], addr clientProof[0], 32) + let proofB64 = encode(proofStr) + # Strip padding + var proofB64Clean = proofB64 + while proofB64Clean.endsWith("="): + proofB64Clean.setLen(proofB64Clean.len - 1) + proofB64Clean = proofB64Clean.replace("+", "-").replace("/", "_") + + let clientFinal = "c=biws,r=" & combinedNonce & ",p=" & proofB64Clean + + # Step 4: Server verifies + let (ok, serverFinal) = am.finishScram(clientFinal) + check ok + check serverFinal.startsWith("v=") + + # Verify server signature ourselves + let expectedServerSig = hmacSha256(serverKey, authMessage) + var expectedStr = newString(32) + copyMem(addr expectedStr[0], unsafeAddr expectedServerSig[0], 32) + let expectedB64 = encode(expectedStr) + check serverFinal == "v=" & expectedB64 + + test "SCRAM-SHA-256 invalid proof rejected": + var am = newAuthManager() + am.registerScramUser("bob", "builder") + + let clientNonce = generateNonce() + let clientFirst = "n,,n=bob,r=" & clientNonce + let serverFirst = am.startScram(clientFirst) + + var combinedNonce = "" + for part in serverFirst.split(","): + if part.startsWith("r="): combinedNonce = part[2..^1] + + let clientFinal = "c=biws,r=" & combinedNonce & ",p=invalidproof" + let (ok, serverFinal) = am.finishScram(clientFinal) + check not ok + check serverFinal == "e=invalid-proof" + +suite "Vector Quantization": + test "Scalar quantization 8-bit": + var sq = newScalarQuantizer(4, bits = 8) + let vectors = @[@[1.0'f32, 2.0'f32, 3.0'f32, 4.0'f32], + @[5.0'f32, 6.0'f32, 7.0'f32, 8.0'f32]] + sq.train(vectors) + let qv = sq.encode(@[3.0'f32, 4.0'f32, 5.0'f32, 6.0'f32]) + check qv.kind == qkScalar8 + check qv.int8Data.len == 4 + + test "Scalar quantization 4-bit": + var sq = newScalarQuantizer(4, bits = 4) + let vectors = @[@[1.0'f32, 2.0'f32, 3.0'f32, 4.0'f32]] + sq.train(vectors) + let qv = sq.encode(@[3.0'f32, 4.0'f32, 5.0'f32, 6.0'f32]) + check qv.kind == qkScalar4 + check qv.int4Data.len == 2 + + test "Product quantization": + var pq = newProductQuantizer(8, nSubspaces = 4, nClusters = 16) + var vectors: seq[seq[float32]] = @[] + for i in 0..<50: + var v: seq[float32] = @[] + for j in 0..<8: + v.add(float32(i * 8 + j) * 0.1) + vectors.add(v) + pq.train(vectors, nIterations = 5) + let qv = pq.encode(vectors[0]) + check qv.kind == qkProduct + check qv.pqCodes.len == 4 + + test "Binary quantization": + let v = @[1.0'f32, -1.0'f32, 0.5'f32, -0.5'f32] + let qv = binaryQuantize(v) + check qv.kind == qkBinary + check qv.binData.len == 1 + +suite "Louvain Community Detection": + test "Detect communities in simple graph": + var g = gengine.newGraph() + # Create two communities + let n1 = gengine.addNode(g, "A") + let n2 = gengine.addNode(g, "B") + let n3 = gengine.addNode(g, "C") + let n4 = gengine.addNode(g, "D") + # Community 1: fully connected + discard gengine.addEdge(g, n1, n2) + discard gengine.addEdge(g, n2, n3) + discard gengine.addEdge(g, n1, n3) + # Community 2 + discard gengine.addEdge(g, n3, n4) # single connection + + let result = louvain(g) + check result.communities.len > 0 + check result.numCommunities >= 1 + + test "Pattern matching": + var g = gengine.newGraph() + let a = gengine.addNode(g, "Person", {"name": "Alice"}.toTable) + let b = gengine.addNode(g, "Person", {"name": "Bob"}.toTable) + let c = gengine.addNode(g, "Person", {"name": "Charlie"}.toTable) + discard gengine.addEdge(g, a, b, "knows") + discard gengine.addEdge(g, b, c, "knows") + discard gengine.addEdge(g, a, c, "knows") + + var pattern = newGraphPattern() + pattern.addNode(0, "Person", {"name": "Alice"}.toTable) + pattern.addNode(1, "Person") + pattern.addEdge(0, 1, "knows") + + let matches = matchPattern(g, pattern) + check matches.len >= 1 + +suite "SSTable Compaction": + test "Create compaction strategy": + var cs = newCompactionStrategy("/tmp/baradb_test_compaction") + check cs.levelCount == 0 + check cs.tableCount == 0 + + test "Add table and check compaction need": + var cs = newCompactionStrategy("/tmp/baradb_test_compaction2") + cs.addTable(SSTableMeta(path: "test.sst", level: 0, minKey: "a", maxKey: "z", + entryCount: 100, sizeBytes: 1024, createdAt: 1)) + check cs.tableCount == 1 + +suite "Page Cache": + test "Cache hit and miss": + var cache = newPageCache(10) + cache.put("key1", cast[seq[byte]]("data1")) + let (found, data) = cache.get("key1") + check found + check cache.hits == 1 + + let (found2, _) = cache.get("missing") + check not found2 + check cache.misses == 1 + + test "LRU eviction": + var cache = newPageCache(2) + cache.put("a", cast[seq[byte]]("1")) + cache.put("b", cast[seq[byte]]("2")) + cache.put("c", cast[seq[byte]]("3")) # evicts "a" + check cache.len == 2 + let (found, _) = cache.get("a") + check not found # evicted + + test "Hit rate": + var cache = newPageCache(10) + cache.put("k", cast[seq[byte]]("v")) + discard cache.get("k") + discard cache.get("k") + discard cache.get("miss") + check cache.hitRate - 0.666 < 0.01 + +suite "Rate Limiter": + test "Token bucket allows requests": + var rl = newRateLimiter(rlaTokenBucket, 1000, 100) + check rl.allowRequest("client1") + check rl.allowRequest("client1") + + test "Sliding window rate limiting": + var rl = newRateLimiter(rlaSlidingWindow, 1000, 3) + check rl.allowRequest("client1") + check rl.allowRequest("client1") + check rl.allowRequest("client1") + check not rl.allowRequest("client1") # over limit + + test "Remaining quota": + var rl = newRateLimiter(rlaTokenBucket, 1000, 10) + discard rl.allowRequest("c1") + let remaining = rl.remainingQuota("c1") + check remaining >= 0 + +suite "FTS Fuzzy Search": + test "Levenshtein distance": + check levenshtein("kitten", "sitting") == 3 + check levenshtein("", "abc") == 3 + check levenshtein("same", "same") == 0 + + test "Fuzzy search": + var idx = newInvertedIndex() + idx.addDocument(1, "Nim programming language") + idx.addDocument(2, "Python is popular") + let results = idx.fuzzySearch("programing", maxDistance = 2) # typo + check results.len >= 0 # may or may not match + + test "Regex search with wildcard": + var idx = newInvertedIndex() + idx.addDocument(1, "fast database engine") + idx.addDocument(2, "slow query optimizer") + let results = idx.regexSearch("fast*") + check results.len >= 0 + +suite "Vector Metadata Filtering": + test "Search with metadata filter": + var idx = vengine.newHNSWIndex(3) + vengine.insert(idx, 1, @[1.0'f32, 0.0'f32, 0.0'f32], + {"category": "A", "region": "US"}.toTable) + vengine.insert(idx, 2, @[0.9'f32, 0.1'f32, 0.0'f32], + {"category": "B", "region": "EU"}.toTable) + vengine.insert(idx, 3, @[1.0'f32, 0.0'f32, 0.0'f32], + {"category": "A", "region": "EU"}.toTable) + + # Filter: only category A + proc filterA(metadata: Table[string, string]): bool = + return metadata.getOrDefault("category", "") == "A" + + let results = vengine.searchWithFilter(idx, @[1.0'f32, 0.0'f32, 0.0'f32], 10, + filter = filterA) + check results.len == 2 # only category A entries + +suite "BaraQL Parser — Extended": + test "Parse GROUP BY": + let ast = parse("SELECT dept, count(*) FROM employees GROUP BY dept") + check ast.stmts.len == 1 + check ast.stmts[0].selGroupBy.len == 1 + + test "Parse GROUP BY with HAVING": + let ast = parse("SELECT dept, count(*) FROM employees GROUP BY dept HAVING count(*) > 5") + check ast.stmts[0].selHaving != nil + + test "Parse ORDER BY with direction": + let ast = parse("SELECT name FROM users ORDER BY age DESC") + check ast.stmts[0].selOrderBy.len == 1 + check ast.stmts[0].selOrderBy[0].orderByDir == sdDesc + + test "Parse ORDER BY multiple columns": + let ast = parse("SELECT * FROM t ORDER BY a ASC, b DESC") + check ast.stmts[0].selOrderBy.len == 2 + + test "Parse INNER JOIN": + let ast = parse("SELECT u.name, o.total FROM users u INNER JOIN orders o ON u.id = o.user_id") + check ast.stmts[0].selJoins.len == 1 + check ast.stmts[0].selJoins[0].joinKind == jkInner + + test "Parse LEFT JOIN": + let ast = parse("SELECT u.name FROM users u LEFT JOIN orders o ON u.id = o.user_id") + check ast.stmts[0].selJoins.len == 1 + check ast.stmts[0].selJoins[0].joinKind == jkLeft + + test "Parse multiple JOINs": + let ast = parse("SELECT * FROM a JOIN b ON a.id = b.aid JOIN c ON b.id = c.bid") + check ast.stmts[0].selJoins.len == 2 + + test "Parse CTE (WITH)": + let ast = parse("WITH active AS (SELECT * FROM users WHERE active = true) SELECT * FROM active") + check ast.stmts[0].selWith.len == 1 + check ast.stmts[0].selWith[0][0] == "active" + check ast.stmts[0].selWith[0][2] == false + + test "Parse multiple CTEs": + let ast = parse("WITH a AS (SELECT id FROM t1), b AS (SELECT id FROM t2) SELECT * FROM a") + check ast.stmts[0].selWith.len == 2 + check ast.stmts[0].selWith[0][2] == false + + test "Parse aggregate functions in SELECT": + let ast = parse("SELECT count(*), sum(amount), avg(price), min(age), max(score) FROM orders") + check ast.stmts[0].selResult.len == 5 + + test "Parse CASE expression": + let ast = parse("SELECT CASE WHEN age > 18 THEN 'adult' ELSE 'minor' END FROM users") + check ast.stmts[0].selResult.len == 1 + + test "Parse BETWEEN": + let ast = parse("SELECT * FROM products WHERE price BETWEEN 10 AND 100") + check ast.stmts[0].selWhere != nil + + test "Parse subquery in FROM": + let ast = parse("SELECT * FROM (SELECT id FROM users) AS sub") + check ast.stmts[0].selFrom != nil + + test "Parse UPDATE SET WHERE": + let ast = parse("UPDATE users SET name = 'Alice' WHERE id = 1") + check ast.stmts[0].updSet.len == 1 + + test "Parse DELETE WHERE": + let ast = parse("DELETE FROM users WHERE id = 1") + check ast.stmts[0].delWhere != nil + + test "Parse CREATE TYPE with properties": + let ast = parse("CREATE TYPE Person { name: str, age: int32 }") + check ast.stmts[0].ctName == "Person" + check ast.stmts[0].ctProperties.len == 2 + +suite "Raft Consensus": + test "Create cluster with nodes": + var cluster = newRaftCluster() + cluster.addNode("n1") + cluster.addNode("n2") + cluster.addNode("n3") + check cluster.nodes.len == 3 + check cluster.nodes["n1"].peers.len == 2 + + test "Initial state is follower": + var cluster = newRaftCluster() + cluster.addNode("n1") + check cluster.nodes["n1"].state == rsFollower + + test "Election — single node becomes leader": + var cluster = newRaftCluster() + cluster.addNode("n1") + let node = cluster.nodes["n1"] + node.becomeCandidate() + node.becomeLeader() + check node.isLeader + check node.leaderId == "n1" + + test "Log replication": + var cluster = newRaftCluster() + cluster.addNode("n1") + let node = cluster.nodes["n1"] + node.becomeCandidate() + node.becomeLeader() + let entry = node.appendLog("SET key1 value1") + check entry.index == 1 + check node.logLen == 1 + let entry2 = node.appendLog("SET key2 value2") + check entry2.index == 2 + check node.logLen == 2 + + test "RequestVote handling": + var cluster = newRaftCluster() + cluster.addNode("n1") + cluster.addNode("n2") + let n1 = cluster.nodes["n1"] + let n2 = cluster.nodes["n2"] + let req = RaftMessage(kind: rmkRequestVote, term: 1, senderId: "n2", + lastLogIndex: 0, lastLogTerm: 0) + let reply = n1.handleRequestVote(req) + check reply.success + check n1.votedFor == "n2" + + test "AppendEntries handling": + var cluster = newRaftCluster() + cluster.addNode("n1") + cluster.addNode("n2") + let n2 = cluster.nodes["n2"] + let msg = RaftMessage(kind: rmkAppendEntries, term: 1, senderId: "n1", + prevLogIndex: 0, prevLogTerm: 0, + entries: @[LogEntry(term: 1, index: 1, command: "SET x 1")], + leaderCommit: 0) + let reply = n2.handleAppendEntries(msg) + check reply.success + check n2.logLen == 1 + +suite "Sharding": + test "Hash-based sharding": + var router = newShardRouter(ShardConfig(numShards: 4, strategy: ssHash)) + check router.shardCount == 4 + let s1 = router.getShard("user_1") + let s2 = router.getShard("user_2") + check s1 >= 0 and s1 < 4 + check s2 >= 0 and s2 < 4 + + test "Consistent hashing": + var router = newShardRouter(ShardConfig(numShards: 4, strategy: ssConsistent)) + router.addVirtualNodes(50) + let s = router.getShard("some_key") + check s >= 0 and s < 4 + + test "Range-based sharding": + var router = newShardRouter(ShardConfig(numShards: 3, strategy: ssRange)) + router.setRangeBounds(@[("a", "f"), ("g", "n"), ("o", "z")]) + check router.getShardRange("apple") == 0 + check router.getShardRange("hello") == 1 + check router.getShardRange("top") == 2 + + test "Rebalance assigns nodes": + var router = newShardRouter(ShardConfig(numShards: 3, replicas: 2, strategy: ssHash)) + router.rebalance(@["node1", "node2", "node3"]) + for shard in router.shards: + check shard.nodeIds.len == 2 # 2 replicas + + test "Replicas of key": + var router = newShardRouter(ShardConfig(numShards: 2, replicas: 1, strategy: ssHash)) + router.rebalance(@["n1", "n2"]) + let replicas = router.replicasOf("test_key") + check replicas.len == 1 + + test "Active shard count": + var router = newShardRouter() + check router.activeShardCount == 4 + +suite "Schema Inheritance": + test "Inheritance — merge properties from base": + var s = newSchema() + let base = newType("Base") + base.addProperty("id", "str", required = true) + base.addProperty("created", "datetime") + s.addType("default", base) + + let child = newType("Person") + child.setBases(@["Base"]) + child.addProperty("name", "str", required = true) + s.addType("default", child) + + let resolved = s.resolveInheritance(child) + check resolved.properties.len == 3 # id + created + name + check "id" in resolved.properties + check "name" in resolved.properties + check "created" in resolved.properties + + test "Multi-level inheritance": + var s = newSchema() + let a = newType("A") + a.addProperty("a1", "str") + s.addType("default", a) + + let b = newType("B") + b.setBases(@["A"]) + b.addProperty("b1", "int32") + s.addType("default", b) + + let c = newType("C") + c.setBases(@["B"]) + c.addProperty("c1", "bool") + s.addType("default", c) + + let resolved = s.resolveInheritance(c) + check resolved.properties.len == 3 # a1 + b1 + c1 + + test "Override base property": + var s = newSchema() + let base = newType("Base") + base.addProperty("name", "str") + s.addType("default", base) + + let child = newType("Child") + child.setBases(@["Base"]) + child.addProperty("name", "text") # override + s.addType("default", child) + + let resolved = s.resolveInheritance(child) + check resolved.properties["name"].typeName == "text" + + test "isSubtype": + var s = newSchema() + let a = newType("A") + s.addType("default", a) + let b = newType("B") + b.setBases(@["A"]) + s.addType("default", b) + let c = newType("C") + c.setBases(@["B"]) + s.addType("default", c) + + check s.isSubtype("C", "A") + check s.isSubtype("C", "B") + check s.isSubtype("B", "A") + check not s.isSubtype("A", "C") + + test "Computed property": + let t = newType("Person") + t.addProperty("firstName", "str") + t.addProperty("lastName", "str") + t.addComputedProperty("fullName", "str", "firstName ++ ' ' ++ lastName") + check t.properties["fullName"].computed + check t.properties["fullName"].expr == "firstName ++ ' ' ++ lastName" + +suite "Codegen": + test "Codegen scan": + let plan = IRPlan(kind: irpkScan, scanTable: "users", scanAlias: "u") + let op = codegenPlan(plan) + check op.kind == sokScan + check op.table == "users" + + test "Codegen filter with point read optimization": + let filterExpr = IRExpr(kind: irekBinary, binOp: irEq, + binLeft: IRExpr(kind: irekField, fieldPath: @["id"]), + binRight: IRExpr(kind: irekLiteral, literal: IRLiteral(kind: vkInt64, int64Val: 42))) + let scan = IRPlan(kind: irpkScan, scanTable: "users", scanAlias: "u") + let plan = IRPlan(kind: irpkFilter, filterSource: scan, filterCond: filterExpr) + let op = codegenPlan(plan) + # Should optimize to point read + check op.kind == sokPointRead + + test "Codegen limit": + let scan = IRPlan(kind: irpkScan, scanTable: "t", scanAlias: "t") + let plan = IRPlan(kind: irpkLimit, limitSource: scan, limitCount: 10, limitOffset: 5) + let op = codegenPlan(plan) + check op.limit == 10 + check op.offset == 5 + + test "Cost estimation": + let scan = newStorageOp(sokScan) + check estimateCost(scan) == 1000.0 + let pointRead = newStorageOp(sokPointRead) + check estimateCost(pointRead) == 1.0 + + test "Explain plan": + let scan = newStorageOp(sokScan) + scan.table = "users" + let explanation = scan.explain() + check "sokScan" in explanation + check "users" in explanation + +suite "Replication": + test "Create replication manager": + var rm = newReplicationManager(rmAsync) + check rm.totalReplicaCount == 0 + check rm.connectedReplicaCount == 0 + + test "Add and connect replicas": + var rm = newReplicationManager(rmAsync) + rm.addReplica(newReplica("r1", "10.0.0.1", 9472)) + rm.addReplica(newReplica("r2", "10.0.0.2", 9472)) + check rm.totalReplicaCount == 2 + + rm.connectReplica("r1") + check rm.connectedReplicaCount == 1 + + test "Async replication — write returns immediately": + var rm = newReplicationManager(rmAsync) + rm.addReplica(newReplica("r1", "10.0.0.1", 9472)) + rm.connectReplica("r1") + + let lsn = rm.writeLsn(@[1'u8, 2, 3]) + check lsn == 1 + # Async doesn't wait — already "acked" + check rm.isFullyAcked(lsn) + + test "Sync replication — wait for ack": + var rm = newReplicationManager(rmSync) + rm.addReplica(newReplica("r1", "10.0.0.1", 9472)) + rm.connectReplica("r1") + + let lsn = rm.writeLsn(@[1'u8, 2, 3]) + check not rm.isFullyAcked(lsn) + + rm.ackLsn("r1", lsn) + check rm.isFullyAcked(lsn) + + test "Semi-sync replication": + var rm = newReplicationManager(rmSemiSync, syncCount = 2) + rm.addReplica(newReplica("r1", "10.0.0.1", 9472)) + rm.addReplica(newReplica("r2", "10.0.0.2", 9472)) + rm.addReplica(newReplica("r3", "10.0.0.3", 9472)) + rm.connectReplica("r1") + rm.connectReplica("r2") + rm.connectReplica("r3") + + let lsn = rm.writeLsn(@[1'u8]) + check not rm.isFullyAcked(lsn) # needs 2 acks + + rm.ackLsn("r1", lsn) + check not rm.isFullyAcked(lsn) # still needs 1 more + + rm.ackLsn("r2", lsn) + check rm.isFullyAcked(lsn) # 2 acks received + + test "Replica status": + var rm = newReplicationManager(rmAsync) + rm.addReplica(newReplica("r1", "10.0.0.1", 9472)) + rm.connectReplica("r1") + let status = rm.replicaStatus() + check status.len == 1 + check status[0][1] == rsStreaming + +]# suite "User Defined Functions": test "Register and call UDF": var reg = newUDFRegistry()