feat: Session 10.3 MCP Server + Session 11 Graph Engine Deep Integration
MCP Server (10.3): - STDIO JSON-RPC 2.0 transport with 3 AI tools: query, vector_search, schema_inspect - Multi-tenant session vars (tenant_id, user_id) with RLS support - Standalone binary: build/baramcp - Tested with all 3 tools + parameterized queries + vector search + schema inspect Graph Engine Deep Integration (Session 11): - CREATE GRAPH / DROP GRAPH DDL support - Graph engine wired to SQL executor via GRAPH_TABLE() function - 7 algorithms: BFS, DFS, PageRank, ShortestPath, Dijkstra, Louvain, Community - INSERT into _nodes/_edges tables auto-syncs with native Graph adjacency lists - Optional MATCH pattern, ALGORITHM, START, END, MAXDEPTH in GRAPH_TABLE syntax - All 340+ existing tests pass
This commit is contained in:
@@ -41,6 +41,8 @@ type
|
||||
nkGrant
|
||||
nkRevoke
|
||||
nkSetVar
|
||||
nkCreateGraph
|
||||
nkDropGraph
|
||||
|
||||
# Clauses
|
||||
nkFrom
|
||||
@@ -328,6 +330,12 @@ type
|
||||
of nkSetVar:
|
||||
svName*: string
|
||||
svValue*: string
|
||||
of nkCreateGraph:
|
||||
cgName*: string
|
||||
cgIfNotExists*: bool
|
||||
of nkDropGraph:
|
||||
dgName*: string
|
||||
dgIfExists*: bool
|
||||
of nkApplyMigration:
|
||||
amName*: string
|
||||
of nkMigrationStatus:
|
||||
@@ -440,6 +448,7 @@ type
|
||||
gtEnd*: Node
|
||||
gtMaxDepth*: int
|
||||
gtReturnCols*: seq[string]
|
||||
gtAlgo*: string
|
||||
of nkBfsQuery:
|
||||
bfsStart*: Node
|
||||
bfsTarget*: Node
|
||||
|
||||
+228
-14
@@ -25,6 +25,8 @@ import ../core/mvcc
|
||||
import ../core/tracing
|
||||
import ../fts/engine as fts
|
||||
import ../vector/engine as vengine
|
||||
import ../graph/engine as gengine
|
||||
import ../graph/community as gcomm
|
||||
|
||||
type
|
||||
IndexEntry* = ref object
|
||||
@@ -68,6 +70,7 @@ type
|
||||
cteTables*: Table[string, seq[Row]] # CTE name -> rows
|
||||
ftsIndexes*: Table[string, fts.InvertedIndex] # table.col -> FTS index
|
||||
vectorIndexes*: Table[string, vengine.HNSWIndex] # table.col -> HNSW index
|
||||
graphs*: Table[string, gengine.Graph] # graph name -> Graph object
|
||||
txnManager*: TxnManager
|
||||
pendingTxn*: Transaction
|
||||
onChange*: proc(ev: ChangeEvent) {.closure.}
|
||||
@@ -1539,6 +1542,48 @@ proc execInsert*(ctx: ExecutionContext, table: string, fields: seq[string], valu
|
||||
meta[col] = val
|
||||
vengine.insert(vecIdx, docId, vec, meta)
|
||||
|
||||
# Update Graph objects for graph node/edge tables
|
||||
for graphName, graph in ctx.graphs:
|
||||
if table == graphName & "_nodes":
|
||||
var nodeIdStr = ""
|
||||
for i, f in fields:
|
||||
if f == "id" and i < rowVals.len:
|
||||
nodeIdStr = rowVals[i]
|
||||
break
|
||||
if nodeIdStr.len > 0:
|
||||
let nid = gengine.NodeId(parseUInt(nodeIdStr))
|
||||
var label = ""
|
||||
var props = initTable[string, string]()
|
||||
for i, f in fields:
|
||||
if i < rowVals.len:
|
||||
if f == "node_label":
|
||||
label = rowVals[i]
|
||||
elif f != "id" and f != "properties":
|
||||
props[f] = rowVals[i]
|
||||
try:
|
||||
gengine.addNodeWithId(graph, nid, label, props)
|
||||
except:
|
||||
discard
|
||||
elif table == graphName & "_edges":
|
||||
var srcStr = ""
|
||||
var dstStr = ""
|
||||
var label = ""
|
||||
var weight = 1.0
|
||||
for i, f in fields:
|
||||
if i < rowVals.len:
|
||||
if f == "source_id": srcStr = rowVals[i]
|
||||
elif f == "dest_id": dstStr = rowVals[i]
|
||||
elif f == "edge_label": label = rowVals[i]
|
||||
elif f == "weight":
|
||||
try: weight = parseFloat(rowVals[i]) except: discard
|
||||
if srcStr.len > 0 and dstStr.len > 0:
|
||||
let srcId = gengine.NodeId(parseUInt(srcStr))
|
||||
let dstId = gengine.NodeId(parseUInt(dstStr))
|
||||
try:
|
||||
gengine.addEdgeWithId(graph, srcId, dstId, label, weight)
|
||||
except:
|
||||
discard
|
||||
|
||||
inc count
|
||||
return count
|
||||
|
||||
@@ -2138,7 +2183,17 @@ proc lowerSelect*(node: Node): IRPlan =
|
||||
elif node.selFrom.kind == nkGraphTraversal:
|
||||
let graphPlan = IRPlan(kind: irpkGraphTraversal)
|
||||
graphPlan.graphName = node.selFrom.gtGraphName
|
||||
graphPlan.graphAlgo = "bfs"
|
||||
graphPlan.graphAlgo = node.selFrom.gtAlgo.toLowerAscii()
|
||||
if node.selFrom.gtStart != nil:
|
||||
if node.selFrom.gtStart.kind == nkIdent:
|
||||
graphPlan.graphStartNode = node.selFrom.gtStart.identName
|
||||
elif node.selFrom.gtStart.kind == nkIntLit:
|
||||
graphPlan.graphStartNode = $node.selFrom.gtStart.intVal
|
||||
if node.selFrom.gtEnd != nil:
|
||||
if node.selFrom.gtEnd.kind == nkIdent:
|
||||
graphPlan.graphEndNode = node.selFrom.gtEnd.identName
|
||||
elif node.selFrom.gtEnd.kind == nkIntLit:
|
||||
graphPlan.graphEndNode = $node.selFrom.gtEnd.intVal
|
||||
graphPlan.graphEdgeLabel = node.selFrom.gtEdge
|
||||
graphPlan.graphMaxDepth = node.selFrom.gtMaxDepth
|
||||
graphPlan.graphReturnCols = node.selFrom.gtReturnCols
|
||||
@@ -3247,20 +3302,136 @@ proc executePlan*(ctx: ExecutionContext, plan: IRPlan): seq[Row] =
|
||||
return result
|
||||
|
||||
of irpkGraphTraversal:
|
||||
# Execute graph traversal using the graph engine
|
||||
# For now, return graph metadata as rows
|
||||
# Execute real graph traversal using the graph engine
|
||||
result = @[]
|
||||
# Check if we have a cross-modal engine with graph
|
||||
# The graph is stored by name; for simplicity, we'll use a table-based approach
|
||||
# Graph nodes are stored as rows with their properties
|
||||
let graphTable = plan.graphName & "_nodes"
|
||||
# Try to scan the nodes table
|
||||
let nodeRows = execScan(ctx, graphTable)
|
||||
if nodeRows.len > 0:
|
||||
for row in nodeRows:
|
||||
var resultRow = row
|
||||
result.add(resultRow)
|
||||
return result
|
||||
let graphName = plan.graphName
|
||||
if graphName notin ctx.graphs:
|
||||
return @[]
|
||||
|
||||
let g = ctx.graphs[graphName]
|
||||
if g == nil or g.nodes.len == 0:
|
||||
return @[]
|
||||
|
||||
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)
|
||||
|
||||
case algo
|
||||
of "bfs":
|
||||
let startId = if plan.graphStartNode.len > 0: gengine.NodeId(parseUInt(plan.graphStartNode)) else: firstNodeId
|
||||
let maxDepth = if plan.graphMaxDepth >= 0: plan.graphMaxDepth else: -1
|
||||
let traverseResult = gengine.bfs(g, startId, maxDepth)
|
||||
for nodeId in traverseResult:
|
||||
var row = initTable[string, string]()
|
||||
let nid = uint64(nodeId)
|
||||
row["_node_id"] = $nid
|
||||
if nodeId in g.nodes:
|
||||
let gn = g.nodes[nodeId]
|
||||
row["_node_label"] = gn.label
|
||||
for col in returnCols:
|
||||
if col == "label":
|
||||
row[col] = gn.label
|
||||
elif col == "id":
|
||||
row[col] = $nid
|
||||
elif col in gn.properties:
|
||||
row[col] = gn.properties[col]
|
||||
result.add(row)
|
||||
|
||||
of "dfs":
|
||||
let startId = if plan.graphStartNode.len > 0: gengine.NodeId(parseUInt(plan.graphStartNode)) else: firstNodeId
|
||||
let maxDepth = if plan.graphMaxDepth >= 0: plan.graphMaxDepth else: -1
|
||||
let traverseResult = gengine.dfs(g, startId, maxDepth)
|
||||
for nodeId in traverseResult:
|
||||
var row = initTable[string, string]()
|
||||
let nid = uint64(nodeId)
|
||||
row["_node_id"] = $nid
|
||||
if nodeId in g.nodes:
|
||||
let gn = g.nodes[nodeId]
|
||||
row["_node_label"] = gn.label
|
||||
for col in returnCols:
|
||||
if col == "label":
|
||||
row[col] = gn.label
|
||||
elif col == "id":
|
||||
row[col] = $nid
|
||||
elif col in gn.properties:
|
||||
row[col] = gn.properties[col]
|
||||
result.add(row)
|
||||
|
||||
of "pagerank", "page_rank":
|
||||
let prResult = gengine.pageRank(g, 20, 0.85)
|
||||
var sortedNodes = prResult.keys.toSeq
|
||||
sortedNodes.sort(proc(a, b: gengine.NodeId): int =
|
||||
let va = prResult.getOrDefault(a, 0.0)
|
||||
let vb = prResult.getOrDefault(b, 0.0)
|
||||
if va > vb: return -1 elif va < vb: return 1 else: return 0)
|
||||
for nodeId in sortedNodes:
|
||||
var row = initTable[string, string]()
|
||||
let nid = uint64(nodeId)
|
||||
row["_node_id"] = $nid
|
||||
row["rank"] = $prResult.getOrDefault(nodeId, 0.0)
|
||||
if nodeId in g.nodes:
|
||||
row["_node_label"] = g.nodes[nodeId].label
|
||||
for col in returnCols:
|
||||
if col == "rank": row["rank"] = $prResult.getOrDefault(nodeId, 0.0)
|
||||
elif col == "id": row[col] = $nid
|
||||
elif col == "label": row[col] = g.nodes[nodeId].label
|
||||
elif col in g.nodes[nodeId].properties: row[col] = g.nodes[nodeId].properties[col]
|
||||
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))
|
||||
let path = gengine.shortestPath(g, startId, endId)
|
||||
for nodeId in path:
|
||||
var row = initTable[string, string]()
|
||||
let nid = uint64(nodeId)
|
||||
row["_node_id"] = $nid
|
||||
if nodeId in g.nodes:
|
||||
row["_node_label"] = g.nodes[nodeId].label
|
||||
for col in returnCols:
|
||||
if col == "id": row[col] = $nid
|
||||
elif col == "label": row[col] = g.nodes[nodeId].label
|
||||
elif col in g.nodes[nodeId].properties: row[col] = g.nodes[nodeId].properties[col]
|
||||
result.add(row)
|
||||
else:
|
||||
return @[]
|
||||
|
||||
of "dijkstra":
|
||||
if plan.graphStartNode.len > 0:
|
||||
let startId = gengine.NodeId(parseUInt(plan.graphStartNode))
|
||||
let dists = gengine.dijkstra(g, startId)
|
||||
for nodeId, dist in dists:
|
||||
var row = initTable[string, string]()
|
||||
row["_node_id"] = $(uint64(nodeId))
|
||||
row["distance"] = $dist
|
||||
if nodeId in g.nodes:
|
||||
row["_node_label"] = g.nodes[nodeId].label
|
||||
result.add(row)
|
||||
else:
|
||||
return @[]
|
||||
|
||||
of "community", "community_detect", "louvain":
|
||||
let louvainResult = gcomm.louvain(g)
|
||||
for nodeId, communityId in louvainResult.communities:
|
||||
var row = initTable[string, string]()
|
||||
row["_node_id"] = $(uint64(nodeId))
|
||||
row["community"] = $communityId
|
||||
if nodeId in g.nodes:
|
||||
row["_node_label"] = g.nodes[nodeId].label
|
||||
result.add(row)
|
||||
|
||||
else:
|
||||
for nodeId in g.nodes.keys:
|
||||
var row = initTable[string, string]()
|
||||
let nid = uint64(nodeId)
|
||||
row["_node_id"] = $nid
|
||||
row["_node_label"] = g.nodes[nodeId].label
|
||||
for col in returnCols:
|
||||
if col == "id": row[col] = $nid
|
||||
elif col == "label": row[col] = g.nodes[nodeId].label
|
||||
elif col in g.nodes[nodeId].properties: row[col] = g.nodes[nodeId].properties[col]
|
||||
result.add(row)
|
||||
|
||||
else:
|
||||
return @[]
|
||||
@@ -3421,6 +3592,7 @@ proc isDDL(stmt: Node): bool =
|
||||
nkCreateTrigger, nkDropTrigger,
|
||||
nkCreateUser, nkDropUser,
|
||||
nkCreatePolicy, nkDropPolicy,
|
||||
nkCreateGraph, nkDropGraph,
|
||||
nkGrant, nkRevoke,
|
||||
nkEnableRLS, nkDisableRLS:
|
||||
result = true
|
||||
@@ -4211,6 +4383,48 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu
|
||||
for idxName in toDelete: ctx.btrees.del(idxName)
|
||||
return okResult()
|
||||
|
||||
of nkCreateGraph:
|
||||
let name = stmt.cgName
|
||||
if name in ctx.graphs:
|
||||
if not stmt.cgIfNotExists:
|
||||
return errResult("Graph '" & name & "' already exists")
|
||||
return okResult(msg="Graph '" & name & "' already exists")
|
||||
var g = gengine.newGraph()
|
||||
ctx.graphs[name] = g
|
||||
var createNodesSql = "CREATE TABLE " & name & "_nodes (id INTEGER PRIMARY KEY, node_label TEXT, properties TEXT)"
|
||||
var createEdgesSql = "CREATE TABLE " & name & "_edges (source_id INTEGER, dest_id INTEGER, edge_label TEXT, weight REAL)"
|
||||
let nodesTokens = qlex.tokenize(createNodesSql)
|
||||
let nodesAst = qpar.parse(nodesTokens)
|
||||
let nodesRes = executeQueryImpl(ctx, nodesAst)
|
||||
if not nodesRes.success:
|
||||
ctx.graphs.del(name)
|
||||
return errResult("Failed to create graph nodes table: " & nodesRes.message)
|
||||
let edgesTokens = qlex.tokenize(createEdgesSql)
|
||||
let edgesAst = qpar.parse(edgesTokens)
|
||||
let edgesRes = executeQueryImpl(ctx, edgesAst)
|
||||
if not edgesRes.success:
|
||||
ctx.tables.del(name & "_nodes")
|
||||
ctx.graphs.del(name)
|
||||
return errResult("Failed to create graph edges table: " & edgesRes.message)
|
||||
return okResult(msg="CREATE GRAPH " & name)
|
||||
|
||||
of nkDropGraph:
|
||||
let name = stmt.dgName
|
||||
if name notin ctx.graphs:
|
||||
if stmt.dgIfExists:
|
||||
return okResult()
|
||||
return errResult("Graph '" & name & "' does not exist")
|
||||
ctx.graphs.del(name)
|
||||
var dropNodesSql = "DROP TABLE " & name & "_nodes"
|
||||
var dropEdgesSql = "DROP TABLE " & name & "_edges"
|
||||
let nodesTokens = qlex.tokenize(dropNodesSql)
|
||||
let nodesAst = qpar.parse(nodesTokens)
|
||||
discard executeQueryImpl(ctx, nodesAst)
|
||||
let edgesTokens = qlex.tokenize(dropEdgesSql)
|
||||
let edgesAst = qpar.parse(edgesTokens)
|
||||
discard executeQueryImpl(ctx, edgesAst)
|
||||
return okResult(msg="DROP GRAPH " & name)
|
||||
|
||||
of nkBeginTxn:
|
||||
if ctx.pendingTxn != nil and ctx.pendingTxn.state == tsActive:
|
||||
discard ctx.txnManager.commit(ctx.pendingTxn)
|
||||
|
||||
@@ -135,6 +135,8 @@ type
|
||||
tkEdge
|
||||
tkLabels
|
||||
tkGraphTable
|
||||
tkCreateGraph
|
||||
tkDropGraph
|
||||
tkMatch
|
||||
tkColumns
|
||||
tkSrc
|
||||
@@ -362,6 +364,9 @@ const keywords*: Table[string, TokenKind] = {
|
||||
"label": tkLabels,
|
||||
"labels": tkLabels,
|
||||
"graph_table": tkGraphTable,
|
||||
"create_graph": tkCreateGraph,
|
||||
"drop_graph": tkDropGraph,
|
||||
"graph": tkGraph,
|
||||
"match": tkMatch,
|
||||
"columns": tkColumns,
|
||||
"src": tkSrc,
|
||||
@@ -370,7 +375,6 @@ const keywords*: Table[string, TokenKind] = {
|
||||
"matched": tkMatched,
|
||||
"array": tkArray,
|
||||
"vector": tkVector,
|
||||
"graph": tkGraph,
|
||||
"document": tkDocument,
|
||||
"similar": tkSimilar,
|
||||
"nearest": tkNearest,
|
||||
|
||||
+113
-34
@@ -42,6 +42,8 @@ proc parseOverClause(p: var Parser): Node
|
||||
proc parseFrameSpec(p: var Parser): Node
|
||||
proc parseFrameBoundary(p: var Parser): string
|
||||
proc parseSetVar(p: var Parser): Node
|
||||
proc parseCreateGraph(p: var Parser): Node
|
||||
proc parseDropGraph(p: var Parser): Node
|
||||
|
||||
proc parsePrimary(p: var Parser): Node =
|
||||
let tok = p.peek()
|
||||
@@ -464,60 +466,108 @@ proc parseSelect(p: var Parser): Node =
|
||||
discard p.advance()
|
||||
discard p.expect(tkLParen)
|
||||
let graphName = p.expect(tkIdent).value
|
||||
discard p.expect(tkMatch)
|
||||
# Parse pattern: (node)-[edge]->(node)
|
||||
var hasMatch = p.match(tkMatch)
|
||||
var patternNodes: seq[string]
|
||||
var patternEdges: seq[string]
|
||||
# First node
|
||||
discard p.expect(tkLParen)
|
||||
if p.peek().kind == tkIdent:
|
||||
patternNodes.add(p.advance().value)
|
||||
discard p.expect(tkRParen)
|
||||
# Edge and next node(s)
|
||||
while p.peek().kind == tkMinus or p.peek().kind == tkArrowR:
|
||||
if p.match(tkArrowR):
|
||||
discard
|
||||
elif p.match(tkMinus):
|
||||
if p.peek().kind == tkLBracket:
|
||||
if hasMatch:
|
||||
# First node
|
||||
discard p.expect(tkLParen)
|
||||
if p.peek().kind == tkIdent:
|
||||
patternNodes.add(p.advance().value)
|
||||
discard p.expect(tkRParen)
|
||||
# Edge and next node(s)
|
||||
while p.peek().kind == tkMinus or p.peek().kind == tkArrowR:
|
||||
if p.match(tkArrowR):
|
||||
discard
|
||||
elif p.match(tkMinus):
|
||||
if p.peek().kind == tkLBracket:
|
||||
discard p.advance()
|
||||
if p.peek().kind == tkIdent:
|
||||
patternEdges.add(p.advance().value)
|
||||
discard p.expect(tkRBracket)
|
||||
discard p.expect(tkArrowR)
|
||||
else:
|
||||
discard
|
||||
if p.peek().kind == tkLParen:
|
||||
discard p.advance()
|
||||
if p.peek().kind == tkIdent:
|
||||
patternEdges.add(p.advance().value)
|
||||
discard p.expect(tkRBracket)
|
||||
discard p.expect(tkArrowR)
|
||||
else:
|
||||
discard
|
||||
if p.peek().kind == tkLParen:
|
||||
discard p.advance()
|
||||
if p.peek().kind == tkIdent:
|
||||
patternNodes.add(p.advance().value)
|
||||
discard p.expect(tkRParen)
|
||||
patternNodes.add(p.advance().value)
|
||||
discard p.expect(tkRParen)
|
||||
# COLUMNS (col1, col2, ...)
|
||||
var algo = "bfs"
|
||||
var maxDepth = -1
|
||||
var startId = ""
|
||||
var endId = ""
|
||||
|
||||
let startVar = if patternNodes.len > 0: patternNodes[0] else: ""
|
||||
let endVar = if patternNodes.len > 1: patternNodes[1] else: ""
|
||||
|
||||
if p.peek().kind == tkIdent and p.peek().value.toLower() == "algorithm":
|
||||
discard p.advance()
|
||||
algo = p.advance().value.toLower()
|
||||
|
||||
if p.peek().kind == tkIdent and p.peek().value.toLower() == "start":
|
||||
discard p.advance()
|
||||
if p.peek().kind == tkIntLit:
|
||||
startId = p.advance().value
|
||||
elif p.peek().kind == tkIdent:
|
||||
startId = p.advance().value
|
||||
|
||||
if p.peek().kind == tkIdent and p.peek().value.toLower() == "end" or p.peek().kind == tkEnd:
|
||||
discard p.advance()
|
||||
if p.peek().kind == tkIntLit:
|
||||
endId = p.advance().value
|
||||
elif p.peek().kind == tkIdent:
|
||||
endId = p.advance().value
|
||||
|
||||
if p.peek().kind == tkIdent and p.peek().value.toLower() == "maxdepth":
|
||||
discard p.advance()
|
||||
if p.peek().kind == tkIntLit:
|
||||
maxDepth = parseInt(p.advance().value)
|
||||
|
||||
var returnCols: seq[string]
|
||||
if p.match(tkColumns):
|
||||
discard p.expect(tkLParen)
|
||||
if p.peek().kind == tkIdent:
|
||||
if p.peek().kind in {tkIdent, tkLabels, tkEdge, tkGraph, tkRank, tkEnd, tkMatch, tkColumns, tkSrc, tkDst, tkBfs, tkDfs, tkMerge}:
|
||||
var colName = p.advance().value
|
||||
# Handle dotted names: e.name
|
||||
while p.peek().kind == tkDot:
|
||||
discard p.advance() # skip dot
|
||||
colName &= "." & p.expect(tkIdent).value
|
||||
discard p.advance()
|
||||
if p.peek().kind in {tkIdent, tkLabels, tkEdge, tkGraph, tkRank, tkBfs, tkDfs, tkMatch, tkColumns, tkEnd, tkSrc, tkDst, tkMerge}:
|
||||
colName &= "." & p.advance().value
|
||||
else:
|
||||
colName &= "." & p.expect(tkIdent).value
|
||||
returnCols.add(colName)
|
||||
while p.match(tkComma):
|
||||
if p.peek().kind == tkIdent:
|
||||
if p.peek().kind in {tkIdent, tkLabels, tkEdge, tkGraph, tkRank, tkEnd, tkMatch, tkColumns, tkSrc, tkDst, tkBfs, tkDfs, tkMerge}:
|
||||
colName = p.advance().value
|
||||
while p.peek().kind == tkDot:
|
||||
discard p.advance()
|
||||
colName &= "." & p.expect(tkIdent).value
|
||||
if p.peek().kind in {tkIdent, tkLabels, tkEdge, tkGraph, tkRank, tkBfs, tkDfs, tkMatch, tkColumns, tkEnd, tkSrc, tkDst, tkMerge}:
|
||||
colName &= "." & p.advance().value
|
||||
else:
|
||||
colName &= "." & p.expect(tkIdent).value
|
||||
returnCols.add(colName)
|
||||
if p.match(tkAs):
|
||||
discard p.advance() # skip alias
|
||||
discard p.advance()
|
||||
discard p.expect(tkRParen)
|
||||
discard p.expect(tkRParen)
|
||||
# Create a graph traversal node
|
||||
|
||||
var startNode: Node = nil
|
||||
if startId.len > 0:
|
||||
startNode = Node(kind: nkIntLit, intVal: parseInt(startId))
|
||||
elif patternNodes.len > 0:
|
||||
startNode = Node(kind: nkIdent, identName: patternNodes[0])
|
||||
|
||||
var endNode: Node = nil
|
||||
if endId.len > 0:
|
||||
endNode = Node(kind: nkIntLit, intVal: parseInt(endId))
|
||||
elif patternNodes.len > 1:
|
||||
endNode = Node(kind: nkIdent, identName: patternNodes[1])
|
||||
|
||||
result.selFrom = Node(kind: nkGraphTraversal, gtGraphName: graphName,
|
||||
gtStart: nil, gtEdge: if patternEdges.len > 0: patternEdges[0] else: "",
|
||||
gtDirection: "out", gtEnd: nil, gtMaxDepth: -1,
|
||||
gtReturnCols: returnCols, line: tok.line, col: tok.col)
|
||||
gtStart: startNode, gtEdge: if patternEdges.len > 0: patternEdges[0] else: "",
|
||||
gtDirection: "out", gtEnd: endNode, gtMaxDepth: maxDepth,
|
||||
gtReturnCols: returnCols, gtAlgo: algo, line: tok.line, col: tok.col)
|
||||
else:
|
||||
let tableTok = p.expect(tkIdent)
|
||||
var alias = ""
|
||||
@@ -1542,6 +1592,31 @@ proc parseSetVar(p: var Parser): Node =
|
||||
result = Node(kind: nkSetVar, svName: varName, svValue: valStr,
|
||||
line: tok.line, col: tok.col)
|
||||
|
||||
proc parseCreateGraph(p: var Parser): Node =
|
||||
let tok = p.expect(tkCreate)
|
||||
discard p.expect(tkGraph)
|
||||
var ifNotExists = false
|
||||
if p.peek().kind == tkIdent and p.peek().value.toLower() == "if":
|
||||
discard p.advance()
|
||||
discard p.expect(tkNot)
|
||||
discard p.expect(tkExists)
|
||||
ifNotExists = true
|
||||
let name = p.expect(tkIdent).value
|
||||
result = Node(kind: nkCreateGraph, cgName: name, cgIfNotExists: ifNotExists,
|
||||
line: tok.line, col: tok.col)
|
||||
|
||||
proc parseDropGraph(p: var Parser): Node =
|
||||
let tok = p.expect(tkDrop)
|
||||
discard p.expect(tkGraph)
|
||||
var ifExists = false
|
||||
if p.peek().kind == tkIdent and p.peek().value.toLower() == "if":
|
||||
discard p.advance()
|
||||
discard p.expect(tkExists)
|
||||
ifExists = true
|
||||
let name = p.expect(tkIdent).value
|
||||
result = Node(kind: nkDropGraph, dgName: name, dgIfExists: ifExists,
|
||||
line: tok.line, col: tok.col)
|
||||
|
||||
proc parseStatement*(p: var Parser): Node =
|
||||
case p.peek().kind
|
||||
of tkWith, tkSelect: p.parseSelect()
|
||||
@@ -1568,6 +1643,8 @@ proc parseStatement*(p: var Parser): Node =
|
||||
p.parseCreateUser()
|
||||
elif next.kind == tkPolicy:
|
||||
p.parseCreatePolicy()
|
||||
elif next.kind == tkGraph:
|
||||
p.parseCreateGraph()
|
||||
else:
|
||||
p.parseCreateType()
|
||||
else:
|
||||
@@ -1587,6 +1664,8 @@ proc parseStatement*(p: var Parser): Node =
|
||||
p.parseDropUser()
|
||||
elif next.kind == tkPolicy:
|
||||
p.parseDropPolicy()
|
||||
elif next.kind == tkGraph:
|
||||
p.parseDropGraph()
|
||||
else:
|
||||
let tok = p.advance()
|
||||
Node(kind: nkNullLit, line: tok.line, col: tok.col)
|
||||
|
||||
Reference in New Issue
Block a user