Skip to content
Closed
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: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ Each entry that ships in a published release links to the PR that introduced it.

## [Unreleased]

### Added

- **`eql_v2_int4` variant family — four capability-encoded domain types for encrypted `int4` columns.** Pick the variant whose operator surface matches the index terms your column carries: `eql_v2_int4` (storage only, every operator blocked — carries `c`), `eql_v2_int4_eq` (HMAC equality only — `=`, `<>` — carries `c`, `hm`), `eql_v2_int4_ord_ore` (equality + ORE-block ordering — `=`, `<>`, `<`, `<=`, `>`, `>=` — carries `c`, `ob`), or `eql_v2_int4_ord` (the recommended ordered name; the identical operator surface to `eql_v2_int4_ord_ore`). Each variant exposes a uniform index extractor — `eql_v2.eq_term(col)` for `eql_v2_int4_eq`, `eql_v2.ord_term(col)` for the ordered variants — and no index recipe needs a `::jsonb` cast. Ordered columns share one functional btree across equality and range, `CREATE INDEX ... USING btree (eql_v2.ord_term(col))`, with `ORDER BY eql_v2.ord_term(col)` sorting in plaintext order; `eql_v2_int4_eq` indexes `eql_v2.eq_term(col)` with `USING hash` or `USING btree`. `eql_v2.ord_term` returns the internal `eql_v2.ore_block_u64_8_256` composite, which carries EQL's existing `DEFAULT` btree operator class, so no operator class is defined on the public domain types. The ordered variants do not carry an `hm` term: ORE on a full-domain `int4` is lossless, so the order term doubles as an exact equality term. All variants live in `public` and survive `eql_v2` uninstall. Each domain carries a `CHECK` constraint requiring the EQL envelope (`v`, `i`), the ciphertext (`c`), and the variant's index term(s), so a payload missing a required key is rejected on insert or cast rather than surfacing later at query time. Note: the ORE operator class is excluded from the Supabase build, so ordered `int4` columns fall back to seq-scan for range on Supabase. ([#225](https://github.com/cipherstash/encrypt-query-language/pull/225))

## [2.3.0] — 2026-05-20

`2.3.0` is a breaking release. Customers re-encrypt their data as part of the upgrade — the crypto-side counterpart (`@cipherstash/protect` / `protect-ffi` / proxy) emits a new ste_vec element shape. See [`docs/upgrading/v2.3.md`](docs/upgrading/v2.3.md) for the consolidated upgrade notes.
Expand Down
15 changes: 15 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search
- `src/operators/` - SQL operators for encrypted data comparisons
- `src/config/` - Configuration management functions
- `src/blake3/`, `src/hmac_256/`, `src/bloom_filter/`, `src/ore_*` - Index implementations
- `src/encrypted_domain/` - Encrypted-domain type families (jsonb-backed PostgreSQL domains, one per operator/index capability)
- `tasks/` - mise task scripts
- `tests/sqlx/` - Rust/SQLx test framework (PostgreSQL 14-17 support)
- `release/` - Generated SQL installation files
Expand All @@ -72,6 +73,20 @@ This is the **Encrypt Query Language (EQL)** - a PostgreSQL extension for search
- **Operators**: Support comparisons between encrypted and plain JSONB data
- **CipherStash Proxy**: Required for encryption/decryption operations

### Encrypted-Domain Types

`src/encrypted_domain/` holds **encrypted-domain type families** — jsonb-backed PostgreSQL domains, one domain per operator/index capability (`eql_v2_<T>` storage-only, `eql_v2_<T>_eq`, `eql_v2_<T>_ord`). `eql_v2_int4` (PR #225) is the reference implementation; `int8`, `bool`, `date`, `float`, `numeric`, `timestamp`, and `jsonb` follow the same pattern.

**Adding a new encrypted-domain type: follow `docs/reference/encrypted-domain-implementation-spec.md`.** It is the consolidated spec, checklist, and per-section reference. The mechanics are fixed; per-type judgment calls (variant set, payload terms, ORE lossless vs lossy, native edge semantics) go in a short type-specific design note resolved *first*.

Footguns the spec exists to prevent:

- **Blockers must never be `STRICT`.** A `STRICT` blocker lets PostgreSQL skip the body and return `NULL` on a `NULL` argument, silently bypassing the "operator not supported" exception.
- **No domain-over-domain** (`CREATE DOMAIN a AS b`). Operators resolve against the ultimate base type (`jsonb`), so a derived domain does not inherit the base domain's operator surface — blockers stop engaging.
- **No operator class on a domain.** Index through a functional index on the extractor (`eq_term` / `ord_term`), whose return type already carries a default opclass.
- **Inlinable functions** (extractors, comparison wrappers) need `LANGUAGE sql`, a single-statement `SELECT`, `IMMUTABLE`, and **no `SET` clause** — a pinned `search_path` disables inlining. Allowlist each one in `tasks/pin_search_path.sql` and `tasks/test/splinter.sh`.
- **Build with `mise run clean && mise run build`** — a bare build can leave stale `release/*.sql`.

### Testing Infrastructure
- Tests are written in Rust using SQLx, located in `tests/sqlx/`
- Tests run against PostgreSQL 14, 15, 16, 17 using Docker containers
Expand Down
Loading