predicate 2.0.1 → 2.1.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 +19 -12
- data/lib/predicate/grammar.rb +1 -0
- data/lib/predicate/grammar.sexp.yml +4 -3
- data/lib/predicate/nodes/eq.rb +7 -10
- data/lib/predicate/nodes/expr.rb +4 -1
- data/lib/predicate/nodes/in.rb +30 -20
- data/lib/predicate/nodes/opaque.rb +18 -0
- data/lib/predicate/processors/to_s.rb +5 -1
- data/lib/predicate/sequel/to_sequel.rb +16 -3
- data/lib/predicate/version.rb +2 -2
- data/lib/predicate.rb +2 -0
- data/spec/factory/test_from_hash.rb +1 -1
- data/spec/factory/test_in.rb +21 -3
- data/spec/nodes/eq/test_and.rb +12 -0
- data/spec/predicate/test_constant_variables.rb +6 -0
- data/spec/predicate/test_constants.rb +6 -0
- data/spec/sequel/test_to_sequel.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3a81672181d7e3403108a8f09f7d7adbb94266c
|
4
|
+
data.tar.gz: ce94a0e1580ea4d77d177926a5e305736b47741b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd1126d3e88102910c26ff3181d9eb842e697b90632f82012c74efbd391a922de6465653ba9968ae802e8968cb8723f1e88fd30d0c630289c8ef6ef1fc86b97a
|
7
|
+
data.tar.gz: b0e52017a78b5a33b9eeb7d62c1e6e08ca2090555a501b21d4407c991cedde6201926c8f2c6d40d5621d7d912935d00584675ab7257ebed62ce197054e5888d1
|
data/lib/predicate/factory.rb
CHANGED
@@ -29,10 +29,13 @@ class Predicate
|
|
29
29
|
_factor_predicate([:not, sexpr(operand)])
|
30
30
|
end
|
31
31
|
|
32
|
-
def in(
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
def in(left, right)
|
33
|
+
left, right = sexpr(left), sexpr(right)
|
34
|
+
if right.literal? && right.value.empty?
|
35
|
+
contradiction
|
36
|
+
else
|
37
|
+
_factor_predicate([:in, left, right])
|
38
|
+
end
|
36
39
|
end
|
37
40
|
alias :among :in
|
38
41
|
|
@@ -63,7 +66,7 @@ class Predicate
|
|
63
66
|
else
|
64
67
|
terms = h.to_a.map{|(k,v)|
|
65
68
|
if v.is_a?(Array)
|
66
|
-
[:in, sexpr(k), v]
|
69
|
+
[:in, sexpr(k), sexpr(v)]
|
67
70
|
else
|
68
71
|
[op, sexpr(k), sexpr(v)]
|
69
72
|
end
|
@@ -77,6 +80,10 @@ class Predicate
|
|
77
80
|
_factor_predicate([:literal, literal])
|
78
81
|
end
|
79
82
|
|
83
|
+
def opaque(arg)
|
84
|
+
_factor_predicate([:opaque, arg])
|
85
|
+
end
|
86
|
+
|
80
87
|
def match(left, right, options = nil)
|
81
88
|
_factor_predicate([:match, sexpr(left), sexpr(right)] + (options.nil? ? [] : [options]))
|
82
89
|
end
|
@@ -89,13 +96,13 @@ class Predicate
|
|
89
96
|
|
90
97
|
def sexpr(expr)
|
91
98
|
case expr
|
92
|
-
when Expr
|
93
|
-
when Predicate
|
94
|
-
when TrueClass
|
95
|
-
when FalseClass
|
96
|
-
when Symbol
|
97
|
-
when Proc
|
98
|
-
when
|
99
|
+
when Expr then expr
|
100
|
+
when Predicate then expr.expr
|
101
|
+
when TrueClass then Grammar.sexpr([:tautology, true])
|
102
|
+
when FalseClass then Grammar.sexpr([:contradiction, false])
|
103
|
+
when Symbol then Grammar.sexpr([:identifier, expr])
|
104
|
+
when Proc then Grammar.sexpr([:native, expr])
|
105
|
+
when SexprLike then Grammar.sexpr(expr)
|
99
106
|
else
|
100
107
|
Grammar.sexpr([:literal, expr])
|
101
108
|
end
|
data/lib/predicate/grammar.rb
CHANGED
@@ -43,14 +43,15 @@ rules:
|
|
43
43
|
gte:
|
44
44
|
- [ term, term ]
|
45
45
|
in:
|
46
|
-
- [ varref,
|
46
|
+
- [ varref, term ]
|
47
47
|
intersect:
|
48
|
-
- [
|
48
|
+
- [ term, term ]
|
49
49
|
match:
|
50
50
|
- [ term, term, options ]
|
51
51
|
term:
|
52
52
|
- varref
|
53
53
|
- literal
|
54
|
+
- opaque
|
54
55
|
varref:
|
55
56
|
- qualified_identifier
|
56
57
|
- identifier
|
@@ -58,7 +59,7 @@ rules:
|
|
58
59
|
- [ "::Proc" ]
|
59
60
|
literal:
|
60
61
|
- "::Object"
|
61
|
-
|
62
|
+
opaque:
|
62
63
|
- "::Object"
|
63
64
|
options:
|
64
65
|
- "::Hash"
|
data/lib/predicate/nodes/eq.rb
CHANGED
@@ -7,20 +7,17 @@ class Predicate
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def &(other)
|
10
|
+
return super unless free_variables == other.free_variables
|
10
11
|
case other
|
11
12
|
when Eq
|
12
|
-
if
|
13
|
-
|
14
|
-
contradiction
|
15
|
-
else
|
16
|
-
super
|
17
|
-
end
|
13
|
+
return self if constants == other.constants
|
14
|
+
return contradiction
|
18
15
|
when In
|
19
|
-
return self if
|
20
|
-
super
|
21
|
-
else
|
22
|
-
super
|
16
|
+
return self if other.right.literal?
|
23
17
|
end
|
18
|
+
super
|
19
|
+
rescue NotSupportedError
|
20
|
+
super
|
24
21
|
end
|
25
22
|
|
26
23
|
def constant_variables
|
data/lib/predicate/nodes/expr.rb
CHANGED
@@ -23,6 +23,10 @@ class Predicate
|
|
23
23
|
sexpr_type == :literal
|
24
24
|
end
|
25
25
|
|
26
|
+
def opaque?
|
27
|
+
sexpr_type == :opaque
|
28
|
+
end
|
29
|
+
|
26
30
|
def identifier?
|
27
31
|
sexpr_type == :identifier
|
28
32
|
end
|
@@ -84,7 +88,6 @@ class Predicate
|
|
84
88
|
def to_s(scope = nil)
|
85
89
|
ToS.call(self, scope: scope)
|
86
90
|
end
|
87
|
-
alias :inspect :to_s
|
88
91
|
|
89
92
|
def sexpr(arg)
|
90
93
|
Factory.sexpr(arg)
|
data/lib/predicate/nodes/in.rb
CHANGED
@@ -6,32 +6,33 @@ class Predicate
|
|
6
6
|
80
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
9
|
+
def left
|
10
10
|
self[1]
|
11
11
|
end
|
12
|
+
alias :identifier :left
|
12
13
|
|
13
|
-
def
|
14
|
+
def right
|
14
15
|
self[2]
|
15
16
|
end
|
16
17
|
|
17
18
|
def &(other)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
19
|
+
# we only optimize with another In
|
20
|
+
return super unless other.is_a?(In)
|
21
|
+
|
22
|
+
# we only optimize is same free variables
|
23
|
+
fv = free_variables
|
24
|
+
return super unless fv.size == 1 && fv == other.free_variables
|
25
|
+
|
26
|
+
# we only optimize if both right terms are literals
|
27
|
+
return super unless right.literal? and other.right.literal?
|
28
|
+
|
29
|
+
intersection = right.value & other.right.value
|
30
|
+
if intersection.empty?
|
31
|
+
Factory.contradiction
|
32
|
+
elsif intersection.size == 1
|
33
|
+
Factory.eq(fv.first, [:literal, intersection.first])
|
33
34
|
else
|
34
|
-
|
35
|
+
Factory.in(fv.first, intersection)
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
@@ -40,11 +41,19 @@ class Predicate
|
|
40
41
|
end
|
41
42
|
|
42
43
|
def constant_variables
|
43
|
-
|
44
|
+
if right.literal? and right.value.size == 1
|
45
|
+
free_variables
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
44
49
|
end
|
45
50
|
|
46
51
|
def constants
|
47
|
-
|
52
|
+
if right.literal? and right.value.size == 1
|
53
|
+
{ identifier.name => right.value.first }
|
54
|
+
else
|
55
|
+
{}
|
56
|
+
end
|
48
57
|
end
|
49
58
|
|
50
59
|
def dyadic_priority
|
@@ -52,6 +61,7 @@ class Predicate
|
|
52
61
|
end
|
53
62
|
|
54
63
|
def evaluate(tuple)
|
64
|
+
values = right.evaluate(tuple)
|
55
65
|
values.include?(identifier.evaluate(tuple))
|
56
66
|
end
|
57
67
|
|
@@ -50,7 +50,7 @@ class Predicate
|
|
50
50
|
alias :on_gte :on_dyadic
|
51
51
|
|
52
52
|
def on_in(sexpr)
|
53
|
-
"#{apply(sexpr.identifier)} IN #{
|
53
|
+
"#{apply(sexpr.identifier)} IN #{apply(sexpr.right)}"
|
54
54
|
end
|
55
55
|
|
56
56
|
def on_intersect(sexpr)
|
@@ -61,6 +61,10 @@ class Predicate
|
|
61
61
|
to_literal(sexpr.last)
|
62
62
|
end
|
63
63
|
|
64
|
+
def on_opaque(sexpr)
|
65
|
+
"OPAQUE #{sexpr.last}"
|
66
|
+
end
|
67
|
+
|
64
68
|
def on_match(sexpr)
|
65
69
|
"#{apply(sexpr.left)} =~ #{apply(sexpr.right)}"
|
66
70
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
class Predicate
|
2
2
|
class ToSequel < Sexpr::Processor
|
3
|
+
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
3
6
|
module Methods
|
4
7
|
def on_identifier(sexpr)
|
5
8
|
::Sequel.identifier(sexpr.last)
|
@@ -43,9 +46,14 @@ class Predicate
|
|
43
46
|
alias :on_gte :on_dyadic_comp
|
44
47
|
|
45
48
|
def on_in(sexpr)
|
46
|
-
left, right = apply(sexpr.identifier), sexpr.
|
47
|
-
|
48
|
-
|
49
|
+
left, right = apply(sexpr.identifier), sexpr.right
|
50
|
+
if right.literal?
|
51
|
+
::Sequel.expr(left => right.value)
|
52
|
+
elsif right.opaque?
|
53
|
+
::Sequel.expr(left => apply(right))
|
54
|
+
else
|
55
|
+
raise Error, "Unable to compile `#{right}` to sequel"
|
56
|
+
end
|
49
57
|
end
|
50
58
|
|
51
59
|
def on_not(sexpr)
|
@@ -74,6 +82,11 @@ class Predicate
|
|
74
82
|
end
|
75
83
|
end
|
76
84
|
|
85
|
+
def on_opaque(sexpr)
|
86
|
+
return [sexpr.last] if sexpr.last.respond_to?(:sql_literal)
|
87
|
+
raise Error, "Unable to compile #{sexpr} to Sequel"
|
88
|
+
end
|
89
|
+
|
77
90
|
def on_unsupported(sexpr)
|
78
91
|
raise NotSupportedError
|
79
92
|
end
|
data/lib/predicate/version.rb
CHANGED
data/lib/predicate.rb
CHANGED
data/spec/factory/test_in.rb
CHANGED
@@ -2,11 +2,29 @@ require_relative "shared/a_comparison_factory_method"
|
|
2
2
|
class Predicate
|
3
3
|
describe Factory, 'in' do
|
4
4
|
|
5
|
-
|
5
|
+
context 'with values on the right' do
|
6
|
+
subject{ Factory.in(:x, [2, 3]) }
|
6
7
|
|
7
|
-
|
8
|
+
it 'is a In' do
|
9
|
+
expect(subject).to be_a(In)
|
10
|
+
end
|
8
11
|
|
9
|
-
|
12
|
+
it 'has the expected AST' do
|
13
|
+
expect(subject).to eql([:in, [:identifier, :x], [:literal, [2, 3]]])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with opaque on the right' do
|
18
|
+
subject{ Factory.in(:x, Factory.opaque([2, 3])) }
|
19
|
+
|
20
|
+
it 'is a In' do
|
21
|
+
expect(subject).to be_a(In)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'has the expected AST' do
|
25
|
+
expect(subject).to eql([:in, [:identifier, :x], [:opaque, [2, 3]]])
|
26
|
+
end
|
27
|
+
end
|
10
28
|
|
11
29
|
end
|
12
30
|
end
|
data/spec/nodes/eq/test_and.rb
CHANGED
@@ -14,5 +14,17 @@ class Predicate
|
|
14
14
|
it{ should be_a(Contradiction) }
|
15
15
|
end
|
16
16
|
|
17
|
+
context 'with an IN on same variable and literal' do
|
18
|
+
let(:right){ Factory.in(:x, [3,4]) }
|
19
|
+
|
20
|
+
it{ should be(left) }
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with an IN on same variable and opaque' do
|
24
|
+
let(:right){ Factory.in(:x, Factory.opaque([3,4])) }
|
25
|
+
|
26
|
+
it{ should be_a(And) }
|
27
|
+
end
|
28
|
+
|
17
29
|
end
|
18
30
|
end
|
@@ -22,6 +22,12 @@ class Predicate
|
|
22
22
|
it{ expect(subject).to eql([]) }
|
23
23
|
end
|
24
24
|
|
25
|
+
describe "on a in with an opaque right term" do
|
26
|
+
let(:p){ Predicate.in(:x, Predicate.opaque([2])) }
|
27
|
+
|
28
|
+
it{ expect(subject).to eql([]) }
|
29
|
+
end
|
30
|
+
|
25
31
|
describe "on a NOT" do
|
26
32
|
let(:p){ !Predicate.coerce(x: 2) }
|
27
33
|
|
@@ -93,7 +93,7 @@ class Predicate
|
|
93
93
|
end
|
94
94
|
}
|
95
95
|
}
|
96
|
-
let(:predicate) { Predicate.in(:price, operand) }
|
96
|
+
let(:predicate) { Predicate.in(:price, Predicate.opaque(operand)) }
|
97
97
|
|
98
98
|
it 'works as expected' do
|
99
99
|
expect(subject).to eql("SELECT * FROM `items` WHERE (`price` IN (Hello World))")
|
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.0
|
4
|
+
version: 2.1.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: 2018-
|
11
|
+
date: 2018-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sexpr
|
@@ -112,6 +112,7 @@ files:
|
|
112
112
|
- lib/predicate/nodes/native.rb
|
113
113
|
- lib/predicate/nodes/neq.rb
|
114
114
|
- lib/predicate/nodes/not.rb
|
115
|
+
- lib/predicate/nodes/opaque.rb
|
115
116
|
- lib/predicate/nodes/or.rb
|
116
117
|
- lib/predicate/nodes/qualified_identifier.rb
|
117
118
|
- lib/predicate/nodes/tautology.rb
|