Booleans
Encrypted booleans are storage-only by design: public.eql_v3_boolean stores and decrypts, carries no index terms, and blocks every comparison.
EQL version
Every scalar type has a storage-only variant — for bool it's the only one. EQL ships public.eql_v3_boolean and nothing else: there is no bool_eq and no bool_ord. An encrypted boolean column can be stored, decrypted, and null-checked; it cannot be filtered, sorted, grouped, or joined on.
Why there are no query variants
A two-value column has too little cardinality for any searchable index to be safe. An equality term over true / false would partition the table into two visible buckets — leaking the value distribution (and, with any outside knowledge, the values themselves) outright. Rather than ship an index term that can't keep its promise, EQL omits the query variants entirely. See Searchable encryption for the general analysis of what index terms reveal.
What works, what raises
public.eql_v3_boolean follows the bare-variant contract described in Core concepts: it carries no index terms, so IS NULL / IS NOT NULL are the only predicates that work. Every comparison operator routes to a blocker and raises — the fail-loud behavior shared by all encrypted variants:
-- ❌ Raises: operator = is not supported for public.eql_v3_boolean
SELECT * FROM users WHERE is_active = $1::public.eql_v3_boolean;
-- ✅ Works: NULL columns are not encrypted
SELECT * FROM users WHERE is_active IS NOT NULL;Filter client-side
Query on other columns, decrypt the boolean in your application, and filter there:
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email public.eql_v3_text_eq, -- exact lookup
created_at public.eql_v3_timestamp_ord, -- range queries, ORDER BY
is_active public.eql_v3_boolean -- storage only (by design)
);-- Narrow the result set with the columns that do carry index terms…
SELECT id, email, is_active FROM users
WHERE created_at >= $1::public.eql_v3_timestamp_ord;
-- …then decrypt is_active in the client and filter on the plaintext.The Stack SDK and CipherStash Proxy decrypt the payload back to a plain boolean on read, so the client-side filter is an ordinary if.
If a boolean genuinely needs to be a server-side predicate, that is a data-modelling signal: consider whether the flag is actually sensitive. A non-sensitive flag can stay a plain PostgreSQL boolean column alongside your encrypted columns.
Storing without querying
bool is the forced case of a pattern available to every scalar type: the bare variant public.eql_v3_<T> (for example public.eql_v3_integer, public.eql_v3_text, public.eql_v3_timestamp) is storage-and-decryption only. It carries no index terms, and every comparison operator raises — use it for columns you only ever store and decrypt, so the database holds no searchable material for them at all.
For every type other than bool, storage-only is a choice you can walk back. If you later need to query, retype the column as a query variant — or, if the payloads already carry the needed term (the client decides which terms travel in the payload), cast at the call site:
SELECT * FROM readings WHERE value::public.eql_v3_integer_ord > $1::public.eql_v3_integer_ord;The variant families and what each one enables are covered in Core concepts; the per-type specifics live in Numbers, Dates & times, and Text.
JSON
The complete reference for encrypted JSON documents with public.eql_v3_json_search — the ste_vec payload shape, value-selector containment, field access, and path queries over ciphertext, with the native jsonb operators that don't apply blocked outright.
Indexes
Create Postgres indexes on encrypted columns using functional indexes over EQL's term-extractor functions.