11import { walk as walkSql } from '@pgsql/traverse' ;
2- import { transformSync , walk as walkPlpgsql } from 'plpgsql-parser' ;
2+ import { parseSql , transformSync , walk as walkPlpgsql } from 'plpgsql-parser' ;
33
44/**
55 * A (possibly schema-qualified) object name extracted from a statement.
@@ -321,6 +321,42 @@ function classifyOne(nodeTag: string, node: any): StatementFacts {
321321 return facts ;
322322}
323323
324+ /** Read a `CreateFunctionStmt` DefElem option's scalar/list value. */
325+ function functionOption ( node : any , defname : string ) : any {
326+ for ( const opt of node . options ?? [ ] ) {
327+ if ( opt ?. DefElem ?. defname === defname ) return opt . DefElem . arg ;
328+ }
329+ return undefined ;
330+ }
331+
332+ /**
333+ * Collect references from a `LANGUAGE sql` function body supplied as a string
334+ * literal (`AS $$ ... $$`). That body is an opaque String node the AST walker
335+ * never parses, so — mirroring the schema transformer's body rewrite — parse
336+ * it standalone and walk each statement with the facts visitor. The standard
337+ * `BEGIN ATOMIC` / `RETURN` `sql_body` form is already part of the AST and is
338+ * covered by the outer walk, so only the string form needs this.
339+ */
340+ function collectSqlBodyReferences ( node : any , facts : StatementFacts ) : void {
341+ const language = functionOption ( node , 'language' ) ?. String ?. sval ;
342+ if ( typeof language !== 'string' || language . toLowerCase ( ) !== 'sql' ) return ;
343+
344+ const asArg = functionOption ( node , 'as' ) ;
345+ const items : any [ ] = asArg ?. List ?. items ?? [ ] ;
346+ const body = items [ 0 ] ?. String ?. sval ;
347+ if ( typeof body !== 'string' ) return ;
348+
349+ try {
350+ const stmts : any [ ] = parseSql ( body ) ?. stmts ?? [ ] ;
351+ const visitor = createFactsVisitor ( facts , facts . bodyReferences ) ;
352+ for ( const stmt of stmts ) {
353+ if ( stmt ?. stmt ) walkSql ( stmt . stmt , visitor ) ;
354+ }
355+ } catch {
356+ // A non-parseable body (C symbol name, etc.) contributes no references.
357+ }
358+ }
359+
324360/**
325361 * Classify each top-level statement in a SQL script into {@link StatementFacts}.
326362 *
@@ -342,6 +378,9 @@ export function classifyStatements(sql: string): StatementFacts[] {
342378 if ( stmtNode ) {
343379 walkSql ( stmtNode , createFactsVisitor ( facts ) ) ;
344380 }
381+ if ( nodeTag === 'CreateFunctionStmt' ) {
382+ collectSqlBodyReferences ( node , facts ) ;
383+ }
345384 allFacts . push ( facts ) ;
346385 }
347386
0 commit comments