1#
2#
3#            Nim's Runtime Library
4#        (c) Copyright 2009 Andreas Rumpf
5#
6#    See the file "copying.txt", included in this
7#    distribution, for details about the copyright.
8#
9
10## The `parsesql` module implements a high performance SQL file
11## parser. It parses PostgreSQL syntax and the SQL ANSI standard.
12##
13## Unstable API.
14
15import strutils, lexbase
16import std/private/decode_helpers
17
18# ------------------- scanner -------------------------------------------------
19
20type
21  TokKind = enum            ## enumeration of all SQL tokens
22    tkInvalid,              ## invalid token
23    tkEof,                  ## end of file reached
24    tkIdentifier,           ## abc
25    tkQuotedIdentifier,     ## "abc"
26    tkStringConstant,       ## 'abc'
27    tkEscapeConstant,       ## e'abc'
28    tkDollarQuotedConstant, ## $tag$abc$tag$
29    tkBitStringConstant,    ## B'00011'
30    tkHexStringConstant,    ## x'00011'
31    tkInteger,
32    tkNumeric,
33    tkOperator,             ## + - * / < > = ~ ! @ # % ^ & | ` ?
34    tkSemicolon,            ## ';'
35    tkColon,                ## ':'
36    tkComma,                ## ','
37    tkParLe,                ## '('
38    tkParRi,                ## ')'
39    tkBracketLe,            ## '['
40    tkBracketRi,            ## ']'
41    tkDot                   ## '.'
42
43  Token = object    # a token
44    kind: TokKind   # the type of the token
45    literal: string # the parsed (string) literal
46
47  SqlLexer* = object of BaseLexer ## the parser object.
48    filename: string
49
50const
51  tokKindToStr: array[TokKind, string] = [
52    "invalid", "[EOF]", "identifier", "quoted identifier", "string constant",
53    "escape string constant", "dollar quoted constant", "bit string constant",
54    "hex string constant", "integer constant", "numeric constant", "operator",
55    ";", ":", ",", "(", ")", "[", "]", "."
56  ]
57
58  reservedKeywords = @[
59    # statements
60    "select", "from", "where", "group", "limit", "having",
61    # functions
62    "count",
63  ]
64
65proc close(L: var SqlLexer) =
66  lexbase.close(L)
67
68proc getColumn(L: SqlLexer): int =
69  ## get the current column the parser has arrived at.
70  result = getColNumber(L, L.bufpos)
71
72proc getLine(L: SqlLexer): int =
73  result = L.lineNumber
74
75proc handleOctChar(c: var SqlLexer, xi: var int) =
76  if c.buf[c.bufpos] in {'0'..'7'}:
77    xi = (xi shl 3) or (ord(c.buf[c.bufpos]) - ord('0'))
78    inc(c.bufpos)
79
80proc getEscapedChar(c: var SqlLexer, tok: var Token) =
81  inc(c.bufpos)
82  case c.buf[c.bufpos]
83  of 'n', 'N':
84    add(tok.literal, '\L')
85    inc(c.bufpos)
86  of 'r', 'R', 'c', 'C':
87    add(tok.literal, '\c')
88    inc(c.bufpos)
89  of 'l', 'L':
90    add(tok.literal, '\L')
91    inc(c.bufpos)
92  of 'f', 'F':
93    add(tok.literal, '\f')
94    inc(c.bufpos)
95  of 'e', 'E':
96    add(tok.literal, '\e')
97    inc(c.bufpos)
98  of 'a', 'A':
99    add(tok.literal, '\a')
100    inc(c.bufpos)
101  of 'b', 'B':
102    add(tok.literal, '\b')
103    inc(c.bufpos)
104  of 'v', 'V':
105    add(tok.literal, '\v')
106    inc(c.bufpos)
107  of 't', 'T':
108    add(tok.literal, '\t')
109    inc(c.bufpos)
110  of '\'', '\"':
111    add(tok.literal, c.buf[c.bufpos])
112    inc(c.bufpos)
113  of '\\':
114    add(tok.literal, '\\')
115    inc(c.bufpos)
116  of 'x', 'X':
117    inc(c.bufpos)
118    var xi = 0
119    if handleHexChar(c.buf[c.bufpos], xi):
120      inc(c.bufpos)
121      if handleHexChar(c.buf[c.bufpos], xi):
122        inc(c.bufpos)
123    add(tok.literal, chr(xi))
124  of '0'..'7':
125    var xi = 0
126    handleOctChar(c, xi)
127    handleOctChar(c, xi)
128    handleOctChar(c, xi)
129    if (xi <= 255): add(tok.literal, chr(xi))
130    else: tok.kind = tkInvalid
131  else: tok.kind = tkInvalid
132
133proc handleCRLF(c: var SqlLexer, pos: int): int =
134  case c.buf[pos]
135  of '\c': result = lexbase.handleCR(c, pos)
136  of '\L': result = lexbase.handleLF(c, pos)
137  else: result = pos
138
139proc skip(c: var SqlLexer) =
140  var pos = c.bufpos
141  var nested = 0
142  while true:
143    case c.buf[pos]
144    of ' ', '\t':
145      inc(pos)
146    of '-':
147      if c.buf[pos+1] == '-':
148        while not (c.buf[pos] in {'\c', '\L', lexbase.EndOfFile}): inc(pos)
149      else:
150        break
151    of '/':
152      if c.buf[pos+1] == '*':
153        inc(pos, 2)
154        while true:
155          case c.buf[pos]
156          of '\0': break
157          of '\c', '\L':
158            pos = handleCRLF(c, pos)
159          of '*':
160            if c.buf[pos+1] == '/':
161              inc(pos, 2)
162              if nested <= 0: break
163              dec(nested)
164            else:
165              inc(pos)
166          of '/':
167            if c.buf[pos+1] == '*':
168              inc(pos, 2)
169              inc(nested)
170            else:
171              inc(pos)
172          else: inc(pos)
173      else: break
174    of '\c', '\L':
175      pos = handleCRLF(c, pos)
176    else:
177      break # EndOfFile also leaves the loop
178  c.bufpos = pos
179
180proc getString(c: var SqlLexer, tok: var Token, kind: TokKind) =
181  var pos = c.bufpos + 1
182  tok.kind = kind
183  block parseLoop:
184    while true:
185      while true:
186        var ch = c.buf[pos]
187        if ch == '\'':
188          if c.buf[pos+1] == '\'':
189            inc(pos, 2)
190            add(tok.literal, '\'')
191          else:
192            inc(pos)
193            break
194        elif ch in {'\c', '\L', lexbase.EndOfFile}:
195          tok.kind = tkInvalid
196          break parseLoop
197        elif (ch == '\\') and kind == tkEscapeConstant:
198          c.bufpos = pos
199          getEscapedChar(c, tok)
200          pos = c.bufpos
201        else:
202          add(tok.literal, ch)
203          inc(pos)
204      c.bufpos = pos
205      var line = c.lineNumber
206      skip(c)
207      if c.lineNumber > line:
208        # a new line whitespace has been parsed, so we check if the string
209        # continues after the whitespace:
210        pos = c.bufpos
211        if c.buf[pos] == '\'': inc(pos)
212        else: break parseLoop
213      else: break parseLoop
214  c.bufpos = pos
215
216proc getDollarString(c: var SqlLexer, tok: var Token) =
217  var pos = c.bufpos + 1
218  tok.kind = tkDollarQuotedConstant
219  var tag = "$"
220  while c.buf[pos] in IdentChars:
221    add(tag, c.buf[pos])
222    inc(pos)
223  if c.buf[pos] == '$': inc(pos)
224  else:
225    tok.kind = tkInvalid
226    return
227  while true:
228    case c.buf[pos]
229    of '\c', '\L':
230      pos = handleCRLF(c, pos)
231      add(tok.literal, "\L")
232    of '\0':
233      tok.kind = tkInvalid
234      break
235    of '$':
236      inc(pos)
237      var tag2 = "$"
238      while c.buf[pos] in IdentChars:
239        add(tag2, c.buf[pos])
240        inc(pos)
241      if c.buf[pos] == '$': inc(pos)
242      if tag2 == tag: break
243      add(tok.literal, tag2)
244      add(tok.literal, '$')
245    else:
246      add(tok.literal, c.buf[pos])
247      inc(pos)
248  c.bufpos = pos
249
250proc getSymbol(c: var SqlLexer, tok: var Token) =
251  var pos = c.bufpos
252  while true:
253    add(tok.literal, c.buf[pos])
254    inc(pos)
255    if c.buf[pos] notin {'a'..'z', 'A'..'Z', '0'..'9', '_', '$',
256        '\128'..'\255'}:
257      break
258  c.bufpos = pos
259  tok.kind = tkIdentifier
260
261proc getQuotedIdentifier(c: var SqlLexer, tok: var Token, quote = '\"') =
262  var pos = c.bufpos + 1
263  tok.kind = tkQuotedIdentifier
264  while true:
265    var ch = c.buf[pos]
266    if ch == quote:
267      if c.buf[pos+1] == quote:
268        inc(pos, 2)
269        add(tok.literal, quote)
270      else:
271        inc(pos)
272        break
273    elif ch in {'\c', '\L', lexbase.EndOfFile}:
274      tok.kind = tkInvalid
275      break
276    else:
277      add(tok.literal, ch)
278      inc(pos)
279  c.bufpos = pos
280
281proc getBitHexString(c: var SqlLexer, tok: var Token, validChars: set[char]) =
282  var pos = c.bufpos + 1
283  block parseLoop:
284    while true:
285      while true:
286        var ch = c.buf[pos]
287        if ch in validChars:
288          add(tok.literal, ch)
289          inc(pos)
290        elif ch == '\'':
291          inc(pos)
292          break
293        else:
294          tok.kind = tkInvalid
295          break parseLoop
296      c.bufpos = pos
297      var line = c.lineNumber
298      skip(c)
299      if c.lineNumber > line:
300        # a new line whitespace has been parsed, so we check if the string
301        # continues after the whitespace:
302        pos = c.bufpos
303        if c.buf[pos] == '\'': inc(pos)
304        else: break parseLoop
305      else: break parseLoop
306  c.bufpos = pos
307
308proc getNumeric(c: var SqlLexer, tok: var Token) =
309  tok.kind = tkInteger
310  var pos = c.bufpos
311  while c.buf[pos] in Digits:
312    add(tok.literal, c.buf[pos])
313    inc(pos)
314  if c.buf[pos] == '.':
315    tok.kind = tkNumeric
316    add(tok.literal, c.buf[pos])
317    inc(pos)
318    while c.buf[pos] in Digits:
319      add(tok.literal, c.buf[pos])
320      inc(pos)
321  if c.buf[pos] in {'E', 'e'}:
322    tok.kind = tkNumeric
323    add(tok.literal, c.buf[pos])
324    inc(pos)
325    if c.buf[pos] == '+':
326      inc(pos)
327    elif c.buf[pos] == '-':
328      add(tok.literal, c.buf[pos])
329      inc(pos)
330    if c.buf[pos] in Digits:
331      while c.buf[pos] in Digits:
332        add(tok.literal, c.buf[pos])
333        inc(pos)
334    else:
335      tok.kind = tkInvalid
336  c.bufpos = pos
337
338proc getOperator(c: var SqlLexer, tok: var Token) =
339  const operators = {'+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%',
340                     '^', '&', '|', '`', '?'}
341  tok.kind = tkOperator
342  var pos = c.bufpos
343  var trailingPlusMinus = false
344  while true:
345    case c.buf[pos]
346    of '-':
347      if c.buf[pos] == '-': break
348      if not trailingPlusMinus and c.buf[pos+1] notin operators and
349           tok.literal.len > 0: break
350    of '/':
351      if c.buf[pos] == '*': break
352    of '~', '!', '@', '#', '%', '^', '&', '|', '`', '?':
353      trailingPlusMinus = true
354    of '+':
355      if not trailingPlusMinus and c.buf[pos+1] notin operators and
356           tok.literal.len > 0: break
357    of '*', '<', '>', '=': discard
358    else: break
359    add(tok.literal, c.buf[pos])
360    inc(pos)
361  c.bufpos = pos
362
363proc getTok(c: var SqlLexer, tok: var Token) =
364  tok.kind = tkInvalid
365  setLen(tok.literal, 0)
366  skip(c)
367  case c.buf[c.bufpos]
368  of ';':
369    tok.kind = tkSemicolon
370    inc(c.bufpos)
371    add(tok.literal, ';')
372  of ',':
373    tok.kind = tkComma
374    inc(c.bufpos)
375    add(tok.literal, ',')
376  of ':':
377    tok.kind = tkColon
378    inc(c.bufpos)
379    add(tok.literal, ':')
380  of 'e', 'E':
381    if c.buf[c.bufpos + 1] == '\'':
382      inc(c.bufpos)
383      getString(c, tok, tkEscapeConstant)
384    else:
385      getSymbol(c, tok)
386  of 'b', 'B':
387    if c.buf[c.bufpos + 1] == '\'':
388      tok.kind = tkBitStringConstant
389      getBitHexString(c, tok, {'0'..'1'})
390    else:
391      getSymbol(c, tok)
392  of 'x', 'X':
393    if c.buf[c.bufpos + 1] == '\'':
394      tok.kind = tkHexStringConstant
395      getBitHexString(c, tok, {'a'..'f', 'A'..'F', '0'..'9'})
396    else:
397      getSymbol(c, tok)
398  of '$': getDollarString(c, tok)
399  of '[':
400    tok.kind = tkBracketLe
401    inc(c.bufpos)
402    add(tok.literal, '[')
403  of ']':
404    tok.kind = tkBracketRi
405    inc(c.bufpos)
406    add(tok.literal, ']')
407  of '(':
408    tok.kind = tkParLe
409    inc(c.bufpos)
410    add(tok.literal, '(')
411  of ')':
412    tok.kind = tkParRi
413    inc(c.bufpos)
414    add(tok.literal, ')')
415  of '.':
416    if c.buf[c.bufpos + 1] in Digits:
417      getNumeric(c, tok)
418    else:
419      tok.kind = tkDot
420      inc(c.bufpos)
421    add(tok.literal, '.')
422  of '0'..'9': getNumeric(c, tok)
423  of '\'': getString(c, tok, tkStringConstant)
424  of '"': getQuotedIdentifier(c, tok, '"')
425  of '`': getQuotedIdentifier(c, tok, '`')
426  of lexbase.EndOfFile:
427    tok.kind = tkEof
428    tok.literal = "[EOF]"
429  of 'a', 'c', 'd', 'f'..'w', 'y', 'z', 'A', 'C', 'D', 'F'..'W', 'Y', 'Z', '_',
430     '\128'..'\255':
431    getSymbol(c, tok)
432  of '+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%',
433     '^', '&', '|', '?':
434    getOperator(c, tok)
435  else:
436    add(tok.literal, c.buf[c.bufpos])
437    inc(c.bufpos)
438
439proc errorStr(L: SqlLexer, msg: string): string =
440  result = "$1($2, $3) Error: $4" % [L.filename, $getLine(L), $getColumn(L), msg]
441
442
443# ----------------------------- parser ----------------------------------------
444
445# Operator/Element   Associativity  Description
446# .                  left           table/column name separator
447# ::                 left           PostgreSQL-style typecast
448# [ ]                left           array element selection
449# -                  right          unary minus
450# ^                  left           exponentiation
451# * / %              left           multiplication, division, modulo
452# + -                left           addition, subtraction
453# IS                                IS TRUE, IS FALSE, IS UNKNOWN, IS NULL
454# ISNULL                            test for null
455# NOTNULL                           test for not null
456# (any other)        left           all other native and user-defined oprs
457# IN                                set membership
458# BETWEEN                           range containment
459# OVERLAPS                          time interval overlap
460# LIKE ILIKE SIMILAR                string pattern matching
461# < >                               less than, greater than
462# =                  right          equality, assignment
463# NOT                right          logical negation
464# AND                left           logical conjunction
465# OR                 left           logical disjunction
466
467type
468  SqlNodeKind* = enum ## kind of SQL abstract syntax tree
469    nkNone,
470    nkIdent,
471    nkQuotedIdent,
472    nkStringLit,
473    nkBitStringLit,
474    nkHexStringLit,
475    nkIntegerLit,
476    nkNumericLit,
477    nkPrimaryKey,
478    nkForeignKey,
479    nkNotNull,
480    nkNull,
481
482    nkStmtList,
483    nkDot,
484    nkDotDot,
485    nkPrefix,
486    nkInfix,
487    nkCall,
488    nkPrGroup,
489    nkColumnReference,
490    nkReferences,
491    nkDefault,
492    nkCheck,
493    nkConstraint,
494    nkUnique,
495    nkIdentity,
496    nkColumnDef,      ## name, datatype, constraints
497    nkInsert,
498    nkUpdate,
499    nkDelete,
500    nkSelect,
501    nkSelectDistinct,
502    nkSelectColumns,
503    nkSelectPair,
504    nkAsgn,
505    nkFrom,
506    nkFromItemPair,
507    nkGroup,
508    nkLimit,
509    nkHaving,
510    nkOrder,
511    nkJoin,
512    nkDesc,
513    nkUnion,
514    nkIntersect,
515    nkExcept,
516    nkColumnList,
517    nkValueList,
518    nkWhere,
519    nkCreateTable,
520    nkCreateTableIfNotExists,
521    nkCreateType,
522    nkCreateTypeIfNotExists,
523    nkCreateIndex,
524    nkCreateIndexIfNotExists,
525    nkEnumDef
526
527const
528  LiteralNodes = {
529    nkIdent, nkQuotedIdent, nkStringLit, nkBitStringLit, nkHexStringLit,
530    nkIntegerLit, nkNumericLit
531  }
532
533type
534  SqlParseError* = object of ValueError ## Invalid SQL encountered
535  SqlNode* = ref SqlNodeObj ## an SQL abstract syntax tree node
536  SqlNodeObj* = object      ## an SQL abstract syntax tree node
537    case kind*: SqlNodeKind ## kind of syntax tree
538    of LiteralNodes:
539      strVal*: string       ## AST leaf: the identifier, numeric literal
540                            ## string literal, etc.
541    else:
542      sons*: seq[SqlNode]   ## the node's children
543
544  SqlParser* = object of SqlLexer ## SQL parser object
545    tok: Token
546
547proc newNode*(k: SqlNodeKind): SqlNode =
548  when defined(js): # bug #14117
549    case k
550    of LiteralNodes:
551      result = SqlNode(kind: k, strVal: "")
552    else:
553      result = SqlNode(kind: k, sons: @[])
554  else:
555    result = SqlNode(kind: k)
556
557proc newNode*(k: SqlNodeKind, s: string): SqlNode =
558  result = SqlNode(kind: k)
559  result.strVal = s
560
561proc newNode*(k: SqlNodeKind, sons: seq[SqlNode]): SqlNode =
562  result = SqlNode(kind: k)
563  result.sons = sons
564
565proc len*(n: SqlNode): int =
566  if n.kind in LiteralNodes:
567    result = 0
568  else:
569    result = n.sons.len
570
571proc `[]`*(n: SqlNode; i: int): SqlNode = n.sons[i]
572proc `[]`*(n: SqlNode; i: BackwardsIndex): SqlNode = n.sons[n.len - int(i)]
573
574proc add*(father, n: SqlNode) =
575  add(father.sons, n)
576
577proc getTok(p: var SqlParser) =
578  getTok(p, p.tok)
579
580proc sqlError(p: SqlParser, msg: string) =
581  var e: ref SqlParseError
582  new(e)
583  e.msg = errorStr(p, msg)
584  raise e
585
586proc isKeyw(p: SqlParser, keyw: string): bool =
587  result = p.tok.kind == tkIdentifier and
588           cmpIgnoreCase(p.tok.literal, keyw) == 0
589
590proc isOpr(p: SqlParser, opr: string): bool =
591  result = p.tok.kind == tkOperator and
592           cmpIgnoreCase(p.tok.literal, opr) == 0
593
594proc optKeyw(p: var SqlParser, keyw: string) =
595  if p.tok.kind == tkIdentifier and cmpIgnoreCase(p.tok.literal, keyw) == 0:
596    getTok(p)
597
598proc expectIdent(p: SqlParser) =
599  if p.tok.kind != tkIdentifier and p.tok.kind != tkQuotedIdentifier:
600    sqlError(p, "identifier expected")
601
602proc expect(p: SqlParser, kind: TokKind) =
603  if p.tok.kind != kind:
604    sqlError(p, tokKindToStr[kind] & " expected")
605
606proc eat(p: var SqlParser, kind: TokKind) =
607  if p.tok.kind == kind:
608    getTok(p)
609  else:
610    sqlError(p, tokKindToStr[kind] & " expected")
611
612proc eat(p: var SqlParser, keyw: string) =
613  if isKeyw(p, keyw):
614    getTok(p)
615  else:
616    sqlError(p, keyw.toUpperAscii() & " expected")
617
618proc opt(p: var SqlParser, kind: TokKind) =
619  if p.tok.kind == kind: getTok(p)
620
621proc parseDataType(p: var SqlParser): SqlNode =
622  if isKeyw(p, "enum"):
623    result = newNode(nkEnumDef)
624    getTok(p)
625    if p.tok.kind == tkParLe:
626      getTok(p)
627      result.add(newNode(nkStringLit, p.tok.literal))
628      getTok(p)
629      while p.tok.kind == tkComma:
630        getTok(p)
631        result.add(newNode(nkStringLit, p.tok.literal))
632        getTok(p)
633      eat(p, tkParRi)
634  else:
635    expectIdent(p)
636    result = newNode(nkIdent, p.tok.literal)
637    getTok(p)
638    # ignore (12, 13) part:
639    if p.tok.kind == tkParLe:
640      getTok(p)
641      expect(p, tkInteger)
642      getTok(p)
643      while p.tok.kind == tkComma:
644        getTok(p)
645        expect(p, tkInteger)
646        getTok(p)
647      eat(p, tkParRi)
648
649proc getPrecedence(p: SqlParser): int =
650  if isOpr(p, "*") or isOpr(p, "/") or isOpr(p, "%"):
651    result = 6
652  elif isOpr(p, "+") or isOpr(p, "-"):
653    result = 5
654  elif isOpr(p, "=") or isOpr(p, "<") or isOpr(p, ">") or isOpr(p, ">=") or
655       isOpr(p, "<=") or isOpr(p, "<>") or isOpr(p, "!=") or isKeyw(p, "is") or
656       isKeyw(p, "like") or isKeyw(p, "in"):
657    result = 4
658  elif isKeyw(p, "and"):
659    result = 3
660  elif isKeyw(p, "or"):
661    result = 2
662  elif isKeyw(p, "between"):
663    result = 1
664  elif p.tok.kind == tkOperator:
665    # user-defined operator:
666    result = 0
667  else:
668    result = - 1
669
670proc parseExpr(p: var SqlParser): SqlNode {.gcsafe.}
671proc parseSelect(p: var SqlParser): SqlNode {.gcsafe.}
672
673proc identOrLiteral(p: var SqlParser): SqlNode =
674  case p.tok.kind
675  of tkQuotedIdentifier:
676    result = newNode(nkQuotedIdent, p.tok.literal)
677    getTok(p)
678  of tkIdentifier:
679    result = newNode(nkIdent, p.tok.literal)
680    getTok(p)
681  of tkStringConstant, tkEscapeConstant, tkDollarQuotedConstant:
682    result = newNode(nkStringLit, p.tok.literal)
683    getTok(p)
684  of tkBitStringConstant:
685    result = newNode(nkBitStringLit, p.tok.literal)
686    getTok(p)
687  of tkHexStringConstant:
688    result = newNode(nkHexStringLit, p.tok.literal)
689    getTok(p)
690  of tkInteger:
691    result = newNode(nkIntegerLit, p.tok.literal)
692    getTok(p)
693  of tkNumeric:
694    result = newNode(nkNumericLit, p.tok.literal)
695    getTok(p)
696  of tkParLe:
697    getTok(p)
698    result = newNode(nkPrGroup)
699    while true:
700      result.add(parseExpr(p))
701      if p.tok.kind != tkComma: break
702      getTok(p)
703    eat(p, tkParRi)
704  else:
705    if p.tok.literal == "*":
706      result = newNode(nkIdent, p.tok.literal)
707      getTok(p)
708    else:
709      sqlError(p, "expression expected")
710      getTok(p) # we must consume a token here to prevent endless loops!
711
712proc primary(p: var SqlParser): SqlNode =
713  if (p.tok.kind == tkOperator and (p.tok.literal == "+" or p.tok.literal ==
714      "-")) or isKeyw(p, "not"):
715    result = newNode(nkPrefix)
716    result.add(newNode(nkIdent, p.tok.literal))
717    getTok(p)
718    result.add(primary(p))
719    return
720  result = identOrLiteral(p)
721  while true:
722    case p.tok.kind
723    of tkParLe:
724      var a = result
725      result = newNode(nkCall)
726      result.add(a)
727      getTok(p)
728      while p.tok.kind != tkParRi:
729        result.add(parseExpr(p))
730        if p.tok.kind == tkComma: getTok(p)
731        else: break
732      eat(p, tkParRi)
733    of tkDot:
734      getTok(p)
735      var a = result
736      if p.tok.kind == tkDot:
737        getTok(p)
738        result = newNode(nkDotDot)
739      else:
740        result = newNode(nkDot)
741      result.add(a)
742      if isOpr(p, "*"):
743        result.add(newNode(nkIdent, "*"))
744      elif p.tok.kind in {tkIdentifier, tkQuotedIdentifier}:
745        result.add(newNode(nkIdent, p.tok.literal))
746      else:
747        sqlError(p, "identifier expected")
748      getTok(p)
749    else: break
750
751proc lowestExprAux(p: var SqlParser, v: var SqlNode, limit: int): int =
752  var
753    v2, node, opNode: SqlNode
754  v = primary(p) # expand while operators have priorities higher than 'limit'
755  var opPred = getPrecedence(p)
756  result = opPred
757  while opPred > limit:
758    node = newNode(nkInfix)
759    opNode = newNode(nkIdent, p.tok.literal.toLowerAscii())
760    getTok(p)
761    result = lowestExprAux(p, v2, opPred)
762    node.add(opNode)
763    node.add(v)
764    node.add(v2)
765    v = node
766    opPred = getPrecedence(p)
767
768proc parseExpr(p: var SqlParser): SqlNode =
769  discard lowestExprAux(p, result, - 1)
770
771proc parseTableName(p: var SqlParser): SqlNode =
772  expectIdent(p)
773  result = primary(p)
774
775proc parseColumnReference(p: var SqlParser): SqlNode =
776  result = parseTableName(p)
777  if p.tok.kind == tkParLe:
778    getTok(p)
779    var a = result
780    result = newNode(nkColumnReference)
781    result.add(a)
782    result.add(parseTableName(p))
783    while p.tok.kind == tkComma:
784      getTok(p)
785      result.add(parseTableName(p))
786    eat(p, tkParRi)
787
788proc parseCheck(p: var SqlParser): SqlNode =
789  getTok(p)
790  result = newNode(nkCheck)
791  result.add(parseExpr(p))
792
793proc parseConstraint(p: var SqlParser): SqlNode =
794  getTok(p)
795  result = newNode(nkConstraint)
796  expectIdent(p)
797  result.add(newNode(nkIdent, p.tok.literal))
798  getTok(p)
799  optKeyw(p, "check")
800  result.add(parseExpr(p))
801
802proc parseParIdentList(p: var SqlParser, father: SqlNode) =
803  eat(p, tkParLe)
804  while true:
805    expectIdent(p)
806    father.add(newNode(nkIdent, p.tok.literal))
807    getTok(p)
808    if p.tok.kind != tkComma: break
809    getTok(p)
810  eat(p, tkParRi)
811
812proc parseColumnConstraints(p: var SqlParser, result: SqlNode) =
813  while true:
814    if isKeyw(p, "default"):
815      getTok(p)
816      var n = newNode(nkDefault)
817      n.add(parseExpr(p))
818      result.add(n)
819    elif isKeyw(p, "references"):
820      getTok(p)
821      var n = newNode(nkReferences)
822      n.add(parseColumnReference(p))
823      result.add(n)
824    elif isKeyw(p, "not"):
825      getTok(p)
826      eat(p, "null")
827      result.add(newNode(nkNotNull))
828    elif isKeyw(p, "null"):
829      getTok(p)
830      result.add(newNode(nkNull))
831    elif isKeyw(p, "identity"):
832      getTok(p)
833      result.add(newNode(nkIdentity))
834    elif isKeyw(p, "primary"):
835      getTok(p)
836      eat(p, "key")
837      result.add(newNode(nkPrimaryKey))
838    elif isKeyw(p, "check"):
839      result.add(parseCheck(p))
840    elif isKeyw(p, "constraint"):
841      result.add(parseConstraint(p))
842    elif isKeyw(p, "unique"):
843      getTok(p)
844      result.add(newNode(nkUnique))
845    else:
846      break
847
848proc parseColumnDef(p: var SqlParser): SqlNode =
849  expectIdent(p)
850  result = newNode(nkColumnDef)
851  result.add(newNode(nkIdent, p.tok.literal))
852  getTok(p)
853  result.add(parseDataType(p))
854  parseColumnConstraints(p, result)
855
856proc parseIfNotExists(p: var SqlParser, k: SqlNodeKind): SqlNode =
857  getTok(p)
858  if isKeyw(p, "if"):
859    getTok(p)
860    eat(p, "not")
861    eat(p, "exists")
862    result = newNode(succ(k))
863  else:
864    result = newNode(k)
865
866proc parseTableConstraint(p: var SqlParser): SqlNode =
867  if isKeyw(p, "primary"):
868    getTok(p)
869    eat(p, "key")
870    result = newNode(nkPrimaryKey)
871    parseParIdentList(p, result)
872  elif isKeyw(p, "foreign"):
873    getTok(p)
874    eat(p, "key")
875    result = newNode(nkForeignKey)
876    parseParIdentList(p, result)
877    eat(p, "references")
878    var m = newNode(nkReferences)
879    m.add(parseColumnReference(p))
880    result.add(m)
881  elif isKeyw(p, "unique"):
882    getTok(p)
883    eat(p, "key")
884    result = newNode(nkUnique)
885    parseParIdentList(p, result)
886  elif isKeyw(p, "check"):
887    result = parseCheck(p)
888  elif isKeyw(p, "constraint"):
889    result = parseConstraint(p)
890  else:
891    sqlError(p, "column definition expected")
892
893proc parseUnique(p: var SqlParser): SqlNode =
894  result = parseExpr(p)
895  if result.kind == nkCall: result.kind = nkUnique
896
897proc parseTableDef(p: var SqlParser): SqlNode =
898  result = parseIfNotExists(p, nkCreateTable)
899  expectIdent(p)
900  result.add(newNode(nkIdent, p.tok.literal))
901  getTok(p)
902  if p.tok.kind == tkParLe:
903    getTok(p)
904    while p.tok.kind != tkParRi:
905      if isKeyw(p, "constraint"):
906        result.add parseConstraint(p)
907      elif isKeyw(p, "primary") or isKeyw(p, "foreign"):
908        result.add parseTableConstraint(p)
909      elif isKeyw(p, "unique"):
910        result.add parseUnique(p)
911      elif p.tok.kind == tkIdentifier or p.tok.kind == tkQuotedIdentifier:
912        result.add(parseColumnDef(p))
913      else:
914        result.add(parseTableConstraint(p))
915      if p.tok.kind != tkComma: break
916      getTok(p)
917    eat(p, tkParRi)
918    # skip additional crap after 'create table (...) crap;'
919    while p.tok.kind notin {tkSemicolon, tkEof}:
920      getTok(p)
921
922proc parseTypeDef(p: var SqlParser): SqlNode =
923  result = parseIfNotExists(p, nkCreateType)
924  expectIdent(p)
925  result.add(newNode(nkIdent, p.tok.literal))
926  getTok(p)
927  eat(p, "as")
928  result.add(parseDataType(p))
929
930proc parseWhere(p: var SqlParser): SqlNode =
931  getTok(p)
932  result = newNode(nkWhere)
933  result.add(parseExpr(p))
934
935proc parseFromItem(p: var SqlParser): SqlNode =
936  result = newNode(nkFromItemPair)
937  if p.tok.kind == tkParLe:
938    getTok(p)
939    var select = parseSelect(p)
940    result.add(select)
941    eat(p, tkParRi)
942  else:
943    result.add(parseExpr(p))
944  if isKeyw(p, "as"):
945    getTok(p)
946    result.add(parseExpr(p))
947
948proc parseIndexDef(p: var SqlParser): SqlNode =
949  result = parseIfNotExists(p, nkCreateIndex)
950  if isKeyw(p, "primary"):
951    getTok(p)
952    eat(p, "key")
953    result.add(newNode(nkPrimaryKey))
954  else:
955    expectIdent(p)
956    result.add(newNode(nkIdent, p.tok.literal))
957    getTok(p)
958  eat(p, "on")
959  expectIdent(p)
960  result.add(newNode(nkIdent, p.tok.literal))
961  getTok(p)
962  eat(p, tkParLe)
963  expectIdent(p)
964  result.add(newNode(nkIdent, p.tok.literal))
965  getTok(p)
966  while p.tok.kind == tkComma:
967    getTok(p)
968    expectIdent(p)
969    result.add(newNode(nkIdent, p.tok.literal))
970    getTok(p)
971  eat(p, tkParRi)
972
973proc parseInsert(p: var SqlParser): SqlNode =
974  getTok(p)
975  eat(p, "into")
976  expectIdent(p)
977  result = newNode(nkInsert)
978  result.add(newNode(nkIdent, p.tok.literal))
979  getTok(p)
980  if p.tok.kind == tkParLe:
981    var n = newNode(nkColumnList)
982    parseParIdentList(p, n)
983    result.add n
984  else:
985    result.add(newNode(nkNone))
986  if isKeyw(p, "default"):
987    getTok(p)
988    eat(p, "values")
989    result.add(newNode(nkDefault))
990  else:
991    eat(p, "values")
992    eat(p, tkParLe)
993    var n = newNode(nkValueList)
994    while true:
995      n.add(parseExpr(p))
996      if p.tok.kind != tkComma: break
997      getTok(p)
998    result.add(n)
999    eat(p, tkParRi)
1000
1001proc parseUpdate(p: var SqlParser): SqlNode =
1002  getTok(p)
1003  result = newNode(nkUpdate)
1004  result.add(primary(p))
1005  eat(p, "set")
1006  while true:
1007    var a = newNode(nkAsgn)
1008    expectIdent(p)
1009    a.add(newNode(nkIdent, p.tok.literal))
1010    getTok(p)
1011    if isOpr(p, "="): getTok(p)
1012    else: sqlError(p, "= expected")
1013    a.add(parseExpr(p))
1014    result.add(a)
1015    if p.tok.kind != tkComma: break
1016    getTok(p)
1017  if isKeyw(p, "where"):
1018    result.add(parseWhere(p))
1019  else:
1020    result.add(newNode(nkNone))
1021
1022proc parseDelete(p: var SqlParser): SqlNode =
1023  getTok(p)
1024  if isOpr(p, "*"):
1025    getTok(p)
1026  result = newNode(nkDelete)
1027  eat(p, "from")
1028  result.add(primary(p))
1029  if isKeyw(p, "where"):
1030    result.add(parseWhere(p))
1031  else:
1032    result.add(newNode(nkNone))
1033
1034proc parseSelect(p: var SqlParser): SqlNode =
1035  getTok(p)
1036  if isKeyw(p, "distinct"):
1037    getTok(p)
1038    result = newNode(nkSelectDistinct)
1039  elif isKeyw(p, "all"):
1040    getTok(p)
1041  result = newNode(nkSelect)
1042  var a = newNode(nkSelectColumns)
1043  while true:
1044    if isOpr(p, "*"):
1045      a.add(newNode(nkIdent, "*"))
1046      getTok(p)
1047    else:
1048      var pair = newNode(nkSelectPair)
1049      pair.add(parseExpr(p))
1050      a.add(pair)
1051      if isKeyw(p, "as"):
1052        getTok(p)
1053        pair.add(parseExpr(p))
1054    if p.tok.kind != tkComma: break
1055    getTok(p)
1056  result.add(a)
1057  if isKeyw(p, "from"):
1058    var f = newNode(nkFrom)
1059    while true:
1060      getTok(p)
1061      f.add(parseFromItem(p))
1062      if p.tok.kind != tkComma: break
1063    result.add(f)
1064  if isKeyw(p, "where"):
1065    result.add(parseWhere(p))
1066  if isKeyw(p, "group"):
1067    getTok(p)
1068    eat(p, "by")
1069    var g = newNode(nkGroup)
1070    while true:
1071      g.add(parseExpr(p))
1072      if p.tok.kind != tkComma: break
1073      getTok(p)
1074    result.add(g)
1075  if isKeyw(p, "order"):
1076    getTok(p)
1077    eat(p, "by")
1078    var n = newNode(nkOrder)
1079    while true:
1080      var e = parseExpr(p)
1081      if isKeyw(p, "asc"):
1082        getTok(p) # is default
1083      elif isKeyw(p, "desc"):
1084        getTok(p)
1085        var x = newNode(nkDesc)
1086        x.add(e)
1087        e = x
1088      n.add(e)
1089      if p.tok.kind != tkComma: break
1090      getTok(p)
1091    result.add(n)
1092  if isKeyw(p, "having"):
1093    var h = newNode(nkHaving)
1094    while true:
1095      getTok(p)
1096      h.add(parseExpr(p))
1097      if p.tok.kind != tkComma: break
1098    result.add(h)
1099  if isKeyw(p, "union"):
1100    result.add(newNode(nkUnion))
1101    getTok(p)
1102  elif isKeyw(p, "intersect"):
1103    result.add(newNode(nkIntersect))
1104    getTok(p)
1105  elif isKeyw(p, "except"):
1106    result.add(newNode(nkExcept))
1107    getTok(p)
1108  if isKeyw(p, "join") or isKeyw(p, "inner") or isKeyw(p, "outer") or isKeyw(p, "cross"):
1109    var join = newNode(nkJoin)
1110    result.add(join)
1111    if isKeyw(p, "join"):
1112      join.add(newNode(nkIdent, ""))
1113      getTok(p)
1114    else:
1115      join.add(newNode(nkIdent, p.tok.literal.toLowerAscii()))
1116      getTok(p)
1117      eat(p, "join")
1118    join.add(parseFromItem(p))
1119    eat(p, "on")
1120    join.add(parseExpr(p))
1121  if isKeyw(p, "limit"):
1122    getTok(p)
1123    var l = newNode(nkLimit)
1124    l.add(parseExpr(p))
1125    result.add(l)
1126
1127proc parseStmt(p: var SqlParser; parent: SqlNode) =
1128  if isKeyw(p, "create"):
1129    getTok(p)
1130    optKeyw(p, "cached")
1131    optKeyw(p, "memory")
1132    optKeyw(p, "temp")
1133    optKeyw(p, "global")
1134    optKeyw(p, "local")
1135    optKeyw(p, "temporary")
1136    optKeyw(p, "unique")
1137    optKeyw(p, "hash")
1138    if isKeyw(p, "table"):
1139      parent.add parseTableDef(p)
1140    elif isKeyw(p, "type"):
1141      parent.add parseTypeDef(p)
1142    elif isKeyw(p, "index"):
1143      parent.add parseIndexDef(p)
1144    else:
1145      sqlError(p, "TABLE expected")
1146  elif isKeyw(p, "insert"):
1147    parent.add parseInsert(p)
1148  elif isKeyw(p, "update"):
1149    parent.add parseUpdate(p)
1150  elif isKeyw(p, "delete"):
1151    parent.add parseDelete(p)
1152  elif isKeyw(p, "select"):
1153    parent.add parseSelect(p)
1154  elif isKeyw(p, "begin"):
1155    getTok(p)
1156  else:
1157    sqlError(p, "SELECT, CREATE, UPDATE or DELETE expected")
1158
1159proc parse(p: var SqlParser): SqlNode =
1160  ## parses the content of `p`'s input stream and returns the SQL AST.
1161  ## Syntax errors raise an `SqlParseError` exception.
1162  result = newNode(nkStmtList)
1163  while p.tok.kind != tkEof:
1164    parseStmt(p, result)
1165    if p.tok.kind == tkEof:
1166      break
1167    eat(p, tkSemicolon)
1168
1169proc close(p: var SqlParser) =
1170  ## closes the parser `p`. The associated input stream is closed too.
1171  close(SqlLexer(p))
1172
1173type
1174  SqlWriter = object
1175    indent: int
1176    upperCase: bool
1177    buffer: string
1178
1179proc add(s: var SqlWriter, thing: char) =
1180  s.buffer.add(thing)
1181
1182proc prepareAdd(s: var SqlWriter) {.inline.} =
1183  if s.buffer.len > 0 and s.buffer[^1] notin {' ', '\L', '(', '.'}:
1184    s.buffer.add(" ")
1185
1186proc add(s: var SqlWriter, thing: string) =
1187  s.prepareAdd
1188  s.buffer.add(thing)
1189
1190proc addKeyw(s: var SqlWriter, thing: string) =
1191  var keyw = thing
1192  if s.upperCase:
1193    keyw = keyw.toUpperAscii()
1194  s.add(keyw)
1195
1196proc addIden(s: var SqlWriter, thing: string) =
1197  var iden = thing
1198  if iden.toLowerAscii() in reservedKeywords:
1199    iden = '"' & iden & '"'
1200  s.add(iden)
1201
1202proc ra(n: SqlNode, s: var SqlWriter) {.gcsafe.}
1203
1204proc rs(n: SqlNode, s: var SqlWriter, prefix = "(", suffix = ")", sep = ", ") =
1205  if n.len > 0:
1206    s.add(prefix)
1207    for i in 0 .. n.len-1:
1208      if i > 0: s.add(sep)
1209      ra(n.sons[i], s)
1210    s.add(suffix)
1211
1212proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',') =
1213  if n.len > 0:
1214    for i in 0 .. n.len-1:
1215      if i > 0: s.add(sep)
1216      ra(n.sons[i], s)
1217
1218proc addMulti(s: var SqlWriter, n: SqlNode, sep = ',', prefix, suffix: char) =
1219  if n.len > 0:
1220    s.add(prefix)
1221    for i in 0 .. n.len-1:
1222      if i > 0: s.add(sep)
1223      ra(n.sons[i], s)
1224    s.add(suffix)
1225
1226proc quoted(s: string): string =
1227  "\"" & replace(s, "\"", "\"\"") & "\""
1228
1229func escape(result: var string; s: string) =
1230  result.add('\'')
1231  for c in items(s):
1232    case c
1233    of '\0'..'\31':
1234      result.add("\\x")
1235      result.add(toHex(ord(c), 2))
1236    of '\'': result.add("''")
1237    else: result.add(c)
1238  result.add('\'')
1239
1240proc ra(n: SqlNode, s: var SqlWriter) =
1241  if n == nil: return
1242  case n.kind
1243  of nkNone: discard
1244  of nkIdent:
1245    if allCharsInSet(n.strVal, {'\33'..'\127'}):
1246      s.add(n.strVal)
1247    else:
1248      s.add(quoted(n.strVal))
1249  of nkQuotedIdent:
1250    s.add(quoted(n.strVal))
1251  of nkStringLit:
1252    s.prepareAdd
1253    s.buffer.escape(n.strVal)
1254  of nkBitStringLit:
1255    s.add("b'" & n.strVal & "'")
1256  of nkHexStringLit:
1257    s.add("x'" & n.strVal & "'")
1258  of nkIntegerLit, nkNumericLit:
1259    s.add(n.strVal)
1260  of nkPrimaryKey:
1261    s.addKeyw("primary key")
1262    rs(n, s)
1263  of nkForeignKey:
1264    s.addKeyw("foreign key")
1265    rs(n, s)
1266  of nkNotNull:
1267    s.addKeyw("not null")
1268  of nkNull:
1269    s.addKeyw("null")
1270  of nkDot:
1271    ra(n.sons[0], s)
1272    s.add('.')
1273    ra(n.sons[1], s)
1274  of nkDotDot:
1275    ra(n.sons[0], s)
1276    s.add(". .")
1277    ra(n.sons[1], s)
1278  of nkPrefix:
1279    ra(n.sons[0], s)
1280    s.add(' ')
1281    ra(n.sons[1], s)
1282  of nkInfix:
1283    ra(n.sons[1], s)
1284    s.add(' ')
1285    ra(n.sons[0], s)
1286    s.add(' ')
1287    ra(n.sons[2], s)
1288  of nkCall, nkColumnReference:
1289    ra(n.sons[0], s)
1290    s.add('(')
1291    for i in 1..n.len-1:
1292      if i > 1: s.add(',')
1293      ra(n.sons[i], s)
1294    s.add(')')
1295  of nkPrGroup:
1296    s.add('(')
1297    s.addMulti(n)
1298    s.add(')')
1299  of nkReferences:
1300    s.addKeyw("references")
1301    ra(n.sons[0], s)
1302  of nkDefault:
1303    s.addKeyw("default")
1304    ra(n.sons[0], s)
1305  of nkCheck:
1306    s.addKeyw("check")
1307    ra(n.sons[0], s)
1308  of nkConstraint:
1309    s.addKeyw("constraint")
1310    ra(n.sons[0], s)
1311    s.addKeyw("check")
1312    ra(n.sons[1], s)
1313  of nkUnique:
1314    s.addKeyw("unique")
1315    rs(n, s)
1316  of nkIdentity:
1317    s.addKeyw("identity")
1318  of nkColumnDef:
1319    rs(n, s, "", "", " ")
1320  of nkStmtList:
1321    for i in 0..n.len-1:
1322      ra(n.sons[i], s)
1323      s.add(';')
1324  of nkInsert:
1325    assert n.len == 3
1326    s.addKeyw("insert into")
1327    ra(n.sons[0], s)
1328    s.add(' ')
1329    ra(n.sons[1], s)
1330    if n.sons[2].kind == nkDefault:
1331      s.addKeyw("default values")
1332    else:
1333      ra(n.sons[2], s)
1334  of nkUpdate:
1335    s.addKeyw("update")
1336    ra(n.sons[0], s)
1337    s.addKeyw("set")
1338    var L = n.len
1339    for i in 1 .. L-2:
1340      if i > 1: s.add(", ")
1341      var it = n.sons[i]
1342      assert it.kind == nkAsgn
1343      ra(it, s)
1344    ra(n.sons[L-1], s)
1345  of nkDelete:
1346    s.addKeyw("delete from")
1347    ra(n.sons[0], s)
1348    ra(n.sons[1], s)
1349  of nkSelect, nkSelectDistinct:
1350    s.addKeyw("select")
1351    if n.kind == nkSelectDistinct:
1352      s.addKeyw("distinct")
1353    for i in 0 ..< n.len:
1354      ra(n.sons[i], s)
1355  of nkSelectColumns:
1356    for i, column in n.sons:
1357      if i > 0: s.add(',')
1358      ra(column, s)
1359  of nkSelectPair:
1360    ra(n.sons[0], s)
1361    if n.sons.len == 2:
1362      s.addKeyw("as")
1363      ra(n.sons[1], s)
1364  of nkFromItemPair:
1365    if n.sons[0].kind in {nkIdent, nkQuotedIdent}:
1366      ra(n.sons[0], s)
1367    else:
1368      assert n.sons[0].kind == nkSelect
1369      s.add('(')
1370      ra(n.sons[0], s)
1371      s.add(')')
1372    if n.sons.len == 2:
1373      s.addKeyw("as")
1374      ra(n.sons[1], s)
1375  of nkAsgn:
1376    ra(n.sons[0], s)
1377    s.add(" = ")
1378    ra(n.sons[1], s)
1379  of nkFrom:
1380    s.addKeyw("from")
1381    s.addMulti(n)
1382  of nkGroup:
1383    s.addKeyw("group by")
1384    s.addMulti(n)
1385  of nkLimit:
1386    s.addKeyw("limit")
1387    s.addMulti(n)
1388  of nkHaving:
1389    s.addKeyw("having")
1390    s.addMulti(n)
1391  of nkOrder:
1392    s.addKeyw("order by")
1393    s.addMulti(n)
1394  of nkJoin:
1395    var joinType = n.sons[0].strVal
1396    if joinType == "":
1397      joinType = "join"
1398    else:
1399      joinType &= " " & "join"
1400    s.addKeyw(joinType)
1401    ra(n.sons[1], s)
1402    s.addKeyw("on")
1403    ra(n.sons[2], s)
1404  of nkDesc:
1405    ra(n.sons[0], s)
1406    s.addKeyw("desc")
1407  of nkUnion:
1408    s.addKeyw("union")
1409  of nkIntersect:
1410    s.addKeyw("intersect")
1411  of nkExcept:
1412    s.addKeyw("except")
1413  of nkColumnList:
1414    rs(n, s)
1415  of nkValueList:
1416    s.addKeyw("values")
1417    rs(n, s)
1418  of nkWhere:
1419    s.addKeyw("where")
1420    ra(n.sons[0], s)
1421  of nkCreateTable, nkCreateTableIfNotExists:
1422    s.addKeyw("create table")
1423    if n.kind == nkCreateTableIfNotExists:
1424      s.addKeyw("if not exists")
1425    ra(n.sons[0], s)
1426    s.add('(')
1427    for i in 1..n.len-1:
1428      if i > 1: s.add(',')
1429      ra(n.sons[i], s)
1430    s.add(");")
1431  of nkCreateType, nkCreateTypeIfNotExists:
1432    s.addKeyw("create type")
1433    if n.kind == nkCreateTypeIfNotExists:
1434      s.addKeyw("if not exists")
1435    ra(n.sons[0], s)
1436    s.addKeyw("as")
1437    ra(n.sons[1], s)
1438  of nkCreateIndex, nkCreateIndexIfNotExists:
1439    s.addKeyw("create index")
1440    if n.kind == nkCreateIndexIfNotExists:
1441      s.addKeyw("if not exists")
1442    ra(n.sons[0], s)
1443    s.addKeyw("on")
1444    ra(n.sons[1], s)
1445    s.add('(')
1446    for i in 2..n.len-1:
1447      if i > 2: s.add(", ")
1448      ra(n.sons[i], s)
1449    s.add(");")
1450  of nkEnumDef:
1451    s.addKeyw("enum")
1452    rs(n, s)
1453
1454proc renderSQL*(n: SqlNode, upperCase = false): string =
1455  ## Converts an SQL abstract syntax tree to its string representation.
1456  var s: SqlWriter
1457  s.buffer = ""
1458  s.upperCase = upperCase
1459  ra(n, s)
1460  return s.buffer
1461
1462proc `$`*(n: SqlNode): string =
1463  ## an alias for `renderSQL`.
1464  renderSQL(n)
1465
1466proc treeReprAux(s: SqlNode, level: int, result: var string) =
1467  result.add('\n')
1468  for i in 0 ..< level: result.add("  ")
1469
1470  result.add($s.kind)
1471  if s.kind in LiteralNodes:
1472    result.add(' ')
1473    result.add(s.strVal)
1474  else:
1475    for son in s.sons:
1476      treeReprAux(son, level + 1, result)
1477
1478proc treeRepr*(s: SqlNode): string =
1479  result = newStringOfCap(128)
1480  treeReprAux(s, 0, result)
1481
1482import streams
1483
1484proc open(L: var SqlLexer, input: Stream, filename: string) =
1485  lexbase.open(L, input)
1486  L.filename = filename
1487
1488proc open(p: var SqlParser, input: Stream, filename: string) =
1489  ## opens the parser `p` and assigns the input stream `input` to it.
1490  ## `filename` is only used for error messages.
1491  open(SqlLexer(p), input, filename)
1492  p.tok.kind = tkInvalid
1493  p.tok.literal = ""
1494  getTok(p)
1495
1496proc parseSQL*(input: Stream, filename: string): SqlNode =
1497  ## parses the SQL from `input` into an AST and returns the AST.
1498  ## `filename` is only used for error messages.
1499  ## Syntax errors raise an `SqlParseError` exception.
1500  var p: SqlParser
1501  open(p, input, filename)
1502  try:
1503    result = parse(p)
1504  finally:
1505    close(p)
1506
1507proc parseSQL*(input: string, filename = ""): SqlNode =
1508  ## parses the SQL from `input` into an AST and returns the AST.
1509  ## `filename` is only used for error messages.
1510  ## Syntax errors raise an `SqlParseError` exception.
1511  parseSQL(newStringStream(input), "")
1512