PostHog / PostHog/duckgres

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

Open
#1,019 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
180
Forks
18
Avg merge
10h 44m
Merged PRs (30d)
85

Description

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the three compat-view definitions in server/catalog.go at lines 1580 and 1667, and server/sessionmeta/sessionmeta.go at line 744; inspect the emitted types with DESCRIBE main.information_schema_columns_compat. Trace where the bare NULL becomes INTEGER, then verify that filtering udt_schema against string literals succeeds across all definitions and assess the other bare NULL columns for the same issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, sql
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.