You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Now that the eql_v3 scalar domains use SQL-standard canonical names (integer/smallint/bigint/boolean/real/double — see #344), consider whether to also expose the Postgres-internal spellings (int4/int2/int8/bool/float4/float8) as aliases, so a caller can type their EQL column with whichever name matches their native column. This issue captures the design considerations from that discussion — chiefly the casting problem, which is what makes "alias" more than a rename.
The core constraint: Postgres domains can't be true aliases
integer/int4, smallint/int2, etc. are aliases in Postgres's native type system (one OID, resolved by the parser). But a DOMAIN is a user-defined type with exactly one name, and there is no CREATE SYNONYM for types. So any "alias" we add is a second, independent domain — distinct OID, distinct operator/cast resolution. This is the whole reason "alias" is a concept worth modelling rather than a free rename.
The casting issue (the crux)
Because each alias is a distinct type, a function or operator declared on eql_v3.integer will not accept an eql_v3.int4 value, and vice-versa:
-- with int4 added as a naive second domain:WHEREeql_v3.eq(col_integer, x::eql_v3.int4) -- fails to resolve
To make the two names actually interchangeable (a real alias, not two unrelated types), we'd need to emit an implicit binary cast between the pair. Both domains are jsonb-backed, so they're binary-compatible:
CREATE CAST (eql_v3.int4ASeql_v3.integer) WITHOUT FUNCTION AS IMPLICIT;
Open questions on the cast:
Does casting to a domain re-run its CHECK? (It should — harmless here since the payload already satisfies it, but needs verifying.)
Direction/pairs: one implicit cast per direction, per domain variant (int4↔integer, int4_eq↔integer_eq, int4_ord↔integer_ord, …). That's 2 × variants casts per aliased family.
Without the cast, "alias" is a lie — you'd ship two silently-incompatible types and a foot-gun.
Related: the native-type-token casting gotcha (found in #344)
A distinct-but-adjacent casting hazard surfaced while landing the canonical rename: the domain token must not be assumed to be a valid native SQL type name. We named float8's domain eql_v3.double, but double is not a native Postgres type (only double precision is) — so x::double / CREATE TABLE (c double) fails with type "double" does not exist. In #344 this bit the SQLx harness's plaintext oracle column; fixed by deriving PLAINTEXT_SQL_TYPE from the cipherstash cast (→ double precision), the same PG_TYPE-vs-plaintext split timestamp uses (domain timestamp, native timestamp with time zone).
Relevance to aliases: if aliases are added, any place that maps a domain token to a native type (fixtures, oracles, docs, codegen) must keep "domain token" and "native SQL type name" separate. An alias like eql_v3.float8 would map back to native double precision too.
Crypto is name-independent (makes aliases cheap on the axis that matters)
Encryption is driven by the cipherstash Cast (int/small_int/big_int/real/double/boolean), keyed on ScalarKind — not the domain name. So an alias domain wraps byte-identical ciphertext: aliases cost nothing on the crypto/fixture axis (the expensive, creds-gated part) and full price only on the SQL surface (domains, extractors, wrappers, operators, casts), bindings, and matrix wiring.
Naive extra DomainFamily per alias — works but produces silently non-interchangeable types. ❌ foot-gun without casts.
Model alias as a first-class catalog concept — add aliases: &[&str] (or canonical + spellings) to DomainFamily; codegen emits the alias domain surface mechanically from the canonical, shares fixtures, and emits the implicit binary casts above so the two names are genuinely interchangeable. Bindings/docs pick the canonical struct name; the alias gets a doc note. This is the only option where "alias" is real.
Recommendation
Only pursue (3) if there's concrete demand for both spellings to be callable (e.g. so the EQL column name reads the same as the native column). Prototype the aliases field + generated implicit casts to size the real diff (and settle the CHECK-on-cast question) before committing. Otherwise (1) stands.
Out of scope / notes
json/jsonb: not an alias pair in Postgres (genuinely different types) — decide separately, not part of this.
timestamp: already carries the domain-token-vs-native-type split (timestamp domain, timestamp with time zone plaintext) — a working precedent for the pattern option (3) needs.
Summary
Now that the
eql_v3scalar domains use SQL-standard canonical names (integer/smallint/bigint/boolean/real/double— see #344), consider whether to also expose the Postgres-internal spellings (int4/int2/int8/bool/float4/float8) as aliases, so a caller can type their EQL column with whichever name matches their native column. This issue captures the design considerations from that discussion — chiefly the casting problem, which is what makes "alias" more than a rename.The core constraint: Postgres domains can't be true aliases
integer/int4,smallint/int2, etc. are aliases in Postgres's native type system (one OID, resolved by the parser). But aDOMAINis a user-defined type with exactly one name, and there is noCREATE SYNONYMfor types. So any "alias" we add is a second, independent domain — distinct OID, distinct operator/cast resolution. This is the whole reason "alias" is a concept worth modelling rather than a free rename.The casting issue (the crux)
Because each alias is a distinct type, a function or operator declared on
eql_v3.integerwill not accept aneql_v3.int4value, and vice-versa:To make the two names actually interchangeable (a real alias, not two unrelated types), we'd need to emit an implicit binary cast between the pair. Both domains are
jsonb-backed, so they're binary-compatible:Open questions on the cast:
CHECK? (It should — harmless here since the payload already satisfies it, but needs verifying.)int4↔integer,int4_eq↔integer_eq,int4_ord↔integer_ord, …). That's2 × variantscasts per aliased family.Without the cast, "alias" is a lie — you'd ship two silently-incompatible types and a foot-gun.
Related: the native-type-token casting gotcha (found in #344)
A distinct-but-adjacent casting hazard surfaced while landing the canonical rename: the domain token must not be assumed to be a valid native SQL type name. We named
float8's domaineql_v3.double, butdoubleis not a native Postgres type (onlydouble precisionis) — sox::double/CREATE TABLE (c double)fails withtype "double" does not exist. In #344 this bit the SQLx harness's plaintext oracle column; fixed by derivingPLAINTEXT_SQL_TYPEfrom the cipherstash cast (→double precision), the samePG_TYPE-vs-plaintext splittimestampuses (domaintimestamp, nativetimestamp with time zone).Relevance to aliases: if aliases are added, any place that maps a domain token to a native type (fixtures, oracles, docs, codegen) must keep "domain token" and "native SQL type name" separate. An alias like
eql_v3.float8would map back to nativedouble precisiontoo.Crypto is name-independent (makes aliases cheap on the axis that matters)
Encryption is driven by the cipherstash
Cast(int/small_int/big_int/real/double/boolean), keyed onScalarKind— not the domain name. So an alias domain wraps byte-identical ciphertext: aliases cost nothing on the crypto/fixture axis (the expensive, creds-gated part) and full price only on the SQL surface (domains, extractors, wrappers, operators, casts), bindings, and matrix wiring.Options (from the earlier discussion)
int4spelling.DomainFamilyper alias — works but produces silently non-interchangeable types. ❌ foot-gun without casts.aliasas a first-class catalog concept — addaliases: &[&str](orcanonical+spellings) toDomainFamily; codegen emits the alias domain surface mechanically from the canonical, shares fixtures, and emits the implicit binary casts above so the two names are genuinely interchangeable. Bindings/docs pick the canonical struct name; the alias gets a doc note. This is the only option where "alias" is real.Recommendation
Only pursue (3) if there's concrete demand for both spellings to be callable (e.g. so the EQL column name reads the same as the native column). Prototype the
aliasesfield + generated implicit casts to size the real diff (and settle the CHECK-on-cast question) before committing. Otherwise (1) stands.Out of scope / notes
json/jsonb: not an alias pair in Postgres (genuinely different types) — decide separately, not part of this.timestamp: already carries the domain-token-vs-native-type split (timestampdomain,timestamp with time zoneplaintext) — a working precedent for the pattern option (3) needs.