predicate 1.0.0 → 1.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: '07778e150b98d73bf35ce337d9874395f1f58e1c'
4
- data.tar.gz: 9d0e523c2f41b8b5c530e67a14a69f3dbdf31914
3
+ metadata.gz: 359d07688b84bc53009ffef594bbb47724840af8
4
+ data.tar.gz: 263848ef20f69b555afa46f851f9ceec8673c72b
5
5
  SHA512:
6
- metadata.gz: 84111b7fe5dc17da15f9cb3afb1cee966cedb9a29ed2e420348071c348d6f7c0cd94265c116afc7cae987ca7a96f6f29e52035c9540db85b817ee219da958c59
7
- data.tar.gz: cb5d7863db1db730e22b7fd6fe1b7023b89a8a2ef442333d8762ff1fe0b585805d10a2dd832ab07fc98453d7186daeac793c8963e4e38da2ea94fdb94c73db3a
6
+ metadata.gz: 9b5fe1cc99457949357f9af96860d6686426efdfdd7d7ec8e71ea2011c896bab05e6660bf8b417f5e58882f4221f955c3d52edd600e90e1ac046bd9ad4aea9df
7
+ data.tar.gz: 98255f1cdc9c13de67466d8028a030d650e5aa66c4c7a6e14bb34016035669e1cdf90302b41623791335c4c73dc35d9b7215eb66168ee8d0ed6bdc567358f954
@@ -25,7 +25,7 @@ class Predicate
25
25
  when FalseClass then contradiction
26
26
  when Symbol then identifier(arg)
27
27
  when Proc then native(arg)
28
- when Hash then eq(arg)
28
+ when Hash then from_hash(arg)
29
29
  else
30
30
  raise ArgumentError, "Unable to coerce `#{arg}` to a predicate"
31
31
  end
@@ -29,21 +29,6 @@ class Predicate
29
29
  _factor_predicate([:not, sexpr(operand)])
30
30
  end
31
31
 
32
- def relation(r)
33
- tuples = r.to_a
34
- case tuples.size
35
- when 0 then contradiction
36
- when 1 then eq(tuples.first)
37
- else
38
- if tuples.first.size==1
39
- k = tuples.first.keys.first
40
- self.in(k, tuples.map{|t| t[k]})
41
- else
42
- tuples.inject(contradiction){|p,t| p | eq(t) }
43
- end
44
- end
45
- end
46
-
47
32
  def in(identifier, values)
48
33
  identifier = sexpr(identifier) if identifier.is_a?(Symbol)
49
34
  _factor_predicate([:in, identifier, values])
@@ -51,17 +36,7 @@ class Predicate
51
36
  alias :among :in
52
37
 
53
38
  def comp(op, h)
54
- h = h.to_hash
55
- if h.empty?
56
- return tautology
57
- elsif h.size==1
58
- _factor_predicate [op, sexpr(h.keys.first), sexpr(h.values.last)]
59
- else
60
- terms = h.to_a.inject([:and]) do |anded,pair|
61
- anded << ([op] << sexpr(pair.first) << sexpr(pair.last))
62
- end
63
- _factor_predicate terms
64
- end
39
+ from_hash(h, op)
65
40
  end
66
41
 
67
42
  [ :eq, :neq, :lt, :lte, :gt, :gte ].each do |m|
@@ -76,6 +51,22 @@ class Predicate
76
51
  [:lte, sexpr(middle), sexpr(upper_bound)]]
77
52
  end
78
53
 
54
+ def from_hash(h, op = :eq)
55
+ if h.empty?
56
+ tautology
57
+ else
58
+ terms = h.to_a.map{|(k,v)|
59
+ if v.is_a?(Array)
60
+ [:in, sexpr(k), v]
61
+ else
62
+ [op, sexpr(k), sexpr(v)]
63
+ end
64
+ }
65
+ terms = terms.size == 1 ? terms.first : terms.unshift(:and)
66
+ _factor_predicate terms
67
+ end
68
+ end
69
+
79
70
  def literal(literal)
80
71
  _factor_predicate([:literal, literal])
81
72
  end
@@ -84,6 +75,8 @@ class Predicate
84
75
  _factor_predicate([:native, arg])
85
76
  end
86
77
 
78
+ protected
79
+
87
80
  def sexpr(expr)
88
81
  case expr
89
82
  when Expr then expr
@@ -0,0 +1,16 @@
1
+ class Predicate
2
+
3
+ module Expr
4
+
5
+ def to_sequel
6
+ ToSequel.call(self)
7
+ end
8
+
9
+ end # module Expr
10
+
11
+ def to_sequel
12
+ expr.to_sequel
13
+ end
14
+
15
+ end # class Predicate
16
+ require_relative 'sequel/to_sequel'
@@ -0,0 +1,69 @@
1
+ class Predicate
2
+ class ToSequel < Sexpr::Processor
3
+
4
+ def on_identifier(sexpr)
5
+ ::Sequel.identifier(sexpr.last)
6
+ end
7
+
8
+ def on_qualified_identifier(sexpr)
9
+ ::Sequel.as(sexpr.qualifier, sexpr.name)
10
+ end
11
+
12
+ def on_literal(sexpr)
13
+ sexpr.last.nil? ? nil : ::Sequel.expr(sexpr.last)
14
+ end
15
+
16
+ ###
17
+
18
+ def on_tautology(sexpr)
19
+ ::Sequel::SQL::BooleanConstant.new(true)
20
+ end
21
+
22
+ def on_contradiction(sexpr)
23
+ ::Sequel::SQL::BooleanConstant.new(false)
24
+ end
25
+
26
+ def on_eq(sexpr)
27
+ left, right = apply(sexpr.left), apply(sexpr.right)
28
+ ::Sequel.expr(left => right)
29
+ end
30
+
31
+ def on_neq(sexpr)
32
+ left, right = apply(sexpr.left), apply(sexpr.right)
33
+ ~::Sequel.expr(left => right)
34
+ end
35
+
36
+ def on_dyadic_comp(sexpr)
37
+ left, right = apply(sexpr.left), apply(sexpr.right)
38
+ left.send(sexpr.operator_symbol, right)
39
+ end
40
+ alias :on_lt :on_dyadic_comp
41
+ alias :on_lte :on_dyadic_comp
42
+ alias :on_gt :on_dyadic_comp
43
+ alias :on_gte :on_dyadic_comp
44
+
45
+ def on_in(sexpr)
46
+ left, right = apply(sexpr.identifier), sexpr.last
47
+ ::Sequel.expr(left => right)
48
+ end
49
+
50
+ def on_not(sexpr)
51
+ ~apply(sexpr.last)
52
+ end
53
+
54
+ def on_and(sexpr)
55
+ body = sexpr.sexpr_body
56
+ body[1..-1].inject(apply(body.first)){|f,t| f & apply(t) }
57
+ end
58
+
59
+ def on_or(sexpr)
60
+ body = sexpr.sexpr_body
61
+ body[1..-1].inject(apply(body.first)){|f,t| f | apply(t) }
62
+ end
63
+
64
+ def on_native(sexpr)
65
+ raise NotImplementedError
66
+ end
67
+
68
+ end # class ToSequel
69
+ end # class Predicate
@@ -1,7 +1,7 @@
1
1
  class Predicate
2
2
  module Version
3
3
  MAJOR = 1
4
- MINOR = 0
4
+ MINOR = 1
5
5
  TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  class Predicate
3
3
  describe Factory, "_factor_predicate" do
4
4
 
5
- subject{ Factory._factor_predicate(arg) }
5
+ subject{ Factory.send(:_factor_predicate, arg) }
6
6
 
7
7
  context "on Expr" do
8
8
  let(:arg){ Grammar.sexpr([:literal, 12]) }
@@ -0,0 +1,48 @@
1
+ require_relative 'shared/a_predicate_ast_node'
2
+ class Predicate
3
+ describe Factory, 'from_hash' do
4
+
5
+ subject{ Factory.from_hash(h) }
6
+
7
+ context "when the hash is empty" do
8
+ let(:h){ {} }
9
+
10
+ it{ should eq(Factory.tautology) }
11
+ end
12
+
13
+ context "when the hash is a singelton" do
14
+ let(:h){ {:x => 12} }
15
+
16
+ it_should_behave_like "a predicate AST node"
17
+ it{ should be_a(Eq) }
18
+ it{ should eq([:eq, [:identifier, :x], [:literal, 12]]) }
19
+ end
20
+
21
+ context "when the hash is not a singleton" do
22
+ let(:h){ {:x => 12, :y => :z} }
23
+ let(:expected){
24
+ [:and,
25
+ [:eq, [:identifier, :x], [:literal, 12]],
26
+ [:eq, [:identifier, :y], [:identifier, :z]]]
27
+ }
28
+
29
+ it_should_behave_like "a predicate AST node"
30
+ it{ should be_a(And) }
31
+ it{ should eq(expected) }
32
+ end
33
+
34
+ context "when the hash has array values" do
35
+ let(:h){ {:x => [12], :y => :z} }
36
+ let(:expected){
37
+ [:and,
38
+ [:in, [:identifier, :x], [12]],
39
+ [:eq, [:identifier, :y], [:identifier, :z]]]
40
+ }
41
+
42
+ it_should_behave_like "a predicate AST node"
43
+ it{ should be_a(And) }
44
+ it{ should eq(expected) }
45
+ end
46
+
47
+ end
48
+ end
@@ -67,7 +67,15 @@ class Predicate
67
67
  let(:arg){ {status: 10, name: "Jones"} }
68
68
 
69
69
  specify{
70
- subject.should eq(Predicate.eq(status: 10) & Predicate.eq(name: "Jones"))
70
+ expect(subject).to eq(Predicate.eq(status: 10) & Predicate.eq(name: "Jones"))
71
+ }
72
+ end
73
+
74
+ describe "from Hash (in)" do
75
+ let(:arg){ {status: [10, 15]} }
76
+
77
+ specify{
78
+ expect(subject).to eq(Predicate.in(:status, [10,15]))
71
79
  }
72
80
  end
73
81
 
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+ require 'sequel'
3
+ require 'sqlite3'
4
+ require 'predicate/sequel'
5
+ class Predicate
6
+ describe ToSequel do
7
+
8
+ DB = Sequel.sqlite
9
+
10
+ before(:all) do
11
+ DB.create_table :items do
12
+ primary_key :id
13
+ String :name
14
+ String :address
15
+ Float :price
16
+ end
17
+ items = DB[:items]
18
+ end
19
+
20
+ subject do
21
+ DB[:items].where(predicate.to_sequel).sql
22
+ end
23
+
24
+ context 'tautology' do
25
+ let(:predicate) { Predicate.tautology }
26
+
27
+ it 'works as expected' do
28
+ expect(subject).to eql("SELECT * FROM `items` WHERE (1 = 1)")
29
+ end
30
+ end
31
+
32
+ context 'contradiction' do
33
+ let(:predicate) { Predicate.contradiction }
34
+
35
+ it 'works as expected' do
36
+ expect(subject).to eql("SELECT * FROM `items` WHERE (1 = 0)")
37
+ end
38
+ end
39
+
40
+ context 'eq(name: "bob")' do
41
+ let(:predicate) { Predicate.eq(:name, "Bob") }
42
+
43
+ it 'works as expected' do
44
+ expect(subject).to eql("SELECT * FROM `items` WHERE (`name` = 'Bob')")
45
+ end
46
+ end
47
+
48
+ context 'eq(name: :address)' do
49
+ let(:predicate) { Predicate.eq(:name, :address) }
50
+
51
+ it 'works as expected' do
52
+ expect(subject).to eql("SELECT * FROM `items` WHERE (`name` = `address`)")
53
+ end
54
+ end
55
+
56
+ context 'neq(name: "bob")' do
57
+ let(:predicate) { Predicate.neq(:name, "Bob") }
58
+
59
+ it 'works as expected' do
60
+ expect(subject).to eql("SELECT * FROM `items` WHERE (`name` != 'Bob')")
61
+ end
62
+ end
63
+
64
+ context 'on_dyadic_comp' do
65
+ let(:predicate) { Predicate.lt(:price, 10.0) }
66
+
67
+ it 'works as expected' do
68
+ expect(subject).to eql("SELECT * FROM `items` WHERE (`price` < 10.0)")
69
+ end
70
+ end
71
+
72
+ context 'in' do
73
+ let(:predicate) { Predicate.in(:price, [10.0, 17.99]) }
74
+
75
+ it 'works as expected' do
76
+ expect(subject).to eql("SELECT * FROM `items` WHERE (`price` IN (10.0, 17.99))")
77
+ end
78
+ end
79
+
80
+ context 'not' do
81
+ let(:predicate) { !Predicate.in(:price, [10.0, 17.99]) }
82
+
83
+ it 'works as expected' do
84
+ expect(subject).to eql("SELECT * FROM `items` WHERE (`price` NOT IN (10.0, 17.99))")
85
+ end
86
+ end
87
+
88
+ context 'and' do
89
+ let(:predicate) { Predicate.eq(:name, "Bob") & Predicate.lt(:price, 10.0) }
90
+
91
+ it 'works as expected' do
92
+ expect(subject).to eql("SELECT * FROM `items` WHERE ((`name` = 'Bob') AND (`price` < 10.0))")
93
+ end
94
+ end
95
+
96
+ context 'or' do
97
+ let(:predicate) { Predicate.eq(:name, "Bob") | Predicate.lt(:price, 10.0) }
98
+
99
+ it 'works as expected' do
100
+ expect(subject).to eql("SELECT * FROM `items` WHERE ((`name` = 'Bob') OR (`price` < 10.0))")
101
+ end
102
+ end
103
+
104
+ context 'from_hash' do
105
+ let(:predicate) { Predicate.from_hash(name: "Bob", price: [10.0,17.99]) }
106
+
107
+ it 'works as expected' do
108
+ expect(subject).to eql("SELECT * FROM `items` WHERE ((`name` = 'Bob') AND (`price` IN (10.0, 17.99)))")
109
+ end
110
+ end
111
+
112
+ context 'native' do
113
+ let(:predicate) { Predicate.native(->(t){ false }) }
114
+
115
+ it 'raises an error' do
116
+ expect { subject }.to raise_error(NotImplementedError)
117
+ end
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,39 @@
1
+ require 'rubygems/package_task'
2
+
3
+ # Dynamically load the gem spec
4
+ gemspec_file = File.expand_path('../../predicate.gemspec', __FILE__)
5
+ gemspec = Kernel.eval(File.read(gemspec_file))
6
+
7
+ Gem::PackageTask.new(gemspec) do |t|
8
+
9
+ # Name of the package
10
+ t.name = gemspec.name
11
+
12
+ # Version of the package
13
+ t.version = gemspec.version
14
+
15
+ # Directory used to store the package files
16
+ t.package_dir = "pkg"
17
+
18
+ # True if a gzipped tar file (tgz) should be produced
19
+ t.need_tar = false
20
+
21
+ # True if a gzipped tar file (tar.gz) should be produced
22
+ t.need_tar_gz = false
23
+
24
+ # True if a bzip2'd tar file (tar.bz2) should be produced
25
+ t.need_tar_bz2 = false
26
+
27
+ # True if a zip file should be produced (default is false)
28
+ t.need_zip = false
29
+
30
+ # List of files to be included in the package.
31
+ t.package_files = gemspec.files
32
+
33
+ # Tar command for gzipped or bzip2ed archives.
34
+ t.tar_command = "tar"
35
+
36
+ # Zip command for zipped archives.
37
+ t.zip_command = "zip"
38
+
39
+ end
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.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
@@ -66,6 +66,34 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sequel
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  description: Predicate provides a simple class and processors to encode and manipulate
70
98
  (tuple) predicates
71
99
  email: blambeau@gmail.com
@@ -103,6 +131,8 @@ files:
103
131
  - lib/predicate/processors/qualifier.rb
104
132
  - lib/predicate/processors/renamer.rb
105
133
  - lib/predicate/processors/to_ruby_code.rb
134
+ - lib/predicate/sequel.rb
135
+ - lib/predicate/sequel/to_sequel.rb
106
136
  - lib/predicate/version.rb
107
137
  - spec/expr/test_to_proc.rb
108
138
  - spec/expr/test_to_ruby_code.rb
@@ -114,6 +144,7 @@ files:
114
144
  - spec/factory/test_contradiction.rb
115
145
  - spec/factory/test_eq.rb
116
146
  - spec/factory/test_factor_predicate.rb
147
+ - spec/factory/test_from_hash.rb
117
148
  - spec/factory/test_gt.rb
118
149
  - spec/factory/test_gte.rb
119
150
  - spec/factory/test_identifier.rb
@@ -155,8 +186,10 @@ files:
155
186
  - spec/predicate/test_tautology.rb
156
187
  - spec/predicate/test_to_proc.rb
157
188
  - spec/predicate/test_to_ruby_code.rb
189
+ - spec/sequel/test_to_sequel.rb
158
190
  - spec/spec_helper.rb
159
191
  - spec/test_predicate.rb
192
+ - tasks/gem.rake
160
193
  - tasks/test.rake
161
194
  homepage: http://github.com/enspirit/predicate
162
195
  licenses: