From 1f33bad9cc4b75f493c9ec69dff0bf051840ff7f Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 20 May 2026 20:57:55 +1000 Subject: [PATCH 1/3] docs(encryption): align index & query recipes with EQL 2.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-2.3 docs assumed a bare `WHERE col = $1` would not engage a functional index, so they taught two models: operator-class B-tree indexes for self-hosted, hand-wrapped `eql_v2.hmac_256(...)` queries for Supabase. EQL 2.3's functional-index inlining makes functional indexes the single recipe for both deployments, and bare-form queries engage them automatically. indexes.mdx — rewrite around functional indexes: one recipe per query type (equality `USING hash (eql_v2.hmac_256)`, match `USING gin (eql_v2.bloom_filter)`, JSONB `USING gin (eql_v2.jsonb_array)`, range `USING btree (eql_v2.ore_block_u64_8_256)`); range is the lone self-hosted-only case (its ORE-term operator class needs superuser); drop the obsolete "Supabase query forms" wrong/right section and the removed-in-2.3 OPE references. queries.mdx — collapse the self-hosted/Supabase equality split into a single bare-form query; fix ORDER BY to the functional `eql_v2.ore_block_u64_8_256(col)` form; replace the dead `cs_ste_vec_v2(...)` function name. searchable-encryption.mdx — same ORDER BY and `cs_ste_vec_v2` fixes. --- .../stack/cipherstash/encryption/indexes.mdx | 106 ++++++++---------- .../stack/cipherstash/encryption/queries.mdx | 27 ++--- .../encryption/searchable-encryption.mdx | 4 +- 3 files changed, 56 insertions(+), 81 deletions(-) diff --git a/content/stack/cipherstash/encryption/indexes.mdx b/content/stack/cipherstash/encryption/indexes.mdx index ccaf4c5..1fd29bf 100644 --- a/content/stack/cipherstash/encryption/indexes.mdx +++ b/content/stack/cipherstash/encryption/indexes.mdx @@ -1,25 +1,27 @@ --- title: Setting up indexes -description: Create PostgreSQL indexes for encrypted columns. Index syntax differs between self-hosted PostgreSQL and managed databases like Supabase. +description: Create PostgreSQL functional indexes for encrypted columns — one recipe per query type, working on self-hosted and managed Postgres alike. --- # Setting up indexes -Encrypted columns need PostgreSQL indexes for fast queries. Without an index, the database performs a sequential scan: correct but slow at scale. +Encrypted columns need PostgreSQL indexes for fast queries. Without one, the database falls back to 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. +EQL indexes an encrypted column with a **functional index** — an ordinary PostgreSQL index built over an EQL extraction function (`eql_v2.hmac_256`, `eql_v2.bloom_filter`, and so on) rather than over the raw column. One recipe per query type works on every PostgreSQL deployment — self-hosted, Supabase, RDS — with no superuser privileges and no custom operator classes. -## Deployment matrix +Bare-form queries engage these indexes automatically. `WHERE email = $1`, `WHERE name LIKE $1`, and `WHERE age < $1` each match their functional index with no query rewriting: EQL's operators inline to the same extraction function the index is built on, so the planner connects the two. -| Query type | Self-hosted (full EQL) | Supabase | +## At a glance + +| Query type | Index recipe | Availability | |---|---|---| -| 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 | +| Equality | `USING hash (eql_v2.hmac_256(col))` | All deployments | +| Pattern match (`LIKE` / `ILIKE`) | `USING gin (eql_v2.bloom_filter(col))` | All deployments | +| JSONB containment | `USING gin (eql_v2.jsonb_array(col))` | All deployments | +| Range / `ORDER BY` | `USING btree (eql_v2.ore_block_u64_8_256(col))` | Self-hosted only | - 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. + The range index is the one exception to "works everywhere": it relies on a B-tree operator class for the ORE term type, and installing an operator class requires superuser. Managed databases like Supabase do not grant superuser, so range indexes are unavailable there. Range *filters* (`>`, `>=`, `<`, `<=`) still return correct results on Supabase — they fall back to a sequential scan. `ORDER BY` on an encrypted column is not supported on Supabase; sort application-side after decrypting. --- @@ -28,21 +30,17 @@ Index syntax differs between deployment types. Self-hosted PostgreSQL with full 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); +CREATE INDEX ON users USING hash (eql_v2.hmac_256(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):** +A bare equality query engages the index — you do not need to mention `eql_v2.hmac_256` in the query itself: ```sql -CREATE INDEX ON users USING hash (eql_v2.hmac_256(email)); +SELECT * FROM users WHERE email = $1; ``` -This form works on both deployment types. Use it when you want one index that works everywhere, or when you are on Supabase. +The `=` operator on an encrypted column inlines to a comparison of the HMAC term, which is exactly what the index stores, so the planner matches the two. See queries: [Equality queries](/stack/cipherstash/encryption/queries#equality) @@ -56,7 +54,11 @@ Match indexes speed up `WHERE col LIKE $1` and `ILIKE` queries. They use a GIN i CREATE INDEX ON users USING gin (eql_v2.bloom_filter(name)); ``` -This form is identical for self-hosted and Supabase. +A bare `LIKE` / `ILIKE` query engages it: + +```sql +SELECT * FROM users WHERE name LIKE $1; +``` See queries: [Match queries](/stack/cipherstash/encryption/queries#match-free-text) @@ -64,80 +66,64 @@ See queries: [Match queries](/stack/cipherstash/encryption/queries#match-free-te ## Range and order -Range indexes support `>`, `>=`, `<`, `<=`, `BETWEEN`, and `ORDER BY` on encrypted columns. - -**Self-hosted (B-tree with operator class):** +Range indexes support `>`, `>=`, `<`, `<=`, `BETWEEN`, and `ORDER BY` on encrypted columns. They use a B-tree index on the Block-ORE term. ```sql -CREATE INDEX ON users USING btree (age); +CREATE INDEX ON users USING btree (eql_v2.ore_block_u64_8_256(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. +A bare range *filter* engages the index: -**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](/stack/cipherstash/encryption/queries#range-and-ordering) - ---- - -## JSONB +```sql +SELECT * FROM users WHERE age < $1; +``` -JSONB indexes support path existence and containment queries on encrypted JSON columns. +For sorting, order by the extraction function. A bare `ORDER BY age` does not match the index expression and falls back to an in-memory sort: ```sql -CREATE INDEX ON documents USING gin (eql_v2.ste_vec(metadata)); +SELECT * FROM users ORDER BY eql_v2.ore_block_u64_8_256(age) LIMIT 10; ``` -This form is identical for self-hosted and Supabase. + + Range indexes are self-hosted only — they need a B-tree operator class on the ORE term type, and operator classes require superuser to install. On Supabase, range filters still return correct results (via sequential scan) but cannot be indexed, and `ORDER BY` on an encrypted column is not supported — sort application-side after decrypting. + -See queries: [JSONB queries](/stack/cipherstash/encryption/queries#jsonb-queries) +See queries: [Range queries](/stack/cipherstash/encryption/queries#range-and-ordering) --- -## 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; -``` +## JSONB -**Right (engages the functional index):** +JSONB indexes support containment queries (`@>`) on encrypted JSON columns. ```sql -SELECT * FROM users WHERE eql_v2.hmac_256(email) = eql_v2.hmac_256($1::eql_v2_encrypted); +CREATE INDEX ON documents USING gin (eql_v2.jsonb_array(metadata)); ``` - - 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. - +This recipe works on all deployments. -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. +See queries: [JSONB queries](/stack/cipherstash/encryption/queries#jsonb-queries) --- ## Complete example ```sql filename="migrations/add_encrypted_indexes.sql" --- Equality index (Supabase-compatible form) +-- Equality CREATE INDEX users_email_eq_idx ON users USING hash (eql_v2.hmac_256(email)); --- Match index +-- Pattern match 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)); +-- JSONB containment +CREATE INDEX documents_metadata_idx ON documents USING gin (eql_v2.jsonb_array(metadata)); --- Range index (self-hosted only — requires operator family) -CREATE INDEX users_age_range_idx ON users USING btree (age); +-- Range / ORDER BY (self-hosted only) +CREATE INDEX users_age_range_idx ON users USING btree (eql_v2.ore_block_u64_8_256(age)); ``` +Run `ANALYZE ` after creating an index and loading data, so the planner has accurate statistics. + --- ## Related diff --git a/content/stack/cipherstash/encryption/queries.mdx b/content/stack/cipherstash/encryption/queries.mdx index 9e4d31c..ae247df 100644 --- a/content/stack/cipherstash/encryption/queries.mdx +++ b/content/stack/cipherstash/encryption/queries.mdx @@ -69,21 +69,13 @@ const { data } = await eSupabase .eq("email", "alice@example.com") ``` -**Raw SQL (self-hosted with EQL operator classes):** +**Raw SQL:** ```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](/stack/cipherstash/encryption/indexes#supabase-query-forms). - +A bare `=` engages the `eql_v2.hmac_256(email)` functional index on every deployment — self-hosted and Supabase alike. The operator inlines to the HMAC comparison the index stores, so there is no need to wrap the query in `eql_v2.hmac_256()`. **Underlying index:** [Equality index setup](/stack/cipherstash/encryption/indexes#equality) @@ -176,12 +168,9 @@ const result = await pgClient.query( **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): +// Order by the extraction function — a bare `ORDER BY age` does not match +// the functional range index expression. Requires a range index, so this +// is self-hosted only. const result = await pgClient.query( "SELECT * FROM users ORDER BY eql_v2.ore_block_u64_8_256(age) ASC", ) @@ -196,7 +185,7 @@ const results = await db .from(usersTable) .where(await encryptionOps.gte(usersTable.age, 18)) -// Sort (requires operator family support; not available on Supabase) +// Sort (requires a range index; self-hosted only) const results = await db .select() .from(usersTable) @@ -217,7 +206,7 @@ const { data } = await eSupabase ``` - `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. + `ORDER BY` on encrypted columns needs the range (Block-ORE) index, which is self-hosted only — its operator class requires superuser to install, and Supabase does not grant superuser. Range _filters_ (`>`, `>=`, `<`, `<=`) return correct results on both self-hosted and Supabase. On Supabase, `ORDER BY` on an encrypted column is not supported — sort application-side after decrypting. **Underlying index:** [Range index setup](/stack/cipherstash/encryption/indexes#range-and-order) @@ -245,7 +234,7 @@ const term = await client.encryptQuery("$.user.role", { }) const result = await pgClient.query( - "SELECT * FROM documents WHERE cs_ste_vec_v2(metadata) @> $1", + "SELECT * FROM documents WHERE eql_v2.jsonb_array(metadata) @> eql_v2.jsonb_array($1::eql_v2_encrypted)", [term.data], ) ``` diff --git a/content/stack/cipherstash/encryption/searchable-encryption.mdx b/content/stack/cipherstash/encryption/searchable-encryption.mdx index f15d179..f7bb0ca 100644 --- a/content/stack/cipherstash/encryption/searchable-encryption.mdx +++ b/content/stack/cipherstash/encryption/searchable-encryption.mdx @@ -134,7 +134,7 @@ const result = await pgClient.query( Use `.orderAndRange()` for sorting and range operations: - If your PostgreSQL database does not support EQL Operator families, use the `eql_v2.ore_block_u64_8_256()` function for `ORDER BY`. Databases with Operator family support can use `ORDER BY` directly on the encrypted column name. + Order by the `eql_v2.ore_block_u64_8_256()` extraction function, not the bare column — a bare `ORDER BY` does not match the functional range index. Range indexes are self-hosted only; `ORDER BY` on an encrypted column is not supported on Supabase. ```typescript filename="search.ts" @@ -253,7 +253,7 @@ const term = await client.encryptQuery([{ }]) const result = await pgClient.query( - "SELECT * FROM documents WHERE cs_ste_vec_v2(metadata_encrypted) @> $1", + "SELECT * FROM documents WHERE eql_v2.jsonb_array(metadata_encrypted) @> eql_v2.jsonb_array($1::eql_v2_encrypted)", [term.data[0]] ) ``` From e741aac2b4bcf390ad6c3833bc405627623ed130 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 20 May 2026 21:39:18 +1000 Subject: [PATCH 2/3] docs(encryption): range index availability is "most providers", not self-hosted only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The range index needs a custom operator class on the ORE term type. That is not a self-hosted-vs-managed split: any provider that allows operator-class creation supports it — self-hosted PostgreSQL and AWS RDS included. Supabase is the notable exception, not "all managed databases". Reword the range-index availability across indexes.mdx, queries.mdx, and searchable-encryption.mdx — "most providers" rather than "self-hosted only", and drop the over-specific "requires superuser" claim. --- content/stack/cipherstash/encryption/indexes.mdx | 16 ++++++++-------- content/stack/cipherstash/encryption/queries.mdx | 8 ++++---- .../encryption/searchable-encryption.mdx | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/content/stack/cipherstash/encryption/indexes.mdx b/content/stack/cipherstash/encryption/indexes.mdx index 1fd29bf..27ebdb7 100644 --- a/content/stack/cipherstash/encryption/indexes.mdx +++ b/content/stack/cipherstash/encryption/indexes.mdx @@ -7,7 +7,7 @@ description: Create PostgreSQL functional indexes for encrypted columns — one Encrypted columns need PostgreSQL indexes for fast queries. Without one, the database falls back to a sequential scan: correct, but slow at scale. -EQL indexes an encrypted column with a **functional index** — an ordinary PostgreSQL index built over an EQL extraction function (`eql_v2.hmac_256`, `eql_v2.bloom_filter`, and so on) rather than over the raw column. One recipe per query type works on every PostgreSQL deployment — self-hosted, Supabase, RDS — with no superuser privileges and no custom operator classes. +EQL indexes an encrypted column with a **functional index** — an ordinary PostgreSQL index built over an EQL extraction function (`eql_v2.hmac_256`, `eql_v2.bloom_filter`, and so on) rather than over the raw column. There is one recipe per query type: equality, pattern matching, and JSONB containment indexes work on every PostgreSQL deployment with no special privileges; range and ordering indexes additionally need a custom operator class, which most providers allow (see the note below). Bare-form queries engage these indexes automatically. `WHERE email = $1`, `WHERE name LIKE $1`, and `WHERE age < $1` each match their functional index with no query rewriting: EQL's operators inline to the same extraction function the index is built on, so the planner connects the two. @@ -15,13 +15,13 @@ Bare-form queries engage these indexes automatically. `WHERE email = $1`, `WHERE | Query type | Index recipe | Availability | |---|---|---| -| Equality | `USING hash (eql_v2.hmac_256(col))` | All deployments | -| Pattern match (`LIKE` / `ILIKE`) | `USING gin (eql_v2.bloom_filter(col))` | All deployments | -| JSONB containment | `USING gin (eql_v2.jsonb_array(col))` | All deployments | -| Range / `ORDER BY` | `USING btree (eql_v2.ore_block_u64_8_256(col))` | Self-hosted only | +| Equality | `USING hash (eql_v2.hmac_256(col))` | All providers | +| Pattern match (`LIKE` / `ILIKE`) | `USING gin (eql_v2.bloom_filter(col))` | All providers | +| JSONB containment | `USING gin (eql_v2.jsonb_array(col))` | All providers | +| Range / `ORDER BY` | `USING btree (eql_v2.ore_block_u64_8_256(col))` | Most providers | - The range index is the one exception to "works everywhere": it relies on a B-tree operator class for the ORE term type, and installing an operator class requires superuser. Managed databases like Supabase do not grant superuser, so range indexes are unavailable there. Range *filters* (`>`, `>=`, `<`, `<=`) still return correct results on Supabase — they fall back to a sequential scan. `ORDER BY` on an encrypted column is not supported on Supabase; sort application-side after decrypting. + The range index carries one caveat: it relies on a B-tree operator class for the ORE term type, and creating an operator class needs elevated privileges. It works on any provider that allows that — self-hosted PostgreSQL and AWS RDS included — but not on every managed platform. Supabase is the notable exception. Where the range index cannot be created, range *filters* (`>`, `>=`, `<`, `<=`) still return correct results (they fall back to a sequential scan), but `ORDER BY` on an encrypted column is unavailable — sort application-side after decrypting. --- @@ -85,7 +85,7 @@ SELECT * FROM users ORDER BY eql_v2.ore_block_u64_8_256(age) LIMIT 10; ``` - Range indexes are self-hosted only — they need a B-tree operator class on the ORE term type, and operator classes require superuser to install. On Supabase, range filters still return correct results (via sequential scan) but cannot be indexed, and `ORDER BY` on an encrypted column is not supported — sort application-side after decrypting. + The range index needs a B-tree operator class on the ORE term type, so it can be created on any provider that allows custom operator classes — self-hosted PostgreSQL, AWS RDS, and most managed Postgres. Supabase is the notable exception: there, range filters still return correct results via sequential scan but cannot be indexed, and `ORDER BY` on an encrypted column is unavailable — sort application-side after decrypting. See queries: [Range queries](/stack/cipherstash/encryption/queries#range-and-ordering) @@ -118,7 +118,7 @@ CREATE INDEX users_name_match_idx ON users USING gin (eql_v2.bloom_filter(name)) -- JSONB containment CREATE INDEX documents_metadata_idx ON documents USING gin (eql_v2.jsonb_array(metadata)); --- Range / ORDER BY (self-hosted only) +-- Range / ORDER BY (most providers — needs custom operator class support) CREATE INDEX users_age_range_idx ON users USING btree (eql_v2.ore_block_u64_8_256(age)); ``` diff --git a/content/stack/cipherstash/encryption/queries.mdx b/content/stack/cipherstash/encryption/queries.mdx index ae247df..b3563b0 100644 --- a/content/stack/cipherstash/encryption/queries.mdx +++ b/content/stack/cipherstash/encryption/queries.mdx @@ -169,8 +169,8 @@ const result = await pgClient.query( ```typescript filename="src/queries.ts" // Order by the extraction function — a bare `ORDER BY age` does not match -// the functional range index expression. Requires a range index, so this -// is self-hosted only. +// the functional range index expression. The range index needs custom +// operator class support — available on most providers, not Supabase. const result = await pgClient.query( "SELECT * FROM users ORDER BY eql_v2.ore_block_u64_8_256(age) ASC", ) @@ -185,7 +185,7 @@ const results = await db .from(usersTable) .where(await encryptionOps.gte(usersTable.age, 18)) -// Sort (requires a range index; self-hosted only) +// Sort (requires a range index; most providers, not Supabase) const results = await db .select() .from(usersTable) @@ -206,7 +206,7 @@ const { data } = await eSupabase ``` - `ORDER BY` on encrypted columns needs the range (Block-ORE) index, which is self-hosted only — its operator class requires superuser to install, and Supabase does not grant superuser. Range _filters_ (`>`, `>=`, `<`, `<=`) return correct results on both self-hosted and Supabase. On Supabase, `ORDER BY` on an encrypted column is not supported — sort application-side after decrypting. + `ORDER BY` on encrypted columns needs the range (Block-ORE) index, which relies on a B-tree operator class — creatable on any provider that allows custom operator classes (self-hosted, AWS RDS, and most managed Postgres). Supabase is the notable exception. Range _filters_ (`>`, `>=`, `<`, `<=`) return correct results everywhere — they fall back to a sequential scan where the index is unavailable. Where the range index cannot be created, `ORDER BY` on an encrypted column is not supported — sort application-side after decrypting. **Underlying index:** [Range index setup](/stack/cipherstash/encryption/indexes#range-and-order) diff --git a/content/stack/cipherstash/encryption/searchable-encryption.mdx b/content/stack/cipherstash/encryption/searchable-encryption.mdx index f7bb0ca..eed6723 100644 --- a/content/stack/cipherstash/encryption/searchable-encryption.mdx +++ b/content/stack/cipherstash/encryption/searchable-encryption.mdx @@ -134,7 +134,7 @@ const result = await pgClient.query( Use `.orderAndRange()` for sorting and range operations: - Order by the `eql_v2.ore_block_u64_8_256()` extraction function, not the bare column — a bare `ORDER BY` does not match the functional range index. Range indexes are self-hosted only; `ORDER BY` on an encrypted column is not supported on Supabase. + Order by the `eql_v2.ore_block_u64_8_256()` extraction function, not the bare column — a bare `ORDER BY` does not match the functional range index. The range index needs custom operator class support, available on most providers (self-hosted, AWS RDS, …) but not Supabase; where it cannot be created, sort application-side after decrypting. ```typescript filename="search.ts" From d20d2108216f4c4c203f7d85a51f48693ddea9ee Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 20 May 2026 21:39:52 +1000 Subject: [PATCH 3/3] docs(encryption): reword the last 'self-hosted only' ORDER BY label --- content/stack/cipherstash/encryption/queries.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/stack/cipherstash/encryption/queries.mdx b/content/stack/cipherstash/encryption/queries.mdx index b3563b0..bf4276e 100644 --- a/content/stack/cipherstash/encryption/queries.mdx +++ b/content/stack/cipherstash/encryption/queries.mdx @@ -165,7 +165,7 @@ const result = await pgClient.query( ) ``` -**SDK, ORDER BY (self-hosted only):** +**SDK, ORDER BY (most providers):** ```typescript filename="src/queries.ts" // Order by the extraction function — a bare `ORDER BY age` does not match