plpgsql-parse 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/body-scanner.d.ts +41 -0
- package/body-scanner.js +80 -0
- package/deparse.d.ts +22 -0
- package/deparse.js +577 -0
- package/esm/body-scanner.js +76 -0
- package/esm/deparse.js +573 -0
- package/esm/index.js +18 -0
- package/esm/parse.js +119 -0
- package/esm/return-info.js +62 -0
- package/esm/types.js +7 -0
- package/index.d.ts +17 -0
- package/index.js +31 -0
- package/package.json +54 -0
- package/parse.d.ts +25 -0
- package/parse.js +124 -0
- package/return-info.d.ts +17 -0
- package/return-info.js +66 -0
- package/types.d.ts +42 -0
- package/types.js +8 -0
package/esm/parse.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced parse that wraps plpgsql-parser and pgsql-parse to preserve
|
|
3
|
+
* both outer SQL comments/whitespace AND comments inside PL/pgSQL
|
|
4
|
+
* function bodies.
|
|
5
|
+
*
|
|
6
|
+
* This module does NOT modify plpgsql-parser or pgsql-parse — it
|
|
7
|
+
* composes them and adds body-comment metadata on top.
|
|
8
|
+
*/
|
|
9
|
+
import { parseSync as parseSqlSync, loadModule, } from '@libpg-query/parser';
|
|
10
|
+
import { parseSync as pgsqlParseSync } from 'pgsql-parse';
|
|
11
|
+
import { scanBodyComments } from './body-scanner';
|
|
12
|
+
export { loadModule };
|
|
13
|
+
/**
|
|
14
|
+
* Check if a statement is a CREATE FUNCTION with LANGUAGE plpgsql.
|
|
15
|
+
*/
|
|
16
|
+
function isPlpgsqlFunction(stmt) {
|
|
17
|
+
const createFunctionStmt = stmt?.CreateFunctionStmt;
|
|
18
|
+
if (!createFunctionStmt)
|
|
19
|
+
return false;
|
|
20
|
+
const options = createFunctionStmt.options;
|
|
21
|
+
if (!options)
|
|
22
|
+
return false;
|
|
23
|
+
for (const opt of options) {
|
|
24
|
+
if (opt?.DefElem?.defname === 'language') {
|
|
25
|
+
const arg = opt.DefElem.arg;
|
|
26
|
+
if (arg?.String?.sval) {
|
|
27
|
+
return arg.String.sval.toLowerCase() === 'plpgsql';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Extract the raw function body and dollar-quote delimiter from a
|
|
35
|
+
* CREATE FUNCTION statement's AST options.
|
|
36
|
+
*/
|
|
37
|
+
function getBodyFromOptions(options) {
|
|
38
|
+
if (!options)
|
|
39
|
+
return null;
|
|
40
|
+
for (const opt of options) {
|
|
41
|
+
if (opt?.DefElem?.defname === 'as') {
|
|
42
|
+
const arg = opt.DefElem.arg;
|
|
43
|
+
if (arg?.List?.items?.[0]?.String?.sval) {
|
|
44
|
+
return { raw: arg.List.items[0].String.sval, delimiter: '$$' };
|
|
45
|
+
}
|
|
46
|
+
if (arg?.String?.sval) {
|
|
47
|
+
return { raw: arg.String.sval, delimiter: '$$' };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parse SQL with full comment preservation — both outer SQL level
|
|
55
|
+
* (between statements) and inside PL/pgSQL function bodies.
|
|
56
|
+
*
|
|
57
|
+
* Combines:
|
|
58
|
+
* - pgsql-parse's scanner for outer SQL comments/whitespace
|
|
59
|
+
* - plpgsql-parser's PL/pgSQL body parsing + hydration
|
|
60
|
+
* - body-scanner for function body comments
|
|
61
|
+
*/
|
|
62
|
+
export function parseSync(sql) {
|
|
63
|
+
// 1. Outer SQL: use pgsql-parse for comment/whitespace preservation
|
|
64
|
+
const enhanced = pgsqlParseSync(sql);
|
|
65
|
+
// 2. Also parse with the standard SQL parser to get statement ASTs
|
|
66
|
+
const sqlResult = parseSqlSync(sql);
|
|
67
|
+
const stmts = sqlResult.stmts ?? [];
|
|
68
|
+
// 3. For each PL/pgSQL function, extract body comments
|
|
69
|
+
const functions = [];
|
|
70
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
71
|
+
const rawStmt = stmts[i];
|
|
72
|
+
const stmt = rawStmt?.stmt;
|
|
73
|
+
if (!stmt || !isPlpgsqlFunction(stmt))
|
|
74
|
+
continue;
|
|
75
|
+
const createFunctionStmt = stmt.CreateFunctionStmt;
|
|
76
|
+
const body = getBodyFromOptions(createFunctionStmt.options);
|
|
77
|
+
if (!body)
|
|
78
|
+
continue;
|
|
79
|
+
// Scan the body for comments
|
|
80
|
+
const comments = scanBodyComments(body.raw);
|
|
81
|
+
if (comments.length === 0)
|
|
82
|
+
continue;
|
|
83
|
+
// Find the matching index in the enhanced stmts array.
|
|
84
|
+
// The enhanced array may have synthetic nodes interleaved,
|
|
85
|
+
// so we match by searching for the RawStmt at the right position.
|
|
86
|
+
const enhancedIndex = findEnhancedStmtIndex(enhanced, i);
|
|
87
|
+
functions.push({
|
|
88
|
+
stmtIndex: enhancedIndex,
|
|
89
|
+
originalBody: body.raw,
|
|
90
|
+
delimiter: body.delimiter,
|
|
91
|
+
comments,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return { enhanced, functions };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Async version of parseSync.
|
|
98
|
+
*/
|
|
99
|
+
export async function parse(sql) {
|
|
100
|
+
return parseSync(sql);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Find the index of the i-th RawStmt in the enhanced stmts array.
|
|
104
|
+
* The enhanced array interleaves RawComment and RawWhitespace nodes,
|
|
105
|
+
* so the n-th real statement isn't at index n.
|
|
106
|
+
*/
|
|
107
|
+
function findEnhancedStmtIndex(enhanced, rawStmtIndex) {
|
|
108
|
+
let realCount = 0;
|
|
109
|
+
for (let j = 0; j < enhanced.stmts.length; j++) {
|
|
110
|
+
const entry = enhanced.stmts[j];
|
|
111
|
+
if ('stmt' in entry) {
|
|
112
|
+
if (realCount === rawStmtIndex)
|
|
113
|
+
return j;
|
|
114
|
+
realCount++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Fallback: return the rawStmtIndex (shouldn't happen)
|
|
118
|
+
return rawStmtIndex;
|
|
119
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return info extraction for PL/pgSQL functions.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the upstream helper from plpgsql-parser's return-info module
|
|
5
|
+
* pattern, but implemented locally to avoid depending on plpgsql-parser
|
|
6
|
+
* (which would create a circular dependency).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Extract return type information from a CreateFunctionStmt AST node.
|
|
10
|
+
*/
|
|
11
|
+
export function getReturnInfo(createFunctionStmt) {
|
|
12
|
+
if (!createFunctionStmt) {
|
|
13
|
+
return { kind: 'scalar' };
|
|
14
|
+
}
|
|
15
|
+
// Procedures have implicit void return
|
|
16
|
+
if (createFunctionStmt.is_procedure) {
|
|
17
|
+
return { kind: 'void' };
|
|
18
|
+
}
|
|
19
|
+
// Check for OUT/INOUT/TABLE parameters
|
|
20
|
+
if (createFunctionStmt.parameters && Array.isArray(createFunctionStmt.parameters)) {
|
|
21
|
+
const hasOutParams = createFunctionStmt.parameters.some((param) => {
|
|
22
|
+
const fp = param?.FunctionParameter;
|
|
23
|
+
if (!fp)
|
|
24
|
+
return false;
|
|
25
|
+
const mode = fp.mode;
|
|
26
|
+
return mode === 'FUNC_PARAM_OUT' ||
|
|
27
|
+
mode === 'FUNC_PARAM_INOUT' ||
|
|
28
|
+
mode === 'FUNC_PARAM_TABLE';
|
|
29
|
+
});
|
|
30
|
+
if (hasOutParams) {
|
|
31
|
+
return { kind: 'out_params' };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Check the return type
|
|
35
|
+
const returnType = createFunctionStmt.returnType;
|
|
36
|
+
if (!returnType) {
|
|
37
|
+
return { kind: 'void' };
|
|
38
|
+
}
|
|
39
|
+
// SETOF
|
|
40
|
+
if (returnType.setof) {
|
|
41
|
+
return { kind: 'setof' };
|
|
42
|
+
}
|
|
43
|
+
// Extract type name
|
|
44
|
+
const names = returnType.names
|
|
45
|
+
?.map((n) => n?.String?.sval)
|
|
46
|
+
.filter((s) => typeof s === 'string') ?? [];
|
|
47
|
+
const lastName = names[names.length - 1]?.toLowerCase() ?? '';
|
|
48
|
+
if (lastName === 'void')
|
|
49
|
+
return { kind: 'void' };
|
|
50
|
+
if (lastName === 'trigger')
|
|
51
|
+
return { kind: 'trigger' };
|
|
52
|
+
return { kind: 'scalar' };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get return info from a parsed function's stmt field.
|
|
56
|
+
*/
|
|
57
|
+
export function getReturnInfoFromParsedFunction(parsedFunction) {
|
|
58
|
+
if (!parsedFunction?.stmt) {
|
|
59
|
+
return { kind: 'scalar' };
|
|
60
|
+
}
|
|
61
|
+
return getReturnInfo(parsedFunction.stmt);
|
|
62
|
+
}
|
package/esm/types.js
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plpgsql-parse — Comment and whitespace preserving PL/pgSQL parser.
|
|
3
|
+
*
|
|
4
|
+
* Drop-in enhancement over plpgsql-parser that preserves SQL -- line
|
|
5
|
+
* comments and vertical whitespace (blank lines) through parse→deparse
|
|
6
|
+
* round trips, both at the outer SQL level (between statements) and
|
|
7
|
+
* inside PL/pgSQL function bodies.
|
|
8
|
+
*
|
|
9
|
+
* This package wraps plpgsql-parser, plpgsql-deparser, and pgsql-parse
|
|
10
|
+
* without modifying any of them.
|
|
11
|
+
*/
|
|
12
|
+
export { parse, parseSync, loadModule } from './parse';
|
|
13
|
+
export { deparse, deparseSync, type DeparseOptions } from './deparse';
|
|
14
|
+
export type { PlpgsqlParseResult, FunctionComments, BodyComment, } from './types';
|
|
15
|
+
export { scanBodyComments, groupCommentsByAnchor, type CommentGroup } from './body-scanner';
|
|
16
|
+
export type { EnhancedParseResult, EnhancedStmt, RawComment, RawWhitespace } from 'pgsql-parse';
|
|
17
|
+
export { isRawComment, isRawWhitespace, isRawStmt } from 'pgsql-parse';
|
package/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* plpgsql-parse — Comment and whitespace preserving PL/pgSQL parser.
|
|
4
|
+
*
|
|
5
|
+
* Drop-in enhancement over plpgsql-parser that preserves SQL -- line
|
|
6
|
+
* comments and vertical whitespace (blank lines) through parse→deparse
|
|
7
|
+
* round trips, both at the outer SQL level (between statements) and
|
|
8
|
+
* inside PL/pgSQL function bodies.
|
|
9
|
+
*
|
|
10
|
+
* This package wraps plpgsql-parser, plpgsql-deparser, and pgsql-parse
|
|
11
|
+
* without modifying any of them.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.isRawStmt = exports.isRawWhitespace = exports.isRawComment = exports.groupCommentsByAnchor = exports.scanBodyComments = exports.deparseSync = exports.deparse = exports.loadModule = exports.parseSync = exports.parse = void 0;
|
|
15
|
+
// Enhanced parse functions (comment/whitespace preserving)
|
|
16
|
+
var parse_1 = require("./parse");
|
|
17
|
+
Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parse_1.parse; } });
|
|
18
|
+
Object.defineProperty(exports, "parseSync", { enumerable: true, get: function () { return parse_1.parseSync; } });
|
|
19
|
+
Object.defineProperty(exports, "loadModule", { enumerable: true, get: function () { return parse_1.loadModule; } });
|
|
20
|
+
// Enhanced deparse functions
|
|
21
|
+
var deparse_1 = require("./deparse");
|
|
22
|
+
Object.defineProperty(exports, "deparse", { enumerable: true, get: function () { return deparse_1.deparse; } });
|
|
23
|
+
Object.defineProperty(exports, "deparseSync", { enumerable: true, get: function () { return deparse_1.deparseSync; } });
|
|
24
|
+
// Body scanner (for advanced use)
|
|
25
|
+
var body_scanner_1 = require("./body-scanner");
|
|
26
|
+
Object.defineProperty(exports, "scanBodyComments", { enumerable: true, get: function () { return body_scanner_1.scanBodyComments; } });
|
|
27
|
+
Object.defineProperty(exports, "groupCommentsByAnchor", { enumerable: true, get: function () { return body_scanner_1.groupCommentsByAnchor; } });
|
|
28
|
+
var pgsql_parse_1 = require("pgsql-parse");
|
|
29
|
+
Object.defineProperty(exports, "isRawComment", { enumerable: true, get: function () { return pgsql_parse_1.isRawComment; } });
|
|
30
|
+
Object.defineProperty(exports, "isRawWhitespace", { enumerable: true, get: function () { return pgsql_parse_1.isRawWhitespace; } });
|
|
31
|
+
Object.defineProperty(exports, "isRawStmt", { enumerable: true, get: function () { return pgsql_parse_1.isRawStmt; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "plpgsql-parse",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"author": "Constructive <developers@constructive.io>",
|
|
5
|
+
"description": "Comment and whitespace preserving PL/pgSQL parser",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "esm/index.js",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"homepage": "https://github.com/constructive-io/pgsql-parser",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"directory": "dist"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/constructive-io/pgsql-parser"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/constructive-io/pgsql-parser/issues"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"copy": "makage assets",
|
|
24
|
+
"clean": "makage clean dist",
|
|
25
|
+
"prepublishOnly": "npm run build",
|
|
26
|
+
"build": "npm run clean && tsc && tsc -p tsconfig.esm.json && npm run copy",
|
|
27
|
+
"build:dev": "npm run clean && tsc --declarationMap && tsc -p tsconfig.esm.json && npm run copy",
|
|
28
|
+
"lint": "eslint . --fix",
|
|
29
|
+
"test": "jest",
|
|
30
|
+
"test:watch": "jest --watch"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"sql",
|
|
34
|
+
"postgres",
|
|
35
|
+
"postgresql",
|
|
36
|
+
"pg",
|
|
37
|
+
"plpgsql",
|
|
38
|
+
"parser",
|
|
39
|
+
"comment",
|
|
40
|
+
"whitespace",
|
|
41
|
+
"round-trip"
|
|
42
|
+
],
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@libpg-query/parser": "^17.6.10",
|
|
45
|
+
"@pgsql/types": "^17.6.2",
|
|
46
|
+
"pgsql-deparser": "17.18.3",
|
|
47
|
+
"pgsql-parse": "0.2.0",
|
|
48
|
+
"plpgsql-deparser": "0.7.7"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"makage": "^0.1.8"
|
|
52
|
+
},
|
|
53
|
+
"gitHead": "b4dd31a9a1b34bdb74c13b5cf597bf8910b1762d"
|
|
54
|
+
}
|
package/parse.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced parse that wraps plpgsql-parser and pgsql-parse to preserve
|
|
3
|
+
* both outer SQL comments/whitespace AND comments inside PL/pgSQL
|
|
4
|
+
* function bodies.
|
|
5
|
+
*
|
|
6
|
+
* This module does NOT modify plpgsql-parser or pgsql-parse — it
|
|
7
|
+
* composes them and adds body-comment metadata on top.
|
|
8
|
+
*/
|
|
9
|
+
import { loadModule } from '@libpg-query/parser';
|
|
10
|
+
import type { PlpgsqlParseResult } from './types';
|
|
11
|
+
export { loadModule };
|
|
12
|
+
/**
|
|
13
|
+
* Parse SQL with full comment preservation — both outer SQL level
|
|
14
|
+
* (between statements) and inside PL/pgSQL function bodies.
|
|
15
|
+
*
|
|
16
|
+
* Combines:
|
|
17
|
+
* - pgsql-parse's scanner for outer SQL comments/whitespace
|
|
18
|
+
* - plpgsql-parser's PL/pgSQL body parsing + hydration
|
|
19
|
+
* - body-scanner for function body comments
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseSync(sql: string): PlpgsqlParseResult;
|
|
22
|
+
/**
|
|
23
|
+
* Async version of parseSync.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parse(sql: string): Promise<PlpgsqlParseResult>;
|
package/parse.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced parse that wraps plpgsql-parser and pgsql-parse to preserve
|
|
4
|
+
* both outer SQL comments/whitespace AND comments inside PL/pgSQL
|
|
5
|
+
* function bodies.
|
|
6
|
+
*
|
|
7
|
+
* This module does NOT modify plpgsql-parser or pgsql-parse — it
|
|
8
|
+
* composes them and adds body-comment metadata on top.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.loadModule = void 0;
|
|
12
|
+
exports.parseSync = parseSync;
|
|
13
|
+
exports.parse = parse;
|
|
14
|
+
const parser_1 = require("@libpg-query/parser");
|
|
15
|
+
Object.defineProperty(exports, "loadModule", { enumerable: true, get: function () { return parser_1.loadModule; } });
|
|
16
|
+
const pgsql_parse_1 = require("pgsql-parse");
|
|
17
|
+
const body_scanner_1 = require("./body-scanner");
|
|
18
|
+
/**
|
|
19
|
+
* Check if a statement is a CREATE FUNCTION with LANGUAGE plpgsql.
|
|
20
|
+
*/
|
|
21
|
+
function isPlpgsqlFunction(stmt) {
|
|
22
|
+
const createFunctionStmt = stmt?.CreateFunctionStmt;
|
|
23
|
+
if (!createFunctionStmt)
|
|
24
|
+
return false;
|
|
25
|
+
const options = createFunctionStmt.options;
|
|
26
|
+
if (!options)
|
|
27
|
+
return false;
|
|
28
|
+
for (const opt of options) {
|
|
29
|
+
if (opt?.DefElem?.defname === 'language') {
|
|
30
|
+
const arg = opt.DefElem.arg;
|
|
31
|
+
if (arg?.String?.sval) {
|
|
32
|
+
return arg.String.sval.toLowerCase() === 'plpgsql';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Extract the raw function body and dollar-quote delimiter from a
|
|
40
|
+
* CREATE FUNCTION statement's AST options.
|
|
41
|
+
*/
|
|
42
|
+
function getBodyFromOptions(options) {
|
|
43
|
+
if (!options)
|
|
44
|
+
return null;
|
|
45
|
+
for (const opt of options) {
|
|
46
|
+
if (opt?.DefElem?.defname === 'as') {
|
|
47
|
+
const arg = opt.DefElem.arg;
|
|
48
|
+
if (arg?.List?.items?.[0]?.String?.sval) {
|
|
49
|
+
return { raw: arg.List.items[0].String.sval, delimiter: '$$' };
|
|
50
|
+
}
|
|
51
|
+
if (arg?.String?.sval) {
|
|
52
|
+
return { raw: arg.String.sval, delimiter: '$$' };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse SQL with full comment preservation — both outer SQL level
|
|
60
|
+
* (between statements) and inside PL/pgSQL function bodies.
|
|
61
|
+
*
|
|
62
|
+
* Combines:
|
|
63
|
+
* - pgsql-parse's scanner for outer SQL comments/whitespace
|
|
64
|
+
* - plpgsql-parser's PL/pgSQL body parsing + hydration
|
|
65
|
+
* - body-scanner for function body comments
|
|
66
|
+
*/
|
|
67
|
+
function parseSync(sql) {
|
|
68
|
+
// 1. Outer SQL: use pgsql-parse for comment/whitespace preservation
|
|
69
|
+
const enhanced = (0, pgsql_parse_1.parseSync)(sql);
|
|
70
|
+
// 2. Also parse with the standard SQL parser to get statement ASTs
|
|
71
|
+
const sqlResult = (0, parser_1.parseSync)(sql);
|
|
72
|
+
const stmts = sqlResult.stmts ?? [];
|
|
73
|
+
// 3. For each PL/pgSQL function, extract body comments
|
|
74
|
+
const functions = [];
|
|
75
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
76
|
+
const rawStmt = stmts[i];
|
|
77
|
+
const stmt = rawStmt?.stmt;
|
|
78
|
+
if (!stmt || !isPlpgsqlFunction(stmt))
|
|
79
|
+
continue;
|
|
80
|
+
const createFunctionStmt = stmt.CreateFunctionStmt;
|
|
81
|
+
const body = getBodyFromOptions(createFunctionStmt.options);
|
|
82
|
+
if (!body)
|
|
83
|
+
continue;
|
|
84
|
+
// Scan the body for comments
|
|
85
|
+
const comments = (0, body_scanner_1.scanBodyComments)(body.raw);
|
|
86
|
+
if (comments.length === 0)
|
|
87
|
+
continue;
|
|
88
|
+
// Find the matching index in the enhanced stmts array.
|
|
89
|
+
// The enhanced array may have synthetic nodes interleaved,
|
|
90
|
+
// so we match by searching for the RawStmt at the right position.
|
|
91
|
+
const enhancedIndex = findEnhancedStmtIndex(enhanced, i);
|
|
92
|
+
functions.push({
|
|
93
|
+
stmtIndex: enhancedIndex,
|
|
94
|
+
originalBody: body.raw,
|
|
95
|
+
delimiter: body.delimiter,
|
|
96
|
+
comments,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return { enhanced, functions };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Async version of parseSync.
|
|
103
|
+
*/
|
|
104
|
+
async function parse(sql) {
|
|
105
|
+
return parseSync(sql);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Find the index of the i-th RawStmt in the enhanced stmts array.
|
|
109
|
+
* The enhanced array interleaves RawComment and RawWhitespace nodes,
|
|
110
|
+
* so the n-th real statement isn't at index n.
|
|
111
|
+
*/
|
|
112
|
+
function findEnhancedStmtIndex(enhanced, rawStmtIndex) {
|
|
113
|
+
let realCount = 0;
|
|
114
|
+
for (let j = 0; j < enhanced.stmts.length; j++) {
|
|
115
|
+
const entry = enhanced.stmts[j];
|
|
116
|
+
if ('stmt' in entry) {
|
|
117
|
+
if (realCount === rawStmtIndex)
|
|
118
|
+
return j;
|
|
119
|
+
realCount++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Fallback: return the rawStmtIndex (shouldn't happen)
|
|
123
|
+
return rawStmtIndex;
|
|
124
|
+
}
|
package/return-info.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return info extraction for PL/pgSQL functions.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the upstream helper from plpgsql-parser's return-info module
|
|
5
|
+
* pattern, but implemented locally to avoid depending on plpgsql-parser
|
|
6
|
+
* (which would create a circular dependency).
|
|
7
|
+
*/
|
|
8
|
+
import type { ReturnInfo, ReturnInfoKind } from 'plpgsql-deparser';
|
|
9
|
+
/**
|
|
10
|
+
* Extract return type information from a CreateFunctionStmt AST node.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getReturnInfo(createFunctionStmt: any): ReturnInfo;
|
|
13
|
+
/**
|
|
14
|
+
* Get return info from a parsed function's stmt field.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getReturnInfoFromParsedFunction(parsedFunction: any): ReturnInfo;
|
|
17
|
+
export type { ReturnInfo, ReturnInfoKind };
|
package/return-info.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Return info extraction for PL/pgSQL functions.
|
|
4
|
+
*
|
|
5
|
+
* Re-exports the upstream helper from plpgsql-parser's return-info module
|
|
6
|
+
* pattern, but implemented locally to avoid depending on plpgsql-parser
|
|
7
|
+
* (which would create a circular dependency).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.getReturnInfo = getReturnInfo;
|
|
11
|
+
exports.getReturnInfoFromParsedFunction = getReturnInfoFromParsedFunction;
|
|
12
|
+
/**
|
|
13
|
+
* Extract return type information from a CreateFunctionStmt AST node.
|
|
14
|
+
*/
|
|
15
|
+
function getReturnInfo(createFunctionStmt) {
|
|
16
|
+
if (!createFunctionStmt) {
|
|
17
|
+
return { kind: 'scalar' };
|
|
18
|
+
}
|
|
19
|
+
// Procedures have implicit void return
|
|
20
|
+
if (createFunctionStmt.is_procedure) {
|
|
21
|
+
return { kind: 'void' };
|
|
22
|
+
}
|
|
23
|
+
// Check for OUT/INOUT/TABLE parameters
|
|
24
|
+
if (createFunctionStmt.parameters && Array.isArray(createFunctionStmt.parameters)) {
|
|
25
|
+
const hasOutParams = createFunctionStmt.parameters.some((param) => {
|
|
26
|
+
const fp = param?.FunctionParameter;
|
|
27
|
+
if (!fp)
|
|
28
|
+
return false;
|
|
29
|
+
const mode = fp.mode;
|
|
30
|
+
return mode === 'FUNC_PARAM_OUT' ||
|
|
31
|
+
mode === 'FUNC_PARAM_INOUT' ||
|
|
32
|
+
mode === 'FUNC_PARAM_TABLE';
|
|
33
|
+
});
|
|
34
|
+
if (hasOutParams) {
|
|
35
|
+
return { kind: 'out_params' };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Check the return type
|
|
39
|
+
const returnType = createFunctionStmt.returnType;
|
|
40
|
+
if (!returnType) {
|
|
41
|
+
return { kind: 'void' };
|
|
42
|
+
}
|
|
43
|
+
// SETOF
|
|
44
|
+
if (returnType.setof) {
|
|
45
|
+
return { kind: 'setof' };
|
|
46
|
+
}
|
|
47
|
+
// Extract type name
|
|
48
|
+
const names = returnType.names
|
|
49
|
+
?.map((n) => n?.String?.sval)
|
|
50
|
+
.filter((s) => typeof s === 'string') ?? [];
|
|
51
|
+
const lastName = names[names.length - 1]?.toLowerCase() ?? '';
|
|
52
|
+
if (lastName === 'void')
|
|
53
|
+
return { kind: 'void' };
|
|
54
|
+
if (lastName === 'trigger')
|
|
55
|
+
return { kind: 'trigger' };
|
|
56
|
+
return { kind: 'scalar' };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get return info from a parsed function's stmt field.
|
|
60
|
+
*/
|
|
61
|
+
function getReturnInfoFromParsedFunction(parsedFunction) {
|
|
62
|
+
if (!parsedFunction?.stmt) {
|
|
63
|
+
return { kind: 'scalar' };
|
|
64
|
+
}
|
|
65
|
+
return getReturnInfo(parsedFunction.stmt);
|
|
66
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for plpgsql-parse — comment-preserving PL/pgSQL parser.
|
|
3
|
+
*
|
|
4
|
+
* Re-uses pgsql-parse's EnhancedParseResult for the outer SQL level
|
|
5
|
+
* and adds body-level comment metadata for PL/pgSQL function bodies.
|
|
6
|
+
*/
|
|
7
|
+
import type { EnhancedParseResult } from 'pgsql-parse';
|
|
8
|
+
/**
|
|
9
|
+
* A comment extracted from a PL/pgSQL function body.
|
|
10
|
+
*/
|
|
11
|
+
export interface BodyComment {
|
|
12
|
+
/** The full comment text including -- prefix */
|
|
13
|
+
text: string;
|
|
14
|
+
/** 1-based line number within the function body */
|
|
15
|
+
lineNo: number;
|
|
16
|
+
/** Whether this is a standalone comment line (vs inline after code) */
|
|
17
|
+
standalone: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Metadata about a PL/pgSQL function's body comments,
|
|
21
|
+
* stored alongside the parsed function data.
|
|
22
|
+
*/
|
|
23
|
+
export interface FunctionComments {
|
|
24
|
+
/** Index of the RawStmt in the EnhancedParseResult.stmts array */
|
|
25
|
+
stmtIndex: number;
|
|
26
|
+
/** The raw body text extracted from the dollar-quoted string */
|
|
27
|
+
originalBody: string;
|
|
28
|
+
/** The dollar-quote delimiter (e.g., '$$', '$fn$') */
|
|
29
|
+
delimiter: string;
|
|
30
|
+
/** Comments found in the body, ordered by line number */
|
|
31
|
+
comments: BodyComment[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The result of parsing SQL with plpgsql-parse.
|
|
35
|
+
* Extends pgsql-parse's EnhancedParseResult with PL/pgSQL body comment info.
|
|
36
|
+
*/
|
|
37
|
+
export interface PlpgsqlParseResult {
|
|
38
|
+
/** The outer SQL parse result with comments/whitespace preserved */
|
|
39
|
+
enhanced: EnhancedParseResult;
|
|
40
|
+
/** Comment metadata for each PL/pgSQL function found */
|
|
41
|
+
functions: FunctionComments[];
|
|
42
|
+
}
|
package/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Types for plpgsql-parse — comment-preserving PL/pgSQL parser.
|
|
4
|
+
*
|
|
5
|
+
* Re-uses pgsql-parse's EnhancedParseResult for the outer SQL level
|
|
6
|
+
* and adds body-level comment metadata for PL/pgSQL function bodies.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|