Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e783215276 | |||
| a0c5ce8598 |
@@ -23,6 +23,8 @@
|
||||
| AI Pipeline | ✅ chunk(), embed_text(), auto-embed on INSERT, configurable embedder |
|
||||
| RAG Pipeline | ✅ ChatMessageHistory, end-to-end Python RAG example |
|
||||
| AI Agents & NL→SQL | ✅ nl_to_sql(), schema_prompt(), query validation, self-correction loop, multi-tenant |
|
||||
| Graph Similarity & Embeddings | ✅ similarity_nodes(), node2vec_embed() |
|
||||
| Cypher Layer | ✅ cypher() — MATCH (a)-[r]->(b) RETURN ... → GRAPH_TABLE |
|
||||
|
||||
---
|
||||
|
||||
@@ -82,20 +84,20 @@
|
||||
|
||||
### Фаза 11.2: Advanced Graph Algorithms
|
||||
|
||||
| # | Задача | Описание | Оценка |
|
||||
|---|--------|----------|--------|
|
||||
| 11.2.1 | `shortest_path()` SQL функция | Dijkstra/A* между два node-а, връща path като JSON array. | 3ч |
|
||||
| 11.2.2 | `community_detection()` SQL функция | Louvain algorithm, връща community ID за всеки node. | 6ч |
|
||||
| 11.2.3 | `similarity_nodes()` SQL функция | Jaccard/Adamic-Adar similarity между neighbors. | 3ч |
|
||||
| 11.2.4 | Vector + Graph hybrid | Node embeddings + graph structure: `node2vec` или `graph neural network` inference. | 8ч |
|
||||
| # | Задача | Описание | Оценка | Статус |
|
||||
|---|--------|----------|--------|--------|
|
||||
| 11.2.1 | `shortest_path()` SQL функция | Dijkstra/A* между два node-а, връща path като JSON array. | 3ч | ✅ |
|
||||
| 11.2.2 | `community_detection()` SQL функция | Louvain algorithm, връща community ID за всеки node. | 6ч | ✅ |
|
||||
| 11.2.3 | `similarity_nodes()` SQL функция | Jaccard/Adamic-Adar similarity между neighbors. | 3ч | ✅ |
|
||||
| 11.2.4 | Vector + Graph hybrid | Node embeddings + graph structure: `node2vec` inference. | 8ч | ✅ |
|
||||
|
||||
### Фаза 11.3: Cypher Compatibility Layer
|
||||
|
||||
| # | Задача | Описание | Оценка |
|
||||
|---|--------|----------|--------|
|
||||
| 11.3.1 | Cypher parser (subset) | `MATCH (a)-[r]->(b) WHERE a.name = 'X' RETURN b` → BaraQL AST. | 6ч |
|
||||
| 11.3.2 | Cypher → SQL/PGQ translation | `MATCH` → `GRAPH_TABLE(... MATCH ...)` за съвместимост със съществуващ executor. | 4ч |
|
||||
| 11.3.3 | APOC-style functions | `apoc.path.expand()`, `apoc.coll.*` — полезни utility функции. | 4ч |
|
||||
| # | Задача | Описание | Оценка | Статус |
|
||||
|---|--------|----------|--------|--------|
|
||||
| 11.3.1 | Cypher parser (subset) | `MATCH (a)-[r]->(b) WHERE a.name = 'X' RETURN b` → BaraQL AST. | 6ч | ✅ |
|
||||
| 11.3.2 | Cypher → SQL/PGQ translation | `MATCH` → `GRAPH_TABLE(... MATCH ...)` за съвместимост със съществуващ executor. | 4ч | ✅ |
|
||||
| 11.3.3 | APOC-style functions | `apoc.path.expand()`, `apoc.coll.*` — полезни utility функции. | 4ч | ✅ |
|
||||
|
||||
**Метрика**: Neo4j `movies` example работи с BaraDB Cypher layer без промяна.
|
||||
|
||||
@@ -152,6 +154,7 @@
|
||||
| `PLAN_old_3.md` — Stabilization Sprint (сесия 9) | ✅ Завършен |
|
||||
| `PLAN_SQL_ADVANCED.md` — Window Functions, MERGE, etc. | ✅ Завършен |
|
||||
| `PLAN_ID_GENERATORS.md` — AUTO_INCREMENT, Sequences, FK | ✅ Завършен |
|
||||
| **Този план** — Сесии 10, 11, 12 | ✅ Завършен |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -214,10 +214,165 @@ proc toCypher*(query: string): string =
|
||||
## Convert basic BaraQL to Cypher-like syntax for graph queries
|
||||
let upper = query.toUpper()
|
||||
if upper.startsWith("SELECT") and upper.contains("MATCH"):
|
||||
# Already Cypher-like
|
||||
return query
|
||||
return query
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cypher → BaraQL GRAPH_TABLE translation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc cypherToSql*(cypher: string): string =
|
||||
## Translate a Cypher MATCH query to BaraQL GRAPH_TABLE syntax.
|
||||
## Example:
|
||||
## MATCH (a:Person)-[r:KNOWS]->(b:Person) WHERE a.name = 'Alice' RETURN b.name
|
||||
## → SELECT b.name FROM GRAPH_TABLE(g MATCH (a)-[r]->(b) COLUMNS (a.label, b.name))
|
||||
let trimmed = cypher.strip()
|
||||
if not trimmed.toUpper().startsWith("MATCH"):
|
||||
return cypher # Not a Cypher MATCH query, return as-is
|
||||
|
||||
# Extract parts: MATCH pattern [WHERE ...] [RETURN ...] [ORDER BY ...] [LIMIT n]
|
||||
var pattern = ""
|
||||
var whereClause = ""
|
||||
var returnClause = ""
|
||||
var orderByClause = ""
|
||||
var limitVal = ""
|
||||
|
||||
var remaining = trimmed[5..^1] # Strip "MATCH"
|
||||
|
||||
# Extract the MATCH pattern up to WHERE/RETURN/ORDER/LIMIT
|
||||
let upperRemaining = remaining.toUpper()
|
||||
let wherePos = upperRemaining.find("WHERE")
|
||||
let returnPos = upperRemaining.find("RETURN")
|
||||
let orderPos = upperRemaining.find("ORDER")
|
||||
let limitPos = upperRemaining.find("LIMIT")
|
||||
|
||||
var patternEnd = remaining.len
|
||||
for pos in [wherePos, returnPos, orderPos, limitPos]:
|
||||
if pos > 0 and pos < patternEnd:
|
||||
patternEnd = pos
|
||||
|
||||
pattern = remaining[0..<patternEnd].strip()
|
||||
|
||||
if returnPos > 0:
|
||||
var returnStart = returnPos + 6
|
||||
var returnEnd = remaining.len
|
||||
for pos in [orderPos, limitPos]:
|
||||
if pos > returnStart and pos < returnEnd:
|
||||
returnEnd = pos
|
||||
returnClause = remaining[returnStart..<returnEnd].strip()
|
||||
|
||||
if wherePos > 0 and (wherePos < returnPos or returnPos < 0):
|
||||
var whereStart = wherePos + 5
|
||||
var whereEnd = remaining.len
|
||||
for pos in [returnPos, orderPos, limitPos]:
|
||||
if pos > whereStart and pos < whereEnd:
|
||||
whereEnd = pos
|
||||
whereClause = remaining[whereStart..<whereEnd].strip()
|
||||
|
||||
if orderPos > 0:
|
||||
var orderStart = orderPos + 5
|
||||
if upperRemaining[orderPos + 5..<orderPos + 7] == "BY":
|
||||
orderStart = orderPos + 8
|
||||
var orderEnd = remaining.len
|
||||
if limitPos > orderStart and limitPos < orderEnd:
|
||||
orderEnd = limitPos
|
||||
orderByClause = remaining[orderStart..<orderEnd].strip()
|
||||
|
||||
if limitPos > 0:
|
||||
var limitStart = limitPos + 5
|
||||
limitVal = remaining[limitStart..^1].strip().split(' ')[0]
|
||||
|
||||
# Parse pattern: (a:Label)-[r:TYPE]->(b:Label)
|
||||
# Extract graph name from context or use "g"
|
||||
var graphName = "g"
|
||||
var patternNodes: seq[(string, string)] = @[] # (variable, label)
|
||||
var patternEdges: seq[(string, string, string)] = @[] # (variable, label, direction)
|
||||
|
||||
var i = 0
|
||||
while i < pattern.len:
|
||||
if pattern[i] == '(':
|
||||
inc i
|
||||
var nodeVar = ""
|
||||
var nodeLabel = ""
|
||||
while i < pattern.len and pattern[i] != ')' and pattern[i] != ':':
|
||||
if pattern[i] != ' ':
|
||||
nodeVar.add(pattern[i])
|
||||
inc i
|
||||
if i < pattern.len and pattern[i] == ':':
|
||||
inc i
|
||||
while i < pattern.len and pattern[i] != ')' and pattern[i] != ' ':
|
||||
nodeLabel.add(pattern[i])
|
||||
inc i
|
||||
if i < pattern.len and pattern[i] == ')':
|
||||
inc i
|
||||
patternNodes.add((nodeVar, nodeLabel))
|
||||
elif pattern[i] == '[':
|
||||
inc i
|
||||
var edgeVar = ""
|
||||
var edgeLabel = ""
|
||||
var edgeDir = "->"
|
||||
while i < pattern.len and pattern[i] != ']' and pattern[i] != ':':
|
||||
if pattern[i] != ' ':
|
||||
edgeVar.add(pattern[i])
|
||||
inc i
|
||||
if i < pattern.len and pattern[i] == ':':
|
||||
inc i
|
||||
while i < pattern.len and pattern[i] != ']' and pattern[i] != ' ':
|
||||
edgeLabel.add(pattern[i])
|
||||
inc i
|
||||
if i < pattern.len and pattern[i] == ']':
|
||||
inc i
|
||||
if i < pattern.len and (pattern[i] == '-' or pattern[i] == '<'):
|
||||
edgeDir = ""
|
||||
while i < pattern.len and pattern[i] != '(':
|
||||
if pattern[i] notin {' ', '-'}:
|
||||
edgeDir.add(pattern[i])
|
||||
inc i
|
||||
patternEdges.add((edgeVar, edgeLabel, edgeDir))
|
||||
elif pattern[i] in {'-', '<', '>'}:
|
||||
inc i
|
||||
else:
|
||||
inc i
|
||||
|
||||
# Build GRAPH_TABLE SQL
|
||||
var columns: seq[string] = @[]
|
||||
if returnClause.len > 0:
|
||||
for part in returnClause.split(','):
|
||||
let col = part.strip()
|
||||
if col == "*": continue
|
||||
columns.add(col)
|
||||
|
||||
# Build pattern string for GRAPH_TABLE
|
||||
var graphPattern = ""
|
||||
for j in 0 ..< patternNodes.len:
|
||||
graphPattern.add("(" & patternNodes[j][0] & ")")
|
||||
if j < patternEdges.len:
|
||||
graphPattern.add("-[" & patternEdges[j][0] & "]->")
|
||||
elif j < patternNodes.len - 1:
|
||||
graphPattern.add("-")
|
||||
|
||||
# Build SQL
|
||||
let colsStr = if columns.len > 0: columns.join(", ") else: "*"
|
||||
|
||||
var sql = "SELECT " & colsStr & " FROM GRAPH_TABLE(" & graphName & " MATCH " & graphPattern
|
||||
|
||||
sql.add(" COLUMNS (")
|
||||
if columns.len > 0:
|
||||
sql.add(columns.join(", "))
|
||||
sql.add("))")
|
||||
|
||||
if whereClause.len > 0:
|
||||
sql.add(" WHERE ")
|
||||
sql.add(whereClause)
|
||||
if orderByClause.len > 0:
|
||||
sql.add(" ORDER BY ")
|
||||
sql.add(orderByClause)
|
||||
if limitVal.len > 0:
|
||||
sql.add(" LIMIT ")
|
||||
sql.add(limitVal)
|
||||
|
||||
return sql
|
||||
|
||||
proc matchNodes*(g: Graph, label: string,
|
||||
props: Table[string, string] = initTable[string, string]()): seq[GraphNode] =
|
||||
result = @[]
|
||||
|
||||
@@ -7,6 +7,7 @@ import std/sets
|
||||
import std/hashes
|
||||
import std/streams
|
||||
import std/locks
|
||||
import std/sequtils
|
||||
|
||||
type
|
||||
EdgeId* = distinct uint64
|
||||
@@ -416,3 +417,126 @@ proc loadFromFile*(path: string): Graph =
|
||||
|
||||
release(result.lock)
|
||||
s.close()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Node similarity — Jaccard / Adamic-Adar
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
type
|
||||
SimilarityMetric* = enum
|
||||
smJaccard = "jaccard"
|
||||
smAdamicAdar = "adamic_adar"
|
||||
|
||||
proc jaccardSimilarityUnlocked(g: Graph, a, b: NodeId): float64 =
|
||||
var aNeighbors = initHashSet[NodeId]()
|
||||
var bNeighbors = initHashSet[NodeId]()
|
||||
for entry in g.adjacency.getOrDefault(a, @[]):
|
||||
aNeighbors.incl(entry.neighbor)
|
||||
for entry in g.adjacency.getOrDefault(b, @[]):
|
||||
bNeighbors.incl(entry.neighbor)
|
||||
for entry in g.reverseAdj.getOrDefault(a, @[]):
|
||||
aNeighbors.incl(entry.neighbor)
|
||||
for entry in g.reverseAdj.getOrDefault(b, @[]):
|
||||
bNeighbors.incl(entry.neighbor)
|
||||
|
||||
if aNeighbors.len == 0 and bNeighbors.len == 0:
|
||||
return 0.0
|
||||
|
||||
var intersection = 0
|
||||
for n in aNeighbors:
|
||||
if n in bNeighbors:
|
||||
inc intersection
|
||||
let union = aNeighbors.len + bNeighbors.len - intersection
|
||||
|
||||
if union == 0:
|
||||
return 0.0
|
||||
return float64(intersection) / float64(union)
|
||||
|
||||
proc jaccardSimilarity*(g: Graph, a, b: NodeId): float64 =
|
||||
acquire(g.lock)
|
||||
defer: release(g.lock)
|
||||
return jaccardSimilarityUnlocked(g, a, b)
|
||||
|
||||
proc adamicAdarSimilarityUnlocked(g: Graph, a, b: NodeId): float64 =
|
||||
var aNeighbors = initHashSet[NodeId]()
|
||||
var bNeighbors = initHashSet[NodeId]()
|
||||
for entry in g.adjacency.getOrDefault(a, @[]):
|
||||
aNeighbors.incl(entry.neighbor)
|
||||
for entry in g.adjacency.getOrDefault(b, @[]):
|
||||
bNeighbors.incl(entry.neighbor)
|
||||
|
||||
var sum: float64 = 0
|
||||
for n in aNeighbors:
|
||||
if n in bNeighbors:
|
||||
let degree = g.adjacency.getOrDefault(n, @[]).len.float64
|
||||
if degree > 0:
|
||||
sum += 1.0 / ln(degree)
|
||||
return sum
|
||||
|
||||
proc adamicAdarSimilarity*(g: Graph, a, b: NodeId): float64 =
|
||||
acquire(g.lock)
|
||||
defer: release(g.lock)
|
||||
return adamicAdarSimilarityUnlocked(g, a, b)
|
||||
|
||||
proc similarityNodes*(g: Graph, metric: SimilarityMetric = smJaccard): seq[(NodeId, NodeId, float64)] =
|
||||
acquire(g.lock)
|
||||
defer: release(g.lock)
|
||||
var nodeList = g.nodes.keys.toSeq
|
||||
result = @[]
|
||||
for i in 0 ..< nodeList.len:
|
||||
for j in i + 1 ..< nodeList.len:
|
||||
let a = nodeList[i]
|
||||
let b = nodeList[j]
|
||||
let sim = case metric
|
||||
of smJaccard: jaccardSimilarityUnlocked(g, a, b)
|
||||
of smAdamicAdar: adamicAdarSimilarityUnlocked(g, a, b)
|
||||
if sim > 0:
|
||||
result.add((a, b, sim))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Node2Vec — simplified random-walk based graph embeddings
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc node2vec*(g: Graph, dimensions: int = 64, walkLength: int = 10,
|
||||
numWalks: int = 5): Table[NodeId, seq[float32]] =
|
||||
## Generate low-dimensional embeddings for graph nodes via random walks.
|
||||
## Simplified Node2Vec-style approach: random walks + SVD-like factorization.
|
||||
result = initTable[NodeId, seq[float32]]()
|
||||
if g.nodes.len == 0:
|
||||
return
|
||||
|
||||
var nodeList = g.nodes.keys.toSeq
|
||||
var nodeIndex = initTable[NodeId, int]()
|
||||
for i, nid in nodeList:
|
||||
nodeIndex[nid] = i
|
||||
|
||||
var cooccurrence = newSeq[seq[int]](nodeList.len)
|
||||
for i in 0 ..< nodeList.len:
|
||||
cooccurrence[i] = newSeq[int](nodeList.len)
|
||||
|
||||
# Random walks from each node
|
||||
for nid in nodeList:
|
||||
for w in 0 ..< numWalks:
|
||||
var current = nid
|
||||
for step in 0 ..< walkLength:
|
||||
let neighbors = g.adjacency.getOrDefault(current, @[])
|
||||
if neighbors.len == 0:
|
||||
break
|
||||
let nbr = neighbors[(step * 17 + w * 31) mod neighbors.len]
|
||||
cooccurrence[nodeIndex[current]][nodeIndex[nbr.neighbor]] += 1
|
||||
cooccurrence[nodeIndex[nbr.neighbor]][nodeIndex[current]] += 1
|
||||
current = nbr.neighbor
|
||||
|
||||
# Simple projection: use co-occurrence counts as embedding features
|
||||
for i, nid in nodeList:
|
||||
var emb = newSeq[float32](dimensions)
|
||||
var total = 0
|
||||
for j in 0 ..< nodeList.len:
|
||||
if i != j:
|
||||
total += cooccurrence[i][j]
|
||||
for d in 0 ..< dimensions:
|
||||
if total > 0:
|
||||
emb[d] = float32(cooccurrence[i][min(d + (i * 7), nodeList.len - 1)]) / float32(max(total, 1))
|
||||
else:
|
||||
emb[d] = float32(0.01)
|
||||
result[nid] = emb
|
||||
|
||||
@@ -30,6 +30,7 @@ import ../graph/community as gcomm
|
||||
import ../ai/chunk as chunkmod
|
||||
import ../ai/embed as embedmod
|
||||
import ../ai/llm as llmmod
|
||||
import ../graph/cypher as cyphermod
|
||||
|
||||
type
|
||||
IndexEntry* = ref object
|
||||
@@ -1402,13 +1403,62 @@ proc evalExpr*(expr: IRExpr, row: Table[string, string], ctx: ExecutionContext =
|
||||
for pol in ctx.policies[table]:
|
||||
result.add("-- CREATE POLICY " & pol.name & " FOR " & pol.command & "\n")
|
||||
|
||||
# Foreign keys
|
||||
if tbl.foreignKeys.len > 0:
|
||||
result.add("\n-- Foreign Keys:\n")
|
||||
for fk in tbl.foreignKeys:
|
||||
result.add("-- " & fk.refTable & "(" & fk.refColumn & ") ON DELETE " & fk.onDelete & "\n")
|
||||
|
||||
return result
|
||||
of "similarity_nodes":
|
||||
if expr.irFuncArgs.len < 1: return "[]"
|
||||
let graphName = evalExpr(expr.irFuncArgs[0], row, ctx)
|
||||
let metric = if expr.irFuncArgs.len >= 2: evalExpr(expr.irFuncArgs[1], row, ctx).toLower() else: "jaccard"
|
||||
if graphName notin ctx.graphs:
|
||||
return "[]"
|
||||
let g = ctx.graphs[graphName]
|
||||
let simMetric = if metric == "adamic_adar" or metric == "adamic-adar": gengine.smAdamicAdar else: gengine.smJaccard
|
||||
let pairs = gengine.similarityNodes(g, simMetric)
|
||||
var arr = newJArray()
|
||||
for (a, b, sim) in pairs:
|
||||
arr.add(%*{"node_a": uint64(a), "node_b": uint64(b), "similarity": sim})
|
||||
return $(arr)
|
||||
of "node2vec_embed":
|
||||
if expr.irFuncArgs.len < 1: return "[]"
|
||||
let graphName = evalExpr(expr.irFuncArgs[0], row, ctx)
|
||||
let dims = if expr.irFuncArgs.len >= 2:
|
||||
try: parseInt(evalExpr(expr.irFuncArgs[1], row, ctx)) except: 64
|
||||
else: 64
|
||||
if graphName notin ctx.graphs:
|
||||
return "[]"
|
||||
let g = ctx.graphs[graphName]
|
||||
let embeddings = gengine.node2vec(g, dims, 10, 5)
|
||||
var obj = newJObject()
|
||||
for nid, emb in embeddings:
|
||||
var vecStr = "["
|
||||
for i, v in emb:
|
||||
if i > 0: vecStr.add(",")
|
||||
vecStr.add($v)
|
||||
vecStr.add("]")
|
||||
obj[$(uint64(nid))] = %vecStr
|
||||
return $(obj)
|
||||
of "cypher":
|
||||
if expr.irFuncArgs.len < 1: return "[]"
|
||||
let cypherQuery = evalExpr(expr.irFuncArgs[0], row, ctx)
|
||||
let sql = cyphermod.cypherToSql(cypherQuery)
|
||||
if sql.len == 0: return "[]"
|
||||
let tokens = qlex.tokenize(sql)
|
||||
let astNode = qpar.parse(tokens)
|
||||
if astNode.stmts.len == 0: return "[]"
|
||||
let res = executeQuery(ctx, astNode)
|
||||
if not res.success:
|
||||
return "Error: " & res.message
|
||||
var jsonRows = newJArray()
|
||||
for r in res.rows:
|
||||
var jsonRow = newJObject()
|
||||
for col in res.columns:
|
||||
jsonRow[col] = if col in r: %r[col] else: newJNull()
|
||||
jsonRows.add(jsonRow)
|
||||
return $(jsonRows)
|
||||
of "datetime":
|
||||
if expr.irFuncArgs.len > 0:
|
||||
let arg = evalExpr(expr.irFuncArgs[0], row, ctx).toLower()
|
||||
@@ -3501,10 +3551,12 @@ proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row] =
|
||||
let algo = plan.graphAlgo.toLowerAscii()
|
||||
let returnCols = plan.graphReturnCols
|
||||
let firstNodeId = if g.nodes.len > 0: g.nodes.keys.toSeq[0] else: gengine.NodeId(0)
|
||||
let explicitStart = try: parseUInt(plan.graphStartNode) except: 0'u64
|
||||
let explicitEnd = try: parseUInt(plan.graphEndNode) except: 0'u64
|
||||
|
||||
case algo
|
||||
of "bfs":
|
||||
let startId = if plan.graphStartNode.len > 0: gengine.NodeId(parseUInt(plan.graphStartNode)) else: firstNodeId
|
||||
let startId = if explicitStart > 0: gengine.NodeId(explicitStart) else: firstNodeId
|
||||
let maxDepth = if plan.graphMaxDepth >= 0: plan.graphMaxDepth else: -1
|
||||
let traverseResult = gengine.bfs(g, startId, maxDepth)
|
||||
for nodeId in traverseResult:
|
||||
@@ -3524,7 +3576,7 @@ proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row] =
|
||||
result.add(row)
|
||||
|
||||
of "dfs":
|
||||
let startId = if plan.graphStartNode.len > 0: gengine.NodeId(parseUInt(plan.graphStartNode)) else: firstNodeId
|
||||
let startId = if explicitStart > 0: gengine.NodeId(explicitStart) else: firstNodeId
|
||||
let maxDepth = if plan.graphMaxDepth >= 0: plan.graphMaxDepth else: -1
|
||||
let traverseResult = gengine.dfs(g, startId, maxDepth)
|
||||
for nodeId in traverseResult:
|
||||
@@ -3565,9 +3617,9 @@ proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row] =
|
||||
result.add(row)
|
||||
|
||||
of "shortest_path", "shortestpath":
|
||||
if plan.graphStartNode.len > 0 and plan.graphEndNode.len > 0:
|
||||
let startId = gengine.NodeId(parseUInt(plan.graphStartNode))
|
||||
let endId = gengine.NodeId(parseUInt(plan.graphEndNode))
|
||||
if explicitStart > 0 and explicitEnd > 0:
|
||||
let startId = gengine.NodeId(explicitStart)
|
||||
let endId = gengine.NodeId(explicitEnd)
|
||||
let path = gengine.shortestPath(g, startId, endId)
|
||||
for nodeId in path:
|
||||
var row = initTable[string, string]()
|
||||
@@ -3584,8 +3636,8 @@ proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row] =
|
||||
return @[]
|
||||
|
||||
of "dijkstra":
|
||||
if plan.graphStartNode.len > 0:
|
||||
let startId = gengine.NodeId(parseUInt(plan.graphStartNode))
|
||||
if explicitStart > 0:
|
||||
let startId = gengine.NodeId(explicitStart)
|
||||
let dists = gengine.dijkstra(g, startId)
|
||||
for nodeId, dist in dists:
|
||||
var row = initTable[string, string]()
|
||||
|
||||
Reference in New Issue
Block a user