predicate 2.0.1 → 2.1.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: 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