predicate 2.2.1 → 2.3.0
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/lib/predicate/factory.rb +5 -1
- data/lib/predicate/grammar.sexp.yml +3 -0
- data/lib/predicate/nodes/eq.rb +2 -2
- data/lib/predicate/nodes/expr.rb +4 -0
- data/lib/predicate/nodes/in.rb +4 -2
- data/lib/predicate/nodes/intersect.rb +1 -1
- data/lib/predicate/nodes/literal.rb +15 -0
- data/lib/predicate/placeholder.rb +4 -0
- data/lib/predicate/processors/binder.rb +23 -0
- data/lib/predicate/processors/to_s.rb +1 -0
- data/lib/predicate/processors.rb +1 -0
- data/lib/predicate/sequel/to_sequel.rb +21 -11
- data/lib/predicate/version.rb +2 -2
- data/lib/predicate.rb +6 -0
- data/spec/predicate/test_and_split.rb +12 -0
- data/spec/predicate/test_attr_split.rb +12 -0
- data/spec/predicate/test_bind.rb +27 -0
- data/spec/predicate/test_constant_variables.rb +32 -0
- data/spec/predicate/test_constants.rb +12 -0
- data/spec/predicate/test_evaluate.rb +10 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 901370b7a22028c4557c52fb7f5cd8d3ac92c97a440cf4d0f772bd592198c3a5
|
|
4
|
+
data.tar.gz: 7dc1e4912f365fc08657fc1b7289f1796989511c8b5e7c5e219bf8fe9eb16ccd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9515618b5f501b1774cae70cd88784d8bc965f2c331132c5c1f11363e3f690df9233f423195e3d0938fdf42cde4f90a68a69f1273e544014a0f43b386b322b8b
|
|
7
|
+
data.tar.gz: 4c4870c03e0aa43e7082136e68d6c17873febcb5608090713fc5ee0873f8f5e5c061a74eb5d57159bac3962f3ca9ce906e629e329bd654c4fd4be61cedad4ea8
|
data/lib/predicate/factory.rb
CHANGED
|
@@ -17,6 +17,10 @@ class Predicate
|
|
|
17
17
|
_factor_predicate([:qualified_identifier, qualifier, name])
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def placeholder
|
|
21
|
+
Placeholder.new
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
def and(left, right = nil)
|
|
21
25
|
_factor_predicate([:and, sexpr(left), sexpr(right)])
|
|
22
26
|
end
|
|
@@ -31,7 +35,7 @@ class Predicate
|
|
|
31
35
|
|
|
32
36
|
def in(left, right)
|
|
33
37
|
left, right = sexpr(left), sexpr(right)
|
|
34
|
-
if right.literal? && right.
|
|
38
|
+
if right.literal? && right.empty_value?
|
|
35
39
|
contradiction
|
|
36
40
|
else
|
|
37
41
|
_factor_predicate([:in, left, right])
|
|
@@ -51,6 +51,7 @@ rules:
|
|
|
51
51
|
term:
|
|
52
52
|
- varref
|
|
53
53
|
- literal
|
|
54
|
+
- placeholder
|
|
54
55
|
- opaque
|
|
55
56
|
varref:
|
|
56
57
|
- qualified_identifier
|
|
@@ -59,6 +60,8 @@ rules:
|
|
|
59
60
|
- [ "::Proc" ]
|
|
60
61
|
literal:
|
|
61
62
|
- "::Object"
|
|
63
|
+
placeholder:
|
|
64
|
+
- "::Predicate::Placeholder"
|
|
62
65
|
opaque:
|
|
63
66
|
- "::Object"
|
|
64
67
|
options:
|
data/lib/predicate/nodes/eq.rb
CHANGED
|
@@ -32,9 +32,9 @@ class Predicate
|
|
|
32
32
|
|
|
33
33
|
def constants
|
|
34
34
|
left, right = sexpr(self.left), sexpr(self.right)
|
|
35
|
-
if left.identifier? && right.literal?
|
|
35
|
+
if left.identifier? && right.literal? && !right.has_placeholder?
|
|
36
36
|
{ left.name => right.value }
|
|
37
|
-
elsif right.identifier? && left.literal?
|
|
37
|
+
elsif right.identifier? && left.literal? && !left.has_placeholder?
|
|
38
38
|
{ right.name => left.value }
|
|
39
39
|
else
|
|
40
40
|
{}
|
data/lib/predicate/nodes/expr.rb
CHANGED
data/lib/predicate/nodes/in.rb
CHANGED
|
@@ -26,6 +26,7 @@ class Predicate
|
|
|
26
26
|
|
|
27
27
|
# we only optimize if both right terms are literals
|
|
28
28
|
return super unless right.literal? and other.right.literal?
|
|
29
|
+
return super if right.has_placeholder? or other.right.has_placeholder?
|
|
29
30
|
|
|
30
31
|
intersection = right.value & other.right.value
|
|
31
32
|
if intersection.empty?
|
|
@@ -45,7 +46,7 @@ class Predicate
|
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
def constant_variables
|
|
48
|
-
if right.literal? and right.
|
|
49
|
+
if right.literal? and right.singleton_value?
|
|
49
50
|
free_variables
|
|
50
51
|
else
|
|
51
52
|
[]
|
|
@@ -53,7 +54,7 @@ class Predicate
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def constants
|
|
56
|
-
if right.literal? and right.
|
|
57
|
+
if right.literal? and right.singleton_value?
|
|
57
58
|
{ identifier.name => right.value.first }
|
|
58
59
|
else
|
|
59
60
|
{}
|
|
@@ -66,6 +67,7 @@ class Predicate
|
|
|
66
67
|
|
|
67
68
|
def evaluate(tuple)
|
|
68
69
|
values = right.evaluate(tuple)
|
|
70
|
+
raise UnboundError if values.is_a?(Placeholder)
|
|
69
71
|
values.include?(identifier.evaluate(tuple))
|
|
70
72
|
end
|
|
71
73
|
|
|
@@ -14,7 +14,22 @@ class Predicate
|
|
|
14
14
|
last
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
def has_placeholder?
|
|
18
|
+
value.is_a?(Placeholder)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def empty_value?
|
|
22
|
+
return false if has_placeholder?
|
|
23
|
+
value.respond_to?(:empty?) && value.empty?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def singleton_value?
|
|
27
|
+
return false if has_placeholder?
|
|
28
|
+
value.respond_to?(:size) && value.size == 1
|
|
29
|
+
end
|
|
30
|
+
|
|
17
31
|
def evaluate(tuple)
|
|
32
|
+
raise UnboundError if has_placeholder?
|
|
18
33
|
value
|
|
19
34
|
end
|
|
20
35
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Predicate
|
|
2
|
+
class Binder < Sexpr::Rewriter
|
|
3
|
+
|
|
4
|
+
grammar Grammar
|
|
5
|
+
|
|
6
|
+
def initialize(binding)
|
|
7
|
+
@binding = binding
|
|
8
|
+
end
|
|
9
|
+
attr_reader :binding
|
|
10
|
+
|
|
11
|
+
def on_literal(sexpr)
|
|
12
|
+
lit = sexpr.last
|
|
13
|
+
if lit.is_a?(Placeholder)
|
|
14
|
+
[:literal, binding[lit]]
|
|
15
|
+
else
|
|
16
|
+
sexpr
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
alias :on_missing :copy_and_apply
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/predicate/processors.rb
CHANGED
|
@@ -13,7 +13,13 @@ class Predicate
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def on_literal(sexpr)
|
|
16
|
-
sexpr.last.nil?
|
|
16
|
+
if sexpr.last.nil?
|
|
17
|
+
nil
|
|
18
|
+
elsif sexpr.last.is_a?(Predicate::Placeholder)
|
|
19
|
+
::Sequel.lit("?", :"$#{sexpr.last.object_id}")
|
|
20
|
+
else
|
|
21
|
+
::Sequel.expr(sexpr.last)
|
|
22
|
+
end
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
###
|
|
@@ -48,18 +54,22 @@ class Predicate
|
|
|
48
54
|
def on_in(sexpr)
|
|
49
55
|
left, right = apply(sexpr.identifier), sexpr.right
|
|
50
56
|
if right.literal?
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
if right.has_placeholder?
|
|
58
|
+
::Sequel.expr(left => [on_literal(right)])
|
|
59
|
+
else
|
|
60
|
+
values = Array(right.value).uniq
|
|
61
|
+
if values.include?(nil)
|
|
62
|
+
nonnil = values.compact
|
|
63
|
+
if nonnil.empty?
|
|
64
|
+
::Sequel.expr(left => nil)
|
|
65
|
+
elsif nonnil.size == 1
|
|
66
|
+
::Sequel.expr(left => nil) | ::Sequel.expr(left => nonnil.first)
|
|
67
|
+
else
|
|
68
|
+
::Sequel.expr(left => nil) | ::Sequel.expr(left => nonnil)
|
|
69
|
+
end
|
|
58
70
|
else
|
|
59
|
-
::Sequel.expr(left =>
|
|
71
|
+
::Sequel.expr(left => right.value)
|
|
60
72
|
end
|
|
61
|
-
else
|
|
62
|
-
::Sequel.expr(left => right.value)
|
|
63
73
|
end
|
|
64
74
|
elsif right.opaque?
|
|
65
75
|
::Sequel.expr(left => apply(right))
|
data/lib/predicate/version.rb
CHANGED
data/lib/predicate.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
require 'sexpr'
|
|
2
2
|
require_relative 'predicate/version'
|
|
3
|
+
require_relative 'predicate/placeholder'
|
|
3
4
|
require_relative 'predicate/factory'
|
|
4
5
|
require_relative 'predicate/grammar'
|
|
5
6
|
require_relative 'predicate/processors'
|
|
6
7
|
class Predicate
|
|
7
8
|
|
|
8
9
|
class NotSupportedError < StandardError; end
|
|
10
|
+
class UnboundError < StandardError; end
|
|
9
11
|
|
|
10
12
|
TupleLike = ->(t){ t.is_a?(Hash) }
|
|
11
13
|
|
|
@@ -90,6 +92,10 @@ class Predicate
|
|
|
90
92
|
Predicate.new(expr.rename(renaming))
|
|
91
93
|
end
|
|
92
94
|
|
|
95
|
+
def bind(binding)
|
|
96
|
+
Predicate.new(expr.bind(binding))
|
|
97
|
+
end
|
|
98
|
+
|
|
93
99
|
def evaluate(tuple)
|
|
94
100
|
expr.evaluate(tuple)
|
|
95
101
|
end
|
|
@@ -53,6 +53,18 @@ class Predicate
|
|
|
53
53
|
it{ should eq([ p.tautology, pred ]) }
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
context "on eq with placeholder" do
|
|
57
|
+
let(:pred){ p.eq(:x, p.placeholder) }
|
|
58
|
+
|
|
59
|
+
it{ should eq([ pred, p.tautology ]) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "on in with placeholder" do
|
|
63
|
+
let(:pred){ p.in(:x, p.placeholder) }
|
|
64
|
+
|
|
65
|
+
it{ should eq([ pred, p.tautology ]) }
|
|
66
|
+
end
|
|
67
|
+
|
|
56
68
|
context "on match (included)" do
|
|
57
69
|
let(:pred){ p.match(:x, "London") }
|
|
58
70
|
|
|
@@ -35,6 +35,18 @@ class Predicate
|
|
|
35
35
|
it{ should eq({ x: pred }) }
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
context "on eq with placeholder" do
|
|
39
|
+
let(:pred){ p.eq(:x, p.placeholder) }
|
|
40
|
+
|
|
41
|
+
it{ should eq({ x: pred }) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context "on in" do
|
|
45
|
+
let(:pred){ p.in(:x, [2]) }
|
|
46
|
+
|
|
47
|
+
it{ should eq({ x: pred }) }
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
context "on match" do
|
|
39
51
|
let(:pred){ p.match(:x, "London") }
|
|
40
52
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
class Predicate
|
|
3
|
+
describe Predicate, "bind" do
|
|
4
|
+
|
|
5
|
+
it 'allowing binding a eq placeholder' do
|
|
6
|
+
p = Predicate.placeholder
|
|
7
|
+
unbound = Predicate.eq(:name, p)
|
|
8
|
+
bind = unbound.bind(p => "blambeau")
|
|
9
|
+
expect(bind).to eql(Predicate.eq(:name, "blambeau"))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'allowing binding an in placeholder' do
|
|
13
|
+
p = Predicate.placeholder
|
|
14
|
+
unbound = Predicate.in(:name, p)
|
|
15
|
+
bind = unbound.bind(p => ["blambeau","llambeau"])
|
|
16
|
+
expect(bind).to eql(Predicate.in(:name, ["blambeau","llambeau"]))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'applies recursively placeholder' do
|
|
20
|
+
p, p2 = Predicate.placeholder, Predicate.placeholder
|
|
21
|
+
unbound = Predicate.eq(:name, p) & Predicate.eq(:last, p2)
|
|
22
|
+
bind = unbound.bind(p => "bernard", p2 => "lambeau")
|
|
23
|
+
expect(bind).to eql(Predicate.eq(:name, "bernard") & Predicate.eq(:last, "lambeau"))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -10,12 +10,44 @@ class Predicate
|
|
|
10
10
|
it{ expect(subject).to eql([:x, :y]) }
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
describe "on a comp(:eq) with placeholder" do
|
|
14
|
+
let(:p){ Predicate.coerce(x: Predicate.placeholder) }
|
|
15
|
+
|
|
16
|
+
it{ expect(subject).to eql([:x]) }
|
|
17
|
+
end
|
|
18
|
+
|
|
13
19
|
describe "on a in with one value" do
|
|
14
20
|
let(:p){ Predicate.in(:x, [2]) }
|
|
15
21
|
|
|
16
22
|
it{ expect(subject).to eql([:x]) }
|
|
17
23
|
end
|
|
18
24
|
|
|
25
|
+
describe "on a in with one placeholder" do
|
|
26
|
+
let(:p){ Predicate.in(:x, Predicate.placeholder) }
|
|
27
|
+
|
|
28
|
+
it{ expect(subject).to eql([]) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "on an intersect with many values" do
|
|
32
|
+
let(:p){ Predicate.intersect(:x, [2, 3]) }
|
|
33
|
+
|
|
34
|
+
it{ expect(subject).to eql([]) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "on an intersect with one value" do
|
|
38
|
+
let(:p){ Predicate.intersect(:x, [2]) }
|
|
39
|
+
|
|
40
|
+
# TODO: is that correct?
|
|
41
|
+
it{ expect(subject).to eql([]) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "on an intersect with a placeholder" do
|
|
45
|
+
let(:p){ Predicate.intersect(:x, Predicate.placeholder) }
|
|
46
|
+
|
|
47
|
+
# TODO: is that correct?
|
|
48
|
+
it{ expect(subject).to eql([]) }
|
|
49
|
+
end
|
|
50
|
+
|
|
19
51
|
describe "on a in with mutiple values" do
|
|
20
52
|
let(:p){ Predicate.in(:x, [2, 3]) }
|
|
21
53
|
|
|
@@ -23,6 +23,12 @@ class Predicate
|
|
|
23
23
|
it{ should eq({x: 2}) }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
context "on eq(identifier,placeholder)" do
|
|
27
|
+
let(:pred){ p.eq(:x, p.placeholder) }
|
|
28
|
+
|
|
29
|
+
it{ should eq({}) }
|
|
30
|
+
end
|
|
31
|
+
|
|
26
32
|
context "on eq(value,identifier)" do
|
|
27
33
|
let(:pred){ p.eq(2, :x) }
|
|
28
34
|
|
|
@@ -107,6 +113,12 @@ class Predicate
|
|
|
107
113
|
it{ should eq({}) }
|
|
108
114
|
end
|
|
109
115
|
|
|
116
|
+
context "on intersect with placeholder" do
|
|
117
|
+
let(:pred){ p.intersect(:x, p.placeholder) }
|
|
118
|
+
|
|
119
|
+
it{ should eq({}) }
|
|
120
|
+
end
|
|
121
|
+
|
|
110
122
|
context "on or (two eqs)" do
|
|
111
123
|
let(:pred){ p.eq(:x, 2) | p.eq(:y, 4) }
|
|
112
124
|
|
|
@@ -216,6 +216,16 @@ class Predicate
|
|
|
216
216
|
end
|
|
217
217
|
end
|
|
218
218
|
|
|
219
|
+
context 'when having a placeholder' do
|
|
220
|
+
let(:predicate){
|
|
221
|
+
Predicate.match(:eq, Predicate.placeholder)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
it "raises an UnboundError" do
|
|
225
|
+
expect{ predicate.evaluate({}) }.to raise_error(UnboundError)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
219
229
|
context 'has a call alias' do
|
|
220
230
|
let(:predicate){
|
|
221
231
|
Predicate.new(Factory.gte(:x => 0))
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: predicate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bernard Lambeau
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-04-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sexpr
|
|
@@ -116,7 +116,9 @@ files:
|
|
|
116
116
|
- lib/predicate/nodes/or.rb
|
|
117
117
|
- lib/predicate/nodes/qualified_identifier.rb
|
|
118
118
|
- lib/predicate/nodes/tautology.rb
|
|
119
|
+
- lib/predicate/placeholder.rb
|
|
119
120
|
- lib/predicate/processors.rb
|
|
121
|
+
- lib/predicate/processors/binder.rb
|
|
120
122
|
- lib/predicate/processors/qualifier.rb
|
|
121
123
|
- lib/predicate/processors/renamer.rb
|
|
122
124
|
- lib/predicate/processors/to_s.rb
|
|
@@ -166,6 +168,7 @@ files:
|
|
|
166
168
|
- spec/nodes/qualified_identifier/test_qualifier.rb
|
|
167
169
|
- spec/predicate/test_and_split.rb
|
|
168
170
|
- spec/predicate/test_attr_split.rb
|
|
171
|
+
- spec/predicate/test_bind.rb
|
|
169
172
|
- spec/predicate/test_bool_and.rb
|
|
170
173
|
- spec/predicate/test_bool_not.rb
|
|
171
174
|
- spec/predicate/test_bool_or.rb
|