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
24 changes: 24 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,27 @@ HTML output is also generated in `docs/api/html/` for local preview only.
- Build system uses `tsort` to resolve dependency order
- Supabase build excludes operator classes (not supported)
- **Documentation**: All functions/types must have Doxygen comments (see Documentation Standards above)

### Function Language Choice (SQL vs PL/pgSQL)

Prefer `LANGUAGE SQL` over `LANGUAGE plpgsql` unless you need procedural features.

| Aspect | LANGUAGE SQL | LANGUAGE plpgsql |
|-------------------|-----------------------------------|-------------------------|
| Inlining | ✅ Can be inlined by planner | ❌ Never inlined |
| Call overhead | Lower (can be optimized away) | Higher (context switch) |
| Index performance | Better for GIN index expressions | Worse |
| Control flow | CASE expression | IF/THEN/ELSE |

**Why SQL wins for simple functions:**

1. **Inlining** - PostgreSQL can inline simple SQL functions into the calling query, eliminating function call overhead entirely. PL/pgSQL functions are never inlined.
2. **Index context** - Functions used in index expressions (e.g., `CREATE INDEX ... USING GIN (eql_v2.jsonb_array(col))`) are called on every row insertion/update. Inlining matters.
3. **Simple logic** - A CASE expression is a single statement. PL/pgSQL's procedural features aren't needed.

**When PL/pgSQL is appropriate:**

- Multiple statements with intermediate variables
- Exception handling (`BEGIN...EXCEPTION...END`)
- Complex control flow (loops, early returns)
- Dynamic SQL (`EXECUTE`)
33 changes: 9 additions & 24 deletions src/encrypted/casts.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,10 @@
CREATE FUNCTION eql_v2.to_encrypted(data jsonb)
RETURNS public.eql_v2_encrypted
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE SQL
AS $$
BEGIN
IF data IS NULL THEN
RETURN NULL;
END IF;

RETURN ROW(data)::public.eql_v2_encrypted;
END;
$$ LANGUAGE plpgsql;
SELECT ROW(data)::public.eql_v2_encrypted;
$$;


--! @brief Implicit cast from JSONB to encrypted type
Expand All @@ -49,15 +44,10 @@ CREATE CAST (jsonb AS public.eql_v2_encrypted)
CREATE FUNCTION eql_v2.to_encrypted(data text)
RETURNS public.eql_v2_encrypted
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE SQL
AS $$
BEGIN
IF data IS NULL THEN
RETURN NULL;
END IF;

RETURN eql_v2.to_encrypted(data::jsonb);
END;
$$ LANGUAGE plpgsql;
SELECT eql_v2.to_encrypted(data::jsonb);
$$;


--! @brief Implicit cast from text to encrypted type
Expand All @@ -84,15 +74,10 @@ CREATE CAST (text AS public.eql_v2_encrypted)
CREATE FUNCTION eql_v2.to_jsonb(e public.eql_v2_encrypted)
RETURNS jsonb
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE SQL
AS $$
BEGIN
IF e IS NULL THEN
RETURN NULL;
END IF;

RETURN e.data;
END;
$$ LANGUAGE plpgsql;
SELECT e.data;
$$;

--! @brief Implicit cast from encrypted type to JSONB
--!
Expand Down
38 changes: 7 additions & 31 deletions src/encrypted/compare.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,11 @@
CREATE FUNCTION eql_v2.compare_literal(a eql_v2_encrypted, b eql_v2_encrypted)
RETURNS integer
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE SQL
AS $$
DECLARE
a_data jsonb;
b_data jsonb;
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;

a_data := a.data;
b_data := b.data;

IF a_data < b_data THEN
RETURN -1;
END IF;

IF a_data > b_data THEN
RETURN 1;
END IF;

RETURN 0;
END;
$$ LANGUAGE plpgsql;
SELECT CASE
WHEN a.data < b.data THEN -1
WHEN a.data > b.data THEN 1
ELSE 0
END;
$$;
24 changes: 9 additions & 15 deletions src/encrypted/functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ $$ LANGUAGE plpgsql;
CREATE FUNCTION eql_v2.ciphertext(val eql_v2_encrypted)
RETURNS text
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE SQL
AS $$
BEGIN
RETURN eql_v2.ciphertext(val.data);
END;
$$ LANGUAGE plpgsql;
SELECT eql_v2.ciphertext(val.data);
$$;

--! @brief State transition function for grouped_value aggregate
--! @internal
Expand Down Expand Up @@ -172,14 +171,10 @@ $$ LANGUAGE plpgsql;
CREATE FUNCTION eql_v2.meta_data(val jsonb)
RETURNS jsonb
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE SQL
AS $$
BEGIN
RETURN jsonb_build_object(
'i', val->'i',
'v', val->'v'
);
END;
$$ LANGUAGE plpgsql;
SELECT jsonb_build_object('i', val->'i', 'v', val->'v');
$$;

--! @brief Extract metadata from encrypted column value
--!
Expand All @@ -200,9 +195,8 @@ $$ LANGUAGE plpgsql;
CREATE FUNCTION eql_v2.meta_data(val eql_v2_encrypted)
RETURNS jsonb
IMMUTABLE STRICT PARALLEL SAFE
LANGUAGE SQL
AS $$
BEGIN
RETURN eql_v2.meta_data(val.data);
END;
$$ LANGUAGE plpgsql;
SELECT eql_v2.meta_data(val.data);
$$;