arel 6.0.0.beta1 → 6.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.travis.yml +3 -1
- data/Gemfile +2 -9
- data/README.markdown +1 -1
- data/Rakefile +11 -16
- data/arel.gemspec +17 -33
- data/lib/arel.rb +1 -1
- data/lib/arel/nodes.rb +1 -0
- data/lib/arel/nodes/binary.rb +1 -3
- data/lib/arel/nodes/extract.rb +4 -11
- data/lib/arel/nodes/matches.rb +14 -0
- data/lib/arel/predications.rb +88 -61
- data/lib/arel/select_manager.rb +9 -0
- data/lib/arel/visitors/depth_first.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/informix.rb +1 -1
- data/lib/arel/visitors/mysql.rb +1 -6
- data/lib/arel/visitors/oracle.rb +1 -1
- data/lib/arel/visitors/reduce.rb +4 -4
- data/lib/arel/visitors/to_sql.rb +35 -31
- data/lib/arel/visitors/visitor.rb +19 -12
- data/lib/arel/visitors/where_sql.rb +2 -0
- data/test/attributes/test_attribute.rb +246 -9
- data/test/nodes/test_binary.rb +26 -0
- data/test/nodes/test_extract.rb +8 -0
- data/test/test_select_manager.rb +23 -4
- data/test/visitors/test_bind_visitor.rb +0 -6
- data/test/visitors/test_depth_first.rb +8 -0
- data/test/visitors/test_informix.rb +6 -6
- data/test/visitors/test_oracle.rb +10 -0
- data/test/visitors/test_to_sql.rb +45 -19
- metadata +16 -82
- data/.autotest +0 -26
- data/.gemtest +0 -0
- data/Manifest.txt +0 -135
data/lib/arel/select_manager.rb
CHANGED
@@ -155,6 +155,15 @@ module Arel
|
|
155
155
|
self
|
156
156
|
end
|
157
157
|
|
158
|
+
def distinct_on(value)
|
159
|
+
if value
|
160
|
+
@ctx.set_quantifier = Arel::Nodes::DistinctOn.new(value)
|
161
|
+
else
|
162
|
+
@ctx.set_quantifier = nil
|
163
|
+
end
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
158
167
|
def order *expr
|
159
168
|
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
|
160
169
|
@ast.orders.concat expr.map { |x|
|
@@ -3,6 +3,7 @@ module Arel
|
|
3
3
|
class DepthFirst < Arel::Visitors::Visitor
|
4
4
|
def initialize block = nil
|
5
5
|
@block = block || Proc.new
|
6
|
+
super()
|
6
7
|
end
|
7
8
|
|
8
9
|
private
|
@@ -86,6 +87,7 @@ module Arel
|
|
86
87
|
alias :visit_Arel_Nodes_RightOuterJoin :binary
|
87
88
|
alias :visit_Arel_Nodes_TableAlias :binary
|
88
89
|
alias :visit_Arel_Nodes_Values :binary
|
90
|
+
alias :visit_Arel_Nodes_Union :binary
|
89
91
|
|
90
92
|
def visit_Arel_Nodes_StringJoin o
|
91
93
|
visit o.left
|
@@ -116,6 +118,8 @@ module Arel
|
|
116
118
|
alias :visit_Arel_Nodes_SqlLiteral :terminal
|
117
119
|
alias :visit_Arel_Nodes_BindParam :terminal
|
118
120
|
alias :visit_Arel_Nodes_Window :terminal
|
121
|
+
alias :visit_Arel_Nodes_True :terminal
|
122
|
+
alias :visit_Arel_Nodes_False :terminal
|
119
123
|
alias :visit_BigDecimal :terminal
|
120
124
|
alias :visit_Bignum :terminal
|
121
125
|
alias :visit_Class :terminal
|
@@ -164,6 +168,7 @@ module Arel
|
|
164
168
|
def visit_Array o
|
165
169
|
o.each { |i| visit i }
|
166
170
|
end
|
171
|
+
alias :visit_Set :visit_Array
|
167
172
|
|
168
173
|
def visit_Hash o
|
169
174
|
o.each { |k,v| visit(k); visit(v) }
|
data/lib/arel/visitors/dot.rb
CHANGED
@@ -15,6 +15,7 @@ module Arel
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def initialize
|
18
|
+
super()
|
18
19
|
@nodes = []
|
19
20
|
@edges = []
|
20
21
|
@node_stack = []
|
@@ -216,6 +217,7 @@ module Arel
|
|
216
217
|
edge(i) { visit x }
|
217
218
|
end
|
218
219
|
end
|
220
|
+
alias :visit_Set :visit_Array
|
219
221
|
|
220
222
|
def visit_edge o, method
|
221
223
|
edge(method) { visit o.send(method) }
|
data/lib/arel/visitors/mysql.rb
CHANGED
data/lib/arel/visitors/oracle.rb
CHANGED
@@ -8,7 +8,7 @@ module Arel
|
|
8
8
|
|
9
9
|
# if need to select first records without ORDER BY and GROUP BY and without DISTINCT
|
10
10
|
# then can use simple ROWNUM in WHERE clause
|
11
|
-
if o.limit && o.orders.empty? && !o.offset && o.cores.first.set_quantifier.class.to_s !~ /Distinct/
|
11
|
+
if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && o.cores.first.set_quantifier.class.to_s !~ /Distinct/
|
12
12
|
o.cores.last.wheres.push Nodes::LessThanOrEqual.new(
|
13
13
|
Nodes::SqlLiteral.new('ROWNUM'), o.limit.expr
|
14
14
|
)
|
data/lib/arel/visitors/reduce.rb
CHANGED
@@ -10,14 +10,14 @@ module Arel
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def visit object, collector
|
13
|
-
send dispatch[object.class], object, collector
|
13
|
+
send dispatch[object.class.name], object, collector
|
14
14
|
rescue NoMethodError => e
|
15
|
-
raise e if respond_to?(dispatch[object.class], true)
|
15
|
+
raise e if respond_to?(dispatch[object.class.name], true)
|
16
16
|
superklass = object.class.ancestors.find { |klass|
|
17
|
-
respond_to?(dispatch[klass], true)
|
17
|
+
respond_to?(dispatch[klass.name], true)
|
18
18
|
}
|
19
19
|
raise(TypeError, "Cannot visit #{object.class}") unless superklass
|
20
|
-
dispatch[object.class] = dispatch[superklass]
|
20
|
+
dispatch[object.class.name] = dispatch[superklass.name]
|
21
21
|
retry
|
22
22
|
end
|
23
23
|
end
|
data/lib/arel/visitors/to_sql.rb
CHANGED
@@ -59,10 +59,8 @@ module Arel
|
|
59
59
|
DISTINCT = 'DISTINCT' # :nodoc:
|
60
60
|
|
61
61
|
def initialize connection
|
62
|
+
super()
|
62
63
|
@connection = connection
|
63
|
-
@schema_cache = connection.schema_cache
|
64
|
-
@quoted_tables = {}
|
65
|
-
@quoted_columns = {}
|
66
64
|
end
|
67
65
|
|
68
66
|
def compile node, &block
|
@@ -71,6 +69,10 @@ module Arel
|
|
71
69
|
|
72
70
|
private
|
73
71
|
|
72
|
+
def schema_cache
|
73
|
+
@connection.schema_cache
|
74
|
+
end
|
75
|
+
|
74
76
|
def visit_Arel_Nodes_DeleteStatement o, collector
|
75
77
|
collector << "DELETE FROM "
|
76
78
|
collector = visit o.relation, collector
|
@@ -162,7 +164,7 @@ module Arel
|
|
162
164
|
end
|
163
165
|
|
164
166
|
def table_exists? name
|
165
|
-
|
167
|
+
schema_cache.table_exists? name
|
166
168
|
end
|
167
169
|
|
168
170
|
def column_for attr
|
@@ -176,7 +178,7 @@ module Arel
|
|
176
178
|
end
|
177
179
|
|
178
180
|
def column_cache(table)
|
179
|
-
|
181
|
+
schema_cache.columns_hash(table)
|
180
182
|
end
|
181
183
|
|
182
184
|
def visit_Arel_Nodes_Values o, collector
|
@@ -227,15 +229,9 @@ module Arel
|
|
227
229
|
def visit_Arel_Nodes_SelectCore o, collector
|
228
230
|
collector << "SELECT"
|
229
231
|
|
230
|
-
|
231
|
-
collector << " "
|
232
|
-
collector = visit o.top, collector
|
233
|
-
end
|
232
|
+
collector = maybe_visit o.top, collector
|
234
233
|
|
235
|
-
|
236
|
-
collector << " "
|
237
|
-
collector = visit o.set_quantifier, collector
|
238
|
-
end
|
234
|
+
collector = maybe_visit o.set_quantifier, collector
|
239
235
|
|
240
236
|
unless o.projections.empty?
|
241
237
|
collector << SPACE
|
@@ -269,10 +265,7 @@ module Arel
|
|
269
265
|
end
|
270
266
|
end
|
271
267
|
|
272
|
-
|
273
|
-
collector << " "
|
274
|
-
collector = visit(o.having, collector)
|
275
|
-
end
|
268
|
+
collector = maybe_visit o.having, collector
|
276
269
|
|
277
270
|
unless o.windows.empty?
|
278
271
|
collector << WINDOW
|
@@ -436,8 +429,12 @@ module Arel
|
|
436
429
|
end
|
437
430
|
|
438
431
|
def visit_Arel_Nodes_Grouping o, collector
|
439
|
-
|
440
|
-
|
432
|
+
if o.expr.is_a? Nodes::Grouping
|
433
|
+
visit(o.expr, collector)
|
434
|
+
else
|
435
|
+
collector << "("
|
436
|
+
visit(o.expr, collector) << ")"
|
437
|
+
end
|
441
438
|
end
|
442
439
|
|
443
440
|
def visit_Arel_SelectManager o, collector
|
@@ -471,14 +468,7 @@ module Arel
|
|
471
468
|
|
472
469
|
def visit_Arel_Nodes_Extract o, collector
|
473
470
|
collector << "EXTRACT(#{o.field.to_s.upcase} FROM "
|
474
|
-
|
475
|
-
collector << ")"
|
476
|
-
if o.alias
|
477
|
-
collector << " AS "
|
478
|
-
visit o.alias, collector
|
479
|
-
else
|
480
|
-
collector
|
481
|
-
end
|
471
|
+
visit(o.expr, collector) << ")"
|
482
472
|
end
|
483
473
|
|
484
474
|
def visit_Arel_Nodes_Count o, collector
|
@@ -540,13 +530,25 @@ module Arel
|
|
540
530
|
def visit_Arel_Nodes_Matches o, collector
|
541
531
|
collector = visit o.left, collector
|
542
532
|
collector << " LIKE "
|
543
|
-
visit o.right, collector
|
533
|
+
collector = visit o.right, collector
|
534
|
+
if o.escape
|
535
|
+
collector << ' ESCAPE '
|
536
|
+
visit o.escape, collector
|
537
|
+
else
|
538
|
+
collector
|
539
|
+
end
|
544
540
|
end
|
545
541
|
|
546
542
|
def visit_Arel_Nodes_DoesNotMatch o, collector
|
547
543
|
collector = visit o.left, collector
|
548
544
|
collector << " NOT LIKE "
|
549
|
-
visit o.right, collector
|
545
|
+
collector = visit o.right, collector
|
546
|
+
if o.escape
|
547
|
+
collector << ' ESCAPE '
|
548
|
+
visit o.escape, collector
|
549
|
+
else
|
550
|
+
collector
|
551
|
+
end
|
550
552
|
end
|
551
553
|
|
552
554
|
def visit_Arel_Nodes_JoinSource o, collector
|
@@ -755,6 +757,7 @@ module Arel
|
|
755
757
|
def visit_Array o, collector
|
756
758
|
inject_join o, collector, ", "
|
757
759
|
end
|
760
|
+
alias :visit_Set :visit_Array
|
758
761
|
|
759
762
|
def quote value, column = nil
|
760
763
|
return value if Arel::Nodes::SqlLiteral === value
|
@@ -763,11 +766,12 @@ module Arel
|
|
763
766
|
|
764
767
|
def quote_table_name name
|
765
768
|
return name if Arel::Nodes::SqlLiteral === name
|
766
|
-
@
|
769
|
+
@connection.quote_table_name(name)
|
767
770
|
end
|
768
771
|
|
769
772
|
def quote_column_name name
|
770
|
-
|
773
|
+
return name if Arel::Nodes::SqlLiteral === name
|
774
|
+
@connection.quote_column_name(name)
|
771
775
|
end
|
772
776
|
|
773
777
|
def maybe_visit thing, collector
|
@@ -1,32 +1,39 @@
|
|
1
1
|
module Arel
|
2
2
|
module Visitors
|
3
3
|
class Visitor
|
4
|
+
def initialize
|
5
|
+
@dispatch = Hash.new do |hash, class_name|
|
6
|
+
raise if class_name == 'Arel::Nodes::Union'
|
7
|
+
hash[class_name] = "visit_#{(class_name || '').gsub('::', '_')}"
|
8
|
+
end
|
9
|
+
|
10
|
+
# pre-populate cache. FIXME: this should be passed in to each
|
11
|
+
# instance, but we can do that later.
|
12
|
+
self.class.private_instance_methods.sort.each do |name|
|
13
|
+
next unless name =~ /^visit_(.*)$/
|
14
|
+
@dispatch[$1.gsub('_', '::')] = name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
4
18
|
def accept object
|
5
19
|
visit object
|
6
20
|
end
|
7
21
|
|
8
22
|
private
|
9
23
|
|
10
|
-
DISPATCH = Hash.new do |hash, visitor_class|
|
11
|
-
hash[visitor_class] =
|
12
|
-
Hash.new do |method_hash, node_class|
|
13
|
-
method_hash[node_class] = "visit_#{(node_class.name || '').gsub('::', '_')}"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
24
|
def dispatch
|
18
|
-
|
25
|
+
@dispatch
|
19
26
|
end
|
20
27
|
|
21
28
|
def visit object
|
22
|
-
send dispatch[object.class], object
|
29
|
+
send dispatch[object.class.name], object
|
23
30
|
rescue NoMethodError => e
|
24
|
-
raise e if respond_to?(dispatch[object.class], true)
|
31
|
+
raise e if respond_to?(dispatch[object.class.name], true)
|
25
32
|
superklass = object.class.ancestors.find { |klass|
|
26
|
-
respond_to?(dispatch[klass], true)
|
33
|
+
respond_to?(dispatch[klass.name], true)
|
27
34
|
}
|
28
35
|
raise(TypeError, "Cannot visit #{object.class}") unless superklass
|
29
|
-
dispatch[object.class] = dispatch[superklass]
|
36
|
+
dispatch[object.class.name] = dispatch[superklass.name]
|
30
37
|
retry
|
31
38
|
end
|
32
39
|
end
|
@@ -66,7 +66,7 @@ module Arel
|
|
66
66
|
relation[:id].gt(10).must_be_kind_of Nodes::GreaterThan
|
67
67
|
end
|
68
68
|
|
69
|
-
it 'should generate
|
69
|
+
it 'should generate > in sql' do
|
70
70
|
relation = Table.new(:users)
|
71
71
|
mgr = relation.project relation[:id]
|
72
72
|
mgr.where relation[:id].gt(10)
|
@@ -85,6 +85,17 @@ module Arel
|
|
85
85
|
SELECT * FROM "users" WHERE "users"."karma" > (SELECT AVG("users"."karma") AS avg_id FROM "users")
|
86
86
|
}
|
87
87
|
end
|
88
|
+
|
89
|
+
it 'should accept various data types.' do
|
90
|
+
relation = Table.new(:users)
|
91
|
+
mgr = relation.project relation[:id]
|
92
|
+
mgr.where relation[:name].gt('fake_name')
|
93
|
+
mgr.to_sql.must_match %{"users"."name" > 'fake_name'}
|
94
|
+
|
95
|
+
current_time = ::Time.now
|
96
|
+
mgr.where relation[:created_at].gt(current_time)
|
97
|
+
mgr.to_sql.must_match %{"users"."created_at" > '#{current_time}'}
|
98
|
+
end
|
88
99
|
end
|
89
100
|
|
90
101
|
describe '#gt_any' do
|
@@ -133,6 +144,17 @@ module Arel
|
|
133
144
|
SELECT "users"."id" FROM "users" WHERE "users"."id" >= 10
|
134
145
|
}
|
135
146
|
end
|
147
|
+
|
148
|
+
it 'should accept various data types.' do
|
149
|
+
relation = Table.new(:users)
|
150
|
+
mgr = relation.project relation[:id]
|
151
|
+
mgr.where relation[:name].gteq('fake_name')
|
152
|
+
mgr.to_sql.must_match %{"users"."name" >= 'fake_name'}
|
153
|
+
|
154
|
+
current_time = ::Time.now
|
155
|
+
mgr.where relation[:created_at].gteq(current_time)
|
156
|
+
mgr.to_sql.must_match %{"users"."created_at" >= '#{current_time}'}
|
157
|
+
end
|
136
158
|
end
|
137
159
|
|
138
160
|
describe '#gteq_any' do
|
@@ -181,6 +203,17 @@ module Arel
|
|
181
203
|
SELECT "users"."id" FROM "users" WHERE "users"."id" < 10
|
182
204
|
}
|
183
205
|
end
|
206
|
+
|
207
|
+
it 'should accept various data types.' do
|
208
|
+
relation = Table.new(:users)
|
209
|
+
mgr = relation.project relation[:id]
|
210
|
+
mgr.where relation[:name].lt('fake_name')
|
211
|
+
mgr.to_sql.must_match %{"users"."name" < 'fake_name'}
|
212
|
+
|
213
|
+
current_time = ::Time.now
|
214
|
+
mgr.where relation[:created_at].lt(current_time)
|
215
|
+
mgr.to_sql.must_match %{"users"."created_at" < '#{current_time}'}
|
216
|
+
end
|
184
217
|
end
|
185
218
|
|
186
219
|
describe '#lt_any' do
|
@@ -229,6 +262,17 @@ module Arel
|
|
229
262
|
SELECT "users"."id" FROM "users" WHERE "users"."id" <= 10
|
230
263
|
}
|
231
264
|
end
|
265
|
+
|
266
|
+
it 'should accept various data types.' do
|
267
|
+
relation = Table.new(:users)
|
268
|
+
mgr = relation.project relation[:id]
|
269
|
+
mgr.where relation[:name].lteq('fake_name')
|
270
|
+
mgr.to_sql.must_match %{"users"."name" <= 'fake_name'}
|
271
|
+
|
272
|
+
current_time = ::Time.now
|
273
|
+
mgr.where relation[:created_at].lteq(current_time)
|
274
|
+
mgr.to_sql.must_match %{"users"."created_at" <= '#{current_time}'}
|
275
|
+
end
|
232
276
|
end
|
233
277
|
|
234
278
|
describe '#lteq_any' do
|
@@ -507,15 +551,109 @@ module Arel
|
|
507
551
|
end
|
508
552
|
end
|
509
553
|
|
554
|
+
describe 'with a range' do
|
555
|
+
it 'can be constructed with a standard range' do
|
556
|
+
attribute = Attribute.new nil, nil
|
557
|
+
node = attribute.between(1..3)
|
558
|
+
|
559
|
+
node.must_equal Nodes::Between.new(
|
560
|
+
attribute,
|
561
|
+
Nodes::And.new([
|
562
|
+
Nodes::Casted.new(1, attribute),
|
563
|
+
Nodes::Casted.new(3, attribute)
|
564
|
+
])
|
565
|
+
)
|
566
|
+
end
|
567
|
+
|
568
|
+
it 'can be constructed with a range starting from -Infinity' do
|
569
|
+
attribute = Attribute.new nil, nil
|
570
|
+
node = attribute.between(-::Float::INFINITY..3)
|
571
|
+
|
572
|
+
node.must_equal Nodes::LessThanOrEqual.new(
|
573
|
+
attribute,
|
574
|
+
Nodes::Casted.new(3, attribute)
|
575
|
+
)
|
576
|
+
end
|
577
|
+
|
578
|
+
it 'can be constructed with an exclusive range starting from -Infinity' do
|
579
|
+
attribute = Attribute.new nil, nil
|
580
|
+
node = attribute.between(-::Float::INFINITY...3)
|
581
|
+
|
582
|
+
node.must_equal Nodes::LessThan.new(
|
583
|
+
attribute,
|
584
|
+
Nodes::Casted.new(3, attribute)
|
585
|
+
)
|
586
|
+
end
|
587
|
+
|
588
|
+
it 'can be constructed with an infinite range' do
|
589
|
+
attribute = Attribute.new nil, nil
|
590
|
+
node = attribute.between(-::Float::INFINITY..::Float::INFINITY)
|
591
|
+
|
592
|
+
node.must_equal Nodes::NotIn.new(attribute, [])
|
593
|
+
end
|
594
|
+
|
595
|
+
it 'can be constructed with a range ending at Infinity' do
|
596
|
+
attribute = Attribute.new nil, nil
|
597
|
+
node = attribute.between(0..::Float::INFINITY)
|
598
|
+
|
599
|
+
node.must_equal Nodes::GreaterThanOrEqual.new(
|
600
|
+
attribute,
|
601
|
+
Nodes::Casted.new(0, attribute)
|
602
|
+
)
|
603
|
+
end
|
604
|
+
|
605
|
+
it 'can be constructed with an exclusive range' do
|
606
|
+
attribute = Attribute.new nil, nil
|
607
|
+
node = attribute.between(0...3)
|
608
|
+
|
609
|
+
node.must_equal Nodes::And.new([
|
610
|
+
Nodes::GreaterThanOrEqual.new(
|
611
|
+
attribute,
|
612
|
+
Nodes::Casted.new(0, attribute)
|
613
|
+
),
|
614
|
+
Nodes::LessThan.new(
|
615
|
+
attribute,
|
616
|
+
Nodes::Casted.new(3, attribute)
|
617
|
+
)
|
618
|
+
])
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
510
622
|
describe '#in' do
|
623
|
+
it 'can be constructed with a subquery' do
|
624
|
+
relation = Table.new(:users)
|
625
|
+
mgr = relation.project relation[:id]
|
626
|
+
mgr.where relation[:name].does_not_match_all(['%chunky%','%bacon%'])
|
627
|
+
attribute = Attribute.new nil, nil
|
628
|
+
|
629
|
+
node = attribute.in(mgr)
|
630
|
+
|
631
|
+
node.must_equal Nodes::In.new(attribute, mgr.ast)
|
632
|
+
end
|
633
|
+
|
511
634
|
it 'can be constructed with a list' do
|
635
|
+
attribute = Attribute.new nil, nil
|
636
|
+
node = attribute.in([1, 2, 3])
|
637
|
+
|
638
|
+
node.must_equal Nodes::In.new(
|
639
|
+
attribute,
|
640
|
+
[
|
641
|
+
Nodes::Casted.new(1, attribute),
|
642
|
+
Nodes::Casted.new(2, attribute),
|
643
|
+
Nodes::Casted.new(3, attribute),
|
644
|
+
]
|
645
|
+
)
|
512
646
|
end
|
513
647
|
|
514
|
-
it '
|
648
|
+
it 'can be constructed with a random object' do
|
515
649
|
attribute = Attribute.new nil, nil
|
516
|
-
|
517
|
-
node
|
518
|
-
|
650
|
+
random_object = Object.new
|
651
|
+
node = attribute.in(random_object)
|
652
|
+
|
653
|
+
node.must_equal Nodes::In.new(
|
654
|
+
attribute,
|
655
|
+
Nodes::Casted.new(random_object, attribute)
|
656
|
+
)
|
519
657
|
end
|
520
658
|
|
521
659
|
it 'should generate IN in sql' do
|
@@ -560,13 +698,112 @@ module Arel
|
|
560
698
|
end
|
561
699
|
end
|
562
700
|
|
701
|
+
describe 'with a range' do
|
702
|
+
it 'can be constructed with a standard range' do
|
703
|
+
attribute = Attribute.new nil, nil
|
704
|
+
node = attribute.not_between(1..3)
|
705
|
+
|
706
|
+
node.must_equal Nodes::Grouping.new(Nodes::Or.new(
|
707
|
+
Nodes::LessThan.new(
|
708
|
+
attribute,
|
709
|
+
Nodes::Casted.new(1, attribute)
|
710
|
+
),
|
711
|
+
Nodes::GreaterThan.new(
|
712
|
+
attribute,
|
713
|
+
Nodes::Casted.new(3, attribute)
|
714
|
+
)
|
715
|
+
))
|
716
|
+
end
|
717
|
+
|
718
|
+
it 'can be constructed with a range starting from -Infinity' do
|
719
|
+
attribute = Attribute.new nil, nil
|
720
|
+
node = attribute.not_between(-::Float::INFINITY..3)
|
721
|
+
|
722
|
+
node.must_equal Nodes::GreaterThan.new(
|
723
|
+
attribute,
|
724
|
+
Nodes::Casted.new(3, attribute)
|
725
|
+
)
|
726
|
+
end
|
727
|
+
|
728
|
+
it 'can be constructed with an exclusive range starting from -Infinity' do
|
729
|
+
attribute = Attribute.new nil, nil
|
730
|
+
node = attribute.not_between(-::Float::INFINITY...3)
|
731
|
+
|
732
|
+
node.must_equal Nodes::GreaterThanOrEqual.new(
|
733
|
+
attribute,
|
734
|
+
Nodes::Casted.new(3, attribute)
|
735
|
+
)
|
736
|
+
end
|
737
|
+
|
738
|
+
it 'can be constructed with an infinite range' do
|
739
|
+
attribute = Attribute.new nil, nil
|
740
|
+
node = attribute.not_between(-::Float::INFINITY..::Float::INFINITY)
|
741
|
+
|
742
|
+
node.must_equal Nodes::In.new(attribute, [])
|
743
|
+
end
|
744
|
+
|
745
|
+
it 'can be constructed with a range ending at Infinity' do
|
746
|
+
attribute = Attribute.new nil, nil
|
747
|
+
node = attribute.not_between(0..::Float::INFINITY)
|
748
|
+
|
749
|
+
node.must_equal Nodes::LessThan.new(
|
750
|
+
attribute,
|
751
|
+
Nodes::Casted.new(0, attribute)
|
752
|
+
)
|
753
|
+
end
|
754
|
+
|
755
|
+
it 'can be constructed with an exclusive range' do
|
756
|
+
attribute = Attribute.new nil, nil
|
757
|
+
node = attribute.not_between(0...3)
|
758
|
+
|
759
|
+
node.must_equal Nodes::Grouping.new(Nodes::Or.new(
|
760
|
+
Nodes::LessThan.new(
|
761
|
+
attribute,
|
762
|
+
Nodes::Casted.new(0, attribute)
|
763
|
+
),
|
764
|
+
Nodes::GreaterThanOrEqual.new(
|
765
|
+
attribute,
|
766
|
+
Nodes::Casted.new(3, attribute)
|
767
|
+
)
|
768
|
+
))
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
563
772
|
describe '#not_in' do
|
773
|
+
it 'can be constructed with a subquery' do
|
774
|
+
relation = Table.new(:users)
|
775
|
+
mgr = relation.project relation[:id]
|
776
|
+
mgr.where relation[:name].does_not_match_all(['%chunky%','%bacon%'])
|
777
|
+
attribute = Attribute.new nil, nil
|
778
|
+
|
779
|
+
node = attribute.not_in(mgr)
|
564
780
|
|
565
|
-
|
781
|
+
node.must_equal Nodes::NotIn.new(attribute, mgr.ast)
|
782
|
+
end
|
783
|
+
|
784
|
+
it 'can be constructed with a list' do
|
566
785
|
attribute = Attribute.new nil, nil
|
567
|
-
node =
|
568
|
-
|
569
|
-
node.
|
786
|
+
node = attribute.not_in([1, 2, 3])
|
787
|
+
|
788
|
+
node.must_equal Nodes::NotIn.new(
|
789
|
+
attribute,
|
790
|
+
[
|
791
|
+
Nodes::Casted.new(1, attribute),
|
792
|
+
Nodes::Casted.new(2, attribute),
|
793
|
+
Nodes::Casted.new(3, attribute),
|
794
|
+
]
|
795
|
+
)
|
796
|
+
end
|
797
|
+
|
798
|
+
it 'can be constructed with a random object' do
|
799
|
+
attribute = Attribute.new nil, nil
|
800
|
+
random_object = Object.new
|
801
|
+
node = attribute.not_in(random_object)
|
802
|
+
|
803
|
+
node.must_equal Nodes::NotIn.new(
|
804
|
+
attribute,
|
805
|
+
Nodes::Casted.new(random_object, attribute)
|
806
|
+
)
|
570
807
|
end
|
571
808
|
|
572
809
|
it 'should generate NOT IN in sql' do
|