Skip to content

hasql: array-of-domain columns need a name-resolved codec, and fail only at run time #15

Description

@alexbiehl

Symptom

A query selecting a column whose type is an array of a domain fails at run time with

UnexpectedColumnTypeStatementError 33 1001 211356
                                      ^     ^ the domain's array OID in this database
                                      bytea[]

Nothing catches it at compile time. It surfaces the first time that query runs.

Why

hasql validates each result column's OID against the decoder. For a scalar domain column PostgreSQL reports the domain's base type in the row description, so Hasql.Decoders.bytea decodes a bytea-backed domain column fine — which is what sqlc-hs emits, and it is correct. For an array of a domain it reports the domain's own array OID instead, and the plain bytea/text decoder's array OID no longer matches.

The array OID is assigned per database, so it cannot be a constant.

What the fix looks like

Hasql.{Encoders,Decoders}.custom takes a type name and Nothing for the static OIDs, and hasql then resolves both the scalar and array OID from pg_type by name at run time. So for an array-of-domain column the codec wants to be

Hasql.Decoders.custom Nothing "opcua_attribute_value" Nothing [] (\_lookup bytes -> ...)

rather than Hasql.Decoders.bytea. Encoders.custom takes one argument more than the decoder — a render function used only in error messages.

sqlc-hs already has everything it needs to decide this: column ^. #isArray says it is an array, and columnDataType (column ^. #type') is the domain name. hasqlColumnCodec is where the choice would go.

Workaround today

Per-column hasql_encoder / hasql_decoder overrides naming a hand-written codec, one pair per column, plus nullable: true and nullable: false variants of each (see the other issue about nullable defaulting). In an application with four such columns that is eight override entries and two hand-written codec pairs to express something the plugin could derive.

Finding the affected columns in a schema:

SELECT c.relname, a.attname
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum > 0 AND NOT a.attisdropped
JOIN pg_type t ON t.oid = a.atttypid
JOIN pg_type el ON el.oid = t.typelem
WHERE c.relkind = 'r' AND t.typcategory = 'A' AND el.typtype = 'd';

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions