967c0855a5
Critical fixes: - Composite PK: execInsert + validateConstraints use all PK columns - nl_to_sql: non-SELECT SQL no longer executed directly during validation - FK check: removed O(N) scanMemTable fallback, uses db.get() with SSTables - exprToSql: nkIdent wrapped in quotes to prevent SQL injection - restoreSchema: try/except around tokenize/parse for crash resilience - recovery.nim: lastTxnId tracking + putUnsafe/deleteUnsafe without WAL - SCRAM: verifyClientProof length check + DefaultIterationCount restored Storage fixes: - lsm.nim: SSTable sort order fixed (ascending), close() flushes all memtables - compaction.nim: tombstones preserved during compaction - wal.nim: header written for empty existing files, readEntries checks magic - btree.nim: B+ tree leaf split keeps boundary key - bloom.nim: deserialize raises on short data - mmap.nim: bounds checks for adviseWillNeed/DontNeed + posix.close() Protocol fixes: - zerocopy.nim: readString bounds check - wire.nim: deserializeValue 32-bit underflow check - auth.nim: JWT JSON escaping for claims - server.nim: readUint32BE bounds check + specific exception handling - raft.nim: readData checks + specific exception handling Tests: - Added Composite Primary Key test suite (4 tests) Build: 0 warnings, 0 errors
1738 lines
60 KiB
Nim
1738 lines
60 KiB
Nim
## 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)
|