predicate 1.1.3 → 1.2.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
  SHA1:
3
- metadata.gz: 301830894045bb25bd0641f77273a4924e8ff743
4
- data.tar.gz: b04f894cb93548246b50085170cc83e3d1476acc
3
+ metadata.gz: 710a45c31d1350beeded48ea522d3e868925e600
4
+ data.tar.gz: 8e9acddd67b337218c749aa6744940b26ca8568e
5
5
  SHA512:
6
- metadata.gz: 3e1968b96700a0cb15da2d143fa3d0776d26ee54855232dc0e30e2854748ffd5b12c3767cb8121d95aa006d6c6a6695bf091ea262d4a8de5e1c5c099e73d730c
7
- data.tar.gz: 3f0c0192c97d90654b36de5dbaf4a94c18a86a1787a13d27ec9d127902b8c48cd5969bca338695024b33dbd62f8dcfcfc1513cb44bcc9adcfbd2d39ef74bd1a0
6
+ metadata.gz: e3d92a4dfa7227cbac8dc6b9dc9e4856861983103e2840ad041721269a66ecce74980750293178b3c3cdc9f34c06aa2d5a396880634f47e84a0b03aaac3a202d
7
+ data.tar.gz: ff1eee7dcc029585926d897eb5f3da17b68dd6f392f6e2c73bb50208ae00d22aedf0422feccea4cf747d869555b37dfa720407aa73746dd74935cfd52384f8de
@@ -35,6 +35,11 @@ class Predicate
35
35
  end
36
36
  alias :among :in
37
37
 
38
+ def intersect(identifier, values)
39
+ identifier = sexpr(identifier) if identifier.is_a?(Symbol)
40
+ _factor_predicate([:intersect, identifier, values])
41
+ end
42
+
38
43
  def comp(op, h)
39
44
  from_hash(h, op)
40
45
  end
@@ -29,5 +29,6 @@ require_relative 'nodes/gte'
29
29
  require_relative 'nodes/lt'
30
30
  require_relative 'nodes/lte'
31
31
  require_relative 'nodes/in'
32
+ require_relative 'nodes/intersect'
32
33
  require_relative 'nodes/literal'
33
34
  require_relative 'nodes/native'
@@ -13,6 +13,7 @@ rules:
13
13
  - gt
14
14
  - gte
15
15
  - in
16
+ - intersect
16
17
  - native
17
18
  tautology:
18
19
  - [ true ]
@@ -42,6 +43,8 @@ rules:
42
43
  - [ term, term ]
43
44
  in:
44
45
  - [ varref, values ]
46
+ intersect:
47
+ - [ varref, values ]
45
48
  term:
46
49
  - varref
47
50
  - literal
@@ -18,6 +18,16 @@ class Predicate
18
18
  end
19
19
  end
20
20
 
21
+ def attr_split
22
+ # Similar to and_split above, but the reasonning applies on
23
+ # attribute names this time.
24
+ sexpr_body.each_with_object({}) do |term, h|
25
+ term.attr_split.each_pair do |a,p|
26
+ h[a] = (h[a] || tautology) & p
27
+ end
28
+ end
29
+ end
30
+
21
31
  def constant_variables
22
32
  sexpr_body.inject([]) do |cvars,expr|
23
33
  cvars | expr.constant_variables
@@ -26,5 +26,9 @@ class Predicate
26
26
  @free_variables ||= []
27
27
  end
28
28
 
29
+ def attr_split
30
+ { nil => self }
31
+ end
32
+
29
33
  end
30
34
  end
@@ -40,6 +40,18 @@ class Predicate
40
40
  (free_variables & attr_list).empty? ? [ tautology, self ] : [ self, tautology ]
41
41
  end
42
42
 
43
+ def attr_split
44
+ # if I have only one variable reference, then I can return
45
+ # myself mapped to that variable...
46
+ if (vars = free_variables).size == 1
47
+ { vars.first => self }
48
+ else
49
+ # I must still map myself to nil to meet the conjunction
50
+ # specification
51
+ { nil => self }
52
+ end
53
+ end
54
+
43
55
  def rename(renaming)
44
56
  Renamer.call(self, :renaming => renaming)
45
57
  end
@@ -0,0 +1,26 @@
1
+ class Predicate
2
+ module Intersect
3
+ include Expr
4
+
5
+ def priority
6
+ 80
7
+ end
8
+
9
+ def identifier
10
+ self[1]
11
+ end
12
+
13
+ def values
14
+ self[2]
15
+ end
16
+
17
+ def free_variables
18
+ @free_variables ||= identifier.free_variables
19
+ end
20
+
21
+ def constant_variables
22
+ values.size == 1 ? free_variables : []
23
+ end
24
+
25
+ end
26
+ end
@@ -10,12 +10,20 @@ class Predicate
10
10
  self[1]
11
11
  end
12
12
 
13
+ # overriden because parent relies on free_variables,
14
+ # which raises an exception
13
15
  def and_split(attr_list)
14
16
  # I possibly make references to those attributes, so
15
17
  # I can't be P2
16
18
  [ self, tautology ]
17
19
  end
18
20
 
21
+ # overriden because parent relies on free_variables,
22
+ # which raises an exception
23
+ def attr_split
24
+ { nil => self }
25
+ end
26
+
19
27
  def to_ruby_code(scope = 't')
20
28
  if proc.respond_to?(:source_code)
21
29
  code = proc.source_code
@@ -26,5 +26,9 @@ class Predicate
26
26
  @free_variables ||= []
27
27
  end
28
28
 
29
+ def attr_split
30
+ {}
31
+ end
32
+
29
33
  end
30
34
  end
@@ -9,8 +9,13 @@ class Predicate
9
9
  attr_reader :qualifier
10
10
 
11
11
  def on_identifier(sexpr)
12
- return sexpr unless q = qualifier[sexpr.name]
13
- [:qualified_identifier, q, sexpr.name]
12
+ case qualifier
13
+ when Symbol
14
+ [:qualified_identifier, qualifier, sexpr.name]
15
+ else
16
+ return sexpr unless q = qualifier[sexpr.name]
17
+ [:qualified_identifier, q, sexpr.name]
18
+ end
14
19
  end
15
20
 
16
21
  def on_native(sexpr)
@@ -49,6 +49,10 @@ class Predicate
49
49
  "#{to_ruby_literal(sexpr.values)}.include?(#{apply(sexpr.identifier)})"
50
50
  end
51
51
 
52
+ def on_intersect(sexpr)
53
+ "!(#{apply(sexpr.identifier)} & #{to_ruby_literal(sexpr.values)}).empty?"
54
+ end
55
+
52
56
  def on_literal(sexpr)
53
57
  to_ruby_literal(sexpr.last)
54
58
  end
@@ -6,7 +6,7 @@ class Predicate
6
6
  end
7
7
 
8
8
  def on_qualified_identifier(sexpr)
9
- ::Sequel.as(sexpr.qualifier, sexpr.name)
9
+ ::Sequel.identifier(sexpr.name).qualify(sexpr.qualifier)
10
10
  end
11
11
 
12
12
  def on_literal(sexpr)
@@ -44,6 +44,7 @@ class Predicate
44
44
 
45
45
  def on_in(sexpr)
46
46
  left, right = apply(sexpr.identifier), sexpr.last
47
+ right = [right] if !right.is_a?(Array) && right.respond_to?(:sql_literal)
47
48
  ::Sequel.expr(left => right)
48
49
  end
49
50
 
@@ -61,9 +62,11 @@ class Predicate
61
62
  body[1..-1].inject(apply(body.first)){|f,t| f | apply(t) }
62
63
  end
63
64
 
64
- def on_native(sexpr)
65
+ def on_unsupported(sexpr)
65
66
  raise NotSupportedError
66
67
  end
68
+ alias :on_native :on_unsupported
69
+ alias :on_intersect :on_unsupported
67
70
 
68
71
  end # class ToSequel
69
72
  end # class Predicate
@@ -1,8 +1,8 @@
1
1
  class Predicate
2
2
  module Version
3
3
  MAJOR = 1
4
- MINOR = 1
5
- TINY = 3
4
+ MINOR = 2
5
+ TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
data/lib/predicate.rb CHANGED
@@ -95,6 +95,20 @@ class Predicate
95
95
  expr.and_split(attr_list).map{|e| Predicate.new(e)}
96
96
  end
97
97
 
98
+ # Returns a hash `(attr -> Pattr)` associating attribute names
99
+ # to predicates, so that each predicate `Pattr` only makes
100
+ # reference to the corresponding attribute name `attr`, while
101
+ # the conjunction of `Pattr`s is still equivalent to the
102
+ # original predicate.
103
+ #
104
+ # A `nil` key may map a predicate that still makes references
105
+ # to more than one attribute.
106
+ def attr_split
107
+ expr.attr_split.each_pair.each_with_object({}) do |(k,v),h|
108
+ h[k] = Predicate.new(v)
109
+ end
110
+ end
111
+
98
112
  def ==(other)
99
113
  other.is_a?(Predicate) && (other.expr==expr)
100
114
  end
@@ -0,0 +1,12 @@
1
+ require_relative "shared/a_comparison_factory_method"
2
+ class Predicate
3
+ describe Factory, 'intersect' do
4
+
5
+ subject{ Factory.intersect(:x, [2, 3]) }
6
+
7
+ it{ should be_a(Intersect) }
8
+
9
+ it{ should eq([:intersect, [:identifier, :x], [2, 3]]) }
10
+
11
+ end
12
+ end
@@ -7,10 +7,15 @@ class Predicate
7
7
  let(:contradiction){
8
8
  [:contradiction, false]
9
9
  }
10
+
10
11
  let(:identifier){
11
12
  [:identifier, :name]
12
13
  }
13
14
 
15
+ let(:values){
16
+ [12, 15]
17
+ }
18
+
14
19
  before do
15
20
  subject.should be_a(Sexpr)
16
21
  end
@@ -87,6 +92,18 @@ class Predicate
87
92
  it{ should be_a(Lte) }
88
93
  end
89
94
 
95
+ describe "in" do
96
+ let(:expr){ [:in, identifier, values] }
97
+
98
+ it{ should be_a(In) }
99
+ end
100
+
101
+ describe "intersect" do
102
+ let(:expr){ [:intersect, identifier, values] }
103
+
104
+ it{ should be_a(Intersect) }
105
+ end
106
+
90
107
  describe "literal" do
91
108
  let(:expr){ [:literal, 12] }
92
109
 
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ class Predicate
3
+ describe And, "attr_split" do
4
+
5
+ let(:tautology){ Factory.tautology }
6
+
7
+ subject{ predicate.attr_split }
8
+
9
+ context 'when each side returns a singleton hash' do
10
+ let(:predicate){ Factory.eq(:x, 2) & Factory.eq(:y, 3) }
11
+
12
+ it 'returns the hash union' do
13
+ expect(subject).to eql({
14
+ :x => Factory.eq(:x, 2),
15
+ :y => Factory.eq(:y, 3)
16
+ })
17
+ end
18
+ end
19
+
20
+ context 'when right and left side share some attributes' do
21
+ let(:predicate){
22
+ Factory.eq(:x, 2) & (
23
+ Factory.eq(:y, 3) & Factory.in(:x, [1, 18])
24
+ )
25
+ }
26
+
27
+ it 'returns the hash union' do
28
+ expect(subject).to eql({
29
+ :x => Factory.eq(:x, 2) & Factory.in(:x, [1, 18]),
30
+ :y => Factory.eq(:y, 3)
31
+ })
32
+ end
33
+ end
34
+
35
+ context 'when right and left side share some attributes, but split cannot be made' do
36
+ let(:predicate){
37
+ Factory.eq(:x, 2) & (
38
+ Factory.eq(:y, 3) | Factory.in(:x, [1, 18])
39
+ )
40
+ }
41
+
42
+ it 'returns the hash union' do
43
+ expect(subject).to eql({
44
+ :x => Factory.eq(:x, 2),
45
+ nil => Factory.eq(:y, 3) | Factory.in(:x, [1, 18])
46
+ })
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ class Predicate
3
+ describe Predicate, "attr_split" do
4
+
5
+ let(:p){ Predicate }
6
+ subject{ pred.attr_split }
7
+
8
+ context "on tautology" do
9
+ let(:pred){ p.tautology }
10
+
11
+ it{ should eq({}) }
12
+ end
13
+
14
+ context "on contradiction" do
15
+ let(:pred){ p.contradiction }
16
+
17
+ it{ should eq({ nil => pred }) }
18
+ end
19
+
20
+ context "on identifier" do
21
+ let(:pred){ p.identifier(:x) }
22
+
23
+ it{ should eq({ x: pred }) }
24
+ end
25
+
26
+ context "on not" do
27
+ let(:pred){ p.not(:x) }
28
+
29
+ it{ should eq({ x: pred }) }
30
+ end
31
+
32
+ context "on eq" do
33
+ let(:pred){ p.eq(:x, 2) }
34
+
35
+ it{ should eq({ x: pred }) }
36
+ end
37
+
38
+ end
39
+ end
@@ -73,5 +73,11 @@ class Predicate
73
73
  specify{ subject.expr.should be_a(Native) }
74
74
  end
75
75
 
76
+ context 'intersect' do
77
+ subject{ Predicate.intersect(:x, [7,8]) }
78
+
79
+ specify{ subject.to_ruby_code.should eq("->(t){ !(t[:x] & [7, 8]).empty? }") }
80
+ end
81
+
76
82
  end
77
83
  end
@@ -6,27 +6,41 @@ class Predicate
6
6
 
7
7
  subject{ predicate.qualify(qualifier) }
8
8
 
9
- let(:qualifier) { {:x => :t} }
9
+ context 'when the qualifier is a Symbol' do
10
+ let(:qualifier) { :t }
11
+ let(:predicate){ p.in(:x, [2]) & p.eq(:y, :z) }
12
+
13
+ it 'works as expected' do
14
+ left = p.in(Factory.qualified_identifier(:t, :x), [2])
15
+ right = p.eq(Factory.qualified_identifier(:t, :y), Factory.qualified_identifier(:t, :z))
16
+ expect(subject).to eql(left & right)
17
+ end
18
+ end
19
+
20
+ context 'when the qualifier is a Hash' do
10
21
 
11
- context 'on a full AST predicate' do
12
- let(:predicate){ p.in(:x, [2]) & p.eq(:y, 3) }
22
+ let(:qualifier) { {:x => :t} }
13
23
 
14
- it{ should eq(p.in(Factory.qualified_identifier(:t, :x), [2]) & p.eq(:y, 3)) }
24
+ context 'on a full AST predicate' do
25
+ let(:predicate){ p.in(:x, [2]) & p.eq(:y, 3) }
15
26
 
16
- specify "it should tag expressions correctly" do
17
- subject.expr.should be_a(Sexpr)
18
- subject.expr.should be_a(Expr)
19
- subject.expr.should be_a(And)
27
+ it{ should eq(p.in(Factory.qualified_identifier(:t, :x), [2]) & p.eq(:y, 3)) }
28
+
29
+ specify "it should tag expressions correctly" do
30
+ subject.expr.should be_a(Sexpr)
31
+ subject.expr.should be_a(Expr)
32
+ subject.expr.should be_a(And)
33
+ end
20
34
  end
21
- end
22
35
 
23
- context 'on a predicate that contains natives' do
24
- let(:predicate){ p.in(:x, [2]) & p.native(lambda{}) }
36
+ context 'on a predicate that contains natives' do
37
+ let(:predicate){ p.in(:x, [2]) & p.native(lambda{}) }
25
38
 
26
- it 'raises an error' do
27
- lambda{
28
- subject
29
- }.should raise_error(NotSupportedError)
39
+ it 'raises an error' do
40
+ lambda{
41
+ subject
42
+ }.should raise_error(NotSupportedError)
43
+ end
30
44
  end
31
45
  end
32
46
 
@@ -45,6 +45,14 @@ class Predicate
45
45
  end
46
46
  end
47
47
 
48
+ context 'qualified eq(name: "bob")' do
49
+ let(:predicate) { Predicate.eq(:name, "Bob").qualify(:t) }
50
+
51
+ it 'works as expected' do
52
+ expect(subject).to eql("SELECT * FROM `items` WHERE (`t`.`name` = 'Bob')")
53
+ end
54
+ end
55
+
48
56
  context 'eq(name: :address)' do
49
57
  let(:predicate) { Predicate.eq(:name, :address) }
50
58
 
@@ -77,6 +85,21 @@ class Predicate
77
85
  end
78
86
  end
79
87
 
88
+ context 'in with something responding to sql_literal' do
89
+ let(:operand){
90
+ Object.new.tap{|o|
91
+ def o.sql_literal(db)
92
+ "Hello World"
93
+ end
94
+ }
95
+ }
96
+ let(:predicate) { Predicate.in(:price, operand) }
97
+
98
+ it 'works as expected' do
99
+ expect(subject).to eql("SELECT * FROM `items` WHERE (`price` IN (Hello World))")
100
+ end
101
+ end
102
+
80
103
  context 'not' do
81
104
  let(:predicate) { !Predicate.in(:price, [10.0, 17.99]) }
82
105
 
@@ -109,6 +132,14 @@ class Predicate
109
132
  end
110
133
  end
111
134
 
135
+ context 'intersect' do
136
+ let(:predicate) { Predicate.intersect(:x, [8, 9]) }
137
+
138
+ it 'raises an error' do
139
+ expect { subject }.to raise_error(NotSupportedError)
140
+ end
141
+ end
142
+
112
143
  context 'native' do
113
144
  let(:predicate) { Predicate.native(->(t){ false }) }
114
145
 
@@ -2,11 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  shared_examples_for "a predicate" do
4
4
 
5
+ let(:w){ [8, 9] }
5
6
  let(:x){ 12 }
6
7
  let(:y){ 13 }
7
8
 
8
9
  it 'provides a proc for easy evaluation' do
9
- got = subject.to_proc.call(x: 12, y: 13)
10
+ got = subject.to_proc.call(w: w, x: x, y: y)
10
11
  [ TrueClass, FalseClass ].should include(got.class)
11
12
  end
12
13
 
@@ -24,7 +25,7 @@ shared_examples_for "a predicate" do
24
25
 
25
26
  it 'has free variables' do
26
27
  (fv = subject.free_variables).should be_a(Array)
27
- (fv - [ :x, :y ]).should be_empty
28
+ (fv - [ :w, :x, :y ]).should be_empty
28
29
  end
29
30
 
30
31
  it 'always splits around and trivially when no free variables are touched' do
@@ -65,6 +66,12 @@ describe "Predicate.among" do
65
66
  it_should_behave_like "a predicate"
66
67
  end
67
68
 
69
+ describe "Predicate.intersect" do
70
+ subject{ Predicate.intersect(:w, [2, 3]) }
71
+
72
+ it_should_behave_like "a predicate"
73
+ end
74
+
68
75
  describe "Predicate.eq" do
69
76
  subject{ Predicate.eq(:x, 2) }
70
77
 
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: 1.1.3
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
@@ -103,6 +103,7 @@ files:
103
103
  - lib/predicate/nodes/gte.rb
104
104
  - lib/predicate/nodes/identifier.rb
105
105
  - lib/predicate/nodes/in.rb
106
+ - lib/predicate/nodes/intersect.rb
106
107
  - lib/predicate/nodes/literal.rb
107
108
  - lib/predicate/nodes/lt.rb
108
109
  - lib/predicate/nodes/lte.rb
@@ -135,6 +136,7 @@ files:
135
136
  - spec/factory/test_gte.rb
136
137
  - spec/factory/test_identifier.rb
137
138
  - spec/factory/test_in.rb
139
+ - spec/factory/test_intersect.rb
138
140
  - spec/factory/test_literal.rb
139
141
  - spec/factory/test_lt.rb
140
142
  - spec/factory/test_lte.rb
@@ -147,6 +149,7 @@ files:
147
149
  - spec/grammar/test_match.rb
148
150
  - spec/grammar/test_sexpr.rb
149
151
  - spec/nodes/and/test_and_split.rb
152
+ - spec/nodes/and/test_attr_split.rb
150
153
  - spec/nodes/dyadic_comp/test_and_split.rb
151
154
  - spec/nodes/identifier/test_and_split.rb
152
155
  - spec/nodes/identifier/test_free_variables.rb
@@ -158,6 +161,7 @@ files:
158
161
  - spec/nodes/qualified_identifier/test_name.rb
159
162
  - spec/nodes/qualified_identifier/test_qualifier.rb
160
163
  - spec/predicate/test_and_split.rb
164
+ - spec/predicate/test_attr_split.rb
161
165
  - spec/predicate/test_bool_and.rb
162
166
  - spec/predicate/test_bool_not.rb
163
167
  - spec/predicate/test_bool_or.rb