sql-parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ class SQLParser::Parser
2
+
3
+ option
4
+ ignorecase
5
+
6
+ macro
7
+ DIGIT [0-9]
8
+ UINT {DIGIT}+
9
+ BLANK \s+
10
+
11
+ YEARS {UINT}
12
+ MONTHS {UINT}
13
+ DAYS {UINT}
14
+ DATE {YEARS}-{MONTHS}-{DAYS}
15
+
16
+ IDENT \w+
17
+
18
+ rule
19
+ # [:state] pattern [actions]
20
+
21
+ # literals
22
+ \"{DATE}\" { [:date_string, Date.parse(text)] }
23
+ \'{DATE}\' { [:date_string, Date.parse(text)] }
24
+
25
+ \' { state = :STRS; [:quote, text] }
26
+ :STRS \' { state = nil; [:quote, text] }
27
+ :STRS .*(?=\') { [:character_string_literal, text.gsub("''", "'")] }
28
+
29
+ \" { state = :STRD; [:quote, text] }
30
+ :STRD \" { state = nil; [:quote, text] }
31
+ :STRD .*(?=\") { [:character_string_literal, text.gsub('""', '"')] }
32
+
33
+ {UINT} { [:unsigned_integer, text.to_i] }
34
+
35
+ # skip
36
+ {BLANK} # no action
37
+
38
+ # keywords
39
+ SELECT { [:SELECT, text] }
40
+ DATE { [:DATE, text] }
41
+ ASC { [:ASC, text] }
42
+ AS { [:AS, text] }
43
+ FROM { [:FROM, text] }
44
+ WHERE { [:WHERE, text] }
45
+ BETWEEN { [:BETWEEN, text] }
46
+ AND { [:AND, text] }
47
+ NOT { [:NOT, text] }
48
+ INNER { [:INNER, text] }
49
+ IN { [:IN, text] }
50
+ ORDER { [:ORDER, text] }
51
+ OR { [:OR, text] }
52
+ LIKE { [:LIKE, text] }
53
+ IS { [:IS, text] }
54
+ NULL { [:NULL, text] }
55
+ COUNT { [:COUNT, text] }
56
+ AVG { [:AVG, text] }
57
+ MAX { [:MAX, text] }
58
+ MIN { [:MIN, text] }
59
+ SUM { [:SUM, text] }
60
+ GROUP { [:GROUP, text] }
61
+ BY { [:BY, text] }
62
+ HAVING { [:HAVING, text] }
63
+ CROSS { [:CROSS, text] }
64
+ JOIN { [:JOIN, text] }
65
+ ON { [:ON, text] }
66
+ LEFT { [:LEFT, text] }
67
+ OUTER { [:OUTER, text] }
68
+ RIGHT { [:RIGHT, text] }
69
+ FULL { [:FULL, text] }
70
+ USING { [:USING, text] }
71
+ EXISTS { [:EXISTS, text] }
72
+ DESC { [:DESC, text] }
73
+ CURRENT_USER { [:CURRENT_USER, text] }
74
+
75
+ # tokens
76
+ E { [:E, text] }
77
+
78
+ <> { [:not_equals_operator, text] }
79
+ != { [:not_equals_operator, text] }
80
+ = { [:equals_operator, text] }
81
+ <= { [:less_than_or_equals_operator, text] }
82
+ < { [:less_than_operator, text] }
83
+ >= { [:greater_than_or_equals_operator, text] }
84
+ > { [:greater_than_operator, text] }
85
+
86
+ \( { [:left_paren, text] }
87
+ \) { [:right_paren, text] }
88
+ \* { [:asterisk, text] }
89
+ \/ { [:solidus, text] }
90
+ \+ { [:plus_sign, text] }
91
+ \- { [:minus_sign, text] }
92
+ \. { [:period, text] }
93
+ , { [:comma, text] }
94
+
95
+ # identifier
96
+ `{IDENT}` { [:identifier, text[1..-2]] }
97
+ {IDENT} { [:identifier, text] }
98
+
99
+ ---- header ----
100
+ require 'date'
@@ -0,0 +1,273 @@
1
+ module SQLParser
2
+
3
+ class Parser < Racc::Parser
4
+
5
+ class ScanError < StandardError ; end
6
+
7
+ attr_reader :lineno
8
+ attr_reader :filename
9
+
10
+ def scan_setup ; end
11
+
12
+ def action &block
13
+ yield
14
+ end
15
+
16
+ def scan_str( str )
17
+ scan_evaluate str
18
+ do_parse
19
+ end
20
+
21
+ def load_file( filename )
22
+ @filename = filename
23
+ open(filename, "r") do |f|
24
+ scan_evaluate f.read
25
+ end
26
+ end
27
+
28
+ def scan_file( filename )
29
+ load_file filename
30
+ do_parse
31
+ end
32
+
33
+ def next_token
34
+ @rex_tokens.shift
35
+ end
36
+
37
+ def scan_evaluate( str )
38
+ scan_setup
39
+ @rex_tokens = []
40
+ @lineno = 1
41
+ ss = StringScanner.new(str)
42
+ state = nil
43
+ until ss.eos?
44
+ text = ss.peek(1)
45
+ @lineno += 1 if text == "\n"
46
+ case state
47
+ when nil
48
+ case
49
+ when (text = ss.scan(/\"[0-9]+-[0-9]+-[0-9]+\"/i))
50
+ @rex_tokens.push action { [:date_string, Date.parse(text)] }
51
+
52
+ when (text = ss.scan(/\'[0-9]+-[0-9]+-[0-9]+\'/i))
53
+ @rex_tokens.push action { [:date_string, Date.parse(text)] }
54
+
55
+ when (text = ss.scan(/\'/i))
56
+ @rex_tokens.push action { state = :STRS; [:quote, text] }
57
+
58
+ when (text = ss.scan(/\"/i))
59
+ @rex_tokens.push action { state = :STRD; [:quote, text] }
60
+
61
+ when (text = ss.scan(/[0-9]+/i))
62
+ @rex_tokens.push action { [:unsigned_integer, text.to_i] }
63
+
64
+ when (text = ss.scan(/\s+/i))
65
+ ;
66
+
67
+ when (text = ss.scan(/SELECT/i))
68
+ @rex_tokens.push action { [:SELECT, text] }
69
+
70
+ when (text = ss.scan(/DATE/i))
71
+ @rex_tokens.push action { [:DATE, text] }
72
+
73
+ when (text = ss.scan(/ASC/i))
74
+ @rex_tokens.push action { [:ASC, text] }
75
+
76
+ when (text = ss.scan(/AS/i))
77
+ @rex_tokens.push action { [:AS, text] }
78
+
79
+ when (text = ss.scan(/FROM/i))
80
+ @rex_tokens.push action { [:FROM, text] }
81
+
82
+ when (text = ss.scan(/WHERE/i))
83
+ @rex_tokens.push action { [:WHERE, text] }
84
+
85
+ when (text = ss.scan(/BETWEEN/i))
86
+ @rex_tokens.push action { [:BETWEEN, text] }
87
+
88
+ when (text = ss.scan(/AND/i))
89
+ @rex_tokens.push action { [:AND, text] }
90
+
91
+ when (text = ss.scan(/NOT/i))
92
+ @rex_tokens.push action { [:NOT, text] }
93
+
94
+ when (text = ss.scan(/INNER/i))
95
+ @rex_tokens.push action { [:INNER, text] }
96
+
97
+ when (text = ss.scan(/IN/i))
98
+ @rex_tokens.push action { [:IN, text] }
99
+
100
+ when (text = ss.scan(/ORDER/i))
101
+ @rex_tokens.push action { [:ORDER, text] }
102
+
103
+ when (text = ss.scan(/OR/i))
104
+ @rex_tokens.push action { [:OR, text] }
105
+
106
+ when (text = ss.scan(/LIKE/i))
107
+ @rex_tokens.push action { [:LIKE, text] }
108
+
109
+ when (text = ss.scan(/IS/i))
110
+ @rex_tokens.push action { [:IS, text] }
111
+
112
+ when (text = ss.scan(/NULL/i))
113
+ @rex_tokens.push action { [:NULL, text] }
114
+
115
+ when (text = ss.scan(/COUNT/i))
116
+ @rex_tokens.push action { [:COUNT, text] }
117
+
118
+ when (text = ss.scan(/AVG/i))
119
+ @rex_tokens.push action { [:AVG, text] }
120
+
121
+ when (text = ss.scan(/MAX/i))
122
+ @rex_tokens.push action { [:MAX, text] }
123
+
124
+ when (text = ss.scan(/MIN/i))
125
+ @rex_tokens.push action { [:MIN, text] }
126
+
127
+ when (text = ss.scan(/SUM/i))
128
+ @rex_tokens.push action { [:SUM, text] }
129
+
130
+ when (text = ss.scan(/GROUP/i))
131
+ @rex_tokens.push action { [:GROUP, text] }
132
+
133
+ when (text = ss.scan(/BY/i))
134
+ @rex_tokens.push action { [:BY, text] }
135
+
136
+ when (text = ss.scan(/HAVING/i))
137
+ @rex_tokens.push action { [:HAVING, text] }
138
+
139
+ when (text = ss.scan(/CROSS/i))
140
+ @rex_tokens.push action { [:CROSS, text] }
141
+
142
+ when (text = ss.scan(/JOIN/i))
143
+ @rex_tokens.push action { [:JOIN, text] }
144
+
145
+ when (text = ss.scan(/ON/i))
146
+ @rex_tokens.push action { [:ON, text] }
147
+
148
+ when (text = ss.scan(/LEFT/i))
149
+ @rex_tokens.push action { [:LEFT, text] }
150
+
151
+ when (text = ss.scan(/OUTER/i))
152
+ @rex_tokens.push action { [:OUTER, text] }
153
+
154
+ when (text = ss.scan(/RIGHT/i))
155
+ @rex_tokens.push action { [:RIGHT, text] }
156
+
157
+ when (text = ss.scan(/FULL/i))
158
+ @rex_tokens.push action { [:FULL, text] }
159
+
160
+ when (text = ss.scan(/USING/i))
161
+ @rex_tokens.push action { [:USING, text] }
162
+
163
+ when (text = ss.scan(/EXISTS/i))
164
+ @rex_tokens.push action { [:EXISTS, text] }
165
+
166
+ when (text = ss.scan(/DESC/i))
167
+ @rex_tokens.push action { [:DESC, text] }
168
+
169
+ when (text = ss.scan(/CURRENT_USER/i))
170
+ @rex_tokens.push action { [:CURRENT_USER, text] }
171
+
172
+ when (text = ss.scan(/E/i))
173
+ @rex_tokens.push action { [:E, text] }
174
+
175
+ when (text = ss.scan(/<>/i))
176
+ @rex_tokens.push action { [:not_equals_operator, text] }
177
+
178
+ when (text = ss.scan(/!=/i))
179
+ @rex_tokens.push action { [:not_equals_operator, text] }
180
+
181
+ when (text = ss.scan(/=/i))
182
+ @rex_tokens.push action { [:equals_operator, text] }
183
+
184
+ when (text = ss.scan(/<=/i))
185
+ @rex_tokens.push action { [:less_than_or_equals_operator, text] }
186
+
187
+ when (text = ss.scan(/</i))
188
+ @rex_tokens.push action { [:less_than_operator, text] }
189
+
190
+ when (text = ss.scan(/>=/i))
191
+ @rex_tokens.push action { [:greater_than_or_equals_operator, text] }
192
+
193
+ when (text = ss.scan(/>/i))
194
+ @rex_tokens.push action { [:greater_than_operator, text] }
195
+
196
+ when (text = ss.scan(/\(/i))
197
+ @rex_tokens.push action { [:left_paren, text] }
198
+
199
+ when (text = ss.scan(/\)/i))
200
+ @rex_tokens.push action { [:right_paren, text] }
201
+
202
+ when (text = ss.scan(/\*/i))
203
+ @rex_tokens.push action { [:asterisk, text] }
204
+
205
+ when (text = ss.scan(/\//i))
206
+ @rex_tokens.push action { [:solidus, text] }
207
+
208
+ when (text = ss.scan(/\+/i))
209
+ @rex_tokens.push action { [:plus_sign, text] }
210
+
211
+ when (text = ss.scan(/\-/i))
212
+ @rex_tokens.push action { [:minus_sign, text] }
213
+
214
+ when (text = ss.scan(/\./i))
215
+ @rex_tokens.push action { [:period, text] }
216
+
217
+ when (text = ss.scan(/,/i))
218
+ @rex_tokens.push action { [:comma, text] }
219
+
220
+ when (text = ss.scan(/`\w+`/i))
221
+ @rex_tokens.push action { [:identifier, text[1..-2]] }
222
+
223
+ when (text = ss.scan(/\w+/i))
224
+ @rex_tokens.push action { [:identifier, text] }
225
+
226
+ when (text = ss.scan(/----/i))
227
+ ;
228
+
229
+ when (text = ss.scan(/require/i))
230
+ ;
231
+
232
+ else
233
+ text = ss.string[ss.pos .. -1]
234
+ raise ScanError, "can not match: '" + text + "'"
235
+ end # if
236
+
237
+ when :STRS
238
+ case
239
+ when (text = ss.scan(/\'/i))
240
+ @rex_tokens.push action { state = nil; [:quote, text] }
241
+
242
+ when (text = ss.scan(/.*(?=\')/i))
243
+ @rex_tokens.push action { [:character_string_literal, text.gsub("''", "'")] }
244
+
245
+ else
246
+ text = ss.string[ss.pos .. -1]
247
+ raise ScanError, "can not match: '" + text + "'"
248
+ end # if
249
+
250
+ when :STRD
251
+ case
252
+ when (text = ss.scan(/\"/i))
253
+ @rex_tokens.push action { state = nil; [:quote, text] }
254
+
255
+ when (text = ss.scan(/.*(?=\")/i))
256
+ @rex_tokens.push action { [:character_string_literal, text.gsub('""', '"')] }
257
+
258
+ else
259
+ text = ss.string[ss.pos .. -1]
260
+ raise ScanError, "can not match: '" + text + "'"
261
+ end # if
262
+
263
+ else
264
+ raise ScanError, "undefined state: '" + state.to_s + "'"
265
+ end
266
+
267
+ end
268
+
269
+ end
270
+
271
+ end
272
+
273
+ end
@@ -0,0 +1,351 @@
1
+ module SQLParser
2
+
3
+ class SQLVisitor
4
+
5
+ def initialize
6
+ @negated = false
7
+ end
8
+
9
+ def visit(node)
10
+ node.accept(self)
11
+ end
12
+
13
+ def visit_DirectSelect(o)
14
+ [
15
+ o.query_expression,
16
+ o.order_by
17
+ ].compact.collect { |e| visit(e) }.join(' ')
18
+ end
19
+
20
+ def visit_OrderBy(o)
21
+ "ORDER BY #{arrayize(o.sort_specification)}"
22
+ end
23
+
24
+ def visit_Subquery(o)
25
+ "(#{visit(o.query_specification)})"
26
+ end
27
+
28
+ def visit_Select(o)
29
+ # FIXME: This feels like a hack
30
+ initialize
31
+
32
+ "SELECT #{visit_all([o.list, o.table_expression].compact).join(' ')}"
33
+ end
34
+
35
+ def visit_SelectList(o)
36
+ arrayize(o.columns)
37
+ end
38
+
39
+ def visit_Distinct(o)
40
+ "DISTINCT(#{visit(o.column)})"
41
+ end
42
+
43
+ def visit_All(o)
44
+ '*'
45
+ end
46
+
47
+ def visit_TableExpression(o)
48
+ [
49
+ o.from_clause,
50
+ o.where_clause,
51
+ o.group_by_clause,
52
+ o.having_clause
53
+ ].compact.collect { |e| visit(e) }.join(' ')
54
+ end
55
+
56
+ def visit_FromClause(o)
57
+ "FROM #{arrayize(o.tables)}"
58
+ end
59
+
60
+ def visit_OrderClause(o)
61
+ "ORDER BY #{arrayize(o.columns)}"
62
+ end
63
+
64
+ def visit_Ascending(o)
65
+ "#{visit(o.column)} ASC"
66
+ end
67
+
68
+ def visit_Descending(o)
69
+ "#{visit(o.column)} DESC"
70
+ end
71
+
72
+ def visit_HavingClause(o)
73
+ "HAVING #{visit(o.search_condition)}"
74
+ end
75
+
76
+ def visit_GroupByClause(o)
77
+ "GROUP BY #{arrayize(o.columns)}"
78
+ end
79
+
80
+ def visit_WhereClause(o)
81
+ "WHERE #{visit(o.search_condition)}"
82
+ end
83
+
84
+ def visit_On(o)
85
+ "ON #{visit(o.search_condition)}"
86
+ end
87
+
88
+ def visit_Using(o)
89
+ "USING (#{arrayize(o.columns)})"
90
+ end
91
+
92
+ def visit_Or(o)
93
+ search_condition('OR', o)
94
+ end
95
+
96
+ def visit_And(o)
97
+ search_condition('AND', o)
98
+ end
99
+
100
+ def visit_Exists(o)
101
+ if @negated
102
+ "NOT EXISTS #{visit(o.table_subquery)}"
103
+ else
104
+ "EXISTS #{visit(o.table_subquery)}"
105
+ end
106
+ end
107
+
108
+ def visit_Is(o)
109
+ if @negated
110
+ comparison('IS NOT', o)
111
+ else
112
+ comparison('IS', o)
113
+ end
114
+ end
115
+
116
+ def visit_Like(o)
117
+ if @negated
118
+ comparison('NOT LIKE', o)
119
+ else
120
+ comparison('LIKE', o)
121
+ end
122
+ end
123
+
124
+ def visit_In(o)
125
+ if @negated
126
+ comparison('NOT IN', o)
127
+ else
128
+ comparison('IN', o)
129
+ end
130
+ end
131
+
132
+ def visit_InValueList(o)
133
+ "(#{arrayize(o.values)})"
134
+ end
135
+
136
+ def visit_Between(o)
137
+ if @negated
138
+ "#{visit(o.left)} NOT BETWEEN #{visit(o.min)} AND #{visit(o.max)}"
139
+ else
140
+ "#{visit(o.left)} BETWEEN #{visit(o.min)} AND #{visit(o.max)}"
141
+ end
142
+ end
143
+
144
+ def visit_GreaterOrEquals(o)
145
+ comparison('>=', o)
146
+ end
147
+
148
+ def visit_LessOrEquals(o)
149
+ comparison('<=', o)
150
+ end
151
+
152
+ def visit_Greater(o)
153
+ comparison('>', o)
154
+ end
155
+
156
+ def visit_Less(o)
157
+ comparison('<', o)
158
+ end
159
+
160
+ def visit_Equals(o)
161
+ if @negated
162
+ comparison('<>', o)
163
+ else
164
+ comparison('=', o)
165
+ end
166
+ end
167
+
168
+ def visit_Sum(o)
169
+ aggregate('SUM', o)
170
+ end
171
+
172
+ def visit_Minimum(o)
173
+ aggregate('MIN', o)
174
+ end
175
+
176
+ def visit_Maximum(o)
177
+ aggregate('MAX', o)
178
+ end
179
+
180
+ def visit_Average(o)
181
+ aggregate('AVG', o)
182
+ end
183
+
184
+ def visit_Count(o)
185
+ aggregate('COUNT', o)
186
+ end
187
+
188
+ def visit_CrossJoin(o)
189
+ "#{visit(o.left)} CROSS JOIN #{visit(o.right)}"
190
+ end
191
+
192
+ def visit_InnerJoin(o)
193
+ qualified_join('INNER', o)
194
+ end
195
+
196
+ def visit_LeftJoin(o)
197
+ qualified_join('LEFT', o)
198
+ end
199
+
200
+ def visit_LeftOuterJoin(o)
201
+ qualified_join('LEFT OUTER', o)
202
+ end
203
+
204
+ def visit_RightJoin(o)
205
+ qualified_join('RIGHT', o)
206
+ end
207
+
208
+ def visit_RightOuterJoin(o)
209
+ qualified_join('RIGHT OUTER', o)
210
+ end
211
+
212
+ def visit_FullJoin(o)
213
+ qualified_join('FULL', o)
214
+ end
215
+
216
+ def visit_FullOuterJoin(o)
217
+ qualified_join('FULL OUTER', o)
218
+ end
219
+
220
+ def visit_Table(o)
221
+ quote(o.name)
222
+ end
223
+
224
+ def visit_QualifiedColumn(o)
225
+ "#{visit(o.table)}.#{visit(o.column)}"
226
+ end
227
+
228
+ def visit_Column(o)
229
+ quote(o.name)
230
+ end
231
+
232
+ def visit_As(o)
233
+ "#{visit(o.value)} AS #{visit(o.column)}"
234
+ end
235
+
236
+ def visit_Multiply(o)
237
+ arithmetic('*', o)
238
+ end
239
+
240
+ def visit_Divide(o)
241
+ arithmetic('/', o)
242
+ end
243
+
244
+ def visit_Add(o)
245
+ arithmetic('+', o)
246
+ end
247
+
248
+ def visit_Subtract(o)
249
+ arithmetic('-', o)
250
+ end
251
+
252
+ def visit_Not(o)
253
+ negate { visit(o.value) }
254
+ end
255
+
256
+ def visit_UnaryPlus(o)
257
+ "+#{visit(o.value)}"
258
+ end
259
+
260
+ def visit_UnaryMinus(o)
261
+ "-#{visit(o.value)}"
262
+ end
263
+
264
+ def visit_True(o)
265
+ 'TRUE'
266
+ end
267
+
268
+ def visit_False(o)
269
+ 'FALSE'
270
+ end
271
+
272
+ def visit_Null(o)
273
+ 'NULL'
274
+ end
275
+
276
+ def visit_CurrentUser(o)
277
+ 'CURRENT_USER'
278
+ end
279
+
280
+ def visit_DateTime(o)
281
+ "'%s'" % escape(o.value.strftime('%Y-%m-%d %H:%M:%S'))
282
+ end
283
+
284
+ def visit_Date(o)
285
+ "DATE '%s'" % escape(o.value.strftime('%Y-%m-%d'))
286
+ end
287
+
288
+ def visit_String(o)
289
+ "'%s'" % escape(o.value)
290
+ end
291
+
292
+ def visit_ApproximateFloat(o)
293
+ "#{visit(o.mantissa)}E#{visit(o.exponent)}"
294
+ end
295
+
296
+ def visit_Float(o)
297
+ o.value.to_s
298
+ end
299
+
300
+ def visit_Integer(o)
301
+ o.value.to_s
302
+ end
303
+
304
+ private
305
+
306
+ def negate
307
+ @negated = true
308
+ yield
309
+ ensure
310
+ @negated = false
311
+ end
312
+
313
+ def quote(str)
314
+ "`#{str}`"
315
+ end
316
+
317
+ def escape(str)
318
+ str.gsub(/'/, "''")
319
+ end
320
+
321
+ def arithmetic(operator, o)
322
+ search_condition(operator, o)
323
+ end
324
+
325
+ def comparison(operator, o)
326
+ [visit(o.left), operator, visit(o.right)].join(' ')
327
+ end
328
+
329
+ def search_condition(operator, o)
330
+ "(#{visit(o.left)} #{operator} #{visit(o.right)})"
331
+ end
332
+
333
+ def visit_all(nodes)
334
+ nodes.collect { |e| visit(e) }
335
+ end
336
+
337
+ def arrayize(arr)
338
+ visit_all(arr).join(', ')
339
+ end
340
+
341
+ def aggregate(function_name, o)
342
+ "#{function_name}(#{visit(o.column)})"
343
+ end
344
+
345
+ def qualified_join(join_type, o)
346
+ "#{visit(o.left)} #{join_type} JOIN #{visit(o.right)} #{visit(o.search_condition)}"
347
+ end
348
+
349
+ end
350
+
351
+ end