Skip to content
Merged
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
11 changes: 9 additions & 2 deletions src/operators/~~.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
--! Uses bloom filter index terms to test substring containment without decryption.
--! Requires 'match' index configuration on the column.
--!
--! Marked IMMUTABLE so the planner inlines the body and a functional index on
--! `eql_v2.bloom_filter(col)` can match `WHERE eql_v2.like(col, val)`.
--!
--! @param a eql_v2_encrypted Haystack (value to search in)
--! @param b eql_v2_encrypted Needle (pattern to search for)
--! @return Boolean True if bloom filter of a contains bloom filter of b
Expand All @@ -19,9 +22,11 @@
--! @see eql_v2.add_search_config
CREATE FUNCTION eql_v2.like(a eql_v2_encrypted, b eql_v2_encrypted)
RETURNS boolean
LANGUAGE SQL
IMMUTABLE STRICT PARALLEL SAFE
AS $$
SELECT eql_v2.bloom_filter(a) @> eql_v2.bloom_filter(b);
$$ LANGUAGE SQL;
$$;

--! @brief Case-insensitive pattern matching helper
--! @internal
Expand All @@ -39,9 +44,11 @@ $$ LANGUAGE SQL;
--! @see eql_v2.add_search_config
CREATE FUNCTION eql_v2.ilike(a eql_v2_encrypted, b eql_v2_encrypted)
RETURNS boolean
LANGUAGE SQL
IMMUTABLE STRICT PARALLEL SAFE
AS $$
SELECT eql_v2.bloom_filter(a) @> eql_v2.bloom_filter(b);
$$ LANGUAGE SQL;
$$;

--! @brief LIKE operator for encrypted values (pattern matching)
--!
Expand Down
36 changes: 36 additions & 0 deletions tests/sqlx/tests/like_operator_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,39 @@ async fn ilike_operator_case_insensitive_matches(pool: PgPool) -> Result<()> {

Ok(())
}

/// Regression test for issue #189: eql_v2.like / eql_v2.ilike must be IMMUTABLE
/// so the planner inlines them and a functional bloom_filter index can match
/// `WHERE eql_v2.like(col, val)`. Without this, queries silently seq-scan.
#[sqlx::test]
async fn like_and_ilike_are_immutable(pool: PgPool) -> Result<()> {
let sql = "SELECT proname, provolatile::text \
FROM pg_proc \
WHERE pronamespace = 'eql_v2'::regnamespace \
AND proname IN ('like', 'ilike') \
AND pronargs = 2 \
ORDER BY proname";

let rows = sqlx::query(sql)
.fetch_all(&pool)
.await
.context("querying pg_proc for like/ilike volatility")?;

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")?;
let volatility: String = row.try_get("provolatile")?;
assert_eq!(
volatility, "i",
"eql_v2.{} must be IMMUTABLE (provolatile='i') for index inlining; got '{}'",
name, volatility,
);
}

Ok(())
}
Loading