From 8ab77ef8f278ee2fba7f1910af2f75855f23af36 Mon Sep 17 00:00:00 2001 From: James Sadler Date: Wed, 22 Jul 2026 22:24:41 +1000 Subject: [PATCH] refactor(showcase): convert the healthcare showcase to EQL v3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The showcase's encrypted columns move from the EQL v2 opaque type + per-column search config to EQL v3 self-configuring domain types. - schema.sql: `pii/medication/procedure eql_v2_encrypted` + `eql_v2.add_search_config('ste_vec', ...)` -> `eql_v3_json_search` columns (the searchable encrypted-JSON / SteVec domain). The domain type alone declares the encryption and searchability, so the add_search_config calls are removed. - data.rs::clear(): drop the `eql_v2_configuration` DELETE — v3 has no such config table (self-configuring domains); clearing now just truncates. - Cargo.toml / README.md / CLAUDE.md / model.rs / main.rs diagram: reworded from EQL v2 to v3, incl. a capability -> v3-domain-suffix mapping table in the showcase CLAUDE.md. The demo queries are unchanged: they are plain SQL (->, ->>, @>, <@, jsonb_path_*, comparisons, ORDER BY, aggregates) that Proxy's mapper now rewrites to the v3 functional-index form. End-to-end validation still needs a live Proxy + database with EQL v3 installed. Compiles clean. Refs CIP-3595. Stable-Commit-Id: q-2sptjp0nj4f9wer95m8yfk4nxg --- packages/showcase/CLAUDE.md | 40 +++++++++--- packages/showcase/Cargo.toml | 2 +- packages/showcase/README.md | 24 +++---- packages/showcase/src/data.rs | 20 +----- packages/showcase/src/main.rs | 12 ++-- packages/showcase/src/model.rs | 6 +- packages/showcase/src/schema.sql | 107 +++++++++++++------------------ 7 files changed, 96 insertions(+), 115 deletions(-) diff --git a/packages/showcase/CLAUDE.md b/packages/showcase/CLAUDE.md index 070928a3..4704f37a 100644 --- a/packages/showcase/CLAUDE.md +++ b/packages/showcase/CLAUDE.md @@ -16,13 +16,30 @@ Its intended purposes are: ## Searchable encryption -Searchable encryption allows queries over encrypted data so long as the data has enabled the appropriate encrypted search configuration for a column. - -The following searchable encryption strategies are supported: +Searchable encryption allows queries over encrypted data so long as the column's +type declares the required capability. + +**EQL v3 model — self-configuring domain types.** Unlike EQL v2 (which used an +opaque `eql_v2_encrypted` type plus an `eql_v2.add_search_config` call per +column), EQL v3 gives each `(token type × capability)` combination its own +Postgres domain type over `jsonb`. The column type alone declares both the +encryption and what can be searched — there is no separate config call. The +capability groups below map to v3 domain-name suffixes: + +| Capability (v2 name) | v3 domain suffix | Operations | +|-----------------------|-------------------------------------------|------------| +| match | `_match` (e.g. `eql_v3_text_match`) | `LIKE`/`ILIKE`, `@@` | +| ore / comparison | `_ord` / `_ord_ore` (e.g. `eql_v3_integer_ord`) | `<` `<=` `=` `<>` `>` `>=`, `MIN`/`MAX` | +| unique / equality | `_eq` (e.g. `eql_v3_text_eq`) | `=` `<>` | +| ste_vec (encrypted JSON) | `json_search` (`eql_v3_json_search`) | `->` `->>` `@>` `<@`, `jsonb_path_*` | + +The showcase uses **`eql_v3_json_search`** (the ste_vec/encrypted-JSON domain) +for its encrypted columns. ### match -Provides support for text search operations over encrypted data. Enables use of the SQL `LIKE`, `NOT LIKE`, `ILIKE`, `NOT ILIKE` keywords on encrypted text. +Text search over encrypted text (`_match` domains). Enables `LIKE`, `NOT LIKE`, +`ILIKE`, `NOT ILIKE` and the `@@` fuzzy-match operator. Operators: @@ -30,10 +47,11 @@ Operators: - `!~~` (same as `NOT LIKE`) - `~~*` (same as `ILIKE`) - `!~~*` (same as `NOT LIKE`) +- `@@` (fuzzy match) ### ore -Provides support for compare & equality operators on encrypted data. +Compare & equality operators on encrypted scalars (`_ord` / `_ord_ore` domains). - `<` (less than) - `<=` (less than or equal) @@ -42,13 +60,15 @@ Provides support for compare & equality operators on encrypted data. - `>` (greater than) - `>=` (greater than or equal) -This implies that the built-in SQL functions `MIN` and `MAX` work on encrypted columns when they have ORE enabled. +This implies that the built-in SQL functions `MIN` and `MAX` work on encrypted +columns typed as an ordering domain. ### unique -Provides support for equality testing of encrypted columns (`=`). +Equality testing of encrypted columns (`=`) via an `_eq` domain. -This strategy is poorly named because it does NOT imply that there is a unique constraint. +This capability is historically called "unique" but does NOT imply a unique +constraint. ### ste_vec @@ -70,7 +90,7 @@ Functions: - `jsonb_array_elements` - `jsonb_array_elements_text` -When an `ste_vec` is created for a JSON document it allows the following operations to be performed: +An `eql_v3_json_search` (ste_vec) column allows the following operations to be performed: - Containment operations (`@>` & `<@`) - Fields or array elements extracted using `->` or `json_query_path` @@ -95,7 +115,7 @@ Encrypted columns can only be passed as arguments to a SQL function if the value For example, the SQL `AVG` function cannot be used on encrypted numeric values. But the SQL `MIN` and `MAX` functions can be used on an encrypted value that has an ORE index. -**IMPORTANT: CAST operations cannot work on encrypted data** because casting would require decryption of the encrypted data within the database, which is impossible. When a column has an `ste_vec` configuration, comparison and ordering operations work directly on the encrypted values without requiring CAST operations. +**IMPORTANT: CAST operations cannot work on encrypted data** because casting would require decryption of the encrypted data within the database, which is impossible. When a column is typed as an `eql_v3_json_search` (ste_vec) domain, comparison and ordering operations work directly on the encrypted values without requiring CAST operations. When generating tests, it is important that Claude understands the fundamental limitations of EQL so that it does not generate test cases or example code that can never work. diff --git a/packages/showcase/Cargo.toml b/packages/showcase/Cargo.toml index f84ec750..5881d1ad 100644 --- a/packages/showcase/Cargo.toml +++ b/packages/showcase/Cargo.toml @@ -2,7 +2,7 @@ name = "showcase" version.workspace = true edition.workspace = true -description = "Healthcare data model demonstrating EQL v2 searchable encryption with realistic encrypted application patterns" +description = "Healthcare data model demonstrating EQL v3 searchable encryption with realistic encrypted application patterns" [dependencies] serde = { version = "1.0", features = ["derive"] } diff --git a/packages/showcase/README.md b/packages/showcase/README.md index 92db1e29..6dae109d 100644 --- a/packages/showcase/README.md +++ b/packages/showcase/README.md @@ -1,6 +1,6 @@ -# EQL v2 JSONB Operations Showcase +# EQL v3 JSONB Operations Showcase -A comprehensive demonstration of EQL v2's JSONB support for searchable encryption with healthcare data. +A comprehensive demonstration of EQL v3's JSONB support for searchable encryption with healthcare data. ## Table of Contents @@ -31,7 +31,7 @@ A comprehensive demonstration of EQL v2's JSONB support for searchable encryptio ## Overview -This showcase demonstrates EQL v2's comprehensive support for JSONB operations on encrypted data. All examples use a realistic healthcare database with encrypted patient information, showcasing how applications can query complex nested data while maintaining searchable encryption. +This showcase demonstrates EQL v3's comprehensive support for JSONB operations on encrypted data. All examples use a realistic healthcare database with encrypted patient information, showcasing how applications can query complex nested data while maintaining searchable encryption. **Key Features:** - ✅ All JSONB operators work with encrypted data @@ -45,21 +45,15 @@ This showcase demonstrates EQL v2's comprehensive support for JSONB operations o The healthcare database includes: ```sql --- Patients table with encrypted PII +-- Patients table with encrypted PII. +-- EQL v3 uses self-configuring domain types: `eql_v3_json_search` is the +-- searchable encrypted-JSON (SteVec) domain, so the column type alone declares +-- the encryption and its searchability — no `add_search_config` call is needed. CREATE TABLE patients ( id uuid, - pii eql_v2_encrypted, -- Complex nested JSONB with medical data + pii eql_v3_json_search, -- Complex nested JSONB with medical data PRIMARY KEY(id) ); - --- EQL search configuration for patient data -SELECT eql_v2.add_search_config( - 'patients', - 'pii', - 'ste_vec', - 'jsonb', - '{"prefix": "patients/pii"}' -); ``` ## Test Data Structure @@ -505,4 +499,4 @@ Examples: ⚠️ **Chained Operators**: The `->` operator cannot be chained on `ste_vec` encrypted columns. Use JSONPath functions like `jsonb_path_query_first()` for deep nested access instead. -This showcase proves that EQL v2 provides comprehensive JSONB support for encrypted data, enabling sophisticated healthcare applications while maintaining strong privacy protections. \ No newline at end of file +This showcase proves that EQL v3 provides comprehensive JSONB support for encrypted data, enabling sophisticated healthcare applications while maintaining strong privacy protections. \ No newline at end of file diff --git a/packages/showcase/src/data.rs b/packages/showcase/src/data.rs index a0085c19..4e0a984e 100644 --- a/packages/showcase/src/data.rs +++ b/packages/showcase/src/data.rs @@ -492,25 +492,11 @@ pub async fn insert_test_data() { } pub async fn clear() { - // HAZARD! - // - // Deleting rows from the eql_v2_configuration table is not officially supported due to the risk of data loss. - // - let sql = r#" - DELETE - FROM public.eql_v2_configuration - WHERE - (data -> 'tables') ?| array[ - 'patients', - 'patient_medications', - 'patient_procedures' - ]; - "#; - + // EQL v3 encrypted columns are self-configuring domain types, so there is no + // `eql_v2_configuration` table to clean up (as there was in EQL v2) — clearing + // the demo just truncates the tables. let client = connect_with_tls(PROXY).await; - client.simple_query(sql).await.unwrap(); - let tables = &[ "patient_medications", "patient_procedures", diff --git a/packages/showcase/src/main.rs b/packages/showcase/src/main.rs index ee14785e..63083a1f 100644 --- a/packages/showcase/src/main.rs +++ b/packages/showcase/src/main.rs @@ -6,7 +6,7 @@ * ║ │ medications │ │ procedures │ │ patients │ ║ * ║ │ │ │ │ │ │ ║ * ║ │ id (uuid) PK │ │ id (uuid) PK │ │ id (uuid) PK │ ║ - * ║ │ name (text) │ │ name (text) │ │ pii (eql_v2_enc) │ ║ + * ║ │ name (text) │ │ name (text) │ │ pii (json_search) │ ║ * ║ │ description │ │ description (text) │ │ │ ║ * ║ │ (text) │ │ code (text) │ │ Contains: │ ║ * ║ └─────────────────┘ │ procedure_type │ │ • first_name │ ║ @@ -23,7 +23,7 @@ * ║ │ FK → patients.id │ │ FK → patients.id │ │ │ ║ * ║ │ │ │ │ │ │ ║ * ║ │ medication │ │ procedure │ │ │ ║ - * ║ │ (eql_v2_encrypted) │ │ (eql_v2_encrypted) │ │ │ ║ + * ║ │ (eql_v3_json_search)│ │ (eql_v3_json_search)│ │ │ ║ * ║ │ │ │ │ │ │ ║ * ║ │ Contains: │ │ Contains: │ │ │ ║ * ║ │ • medication_id ────┼───┤ • procedure_id ─────┼───────┤ │ ║ @@ -43,7 +43,7 @@ * ║ • All with CASCADE DELETE for referential integrity │ │ ║ * ║ │ │ ║ * ║ Encryption Details: └─────────────────────┘ ║ - * ║ • PII data in patients.pii is encrypted using EQL v2 ║ + * ║ • PII data in patients.pii is encrypted using EQL v3 ║ * ║ • Junction tables store encrypted procedure/medication details ║ * ║ • Foreign keys enforce referential integrity with CASCADE DELETE ║ * ║ • Reference tables contain plaintext lookup data ║ @@ -66,7 +66,7 @@ use crate::{ #[tokio::main] async fn main() -> Result<(), Box> { - println!("🩺 Healthcare Database Showcase - EQL v2 Searchable Encryption"); + println!("🩺 Healthcare Database Showcase - EQL v3 Searchable Encryption"); println!("============================================================"); trace(); @@ -148,7 +148,7 @@ async fn main() -> Result<(), Box> { println!("\n🎉 === ALL TESTS COMPLETED SUCCESSFULLY! ==="); println!(); println!("🔒 This comprehensive demonstration showcases:"); - println!(" • EQL v2 searchable encryption for sensitive patient data"); + println!(" • EQL v3 searchable encryption for sensitive patient data"); println!(" • All supported JSONB operators: ->, ->>, @>, <@"); println!(" • JSONB functions: jsonb_path_exists, jsonb_path_query_first, jsonb_path_query"); println!(" • Comparison operations on extracted JSONB fields"); @@ -157,7 +157,7 @@ async fn main() -> Result<(), Box> { println!(" • Realistic medical data with nested objects, arrays, and mixed data types"); println!(" • Secure querying of encrypted data while maintaining privacy"); println!(); - println!("✨ EQL v2 provides comprehensive JSONB support for encrypted healthcare data!"); + println!("✨ EQL v3 provides comprehensive JSONB support for encrypted healthcare data!"); Ok(()) } diff --git a/packages/showcase/src/model.rs b/packages/showcase/src/model.rs index 8c241cc9..c38d6f45 100644 --- a/packages/showcase/src/model.rs +++ b/packages/showcase/src/model.rs @@ -58,7 +58,7 @@ impl Procedure { /// Represents a patient in the healthcare system. /// -/// This struct demonstrates the use of EQL v2 encryption for protecting sensitive patient data. +/// This struct demonstrates the use of EQL v3 encryption for protecting sensitive patient data. /// The patient's personally identifiable information (PII) is encrypted to ensure privacy and compliance /// with healthcare regulations like HIPAA. #[derive(Serialize)] @@ -111,7 +111,7 @@ impl Patient { /// Contains personally identifiable information for a patient. /// -/// This data is sensitive and must be encrypted when stored in the database. EQL v2 provides +/// This data is sensitive and must be encrypted when stored in the database. EQL v3 provides /// searchable encryption, allowing healthcare providers to query patient data while maintaining /// strong privacy protections. Enhanced fields are optional to support both basic and complex /// patient records. @@ -279,7 +279,7 @@ pub struct LabResults { /// Represents a medication prescription for a patient. /// /// This struct links patients to their prescribed medications and contains sensitive medical information -/// that requires encryption. The prescription details are stored using EQL v2 encryption to protect +/// that requires encryption. The prescription details are stored using EQL v3 encryption to protect /// patient privacy while enabling necessary medical queries. #[derive(Serialize)] pub struct Prescription { diff --git a/packages/showcase/src/schema.sql b/packages/showcase/src/schema.sql index d5ddafca..d1295d46 100644 --- a/packages/showcase/src/schema.sql +++ b/packages/showcase/src/schema.sql @@ -1,67 +1,48 @@ - -- Patients table with encrypted PII - DROP TABLE IF EXISTS patients CASCADE; - CREATE TABLE patients ( - id uuid, - pii eql_v2_encrypted, - PRIMARY KEY(id) - ); +-- Patients table with encrypted PII. +-- +-- EQL v3 uses self-configuring domain types: `eql_v3_json_search` is the +-- searchable encrypted-JSON (SteVec) domain, so the column type alone declares +-- the encryption and its searchability — there is no separate +-- `eql_v2.add_search_config` call as in EQL v2. +DROP TABLE IF EXISTS patients CASCADE; +CREATE TABLE patients ( + id uuid, + pii eql_v3_json_search, + PRIMARY KEY(id) +); - SELECT eql_v2.add_search_config( - 'patients', - 'pii', - 'ste_vec', - 'jsonb', - '{"prefix": "patients/pii"}' - ); +-- Medications reference table (plaintext) +DROP TABLE IF EXISTS medications CASCADE; +CREATE TABLE medications ( + id uuid, + name text, + description text, + PRIMARY KEY(id) +); - -- Medications reference table (plaintext) - DROP TABLE IF EXISTS medications CASCADE; - CREATE TABLE medications ( - id uuid, - name text, - description text, - PRIMARY KEY(id) - ); +-- Procedures reference table (plaintext) +DROP TABLE IF EXISTS procedures CASCADE; +CREATE TABLE procedures ( + id uuid, + name text, + description text, + code text, + procedure_type text, + PRIMARY KEY(id) +); - -- Procedures reference table (plaintext) - DROP TABLE IF EXISTS procedures CASCADE; - CREATE TABLE procedures ( - id uuid, - name text, - description text, - code text, - procedure_type text, - PRIMARY KEY(id) - ); +-- Patient medications junction table with encrypted details +DROP TABLE IF EXISTS patient_medications CASCADE; +CREATE TABLE patient_medications ( + patient_id uuid, + medication eql_v3_json_search, + FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE +); - -- Patient medications junction table with encrypted details - DROP TABLE IF EXISTS patient_medications CASCADE; - CREATE TABLE patient_medications ( - patient_id uuid, - medication eql_v2_encrypted, - FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE - ); - - SELECT eql_v2.add_search_config( - 'patient_medications', - 'medication', - 'ste_vec', - 'jsonb', - '{"prefix": "patient_medications/medication"}' - ); - - -- Patient procedures junction table with encrypted details - DROP TABLE IF EXISTS patient_procedures CASCADE; - CREATE TABLE patient_procedures ( - patient_id uuid, - procedure eql_v2_encrypted, - FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE - ); - - SELECT eql_v2.add_search_config( - 'patient_procedures', - 'procedure', - 'ste_vec', - 'jsonb', - '{"prefix": "patient_procedures/procedure"}' - ); \ No newline at end of file +-- Patient procedures junction table with encrypted details +DROP TABLE IF EXISTS patient_procedures CASCADE; +CREATE TABLE patient_procedures ( + patient_id uuid, + procedure eql_v3_json_search, + FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE +);