predicate 1.1.3 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/predicate/factory.rb +5 -0
- data/lib/predicate/grammar.rb +1 -0
- data/lib/predicate/grammar.sexp.yml +3 -0
- data/lib/predicate/nodes/and.rb +10 -0
- data/lib/predicate/nodes/contradiction.rb +4 -0
- data/lib/predicate/nodes/expr.rb +12 -0
- data/lib/predicate/nodes/intersect.rb +26 -0
- data/lib/predicate/nodes/native.rb +8 -0
- data/lib/predicate/nodes/tautology.rb +4 -0
- data/lib/predicate/processors/qualifier.rb +7 -2
- data/lib/predicate/processors/to_ruby_code.rb +4 -0
- data/lib/predicate/sequel/to_sequel.rb +5 -2
- data/lib/predicate/version.rb +2 -2
- data/lib/predicate.rb +14 -0
- data/spec/factory/test_intersect.rb +12 -0
- data/spec/grammar/test_sexpr.rb +17 -0
- data/spec/nodes/and/test_attr_split.rb +50 -0
- data/spec/predicate/test_attr_split.rb +39 -0
- data/spec/predicate/test_factory_methods.rb +6 -0
- data/spec/predicate/test_qualify.rb +29 -15
- data/spec/sequel/test_to_sequel.rb +31 -0
- data/spec/test_predicate.rb +9 -2
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 710a45c31d1350beeded48ea522d3e868925e600
|
4
|
+
data.tar.gz: 8e9acddd67b337218c749aa6744940b26ca8568e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3d92a4dfa7227cbac8dc6b9dc9e4856861983103e2840ad041721269a66ecce74980750293178b3c3cdc9f34c06aa2d5a396880634f47e84a0b03aaac3a202d
|
7
|
+
data.tar.gz: ff1eee7dcc029585926d897eb5f3da17b68dd6f392f6e2c73bb50208ae00d22aedf0422feccea4cf747d869555b37dfa720407aa73746dd74935cfd52384f8de
|
data/lib/predicate/factory.rb
CHANGED
@@ -35,6 +35,11 @@ class Predicate
|
|
35
35
|
end
|
36
36
|
alias :among :in
|
37
37
|
|
38
|
+
def intersect(identifier, values)
|
39
|
+
identifier = sexpr(identifier) if identifier.is_a?(Symbol)
|
40
|
+
_factor_predicate([:intersect, identifier, values])
|
41
|
+
end
|
42
|
+
|
38
43
|
def comp(op, h)
|
39
44
|
from_hash(h, op)
|
40
45
|
end
|
data/lib/predicate/grammar.rb
CHANGED
data/lib/predicate/nodes/and.rb
CHANGED
@@ -18,6 +18,16 @@ class Predicate
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
def attr_split
|
22
|
+
# Similar to and_split above, but the reasonning applies on
|
23
|
+
# attribute names this time.
|
24
|
+
sexpr_body.each_with_object({}) do |term, h|
|
25
|
+
term.attr_split.each_pair do |a,p|
|
26
|
+
h[a] = (h[a] || tautology) & p
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
21
31
|
def constant_variables
|
22
32
|
sexpr_body.inject([]) do |cvars,expr|
|
23
33
|
cvars | expr.constant_variables
|
data/lib/predicate/nodes/expr.rb
CHANGED
@@ -40,6 +40,18 @@ class Predicate
|
|
40
40
|
(free_variables & attr_list).empty? ? [ tautology, self ] : [ self, tautology ]
|
41
41
|
end
|
42
42
|
|
43
|
+
def attr_split
|
44
|
+
# if I have only one variable reference, then I can return
|
45
|
+
# myself mapped to that variable...
|
46
|
+
if (vars = free_variables).size == 1
|
47
|
+
{ vars.first => self }
|
48
|
+
else
|
49
|
+
# I must still map myself to nil to meet the conjunction
|
50
|
+
# specification
|
51
|
+
{ nil => self }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
43
55
|
def rename(renaming)
|
44
56
|
Renamer.call(self, :renaming => renaming)
|
45
57
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Predicate
|
2
|
+
module Intersect
|
3
|
+
include Expr
|
4
|
+
|
5
|
+
def priority
|
6
|
+
80
|
7
|
+
end
|
8
|
+
|
9
|
+
def identifier
|
10
|
+
self[1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def values
|
14
|
+
self[2]
|
15
|
+
end
|
16
|
+
|
17
|
+
def free_variables
|
18
|
+
@free_variables ||= identifier.free_variables
|
19
|
+
end
|
20
|
+
|
21
|
+
def constant_variables
|
22
|
+
values.size == 1 ? free_variables : []
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -10,12 +10,20 @@ class Predicate
|
|
10
10
|
self[1]
|
11
11
|
end
|
12
12
|
|
13
|
+
# overriden because parent relies on free_variables,
|
14
|
+
# which raises an exception
|
13
15
|
def and_split(attr_list)
|
14
16
|
# I possibly make references to those attributes, so
|
15
17
|
# I can't be P2
|
16
18
|
[ self, tautology ]
|
17
19
|
end
|
18
20
|
|
21
|
+
# overriden because parent relies on free_variables,
|
22
|
+
# which raises an exception
|
23
|
+
def attr_split
|
24
|
+
{ nil => self }
|
25
|
+
end
|
26
|
+
|
19
27
|
def to_ruby_code(scope = 't')
|
20
28
|
if proc.respond_to?(:source_code)
|
21
29
|
code = proc.source_code
|
@@ -9,8 +9,13 @@ class Predicate
|
|
9
9
|
attr_reader :qualifier
|
10
10
|
|
11
11
|
def on_identifier(sexpr)
|
12
|
-
|
13
|
-
|
12
|
+
case qualifier
|
13
|
+
when Symbol
|
14
|
+
[:qualified_identifier, qualifier, sexpr.name]
|
15
|
+
else
|
16
|
+
return sexpr unless q = qualifier[sexpr.name]
|
17
|
+
[:qualified_identifier, q, sexpr.name]
|
18
|
+
end
|
14
19
|
end
|
15
20
|
|
16
21
|
def on_native(sexpr)
|
@@ -49,6 +49,10 @@ class Predicate
|
|
49
49
|
"#{to_ruby_literal(sexpr.values)}.include?(#{apply(sexpr.identifier)})"
|
50
50
|
end
|
51
51
|
|
52
|
+
def on_intersect(sexpr)
|
53
|
+
"!(#{apply(sexpr.identifier)} & #{to_ruby_literal(sexpr.values)}).empty?"
|
54
|
+
end
|
55
|
+
|
52
56
|
def on_literal(sexpr)
|
53
57
|
to_ruby_literal(sexpr.last)
|
54
58
|
end
|
@@ -6,7 +6,7 @@ class Predicate
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def on_qualified_identifier(sexpr)
|
9
|
-
::Sequel.
|
9
|
+
::Sequel.identifier(sexpr.name).qualify(sexpr.qualifier)
|
10
10
|
end
|
11
11
|
|
12
12
|
def on_literal(sexpr)
|
@@ -44,6 +44,7 @@ class Predicate
|
|
44
44
|
|
45
45
|
def on_in(sexpr)
|
46
46
|
left, right = apply(sexpr.identifier), sexpr.last
|
47
|
+
right = [right] if !right.is_a?(Array) && right.respond_to?(:sql_literal)
|
47
48
|
::Sequel.expr(left => right)
|
48
49
|
end
|
49
50
|
|
@@ -61,9 +62,11 @@ class Predicate
|
|
61
62
|
body[1..-1].inject(apply(body.first)){|f,t| f | apply(t) }
|
62
63
|
end
|
63
64
|
|
64
|
-
def
|
65
|
+
def on_unsupported(sexpr)
|
65
66
|
raise NotSupportedError
|
66
67
|
end
|
68
|
+
alias :on_native :on_unsupported
|
69
|
+
alias :on_intersect :on_unsupported
|
67
70
|
|
68
71
|
end # class ToSequel
|
69
72
|
end # class Predicate
|
data/lib/predicate/version.rb
CHANGED
data/lib/predicate.rb
CHANGED
@@ -95,6 +95,20 @@ class Predicate
|
|
95
95
|
expr.and_split(attr_list).map{|e| Predicate.new(e)}
|
96
96
|
end
|
97
97
|
|
98
|
+
# Returns a hash `(attr -> Pattr)` associating attribute names
|
99
|
+
# to predicates, so that each predicate `Pattr` only makes
|
100
|
+
# reference to the corresponding attribute name `attr`, while
|
101
|
+
# the conjunction of `Pattr`s is still equivalent to the
|
102
|
+
# original predicate.
|
103
|
+
#
|
104
|
+
# A `nil` key may map a predicate that still makes references
|
105
|
+
# to more than one attribute.
|
106
|
+
def attr_split
|
107
|
+
expr.attr_split.each_pair.each_with_object({}) do |(k,v),h|
|
108
|
+
h[k] = Predicate.new(v)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
98
112
|
def ==(other)
|
99
113
|
other.is_a?(Predicate) && (other.expr==expr)
|
100
114
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative "shared/a_comparison_factory_method"
|
2
|
+
class Predicate
|
3
|
+
describe Factory, 'intersect' do
|
4
|
+
|
5
|
+
subject{ Factory.intersect(:x, [2, 3]) }
|
6
|
+
|
7
|
+
it{ should be_a(Intersect) }
|
8
|
+
|
9
|
+
it{ should eq([:intersect, [:identifier, :x], [2, 3]]) }
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
data/spec/grammar/test_sexpr.rb
CHANGED
@@ -7,10 +7,15 @@ class Predicate
|
|
7
7
|
let(:contradiction){
|
8
8
|
[:contradiction, false]
|
9
9
|
}
|
10
|
+
|
10
11
|
let(:identifier){
|
11
12
|
[:identifier, :name]
|
12
13
|
}
|
13
14
|
|
15
|
+
let(:values){
|
16
|
+
[12, 15]
|
17
|
+
}
|
18
|
+
|
14
19
|
before do
|
15
20
|
subject.should be_a(Sexpr)
|
16
21
|
end
|
@@ -87,6 +92,18 @@ class Predicate
|
|
87
92
|
it{ should be_a(Lte) }
|
88
93
|
end
|
89
94
|
|
95
|
+
describe "in" do
|
96
|
+
let(:expr){ [:in, identifier, values] }
|
97
|
+
|
98
|
+
it{ should be_a(In) }
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "intersect" do
|
102
|
+
let(:expr){ [:intersect, identifier, values] }
|
103
|
+
|
104
|
+
it{ should be_a(Intersect) }
|
105
|
+
end
|
106
|
+
|
90
107
|
describe "literal" do
|
91
108
|
let(:expr){ [:literal, 12] }
|
92
109
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
class Predicate
|
3
|
+
describe And, "attr_split" do
|
4
|
+
|
5
|
+
let(:tautology){ Factory.tautology }
|
6
|
+
|
7
|
+
subject{ predicate.attr_split }
|
8
|
+
|
9
|
+
context 'when each side returns a singleton hash' do
|
10
|
+
let(:predicate){ Factory.eq(:x, 2) & Factory.eq(:y, 3) }
|
11
|
+
|
12
|
+
it 'returns the hash union' do
|
13
|
+
expect(subject).to eql({
|
14
|
+
:x => Factory.eq(:x, 2),
|
15
|
+
:y => Factory.eq(:y, 3)
|
16
|
+
})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when right and left side share some attributes' do
|
21
|
+
let(:predicate){
|
22
|
+
Factory.eq(:x, 2) & (
|
23
|
+
Factory.eq(:y, 3) & Factory.in(:x, [1, 18])
|
24
|
+
)
|
25
|
+
}
|
26
|
+
|
27
|
+
it 'returns the hash union' do
|
28
|
+
expect(subject).to eql({
|
29
|
+
:x => Factory.eq(:x, 2) & Factory.in(:x, [1, 18]),
|
30
|
+
:y => Factory.eq(:y, 3)
|
31
|
+
})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'when right and left side share some attributes, but split cannot be made' do
|
36
|
+
let(:predicate){
|
37
|
+
Factory.eq(:x, 2) & (
|
38
|
+
Factory.eq(:y, 3) | Factory.in(:x, [1, 18])
|
39
|
+
)
|
40
|
+
}
|
41
|
+
|
42
|
+
it 'returns the hash union' do
|
43
|
+
expect(subject).to eql({
|
44
|
+
:x => Factory.eq(:x, 2),
|
45
|
+
nil => Factory.eq(:y, 3) | Factory.in(:x, [1, 18])
|
46
|
+
})
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
class Predicate
|
3
|
+
describe Predicate, "attr_split" do
|
4
|
+
|
5
|
+
let(:p){ Predicate }
|
6
|
+
subject{ pred.attr_split }
|
7
|
+
|
8
|
+
context "on tautology" do
|
9
|
+
let(:pred){ p.tautology }
|
10
|
+
|
11
|
+
it{ should eq({}) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "on contradiction" do
|
15
|
+
let(:pred){ p.contradiction }
|
16
|
+
|
17
|
+
it{ should eq({ nil => pred }) }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "on identifier" do
|
21
|
+
let(:pred){ p.identifier(:x) }
|
22
|
+
|
23
|
+
it{ should eq({ x: pred }) }
|
24
|
+
end
|
25
|
+
|
26
|
+
context "on not" do
|
27
|
+
let(:pred){ p.not(:x) }
|
28
|
+
|
29
|
+
it{ should eq({ x: pred }) }
|
30
|
+
end
|
31
|
+
|
32
|
+
context "on eq" do
|
33
|
+
let(:pred){ p.eq(:x, 2) }
|
34
|
+
|
35
|
+
it{ should eq({ x: pred }) }
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -6,27 +6,41 @@ class Predicate
|
|
6
6
|
|
7
7
|
subject{ predicate.qualify(qualifier) }
|
8
8
|
|
9
|
-
|
9
|
+
context 'when the qualifier is a Symbol' do
|
10
|
+
let(:qualifier) { :t }
|
11
|
+
let(:predicate){ p.in(:x, [2]) & p.eq(:y, :z) }
|
12
|
+
|
13
|
+
it 'works as expected' do
|
14
|
+
left = p.in(Factory.qualified_identifier(:t, :x), [2])
|
15
|
+
right = p.eq(Factory.qualified_identifier(:t, :y), Factory.qualified_identifier(:t, :z))
|
16
|
+
expect(subject).to eql(left & right)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when the qualifier is a Hash' do
|
10
21
|
|
11
|
-
|
12
|
-
let(:predicate){ p.in(:x, [2]) & p.eq(:y, 3) }
|
22
|
+
let(:qualifier) { {:x => :t} }
|
13
23
|
|
14
|
-
|
24
|
+
context 'on a full AST predicate' do
|
25
|
+
let(:predicate){ p.in(:x, [2]) & p.eq(:y, 3) }
|
15
26
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
27
|
+
it{ should eq(p.in(Factory.qualified_identifier(:t, :x), [2]) & p.eq(:y, 3)) }
|
28
|
+
|
29
|
+
specify "it should tag expressions correctly" do
|
30
|
+
subject.expr.should be_a(Sexpr)
|
31
|
+
subject.expr.should be_a(Expr)
|
32
|
+
subject.expr.should be_a(And)
|
33
|
+
end
|
20
34
|
end
|
21
|
-
end
|
22
35
|
|
23
|
-
|
24
|
-
|
36
|
+
context 'on a predicate that contains natives' do
|
37
|
+
let(:predicate){ p.in(:x, [2]) & p.native(lambda{}) }
|
25
38
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
it 'raises an error' do
|
40
|
+
lambda{
|
41
|
+
subject
|
42
|
+
}.should raise_error(NotSupportedError)
|
43
|
+
end
|
30
44
|
end
|
31
45
|
end
|
32
46
|
|
@@ -45,6 +45,14 @@ class Predicate
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
context 'qualified eq(name: "bob")' do
|
49
|
+
let(:predicate) { Predicate.eq(:name, "Bob").qualify(:t) }
|
50
|
+
|
51
|
+
it 'works as expected' do
|
52
|
+
expect(subject).to eql("SELECT * FROM `items` WHERE (`t`.`name` = 'Bob')")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
48
56
|
context 'eq(name: :address)' do
|
49
57
|
let(:predicate) { Predicate.eq(:name, :address) }
|
50
58
|
|
@@ -77,6 +85,21 @@ class Predicate
|
|
77
85
|
end
|
78
86
|
end
|
79
87
|
|
88
|
+
context 'in with something responding to sql_literal' do
|
89
|
+
let(:operand){
|
90
|
+
Object.new.tap{|o|
|
91
|
+
def o.sql_literal(db)
|
92
|
+
"Hello World"
|
93
|
+
end
|
94
|
+
}
|
95
|
+
}
|
96
|
+
let(:predicate) { Predicate.in(:price, operand) }
|
97
|
+
|
98
|
+
it 'works as expected' do
|
99
|
+
expect(subject).to eql("SELECT * FROM `items` WHERE (`price` IN (Hello World))")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
80
103
|
context 'not' do
|
81
104
|
let(:predicate) { !Predicate.in(:price, [10.0, 17.99]) }
|
82
105
|
|
@@ -109,6 +132,14 @@ class Predicate
|
|
109
132
|
end
|
110
133
|
end
|
111
134
|
|
135
|
+
context 'intersect' do
|
136
|
+
let(:predicate) { Predicate.intersect(:x, [8, 9]) }
|
137
|
+
|
138
|
+
it 'raises an error' do
|
139
|
+
expect { subject }.to raise_error(NotSupportedError)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
112
143
|
context 'native' do
|
113
144
|
let(:predicate) { Predicate.native(->(t){ false }) }
|
114
145
|
|
data/spec/test_predicate.rb
CHANGED
@@ -2,11 +2,12 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
shared_examples_for "a predicate" do
|
4
4
|
|
5
|
+
let(:w){ [8, 9] }
|
5
6
|
let(:x){ 12 }
|
6
7
|
let(:y){ 13 }
|
7
8
|
|
8
9
|
it 'provides a proc for easy evaluation' do
|
9
|
-
got = subject.to_proc.call(x:
|
10
|
+
got = subject.to_proc.call(w: w, x: x, y: y)
|
10
11
|
[ TrueClass, FalseClass ].should include(got.class)
|
11
12
|
end
|
12
13
|
|
@@ -24,7 +25,7 @@ shared_examples_for "a predicate" do
|
|
24
25
|
|
25
26
|
it 'has free variables' do
|
26
27
|
(fv = subject.free_variables).should be_a(Array)
|
27
|
-
(fv - [ :x, :y ]).should be_empty
|
28
|
+
(fv - [ :w, :x, :y ]).should be_empty
|
28
29
|
end
|
29
30
|
|
30
31
|
it 'always splits around and trivially when no free variables are touched' do
|
@@ -65,6 +66,12 @@ describe "Predicate.among" do
|
|
65
66
|
it_should_behave_like "a predicate"
|
66
67
|
end
|
67
68
|
|
69
|
+
describe "Predicate.intersect" do
|
70
|
+
subject{ Predicate.intersect(:w, [2, 3]) }
|
71
|
+
|
72
|
+
it_should_behave_like "a predicate"
|
73
|
+
end
|
74
|
+
|
68
75
|
describe "Predicate.eq" do
|
69
76
|
subject{ Predicate.eq(:x, 2) }
|
70
77
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: predicate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
@@ -103,6 +103,7 @@ files:
|
|
103
103
|
- lib/predicate/nodes/gte.rb
|
104
104
|
- lib/predicate/nodes/identifier.rb
|
105
105
|
- lib/predicate/nodes/in.rb
|
106
|
+
- lib/predicate/nodes/intersect.rb
|
106
107
|
- lib/predicate/nodes/literal.rb
|
107
108
|
- lib/predicate/nodes/lt.rb
|
108
109
|
- lib/predicate/nodes/lte.rb
|
@@ -135,6 +136,7 @@ files:
|
|
135
136
|
- spec/factory/test_gte.rb
|
136
137
|
- spec/factory/test_identifier.rb
|
137
138
|
- spec/factory/test_in.rb
|
139
|
+
- spec/factory/test_intersect.rb
|
138
140
|
- spec/factory/test_literal.rb
|
139
141
|
- spec/factory/test_lt.rb
|
140
142
|
- spec/factory/test_lte.rb
|
@@ -147,6 +149,7 @@ files:
|
|
147
149
|
- spec/grammar/test_match.rb
|
148
150
|
- spec/grammar/test_sexpr.rb
|
149
151
|
- spec/nodes/and/test_and_split.rb
|
152
|
+
- spec/nodes/and/test_attr_split.rb
|
150
153
|
- spec/nodes/dyadic_comp/test_and_split.rb
|
151
154
|
- spec/nodes/identifier/test_and_split.rb
|
152
155
|
- spec/nodes/identifier/test_free_variables.rb
|
@@ -158,6 +161,7 @@ files:
|
|
158
161
|
- spec/nodes/qualified_identifier/test_name.rb
|
159
162
|
- spec/nodes/qualified_identifier/test_qualifier.rb
|
160
163
|
- spec/predicate/test_and_split.rb
|
164
|
+
- spec/predicate/test_attr_split.rb
|
161
165
|
- spec/predicate/test_bool_and.rb
|
162
166
|
- spec/predicate/test_bool_not.rb
|
163
167
|
- spec/predicate/test_bool_or.rb
|