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
@@ -18,8 +18,8 @@ module Arel
18
18
  DISPATCH[self.class]
19
19
  end
20
20
 
21
- def visit object, attribute = nil
22
- send dispatch[object.class], object, attribute
21
+ def visit object
22
+ send dispatch[object.class], object
23
23
  rescue NoMethodError => e
24
24
  raise e if respond_to?(dispatch[object.class], true)
25
25
  superklass = object.class.ancestors.find { |klass|
@@ -1,8 +1,9 @@
1
1
  module Arel
2
2
  module Visitors
3
3
  class WhereSql < Arel::Visitors::ToSql
4
- def visit_Arel_Nodes_SelectCore o, a
5
- "WHERE #{o.wheres.map { |x| visit x, a }.join ' AND ' }"
4
+ def visit_Arel_Nodes_SelectCore o, collector
5
+ collector << "WHERE "
6
+ inject_join o.wheres, collector, ' AND '
6
7
  end
7
8
  end
8
9
  end
@@ -74,6 +74,17 @@ module Arel
74
74
  SELECT "users"."id" FROM "users" WHERE "users"."id" > 10
75
75
  }
76
76
  end
77
+
78
+ it 'should handle comparing with a subquery' do
79
+ users = Table.new(:users)
80
+
81
+ avg = users.project(users[:karma].average)
82
+ mgr = users.project(Arel.star).where(users[:karma].gt(avg))
83
+
84
+ mgr.to_sql.must_be_like %{
85
+ SELECT * FROM "users" WHERE "users"."karma" > (SELECT AVG("users"."karma") AS avg_id FROM "users")
86
+ }
87
+ end
77
88
  end
78
89
 
79
90
  describe '#gt_any' do
@@ -329,7 +340,7 @@ module Arel
329
340
  attribute = Attribute.new nil, nil
330
341
  equality = attribute.eq 1
331
342
  equality.left.must_equal attribute
332
- equality.right.must_equal 1
343
+ equality.right.val.must_equal 1
333
344
  equality.must_be_kind_of Nodes::Equality
334
345
  end
335
346
 
@@ -550,8 +561,6 @@ module Arel
550
561
  end
551
562
 
552
563
  describe '#not_in' do
553
- it 'can be constructed with a list' do
554
- end
555
564
 
556
565
  it 'should return a NotIn node' do
557
566
  attribute = Attribute.new nil, nil
@@ -0,0 +1,70 @@
1
+ require 'helper'
2
+ require 'arel/collectors/bind'
3
+
4
+ module Arel
5
+ module Collectors
6
+ class TestBindCollector < Arel::Test
7
+ def setup
8
+ @conn = FakeRecord::Base.new
9
+ @visitor = Visitors::ToSql.new @conn.connection
10
+ super
11
+ end
12
+
13
+ def collect node
14
+ @visitor.accept(node, Collectors::Bind.new)
15
+ end
16
+
17
+ def compile node
18
+ collect(node).value
19
+ end
20
+
21
+ def ast_with_binds bv
22
+ table = Table.new(:users)
23
+ manager = Arel::SelectManager.new Table.engine, table
24
+ manager.where(table[:age].eq(bv))
25
+ manager.where(table[:name].eq(bv))
26
+ manager.ast
27
+ end
28
+
29
+ def test_leaves_binds
30
+ node = Nodes::BindParam.new 'omg'
31
+ list = compile node
32
+ assert_equal node, list.first
33
+ assert_equal node.class, list.first.class
34
+ end
35
+
36
+ def test_adds_strings
37
+ bv = Nodes::BindParam.new('?')
38
+ list = compile ast_with_binds bv
39
+ assert_operator list.length, :>, 0
40
+ assert_equal bv, list.grep(Nodes::BindParam).first
41
+ assert_equal bv.class, list.grep(Nodes::BindParam).first.class
42
+ end
43
+
44
+ def test_substitute_binds
45
+ bv = Nodes::BindParam.new('?')
46
+ collector = collect ast_with_binds bv
47
+
48
+ values = collector.value
49
+
50
+ offsets = values.map.with_index { |v,i|
51
+ [v,i]
52
+ }.find_all { |(v,_)| Nodes::BindParam === v }.map(&:last)
53
+
54
+ list = collector.substitute_binds ["hello", "world"]
55
+ assert_equal "hello", list[offsets[0]]
56
+ assert_equal "world", list[offsets[1]]
57
+
58
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = hello AND "users"."name" = world', list.join
59
+ end
60
+
61
+ def test_compile
62
+ bv = Nodes::BindParam.new('?')
63
+ collector = collect ast_with_binds bv
64
+
65
+ sql = collector.compile ["hello", "world"]
66
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = hello AND "users"."name" = world', sql
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,38 @@
1
+ require 'helper'
2
+ require 'arel/collectors/bind'
3
+
4
+ module Arel
5
+ module Collectors
6
+ class TestSqlString < Arel::Test
7
+ def setup
8
+ @conn = FakeRecord::Base.new
9
+ @visitor = Visitors::ToSql.new @conn.connection
10
+ super
11
+ end
12
+
13
+ def collect node
14
+ @visitor.accept(node, Collectors::SQLString.new)
15
+ end
16
+
17
+ def compile node
18
+ collect(node).value
19
+ end
20
+
21
+ def ast_with_binds bv
22
+ table = Table.new(:users)
23
+ manager = Arel::SelectManager.new Table.engine, table
24
+ manager.where(table[:age].eq(bv))
25
+ manager.where(table[:name].eq(bv))
26
+ manager.ast
27
+ end
28
+
29
+ def test_compile
30
+ bv = Nodes::BindParam.new('?')
31
+ collector = collect ast_with_binds bv
32
+
33
+ sql = collector.compile ["hello", "world"]
34
+ assert_equal 'SELECT FROM "users" WHERE "users"."age" = ? AND "users"."name" = ?', sql
35
+ end
36
+ end
37
+ end
38
+ end
@@ -4,10 +4,19 @@ require 'fileutils'
4
4
  require 'arel'
5
5
 
6
6
  require 'support/fake_record'
7
- Arel::Table.engine = Arel::Sql::Engine.new(FakeRecord::Base.new)
7
+ Arel::Table.engine = FakeRecord::Base.new
8
8
 
9
9
  class Object
10
10
  def must_be_like other
11
11
  gsub(/\s+/, ' ').strip.must_equal other.gsub(/\s+/, ' ').strip
12
12
  end
13
13
  end
14
+
15
+ module Arel
16
+ class Test < MiniTest::Test
17
+ def assert_like expected, actual
18
+ assert_equal expected.gsub(/\s+/, ' ').strip,
19
+ actual.gsub(/\s+/, ' ').strip
20
+ end
21
+ end
22
+ end
@@ -10,13 +10,13 @@ module Arel
10
10
  def test_default_to_sql
11
11
  viz = Arel::Visitors::ToSql.new Table.engine.connection_pool
12
12
  node = Arel::Nodes::Bin.new(Arel.sql('zomg'))
13
- assert_equal 'zomg', viz.accept(node)
13
+ assert_equal 'zomg', viz.accept(node, Collectors::SQLString.new).value
14
14
  end
15
15
 
16
16
  def test_mysql_to_sql
17
17
  viz = Arel::Visitors::MySQL.new Table.engine.connection_pool
18
18
  node = Arel::Nodes::Bin.new(Arel.sql('zomg'))
19
- assert_equal 'BINARY zomg', viz.accept(node)
19
+ assert_equal 'BINARY zomg', viz.accept(node, Collectors::SQLString.new).value
20
20
  end
21
21
 
22
22
  def test_equality_with_same_ivars
@@ -1,12 +1,6 @@
1
1
  require 'helper'
2
2
 
3
3
  describe Arel::Nodes::Count do
4
- describe 'backwards compatibility' do
5
- it 'must be an expression' do
6
- Arel::Nodes::Count.new('foo').must_be_kind_of Arel::Expression
7
- end
8
- end
9
-
10
4
  describe "as" do
11
5
  it 'should alias the count' do
12
6
  table = Arel::Table.new :users
@@ -43,7 +43,7 @@ module Arel
43
43
  attr = Table.new(:users)[:id]
44
44
  test = attr.eq(10)
45
45
  test.to_sql engine
46
- engine.connection.quote_count.must_equal 2
46
+ engine.connection.quote_count.must_equal 3
47
47
  end
48
48
  end
49
49
  end
@@ -4,7 +4,7 @@ module Arel
4
4
  module Nodes
5
5
  describe 'Grouping' do
6
6
  it 'should create Equality nodes' do
7
- grouping = Grouping.new('foo')
7
+ grouping = Grouping.new(Nodes.build_quoted('foo'))
8
8
  grouping.eq('foo').to_sql.must_be_like %q{('foo') = 'foo'}
9
9
  end
10
10
 
@@ -18,7 +18,7 @@ module Arel
18
18
  assert_equal 'zomg', aliaz.right
19
19
  end
20
20
 
21
- def test_opertaion_ordering
21
+ def test_operation_ordering
22
22
  operation = InfixOperation.new :+, 1, 2
23
23
  ordering = operation.desc
24
24
  assert_kind_of Descending, ordering
@@ -11,20 +11,20 @@ module Arel
11
11
 
12
12
  dolly = core.clone
13
13
 
14
- dolly.froms.must_equal core.froms
15
- dolly.projections.must_equal core.projections
16
- dolly.wheres.must_equal core.wheres
14
+ assert_equal core.froms, dolly.froms
15
+ assert_equal core.projections, dolly.projections
16
+ assert_equal core.wheres, dolly.wheres
17
17
 
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
18
+ refute_same core.froms, dolly.froms
19
+ refute_same core.projections, dolly.projections
20
+ refute_same core.wheres, dolly.wheres
21
21
  end
22
22
 
23
23
  def test_set_quantifier
24
24
  core = Arel::Nodes::SelectCore.new
25
25
  core.set_quantifier = Arel::Nodes::Distinct.new
26
26
  viz = Arel::Visitors::ToSql.new Table.engine.connection_pool
27
- assert_match 'DISTINCT', viz.accept(core)
27
+ assert_match 'DISTINCT', viz.accept(core, Collectors::SQLString.new).value
28
28
  end
29
29
 
30
30
  def test_equality_with_same_ivars
@@ -5,7 +5,11 @@ module Arel
5
5
  module Nodes
6
6
  describe 'sql literal' do
7
7
  before do
8
- @visitor = Visitors::ToSql.new Table.engine.connection_pool
8
+ @visitor = Visitors::ToSql.new Table.engine.connection
9
+ end
10
+
11
+ def compile node
12
+ @visitor.accept(node, Collectors::SQLString.new).value
9
13
  end
10
14
 
11
15
  describe 'sql' do
@@ -18,19 +22,19 @@ module Arel
18
22
  describe 'count' do
19
23
  it 'makes a count node' do
20
24
  node = SqlLiteral.new('*').count
21
- @visitor.accept(node).must_be_like %{ COUNT(*) }
25
+ compile(node).must_be_like %{ COUNT(*) }
22
26
  end
23
27
 
24
28
  it 'makes a distinct node' do
25
29
  node = SqlLiteral.new('*').count true
26
- @visitor.accept(node).must_be_like %{ COUNT(DISTINCT *) }
30
+ compile(node).must_be_like %{ COUNT(DISTINCT *) }
27
31
  end
28
32
  end
29
33
 
30
34
  describe 'equality' do
31
35
  it 'makes an equality node' do
32
36
  node = SqlLiteral.new('foo').eq(1)
33
- @visitor.accept(node).must_be_like %{ foo = 1 }
37
+ compile(node).must_be_like %{ foo = 1 }
34
38
  end
35
39
 
36
40
  it 'is equal with equal contents' do
@@ -47,14 +51,14 @@ module Arel
47
51
  describe 'grouped "or" equality' do
48
52
  it 'makes a grouping node with an or node' do
49
53
  node = SqlLiteral.new('foo').eq_any([1,2])
50
- @visitor.accept(node).must_be_like %{ (foo = 1 OR foo = 2) }
54
+ compile(node).must_be_like %{ (foo = 1 OR foo = 2) }
51
55
  end
52
56
  end
53
57
 
54
58
  describe 'grouped "and" equality' do
55
59
  it 'makes a grouping node with an or node' do
56
60
  node = SqlLiteral.new('foo').eq_all([1,2])
57
- @visitor.accept(node).must_be_like %{ (foo = 1 AND foo = 2) }
61
+ compile(node).must_be_like %{ (foo = 1 AND foo = 2) }
58
62
  end
59
63
  end
60
64
 
@@ -7,9 +7,11 @@ module Arel
7
7
  it 'is equal with equal ivars' do
8
8
  window1 = Window.new
9
9
  window1.orders = [1, 2]
10
+ window1.partitions = [1]
10
11
  window1.frame 3
11
12
  window2 = Window.new
12
13
  window2.orders = [1, 2]
14
+ window2.partitions = [1]
13
15
  window2.frame 3
14
16
  array = [window1, window2]
15
17
  assert_equal 1, array.uniq.size
@@ -18,9 +20,11 @@ module Arel
18
20
  it 'is not equal with different ivars' do
19
21
  window1 = Window.new
20
22
  window1.orders = [1, 2]
23
+ window1.partitions = [1]
21
24
  window1.frame 3
22
25
  window2 = Window.new
23
26
  window2.orders = [1, 2]
27
+ window1.partitions = [1]
24
28
  window2.frame 4
25
29
  array = [window1, window2]
26
30
  assert_equal 2, array.uniq.size
@@ -33,9 +37,11 @@ module Arel
33
37
  it 'is equal with equal ivars' do
34
38
  window1 = NamedWindow.new 'foo'
35
39
  window1.orders = [1, 2]
40
+ window1.partitions = [1]
36
41
  window1.frame 3
37
42
  window2 = NamedWindow.new 'foo'
38
43
  window2.orders = [1, 2]
44
+ window2.partitions = [1]
39
45
  window2.frame 3
40
46
  array = [window1, window2]
41
47
  assert_equal 1, array.uniq.size
@@ -44,9 +50,11 @@ module Arel
44
50
  it 'is not equal with different ivars' do
45
51
  window1 = NamedWindow.new 'foo'
46
52
  window1.orders = [1, 2]
53
+ window1.partitions = [1]
47
54
  window1.frame 3
48
55
  window2 = NamedWindow.new 'bar'
49
56
  window2.orders = [1, 2]
57
+ window2.partitions = [1]
50
58
  window2.frame 3
51
59
  array = [window1, window2]
52
60
  assert_equal 2, array.uniq.size
@@ -68,6 +76,4 @@ module Arel
68
76
  end
69
77
  end
70
78
  end
71
- end
72
-
73
-
79
+ end
@@ -60,12 +60,20 @@ module FakeRecord
60
60
  end
61
61
 
62
62
  def quote thing, column = nil
63
- if column && column.type == :integer
64
- return 'NULL' if thing.nil?
65
- return thing.to_i
63
+ if column && !thing.nil?
64
+ case column.type
65
+ when :integer
66
+ thing = thing.to_i
67
+ when :string
68
+ thing = thing.to_s
69
+ end
66
70
  end
67
71
 
68
72
  case thing
73
+ when DateTime
74
+ "'#{thing.strftime("%Y-%m-%d %H:%M:%S")}'"
75
+ when Date
76
+ "'#{thing.strftime("%Y-%m-%d")}'"
69
77
  when true
70
78
  "'t'"
71
79
  when false
@@ -75,7 +83,7 @@ module FakeRecord
75
83
  when Numeric
76
84
  thing
77
85
  else
78
- "'#{thing}'"
86
+ "'#{thing.to_s.gsub("'", "\\\\'")}'"
79
87
  end
80
88
  end
81
89
  end
@@ -107,6 +115,10 @@ module FakeRecord
107
115
  def schema_cache
108
116
  connection
109
117
  end
118
+
119
+ def quote thing, column = nil
120
+ connection.quote thing, column
121
+ end
110
122
  end
111
123
 
112
124
  class Base
@@ -37,7 +37,7 @@ module Arel
37
37
  lower = @factory.lower :one
38
38
  assert_instance_of Nodes::NamedFunction, lower
39
39
  assert_equal 'LOWER', lower.name
40
- assert_equal [:one], lower.expressions
40
+ assert_equal [:one], lower.expressions.map(&:expr)
41
41
  end
42
42
  end
43
43
  end
@@ -20,9 +20,10 @@ module Arel
20
20
 
21
21
  it 'allows sql literals' do
22
22
  manager = Arel::InsertManager.new Table.engine
23
+ manager.into Table.new(:users)
23
24
  manager.values = manager.create_values [Arel.sql('*')], %w{ a }
24
25
  manager.to_sql.must_be_like %{
25
- INSERT INTO NULL VALUES (*)
26
+ INSERT INTO \"users\" VALUES (*)
26
27
  }
27
28
  end
28
29
 
@@ -77,14 +78,19 @@ module Arel
77
78
  }
78
79
  end
79
80
 
80
- it 'takes an empty list' do
81
+ it 'noop for empty list' do
82
+ table = Table.new(:users)
81
83
  manager = Arel::InsertManager.new Table.engine
84
+ manager.insert [[table[:id], 1]]
82
85
  manager.insert []
86
+ manager.to_sql.must_be_like %{
87
+ INSERT INTO "users" ("id") VALUES (1)
88
+ }
83
89
  end
84
90
  end
85
91
 
86
92
  describe 'into' do
87
- it 'takes an engine' do
93
+ it 'takes a Table and chains' do
88
94
  manager = Arel::InsertManager.new Table.engine
89
95
  manager.into(Table.new(:users)).must_equal manager
90
96
  end
@@ -125,7 +131,7 @@ module Arel
125
131
  end
126
132
 
127
133
  describe "combo" do
128
- it "puts shit together" do
134
+ it "combines columns and values list in order" do
129
135
  table = Table.new :users
130
136
  manager = Arel::InsertManager.new Table.engine
131
137
  manager.into table
@@ -138,5 +144,28 @@ module Arel
138
144
  }
139
145
  end
140
146
  end
147
+
148
+ describe "select" do
149
+
150
+ it "accepts a select query in place of a VALUES clause" do
151
+ table = Table.new :users
152
+
153
+ manager = Arel::InsertManager.new Table.engine
154
+ manager.into table
155
+
156
+ select = Arel::SelectManager.new Table.engine
157
+ select.project Arel.sql('1')
158
+ select.project Arel.sql('"aaron"')
159
+
160
+ manager.select select
161
+ manager.columns << table[:id]
162
+ manager.columns << table[:name]
163
+ manager.to_sql.must_be_like %{
164
+ INSERT INTO "users" ("id", "name") (SELECT 1, "aaron")
165
+ }
166
+ end
167
+
168
+ end
169
+
141
170
  end
142
171
  end