arel 5.0.1.20140414130214 → 6.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -13,11 +13,15 @@ module Arel
13
13
  def columns; @ast.columns end
14
14
  def values= val; @ast.values = val; end
15
15
 
16
+ def select select
17
+ @ast.select = select
18
+ end
19
+
16
20
  def insert fields
17
21
  return if fields.empty?
18
22
 
19
23
  if String === fields
20
- @ast.values = SqlLiteral.new(fields)
24
+ @ast.values = Nodes::SqlLiteral.new(fields)
21
25
  else
22
26
  @ast.relation ||= fields.first.first.relation
23
27
 
@@ -45,8 +45,49 @@ require 'arel/nodes/named_function'
45
45
  require 'arel/nodes/window'
46
46
 
47
47
  # joins
48
+ require 'arel/nodes/full_outer_join'
48
49
  require 'arel/nodes/inner_join'
49
50
  require 'arel/nodes/outer_join'
51
+ require 'arel/nodes/right_outer_join'
50
52
  require 'arel/nodes/string_join'
51
53
 
52
54
  require 'arel/nodes/sql_literal'
55
+
56
+ module Arel
57
+ module Nodes
58
+ class Casted < Arel::Nodes::Node # :nodoc:
59
+ attr_reader :val, :attribute
60
+ def initialize val, attribute
61
+ @val = val
62
+ @attribute = attribute
63
+ super()
64
+ end
65
+
66
+ def nil?; @val.nil?; end
67
+
68
+ def eql? other
69
+ self.class == other.class &&
70
+ self.val == other.val &&
71
+ self.attribute == other.attribute
72
+ end
73
+ alias :== :eql?
74
+ end
75
+
76
+ class Quoted < Arel::Nodes::Unary # :nodoc:
77
+ end
78
+
79
+ def self.build_quoted other, attribute = nil
80
+ case other
81
+ when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager
82
+ other
83
+ else
84
+ case attribute
85
+ when Arel::Attributes::Attribute
86
+ Casted.new other, attribute
87
+ else
88
+ Quoted.new other
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -3,12 +3,8 @@ module Arel
3
3
  class And < Arel::Nodes::Node
4
4
  attr_reader :children
5
5
 
6
- def initialize children, right = nil
6
+ def initialize children
7
7
  super()
8
- unless Array === children
9
- warn "(#{caller.first}) AND nodes should be created with a list"
10
- children = [children, right]
11
- end
12
8
  @children = children
13
9
  end
14
10
 
@@ -40,7 +40,9 @@ module Arel
40
40
  Matches
41
41
  NotEqual
42
42
  NotIn
43
+ NotRegexp
43
44
  Or
45
+ Regexp
44
46
  Union
45
47
  UnionAll
46
48
  Intersect
@@ -1,8 +1,6 @@
1
1
  module Arel
2
2
  module Nodes
3
-
4
3
  class Extract < Arel::Nodes::Unary
5
- include Arel::Expression
6
4
  include Arel::Predications
7
5
 
8
6
  attr_accessor :field
@@ -0,0 +1,6 @@
1
+ module Arel
2
+ module Nodes
3
+ class FullOuterJoin < Arel::Nodes::Join
4
+ end
5
+ end
6
+ end
@@ -1,7 +1,6 @@
1
1
  module Arel
2
2
  module Nodes
3
3
  class Function < Arel::Nodes::Node
4
- include Arel::Expression
5
4
  include Arel::Predications
6
5
  include Arel::WindowPredications
7
6
  attr_accessor :expressions, :alias, :distinct
@@ -1,29 +1,32 @@
1
1
  module Arel
2
2
  module Nodes
3
3
  class InsertStatement < Arel::Nodes::Node
4
- attr_accessor :relation, :columns, :values
4
+ attr_accessor :relation, :columns, :values, :select
5
5
 
6
6
  def initialize
7
7
  super()
8
8
  @relation = nil
9
9
  @columns = []
10
10
  @values = nil
11
+ @select = nil
11
12
  end
12
13
 
13
14
  def initialize_copy other
14
15
  super
15
16
  @columns = @columns.clone
16
17
  @values = @values.clone if @values
18
+ @select = @select.clone if @select
17
19
  end
18
20
 
19
21
  def hash
20
- [@relation, @columns, @values].hash
22
+ [@relation, @columns, @values, @select].hash
21
23
  end
22
24
 
23
25
  def eql? other
24
26
  self.class == other.class &&
25
27
  self.relation == other.relation &&
26
28
  self.columns == other.columns &&
29
+ self.select == other.select &&
27
30
  self.values == other.values
28
31
  end
29
32
  alias :== :eql?
@@ -1,3 +1,5 @@
1
+ require 'arel/collectors/sql_string'
2
+
1
3
  module Arel
2
4
  module Nodes
3
5
  ###
@@ -42,7 +44,9 @@ module Arel
42
44
  #
43
45
  # Maybe we should just use `Table.engine`? :'(
44
46
  def to_sql engine = Table.engine
45
- engine.connection.visitor.accept self
47
+ collector = Arel::Collectors::SQLString.new
48
+ collector = engine.connection.visitor.accept self, collector
49
+ collector.value
46
50
  end
47
51
 
48
52
  # Iterate through AST, nodes will be yielded depth-first
@@ -0,0 +1,6 @@
1
+ module Arel
2
+ module Nodes
3
+ class RightOuterJoin < Arel::Nodes::Join
4
+ end
5
+ end
6
+ end
@@ -1,11 +1,12 @@
1
1
  module Arel
2
2
  module Nodes
3
3
  class Window < Arel::Nodes::Node
4
- include Arel::Expression
5
- attr_accessor :orders, :framing
4
+ attr_accessor :orders, :framing, :partitions
6
5
 
7
6
  def initialize
8
7
  @orders = []
8
+ @partitions = []
9
+ @framing = nil
9
10
  end
10
11
 
11
12
  def order *expr
@@ -16,16 +17,32 @@ module Arel
16
17
  self
17
18
  end
18
19
 
20
+ def partition *expr
21
+ # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
22
+ @partitions.concat expr.map { |x|
23
+ String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
24
+ }
25
+ self
26
+ end
27
+
19
28
  def frame(expr)
20
29
  @framing = expr
21
30
  end
22
31
 
23
32
  def rows(expr = nil)
24
- frame(Rows.new(expr))
33
+ if @framing
34
+ Rows.new(expr)
35
+ else
36
+ frame(Rows.new(expr))
37
+ end
25
38
  end
26
39
 
27
40
  def range(expr = nil)
28
- frame(Range.new(expr))
41
+ if @framing
42
+ Range.new(expr)
43
+ else
44
+ frame(Range.new(expr))
45
+ end
29
46
  end
30
47
 
31
48
  def initialize_copy other
@@ -40,7 +57,8 @@ module Arel
40
57
  def eql? other
41
58
  self.class == other.class &&
42
59
  self.orders == other.orders &&
43
- self.framing == other.framing
60
+ self.framing == other.framing &&
61
+ self.partitions == other.partitions
44
62
  end
45
63
  alias :== :eql?
46
64
  end
@@ -1,7 +1,7 @@
1
1
  module Arel
2
2
  module Predications
3
3
  def not_eq other
4
- Nodes::NotEqual.new self, other
4
+ Nodes::NotEqual.new self, Nodes.build_quoted(other, self)
5
5
  end
6
6
 
7
7
  def not_eq_any others
@@ -13,7 +13,7 @@ module Arel
13
13
  end
14
14
 
15
15
  def eq other
16
- Nodes::Equality.new self, other
16
+ Nodes::Equality.new self, Nodes.build_quoted(other, self)
17
17
  end
18
18
 
19
19
  def eq_any others
@@ -21,7 +21,7 @@ module Arel
21
21
  end
22
22
 
23
23
  def eq_all others
24
- grouping_all :eq, others
24
+ grouping_all :eq, others.map { |x| Nodes.build_quoted(x, self) }
25
25
  end
26
26
 
27
27
  def in other
@@ -29,23 +29,27 @@ module Arel
29
29
  when Arel::SelectManager
30
30
  Arel::Nodes::In.new(self, other.ast)
31
31
  when Range
32
- if other.begin == -Float::INFINITY && other.end == Float::INFINITY
33
- Nodes::NotIn.new self, []
32
+ if other.begin == -Float::INFINITY
33
+ if other.end == Float::INFINITY
34
+ Nodes::NotIn.new self, []
35
+ elsif other.exclude_end?
36
+ Nodes::LessThan.new(self, Nodes.build_quoted(other.end, self))
37
+ else
38
+ Nodes::LessThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
39
+ end
34
40
  elsif other.end == Float::INFINITY
35
- Nodes::GreaterThanOrEqual.new(self, other.begin)
36
- elsif other.begin == -Float::INFINITY && other.exclude_end?
37
- Nodes::LessThan.new(self, other.end)
38
- elsif other.begin == -Float::INFINITY
39
- Nodes::LessThanOrEqual.new(self, other.end)
41
+ Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.begin, self))
40
42
  elsif other.exclude_end?
41
- left = Nodes::GreaterThanOrEqual.new(self, other.begin)
42
- right = Nodes::LessThan.new(self, other.end)
43
+ left = Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.begin, self))
44
+ right = Nodes::LessThan.new(self, Nodes.build_quoted(other.end, self))
43
45
  Nodes::And.new [left, right]
44
46
  else
45
- Nodes::Between.new(self, Nodes::And.new([other.begin, other.end]))
47
+ Nodes::Between.new(self, Nodes::And.new([Nodes.build_quoted(other.begin, self), Nodes.build_quoted(other.end, self)]))
46
48
  end
49
+ when Array
50
+ Nodes::In.new self, other.map { |x| Nodes.build_quoted(x, self) }
47
51
  else
48
- Nodes::In.new self, other
52
+ Nodes::In.new self, Nodes.build_quoted(other, self)
49
53
  end
50
54
  end
51
55
 
@@ -62,25 +66,29 @@ module Arel
62
66
  when Arel::SelectManager
63
67
  Arel::Nodes::NotIn.new(self, other.ast)
64
68
  when Range
65
- if other.begin == -Float::INFINITY && other.end == Float::INFINITY
66
- Nodes::In.new self, []
67
- elsif other.end == Float::INFINITY
68
- Nodes::LessThan.new(self, other.begin)
69
- elsif other.begin == -Float::INFINITY && other.exclude_end?
70
- Nodes::GreaterThanOrEqual.new(self, other.end)
71
- elsif other.begin == -Float::INFINITY
72
- Nodes::GreaterThan.new(self, other.end)
73
- elsif other.exclude_end?
74
- left = Nodes::LessThan.new(self, other.begin)
75
- right = Nodes::GreaterThanOrEqual.new(self, other.end)
76
- Nodes::Or.new left, right
69
+ if other.begin == -Float::INFINITY # The range begins with negative infinity
70
+ if other.end == Float::INFINITY
71
+ Nodes::In.new self, [] # The range is infinite, so return an empty range
72
+ elsif other.exclude_end?
73
+ Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
74
+ else
75
+ Nodes::GreaterThan.new(self, Nodes.build_quoted(other.end, self))
76
+ end
77
+ elsif other.end == Float::INFINITY
78
+ Nodes::LessThan.new(self, Nodes.build_quoted(other.begin, self))
77
79
  else
78
- left = Nodes::LessThan.new(self, other.begin)
79
- right = Nodes::GreaterThan.new(self, other.end)
80
+ left = Nodes::LessThan.new(self, Nodes.build_quoted(other.begin, self))
81
+ if other.exclude_end?
82
+ right = Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
83
+ else
84
+ right = Nodes::GreaterThan.new(self, Nodes.build_quoted(other.end, self))
85
+ end
80
86
  Nodes::Or.new left, right
81
87
  end
88
+ when Array
89
+ Nodes::NotIn.new self, other.map { |x| Nodes.build_quoted(x, self) }
82
90
  else
83
- Nodes::NotIn.new self, other
91
+ Nodes::NotIn.new self, Nodes.build_quoted(other, self)
84
92
  end
85
93
  end
86
94
 
@@ -93,7 +101,7 @@ module Arel
93
101
  end
94
102
 
95
103
  def matches other
96
- Nodes::Matches.new self, other
104
+ Nodes::Matches.new self, Nodes.build_quoted(other, self)
97
105
  end
98
106
 
99
107
  def matches_any others
@@ -105,7 +113,7 @@ module Arel
105
113
  end
106
114
 
107
115
  def does_not_match other
108
- Nodes::DoesNotMatch.new self, other
116
+ Nodes::DoesNotMatch.new self, Nodes.build_quoted(other, self)
109
117
  end
110
118
 
111
119
  def does_not_match_any others
@@ -117,7 +125,7 @@ module Arel
117
125
  end
118
126
 
119
127
  def gteq right
120
- Nodes::GreaterThanOrEqual.new self, right
128
+ Nodes::GreaterThanOrEqual.new self, Nodes.build_quoted(right, self)
121
129
  end
122
130
 
123
131
  def gteq_any others
@@ -129,7 +137,7 @@ module Arel
129
137
  end
130
138
 
131
139
  def gt right
132
- Nodes::GreaterThan.new self, right
140
+ Nodes::GreaterThan.new self, Nodes.build_quoted(right, self)
133
141
  end
134
142
 
135
143
  def gt_any others
@@ -1,3 +1,5 @@
1
+ require 'arel/collectors/sql_string'
2
+
1
3
  module Arel
2
4
  class SelectManager < Arel::TreeManager
3
5
  include Arel::Crud
@@ -17,7 +19,7 @@ module Arel
17
19
  end
18
20
 
19
21
  def limit
20
- @ast.limit && @ast.limit.expr
22
+ @ast.limit && @ast.limit.expr.expr
21
23
  end
22
24
  alias :taken :limit
23
25
 
@@ -84,9 +86,6 @@ module Arel
84
86
 
85
87
  def from table
86
88
  table = Nodes::SqlLiteral.new(table) if String === table
87
- # FIXME: this is a hack to support
88
- # test_with_two_tables_in_from_without_getting_double_quoted
89
- # from the AR tests.
90
89
 
91
90
  case table
92
91
  when Nodes::Join
@@ -107,7 +106,7 @@ module Arel
107
106
 
108
107
  case relation
109
108
  when String, Nodes::SqlLiteral
110
- raise if relation.blank?
109
+ raise if relation.empty?
111
110
  klass = Nodes::StringJoin
112
111
  end
113
112
 
@@ -115,6 +114,10 @@ module Arel
115
114
  self
116
115
  end
117
116
 
117
+ def outer_join relation
118
+ join(relation, Nodes::OuterJoin)
119
+ end
120
+
118
121
  def having *exprs
119
122
  @ctx.having = Nodes::Having.new(collapse(exprs, @ctx.having))
120
123
  self
@@ -130,7 +133,7 @@ module Arel
130
133
  # FIXME: converting these to SQLLiterals is probably not good, but
131
134
  # rails tests require it.
132
135
  @ctx.projections.concat projections.map { |x|
133
- STRING_OR_SYMBOL_CLASS.include?(x.class) ? SqlLiteral.new(x.to_s) : x
136
+ STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
134
137
  }
135
138
  self
136
139
  end
@@ -149,6 +152,7 @@ module Arel
149
152
  else
150
153
  @ctx.set_quantifier = nil
151
154
  end
155
+ self
152
156
  end
153
157
 
154
158
  def order *expr
@@ -167,7 +171,7 @@ module Arel
167
171
  return if @ctx.wheres.empty?
168
172
 
169
173
  viz = Visitors::WhereSql.new @engine.connection
170
- Nodes::SqlLiteral.new viz.accept @ctx
174
+ Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value
171
175
  end
172
176
 
173
177
  def union operation, other = nil
@@ -203,8 +207,8 @@ module Arel
203
207
 
204
208
  def take limit
205
209
  if limit
206
- @ast.limit = Nodes::Limit.new(limit)
207
- @ctx.top = Nodes::Top.new(limit)
210
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit))
211
+ @ctx.top = Nodes::Top.new(Nodes.build_quoted(limit))
208
212
  else
209
213
  @ast.limit = nil
210
214
  @ctx.top = nil
@@ -213,20 +217,6 @@ module Arel
213
217
  end
214
218
  alias limit= take
215
219
 
216
- def join_sql
217
- return nil if @ctx.source.right.empty?
218
-
219
- sql = visitor.dup.extend(Visitors::JoinSql).accept @ctx
220
- Nodes::SqlLiteral.new sql
221
- end
222
-
223
- def order_clauses
224
- visitor = Visitors::OrderClauses.new(@engine.connection)
225
- visitor.accept(@ast).map { |x|
226
- Nodes::SqlLiteral.new x
227
- }
228
- end
229
-
230
220
  def join_sources
231
221
  @ctx.source.right
232
222
  end
@@ -235,14 +225,6 @@ module Arel
235
225
  @ctx.source
236
226
  end
237
227
 
238
- def joins manager
239
- if $VERBOSE
240
- warn "joins is deprecated and will be removed in 4.0.0"
241
- warn "please remove your call to joins from #{caller.first}"
242
- end
243
- manager.join_sql
244
- end
245
-
246
228
  class Row < Struct.new(:data) # :nodoc:
247
229
  def id
248
230
  data['id']
@@ -255,12 +237,6 @@ module Arel
255
237
  end
256
238
  end
257
239
 
258
- def to_a # :nodoc:
259
- warn "to_a is deprecated. Please remove it from #{caller[0]}"
260
- # FIXME: I think `select` should be made public...
261
- @engine.connection.send(:select, to_sql, 'AREL').map { |x| Row.new(x) }
262
- end
263
-
264
240
  private
265
241
  def collapse exprs, existing = nil
266
242
  exprs = exprs.unshift(existing.expr) if existing