predicate 2.6.0 → 2.7.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 +5 -5
- data/README.md +33 -1
- data/lib/predicate/factory.rb +7 -3
- data/lib/predicate/grammar.sexp.yml +2 -0
- data/lib/predicate/postgres/ext/factory.rb +28 -0
- data/lib/predicate/postgres/ext/to_sequel.rb +26 -0
- data/lib/predicate/postgres/ext.rb +2 -0
- data/lib/predicate/postgres/pg_array/empty.rb +10 -0
- data/lib/predicate/postgres/pg_array/literal.rb +10 -0
- data/lib/predicate/postgres/pg_array/overlaps.rb +10 -0
- data/lib/predicate/postgres/pg_array.rb +25 -0
- data/lib/predicate/postgres/rewriter.rb +49 -0
- data/lib/predicate/postgres.rb +4 -0
- data/lib/predicate/sequel/to_sequel.rb +1 -0
- data/lib/predicate/version.rb +1 -1
- data/lib/predicate.rb +6 -2
- data/spec/postgres/test_factory.rb +48 -0
- data/spec/postgres/test_to_postgres.rb +41 -0
- data/spec/postgres/test_to_sequel.rb +44 -0
- data/spec/sequel/test_to_sequel.rb +1 -10
- data/spec/shared/a_predicate.rb +4 -0
- data/spec/spec_helper.rb +17 -1
- metadata +33 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9cef06edb829e0b79b74be5d6f69b329f1cc6656
|
4
|
+
data.tar.gz: 32535df41b6b08a634d818308c05424509683b58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 650836d9f3b0534271a38b2e58f59f4f93aad9fad9b60017777a0c1ab91e373ffe458d1a77bcc2309405c267b657c89121abb401f6687d19f8f79196d209dd06
|
7
|
+
data.tar.gz: 70a98a7269cda44050632396a4c90cfafddf16a8b06b1dc76fc1962cab7b4e8520e64244dc20cbf8e2369cbf21e7733f913d7b80cffa4dda0ec26b81decaaaae
|
data/README.md
CHANGED
@@ -274,7 +274,7 @@ given list.
|
|
274
274
|
|
275
275
|
```ruby
|
276
276
|
p = Predicate.eq(x: 2, y: 4) # x = 2 & y = 4
|
277
|
-
p1, p2 = p.and_split([:x]) # p1 is x = 2 ; p2 is y = 4
|
277
|
+
p1, p2 = p.and_split([:x]) # p1 is x = 2 ; p2 is y = 4
|
278
278
|
```
|
279
279
|
|
280
280
|
Observe that `and_split` is always possible but may degenerate to an
|
@@ -304,6 +304,38 @@ split = p.attr_split
|
|
304
304
|
# }
|
305
305
|
```
|
306
306
|
|
307
|
+
## Working with PostgreSQL
|
308
|
+
|
309
|
+
(experimental) Predicate supports compiling certain high-level expressions
|
310
|
+
to PostgreSQL native operators. It works in an direct or indirect way:
|
311
|
+
|
312
|
+
```
|
313
|
+
require 'predicate'
|
314
|
+
require 'predicate/postgres'
|
315
|
+
|
316
|
+
# In direct way, you simply create the predicates using PostgreSQL's own
|
317
|
+
# operators
|
318
|
+
p = Predicate.pg_array_overlaps(:x, ['foo', 'bar'])
|
319
|
+
p.to_sequel
|
320
|
+
|
321
|
+
# In indirect way, you use high-level predicates and convert them to
|
322
|
+
# PostgreSQL later using `to_postgres`
|
323
|
+
p = Predicate.interect(:x, ['foo', 'bar'])
|
324
|
+
p = p.to_postgres
|
325
|
+
p.to_sequel
|
326
|
+
```
|
327
|
+
|
328
|
+
Only a few array operators & translations exist, and only on `varchar[]`
|
329
|
+
types. Additional support will be added later. The following
|
330
|
+
translations are implemented (and methods on the right directly available
|
331
|
+
on the `Predicate` class):
|
332
|
+
|
333
|
+
```
|
334
|
+
-> pg_array_literal
|
335
|
+
intersect -> pg_array_overlaps
|
336
|
+
empty -> pg_array_empty
|
337
|
+
```
|
338
|
+
|
307
339
|
## Working with abstract variables
|
308
340
|
|
309
341
|
WARNING: this `var` feature is only compatible with `Predicate#evaluate`
|
data/lib/predicate/factory.rb
CHANGED
@@ -201,7 +201,7 @@ class Predicate
|
|
201
201
|
end
|
202
202
|
end
|
203
203
|
|
204
|
-
|
204
|
+
public
|
205
205
|
|
206
206
|
def sexpr(expr)
|
207
207
|
case expr
|
@@ -217,8 +217,12 @@ class Predicate
|
|
217
217
|
end
|
218
218
|
end
|
219
219
|
|
220
|
-
def _factor_predicate(arg)
|
221
|
-
sexpr(arg)
|
220
|
+
def _factor_predicate(arg, *mods)
|
221
|
+
expr = sexpr(arg)
|
222
|
+
mods.each do |mod|
|
223
|
+
expr.extend(mod)
|
224
|
+
end
|
225
|
+
expr
|
222
226
|
end
|
223
227
|
|
224
228
|
extend(self)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Predicate
|
2
|
+
module Factory
|
3
|
+
def pg_array_literal(value, type = :varchar)
|
4
|
+
_factor_predicate([
|
5
|
+
:pg_array_literal,
|
6
|
+
value,
|
7
|
+
type
|
8
|
+
], Postgres::PgArray::Literal)
|
9
|
+
end
|
10
|
+
|
11
|
+
def pg_array_overlaps(left, right, type = :varchar)
|
12
|
+
_factor_predicate([
|
13
|
+
:pg_array_overlaps,
|
14
|
+
sexpr(left),
|
15
|
+
sexpr(right),
|
16
|
+
type
|
17
|
+
], Postgres::PgArray::Overlaps)
|
18
|
+
end
|
19
|
+
|
20
|
+
def pg_array_empty(operand, type = :varchar)
|
21
|
+
_factor_predicate([
|
22
|
+
:pg_empty,
|
23
|
+
sexpr(operand),
|
24
|
+
type
|
25
|
+
], Postgres::PgArray::Empty)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Predicate
|
2
|
+
module Postgres
|
3
|
+
module ToSequel
|
4
|
+
def on_pg_array_literal(sexpr)
|
5
|
+
PgArray.to_pg_array(sexpr[1], sexpr[2])
|
6
|
+
end
|
7
|
+
|
8
|
+
def on_pg_array_overlaps(sexpr)
|
9
|
+
type = sexpr.last
|
10
|
+
l = PgArray.to_pg_array(apply(sexpr.left), type)
|
11
|
+
r = PgArray.to_pg_array(apply(sexpr.right), type)
|
12
|
+
l.overlaps(r)
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_pg_array_empty(sexpr)
|
16
|
+
left = PgArray.to_pg_array(apply(sexpr.operand))
|
17
|
+
right = PgArray.to_pg_array([], sexpr.last)
|
18
|
+
::Sequel.expr(left => right)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Predicate::ToSequel
|
25
|
+
include Predicate::Postgres::ToSequel
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Predicate
|
2
|
+
module Postgres
|
3
|
+
module PgArray
|
4
|
+
|
5
|
+
def to_pg_array(arg, type = :varchar)
|
6
|
+
if arg.is_a?(Sequel::Postgres::PGArray)
|
7
|
+
arg
|
8
|
+
elsif arg.is_a?(Sequel::SQL::Wrapper)
|
9
|
+
::Sequel.pg_array(arg.value, type)
|
10
|
+
elsif arg.is_a?(Array)
|
11
|
+
::Sequel.pg_array(arg, type)
|
12
|
+
elsif arg.respond_to?(:pg_array)
|
13
|
+
arg.pg_array
|
14
|
+
else
|
15
|
+
raise NotSupportedError, "Unexpected pg_array arg `#{arg}`::`#{arg.class}`"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
module_function :to_pg_array
|
19
|
+
|
20
|
+
end # module PgArray
|
21
|
+
end # module Postgres
|
22
|
+
end # class Predicate
|
23
|
+
require_relative 'pg_array/empty'
|
24
|
+
require_relative 'pg_array/overlaps'
|
25
|
+
require_relative 'pg_array/literal'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Predicate
|
2
|
+
module Postgres
|
3
|
+
class Rewriter < Sexpr::Rewriter
|
4
|
+
grammar Grammar
|
5
|
+
|
6
|
+
class ToLiteral < Sexpr::Rewriter
|
7
|
+
grammar Grammar
|
8
|
+
|
9
|
+
def on_literal(sexpr)
|
10
|
+
if sexpr.last.is_a?(Array)
|
11
|
+
[ :pg_array_literal, sexpr.last, :varchar ]
|
12
|
+
else
|
13
|
+
sexpr
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias :on_missing :copy_and_apply
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_intersect(sexpr)
|
21
|
+
rewriter = ToLiteral.new
|
22
|
+
rewritten = sexpr[1..-1]
|
23
|
+
.map{|expr| rewriter.call(expr) }
|
24
|
+
.unshift(:pg_array_overlaps)
|
25
|
+
rewritten.extend(PgArray::Overlaps)
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_empty(sexpr)
|
29
|
+
rewritten = sexpr[1..-1]
|
30
|
+
.map{|expr| apply(expr) }
|
31
|
+
.unshift(:pg_array_empty)
|
32
|
+
.push(:varchar)
|
33
|
+
rewritten.extend(PgArray::Empty)
|
34
|
+
end
|
35
|
+
|
36
|
+
alias :on_missing :copy_and_apply
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Expr
|
41
|
+
def to_postgres(*args)
|
42
|
+
Postgres::Rewriter.new(*args).call(self)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_postgres(*args)
|
47
|
+
Predicate.new(expr.to_postgres(*args))
|
48
|
+
end
|
49
|
+
end
|
data/lib/predicate/version.rb
CHANGED
data/lib/predicate.rb
CHANGED
@@ -51,8 +51,12 @@ class Predicate
|
|
51
51
|
|
52
52
|
private
|
53
53
|
|
54
|
-
def _factor_predicate(arg)
|
55
|
-
|
54
|
+
def _factor_predicate(arg, *mods)
|
55
|
+
expr = Grammar.sexpr(arg)
|
56
|
+
mods.each do |mod|
|
57
|
+
expr.extend(mod)
|
58
|
+
end
|
59
|
+
Predicate.new(expr)
|
56
60
|
end
|
57
61
|
|
58
62
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Predicate
|
4
|
+
describe 'pg_factory' do
|
5
|
+
|
6
|
+
subject{
|
7
|
+
predicate
|
8
|
+
}
|
9
|
+
|
10
|
+
let(:predicate) {
|
11
|
+
Predicate.tautology
|
12
|
+
}
|
13
|
+
|
14
|
+
it_should_behave_like "a predicate"
|
15
|
+
|
16
|
+
context 'pg_array_literal' do
|
17
|
+
let(:predicate) do
|
18
|
+
Predicate.pg_array_literal([12])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'pg_array_empty' do
|
23
|
+
let(:predicate) do
|
24
|
+
Predicate.pg_array_empty(:x)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'pg_array_overlaps' do
|
29
|
+
let(:predicate) do
|
30
|
+
Predicate.pg_array_overlaps(:x, [1,2,3])
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has expected free variables' do
|
34
|
+
expect(subject.free_variables).to eql([:x])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'pg_array_overlaps (2)' do
|
39
|
+
let(:predicate) do
|
40
|
+
Predicate.pg_array_overlaps(:x, :y)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'has expected free variables' do
|
44
|
+
expect(subject.free_variables).to eql([:x, :y])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Predicate
|
4
|
+
describe 'to_postgres' do
|
5
|
+
subject {
|
6
|
+
predicate.to_postgres
|
7
|
+
}
|
8
|
+
|
9
|
+
context 'on an intersect' do
|
10
|
+
let(:predicate){
|
11
|
+
Predicate.intersect(:x, [1, 2, 3])
|
12
|
+
}
|
13
|
+
|
14
|
+
it 'returns another predicate' do
|
15
|
+
expect(subject).to be_a(Predicate)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'replaces intersect by pg_array_overlaps' do
|
19
|
+
expect(subject.sexpr).to eql([
|
20
|
+
:pg_array_overlaps,
|
21
|
+
[ :identifier, :x ],
|
22
|
+
[ :pg_array_literal, [1, 2, 3], :varchar ]
|
23
|
+
])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'on an intersect' do
|
28
|
+
let(:predicate){
|
29
|
+
Predicate.empty(:x)
|
30
|
+
}
|
31
|
+
|
32
|
+
it 'replaces empty by pg_array_empty' do
|
33
|
+
expect(subject.sexpr).to eql([
|
34
|
+
:pg_array_empty,
|
35
|
+
[ :identifier, :x ],
|
36
|
+
:varchar
|
37
|
+
])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
class Predicate
|
3
|
+
describe ToSequel do
|
4
|
+
PG_DB = Sequel.connect(ENV['PREDICATE_PG_URL'])
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
create_items_database(PG_DB)
|
8
|
+
items = PG_DB[:items]
|
9
|
+
end
|
10
|
+
|
11
|
+
subject do
|
12
|
+
dataset = PG_DB[:items].where(predicate.to_postgres.to_sequel)
|
13
|
+
dataset
|
14
|
+
end
|
15
|
+
|
16
|
+
# after do
|
17
|
+
# subject.to_a
|
18
|
+
# end
|
19
|
+
|
20
|
+
context 'tautology' do
|
21
|
+
let(:predicate) { Predicate.tautology }
|
22
|
+
|
23
|
+
it 'works as expected' do
|
24
|
+
expect(subject.sql).to eql(%Q{SELECT * FROM "items" WHERE true})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'intersect' do
|
29
|
+
let(:predicate) { Predicate.intersect(:x, [1, 2, 3]) }
|
30
|
+
|
31
|
+
it 'works as expected' do
|
32
|
+
expect(subject.sql).to eql(%Q{SELECT * FROM "items" WHERE ("x" && ARRAY[1,2,3])})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'empty' do
|
37
|
+
let(:predicate) { Predicate.empty(:x) }
|
38
|
+
|
39
|
+
it 'works as expected' do
|
40
|
+
expect(subject.sql).to eql(%Q{SELECT * FROM "items" WHERE ("x" = '{}'::varchar[])})
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end if ENV['PREDICATE_PG_URL']
|
44
|
+
end
|
@@ -1,19 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'sequel'
|
3
|
-
require 'sqlite3'
|
4
|
-
require 'predicate/sequel'
|
5
2
|
class Predicate
|
6
3
|
describe ToSequel do
|
7
4
|
|
8
5
|
DB = Sequel.sqlite
|
9
6
|
|
10
7
|
before(:all) do
|
11
|
-
DB
|
12
|
-
primary_key :id
|
13
|
-
String :name
|
14
|
-
String :address
|
15
|
-
Float :price
|
16
|
-
end
|
8
|
+
create_items_database(DB)
|
17
9
|
items = DB[:items]
|
18
10
|
end
|
19
11
|
|
@@ -229,6 +221,5 @@ class Predicate
|
|
229
221
|
expect { subject }.to raise_error(NotSupportedError)
|
230
222
|
end
|
231
223
|
end
|
232
|
-
|
233
224
|
end
|
234
225
|
end
|
data/spec/shared/a_predicate.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,26 @@
|
|
1
1
|
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
-
require
|
2
|
+
require 'rspec'
|
3
|
+
require 'sequel'
|
4
|
+
require 'sqlite3'
|
5
|
+
require 'pg'
|
3
6
|
require 'predicate'
|
7
|
+
require 'predicate/sequel'
|
8
|
+
require 'predicate/postgres'
|
4
9
|
require_relative 'shared/a_predicate'
|
5
10
|
|
11
|
+
Sequel.extension :pg_array, :pg_array_ops
|
6
12
|
module Helpers
|
7
13
|
|
14
|
+
def create_items_database(db)
|
15
|
+
db.drop_table? :items
|
16
|
+
db.create_table :items do
|
17
|
+
primary_key :id
|
18
|
+
String :name
|
19
|
+
String :address
|
20
|
+
Float :price
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
8
24
|
end
|
9
25
|
|
10
26
|
RSpec.configure do |c|
|
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.
|
4
|
+
version: 2.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sexpr
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg
|
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'
|
83
97
|
description: Predicate provides a simple class and processors to encode and manipulate
|
84
98
|
(tuple) predicates
|
85
99
|
email: blambeau@gmail.com
|
@@ -129,6 +143,15 @@ files:
|
|
129
143
|
- lib/predicate/nodes/unary_func.rb
|
130
144
|
- lib/predicate/nodes/var.rb
|
131
145
|
- lib/predicate/placeholder.rb
|
146
|
+
- lib/predicate/postgres.rb
|
147
|
+
- lib/predicate/postgres/ext.rb
|
148
|
+
- lib/predicate/postgres/ext/factory.rb
|
149
|
+
- lib/predicate/postgres/ext/to_sequel.rb
|
150
|
+
- lib/predicate/postgres/pg_array.rb
|
151
|
+
- lib/predicate/postgres/pg_array/empty.rb
|
152
|
+
- lib/predicate/postgres/pg_array/literal.rb
|
153
|
+
- lib/predicate/postgres/pg_array/overlaps.rb
|
154
|
+
- lib/predicate/postgres/rewriter.rb
|
132
155
|
- lib/predicate/processors.rb
|
133
156
|
- lib/predicate/processors/binder.rb
|
134
157
|
- lib/predicate/processors/qualifier.rb
|
@@ -191,6 +214,9 @@ files:
|
|
191
214
|
- spec/nodes/qualified_identifier/test_free_variables.rb
|
192
215
|
- spec/nodes/qualified_identifier/test_name.rb
|
193
216
|
- spec/nodes/qualified_identifier/test_qualifier.rb
|
217
|
+
- spec/postgres/test_factory.rb
|
218
|
+
- spec/postgres/test_to_postgres.rb
|
219
|
+
- spec/postgres/test_to_sequel.rb
|
194
220
|
- spec/predicate/test_and_split.rb
|
195
221
|
- spec/predicate/test_attr_split.rb
|
196
222
|
- spec/predicate/test_bind.rb
|
@@ -222,7 +248,7 @@ homepage: http://github.com/enspirit/predicate
|
|
222
248
|
licenses:
|
223
249
|
- MIT
|
224
250
|
metadata: {}
|
225
|
-
post_install_message:
|
251
|
+
post_install_message:
|
226
252
|
rdoc_options: []
|
227
253
|
require_paths:
|
228
254
|
- lib
|
@@ -237,8 +263,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
237
263
|
- !ruby/object:Gem::Version
|
238
264
|
version: '0'
|
239
265
|
requirements: []
|
240
|
-
|
241
|
-
|
266
|
+
rubyforge_project:
|
267
|
+
rubygems_version: 2.6.14.4
|
268
|
+
signing_key:
|
242
269
|
specification_version: 4
|
243
270
|
summary: Predicate provides a simple class and processors to encode and manipulate
|
244
271
|
(tuple) predicates
|