predicate 2.0.1 → 2.1.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 +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
|