From 0fb61315f0687fff9a301ec02425280a9a4c8b3c Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Wed, 7 Jan 2026 02:45:33 +0000 Subject: [PATCH] docs: update READMEs with new APIs and usage examples plpgsql-parser: - Add Return Type Helpers section (getReturnInfoFromParsedFunction) - Add Schema Rename Example showing heterogeneous AST transformation - Update Re-exports section with new exports plpgsql-deparser: - Add Return Context section for correct RETURN statement handling - Add Hydration Utilities section (hydrate/dehydrate/isHydratedExpr/getOriginalQuery) --- packages/plpgsql-deparser/README.md | 44 +++++++++++++++++ packages/plpgsql-parser/README.md | 74 +++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/packages/plpgsql-deparser/README.md b/packages/plpgsql-deparser/README.md index ad8148d1..11161166 100644 --- a/packages/plpgsql-deparser/README.md +++ b/packages/plpgsql-deparser/README.md @@ -175,6 +175,50 @@ interface PLpgSQLDeparserOptions { } ``` +## Return Context + +For correct `RETURN` statement handling, you can pass return type information: + +```typescript +import { deparseSync, ReturnInfo } from 'plpgsql-deparser'; + +// Without return info, the deparser uses conservative defaults +const body1 = deparseSync(parseResult); + +// With return info, the deparser emits correct RETURN vs RETURN NULL +const returnInfo: ReturnInfo = { kind: 'setof' }; +const body2 = deparseSync(parseResult, {}, returnInfo); +``` + +Supported return kinds: `'void'`, `'scalar'`, `'setof'`, `'trigger'`, `'out_params'` + +## Hydration Utilities + +The deparser includes utilities for working with "hydrated" PL/pgSQL ASTs, where embedded SQL expressions are parsed into SQL AST nodes for transformation. + +```typescript +import { + hydratePlpgsqlAst, + dehydratePlpgsqlAst, + isHydratedExpr, + getOriginalQuery +} from 'plpgsql-deparser'; + +// Hydrate: convert PLpgSQL_expr.query strings into SQL AST nodes +const hydrated = hydratePlpgsqlAst(plpgsqlFunction); + +// Now you can traverse and modify embedded SQL expressions as AST nodes +// (e.g., rename schemas, rewrite table references) + +// Dehydrate: convert SQL AST nodes back to query strings +const dehydrated = dehydratePlpgsqlAst(hydrated); + +// Then deparse to get the final function body +const body = deparseFunctionSync(dehydrated); +``` + +The `isHydratedExpr()` helper checks if an expression has been hydrated, and `getOriginalQuery()` retrieves the original query string from a hydrated expression. + ## Note on AST Structure The PL/pgSQL AST returned by `parsePlPgSQL` represents the internal structure of function bodies, not the `CREATE FUNCTION` statement itself. To get a complete function definition, you would need to: diff --git a/packages/plpgsql-parser/README.md b/packages/plpgsql-parser/README.md index addac178..c5b5780c 100644 --- a/packages/plpgsql-parser/README.md +++ b/packages/plpgsql-parser/README.md @@ -191,6 +191,78 @@ class PLpgSQLNodePath { } ``` +## Return Type Helpers + +Extract return type information from `CreateFunctionStmt` for correct RETURN statement handling: + +```typescript +import { parse, getReturnInfoFromParsedFunction, loadModule } from 'plpgsql-parser'; + +await loadModule(); + +const parsed = parse(` + CREATE FUNCTION get_users() RETURNS SETOF users LANGUAGE plpgsql AS $$ + BEGIN + RETURN QUERY SELECT * FROM users; + RETURN; + END; + $$; +`); + +const returnInfo = getReturnInfoFromParsedFunction(parsed.functions[0]); +console.log(returnInfo.kind); // 'setof' +``` + +The helper detects: `'void'`, `'scalar'`, `'setof'`, `'trigger'`, `'out_params'` + +## Schema Rename Example + +Transform schema names across both SQL and embedded PL/pgSQL expressions: + +```typescript +import { parse, walk, walkParsedScript, deparseSync, loadModule } from 'plpgsql-parser'; +import { walk as walkSql } from '@pgsql/traverse'; + +await loadModule(); + +const schemaMap = { app_public: 'myapp_v2', app_private: 'myapp_internal' }; + +function renameSchema(node: any) { + if (node.schemaname && schemaMap[node.schemaname]) { + node.schemaname = schemaMap[node.schemaname]; + } +} + +const parsed = parse(` + CREATE FUNCTION app_public.get_user(p_id int) + RETURNS app_public.users + LANGUAGE plpgsql AS $$ + BEGIN + RETURN (SELECT * FROM app_public.users WHERE id = p_id); + END; + $$; +`); + +// Rename schemas in outer SQL AST (function name, return type) +walkSql(parsed.sql, { + RangeVar: (path) => renameSchema(path.node), + TypeName: (path) => { + const names = path.node.names; + if (names?.[0]?.String?.sval && schemaMap[names[0].String.sval]) { + names[0].String.sval = schemaMap[names[0].String.sval]; + } + }, +}); + +// Rename schemas in PL/pgSQL embedded SQL (SELECT, INSERT, etc.) +walkParsedScript(parsed, {}, { + RangeVar: (path) => renameSchema(path.node), +}); + +const output = deparseSync(parsed); +// All app_public references are now myapp_v2 +``` + ## Re-exports For power users, the package re-exports underlying primitives: @@ -201,6 +273,8 @@ For power users, the package re-exports underlying primitives: - `deparsePlpgsqlBody` - PL/pgSQL deparser from `plpgsql-deparser` - `hydratePlpgsqlAst` - Hydration utility from `plpgsql-deparser` - `dehydratePlpgsqlAst` - Dehydration utility from `plpgsql-deparser` +- `getReturnInfo` - Extract return type from `CreateFunctionStmt` +- `ReturnInfo`, `ReturnInfoKind` - Return type info types ## License