Skip to content

feat(workspace): resolve statements in BEGIN ATOMIC and quoted function bodies - #918

Open
psteinroe wants to merge 1 commit into
feat/editor-best-effort-callfrom
feat/sql-function-bodies
Open

psteinroe wants to merge 1 commit into
feat/editor-best-effort-callfrom
feat/sql-function-bodies

Conversation

@psteinroe

@psteinroe psteinroe commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #914.

Statements in BEGIN ATOMIC bodies, RETURN bodies, and '...' bodies containing '' got no semantic tokens, inlay hints, hover, or lint diagnostics. This is workspace code shared with the linter, so it applies to linting too: these bodies are now linted like $$ bodies.

create function atomic_fn(p int) returns int language sql
begin atomic select p + id from users; end;
-- before: nothing; after: p is a parameter, id a column, users a table, hover works

create function quoted_fn(p text) returns text language sql
as 'select name || ''x'' || p from users where nope = 1';
-- before: nothing; after: tokens and hover, and `unknownColumn` on `nope` at the right place

What was wrong

  • get_sql_fn_body found the body with content.find(&body), using the unescaped body text. With a doubled quote in the source the text isn't there, so the body was never a statement of its own. And it could match an earlier occurrence of the same text, like a parameter default 'select 1' before as 'select 1'.
  • CreateFunctionStmt.sql_body (BEGIN ATOMIC ... END and RETURN expr) wasn't looked at, and get_sql_fn_signature required a LANGUAGE option, which these bodies don't need.

What changed

  • get_sql_fn_bodies (server/sql_function.rs) scans the CREATE FUNCTION statement and returns its body statements:
    • AS '...' / AS $tag$...$tag$: the string constant right after an AS token whose value is the body. For '...', it records where the '' escapes are.
    • BEGIN ATOMIC: one statement per ;, tracking parentheses and CASE ... END, up to the END that starts a statement. If the count doesn't match the parsed body, nothing is returned.
    • RETURN expr: resolved as select expr, which has the same length, so offsets don't move.
    • E'...' and U&'...' bodies are not supported (their unescaped value won't match).
  • pgls_analyser::StatementOffsets maps offsets between a statement's text and the document through the unescaping. AnalysableStatement carries it, with document_range, document_offset and statement_offset. The analyser maps diagnostic spans with it; fixes on such statements were already dropped because the lengths differ. The workspace uses it for semantic tokens, inlay hints, references_at, hover and completions (CompletionsResult::with_offset became with_ranges). Document::statement_offsets recomputes the map from the parent only for body statements that contain a '.
  • ParseIterator queues every body statement, in order.
  • Docs: the limitations list now only mentions PL/pgSQL and E'...' bodies.

Tests

  • sql_function unit tests: a default with the same text as the body, $body$ quoting, the offset map through '', BEGIN ATOMIC with CASE ... END and two statements, RETURN without a LANGUAGE option.
  • StatementOffsets unit test, both directions, including the second byte of an escape.
  • Workspace: tokens, hover and an unknownColumn diagnostic in BEGIN ATOMIC, RETURN and '' bodies; formatting of a '' body (now formatted like other bodies, into $$) and an idempotent BEGIN ATOMIC function.
  • I also ran the typecheck rules over the body statements of the Postgres regression files (15–18, ~380 body statements, 17 BEGIN ATOMIC functions per version) with a throwaway test. No findings in the new kinds of bodies. There is one pre-existing false positive in a $$ body on main, select array_prepend($1, $2) in formarray(anyelement, variadic anyarray) (polymorphism.sql); the regression suite doesn't analyse function bodies, so it doesn't catch it.

Risks / conflicts

  • ParseIterator::next is also changed on main (recursion removed). Expect a small conflict in the body-queueing block.
  • Every iteration of the document (linting, the editor walk) now scans each CREATE FUNCTION statement to find its body instead of a substring search. Repeated editor requests in perf(workspace): cache the walk of a document for hover, inlay hints and semantic tokens #930 skip the iteration.
  • The PL/pgSQL body range in pg_query.rs still uses content.find.

…on bodies

Find the body of a SQL function by the string constant after AS instead
of searching for its text, which failed when the body contains a doubled
quote and could match an earlier occurrence of the same text. Statements
of '...' bodies keep a map of their offsets through the unescaping, which
diagnostics, semantic tokens, inlay hints, hover and completions use.

The statements of BEGIN ATOMIC bodies, and the expression of RETURN
bodies, are statements of their own, like the statements of quoted
bodies.

This branch has not been deployed

No deployments
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.

1 participant