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
@@ -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