arel 5.0.1.20140414130214 → 6.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 (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