square-arel 2.0.9.20110222133018

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/.autotest +26 -0
  2. data/History.txt +105 -0
  3. data/MIT-LICENSE.txt +20 -0
  4. data/Manifest.txt +124 -0
  5. data/README.markdown +94 -0
  6. data/Rakefile +20 -0
  7. data/lib/arel.rb +39 -0
  8. data/lib/arel/attributes.rb +20 -0
  9. data/lib/arel/attributes/attribute.rb +18 -0
  10. data/lib/arel/compatibility/wheres.rb +33 -0
  11. data/lib/arel/crud.rb +37 -0
  12. data/lib/arel/delete_manager.rb +18 -0
  13. data/lib/arel/deprecated.rb +4 -0
  14. data/lib/arel/expression.rb +4 -0
  15. data/lib/arel/expressions.rb +23 -0
  16. data/lib/arel/insert_manager.rb +34 -0
  17. data/lib/arel/nodes.rb +53 -0
  18. data/lib/arel/nodes/and.rb +6 -0
  19. data/lib/arel/nodes/as.rb +6 -0
  20. data/lib/arel/nodes/assignment.rb +6 -0
  21. data/lib/arel/nodes/avg.rb +6 -0
  22. data/lib/arel/nodes/between.rb +6 -0
  23. data/lib/arel/nodes/binary.rb +12 -0
  24. data/lib/arel/nodes/count.rb +13 -0
  25. data/lib/arel/nodes/delete_statement.rb +19 -0
  26. data/lib/arel/nodes/does_not_match.rb +6 -0
  27. data/lib/arel/nodes/equality.rb +9 -0
  28. data/lib/arel/nodes/except.rb +7 -0
  29. data/lib/arel/nodes/exists.rb +7 -0
  30. data/lib/arel/nodes/function.rb +18 -0
  31. data/lib/arel/nodes/greater_than.rb +6 -0
  32. data/lib/arel/nodes/greater_than_or_equal.rb +6 -0
  33. data/lib/arel/nodes/group.rb +6 -0
  34. data/lib/arel/nodes/grouping.rb +6 -0
  35. data/lib/arel/nodes/having.rb +6 -0
  36. data/lib/arel/nodes/in.rb +6 -0
  37. data/lib/arel/nodes/inner_join.rb +6 -0
  38. data/lib/arel/nodes/insert_statement.rb +19 -0
  39. data/lib/arel/nodes/intersect.rb +7 -0
  40. data/lib/arel/nodes/join.rb +13 -0
  41. data/lib/arel/nodes/less_than.rb +6 -0
  42. data/lib/arel/nodes/less_than_or_equal.rb +6 -0
  43. data/lib/arel/nodes/limit.rb +7 -0
  44. data/lib/arel/nodes/lock.rb +6 -0
  45. data/lib/arel/nodes/matches.rb +6 -0
  46. data/lib/arel/nodes/max.rb +6 -0
  47. data/lib/arel/nodes/min.rb +6 -0
  48. data/lib/arel/nodes/node.rb +44 -0
  49. data/lib/arel/nodes/not.rb +6 -0
  50. data/lib/arel/nodes/not_equal.rb +6 -0
  51. data/lib/arel/nodes/not_in.rb +6 -0
  52. data/lib/arel/nodes/offset.rb +7 -0
  53. data/lib/arel/nodes/on.rb +6 -0
  54. data/lib/arel/nodes/or.rb +6 -0
  55. data/lib/arel/nodes/ordering.rb +20 -0
  56. data/lib/arel/nodes/outer_join.rb +6 -0
  57. data/lib/arel/nodes/select_core.rb +26 -0
  58. data/lib/arel/nodes/select_statement.rb +22 -0
  59. data/lib/arel/nodes/sql_literal.rb +8 -0
  60. data/lib/arel/nodes/string_join.rb +11 -0
  61. data/lib/arel/nodes/sum.rb +6 -0
  62. data/lib/arel/nodes/table_alias.rb +13 -0
  63. data/lib/arel/nodes/top.rb +6 -0
  64. data/lib/arel/nodes/unary.rb +11 -0
  65. data/lib/arel/nodes/union.rb +7 -0
  66. data/lib/arel/nodes/union_all.rb +7 -0
  67. data/lib/arel/nodes/unqualified_column.rb +16 -0
  68. data/lib/arel/nodes/update_statement.rb +21 -0
  69. data/lib/arel/nodes/values.rb +14 -0
  70. data/lib/arel/predications.rb +183 -0
  71. data/lib/arel/relation.rb +6 -0
  72. data/lib/arel/select_manager.rb +237 -0
  73. data/lib/arel/sql/engine.rb +10 -0
  74. data/lib/arel/sql_literal.rb +4 -0
  75. data/lib/arel/table.rb +134 -0
  76. data/lib/arel/tree_manager.rb +36 -0
  77. data/lib/arel/update_manager.rb +49 -0
  78. data/lib/arel/visitors.rb +38 -0
  79. data/lib/arel/visitors/depth_first.rb +154 -0
  80. data/lib/arel/visitors/dot.rb +230 -0
  81. data/lib/arel/visitors/join_sql.rb +40 -0
  82. data/lib/arel/visitors/mssql.rb +16 -0
  83. data/lib/arel/visitors/mysql.rb +34 -0
  84. data/lib/arel/visitors/oracle.rb +116 -0
  85. data/lib/arel/visitors/order_clauses.rb +11 -0
  86. data/lib/arel/visitors/postgresql.rb +58 -0
  87. data/lib/arel/visitors/sqlite.rb +11 -0
  88. data/lib/arel/visitors/to_sql.rb +331 -0
  89. data/lib/arel/visitors/visitor.rb +27 -0
  90. data/lib/arel/visitors/where_sql.rb +9 -0
  91. data/square-arel.gemspec +36 -0
  92. data/test/attributes/test_attribute.rb +664 -0
  93. data/test/helper.rb +13 -0
  94. data/test/nodes/test_as.rb +16 -0
  95. data/test/nodes/test_count.rb +18 -0
  96. data/test/nodes/test_delete_statement.rb +14 -0
  97. data/test/nodes/test_equality.rb +74 -0
  98. data/test/nodes/test_insert_statement.rb +18 -0
  99. data/test/nodes/test_node.rb +33 -0
  100. data/test/nodes/test_not.rb +20 -0
  101. data/test/nodes/test_or.rb +22 -0
  102. data/test/nodes/test_select_core.rb +22 -0
  103. data/test/nodes/test_select_statement.rb +13 -0
  104. data/test/nodes/test_sql_literal.rb +52 -0
  105. data/test/nodes/test_sum.rb +12 -0
  106. data/test/nodes/test_update_statement.rb +18 -0
  107. data/test/support/fake_record.rb +91 -0
  108. data/test/test_activerecord_compat.rb +18 -0
  109. data/test/test_attributes.rb +46 -0
  110. data/test/test_crud.rb +69 -0
  111. data/test/test_delete_manager.rb +42 -0
  112. data/test/test_insert_manager.rb +125 -0
  113. data/test/test_select_manager.rb +659 -0
  114. data/test/test_table.rb +193 -0
  115. data/test/test_update_manager.rb +86 -0
  116. data/test/visitors/test_depth_first.rb +212 -0
  117. data/test/visitors/test_dot.rb +29 -0
  118. data/test/visitors/test_join_sql.rb +35 -0
  119. data/test/visitors/test_mssql.rb +18 -0
  120. data/test/visitors/test_mysql.rb +45 -0
  121. data/test/visitors/test_oracle.rb +147 -0
  122. data/test/visitors/test_postgres.rb +36 -0
  123. data/test/visitors/test_sqlite.rb +18 -0
  124. data/test/visitors/test_to_sql.rb +255 -0
  125. metadata +261 -0
@@ -0,0 +1,29 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Visitors
5
+ class TestDot < MiniTest::Unit::TestCase
6
+ def setup
7
+ @visitor = Visitors::Dot.new
8
+ end
9
+
10
+ # unary ops
11
+ [
12
+ Arel::Nodes::Not,
13
+ Arel::Nodes::Group,
14
+ Arel::Nodes::On,
15
+ Arel::Nodes::Grouping,
16
+ Arel::Nodes::Offset,
17
+ Arel::Nodes::Having,
18
+ Arel::Nodes::UnqualifiedColumn,
19
+ Arel::Nodes::Top,
20
+ Arel::Nodes::Limit,
21
+ ].each do |klass|
22
+ define_method("test_#{klass.name.gsub('::', '_')}") do
23
+ op = klass.new(:a)
24
+ @visitor.accept op
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,35 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Visitors
5
+ describe 'the join_sql visitor' do
6
+ before do
7
+ @visitor = JoinSql.new Table.engine
8
+ end
9
+
10
+ describe 'inner join' do
11
+ it 'should visit left if left is a join' do
12
+ t = Table.new :users
13
+ join = Nodes::InnerJoin.new t, t, Nodes::On.new(t[:id])
14
+ j2 = Nodes::InnerJoin.new join, t, Nodes::On.new(t[:id])
15
+ @visitor.accept(j2).must_be_like %{
16
+ INNER JOIN "users" ON "users"."id"
17
+ INNER JOIN "users" ON "users"."id"
18
+ }
19
+ end
20
+ end
21
+
22
+ describe 'outer join' do
23
+ it 'should visit left if left is a join' do
24
+ t = Table.new :users
25
+ join = Nodes::OuterJoin.new t, t, Nodes::On.new(t[:id])
26
+ j2 = Nodes::OuterJoin.new join, t, Nodes::On.new(t[:id])
27
+ @visitor.accept(j2).must_be_like %{
28
+ LEFT OUTER JOIN "users" ON "users"."id"
29
+ LEFT OUTER JOIN "users" ON "users"."id"
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Visitors
5
+ describe 'the mssql visitor' do
6
+ before do
7
+ @visitor = MSSQL.new Table.engine
8
+ end
9
+
10
+ it 'uses TOP to limit results' do
11
+ stmt = Nodes::SelectStatement.new
12
+ stmt.cores.last.top = Nodes::Top.new(1)
13
+ sql = @visitor.accept(stmt)
14
+ sql.must_be_like "SELECT TOP 1"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Visitors
5
+ describe 'the mysql visitor' do
6
+ before do
7
+ @visitor = MySQL.new Table.engine
8
+ end
9
+
10
+ ###
11
+ # :'(
12
+ # http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
13
+ it 'defaults limit to 18446744073709551615' do
14
+ stmt = Nodes::SelectStatement.new
15
+ stmt.offset = Nodes::Offset.new(1)
16
+ sql = @visitor.accept(stmt)
17
+ sql.must_be_like "SELECT FROM DUAL LIMIT 18446744073709551615 OFFSET 1"
18
+ end
19
+
20
+ it "should escape LIMIT" do
21
+ sc = Arel::Nodes::UpdateStatement.new
22
+ sc.limit = Nodes::Limit.new("omg")
23
+ assert_equal("UPDATE NULL LIMIT 'omg'", @visitor.accept(sc))
24
+ end
25
+
26
+ it 'uses DUAL for empty from' do
27
+ stmt = Nodes::SelectStatement.new
28
+ sql = @visitor.accept(stmt)
29
+ sql.must_be_like "SELECT FROM DUAL"
30
+ end
31
+
32
+ describe 'locking' do
33
+ it 'defaults to FOR UPDATE when locking' do
34
+ node = Nodes::Lock.new(Arel.sql('FOR UPDATE'))
35
+ @visitor.accept(node).must_be_like "FOR UPDATE"
36
+ end
37
+
38
+ it 'allows a custom string to be used as a lock' do
39
+ node = Nodes::Lock.new(Arel.sql('LOCK IN SHARE MODE'))
40
+ @visitor.accept(node).must_be_like "LOCK IN SHARE MODE"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,147 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Visitors
5
+ describe 'the oracle visitor' do
6
+ before do
7
+ @visitor = Oracle.new Table.engine
8
+ end
9
+
10
+ it 'modifies order when there is distinct and first value' do
11
+ # *sigh*
12
+ select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
13
+ stmt = Nodes::SelectStatement.new
14
+ stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
15
+ stmt.orders << Nodes::SqlLiteral.new('foo')
16
+ sql = @visitor.accept(stmt)
17
+ sql.must_be_like %{
18
+ SELECT #{select} ORDER BY alias_0__
19
+ }
20
+ end
21
+
22
+ it 'is idempotent with crazy query' do
23
+ # *sigh*
24
+ select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
25
+ stmt = Nodes::SelectStatement.new
26
+ stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
27
+ stmt.orders << Nodes::SqlLiteral.new('foo')
28
+
29
+ sql = @visitor.accept(stmt)
30
+ sql2 = @visitor.accept(stmt)
31
+ sql.must_equal sql2
32
+ end
33
+
34
+ it 'splits orders with commas' do
35
+ # *sigh*
36
+ select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
37
+ stmt = Nodes::SelectStatement.new
38
+ stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
39
+ stmt.orders << Nodes::SqlLiteral.new('foo, bar')
40
+ sql = @visitor.accept(stmt)
41
+ sql.must_be_like %{
42
+ SELECT #{select} ORDER BY alias_0__, alias_1__
43
+ }
44
+ end
45
+
46
+ it 'splits orders with commas and function calls' do
47
+ # *sigh*
48
+ select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
49
+ stmt = Nodes::SelectStatement.new
50
+ stmt.cores.first.projections << Nodes::SqlLiteral.new(select)
51
+ stmt.orders << Nodes::SqlLiteral.new('NVL(LOWER(bar, foo), foo) DESC, UPPER(baz)')
52
+ sql = @visitor.accept(stmt)
53
+ sql.must_be_like %{
54
+ SELECT #{select} ORDER BY alias_0__ DESC, alias_1__
55
+ }
56
+ end
57
+
58
+ describe 'Nodes::SelectStatement' do
59
+ describe 'limit' do
60
+ it 'adds a rownum clause' do
61
+ stmt = Nodes::SelectStatement.new
62
+ stmt.limit = Nodes::Limit.new(10)
63
+ sql = @visitor.accept stmt
64
+ sql.must_be_like %{ SELECT WHERE ROWNUM <= 10 }
65
+ end
66
+
67
+ it 'is idempotent' do
68
+ stmt = Nodes::SelectStatement.new
69
+ stmt.orders << Nodes::SqlLiteral.new('foo')
70
+ stmt.limit = Nodes::Limit.new(10)
71
+ sql = @visitor.accept stmt
72
+ sql2 = @visitor.accept stmt
73
+ sql.must_equal sql2
74
+ end
75
+
76
+ it 'creates a subquery when there is order_by' do
77
+ stmt = Nodes::SelectStatement.new
78
+ stmt.orders << Nodes::SqlLiteral.new('foo')
79
+ stmt.limit = Nodes::Limit.new(10)
80
+ sql = @visitor.accept stmt
81
+ sql.must_be_like %{
82
+ SELECT * FROM (SELECT ORDER BY foo) WHERE ROWNUM <= 10
83
+ }
84
+ end
85
+
86
+ it 'creates a subquery when there is DISTINCT' do
87
+ stmt = Nodes::SelectStatement.new
88
+ stmt.cores.first.projections << Nodes::SqlLiteral.new('DISTINCT id')
89
+ stmt.limit = Arel::Nodes::Limit.new(10)
90
+ sql = @visitor.accept stmt
91
+ sql.must_be_like %{
92
+ SELECT * FROM (SELECT DISTINCT id) WHERE ROWNUM <= 10
93
+ }
94
+ end
95
+
96
+ it 'creates a different subquery when there is an offset' do
97
+ stmt = Nodes::SelectStatement.new
98
+ stmt.limit = Nodes::Limit.new(10)
99
+ stmt.offset = Nodes::Offset.new(10)
100
+ sql = @visitor.accept stmt
101
+ sql.must_be_like %{
102
+ SELECT * FROM (
103
+ SELECT raw_sql_.*, rownum raw_rnum_
104
+ FROM (SELECT ) raw_sql_
105
+ WHERE rownum <= 20
106
+ )
107
+ WHERE raw_rnum_ > 10
108
+ }
109
+ end
110
+
111
+ it 'is idempotent with different subquery' do
112
+ stmt = Nodes::SelectStatement.new
113
+ stmt.limit = Nodes::Limit.new(10)
114
+ stmt.offset = Nodes::Offset.new(10)
115
+ sql = @visitor.accept stmt
116
+ sql2 = @visitor.accept stmt
117
+ sql.must_equal sql2
118
+ end
119
+ end
120
+
121
+ describe 'only offset' do
122
+ it 'creates a select from subquery with rownum condition' do
123
+ stmt = Nodes::SelectStatement.new
124
+ stmt.offset = Nodes::Offset.new(10)
125
+ sql = @visitor.accept stmt
126
+ sql.must_be_like %{
127
+ SELECT * FROM (
128
+ SELECT raw_sql_.*, rownum raw_rnum_
129
+ FROM (SELECT ) raw_sql_
130
+ )
131
+ WHERE raw_rnum_ > 10
132
+ }
133
+ end
134
+ end
135
+
136
+ it 'modified except to be minus' do
137
+ left = Nodes::SqlLiteral.new("SELECT * FROM users WHERE age > 10")
138
+ right = Nodes::SqlLiteral.new("SELECT * FROM users WHERE age > 20")
139
+ sql = @visitor.accept Nodes::Except.new(left, right)
140
+ sql.must_be_like %{
141
+ ( SELECT * FROM users WHERE age > 10 MINUS SELECT * FROM users WHERE age > 20 )
142
+ }
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,36 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Visitors
5
+ describe 'the postgres visitor' do
6
+ before do
7
+ @visitor = PostgreSQL.new Table.engine
8
+ end
9
+
10
+ describe 'locking' do
11
+ it 'defaults to FOR UPDATE' do
12
+ @visitor.accept(Nodes::Lock.new(Arel.sql('FOR UPDATE'))).must_be_like %{
13
+ FOR UPDATE
14
+ }
15
+ end
16
+
17
+ it 'allows a custom string to be used as a lock' do
18
+ node = Nodes::Lock.new(Arel.sql('FOR SHARE'))
19
+ @visitor.accept(node).must_be_like %{
20
+ FOR SHARE
21
+ }
22
+ end
23
+ end
24
+
25
+ it "should escape LIMIT" do
26
+ sc = Arel::Nodes::SelectStatement.new
27
+ sc.limit = Nodes::Limit.new("omg")
28
+ sc.cores.first.projections << 'DISTINCT ON'
29
+ sc.orders << "xyz"
30
+ sql = @visitor.accept(sc)
31
+ assert_match(/LIMIT 'omg'/, sql)
32
+ assert_equal 1, sql.scan(/LIMIT/).length, 'should have one limit'
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ require 'helper'
2
+
3
+ module Arel
4
+ module Visitors
5
+ describe 'the sqlite visitor' do
6
+ before do
7
+ @visitor = SQLite.new Table.engine
8
+ end
9
+
10
+ it 'defaults limit to -1' do
11
+ stmt = Nodes::SelectStatement.new
12
+ stmt.offset = Nodes::Offset.new(1)
13
+ sql = @visitor.accept(stmt)
14
+ sql.must_be_like "SELECT LIMIT -1 OFFSET 1"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,255 @@
1
+ require 'helper'
2
+
3
+ class Arel::Visitors::ToSql
4
+ def last_column; Thread.current[:arel_visitors_to_sql_last_column] || @last_column; end
5
+ end
6
+
7
+ module Arel
8
+ module Visitors
9
+ describe 'the to_sql visitor' do
10
+ before do
11
+ @visitor = ToSql.new Table.engine
12
+ @table = Table.new(:users)
13
+ @attr = @table[:id]
14
+ end
15
+
16
+ it "should be thread safe around usage of last_column" do
17
+ visit_integer_column = Thread.new do
18
+ Thread.stop
19
+ @visitor.send(:visit_Arel_Attributes_Attribute, @attr)
20
+ end
21
+
22
+ @visitor.accept(@table[:name])
23
+ assert_equal(:string, @visitor.last_column.type)
24
+ visit_integer_column.run
25
+ visit_integer_column.join
26
+ assert_equal(:string, @visitor.last_column.type)
27
+ end
28
+
29
+ describe 'equality' do
30
+ it 'should handle false' do
31
+ sql = @visitor.accept Nodes::Equality.new(false, false)
32
+ sql.must_be_like %{ 'f' = 'f' }
33
+ end
34
+
35
+ it 'should use the column to quote' do
36
+ table = Table.new(:users)
37
+ sql = @visitor.accept Nodes::Equality.new(table[:id], '1-fooo')
38
+ sql.must_be_like %{ "users"."id" = 1 }
39
+ end
40
+ end
41
+
42
+ it "should visit string subclass" do
43
+ @visitor.accept(Class.new(String).new(":'("))
44
+ @visitor.accept(Class.new(Class.new(String)).new(":'("))
45
+ end
46
+
47
+ it "should visit_Class" do
48
+ @visitor.accept(DateTime).must_equal "'DateTime'"
49
+ end
50
+
51
+ it "should escape LIMIT" do
52
+ sc = Arel::Nodes::SelectStatement.new
53
+ sc.limit = Arel::Nodes::Limit.new("omg")
54
+ assert_match(/LIMIT 'omg'/, @visitor.accept(sc))
55
+ end
56
+
57
+ it "should visit_DateTime" do
58
+ @visitor.accept DateTime.now
59
+ end
60
+
61
+ it "should visit_Float" do
62
+ @visitor.accept 2.14
63
+ end
64
+
65
+ it "should visit_Not" do
66
+ sql = @visitor.accept Nodes::Not.new(Arel.sql("foo"))
67
+ sql.must_be_like "NOT (foo)"
68
+ end
69
+
70
+ it "should apply Not to the whole expression" do
71
+ node = Nodes::And.new @attr.eq(10), @attr.eq(11)
72
+ sql = @visitor.accept Nodes::Not.new(node)
73
+ sql.must_be_like %{NOT ("users"."id" = 10 AND "users"."id" = 11)}
74
+ end
75
+
76
+ it "should visit_As" do
77
+ as = Nodes::As.new(Arel.sql("foo"), Arel.sql("bar"))
78
+ sql = @visitor.accept as
79
+ sql.must_be_like "foo AS bar"
80
+ end
81
+
82
+ it "should visit_Bignum" do
83
+ @visitor.accept 8787878092
84
+ end
85
+
86
+ it "should visit_Hash" do
87
+ @visitor.accept({:a => 1})
88
+ end
89
+
90
+ it "should visit_BigDecimal" do
91
+ @visitor.accept BigDecimal.new('2.14')
92
+ end
93
+
94
+ it "should visit_Date" do
95
+ @visitor.accept Date.today
96
+ end
97
+
98
+ it "should visit_NilClass" do
99
+ @visitor.accept(nil).must_be_like "NULL"
100
+ end
101
+
102
+ it "should visit_Arel_Nodes_And" do
103
+ node = Nodes::And.new @attr.eq(10), @attr.eq(11)
104
+ @visitor.accept(node).must_be_like %{
105
+ "users"."id" = 10 AND "users"."id" = 11
106
+ }
107
+ end
108
+
109
+ it "should visit_Arel_Nodes_Or" do
110
+ node = Nodes::Or.new @attr.eq(10), @attr.eq(11)
111
+ @visitor.accept(node).must_be_like %{
112
+ "users"."id" = 10 OR "users"."id" = 11
113
+ }
114
+ end
115
+
116
+ it "should visit visit_Arel_Attributes_Time" do
117
+ attr = Attributes::Time.new(@attr.relation, @attr.name, @attr.column)
118
+ @visitor.accept attr
119
+ end
120
+
121
+ it "should visit_TrueClass" do
122
+ test = Table.new(:users)[:bool].eq(true)
123
+ @visitor.accept(test).must_be_like %{ "users"."bool" = 't' }
124
+ end
125
+
126
+ describe "Nodes::Ordering" do
127
+ it "should know how to visit" do
128
+ node = @attr.desc
129
+ @visitor.accept(node).must_be_like %{
130
+ "users"."id" DESC
131
+ }
132
+ end
133
+ end
134
+
135
+ describe "Nodes::In" do
136
+ it "should know how to visit" do
137
+ node = @attr.in [1, 2, 3]
138
+ @visitor.accept(node).must_be_like %{
139
+ "users"."id" IN (1, 2, 3)
140
+ }
141
+ end
142
+
143
+ it "should turn empty right to NULL" do
144
+ node = @attr.in []
145
+ @visitor.accept(node).must_be_like %{
146
+ "users"."id" IN (NULL)
147
+ }
148
+ end
149
+
150
+ it 'can handle two dot ranges' do
151
+ node = @attr.in 1..3
152
+ @visitor.accept(node).must_be_like %{
153
+ "users"."id" BETWEEN 1 AND 3
154
+ }
155
+ end
156
+
157
+ it 'can handle three dot ranges' do
158
+ node = @attr.in 1...3
159
+ @visitor.accept(node).must_be_like %{
160
+ "users"."id" >= 1 AND "users"."id" < 3
161
+ }
162
+ end
163
+
164
+ it 'can handle subqueries' do
165
+ table = Table.new(:users)
166
+ subquery = table.project(:id).where(table[:name].eq('Aaron'))
167
+ node = @attr.in subquery
168
+ @visitor.accept(node).must_be_like %{
169
+ "users"."id" IN (SELECT id FROM "users" WHERE "users"."name" = 'Aaron')
170
+ }
171
+ end
172
+
173
+ it 'uses the same column for escaping values' do
174
+ @attr = Table.new(:users)[:name]
175
+ visitor = Class.new(ToSql) do
176
+ attr_accessor :expected
177
+
178
+ def quote value, column = nil
179
+ raise unless column == expected
180
+ super
181
+ end
182
+ end
183
+ in_node = Nodes::In.new @attr, %w{ a b c }
184
+ visitor = visitor.new(Table.engine)
185
+ visitor.expected = @attr.column
186
+ visitor.accept(in_node).must_equal %("users"."name" IN ('a', 'b', 'c'))
187
+ end
188
+ end
189
+
190
+ describe "Nodes::NotIn" do
191
+ it "should know how to visit" do
192
+ node = @attr.not_in [1, 2, 3]
193
+ @visitor.accept(node).must_be_like %{
194
+ "users"."id" NOT IN (1, 2, 3)
195
+ }
196
+ end
197
+
198
+ it "should turn empty right to NULL" do
199
+ node = @attr.not_in []
200
+ @visitor.accept(node).must_be_like %{
201
+ "users"."id" NOT IN (NULL)
202
+ }
203
+ end
204
+
205
+ it 'can handle two dot ranges' do
206
+ node = @attr.not_in 1..3
207
+ @visitor.accept(node).must_be_like %{
208
+ "users"."id" < 1 OR "users"."id" > 3
209
+ }
210
+ end
211
+
212
+ it 'can handle three dot ranges' do
213
+ node = @attr.not_in 1...3
214
+ @visitor.accept(node).must_be_like %{
215
+ "users"."id" < 1 OR "users"."id" >= 3
216
+ }
217
+ end
218
+
219
+ it 'can handle subqueries' do
220
+ table = Table.new(:users)
221
+ subquery = table.project(:id).where(table[:name].eq('Aaron'))
222
+ node = @attr.not_in subquery
223
+ @visitor.accept(node).must_be_like %{
224
+ "users"."id" NOT IN (SELECT id FROM "users" WHERE "users"."name" = 'Aaron')
225
+ }
226
+ end
227
+
228
+ it 'uses the same column for escaping values' do
229
+ @attr = Table.new(:users)[:name]
230
+ visitor = Class.new(ToSql) do
231
+ attr_accessor :expected
232
+
233
+ def quote value, column = nil
234
+ raise unless column == expected
235
+ super
236
+ end
237
+ end
238
+ in_node = Nodes::NotIn.new @attr, %w{ a b c }
239
+ visitor = visitor.new(Table.engine)
240
+ visitor.expected = @attr.column
241
+ visitor.accept(in_node).must_equal %("users"."name" NOT IN ('a', 'b', 'c'))
242
+ end
243
+ end
244
+
245
+ describe 'Equality' do
246
+ it "should escape strings" do
247
+ test = Table.new(:users)[:name].eq 'Aaron Patterson'
248
+ @visitor.accept(test).must_be_like %{
249
+ "users"."name" = 'Aaron Patterson'
250
+ }
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end