arel 3.0.3 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +2 -2
  3. data/Gemfile +3 -4
  4. data/History.txt +0 -51
  5. data/Manifest.txt +8 -2
  6. data/README.markdown +1 -1
  7. data/Rakefile +1 -1
  8. data/arel.gemspec +21 -29
  9. data/lib/arel.rb +1 -3
  10. data/lib/arel/nodes.rb +1 -0
  11. data/lib/arel/nodes/and.rb +10 -0
  12. data/lib/arel/nodes/binary.rb +11 -0
  13. data/lib/arel/nodes/extract.rb +11 -0
  14. data/lib/arel/nodes/false.rb +7 -0
  15. data/lib/arel/nodes/function.rb +11 -0
  16. data/lib/arel/nodes/grouping.rb +7 -0
  17. data/lib/arel/nodes/insert_statement.rb +12 -0
  18. data/lib/arel/nodes/named_function.rb +9 -0
  19. data/lib/arel/nodes/select_core.rb +20 -0
  20. data/lib/arel/nodes/select_statement.rb +15 -1
  21. data/lib/arel/nodes/table_alias.rb +4 -0
  22. data/lib/arel/nodes/terminal.rb +7 -0
  23. data/lib/arel/nodes/true.rb +7 -0
  24. data/lib/arel/nodes/unary.rb +10 -1
  25. data/lib/arel/nodes/update_statement.rb +15 -0
  26. data/lib/arel/nodes/window.rb +30 -3
  27. data/lib/arel/select_manager.rb +4 -0
  28. data/lib/arel/table.rb +13 -0
  29. data/lib/arel/tree_manager.rb +0 -2
  30. data/lib/arel/visitors/bind_visitor.rb +10 -0
  31. data/lib/arel/visitors/dot.rb +1 -1
  32. data/lib/arel/visitors/oracle.rb +1 -2
  33. data/lib/arel/visitors/to_sql.rb +138 -27
  34. data/test/nodes/test_and.rb +20 -0
  35. data/test/nodes/test_as.rb +12 -0
  36. data/test/nodes/test_ascending.rb +10 -0
  37. data/test/nodes/test_bin.rb +10 -0
  38. data/test/nodes/test_count.rb +12 -0
  39. data/test/nodes/test_delete_statement.rb +20 -0
  40. data/test/nodes/test_descending.rb +10 -0
  41. data/test/nodes/test_distinct.rb +20 -0
  42. data/test/nodes/test_equality.rb +10 -0
  43. data/test/nodes/test_extract.rb +14 -0
  44. data/test/nodes/test_false.rb +20 -0
  45. data/test/nodes/test_grouping.rb +25 -0
  46. data/test/nodes/test_infix_operation.rb +10 -0
  47. data/test/nodes/test_insert_statement.rb +24 -0
  48. data/test/nodes/test_named_function.rb +16 -0
  49. data/test/nodes/test_not.rb +12 -0
  50. data/test/nodes/test_or.rb +12 -0
  51. data/test/nodes/test_over.rb +18 -0
  52. data/test/nodes/test_select_core.rb +38 -0
  53. data/test/nodes/test_select_statement.rb +36 -0
  54. data/test/nodes/test_sql_literal.rb +10 -0
  55. data/test/nodes/test_sum.rb +12 -0
  56. data/test/nodes/test_table_alias.rb +36 -0
  57. data/test/nodes/test_true.rb +21 -0
  58. data/test/nodes/test_update_statement.rb +40 -0
  59. data/test/nodes/test_window.rb +73 -0
  60. data/test/test_attributes.rb +12 -0
  61. data/test/test_insert_manager.rb +0 -2
  62. data/test/test_select_manager.rb +10 -5
  63. data/test/test_table.rb +24 -0
  64. data/test/test_update_manager.rb +8 -0
  65. data/test/visitors/test_bind_visitor.rb +20 -1
  66. data/test/visitors/test_oracle.rb +1 -2
  67. data/test/visitors/test_to_sql.rb +44 -0
  68. metadata +76 -86
  69. data/lib/arel/nodes/ordering.rb +0 -6
  70. data/lib/arel/relation.rb +0 -6
@@ -12,6 +12,10 @@ module Arel
12
12
  def table_name
13
13
  relation.respond_to?(:name) ? relation.name : name
14
14
  end
15
+
16
+ def engine
17
+ relation.engine
18
+ end
15
19
  end
16
20
  end
17
21
  end
@@ -1,6 +1,13 @@
1
1
  module Arel
2
2
  module Nodes
3
3
  class Distinct < Arel::Nodes::Node
4
+ def hash
5
+ self.class.hash
6
+ end
7
+
8
+ def eql? other
9
+ self.class == other.class
10
+ end
4
11
  end
5
12
  end
6
13
  end
@@ -1,6 +1,13 @@
1
1
  module Arel
2
2
  module Nodes
3
3
  class True < Arel::Nodes::Node
4
+ def hash
5
+ self.class.hash
6
+ end
7
+
8
+ def eql? other
9
+ self.class == other.class
10
+ end
4
11
  end
5
12
  end
6
13
  end
@@ -7,12 +7,21 @@ module Arel
7
7
  def initialize expr
8
8
  @expr = expr
9
9
  end
10
+
11
+ def hash
12
+ @expr.hash
13
+ end
14
+
15
+ def eql? other
16
+ self.class == other.class &&
17
+ self.expr == other.expr
18
+ end
19
+ alias :== :eql?
10
20
  end
11
21
 
12
22
  %w{
13
23
  Bin
14
24
  Group
15
- Grouping
16
25
  Having
17
26
  Limit
18
27
  Not
@@ -18,6 +18,21 @@ module Arel
18
18
  @wheres = @wheres.clone
19
19
  @values = @values.clone
20
20
  end
21
+
22
+ def hash
23
+ [@relation, @wheres, @values, @orders, @limit, @key].hash
24
+ end
25
+
26
+ def eql? other
27
+ self.class == other.class &&
28
+ self.relation == other.relation &&
29
+ self.wheres == other.wheres &&
30
+ self.values == other.values &&
31
+ self.orders == other.orders &&
32
+ self.limit == other.limit &&
33
+ self.key == other.key
34
+ end
35
+ alias :== :eql?
21
36
  end
22
37
  end
23
38
  end
@@ -17,7 +17,6 @@ module Arel
17
17
  end
18
18
 
19
19
  def frame(expr)
20
- raise ArgumentError, "Window frame cannot be set more than once" if @frame
21
20
  @framing = expr
22
21
  end
23
22
 
@@ -33,6 +32,17 @@ module Arel
33
32
  super
34
33
  @orders = @orders.map { |x| x.clone }
35
34
  end
35
+
36
+ def hash
37
+ [@orders, @framing].hash
38
+ end
39
+
40
+ def eql? other
41
+ self.class == other.class &&
42
+ self.orders == other.orders &&
43
+ self.framing == other.framing
44
+ end
45
+ alias :== :eql?
36
46
  end
37
47
 
38
48
  class NamedWindow < Window
@@ -47,6 +57,15 @@ module Arel
47
57
  super
48
58
  @name = other.name.clone
49
59
  end
60
+
61
+ def hash
62
+ super ^ @name.hash
63
+ end
64
+
65
+ def eql? other
66
+ super && self.name == other.name
67
+ end
68
+ alias :== :eql?
50
69
  end
51
70
 
52
71
  class Rows < Unary
@@ -61,7 +80,15 @@ module Arel
61
80
  end
62
81
  end
63
82
 
64
- class CurrentRow < Arel::Nodes::Node; end
83
+ class CurrentRow < Node
84
+ def hash
85
+ self.class.hash
86
+ end
87
+
88
+ def eql? other
89
+ self.class == other.class
90
+ end
91
+ end
65
92
 
66
93
  class Preceding < Unary
67
94
  def initialize(expr = nil)
@@ -75,4 +102,4 @@ module Arel
75
102
  end
76
103
  end
77
104
  end
78
- end
105
+ end
@@ -141,6 +141,10 @@ module Arel
141
141
  self
142
142
  end
143
143
 
144
+ def projections
145
+ @ctx.projections
146
+ end
147
+
144
148
  def projections= projections
145
149
  @ctx.projections = projections
146
150
  end
@@ -123,6 +123,19 @@ Arel 4.0.0 with no replacement. PEW PEW PEW!!!
123
123
  InsertManager.new(@engine)
124
124
  end
125
125
 
126
+ def hash
127
+ [@name, @engine, @aliases, @table_alias].hash
128
+ end
129
+
130
+ def eql? other
131
+ self.class == other.class &&
132
+ self.name == other.name &&
133
+ self.engine == other.engine &&
134
+ self.aliases == other.aliases &&
135
+ self.table_alias == other.table_alias
136
+ end
137
+ alias :== :eql?
138
+
126
139
  private
127
140
 
128
141
  def attributes_for columns
@@ -1,7 +1,5 @@
1
1
  module Arel
2
2
  class TreeManager
3
- # FIXME: Remove this.
4
- include Arel::Relation
5
3
  include Arel::FactoryMethods
6
4
 
7
5
  attr_reader :ast, :engine
@@ -12,6 +12,15 @@ module Arel
12
12
  end
13
13
 
14
14
  private
15
+
16
+ def visit_Arel_Nodes_Assignment o
17
+ if o.right.is_a? Arel::Nodes::BindParam
18
+ "#{visit o.left} = #{visit o.right}"
19
+ else
20
+ super
21
+ end
22
+ end
23
+
15
24
  def visit_Arel_Nodes_BindParam o
16
25
  if @block
17
26
  @block.call
@@ -19,6 +28,7 @@ module Arel
19
28
  super
20
29
  end
21
30
  end
31
+
22
32
  end
23
33
  end
24
34
  end
@@ -65,7 +65,6 @@ module Arel
65
65
  visit_edge o, "expr"
66
66
  end
67
67
  alias :visit_Arel_Nodes_Group :unary
68
- alias :visit_Arel_Nodes_BindParam :unary
69
68
  alias :visit_Arel_Nodes_Grouping :unary
70
69
  alias :visit_Arel_Nodes_Having :unary
71
70
  alias :visit_Arel_Nodes_Limit :unary
@@ -195,6 +194,7 @@ module Arel
195
194
  alias :visit_TrueClass :visit_String
196
195
  alias :visit_FalseClass :visit_String
197
196
  alias :visit_Arel_SqlLiteral :visit_String
197
+ alias :visit_Arel_Nodes_BindParam :visit_String
198
198
  alias :visit_Fixnum :visit_String
199
199
  alias :visit_BigDecimal :visit_String
200
200
  alias :visit_Float :visit_String
@@ -25,9 +25,8 @@ module Arel
25
25
  SELECT * FROM (
26
26
  SELECT raw_sql_.*, rownum raw_rnum_
27
27
  FROM (#{sql}) raw_sql_
28
- WHERE rownum <= #{offset.expr.to_i + limit}
29
28
  )
30
- WHERE #{visit offset}
29
+ WHERE raw_rnum_ >= #{offset.expr.to_i + 1 } and rownum <= #{limit}
31
30
  eosql
32
31
  end
33
32
 
@@ -4,6 +4,55 @@ require 'date'
4
4
  module Arel
5
5
  module Visitors
6
6
  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
+ ORDER_BY = ' ORDER BY ' # :nodoc:
51
+ WINDOW = ' WINDOW ' # :nodoc:
52
+ AND = ' AND ' # :nodoc:
53
+
54
+ DISTINCT = 'DISTINCT' # :nodoc:
55
+
7
56
  attr_accessor :last_column
8
57
 
9
58
  def initialize connection
@@ -23,7 +72,7 @@ module Arel
23
72
  def visit_Arel_Nodes_DeleteStatement o
24
73
  [
25
74
  "DELETE FROM #{visit o.relation}",
26
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?)
75
+ ("WHERE #{o.wheres.map { |x| visit x }.join AND}" unless o.wheres.empty?)
27
76
  ].compact.join ' '
28
77
  end
29
78
 
@@ -116,28 +165,80 @@ key on UpdateManager using UpdateManager#key=
116
165
  end
117
166
 
118
167
  def visit_Arel_Nodes_SelectStatement o
119
- [
120
- (visit(o.with) if o.with),
121
- o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
122
- ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
123
- (visit(o.limit) if o.limit),
124
- (visit(o.offset) if o.offset),
125
- (visit(o.lock) if o.lock),
126
- ].compact.join ' '
168
+ str = ''
169
+
170
+ if o.with
171
+ str << visit(o.with)
172
+ str << SPACE
173
+ end
174
+
175
+ o.cores.each { |x| str << visit_Arel_Nodes_SelectCore(x) }
176
+
177
+ unless o.orders.empty?
178
+ str << SPACE
179
+ str << ORDER_BY
180
+ len = o.orders.length - 1
181
+ o.orders.each_with_index { |x, i|
182
+ str << visit(x)
183
+ str << COMMA unless len == i
184
+ }
185
+ end
186
+
187
+ str << " #{visit(o.limit)}" if o.limit
188
+ str << " #{visit(o.offset)}" if o.offset
189
+ str << " #{visit(o.lock)}" if o.lock
190
+
191
+ str.strip!
192
+ str
127
193
  end
128
194
 
129
195
  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 ' '
196
+ str = "SELECT"
197
+
198
+ str << " #{visit(o.top)}" if o.top
199
+ str << " #{visit(o.set_quantifier)}" if o.set_quantifier
200
+
201
+ unless o.projections.empty?
202
+ str << SPACE
203
+ len = o.projections.length - 1
204
+ o.projections.each_with_index do |x, i|
205
+ str << visit(x)
206
+ str << COMMA unless len == i
207
+ end
208
+ end
209
+
210
+ str << " FROM #{visit(o.source)}" if o.source && !o.source.empty?
211
+
212
+ unless o.wheres.empty?
213
+ str << WHERE
214
+ len = o.wheres.length - 1
215
+ o.wheres.each_with_index do |x, i|
216
+ str << visit(x)
217
+ str << AND unless len == i
218
+ end
219
+ end
220
+
221
+ unless o.groups.empty?
222
+ str << GROUP_BY
223
+ len = o.groups.length - 1
224
+ o.groups.each_with_index do |x, i|
225
+ str << visit(x)
226
+ str << COMMA unless len == i
227
+ end
228
+ end
229
+
230
+ str << " #{visit(o.having)}" if o.having
231
+
232
+ unless o.windows.empty?
233
+ str << WINDOW
234
+ len = o.windows.length - 1
235
+ o.windows.each_with_index do |x, i|
236
+ str << visit(x)
237
+ str << COMMA unless len == i
238
+ end
239
+ end
240
+
241
+ str
141
242
  end
142
243
 
143
244
  def visit_Arel_Nodes_Bin o
@@ -145,7 +246,7 @@ key on UpdateManager using UpdateManager#key=
145
246
  end
146
247
 
147
248
  def visit_Arel_Nodes_Distinct o
148
- 'DISTINCT'
249
+ DISTINCT
149
250
  end
150
251
 
151
252
  def visit_Arel_Nodes_DistinctOn o
@@ -254,6 +355,10 @@ key on UpdateManager using UpdateManager#key=
254
355
  "(#{visit o.expr})"
255
356
  end
256
357
 
358
+ def visit_Arel_SelectManager o
359
+ "(#{o.to_sql.rstrip})"
360
+ end
361
+
257
362
  def visit_Arel_Nodes_Ascending o
258
363
  "#{visit o.expr} ASC"
259
364
  end
@@ -283,22 +388,22 @@ key on UpdateManager using UpdateManager#key=
283
388
  end
284
389
 
285
390
  def visit_Arel_Nodes_Sum o
286
- "SUM(#{o.expressions.map { |x|
391
+ "SUM(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
287
392
  visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
288
393
  end
289
394
 
290
395
  def visit_Arel_Nodes_Max o
291
- "MAX(#{o.expressions.map { |x|
396
+ "MAX(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
292
397
  visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
293
398
  end
294
399
 
295
400
  def visit_Arel_Nodes_Min o
296
- "MIN(#{o.expressions.map { |x|
401
+ "MIN(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
297
402
  visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
298
403
  end
299
404
 
300
405
  def visit_Arel_Nodes_Avg o
301
- "AVG(#{o.expressions.map { |x|
406
+ "AVG(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
302
407
  visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
303
408
  end
304
409
 
@@ -350,7 +455,12 @@ key on UpdateManager using UpdateManager#key=
350
455
  end
351
456
 
352
457
  def visit_Arel_Nodes_InnerJoin o
353
- "INNER JOIN #{visit o.left} #{visit o.right if o.right}"
458
+ s = "INNER JOIN #{visit o.left}"
459
+ if o.right
460
+ s << SPACE
461
+ s << visit(o.right)
462
+ end
463
+ s
354
464
  end
355
465
 
356
466
  def visit_Arel_Nodes_On o
@@ -475,10 +585,11 @@ key on UpdateManager using UpdateManager#key=
475
585
  alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
476
586
 
477
587
  def visit_Array o
478
- o.empty? ? 'NULL' : o.map { |x| visit x }.join(', ')
588
+ o.map { |x| visit x }.join(', ')
479
589
  end
480
590
 
481
591
  def quote value, column = nil
592
+ return value if Arel::Nodes::SqlLiteral === value
482
593
  @connection.quote value, column
483
594
  end
484
595