Dates & times
The complete reference for encrypted date and timestamp columns: the domain variants, the payload they carry, and time-window, newest-first, and MIN/MAX queries.
EQL version
date and timestamp columns carry the same capabilities as encrypted numbers — equality, ranges, ordering, MIN / MAX — but the queries they serve are temporal: time windows, newest-first listings, retention cutoffs, "when did this last happen".
Variants
Both types generate the same jsonb-backed domain variants. The generic form:
| Domain variant | Capability |
|---|---|
public.eql_v3_<T> | Storage and decryption only. |
public.eql_v3_<T>_eq | Equality: =, <>, IN, GROUP BY, DISTINCT, equijoins. |
public.eql_v3_<T>_ord | Comparisons, BETWEEN, ORDER BY, MIN / MAX — plus equality. |
public.eql_v3_<T>_ord_ope | The byte-identical twin of <T>_ord, with OPE pinned. See SEM specifiers. |
public.eql_v3_<T>_ord_ore | As <T>_ord, with the ORE mechanism pinned. |
<T> is the encrypted type name from the table below, so public.eql_v3_<T>_ord is public.eql_v3_timestamp_ord for timestamp.
And every concrete domain this page covers:
| Type | Variants |
|---|---|
date | public.eql_v3_date · public.eql_v3_date_eq · public.eql_v3_date_ord · public.eql_v3_date_ord_ope · public.eql_v3_date_ord_ore |
timestamp | public.eql_v3_timestamp · public.eql_v3_timestamp_eq · public.eql_v3_timestamp_ord · public.eql_v3_timestamp_ord_ope · public.eql_v3_timestamp_ord_ore |
Time columns are nearly always ranged and sorted, so _ord is the usual choice. Declare only the capability you query on — each capability stores extra searchable material with defined leakage (see Searchable encryption), and the variant model itself is covered in Core concepts.
Example
An audit-events table where the timestamps drive time-window queries and sorting:
CREATE TABLE audit_events (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
occurred_at public.eql_v3_timestamp_ord, -- time windows, newest-first, MIN/MAX
review_due public.eql_v3_date_ord, -- range filters
sealed_on public.eql_v3_date -- store and decrypt only
);SEM specifiers
Both types take the same mechanism specifiers on their orderable variant (the concept is defined in Core concepts):
| Specifier | Mechanism | Ordering term | Extractor |
|---|---|---|---|
_ord | The default, currently CLLW OPE | op | eql_v3.ord_term |
_ord_ope | CLLW OPE, pinned explicitly | op | eql_v3.ord_term |
_ord_ore | Block-ORE, pinned explicitly | ob | eql_v3.ord_term_ore |
<T>_ord and <T>_ord_ope are byte-identical today. Pin _ord_ope when you want a column's mechanism frozen against a future change of the default.
Block-ORE terms sort only under a custom btree operator class, and creating one requires superuser. Where the EQL installer runs as a non-superuser, which is the case on most managed Postgres including cloud Supabase, it cannot create the class, so it disables every ORE-backed domain rather than let them install half-working. <T>_ord_ore then raises feature_not_supported on the first value written to it. Use <T>_ord there.
Payload
A value for an _ord column carries the shared envelope keys (v, i, c — see Core concepts) plus the op ordering term. Here is a payload for the public.eql_v3_timestamp_ord occurred_at column:
{
"v": 3,
"i": { "t": "audit_events", "c": "occurred_at" },
"c": "mBbKmsMM%bK#QQOx1yLDBHyD...",
"op": "5f2b1a9e4c07d38b6ea15c92..."
}opis the only index term. It is a hex-encoded CLLW OPE ciphertext, which Postgres sorts by nativebyteacomparison. An_ordpayload carries nohm, because ordering over a date or timestamp is equality-lossless:=and<>resolve against the same term.- An
_ord_orepayload carriesobin place ofop: an array of block-ORE ciphertexts, 12 blocks for atimestamp.
Operators
| SQL operator | public.eql_v3_<T> | <T>_eq | <T>_ord variants |
|---|---|---|---|
= / <> | ❌ | ✅ | ✅ |
< <= > >= | ❌ | ❌ | ✅ |
BETWEEN (desugars to >= and <=) | ❌ | ❌ | ✅ |
IN (desugars to =) | ❌ | ✅ | ✅ |
GROUP BY / DISTINCT | ❌ | ✅ | ✅ |
ORDER BY | ❌ | ❌ | ✅ |
IS NULL / IS NOT NULL | ✅ | ✅ | ✅ |
Blocked operator cells raise an operator … is not supported exception — they never silently return wrong rows. ORDER BY is the one blocked cell that doesn't raise: it isn't an operator, so sorting a variant without an ordering term runs — but the order is meaningless (see Sorting). Operands must be typed ($1::public.eql_v3_timestamp_ord), or PostgreSQL resolves the native jsonb operator instead of the encrypted one. Both rules are covered in Core concepts.
Functions
Every operator has a function form, for managed platforms that disallow custom operators — same typed arguments, identical resolution. Each lists the encrypted domains it applies to; the MIN / MAX aggregates only exist as functions.
eql_v3.eq(a, b)
SELECT * FROM events
WHERE eql_v3.eq(occurred_at, $1::public.eql_v3_timestamp_eq);eql_v3.neq(a, b)
SELECT * FROM events
WHERE eql_v3.neq(occurred_at, $1::public.eql_v3_timestamp_eq);eql_v3.lt / lte / gt / gte
-- a range uses two of the four
SELECT * FROM events
WHERE eql_v3.gte(occurred_at, $1::public.eql_v3_timestamp_ord)
AND eql_v3.lt(occurred_at, $2::public.eql_v3_timestamp_ord);eql_v3.min(col)
-- compares ordering terms; result decrypts client-side
SELECT eql_v3.min(occurred_at) FROM events;eql_v3.max(col)
SELECT eql_v3.max(occurred_at) FROM events;Where to next
Numbers
The complete reference for encrypted numeric columns: the int, float, and numeric domain variants, the ordering term they carry, and range, ORDER BY, and MIN/MAX queries.
Text
The complete reference for encrypted text columns: every text domain variant, the multi-term payload, why LIKE is gone everywhere, and @@ bloom-filter fuzzy match as the encrypted free-text search.