predicate 1.1.3 → 1.2.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
  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