@pgsql/transform 18.1.1 → 18.3.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 +29 -73
- package/categorize.d.ts +52 -0
- package/categorize.js +56 -0
- package/esm/categorize.js +51 -0
- package/esm/facts.js +289 -0
- package/esm/fixture-closure.js +197 -0
- package/esm/index.js +7 -22
- package/esm/qualify.js +297 -0
- package/esm/round-trip-core.js +126 -0
- package/esm/round-trip.js +116 -0
- package/esm/transform.js +1169 -0
- package/facts.d.ts +69 -0
- package/facts.js +292 -0
- package/fixture-closure.d.ts +71 -0
- package/fixture-closure.js +200 -0
- package/index.d.ts +14 -28
- package/index.js +43 -76
- package/package.json +22 -14
- package/qualify.d.ts +125 -0
- package/qualify.js +303 -0
- package/round-trip-core.d.ts +60 -0
- package/round-trip-core.js +133 -0
- package/round-trip.d.ts +47 -0
- package/round-trip.js +123 -0
- package/transform.d.ts +259 -0
- package/transform.js +1190 -0
- package/13/enums.d.ts +0 -62
- package/13/enums.js +0 -2
- package/13/types.d.ts +0 -2103
- package/13/types.js +0 -2
- package/14/enums.d.ts +0 -62
- package/14/enums.js +0 -2
- package/14/types.d.ts +0 -2164
- package/14/types.js +0 -2
- package/15/enums.d.ts +0 -64
- package/15/enums.js +0 -2
- package/15/types.d.ts +0 -2222
- package/15/types.js +0 -2
- package/16/enums.d.ts +0 -69
- package/16/enums.js +0 -2
- package/16/types.d.ts +0 -2328
- package/16/types.js +0 -2
- package/17/enums.d.ts +0 -76
- package/17/enums.js +0 -2
- package/17/types.d.ts +0 -2488
- package/17/types.js +0 -2
- package/18/enums.d.ts +0 -78
- package/18/enums.js +0 -2
- package/18/types.d.ts +0 -2538
- package/18/types.js +0 -2
- package/esm/13/enums.js +0 -1
- package/esm/13/types.js +0 -1
- package/esm/14/enums.js +0 -1
- package/esm/14/types.js +0 -1
- package/esm/15/enums.js +0 -1
- package/esm/15/types.js +0 -1
- package/esm/16/enums.js +0 -1
- package/esm/16/types.js +0 -1
- package/esm/17/enums.js +0 -1
- package/esm/17/types.js +0 -1
- package/esm/18/enums.js +0 -1
- package/esm/18/types.js +0 -1
- package/esm/multi-version-transformer.js +0 -80
- package/esm/transformers/context.js +0 -1
- package/esm/transformers/v13-to-v14.js +0 -2767
- package/esm/transformers/v14-to-v15.js +0 -1218
- package/esm/transformers/v15-to-v16.js +0 -2935
- package/esm/transformers/v16-to-v17.js +0 -1526
- package/esm/transformers/v17-to-v18.js +0 -204
- package/esm/transformers-direct/index.js +0 -13
- package/esm/transformers-direct/v13-to-v17/index.js +0 -74
- package/esm/transformers-direct/v13-to-v18/index.js +0 -84
- package/esm/transformers-direct/v14-to-v17/index.js +0 -63
- package/esm/transformers-direct/v14-to-v18/index.js +0 -73
- package/esm/transformers-direct/v15-to-v17/index.js +0 -53
- package/esm/transformers-direct/v15-to-v18/index.js +0 -62
- package/esm/transformers-direct/v16-to-v17/index.js +0 -40
- package/esm/transformers-direct/v16-to-v18/index.js +0 -51
- package/esm/transformers-direct/v17-to-v18/index.js +0 -39
- package/multi-version-transformer.d.ts +0 -14
- package/multi-version-transformer.js +0 -84
- package/transformers/context.d.ts +0 -4
- package/transformers/context.js +0 -2
- package/transformers/v13-to-v14.d.ts +0 -255
- package/transformers/v13-to-v14.js +0 -2771
- package/transformers/v14-to-v15.d.ts +0 -615
- package/transformers/v14-to-v15.js +0 -1222
- package/transformers/v15-to-v16.d.ts +0 -632
- package/transformers/v15-to-v16.js +0 -2939
- package/transformers/v16-to-v17.d.ts +0 -640
- package/transformers/v16-to-v17.js +0 -1530
- package/transformers/v17-to-v18.d.ts +0 -54
- package/transformers/v17-to-v18.js +0 -208
- package/transformers-direct/index.d.ts +0 -13
- package/transformers-direct/index.js +0 -25
- package/transformers-direct/v13-to-v17/index.d.ts +0 -27
- package/transformers-direct/v13-to-v17/index.js +0 -78
- package/transformers-direct/v13-to-v18/index.d.ts +0 -28
- package/transformers-direct/v13-to-v18/index.js +0 -88
- package/transformers-direct/v14-to-v17/index.d.ts +0 -26
- package/transformers-direct/v14-to-v17/index.js +0 -67
- package/transformers-direct/v14-to-v18/index.d.ts +0 -27
- package/transformers-direct/v14-to-v18/index.js +0 -77
- package/transformers-direct/v15-to-v17/index.d.ts +0 -25
- package/transformers-direct/v15-to-v17/index.js +0 -57
- package/transformers-direct/v15-to-v18/index.d.ts +0 -26
- package/transformers-direct/v15-to-v18/index.js +0 -66
- package/transformers-direct/v16-to-v17/index.d.ts +0 -24
- package/transformers-direct/v16-to-v17/index.js +0 -44
- package/transformers-direct/v16-to-v18/index.d.ts +0 -25
- package/transformers-direct/v16-to-v18/index.js +0 -55
- package/transformers-direct/v17-to-v18/index.d.ts +0 -24
- package/transformers-direct/v17-to-v18/index.js +0 -43
package/transform.js
ADDED
|
@@ -0,0 +1,1190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AST-based Schema Name Transformer (Core Logic)
|
|
4
|
+
*
|
|
5
|
+
* Pure transformation functions for converting schema names in SQL strings.
|
|
6
|
+
* No file I/O — takes SQL strings in and returns transformed SQL strings out.
|
|
7
|
+
*
|
|
8
|
+
* The transformer handles:
|
|
9
|
+
* - Schema-qualified identifiers in RangeVar nodes (schema.table)
|
|
10
|
+
* - Schema names in CreateSchemaStmt nodes
|
|
11
|
+
* - Schema-qualified function names in FuncCall and CreateFunctionStmt nodes
|
|
12
|
+
* - Schema-qualified type names in TypeName nodes
|
|
13
|
+
* - Schema names in GrantStmt objects array
|
|
14
|
+
* - Schema names in VariableSetStmt (SET search_path)
|
|
15
|
+
* - Schema names in AlterDefaultPrivilegesStmt
|
|
16
|
+
* - Schema names in DropStmt (DROP SCHEMA)
|
|
17
|
+
* - Schema-qualified trigger function names in CreateTrigStmt
|
|
18
|
+
* - Schema-qualified object references in CommentStmt
|
|
19
|
+
* - Schema-qualified names in DefineStmt (CREATE TYPE, CREATE AGGREGATE)
|
|
20
|
+
* - Schema-qualified names in CreateDomainStmt, CreateEnumStmt, AlterEnumStmt
|
|
21
|
+
* - Schema-qualified names in AlterDomainStmt, AlterTypeStmt
|
|
22
|
+
* - Schema-qualified names in ObjectWithArgs (ALTER FUNCTION, etc.)
|
|
23
|
+
* - Schema name in AlterObjectSchemaStmt (ALTER ... SET SCHEMA)
|
|
24
|
+
* - Schema names inside PL/pgSQL function bodies (hydrated AST)
|
|
25
|
+
* - Comment headers, verify function calls, JSON string values (regex fallback)
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.transformSchemaName = transformSchemaName;
|
|
29
|
+
exports.shouldTransformSchema = shouldTransformSchema;
|
|
30
|
+
exports.transformNameList = transformNameList;
|
|
31
|
+
exports.transformSchemaNameField = transformSchemaNameField;
|
|
32
|
+
exports.transformRelation = transformRelation;
|
|
33
|
+
exports.transformSchemaRefsInString = transformSchemaRefsInString;
|
|
34
|
+
exports.createSqlVisitor = createSqlVisitor;
|
|
35
|
+
exports.validateNoUntransformedSchemas = validateNoUntransformedSchemas;
|
|
36
|
+
exports.createPlpgsqlVisitor = createPlpgsqlVisitor;
|
|
37
|
+
exports.transformPlpgsqlTypeAst = transformPlpgsqlTypeAst;
|
|
38
|
+
exports.transformPlpgsqlTypeString = transformPlpgsqlTypeString;
|
|
39
|
+
exports.walkPlpgsqlForSchemas = walkPlpgsqlForSchemas;
|
|
40
|
+
exports.escapeRegexp = escapeRegexp;
|
|
41
|
+
exports.extractPgpmHeader = extractPgpmHeader;
|
|
42
|
+
exports.transformComments = transformComments;
|
|
43
|
+
exports.transformVerifyCalls = transformVerifyCalls;
|
|
44
|
+
exports.transformJsonStringValues = transformJsonStringValues;
|
|
45
|
+
exports.transformSql = transformSql;
|
|
46
|
+
exports.transformSqlStatement = transformSqlStatement;
|
|
47
|
+
const quotes_1 = require("@pgsql/quotes");
|
|
48
|
+
const traverse_1 = require("@pgsql/traverse");
|
|
49
|
+
const plpgsql_parser_1 = require("plpgsql-parser");
|
|
50
|
+
const qualify_1 = require("./qualify");
|
|
51
|
+
const round_trip_1 = require("./round-trip");
|
|
52
|
+
/**
|
|
53
|
+
* Create a fresh result object
|
|
54
|
+
*/
|
|
55
|
+
function createResult() {
|
|
56
|
+
return {
|
|
57
|
+
schemasFound: new Set(),
|
|
58
|
+
schemasTransformed: new Map(),
|
|
59
|
+
errors: [],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Transform a schema name if it exists in the mapping
|
|
64
|
+
*/
|
|
65
|
+
function transformSchemaName(schemaName, schemaMapping) {
|
|
66
|
+
if (!schemaName)
|
|
67
|
+
return schemaName;
|
|
68
|
+
return schemaMapping.get(schemaName) ?? schemaName;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if a schema name should be transformed
|
|
72
|
+
*/
|
|
73
|
+
function shouldTransformSchema(schemaName, schemaMapping) {
|
|
74
|
+
if (!schemaName)
|
|
75
|
+
return false;
|
|
76
|
+
return schemaMapping.has(schemaName);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Transform schema names in a String node array (used for funcname, names, etc.)
|
|
80
|
+
* These arrays contain String nodes like { String: { sval: 'schema_name' } }
|
|
81
|
+
*/
|
|
82
|
+
function transformNameList(names, schemaMapping, result) {
|
|
83
|
+
if (!names || names.length < 2)
|
|
84
|
+
return;
|
|
85
|
+
// For schema-qualified names, the first element is the schema
|
|
86
|
+
const first = names[0];
|
|
87
|
+
if (first?.String?.sval) {
|
|
88
|
+
const schemaName = first.String.sval;
|
|
89
|
+
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
90
|
+
result.schemasFound.add(schemaName);
|
|
91
|
+
const newName = schemaMapping.get(schemaName);
|
|
92
|
+
if (newName) {
|
|
93
|
+
first.String.sval = newName;
|
|
94
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Transform a bare schema-name field on a node (e.g. { String: { sval } }
|
|
101
|
+
* entries in GrantStmt.objects, RenameStmt.subname, CommentStmt.object).
|
|
102
|
+
* Returns the (possibly new) schema name.
|
|
103
|
+
*/
|
|
104
|
+
function transformSchemaNameField(container, field, schemaMapping, result) {
|
|
105
|
+
const schemaName = container?.[field];
|
|
106
|
+
if (typeof schemaName !== 'string')
|
|
107
|
+
return;
|
|
108
|
+
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
109
|
+
result.schemasFound.add(schemaName);
|
|
110
|
+
const newName = schemaMapping.get(schemaName);
|
|
111
|
+
if (newName) {
|
|
112
|
+
container[field] = newName;
|
|
113
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Transform a RangeVar-like relation object (has schemaname/relname fields).
|
|
119
|
+
* Many statement nodes embed a relation that the walker does NOT auto-recurse into.
|
|
120
|
+
*/
|
|
121
|
+
function transformRelation(relation, schemaMapping, result) {
|
|
122
|
+
if (relation?.schemaname && shouldTransformSchema(relation.schemaname, schemaMapping)) {
|
|
123
|
+
const oldName = relation.schemaname;
|
|
124
|
+
result.schemasFound.add(oldName);
|
|
125
|
+
const newName = schemaMapping.get(oldName);
|
|
126
|
+
if (newName) {
|
|
127
|
+
relation.schemaname = newName;
|
|
128
|
+
result.schemasTransformed.set(oldName, newName);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Transform schema-qualified references embedded in a plain string
|
|
134
|
+
* (e.g. advisory-lock keys like 'schema-name.fn_name', COMMENT text,
|
|
135
|
+
* RAISE messages). Only occurrences of a mapped schema name immediately
|
|
136
|
+
* followed by a dot are rewritten.
|
|
137
|
+
*/
|
|
138
|
+
function transformSchemaRefsInString(str, schemaMapping, result) {
|
|
139
|
+
let out = str;
|
|
140
|
+
for (const [oldSchema, newSchema] of schemaMapping) {
|
|
141
|
+
const pattern = new RegExp(`(?<![\\w-])("?)${escapeRegexp(oldSchema)}\\1(?=\\.)`, 'g');
|
|
142
|
+
const before = out;
|
|
143
|
+
out = out.replace(pattern, `$1${newSchema}$1`);
|
|
144
|
+
if (out !== before) {
|
|
145
|
+
result.schemasFound.add(oldSchema);
|
|
146
|
+
result.schemasTransformed.set(oldSchema, newSchema);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create a SQL AST visitor that transforms schema names.
|
|
153
|
+
*
|
|
154
|
+
* The walker from @pgsql/traverse auto-recurses into child nodes, so visitors
|
|
155
|
+
* like RangeVar and TypeName fire for every occurrence regardless of parent.
|
|
156
|
+
* However, some node types carry schema names as plain strings or name lists
|
|
157
|
+
* that require explicit handlers.
|
|
158
|
+
*/
|
|
159
|
+
function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
160
|
+
const assumeSchemasExist = visitorOptions?.assumeSchemasExist;
|
|
161
|
+
return {
|
|
162
|
+
// Transform RangeVar nodes (table references)
|
|
163
|
+
RangeVar: (path) => {
|
|
164
|
+
const node = path.node;
|
|
165
|
+
if (node.schemaname && shouldTransformSchema(node.schemaname, schemaMapping)) {
|
|
166
|
+
const oldName = node.schemaname;
|
|
167
|
+
result.schemasFound.add(oldName);
|
|
168
|
+
const newName = schemaMapping.get(oldName);
|
|
169
|
+
if (newName) {
|
|
170
|
+
node.schemaname = newName;
|
|
171
|
+
result.schemasTransformed.set(oldName, newName);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
// Transform CreateSchemaStmt nodes
|
|
176
|
+
CreateSchemaStmt: (path) => {
|
|
177
|
+
const node = path.node;
|
|
178
|
+
if (node.schemaname && shouldTransformSchema(node.schemaname, schemaMapping)) {
|
|
179
|
+
const oldName = node.schemaname;
|
|
180
|
+
result.schemasFound.add(oldName);
|
|
181
|
+
const newName = schemaMapping.get(oldName);
|
|
182
|
+
if (newName) {
|
|
183
|
+
node.schemaname = newName;
|
|
184
|
+
result.schemasTransformed.set(oldName, newName);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (node.schemaname && assumeSchemasExist?.has(node.schemaname)) {
|
|
188
|
+
node.if_not_exists = true;
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
// Transform FuncCall nodes (function calls with schema-qualified names)
|
|
192
|
+
FuncCall: (path) => {
|
|
193
|
+
const node = path.node;
|
|
194
|
+
transformNameList(node.funcname, schemaMapping, result);
|
|
195
|
+
},
|
|
196
|
+
// Transform CallStmt (CALL schema.procedure(...)).
|
|
197
|
+
// Note: The walker does NOT auto-recurse into CallStmt.funccall,
|
|
198
|
+
// so the FuncCall visitor above never sees it.
|
|
199
|
+
CallStmt: (path) => {
|
|
200
|
+
const node = path.node;
|
|
201
|
+
if (node.funccall?.funcname) {
|
|
202
|
+
transformNameList(node.funccall.funcname, schemaMapping, result);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
// Transform TypeName nodes (type references with schema-qualified names)
|
|
206
|
+
TypeName: (path) => {
|
|
207
|
+
const node = path.node;
|
|
208
|
+
transformNameList(node.names, schemaMapping, result);
|
|
209
|
+
},
|
|
210
|
+
// Transform ColumnRef nodes (column references with schema-qualified names)
|
|
211
|
+
ColumnRef: (path) => {
|
|
212
|
+
const node = path.node;
|
|
213
|
+
transformNameList(node.fields, schemaMapping, result);
|
|
214
|
+
},
|
|
215
|
+
// Transform GrantStmt objects (GRANT ON SCHEMA schema;
|
|
216
|
+
// GRANT ... ON ALL TABLES/FUNCTIONS/SEQUENCES IN SCHEMA schema)
|
|
217
|
+
GrantStmt: (path) => {
|
|
218
|
+
const node = path.node;
|
|
219
|
+
// For schema-name targets, objects contains String nodes with schema names
|
|
220
|
+
if ((node.objtype === 'OBJECT_SCHEMA' || node.targtype === 'ACL_TARGET_ALL_IN_SCHEMA') && node.objects) {
|
|
221
|
+
for (const obj of node.objects) {
|
|
222
|
+
if (obj?.String) {
|
|
223
|
+
transformSchemaNameField(obj.String, 'sval', schemaMapping, result);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
// Transform VariableSetStmt (SET search_path)
|
|
229
|
+
VariableSetStmt: (path) => {
|
|
230
|
+
const node = path.node;
|
|
231
|
+
if (node.name === 'search_path' && node.args) {
|
|
232
|
+
for (const arg of node.args) {
|
|
233
|
+
// search_path args can be String nodes or A_Const with sval
|
|
234
|
+
if (arg?.String?.sval) {
|
|
235
|
+
const schemaName = arg.String.sval;
|
|
236
|
+
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
237
|
+
result.schemasFound.add(schemaName);
|
|
238
|
+
const newName = schemaMapping.get(schemaName);
|
|
239
|
+
if (newName) {
|
|
240
|
+
arg.String.sval = newName;
|
|
241
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (arg?.A_Const?.sval?.sval) {
|
|
246
|
+
const schemaName = arg.A_Const.sval.sval;
|
|
247
|
+
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
248
|
+
result.schemasFound.add(schemaName);
|
|
249
|
+
const newName = schemaMapping.get(schemaName);
|
|
250
|
+
if (newName) {
|
|
251
|
+
arg.A_Const.sval.sval = newName;
|
|
252
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
// Transform AlterDefaultPrivilegesStmt (ALTER DEFAULT PRIVILEGES IN SCHEMA)
|
|
260
|
+
AlterDefaultPrivilegesStmt: (path) => {
|
|
261
|
+
const node = path.node;
|
|
262
|
+
if (node.options) {
|
|
263
|
+
for (const opt of node.options) {
|
|
264
|
+
if (opt?.DefElem?.defname === 'schemas' && opt.DefElem.arg?.List?.items) {
|
|
265
|
+
for (const item of opt.DefElem.arg.List.items) {
|
|
266
|
+
if (item?.String?.sval) {
|
|
267
|
+
const schemaName = item.String.sval;
|
|
268
|
+
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
269
|
+
result.schemasFound.add(schemaName);
|
|
270
|
+
const newName = schemaMapping.get(schemaName);
|
|
271
|
+
if (newName) {
|
|
272
|
+
item.String.sval = newName;
|
|
273
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
// Transform DropStmt (DROP SCHEMA name; DROP TABLE/INDEX/POLICY/TRIGGER/
|
|
283
|
+
// TYPE/FUNCTION schema.obj). The walker does NOT auto-recurse into
|
|
284
|
+
// DropStmt.objects.
|
|
285
|
+
DropStmt: (path) => {
|
|
286
|
+
const node = path.node;
|
|
287
|
+
if (node.removeType !== 'OBJECT_SCHEMA' && Array.isArray(node.objects)) {
|
|
288
|
+
for (const obj of node.objects) {
|
|
289
|
+
if (obj?.List?.items) {
|
|
290
|
+
transformNameList(obj.List.items, schemaMapping, result);
|
|
291
|
+
}
|
|
292
|
+
else if (obj?.ObjectWithArgs?.objname) {
|
|
293
|
+
transformNameList(obj.ObjectWithArgs.objname, schemaMapping, result);
|
|
294
|
+
}
|
|
295
|
+
else if (obj?.TypeName?.names) {
|
|
296
|
+
transformNameList(obj.TypeName.names, schemaMapping, result);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (node.removeType === 'OBJECT_SCHEMA' && node.objects) {
|
|
302
|
+
for (const obj of node.objects) {
|
|
303
|
+
// DROP SCHEMA objects can be List of String nodes
|
|
304
|
+
if (obj?.List?.items) {
|
|
305
|
+
for (const item of obj.List.items) {
|
|
306
|
+
if (item?.String?.sval) {
|
|
307
|
+
const schemaName = item.String.sval;
|
|
308
|
+
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
309
|
+
result.schemasFound.add(schemaName);
|
|
310
|
+
const newName = schemaMapping.get(schemaName);
|
|
311
|
+
if (newName) {
|
|
312
|
+
item.String.sval = newName;
|
|
313
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (obj?.String?.sval) {
|
|
320
|
+
const schemaName = obj.String.sval;
|
|
321
|
+
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
322
|
+
result.schemasFound.add(schemaName);
|
|
323
|
+
const newName = schemaMapping.get(schemaName);
|
|
324
|
+
if (newName) {
|
|
325
|
+
obj.String.sval = newName;
|
|
326
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
// Transform InsertStmt (INSERT INTO schema.table)
|
|
334
|
+
// Note: The walker does NOT auto-recurse into InsertStmt.relation.
|
|
335
|
+
InsertStmt: (path) => {
|
|
336
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
337
|
+
},
|
|
338
|
+
// Transform CreateStmt (CREATE TABLE schema.table)
|
|
339
|
+
// Note: The walker does NOT auto-recurse into CreateStmt.relation.
|
|
340
|
+
CreateStmt: (path) => {
|
|
341
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
342
|
+
},
|
|
343
|
+
// Transform UpdateStmt (UPDATE schema.table SET ...)
|
|
344
|
+
// Note: The walker does NOT auto-recurse into UpdateStmt.relation.
|
|
345
|
+
UpdateStmt: (path) => {
|
|
346
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
347
|
+
},
|
|
348
|
+
// Transform DeleteStmt (DELETE FROM schema.table)
|
|
349
|
+
// Note: The walker does NOT auto-recurse into DeleteStmt.relation.
|
|
350
|
+
DeleteStmt: (path) => {
|
|
351
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
352
|
+
},
|
|
353
|
+
// Transform MergeStmt (MERGE INTO schema.table USING ...)
|
|
354
|
+
// Note: The walker does NOT auto-recurse into MergeStmt.relation.
|
|
355
|
+
MergeStmt: (path) => {
|
|
356
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
357
|
+
},
|
|
358
|
+
// Transform IndexStmt (CREATE INDEX ... ON schema.table)
|
|
359
|
+
// Note: The walker does NOT auto-recurse into IndexStmt.relation.
|
|
360
|
+
IndexStmt: (path) => {
|
|
361
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
362
|
+
},
|
|
363
|
+
// Transform Constraint (REFERENCES schema.table for foreign keys)
|
|
364
|
+
// Note: The walker does NOT auto-recurse into Constraint.pktable.
|
|
365
|
+
Constraint: (path) => {
|
|
366
|
+
const node = path.node;
|
|
367
|
+
if (node.contype === 'CONSTR_FOREIGN' && node.pktable) {
|
|
368
|
+
transformRelation(node.pktable, schemaMapping, result);
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
// Transform CompositeTypeStmt (CREATE TYPE schema.name AS (...))
|
|
372
|
+
// Note: The walker does NOT auto-recurse into CompositeTypeStmt.typevar.
|
|
373
|
+
CompositeTypeStmt: (path) => {
|
|
374
|
+
transformRelation(path.node.typevar, schemaMapping, result);
|
|
375
|
+
},
|
|
376
|
+
// Transform ColumnDef (column definitions with schema-qualified types)
|
|
377
|
+
// Note: The walker does NOT auto-recurse into ColumnDef.typeName.
|
|
378
|
+
ColumnDef: (path) => {
|
|
379
|
+
const node = path.node;
|
|
380
|
+
if (node.typeName?.names) {
|
|
381
|
+
transformNameList(node.typeName.names, schemaMapping, result);
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
// Transform ViewStmt (CREATE VIEW schema.viewname AS ...)
|
|
385
|
+
// Note: The walker does NOT auto-recurse into ViewStmt.view.
|
|
386
|
+
ViewStmt: (path) => {
|
|
387
|
+
transformRelation(path.node.view, schemaMapping, result);
|
|
388
|
+
},
|
|
389
|
+
// Transform AlterTableStmt (ALTER TABLE schema.table ...)
|
|
390
|
+
// Note: The walker does NOT auto-recurse into AlterTableStmt.relation.
|
|
391
|
+
AlterTableStmt: (path) => {
|
|
392
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
393
|
+
},
|
|
394
|
+
// Transform CreateSeqStmt (CREATE SEQUENCE schema.seqname)
|
|
395
|
+
// Note: The walker does NOT auto-recurse into CreateSeqStmt.sequence.
|
|
396
|
+
CreateSeqStmt: (path) => {
|
|
397
|
+
transformRelation(path.node.sequence, schemaMapping, result);
|
|
398
|
+
},
|
|
399
|
+
// Transform AlterSeqStmt (ALTER SEQUENCE schema.seqname [OWNED BY schema.table.col])
|
|
400
|
+
// Note: The walker does NOT auto-recurse into AlterSeqStmt.sequence.
|
|
401
|
+
// Also handles OWNED BY clause where the schema name is in DefElem.arg.List.items.
|
|
402
|
+
AlterSeqStmt: (path) => {
|
|
403
|
+
const node = path.node;
|
|
404
|
+
transformRelation(node.sequence, schemaMapping, result);
|
|
405
|
+
// Handle OWNED BY option: schema name is in the name list
|
|
406
|
+
if (node.options) {
|
|
407
|
+
for (const opt of node.options) {
|
|
408
|
+
if (opt?.DefElem?.defname === 'owned_by' && opt.DefElem?.arg?.List?.items) {
|
|
409
|
+
transformNameList(opt.DefElem.arg.List.items, schemaMapping, result);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
// Transform CreatePolicyStmt (CREATE POLICY ... ON schema.table)
|
|
415
|
+
// Note: The walker does NOT auto-recurse into CreatePolicyStmt.table.
|
|
416
|
+
CreatePolicyStmt: (path) => {
|
|
417
|
+
transformRelation(path.node.table, schemaMapping, result);
|
|
418
|
+
},
|
|
419
|
+
// Transform RuleStmt (CREATE RULE ... ON schema.table)
|
|
420
|
+
// Note: The walker does NOT auto-recurse into RuleStmt.relation.
|
|
421
|
+
RuleStmt: (path) => {
|
|
422
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
423
|
+
},
|
|
424
|
+
// Transform CopyStmt (COPY schema.table ...)
|
|
425
|
+
// Note: The walker does NOT auto-recurse into CopyStmt.relation.
|
|
426
|
+
CopyStmt: (path) => {
|
|
427
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
428
|
+
},
|
|
429
|
+
// Transform ClusterStmt (CLUSTER schema.table USING index)
|
|
430
|
+
// Note: The walker does NOT auto-recurse into ClusterStmt.relation.
|
|
431
|
+
ClusterStmt: (path) => {
|
|
432
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
433
|
+
},
|
|
434
|
+
// Transform VacuumRelation (VACUUM/ANALYZE schema.table)
|
|
435
|
+
// Note: The walker visits VacuumRelation but does NOT recurse into its relation.
|
|
436
|
+
VacuumRelation: (path) => {
|
|
437
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
438
|
+
},
|
|
439
|
+
// Transform RefreshMatViewStmt (REFRESH MATERIALIZED VIEW schema.view)
|
|
440
|
+
// Note: The walker does NOT auto-recurse into RefreshMatViewStmt.relation.
|
|
441
|
+
RefreshMatViewStmt: (path) => {
|
|
442
|
+
transformRelation(path.node.relation, schemaMapping, result);
|
|
443
|
+
},
|
|
444
|
+
// Transform CreateTableAsStmt (CREATE MATERIALIZED VIEW schema.viewname AS ...)
|
|
445
|
+
// Note: The walker does NOT auto-recurse into CreateTableAsStmt.into.rel.
|
|
446
|
+
CreateTableAsStmt: (path) => {
|
|
447
|
+
transformRelation(path.node.into?.rel, schemaMapping, result);
|
|
448
|
+
},
|
|
449
|
+
// Transform TypeCast nodes (type casts like NULL::schema.type)
|
|
450
|
+
// Note: The walker visits TypeCast but does NOT recurse into the TypeName node.
|
|
451
|
+
TypeCast: (path) => {
|
|
452
|
+
const node = path.node;
|
|
453
|
+
if (node.typeName?.names) {
|
|
454
|
+
transformNameList(node.typeName.names, schemaMapping, result);
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
// Transform CreateFunctionStmt (CREATE FUNCTION schema.funcname)
|
|
458
|
+
// Also handles RETURNS [SETOF] schema.type via returnType.names
|
|
459
|
+
CreateFunctionStmt: (path) => {
|
|
460
|
+
const node = path.node;
|
|
461
|
+
transformNameList(node.funcname, schemaMapping, result);
|
|
462
|
+
// Transform the return type (e.g., RETURNS SETOF schema.tablename)
|
|
463
|
+
if (node.returnType?.names) {
|
|
464
|
+
transformNameList(node.returnType.names, schemaMapping, result);
|
|
465
|
+
}
|
|
466
|
+
// Transform parameter types (the walker does NOT auto-recurse into
|
|
467
|
+
// FunctionParameter.argType TypeName nodes)
|
|
468
|
+
if (Array.isArray(node.parameters)) {
|
|
469
|
+
for (const param of node.parameters) {
|
|
470
|
+
if (param?.FunctionParameter?.argType?.names) {
|
|
471
|
+
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Transform schema-qualified references inside the function body
|
|
476
|
+
// (AS $$...$$ is a String under the 'as' DefElem, which the walker
|
|
477
|
+
// does not parse as SQL). LANGUAGE plpgsql bodies are additionally
|
|
478
|
+
// rebuilt from the PL/pgSQL AST by transformSync; for LANGUAGE sql
|
|
479
|
+
// functions this string is the deparsed body.
|
|
480
|
+
if (Array.isArray(node.options)) {
|
|
481
|
+
for (const opt of node.options) {
|
|
482
|
+
if (opt?.DefElem?.defname === 'as' && opt.DefElem.arg?.List?.items) {
|
|
483
|
+
for (const item of opt.DefElem.arg.List.items) {
|
|
484
|
+
if (typeof item?.String?.sval === 'string' && item.String.sval.includes('.')) {
|
|
485
|
+
item.String.sval = transformSchemaRefsInString(item.String.sval, schemaMapping, result);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
// Transform CreateTrigStmt (CREATE TRIGGER ... EXECUTE PROCEDURE schema.func)
|
|
493
|
+
// Note: The walker does NOT auto-recurse into CreateTrigStmt.relation,
|
|
494
|
+
// so we must explicitly transform the RangeVar here.
|
|
495
|
+
CreateTrigStmt: (path) => {
|
|
496
|
+
const node = path.node;
|
|
497
|
+
transformNameList(node.funcname, schemaMapping, result);
|
|
498
|
+
transformRelation(node.relation, schemaMapping, result);
|
|
499
|
+
},
|
|
500
|
+
// Transform CommentStmt (COMMENT ON TABLE/COLUMN/FUNCTION schema.obj,
|
|
501
|
+
// COMMENT ON SCHEMA schema)
|
|
502
|
+
CommentStmt: (path) => {
|
|
503
|
+
const node = path.node;
|
|
504
|
+
// The object field structure varies by objtype.
|
|
505
|
+
// For tables/views/etc, object is { List: { items: [{ String: ... }, ...] } }
|
|
506
|
+
// For functions, object is { ObjectWithArgs: { objname: [...] } }
|
|
507
|
+
// For schemas, object is a bare { String: { sval } }
|
|
508
|
+
if (node.object) {
|
|
509
|
+
// Handle List-wrapped objects (COMMENT ON TABLE schema.tbl, COMMENT ON COLUMN schema.tbl.col)
|
|
510
|
+
if (node.object?.List?.items) {
|
|
511
|
+
transformNameList(node.object.List.items, schemaMapping, result);
|
|
512
|
+
}
|
|
513
|
+
// Handle ObjectWithArgs-style objects (COMMENT ON FUNCTION schema.func(args))
|
|
514
|
+
else if (node.object?.ObjectWithArgs?.objname) {
|
|
515
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
516
|
+
}
|
|
517
|
+
// Handle bare schema names (COMMENT ON SCHEMA schema)
|
|
518
|
+
else if (node.object?.String && node.objtype === 'OBJECT_SCHEMA') {
|
|
519
|
+
transformSchemaNameField(node.object.String, 'sval', schemaMapping, result);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// Transform schema-qualified references inside the comment text itself
|
|
523
|
+
if (typeof node.comment === 'string') {
|
|
524
|
+
node.comment = transformSchemaRefsInString(node.comment, schemaMapping, result);
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
// Transform schema-qualified references embedded in string constants
|
|
528
|
+
// (e.g. advisory-lock keys: hashtextextended('schema.fn_name', 0))
|
|
529
|
+
A_Const: (path) => {
|
|
530
|
+
const node = path.node;
|
|
531
|
+
if (typeof node.sval?.sval === 'string' && node.sval.sval.includes('.')) {
|
|
532
|
+
node.sval.sval = transformSchemaRefsInString(node.sval.sval, schemaMapping, result);
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
// Transform DefineStmt (CREATE TYPE schema.name, CREATE AGGREGATE schema.agg)
|
|
536
|
+
DefineStmt: (path) => {
|
|
537
|
+
const node = path.node;
|
|
538
|
+
transformNameList(node.defnames, schemaMapping, result);
|
|
539
|
+
},
|
|
540
|
+
// Transform CreateDomainStmt (CREATE DOMAIN schema.domname)
|
|
541
|
+
CreateDomainStmt: (path) => {
|
|
542
|
+
const node = path.node;
|
|
543
|
+
transformNameList(node.domainname, schemaMapping, result);
|
|
544
|
+
},
|
|
545
|
+
// Transform CreateEnumStmt (CREATE TYPE schema.enumname AS ENUM)
|
|
546
|
+
CreateEnumStmt: (path) => {
|
|
547
|
+
const node = path.node;
|
|
548
|
+
transformNameList(node.typeName, schemaMapping, result);
|
|
549
|
+
},
|
|
550
|
+
// Transform AlterEnumStmt (ALTER TYPE schema.enumname ADD VALUE)
|
|
551
|
+
AlterEnumStmt: (path) => {
|
|
552
|
+
const node = path.node;
|
|
553
|
+
transformNameList(node.typeName, schemaMapping, result);
|
|
554
|
+
},
|
|
555
|
+
// Transform AlterDomainStmt (ALTER DOMAIN schema.domname)
|
|
556
|
+
AlterDomainStmt: (path) => {
|
|
557
|
+
const node = path.node;
|
|
558
|
+
transformNameList(node.typeName, schemaMapping, result);
|
|
559
|
+
},
|
|
560
|
+
// Transform AlterTypeStmt (ALTER TYPE schema.typename)
|
|
561
|
+
// Note: composite type alters use RangeVar (handled by RangeVar visitor),
|
|
562
|
+
// but some ALTER TYPE statements use typeName as a name list
|
|
563
|
+
AlterTypeStmt: (path) => {
|
|
564
|
+
const node = path.node;
|
|
565
|
+
transformNameList(node.typeName, schemaMapping, result);
|
|
566
|
+
},
|
|
567
|
+
// Transform ObjectWithArgs (used in ALTER FUNCTION, DROP FUNCTION with args, etc.)
|
|
568
|
+
ObjectWithArgs: (path) => {
|
|
569
|
+
const node = path.node;
|
|
570
|
+
transformNameList(node.objname, schemaMapping, result);
|
|
571
|
+
},
|
|
572
|
+
// Transform AlterObjectSchemaStmt (ALTER ... SET SCHEMA newschema)
|
|
573
|
+
// The walker does NOT auto-recurse into AlterObjectSchemaStmt.relation
|
|
574
|
+
// or .object, so we must transform relation, object names, and newschema.
|
|
575
|
+
AlterObjectSchemaStmt: (path) => {
|
|
576
|
+
const node = path.node;
|
|
577
|
+
transformRelation(node.relation, schemaMapping, result);
|
|
578
|
+
// Non-relation objects (ALTER TYPE/FUNCTION ... SET SCHEMA)
|
|
579
|
+
if (node.object?.List?.items) {
|
|
580
|
+
transformNameList(node.object.List.items, schemaMapping, result);
|
|
581
|
+
}
|
|
582
|
+
else if (node.object?.ObjectWithArgs?.objname) {
|
|
583
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
584
|
+
}
|
|
585
|
+
else if (node.object?.TypeName?.names) {
|
|
586
|
+
transformNameList(node.object.TypeName.names, schemaMapping, result);
|
|
587
|
+
}
|
|
588
|
+
// Transform the newschema (destination schema)
|
|
589
|
+
transformSchemaNameField(node, 'newschema', schemaMapping, result);
|
|
590
|
+
},
|
|
591
|
+
// Transform RenameStmt (ALTER SCHEMA old RENAME TO new;
|
|
592
|
+
// ALTER TABLE schema.tbl RENAME ...). The walker does NOT auto-recurse
|
|
593
|
+
// into RenameStmt.relation or .object.
|
|
594
|
+
RenameStmt: (path) => {
|
|
595
|
+
const node = path.node;
|
|
596
|
+
transformRelation(node.relation, schemaMapping, result);
|
|
597
|
+
if (node.renameType === 'OBJECT_SCHEMA') {
|
|
598
|
+
// subname is the old schema name; newname is the target
|
|
599
|
+
transformSchemaNameField(node, 'subname', schemaMapping, result);
|
|
600
|
+
transformSchemaNameField(node, 'newname', schemaMapping, result);
|
|
601
|
+
}
|
|
602
|
+
if (node.object?.List?.items) {
|
|
603
|
+
transformNameList(node.object.List.items, schemaMapping, result);
|
|
604
|
+
}
|
|
605
|
+
else if (node.object?.ObjectWithArgs?.objname) {
|
|
606
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
// Transform AlterFunctionStmt (ALTER FUNCTION schema.fn(...) SET ...)
|
|
610
|
+
AlterFunctionStmt: (path) => {
|
|
611
|
+
const node = path.node;
|
|
612
|
+
if (node.func?.objname) {
|
|
613
|
+
transformNameList(node.func.objname, schemaMapping, result);
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
// Transform AlterOwnerStmt (ALTER ... OWNER TO role)
|
|
617
|
+
AlterOwnerStmt: (path) => {
|
|
618
|
+
const node = path.node;
|
|
619
|
+
transformRelation(node.relation, schemaMapping, result);
|
|
620
|
+
if (node.object?.List?.items) {
|
|
621
|
+
transformNameList(node.object.List.items, schemaMapping, result);
|
|
622
|
+
}
|
|
623
|
+
else if (node.object?.ObjectWithArgs?.objname) {
|
|
624
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
625
|
+
}
|
|
626
|
+
else if (node.object?.String && node.objectType === 'OBJECT_SCHEMA') {
|
|
627
|
+
transformSchemaNameField(node.object.String, 'sval', schemaMapping, result);
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
// Transform CreateCastStmt (CREATE CAST (src AS tgt) WITH FUNCTION fn)
|
|
631
|
+
CreateCastStmt: (path) => {
|
|
632
|
+
const node = path.node;
|
|
633
|
+
if (node.sourcetype?.names) {
|
|
634
|
+
transformNameList(node.sourcetype.names, schemaMapping, result);
|
|
635
|
+
}
|
|
636
|
+
if (node.targettype?.names) {
|
|
637
|
+
transformNameList(node.targettype.names, schemaMapping, result);
|
|
638
|
+
}
|
|
639
|
+
if (node.func?.objname) {
|
|
640
|
+
transformNameList(node.func.objname, schemaMapping, result);
|
|
641
|
+
}
|
|
642
|
+
if (Array.isArray(node.func?.objargs)) {
|
|
643
|
+
for (const arg of node.func.objargs) {
|
|
644
|
+
if (arg?.TypeName?.names) {
|
|
645
|
+
transformNameList(arg.TypeName.names, schemaMapping, result);
|
|
646
|
+
}
|
|
647
|
+
else if (arg?.names) {
|
|
648
|
+
transformNameList(arg.names, schemaMapping, result);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (Array.isArray(node.func?.objfuncargs)) {
|
|
653
|
+
for (const param of node.func.objfuncargs) {
|
|
654
|
+
if (param?.FunctionParameter?.argType?.names) {
|
|
655
|
+
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
// Transform CreateEventTrigStmt (CREATE EVENT TRIGGER ... EXECUTE FUNCTION schema.fn())
|
|
661
|
+
CreateEventTrigStmt: (path) => {
|
|
662
|
+
const node = path.node;
|
|
663
|
+
transformNameList(node.funcname, schemaMapping, result);
|
|
664
|
+
},
|
|
665
|
+
// Transform IndexElem opclass (CREATE INDEX ... (col schema.opclass))
|
|
666
|
+
IndexElem: (path) => {
|
|
667
|
+
const node = path.node;
|
|
668
|
+
transformNameList(node.opclass, schemaMapping, result);
|
|
669
|
+
},
|
|
670
|
+
// Transform SecLabelStmt object (SECURITY LABEL ... ON COLUMN schema.tbl.col)
|
|
671
|
+
SecLabelStmt: (path) => {
|
|
672
|
+
const node = path.node;
|
|
673
|
+
if (node.object?.List?.items) {
|
|
674
|
+
transformNameList(node.object.List.items, schemaMapping, result);
|
|
675
|
+
}
|
|
676
|
+
else if (node.object?.ObjectWithArgs?.objname) {
|
|
677
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
// Transform DO block bodies. The body is an opaque string under the 'as'
|
|
681
|
+
// DefElem; schema-qualified references are rewritten with the strict
|
|
682
|
+
// schema-followed-by-dot pattern.
|
|
683
|
+
DoStmt: (path) => {
|
|
684
|
+
const node = path.node;
|
|
685
|
+
if (Array.isArray(node.args)) {
|
|
686
|
+
for (const arg of node.args) {
|
|
687
|
+
if (arg?.DefElem?.defname === 'as' && typeof arg.DefElem.arg?.String?.sval === 'string') {
|
|
688
|
+
arg.DefElem.arg.String.sval = transformSchemaRefsInString(arg.DefElem.arg.String.sval, schemaMapping, result);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Validate that no untransformed schema names remain in the output.
|
|
697
|
+
* Checks both schema-qualified references (schema.object) and standalone
|
|
698
|
+
* schema name contexts (ON SCHEMA, IN SCHEMA, CREATE SCHEMA, etc.).
|
|
699
|
+
*
|
|
700
|
+
* Throws an error if any schema names from the mapping are found in the output,
|
|
701
|
+
* indicating that the AST visitor is missing a handler for that node type.
|
|
702
|
+
*/
|
|
703
|
+
function validateNoUntransformedSchemas(content, schemaMapping) {
|
|
704
|
+
if (schemaMapping.size === 0) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
for (const [oldSchema, newSchema] of schemaMapping) {
|
|
708
|
+
const escapedSchema = escapeRegexp(oldSchema);
|
|
709
|
+
// Pattern 1: quoted or unquoted schema name followed by dot (schema-qualified)
|
|
710
|
+
const dotPattern = new RegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)`, 'g');
|
|
711
|
+
// Pattern 2: standalone schema name in known SQL contexts
|
|
712
|
+
// Note: We don't use trailing \b because it fails after closing quotes
|
|
713
|
+
// (both '"' and whitespace are non-word characters, so no boundary exists).
|
|
714
|
+
const standalonePattern = new RegExp(`(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` +
|
|
715
|
+
`(?:"${escapedSchema}"|\\b${escapedSchema}\\b)`, 'gi');
|
|
716
|
+
const dotMatches = content.match(dotPattern);
|
|
717
|
+
const standaloneMatches = content.match(standalonePattern);
|
|
718
|
+
const totalMatches = (dotMatches?.length || 0) + (standaloneMatches?.length || 0);
|
|
719
|
+
if (totalMatches > 0) {
|
|
720
|
+
const lines = content.split('\n');
|
|
721
|
+
const locations = [];
|
|
722
|
+
const combinedPattern = new RegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)|` +
|
|
723
|
+
`(?:ON\\s+SCHEMA\\s+|IN\\s+SCHEMA\\s+|CREATE\\s+SCHEMA\\s+|DROP\\s+SCHEMA\\s+(?:IF\\s+EXISTS\\s+)?|SET\\s+SCHEMA\\s+)` +
|
|
724
|
+
`(?:"${escapedSchema}"|\\b${escapedSchema}\\b)`, 'gi');
|
|
725
|
+
for (let i = 0; i < lines.length; i++) {
|
|
726
|
+
if (combinedPattern.test(lines[i])) {
|
|
727
|
+
locations.push(` Line ${i + 1}: ${lines[i].trim()}`);
|
|
728
|
+
}
|
|
729
|
+
combinedPattern.lastIndex = 0;
|
|
730
|
+
}
|
|
731
|
+
throw new Error(`AST transformation incomplete: found ${totalMatches} untransformed schema name(s) "${oldSchema}" ` +
|
|
732
|
+
`that should have been transformed to "${newSchema}". ` +
|
|
733
|
+
`This indicates a missing visitor handler in create_sql_visitor or walk_plpgsql_for_schemas.\n` +
|
|
734
|
+
`Locations:\n${locations.join('\n')}`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Create a PL/pgSQL visitor that transforms schema names in PL/pgSQL-specific nodes.
|
|
740
|
+
*/
|
|
741
|
+
function createPlpgsqlVisitor(schemaMapping, result) {
|
|
742
|
+
return {
|
|
743
|
+
PLpgSQL_type: (path) => {
|
|
744
|
+
const node = path.node;
|
|
745
|
+
if (node.typname) {
|
|
746
|
+
for (const [oldSchema, newSchema] of schemaMapping.entries()) {
|
|
747
|
+
if (node.typname.startsWith(oldSchema + '.')) {
|
|
748
|
+
const typeName = node.typname.substring(oldSchema.length + 1);
|
|
749
|
+
result.schemasFound.add(oldSchema);
|
|
750
|
+
node.typname = newSchema + '.' + typeName;
|
|
751
|
+
result.schemasTransformed.set(oldSchema, newSchema);
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
PLpgSQL_var: (path) => {
|
|
758
|
+
const node = path.node;
|
|
759
|
+
if (node.refname) {
|
|
760
|
+
for (const [oldSchema, newSchema] of schemaMapping.entries()) {
|
|
761
|
+
if (node.refname.startsWith(oldSchema + '.')) {
|
|
762
|
+
const rest = node.refname.substring(oldSchema.length + 1);
|
|
763
|
+
result.schemasFound.add(oldSchema);
|
|
764
|
+
node.refname = newSchema + '.' + rest;
|
|
765
|
+
result.schemasTransformed.set(oldSchema, newSchema);
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Transform a PLpgSQL_type typname using proper AST parsing.
|
|
775
|
+
*/
|
|
776
|
+
function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
777
|
+
let suffix = '';
|
|
778
|
+
let baseTypname = typname;
|
|
779
|
+
const rowtypeMatch = typname.match(/(%rowtype|%type)$/i);
|
|
780
|
+
if (rowtypeMatch) {
|
|
781
|
+
suffix = rowtypeMatch[1];
|
|
782
|
+
baseTypname = typname.substring(0, typname.length - suffix.length);
|
|
783
|
+
}
|
|
784
|
+
let needsTransform = false;
|
|
785
|
+
for (const oldSchema of schemaMapping.keys()) {
|
|
786
|
+
if (baseTypname.startsWith(oldSchema + '.') || baseTypname.startsWith('"' + oldSchema + '".')) {
|
|
787
|
+
needsTransform = true;
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (!needsTransform) {
|
|
792
|
+
return typname;
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
const sql = `SELECT NULL::${baseTypname}`;
|
|
796
|
+
const parseResult = (0, plpgsql_parser_1.parseSql)(sql);
|
|
797
|
+
if (!parseResult?.stmts?.[0]?.stmt) {
|
|
798
|
+
return transformPlpgsqlTypeString(typname, schemaMapping, result);
|
|
799
|
+
}
|
|
800
|
+
const sqlVisitor = {
|
|
801
|
+
TypeName: (path) => {
|
|
802
|
+
const typeNode = path.node;
|
|
803
|
+
if (typeNode.names && Array.isArray(typeNode.names)) {
|
|
804
|
+
transformNameList(typeNode.names, schemaMapping, result);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
(0, traverse_1.walk)(parseResult.stmts[0].stmt, sqlVisitor);
|
|
809
|
+
const deparsed = plpgsql_parser_1.Deparser.deparse(parseResult.stmts[0].stmt);
|
|
810
|
+
const match = deparsed.match(/SELECT\s+NULL::(.+)/i);
|
|
811
|
+
if (match) {
|
|
812
|
+
const transformedTypname = match[1].trim().replace(/;$/, '');
|
|
813
|
+
return transformedTypname + suffix;
|
|
814
|
+
}
|
|
815
|
+
return transformPlpgsqlTypeString(typname, schemaMapping, result);
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
return transformPlpgsqlTypeString(typname, schemaMapping, result);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Fallback string-based transformation for PLpgSQL_type typname.
|
|
823
|
+
* Uses @pgsql/quotes QuoteUtils for proper identifier quoting.
|
|
824
|
+
*/
|
|
825
|
+
function transformPlpgsqlTypeString(typname, schemaMapping, result) {
|
|
826
|
+
for (const [oldSchema, newSchema] of schemaMapping.entries()) {
|
|
827
|
+
// Unquoted schema: old_schema.rest
|
|
828
|
+
if (typname.startsWith(oldSchema + '.')) {
|
|
829
|
+
const rest = typname.substring(oldSchema.length + 1);
|
|
830
|
+
result.schemasFound.add(oldSchema);
|
|
831
|
+
result.schemasTransformed.set(oldSchema, newSchema);
|
|
832
|
+
return quotes_1.QuoteUtils.quoteIdentifier(newSchema) + '.' + rest;
|
|
833
|
+
}
|
|
834
|
+
// Quoted schema: "old_schema".rest
|
|
835
|
+
if (typname.startsWith('"' + oldSchema + '".')) {
|
|
836
|
+
const rest = typname.substring(oldSchema.length + 3);
|
|
837
|
+
result.schemasFound.add(oldSchema);
|
|
838
|
+
result.schemasTransformed.set(oldSchema, newSchema);
|
|
839
|
+
return quotes_1.QuoteUtils.quoteIdentifier(newSchema) + '.' + rest;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return typname;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Recursively walk the PL/pgSQL AST to transform schema names.
|
|
846
|
+
*/
|
|
847
|
+
function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
848
|
+
if (node === null || node === undefined || typeof node !== 'object') {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (Array.isArray(node)) {
|
|
852
|
+
for (let i = 0; i < node.length; i++) {
|
|
853
|
+
walkPlpgsqlForSchemas(node[i], schemaMapping, result);
|
|
854
|
+
}
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
// Fallback for SQL expressions the plpgsql walker does not reach with the
|
|
858
|
+
// SQL visitor (e.g. statements inside EXCEPTION handler bodies). The
|
|
859
|
+
// deparser renders exprs from their query string, so rewriting mapped
|
|
860
|
+
// schema-followed-by-dot references here is safe and idempotent.
|
|
861
|
+
if ('PLpgSQL_expr' in node) {
|
|
862
|
+
const expr = node.PLpgSQL_expr;
|
|
863
|
+
if (typeof expr.query === 'string' && expr.query.includes('.')) {
|
|
864
|
+
expr.query = transformSchemaRefsInString(expr.query, schemaMapping, result);
|
|
865
|
+
}
|
|
866
|
+
else if (expr.query && typeof expr.query === 'object' && expr.query.kind === 'sql-stmt') {
|
|
867
|
+
// Hydrated exprs carry { original, parseResult }; the deparser renders
|
|
868
|
+
// from parseResult, so visit its statements with the SQL visitor.
|
|
869
|
+
const sqlVisitor = createSqlVisitor(schemaMapping, result);
|
|
870
|
+
if (Array.isArray(expr.query.parseResult?.stmts)) {
|
|
871
|
+
for (const stmt of expr.query.parseResult.stmts) {
|
|
872
|
+
if (stmt?.stmt) {
|
|
873
|
+
(0, traverse_1.walk)(stmt.stmt, sqlVisitor);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (typeof expr.query.original === 'string' && expr.query.original.includes('.')) {
|
|
878
|
+
expr.query.original = transformSchemaRefsInString(expr.query.original, schemaMapping, result);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
// Transform schema-qualified references inside RAISE message strings
|
|
883
|
+
if ('PLpgSQL_stmt_raise' in node) {
|
|
884
|
+
const raise = node.PLpgSQL_stmt_raise;
|
|
885
|
+
if (typeof raise.message === 'string' && raise.message.includes('.')) {
|
|
886
|
+
raise.message = transformSchemaRefsInString(raise.message, schemaMapping, result);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if ('PLpgSQL_type' in node) {
|
|
890
|
+
const plType = node.PLpgSQL_type;
|
|
891
|
+
if (plType.typname) {
|
|
892
|
+
if (typeof plType.typname === 'object' && plType.typname.kind === 'type-name') {
|
|
893
|
+
// Transform schema names directly in the typeNameNode.names array.
|
|
894
|
+
// We cannot use walkSql here because the raw typeNameNode is not
|
|
895
|
+
// wrapped in the expected AST envelope that the traverse walker needs.
|
|
896
|
+
if (plType.typname.typeNameNode?.names) {
|
|
897
|
+
transformNameList(plType.typname.typeNameNode.names, schemaMapping, result);
|
|
898
|
+
}
|
|
899
|
+
// The deparser can fall back to the 'original' string to render
|
|
900
|
+
// DECLARE types, so update it as well. Rewrite only the schema
|
|
901
|
+
// prefix within the string so array bounds ([]) and %rowtype/%type
|
|
902
|
+
// suffixes are preserved.
|
|
903
|
+
if (typeof plType.typname.original === 'string') {
|
|
904
|
+
plType.typname.original = transformPlpgsqlTypeString(plType.typname.original, schemaMapping, result);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
else if (typeof plType.typname === 'string') {
|
|
908
|
+
plType.typname = transformPlpgsqlTypeAst(plType.typname, schemaMapping, result);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
for (const value of Object.values(node)) {
|
|
913
|
+
walkPlpgsqlForSchemas(value, schemaMapping, result);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Escape a string for use in a regular expression
|
|
918
|
+
*/
|
|
919
|
+
function escapeRegexp(str) {
|
|
920
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Extract pgpm header comments from the beginning of SQL content.
|
|
924
|
+
*/
|
|
925
|
+
function extractPgpmHeader(content) {
|
|
926
|
+
const lines = content.split('\n');
|
|
927
|
+
let headerEndIndex = 0;
|
|
928
|
+
for (let i = 0; i < lines.length; i++) {
|
|
929
|
+
const line = lines[i];
|
|
930
|
+
const trimmed = line.trim();
|
|
931
|
+
if (trimmed === '') {
|
|
932
|
+
headerEndIndex = i + 1;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (trimmed === '--') {
|
|
936
|
+
headerEndIndex = i + 1;
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (trimmed.startsWith('-- Deploy') ||
|
|
940
|
+
trimmed.startsWith('-- Revert') ||
|
|
941
|
+
trimmed.startsWith('-- Verify') ||
|
|
942
|
+
trimmed.startsWith('-- requires:') ||
|
|
943
|
+
trimmed.startsWith('-- made with')) {
|
|
944
|
+
headerEndIndex = i + 1;
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
if (trimmed.startsWith('--')) {
|
|
948
|
+
headerEndIndex = i + 1;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
// psql meta-command guard emitted by pgpm package exports, e.g.
|
|
952
|
+
// \echo Use "CREATE EXTENSION foo" to load this file. \quit
|
|
953
|
+
if (trimmed.startsWith('\\echo')) {
|
|
954
|
+
headerEndIndex = i + 1;
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
const headerLines = lines.slice(0, headerEndIndex);
|
|
960
|
+
const bodyLines = lines.slice(headerEndIndex);
|
|
961
|
+
return {
|
|
962
|
+
header: headerLines.join('\n') + (headerLines.length > 0 ? '\n' : ''),
|
|
963
|
+
body: bodyLines.join('\n')
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Transform comment headers (-- Deploy:, -- requires:, etc.)
|
|
968
|
+
* These are not part of the SQL AST, so we use regexp for these.
|
|
969
|
+
*/
|
|
970
|
+
function transformComments(content, schemaMapping, result) {
|
|
971
|
+
const commentPattern = /^(-- (?:Deploy|requires|Revert|Verify):?\s*)(.*)$/gm;
|
|
972
|
+
const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
|
|
973
|
+
return content.replace(commentPattern, (match, prefix, pathPart) => {
|
|
974
|
+
let newPath = pathPart;
|
|
975
|
+
for (const schema of schemas) {
|
|
976
|
+
const newName = schemaMapping.get(schema);
|
|
977
|
+
if (!newName)
|
|
978
|
+
continue;
|
|
979
|
+
result.schemasFound.add(schema);
|
|
980
|
+
const pathPattern = new RegExp(`(schemas/)${escapeRegexp(schema)}(/|$)`, 'g');
|
|
981
|
+
const before = newPath;
|
|
982
|
+
newPath = newPath.replace(pathPattern, `$1${newName}$2`);
|
|
983
|
+
if (newPath !== before) {
|
|
984
|
+
result.schemasTransformed.set(schema, newName);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return prefix + newPath;
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Transform verify function calls that use string literals.
|
|
992
|
+
* These are inside SQL strings and not part of the main AST.
|
|
993
|
+
*/
|
|
994
|
+
function transformVerifyCalls(content, schemaMapping, result) {
|
|
995
|
+
const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
|
|
996
|
+
let newContent = content;
|
|
997
|
+
for (const schema of schemas) {
|
|
998
|
+
const newName = schemaMapping.get(schema);
|
|
999
|
+
if (!newName)
|
|
1000
|
+
continue;
|
|
1001
|
+
const escapedSchema = escapeRegexp(schema);
|
|
1002
|
+
const verifyPattern = new RegExp(`(verify_(?:function|table|trigger|type|domain|view|index|constraint|schema|policy|table_grant|function_grant|sequence_grant|type_grant)\\s*\\(\\s*')${escapedSchema}(\\.|'\\s*\\))`, 'gi');
|
|
1003
|
+
const before = newContent;
|
|
1004
|
+
newContent = newContent.replace(verifyPattern, `$1${newName}$2`);
|
|
1005
|
+
if (newContent !== before) {
|
|
1006
|
+
result.schemasFound.add(schema);
|
|
1007
|
+
result.schemasTransformed.set(schema, newName);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return newContent;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Transform schema names inside JSON/JSONB string values.
|
|
1014
|
+
*/
|
|
1015
|
+
function transformJsonStringValues(content, schemaMapping, result) {
|
|
1016
|
+
const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
|
|
1017
|
+
let newContent = content;
|
|
1018
|
+
for (const schema of schemas) {
|
|
1019
|
+
const newName = schemaMapping.get(schema);
|
|
1020
|
+
if (!newName)
|
|
1021
|
+
continue;
|
|
1022
|
+
const escapedSchema = escapeRegexp(schema);
|
|
1023
|
+
const jsonValuePattern = new RegExp(`(:")${escapedSchema}(")`, 'g');
|
|
1024
|
+
const before = newContent;
|
|
1025
|
+
newContent = newContent.replace(jsonValuePattern, `$1${newName}$2`);
|
|
1026
|
+
if (newContent !== before) {
|
|
1027
|
+
result.schemasFound.add(schema);
|
|
1028
|
+
result.schemasTransformed.set(schema, newName);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return newContent;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Transform a single SQL string using full AST-based transformation.
|
|
1035
|
+
*
|
|
1036
|
+
* This is the main entry point for transforming SQL content. It:
|
|
1037
|
+
* 1. Runs any user-supplied pre-passes (string-level)
|
|
1038
|
+
* 2. Extracts pgpm header comments and transforms them
|
|
1039
|
+
* 3. Runs full AST transformation on the SQL body
|
|
1040
|
+
* 4. Runs any user-supplied post-passes (string-level)
|
|
1041
|
+
* 5. Validates no untransformed schema names remain
|
|
1042
|
+
* 6. Returns the combined result
|
|
1043
|
+
*
|
|
1044
|
+
* App-specific string-level transforms (verify calls, JSON values, etc.)
|
|
1045
|
+
* are NOT included by default — pass them via `options.pre_passes` or
|
|
1046
|
+
* `options.post_passes`. The built-in passes `transform_verify_calls`
|
|
1047
|
+
* and `transform_json_string_values` are exported for convenience.
|
|
1048
|
+
*/
|
|
1049
|
+
function transformSql(content, schemaMapping, options, result) {
|
|
1050
|
+
// Support legacy signature: transform_sql(content, mapping, result?)
|
|
1051
|
+
let opts = {};
|
|
1052
|
+
let r;
|
|
1053
|
+
if (options && 'schemasFound' in options) {
|
|
1054
|
+
// Called with legacy signature: (content, mapping, result)
|
|
1055
|
+
r = options;
|
|
1056
|
+
}
|
|
1057
|
+
else {
|
|
1058
|
+
opts = options || {};
|
|
1059
|
+
r = result || createResult();
|
|
1060
|
+
}
|
|
1061
|
+
if (schemaMapping.size === 0) {
|
|
1062
|
+
return { content, result: r };
|
|
1063
|
+
}
|
|
1064
|
+
let newContent = content;
|
|
1065
|
+
// Run pre-passes (app-specific string-level transforms)
|
|
1066
|
+
if (opts.prePasses) {
|
|
1067
|
+
for (const pass of opts.prePasses) {
|
|
1068
|
+
newContent = pass(newContent, schemaMapping, r);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
// Main pass: AST transformation with header handling
|
|
1072
|
+
newContent = transformSqlContentAst(newContent, schemaMapping, r, opts);
|
|
1073
|
+
// Run post-passes (app-specific string-level transforms)
|
|
1074
|
+
if (opts.postPasses) {
|
|
1075
|
+
for (const pass of opts.postPasses) {
|
|
1076
|
+
newContent = pass(newContent, schemaMapping, r);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return { content: newContent, result: r };
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Transform a single SQL statement string using AST.
|
|
1083
|
+
* Unlike transform_sql, this does NOT handle headers, verify calls, or JSON values.
|
|
1084
|
+
* Use this for testing individual SQL statements.
|
|
1085
|
+
*/
|
|
1086
|
+
function transformSqlStatement(sql, schemaMapping, result) {
|
|
1087
|
+
const r = result || createResult();
|
|
1088
|
+
if (schemaMapping.size === 0) {
|
|
1089
|
+
return { sql, result: r };
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
const transformed = (0, plpgsql_parser_1.transformSync)(sql, (ctx) => {
|
|
1093
|
+
const sqlVisitor = createSqlVisitor(schemaMapping, r);
|
|
1094
|
+
if (ctx.sql?.stmts) {
|
|
1095
|
+
for (const stmt of ctx.sql.stmts) {
|
|
1096
|
+
if (stmt?.stmt) {
|
|
1097
|
+
(0, traverse_1.walk)(stmt.stmt, sqlVisitor);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
for (const fn of ctx.functions) {
|
|
1102
|
+
if (fn.plpgsql?.hydrated) {
|
|
1103
|
+
(0, plpgsql_parser_1.walk)(fn.plpgsql.hydrated, {}, {
|
|
1104
|
+
walkSqlExpressions: true,
|
|
1105
|
+
sqlVisitor: sqlVisitor
|
|
1106
|
+
});
|
|
1107
|
+
walkPlpgsqlForSchemas(fn.plpgsql.hydrated, schemaMapping, r);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}, { hydrate: true, pretty: true });
|
|
1111
|
+
return { sql: transformed, result: r };
|
|
1112
|
+
}
|
|
1113
|
+
catch (error) {
|
|
1114
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1115
|
+
r.errors.push({ file: 'inline', error: errMsg });
|
|
1116
|
+
throw error;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Internal: Transform SQL content with header extraction and AST transformation.
|
|
1121
|
+
*/
|
|
1122
|
+
function transformSqlContentAst(content, schemaMapping, result, options) {
|
|
1123
|
+
if (schemaMapping.size === 0) {
|
|
1124
|
+
return content;
|
|
1125
|
+
}
|
|
1126
|
+
const roundTrip = options?.roundTrip;
|
|
1127
|
+
const assumeSchemasExist = options?.assumeSchemasExist?.length
|
|
1128
|
+
? new Set(options.assumeSchemasExist)
|
|
1129
|
+
: undefined;
|
|
1130
|
+
const { header, body } = extractPgpmHeader(content);
|
|
1131
|
+
let transformedHeader = header;
|
|
1132
|
+
if (header.length > 0) {
|
|
1133
|
+
transformedHeader = transformComments(header, schemaMapping, result);
|
|
1134
|
+
}
|
|
1135
|
+
let transformedBody = body;
|
|
1136
|
+
if (options?.qualifyUnqualified && transformedBody.trim().length > 0) {
|
|
1137
|
+
try {
|
|
1138
|
+
transformedBody = (0, qualify_1.qualifyUnqualified)(transformedBody, options.qualifyUnqualified).sql;
|
|
1139
|
+
}
|
|
1140
|
+
catch (error) {
|
|
1141
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1142
|
+
result.errors.push({ file: 'inline', error: errMsg });
|
|
1143
|
+
throw error;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (transformedBody.trim().length > 0) {
|
|
1147
|
+
let before;
|
|
1148
|
+
try {
|
|
1149
|
+
transformedBody = (0, plpgsql_parser_1.transformSync)(transformedBody, (ctx) => {
|
|
1150
|
+
const sqlVisitor = createSqlVisitor(schemaMapping, result, { assumeSchemasExist });
|
|
1151
|
+
if (ctx.sql?.stmts) {
|
|
1152
|
+
for (const stmt of ctx.sql.stmts) {
|
|
1153
|
+
if (stmt?.stmt) {
|
|
1154
|
+
(0, traverse_1.walk)(stmt.stmt, sqlVisitor);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
for (const fn of ctx.functions) {
|
|
1159
|
+
if (fn.plpgsql?.hydrated) {
|
|
1160
|
+
(0, plpgsql_parser_1.walk)(fn.plpgsql.hydrated, {}, {
|
|
1161
|
+
walkSqlExpressions: true,
|
|
1162
|
+
sqlVisitor: sqlVisitor
|
|
1163
|
+
});
|
|
1164
|
+
walkPlpgsqlForSchemas(fn.plpgsql.hydrated, schemaMapping, result);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (roundTrip) {
|
|
1168
|
+
before = (0, round_trip_1.captureTransformAsts)(ctx);
|
|
1169
|
+
}
|
|
1170
|
+
}, { hydrate: true, pretty: true });
|
|
1171
|
+
}
|
|
1172
|
+
catch (error) {
|
|
1173
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1174
|
+
result.errors.push({ file: 'inline', error: errMsg });
|
|
1175
|
+
throw error;
|
|
1176
|
+
}
|
|
1177
|
+
validateNoUntransformedSchemas(transformedBody, schemaMapping);
|
|
1178
|
+
if (roundTrip && before) {
|
|
1179
|
+
try {
|
|
1180
|
+
(0, round_trip_1.validateRoundTrip)(before, transformedBody);
|
|
1181
|
+
}
|
|
1182
|
+
catch (error) {
|
|
1183
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1184
|
+
result.errors.push({ file: 'inline', error: errMsg });
|
|
1185
|
+
throw error;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return transformedHeader + transformedBody;
|
|
1190
|
+
}
|