arel 6.0.0.beta1 → 6.0.0.beta2
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.
- 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
|