predicate 1.3.4 → 2.0.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 +4 -0
- data/lib/predicate/grammar.rb +1 -0
- data/lib/predicate/grammar.sexp.yml +5 -0
- data/lib/predicate/nodes/expr.rb +0 -9
- data/lib/predicate/nodes/match.rb +54 -0
- data/lib/predicate/nodes/native.rb +0 -12
- data/lib/predicate/processors/to_s.rb +4 -0
- data/lib/predicate/processors.rb +0 -1
- data/lib/predicate/sequel/to_sequel.rb +66 -53
- data/lib/predicate/version.rb +3 -3
- data/lib/predicate.rb +0 -8
- data/spec/factory/test_match.rb +27 -0
- data/spec/grammar/test_match.rb +12 -0
- data/spec/nodes/and/test_and.rb +0 -8
- data/spec/predicate/test_and_split.rb +18 -0
- data/spec/predicate/test_attr_split.rb +12 -0
- data/spec/predicate/test_bool_and.rb +0 -8
- data/spec/predicate/test_bool_or.rb +0 -8
- data/spec/predicate/test_coerce.rb +0 -1
- data/spec/predicate/test_constants.rb +6 -0
- data/spec/predicate/test_evaluate.rb +118 -0
- data/spec/sequel/test_to_sequel.rb +33 -0
- data/spec/test_predicate.rb +0 -5
- metadata +3 -7
- data/lib/predicate/processors/to_ruby_code.rb +0 -76
- data/spec/expr/test_to_proc.rb +0 -16
- data/spec/expr/test_to_ruby_code.rb +0 -152
- data/spec/predicate/test_factory_methods.rb +0 -83
- data/spec/predicate/test_to_proc.rb +0 -14
- data/spec/predicate/test_to_ruby_code.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95da7e03712cd1ef82ff2668c4411005c006e8b4
|
4
|
+
data.tar.gz: 1e65d370752b431a2a6da2d8aee641a37529c4ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97451f6bf7310bc89fe7093c08a8ae33d2032d243b54f8db20457b12511d70645329581ffde5e440da076ee8dbc269ddea09e039ee616a364e55274eedb9e581
|
7
|
+
data.tar.gz: b2d8cbd6d1986176745fdda9db7c9e66621e48324f02163e3021c42bb122c95fe57e385c899c2513496a637763a97f7ed86afb3e374c6a71b0ad4773cade69fc
|
data/lib/predicate/factory.rb
CHANGED
@@ -77,6 +77,10 @@ class Predicate
|
|
77
77
|
_factor_predicate([:literal, literal])
|
78
78
|
end
|
79
79
|
|
80
|
+
def match(left, right, options = nil)
|
81
|
+
_factor_predicate([:match, sexpr(left), sexpr(right)] + (options.nil? ? [] : [options]))
|
82
|
+
end
|
83
|
+
|
80
84
|
def native(arg)
|
81
85
|
_factor_predicate([:native, arg])
|
82
86
|
end
|
data/lib/predicate/grammar.rb
CHANGED
@@ -14,6 +14,7 @@ rules:
|
|
14
14
|
- gte
|
15
15
|
- in
|
16
16
|
- intersect
|
17
|
+
- match
|
17
18
|
- native
|
18
19
|
tautology:
|
19
20
|
- [ true ]
|
@@ -45,6 +46,8 @@ rules:
|
|
45
46
|
- [ varref, values ]
|
46
47
|
intersect:
|
47
48
|
- [ varref, values ]
|
49
|
+
match:
|
50
|
+
- [ term, term, options ]
|
48
51
|
term:
|
49
52
|
- varref
|
50
53
|
- literal
|
@@ -57,5 +60,7 @@ rules:
|
|
57
60
|
- "::Object"
|
58
61
|
values:
|
59
62
|
- "::Object"
|
63
|
+
options:
|
64
|
+
- "::Hash"
|
60
65
|
name:
|
61
66
|
!ruby/regexp /^[a-zA-Z0-9_]+[?!]?$/
|
data/lib/predicate/nodes/expr.rb
CHANGED
@@ -81,20 +81,11 @@ class Predicate
|
|
81
81
|
{}
|
82
82
|
end
|
83
83
|
|
84
|
-
def to_ruby_code(scope = 't')
|
85
|
-
code = ToRubyCode.call(self, scope: scope)
|
86
|
-
"->(t){ #{code} }"
|
87
|
-
end
|
88
|
-
|
89
84
|
def to_s(scope = nil)
|
90
85
|
ToS.call(self, scope: scope)
|
91
86
|
end
|
92
87
|
alias :inspect :to_s
|
93
88
|
|
94
|
-
def to_proc(scope = 't')
|
95
|
-
Kernel.eval(to_ruby_code(scope))
|
96
|
-
end
|
97
|
-
|
98
89
|
def sexpr(arg)
|
99
90
|
Factory.sexpr(arg)
|
100
91
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Predicate
|
2
|
+
module Match
|
3
|
+
include Expr
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
case_sensitive: true
|
7
|
+
}
|
8
|
+
|
9
|
+
def priority
|
10
|
+
80
|
11
|
+
end
|
12
|
+
|
13
|
+
def left
|
14
|
+
self[1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def right
|
18
|
+
self[2]
|
19
|
+
end
|
20
|
+
|
21
|
+
def options
|
22
|
+
@options ||= DEFAULT_OPTIONS.merge(self[3] || {})
|
23
|
+
end
|
24
|
+
|
25
|
+
def case_sentitive?
|
26
|
+
options[:case_sensitive]
|
27
|
+
end
|
28
|
+
|
29
|
+
def free_variables
|
30
|
+
@free_variables ||= left.free_variables | right.free_variables
|
31
|
+
end
|
32
|
+
|
33
|
+
def dyadic_priority
|
34
|
+
800
|
35
|
+
end
|
36
|
+
|
37
|
+
def evaluate(tuple)
|
38
|
+
l = left.evaluate(tuple)
|
39
|
+
r = right.evaluate(tuple)
|
40
|
+
if l.nil? or r.nil?
|
41
|
+
nil
|
42
|
+
elsif l.is_a?(Regexp)
|
43
|
+
l =~ r.to_s
|
44
|
+
elsif r.is_a?(Regexp)
|
45
|
+
r =~ l.to_s
|
46
|
+
elsif options[:case_sensitive]
|
47
|
+
l.to_s.include?(r.to_s)
|
48
|
+
else
|
49
|
+
l.to_s.downcase.include?(r.to_s.downcase)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -24,18 +24,6 @@ class Predicate
|
|
24
24
|
{ nil => self }
|
25
25
|
end
|
26
26
|
|
27
|
-
def to_ruby_code(scope = 't')
|
28
|
-
if proc.respond_to?(:source_code)
|
29
|
-
code = proc.source_code
|
30
|
-
return code if code
|
31
|
-
end
|
32
|
-
raise NotSupportedError
|
33
|
-
end
|
34
|
-
|
35
|
-
def to_proc(scope = 't')
|
36
|
-
proc
|
37
|
-
end
|
38
|
-
|
39
27
|
def evaluate(tuple)
|
40
28
|
proc.call(tuple)
|
41
29
|
end
|
data/lib/predicate/processors.rb
CHANGED
@@ -1,72 +1,85 @@
|
|
1
1
|
class Predicate
|
2
2
|
class ToSequel < Sexpr::Processor
|
3
|
+
module Methods
|
4
|
+
def on_identifier(sexpr)
|
5
|
+
::Sequel.identifier(sexpr.last)
|
6
|
+
end
|
3
7
|
|
4
|
-
|
5
|
-
|
6
|
-
|
8
|
+
def on_qualified_identifier(sexpr)
|
9
|
+
::Sequel.identifier(sexpr.name).qualify(sexpr.qualifier)
|
10
|
+
end
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
12
|
+
def on_literal(sexpr)
|
13
|
+
sexpr.last.nil? ? nil : ::Sequel.expr(sexpr.last)
|
14
|
+
end
|
11
15
|
|
12
|
-
|
13
|
-
sexpr.last.nil? ? nil : ::Sequel.expr(sexpr.last)
|
14
|
-
end
|
16
|
+
###
|
15
17
|
|
16
|
-
|
18
|
+
def on_tautology(sexpr)
|
19
|
+
::Sequel::SQL::BooleanConstant.new(true)
|
20
|
+
end
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
|
22
|
+
def on_contradiction(sexpr)
|
23
|
+
::Sequel::SQL::BooleanConstant.new(false)
|
24
|
+
end
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
def on_eq(sexpr)
|
27
|
+
left, right = apply(sexpr.left), apply(sexpr.right)
|
28
|
+
::Sequel.expr(left => right)
|
29
|
+
end
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
31
|
+
def on_neq(sexpr)
|
32
|
+
left, right = apply(sexpr.left), apply(sexpr.right)
|
33
|
+
~::Sequel.expr(left => right)
|
34
|
+
end
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
36
|
+
def on_dyadic_comp(sexpr)
|
37
|
+
left, right = apply(sexpr.left), apply(sexpr.right)
|
38
|
+
left.send(sexpr.operator_symbol, right)
|
39
|
+
end
|
40
|
+
alias :on_lt :on_dyadic_comp
|
41
|
+
alias :on_lte :on_dyadic_comp
|
42
|
+
alias :on_gt :on_dyadic_comp
|
43
|
+
alias :on_gte :on_dyadic_comp
|
35
44
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
alias :on_lte :on_dyadic_comp
|
42
|
-
alias :on_gt :on_dyadic_comp
|
43
|
-
alias :on_gte :on_dyadic_comp
|
45
|
+
def on_in(sexpr)
|
46
|
+
left, right = apply(sexpr.identifier), sexpr.last
|
47
|
+
right = [right] if !right.is_a?(Array) && right.respond_to?(:sql_literal)
|
48
|
+
::Sequel.expr(left => right)
|
49
|
+
end
|
44
50
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
::Sequel.expr(left => right)
|
49
|
-
end
|
51
|
+
def on_not(sexpr)
|
52
|
+
~apply(sexpr.last)
|
53
|
+
end
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
def on_and(sexpr)
|
56
|
+
body = sexpr.sexpr_body
|
57
|
+
body[1..-1].inject(apply(body.first)){|f,t| f & apply(t) }
|
58
|
+
end
|
54
59
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
60
|
+
def on_or(sexpr)
|
61
|
+
body = sexpr.sexpr_body
|
62
|
+
body[1..-1].inject(apply(body.first)){|f,t| f | apply(t) }
|
63
|
+
end
|
59
64
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
65
|
+
def on_match(sexpr)
|
66
|
+
left, right = sexpr.left, sexpr.right
|
67
|
+
left = [ left.first, "%#{left.last}%" ] if left.first == :literal && !left.last.is_a?(Regexp)
|
68
|
+
right = [ right.first, "%#{right.last}%" ] if right.first == :literal && !right.last.is_a?(Regexp)
|
69
|
+
left, right = apply(left), apply(right)
|
70
|
+
if sexpr.case_sentitive?
|
71
|
+
left.like(right)
|
72
|
+
else
|
73
|
+
left.ilike(right)
|
74
|
+
end
|
75
|
+
end
|
64
76
|
|
65
|
-
|
66
|
-
|
77
|
+
def on_unsupported(sexpr)
|
78
|
+
raise NotSupportedError
|
79
|
+
end
|
80
|
+
alias :on_native :on_unsupported
|
81
|
+
alias :on_intersect :on_unsupported
|
67
82
|
end
|
68
|
-
|
69
|
-
alias :on_intersect :on_unsupported
|
70
|
-
|
83
|
+
include Methods
|
71
84
|
end # class ToSequel
|
72
85
|
end # class Predicate
|
data/lib/predicate/version.rb
CHANGED
data/lib/predicate.rb
CHANGED
@@ -122,16 +122,8 @@ class Predicate
|
|
122
122
|
expr.hash
|
123
123
|
end
|
124
124
|
|
125
|
-
def to_ruby_code(scope = "t")
|
126
|
-
expr.to_ruby_code(scope)
|
127
|
-
end
|
128
|
-
|
129
125
|
def to_s(scope = nil)
|
130
126
|
expr.to_s(scope)
|
131
127
|
end
|
132
128
|
|
133
|
-
def to_proc
|
134
|
-
@proc ||= expr.to_proc("t")
|
135
|
-
end
|
136
|
-
|
137
129
|
end # class Predicate
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'shared/a_predicate_ast_node'
|
2
|
+
class Predicate
|
3
|
+
describe Factory, 'match' do
|
4
|
+
include Factory
|
5
|
+
|
6
|
+
context 'without options' do
|
7
|
+
subject{ match(:name, "London") }
|
8
|
+
|
9
|
+
it_should_behave_like "a predicate AST node"
|
10
|
+
|
11
|
+
it{ should be_a(Match) }
|
12
|
+
|
13
|
+
it{ should eql([:match, [:identifier, :name], [:literal, "London"]]) }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'with options' do
|
17
|
+
subject{ match(:name, "London", case_sensitive: false) }
|
18
|
+
|
19
|
+
it_should_behave_like "a predicate AST node"
|
20
|
+
|
21
|
+
it{ should be_a(Match) }
|
22
|
+
|
23
|
+
it{ should eql([:match, [:identifier, :name], [:literal, "London"], {case_sensitive: false}]) }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/spec/grammar/test_match.rb
CHANGED
@@ -78,6 +78,18 @@ class Predicate
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
+
context "match" do
|
82
|
+
subject{ Grammar[:match] }
|
83
|
+
|
84
|
+
it 'matches valid ASTs' do
|
85
|
+
subject.should be_match([:match, [:identifier, :name], [:literal, "London"], {}])
|
86
|
+
subject.should be_match([:match, [:identifier, :name], [:literal, /London/], {}])
|
87
|
+
end
|
88
|
+
it 'does not match invalid ASTs' do
|
89
|
+
subject.should_not be_match([:native, 12])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
81
93
|
context "native" do
|
82
94
|
subject{ Grammar[:native] }
|
83
95
|
|
data/spec/nodes/and/test_and.rb
CHANGED
@@ -17,10 +17,6 @@ class Predicate
|
|
17
17
|
expect(subject).to be_a(And)
|
18
18
|
expect(subject.size).to eql(4)
|
19
19
|
end
|
20
|
-
|
21
|
-
it 'is as expected' do
|
22
|
-
expect(subject.to_ruby_code).to eql("->(t){ (t[:x] == 2) && (t[:y] == 3) && (t[:z] == 4) }")
|
23
|
-
end
|
24
20
|
end
|
25
21
|
|
26
22
|
context 'with another and' do
|
@@ -32,10 +28,6 @@ class Predicate
|
|
32
28
|
expect(subject).to be_a(And)
|
33
29
|
expect(subject.size).to eql(5)
|
34
30
|
end
|
35
|
-
|
36
|
-
it 'is as expected' do
|
37
|
-
expect(subject.to_ruby_code).to eql("->(t){ (t[:x] == 2) && (t[:y] == 3) && (t[:w] == 5) && (t[:z] == 4) }")
|
38
|
-
end
|
39
31
|
end
|
40
32
|
|
41
33
|
end
|
@@ -53,5 +53,23 @@ class Predicate
|
|
53
53
|
it{ should eq([ p.tautology, pred ]) }
|
54
54
|
end
|
55
55
|
|
56
|
+
context "on match (included)" do
|
57
|
+
let(:pred){ p.match(:x, "London") }
|
58
|
+
|
59
|
+
it{ should eq([ pred, p.tautology ]) }
|
60
|
+
end
|
61
|
+
|
62
|
+
context "on match (excluded)" do
|
63
|
+
let(:pred){ p.match(:y, "London") }
|
64
|
+
|
65
|
+
it{ should eq([ p.tautology, pred ]) }
|
66
|
+
end
|
67
|
+
|
68
|
+
context "on match (included on right)" do
|
69
|
+
let(:pred){ p.match(:y, :x) }
|
70
|
+
|
71
|
+
it{ should eq([ pred, p.tautology ]) }
|
72
|
+
end
|
73
|
+
|
56
74
|
end
|
57
75
|
end
|
@@ -35,5 +35,17 @@ class Predicate
|
|
35
35
|
it{ should eq({ x: pred }) }
|
36
36
|
end
|
37
37
|
|
38
|
+
context "on match" do
|
39
|
+
let(:pred){ p.match(:x, "London") }
|
40
|
+
|
41
|
+
it{ should eq({ x: pred }) }
|
42
|
+
end
|
43
|
+
|
44
|
+
context "on match with two identifiers" do
|
45
|
+
let(:pred){ p.match(:x, :y) }
|
46
|
+
|
47
|
+
it{ should eq({ nil => pred }) }
|
48
|
+
end
|
49
|
+
|
38
50
|
end
|
39
51
|
end
|
@@ -22,14 +22,6 @@ class Predicate
|
|
22
22
|
it{ should be(left) }
|
23
23
|
end
|
24
24
|
|
25
|
-
context 'with another predicate' do
|
26
|
-
let(:right){ Predicate.coerce(y: 3) }
|
27
|
-
|
28
|
-
it 'should be a expected' do
|
29
|
-
subject.to_ruby_code.should eq("->(t){ (t[:x] == 2) && (t[:y] == 3) }")
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
25
|
context 'with tautology' do
|
34
26
|
let(:right){ Predicate.tautology }
|
35
27
|
|
@@ -22,13 +22,5 @@ class Predicate
|
|
22
22
|
it{ should be(left) }
|
23
23
|
end
|
24
24
|
|
25
|
-
context 'with another predicate' do
|
26
|
-
let(:right){ Predicate.coerce(y: 3) }
|
27
|
-
|
28
|
-
it 'should be a expected' do
|
29
|
-
subject.to_ruby_code.should eq("->(t){ (t[:x] == 2) || (t[:y] == 3) }")
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
25
|
end
|
34
26
|
end
|
@@ -98,5 +98,123 @@ class Predicate
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
+
context 'on a match against a string' do
|
102
|
+
let(:predicate){
|
103
|
+
Predicate.match(:x, "12")
|
104
|
+
}
|
105
|
+
|
106
|
+
it "matches when expected" do
|
107
|
+
[
|
108
|
+
{ x: 12 },
|
109
|
+
{ x: "12" },
|
110
|
+
{ x: "Once upon a time, in 12" },
|
111
|
+
{ x: "Once upon a time, in 12!" },
|
112
|
+
].each do |tuple|
|
113
|
+
expect(predicate.evaluate(tuple)).to be_truthy
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it "does not match when not expected" do
|
118
|
+
[
|
119
|
+
{ x: 17 },
|
120
|
+
{ x: nil },
|
121
|
+
{ x: "Londan" },
|
122
|
+
{ x: "london" },
|
123
|
+
{ x: "Once upon a time, in Londan" },
|
124
|
+
{ x: "Once upon a time, in Londan!" },
|
125
|
+
{ },
|
126
|
+
{ y: "London" }
|
127
|
+
].each do |tuple|
|
128
|
+
expect(predicate.evaluate(tuple)).to be_falsy
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'on a match against a string, case insensitive' do
|
134
|
+
let(:predicate){
|
135
|
+
Predicate.match(:x, "London", case_sensitive: false)
|
136
|
+
}
|
137
|
+
|
138
|
+
it "matches when expected" do
|
139
|
+
[
|
140
|
+
{ x: "London" },
|
141
|
+
{ x: "london" },
|
142
|
+
{ x: "Once upon a time, in London" },
|
143
|
+
{ x: "Once upon a time, in London!" },
|
144
|
+
{ x: "Once upon a time, in london!" },
|
145
|
+
].each do |tuple|
|
146
|
+
expect(predicate.evaluate(tuple)).to be_truthy
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it "does not match when not expected" do
|
151
|
+
[
|
152
|
+
{ x: "Londan" },
|
153
|
+
{ x: "Once upon a time, in Londan" },
|
154
|
+
{ x: "Once upon a time, in Londan!" },
|
155
|
+
{ },
|
156
|
+
{ y: "London" }
|
157
|
+
].each do |tuple|
|
158
|
+
expect(predicate.evaluate(tuple)).to be_falsy
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'on a match against a regexp' do
|
164
|
+
let(:predicate){
|
165
|
+
Predicate.match(:x, /London/i)
|
166
|
+
}
|
167
|
+
|
168
|
+
it "matches when expected" do
|
169
|
+
[
|
170
|
+
{ x: "London" },
|
171
|
+
{ x: "london" },
|
172
|
+
{ x: "Once upon a time, in London" },
|
173
|
+
{ x: "Once upon a time, in London!" },
|
174
|
+
{ x: "Once upon a time, in london!" },
|
175
|
+
].each do |tuple|
|
176
|
+
expect(predicate.evaluate(tuple)).to be_truthy
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
it "does not match when not expected" do
|
181
|
+
[
|
182
|
+
{ x: 17 },
|
183
|
+
{ x: "Londan" },
|
184
|
+
{ x: "Once upon a time, in Londan" },
|
185
|
+
{ x: "Once upon a time, in Londan!" },
|
186
|
+
{ },
|
187
|
+
{ y: "London" }
|
188
|
+
].each do |tuple|
|
189
|
+
expect(predicate.evaluate(tuple)).to be_falsy
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'on a match against a another attribute' do
|
195
|
+
let(:predicate){
|
196
|
+
Predicate.match(:x, :y)
|
197
|
+
}
|
198
|
+
|
199
|
+
it "matches when expected" do
|
200
|
+
[
|
201
|
+
{ x: "London", y: "London" },
|
202
|
+
{ x: "Once upon a time in London", y: "London" },
|
203
|
+
].each do |tuple|
|
204
|
+
expect(predicate.evaluate(tuple)).to be_truthy
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it "does not match when not expected" do
|
209
|
+
[
|
210
|
+
{ x: 17 },
|
211
|
+
{ x: "Londan", y: "London" },
|
212
|
+
{ x: "London", y: "Once upon a time in London" },
|
213
|
+
].each do |tuple|
|
214
|
+
expect(predicate.evaluate(tuple)).to be_falsy
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
101
219
|
end
|
102
220
|
end
|
@@ -140,6 +140,39 @@ class Predicate
|
|
140
140
|
end
|
141
141
|
end
|
142
142
|
|
143
|
+
context 'match' do
|
144
|
+
let(:predicate) { Predicate.match(left, right, options) }
|
145
|
+
let(:options){ nil }
|
146
|
+
|
147
|
+
context '(attr, String)' do
|
148
|
+
let(:left){ :x }
|
149
|
+
let(:right){ "London" }
|
150
|
+
|
151
|
+
it 'works as expected' do
|
152
|
+
expect(subject).to eql("SELECT * FROM `items` WHERE (`x` LIKE '%London%' ESCAPE '\\')")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context '(attr, String, case_sensitive: false)' do
|
157
|
+
let(:left){ :x }
|
158
|
+
let(:right){ "London" }
|
159
|
+
let(:options){ { case_sensitive: false } }
|
160
|
+
|
161
|
+
it 'works as expected' do
|
162
|
+
expect(subject).to eql("SELECT * FROM `items` WHERE (UPPER(`x`) LIKE UPPER('%London%') ESCAPE '\\')")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context '(attr, Regexp)' do
|
167
|
+
let(:left){ :x }
|
168
|
+
let(:right){ /London/i }
|
169
|
+
|
170
|
+
pending 'works as expected' do
|
171
|
+
expect(subject).to eql("SELECT * FROM `items` WHERE (`x` LIKE '%London%' ESCAPE '\\')")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
143
176
|
context 'native' do
|
144
177
|
let(:predicate) { Predicate.native(->(t){ false }) }
|
145
178
|
|
data/spec/test_predicate.rb
CHANGED
@@ -6,11 +6,6 @@ shared_examples_for "a predicate" do
|
|
6
6
|
let(:x){ 12 }
|
7
7
|
let(:y){ 13 }
|
8
8
|
|
9
|
-
it 'provides a proc for easy evaluation' do
|
10
|
-
got = subject.to_proc.call(w: w, x: x, y: y)
|
11
|
-
[ TrueClass, FalseClass ].should include(got.class)
|
12
|
-
end
|
13
|
-
|
14
9
|
it 'can be negated easily' do
|
15
10
|
(!subject).should be_a(Predicate)
|
16
11
|
end
|
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:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- lib/predicate/nodes/literal.rb
|
108
108
|
- lib/predicate/nodes/lt.rb
|
109
109
|
- lib/predicate/nodes/lte.rb
|
110
|
+
- lib/predicate/nodes/match.rb
|
110
111
|
- lib/predicate/nodes/nadic_bool.rb
|
111
112
|
- lib/predicate/nodes/native.rb
|
112
113
|
- lib/predicate/nodes/neq.rb
|
@@ -117,13 +118,10 @@ files:
|
|
117
118
|
- lib/predicate/processors.rb
|
118
119
|
- lib/predicate/processors/qualifier.rb
|
119
120
|
- lib/predicate/processors/renamer.rb
|
120
|
-
- lib/predicate/processors/to_ruby_code.rb
|
121
121
|
- lib/predicate/processors/to_s.rb
|
122
122
|
- lib/predicate/sequel.rb
|
123
123
|
- lib/predicate/sequel/to_sequel.rb
|
124
124
|
- lib/predicate/version.rb
|
125
|
-
- spec/expr/test_to_proc.rb
|
126
|
-
- spec/expr/test_to_ruby_code.rb
|
127
125
|
- spec/factory/shared/a_comparison_factory_method.rb
|
128
126
|
- spec/factory/shared/a_predicate_ast_node.rb
|
129
127
|
- spec/factory/test_and.rb
|
@@ -141,6 +139,7 @@ files:
|
|
141
139
|
- spec/factory/test_literal.rb
|
142
140
|
- spec/factory/test_lt.rb
|
143
141
|
- spec/factory/test_lte.rb
|
142
|
+
- spec/factory/test_match.rb
|
144
143
|
- spec/factory/test_native.rb
|
145
144
|
- spec/factory/test_neq.rb
|
146
145
|
- spec/factory/test_not.rb
|
@@ -174,14 +173,11 @@ files:
|
|
174
173
|
- spec/predicate/test_constants.rb
|
175
174
|
- spec/predicate/test_contradiction.rb
|
176
175
|
- spec/predicate/test_evaluate.rb
|
177
|
-
- spec/predicate/test_factory_methods.rb
|
178
176
|
- spec/predicate/test_free_variables.rb
|
179
177
|
- spec/predicate/test_hash_and_equal.rb
|
180
178
|
- spec/predicate/test_qualify.rb
|
181
179
|
- spec/predicate/test_rename.rb
|
182
180
|
- spec/predicate/test_tautology.rb
|
183
|
-
- spec/predicate/test_to_proc.rb
|
184
|
-
- spec/predicate/test_to_ruby_code.rb
|
185
181
|
- spec/sequel/test_to_sequel.rb
|
186
182
|
- spec/spec_helper.rb
|
187
183
|
- spec/test_predicate.rb
|
@@ -1,76 +0,0 @@
|
|
1
|
-
class Predicate
|
2
|
-
class ToRubyCode < Sexpr::Processor
|
3
|
-
|
4
|
-
def on_tautology(sexpr)
|
5
|
-
"true"
|
6
|
-
end
|
7
|
-
|
8
|
-
def on_contradiction(sexpr)
|
9
|
-
"false"
|
10
|
-
end
|
11
|
-
|
12
|
-
def on_qualified_identifier(sexpr)
|
13
|
-
"#{sexpr.qualifier}[:#{sexpr.name}]"
|
14
|
-
end
|
15
|
-
|
16
|
-
def on_identifier(sexpr)
|
17
|
-
if s = options[:scope]
|
18
|
-
"#{s}[:#{sexpr.last.to_s}]"
|
19
|
-
else
|
20
|
-
sexpr.last.to_s
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def on_not(sexpr)
|
25
|
-
"#{sexpr.operator_symbol}" << apply(sexpr.last, sexpr)
|
26
|
-
end
|
27
|
-
|
28
|
-
def on_nadic_bool(sexpr)
|
29
|
-
sexpr.sexpr_body.map{|term|
|
30
|
-
apply(term, sexpr)
|
31
|
-
}.join(" #{sexpr.operator_symbol} ")
|
32
|
-
end
|
33
|
-
alias :on_and :on_nadic_bool
|
34
|
-
alias :on_or :on_nadic_bool
|
35
|
-
|
36
|
-
def on_dyadic(sexpr)
|
37
|
-
sexpr.sexpr_body.map{|term|
|
38
|
-
apply(term, sexpr)
|
39
|
-
}.join(" #{sexpr.operator_symbol} ")
|
40
|
-
end
|
41
|
-
alias :on_eq :on_dyadic
|
42
|
-
alias :on_neq :on_dyadic
|
43
|
-
alias :on_lt :on_dyadic
|
44
|
-
alias :on_lte :on_dyadic
|
45
|
-
alias :on_gt :on_dyadic
|
46
|
-
alias :on_gte :on_dyadic
|
47
|
-
|
48
|
-
def on_in(sexpr)
|
49
|
-
"#{to_ruby_literal(sexpr.values)}.include?(#{apply(sexpr.identifier)})"
|
50
|
-
end
|
51
|
-
|
52
|
-
def on_intersect(sexpr)
|
53
|
-
t_x = apply(sexpr.identifier)
|
54
|
-
"!#{t_x}.nil? && !(#{t_x} & #{to_ruby_literal(sexpr.values)}).empty?"
|
55
|
-
end
|
56
|
-
|
57
|
-
def on_literal(sexpr)
|
58
|
-
to_ruby_literal(sexpr.last)
|
59
|
-
end
|
60
|
-
|
61
|
-
protected
|
62
|
-
|
63
|
-
def to_ruby_literal(x)
|
64
|
-
x.inspect
|
65
|
-
end
|
66
|
-
|
67
|
-
def apply(sexpr, parent = nil)
|
68
|
-
code = super(sexpr)
|
69
|
-
if parent && (parent.priority >= sexpr.priority)
|
70
|
-
code = "(" << code << ")"
|
71
|
-
end
|
72
|
-
code
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
data/spec/expr/test_to_proc.rb
DELETED
@@ -1,152 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
class Predicate
|
3
|
-
describe Expr, "to_ruby_code" do
|
4
|
-
|
5
|
-
let(:f){ Factory }
|
6
|
-
|
7
|
-
subject{ expr.to_ruby_code }
|
8
|
-
|
9
|
-
after do
|
10
|
-
unless expr.is_a?(Native)
|
11
|
-
lambda{
|
12
|
-
eval(subject)
|
13
|
-
}.should_not raise_error
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context 'tautology' do
|
18
|
-
let(:expr){ f.tautology }
|
19
|
-
|
20
|
-
it{ should eq("->(t){ true }") }
|
21
|
-
end
|
22
|
-
|
23
|
-
context 'contradiction' do
|
24
|
-
let(:expr){ f.contradiction }
|
25
|
-
|
26
|
-
it{ should eq("->(t){ false }") }
|
27
|
-
end
|
28
|
-
|
29
|
-
describe "identifier" do
|
30
|
-
let(:expr){ f.identifier(:name) }
|
31
|
-
|
32
|
-
it{ should eq("->(t){ t[:name] }") }
|
33
|
-
end
|
34
|
-
|
35
|
-
describe "and" do
|
36
|
-
let(:expr){ f.and(f.contradiction, f.contradiction) }
|
37
|
-
|
38
|
-
it{ should eq("->(t){ false && false }") }
|
39
|
-
end
|
40
|
-
|
41
|
-
describe "or" do
|
42
|
-
let(:expr){ f.or(f.contradiction, f.contradiction) }
|
43
|
-
|
44
|
-
it{ should eq("->(t){ false || false }") }
|
45
|
-
end
|
46
|
-
|
47
|
-
describe "not" do
|
48
|
-
let(:expr){ f.not(f.contradiction) }
|
49
|
-
|
50
|
-
it{ should eq("->(t){ !false }") }
|
51
|
-
end
|
52
|
-
|
53
|
-
describe "comp" do
|
54
|
-
let(:expr){ f.comp(:lt, {:x => 2}) }
|
55
|
-
|
56
|
-
it{ should eq("->(t){ t[:x] < 2 }") }
|
57
|
-
end
|
58
|
-
|
59
|
-
describe "eq" do
|
60
|
-
let(:expr){ f.eq(:x, 2) }
|
61
|
-
|
62
|
-
it{ should eq("->(t){ t[:x] == 2 }") }
|
63
|
-
end
|
64
|
-
|
65
|
-
describe "eq (parentheses required)" do
|
66
|
-
let(:expr){ f.eq(f.eq(:y, 2), true) }
|
67
|
-
|
68
|
-
it{ should eq("->(t){ (t[:y] == 2) == true }") }
|
69
|
-
end
|
70
|
-
|
71
|
-
describe "comp (multiple)" do
|
72
|
-
let(:expr){ f.comp(:eq, :x => 2, :y => 3) }
|
73
|
-
|
74
|
-
it{ should eq("->(t){ (t[:x] == 2) && (t[:y] == 3) }") }
|
75
|
-
end
|
76
|
-
|
77
|
-
describe "in" do
|
78
|
-
let(:expr){ f.in(:x, [2, 3]) }
|
79
|
-
|
80
|
-
it{ should eq("->(t){ [2, 3].include?(t[:x]) }") }
|
81
|
-
end
|
82
|
-
|
83
|
-
describe "neq" do
|
84
|
-
let(:expr){ f.neq(:x, 2) }
|
85
|
-
|
86
|
-
it{ should eq("->(t){ t[:x] != 2 }") }
|
87
|
-
end
|
88
|
-
|
89
|
-
describe "gt" do
|
90
|
-
let(:expr){ f.gt(:x, 2) }
|
91
|
-
|
92
|
-
it{ should eq("->(t){ t[:x] > 2 }") }
|
93
|
-
end
|
94
|
-
|
95
|
-
describe "gte" do
|
96
|
-
let(:expr){ f.gte(:x, 2) }
|
97
|
-
|
98
|
-
it{ should eq("->(t){ t[:x] >= 2 }") }
|
99
|
-
end
|
100
|
-
|
101
|
-
describe "lt" do
|
102
|
-
let(:expr){ f.lt(:x, 2) }
|
103
|
-
|
104
|
-
it{ should eq("->(t){ t[:x] < 2 }") }
|
105
|
-
end
|
106
|
-
|
107
|
-
describe "lte" do
|
108
|
-
let(:expr){ f.lte(:x, 2) }
|
109
|
-
|
110
|
-
it{ should eq("->(t){ t[:x] <= 2 }") }
|
111
|
-
end
|
112
|
-
|
113
|
-
describe "literal" do
|
114
|
-
let(:expr){ f.literal(12) }
|
115
|
-
|
116
|
-
it{ should eq("->(t){ 12 }") }
|
117
|
-
end
|
118
|
-
|
119
|
-
describe "native" do
|
120
|
-
let(:expr){ f.native(lambda{}) }
|
121
|
-
|
122
|
-
specify{
|
123
|
-
lambda{ subject }.should raise_error(NotSupportedError)
|
124
|
-
}
|
125
|
-
end
|
126
|
-
|
127
|
-
describe "conjunction of two eqs" do
|
128
|
-
let(:expr){
|
129
|
-
f.and(f.eq(:x, 2), f.eq(:y, 3))
|
130
|
-
}
|
131
|
-
|
132
|
-
it{ should eq("->(t){ (t[:x] == 2) && (t[:y] == 3) }") }
|
133
|
-
end
|
134
|
-
|
135
|
-
describe "conjunction of two comps" do
|
136
|
-
let(:expr){
|
137
|
-
f.and(f.comp(:eq, :x => 2), f.comp(:eq, :y => 3))
|
138
|
-
}
|
139
|
-
|
140
|
-
it{ should eq("->(t){ (t[:x] == 2) && (t[:y] == 3) }") }
|
141
|
-
end
|
142
|
-
|
143
|
-
describe "or and and" do
|
144
|
-
let(:expr){
|
145
|
-
f.and(f.eq(:x, 2), f.or(true, false))
|
146
|
-
}
|
147
|
-
|
148
|
-
it{ should eq("->(t){ (t[:x] == 2) && (true || false) }") }
|
149
|
-
end
|
150
|
-
|
151
|
-
end
|
152
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
class Predicate
|
3
|
-
describe Predicate, "factory methods" do
|
4
|
-
|
5
|
-
before do
|
6
|
-
subject.should be_a(Predicate)
|
7
|
-
subject.expr.should be_a(Expr)
|
8
|
-
end
|
9
|
-
|
10
|
-
context "tautology" do
|
11
|
-
subject{ Predicate.tautology }
|
12
|
-
|
13
|
-
specify{ subject.to_ruby_code.should eq("->(t){ true }") }
|
14
|
-
end
|
15
|
-
|
16
|
-
context "contradiction" do
|
17
|
-
subject{ Predicate.contradiction }
|
18
|
-
|
19
|
-
specify{ subject.to_ruby_code.should eq("->(t){ false }") }
|
20
|
-
end
|
21
|
-
|
22
|
-
context "identifier" do
|
23
|
-
subject{ Predicate.identifier(:x) }
|
24
|
-
|
25
|
-
specify{ subject.to_ruby_code.should eq("->(t){ t[:x] }") }
|
26
|
-
end
|
27
|
-
|
28
|
-
context "eq" do
|
29
|
-
subject{ Predicate.eq(:x, 2) }
|
30
|
-
|
31
|
-
specify{ subject.to_ruby_code.should eq("->(t){ t[:x] == 2 }") }
|
32
|
-
end
|
33
|
-
|
34
|
-
context "neq" do
|
35
|
-
subject{ Predicate.neq(:x, 2) }
|
36
|
-
|
37
|
-
specify{ subject.to_ruby_code.should eq("->(t){ t[:x] != 2 }") }
|
38
|
-
end
|
39
|
-
|
40
|
-
context "lt" do
|
41
|
-
subject{ Predicate.lt(:x, 2) }
|
42
|
-
|
43
|
-
specify{ subject.to_ruby_code.should eq("->(t){ t[:x] < 2 }") }
|
44
|
-
end
|
45
|
-
|
46
|
-
context "lte" do
|
47
|
-
subject{ Predicate.lte(:x, 2) }
|
48
|
-
|
49
|
-
specify{ subject.to_ruby_code.should eq("->(t){ t[:x] <= 2 }") }
|
50
|
-
end
|
51
|
-
|
52
|
-
context "gt" do
|
53
|
-
subject{ Predicate.gt(:x, 2) }
|
54
|
-
|
55
|
-
specify{ subject.to_ruby_code.should eq("->(t){ t[:x] > 2 }") }
|
56
|
-
end
|
57
|
-
|
58
|
-
context "gte" do
|
59
|
-
subject{ Predicate.gte(:x, 2) }
|
60
|
-
|
61
|
-
specify{ subject.to_ruby_code.should eq("->(t){ t[:x] >= 2 }") }
|
62
|
-
end
|
63
|
-
|
64
|
-
context "literal" do
|
65
|
-
subject{ Predicate.literal(2) }
|
66
|
-
|
67
|
-
specify{ subject.to_ruby_code.should eq("->(t){ 2 }") }
|
68
|
-
end
|
69
|
-
|
70
|
-
context "native" do
|
71
|
-
subject{ Predicate.native(lambda{}) }
|
72
|
-
|
73
|
-
specify{ subject.expr.should be_a(Native) }
|
74
|
-
end
|
75
|
-
|
76
|
-
context 'intersect' do
|
77
|
-
subject{ Predicate.intersect(:x, [7,8]) }
|
78
|
-
|
79
|
-
specify{ subject.to_ruby_code.should eq("->(t){ !t[:x].nil? && !(t[:x] & [7, 8]).empty? }") }
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
class Predicate
|
3
|
-
describe Predicate, "to_ruby_code" do
|
4
|
-
|
5
|
-
subject{ p.to_ruby_code }
|
6
|
-
|
7
|
-
describe "on a comp(:eq)" do
|
8
|
-
let(:p){ Predicate.coerce(:x => 2) }
|
9
|
-
|
10
|
-
it{ should eq("->(t){ t[:x] == 2 }") }
|
11
|
-
end
|
12
|
-
|
13
|
-
describe "with qualified identifiers" do
|
14
|
-
let(:p){ Predicate.eq(Factory.qualified_identifier(:t, :y), 2) }
|
15
|
-
|
16
|
-
it{ should eq("->(t){ t[:y] == 2 }") }
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
end
|