sql-parser2 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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