Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 31 additions & 26 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# 2.9.0 - 2025-04-03
## 2.10.0 - 2026-04-24

* Add support for compiling `Predicate.native` to Sequel, if the
operand respond to :sql or :sql_literal

## 2.9.0 - 2025-04-03

* Add Predicate#to_hashes that returns a pair of positive and
negative hashes. Generalization of to_hash where neq are used.

# 2.8.0 - 2023-06-09
## 2.8.0 - 2023-06-09

* BREAKING: removed support for ruby < 2.7 and upgraded sexpr
to 1.1.x
Expand All @@ -13,18 +18,18 @@
Implementation is fuly functional, yet error messages will
be improved in the future by a better usage of minitest itself.

# 2.7.1 - 2022-04-21
## 2.7.1 - 2022-04-21

* Add shadow support for Exists tree nodes, that are used by
Bmg for some `restrict -> WHERE` translations.

# 2.7.0 - 2022-02-10
## 2.7.0 - 2022-02-10

* Add (experimental) support for translation of `empty` and
`intersect` on arrays to usage of PostgreSQL's `pg_array`
operators (`overlaps` in particular).

# 2.6.0 - 2021-12-11
## 2.6.0 - 2021-12-11

* Upgraded `sexpr` to 1.0

Expand All @@ -40,7 +45,7 @@
Predicate.h(:x => [2,3], ...) # in(:x, [2,2]) & ...
Predicate.h(:x => /a-z/, ...) # match(:x, /a-z/) & ...

# 2.5.0 - 2020-12-30
## 2.5.0 - 2020-12-30

* Add `Predicate.dsl` for building complex expressions without having
to prefix factory methods with `Predicate.` all the time.
Expand Down Expand Up @@ -70,31 +75,31 @@
* Enhanced README, specified the public API, add jeny code blocks to
help contributors providing new predicates.

# 2.4.0 / 2020-07-23
## 2.4.0 / 2020-07-23

* Add Predicate#to_hash that allows getting back a Hash object
representing the same predicate as of `Predicate.coerce` semantics.
The method raises an ArgumentError if the predicate cannot be
simplified so as to preserve the semantics.

# 2.3.3 / 2020-07-08
## 2.3.3 / 2020-07-08

* Add Predicate#unqualify that transforms all qualified identifiers to
normal identifiers. The resulting predicate might not be semantically
equivalent.

# 2.3.2 / 2020-07-08
## 2.3.2 / 2020-07-08

* Fix Predicate#& when using qualified identifier. The qualifier was not
correctly taken into account, yielding conjunctions wrongly loosing
terms.

# 2.3.1 / 2020-04-29
## 2.3.1 / 2020-04-29

* Fix #eq against #in having a placeholder. Yields a undefined method
`include?`.

# 2.3.0 / 2020-04-20
## 2.3.0 / 2020-04-20

* Add an experimental support for literal placeholders. The aim is to let
build complex expressions with unknown literals, to be bound later using
Expand All @@ -108,12 +113,12 @@

* Fix Predicate.intersect having wrong #constants_variables logic.

# 2.2.1 / 2020-01-21
## 2.2.1 / 2020-01-21

* Fix `in(:x, [2, 3]) & eq(:x, 1)` begin wrongly optimized as `eq(:x, 1)`
while it must yield a contradiction.

# 2.2.0 / 2019-06-07
## 2.2.0 / 2019-06-07

* Fix SQL compilation of `Predicate#in` where the list of values
contains nil, including edge cases (e.g. where only nil is present).
Expand All @@ -122,13 +127,13 @@
* Add `Predicate#call` alias to `Predicate#evaluate` in order to let
client write simpler expressions.

# 2.1
## 2.1

* Introduction of an `opaque` node kind to help with pseudo-literals
used in IN expressions. IN expressions now accept any right term,
instead of an array of values.

# 2.0 - 2018-05-28
## 2.0 - 2018-05-28

* Add `Predicate#match` to match attributes against strings and
regular expressions.
Expand All @@ -138,25 +143,25 @@
also because they tend to be dangerous to use from a security point
of view (allowing end user code injection).

# 1.3.4 / 2018-03-30
## 1.3.4 / 2018-03-30

* `Predicate.in` now returns a contradiction when the set of values
is known to be empty.

# 1.3.3 / 2018-03-16
## 1.3.3 / 2018-03-16

* Add `Predicate#to_s` and `#inspect` with a more readable predicate
representation.

# 1.3.2 / 2018-03-14
## 1.3.2 / 2018-03-14

* Fix `#evaluate` on `Predicate.in`.

# 1.3.1 / 2018-03-13
## 1.3.1 / 2018-03-13

* Fix `#evaluate` on `Predicate.intersect`.

# 1.3 / 2018-03-13
## 1.3 / 2018-03-13

* Add various & optimizations, typically those yielding tautologies
and contradictions.
Expand All @@ -172,7 +177,7 @@
* Changed `Predicate#evaluate` to avoid relying on an unsafe ruby
code generation.

# 1.2 / 2018-03-09
## 1.2 / 2018-03-09

* Add `Predicate#intersect` that has same limitations than `#in`
(only `identifier OP values` is supported) but an array/set
Expand All @@ -195,22 +200,22 @@
is a Sequel compatible literal, that is, an object responding to
`:sql_literal`.

# 1.1.3 / 2018-03-07
## 1.1.3 / 2018-03-07

* Document `and_split` and review all actual implementations to make sure
of their correctness. Let Native implement the specification without
throwing a NotSupportedError.

# 1.1.2 / 2018-03-06
## 1.1.2 / 2018-03-06

* Fix error raised by Sequel when trying to compile a Native predicate.
Predicate::NotSupportedError must be raised, not NotImplementedError.

# 1.1.1 / 2018-03-03
## 1.1.1 / 2018-03-03

* Removed unnecessary & unused 'path' dependency.

# 1.1.0 / 2018-03-03
## 1.1.0 / 2018-03-03

* Adds `Predicate.from_hash(x: 12, y: ['foo', 'bar'])`, also supported
by `Predicate.coerce(...)`, with `x = 12 and y in ('foo','bar')`
Expand All @@ -221,7 +226,7 @@
must be required by the user. `Predicate.to_sequel` is only available
if `require 'predicate/sequel'` is done first.

# 1.0.0 / 2018-03-03
## 1.0.0 / 2018-03-03

Predicate 1.0.0, extracted & refactored from alf-core.

Expand Down
7 changes: 6 additions & 1 deletion lib/predicate/sequel/to_sequel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,16 @@ def on_opaque(sexpr)
raise Error, "Unable to compile #{sexpr} to Sequel"
end

def on_native(sexpr)
return sexpr.last.sql_literal if sexpr.last.respond_to?(:sql_literal)
return sexpr.last.sql if sexpr.last.respond_to?(:sql)
on_unsupported(sexpr)
end

def on_unsupported(sexpr)
raise NotSupportedError, "Unsupported predicate #{sexpr}"
end
alias :on_var :on_unsupported
alias :on_native :on_unsupported
alias :on_intersect :on_unsupported
alias :on_subset :on_unsupported
alias :on_superset :on_unsupported
Expand Down
2 changes: 1 addition & 1 deletion lib/predicate/version.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Predicate
module Version
MAJOR = 2
MINOR = 9
MINOR = 10
TINY = 0
end
VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
Expand Down
72 changes: 72 additions & 0 deletions spec/predicate/test_to_hashes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require 'spec_helper'
class Predicate
describe Predicate, "to_hashes" do

let(:p){ Predicate }

subject{ predicate.to_hashes }

context "tautology" do
let(:predicate){ Predicate.tautology }

it{ expect(subject).to eql([{},{}]) }
end

context "contradiction" do
let(:predicate){ Predicate.contradiction }

it{ expect{ subject }.to raise_error(ArgumentError) }
end

context "eq" do
let(:predicate){ Predicate.eq(:x, 2) }

it{ expect(subject).to eql([{x: 2}, {}]) }
end

context "neq" do
let(:predicate){ Predicate.neq(:x, 2) }

it{ expect(subject).to eql([{},{x: 2}]) }
end

context "in" do
let(:predicate){ Predicate.in(:x, [2,3]) }

it{ expect(subject).to eql([{x: [2,3]},{}]) }
end

context "and" do
let(:predicate){ Predicate.eq(:x, 3) & Predicate.in(:y, [2,3]) }

it{ expect(subject).to eql([{x: 3, y: [2,3]},{}]) }
end

context "and with a neq" do
let(:predicate){ Predicate.neq(:x, 3) & Predicate.in(:y, [2,3]) }

it{ expect(subject).to eql([{y: [2,3]},{x: 3}]) }
end

context "not nil & eq" do
let(:predicate){ Predicate.neq(:x, nil) & Predicate.eq(:x, [2,3]) }

it{ expect(subject).to eql([{x: [2,3]},{x: nil}]) }
end

## unsupported

context "not" do
let(:predicate){ Predicate.not(Predicate.eq(:x, 2)) }

it{ expect{ subject }.to raise_error(ArgumentError) }
end

context "not-and" do
let(:predicate){ Predicate.not(Predicate.eq(:x, 3) & Predicate.in(:y, [2,3])) }

it{ expect{ subject }.to raise_error(ArgumentError) }
end

end
end
53 changes: 38 additions & 15 deletions spec/sequel/test_to_sequel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,6 @@ class Predicate
end
end

context 'in with something responding to sql_literal' do
let(:operand){
Object.new.tap{|o|
def o.sql_literal(db)
"Hello World"
end
}
}
let(:predicate) { Predicate.in(:price, Predicate.opaque(operand)) }

it 'works as expected' do
expect(subject).to eql("SELECT * FROM `items` WHERE (`price` IN (Hello World))")
end
end

context 'not' do
let(:predicate) { !Predicate.in(:price, [10.0, 17.99]) }

Expand Down Expand Up @@ -220,5 +205,43 @@ def o.sql_literal(db)
expect { subject }.to raise_error(NotSupportedError)
end
end

class SqlAble
def initialize(value)
@value = value
end

def sql(*args, &bl)
@value
end
alias :sql_literal :sql
end

context 'opaque in an IN' do
let(:operand) {
SqlAble.new('Hello World')
}
let(:predicate) {
Predicate.in(:price, Predicate.opaque(operand))
}

it 'works as expected' do
expect(subject).to eql("SELECT * FROM `items` WHERE (`price` IN (Hello World))")
end
end

context 'native' do
let(:operand) {
SqlAble.new(Sequel.lit("1=1"))
}
let(:predicate) {
Predicate.native(operand)
}

it 'works as expected' do
expect(subject).to eql("SELECT * FROM `items` WHERE (1=1)")
end
end

end
end
Loading