predicate 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db81e00071c6190ac6692ccd9a3b5cd384ba7c0ee9a636d4b4b75c841a84f628
4
- data.tar.gz: dbcf66021eee203a6f943f2ca5ca02a101a9306aa8ee4b1ffaeb3131d0d0c524
3
+ metadata.gz: 901370b7a22028c4557c52fb7f5cd8d3ac92c97a440cf4d0f772bd592198c3a5
4
+ data.tar.gz: 7dc1e4912f365fc08657fc1b7289f1796989511c8b5e7c5e219bf8fe9eb16ccd
5
5
  SHA512:
6
- metadata.gz: cfef5e3953b9b9416be3a72305b0a33ebf063548735bd7a477f1f41aadac0936ef4cfc477092c5de4946e0df77b53706d306579f17aacfd91a7a6b97371f0ece
7
- data.tar.gz: 664b4ee4aba8a4e354b70021944fdf76796a8aee3558616d7f58472e0f11a607b4ae23694d15a44af203f56e960c06d22a04393db24eb5c92d25452be7b8585d
6
+ metadata.gz: 9515618b5f501b1774cae70cd88784d8bc965f2c331132c5c1f11363e3f690df9233f423195e3d0938fdf42cde4f90a68a69f1273e544014a0f43b386b322b8b
7
+ data.tar.gz: 4c4870c03e0aa43e7082136e68d6c17873febcb5608090713fc5ee0873f8f5e5c061a74eb5d57159bac3962f3ca9ce906e629e329bd654c4fd4be61cedad4ea8
@@ -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.value.empty?
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:
@@ -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
  {}
@@ -77,6 +77,10 @@ class Predicate
77
77
  Qualifier.new(qualifier).call(self)
78
78
  end
79
79
 
80
+ def bind(binding)
81
+ Binder.new(binding).call(self)
82
+ end
83
+
80
84
  def constant_variables
81
85
  []
82
86
  end
@@ -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.value.size == 1
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.value.size == 1
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
 
@@ -19,7 +19,7 @@ class Predicate
19
19
  end
20
20
 
21
21
  def constant_variables
22
- values.size == 1 ? free_variables : []
22
+ []
23
23
  end
24
24
 
25
25
  def evaluate(tuple)
@@ -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,4 @@
1
+ class Predicate
2
+ class Placeholder
3
+ end # class Placeholder
4
+ end # class Predicate
@@ -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
@@ -81,6 +81,7 @@ class Predicate
81
81
 
82
82
  def to_literal(x)
83
83
  case x
84
+ when Placeholder then "$#{x.object_id}"
84
85
  when Array then "{" << x.map{|y| to_literal(y) }.join(',') << "}"
85
86
  else x.inspect
86
87
  end
@@ -1,3 +1,4 @@
1
1
  require_relative "processors/to_s"
2
2
  require_relative "processors/renamer"
3
3
  require_relative "processors/qualifier"
4
+ require_relative "processors/binder"
@@ -13,7 +13,13 @@ class Predicate
13
13
  end
14
14
 
15
15
  def on_literal(sexpr)
16
- sexpr.last.nil? ? nil : ::Sequel.expr(sexpr.last)
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
- values = Array(right.value).uniq
52
- if values.include?(nil)
53
- nonnil = values.compact
54
- if nonnil.empty?
55
- ::Sequel.expr(left => nil)
56
- elsif nonnil.size == 1
57
- ::Sequel.expr(left => nil) | ::Sequel.expr(left => nonnil.first)
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 => nil) | ::Sequel.expr(left => nonnil)
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))
@@ -1,8 +1,8 @@
1
1
  class Predicate
2
2
  module Version
3
3
  MAJOR = 2
4
- MINOR = 2
5
- TINY = 1
4
+ MINOR = 3
5
+ TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
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.2.1
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-01-31 00:00:00.000000000 Z
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