Skip to content

fix: typecheck false positive for SQL function args of table row type#770

Open
mvanhorn wants to merge 1 commit into
supabase-community:mainfrom
mvanhorn:fix/705-typecheck-row-type-false-positive
Open

fix: typecheck false positive for SQL function args of table row type#770
mvanhorn wants to merge 1 commit into
supabase-community:mainfrom
mvanhorn:fix/705-typecheck-row-type-false-positive

Conversation

@mvanhorn

Copy link
Copy Markdown
Contributor

Summary

pg typecheck reported a false-positive missing FROM-clause entry for table "row_arg" when a SQL function argument's type is a table's row (composite) type, for example:

CREATE TABLE public.tbl (id SERIAL PRIMARY KEY, name TEXT);

CREATE FUNCTION public.get_tbl_name(row_arg public.tbl)
  RETURNS TEXT LANGUAGE sql AS $$
    SELECT row_arg.name;
  $$;

This is valid in Postgres because the argument resolves to the row, but the language server flagged row_arg.name as an error.

Why this matters

Passing a table's row type as a function argument is a documented, common Postgres pattern. Reporting it as an error is a false positive: it flags correct code, which undermines trust in pg typecheck and can hide the real diagnostics a user cares about. This was reported in #705 with a minimal reproduction and confirmed by a maintainer.

Cause

To type-check a SQL function body, apply_identifiers replaces fn.arg[.field] references with default literals, resolving the argument type through the schema cache's types list. The query that populates that list (crates/pgls_schema_cache/src/queries/types.sql) only admitted base/enum types and standalone composites created with CREATE TYPE ... AS (...) (relkind = 'c'). A table's implicit row type has relkind = 'r' (or 'p' for a partitioned table), so it was never loaded. resolve_type then returned None, the row_arg.name reference was left unreplaced, and Postgres prepare reported the missing-FROM error.

Fix

Extend types.sql to also load the composite row types exposed by relations (ordinary tables, partitioned tables, views, materialized views, and foreign tables) along with their column attributes, admitting relkind IN ('c', 'r', 'p', 'v', 'm', 'f') in both the attributes sub-select and the outer type filter. The attribute join is restricted to attnum > 0 so relation system columns such as ctid/xmin are not mistaken for real fields. With the row type present in the cache, the existing composite-field-access branch in resolve_type mocks row_arg.name with the column's default literal, so the body type-checks cleanly. This completes the existing composite-type mocking path rather than depending on a separate type checker. The prepared .sqlx offline query cache is regenerated to match the updated query.

Tests

  • Integration (crates/pgls_typecheck/tests/diagnostics.rs): the reported MRE (select row_arg.name with row_arg public.tbl) now produces no diagnostic; a view-typed argument is covered as well.
  • Unit (crates/pgls_typecheck/src/typed_identifier.rs): a table-backed row-type argument has its .id and .name field references replaced with the columns' default literals, and a system-column reference (row_arg.ctid) is deliberately left unresolved so it still surfaces downstream.

Known limitations

Field access on row-type arguments uses the same identifier-substitution mocking as other function parameters, so it inherits that mechanism's existing limitations: a parameter name that also shadows a FROM alias, or an unqualified row-type name that is ambiguous across schemas under a custom search path, can still be substituted where Postgres would resolve differently. Addressing those cases belongs to the dedicated type checker discussed in #692 rather than to this query change.

Fixes #705

AI was used for assistance.

pg typecheck raised a false-positive "missing FROM-clause entry" when a
SQL function argument's type is a table's row (composite) type, e.g.
`get_tbl_name(row_arg public.tbl) ... SELECT row_arg.name`. The
schema-cache types query only loaded standalone composites (relkind 'c')
and skipped relation row types, so resolve_type returned None and the
field reference was left unreplaced.

Load row types for tables, partitioned tables, views, materialized views
and foreign tables (all relkinds that expose a composite row type) along
with their user column attributes, restricting the attribute join to
`attnum > 0` so system columns like ctid/xmin are not mistaken for real
fields. The prepared .sqlx offline cache is regenerated to match.

Fixes supabase-community#705
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQL Function: "missing FROM-clause" false positive when argument is row type

1 participant