From e3a23e7d169ec9f64abf57ee5b9f8cb908e77d0e Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 6 May 2026 15:26:20 +1000 Subject: [PATCH 1/2] perf: mark eql_v2.like / eql_v2.ilike IMMUTABLE Without an explicit volatility marker, LANGUAGE sql functions default to VOLATILE. The planner refuses to inline volatile calls, so `WHERE eql_v2.like(col, val)` was opaque to a functional bloom_filter index and silently fell back to a sequential scan. Both functions are pure over their inputs (their body wraps `eql_v2.bloom_filter`, which is already IMMUTABLE STRICT PARALLEL SAFE), so inheriting the same profile is correct. Adds a regression test that pins provolatile=i for both functions. Closes #189 --- src/operators/~~.sql | 16 ++++++++++++---- src/operators/~~_test.sql | 31 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/operators/~~.sql b/src/operators/~~.sql index 0af0aa887..8ef0ced81 100644 --- a/src/operators/~~.sql +++ b/src/operators/~~.sql @@ -14,10 +14,15 @@ +-- IMMUTABLE so the planner inlines the body into the query and a functional +-- index on `eql_v2.bloom_filter(col)` can match `WHERE eql_v2.like(col, val)`. CREATE FUNCTION eql_v2.like(a eql_v2_encrypted, b eql_v2_encrypted) -RETURNS boolean AS $$ +RETURNS boolean +LANGUAGE SQL +IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.bloom_filter(a) @> eql_v2.bloom_filter(b); -$$ LANGUAGE SQL; +$$; -- @@ -25,9 +30,12 @@ $$ LANGUAGE SQL; -- Function preserves the SQL semantics -- CREATE FUNCTION eql_v2.ilike(a eql_v2_encrypted, b eql_v2_encrypted) -RETURNS boolean AS $$ +RETURNS boolean +LANGUAGE SQL +IMMUTABLE STRICT PARALLEL SAFE +AS $$ SELECT eql_v2.bloom_filter(a) @> eql_v2.bloom_filter(b); -$$ LANGUAGE SQL; +$$; diff --git a/src/operators/~~_test.sql b/src/operators/~~_test.sql index 6bb054611..1a26a40a0 100644 --- a/src/operators/~~_test.sql +++ b/src/operators/~~_test.sql @@ -105,4 +105,35 @@ $$ LANGUAGE plpgsql; +-- +-- like/ilike must be IMMUTABLE so the planner inlines them into the query +-- and a functional bloom_filter index can match. See issue #189. +-- +DO $$ +DECLARE + vol "char"; + BEGIN + SELECT provolatile INTO vol + FROM pg_proc + WHERE pronamespace = 'eql_v2'::regnamespace + AND proname = 'like' + AND pronargs = 2; + + IF vol <> 'i' THEN + RAISE EXCEPTION 'eql_v2.like must be IMMUTABLE (provolatile=i), got %', vol; + END IF; + + SELECT provolatile INTO vol + FROM pg_proc + WHERE pronamespace = 'eql_v2'::regnamespace + AND proname = 'ilike' + AND pronargs = 2; + + IF vol <> 'i' THEN + RAISE EXCEPTION 'eql_v2.ilike must be IMMUTABLE (provolatile=i), got %', vol; + END IF; + END; +$$ LANGUAGE plpgsql; + + SELECT drop_table_with_encrypted(); \ No newline at end of file From 2e35f0f342ce4da527b9aad65f7a9f7f03bc2a23 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 6 May 2026 16:03:30 +1000 Subject: [PATCH 2/2] style: cargo fmt like_operator_tests --- tests/sqlx/tests/like_operator_tests.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/sqlx/tests/like_operator_tests.rs b/tests/sqlx/tests/like_operator_tests.rs index 3e23cd25a..ad0627274 100644 --- a/tests/sqlx/tests/like_operator_tests.rs +++ b/tests/sqlx/tests/like_operator_tests.rs @@ -178,7 +178,11 @@ async fn like_and_ilike_are_immutable(pool: PgPool) -> Result<()> { .await .context("querying pg_proc for like/ilike volatility")?; - assert_eq!(rows.len(), 2, "expected eql_v2.like and eql_v2.ilike to exist"); + assert_eq!( + rows.len(), + 2, + "expected eql_v2.like and eql_v2.ilike to exist" + ); for row in rows { let name: String = row.try_get("proname")?;