Skip to content

feat(transform): classify references inside LANGUAGE sql function bodies - #320

Merged
pyramation merged 1 commit into
mainfrom
feat/classify-sql-language-bodies
Jul 29, 2026
Merged

feat(transform): classify references inside LANGUAGE sql function bodies#320
pyramation merged 1 commit into
mainfrom
feat/classify-sql-language-bodies

Conversation

@pyramation

Copy link
Copy Markdown
Collaborator

Summary

classifyStatements discovered references inside PL/pgSQL bodies (via the hydrated plpgsql walk) but treated a LANGUAGE sql function body supplied as a string literal (AS $$ … $$) as opaque — so its references were invisible:

classifyStatements(`CREATE FUNCTION catalog.product_slug() RETURNS text AS $$
  SELECT catalog.slugify(name) FROM catalog.products LIMIT 1;
$$ LANGUAGE sql STABLE;`)
// before: references: []            ← wrong
// after:  references: [catalog.products, catalog.slugify]

This is a correctness gap for any consumer that builds a dependency/reachability graph from the classifier facts: a LANGUAGE sql function reading a table produced no edge to that table. LANGUAGE sql bodies are extremely common, so the missing edges are not an edge case.

What changed

facts.ts now mirrors the schema transformer's existing body handling (transformSqlBodyString): for a CreateFunctionStmt whose language option is sql, it pulls the body string out of the as DefElem list, parses it standalone with parseSql, and walks each statement with the same facts visitor used for the outer AST — feeding both references and bodyReferences.

classifyStatements:
  for each CreateFunctionStmt:
    if language == 'sql' and AS is a string body:
      for stmt in parseSql(body).stmts:
        walkSql(stmt, factsVisitor(facts, facts.bodyReferences))

Scope guards:

  • Only LANGUAGE sql string bodies are parsed. LANGUAGE c (AS 'MODULE_PATHNAME','sym') and other languages are left untouched (verified by test).
  • The standard SQL-standard BEGIN ATOMIC … END / RETURN sql_body form is already part of the AST and covered by the outer walk — untouched here.
  • A body that doesn't parse standalone (odd fragments, C symbols) contributes no references (try/catch), matching the transformer's fallback behavior.
  • Self-references are still filtered out downstream, so a function calling a peer in the same body links to the peer but not to itself.

Tests

__tests__/facts.test.ts: adds coverage for reference extraction from a LANGUAGE sql string body (into both references and bodyReferences, excluding self-ref) and a negative test that a LANGUAGE c body string is not parsed as SQL. Full suite: 174 passed (172 previous + 2 new).

Downstream

Consumed by pgpm's shared/per-tenant object partitioner (constructive), which classifies which changes are safe to deploy once vs. must be materialized per tenant — soundness there depends on seeing references inside LANGUAGE sql bodies. Needs a publish to be consumed downstream.

Link to Devin session: https://app.devin.ai/sessions/025fb88043964fdbb335ac5e39df2478
Requested by: @pyramation

@pyramation pyramation self-assigned this Jul 29, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit 05ed735 into main Jul 29, 2026
11 checks passed
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