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 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