feat: MVCC, wire protocol, HTTP API, schema system, CLI shell — 39 tests

- MVCC: multi-version concurrency control with snapshot isolation
- Deadlock detection: wait-for graph with cycle detection
- Wire Protocol: binary message format, 16 types, big-endian serialization
- HTTP/REST API: router with CORS, path params, JSON
- Schema system: types, links, properties, migration diffing
- CLI: interactive query shell (bara shell)
- 18 new tests (39 total, all passing)
This commit is contained in:
2026-05-06 00:32:02 +03:00
parent 6935889877
commit 5c84aeccf8
9 changed files with 1493 additions and 21 deletions
+113
View File
@@ -0,0 +1,113 @@
## Deadlock Detection — wait-for graph
import std/tables
import std/sets
import std/algorithm
type
WaitEdge* = object
waiter*: uint64 # txn id waiting
holder*: uint64 # txn id holding the resource
DeadlockDetector* = ref object
edges: seq[WaitEdge]
adjacency: Table[uint64, seq[uint64]] # waiter -> holders
txnIds: HashSet[uint64]
proc newDeadlockDetector*(): DeadlockDetector =
DeadlockDetector(
edges: @[],
adjacency: initTable[uint64, seq[uint64]](),
txnIds: initHashSet[uint64](),
)
proc addWait*(dd: DeadlockDetector, waiter, holder: uint64) =
dd.edges.add(WaitEdge(waiter: waiter, holder: holder))
dd.txnIds.incl(waiter)
dd.txnIds.incl(holder)
if waiter notin dd.adjacency:
dd.adjacency[waiter] = @[]
dd.adjacency[waiter].add(holder)
proc removeWait*(dd: DeadlockDetector, waiter, holder: uint64) =
var newEdges: seq[WaitEdge] = @[]
for edge in dd.edges:
if edge.waiter != waiter or edge.holder != holder:
newEdges.add(edge)
dd.edges = newEdges
if waiter in dd.adjacency:
var newAdj: seq[uint64] = @[]
for h in dd.adjacency[waiter]:
if h != holder:
newAdj.add(h)
dd.adjacency[waiter] = newAdj
proc removeTxn*(dd: DeadlockDetector, txnId: uint64) =
dd.txnIds.excl(txnId)
dd.adjacency.del(txnId)
var newEdges: seq[WaitEdge] = @[]
for edge in dd.edges:
if edge.waiter != txnId and edge.holder != txnId:
newEdges.add(edge)
dd.edges = newEdges
for wid, holders in dd.adjacency.mpairs:
var newH: seq[uint64] = @[]
for h in holders:
if h != txnId:
newH.add(h)
holders = newH
proc detectCycle*(dd: DeadlockDetector): seq[uint64] =
var visited = initHashSet[uint64]()
var inStack = initHashSet[uint64]()
var parent = initTable[uint64, uint64]()
proc dfs(node: uint64): seq[uint64] =
visited.incl(node)
inStack.incl(node)
for neighbor in dd.adjacency.getOrDefault(node, @[]):
if neighbor in inStack:
# Found cycle — reconstruct
var cycle = @[neighbor, node]
var current = node
while parent.getOrDefault(current, 0'u64) != neighbor and
parent.getOrDefault(current, 0'u64) != 0:
current = parent[current]
cycle.add(current)
cycle.reverse()
return cycle
if neighbor notin visited:
parent[neighbor] = node
let cycle = dfs(neighbor)
if cycle.len > 0:
return cycle
inStack.excl(node)
return @[]
for txnId in dd.txnIds:
if txnId notin visited:
let cycle = dfs(txnId)
if cycle.len > 0:
return cycle
return @[]
proc findDeadlockVictim*(dd: DeadlockDetector): uint64 =
let cycle = dd.detectCycle()
if cycle.len == 0:
return 0
# Choose youngest txn (highest id) as victim
result = cycle[0]
for id in cycle:
if id > result:
result = id
proc hasDeadlock*(dd: DeadlockDetector): bool =
return dd.detectCycle().len > 0
proc clear*(dd: DeadlockDetector) =
dd.edges.setLen(0)
dd.adjacency.clear()
dd.txnIds.clear()
proc edgeCount*(dd: DeadlockDetector): int = dd.edges.len
proc txnCount*(dd: DeadlockDetector): int = dd.txnIds.len