Bug fixes: composite PK, nl_to_sql sandbox, FK check, SQL injection, storage correctness

Critical fixes:
- Composite PK: execInsert + validateConstraints use all PK columns
- nl_to_sql: non-SELECT SQL no longer executed directly during validation
- FK check: removed O(N) scanMemTable fallback, uses db.get() with SSTables
- exprToSql: nkIdent wrapped in quotes to prevent SQL injection
- restoreSchema: try/except around tokenize/parse for crash resilience
- recovery.nim: lastTxnId tracking + putUnsafe/deleteUnsafe without WAL
- SCRAM: verifyClientProof length check + DefaultIterationCount restored

Storage fixes:
- lsm.nim: SSTable sort order fixed (ascending), close() flushes all memtables
- compaction.nim: tombstones preserved during compaction
- wal.nim: header written for empty existing files, readEntries checks magic
- btree.nim: B+ tree leaf split keeps boundary key
- bloom.nim: deserialize raises on short data
- mmap.nim: bounds checks for adviseWillNeed/DontNeed + posix.close()

Protocol fixes:
- zerocopy.nim: readString bounds check
- wire.nim: deserializeValue 32-bit underflow check
- auth.nim: JWT JSON escaping for claims
- server.nim: readUint32BE bounds check + specific exception handling
- raft.nim: readData checks + specific exception handling

Tests:
- Added Composite Primary Key test suite (4 tests)

Build: 0 warnings, 0 errors
This commit is contained in:
2026-05-18 11:33:11 +03:00
parent a28c845476
commit 967c0855a5
25 changed files with 376 additions and 171 deletions
+36 -10
View File
@@ -2,6 +2,7 @@
import std/tables
import std/sets
import std/algorithm
import std/locks
type
WaitEdge* = object
@@ -12,15 +13,18 @@ type
edges: seq[WaitEdge]
adjacency: Table[uint64, seq[uint64]] # waiter -> holders
txnIds: HashSet[uint64]
lock: Lock
proc newDeadlockDetector*(): DeadlockDetector =
DeadlockDetector(
edges: @[],
adjacency: initTable[uint64, seq[uint64]](),
txnIds: initHashSet[uint64](),
)
new(result)
initLock(result.lock)
result.edges = @[]
result.adjacency = initTable[uint64, seq[uint64]]()
result.txnIds = initHashSet[uint64]()
proc addWait*(dd: DeadlockDetector, waiter, holder: uint64) =
acquire(dd.lock)
defer: release(dd.lock)
dd.edges.add(WaitEdge(waiter: waiter, holder: holder))
dd.txnIds.incl(waiter)
dd.txnIds.incl(holder)
@@ -29,6 +33,8 @@ proc addWait*(dd: DeadlockDetector, waiter, holder: uint64) =
dd.adjacency[waiter].add(holder)
proc removeWait*(dd: DeadlockDetector, waiter, holder: uint64) =
acquire(dd.lock)
defer: release(dd.lock)
var newEdges: seq[WaitEdge] = @[]
for edge in dd.edges:
if edge.waiter != waiter or edge.holder != holder:
@@ -43,6 +49,8 @@ proc removeWait*(dd: DeadlockDetector, waiter, holder: uint64) =
dd.adjacency[waiter] = newAdj
proc removeTxn*(dd: DeadlockDetector, txnId: uint64) =
acquire(dd.lock)
defer: release(dd.lock)
dd.txnIds.excl(txnId)
dd.adjacency.del(txnId)
var newEdges: seq[WaitEdge] = @[]
@@ -57,7 +65,7 @@ proc removeTxn*(dd: DeadlockDetector, txnId: uint64) =
newH.add(h)
holders = newH
proc detectCycle*(dd: DeadlockDetector): seq[uint64] =
proc detectCycleUnsafe(dd: DeadlockDetector): seq[uint64] =
var visited = initHashSet[uint64]()
var inStack = initHashSet[uint64]()
var parent = initTable[uint64, uint64]()
@@ -98,8 +106,15 @@ proc detectCycle*(dd: DeadlockDetector): seq[uint64] =
return cycle
return @[]
proc detectCycle*(dd: DeadlockDetector): seq[uint64] =
acquire(dd.lock)
defer: release(dd.lock)
detectCycleUnsafe(dd)
proc findDeadlockVictim*(dd: DeadlockDetector): uint64 =
let cycle = dd.detectCycle()
acquire(dd.lock)
defer: release(dd.lock)
let cycle = detectCycleUnsafe(dd)
if cycle.len == 0:
return 0
# Choose youngest txn (highest id) as victim
@@ -109,12 +124,23 @@ proc findDeadlockVictim*(dd: DeadlockDetector): uint64 =
result = id
proc hasDeadlock*(dd: DeadlockDetector): bool =
return dd.detectCycle().len > 0
acquire(dd.lock)
defer: release(dd.lock)
return detectCycleUnsafe(dd).len > 0
proc clear*(dd: DeadlockDetector) =
acquire(dd.lock)
defer: release(dd.lock)
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
proc edgeCount*(dd: DeadlockDetector): int =
acquire(dd.lock)
defer: release(dd.lock)
dd.edges.len
proc txnCount*(dd: DeadlockDetector): int =
acquire(dd.lock)
defer: release(dd.lock)
dd.txnIds.len