arel 2.0.10 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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