arel 2.0.10 → 2.1.0

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 (102) hide show
  1. data/History.txt +51 -4
  2. data/Manifest.txt +15 -31
  3. data/README.markdown +27 -3
  4. data/Rakefile +0 -2
  5. data/arel.gemspec +20 -19
  6. data/lib/arel.rb +9 -1
  7. data/lib/arel/alias_predication.rb +7 -0
  8. data/lib/arel/attributes/attribute.rb +10 -1
  9. data/lib/arel/crud.rb +43 -8
  10. data/lib/arel/expression.rb +1 -0
  11. data/lib/arel/factory_methods.rb +35 -0
  12. data/lib/arel/insert_manager.rb +5 -1
  13. data/lib/arel/math.rb +19 -0
  14. data/lib/arel/nodes.rb +32 -42
  15. data/lib/arel/nodes/and.rb +18 -1
  16. data/lib/arel/nodes/binary.rb +28 -0
  17. data/lib/arel/nodes/count.rb +0 -3
  18. data/lib/arel/nodes/function.rb +13 -2
  19. data/lib/arel/nodes/infix_operation.rb +43 -0
  20. data/lib/arel/nodes/join_source.rb +14 -0
  21. data/lib/arel/nodes/named_function.rb +14 -0
  22. data/lib/arel/nodes/node.rb +5 -2
  23. data/lib/arel/nodes/select_core.rb +24 -10
  24. data/lib/arel/nodes/select_statement.rb +8 -6
  25. data/lib/arel/nodes/sql_literal.rb +2 -0
  26. data/lib/arel/nodes/string_join.rb +2 -4
  27. data/lib/arel/nodes/table_alias.rb +7 -3
  28. data/lib/arel/nodes/{as.rb → terminal.rb} +1 -1
  29. data/lib/arel/nodes/unary.rb +17 -0
  30. data/lib/arel/nodes/unqualified_column.rb +4 -0
  31. data/lib/arel/nodes/update_statement.rb +4 -2
  32. data/lib/arel/nodes/with.rb +10 -0
  33. data/lib/arel/order_predications.rb +13 -0
  34. data/lib/arel/predications.rb +6 -24
  35. data/lib/arel/select_manager.rb +95 -40
  36. data/lib/arel/table.rb +32 -19
  37. data/lib/arel/tree_manager.rb +1 -0
  38. data/lib/arel/update_manager.rb +4 -0
  39. data/lib/arel/visitors.rb +2 -0
  40. data/lib/arel/visitors/depth_first.rb +19 -9
  41. data/lib/arel/visitors/dot.rb +42 -25
  42. data/lib/arel/visitors/ibm_db.rb +12 -0
  43. data/lib/arel/visitors/join_sql.rb +2 -23
  44. data/lib/arel/visitors/mssql.rb +7 -0
  45. data/lib/arel/visitors/mysql.rb +4 -0
  46. data/lib/arel/visitors/oracle.rb +2 -2
  47. data/lib/arel/visitors/postgresql.rb +2 -38
  48. data/lib/arel/visitors/to_sql.rb +148 -67
  49. data/test/attributes/test_attribute.rb +3 -3
  50. data/test/helper.rb +1 -1
  51. data/test/nodes/test_as.rb +6 -0
  52. data/test/nodes/test_bin.rb +23 -0
  53. data/test/nodes/test_named_function.rb +30 -0
  54. data/test/nodes/test_node.rb +10 -0
  55. data/test/nodes/test_not.rb +4 -7
  56. data/test/nodes/test_select_core.rb +23 -14
  57. data/test/support/fake_record.rb +21 -4
  58. data/test/test_attributes.rb +8 -0
  59. data/test/test_crud.rb +6 -12
  60. data/test/test_factory_methods.rb +34 -0
  61. data/test/test_insert_manager.rb +19 -0
  62. data/test/test_select_manager.rb +343 -64
  63. data/test/test_table.rb +36 -45
  64. data/test/visitors/test_depth_first.rb +29 -11
  65. data/test/visitors/test_dot.rb +47 -0
  66. data/test/visitors/test_ibm_db.rb +27 -0
  67. data/test/visitors/test_join_sql.rb +14 -7
  68. data/test/visitors/test_mssql.rb +9 -0
  69. data/test/visitors/test_oracle.rb +11 -10
  70. data/test/visitors/test_postgres.rb +12 -0
  71. data/test/visitors/test_to_sql.rb +80 -17
  72. metadata +80 -101
  73. data/lib/arel/nodes/assignment.rb +0 -6
  74. data/lib/arel/nodes/avg.rb +0 -6
  75. data/lib/arel/nodes/between.rb +0 -6
  76. data/lib/arel/nodes/does_not_match.rb +0 -6
  77. data/lib/arel/nodes/except.rb +0 -7
  78. data/lib/arel/nodes/exists.rb +0 -7
  79. data/lib/arel/nodes/greater_than.rb +0 -6
  80. data/lib/arel/nodes/greater_than_or_equal.rb +0 -6
  81. data/lib/arel/nodes/group.rb +0 -6
  82. data/lib/arel/nodes/grouping.rb +0 -6
  83. data/lib/arel/nodes/having.rb +0 -6
  84. data/lib/arel/nodes/intersect.rb +0 -7
  85. data/lib/arel/nodes/join.rb +0 -13
  86. data/lib/arel/nodes/less_than.rb +0 -6
  87. data/lib/arel/nodes/less_than_or_equal.rb +0 -6
  88. data/lib/arel/nodes/limit.rb +0 -7
  89. data/lib/arel/nodes/lock.rb +0 -6
  90. data/lib/arel/nodes/matches.rb +0 -6
  91. data/lib/arel/nodes/max.rb +0 -6
  92. data/lib/arel/nodes/min.rb +0 -6
  93. data/lib/arel/nodes/not.rb +0 -6
  94. data/lib/arel/nodes/not_equal.rb +0 -6
  95. data/lib/arel/nodes/not_in.rb +0 -6
  96. data/lib/arel/nodes/offset.rb +0 -7
  97. data/lib/arel/nodes/on.rb +0 -6
  98. data/lib/arel/nodes/or.rb +0 -6
  99. data/lib/arel/nodes/sum.rb +0 -6
  100. data/lib/arel/nodes/top.rb +0 -6
  101. data/lib/arel/nodes/union.rb +0 -7
  102. data/lib/arel/nodes/union_all.rb +0 -7
@@ -326,7 +326,7 @@ module Arel
326
326
 
327
327
  describe '#eq' do
328
328
  it 'should return an equality node' do
329
- attribute = Attribute.new nil, nil, nil
329
+ attribute = Attribute.new nil, nil
330
330
  equality = attribute.eq 1
331
331
  equality.left.must_equal attribute
332
332
  equality.right.must_equal 1
@@ -501,7 +501,7 @@ module Arel
501
501
  end
502
502
 
503
503
  it 'should return an in node' do
504
- attribute = Attribute.new nil, nil, nil
504
+ attribute = Attribute.new nil, nil
505
505
  node = Nodes::In.new attribute, [1,2,3]
506
506
  node.left.must_equal attribute
507
507
  node.right.must_equal [1, 2, 3]
@@ -554,7 +554,7 @@ module Arel
554
554
  end
555
555
 
556
556
  it 'should return a NotIn node' do
557
- attribute = Attribute.new nil, nil, nil
557
+ attribute = Attribute.new nil, nil
558
558
  node = Nodes::NotIn.new attribute, [1,2,3]
559
559
  node.left.must_equal attribute
560
560
  node.right.must_equal [1, 2, 3]
@@ -8,6 +8,6 @@ Arel::Table.engine = Arel::Sql::Engine.new(FakeRecord::Base.new)
8
8
 
9
9
  class Object
10
10
  def must_be_like other
11
- self.gsub(/\s+/, ' ').strip.must_equal other.gsub(/\s+/, ' ').strip
11
+ gsub(/\s+/, ' ').strip.must_equal other.gsub(/\s+/, ' ').strip
12
12
  end
13
13
  end
@@ -10,6 +10,12 @@ module Arel
10
10
  assert_equal attr, as.left
11
11
  assert_equal 'foo', as.right
12
12
  end
13
+
14
+ it 'converts right to SqlLiteral if a string' do
15
+ attr = Table.new(:users)[:id]
16
+ as = attr.as('foo')
17
+ assert_kind_of Arel::Nodes::SqlLiteral, as.right
18
+ end
13
19
  end
14
20
  end
15
21
  end
@@ -0,0 +1,23 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class TestBin < MiniTest::Unit::TestCase
6
+ def test_new
7
+ assert Arel::Nodes::Bin.new('zomg')
8
+ end
9
+
10
+ def test_default_to_sql
11
+ viz = Arel::Visitors::ToSql.new Table.engine
12
+ node = Arel::Nodes::Bin.new(Arel.sql('zomg'))
13
+ assert_equal 'zomg', viz.accept(node)
14
+ end
15
+
16
+ def test_mysql_to_sql
17
+ viz = Arel::Visitors::MySQL.new Table.engine
18
+ node = Arel::Nodes::Bin.new(Arel.sql('zomg'))
19
+ assert_equal 'BINARY zomg', viz.accept(node)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Nodes
5
+ class TestNamedFunction < MiniTest::Unit::TestCase
6
+ def test_construct
7
+ function = NamedFunction.new 'omg', 'zomg'
8
+ assert_equal 'omg', function.name
9
+ assert_equal 'zomg', function.expressions
10
+ end
11
+
12
+ def test_function_alias
13
+ function = NamedFunction.new 'omg', 'zomg'
14
+ function = function.as('wth')
15
+ assert_equal 'omg', function.name
16
+ assert_equal 'zomg', function.expressions
17
+ assert_kind_of SqlLiteral, function.alias
18
+ assert_equal 'wth', function.alias
19
+ end
20
+
21
+ def test_construct_with_alias
22
+ function = NamedFunction.new 'omg', 'zomg', 'wth'
23
+ assert_equal 'omg', function.name
24
+ assert_equal 'zomg', function.expressions
25
+ assert_kind_of SqlLiteral, function.alias
26
+ assert_equal 'wth', function.alias
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,11 +2,16 @@ require 'helper'
2
2
 
3
3
  module Arel
4
4
  class TestNode < MiniTest::Unit::TestCase
5
+ def test_includes_factory_methods
6
+ assert Node.new.respond_to?(:create_join)
7
+ end
8
+
5
9
  def test_all_nodes_are_nodes
6
10
  Nodes.constants.map { |k|
7
11
  Nodes.const_get(k)
8
12
  }.grep(Class).each do |klass|
9
13
  next if Nodes::SqlLiteral == klass
14
+ next if klass.name =~ /^Arel::Nodes::Test/
10
15
  assert klass.ancestors.include?(Nodes::Node), klass.name
11
16
  end
12
17
  end
@@ -24,5 +29,10 @@ module Arel
24
29
  node.each.each { |n| list << n }
25
30
  assert_equal [node], list
26
31
  end
32
+
33
+ def test_enumerable
34
+ node = Nodes::Node.new
35
+ assert_kind_of Enumerable, node
36
+ end
27
37
  end
28
38
  end
@@ -6,13 +6,10 @@ module Arel
6
6
  describe '#not' do
7
7
  it 'makes a NOT node' do
8
8
  attr = Table.new(:users)[:id]
9
- left = attr.eq(10)
10
- right = attr.eq(11)
11
- node = left.or right
12
- node.expr.left.must_equal left
13
- node.expr.right.must_equal right
14
-
15
- node.or(right).not
9
+ expr = attr.eq(10)
10
+ node = expr.not
11
+ node.must_be_kind_of Not
12
+ node.expr.must_equal expr
16
13
  end
17
14
  end
18
15
  end
@@ -1,22 +1,31 @@
1
1
  require 'helper'
2
2
 
3
- describe Arel::Nodes::SelectCore do
4
- describe "#clone" do
5
- it "clones froms, projections and wheres" do
6
- core = Arel::Nodes::SelectCore.new
7
- core.froms = %w[a b c]
8
- core.projections = %w[d e f]
9
- core.wheres = %w[g h i]
3
+ module Arel
4
+ module Nodes
5
+ class TestSelectCore < MiniTest::Unit::TestCase
6
+ def test_clone
7
+ core = Arel::Nodes::SelectCore.new
8
+ core.froms = %w[a b c]
9
+ core.projections = %w[d e f]
10
+ core.wheres = %w[g h i]
10
11
 
11
- dolly = core.clone
12
+ dolly = core.clone
12
13
 
13
- dolly.froms.must_equal core.froms
14
- dolly.projections.must_equal core.projections
15
- dolly.wheres.must_equal core.wheres
14
+ dolly.froms.must_equal core.froms
15
+ dolly.projections.must_equal core.projections
16
+ dolly.wheres.must_equal core.wheres
16
17
 
17
- dolly.froms.wont_be_same_as core.froms
18
- dolly.projections.wont_be_same_as core.projections
19
- dolly.wheres.wont_be_same_as core.wheres
18
+ dolly.froms.wont_be_same_as core.froms
19
+ dolly.projections.wont_be_same_as core.projections
20
+ dolly.wheres.wont_be_same_as core.wheres
21
+ end
22
+
23
+ def test_set_quantifier
24
+ core = Arel::Nodes::SelectCore.new
25
+ core.set_quantifier = Arel::Nodes::Distinct.new
26
+ viz = Arel::Visitors::ToSql.new Table.engine
27
+ assert_match 'DISTINCT', viz.accept(core)
28
+ end
20
29
  end
21
30
  end
22
31
  end
@@ -3,20 +3,29 @@ module FakeRecord
3
3
  end
4
4
 
5
5
  class Connection
6
- attr_reader :tables
6
+ attr_reader :tables, :columns_hash
7
7
 
8
8
  def initialize
9
- @tables = %w{ users photos developers }
9
+ @tables = %w{ users photos developers products}
10
10
  @columns = {
11
11
  'users' => [
12
12
  Column.new('id', :integer),
13
13
  Column.new('name', :string),
14
14
  Column.new('bool', :boolean),
15
- Column.new('created_at', :date),
15
+ Column.new('created_at', :date)
16
+ ],
17
+ 'products' => [
18
+ Column.new('id', :integer),
19
+ Column.new('price', :decimal)
16
20
  ]
17
21
  }
22
+ @columns_hash = {
23
+ 'users' => Hash[@columns['users'].map { |x| [x.name, x] }],
24
+ 'products' => Hash[@columns['products'].map { |x| [x.name, x] }]
25
+ }
18
26
  @primary_keys = {
19
- 'users' => 'id'
27
+ 'users' => 'id',
28
+ 'products' => 'id'
20
29
  }
21
30
  end
22
31
 
@@ -75,6 +84,14 @@ module FakeRecord
75
84
  def with_connection
76
85
  yield connection
77
86
  end
87
+
88
+ def table_exists? name
89
+ connection.tables.include? name.to_s
90
+ end
91
+
92
+ def columns_hash
93
+ connection.columns_hash
94
+ end
78
95
  end
79
96
 
80
97
  class Base
@@ -2,6 +2,14 @@ require 'helper'
2
2
 
3
3
  module Arel
4
4
  describe 'Attributes' do
5
+ it 'responds to lower' do
6
+ relation = Table.new(:users)
7
+ attribute = relation[:foo]
8
+ node = attribute.lower
9
+ assert_equal 'LOWER', node.name
10
+ assert_equal [attribute], node.expressions
11
+ end
12
+
5
13
  describe 'for' do
6
14
  it 'deals with unknown column types' do
7
15
  column = Struct.new(:type).new :crazy
@@ -35,10 +35,8 @@ module Arel
35
35
  table = Table.new :users
36
36
  fc = FakeCrudder.new
37
37
  fc.from table
38
- fc.insert [[table[:id], 'foo']]
39
- fc.engine.calls.find { |method, _|
40
- method == :insert
41
- }.wont_be_nil
38
+ im = fc.compile_insert [[table[:id], 'foo']]
39
+ assert_instance_of Arel::InsertManager, im
42
40
  end
43
41
  end
44
42
 
@@ -47,10 +45,8 @@ module Arel
47
45
  table = Table.new :users
48
46
  fc = FakeCrudder.new
49
47
  fc.from table
50
- fc.update [[table[:id], 'foo']]
51
- fc.engine.calls.find { |method, _|
52
- method == :update
53
- }.wont_be_nil
48
+ stmt = fc.compile_update [[table[:id], 'foo']]
49
+ assert_instance_of Arel::UpdateManager, stmt
54
50
  end
55
51
  end
56
52
 
@@ -59,10 +55,8 @@ module Arel
59
55
  table = Table.new :users
60
56
  fc = FakeCrudder.new
61
57
  fc.from table
62
- fc.delete
63
- fc.engine.calls.find { |method, _|
64
- method == :delete
65
- }.wont_be_nil
58
+ stmt = fc.compile_delete
59
+ assert_instance_of Arel::DeleteManager, stmt
66
60
  end
67
61
  end
68
62
  end
@@ -0,0 +1,34 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module FactoryMethods
5
+ class TestFactoryMethods < MiniTest::Unit::TestCase
6
+ class Factory
7
+ include Arel::FactoryMethods
8
+ end
9
+
10
+ def setup
11
+ @factory = Factory.new
12
+ end
13
+
14
+ def test_create_join
15
+ join = @factory.create_join :one, :two
16
+ assert_kind_of Nodes::Join, join
17
+ assert_equal :two, join.right
18
+ end
19
+
20
+ def test_create_on
21
+ on = @factory.create_on :one
22
+ assert_instance_of Nodes::On, on
23
+ assert_equal :one, on.expr
24
+ end
25
+
26
+ def test_lower
27
+ lower = @factory.lower :one
28
+ assert_instance_of Nodes::NamedFunction, lower
29
+ assert_equal 'LOWER', lower.name
30
+ assert_equal [:one], lower.expressions
31
+ end
32
+ end
33
+ end
34
+ end
@@ -9,6 +9,25 @@ module Arel
9
9
  end
10
10
 
11
11
  describe 'insert' do
12
+ it 'can create a Values node' do
13
+ table = Table.new(:users)
14
+ manager = Arel::InsertManager.new Table.engine
15
+ values = manager.create_values %w{ a b }, %w{ c d }
16
+
17
+ assert_kind_of Arel::Nodes::Values, values
18
+ assert_equal %w{ a b }, values.left
19
+ assert_equal %w{ c d }, values.right
20
+ end
21
+
22
+ it 'allows sql literals' do
23
+ table = Table.new(:users)
24
+ manager = Arel::InsertManager.new Table.engine
25
+ manager.values = manager.create_values [Arel.sql('*')], %w{ a }
26
+ manager.to_sql.must_be_like %{
27
+ INSERT INTO NULL VALUES (*)
28
+ }
29
+ end
30
+
12
31
  it "inserts false" do
13
32
  table = Table.new(:users)
14
33
  manager = Arel::InsertManager.new Table.engine
@@ -26,6 +26,21 @@ module Arel
26
26
  def quote_table_name thing; @engine.connection.quote_table_name thing end
27
27
  def quote_column_name thing; @engine.connection.quote_column_name thing end
28
28
  def quote thing, column; @engine.connection.quote thing, column end
29
+ def columns table, message = nil
30
+ @engine.connection.columns table, message
31
+ end
32
+
33
+ def columns_hash
34
+ @engine.connection.columns_hash
35
+ end
36
+
37
+ def table_exists? name
38
+ @engine.connection.table_exists? name
39
+ end
40
+
41
+ def tables
42
+ @engine.connection.tables
43
+ end
29
44
 
30
45
  def execute sql, name = nil, *args
31
46
  @executed << sql
@@ -36,6 +51,12 @@ module Arel
36
51
  end
37
52
 
38
53
  describe 'select manager' do
54
+ def test_join_sources
55
+ manager = Arel::SelectManager.new Table.engine
56
+ manager.join_sources << Arel::Nodes::StringJoin.new('foo')
57
+ assert_equal "SELECT FROM 'foo'", manager.to_sql
58
+ end
59
+
39
60
  describe 'backwards compatibility' do
40
61
  describe 'project' do
41
62
  it 'accepts symbols as sql literals' do
@@ -70,6 +91,34 @@ module Arel
70
91
  end
71
92
  end
72
93
 
94
+ describe 'as' do
95
+ it 'makes an AS node by grouping the AST' do
96
+ manager = Arel::SelectManager.new Table.engine
97
+ as = manager.as(Arel.sql('foo'))
98
+ assert_kind_of Arel::Nodes::Grouping, as.left
99
+ assert_equal manager.ast, as.left.expr
100
+ assert_equal 'foo', as.right
101
+ end
102
+
103
+ it 'converts right to SqlLiteral if a string' do
104
+ manager = Arel::SelectManager.new Table.engine
105
+ as = manager.as('foo')
106
+ assert_kind_of Arel::Nodes::SqlLiteral, as.right
107
+ end
108
+
109
+ it 'can make a subselect' do
110
+ manager = Arel::SelectManager.new Table.engine
111
+ manager.project Arel.star
112
+ manager.from Arel.sql('zomg')
113
+ as = manager.as(Arel.sql('foo'))
114
+
115
+ manager = Arel::SelectManager.new Table.engine
116
+ manager.project Arel.sql('name')
117
+ manager.from as
118
+ manager.to_sql.must_be_like "SELECT name FROM (SELECT * FROM zomg ) foo"
119
+ end
120
+ end
121
+
73
122
  describe 'from' do
74
123
  it 'ignores strings when table of same name exists' do
75
124
  table = Table.new :users
@@ -80,15 +129,47 @@ module Arel
80
129
  manager.project table['id']
81
130
  manager.to_sql.must_be_like 'SELECT "users"."id" FROM users'
82
131
  end
132
+
133
+ it 'should support any ast' do
134
+ table = Table.new :users
135
+ manager1 = Arel::SelectManager.new Table.engine
136
+
137
+ manager2 = Arel::SelectManager.new Table.engine
138
+ manager2.project(Arel.sql('*'))
139
+ manager2.from table
140
+
141
+ manager1.project Arel.sql('lol')
142
+ as = manager2.as Arel.sql('omg')
143
+ manager1.from(as)
144
+
145
+ manager1.to_sql.must_be_like %{
146
+ SELECT lol FROM (SELECT * FROM "users" ) omg
147
+ }
148
+ end
83
149
  end
84
150
 
85
- describe '#having' do
151
+ describe 'having' do
86
152
  it 'converts strings to SQLLiterals' do
87
153
  table = Table.new :users
88
154
  mgr = table.from table
89
155
  mgr.having 'foo'
90
156
  mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo }
91
157
  end
158
+
159
+ it 'can have multiple items specified separately' do
160
+ table = Table.new :users
161
+ mgr = table.from table
162
+ mgr.having 'foo'
163
+ mgr.having 'bar'
164
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo AND bar }
165
+ end
166
+
167
+ it 'can have multiple items specified together' do
168
+ table = Table.new :users
169
+ mgr = table.from table
170
+ mgr.having 'foo', 'bar'
171
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" HAVING foo AND bar }
172
+ end
92
173
  end
93
174
 
94
175
  describe 'on' do
@@ -118,6 +199,16 @@ module Arel
118
199
  m2.project "foo"
119
200
  mgr.to_sql.wont_equal m2.to_sql
120
201
  end
202
+
203
+ it 'makes updates to the correct copy' do
204
+ table = Table.new :users, :engine => Table.engine, :as => 'foo'
205
+ mgr = table.from table
206
+ m2 = mgr.clone
207
+ m3 = m2.clone
208
+ m2.project "foo"
209
+ mgr.to_sql.wont_equal m2.to_sql
210
+ m3.to_sql.must_equal mgr.to_sql
211
+ end
121
212
  end
122
213
 
123
214
  describe 'initialize' do
@@ -125,7 +216,7 @@ module Arel
125
216
  table = Table.new :users, :engine => Table.engine, :as => 'foo'
126
217
  mgr = table.from table
127
218
  mgr.skip 10
128
- mgr.to_sql.must_be_like %{ SELECT FROM "users" "foo" OFFSET 10 }
219
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" foo OFFSET 10 }
129
220
  end
130
221
  end
131
222
 
@@ -144,6 +235,32 @@ module Arel
144
235
  end
145
236
  end
146
237
 
238
+ describe 'offset' do
239
+ it 'should add an offset' do
240
+ table = Table.new :users
241
+ mgr = table.from table
242
+ mgr.offset = 10
243
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 }
244
+ end
245
+
246
+ it 'should remove an offset' do
247
+ table = Table.new :users
248
+ mgr = table.from table
249
+ mgr.offset = 10
250
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" OFFSET 10 }
251
+
252
+ mgr.offset = nil
253
+ mgr.to_sql.must_be_like %{ SELECT FROM "users" }
254
+ end
255
+
256
+ it 'should return the offset' do
257
+ table = Table.new :users
258
+ mgr = table.from table
259
+ mgr.offset = 10
260
+ assert_equal 10, mgr.offset
261
+ end
262
+ end
263
+
147
264
  describe 'exists' do
148
265
  it 'should create an exists clause' do
149
266
  table = Table.new(:users)
@@ -164,6 +281,130 @@ module Arel
164
281
  end
165
282
  end
166
283
 
284
+ describe 'union' do
285
+ before do
286
+ table = Table.new :users
287
+ @m1 = Arel::SelectManager.new Table.engine, table
288
+ @m1.project Arel.star
289
+ @m1.where(table[:age].lt(18))
290
+
291
+ @m2 = Arel::SelectManager.new Table.engine, table
292
+ @m2.project Arel.star
293
+ @m2.where(table[:age].gt(99))
294
+
295
+
296
+ end
297
+
298
+ it 'should union two managers' do
299
+ # FIXME should this union "managers" or "statements" ?
300
+ # FIXME this probably shouldn't return a node
301
+ node = @m1.union @m2
302
+
303
+ # maybe FIXME: decide when wrapper parens are needed
304
+ node.to_sql.must_be_like %{
305
+ ( SELECT * FROM "users" WHERE "users"."age" < 18 UNION SELECT * FROM "users" WHERE "users"."age" > 99 )
306
+ }
307
+ end
308
+
309
+ it 'should union all' do
310
+ node = @m1.union :all, @m2
311
+
312
+ node.to_sql.must_be_like %{
313
+ ( SELECT * FROM "users" WHERE "users"."age" < 18 UNION ALL SELECT * FROM "users" WHERE "users"."age" > 99 )
314
+ }
315
+ end
316
+
317
+ end
318
+
319
+ describe 'intersect' do
320
+ before do
321
+ table = Table.new :users
322
+ @m1 = Arel::SelectManager.new Table.engine, table
323
+ @m1.project Arel.star
324
+ @m1.where(table[:age].gt(18))
325
+
326
+ @m2 = Arel::SelectManager.new Table.engine, table
327
+ @m2.project Arel.star
328
+ @m2.where(table[:age].lt(99))
329
+
330
+
331
+ end
332
+
333
+ it 'should interect two managers' do
334
+ # FIXME should this intersect "managers" or "statements" ?
335
+ # FIXME this probably shouldn't return a node
336
+ node = @m1.intersect @m2
337
+
338
+ # maybe FIXME: decide when wrapper parens are needed
339
+ node.to_sql.must_be_like %{
340
+ ( SELECT * FROM "users" WHERE "users"."age" > 18 INTERSECT SELECT * FROM "users" WHERE "users"."age" < 99 )
341
+ }
342
+ end
343
+
344
+ end
345
+
346
+ describe 'except' do
347
+ before do
348
+ table = Table.new :users
349
+ @m1 = Arel::SelectManager.new Table.engine, table
350
+ @m1.project Arel.star
351
+ @m1.where(table[:age].in(18..60))
352
+
353
+ @m2 = Arel::SelectManager.new Table.engine, table
354
+ @m2.project Arel.star
355
+ @m2.where(table[:age].in(40..99))
356
+
357
+
358
+ end
359
+
360
+ it 'should except two managers' do
361
+ # FIXME should this except "managers" or "statements" ?
362
+ # FIXME this probably shouldn't return a node
363
+ node = @m1.except @m2
364
+
365
+ # maybe FIXME: decide when wrapper parens are needed
366
+ node.to_sql.must_be_like %{
367
+ ( SELECT * FROM "users" WHERE "users"."age" BETWEEN 18 AND 60 EXCEPT SELECT * FROM "users" WHERE "users"."age" BETWEEN 40 AND 99 )
368
+ }
369
+ end
370
+
371
+ end
372
+
373
+ describe 'with' do
374
+
375
+ it "should support WITH RECURSIVE" do
376
+ comments = Table.new(:comments)
377
+ comments_id = comments[:id]
378
+ comments_parent_id = comments[:parent_id]
379
+
380
+ replies = Table.new(:replies)
381
+ replies_id = replies[:id]
382
+
383
+ recursive_term = Arel::SelectManager.new Table.engine
384
+ recursive_term.from(comments).project(comments_id, comments_parent_id).where(comments_id.eq 42)
385
+
386
+ non_recursive_term = Arel::SelectManager.new Table.engine
387
+ non_recursive_term.from(comments).project(comments_id, comments_parent_id).join(replies).on(comments_parent_id.eq replies_id)
388
+
389
+ union = recursive_term.union(non_recursive_term)
390
+
391
+ as_statement = Arel::Nodes::As.new replies, union
392
+
393
+ manager = Arel::SelectManager.new Table.engine
394
+ manager.with(:recursive, as_statement).from(replies).project(Arel.star)
395
+
396
+ sql = manager.to_sql
397
+ sql.must_be_like %{
398
+ WITH RECURSIVE "replies" AS (
399
+ SELECT "comments"."id", "comments"."parent_id" FROM "comments" WHERE "comments"."id" = 42
400
+ UNION
401
+ SELECT "comments"."id", "comments"."parent_id" FROM "comments" INNER JOIN "replies" ON "comments"."parent_id" = "replies"."id"
402
+ )
403
+ SELECT * FROM "replies"
404
+ }
405
+ end
406
+ end
407
+
167
408
  describe 'ast' do
168
409
  it 'should return the ast' do
169
410
  table = Table.new :users
@@ -243,6 +484,29 @@ module Arel
243
484
  manager = Arel::SelectManager.new Table.engine
244
485
  manager.order(table[:id]).must_equal manager
245
486
  end
487
+
488
+ it 'has order attributes' do
489
+ table = Table.new :users
490
+ manager = Arel::SelectManager.new Table.engine
491
+ manager.project SqlLiteral.new '*'
492
+ manager.from table
493
+ manager.order table[:id].desc
494
+ manager.to_sql.must_be_like %{
495
+ SELECT * FROM "users" ORDER BY "users"."id" DESC
496
+ }
497
+ end
498
+
499
+ it 'has order attributes for expressions' do
500
+ table = Table.new :users
501
+ manager = Arel::SelectManager.new Table.engine
502
+ manager.project SqlLiteral.new '*'
503
+ manager.from table
504
+ manager.order table[:id].count.desc
505
+ manager.to_sql.must_be_like %{
506
+ SELECT * FROM "users" ORDER BY COUNT("users"."id") DESC
507
+ }
508
+ end
509
+
246
510
  end
247
511
 
248
512
  describe 'on' do
@@ -284,6 +548,41 @@ module Arel
284
548
  end
285
549
  end
286
550
 
551
+ it 'should hand back froms' do
552
+ relation = Arel::SelectManager.new Table.engine
553
+ assert_equal [], relation.froms
554
+ end
555
+
556
+ it 'should create and nodes' do
557
+ relation = Arel::SelectManager.new Table.engine
558
+ children = ['foo', 'bar', 'baz']
559
+ clause = relation.create_and children
560
+ assert_kind_of Arel::Nodes::And, clause
561
+ assert_equal children, clause.children
562
+ end
563
+
564
+ it 'should create insert managers' do
565
+ relation = Arel::SelectManager.new Table.engine
566
+ insert = relation.create_insert
567
+ assert_kind_of Arel::InsertManager, insert
568
+ end
569
+
570
+ it 'should create join nodes' do
571
+ relation = Arel::SelectManager.new Table.engine
572
+ join = relation.create_join 'foo', 'bar'
573
+ assert_kind_of Arel::Nodes::InnerJoin, join
574
+ assert_equal 'foo', join.left
575
+ assert_equal 'bar', join.right
576
+ end
577
+
578
+ it 'should create join nodes with a klass' do
579
+ relation = Arel::SelectManager.new Table.engine
580
+ join = relation.create_join 'foo', 'bar', Arel::Nodes::OuterJoin
581
+ assert_kind_of Arel::Nodes::OuterJoin, join
582
+ assert_equal 'foo', join.left
583
+ assert_equal 'bar', join.right
584
+ end
585
+
287
586
  describe 'join' do
288
587
  it 'responds to join' do
289
588
  left = Table.new :users
@@ -326,30 +625,27 @@ module Arel
326
625
  table = Table.new :users
327
626
  aliaz = table.alias
328
627
  manager = Arel::SelectManager.new Table.engine
329
- manager.from Nodes::InnerJoin.new(table, aliaz, table[:id].eq(aliaz[:id]))
628
+ manager.from Nodes::InnerJoin.new(aliaz, table[:id].eq(aliaz[:id]))
330
629
  manager.join_sql.must_be_like %{
331
630
  INNER JOIN "users" "users_2" "users"."id" = "users_2"."id"
332
631
  }
333
- manager.joins(manager).must_equal manager.join_sql
334
632
  end
335
633
 
336
634
  it 'returns outer join sql' do
337
635
  table = Table.new :users
338
636
  aliaz = table.alias
339
637
  manager = Arel::SelectManager.new Table.engine
340
- manager.from Nodes::OuterJoin.new(table, aliaz, table[:id].eq(aliaz[:id]))
638
+ manager.from Nodes::OuterJoin.new(aliaz, table[:id].eq(aliaz[:id]))
341
639
  manager.join_sql.must_be_like %{
342
640
  LEFT OUTER JOIN "users" "users_2" "users"."id" = "users_2"."id"
343
641
  }
344
- manager.joins(manager).must_equal manager.join_sql
345
642
  end
346
643
 
347
644
  it 'returns string join sql' do
348
645
  table = Table.new :users
349
646
  manager = Arel::SelectManager.new Table.engine
350
- manager.from Nodes::StringJoin.new(table, 'hello')
647
+ manager.from Nodes::StringJoin.new('hello')
351
648
  manager.join_sql.must_be_like %{ 'hello' }
352
- manager.joins(manager).must_equal manager.join_sql
353
649
  end
354
650
 
355
651
  it 'returns nil join sql' do
@@ -411,9 +707,9 @@ module Arel
411
707
  table = Table.new :users
412
708
  manager = Arel::SelectManager.new engine
413
709
  manager.from table
414
- manager.delete
710
+ stmt = manager.compile_delete
415
711
 
416
- engine.executed.last.must_be_like %{ DELETE FROM "users" }
712
+ stmt.to_sql.must_be_like %{ DELETE FROM "users" }
417
713
  end
418
714
 
419
715
  it "copies where" do
@@ -422,9 +718,9 @@ module Arel
422
718
  manager = Arel::SelectManager.new engine
423
719
  manager.from table
424
720
  manager.where table[:id].eq 10
425
- manager.delete
721
+ stmt = manager.compile_delete
426
722
 
427
- engine.executed.last.must_be_like %{
723
+ stmt.to_sql.must_be_like %{
428
724
  DELETE FROM "users" WHERE "users"."id" = 10
429
725
  }
430
726
  end
@@ -454,9 +750,10 @@ module Arel
454
750
  manager = Arel::SelectManager.new engine
455
751
  manager.from table
456
752
  manager.take 1
457
- manager.update(SqlLiteral.new('foo = bar'))
753
+ stmt = manager.compile_update(SqlLiteral.new('foo = bar'))
754
+ stmt.key = table['id']
458
755
 
459
- engine.executed.last.must_be_like %{
756
+ stmt.to_sql.must_be_like %{
460
757
  UPDATE "users" SET foo = bar
461
758
  WHERE "users"."id" IN (SELECT "users"."id" FROM "users" LIMIT 1)
462
759
  }
@@ -468,9 +765,10 @@ module Arel
468
765
  manager = Arel::SelectManager.new engine
469
766
  manager.from table
470
767
  manager.order :foo
471
- manager.update(SqlLiteral.new('foo = bar'))
768
+ stmt = manager.compile_update(SqlLiteral.new('foo = bar'))
769
+ stmt.key = table['id']
472
770
 
473
- engine.executed.last.must_be_like %{
771
+ stmt.to_sql.must_be_like %{
474
772
  UPDATE "users" SET foo = bar
475
773
  WHERE "users"."id" IN (SELECT "users"."id" FROM "users" ORDER BY foo)
476
774
  }
@@ -481,9 +779,9 @@ module Arel
481
779
  table = Table.new :users
482
780
  manager = Arel::SelectManager.new engine
483
781
  manager.from table
484
- manager.update(SqlLiteral.new('foo = bar'))
782
+ stmt = manager.compile_update(SqlLiteral.new('foo = bar'))
485
783
 
486
- engine.executed.last.must_be_like %{ UPDATE "users" SET foo = bar }
784
+ stmt.to_sql.must_be_like %{ UPDATE "users" SET foo = bar }
487
785
  end
488
786
 
489
787
  it 'copies where clauses' do
@@ -492,21 +790,35 @@ module Arel
492
790
  manager = Arel::SelectManager.new engine
493
791
  manager.where table[:id].eq 10
494
792
  manager.from table
495
- manager.update(table[:id] => 1)
793
+ stmt = manager.compile_update(table[:id] => 1)
496
794
 
497
- engine.executed.last.must_be_like %{
795
+ stmt.to_sql.must_be_like %{
498
796
  UPDATE "users" SET "id" = 1 WHERE "users"."id" = 10
499
797
  }
500
798
  end
501
799
 
800
+ it 'copies where clauses when nesting is triggered' do
801
+ engine = EngineProxy.new Table.engine
802
+ table = Table.new :users
803
+ manager = Arel::SelectManager.new engine
804
+ manager.where table[:foo].eq 10
805
+ manager.take 42
806
+ manager.from table
807
+ stmt = manager.compile_update(table[:id] => 1)
808
+
809
+ stmt.to_sql.must_be_like %{
810
+ UPDATE "users" SET "id" = 1 WHERE "users"."id" IN (SELECT "users"."id" FROM "users" WHERE "users"."foo" = 10 LIMIT 42)
811
+ }
812
+ end
813
+
502
814
  it 'executes an update statement' do
503
815
  engine = EngineProxy.new Table.engine
504
816
  table = Table.new :users
505
817
  manager = Arel::SelectManager.new engine
506
818
  manager.from table
507
- manager.update(table[:id] => 1)
819
+ stmt = manager.compile_update(table[:id] => 1)
508
820
 
509
- engine.executed.last.must_be_like %{
821
+ stmt.to_sql.must_be_like %{
510
822
  UPDATE "users" SET "id" = 1
511
823
  }
512
824
  end
@@ -555,6 +867,15 @@ module Arel
555
867
  manager = Arel::SelectManager.new Table.engine
556
868
  manager.take(1).must_equal manager
557
869
  end
870
+
871
+ it 'removes LIMIT when nil is passed' do
872
+ manager = Arel::SelectManager.new Table.engine
873
+ manager.limit = 10
874
+ assert_match('LIMIT', manager.to_sql)
875
+
876
+ manager.limit = nil
877
+ refute_match('LIMIT', manager.to_sql)
878
+ end
558
879
  end
559
880
 
560
881
  describe 'where' do
@@ -614,46 +935,4 @@ module Arel
614
935
  end
615
936
  end
616
937
  end
617
-
618
- describe 'set operations' do
619
- before do
620
- @table = Table.new :users
621
- @m1 = Arel::SelectManager.new Table.engine, @table
622
- @m1.project Arel.sql('*')
623
- @m2 = Arel::SelectManager.new Table.engine, @table
624
- @m2.project Arel.sql('*')
625
- end
626
-
627
- it 'supports union' do
628
- @m1.where(@table[:id].lt(18))
629
- @m2.where(@table[:id].gt(99))
630
- (@m1.union @m2).to_sql.must_be_like %{
631
- ( SELECT * FROM "users" WHERE "users"."id" < 18 UNION SELECT * FROM "users" WHERE "users"."id" > 99 )
632
- }
633
- end
634
-
635
- it 'supports union all' do
636
- @m1.where(@table[:id].lt(18))
637
- @m2.where(@table[:id].gt(99))
638
- (@m1.union :all, @m2).to_sql.must_be_like %{
639
- ( SELECT * FROM "users" WHERE "users"."id" < 18 UNION ALL SELECT * FROM "users" WHERE "users"."id" > 99 )
640
- }
641
- end
642
-
643
- it 'supports intersect' do
644
- @m1.where(@table[:id].gt(18))
645
- @m2.where(@table[:id].lt(99))
646
- (@m1.intersect @m2).to_sql.must_be_like %{
647
- ( SELECT * FROM "users" WHERE "users"."id" > 18 INTERSECT SELECT * FROM "users" WHERE "users"."id" < 99 )
648
- }
649
- end
650
-
651
- it 'supports except' do
652
- @m1.where(@table[:id].in(18..60))
653
- @m2.where(@table[:id].in(40..99))
654
- (@m1.except @m2).to_sql.must_be_like %{
655
- ( SELECT * FROM "users" WHERE "users"."id" BETWEEN 18 AND 60 EXCEPT SELECT * FROM "users" WHERE "users"."id" BETWEEN 40 AND 99 )
656
- }
657
- end
658
- end
659
938
  end