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 +4 -4
- data/lib/predicate.rb +1 -1
- data/lib/predicate/factory.rb +19 -26
- data/lib/predicate/sequel.rb +16 -0
- data/lib/predicate/sequel/to_sequel.rb +69 -0
- data/lib/predicate/version.rb +1 -1
- data/spec/factory/test_factor_predicate.rb +1 -1
- data/spec/factory/test_from_hash.rb +48 -0
- data/spec/predicate/test_coerce.rb +9 -1
- data/spec/sequel/test_to_sequel.rb +121 -0
- data/tasks/gem.rake +39 -0
- metadata +34 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 359d07688b84bc53009ffef594bbb47724840af8
|
4
|
+
data.tar.gz: 263848ef20f69b555afa46f851f9ceec8673c72b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b5fe1cc99457949357f9af96860d6686426efdfdd7d7ec8e71ea2011c896bab05e6660bf8b417f5e58882f4221f955c3d52edd600e90e1ac046bd9ad4aea9df
|
7
|
+
data.tar.gz: 98255f1cdc9c13de67466d8028a030d650e5aa66c4c7a6e14bb34016035669e1cdf90302b41623791335c4c73dc35d9b7215eb66168ee8d0ed6bdc567358f954
|
data/lib/predicate.rb
CHANGED
@@ -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
|
28
|
+
when Hash then from_hash(arg)
|
29
29
|
else
|
30
30
|
raise ArgumentError, "Unable to coerce `#{arg}` to a predicate"
|
31
31
|
end
|
data/lib/predicate/factory.rb
CHANGED
@@ -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
|
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,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
|
data/lib/predicate/version.rb
CHANGED
@@ -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.
|
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
|
data/tasks/gem.rake
ADDED
@@ -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.
|
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:
|