1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | read = require('fs').readFileSync
|
9 | kwFile = __dirname + '/sql_keywords.txt'
|
10 | keywords = read(kwFile, 'ascii').split('\n').filter(Boolean)
|
11 | {Select, Update, Delete, Insert, Relation, Field} = require './nodes'
|
12 |
|
13 | class BaseDialect
|
14 |
|
15 | reset: ->
|
16 |
|
17 | compile: (root) ->
|
18 | visitor = new Visitor(@)
|
19 | text = visitor.compile(root)
|
20 | [text, visitor.params]
|
21 |
|
22 | renderString: (s) ->
|
23 | path = @path.map((p) -> p.constructor?.name).join(' > ')
|
24 | @path = []
|
25 | throw new Error "raw string compiled! " + path
|
26 |
|
27 | needsQuote = /\s|"|\./
|
28 | doubleQuote = /"/g
|
29 |
|
30 | quote: (s) ->
|
31 | if s?.match(needsQuote) or @isKeyword(s)
|
32 | '"' + s.replace(doubleQuote, '\\"') + '"'
|
33 | else
|
34 | s
|
35 |
|
36 | isKeyword: (word) ->
|
37 | keywords.indexOf(word.toUpperCase()) isnt -1
|
38 |
|
39 | operator: (op) ->
|
40 | switch (op = op.toUpperCase())
|
41 | when 'NE', '!=', '<>' then '!='
|
42 | when 'EQ', '=' then '='
|
43 | when 'LT', '<' then '<'
|
44 | when 'GT', '>' then '>'
|
45 | when 'LTE', '<=' then '<='
|
46 | when 'GTE', '>=' then '>='
|
47 | when 'LIKE', 'ILIKE', 'IN', 'NOT IN', 'IS', 'IS NOT' then op
|
48 | else throw new Error("Unsupported comparison operator: #{op}")
|
49 |
|
50 | placeholder: (position) ->
|
51 | "$#{position}"
|
52 |
|
53 | class Visitor
|
54 | constructor: (@dialect) ->
|
55 | @path = []
|
56 | @params = []
|
57 |
|
58 | compile: (node, allowOverride=true) ->
|
59 | @path.push(node)
|
60 | name = node?.__proto__?.constructor?.name
|
61 | if allowOverride and name and custom = @dialect['render' + name]
|
62 | string = custom.call(@, node)
|
63 | else
|
64 | string = node.compile(@, @path)
|
65 | @path.pop(node)
|
66 | return string
|
67 |
|
68 | maybeParens: (it) -> if /\s/.exec it then "(#{it})" else it
|
69 |
|
70 | operator: (string) ->
|
71 | @dialect.operator(string)
|
72 |
|
73 | parameter: (val) ->
|
74 | @params.push val
|
75 | @dialect.placeholder(@params.length)
|
76 |
|
77 | quote: (string) ->
|
78 | @dialect.quote(string, @path)
|
79 |
|
80 | class PrettyDialect extends BaseDialect
|
81 | renderJoin: (node) -> "\n" + @compile(node, false)
|
82 | renderWhere: (node) -> "\n" + @compile(node, false)
|
83 | renderHaving: (node) -> "\n" + @compile(node, false)
|
84 | renderOrderBy: (node) -> "\n" + @compile(node, false)
|
85 | renderGroupBy: (node) -> "\n" + @compile(node, false)
|
86 | renderRelationSet: (node) -> "\n" + @compile(node, false)
|
87 | renderSelectColumnSet: (node) ->
|
88 | glue = node.glue
|
89 | last = node.nodes.length
|
90 | lines = []
|
91 | thisLine = []
|
92 | thisLineLength = 81
|
93 | for node in node.nodes
|
94 | text = @compile(node)
|
95 | size = text.length + glue.length
|
96 | if thisLineLength + size > 50
|
97 | lines.push(thisLine.join(glue))
|
98 | thisLine = []
|
99 | thisLineLength = 0
|
100 | thisLineLength += size
|
101 | thisLine.push text
|
102 | lines.shift()
|
103 | lines.push(thisLine.join(glue))
|
104 | lines.join("\n ")
|
105 |
|
106 |
|
107 | class PostgresDialect extends BaseDialect
|
108 | operator: (op) ->
|
109 | switch op.toLowerCase()
|
110 | when 'hasKey' then '?'
|
111 | when '->' then '->'
|
112 | else super op
|
113 |
|
114 | isKeyword: (s) -> s? and s isnt '*'
|
115 |
|
116 | class MySQLDialect extends BaseDialect
|
117 | placeholder: -> '?'
|
118 |
|
119 | quote: (s, path) ->
|
120 | |
121 |
|
122 |
|
123 |
|
124 |
|
125 | node = path[path.length - 1]
|
126 | if s is '*' or path.some((node) -> node instanceof Insert.ColumnList)
|
127 | s
|
128 | else if node instanceof Field or node instanceof Relation
|
129 | "`#{s}`"
|
130 | else
|
131 | super
|
132 |
|
133 |
|
134 | class SQLite3Dialect extends BaseDialect
|
135 | placeholder: -> '?'
|
136 |
|
137 | renderInsertData: (node) ->
|
138 | if node.nodes.length < 2
|
139 | node.compile(@, @path)
|
140 | else
|
141 | node.glue = ' UNION ALL SELECT '
|
142 | string = node.compile(@, @path)
|
143 | .replace('VALUES', 'SELECT')
|
144 | .replace(/[()]/g, '')
|
145 | node.glue = ', '
|
146 | string
|
147 |
|
148 | module.exports =
|
149 | base: BaseDialect
|
150 | pretty: PrettyDialect
|
151 | postgres: PostgresDialect
|
152 | mysql: MySQLDialect
|
153 | sqlite3: SQLite3Dialect
|
154 |
|