adding some roflscale to the sql visitor · rails/arel@e232226

3 min read Original article ↗

@@ -4,6 +4,54 @@

44

module Arel

55

module Visitors

66

class 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+755

attr_accessor :last_column

856957

def initialize connection

@@ -23,7 +71,7 @@ def accept object

2371

def 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 ' '

2876

end

2977

@@ -127,25 +175,60 @@ def visit_Arel_Nodes_SelectStatement o

127175

end

128176129177

def 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

141224

end

142225143226

def visit_Arel_Nodes_Bin o

144227

visit o.expr

145228

end

146229147230

def visit_Arel_Nodes_Distinct o

148-

'DISTINCT'

231+

DISTINCT

149232

end

150233151234

def visit_Arel_Nodes_DistinctOn o