square-arel 2.0.9.20110222133018

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