fix: deadlock detector, graph/query/lexer бъгове
Поправени проблеми: - Deadlock detector: cycle reconstruction вече проверява пълен цикъл - Graph cypher: executeCypher acquire-ва lock при четене - Graph engine: loadFromFile acquire-ва lock при зареждане - Graph engine: lock поле е exported за външен достъп - Query: parseRowData поддържа escape-ване на , и = в стойности - Query: execUpdateRow escape-ва стойности при сериализация - Lexer: readNumber спира при втора точка (1.2.3 вече не е валиден float) - Lexer: skipBlockComment хвърля ValueError при незатворен коментар 292 теста, 0 failure-а.
This commit is contained in:
@@ -73,7 +73,14 @@ proc detectCycle*(dd: DeadlockDetector): seq[uint64] =
|
|||||||
while parent.getOrDefault(current, 0'u64) != neighbor and
|
while parent.getOrDefault(current, 0'u64) != neighbor and
|
||||||
parent.getOrDefault(current, 0'u64) != 0:
|
parent.getOrDefault(current, 0'u64) != 0:
|
||||||
current = parent[current]
|
current = parent[current]
|
||||||
|
if current == 0: break
|
||||||
cycle.add(current)
|
cycle.add(current)
|
||||||
|
# Verify we actually closed the cycle back to neighbor
|
||||||
|
if cycle[^1] != neighbor and parent.getOrDefault(cycle[^1], 0'u64) == neighbor:
|
||||||
|
cycle.add(neighbor)
|
||||||
|
elif cycle[^1] != neighbor:
|
||||||
|
# Incomplete cycle — should not happen with valid parent chain
|
||||||
|
return @[]
|
||||||
cycle.reverse()
|
cycle.reverse()
|
||||||
return cycle
|
return cycle
|
||||||
if neighbor notin visited:
|
if neighbor notin visited:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## Cypher-like Graph Query Extension for BaraQL
|
## Cypher-like Graph Query Extension for BaraQL
|
||||||
import std/tables
|
import std/tables
|
||||||
import std/strutils
|
import std/strutils
|
||||||
|
import std/locks
|
||||||
import engine
|
import engine
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -179,6 +180,9 @@ proc executeCypher*(g: Graph, query: CypherQuery): CypherResult =
|
|||||||
if query.pattern.nodes.len == 0:
|
if query.pattern.nodes.len == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
acquire(g.lock)
|
||||||
|
defer: release(g.lock)
|
||||||
|
|
||||||
# Basic MATCH execution
|
# Basic MATCH execution
|
||||||
if query.kind == "MATCH":
|
if query.kind == "MATCH":
|
||||||
# For each node matching the pattern, collect results
|
# For each node matching the pattern, collect results
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ type
|
|||||||
reverseAdj*: Table[NodeId, seq[AdjacencyEntry]] # incoming
|
reverseAdj*: Table[NodeId, seq[AdjacencyEntry]] # incoming
|
||||||
nextNodeId: uint64
|
nextNodeId: uint64
|
||||||
nextEdgeId: uint64
|
nextEdgeId: uint64
|
||||||
lock: Lock
|
lock*: Lock
|
||||||
|
|
||||||
proc `==`*(a, b: EdgeId): bool = uint64(a) == uint64(b)
|
proc `==`*(a, b: EdgeId): bool = uint64(a) == uint64(b)
|
||||||
proc `==`*(a, b: NodeId): bool = uint64(a) == uint64(b)
|
proc `==`*(a, b: NodeId): bool = uint64(a) == uint64(b)
|
||||||
@@ -352,6 +352,7 @@ proc loadFromFile*(path: string): Graph =
|
|||||||
lock: Lock(),
|
lock: Lock(),
|
||||||
)
|
)
|
||||||
initLock(result.lock)
|
initLock(result.lock)
|
||||||
|
acquire(result.lock)
|
||||||
|
|
||||||
for i in 0 ..< nodeCount:
|
for i in 0 ..< nodeCount:
|
||||||
let id = NodeId(s.readUint64())
|
let id = NodeId(s.readUint64())
|
||||||
@@ -385,4 +386,5 @@ proc loadFromFile*(path: string): Graph =
|
|||||||
result.reverseAdj[dst].add(AdjacencyEntry(edgeId: id, neighbor: src,
|
result.reverseAdj[dst].add(AdjacencyEntry(edgeId: id, neighbor: src,
|
||||||
weight: weight, label: label))
|
weight: weight, label: label))
|
||||||
|
|
||||||
|
release(result.lock)
|
||||||
s.close()
|
s.close()
|
||||||
|
|||||||
@@ -406,14 +406,49 @@ proc getValue(values: seq[string], fields: seq[string], colName: string): string
|
|||||||
proc isNull*(value: string): bool =
|
proc isNull*(value: string): bool =
|
||||||
value.len == 0 or value.toLower() == "null"
|
value.len == 0 or value.toLower() == "null"
|
||||||
|
|
||||||
|
proc escapeRowVal(v: string): string =
|
||||||
|
v.replace("\\", "\\\\").replace(",", "\\,").replace("=", "\\=")
|
||||||
|
|
||||||
|
proc unescapeRowVal(v: string): string =
|
||||||
|
result = ""
|
||||||
|
var i = 0
|
||||||
|
while i < v.len:
|
||||||
|
if v[i] == '\\' and i + 1 < v.len:
|
||||||
|
case v[i+1]
|
||||||
|
of '\\', ',', '=':
|
||||||
|
result &= v[i+1]
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
else: discard
|
||||||
|
result &= v[i]
|
||||||
|
inc i
|
||||||
|
|
||||||
proc parseRowData(valStr: string): Table[string, string] =
|
proc parseRowData(valStr: string): Table[string, string] =
|
||||||
## Parse "col1=val1,col2=val2" into a table
|
## Parse "col1=val1,col2=val2" into a table
|
||||||
result = initTable[string, string]()
|
result = initTable[string, string]()
|
||||||
for part in valStr.split(","):
|
var i = 0
|
||||||
|
var part = ""
|
||||||
|
while i < valStr.len:
|
||||||
|
if valStr[i] == '\\' and i + 1 < valStr.len:
|
||||||
|
part &= valStr[i]
|
||||||
|
part &= valStr[i+1]
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if valStr[i] == ',':
|
||||||
|
let eqPos = part.find('=')
|
||||||
|
if eqPos >= 0:
|
||||||
|
let k = part[0..<eqPos].strip()
|
||||||
|
let v = unescapeRowVal(part[eqPos+1..^1].strip())
|
||||||
|
result[k] = v
|
||||||
|
part = ""
|
||||||
|
else:
|
||||||
|
part &= valStr[i]
|
||||||
|
inc i
|
||||||
|
if part.len > 0:
|
||||||
let eqPos = part.find('=')
|
let eqPos = part.find('=')
|
||||||
if eqPos >= 0:
|
if eqPos >= 0:
|
||||||
let k = part[0..<eqPos].strip()
|
let k = part[0..<eqPos].strip()
|
||||||
let v = part[eqPos+1..^1].strip()
|
let v = unescapeRowVal(part[eqPos+1..^1].strip())
|
||||||
result[k] = v
|
result[k] = v
|
||||||
|
|
||||||
proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row]
|
proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row]
|
||||||
@@ -844,7 +879,7 @@ proc execUpdateRow*(ctx: ExecutionContext, table: string, key: string, sets: Tab
|
|||||||
return 0
|
return 0
|
||||||
var parts: seq[string] = @[]
|
var parts: seq[string] = @[]
|
||||||
for col, val in parsed:
|
for col, val in parsed:
|
||||||
parts.add(col & "=" & val)
|
parts.add(col & "=" & escapeRowVal(val))
|
||||||
let newVal = parts.join(",")
|
let newVal = parts.join(",")
|
||||||
# Update indexes: remove old, insert new
|
# Update indexes: remove old, insert new
|
||||||
for colName in ctx.btrees.keys.toSeq():
|
for colName in ctx.btrees.keys.toSeq():
|
||||||
|
|||||||
@@ -363,6 +363,7 @@ proc skipBlockComment(l: var Lexer) =
|
|||||||
discard l.advance()
|
discard l.advance()
|
||||||
return
|
return
|
||||||
discard l.advance()
|
discard l.advance()
|
||||||
|
raise newException(ValueError, "Unclosed block comment at line " & $l.line & ", col " & $l.col)
|
||||||
|
|
||||||
proc readString(l: var Lexer, quote: char): string =
|
proc readString(l: var Lexer, quote: char): string =
|
||||||
result = ""
|
result = ""
|
||||||
@@ -389,6 +390,8 @@ proc readNumber(l: var Lexer, startLine, startCol: int): Token =
|
|||||||
var isFloat = false
|
var isFloat = false
|
||||||
while l.pos < l.input.len and (l.input[l.pos] in Digits or l.input[l.pos] == '.'):
|
while l.pos < l.input.len and (l.input[l.pos] in Digits or l.input[l.pos] == '.'):
|
||||||
if l.input[l.pos] == '.':
|
if l.input[l.pos] == '.':
|
||||||
|
if isFloat:
|
||||||
|
break # second dot ends the number
|
||||||
isFloat = true
|
isFloat = true
|
||||||
numStr.add(l.input[l.pos])
|
numStr.add(l.input[l.pos])
|
||||||
discard l.advance()
|
discard l.advance()
|
||||||
|
|||||||
Reference in New Issue
Block a user