@@ -4,6 +4,54 @@
44module Arel
55module Visitors
66class ToSql < Arel::Visitors::Visitor
7+##
8+# This is some roflscale crazy stuff. I'm roflscaling this because
9+# building SQL queries is a hotspot. I will explain the roflscale so that
10+# others will not rm this code.
11+#
12+# In YARV, string literals in a method body will get duped when the byte
13+# code is executed. Let's take a look:
14+#
15+# > puts RubyVM::InstructionSequence.new('def foo; "bar"; end').disasm
16+#
17+# == disasm: <RubyVM::InstructionSequence:foo@<compiled>>=====
18+# 0000 trace 8
19+# 0002 trace 1
20+# 0004 putstring "bar"
21+# 0006 trace 16
22+# 0008 leave
23+#
24+# The `putstring` bytecode will dup the string and push it on the stack.
25+# In many cases in our SQL visitor, that string is never mutated, so there
26+# is no need to dup the literal.
27+#
28+# If we change to a constant lookup, the string will not be duped, and we
29+# can reduce the objects in our system:
30+#
31+# > puts RubyVM::InstructionSequence.new('BAR = "bar"; def foo; BAR; end').disasm
32+#
33+# == disasm: <RubyVM::InstructionSequence:foo@<compiled>>========
34+# 0000 trace 8
35+# 0002 trace 1
36+# 0004 getinlinecache 11, <ic:0>
37+# 0007 getconstant :BAR
38+# 0009 setinlinecache <ic:0>
39+# 0011 trace 16
40+# 0013 leave
41+#
42+# `getconstant` should be a hash lookup, and no object is duped when the
43+# value of the constant is pushed on the stack. Hence the crazy
44+# constants below.
45+46+WHERE = ' WHERE ' # :nodoc:
47+SPACE = ' ' # :nodoc:
48+COMMA = ', ' # :nodoc:
49+GROUP_BY = ' GROUP BY ' # :nodoc:
50+WINDOW = ' WINDOW ' # :nodoc:
51+AND = ' AND ' # :nodoc:
52+53+DISTINCT = 'DISTINCT' # :nodoc:
54+755attr_accessor :last_column
856957def initialize connection
@@ -23,7 +71,7 @@ def accept object
2371def visit_Arel_Nodes_DeleteStatement o
2472[
2573"DELETE FROM #{visit o.relation}",
26-("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?)
74+("WHERE #{o.wheres.map { |x| visit x }.join AND}" unless o.wheres.empty?)
2775].compact.join ' '
2876end
2977@@ -127,25 +175,60 @@ def visit_Arel_Nodes_SelectStatement o
127175end
128176129177def visit_Arel_Nodes_SelectCore o
130-[
131-"SELECT",
132-(visit(o.top) if o.top),
133-(visit(o.set_quantifier) if o.set_quantifier),
134-("#{o.projections.map { |x| visit x }.join ', '}" unless o.projections.empty?),
135-("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
136-("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
137-("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
138-(visit(o.having) if o.having),
139-("WINDOW #{o.windows.map { |x| visit x }.join ', ' }" unless o.windows.empty?)
140-].compact.join ' '
178+str = "SELECT"
179+180+str << " #{visit(o.top)}" if o.top
181+str << " #{visit(o.set_quantifier)}" if o.set_quantifier
182+183+unless o.projections.empty?
184+str << SPACE
185+len = o.projections.length - 1
186+o.projections.each_with_index do |x, i|
187+str << visit(x)
188+str << COMMA unless len == i
189+end
190+end
191+192+str << " FROM #{visit(o.source)}" if o.source && !o.source.empty?
193+194+unless o.wheres.empty?
195+str << WHERE
196+len = o.wheres.length - 1
197+o.wheres.each_with_index do |x, i|
198+str << visit(x)
199+str << AND unless len == i
200+end
201+end
202+203+unless o.groups.empty?
204+str << GROUP_BY
205+len = o.groups.length - 1
206+o.groups.each_with_index do |x, i|
207+str << visit(x)
208+str << COMMA unless len == i
209+end
210+end
211+212+str << " #{visit(o.having)}" if o.having
213+214+unless o.windows.empty?
215+str << WINDOW
216+len = o.windows.length - 1
217+o.windows.each_with_index do |x, i|
218+str << visit(x)
219+str << COMMA unless len == i
220+end
221+end
222+223+str
141224end
142225143226def visit_Arel_Nodes_Bin o
144227visit o.expr
145228end
146229147230def visit_Arel_Nodes_Distinct o
148-'DISTINCT'
231+DISTINCT
149232end
150233151234def visit_Arel_Nodes_DistinctOn o