arel 5.0.1.20140414130214 → 6.0.0.beta1

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.
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