## BaraQL Parser — recursive descent parser import std/strutils import lexer import ast import ../core/types type Parser* = object tokens: seq[Token] pos: int proc newParser*(tokens: seq[Token]): Parser = Parser(tokens: tokens, pos: 0) proc peek(p: Parser): Token = if p.pos < p.tokens.len: return p.tokens[p.pos] Token(kind: tkEof) proc advance(p: var Parser): Token = result = p.tokens[p.pos] inc p.pos proc expect(p: var Parser, kind: TokenKind): Token = let tok = p.advance() if tok.kind != kind: raise newException(ValueError, "Expected " & $kind & " but got " & $tok.kind & " at line " & $tok.line) return tok proc match(p: var Parser, kind: TokenKind): bool = if p.peek().kind == kind: discard p.advance() return true return false proc parseExpr(p: var Parser): Node proc parseSelect(p: var Parser): Node proc parseStatement*(p: var Parser): Node proc parseCreateType(p: var Parser): Node 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() case tok.kind of tkIntLit: discard p.advance() Node(kind: nkIntLit, intVal: parseInt(tok.value), line: tok.line, col: tok.col) of tkFloatLit: discard p.advance() Node(kind: nkFloatLit, floatVal: parseFloat(tok.value), line: tok.line, col: tok.col) of tkStringLit: discard p.advance() Node(kind: nkStringLit, strVal: tok.value, line: tok.line, col: tok.col) of tkBoolLit, tkTrue, tkFalse: discard p.advance() Node(kind: nkBoolLit, boolVal: tok.value == "true" or tok.kind == tkTrue, line: tok.line, col: tok.col) of tkNull: discard p.advance() Node(kind: nkNullLit, line: tok.line, col: tok.col) of tkCurrentUser: discard p.advance() Node(kind: nkCurrentUser, line: tok.line, col: tok.col) of tkCurrentRole: discard p.advance() Node(kind: nkCurrentRole, line: tok.line, col: tok.col) of tkIdent, tkRowNumber, tkRank, tkDenseRank, tkLead, tkLag, tkFirstValue, tkLastValue, tkNtile: discard p.advance() let funcName = tok.value # Check for function call: ident(...) if p.peek().kind == tkLParen: discard p.advance() # consume ( var args: seq[Node] = @[] if p.peek().kind != tkRParen: args.add(p.parseExpr()) while p.match(tkComma): args.add(p.parseExpr()) discard p.expect(tkRParen) var funcNode = Node(kind: nkFuncCall, funcName: funcName, funcArgs: args, line: tok.line, col: tok.col) # Check for window function: func(...) OVER (...) if p.peek().kind == tkOver: let overClause = p.parseOverClause() return Node(kind: nkWindowExpr, winFunc: funcName, winArgs: args, winOver: overClause, line: tok.line, col: tok.col) return funcNode # Check for dotted path: ident.ident.ident var parts = @[funcName] while p.peek().kind == tkDot: discard p.advance() # consume . parts.add(p.expect(tkIdent).value) if parts.len == 1: return Node(kind: nkIdent, identName: funcName, line: tok.line, col: tok.col) return Node(kind: nkPath, pathParts: parts, line: tok.line, col: tok.col) of tkLParen: discard p.advance() # Check for subquery if p.peek().kind == tkSelect: let sub = p.parseSelect() discard p.expect(tkRParen) return Node(kind: nkSubquery, subQuery: sub, line: tok.line, col: tok.col) let expr = p.parseExpr() discard p.expect(tkRParen) expr of tkLBracket: discard p.advance() var elems: seq[Node] = @[] if p.peek().kind != tkRBracket: elems.add(p.parseExpr()) while p.match(tkComma): elems.add(p.parseExpr()) discard p.expect(tkRBracket) Node(kind: nkArrayLit, arrayElems: elems, line: tok.line, col: tok.col) of tkSelect: p.parseSelect() of tkExists: discard p.advance() discard p.expect(tkLParen) let sub = p.parseSelect() discard p.expect(tkRParen) Node(kind: nkExists, existsExpr: sub, line: tok.line, col: tok.col) of tkStar: discard p.advance() Node(kind: nkStar, line: tok.line, col: tok.col) of tkPlaceholder: discard p.advance() Node(kind: nkPlaceholder, line: tok.line, col: tok.col) of tkNot: discard p.advance() let operand = p.parsePrimary() Node(kind: nkUnaryOp, unOp: ukNot, unOperand: operand, line: tok.line, col: tok.col) of tkMinus: discard p.advance() let operand = p.parsePrimary() Node(kind: nkUnaryOp, unOp: ukNeg, unOperand: operand, line: tok.line, col: tok.col) of tkCount, tkSum, tkAvg, tkMin, tkMax, tkArrayAgg, tkStringAgg: let funcName = tok.value discard p.advance() discard p.expect(tkLParen) var args: seq[Node] = @[] # Handle DISTINCT inside aggregate var hasDistinct = false if p.peek().kind == tkDistinct: discard p.advance() hasDistinct = true if p.peek().kind != tkRParen: args.add(p.parseExpr()) while p.match(tkComma): args.add(p.parseExpr()) discard p.expect(tkRParen) var node = Node(kind: nkFuncCall, funcName: funcName.toLower(), funcArgs: args, line: tok.line, col: tok.col) # Handle FILTER (WHERE ...) if p.match(tkFilter): discard p.expect(tkLParen) discard p.expect(tkWhere) node.funcFilter = p.parseExpr() discard p.expect(tkRParen) return node of tkCase: discard p.advance() var caseExpr: Node = nil # CASE expr WHEN ... THEN ... ELSE ... END if p.peek().kind != tkWhen: caseExpr = p.parseExpr() var whens: seq[(Node, Node)] = @[] while p.match(tkWhen): let cond = p.parseExpr() discard p.expect(tkThen) let val = p.parseExpr() whens.add((cond, val)) var elseExpr: Node = nil if p.match(tkElse): elseExpr = p.parseExpr() discard p.expect(tkEnd) Node(kind: nkCase, caseExpr: caseExpr, caseWhens: whens, caseElse: elseExpr, line: tok.line, col: tok.col) else: raise newException(ValueError, "Unexpected token " & $tok.kind & " at line " & $tok.line & ", col " & $tok.col) proc parseOverClause(p: var Parser): Node = ## Parse OVER ( [PARTITION BY expr, ...] [ORDER BY expr [ASC|DESC], ...] [frame] ) discard p.expect(tkOver) discard p.expect(tkLParen) result = Node(kind: nkOverClause) # PARTITION BY if p.match(tkPartition): discard p.expect(tkBy) result.overPartition.add(p.parseExpr()) while p.match(tkComma): result.overPartition.add(p.parseExpr()) # ORDER BY if p.match(tkOrder): discard p.expect(tkBy) var expr = p.parseExpr() var dir = sdAsc if p.match(tkDesc): dir = sdDesc elif p.match(tkAsc): dir = sdAsc result.overOrderBy.add(Node(kind: nkOrderBy, orderByExpr: expr, orderByDir: dir)) while p.match(tkComma): expr = p.parseExpr() dir = sdAsc if p.match(tkDesc): dir = sdDesc elif p.match(tkAsc): dir = sdAsc result.overOrderBy.add(Node(kind: nkOrderBy, orderByExpr: expr, orderByDir: dir)) # Frame specification if p.peek().kind in {tkRows, tkRange}: result.overFrame = p.parseFrameSpec() discard p.expect(tkRParen) proc parseFrameSpec(p: var Parser): Node = ## Parse ROWS|RANGE frame let modeTok = p.advance() # tkRows or tkRange result = Node(kind: nkFrameSpec, frameMode: modeTok.value) if p.match(tkBetween): result.frameStartType = p.parseFrameBoundary() discard p.expect(tkAnd) result.frameEndType = p.parseFrameBoundary() else: result.frameStartType = p.parseFrameBoundary() result.frameEndType = "CURRENT ROW" proc parseFrameBoundary(p: var Parser): string = ## Returns boundary type string like "UNBOUNDED PRECEDING", "CURRENT ROW", "1 PRECEDING" if p.match(tkUnbounded): if p.match(tkPreceding): return "UNBOUNDED PRECEDING" elif p.match(tkFollowing): return "UNBOUNDED FOLLOWING" else: raise newException(ValueError, "Expected PRECEDING or FOLLOWING after UNBOUNDED") elif p.match(tkCurrent): discard p.expect(tkRow) return "CURRENT ROW" else: # Expect a simple integer literal for offset boundaries let offsetExpr = p.parsePrimary() var offsetStr = "" case offsetExpr.kind: of nkIntLit: offsetStr = $offsetExpr.intVal of nkIdent: offsetStr = offsetExpr.identName else: raise newException(ValueError, "Expected integer literal or identifier in frame boundary, got " & $offsetExpr.kind) if p.match(tkPreceding): return offsetStr & " PRECEDING" elif p.match(tkFollowing): return offsetStr & " FOLLOWING" else: raise newException(ValueError, "Expected PRECEDING or FOLLOWING after frame offset") proc parsePostfix(p: var Parser): Node = result = p.parsePrimary() while p.peek().kind in {tkArrowR, tkArrowRR}: let isText = p.peek().kind == tkArrowRR discard p.advance() let key = p.expect(tkStringLit).value result = Node(kind: nkJsonPath, jpLeft: result, jpKey: key, jpAsText: isText, line: p.peek().line, col: p.peek().col) proc parseMulDiv(p: var Parser): Node = result = p.parsePostfix() while p.peek().kind in {tkStar, tkSlash, tkPercent, tkFloorDiv}: let op = case p.peek().kind of tkStar: bkMul of tkSlash: bkDiv of tkPercent: bkMod of tkFloorDiv: bkFloorDiv else: bkMul let tok = p.advance() let right = p.parsePostfix() result = Node(kind: nkBinOp, binOp: op, binLeft: result, binRight: right, line: tok.line, col: tok.col) proc parseAddSub(p: var Parser): Node = result = p.parseMulDiv() while p.peek().kind in {tkPlus, tkMinus, tkConcat}: let op = case p.peek().kind of tkPlus: bkAdd of tkMinus: bkSub of tkConcat: bkConcat else: bkAdd let tok = p.advance() let right = p.parseMulDiv() result = Node(kind: nkBinOp, binOp: op, binLeft: result, binRight: right, line: tok.line, col: tok.col) proc parseComparison(p: var Parser): Node = result = p.parseAddSub() # Handle BETWEEN ... AND ... if p.peek().kind == tkBetween: let tok = p.advance() let low = p.parseAddSub() discard p.expect(tkAnd) let high = p.parseAddSub() return Node(kind: nkBetweenExpr, betweenExpr: result, betweenLow: low, betweenHigh: high, line: tok.line, col: tok.col) # Handle IN (subquery | list) if p.peek().kind == tkIn: let tok = p.advance() let right = p.parseAddSub() return Node(kind: nkInExpr, inLeft: result, inRight: right, line: tok.line, col: tok.col) # Handle LIKE / ILIKE if p.peek().kind in {tkLike, tkILike}: let isILike = p.peek().kind == tkILike let tok = p.advance() let pattern = p.parseAddSub() return Node(kind: nkLikeExpr, likeExpr: result, likePattern: pattern, likeCaseInsensitive: isILike, line: tok.line, col: tok.col) # Handle IS NULL / IS NOT NULL if p.peek().kind == tkIs: let tok = p.advance() var negated = false if p.peek().kind == tkNot: discard p.advance() negated = true if p.peek().kind != tkNull: raise newException(ValueError, "Expected NULL after IS " & (if negated: "NOT " else: "") & "at line " & $tok.line) discard p.advance() # consume NULL token return Node(kind: nkIsExpr, isExpr: result, isNegated: negated, line: tok.line, col: tok.col) while p.peek().kind in {tkEq, tkNotEq, tkLt, tkLtEq, tkGt, tkGtEq, tkFtsMatch, tkDistanceOp, tkJsonContains, tkJsonContainedBy, tkJsonHasAny, tkJsonHasAll}: let op = case p.peek().kind of tkEq: bkEq of tkNotEq: bkNotEq of tkLt: bkLt of tkLtEq: bkLtEq of tkGt: bkGt of tkGtEq: bkGtEq of tkFtsMatch: bkFtsMatch of tkDistanceOp: bkDistance of tkJsonContains: bkJsonContains of tkJsonContainedBy: bkJsonContainedBy of tkJsonHasAny: bkJsonHasAny of tkJsonHasAll: bkJsonHasAll else: bkEq let tok = p.advance() let right = p.parseAddSub() result = Node(kind: nkBinOp, binOp: op, binLeft: result, binRight: right, line: tok.line, col: tok.col) proc parseNot(p: var Parser): Node = if p.peek().kind == tkNot: let tok = p.advance() let operand = p.parseComparison() return Node(kind: nkUnaryOp, unOp: ukNot, unOperand: operand, line: tok.line, col: tok.col) return p.parseComparison() proc parseAnd(p: var Parser): Node = result = p.parseNot() while p.peek().kind == tkAnd: let tok = p.advance() let right = p.parseNot() result = Node(kind: nkBinOp, binOp: bkAnd, binLeft: result, binRight: right, line: tok.line, col: tok.col) proc parseOr(p: var Parser): Node = result = p.parseAnd() while p.peek().kind == tkOr: let tok = p.advance() let right = p.parseAnd() result = Node(kind: nkBinOp, binOp: bkOr, binLeft: result, binRight: right, line: tok.line, col: tok.col) proc parseExpr(p: var Parser): Node = return p.parseOr() proc parseJoinType(p: var Parser): JoinKind = if p.match(tkInner): return jkInner elif p.match(tkLeft): if p.match(tkOuter): discard return jkLeft elif p.match(tkRight): if p.match(tkOuter): discard return jkRight elif p.match(tkFull): if p.match(tkOuter): discard return jkFull elif p.match(tkCross): return jkCross return jkInner proc parseWith(p: var Parser): Node = # WITH [RECURSIVE] name AS (select), name2 AS (select2) SELECT ... let tok = p.expect(tkWith) result = Node(kind: nkWith, line: tok.line, col: tok.col) result.withBindings = @[] var isRecursive = false if p.peek().kind == tkRecursive: discard p.advance() isRecursive = true # Parse first CTE let cteName = p.expect(tkIdent).value discard p.expect(tkAs) discard p.expect(tkLParen) let cteQuery = p.parseSelect() discard p.expect(tkRParen) result.withBindings.add((cteName, cteQuery, isRecursive)) # Parse additional CTEs while p.match(tkComma): let name = p.expect(tkIdent).value discard p.expect(tkAs) discard p.expect(tkLParen) let query = p.parseSelect() discard p.expect(tkRParen) result.withBindings.add((name, query, isRecursive)) proc parseSelect(p: var Parser): Node = # Handle WITH (CTE) var withClause: Node = nil if p.peek().kind == tkWith: withClause = p.parseWith() let tok = p.expect(tkSelect) result = Node(kind: nkSelect, line: tok.line, col: tok.col) if withClause != nil: result.selWith = withClause.withBindings if p.peek().kind == tkDistinct: discard p.advance() result.selDistinct = true # Parse SELECT list result.selResult = @[] var expr = p.parseExpr() if p.match(tkAs): expr.exprAlias = p.expect(tkIdent).value result.selResult.add(expr) while p.match(tkComma): expr = p.parseExpr() if p.match(tkAs): expr.exprAlias = p.expect(tkIdent).value result.selResult.add(expr) # Parse FROM result.selJoins = @[] if p.match(tkFrom): # Handle subquery: (SELECT ...) AS alias if p.peek().kind == tkLParen: discard p.advance() # consume ( let subquery = p.parseSelect() discard p.expect(tkRParen) var alias = "" if p.match(tkAs): alias = p.expect(tkIdent).value elif p.peek().kind == tkIdent: alias = p.advance().value result.selFrom = Node(kind: nkFrom, fromTable: "(subquery)", fromAlias: alias, fromSubquery: subquery, line: tok.line, col: tok.col) elif p.peek().kind == tkGraphTable: # GRAPH_TABLE(name MATCH (pattern) COLUMNS (cols)) discard p.advance() discard p.expect(tkLParen) let graphName = p.expect(tkIdent).value var hasMatch = p.match(tkMatch) var patternNodes: seq[string] var patternEdges: seq[string] 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: 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 in {tkIdent, tkLabels, tkEdge, tkGraph, tkRank, tkEnd, tkMatch, tkColumns, tkSrc, tkDst, tkBfs, tkDfs, tkMerge}: var colName = p.advance().value while p.peek().kind == tkDot: 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 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() 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() discard p.expect(tkRParen) discard p.expect(tkRParen) 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: 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 = "" if p.match(tkAs): alias = p.expect(tkIdent).value elif p.peek().kind == tkIdent: alias = p.advance().value result.selFrom = Node(kind: nkFrom, fromTable: tableTok.value, fromAlias: alias, line: tableTok.line, col: tableTok.col) # Comma join: FROM t1, t2 → implicit CROSS JOIN while p.peek().kind == tkComma: discard p.advance() let nextTableTok = p.expect(tkIdent) var nextAlias = "" if p.match(tkAs): nextAlias = p.expect(tkIdent).value elif p.peek().kind == tkIdent: nextAlias = p.advance().value let joinNode = Node(kind: nkJoin, joinKind: jkCross, joinTarget: Node(kind: nkFrom, fromTable: nextTableTok.value, fromAlias: nextAlias, line: nextTableTok.line, col: nextTableTok.col), joinOn: nil, joinAlias: nextAlias, joinLateral: false, line: nextTableTok.line, col: nextTableTok.col) result.selJoins.add(joinNode) # Parse PIVOT / UNPIVOT after FROM source if p.peek().kind == tkPivot: discard p.advance() discard p.expect(tkLParen) let aggFunc = p.parseExpr() # e.g. SUM(salary) discard p.expect(tkFor) let forCol = p.expect(tkIdent).value discard p.expect(tkIn) discard p.expect(tkLParen) var inValues: seq[string] = @[] inValues.add(p.expect(tkStringLit).value) while p.match(tkComma): inValues.add(p.expect(tkStringLit).value) discard p.expect(tkRParen) discard p.expect(tkRParen) result.selFrom = Node(kind: nkPivot, pivotSource: result.selFrom, pivotAgg: aggFunc, pivotForCol: forCol, pivotInValues: inValues, line: tok.line, col: tok.col) elif p.peek().kind == tkUnpivot: discard p.advance() discard p.expect(tkLParen) let valCol = p.expect(tkIdent).value discard p.expect(tkFor) let forCol = p.expect(tkIdent).value discard p.expect(tkIn) discard p.expect(tkLParen) var inCols: seq[string] = @[] inCols.add(p.expect(tkIdent).value) while p.match(tkComma): inCols.add(p.expect(tkIdent).value) discard p.expect(tkRParen) discard p.expect(tkRParen) result.selFrom = Node(kind: nkUnpivot, unpivotSource: result.selFrom, unpivotValueCol: valCol, unpivotForCol: forCol, unpivotInCols: inCols, line: tok.line, col: tok.col) # Parse JOINs while p.peek().kind == tkJoin or p.peek().kind == tkLateral or (p.peek().kind in {tkInner, tkLeft, tkRight, tkFull, tkCross} and p.pos + 1 < p.tokens.len and p.tokens[p.pos + 1].kind in {tkJoin, tkLateral}): var isLateral = false var jk = jkInner # Check for LATERAL before join type if p.match(tkLateral): isLateral = true # Could be standalone LATERAL or LATERAL JOIN if p.peek().kind == tkJoin: discard p.advance() else: jk = p.parseJoinType() discard p.expect(tkJoin) # Check for LATERAL after JOIN keyword if p.match(tkLateral): isLateral = true var joinTarget: Node var joinAlias = "" if isLateral: # LATERAL (subquery) AS alias discard p.expect(tkLParen) let subquery = p.parseSelect() discard p.expect(tkRParen) if p.match(tkAs): joinAlias = p.expect(tkIdent).value elif p.peek().kind == tkIdent: joinAlias = p.advance().value joinTarget = Node(kind: nkSubquery, subQuery: subquery, line: tok.line, col: tok.col) else: let joinTable = p.expect(tkIdent) if p.match(tkAs): joinAlias = p.expect(tkIdent).value elif p.peek().kind == tkIdent: joinAlias = p.advance().value joinTarget = Node(kind: nkFrom, fromTable: joinTable.value, fromAlias: joinAlias, line: joinTable.line, col: joinTable.col) var joinCond: Node = nil if p.match(tkOn): joinCond = p.parseExpr() result.selJoins.add(Node(kind: nkJoin, joinKind: jk, joinTarget: joinTarget, joinOn: joinCond, joinAlias: joinAlias, joinLateral: isLateral, line: tok.line, col: tok.col)) # Parse WHERE if p.match(tkWhere): result.selWhere = Node(kind: nkWhere, whereExpr: p.parseExpr()) # Parse GROUP BY if p.match(tkGroup): discard p.expect(tkBy) result.selGroupBy = @[] result.selGroupingSetsKind = gskNone result.selGroupingSets = @[] # Check for GROUPING SETS, ROLLUP, CUBE if p.peek().kind == tkGrouping: discard p.advance() discard p.expect(tkSets) result.selGroupingSetsKind = gskGroupingSets discard p.expect(tkLParen) # Parse each set: (col1, col2) or () while true: discard p.expect(tkLParen) var setExprs: seq[Node] = @[] if p.peek().kind != tkRParen: setExprs.add(p.parseExpr()) while p.match(tkComma): setExprs.add(p.parseExpr()) discard p.expect(tkRParen) result.selGroupingSets.add(setExprs) if not p.match(tkComma): break discard p.expect(tkRParen) elif p.peek().kind == tkRollup: discard p.advance() result.selGroupingSetsKind = gskRollup discard p.expect(tkLParen) result.selGroupBy.add(p.parseExpr()) while p.match(tkComma): result.selGroupBy.add(p.parseExpr()) discard p.expect(tkRParen) elif p.peek().kind == tkCube: discard p.advance() result.selGroupingSetsKind = gskCube discard p.expect(tkLParen) result.selGroupBy.add(p.parseExpr()) while p.match(tkComma): result.selGroupBy.add(p.parseExpr()) discard p.expect(tkRParen) else: # Regular GROUP BY result.selGroupBy.add(p.parseExpr()) while p.match(tkComma): result.selGroupBy.add(p.parseExpr()) # Parse HAVING if p.match(tkHaving): result.selHaving = Node(kind: nkHaving, havingExpr: p.parseExpr()) # Parse ORDER BY if p.match(tkOrder): discard p.expect(tkBy) result.selOrderBy = @[] var firstExpr = p.parseExpr() var firstDir = sdAsc if p.match(tkDesc): firstDir = sdDesc elif p.match(tkAsc): firstDir = sdAsc result.selOrderBy.add(Node(kind: nkOrderBy, orderByExpr: firstExpr, orderByDir: firstDir)) while p.match(tkComma): let expr = p.parseExpr() var dir = sdAsc if p.match(tkDesc): dir = sdDesc elif p.match(tkAsc): dir = sdAsc result.selOrderBy.add(Node(kind: nkOrderBy, orderByExpr: expr, orderByDir: dir)) # Parse LIMIT if p.match(tkLimit): result.selLimit = Node(kind: nkLimit, limitExpr: p.parseExpr()) # Parse OFFSET if p.match(tkOffset): result.selOffset = Node(kind: nkOffset, offsetExpr: p.parseExpr()) # Parse set operations (UNION / INTERSECT / EXCEPT) # Left-associative; same precedence level for all while p.peek().kind in {tkUnion, tkIntersect, tkExcept}: var sopKind: SetOpKind let tok = p.advance() case tok.kind: of tkUnion: sopKind = sdkUnion of tkIntersect: sopKind = sdkIntersect of tkExcept: sopKind = sdkExcept else: break var isAll = false if p.match(tkAll): isAll = true let right = p.parseSelect() let left = result result = Node(kind: nkSetOp, line: tok.line, col: tok.col) result.setOpKind = sopKind result.setOpAll = isAll result.setOpLeft = left result.setOpRight = right proc parseInsert(p: var Parser): Node = let tok = p.expect(tkInsert) discard p.match(tkInto) # optional INTO let target = p.expect(tkIdent).value result = Node(kind: nkInsert, insTarget: target, line: tok.line, col: tok.col) result.insFields = @[] result.insValues = @[] result.insReturning = @[] # Parse column list: (col1, col2, ...) if p.peek().kind == tkLParen: discard p.advance() result.insFields.add(p.parseExpr()) while p.match(tkComma): result.insFields.add(p.parseExpr()) discard p.expect(tkRParen) # Parse VALUES if p.match(tkValues): while true: var row: seq[Node] = @[] if p.peek().kind == tkLParen: discard p.advance() row.add(p.parseExpr()) while p.match(tkComma): row.add(p.parseExpr()) discard p.expect(tkRParen) else: row.add(p.parseExpr()) result.insValues.add(Node(kind: nkArrayLit, arrayElems: row)) if p.peek().kind != tkComma: break discard p.advance() # Parse ON CONFLICT if p.peek().kind == tkOn: discard p.advance() if p.peek().kind == tkIdent and p.peek().value.toLower() == "conflict": discard p.advance() result.insConflict = p.parseExpr() # Parse RETURNING if p.match(tkReturning): result.insReturning.add(p.parseExpr()) while p.match(tkComma): result.insReturning.add(p.parseExpr()) proc parseUpdate(p: var Parser): Node = let tok = p.expect(tkUpdate) let target = p.expect(tkIdent).value result = Node(kind: nkUpdate, updTarget: target, line: tok.line, col: tok.col) if p.match(tkSet): result.updSet = @[] let field = p.expect(tkIdent).value discard p.match(tkEq) # = or := let val = p.parseExpr() result.updSet.add(Node(kind: nkBinOp, binOp: bkAssign, binLeft: Node(kind: nkIdent, identName: field), binRight: val)) while p.match(tkComma): let f = p.expect(tkIdent).value discard p.match(tkEq) let v = p.parseExpr() result.updSet.add(Node(kind: nkBinOp, binOp: bkAssign, binLeft: Node(kind: nkIdent, identName: f), binRight: v)) if p.match(tkWhere): result.updWhere = Node(kind: nkWhere, whereExpr: p.parseExpr()) if p.match(tkReturning): result.updReturning = @[] result.updReturning.add(p.parseExpr()) while p.match(tkComma): result.updReturning.add(p.parseExpr()) proc parseDelete(p: var Parser): Node = let tok = p.expect(tkDelete) discard p.match(tkFrom) # optional FROM keyword let target = p.expect(tkIdent).value result = Node(kind: nkDelete, delTarget: target, line: tok.line, col: tok.col) if p.match(tkWhere): result.delWhere = Node(kind: nkWhere, whereExpr: p.parseExpr()) if p.match(tkReturning): result.delReturning = @[] result.delReturning.add(p.parseExpr()) while p.match(tkComma): result.delReturning.add(p.parseExpr()) proc parseMerge(p: var Parser): Node = let tok = p.expect(tkMerge) discard p.match(tkInto) # optional INTO result = Node(kind: nkMerge, line: tok.line, col: tok.col) result.mergeTarget = p.expect(tkIdent).value if p.match(tkAs): result.mergeTargetAlias = p.expect(tkIdent).value elif p.peek().kind == tkIdent: result.mergeTargetAlias = p.advance().value discard p.expect(tkUsing) # Source can be a table name or a subquery if p.peek().kind == tkLParen: discard p.advance() # consume ( result.mergeSource = p.parseSelect() discard p.expect(tkRParen) else: let srcTable = p.expect(tkIdent).value result.mergeSource = Node(kind: nkIdent, identName: srcTable, line: tok.line, col: tok.col) if p.match(tkAs): result.mergeSourceAlias = p.expect(tkIdent).value elif p.peek().kind == tkIdent: result.mergeSourceAlias = p.advance().value discard p.expect(tkOn) result.mergeOn = p.parseExpr() # WHEN MATCHED THEN UPDATE SET ... if p.match(tkWhen): if p.match(tkNot): discard p.expect(tkMatched) discard p.expect(tkThen) discard p.expect(tkInsert) discard p.expect(tkLParen) result.mergeNotMatchedInsert.add(Node(kind: nkIdent, identName: p.expect(tkIdent).value)) while p.match(tkComma): result.mergeNotMatchedInsert.add(Node(kind: nkIdent, identName: p.expect(tkIdent).value)) discard p.expect(tkRParen) discard p.expect(tkValues) discard p.expect(tkLParen) result.mergeNotMatchedValues.add(p.parseExpr()) while p.match(tkComma): result.mergeNotMatchedValues.add(p.parseExpr()) discard p.expect(tkRParen) else: discard p.expect(tkMatched) discard p.expect(tkThen) discard p.expect(tkUpdate) discard p.expect(tkSet) let col = p.expect(tkIdent).value discard p.expect(tkEq) result.mergeMatchedUpdate.add(Node(kind: nkBinOp, binOp: bkAssign, binLeft: Node(kind: nkIdent, identName: col), binRight: p.parseExpr())) while p.match(tkComma): let col2 = p.expect(tkIdent).value discard p.expect(tkEq) result.mergeMatchedUpdate.add(Node(kind: nkBinOp, binOp: bkAssign, binLeft: Node(kind: nkIdent, identName: col2), binRight: p.parseExpr())) proc parseCreateType(p: var Parser): Node = let tok = p.expect(tkCreate) discard p.expect(tkType) let name = p.expect(tkIdent).value result = Node(kind: nkCreateType, ctName: name, line: tok.line, col: tok.col) result.ctBases = @[] if p.match(tkIdent): discard if p.match(tkLBrace): result.ctProperties = @[] result.ctLinks = @[] while p.peek().kind != tkRbrace and p.peek().kind != tkEof: discard p.match(tkComma) var isRequired = false var isMulti = false if p.peek().kind == tkRequired: discard p.advance() isRequired = true if p.peek().kind == tkMulti: discard p.advance() isMulti = true let fieldTok = p.expect(tkIdent) if p.peek().kind == tkArrow: discard p.advance() let target = p.expect(tkIdent).value result.ctLinks.add(Node(kind: nkLinkDef, ldName: fieldTok.value, ldTarget: target, ldRequired: isRequired, ldCardinality: if isMulti: Many else: One)) else: var typeName = "" if p.match(tkColon): typeName = p.expect(tkIdent).value result.ctProperties.add(Node(kind: nkPropertyDef, pdName: fieldTok.value, pdType: typeName, pdRequired: isRequired)) discard p.match(tkSemicolon) discard p.expect(tkRbrace) proc parseCreateTable(p: var Parser): Node = let tok = p.expect(tkCreate) result = Node(kind: nkCreateTable, line: tok.line, col: tok.col) result.crtColumns = @[] result.crtConstraints = @[] # Optional IF NOT EXISTS if p.peek().kind == tkIf: discard p.advance() discard p.expect(tkNot) discard p.expect(tkExists) result.crtIfNotExists = true discard p.expect(tkTable) if p.peek().kind == tkIf: discard p.advance() # if discard p.expect(tkNot) discard p.expect(tkExists) result.crtIfNotExists = true result.crtName = p.expect(tkIdent).value discard p.expect(tkLParen) while p.peek().kind != tkRParen and p.peek().kind != tkEof: discard p.match(tkComma) # Check if this is a table-level constraint if p.peek().kind in {tkPrimary, tkForeign, tkUnique, tkCheck}: let cst = Node(kind: nkConstraintDef) cst.cstColumns = @[]; cst.cstRefColumns = @[] if p.match(tkPrimary): discard p.expect(tkKey) cst.cstType = "pkey" if p.peek().kind == tkLParen: discard p.advance() cst.cstColumns.add(p.expect(tkIdent).value) while p.match(tkComma): cst.cstColumns.add(p.expect(tkIdent).value) discard p.expect(tkRParen) elif p.match(tkForeign): discard p.expect(tkKey) cst.cstType = "fkey" if p.peek().kind == tkLParen: discard p.advance() cst.cstColumns.add(p.expect(tkIdent).value) while p.match(tkComma): cst.cstColumns.add(p.expect(tkIdent).value) discard p.expect(tkRParen) discard p.expect(tkReferences) cst.cstRefTable = p.expect(tkIdent).value if p.peek().kind == tkLParen: discard p.advance() cst.cstRefColumns.add(p.expect(tkIdent).value) while p.match(tkComma): cst.cstRefColumns.add(p.expect(tkIdent).value) discard p.expect(tkRParen) if p.peek().kind == tkOn: discard p.advance() if p.peek().kind == tkDelete: discard p.advance() if p.peek().kind == tkCascade: discard p.advance() cst.cstOnDelete = "CASCADE" elif p.peek().kind == tkSet: discard p.advance() discard p.match(tkNull) cst.cstOnDelete = "SET NULL" elif p.peek().kind == tkRestrict: discard p.advance() cst.cstOnDelete = "RESTRICT" elif p.peek().kind == tkUpdate: discard p.advance() if p.peek().kind == tkCascade: discard p.advance() cst.cstOnUpdate = "CASCADE" elif p.peek().kind == tkSet: discard p.advance() discard p.match(tkNull) cst.cstOnUpdate = "SET NULL" elif p.peek().kind == tkRestrict: discard p.advance() cst.cstOnUpdate = "RESTRICT" elif p.peek().kind == tkSet: discard p.advance() discard p.match(tkNull) cst.cstOnDelete = "SET NULL" elif p.peek().kind == tkRestrict: discard p.advance() cst.cstOnDelete = "RESTRICT" elif p.peek().kind == tkUpdate: discard p.advance() if p.peek().kind == tkCascade: discard p.advance() cst.cstOnUpdate = "CASCADE" elif p.peek().kind == tkSet: discard p.advance() discard p.match(tkNull) cst.cstOnUpdate = "SET NULL" elif p.peek().kind == tkRestrict: discard p.advance() cst.cstOnUpdate = "RESTRICT" elif p.match(tkUnique): cst.cstType = "unique" if p.peek().kind == tkLParen: discard p.advance() cst.cstColumns.add(p.expect(tkIdent).value) while p.match(tkComma): cst.cstColumns.add(p.expect(tkIdent).value) discard p.expect(tkRParen) elif p.match(tkCheck): cst.cstType = "check" discard p.expect(tkLParen) cst.cstCheck = p.parseExpr() discard p.expect(tkRParen) result.crtConstraints.add(cst) continue # Parse column definition let colName = p.expect(tkIdent).value var colType = "" if p.peek().kind == tkIdent: colType = p.advance().value.toUpper() if p.peek().kind == tkLParen: discard p.advance() let size = p.expect(tkIntLit).value colType &= "(" & size & ")" discard p.expect(tkRParen) elif p.peek().kind == tkVector: discard p.advance() colType = "VECTOR" if p.peek().kind == tkLParen: discard p.advance() let size = p.expect(tkIntLit).value colType &= "(" & size & ")" discard p.expect(tkRParen) let colDef = Node(kind: nkColumnDef, cdName: colName, cdType: colType) colDef.cdConstraints = @[] # SERIAL / BIGSERIAL imply AUTO_INCREMENT if colType in @["SERIAL", "BIGSERIAL"]: colDef.cdAutoIncrement = true if colType == "SERIAL": colDef.cdType = "INTEGER" else: colDef.cdType = "BIGINT" # Parse column constraints while p.peek().kind in {tkPrimary, tkNot, tkNull, tkUnique, tkCheck, tkDefault, tkReferences, tkAutoIncrement, tkOn}: let cst = Node(kind: nkConstraintDef) cst.cstColumns = @[colName]; cst.cstRefColumns = @[] if p.match(tkPrimary): discard p.expect(tkKey) cst.cstType = "pkey" elif p.match(tkAutoIncrement): colDef.cdAutoIncrement = true continue elif p.match(tkNot): discard p.expect(tkNull) cst.cstType = "notnull" elif p.match(tkNull): cst.cstType = "null" elif p.match(tkUnique): cst.cstType = "unique" elif p.match(tkCheck): cst.cstType = "check" discard p.expect(tkLParen) cst.cstCheck = p.parseExpr() discard p.expect(tkRParen) elif p.match(tkDefault): cst.cstType = "default" cst.cstDefault = p.parseExpr() elif p.match(tkReferences): cst.cstType = "fkey" cst.cstRefTable = p.expect(tkIdent).value if p.peek().kind == tkLParen: discard p.advance() cst.cstRefColumns.add(p.expect(tkIdent).value) while p.match(tkComma): cst.cstRefColumns.add(p.expect(tkIdent).value) discard p.expect(tkRParen) if p.peek().kind == tkOn: discard p.advance() if p.peek().kind == tkDelete: discard p.advance() if p.peek().kind == tkCascade: discard p.advance() cst.cstOnDelete = "CASCADE" elif p.peek().kind == tkSet: discard p.advance() discard p.match(tkNull) cst.cstOnDelete = "SET NULL" elif p.peek().kind == tkRestrict: discard p.advance() cst.cstOnDelete = "RESTRICT" elif p.peek().kind == tkUpdate: discard p.advance() if p.peek().kind == tkCascade: discard p.advance() cst.cstOnUpdate = "CASCADE" elif p.peek().kind == tkSet: discard p.advance() discard p.match(tkNull) cst.cstOnUpdate = "SET NULL" elif p.peek().kind == tkRestrict: discard p.advance() cst.cstOnUpdate = "RESTRICT" colDef.cdConstraints.add(cst) result.crtColumns.add(colDef) discard p.expect(tkRParen) proc parseDropTable(p: var Parser): Node = let tok = p.expect(tkDrop) result = Node(kind: nkDropTable, line: tok.line, col: tok.col) if p.peek().kind == tkTable: discard p.advance() if p.peek().kind == tkIdent and p.peek().value.toLower() == "if": discard p.advance() discard p.expect(tkExists) result.drtIfExists = true result.drtName = p.expect(tkIdent).value proc parseAlterTable(p: var Parser): Node = let tok = p.expect(tkAlter) discard p.expect(tkTable) let tableName = p.expect(tkIdent).value # Check for ENABLE/DISABLE ROW LEVEL SECURITY if p.peek().kind == tkEnable: discard p.advance() discard p.expect(tkRow) # ROW discard p.expect(tkIdent) # LEVEL discard p.expect(tkIdent) # SECURITY return Node(kind: nkEnableRLS, erlsTable: tableName, line: tok.line, col: tok.col) elif p.peek().kind == tkDisable: discard p.advance() discard p.expect(tkRow) # ROW discard p.expect(tkIdent) # LEVEL discard p.expect(tkIdent) # SECURITY return Node(kind: nkDisableRLS, drlsTable: tableName, line: tok.line, col: tok.col) result = Node(kind: nkAlterTable, line: tok.line, col: tok.col) result.altName = tableName result.altOps = @[] if p.match(tkAdd): discard p.match(tkColumn) let colName = p.expect(tkIdent).value var colType = "" if p.peek().kind == tkIdent: colType = p.advance().value.toUpper() result.altOps.add(Node(kind: nkColumnDef, cdName: colName, cdType: colType)) proc parseCreateIndex(p: var Parser): Node = let tok = p.expect(tkCreate) var isUnique = false if p.peek().kind == tkUnique: discard p.advance() isUnique = true discard p.expect(tkIndex) var idxName = "" if p.peek().kind == tkIdent and p.peek().value.toLower() != "on": idxName = p.advance().value discard p.match(tkOn) let tableName = p.expect(tkIdent).value discard p.match(tkLParen) var colNames: seq[string] = @[] colNames.add(p.expect(tkIdent).value) while p.match(tkComma): colNames.add(p.expect(tkIdent).value) discard p.match(tkRParen) var idxKind = ikBTree if p.match(tkUsing): let idxMethod = p.expect(tkIdent).value.toLower() if idxMethod == "fts" or idxMethod == "fulltext": idxKind = ikFullText elif idxMethod == "hnsw": idxKind = ikHNSW elif idxMethod == "ivfpq": idxKind = ikIVFPQ result = Node(kind: nkCreateIndex, ciName: idxName, ciTarget: tableName, ciColumns: colNames, ciKind: idxKind, line: tok.line, col: tok.col) proc parseBeginTxn(p: var Parser): Node = let tok = p.expect(tkBegin) result = Node(kind: nkBeginTxn, line: tok.line, col: tok.col) discard p.match(tkIdent) # optional TRANSACTION / WORK result.btxnMode = "" proc parseCommitTxn(p: var Parser): Node = let tok = p.expect(tkCommit) result = Node(kind: nkCommitTxn, line: tok.line, col: tok.col) discard p.match(tkIdent) # optional TRANSACTION / WORK proc parseRollbackTxn(p: var Parser): Node = let tok = p.expect(tkRollback) result = Node(kind: nkRollbackTxn, line: tok.line, col: tok.col) discard p.match(tkIdent) # optional TRANSACTION / WORK proc parseExplain(p: var Parser): Node = let tok = p.expect(tkExplain) result = Node(kind: nkExplainStmt, line: tok.line, col: tok.col) if p.peek().kind == tkIdent and p.peek().value.toLower() == "analyze": discard p.advance() result.expAnalyze = true result.expStmt = p.parseStatement() proc parseCreateView(p: var Parser): Node = let tok = p.expect(tkCreate) var orReplace = false if p.peek().kind == tkIdent and p.peek().value.toLower() == "or": discard p.advance() discard p.expect(tkIdent) # REPLACE orReplace = true discard p.expect(tkView) let name = p.expect(tkIdent).value discard p.expect(tkAs) let query = p.parseSelect() result = Node(kind: nkCreateView, cvName: name, cvQuery: query, cvOrReplace: orReplace, line: tok.line, col: tok.col) proc parseDropView(p: var Parser): Node = let tok = p.expect(tkDrop) discard p.expect(tkView) 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: nkDropView, dvName: name, dvIfExists: ifExists, line: tok.line, col: tok.col) proc parseCreateTrigger(p: var Parser): Node = let tok = p.expect(tkCreate) discard p.expect(tkTrigger) let name = p.expect(tkIdent).value # Parse timing: BEFORE | AFTER | INSTEAD OF var timing = "" let timingTok = p.peek() if timingTok.kind == tkBefore: discard p.advance() timing = "before" elif timingTok.kind == tkAfter: discard p.advance() timing = "after" elif timingTok.kind == tkInstead: discard p.advance() discard p.expect(tkOf) timing = "instead of" else: raise newException(ValueError, "Expected BEFORE, AFTER, or INSTEAD OF in TRIGGER definition") # Parse event: INSERT | UPDATE | DELETE var event = "" let eventTok = p.peek() if eventTok.kind == tkInsert: discard p.advance() event = "INSERT" elif eventTok.kind == tkUpdate: discard p.advance() event = "UPDATE" elif eventTok.kind == tkDelete: discard p.advance() event = "DELETE" else: raise newException(ValueError, "Expected INSERT, UPDATE, or DELETE in TRIGGER definition") discard p.expect(tkOn) let tableName = p.expect(tkIdent).value discard p.expect(tkAs) # Parse action as raw string until end of statement var actionStr = "" while p.pos < p.tokens.len and p.tokens[p.pos].kind != tkSemicolon: actionStr.add(p.tokens[p.pos].value) actionStr.add(" ") discard p.advance() let actionNode = Node(kind: nkStringLit, strVal: actionStr.strip()) result = Node(kind: nkCreateTrigger, trigName: name, trigTable: tableName, trigTiming: timing, trigEvent: event, trigAction: actionNode, line: tok.line, col: tok.col) proc parseDropTrigger(p: var Parser): Node = let tok = p.expect(tkDrop) discard p.expect(tkTrigger) var ifExists = false if p.peek().kind == tkIf: discard p.advance() discard p.expect(tkExists) ifExists = true let name = p.expect(tkIdent).value result = Node(kind: nkDropTrigger, trigDropName: name, trigDropIfExists: ifExists, line: tok.line, col: tok.col) proc parseDropIndex(p: var Parser): Node = let tok = p.expect(tkDrop) discard p.expect(tkIndex) var ifExists = false if p.peek().kind == tkIf: discard p.advance() discard p.expect(tkExists) ifExists = true let name = p.expect(tkIdent).value result = Node(kind: nkDropIndex, diName: name, line: tok.line, col: tok.col) proc parseCreateMigration(p: var Parser): Node = let tok = p.expect(tkCreate) discard p.expect(tkMigration) let name = p.expect(tkIdent).value var upBody = "" var downBody = "" if p.peek().kind == tkAs: # Legacy syntax: CREATE MIGRATION name AS 'body' discard p.advance() upBody = p.expect(tkStringLit).value elif p.peek().kind == tkLBrace: # New syntax: CREATE MIGRATION name { UP: ...; DOWN: ...; } discard p.advance() # { while p.peek().kind != tkRBrace and p.peek().kind != tkEof: var section = "" let sectionTok = p.peek() if sectionTok.kind == tkUp: discard p.advance() section = "up" elif sectionTok.kind == tkDown: discard p.advance() section = "down" elif sectionTok.kind == tkIdent: section = p.expect(tkIdent).value.toLower() else: raise newException(ValueError, "Expected UP or DOWN in migration body, got: " & $sectionTok.kind) discard p.expect(tkColon) var bodyStr = "" while p.peek().kind != tkRBrace and p.peek().kind != tkEof: # Check if next token starts a new section let nextTok = p.peek() if (nextTok.kind == tkUp or nextTok.kind == tkDown or (nextTok.kind == tkIdent and (nextTok.value.toLower() == "up" or nextTok.value.toLower() == "down"))) and p.pos + 1 < p.tokens.len and p.tokens[p.pos + 1].kind == tkColon: break bodyStr.add(p.tokens[p.pos].value) bodyStr.add(" ") discard p.advance() if section == "up": upBody = bodyStr.strip() elif section == "down": downBody = bodyStr.strip() else: raise newException(ValueError, "Expected UP or DOWN in migration body, got: " & section) discard p.expect(tkRBrace) result = Node(kind: nkCreateMigration, cmName: name, cmBody: upBody, cmDownBody: downBody, cmChecksum: "", cmAuthor: "", cmTimestamp: 0, line: tok.line, col: tok.col) proc parseApplyMigration(p: var Parser): Node = let tok = p.expect(tkApply) discard p.expect(tkMigration) let name = p.expect(tkIdent).value result = Node(kind: nkApplyMigration, amName: name, line: tok.line, col: tok.col) proc parseMigrationStatus(p: var Parser): Node = let tok = p.expect(tkMigration) discard p.expect(tkStatus) result = Node(kind: nkMigrationStatus, line: tok.line, col: tok.col) proc parseMigrationUp(p: var Parser): Node = let tok = p.expect(tkMigration) discard p.expect(tkUp) var count = 0 if p.peek().kind == tkIntLit: count = parseInt(p.expect(tkIntLit).value) result = Node(kind: nkMigrationUp, muCount: count, line: tok.line, col: tok.col) proc parseMigrationDown(p: var Parser): Node = let tok = p.expect(tkMigration) discard p.expect(tkDown) var count = 1 if p.peek().kind == tkIntLit: count = parseInt(p.expect(tkIntLit).value) result = Node(kind: nkMigrationDown, mdCount: count, line: tok.line, col: tok.col) proc parseMigrationDryRun(p: var Parser): Node = let tok = p.expect(tkMigration) discard p.expect(tkDryRun) let name = p.expect(tkIdent).value result = Node(kind: nkMigrationDryRun, mdrName: name, line: tok.line, col: tok.col) proc parseCreateUser(p: var Parser): Node = let tok = p.expect(tkCreate) discard p.expect(tkUser) let name = p.expect(tkIdent).value var password = "" var isSuper = false if p.peek().kind == tkWith: discard p.advance() if p.peek().kind == tkIdent and p.peek().value.toLower() == "password": discard p.advance() password = p.expect(tkStringLit).value while p.peek().kind == tkIdent: let opt = p.peek().value.toLower() if opt == "superuser": discard p.advance() isSuper = true elif opt == "nosuperuser": discard p.advance() isSuper = false else: break result = Node(kind: nkCreateUser, cuName: name, cuPassword: password, cuSuperuser: isSuper, line: tok.line, col: tok.col) proc parseDropUser(p: var Parser): Node = let tok = p.expect(tkDrop) discard p.expect(tkUser) var ifExists = false if p.peek().kind == tkIf: discard p.advance() discard p.expect(tkExists) ifExists = true let name = p.expect(tkIdent).value result = Node(kind: nkDropUser, duName: name, duIfExists: ifExists, line: tok.line, col: tok.col) proc parseCreatePolicy(p: var Parser): Node = let tok = p.expect(tkCreate) discard p.expect(tkPolicy) let name = p.expect(tkIdent).value discard p.expect(tkOn) let tableName = p.expect(tkIdent).value var cmd = "ALL" var usingNode: Node = nil var withCheckNode: Node = nil if p.peek().kind == tkFor: discard p.advance() let cmdTok = p.peek() if cmdTok.kind == tkIdent or cmdTok.kind == tkSelect or cmdTok.kind == tkInsert or cmdTok.kind == tkUpdate or cmdTok.kind == tkDelete: discard p.advance() cmd = cmdTok.value.toUpper() else: raise newException(ValueError, "Expected ALL, SELECT, INSERT, UPDATE, or DELETE in POLICY definition") if p.peek().kind == tkUsing: discard p.advance() usingNode = p.parseExpr() if p.peek().kind == tkWith: discard p.advance() discard p.expect(tkCheck) withCheckNode = p.parseExpr() result = Node(kind: nkCreatePolicy, cpName: name, cpTable: tableName, cpCommand: cmd, cpUsing: usingNode, cpWithCheck: withCheckNode, line: tok.line, col: tok.col) proc parseDropPolicy(p: var Parser): Node = let tok = p.expect(tkDrop) discard p.expect(tkPolicy) var ifExists = false if p.peek().kind == tkIf: discard p.advance() discard p.expect(tkExists) ifExists = true let name = p.expect(tkIdent).value discard p.expect(tkOn) let tableName = p.expect(tkIdent).value result = Node(kind: nkDropPolicy, dpName: name, dpTable: tableName, dpIfExists: ifExists, line: tok.line, col: tok.col) proc parseGrant(p: var Parser): Node = let tok = p.expect(tkGrant) var priv = "" let privTok = p.peek() if privTok.kind == tkIdent or privTok.kind == tkSelect or privTok.kind == tkInsert or privTok.kind == tkUpdate or privTok.kind == tkDelete: discard p.advance() priv = privTok.value.toUpper() else: raise newException(ValueError, "Expected privilege in GRANT") discard p.expect(tkOn) let tableName = p.expect(tkIdent).value discard p.expect(tkTo) let grantee = p.expect(tkIdent).value result = Node(kind: nkGrant, grPrivilege: priv, grTable: tableName, grGrantee: grantee, line: tok.line, col: tok.col) proc parseRevoke(p: var Parser): Node = let tok = p.expect(tkRevoke) var priv = "" let privTok = p.peek() if privTok.kind == tkIdent or privTok.kind == tkSelect or privTok.kind == tkInsert or privTok.kind == tkUpdate or privTok.kind == tkDelete: discard p.advance() priv = privTok.value.toUpper() else: raise newException(ValueError, "Expected privilege in REVOKE") discard p.expect(tkOn) let tableName = p.expect(tkIdent).value discard p.expect(tkFrom) let grantee = p.expect(tkIdent).value result = Node(kind: nkRevoke, rvPrivilege: priv, rvTable: tableName, rvGrantee: grantee, line: tok.line, col: tok.col) proc parseSetVar(p: var Parser): Node = let tok = p.expect(tkSet) var varName = p.expect(tkIdent).value while p.peek().kind == tkDot: discard p.advance() varName.add(".") varName.add(p.expect(tkIdent).value) if p.match(tkEq) or p.match(tkTo): discard let valTok = p.peek() var valStr = "" case valTok.kind of tkStringLit: valStr = valTok.value discard p.advance() of tkIntLit: valStr = valTok.value discard p.advance() of tkFloatLit: valStr = valTok.value discard p.advance() of tkIdent: valStr = valTok.value discard p.advance() else: raise newException(ValueError, "Expected value after SET " & varName) 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() of tkInsert: p.parseInsert() of tkUpdate: p.parseUpdate() of tkDelete: p.parseDelete() of tkMerge: p.parseMerge() of tkCreate: if p.pos + 1 < p.tokens.len: let next = p.tokens[p.pos + 1] if next.kind == tkTable or (next.kind == tkIdent and next.value.toLower() == "if"): p.parseCreateTable() elif next.kind == tkIndex or next.kind == tkUnique: p.parseCreateIndex() elif next.kind == tkView or (next.kind == tkIdent and next.value.toLower() == "or"): p.parseCreateView() elif next.kind == tkTrigger: p.parseCreateTrigger() elif next.kind == tkMigration: p.parseCreateMigration() elif next.kind == tkUser: p.parseCreateUser() elif next.kind == tkPolicy: p.parseCreatePolicy() elif next.kind == tkGraph: p.parseCreateGraph() else: p.parseCreateType() else: p.parseCreateType() of tkDrop: if p.pos + 1 < p.tokens.len: let next = p.tokens[p.pos + 1] if next.kind == tkTable: p.parseDropTable() elif next.kind == tkView: p.parseDropView() elif next.kind == tkTrigger: p.parseDropTrigger() elif next.kind == tkIndex: p.parseDropIndex() elif next.kind == tkUser: 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) else: let tok = p.advance() Node(kind: nkNullLit, line: tok.line, col: tok.col) of tkAlter: if p.pos + 1 < p.tokens.len and p.tokens[p.pos + 1].kind == tkTable: p.parseAlterTable() else: let tok = p.advance() Node(kind: nkNullLit, line: tok.line, col: tok.col) of tkGrant: p.parseGrant() of tkRevoke: p.parseRevoke() of tkSet: p.parseSetVar() of tkApply: if p.pos + 1 < p.tokens.len and p.tokens[p.pos + 1].kind == tkMigration: p.parseApplyMigration() else: let tok = p.advance() Node(kind: nkNullLit, line: tok.line, col: tok.col) of tkMigration: if p.pos + 1 < p.tokens.len: let next = p.tokens[p.pos + 1] if next.kind == tkStatus: p.parseMigrationStatus() elif next.kind == tkUp: p.parseMigrationUp() elif next.kind == tkDown: p.parseMigrationDown() elif next.kind == tkDryRun: p.parseMigrationDryRun() else: let tok = p.advance() Node(kind: nkNullLit, line: tok.line, col: tok.col) else: let tok = p.advance() Node(kind: nkNullLit, line: tok.line, col: tok.col) of tkBegin: p.parseBeginTxn() of tkCommit: p.parseCommitTxn() of tkRollback: p.parseRollbackTxn() of tkExplain: p.parseExplain() of tkRecover: let tok = p.advance() discard p.expect(tkTo) discard p.expect(tkTimestamp) let tsLit = p.expect(tkStringLit) Node(kind: nkRecoverToTimestamp, recoverTimestamp: tsLit.value, line: tok.line, col: tok.col) else: let tok = p.advance() Node(kind: nkNullLit, line: tok.line, col: tok.col) proc parse*(tokens: seq[Token]): Node = var parser = newParser(tokens) result = Node(kind: nkStatementList) while parser.peek().kind != tkEof: result.stmts.add(parser.parseStatement()) discard parser.match(tkSemicolon) proc parse*(input: string): Node = let tokens = tokenize(input) parse(tokens)