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
4 changes: 2 additions & 2 deletions docs/reference/sql-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ When the `ste_vec` index is configured, CipherStash Proxy rewrites these standar
| `jsonb_array_elements(arr)` | `eql_v2.jsonb_array_elements(arr)` | Path must resolve to a JSON array node | Set-returning; yields `eql_v2_encrypted`. |
| `jsonb_array_elements_text(arr)` | `eql_v2.jsonb_array_elements_text(arr)` | Path must resolve to a JSON array node | Set-returning; yields ciphertext as `text`. |
| `COUNT(col)` | plain `count(*)` | — | No encrypted term required. |
| `COUNT(DISTINCT col)` | deterministic dedup | `unique`, `ore`, **or** `ope` on the extracted node | For a JSON leaf, that means Object / Array / Bool / Null (dedup via `b3`) or String / Number (dedup via `ocv`/`ocf`). |
| `MIN(col)` / `MAX(col)` | `eql_v2` ORE/OPE aggregates | `ore`, `ope`, **or** ste_vec-extracted String / Number node | Requires a node that emits `ocv` / `ocf` (or a sibling `ore` / `ope` index). |
| `COUNT(DISTINCT col)` | deterministic dedup | An extracted node that emits `b3`, `ocv`, or `ocf` (or a `unique` / `ore` / `ope` index on the outer column) | A ste_vec-extracted leaf dedups via `b3` (Object / Array / Bool / Null) or `ocv` / `ocf` (String / Number). `ope` is never emitted by ste_vec extraction; it only applies to the outer column. |
| `MIN(col)` / `MAX(col)` | `eql_v2` ORE/OPE aggregates | A ste_vec-extracted String / Number node (`ocv` / `ocf`), **or** a sibling `ore` / `ope` index on the outer column | ste_vec extraction can only produce `ocv` / `ocf` ordering terms. Whole-column ordering uses the outer-column `ore` or `ope` index. |

Additionally, `eql_v2.jsonb_array`, `eql_v2.jsonb_contains`, and `eql_v2.jsonb_contained_by` are EQL helpers (not automatic rewrites) used when building **GIN-indexed** containment queries. See [GIN Indexes for JSONB Containment](./database-indexes.md#gin-indexes-for-jsonb-containment) for the full setup.

Expand Down
26 changes: 10 additions & 16 deletions src/ope_cllw_u64_65/compare.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@
--! their fixed-width CLWW OPE ciphertext index terms. Used internally by range
--! operators (<, <=, >, >=) for order-preserving comparisons without decryption.
--!
--! @param a eql_v2_encrypted First encrypted value to compare
--! @param b eql_v2_encrypted Second encrypted value to compare
--! @param a eql_v2_encrypted First encrypted value to compare (NOT NULL — function is STRICT)
--! @param b eql_v2_encrypted Second encrypted value to compare (NOT NULL — function is STRICT)
--! @return Integer -1 if a < b, 0 if a = b, 1 if a > b
--!
--! @note NULL values are sorted before non-NULL values
--! @note Declared STRICT, so NULL function inputs short-circuit to NULL before
--! the body runs. The internal `a_term IS NULL` / `b_term IS NULL`
--! branches are NOT redundant with STRICT — they handle the case where
--! a non-NULL `eql_v2_encrypted` payload simply lacks the `opf` field
--! (i.e. `has_ope_cllw_u64_65` returned false). A NULL term sorts before
--! a present term, mirroring the defensive pattern used in
--! compare_ore_block_u64_8_256.
--! @note OPE ciphertexts compare via standard lexicographic bytea ordering —
--! no custom per-byte protocol required (unlike the ORE CLWW variants)
--! no custom per-byte protocol required (unlike the ORE CLLW variants).
--!
--! @see eql_v2.ope_cllw_u64_65
--! @see eql_v2.has_ope_cllw_u64_65
Expand All @@ -27,18 +33,6 @@ AS $$
a_term eql_v2.ope_cllw_u64_65;
b_term eql_v2.ope_cllw_u64_65;
BEGIN
IF a IS NULL AND b IS NULL THEN
Comment thread
freshtonic marked this conversation as resolved.
RETURN 0;
END IF;

IF a IS NULL THEN
RETURN -1;
END IF;

IF b IS NULL THEN
RETURN 1;
END IF;

IF eql_v2.has_ope_cllw_u64_65(a) THEN
a_term := eql_v2.ope_cllw_u64_65(a);
END IF;
Expand Down
30 changes: 10 additions & 20 deletions src/ope_cllw_u64_65/functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
--! Extracts the fixed-width CLWW OPE ciphertext from the 'opf' field of an
--! encrypted data payload. Used internally for range query comparisons.
--!
--! @param jsonb containing encrypted EQL payload
--! @param val jsonb encrypted EQL payload
--! @return eql_v2.ope_cllw_u64_65 CLWW OPE ciphertext
--! @throws Exception if 'opf' field is missing when ope index is expected
--!
Expand All @@ -19,10 +19,6 @@ CREATE FUNCTION eql_v2.ope_cllw_u64_65(val jsonb)
IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
IF val IS NULL THEN
RETURN NULL;
END IF;

IF NOT (eql_v2.has_ope_cllw_u64_65(val)) THEN
RAISE 'Expected a ope_cllw_u64_65 index (opf) value in json: %', val;
END IF;
Expand All @@ -37,53 +33,47 @@ $$ LANGUAGE plpgsql;
--! Extracts the fixed-width CLWW OPE ciphertext from an encrypted column value
--! by accessing its underlying JSONB data field.
--!
--! @param eql_v2_encrypted Encrypted column value
--! @param val eql_v2_encrypted Encrypted column value
--! @return eql_v2.ope_cllw_u64_65 CLWW OPE ciphertext
--!
--! @see eql_v2.ope_cllw_u64_65(jsonb)
CREATE FUNCTION eql_v2.ope_cllw_u64_65(val eql_v2_encrypted)
RETURNS eql_v2.ope_cllw_u64_65
IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
RETURN (SELECT eql_v2.ope_cllw_u64_65(val.data));
END;
$$ LANGUAGE plpgsql;
SELECT eql_v2.ope_cllw_u64_65(val.data);
$$ LANGUAGE sql;


--! @brief Check if JSONB payload contains CLWW OPE index term
--!
--! Tests whether the encrypted data payload includes an 'opf' field,
--! indicating a fixed-width CLWW OPE ciphertext is available for range queries.
--!
--! @param jsonb containing encrypted EQL payload
--! @param val jsonb encrypted EQL payload
--! @return Boolean True if 'opf' field is present and non-null
--!
--! @see eql_v2.ope_cllw_u64_65
CREATE FUNCTION eql_v2.has_ope_cllw_u64_65(val jsonb)
RETURNS boolean
IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
RETURN val ->> 'opf' IS NOT NULL;
END;
$$ LANGUAGE plpgsql;
SELECT val ->> 'opf' IS NOT NULL;
$$ LANGUAGE sql;


--! @brief Check if encrypted column value contains CLWW OPE index term
--!
--! Tests whether an encrypted column value includes a fixed-width CLWW OPE
--! ciphertext by checking its underlying JSONB data field.
--!
--! @param eql_v2_encrypted Encrypted column value
--! @param val eql_v2_encrypted Encrypted column value
--! @return Boolean True if CLWW OPE ciphertext is present
--!
--! @see eql_v2.has_ope_cllw_u64_65(jsonb)
CREATE FUNCTION eql_v2.has_ope_cllw_u64_65(val eql_v2_encrypted)
RETURNS boolean
IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
RETURN eql_v2.has_ope_cllw_u64_65(val.data);
END;
$$ LANGUAGE plpgsql;
SELECT eql_v2.has_ope_cllw_u64_65(val.data);
$$ LANGUAGE sql;
4 changes: 2 additions & 2 deletions src/ope_cllw_u64_65/types.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
--! @brief CLWW OPE index term type for fixed-width numeric range queries
--!
--! Composite type for CLWW (Chenette, Lewi, Weis, Wu) Order-Preserving Encryption
--! over 64-bit integers. Ciphertexts are 65 bytes (8 bytes per plaintext bit plus
--! one reserved carry byte).
--! over 64-bit integers. Ciphertexts are 65 bytes (8 bytes per plaintext byte,
--! plus one reserved carry byte).
--!
--! Ciphertexts compare with **standard lexicographic byte ordering** — unlike
--! the ORE variants there is no custom per-byte compare protocol. The ciphertext
Expand Down
26 changes: 10 additions & 16 deletions src/ope_cllw_var_8/compare.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@
--! range operators (<, <=, >, >=) for order-preserving comparisons without
--! decryption.
--!
--! @param a eql_v2_encrypted First encrypted value to compare
--! @param b eql_v2_encrypted Second encrypted value to compare
--! @param a eql_v2_encrypted First encrypted value to compare (NOT NULL — function is STRICT)
--! @param b eql_v2_encrypted Second encrypted value to compare (NOT NULL — function is STRICT)
--! @return Integer -1 if a < b, 0 if a = b, 1 if a > b
--!
--! @note NULL values are sorted before non-NULL values
--! @note Declared STRICT, so NULL function inputs short-circuit to NULL before
--! the body runs. The internal `a_term IS NULL` / `b_term IS NULL`
--! branches are NOT redundant with STRICT — they handle the case where
--! a non-NULL `eql_v2_encrypted` payload simply lacks the `opv` field
--! (i.e. `has_ope_cllw_var_8` returned false). A NULL term sorts before
--! a present term, mirroring the defensive pattern used in
--! compare_ore_block_u64_8_256.
--! @note OPE ciphertexts compare via standard lexicographic bytea ordering —
--! bytea compare handles variable-length inputs (shorter prefix is less)
--! bytea compare handles variable-length inputs (shorter prefix is less).
--!
--! @see eql_v2.ope_cllw_var_8
--! @see eql_v2.has_ope_cllw_var_8
Expand All @@ -28,18 +34,6 @@ AS $$
a_term eql_v2.ope_cllw_var_8;
b_term eql_v2.ope_cllw_var_8;
BEGIN
IF a IS NULL AND b IS NULL THEN
RETURN 0;
END IF;

IF a IS NULL THEN
RETURN -1;
END IF;

IF b IS NULL THEN
RETURN 1;
END IF;

IF eql_v2.has_ope_cllw_var_8(a) THEN
a_term := eql_v2.ope_cllw_var_8(a);
END IF;
Expand Down
30 changes: 10 additions & 20 deletions src/ope_cllw_var_8/functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
--! Extracts the variable-width CLWW OPE ciphertext from the 'opv' field of an
--! encrypted data payload. Used internally for range query comparisons.
--!
--! @param jsonb containing encrypted EQL payload
--! @param val jsonb encrypted EQL payload
--! @return eql_v2.ope_cllw_var_8 Variable-width CLWW OPE ciphertext
--! @throws Exception if 'opv' field is missing when ope index is expected
--!
Expand All @@ -19,10 +19,6 @@ CREATE FUNCTION eql_v2.ope_cllw_var_8(val jsonb)
IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
IF val IS NULL THEN
RETURN NULL;
END IF;

IF NOT (eql_v2.has_ope_cllw_var_8(val)) THEN
RAISE 'Expected a ope_cllw_var_8 index (opv) value in json: %', val;
END IF;
Expand All @@ -37,53 +33,47 @@ $$ LANGUAGE plpgsql;
--! Extracts the variable-width CLWW OPE ciphertext from an encrypted column value
--! by accessing its underlying JSONB data field.
--!
--! @param eql_v2_encrypted Encrypted column value
--! @param val eql_v2_encrypted Encrypted column value
--! @return eql_v2.ope_cllw_var_8 Variable-width CLWW OPE ciphertext
--!
--! @see eql_v2.ope_cllw_var_8(jsonb)
CREATE FUNCTION eql_v2.ope_cllw_var_8(val eql_v2_encrypted)
RETURNS eql_v2.ope_cllw_var_8
IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
RETURN (SELECT eql_v2.ope_cllw_var_8(val.data));
END;
$$ LANGUAGE plpgsql;
SELECT eql_v2.ope_cllw_var_8(val.data);
$$ LANGUAGE sql;


--! @brief Check if JSONB payload contains variable-width CLWW OPE index term
--!
--! Tests whether the encrypted data payload includes an 'opv' field,
--! indicating a variable-width CLWW OPE ciphertext is available for range queries.
--!
--! @param jsonb containing encrypted EQL payload
--! @param val jsonb encrypted EQL payload
--! @return Boolean True if 'opv' field is present and non-null
--!
--! @see eql_v2.ope_cllw_var_8
CREATE FUNCTION eql_v2.has_ope_cllw_var_8(val jsonb)
RETURNS boolean
IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
RETURN val ->> 'opv' IS NOT NULL;
END;
$$ LANGUAGE plpgsql;
SELECT val ->> 'opv' IS NOT NULL;
$$ LANGUAGE sql;


--! @brief Check if encrypted column value contains variable-width CLWW OPE index term
--!
--! Tests whether an encrypted column value includes a variable-width CLWW OPE
--! ciphertext by checking its underlying JSONB data field.
--!
--! @param eql_v2_encrypted Encrypted column value
--! @param val eql_v2_encrypted Encrypted column value
--! @return Boolean True if variable-width CLWW OPE ciphertext is present
--!
--! @see eql_v2.has_ope_cllw_var_8(jsonb)
CREATE FUNCTION eql_v2.has_ope_cllw_var_8(val eql_v2_encrypted)
RETURNS boolean
IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
RETURN eql_v2.has_ope_cllw_var_8(val.data);
END;
$$ LANGUAGE plpgsql;
SELECT eql_v2.has_ope_cllw_var_8(val.data);
$$ LANGUAGE sql;
16 changes: 4 additions & 12 deletions src/operators/order_by.sql
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,12 @@ $$ LANGUAGE plpgsql;
CREATE FUNCTION eql_v2.order_by_ope(a eql_v2_encrypted)
RETURNS bytea
IMMUTABLE STRICT PARALLEL SAFE
SET search_path = pg_catalog, extensions, public
AS $$
BEGIN
IF eql_v2.has_ope_cllw_u64_65(a) THEN
RETURN (eql_v2.ope_cllw_u64_65(a)).bytes;
END IF;

IF eql_v2.has_ope_cllw_var_8(a) THEN
RETURN (eql_v2.ope_cllw_var_8(a)).bytes;
END IF;

RETURN NULL;
SELECT CASE
WHEN eql_v2.has_ope_cllw_u64_65(a) THEN (eql_v2.ope_cllw_u64_65(a)).bytes
WHEN eql_v2.has_ope_cllw_var_8(a) THEN (eql_v2.ope_cllw_var_8(a)).bytes
END;
$$ LANGUAGE plpgsql;
$$ LANGUAGE sql;



Loading