Skip to content
Open
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
10 changes: 10 additions & 0 deletions src/v3/sem/ore_block_u64_8_256/functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,16 @@ $$ LANGUAGE plpgsql;
--! @param b eql_v3.ore_block_u64_8_256_term Second ORE term
--! @return integer -1 if a < b, 0 if a = b, 1 if a > b
--! @throws Exception if ciphertexts are different lengths
--! @note Marked `IMMUTABLE` (the three `compare_ore_block_u64_8_256_term(s)`
--! overloads all are). This deliberately diverges from the v2 originals,
--! which carry no volatility marker and so default to `VOLATILE`. The
--! comparison is deterministic — its only crypto call, pgcrypto `encrypt()`,
--! is itself `IMMUTABLE STRICT PARALLEL SAFE` — so `IMMUTABLE` lets the
--! planner fold/cache these in ordering and index contexts. NOT `STRICT`:
--! the NULL-handling branches below are load-bearing for the array overload.
CREATE FUNCTION eql_v3.compare_ore_block_u64_8_256_term(a eql_v3.ore_block_u64_8_256_term, b eql_v3.ore_block_u64_8_256_term)
RETURNS integer
IMMUTABLE
SET search_path = pg_catalog, extensions, public
AS $$
DECLARE
Expand Down Expand Up @@ -169,6 +177,7 @@ $$ LANGUAGE plpgsql;
--! @return integer -1/0/1, or NULL if either array is NULL
CREATE FUNCTION eql_v3.compare_ore_block_u64_8_256_terms(a eql_v3.ore_block_u64_8_256_term[], b eql_v3.ore_block_u64_8_256_term[])
RETURNS integer
IMMUTABLE
SET search_path = pg_catalog, extensions, public
AS $$
DECLARE
Expand Down Expand Up @@ -208,6 +217,7 @@ $$ LANGUAGE plpgsql;
--! @return integer -1/0/1
CREATE FUNCTION eql_v3.compare_ore_block_u64_8_256_terms(a eql_v3.ore_block_u64_8_256, b eql_v3.ore_block_u64_8_256)
RETURNS integer
IMMUTABLE
SET search_path = pg_catalog, extensions, public
AS $$
BEGIN
Expand Down
10 changes: 5 additions & 5 deletions tests/sqlx/src/fixtures/cipherstash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,18 @@ async fn build_cipher() -> Result<Arc<ScopedCipher<AutoStrategy>>> {
Ok(Arc::new(cipher))
}

/// The single encrypted-payload column name. Single-sourced here so the
/// `ColumnConfig` built for encryption and the `INSERT` target column in the
/// driver cannot drift apart.
pub const PAYLOAD_COLUMN: &str = "payload";

/// Build a `ColumnConfig` from the fixture spec's index list + cast.
///
/// `IndexKind` is a typed enum — every value is a real EQL index by
/// construction, so the mapping is total and `column_config_for` cannot
/// fail on an unknown index name. Extending fixture coverage to a new
/// index is one variant on `IndexKind` plus one arm here, both compile-
/// time checked.
/// The single encrypted-payload column name. Single-sourced here so the
/// `ColumnConfig` built for encryption and the `INSERT` target column in the
/// driver cannot drift apart.
pub const PAYLOAD_COLUMN: &str = "payload";

pub fn column_config_for(spec_indexes: &[IndexKind], cast: Cast) -> Result<ColumnConfig> {
let column_type = cast_to_column_type(cast)?;
let mut config = ColumnConfig::build(PAYLOAD_COLUMN).casts_as(column_type);
Expand Down
43 changes: 43 additions & 0 deletions tests/sqlx/tests/encrypted_domain/family/sem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,46 @@ async fn jsonb_array_to_ore_block_input_shapes(pool: PgPool) -> Result<()> {

Ok(())
}

/// T8 — Volatility pin: all three `compare_ore_block_u64_8_256_term(s)` overloads
/// (term×term, term[]×term[], composite×composite) must be `IMMUTABLE`
/// (`provolatile = 'i'`). This deliberately diverges from the `eql_v2`
/// originals, which carry no marker and default to `VOLATILE`. The comparison
/// is deterministic — pgcrypto `encrypt()` is itself `IMMUTABLE` — and the
/// marker is what lets the planner fold/cache these in ordering/index contexts,
/// so a silent regression to `VOLATILE` (e.g. dropping the keyword on a future
/// edit) must fail CI.
#[sqlx::test]
async fn ore_comparators_are_immutable(pool: PgPool) -> Result<()> {
let rows: Vec<(String, String)> = sqlx::query_as(
r#"
SELECT pg_catalog.pg_get_function_arguments(p.oid) AS args,
p.provolatile::text AS provolatile
FROM pg_catalog.pg_proc p
WHERE p.pronamespace = 'eql_v3'::regnamespace
AND p.proname IN (
'compare_ore_block_u64_8_256_term',
'compare_ore_block_u64_8_256_terms'
)
ORDER BY args
"#,
)
.fetch_all(&pool)
.await?;

// Pin the count so an overload silently disappearing (or a fourth appearing)
// also fails, not just a volatility flip.
assert_eq!(
rows.len(),
3,
"expected exactly 3 compare overloads, found: {rows:?}"
);

for (args, provolatile) in &rows {
assert_eq!(
provolatile, "i",
"compare_ore_block_u64_8_256_term(s)({args}) must be IMMUTABLE, got provolatile={provolatile}"
);
}
Ok(())
}