sql-parser2 0.0.3

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