Skip to content

information_schema compat: NULL AS udt_schema is INTEGER-typed, so filtering it against a string errors — breaks Metabase column sync #1019

Description

@likeshumidity

Problem

information_schema.columns is rewritten to main.information_schema_columns_compat (transpiler/transform/information_schema.go), and all three definitions of that view declare the UDT columns as bare, untyped NULL:

Definition udt_schema udt_name
server/catalog.go:1580 (standalone) NULL (:1640) NULL (:1641)
server/catalog.go:1667 (DuckLake mode) NULL (:1727) NULL (:1728)
server/sessionmeta/sessionmeta.go:744 (session) NULL (:884) c.udt_name (:885)

A bare NULL column resolves to INTEGER in DuckDB's catalog — DESCRIBE reports it as INTEGER, and that is the type a client sees over the wire. Any client that compares udt_schema to a string therefore fails, because the string literal gets coerced toward INT32 rather than the column being coerced toward text.

Metabase's Postgres driver does exactly that. Its column-sync query (src/metabase/driver/postgres.clj:339) schema-qualifies enum types with:

CASE WHEN c.udt_schema IN ('public', 'pg_catalog')
     THEN format('%s', c.udt_name)
     ELSE format('"%s"."%s"', c.udt_schema, c.udt_name)
END AS "database-type"

which comes back as:

ERROR: rpc error: code = Unknown desc = Conversion Error: Could not convert string 'public' to INT32

LINE 1: SELECT c.column_name AS name, CASE WHEN c.udt_schema IN ('public', 'pg_catalog') THEN COALESCE(c.udt_name::"varchar...
                                                                 ^

The caret is on the 'public' literal in the udt_schema IN list.

(The COALESCE(...::"varchar", '') in the echoed statement is the transpiler's own format('%s', …) rewrite from transpiler/transform/functions_compat.go:109, not what the client sent — noting it so the echo isn't mistaken for a client-side oddity.)

Impact

Metabase issues one describe-fields statement per schema covering every table at once, so a single failure means zero columns sync for the entire database. Tables show up with row counts, but every question against them fails with Table 'X' has no Fields associated with it. The database looks connected and half-populated rather than broken, which makes it hard to diagnose from the client side.

More generally this hits any client that filters or joins on udt_schema — a common pattern for resolving user-defined and enum types.

Reproduction

Client side: point Metabase (v1.63.2 / OSS v0.63.2, Postgres driver) at a duckgres endpoint, add it as a database, and let sync run. Field sync fails for every table; the error above appears in the Metabase logs from metabase.sync.util.

Minimal, engine-side — an INTEGER-typed udt_schema reproduces the error text and caret position exactly (DuckDB 1.5.5):

CREATE TABLE cols AS SELECT 'x' AS column_name, NULL AS udt_schema;
DESCRIBE cols;  -- udt_schema INTEGER
SELECT c.column_name FROM cols c WHERE c.udt_schema IN ('public','pg_catalog');
-- Conversion Error: Could not convert string 'public' to INT32

One thing I could not pin down from outside: a plain DuckDB view over NULL AS udt_schema keeps the column as SQLNULL at runtime and tolerates the comparison, even though DESCRIBE reports INTEGER. The failure needs the column to be genuinely INTEGER-typed by the time the predicate binds. So the type is hardening somewhere in the duckgres path — worker dispatch, the compat-view rebind, or the pgwire type resolution — and it would be worth confirming where before fixing, in case other compat columns are affected the same way. DESCRIBE main.information_schema_columns_compat on a live session should show it.

Notes on the fix

The narrow fix is a typed NULL in all three definitions:

NULL::VARCHAR AS udt_catalog,
NULL::VARCHAR AS udt_schema,

The idiom is already used elsewhere in the same files — CAST(NULL AS VARCHAR) AS type_udt_name at server/catalog.go:1905, NULL::INTEGER AS cache_size at server/sessionmeta/sessionmeta.go:731 — so not sure if this was intentional or unintentional difference for udt_catalog and udt_schema.

Two additional items you're there:

  1. Might be worth an audit of the other bare NULL AS <col> columns in the compat views. Under the SQL standard, most information_schema.columns columns are sql_identifier / character_data, i.e. text. Every one currently emitted as an untyped NULL is a latent version of this bug for any client that filters on it.

  2. udt_name diverges between the definitions. sessionmeta.go:885 returns the real c.udt_name, but both catalog.go definitions return NULL. Even after the type fix, a client on those paths gets a NULL udt_name for every column — so anything deriving a column's type from udt_name still comes back empty.


Found while investigating a Metabase instance syncing against a hosted PostHog warehouse endpoint. Source references are against main as of 2026-07-30; error text and caret position reproduced locally against DuckDB 1.5.5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions