UNPKG

4.21 kBtext/coffeescriptView Raw
1###
2Dialects are responsible for compiling an AST to a SQL string compatible with
3a particular DBMS. They are rarely used directly, instead a query is usually
4bound to an `engine <Engines>`_ that will delegate compiling to it's dialect
5instance.
6###
7
8read = require('fs').readFileSync
9kwFile = __dirname + '/sql_keywords.txt'
10keywords = read(kwFile, 'ascii').split('\n').filter(Boolean)
11{Select, Update, Delete, Insert, Relation, Field} = require './nodes'
12
13class 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
80class 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
107class 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
116class MySQLDialect extends BaseDialect
117 placeholder: -> '?'
118
119 quote: (s, path) ->
120 ###
121 MySQL has two special cases for quoting:
122 - The column names in an insert column list are not quoted
123 - table and field names are quoted with backticks.
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
134class 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
148module.exports =
149 base: BaseDialect
150 pretty: PrettyDialect
151 postgres: PostgresDialect
152 mysql: MySQLDialect
153 sqlite3: SQLite3Dialect
154