predicate 2.0.1 → 2.1.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: 9fca9de34caf91b943ce07da44ab519042b4b1e9
4
- data.tar.gz: f5655e929360dab91af6533db03674a7e259c465
3
+ metadata.gz: d3a81672181d7e3403108a8f09f7d7adbb94266c
4
+ data.tar.gz: ce94a0e1580ea4d77d177926a5e305736b47741b
5
5
  SHA512:
6
- metadata.gz: 14b384734d66a9ff1886c4737cab98e247e484d7c11b7459393fcf27fa4bdf18f2042a4a83cdc55e72b840d1e9db92472a45e5df36649a7f54b7ec2f2f64ded8
7
- data.tar.gz: cf72d2ba84a77d59c27f84880dea9e5a4b19636cf3fd4345cc1fcb3fbe3d3ed59d7f3e41001ad9098633711cf7fe9fd8a6b36dbffe94d8f96c2e8a91c1f4c1a3
6
+ metadata.gz: cd1126d3e88102910c26ff3181d9eb842e697b90632f82012c74efbd391a922de6465653ba9968ae802e8968cb8723f1e88fd30d0c630289c8ef6ef1fc86b97a
7
+ data.tar.gz: b0e52017a78b5a33b9eeb7d62c1e6e08ca2090555a501b21d4407c991cedde6201926c8f2c6d40d5621d7d912935d00584675ab7257ebed62ce197054e5888d1
@@ -29,10 +29,13 @@ class Predicate
29
29
  _factor_predicate([:not, sexpr(operand)])
30
30
  end
31
31
 
32
- def in(identifier, values)
33
- return contradiction if values.is_a?(Array) && values.empty?
34
- identifier = sexpr(identifier) if identifier.is_a?(Symbol)
35
- _factor_predicate([:in, identifier, values])
32
+ def in(left, right)
33
+ left, right = sexpr(left), sexpr(right)
34
+ if right.literal? && right.value.empty?
35
+ contradiction
36
+ else
37
+ _factor_predicate([:in, left, right])
38
+ end
36
39
  end
37
40
  alias :among :in
38
41
 
@@ -63,7 +66,7 @@ class Predicate
63
66
  else
64
67
  terms = h.to_a.map{|(k,v)|
65
68
  if v.is_a?(Array)
66
- [:in, sexpr(k), v]
69
+ [:in, sexpr(k), sexpr(v)]
67
70
  else
68
71
  [op, sexpr(k), sexpr(v)]
69
72
  end
@@ -77,6 +80,10 @@ class Predicate
77
80
  _factor_predicate([:literal, literal])
78
81
  end
79
82
 
83
+ def opaque(arg)
84
+ _factor_predicate([:opaque, arg])
85
+ end
86
+
80
87
  def match(left, right, options = nil)
81
88
  _factor_predicate([:match, sexpr(left), sexpr(right)] + (options.nil? ? [] : [options]))
82
89
  end
@@ -89,13 +96,13 @@ class Predicate
89
96
 
90
97
  def sexpr(expr)
91
98
  case expr
92
- when Expr then expr
93
- when Predicate then expr.expr
94
- when TrueClass then Grammar.sexpr([:tautology, true])
95
- when FalseClass then Grammar.sexpr([:contradiction, false])
96
- when Symbol then Grammar.sexpr([:identifier, expr])
97
- when Proc then Grammar.sexpr([:native, expr])
98
- when Array then Grammar.sexpr(expr)
99
+ when Expr then expr
100
+ when Predicate then expr.expr
101
+ when TrueClass then Grammar.sexpr([:tautology, true])
102
+ when FalseClass then Grammar.sexpr([:contradiction, false])
103
+ when Symbol then Grammar.sexpr([:identifier, expr])
104
+ when Proc then Grammar.sexpr([:native, expr])
105
+ when SexprLike then Grammar.sexpr(expr)
99
106
  else
100
107
  Grammar.sexpr([:literal, expr])
101
108
  end
@@ -33,3 +33,4 @@ require_relative 'nodes/intersect'
33
33
  require_relative 'nodes/literal'
34
34
  require_relative 'nodes/match'
35
35
  require_relative 'nodes/native'
36
+ require_relative 'nodes/opaque'
@@ -43,14 +43,15 @@ rules:
43
43
  gte:
44
44
  - [ term, term ]
45
45
  in:
46
- - [ varref, values ]
46
+ - [ varref, term ]
47
47
  intersect:
48
- - [ varref, values ]
48
+ - [ term, term ]
49
49
  match:
50
50
  - [ term, term, options ]
51
51
  term:
52
52
  - varref
53
53
  - literal
54
+ - opaque
54
55
  varref:
55
56
  - qualified_identifier
56
57
  - identifier
@@ -58,7 +59,7 @@ rules:
58
59
  - [ "::Proc" ]
59
60
  literal:
60
61
  - "::Object"
61
- values:
62
+ opaque:
62
63
  - "::Object"
63
64
  options:
64
65
  - "::Hash"
@@ -7,20 +7,17 @@ class Predicate
7
7
  end
8
8
 
9
9
  def &(other)
10
+ return super unless free_variables == other.free_variables
10
11
  case other
11
12
  when Eq
12
- if free_variables == other.free_variables
13
- return self if constants == other.constants
14
- contradiction
15
- else
16
- super
17
- end
13
+ return self if constants == other.constants
14
+ return contradiction
18
15
  when In
19
- return self if free_variables == other.free_variables
20
- super
21
- else
22
- super
16
+ return self if other.right.literal?
23
17
  end
18
+ super
19
+ rescue NotSupportedError
20
+ super
24
21
  end
25
22
 
26
23
  def constant_variables
@@ -23,6 +23,10 @@ class Predicate
23
23
  sexpr_type == :literal
24
24
  end
25
25
 
26
+ def opaque?
27
+ sexpr_type == :opaque
28
+ end
29
+
26
30
  def identifier?
27
31
  sexpr_type == :identifier
28
32
  end
@@ -84,7 +88,6 @@ class Predicate
84
88
  def to_s(scope = nil)
85
89
  ToS.call(self, scope: scope)
86
90
  end
87
- alias :inspect :to_s
88
91
 
89
92
  def sexpr(arg)
90
93
  Factory.sexpr(arg)
@@ -6,32 +6,33 @@ class Predicate
6
6
  80
7
7
  end
8
8
 
9
- def identifier
9
+ def left
10
10
  self[1]
11
11
  end
12
+ alias :identifier :left
12
13
 
13
- def values
14
+ def right
14
15
  self[2]
15
16
  end
16
17
 
17
18
  def &(other)
18
- case other
19
- when In
20
- fv = free_variables
21
- if fv.size == 1 && fv == other.free_variables
22
- intersection = values & other.values
23
- if intersection.empty?
24
- Factory.contradiction
25
- elsif intersection.size == 1
26
- Factory.eq(fv.first, [:literal, intersection.first])
27
- else
28
- Factory.in(fv.first, intersection)
29
- end
30
- else
31
- super
32
- end
19
+ # we only optimize with another In
20
+ return super unless other.is_a?(In)
21
+
22
+ # we only optimize is same free variables
23
+ fv = free_variables
24
+ return super unless fv.size == 1 && fv == other.free_variables
25
+
26
+ # we only optimize if both right terms are literals
27
+ return super unless right.literal? and other.right.literal?
28
+
29
+ intersection = right.value & other.right.value
30
+ if intersection.empty?
31
+ Factory.contradiction
32
+ elsif intersection.size == 1
33
+ Factory.eq(fv.first, [:literal, intersection.first])
33
34
  else
34
- super
35
+ Factory.in(fv.first, intersection)
35
36
  end
36
37
  end
37
38
 
@@ -40,11 +41,19 @@ class Predicate
40
41
  end
41
42
 
42
43
  def constant_variables
43
- values.size == 1 ? free_variables : []
44
+ if right.literal? and right.value.size == 1
45
+ free_variables
46
+ else
47
+ []
48
+ end
44
49
  end
45
50
 
46
51
  def constants
47
- values.size == 1 ? { identifier.name => values.first } : {}
52
+ if right.literal? and right.value.size == 1
53
+ { identifier.name => right.value.first }
54
+ else
55
+ {}
56
+ end
48
57
  end
49
58
 
50
59
  def dyadic_priority
@@ -52,6 +61,7 @@ class Predicate
52
61
  end
53
62
 
54
63
  def evaluate(tuple)
64
+ values = right.evaluate(tuple)
55
65
  values.include?(identifier.evaluate(tuple))
56
66
  end
57
67
 
@@ -0,0 +1,18 @@
1
+ class Predicate
2
+ module Opaque
3
+ include Expr
4
+
5
+ def priority
6
+ 100
7
+ end
8
+
9
+ def free_variables
10
+ @free_variables ||= []
11
+ end
12
+
13
+ def evaluate(tuple)
14
+ raise NotImplementedError, "Opaque#evaluate is not defined"
15
+ end
16
+
17
+ end
18
+ end
@@ -50,7 +50,7 @@ class Predicate
50
50
  alias :on_gte :on_dyadic
51
51
 
52
52
  def on_in(sexpr)
53
- "#{apply(sexpr.identifier)} IN #{to_literal(sexpr.values)}"
53
+ "#{apply(sexpr.identifier)} IN #{apply(sexpr.right)}"
54
54
  end
55
55
 
56
56
  def on_intersect(sexpr)
@@ -61,6 +61,10 @@ class Predicate
61
61
  to_literal(sexpr.last)
62
62
  end
63
63
 
64
+ def on_opaque(sexpr)
65
+ "OPAQUE #{sexpr.last}"
66
+ end
67
+
64
68
  def on_match(sexpr)
65
69
  "#{apply(sexpr.left)} =~ #{apply(sexpr.right)}"
66
70
  end
@@ -1,5 +1,8 @@
1
1
  class Predicate
2
2
  class ToSequel < Sexpr::Processor
3
+
4
+ class Error < StandardError; end
5
+
3
6
  module Methods
4
7
  def on_identifier(sexpr)
5
8
  ::Sequel.identifier(sexpr.last)
@@ -43,9 +46,14 @@ class Predicate
43
46
  alias :on_gte :on_dyadic_comp
44
47
 
45
48
  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
+ left, right = apply(sexpr.identifier), sexpr.right
50
+ if right.literal?
51
+ ::Sequel.expr(left => right.value)
52
+ elsif right.opaque?
53
+ ::Sequel.expr(left => apply(right))
54
+ else
55
+ raise Error, "Unable to compile `#{right}` to sequel"
56
+ end
49
57
  end
50
58
 
51
59
  def on_not(sexpr)
@@ -74,6 +82,11 @@ class Predicate
74
82
  end
75
83
  end
76
84
 
85
+ def on_opaque(sexpr)
86
+ return [sexpr.last] if sexpr.last.respond_to?(:sql_literal)
87
+ raise Error, "Unable to compile #{sexpr} to Sequel"
88
+ end
89
+
77
90
  def on_unsupported(sexpr)
78
91
  raise NotSupportedError
79
92
  end
@@ -1,8 +1,8 @@
1
1
  class Predicate
2
2
  module Version
3
3
  MAJOR = 2
4
- MINOR = 0
5
- TINY = 1
4
+ MINOR = 1
5
+ TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
data/lib/predicate.rb CHANGED
@@ -9,6 +9,8 @@ class Predicate
9
9
 
10
10
  TupleLike = ->(t){ t.is_a?(Hash) }
11
11
 
12
+ SexprLike = ->(x) { x.is_a?(Array) && x.first.is_a?(Symbol) }
13
+
12
14
  def initialize(sexpr)
13
15
  @sexpr = sexpr
14
16
  end
@@ -35,7 +35,7 @@ class Predicate
35
35
  let(:h){ {:x => [12], :y => :z} }
36
36
  let(:expected){
37
37
  [:and,
38
- [:in, [:identifier, :x], [12]],
38
+ [:in, [:identifier, :x], [:literal, [12]]],
39
39
  [:eq, [:identifier, :y], [:identifier, :z]]]
40
40
  }
41
41
 
@@ -2,11 +2,29 @@ require_relative "shared/a_comparison_factory_method"
2
2
  class Predicate
3
3
  describe Factory, 'in' do
4
4
 
5
- subject{ Factory.in(:x, [2, 3]) }
5
+ context 'with values on the right' do
6
+ subject{ Factory.in(:x, [2, 3]) }
6
7
 
7
- it{ should be_a(In) }
8
+ it 'is a In' do
9
+ expect(subject).to be_a(In)
10
+ end
8
11
 
9
- it{ should eq([:in, [:identifier, :x], [2, 3]]) }
12
+ it 'has the expected AST' do
13
+ expect(subject).to eql([:in, [:identifier, :x], [:literal, [2, 3]]])
14
+ end
15
+ end
16
+
17
+ context 'with opaque on the right' do
18
+ subject{ Factory.in(:x, Factory.opaque([2, 3])) }
19
+
20
+ it 'is a In' do
21
+ expect(subject).to be_a(In)
22
+ end
23
+
24
+ it 'has the expected AST' do
25
+ expect(subject).to eql([:in, [:identifier, :x], [:opaque, [2, 3]]])
26
+ end
27
+ end
10
28
 
11
29
  end
12
30
  end
@@ -14,5 +14,17 @@ class Predicate
14
14
  it{ should be_a(Contradiction) }
15
15
  end
16
16
 
17
+ context 'with an IN on same variable and literal' do
18
+ let(:right){ Factory.in(:x, [3,4]) }
19
+
20
+ it{ should be(left) }
21
+ end
22
+
23
+ context 'with an IN on same variable and opaque' do
24
+ let(:right){ Factory.in(:x, Factory.opaque([3,4])) }
25
+
26
+ it{ should be_a(And) }
27
+ end
28
+
17
29
  end
18
30
  end
@@ -22,6 +22,12 @@ class Predicate
22
22
  it{ expect(subject).to eql([]) }
23
23
  end
24
24
 
25
+ describe "on a in with an opaque right term" do
26
+ let(:p){ Predicate.in(:x, Predicate.opaque([2])) }
27
+
28
+ it{ expect(subject).to eql([]) }
29
+ end
30
+
25
31
  describe "on a NOT" do
26
32
  let(:p){ !Predicate.coerce(x: 2) }
27
33
 
@@ -65,6 +65,12 @@ class Predicate
65
65
  it{ should eq({}) }
66
66
  end
67
67
 
68
+ context "on in (opaque)" do
69
+ let(:pred){ p.in(:x, p.opaque([2])) }
70
+
71
+ it{ should eq({}) }
72
+ end
73
+
68
74
  context "on and (two eqs)" do
69
75
  let(:pred){ p.eq(:x, 2) & p.eq(:y, 4) }
70
76
 
@@ -93,7 +93,7 @@ class Predicate
93
93
  end
94
94
  }
95
95
  }
96
- let(:predicate) { Predicate.in(:price, operand) }
96
+ let(:predicate) { Predicate.in(:price, Predicate.opaque(operand)) }
97
97
 
98
98
  it 'works as expected' do
99
99
  expect(subject).to eql("SELECT * FROM `items` WHERE (`price` IN (Hello World))")
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.0.1
4
+ version: 2.1.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: 2018-05-28 00:00:00.000000000 Z
11
+ date: 2018-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sexpr
@@ -112,6 +112,7 @@ files:
112
112
  - lib/predicate/nodes/native.rb
113
113
  - lib/predicate/nodes/neq.rb
114
114
  - lib/predicate/nodes/not.rb
115
+ - lib/predicate/nodes/opaque.rb
115
116
  - lib/predicate/nodes/or.rb
116
117
  - lib/predicate/nodes/qualified_identifier.rb
117
118
  - lib/predicate/nodes/tautology.rb