predicate 1.0.0 → 1.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: '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: