refactor(exec): extract FK enforcement into exec/fk.nim
This commit is contained in:
@@ -49,6 +49,7 @@ import exec/eval
|
||||
import exec/lower
|
||||
import exec/scan # internal — not re-exported
|
||||
import exec/dml
|
||||
import exec/fk # internal — not re-exported
|
||||
export types
|
||||
export values
|
||||
export schema
|
||||
@@ -68,96 +69,6 @@ proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row]
|
||||
proc executeQuery*(ctx: ExecutionContext, astNode: Node, params: seq[WireValue] = @[]): ExecResult
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Foreign Key Enforcement
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
proc findReferencingRows(ctx: ExecutionContext, childTable: string, fkCol: string, fkValue: string): seq[Row] =
|
||||
result = @[]
|
||||
for row in execScan(ctx, childTable):
|
||||
if fkCol in row and valueToString(row[fkCol]) == fkValue:
|
||||
result.add(row)
|
||||
|
||||
proc enforceFkOnDelete(ctx: ExecutionContext, parentTable: string, parentCol: string, parentVal: string): (bool, string) =
|
||||
for childTblName, childTbl in ctx.tables:
|
||||
for col in childTbl.columns:
|
||||
if col.fkTable == parentTable and col.fkColumn == parentCol:
|
||||
let action = if col.fkOnDelete.len > 0: col.fkOnDelete else: "RESTRICT"
|
||||
let refs = findReferencingRows(ctx, childTblName, col.name, parentVal)
|
||||
if refs.len > 0:
|
||||
case action
|
||||
of "CASCADE":
|
||||
for refRow in refs:
|
||||
if "$key" in refRow:
|
||||
var dummy: seq[(string, seq[byte])] = @[]
|
||||
discard execDelete(ctx, childTblName, valueToString(refRow["$key"]), dummy)
|
||||
of "SET NULL":
|
||||
for refRow in refs:
|
||||
if "$key" in refRow:
|
||||
var sets = initTable[string, string]()
|
||||
sets[col.name] = "\\N"
|
||||
var dummy: seq[(string, seq[byte])] = @[]
|
||||
discard execUpdateRow(ctx, childTblName, valueToString(refRow["$key"]), sets, dummy)
|
||||
of "RESTRICT", "NO ACTION":
|
||||
return (false, "FOREIGN KEY violation: row is referenced by " & childTblName & "." & col.name)
|
||||
return (true, "")
|
||||
|
||||
proc enforceFkOnUpdate(ctx: ExecutionContext, parentTable: string, parentCol: string, oldVal: string, newVal: string): (bool, string) =
|
||||
for childTblName, childTbl in ctx.tables:
|
||||
for col in childTbl.columns:
|
||||
if col.fkTable == parentTable and col.fkColumn == parentCol:
|
||||
let action = if col.fkOnUpdate.len > 0: col.fkOnUpdate else: "RESTRICT"
|
||||
let refs = findReferencingRows(ctx, childTblName, col.name, oldVal)
|
||||
if refs.len > 0:
|
||||
case action
|
||||
of "CASCADE":
|
||||
for refRow in refs:
|
||||
if "$key" in refRow:
|
||||
var sets = initTable[string, string]()
|
||||
sets[col.name] = newVal
|
||||
var dummy: seq[(string, seq[byte])] = @[]
|
||||
discard execUpdateRow(ctx, childTblName, valueToString(refRow["$key"]), sets, dummy)
|
||||
of "SET NULL":
|
||||
for refRow in refs:
|
||||
if "$key" in refRow:
|
||||
var sets = initTable[string, string]()
|
||||
sets[col.name] = "\\N"
|
||||
var dummy: seq[(string, seq[byte])] = @[]
|
||||
discard execUpdateRow(ctx, childTblName, valueToString(refRow["$key"]), sets, dummy)
|
||||
of "RESTRICT", "NO ACTION":
|
||||
return (false, "FOREIGN KEY violation: row is referenced by " & childTblName & "." & col.name)
|
||||
return (true, "")
|
||||
|
||||
proc enforceFkOnChildUpdate(ctx: ExecutionContext, childTable: string, fkCol: string, newVal: string): (bool, string) =
|
||||
let tbl = ctx.getTableDef(childTable)
|
||||
var parentTable = ""
|
||||
var parentCol = ""
|
||||
for col in tbl.columns:
|
||||
if col.name == fkCol:
|
||||
parentTable = col.fkTable
|
||||
parentCol = col.fkColumn
|
||||
break
|
||||
if parentTable.len == 0 or parentCol.len == 0:
|
||||
return (true, "")
|
||||
if isNull(newVal):
|
||||
return (true, "")
|
||||
let fkKey = parentTable & "." & parentCol & "=" & newVal
|
||||
let (fkExists, _) = ctx.db.get(fkKey)
|
||||
if fkExists:
|
||||
return (true, "")
|
||||
var found = false
|
||||
let prefix = parentTable & "."
|
||||
for entry in ctx.db.scanMemTable():
|
||||
if entry.deleted: continue
|
||||
if entry.key.startsWith(prefix):
|
||||
let rest = entry.key[prefix.len..^1]
|
||||
if rest.startsWith(parentCol & "=") and rest[parentCol.len+1..^1] == newVal:
|
||||
found = true
|
||||
break
|
||||
if not found:
|
||||
return (false, "FOREIGN KEY violation: '" & newVal & "' not found in " & parentTable & "." & parentCol)
|
||||
return (true, "")
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Constraint Validation
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user