From efc919421f52acc7c2c74809445c61ef5ada5a10 Mon Sep 17 00:00:00 2001 From: Bernard Lambeau Date: Fri, 24 Apr 2026 11:32:54 +0200 Subject: [PATCH 1/3] Add unit test for to_hashes --- spec/predicate/test_to_hashes.rb | 72 ++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 spec/predicate/test_to_hashes.rb diff --git a/spec/predicate/test_to_hashes.rb b/spec/predicate/test_to_hashes.rb new file mode 100644 index 0000000..88dd55a --- /dev/null +++ b/spec/predicate/test_to_hashes.rb @@ -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 From ef628328611db89cfe954aa911ca7fc10e4457f2 Mon Sep 17 00:00:00 2001 From: Bernard Lambeau Date: Fri, 24 Apr 2026 11:35:56 +0200 Subject: [PATCH 2/3] Add support for compiling `Predicate.native` to Sequel --- CHANGELOG.md | 5 +++ lib/predicate/sequel/to_sequel.rb | 7 +++- spec/sequel/test_to_sequel.rb | 53 ++++++++++++++++++++++--------- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eceaa3..4a749a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.10.0 + +* 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 diff --git a/lib/predicate/sequel/to_sequel.rb b/lib/predicate/sequel/to_sequel.rb index f5179cf..eb256f4 100644 --- a/lib/predicate/sequel/to_sequel.rb +++ b/lib/predicate/sequel/to_sequel.rb @@ -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 diff --git a/spec/sequel/test_to_sequel.rb b/spec/sequel/test_to_sequel.rb index 7368ee8..41d58ae 100644 --- a/spec/sequel/test_to_sequel.rb +++ b/spec/sequel/test_to_sequel.rb @@ -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]) } @@ -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 From 10b5f2731931d16fba70eba9e0d597756a32fbf6 Mon Sep 17 00:00:00 2001 From: Bernard Lambeau Date: Fri, 24 Apr 2026 11:37:08 +0200 Subject: [PATCH 3/3] Bump to 2.10.0 and release --- CHANGELOG.md | 54 ++++++++++++++++++++-------------------- lib/predicate/version.rb | 2 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a749a9..f2b3f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,14 @@ -# 2.10.0 +## 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 +## 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 @@ -18,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 @@ -45,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. @@ -75,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 @@ -113,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). @@ -127,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. @@ -143,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. @@ -177,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 @@ -200,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')` @@ -226,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. diff --git a/lib/predicate/version.rb b/lib/predicate/version.rb index f5a4dd6..d7f21c0 100644 --- a/lib/predicate/version.rb +++ b/lib/predicate/version.rb @@ -1,7 +1,7 @@ class Predicate module Version MAJOR = 2 - MINOR = 9 + MINOR = 10 TINY = 0 end VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"