sql-parser 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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