diff --git a/content/docs/reference/eql/meta.json b/content/docs/reference/eql/meta.json index f4268ce..76a855b 100644 --- a/content/docs/reference/eql/meta.json +++ b/content/docs/reference/eql/meta.json @@ -14,6 +14,8 @@ "filtering", "sorting", "grouping-and-aggregates", - "joins" + "joins", + "---Previous versions---", + "v2" ] } diff --git a/content/docs/reference/eql/v2/index.mdx b/content/docs/reference/eql/v2/index.mdx new file mode 100644 index 0000000..d25131a --- /dev/null +++ b/content/docs/reference/eql/v2/index.mdx @@ -0,0 +1,122 @@ +--- +title: EQL v2 +description: PostgreSQL types, operators, and functions for querying encrypted data with EQL v2 (v2.2) — the eql_v2_encrypted type and searchable index types. +type: reference +components: [eql] +verifiedAgainst: + eql: "2.2" +--- + + +This section documents **EQL v2** (v2.2), the release existing CipherStash deployments run. Starting a new project? Use **EQL v3** — see the [EQL reference](/reference/eql). + + +**Encrypt Query Language (EQL)** is a set of PostgreSQL types, operators, and functions that enable queries on encrypted data without decryption. EQL works seamlessly with the [CipherCell](/reference/eql/v2/payload) format to provide searchable encryption capabilities directly in PostgreSQL. + +## What is EQL? + +EQL provides the database-side components needed to query encrypted data. Unlike traditional PostgreSQL extensions, EQL is implemented as a collection of types, operators, and functions, making it compatible with managed database providers like AWS RDS that restrict extension installation. + +When combined with the [Encryption SDK](/stack/cipherstash/encryption) or [CipherStash Proxy](/stack/cipherstash/proxy), EQL enables: + +- **Exact match queries** using encrypted equality operators +- **Range queries** with order-preserving encryption +- **Pattern matching** using encrypted Bloom filters +- **Unique constraints** on encrypted columns +- **JSON/JSONB operations** on encrypted structured data + +## Core components + +### The `eql_v2_encrypted` type + +The foundation of EQL is the `eql_v2_encrypted` data type, which stores [CipherCells](/reference/eql/v2/payload) containing encrypted data and searchable encrypted metadata. + +```sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email eql_v2_encrypted, + name eql_v2_encrypted +); +``` + + +The `eql_v2_encrypted` type is required for searchable encryption in PostgreSQL. Regular `JSON` or `JSONB` types can store CipherCells but do not support encrypted queries. + + +### Operators + +EQL provides PostgreSQL operators that work directly with encrypted data: + +```sql +-- Exact match +SELECT * FROM users WHERE email = 'encrypted_search_value'::eql_v2_encrypted; + +-- Range queries +SELECT * FROM products WHERE price > 'encrypted_value'::eql_v2_encrypted; + +-- Pattern matching +SELECT * FROM documents WHERE content LIKE '%encrypted_pattern%'; +``` + +### Functions + +EQL ships in the `eql_v2` schema, with functions in three groups: + +- **Configuration** — register tables, columns, and searchable indexes in the EQL configuration. +- **Index-term extraction** — `eql_v2.hmac_256()` (exact match), `eql_v2.bloom_filter()` (pattern matching), and `eql_v2.ore_block_u64_8_256()` (range) extract a searchable term from an encrypted value. These back the functional indexes — see [Setting up indexes](/reference/eql/v2/indexes). +- **Comparison** — operators (`=`, `<`, `LIKE`, `@>`, …) are the query surface over encrypted columns. + +For the complete, per-version function reference — every signature, parameter, and return type — see the [EQL API reference](/stack/reference/eql/), generated from each EQL release. + +## Index types + +EQL supports multiple searchable encryption index types. Each index type enables different query patterns: + +### `unique`: Exact match + +Enables exact equality queries and unique constraints using HMAC-SHA256. + +[Learn more about exact indexes](/stack/cipherstash/encryption/searchable-encryption#exact-match) + +### `ore`: Range queries + +Enables range comparisons (`<`, `>`, `BETWEEN`) and ordering (`ORDER BY`) using Order Revealing Encryption. + +[Learn more about range indexes](/stack/cipherstash/encryption/searchable-encryption#range--order) + +### `match`: Pattern matching + +Enables substring and full-text search (`LIKE`, `ILIKE`) using encrypted Bloom filters with trigrams. + +[Learn more about match indexes](/stack/cipherstash/encryption/searchable-encryption#match-pattern) + +### `ste_vec`: Structured data + +Enables containment queries and JSON-style operations on encrypted arrays and JSONB data. + +## How it works + +EQL leverages PostgreSQL's native indexing capabilities to enable efficient queries on encrypted data. The searchable encrypted metadata within [CipherCells](/reference/eql/v2/payload) is indexed using standard PostgreSQL index types (B-tree for exact/range, GIN for pattern matching). + +When a query is executed: + +1. **Client-side**: The application encrypts the search value using the same encryption scheme, producing a CipherCell with the appropriate searchable encrypted metadata +2. **Database-side**: EQL operators extract and compare the searchable encrypted metadata from both the stored CipherCells and the search CipherCell +3. **Result**: Matching rows are returned without ever decrypting the data in the database + +## Compatibility + +EQL is designed to work with: + +- **PostgreSQL 14+**: Full support for all EQL features +- **Managed databases**: Works with AWS RDS, Azure Database, Google Cloud SQL, and other managed PostgreSQL providers +- **CipherStash SDKs**: Integrates with all CipherStash SDKs as well as CipherStash Proxy + + +Unlike PostgreSQL extensions that require `CREATE EXTENSION`, EQL types and functions are installed directly into your database schema, making it compatible with managed database environments that restrict extension installation. + + +## Related documentation + +- [CipherCell format](/reference/eql/v2/payload): The data structure used by EQL +- [Supported queries](/stack/cipherstash/encryption/searchable-encryption): Available searchable encryption schemes diff --git a/content/docs/reference/eql/v2/indexes.mdx b/content/docs/reference/eql/v2/indexes.mdx new file mode 100644 index 0000000..6a84ca4 --- /dev/null +++ b/content/docs/reference/eql/v2/indexes.mdx @@ -0,0 +1,154 @@ +--- +title: Setting up indexes +description: Create PostgreSQL indexes for encrypted columns. Index syntax differs between self-hosted PostgreSQL and managed databases like Supabase. +type: reference +components: [eql] +verifiedAgainst: + eql: "2.2" +--- + + +EQL v2 reference, for existing v2.2 deployments. New projects use [EQL v3](/reference/eql). + + +Encrypted columns need PostgreSQL indexes for fast queries. Without an index, the database performs a sequential scan: correct but slow at scale. + +Index syntax differs between deployment types. Self-hosted PostgreSQL with full EQL installed supports custom operator classes and can use B-tree indexes directly on `eql_v2_encrypted` columns. Managed databases like Supabase cannot install operator families (they require superuser), so indexes must use extraction functions instead. + +## Deployment matrix + +| Query type | Self-hosted (full EQL) | Supabase | +|---|---|---| +| Equality | `USING btree (col)` with opclass, or `USING hash (eql_v2.hmac_256(col))` | `USING hash (eql_v2.hmac_256(col))` only | +| Range / ORDER BY | `USING btree (col)` with opclass | None (OPE-index work in progress) | +| Pattern match | `USING gin (eql_v2.bloom_filter(col))` | Same | +| JSONB containment | `USING gin (eql_v2.ste_vec(col))` | Same | + + + Range filters (`>`, `>=`, `<`, `<=`) work on Supabase without a range index (they use a sequential scan). `ORDER BY` on encrypted columns is not supported on Supabase at all. Sort application-side after decrypting results. Operator family support for Supabase is in development. + + +--- + +## Equality + +Equality indexes speed up `WHERE col = $1` queries and `IN` lists. + +**Self-hosted (B-tree with operator class):** + +```sql +CREATE INDEX ON users USING btree (email); +``` + +This works because the full EQL install registers a B-tree operator class for `eql_v2_encrypted` that compares HMAC terms. + +**Self-hosted or Supabase (hash on extraction function):** + +```sql +CREATE INDEX ON users USING hash (eql_v2.hmac_256(email)); +``` + +This form works on both deployment types. Use it when you want one index that works everywhere, or when you are on Supabase. + +See queries: [Equality queries](/reference/eql/v2/queries#equality) + +--- + +## Match + +Match indexes speed up `WHERE col LIKE $1` and `ILIKE` queries. They use a GIN index on the Bloom filter extracted from each encrypted value. + +```sql +CREATE INDEX ON users USING gin (eql_v2.bloom_filter(name)); +``` + +This form is identical for self-hosted and Supabase. + +See queries: [Match queries](/reference/eql/v2/queries#match-free-text) + +--- + +## Range and order + +Range indexes support `>`, `>=`, `<`, `<=`, `BETWEEN`, and `ORDER BY` on encrypted columns. + +**Self-hosted (B-tree with operator class):** + +```sql +CREATE INDEX ON users USING btree (age); +``` + +Requires the EQL operator family (`CREATE OPERATOR FAMILY`) to be installed. The full EQL install includes this. The `--exclude-operator-family` install flag omits it. + +**Supabase:** + +Functional range indexes for Supabase are not yet available. Range _filters_ work without an index (sequential scan). `ORDER BY` on encrypted columns is not supported on Supabase. + +See queries: [Range queries](/reference/eql/v2/queries#range-and-ordering) + +--- + +## JSONB + +JSONB indexes support path existence and containment queries on encrypted JSON columns. + +```sql +CREATE INDEX ON documents USING gin (eql_v2.ste_vec(metadata)); +``` + +This form is identical for self-hosted and Supabase. + +See queries: [JSONB queries](/reference/eql/v2/queries#jsonb-queries) + +--- + +## Supabase query forms + +This is the most common source of silent performance problems with encrypted columns on Supabase. + +A functional index on `eql_v2.hmac_256(email)` is only engaged when the query uses the same extraction function. A bare `WHERE email = $1` query does not use the index, even if the index exists. The database falls back to a sequential scan: your query returns correct results, but it scans every row. + +**Wrong (does not use functional index):** + +```sql +SELECT * FROM users WHERE email = $1::eql_v2_encrypted; +``` + +**Right (engages the functional index):** + +```sql +SELECT * FROM users WHERE eql_v2.hmac_256(email) = eql_v2.hmac_256($1::eql_v2_encrypted); +``` + + + SDK wrappers (Drizzle adapter, Supabase wrapper) generate the correct query form automatically. This only matters when you write raw SQL queries against Supabase encrypted columns. If you are using the Drizzle adapter or Supabase wrapper, no action is needed. + + +The same principle applies to `eql_v2.bloom_filter` and `eql_v2.ste_vec` indexes: the extraction function must appear in both the index definition and the query predicate. + +--- + +## Complete example + +```sql filename="migrations/add_encrypted_indexes.sql" +-- Equality index (Supabase-compatible form) +CREATE INDEX users_email_eq_idx ON users USING hash (eql_v2.hmac_256(email)); + +-- Match index +CREATE INDEX users_name_match_idx ON users USING gin (eql_v2.bloom_filter(name)); + +-- JSONB index +CREATE INDEX documents_metadata_ste_idx ON documents USING gin (eql_v2.ste_vec(metadata)); + +-- Range index (self-hosted only — requires operator family) +CREATE INDEX users_age_range_idx ON users USING btree (age); +``` + +--- + +## Related + +- [Searchable encryption queries](/reference/eql/v2/queries): Query patterns for each index type +- [Searchable encryption overview](/stack/cipherstash/encryption/searchable-encryption): How searchable indexes work +- [Supabase integration](/stack/cipherstash/supabase): Supabase-specific setup and limitations +- [EQL guide](/reference/eql/v2): Full reference for EQL types and functions diff --git a/content/docs/reference/eql/v2/meta.json b/content/docs/reference/eql/v2/meta.json new file mode 100644 index 0000000..68333fd --- /dev/null +++ b/content/docs/reference/eql/v2/meta.json @@ -0,0 +1,4 @@ +{ + "title": "EQL v2", + "pages": ["indexes", "queries", "payload"] +} diff --git a/content/docs/reference/eql/v2/payload.mdx b/content/docs/reference/eql/v2/payload.mdx new file mode 100644 index 0000000..953264d --- /dev/null +++ b/content/docs/reference/eql/v2/payload.mdx @@ -0,0 +1,309 @@ +--- +title: The CipherCell +description: Understand the CipherCell, the CipherStash JSON format that stores ciphertext, searchable encrypted metadata, and fields for querying encrypted data via EQL. +type: reference +components: [eql] +verifiedAgainst: + eql: "2.2" +--- + + +EQL v2 reference, for existing v2.2 deployments. New projects use [EQL v3](/reference/eql). + + +The **CipherCell** is CipherStash's standard format for storing encrypted data in a database. It is a JSON-based structure that combines **encrypted values**, **searchable encrypted metadata**, and **non-sensitive metadata** into a single, self-contained record. +CipherCells are designed to make encrypted data practical to work with in real applications. They can be stored in existing databases (such as PostgreSQL jsonb columns), indexed, queried, and audited without exposing plaintext. + +## What a CipherCell contains + +A CipherCell typically includes: + +- **Encrypted data** + - The ciphertext for one or more sensitive values. + - Each value is encrypted independently using strong authenticated encryption and unique per-value keys. +- **Searchable Encrypted Metadata (SEM)** + - Additional cryptographic material derived from the plaintext that enables secure querying using searchable encryption. + - This metadata allows operations such as equality checks, range queries, or text search to be performed **without decrypting the data**. + - The database can evaluate queries over this metadata, but cannot recover the original values. +- **Non-sensitive metadata** + - Plaintext fields that are safe to expose, such as schema identifiers, versioning information, timestamps, or application-level IDs. + - Keeping this metadata unencrypted allows efficient filtering, indexing, and integration with existing tooling. + +## How CipherCells are used + +CipherCells are stored directly in the database, usually in a JSON-compatible column. +[Encrypt Query Language (EQL)](/reference/eql/v2) understands this structure and provides database functions and operators that work over CipherCells, enabling encrypted search and filtering while preserving strong security guarantees. + +From an application's perspective, a CipherCell behaves like a regular database value: + +- Applications write encrypted data as JSON +- Databases store and index it +- Queries operate on Searchable Encrypted Metadata (SEM) +- Decryption happens only in trusted application code with the right keys and claims + +## Why CipherCells exist + +The CipherCell format solves a common problem with encryption at rest: traditional encryption makes data opaque and hard to query. CipherCells retain the benefits of encryption while enabling: + +- Fine-grained, **per-value** protection +- Searchable encryption over structured data +- Compatibility with existing databases and ORMs +- Clear separation between sensitive and non-sensitive information + +A CipherCell is the **unit of encrypted storage** in CipherStash: a portable, self-describing JSON record that makes encrypted data usable, searchable, and auditable by default. + +## Structure + + +**Required fields**: Only `i` (identifier) and `v` (version) are required. + +**Payload requirement**: Either `c` (ciphertext) or `sv` (structured encryption vector) must be present, but never both. + +**Optional fields**: All searchable encrypted metadata (SEM) fields are optional and only included when the corresponding index types are configured. + + +A CipherCell is stored as a JSON object with the following top-level structure: + +```json +{ + "i": { + "t": "table_name", + "c": "column_name" + }, + "v": 2, + "c": "encrypted_data_in_messagepack_base85", + "hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777", + "ob": ["ore_block_1", "ore_block_2"], + "bf": [1234, 5678, 9012] +} +``` + +## Top-level fields + +### `i` - Identifier + +The table and column identifier for this encrypted data. + +**Type**: Object with `t` (table) and `c` (column) properties + +**Required**: Yes + +```json +{ + "i": { + "t": "users", + "c": "email" + } +} +``` + +This field identifies which table and column the encrypted data belongs to, enabling proper decryption and index usage. + +### `v` - Version + +The encryption version used for this CipherCell. + +**Type**: Integer + +**Required**: Yes + +```json +{ + "v": 2 +} +``` + +The version field allows for cryptographic algorithm upgrades over time while maintaining backward compatibility. + +### `c` - Ciphertext (required unless `sv` is present) + +The encrypted record containing the actual plaintext data. + +**Type**: String (MessagePack encoded and Base85 encoded) + +**Required**: Yes (unless `sv` field is present) + +```json +{ + "c": "Xk}0>Z*pVbW@%*8a%F0@" +} +``` + + +Either `c` or `sv` must be present in every CipherCell, but never both. Use `c` for standard encrypted values and `sv` for structured encryption vectors (arrays or JSON structures). + + +### `a` - Array item flag + +Indicates whether this CipherCell represents an item within an array. + +**Type**: Boolean + +**Required**: No + +```json +{ + "a": true +} +``` + +## Searchable Encrypted Metadata (SEM) + +The CipherCell can contain various types of searchable encrypted metadata, each enabling different query capabilities. All SEM fields are optional and only included when the corresponding index type is configured. + +### `hm` - HMAC-SHA256 + +Enables exact match queries using HMAC-SHA256. + +**Type**: Hex-encoded string (64 characters) + +**Index Type**: [Exact](/stack/cipherstash/encryption/searchable-encryption#exact-match) + +```json +{ + "hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777" +} +``` + +### `ob` - ORE Block + +Enables range queries and ordering using Order Revealing Encryption. + +**Type**: Array of strings + +**Index Type**: [Order / Range](/stack/cipherstash/encryption/searchable-encryption#range--order) + +```json +{ + "ob": [ + "01a2b3c4d5e6f7g8h9i0", + "j1k2l3m4n5o6p7q8r9s0" + ] +} +``` + +### `bf` - Bloom Filter + +Enables substring and pattern matching queries using encrypted Bloom filters with trigrams. + +**Type**: Array of integers + +**Index Type**: [Match](/stack/cipherstash/encryption/searchable-encryption#match-pattern) + +```json +{ + "bf": [1234, 5678, 9012, 3456, 7890] +} +``` + +### `b3` - Blake3 + +Blake3 hash for exact matches in structured encryption vectors. + +**Type**: Hex-encoded string + +**Used in**: SteVec (Structured Encryption Vector) subfield + +### `s` - Selector + +Selector value for field selection in structured encryption vectors. + +**Type**: String + +**Used in**: SteVec (Structured Encryption Vector) subfield + +### `ocf` - ORE CLWW Fixed-Width + +ORE CLWW (Chenette-Lewi-Weis-Wu) fixed-width scheme for 64-bit integer values in structured encryption vectors. + +**Type**: String + +**Used in**: SteVec (Structured Encryption Vector) subfield + +### `ocv` - ORE CLWW Variable-Width + +ORE CLWW variable-width scheme for string comparison in structured encryption vectors. + +**Type**: String + +**Used in**: SteVec (Structured Encryption Vector) subfield + +### `sv` - Structured Encryption Vector (SteVec) (required unless `c` is present) + +Nested array of CipherCells for supporting containment queries and JSON-style operations. + +**Type**: Array of CipherCell objects + +**Required**: Yes (unless `c` attribute is present) + +```json +{ + "sv": [ + { + "c": "Xk}0>Z*pVbW@%*8a%F0@", + "hm": "hash1...", + "s": "selector1" + }, + { + "c": "Yl~1?A+qWcX#&+9b&G1#", + "hm": "hash2...", + "s": "selector2" + } + ] +} +``` + +SteVec enables queries on array elements and JSON document structures while maintaining encryption. Each element in the `sv` array is itself a CipherCell that can contain SEM fields like `b3`, `s`, `ocf`, and `ocv`. + +## Complete example + +Here's a complete CipherCell with multiple index types enabled: + +```json +{ + "i": { + "t": "products", + "c": "price" + }, + "v": 2, + "c": "Xk}0>Z*pVbW@%*8a%F0@Yl~1?A+qWcX#&+9b&G1#", + "hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777", + "ob": [ + "01a2b3c4d5e6f7g8h9i0", + "j1k2l3m4n5o6p7q8r9s0", + "t1u2v3w4x5y6z7a8b9c0" + ], + "bf": [1234, 5678, 9012, 3456, 7890, 2345, 6789] +} +``` + +This CipherCell: +- Belongs to the `price` column of the `products` table +- Uses encryption version 2 +- Contains the encrypted plaintext value +- Supports exact match queries via `hm` +- Supports range queries and ordering via `ob` +- Supports pattern matching via `bf` + +## Design principles + +### Minimal storage + +Only the index types configured for a column are included in the CipherCell. This minimizes storage overhead and ensures optimal performance. + +### Composable indexes + +Multiple index types can be combined on a single column, enabling both exact matches and range queries, or exact matches and pattern matching, depending on application needs. + +### Forward compatibility + +The version field (`v`) enables cryptographic algorithm upgrades without requiring full database re-encryption. Older versions can coexist with newer versions during migration. + +### Standardized format + +The CipherCell format is consistent across all CipherStash SDKs and tools, ensuring interoperability and portability of encrypted data. + +## Database storage + +CipherCells can be stored as JSON in any database that supports JSON data types. +However, for search to be supported using the [Encryption SDK](/stack/cipherstash/encryption) or [CipherStash Proxy](/stack/cipherstash/proxy), the `eql_v2.encrypted` database type must be used which is available when the Encrypt Query Language (EQL) helpers have been installed. diff --git a/content/docs/reference/eql/v2/queries.mdx b/content/docs/reference/eql/v2/queries.mdx new file mode 100644 index 0000000..cbf0f81 --- /dev/null +++ b/content/docs/reference/eql/v2/queries.mdx @@ -0,0 +1,277 @@ +--- +title: Searchable encryption queries +description: Equality, match, and range query patterns for encrypted PostgreSQL columns, with SDK predicates and raw SQL forms. +type: reference +components: [eql] +verifiedAgainst: + eql: "2.2" +--- + + +EQL v2 reference, for existing v2.2 deployments. New projects use [EQL v3](/reference/eql). + + +This page covers the three query families available for encrypted columns: equality, match (free-text), and range/order. Each section shows the SDK predicate, the raw SQL form, the underlying EQL index, and links to the corresponding index setup. + +For index creation (the `CREATE INDEX` statements your database needs), see [Setting up indexes](/reference/eql/v2/indexes). + +For a conceptual overview of how searchable encryption works, see [Searchable encryption](/stack/cipherstash/encryption/searchable-encryption). + +## Equality + +Exact match on an encrypted column. Uses the `unique` (HMAC-SHA256) index. + +**Schema:** + +```typescript filename="src/schema.ts" +import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" + +const users = encryptedTable("users", { + email: encryptedColumn("email").equality(), +}) +``` + +**SDK (single value):** + +```typescript filename="src/queries.ts" +const term = await client.encryptQuery("alice@example.com", { + column: users.email, + table: users, + queryType: "equality", +}) + +const result = await pgClient.query( + "SELECT * FROM users WHERE email = $1", + [term.data], +) +``` + +**SDK (IN list):** + +```typescript filename="src/queries.ts" +const terms = await client.encryptQuery([ + { value: "alice@example.com", column: users.email, table: users, queryType: "equality" as const }, + { value: "bob@example.com", column: users.email, table: users, queryType: "equality" as const }, +]) + +// Use each term.data as a separate parameter, or build an ANY($1) query. +``` + +**Drizzle:** + +```typescript filename="src/queries.ts" +const results = await db + .select() + .from(usersTable) + .where(await encryptionOps.eq(usersTable.email, "alice@example.com")) +``` + +**Supabase wrapper:** + +```typescript filename="src/queries.ts" +const { data } = await eSupabase + .from("users", users) + .select("id, email") + .eq("email", "alice@example.com") +``` + +**Raw SQL (self-hosted with EQL operator classes):** + +```sql +SELECT * FROM users WHERE email = $1::eql_v2_encrypted; +``` + +**Raw SQL (Supabase / functional index form):** + +```sql +SELECT * FROM users WHERE eql_v2.hmac_256(email) = eql_v2.hmac_256($1::eql_v2_encrypted); +``` + + + On Supabase, bare `WHERE email = $1` does not use the functional index. Wrap both sides with `eql_v2.hmac_256()` to engage the hash index. The SDK wrappers (Drizzle, Supabase wrapper) handle this automatically. See [Index setup: Supabase callout](/reference/eql/v2/indexes#supabase-query-forms). + + +**Underlying index:** [Equality index setup](/reference/eql/v2/indexes#equality) + +--- + +## Match (free-text) + +Substring and full-text search on an encrypted column. Uses the `match` (Bloom filter) index. Corresponds to `LIKE` / `ILIKE` semantics. + +**Schema:** + +```typescript filename="src/schema.ts" +const users = encryptedTable("users", { + name: encryptedColumn("name").freeTextSearch(), +}) +``` + +**SDK:** + +```typescript filename="src/queries.ts" +const term = await client.encryptQuery("alice", { + column: users.name, + table: users, + queryType: "freeTextSearch", +}) + +const result = await pgClient.query( + "SELECT * FROM users WHERE name LIKE $1", + [term.data], +) +``` + +**Drizzle:** + +```typescript filename="src/queries.ts" +const results = await db + .select() + .from(usersTable) + .where(await encryptionOps.ilike(usersTable.name, "%alice%")) +``` + +**Supabase wrapper:** + +```typescript filename="src/queries.ts" +const { data } = await eSupabase + .from("users", users) + .select("id, name") + .ilike("name", "%alice%") +``` + +**Raw SQL:** + +```sql +SELECT * FROM users WHERE name LIKE $1; +``` + +The Bloom filter index uses a GIN index on the extracted filter term. See [Match index setup](/reference/eql/v2/indexes#match). + +**Underlying index:** [Match index setup](/reference/eql/v2/indexes#match) + +--- + +## Range and ordering + +Comparison (`>`, `>=`, `<`, `<=`, `BETWEEN`) and `ORDER BY` on an encrypted column. Uses the `ore` (Order Revealing Encryption) index. + +**Schema:** + +```typescript filename="src/schema.ts" +const users = encryptedTable("users", { + age: encryptedColumn("age").dataType("number").orderAndRange(), +}) +``` + +**SDK (range filter):** + +```typescript filename="src/queries.ts" +const term = await client.encryptQuery(21, { + column: users.age, + table: users, + queryType: "orderAndRange", +}) + +const result = await pgClient.query( + "SELECT * FROM users WHERE age > $1", + [term.data], +) +``` + +**SDK, ORDER BY (self-hosted only):** + +```typescript filename="src/queries.ts" +// Self-hosted PostgreSQL with EQL operator families installed: +const result = await pgClient.query( + "SELECT * FROM users ORDER BY age ASC", +) + +// Without operator family support (Supabase, or --exclude-operator-family): +const result = await pgClient.query( + "SELECT * FROM users ORDER BY eql_v2.ore_block_u64_8_256(age) ASC", +) +``` + +**Drizzle:** + +```typescript filename="src/queries.ts" +// Range +const results = await db + .select() + .from(usersTable) + .where(await encryptionOps.gte(usersTable.age, 18)) + +// Sort (requires operator family support; not available on Supabase) +const results = await db + .select() + .from(usersTable) + .orderBy(encryptionOps.asc(usersTable.age)) +``` + +**Supabase wrapper:** + +```typescript filename="src/queries.ts" +// Range filter works +const { data } = await eSupabase + .from("users", users) + .select("id, age") + .gte("age", 18) + +// ORDER BY on encrypted columns is not supported on Supabase. +// Sort application-side after decrypting. +``` + + + `ORDER BY` on encrypted columns requires EQL operator families, which need superuser access to install. Supabase does not grant superuser. Range _filters_ (`>`, `>=`, `<`, `<=`) work on both self-hosted and Supabase. Sorting on encrypted columns is not currently supported on Supabase. Sort application-side after decrypting results. Operator family support for Supabase is being developed in collaboration with the Supabase and CipherStash teams. + + +**Underlying index:** [Range index setup](/reference/eql/v2/indexes#range-and-order) + +--- + +## JSONB queries + +Query encrypted JSON columns using path existence or containment. Uses the `ste_vec` index. + +**Schema:** + +```typescript filename="src/schema.ts" +const documents = encryptedTable("documents", { + metadata: encryptedColumn("metadata").searchableJson(), +}) +``` + +**SDK (path existence):** + +```typescript filename="src/queries.ts" +const term = await client.encryptQuery("$.user.role", { + column: documents.metadata, + table: documents, +}) + +const result = await pgClient.query( + "SELECT * FROM documents WHERE eql_v2.ste_vec(metadata) @> $1", + [term.data], +) +``` + +**SDK (containment):** + +```typescript filename="src/queries.ts" +const term = await client.encryptQuery({ role: "admin" }, { + column: documents.metadata, + table: documents, +}) +``` + +**Drizzle:** + +```typescript filename="src/queries.ts" +const results = await db + .select() + .from(documentsTable) + .where(await encryptionOps.jsonbPathExists(documentsTable.metadata, "$.user.role")) +``` + +**Underlying index:** [JSONB index setup](/reference/eql/v2/indexes#jsonb)