@pgsql/transform 18.11.1 → 18.13.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/README.md +23 -0
- package/esm/extension-transform.js +8 -19
- package/esm/facts.js +5 -5
- package/esm/qualify.js +6 -6
- package/esm/role-transform.js +2 -2
- package/esm/transform.js +22 -40
- package/extension-transform.js +6 -17
- package/facts.js +3 -3
- package/package.json +4 -4
- package/qualify.js +4 -4
- package/role-transform.js +1 -1
- package/transform.js +20 -38
package/README.md
CHANGED
|
@@ -58,6 +58,29 @@ Qualify unqualified object references against an inventory of known objects, wit
|
|
|
58
58
|
|
|
59
59
|
`normalizeTree` / `cleanTree` / `validateRoundTrip` — dependency-free AST normalization and mutation-aware parse→deparse→re-parse validation.
|
|
60
60
|
|
|
61
|
+
## Relationship to `@pgsql/traverse`
|
|
62
|
+
|
|
63
|
+
This package owns **policy**, not traversal. Every entry point above parses once,
|
|
64
|
+
then drives `walk` from `@pgsql/traverse` over the parsed script — statements and
|
|
65
|
+
hydrated PL/pgSQL bodies alike — with a visitor built here:
|
|
66
|
+
|
|
67
|
+
| Concern | Lives here |
|
|
68
|
+
|---|---|
|
|
69
|
+
| Which schema/role/extension a name maps to | `SchemaRouter`, `RoleRouter`, extension routes |
|
|
70
|
+
| What counts as a reference, a creation, a security-relevant statement | `classifyStatements` |
|
|
71
|
+
| When an unqualified name should be qualified | `qualifyUnqualified` |
|
|
72
|
+
| Whether the rewritten SQL still parses to the same tree | `validateRoundTrip` |
|
|
73
|
+
| How to reach every node of a SQL or PL/pgSQL AST | **`@pgsql/traverse`** |
|
|
74
|
+
|
|
75
|
+
So mapping logic never moves into the walker, and traversal logic never moves in
|
|
76
|
+
here. Some passes keep per-statement state (CTE names in `qualifyUnqualified`,
|
|
77
|
+
per-statement facts in `classifyStatements`) and drive the `walkSqlAst` /
|
|
78
|
+
`walkPlpgsqlAst` primitives directly with a visitor per statement.
|
|
79
|
+
|
|
80
|
+
Consequence for contributors: `__fixtures__/output/` is the regression gate for
|
|
81
|
+
traversal changes made *anywhere* in the ecosystem. A walker change that alters
|
|
82
|
+
these golden files is a behavior change, not a refactor.
|
|
83
|
+
|
|
61
84
|
## Scripts
|
|
62
85
|
|
|
63
86
|
- `npm run fixtures` — regenerate `__fixtures__/output/` golden files from `__fixtures__/input/`
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* inventory, so built-ins that merely share a name with an extension symbol
|
|
15
15
|
* (and symbols that have graduated into core) are left untouched.
|
|
16
16
|
*/
|
|
17
|
-
import { walk
|
|
18
|
-
import { Deparser, parseSql, transformSync
|
|
17
|
+
import { walk, walkSqlAst } from '@pgsql/traverse';
|
|
18
|
+
import { Deparser, parseSql, transformSync } from 'plpgsql-parser';
|
|
19
19
|
import { ExtensionRouter } from './extension-router';
|
|
20
20
|
/** Create a fresh result tracker. */
|
|
21
21
|
export function createExtensionResult() {
|
|
@@ -167,7 +167,7 @@ function rewriteSqlBodyString(body, router, result) {
|
|
|
167
167
|
for (const stmt of stmts) {
|
|
168
168
|
if (!stmt?.stmt)
|
|
169
169
|
continue;
|
|
170
|
-
|
|
170
|
+
walkSqlAst(stmt.stmt, visitor);
|
|
171
171
|
pieces.push(Deparser.deparse(stmt.stmt));
|
|
172
172
|
}
|
|
173
173
|
return pieces.join(';\n');
|
|
@@ -181,22 +181,11 @@ export function transformExtensions(sql, router, options = {}) {
|
|
|
181
181
|
const resolved = ExtensionRouter.from(router, options);
|
|
182
182
|
const result = createExtensionResult();
|
|
183
183
|
const out = transformSync(sql, (ctx) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
if (resolved.hasSymbolRoutes()) {
|
|
191
|
-
for (const fn of ctx.functions ?? []) {
|
|
192
|
-
if (fn.plpgsql?.hydrated) {
|
|
193
|
-
walkPlpgsql(fn.plpgsql.hydrated, {}, {
|
|
194
|
-
walkSqlExpressions: true,
|
|
195
|
-
sqlVisitor: visitor
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
184
|
+
walk(ctx, createExtensionVisitor(resolved, result), {
|
|
185
|
+
// Bodies only matter when a symbol (function/operator/type) is routed;
|
|
186
|
+
// a bare extension rename never appears inside one.
|
|
187
|
+
walkFunctionBodies: resolved.hasSymbolRoutes()
|
|
188
|
+
});
|
|
200
189
|
}, { hydrate: true, pretty: true });
|
|
201
190
|
return { sql: out, result };
|
|
202
191
|
}
|
package/esm/facts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parseSql, transformSync,
|
|
1
|
+
import { walkSqlAst } from '@pgsql/traverse';
|
|
2
|
+
import { parseSql, transformSync, walkPlpgsqlAst } from 'plpgsql-parser';
|
|
3
3
|
const SECURITY_TAGS = new Set([
|
|
4
4
|
'CreatePolicyStmt',
|
|
5
5
|
'AlterPolicyStmt',
|
|
@@ -332,7 +332,7 @@ function collectSqlBodyReferences(node, facts) {
|
|
|
332
332
|
const visitor = createFactsVisitor(facts, facts.bodyReferences);
|
|
333
333
|
for (const stmt of stmts) {
|
|
334
334
|
if (stmt?.stmt)
|
|
335
|
-
|
|
335
|
+
walkSqlAst(stmt.stmt, visitor);
|
|
336
336
|
}
|
|
337
337
|
}
|
|
338
338
|
catch {
|
|
@@ -359,7 +359,7 @@ export function classifyStatements(sql) {
|
|
|
359
359
|
facts.span = { start, len: stmt?.stmt_len ?? Math.max(0, sql.length - start) };
|
|
360
360
|
if (stmtNode) {
|
|
361
361
|
facts.stmt = stmtNode;
|
|
362
|
-
|
|
362
|
+
walkSqlAst(stmtNode, createFactsVisitor(facts));
|
|
363
363
|
}
|
|
364
364
|
if (nodeTag === 'CreateFunctionStmt') {
|
|
365
365
|
collectSqlBodyReferences(node, facts);
|
|
@@ -373,7 +373,7 @@ export function classifyStatements(sql) {
|
|
|
373
373
|
// Refs already found outside the body (signature types, defaults)
|
|
374
374
|
// constrain deploy order and are not body-only.
|
|
375
375
|
const outer = new Set(facts.references.map(r => `${r.schema ?? '?'}.${r.name}`));
|
|
376
|
-
|
|
376
|
+
walkPlpgsqlAst(fn.plpgsql.hydrated, {
|
|
377
377
|
PLpgSQL_stmt_dynexecute: () => { facts.dynamicSql = true; },
|
|
378
378
|
PLpgSQL_stmt_dynfors: () => { facts.dynamicSql = true; }
|
|
379
379
|
}, {
|
package/esm/qualify.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* defined by a CTE in the same statement are excluded even when they collide
|
|
15
15
|
* with a routed relation.
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
18
|
-
import { Deparser, parseSql, transformSync,
|
|
17
|
+
import { walkSqlAst } from '@pgsql/traverse';
|
|
18
|
+
import { Deparser, parseSql, transformSync, walkPlpgsqlAst } from 'plpgsql-parser';
|
|
19
19
|
import { classifyStatements } from './facts';
|
|
20
20
|
const RELATION_TAGS = new Set([
|
|
21
21
|
'CreateStmt',
|
|
@@ -119,7 +119,7 @@ function resolveRoutes(sql, options) {
|
|
|
119
119
|
}
|
|
120
120
|
function collectCteNames(stmt) {
|
|
121
121
|
const names = new Set();
|
|
122
|
-
|
|
122
|
+
walkSqlAst(stmt, {
|
|
123
123
|
CommonTableExpr: (path) => {
|
|
124
124
|
if (path.node?.ctename)
|
|
125
125
|
names.add(path.node.ctename);
|
|
@@ -142,7 +142,7 @@ function qualifySqlBodyString(body, routes, result) {
|
|
|
142
142
|
if (!stmt?.stmt)
|
|
143
143
|
continue;
|
|
144
144
|
const visitor = createQualifyVisitor({ routes, cteNames: collectCteNames(stmt.stmt) }, result);
|
|
145
|
-
|
|
145
|
+
walkSqlAst(stmt.stmt, visitor);
|
|
146
146
|
pieces.push(Deparser.deparse(stmt.stmt));
|
|
147
147
|
}
|
|
148
148
|
return pieces.join(';\n');
|
|
@@ -259,13 +259,13 @@ export function qualifyUnqualified(sql, options) {
|
|
|
259
259
|
for (const stmt of stmts) {
|
|
260
260
|
if (stmt?.stmt) {
|
|
261
261
|
const visitor = createQualifyVisitor({ routes, cteNames: collectCteNames(stmt.stmt) }, result);
|
|
262
|
-
|
|
262
|
+
walkSqlAst(stmt.stmt, visitor);
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
const bodyVisitor = createQualifyVisitor({ routes }, result);
|
|
266
266
|
for (const fn of ctx.functions) {
|
|
267
267
|
if (fn.plpgsql?.hydrated) {
|
|
268
|
-
|
|
268
|
+
walkPlpgsqlAst(fn.plpgsql.hydrated, {}, {
|
|
269
269
|
walkSqlExpressions: true,
|
|
270
270
|
sqlVisitor: bodyVisitor
|
|
271
271
|
});
|
package/esm/role-transform.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* carry no `rolename` and a non-`CSTRING` `roletype`, so they are never
|
|
21
21
|
* rewritten.
|
|
22
22
|
*/
|
|
23
|
-
import {
|
|
23
|
+
import { walkSqlAst } from '@pgsql/traverse';
|
|
24
24
|
import { transformSync } from 'plpgsql-parser';
|
|
25
25
|
import { RoleRouter } from './role-router';
|
|
26
26
|
export function createRoleResult() {
|
|
@@ -122,7 +122,7 @@ export function transformRoles(sql, router) {
|
|
|
122
122
|
const visitor = createRoleVisitor(resolved, result);
|
|
123
123
|
for (const stmt of stmts) {
|
|
124
124
|
if (stmt?.stmt)
|
|
125
|
-
|
|
125
|
+
walkSqlAst(stmt.stmt, visitor);
|
|
126
126
|
}
|
|
127
127
|
}, { hydrate: true, pretty: true });
|
|
128
128
|
return { sql: out, result };
|
package/esm/transform.js
CHANGED
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
* - Comment headers, verify function calls, JSON string values (regex fallback)
|
|
25
25
|
*/
|
|
26
26
|
import { QuoteUtils } from '@pgsql/quotes';
|
|
27
|
-
import { walk
|
|
28
|
-
import { Deparser, parseSql, transformSync
|
|
27
|
+
import { walk, walkSqlAst } from '@pgsql/traverse';
|
|
28
|
+
import { Deparser, parseSql, transformSync } from 'plpgsql-parser';
|
|
29
29
|
import { qualifyUnqualified } from './qualify';
|
|
30
30
|
import { captureTransformAsts, validateRoundTrip } from './round-trip';
|
|
31
31
|
import { SchemaRouter } from './router';
|
|
@@ -721,7 +721,7 @@ export function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
|
721
721
|
}
|
|
722
722
|
}
|
|
723
723
|
};
|
|
724
|
-
|
|
724
|
+
walkSqlAst(parseResult.stmts[0].stmt, sqlVisitor);
|
|
725
725
|
const deparsed = Deparser.deparse(parseResult.stmts[0].stmt);
|
|
726
726
|
const match = deparsed.match(/SELECT\s+NULL::(.+)/i);
|
|
727
727
|
if (match) {
|
|
@@ -786,7 +786,7 @@ export function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
|
786
786
|
if (Array.isArray(expr.query.parseResult?.stmts)) {
|
|
787
787
|
for (const stmt of expr.query.parseResult.stmts) {
|
|
788
788
|
if (stmt?.stmt) {
|
|
789
|
-
|
|
789
|
+
walkSqlAst(stmt.stmt, sqlVisitor);
|
|
790
790
|
}
|
|
791
791
|
}
|
|
792
792
|
}
|
|
@@ -807,7 +807,7 @@ export function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
|
807
807
|
if (plType.typname) {
|
|
808
808
|
if (typeof plType.typname === 'object' && plType.typname.kind === 'type-name') {
|
|
809
809
|
// Transform schema names directly in the typeNameNode.names array.
|
|
810
|
-
// We cannot use
|
|
810
|
+
// We cannot use walkSqlAst here because the raw typeNameNode is not
|
|
811
811
|
// wrapped in the expected AST envelope that the traverse walker needs.
|
|
812
812
|
if (plType.typname.typeNameNode?.names) {
|
|
813
813
|
transformNameList(plType.typname.typeNameNode.names, schemaMapping, result, 'type');
|
|
@@ -849,7 +849,7 @@ function transformSqlBodyString(body, router, result) {
|
|
|
849
849
|
for (const stmt of stmts) {
|
|
850
850
|
if (!stmt?.stmt)
|
|
851
851
|
continue;
|
|
852
|
-
|
|
852
|
+
walkSqlAst(stmt.stmt, visitor);
|
|
853
853
|
pieces.push(Deparser.deparse(stmt.stmt));
|
|
854
854
|
}
|
|
855
855
|
return pieces.join(';\n');
|
|
@@ -858,6 +858,20 @@ function transformSqlBodyString(body, router, result) {
|
|
|
858
858
|
return body.includes('.') ? transformSchemaRefsInString(body, router, result) : body;
|
|
859
859
|
}
|
|
860
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* Apply schema renaming to a hydrated parse context: every top-level statement
|
|
863
|
+
* and every hydrated PL/pgSQL body in one walk. `walkPlpgsqlForSchemas` handles
|
|
864
|
+
* what the SQL visitor cannot see — PL/pgSQL-only nodes such as declared
|
|
865
|
+
* variable types, which carry schema-qualified names as plain strings.
|
|
866
|
+
*/
|
|
867
|
+
function transformSchemasInContext(ctx, schemaMapping, result, visitorOptions) {
|
|
868
|
+
walk(ctx, createSqlVisitor(schemaMapping, result, visitorOptions));
|
|
869
|
+
for (const fn of ctx.functions ?? []) {
|
|
870
|
+
if (fn.plpgsql?.hydrated) {
|
|
871
|
+
walkPlpgsqlForSchemas(fn.plpgsql.hydrated, schemaMapping, result);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
861
875
|
/**
|
|
862
876
|
* Escape a string for use in a regular expression
|
|
863
877
|
*/
|
|
@@ -1037,23 +1051,7 @@ export function transformSqlStatement(sql, schemaMapping, result) {
|
|
|
1037
1051
|
}
|
|
1038
1052
|
try {
|
|
1039
1053
|
const transformed = transformSync(sql, (ctx) => {
|
|
1040
|
-
|
|
1041
|
-
if (ctx.sql?.stmts) {
|
|
1042
|
-
for (const stmt of ctx.sql.stmts) {
|
|
1043
|
-
if (stmt?.stmt) {
|
|
1044
|
-
walkSql(stmt.stmt, sqlVisitor);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
for (const fn of ctx.functions) {
|
|
1049
|
-
if (fn.plpgsql?.hydrated) {
|
|
1050
|
-
walkPlpgsql(fn.plpgsql.hydrated, {}, {
|
|
1051
|
-
walkSqlExpressions: true,
|
|
1052
|
-
sqlVisitor: sqlVisitor
|
|
1053
|
-
});
|
|
1054
|
-
walkPlpgsqlForSchemas(fn.plpgsql.hydrated, schemaMapping, r);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1054
|
+
transformSchemasInContext(ctx, schemaMapping, r);
|
|
1057
1055
|
}, { hydrate: true, pretty: true });
|
|
1058
1056
|
return { sql: transformed, result: r };
|
|
1059
1057
|
}
|
|
@@ -1095,23 +1093,7 @@ function transformSqlContentAst(content, schemaMapping, result, options) {
|
|
|
1095
1093
|
let before;
|
|
1096
1094
|
try {
|
|
1097
1095
|
transformedBody = transformSync(transformedBody, (ctx) => {
|
|
1098
|
-
|
|
1099
|
-
if (ctx.sql?.stmts) {
|
|
1100
|
-
for (const stmt of ctx.sql.stmts) {
|
|
1101
|
-
if (stmt?.stmt) {
|
|
1102
|
-
walkSql(stmt.stmt, sqlVisitor);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
for (const fn of ctx.functions) {
|
|
1107
|
-
if (fn.plpgsql?.hydrated) {
|
|
1108
|
-
walkPlpgsql(fn.plpgsql.hydrated, {}, {
|
|
1109
|
-
walkSqlExpressions: true,
|
|
1110
|
-
sqlVisitor: sqlVisitor
|
|
1111
|
-
});
|
|
1112
|
-
walkPlpgsqlForSchemas(fn.plpgsql.hydrated, schemaMapping, result);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1096
|
+
transformSchemasInContext(ctx, schemaMapping, result, { assumeSchemasExist });
|
|
1115
1097
|
if (roundTrip) {
|
|
1116
1098
|
before = captureTransformAsts(ctx);
|
|
1117
1099
|
}
|
package/extension-transform.js
CHANGED
|
@@ -172,7 +172,7 @@ function rewriteSqlBodyString(body, router, result) {
|
|
|
172
172
|
for (const stmt of stmts) {
|
|
173
173
|
if (!stmt?.stmt)
|
|
174
174
|
continue;
|
|
175
|
-
(0, traverse_1.
|
|
175
|
+
(0, traverse_1.walkSqlAst)(stmt.stmt, visitor);
|
|
176
176
|
pieces.push(plpgsql_parser_1.Deparser.deparse(stmt.stmt));
|
|
177
177
|
}
|
|
178
178
|
return pieces.join(';\n');
|
|
@@ -186,22 +186,11 @@ function transformExtensions(sql, router, options = {}) {
|
|
|
186
186
|
const resolved = extension_router_1.ExtensionRouter.from(router, options);
|
|
187
187
|
const result = createExtensionResult();
|
|
188
188
|
const out = (0, plpgsql_parser_1.transformSync)(sql, (ctx) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
if (resolved.hasSymbolRoutes()) {
|
|
196
|
-
for (const fn of ctx.functions ?? []) {
|
|
197
|
-
if (fn.plpgsql?.hydrated) {
|
|
198
|
-
(0, plpgsql_parser_1.walk)(fn.plpgsql.hydrated, {}, {
|
|
199
|
-
walkSqlExpressions: true,
|
|
200
|
-
sqlVisitor: visitor
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
189
|
+
(0, traverse_1.walk)(ctx, createExtensionVisitor(resolved, result), {
|
|
190
|
+
// Bodies only matter when a symbol (function/operator/type) is routed;
|
|
191
|
+
// a bare extension rename never appears inside one.
|
|
192
|
+
walkFunctionBodies: resolved.hasSymbolRoutes()
|
|
193
|
+
});
|
|
205
194
|
}, { hydrate: true, pretty: true });
|
|
206
195
|
return { sql: out, result };
|
|
207
196
|
}
|
package/facts.js
CHANGED
|
@@ -335,7 +335,7 @@ function collectSqlBodyReferences(node, facts) {
|
|
|
335
335
|
const visitor = createFactsVisitor(facts, facts.bodyReferences);
|
|
336
336
|
for (const stmt of stmts) {
|
|
337
337
|
if (stmt?.stmt)
|
|
338
|
-
(0, traverse_1.
|
|
338
|
+
(0, traverse_1.walkSqlAst)(stmt.stmt, visitor);
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
catch {
|
|
@@ -362,7 +362,7 @@ function classifyStatements(sql) {
|
|
|
362
362
|
facts.span = { start, len: stmt?.stmt_len ?? Math.max(0, sql.length - start) };
|
|
363
363
|
if (stmtNode) {
|
|
364
364
|
facts.stmt = stmtNode;
|
|
365
|
-
(0, traverse_1.
|
|
365
|
+
(0, traverse_1.walkSqlAst)(stmtNode, createFactsVisitor(facts));
|
|
366
366
|
}
|
|
367
367
|
if (nodeTag === 'CreateFunctionStmt') {
|
|
368
368
|
collectSqlBodyReferences(node, facts);
|
|
@@ -376,7 +376,7 @@ function classifyStatements(sql) {
|
|
|
376
376
|
// Refs already found outside the body (signature types, defaults)
|
|
377
377
|
// constrain deploy order and are not body-only.
|
|
378
378
|
const outer = new Set(facts.references.map(r => `${r.schema ?? '?'}.${r.name}`));
|
|
379
|
-
(0, plpgsql_parser_1.
|
|
379
|
+
(0, plpgsql_parser_1.walkPlpgsqlAst)(fn.plpgsql.hydrated, {
|
|
380
380
|
PLpgSQL_stmt_dynexecute: () => { facts.dynamicSql = true; },
|
|
381
381
|
PLpgSQL_stmt_dynfors: () => { facts.dynamicSql = true; }
|
|
382
382
|
}, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgsql/transform",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.13.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "AST-based SQL transformation, qualification, classification and closure analysis for PostgreSQL",
|
|
6
6
|
"main": "index.js",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@pgsql/quotes": "18.1.0",
|
|
36
|
-
"@pgsql/traverse": "18.
|
|
37
|
-
"plpgsql-parser": "18.
|
|
36
|
+
"@pgsql/traverse": "18.6.0",
|
|
37
|
+
"plpgsql-parser": "18.4.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"makage": "^0.1.8"
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"classify",
|
|
50
50
|
"plpgsql"
|
|
51
51
|
],
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "571af81b3e5e5582eae226053b820f9c90c898b6"
|
|
53
53
|
}
|
package/qualify.js
CHANGED
|
@@ -125,7 +125,7 @@ function resolveRoutes(sql, options) {
|
|
|
125
125
|
}
|
|
126
126
|
function collectCteNames(stmt) {
|
|
127
127
|
const names = new Set();
|
|
128
|
-
(0, traverse_1.
|
|
128
|
+
(0, traverse_1.walkSqlAst)(stmt, {
|
|
129
129
|
CommonTableExpr: (path) => {
|
|
130
130
|
if (path.node?.ctename)
|
|
131
131
|
names.add(path.node.ctename);
|
|
@@ -148,7 +148,7 @@ function qualifySqlBodyString(body, routes, result) {
|
|
|
148
148
|
if (!stmt?.stmt)
|
|
149
149
|
continue;
|
|
150
150
|
const visitor = createQualifyVisitor({ routes, cteNames: collectCteNames(stmt.stmt) }, result);
|
|
151
|
-
(0, traverse_1.
|
|
151
|
+
(0, traverse_1.walkSqlAst)(stmt.stmt, visitor);
|
|
152
152
|
pieces.push(plpgsql_parser_1.Deparser.deparse(stmt.stmt));
|
|
153
153
|
}
|
|
154
154
|
return pieces.join(';\n');
|
|
@@ -265,13 +265,13 @@ function qualifyUnqualified(sql, options) {
|
|
|
265
265
|
for (const stmt of stmts) {
|
|
266
266
|
if (stmt?.stmt) {
|
|
267
267
|
const visitor = createQualifyVisitor({ routes, cteNames: collectCteNames(stmt.stmt) }, result);
|
|
268
|
-
(0, traverse_1.
|
|
268
|
+
(0, traverse_1.walkSqlAst)(stmt.stmt, visitor);
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
const bodyVisitor = createQualifyVisitor({ routes }, result);
|
|
272
272
|
for (const fn of ctx.functions) {
|
|
273
273
|
if (fn.plpgsql?.hydrated) {
|
|
274
|
-
(0, plpgsql_parser_1.
|
|
274
|
+
(0, plpgsql_parser_1.walkPlpgsqlAst)(fn.plpgsql.hydrated, {}, {
|
|
275
275
|
walkSqlExpressions: true,
|
|
276
276
|
sqlVisitor: bodyVisitor
|
|
277
277
|
});
|
package/role-transform.js
CHANGED
|
@@ -127,7 +127,7 @@ function transformRoles(sql, router) {
|
|
|
127
127
|
const visitor = createRoleVisitor(resolved, result);
|
|
128
128
|
for (const stmt of stmts) {
|
|
129
129
|
if (stmt?.stmt)
|
|
130
|
-
(0, traverse_1.
|
|
130
|
+
(0, traverse_1.walkSqlAst)(stmt.stmt, visitor);
|
|
131
131
|
}
|
|
132
132
|
}, { hydrate: true, pretty: true });
|
|
133
133
|
return { sql: out, result };
|
package/transform.js
CHANGED
|
@@ -742,7 +742,7 @@ function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
744
|
};
|
|
745
|
-
(0, traverse_1.
|
|
745
|
+
(0, traverse_1.walkSqlAst)(parseResult.stmts[0].stmt, sqlVisitor);
|
|
746
746
|
const deparsed = plpgsql_parser_1.Deparser.deparse(parseResult.stmts[0].stmt);
|
|
747
747
|
const match = deparsed.match(/SELECT\s+NULL::(.+)/i);
|
|
748
748
|
if (match) {
|
|
@@ -807,7 +807,7 @@ function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
|
807
807
|
if (Array.isArray(expr.query.parseResult?.stmts)) {
|
|
808
808
|
for (const stmt of expr.query.parseResult.stmts) {
|
|
809
809
|
if (stmt?.stmt) {
|
|
810
|
-
(0, traverse_1.
|
|
810
|
+
(0, traverse_1.walkSqlAst)(stmt.stmt, sqlVisitor);
|
|
811
811
|
}
|
|
812
812
|
}
|
|
813
813
|
}
|
|
@@ -828,7 +828,7 @@ function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
|
828
828
|
if (plType.typname) {
|
|
829
829
|
if (typeof plType.typname === 'object' && plType.typname.kind === 'type-name') {
|
|
830
830
|
// Transform schema names directly in the typeNameNode.names array.
|
|
831
|
-
// We cannot use
|
|
831
|
+
// We cannot use walkSqlAst here because the raw typeNameNode is not
|
|
832
832
|
// wrapped in the expected AST envelope that the traverse walker needs.
|
|
833
833
|
if (plType.typname.typeNameNode?.names) {
|
|
834
834
|
transformNameList(plType.typname.typeNameNode.names, schemaMapping, result, 'type');
|
|
@@ -870,7 +870,7 @@ function transformSqlBodyString(body, router, result) {
|
|
|
870
870
|
for (const stmt of stmts) {
|
|
871
871
|
if (!stmt?.stmt)
|
|
872
872
|
continue;
|
|
873
|
-
(0, traverse_1.
|
|
873
|
+
(0, traverse_1.walkSqlAst)(stmt.stmt, visitor);
|
|
874
874
|
pieces.push(plpgsql_parser_1.Deparser.deparse(stmt.stmt));
|
|
875
875
|
}
|
|
876
876
|
return pieces.join(';\n');
|
|
@@ -879,6 +879,20 @@ function transformSqlBodyString(body, router, result) {
|
|
|
879
879
|
return body.includes('.') ? transformSchemaRefsInString(body, router, result) : body;
|
|
880
880
|
}
|
|
881
881
|
}
|
|
882
|
+
/**
|
|
883
|
+
* Apply schema renaming to a hydrated parse context: every top-level statement
|
|
884
|
+
* and every hydrated PL/pgSQL body in one walk. `walkPlpgsqlForSchemas` handles
|
|
885
|
+
* what the SQL visitor cannot see — PL/pgSQL-only nodes such as declared
|
|
886
|
+
* variable types, which carry schema-qualified names as plain strings.
|
|
887
|
+
*/
|
|
888
|
+
function transformSchemasInContext(ctx, schemaMapping, result, visitorOptions) {
|
|
889
|
+
(0, traverse_1.walk)(ctx, createSqlVisitor(schemaMapping, result, visitorOptions));
|
|
890
|
+
for (const fn of ctx.functions ?? []) {
|
|
891
|
+
if (fn.plpgsql?.hydrated) {
|
|
892
|
+
walkPlpgsqlForSchemas(fn.plpgsql.hydrated, schemaMapping, result);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
882
896
|
/**
|
|
883
897
|
* Escape a string for use in a regular expression
|
|
884
898
|
*/
|
|
@@ -1058,23 +1072,7 @@ function transformSqlStatement(sql, schemaMapping, result) {
|
|
|
1058
1072
|
}
|
|
1059
1073
|
try {
|
|
1060
1074
|
const transformed = (0, plpgsql_parser_1.transformSync)(sql, (ctx) => {
|
|
1061
|
-
|
|
1062
|
-
if (ctx.sql?.stmts) {
|
|
1063
|
-
for (const stmt of ctx.sql.stmts) {
|
|
1064
|
-
if (stmt?.stmt) {
|
|
1065
|
-
(0, traverse_1.walk)(stmt.stmt, sqlVisitor);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
for (const fn of ctx.functions) {
|
|
1070
|
-
if (fn.plpgsql?.hydrated) {
|
|
1071
|
-
(0, plpgsql_parser_1.walk)(fn.plpgsql.hydrated, {}, {
|
|
1072
|
-
walkSqlExpressions: true,
|
|
1073
|
-
sqlVisitor: sqlVisitor
|
|
1074
|
-
});
|
|
1075
|
-
walkPlpgsqlForSchemas(fn.plpgsql.hydrated, schemaMapping, r);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1075
|
+
transformSchemasInContext(ctx, schemaMapping, r);
|
|
1078
1076
|
}, { hydrate: true, pretty: true });
|
|
1079
1077
|
return { sql: transformed, result: r };
|
|
1080
1078
|
}
|
|
@@ -1116,23 +1114,7 @@ function transformSqlContentAst(content, schemaMapping, result, options) {
|
|
|
1116
1114
|
let before;
|
|
1117
1115
|
try {
|
|
1118
1116
|
transformedBody = (0, plpgsql_parser_1.transformSync)(transformedBody, (ctx) => {
|
|
1119
|
-
|
|
1120
|
-
if (ctx.sql?.stmts) {
|
|
1121
|
-
for (const stmt of ctx.sql.stmts) {
|
|
1122
|
-
if (stmt?.stmt) {
|
|
1123
|
-
(0, traverse_1.walk)(stmt.stmt, sqlVisitor);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
for (const fn of ctx.functions) {
|
|
1128
|
-
if (fn.plpgsql?.hydrated) {
|
|
1129
|
-
(0, plpgsql_parser_1.walk)(fn.plpgsql.hydrated, {}, {
|
|
1130
|
-
walkSqlExpressions: true,
|
|
1131
|
-
sqlVisitor: sqlVisitor
|
|
1132
|
-
});
|
|
1133
|
-
walkPlpgsqlForSchemas(fn.plpgsql.hydrated, schemaMapping, result);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1117
|
+
transformSchemasInContext(ctx, schemaMapping, result, { assumeSchemasExist });
|
|
1136
1118
|
if (roundTrip) {
|
|
1137
1119
|
before = (0, round_trip_1.captureTransformAsts)(ctx);
|
|
1138
1120
|
}
|