arel 3.0.3 → 4.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 (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