feat: full BaraQL (GROUP BY/JOIN/CTE/aggregates), Raft consensus, sharding — 107 tests

BaraQL parser extended:
- GROUP BY + HAVING clauses
- JOIN (INNER, LEFT, RIGHT, FULL, CROSS) with ON conditions
- CTE (WITH ... AS) with multiple CTEs
- Aggregate functions (count, sum, avg, min, max)
- CASE/WHEN/THEN/ELSE/END expressions
- BETWEEN, IN, LIKE/ILIKE, IS NULL/IS NOT NULL
- Dotted path identifiers (table.column)
- Subqueries in FROM clause
- CREATE TYPE with colon syntax (name: type)
- UPDATE SET ... WHERE, DELETE FROM ... WHERE

Infrastructure:
- Raft consensus: leader election, log replication, RequestVote/AppendEntries
- Sharding: hash-based, range-based, consistent hashing, rebalancing
- Fixed lexer: = is equality, := is assignment, added : (colon) token
- 34 new parser tests (107 total, all passing)
This commit is contained in:
2026-05-06 01:15:33 +03:00
parent 67213826a8
commit 3162271328
7 changed files with 875 additions and 27 deletions
+172
View File
@@ -7,6 +7,8 @@ import barabadb/core/types
import barabadb/core/mvcc
import barabadb/core/deadlock
import barabadb/core/columnar
import barabadb/core/raft
import barabadb/core/sharding
import barabadb/storage/bloom
import barabadb/storage/wal
import barabadb/storage/lsm
@@ -782,3 +784,173 @@ suite "Vector Metadata Filtering":
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"
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
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