{"record":{"id":"e8a9587077601491","repo":"windmill-labs/windmill","slug":"unexpected-null-table-name-for-schema-in-pu","errorCode":null,"errorMessage":"Unexpected NULL `table_name` for schema `{}` in publication `{}`. This should never happen unless PostgreSQL internals are corrupted.","messagePattern":"Unexpected NULL `table_name` for schema `(.+?)` in publication `(.+?)`\\. This should never happen unless PostgreSQL internals are corrupted\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-trigger-postgres/src/handler.rs","lineNumber":942,"sourceCode":"\n    let mut table_to_track: HashMap<String, Relations> = HashMap::new();\n\n    for row in rows {\n        let schema_name: Option<String> = row.get(\"schema_name\");\n        let table_name: Option<String> = row.get(\"table_name\");\n        let columns: Option<Vec<String>> = row.get(\"columns\");\n        let where_clause: Option<String> = row.get(\"where_clause\");\n\n        let schema_name = schema_name.ok_or_else::<Error, _>(|| {\n            anyhow::anyhow!(\n                \"Unexpected NULL `schema_name` in publication entry (pubname: `{}`). This should never happen unless PostgreSQL internals are corrupted.\",\n                publication_name,\n            )\n            .into()\n        })?;\n\n        let table_name = table_name.ok_or_else::<Error, _>(|| {\n            anyhow::anyhow!(\n                \"Unexpected NULL `table_name` for schema `{}` in publication `{}`. This should never happen unless PostgreSQL internals are corrupted.\",\n                schema_name,\n                publication_name,\n            )\n            .into()\n        })?;\n\n        let entry = table_to_track.entry(schema_name.clone());\n        let table_to_track_item = TableToTrack::new(table_name, where_clause, columns);\n\n        match entry {\n            std::collections::hash_map::Entry::Occupied(mut occupied) => {\n                occupied.get_mut().add_new_table(table_to_track_item);\n            }\n            std::collections::hash_map::Entry::Vacant(vacant) => {\n                vacant.insert(Relations::new(schema_name, vec![table_to_track_item]));\n            }\n        }","sourceCodeStart":924,"sourceCodeEnd":960,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-trigger-postgres/src/handler.rs#L924-L960","documentation":"Same catalog-integrity guard as its sibling: when reading publication relations, a NULL table_name for a known schema inside a publication is treated as impossible in a healthy PostgreSQL and aborts with this error naming the schema and publication.","triggerScenarios":"The publication-info query returns a row where table_name is NULL for the given schema in publication {pubname} — indicating corrupted pg catalog data or a non-standard server answering the query.","commonSituations":"Corrupted system catalogs after failed upgrades/restores; third-party Postgres-compatible servers (proxies, shims) returning NULL columns; version mismatches between the driver's query assumptions and the server.","solutions":["Inspect pg_publication_tables directly on the server to confirm what the catalog really contains","Restore catalog integrity (clean backup restore or recovery via support channels)","Test against vanilla PostgreSQL of a supported version to rule out shim/proxy interference","Drop and recreate the publication if only its metadata is corrupted","File a Windmill issue with Postgres version and query output if it reproduces on a healthy DB"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"-- detect NULL table names in the publication before use\nSELECT pubname, schemaname, tablename\nFROM pg_publication_tables\nWHERE pubname = 'my_publication' AND tablename IS NULL;\n-- must return zero rows","typeGuard":null,"tryCatchPattern":"if let Err(e) = get_publication_info(...).await {\n    if e.to_string().contains(\"Unexpected NULL `table_name`\") {\n        // recreate the publication metadata instead of retrying\n        sqlx::query(\"DROP PUBLICATION my_publication\").execute(&mut *tx).await?;\n        sqlx::query(\"CREATE PUBLICATION my_publication FOR TABLE ...\").execute(&mut *tx).await?;\n    }\n    return Err(e.into());\n}","preventionTips":["Recreate the publication if its metadata rows look NULL/corrupt","Compare behavior on vanilla PostgreSQL to rule out server shims","Check the publication after major Postgres version upgrades","Pin to Postgres versions in Windmill's supported matrix"],"tags":["postgres","catalog","corruption","triggers"],"backgroundTag":"postgres-catalog-corruption","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}