arel 5.0.1.20140414130214 → 6.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -2
  3. data/History.txt +9 -4
  4. data/Manifest.txt +9 -7
  5. data/README.markdown +85 -8
  6. data/Rakefile +1 -1
  7. data/arel.gemspec +15 -16
  8. data/lib/arel.rb +1 -12
  9. data/lib/arel/collectors/bind.rb +36 -0
  10. data/lib/arel/collectors/plain_string.rb +18 -0
  11. data/lib/arel/collectors/sql_string.rb +18 -0
  12. data/lib/arel/factory_methods.rb +1 -1
  13. data/lib/arel/insert_manager.rb +5 -1
  14. data/lib/arel/nodes.rb +41 -0
  15. data/lib/arel/nodes/and.rb +1 -5
  16. data/lib/arel/nodes/binary.rb +2 -0
  17. data/lib/arel/nodes/extract.rb +0 -2
  18. data/lib/arel/nodes/full_outer_join.rb +6 -0
  19. data/lib/arel/nodes/function.rb +0 -1
  20. data/lib/arel/nodes/insert_statement.rb +5 -2
  21. data/lib/arel/nodes/node.rb +5 -1
  22. data/lib/arel/nodes/right_outer_join.rb +6 -0
  23. data/lib/arel/nodes/window.rb +23 -5
  24. data/lib/arel/predications.rb +41 -33
  25. data/lib/arel/select_manager.rb +13 -37
  26. data/lib/arel/table.rb +13 -9
  27. data/lib/arel/tree_manager.rb +8 -2
  28. data/lib/arel/update_manager.rb +2 -2
  29. data/lib/arel/visitors.rb +0 -2
  30. data/lib/arel/visitors/bind_substitute.rb +9 -0
  31. data/lib/arel/visitors/bind_visitor.rb +10 -5
  32. data/lib/arel/visitors/depth_first.rb +60 -57
  33. data/lib/arel/visitors/dot.rb +84 -80
  34. data/lib/arel/visitors/ibm_db.rb +4 -2
  35. data/lib/arel/visitors/informix.rb +39 -21
  36. data/lib/arel/visitors/mssql.rb +41 -23
  37. data/lib/arel/visitors/mysql.rb +48 -22
  38. data/lib/arel/visitors/oracle.rb +33 -24
  39. data/lib/arel/visitors/postgresql.rb +15 -8
  40. data/lib/arel/visitors/reduce.rb +25 -0
  41. data/lib/arel/visitors/sqlite.rb +3 -2
  42. data/lib/arel/visitors/to_sql.rb +455 -248
  43. data/lib/arel/visitors/visitor.rb +2 -2
  44. data/lib/arel/visitors/where_sql.rb +3 -2
  45. data/test/attributes/test_attribute.rb +12 -3
  46. data/test/collectors/test_bind_collector.rb +70 -0
  47. data/test/collectors/test_sql_string.rb +38 -0
  48. data/test/helper.rb +10 -1
  49. data/test/nodes/test_bin.rb +2 -2
  50. data/test/nodes/test_count.rb +0 -6
  51. data/test/nodes/test_equality.rb +1 -1
  52. data/test/nodes/test_grouping.rb +1 -1
  53. data/test/nodes/test_infix_operation.rb +1 -1
  54. data/test/nodes/test_select_core.rb +7 -7
  55. data/test/nodes/test_sql_literal.rb +10 -6
  56. data/test/nodes/test_window.rb +9 -3
  57. data/test/support/fake_record.rb +16 -4
  58. data/test/test_factory_methods.rb +1 -1
  59. data/test/test_insert_manager.rb +33 -4
  60. data/test/test_select_manager.rb +164 -92
  61. data/test/test_table.rb +49 -4
  62. data/test/visitors/test_bind_visitor.rb +18 -10
  63. data/test/visitors/test_depth_first.rb +12 -0
  64. data/test/visitors/test_dot.rb +4 -4
  65. data/test/visitors/test_ibm_db.rb +11 -5
  66. data/test/visitors/test_informix.rb +14 -8
  67. data/test/visitors/test_mssql.rb +12 -8
  68. data/test/visitors/test_mysql.rb +17 -12
  69. data/test/visitors/test_oracle.rb +25 -21
  70. data/test/visitors/test_postgres.rb +50 -12
  71. data/test/visitors/test_sqlite.rb +2 -2
  72. data/test/visitors/test_to_sql.rb +177 -81
  73. metadata +24 -19
  74. data/lib/arel/deprecated.rb +0 -4
  75. data/lib/arel/expression.rb +0 -5
  76. data/lib/arel/sql/engine.rb +0 -10
  77. data/lib/arel/sql_literal.rb +0 -4
  78. data/lib/arel/visitors/join_sql.rb +0 -19
  79. data/lib/arel/visitors/order_clauses.rb +0 -11
  80. data/test/visitors/test_join_sql.rb +0 -42
@@ -4,10 +4,11 @@ module Arel
4
4
  private
5
5
 
6
6
  # Locks are not supported in SQLite
7
- def visit_Arel_Nodes_Lock o, a
7
+ def visit_Arel_Nodes_Lock o, collector
8
+ collector
8
9
  end
9
10
 
10
- def visit_Arel_Nodes_SelectStatement o, a
11
+ def visit_Arel_Nodes_SelectStatement o, collector
11
12
  o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
12
13
  super
13
14
  end
@@ -1,9 +1,10 @@
1
1
  require 'bigdecimal'
2
2
  require 'date'
3
+ require 'arel/visitors/reduce'
3
4
 
4
5
  module Arel
5
6
  module Visitors
6
- class ToSql < Arel::Visitors::Visitor
7
+ class ToSql < Arel::Visitors::Reduce
7
8
  ##
8
9
  # This is some roflscale crazy stuff. I'm roflscaling this because
9
10
  # building SQL queries is a hotspot. I will explain the roflscale so that
@@ -64,13 +65,21 @@ module Arel
64
65
  @quoted_columns = {}
65
66
  end
66
67
 
68
+ def compile node, &block
69
+ accept(node, Arel::Collectors::SQLString.new, &block).value
70
+ end
71
+
67
72
  private
68
73
 
69
- def visit_Arel_Nodes_DeleteStatement o, a
70
- [
71
- "DELETE FROM #{visit o.relation}",
72
- ("WHERE #{o.wheres.map { |x| visit x }.join AND}" unless o.wheres.empty?)
73
- ].compact.join ' '
74
+ def visit_Arel_Nodes_DeleteStatement o, collector
75
+ collector << "DELETE FROM "
76
+ collector = visit o.relation, collector
77
+ if o.wheres.any?
78
+ collector << " WHERE "
79
+ inject_join o.wheres, collector, AND
80
+ else
81
+ collector
82
+ end
74
83
  end
75
84
 
76
85
  # FIXME: we should probably have a 2-pass visitor for this
@@ -85,43 +94,71 @@ module Arel
85
94
  stmt
86
95
  end
87
96
 
88
- def visit_Arel_Nodes_UpdateStatement o, a
97
+ def visit_Arel_Nodes_UpdateStatement o, collector
89
98
  if o.orders.empty? && o.limit.nil?
90
99
  wheres = o.wheres
91
100
  else
92
101
  wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
93
102
  end
94
103
 
95
- [
96
- "UPDATE #{visit o.relation, a}",
97
- ("SET #{o.values.map { |value| visit value, a }.join ', '}" unless o.values.empty?),
98
- ("WHERE #{wheres.map { |x| visit x, a }.join ' AND '}" unless wheres.empty?),
99
- ].compact.join ' '
104
+ collector << "UPDATE "
105
+ collector = visit o.relation, collector
106
+ unless o.values.empty?
107
+ collector << " SET "
108
+ collector = inject_join o.values, collector, ", "
109
+ end
110
+
111
+ unless wheres.empty?
112
+ collector << " WHERE "
113
+ collector = inject_join wheres, collector, " AND "
114
+ end
115
+
116
+ collector
100
117
  end
101
118
 
102
- def visit_Arel_Nodes_InsertStatement o, a
103
- [
104
- "INSERT INTO #{visit o.relation, a}",
119
+ def visit_Arel_Nodes_InsertStatement o, collector
120
+ collector << "INSERT INTO "
121
+ collector = visit o.relation, collector
122
+ if o.columns.any?
123
+ collector << " (#{o.columns.map { |x|
124
+ quote_column_name x.name
125
+ }.join ', '})"
126
+ end
105
127
 
106
- ("(#{o.columns.map { |x|
107
- quote_column_name x.name
108
- }.join ', '})" unless o.columns.empty?),
128
+ if o.values
129
+ maybe_visit o.values, collector
130
+ elsif o.select
131
+ maybe_visit o.select, collector
132
+ else
133
+ collector
134
+ end
135
+ end
109
136
 
110
- (visit o.values, a if o.values),
111
- ].compact.join ' '
137
+ def visit_Arel_Nodes_Exists o, collector
138
+ collector << "EXISTS ("
139
+ collector = visit(o.expressions, collector) << ")"
140
+ if o.alias
141
+ collector << " AS "
142
+ visit o.alias, collector
143
+ else
144
+ collector
145
+ end
112
146
  end
113
147
 
114
- def visit_Arel_Nodes_Exists o, a
115
- "EXISTS (#{visit o.expressions, a})#{
116
- o.alias ? " AS #{visit o.alias, a}" : ''}"
148
+ def visit_Arel_Nodes_Casted o, collector
149
+ collector << quoted(o.val, o.attribute).to_s
117
150
  end
118
151
 
119
- def visit_Arel_Nodes_True o, a
120
- "TRUE"
152
+ def visit_Arel_Nodes_Quoted o, collector
153
+ collector << quoted(o.expr, nil).to_s
121
154
  end
122
155
 
123
- def visit_Arel_Nodes_False o, a
124
- "FALSE"
156
+ def visit_Arel_Nodes_True o, collector
157
+ collector << "TRUE"
158
+ end
159
+
160
+ def visit_Arel_Nodes_False o, collector
161
+ collector << "FALSE"
125
162
  end
126
163
 
127
164
  def table_exists? name
@@ -142,402 +179,527 @@ module Arel
142
179
  @schema_cache.columns_hash(table)
143
180
  end
144
181
 
145
- def visit_Arel_Nodes_Values o, a
146
- "VALUES (#{o.expressions.zip(o.columns).map { |value, attr|
182
+ def visit_Arel_Nodes_Values o, collector
183
+ collector << "VALUES ("
184
+
185
+ len = o.expressions.length - 1
186
+ o.expressions.zip(o.columns).each_with_index { |(value, attr), i|
147
187
  if Nodes::SqlLiteral === value
148
- visit value, a
188
+ collector = visit value, collector
149
189
  else
150
- quote(value, attr && column_for(attr))
190
+ collector << quote(value, attr && column_for(attr)).to_s
151
191
  end
152
- }.join ', '})"
153
- end
192
+ unless i == len
193
+ collector << ', '
194
+ end
195
+ }
154
196
 
155
- def visit_Arel_Nodes_SelectStatement o, a
156
- str = ''
197
+ collector << ")"
198
+ end
157
199
 
200
+ def visit_Arel_Nodes_SelectStatement o, collector
158
201
  if o.with
159
- str << visit(o.with, a)
160
- str << SPACE
202
+ collector = visit o.with, collector
203
+ collector << SPACE
161
204
  end
162
205
 
163
- o.cores.each { |x| str << visit_Arel_Nodes_SelectCore(x, a) }
206
+ collector = o.cores.inject(collector) { |c,x|
207
+ visit_Arel_Nodes_SelectCore(x, c)
208
+ }
164
209
 
165
210
  unless o.orders.empty?
166
- str << SPACE
167
- str << ORDER_BY
211
+ collector << SPACE
212
+ collector << ORDER_BY
168
213
  len = o.orders.length - 1
169
214
  o.orders.each_with_index { |x, i|
170
- str << visit(x, a)
171
- str << COMMA unless len == i
215
+ collector = visit(x, collector)
216
+ collector << COMMA unless len == i
172
217
  }
173
218
  end
174
219
 
175
- str << " #{visit(o.limit, a)}" if o.limit
176
- str << " #{visit(o.offset, a)}" if o.offset
177
- str << " #{visit(o.lock, a)}" if o.lock
220
+ collector = maybe_visit o.limit, collector
221
+ collector = maybe_visit o.offset, collector
222
+ collector = maybe_visit o.lock, collector
178
223
 
179
- str.strip!
180
- str
224
+ collector
181
225
  end
182
226
 
183
- def visit_Arel_Nodes_SelectCore o, a
184
- str = "SELECT"
227
+ def visit_Arel_Nodes_SelectCore o, collector
228
+ collector << "SELECT"
185
229
 
186
- str << " #{visit(o.top, a)}" if o.top
187
- str << " #{visit(o.set_quantifier, a)}" if o.set_quantifier
230
+ if o.top
231
+ collector << " "
232
+ collector = visit o.top, collector
233
+ end
234
+
235
+ if o.set_quantifier
236
+ collector << " "
237
+ collector = visit o.set_quantifier, collector
238
+ end
188
239
 
189
240
  unless o.projections.empty?
190
- str << SPACE
241
+ collector << SPACE
191
242
  len = o.projections.length - 1
192
243
  o.projections.each_with_index do |x, i|
193
- str << visit(x, a)
194
- str << COMMA unless len == i
244
+ collector = visit(x, collector)
245
+ collector << COMMA unless len == i
195
246
  end
196
247
  end
197
248
 
198
- str << " FROM #{visit(o.source, a)}" if o.source && !o.source.empty?
249
+ if o.source && !o.source.empty?
250
+ collector << " FROM "
251
+ collector = visit o.source, collector
252
+ end
199
253
 
200
254
  unless o.wheres.empty?
201
- str << WHERE
255
+ collector << WHERE
202
256
  len = o.wheres.length - 1
203
257
  o.wheres.each_with_index do |x, i|
204
- str << visit(x, a)
205
- str << AND unless len == i
258
+ collector = visit(x, collector)
259
+ collector << AND unless len == i
206
260
  end
207
261
  end
208
262
 
209
263
  unless o.groups.empty?
210
- str << GROUP_BY
264
+ collector << GROUP_BY
211
265
  len = o.groups.length - 1
212
266
  o.groups.each_with_index do |x, i|
213
- str << visit(x, a)
214
- str << COMMA unless len == i
267
+ collector = visit(x, collector)
268
+ collector << COMMA unless len == i
215
269
  end
216
270
  end
217
271
 
218
- str << " #{visit(o.having, a)}" if o.having
272
+ if o.having
273
+ collector << " "
274
+ collector = visit(o.having, collector)
275
+ end
219
276
 
220
277
  unless o.windows.empty?
221
- str << WINDOW
278
+ collector << WINDOW
222
279
  len = o.windows.length - 1
223
280
  o.windows.each_with_index do |x, i|
224
- str << visit(x, a)
225
- str << COMMA unless len == i
281
+ collector = visit(x, collector)
282
+ collector << COMMA unless len == i
226
283
  end
227
284
  end
228
285
 
229
- str
286
+ collector
230
287
  end
231
288
 
232
- def visit_Arel_Nodes_Bin o, a
233
- visit o.expr, a
289
+ def visit_Arel_Nodes_Bin o, collector
290
+ visit o.expr, collector
234
291
  end
235
292
 
236
- def visit_Arel_Nodes_Distinct o, a
237
- DISTINCT
293
+ def visit_Arel_Nodes_Distinct o, collector
294
+ collector << DISTINCT
238
295
  end
239
296
 
240
- def visit_Arel_Nodes_DistinctOn o, a
297
+ def visit_Arel_Nodes_DistinctOn o, collector
241
298
  raise NotImplementedError, 'DISTINCT ON not implemented for this db'
242
299
  end
243
300
 
244
- def visit_Arel_Nodes_With o, a
245
- "WITH #{o.children.map { |x| visit x, a }.join(', ')}"
301
+ def visit_Arel_Nodes_With o, collector
302
+ collector << "WITH "
303
+ inject_join o.children, collector, ', '
246
304
  end
247
305
 
248
- def visit_Arel_Nodes_WithRecursive o, a
249
- "WITH RECURSIVE #{o.children.map { |x| visit x, a }.join(', ')}"
306
+ def visit_Arel_Nodes_WithRecursive o, collector
307
+ collector << "WITH RECURSIVE "
308
+ inject_join o.children, collector, ', '
250
309
  end
251
310
 
252
- def visit_Arel_Nodes_Union o, a
253
- "( #{visit o.left, a} UNION #{visit o.right, a} )"
311
+ def visit_Arel_Nodes_Union o, collector
312
+ collector << "( "
313
+ infix_value(o, collector, " UNION ") << " )"
254
314
  end
255
315
 
256
- def visit_Arel_Nodes_UnionAll o, a
257
- "( #{visit o.left, a} UNION ALL #{visit o.right, a} )"
316
+ def visit_Arel_Nodes_UnionAll o, collector
317
+ collector << "( "
318
+ infix_value(o, collector, " UNION ALL ") << " )"
258
319
  end
259
320
 
260
- def visit_Arel_Nodes_Intersect o, a
261
- "( #{visit o.left, a} INTERSECT #{visit o.right, a} )"
321
+ def visit_Arel_Nodes_Intersect o, collector
322
+ collector << "( "
323
+ infix_value(o, collector, " INTERSECT ") << " )"
262
324
  end
263
325
 
264
- def visit_Arel_Nodes_Except o, a
265
- "( #{visit o.left, a} EXCEPT #{visit o.right, a} )"
326
+ def visit_Arel_Nodes_Except o, collector
327
+ collector << "( "
328
+ infix_value(o, collector, " EXCEPT ") << " )"
266
329
  end
267
330
 
268
- def visit_Arel_Nodes_NamedWindow o, a
269
- "#{quote_column_name o.name} AS #{visit_Arel_Nodes_Window o, a}"
331
+ def visit_Arel_Nodes_NamedWindow o, collector
332
+ collector << quote_column_name(o.name)
333
+ collector << " AS "
334
+ visit_Arel_Nodes_Window o, collector
270
335
  end
271
336
 
272
- def visit_Arel_Nodes_Window o, a
273
- s = [
274
- ("ORDER BY #{o.orders.map { |x| visit(x, a) }.join(', ')}" unless o.orders.empty?),
275
- (visit o.framing, a if o.framing)
276
- ].compact.join ' '
277
- "(#{s})"
337
+ def visit_Arel_Nodes_Window o, collector
338
+ collector << "("
339
+
340
+ if o.partitions.any?
341
+ collector << "PARTITION BY "
342
+ collector = inject_join o.partitions, collector, ", "
343
+ end
344
+
345
+ if o.orders.any?
346
+ collector << ' ' if o.partitions.any?
347
+ collector << "ORDER BY "
348
+ collector = inject_join o.orders, collector, ", "
349
+ end
350
+
351
+ if o.framing
352
+ collector << ' ' if o.partitions.any? or o.orders.any?
353
+ collector = visit o.framing, collector
354
+ end
355
+
356
+ collector << ")"
278
357
  end
279
358
 
280
- def visit_Arel_Nodes_Rows o, a
359
+ def visit_Arel_Nodes_Rows o, collector
281
360
  if o.expr
282
- "ROWS #{visit o.expr, a}"
361
+ collector << "ROWS "
362
+ visit o.expr, collector
283
363
  else
284
- "ROWS"
364
+ collector << "ROWS"
285
365
  end
286
366
  end
287
367
 
288
- def visit_Arel_Nodes_Range o, a
368
+ def visit_Arel_Nodes_Range o, collector
289
369
  if o.expr
290
- "RANGE #{visit o.expr, a}"
370
+ collector << "RANGE "
371
+ visit o.expr, collector
291
372
  else
292
- "RANGE"
373
+ collector << "RANGE"
293
374
  end
294
375
  end
295
376
 
296
- def visit_Arel_Nodes_Preceding o, a
297
- "#{o.expr ? visit(o.expr, a) : 'UNBOUNDED'} PRECEDING"
377
+ def visit_Arel_Nodes_Preceding o, collector
378
+ collector = if o.expr
379
+ visit o.expr, collector
380
+ else
381
+ collector << "UNBOUNDED"
382
+ end
383
+
384
+ collector << " PRECEDING"
298
385
  end
299
386
 
300
- def visit_Arel_Nodes_Following o, a
301
- "#{o.expr ? visit(o.expr, a) : 'UNBOUNDED'} FOLLOWING"
387
+ def visit_Arel_Nodes_Following o, collector
388
+ collector = if o.expr
389
+ visit o.expr, collector
390
+ else
391
+ collector << "UNBOUNDED"
392
+ end
393
+
394
+ collector << " FOLLOWING"
302
395
  end
303
396
 
304
- def visit_Arel_Nodes_CurrentRow o, a
305
- "CURRENT ROW"
397
+ def visit_Arel_Nodes_CurrentRow o, collector
398
+ collector << "CURRENT ROW"
306
399
  end
307
400
 
308
- def visit_Arel_Nodes_Over o, a
401
+ def visit_Arel_Nodes_Over o, collector
309
402
  case o.right
310
- when nil
311
- "#{visit o.left, a} OVER ()"
312
- when Arel::Nodes::SqlLiteral
313
- "#{visit o.left, a} OVER #{visit o.right, a}"
314
- when String, Symbol
315
- "#{visit o.left, a} OVER #{quote_column_name o.right.to_s}"
316
- else
317
- "#{visit o.left, a} OVER #{visit o.right, a}"
403
+ when nil
404
+ visit(o.left, collector) << " OVER ()"
405
+ when Arel::Nodes::SqlLiteral
406
+ infix_value o, collector, " OVER "
407
+ when String, Symbol
408
+ visit(o.left, collector) << " OVER #{quote_column_name o.right.to_s}"
409
+ else
410
+ infix_value o, collector, " OVER "
318
411
  end
319
412
  end
320
413
 
321
- def visit_Arel_Nodes_Having o, a
322
- "HAVING #{visit o.expr, a}"
414
+ def visit_Arel_Nodes_Having o, collector
415
+ collector << "HAVING "
416
+ visit o.expr, collector
323
417
  end
324
418
 
325
- def visit_Arel_Nodes_Offset o, a
326
- "OFFSET #{visit o.expr, a}"
419
+ def visit_Arel_Nodes_Offset o, collector
420
+ collector << "OFFSET "
421
+ visit o.expr, collector
327
422
  end
328
423
 
329
- def visit_Arel_Nodes_Limit o, a
330
- "LIMIT #{visit o.expr, a}"
424
+ def visit_Arel_Nodes_Limit o, collector
425
+ collector << "LIMIT "
426
+ visit o.expr, collector
331
427
  end
332
428
 
333
429
  # FIXME: this does nothing on most databases, but does on MSSQL
334
- def visit_Arel_Nodes_Top o, a
335
- ""
430
+ def visit_Arel_Nodes_Top o, collector
431
+ collector
336
432
  end
337
433
 
338
- def visit_Arel_Nodes_Lock o, a
339
- visit o.expr, a
434
+ def visit_Arel_Nodes_Lock o, collector
435
+ visit o.expr, collector
340
436
  end
341
437
 
342
- def visit_Arel_Nodes_Grouping o, a
343
- "(#{visit o.expr, a})"
438
+ def visit_Arel_Nodes_Grouping o, collector
439
+ collector << "("
440
+ visit(o.expr, collector) << ")"
344
441
  end
345
442
 
346
- def visit_Arel_SelectManager o, a
347
- "(#{o.to_sql.rstrip})"
443
+ def visit_Arel_SelectManager o, collector
444
+ collector << "(#{o.to_sql.rstrip})"
348
445
  end
349
446
 
350
- def visit_Arel_Nodes_Ascending o, a
351
- "#{visit o.expr, a} ASC"
447
+ def visit_Arel_Nodes_Ascending o, collector
448
+ visit(o.expr, collector) << " ASC"
352
449
  end
353
450
 
354
- def visit_Arel_Nodes_Descending o, a
355
- "#{visit o.expr, a} DESC"
451
+ def visit_Arel_Nodes_Descending o, collector
452
+ visit(o.expr, collector) << " DESC"
356
453
  end
357
454
 
358
- def visit_Arel_Nodes_Group o, a
359
- visit o.expr, a
455
+ def visit_Arel_Nodes_Group o, collector
456
+ visit o.expr, collector
360
457
  end
361
458
 
362
- def visit_Arel_Nodes_NamedFunction o, a
363
- "#{o.name}(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
364
- visit x, a
365
- }.join(', ')})#{o.alias ? " AS #{visit o.alias, a}" : ''}"
459
+ def visit_Arel_Nodes_NamedFunction o, collector
460
+ collector << o.name
461
+ collector << "("
462
+ collector << "DISTINCT " if o.distinct
463
+ collector = inject_join(o.expressions, collector, ", ") << ")"
464
+ if o.alias
465
+ collector << " AS "
466
+ visit o.alias, collector
467
+ else
468
+ collector
469
+ end
470
+ end
471
+
472
+ def visit_Arel_Nodes_Extract o, collector
473
+ collector << "EXTRACT(#{o.field.to_s.upcase} FROM "
474
+ collector = visit o.expr, collector
475
+ collector << ")"
476
+ if o.alias
477
+ collector << " AS "
478
+ visit o.alias, collector
479
+ else
480
+ collector
481
+ end
482
+ end
483
+
484
+ def visit_Arel_Nodes_Count o, collector
485
+ aggregate "COUNT", o, collector
486
+ end
487
+
488
+ def visit_Arel_Nodes_Sum o, collector
489
+ aggregate "SUM", o, collector
366
490
  end
367
491
 
368
- def visit_Arel_Nodes_Extract o, a
369
- "EXTRACT(#{o.field.to_s.upcase} FROM #{visit o.expr, a})#{o.alias ? " AS #{visit o.alias, a}" : ''}"
492
+ def visit_Arel_Nodes_Max o, collector
493
+ aggregate "MAX", o, collector
370
494
  end
371
495
 
372
- def visit_Arel_Nodes_Count o, a
373
- "COUNT(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
374
- visit x, a
375
- }.join(', ')})#{o.alias ? " AS #{visit o.alias, a}" : ''}"
496
+ def visit_Arel_Nodes_Min o, collector
497
+ aggregate "MIN", o, collector
376
498
  end
377
499
 
378
- def visit_Arel_Nodes_Sum o, a
379
- "SUM(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
380
- visit x, a }.join(', ')})#{o.alias ? " AS #{visit o.alias, a}" : ''}"
500
+ def visit_Arel_Nodes_Avg o, collector
501
+ aggregate "AVG", o, collector
381
502
  end
382
503
 
383
- def visit_Arel_Nodes_Max o, a
384
- "MAX(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
385
- visit x, a }.join(', ')})#{o.alias ? " AS #{visit o.alias, a}" : ''}"
504
+ def visit_Arel_Nodes_TableAlias o, collector
505
+ collector = visit o.relation, collector
506
+ collector << " "
507
+ collector << quote_table_name(o.name)
386
508
  end
387
509
 
388
- def visit_Arel_Nodes_Min o, a
389
- "MIN(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
390
- visit x, a }.join(', ')})#{o.alias ? " AS #{visit o.alias, a}" : ''}"
510
+ def visit_Arel_Nodes_Between o, collector
511
+ collector = visit o.left, collector
512
+ collector << " BETWEEN "
513
+ visit o.right, collector
391
514
  end
392
515
 
393
- def visit_Arel_Nodes_Avg o, a
394
- "AVG(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
395
- visit x, a }.join(', ')})#{o.alias ? " AS #{visit o.alias, a}" : ''}"
516
+ def visit_Arel_Nodes_GreaterThanOrEqual o, collector
517
+ collector = visit o.left, collector
518
+ collector << " >= "
519
+ visit o.right, collector
396
520
  end
397
521
 
398
- def visit_Arel_Nodes_TableAlias o, a
399
- "#{visit o.relation, a} #{quote_table_name o.name}"
522
+ def visit_Arel_Nodes_GreaterThan o, collector
523
+ collector = visit o.left, collector
524
+ collector << " > "
525
+ visit o.right, collector
400
526
  end
401
527
 
402
- def visit_Arel_Nodes_Between o, a
403
- a = o.left if Arel::Attributes::Attribute === o.left
404
- "#{visit o.left, a} BETWEEN #{visit o.right, a}"
528
+ def visit_Arel_Nodes_LessThanOrEqual o, collector
529
+ collector = visit o.left, collector
530
+ collector << " <= "
531
+ visit o.right, collector
405
532
  end
406
533
 
407
- def visit_Arel_Nodes_GreaterThanOrEqual o, a
408
- a = o.left if Arel::Attributes::Attribute === o.left
409
- "#{visit o.left, a} >= #{visit o.right, a}"
534
+ def visit_Arel_Nodes_LessThan o, collector
535
+ collector = visit o.left, collector
536
+ collector << " < "
537
+ visit o.right, collector
410
538
  end
411
539
 
412
- def visit_Arel_Nodes_GreaterThan o, a
413
- a = o.left if Arel::Attributes::Attribute === o.left
414
- "#{visit o.left, a} > #{visit o.right, a}"
540
+ def visit_Arel_Nodes_Matches o, collector
541
+ collector = visit o.left, collector
542
+ collector << " LIKE "
543
+ visit o.right, collector
415
544
  end
416
545
 
417
- def visit_Arel_Nodes_LessThanOrEqual o, a
418
- a = o.left if Arel::Attributes::Attribute === o.left
419
- "#{visit o.left, a} <= #{visit o.right, a}"
546
+ def visit_Arel_Nodes_DoesNotMatch o, collector
547
+ collector = visit o.left, collector
548
+ collector << " NOT LIKE "
549
+ visit o.right, collector
420
550
  end
421
551
 
422
- def visit_Arel_Nodes_LessThan o, a
423
- a = o.left if Arel::Attributes::Attribute === o.left
424
- "#{visit o.left, a} < #{visit o.right, a}"
552
+ def visit_Arel_Nodes_JoinSource o, collector
553
+ if o.left
554
+ collector = visit o.left, collector
555
+ end
556
+ if o.right.any?
557
+ collector << " " if o.left
558
+ collector = inject_join o.right, collector, ' '
559
+ end
560
+ collector
425
561
  end
426
562
 
427
- def visit_Arel_Nodes_Matches o, a
428
- a = o.left if Arel::Attributes::Attribute === o.left
429
- "#{visit o.left, a} LIKE #{visit o.right, a}"
563
+ def visit_Arel_Nodes_Regexp o, collector
564
+ raise NotImplementedError, '~ not implemented for this db'
430
565
  end
431
566
 
432
- def visit_Arel_Nodes_DoesNotMatch o, a
433
- a = o.left if Arel::Attributes::Attribute === o.left
434
- "#{visit o.left, a} NOT LIKE #{visit o.right, a}"
567
+ def visit_Arel_Nodes_NotRegexp o, collector
568
+ raise NotImplementedError, '!~ not implemented for this db'
435
569
  end
436
570
 
437
- def visit_Arel_Nodes_JoinSource o, a
438
- [
439
- (visit(o.left, a) if o.left),
440
- o.right.map { |j| visit j, a }.join(' ')
441
- ].compact.join ' '
571
+ def visit_Arel_Nodes_StringJoin o, collector
572
+ visit o.left, collector
442
573
  end
443
574
 
444
- def visit_Arel_Nodes_StringJoin o, a
445
- visit o.left, a
575
+ def visit_Arel_Nodes_FullOuterJoin o
576
+ "FULL OUTER JOIN #{visit o.left} #{visit o.right}"
446
577
  end
447
578
 
448
- def visit_Arel_Nodes_OuterJoin o, a
449
- "LEFT OUTER JOIN #{visit o.left, a} #{visit o.right, a}"
579
+ def visit_Arel_Nodes_OuterJoin o, collector
580
+ collector << "LEFT OUTER JOIN "
581
+ collector = visit o.left, collector
582
+ collector << " "
583
+ visit o.right, collector
450
584
  end
451
585
 
452
- def visit_Arel_Nodes_InnerJoin o, a
453
- s = "INNER JOIN #{visit o.left, a}"
586
+ def visit_Arel_Nodes_RightOuterJoin o
587
+ "RIGHT OUTER JOIN #{visit o.left} #{visit o.right}"
588
+ end
589
+
590
+ def visit_Arel_Nodes_InnerJoin o, collector
591
+ collector << "INNER JOIN "
592
+ collector = visit o.left, collector
454
593
  if o.right
455
- s << SPACE
456
- s << visit(o.right, a)
594
+ collector << SPACE
595
+ visit(o.right, collector)
596
+ else
597
+ collector
457
598
  end
458
- s
459
599
  end
460
600
 
461
- def visit_Arel_Nodes_On o, a
462
- "ON #{visit o.expr, a}"
601
+ def visit_Arel_Nodes_On o, collector
602
+ collector << "ON "
603
+ visit o.expr, collector
463
604
  end
464
605
 
465
- def visit_Arel_Nodes_Not o, a
466
- "NOT (#{visit o.expr, a})"
606
+ def visit_Arel_Nodes_Not o, collector
607
+ collector << "NOT ("
608
+ visit(o.expr, collector) << ")"
467
609
  end
468
610
 
469
- def visit_Arel_Table o, a
611
+ def visit_Arel_Table o, collector
470
612
  if o.table_alias
471
- "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
613
+ collector << "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
472
614
  else
473
- quote_table_name o.name
615
+ collector << quote_table_name(o.name)
474
616
  end
475
617
  end
476
618
 
477
- def visit_Arel_Nodes_In o, a
619
+ def visit_Arel_Nodes_In o, collector
478
620
  if Array === o.right && o.right.empty?
479
- '1=0'
621
+ collector << '1=0'
480
622
  else
481
- a = o.left if Arel::Attributes::Attribute === o.left
482
- "#{visit o.left, a} IN (#{visit o.right, a})"
623
+ collector = visit o.left, collector
624
+ collector << " IN ("
625
+ visit(o.right, collector) << ")"
483
626
  end
484
627
  end
485
628
 
486
- def visit_Arel_Nodes_NotIn o, a
629
+ def visit_Arel_Nodes_NotIn o, collector
487
630
  if Array === o.right && o.right.empty?
488
- '1=1'
631
+ collector << '1=1'
489
632
  else
490
- a = o.left if Arel::Attributes::Attribute === o.left
491
- "#{visit o.left, a} NOT IN (#{visit o.right, a})"
633
+ collector = visit o.left, collector
634
+ collector << " NOT IN ("
635
+ collector = visit o.right, collector
636
+ collector << ")"
492
637
  end
493
638
  end
494
639
 
495
- def visit_Arel_Nodes_And o, a
496
- o.children.map { |x| visit x, a }.join ' AND '
640
+ def visit_Arel_Nodes_And o, collector
641
+ inject_join o.children, collector, " AND "
497
642
  end
498
643
 
499
- def visit_Arel_Nodes_Or o, a
500
- "#{visit o.left, a} OR #{visit o.right, a}"
644
+ def visit_Arel_Nodes_Or o, collector
645
+ collector = visit o.left, collector
646
+ collector << " OR "
647
+ visit o.right, collector
501
648
  end
502
649
 
503
- def visit_Arel_Nodes_Assignment o, a
504
- right = quote(o.right, column_for(o.left))
505
- "#{visit o.left, a} = #{right}"
650
+ def visit_Arel_Nodes_Assignment o, collector
651
+ case o.right
652
+ when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute, Arel::Nodes::BindParam
653
+ collector = visit o.left, collector
654
+ collector << " = "
655
+ visit o.right, collector
656
+ else
657
+ collector = visit o.left, collector
658
+ collector << " = "
659
+ collector << quote(o.right, column_for(o.left)).to_s
660
+ end
506
661
  end
507
662
 
508
- def visit_Arel_Nodes_Equality o, a
663
+ def visit_Arel_Nodes_Equality o, collector
509
664
  right = o.right
510
665
 
511
- a = o.left if Arel::Attributes::Attribute === o.left
666
+ collector = visit o.left, collector
667
+
512
668
  if right.nil?
513
- "#{visit o.left, a} IS NULL"
669
+ collector << " IS NULL"
514
670
  else
515
- "#{visit o.left, a} = #{visit right, a}"
671
+ collector << " = "
672
+ visit right, collector
516
673
  end
517
674
  end
518
675
 
519
- def visit_Arel_Nodes_NotEqual o, a
676
+ def visit_Arel_Nodes_NotEqual o, collector
520
677
  right = o.right
521
678
 
522
- a = o.left if Arel::Attributes::Attribute === o.left
679
+ collector = visit o.left, collector
680
+
523
681
  if right.nil?
524
- "#{visit o.left, a} IS NOT NULL"
682
+ collector << " IS NOT NULL"
525
683
  else
526
- "#{visit o.left, a} != #{visit right, a}"
684
+ collector << " != "
685
+ visit right, collector
527
686
  end
528
687
  end
529
688
 
530
- def visit_Arel_Nodes_As o, a
531
- "#{visit o.left, a} AS #{visit o.right, a}"
689
+ def visit_Arel_Nodes_As o, collector
690
+ collector = visit o.left, collector
691
+ collector << " AS "
692
+ visit o.right, collector
532
693
  end
533
694
 
534
- def visit_Arel_Nodes_UnqualifiedColumn o, a
535
- "#{quote_column_name o.name}"
695
+ def visit_Arel_Nodes_UnqualifiedColumn o, collector
696
+ collector << "#{quote_column_name o.name}"
697
+ collector
536
698
  end
537
699
 
538
- def visit_Arel_Attributes_Attribute o, a
700
+ def visit_Arel_Attributes_Attribute o, collector
539
701
  join_name = o.relation.table_alias || o.relation.name
540
- "#{quote_table_name join_name}.#{quote_column_name o.name}"
702
+ collector << "#{quote_table_name join_name}.#{quote_column_name o.name}"
541
703
  end
542
704
  alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
543
705
  alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
@@ -546,11 +708,13 @@ module Arel
546
708
  alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
547
709
  alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
548
710
 
549
- def literal o, a; o end
711
+ def literal o, collector; collector << o.to_s; end
712
+
713
+ def visit_Arel_Nodes_BindParam o, collector
714
+ collector.add_bind o
715
+ end
550
716
 
551
- alias :visit_Arel_Nodes_BindParam :literal
552
717
  alias :visit_Arel_Nodes_SqlLiteral :literal
553
- alias :visit_Arel_SqlLiteral :literal # This is deprecated
554
718
  alias :visit_Bignum :literal
555
719
  alias :visit_Fixnum :literal
556
720
 
@@ -558,23 +722,29 @@ module Arel
558
722
  quote(o, column_for(a))
559
723
  end
560
724
 
561
- alias :visit_ActiveSupport_Multibyte_Chars :quoted
562
- alias :visit_ActiveSupport_StringInquirer :quoted
563
- alias :visit_BigDecimal :quoted
564
- alias :visit_Class :quoted
565
- alias :visit_Date :quoted
566
- alias :visit_DateTime :quoted
567
- alias :visit_FalseClass :quoted
568
- alias :visit_Float :quoted
569
- alias :visit_Hash :quoted
570
- alias :visit_NilClass :quoted
571
- alias :visit_String :quoted
572
- alias :visit_Symbol :quoted
573
- alias :visit_Time :quoted
574
- alias :visit_TrueClass :quoted
725
+ def unsupported o, collector
726
+ raise "unsupported: #{o.class.name}"
727
+ end
728
+
729
+ alias :visit_ActiveSupport_Multibyte_Chars :unsupported
730
+ alias :visit_ActiveSupport_StringInquirer :unsupported
731
+ alias :visit_BigDecimal :unsupported
732
+ alias :visit_Class :unsupported
733
+ alias :visit_Date :unsupported
734
+ alias :visit_DateTime :unsupported
735
+ alias :visit_FalseClass :unsupported
736
+ alias :visit_Float :unsupported
737
+ alias :visit_Hash :unsupported
738
+ alias :visit_NilClass :unsupported
739
+ alias :visit_String :unsupported
740
+ alias :visit_Symbol :unsupported
741
+ alias :visit_Time :unsupported
742
+ alias :visit_TrueClass :unsupported
575
743
 
576
- def visit_Arel_Nodes_InfixOperation o, a
577
- "#{visit o.left, a} #{o.operator} #{visit o.right, a}"
744
+ def visit_Arel_Nodes_InfixOperation o, collector
745
+ collector = visit o.left, collector
746
+ collector << " #{o.operator} "
747
+ visit o.right, collector
578
748
  end
579
749
 
580
750
  alias :visit_Arel_Nodes_Addition :visit_Arel_Nodes_InfixOperation
@@ -582,8 +752,8 @@ module Arel
582
752
  alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation
583
753
  alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
584
754
 
585
- def visit_Array o, a
586
- o.map { |x| visit x, a }.join(', ')
755
+ def visit_Array o, collector
756
+ inject_join o, collector, ", "
587
757
  end
588
758
 
589
759
  def quote value, column = nil
@@ -599,6 +769,43 @@ module Arel
599
769
  def quote_column_name name
600
770
  @quoted_columns[name] ||= Arel::Nodes::SqlLiteral === name ? name : @connection.quote_column_name(name)
601
771
  end
772
+
773
+ def maybe_visit thing, collector
774
+ return collector unless thing
775
+ collector << " "
776
+ visit thing, collector
777
+ end
778
+
779
+ def inject_join list, collector, join_str
780
+ len = list.length - 1
781
+ list.each_with_index.inject(collector) { |c, (x,i)|
782
+ if i == len
783
+ visit x, c
784
+ else
785
+ visit(x, c) << join_str
786
+ end
787
+ }
788
+ end
789
+
790
+ def infix_value o, collector, value
791
+ collector = visit o.left, collector
792
+ collector << value
793
+ visit o.right, collector
794
+ end
795
+
796
+ def aggregate name, o, collector
797
+ collector << "#{name}("
798
+ if o.distinct
799
+ collector << "DISTINCT "
800
+ end
801
+ collector = inject_join(o.expressions, collector, ", ") << ")"
802
+ if o.alias
803
+ collector << " AS "
804
+ visit o.alias, collector
805
+ else
806
+ collector
807
+ end
808
+ end
602
809
  end
603
810
  end
604
811
  end