fix(raft): multi-stmt write gate, rich apply, delete kv convention
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled

- Reject follower writes if any statement in the batch is DML/COMMIT
  (not only stmts[0]).
- COMMIT always emits empty-valued kvPairs for isDelete entries.
- applyCommand updates LSM plus secondary B-tree/FTS/HNSW indexes
  (applyReplicatedPut/Delete) so follower index scans see replicated rows.
- Tests: not-leader append, commit timeout, index apply unit, E2E
  index-backed SELECT on follower.
This commit is contained in:
2026-07-30 21:14:46 +03:00
parent a462d21b25
commit 0d51497f57
7 changed files with 193 additions and 9 deletions
+8 -2
View File
@@ -253,8 +253,14 @@ proc executeQuery(db: LSMTree, ctx: ExecutionContext, query: string, params: seq
return (true, QueryResult(), "")
# C3b: writes go through the Raft log — only the leader may accept them.
if raftNode != nil and isWrite(astNode.stmts[0]):
if raftNode.state != rsLeader:
# Inspect every statement so "SELECT 1; INSERT ..." cannot bypass the gate.
if raftNode != nil:
var hasWrite = false
for stmt in astNode.stmts:
if isWrite(stmt):
hasWrite = true
break
if hasWrite and raftNode.state != rsLeader:
let who = if raftNode.leaderId.len > 0: raftNode.leaderId else: "none elected"
return (false, QueryResult(), "not leader; leader is '" & who & "'")