@pgsql/transform 18.3.1 → 18.4.1
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/esm/index.js +1 -0
- package/esm/router.js +121 -0
- package/esm/transform.js +178 -121
- package/index.d.ts +3 -1
- package/index.js +3 -1
- package/package.json +2 -2
- package/router.d.ts +94 -0
- package/router.js +125 -0
- package/transform.d.ts +21 -12
- package/transform.js +178 -121
package/transform.js
CHANGED
|
@@ -49,6 +49,15 @@ const traverse_1 = require("@pgsql/traverse");
|
|
|
49
49
|
const plpgsql_parser_1 = require("plpgsql-parser");
|
|
50
50
|
const qualify_1 = require("./qualify");
|
|
51
51
|
const round_trip_1 = require("./round-trip");
|
|
52
|
+
const router_1 = require("./router");
|
|
53
|
+
/** Coerce any accepted mapping form into a {@link SchemaRouter}. */
|
|
54
|
+
function asRouter(mapping) {
|
|
55
|
+
return router_1.SchemaRouter.from(mapping);
|
|
56
|
+
}
|
|
57
|
+
/** Flat schema-level view for string-level passes that operate per source schema. */
|
|
58
|
+
function schemaLevelMap(mapping) {
|
|
59
|
+
return mapping instanceof router_1.SchemaRouter ? mapping.schemaLevelMap() : mapping;
|
|
60
|
+
}
|
|
52
61
|
/**
|
|
53
62
|
* Create a fresh result object
|
|
54
63
|
*/
|
|
@@ -78,21 +87,26 @@ function shouldTransformSchema(schemaName, schemaMapping) {
|
|
|
78
87
|
/**
|
|
79
88
|
* Transform schema names in a String node array (used for funcname, names, etc.)
|
|
80
89
|
* These arrays contain String nodes like { String: { sval: 'schema_name' } }
|
|
90
|
+
*
|
|
91
|
+
* `ns` names the namespace of the referenced object so object-level routes can
|
|
92
|
+
* apply; the object's own name is the last element of the list. When `ns` is
|
|
93
|
+
* `unknown` (the default) only the schema-level default applies — identical to
|
|
94
|
+
* the historic whole-schema behaviour.
|
|
81
95
|
*/
|
|
82
|
-
function transformNameList(names, schemaMapping, result) {
|
|
96
|
+
function transformNameList(names, schemaMapping, result, ns = 'unknown') {
|
|
83
97
|
if (!names || names.length < 2)
|
|
84
98
|
return;
|
|
85
|
-
|
|
99
|
+
const router = asRouter(schemaMapping);
|
|
100
|
+
// For schema-qualified names, the first element is the schema.
|
|
86
101
|
const first = names[0];
|
|
87
102
|
if (first?.String?.sval) {
|
|
88
103
|
const schemaName = first.String.sval;
|
|
89
|
-
|
|
104
|
+
const objName = names[names.length - 1]?.String?.sval;
|
|
105
|
+
const newName = router.resolve(schemaName, objName, ns);
|
|
106
|
+
if (newName && newName !== schemaName) {
|
|
90
107
|
result.schemasFound.add(schemaName);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
first.String.sval = newName;
|
|
94
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
95
|
-
}
|
|
108
|
+
first.String.sval = newName;
|
|
109
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
96
110
|
}
|
|
97
111
|
}
|
|
98
112
|
}
|
|
@@ -105,13 +119,13 @@ function transformSchemaNameField(container, field, schemaMapping, result) {
|
|
|
105
119
|
const schemaName = container?.[field];
|
|
106
120
|
if (typeof schemaName !== 'string')
|
|
107
121
|
return;
|
|
108
|
-
|
|
122
|
+
// A bare schema name is an operation on the schema itself: only the
|
|
123
|
+
// schema-level default applies.
|
|
124
|
+
const newName = asRouter(schemaMapping).resolve(schemaName, undefined, 'schema');
|
|
125
|
+
if (newName && newName !== schemaName) {
|
|
109
126
|
result.schemasFound.add(schemaName);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
container[field] = newName;
|
|
113
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
114
|
-
}
|
|
127
|
+
container[field] = newName;
|
|
128
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
131
|
/**
|
|
@@ -119,14 +133,16 @@ function transformSchemaNameField(container, field, schemaMapping, result) {
|
|
|
119
133
|
* Many statement nodes embed a relation that the walker does NOT auto-recurse into.
|
|
120
134
|
*/
|
|
121
135
|
function transformRelation(relation, schemaMapping, result) {
|
|
122
|
-
if (relation?.schemaname
|
|
123
|
-
|
|
136
|
+
if (!relation?.schemaname)
|
|
137
|
+
return;
|
|
138
|
+
const oldName = relation.schemaname;
|
|
139
|
+
// A RangeVar names a relation (table/view/sequence/matview); route by the
|
|
140
|
+
// relation name so object-level routes can send it to its own schema.
|
|
141
|
+
const newName = asRouter(schemaMapping).resolve(oldName, relation.relname, 'relation');
|
|
142
|
+
if (newName && newName !== oldName) {
|
|
124
143
|
result.schemasFound.add(oldName);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
relation.schemaname = newName;
|
|
128
|
-
result.schemasTransformed.set(oldName, newName);
|
|
129
|
-
}
|
|
144
|
+
relation.schemaname = newName;
|
|
145
|
+
result.schemasTransformed.set(oldName, newName);
|
|
130
146
|
}
|
|
131
147
|
}
|
|
132
148
|
/**
|
|
@@ -137,7 +153,9 @@ function transformRelation(relation, schemaMapping, result) {
|
|
|
137
153
|
*/
|
|
138
154
|
function transformSchemaRefsInString(str, schemaMapping, result) {
|
|
139
155
|
let out = str;
|
|
140
|
-
|
|
156
|
+
// References embedded in opaque strings carry no object identity, so only
|
|
157
|
+
// whole-schema (schema-level default) routes can be applied here.
|
|
158
|
+
for (const [oldSchema, newSchema] of schemaLevelMap(schemaMapping)) {
|
|
141
159
|
const pattern = new RegExp(`(?<![\\w-])("?)${escapeRegexp(oldSchema)}\\1(?=\\.)`, 'g');
|
|
142
160
|
const before = out;
|
|
143
161
|
out = out.replace(pattern, `$1${newSchema}$1`);
|
|
@@ -157,30 +175,59 @@ function transformSchemaRefsInString(str, schemaMapping, result) {
|
|
|
157
175
|
* that require explicit handlers.
|
|
158
176
|
*/
|
|
159
177
|
function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
178
|
+
const router = asRouter(schemaMapping);
|
|
160
179
|
const assumeSchemasExist = visitorOptions?.assumeSchemasExist;
|
|
180
|
+
const useAstBodies = router.hasObjectRoutes();
|
|
181
|
+
// Rewrite a bare schema-name string field in place (schema-level default).
|
|
182
|
+
const rewriteSchemaField = (container, key) => {
|
|
183
|
+
const schemaName = container?.[key];
|
|
184
|
+
if (typeof schemaName !== 'string')
|
|
185
|
+
return;
|
|
186
|
+
const newName = router.resolve(schemaName, undefined, 'schema');
|
|
187
|
+
if (newName && newName !== schemaName) {
|
|
188
|
+
result.schemasFound.add(schemaName);
|
|
189
|
+
container[key] = newName;
|
|
190
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
// Namespace of the object a DROP/ALTER targets, from its removeType, so
|
|
194
|
+
// object routes apply to DROP TABLE/FUNCTION/TYPE (e.g. revert scripts).
|
|
195
|
+
const namespaceOfObjectType = (objType) => {
|
|
196
|
+
switch (objType) {
|
|
197
|
+
case 'OBJECT_TABLE':
|
|
198
|
+
case 'OBJECT_VIEW':
|
|
199
|
+
case 'OBJECT_SEQUENCE':
|
|
200
|
+
case 'OBJECT_MATVIEW':
|
|
201
|
+
case 'OBJECT_FOREIGN_TABLE':
|
|
202
|
+
case 'OBJECT_INDEX':
|
|
203
|
+
return 'relation';
|
|
204
|
+
case 'OBJECT_FUNCTION':
|
|
205
|
+
case 'OBJECT_PROCEDURE':
|
|
206
|
+
case 'OBJECT_AGGREGATE':
|
|
207
|
+
case 'OBJECT_ROUTINE':
|
|
208
|
+
return 'function';
|
|
209
|
+
case 'OBJECT_TYPE':
|
|
210
|
+
case 'OBJECT_DOMAIN':
|
|
211
|
+
return 'type';
|
|
212
|
+
default:
|
|
213
|
+
return 'unknown';
|
|
214
|
+
}
|
|
215
|
+
};
|
|
161
216
|
return {
|
|
162
217
|
// Transform RangeVar nodes (table references)
|
|
163
218
|
RangeVar: (path) => {
|
|
164
|
-
|
|
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
|
-
}
|
|
219
|
+
transformRelation(path.node, router, result);
|
|
174
220
|
},
|
|
175
221
|
// Transform CreateSchemaStmt nodes
|
|
176
222
|
CreateSchemaStmt: (path) => {
|
|
177
223
|
const node = path.node;
|
|
178
|
-
if (node.schemaname
|
|
224
|
+
if (node.schemaname) {
|
|
179
225
|
const oldName = node.schemaname;
|
|
180
|
-
|
|
181
|
-
const newName =
|
|
182
|
-
if (newName) {
|
|
226
|
+
// The schema object itself: schema-level default only.
|
|
227
|
+
const newName = router.resolve(oldName, undefined, 'schema');
|
|
228
|
+
if (newName && newName !== oldName) {
|
|
183
229
|
node.schemaname = newName;
|
|
230
|
+
result.schemasFound.add(oldName);
|
|
184
231
|
result.schemasTransformed.set(oldName, newName);
|
|
185
232
|
}
|
|
186
233
|
}
|
|
@@ -191,7 +238,7 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
191
238
|
// Transform FuncCall nodes (function calls with schema-qualified names)
|
|
192
239
|
FuncCall: (path) => {
|
|
193
240
|
const node = path.node;
|
|
194
|
-
transformNameList(node.funcname,
|
|
241
|
+
transformNameList(node.funcname, router, result, 'function');
|
|
195
242
|
},
|
|
196
243
|
// Transform CallStmt (CALL schema.procedure(...)).
|
|
197
244
|
// Note: The walker does NOT auto-recurse into CallStmt.funccall,
|
|
@@ -199,18 +246,21 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
199
246
|
CallStmt: (path) => {
|
|
200
247
|
const node = path.node;
|
|
201
248
|
if (node.funccall?.funcname) {
|
|
202
|
-
transformNameList(node.funccall.funcname,
|
|
249
|
+
transformNameList(node.funccall.funcname, router, result, 'function');
|
|
203
250
|
}
|
|
204
251
|
},
|
|
205
252
|
// Transform TypeName nodes (type references with schema-qualified names)
|
|
206
253
|
TypeName: (path) => {
|
|
207
254
|
const node = path.node;
|
|
208
|
-
transformNameList(node.names,
|
|
255
|
+
transformNameList(node.names, router, result, 'type');
|
|
209
256
|
},
|
|
210
|
-
// Transform ColumnRef nodes (column references with schema-qualified names)
|
|
257
|
+
// Transform ColumnRef nodes (column references with schema-qualified names).
|
|
258
|
+
// A qualified column is schema.table.column — the routed object is the
|
|
259
|
+
// table, which we cannot disambiguate from the schema here, so only the
|
|
260
|
+
// schema-level default applies.
|
|
211
261
|
ColumnRef: (path) => {
|
|
212
262
|
const node = path.node;
|
|
213
|
-
transformNameList(node.fields,
|
|
263
|
+
transformNameList(node.fields, router, result);
|
|
214
264
|
},
|
|
215
265
|
// Transform GrantStmt objects (GRANT ON SCHEMA schema;
|
|
216
266
|
// GRANT ... ON ALL TABLES/FUNCTIONS/SEQUENCES IN SCHEMA schema)
|
|
@@ -232,26 +282,10 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
232
282
|
for (const arg of node.args) {
|
|
233
283
|
// search_path args can be String nodes or A_Const with sval
|
|
234
284
|
if (arg?.String?.sval) {
|
|
235
|
-
|
|
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
|
-
}
|
|
285
|
+
rewriteSchemaField(arg.String, 'sval');
|
|
244
286
|
}
|
|
245
287
|
else if (arg?.A_Const?.sval?.sval) {
|
|
246
|
-
|
|
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
|
-
}
|
|
288
|
+
rewriteSchemaField(arg.A_Const.sval, 'sval');
|
|
255
289
|
}
|
|
256
290
|
}
|
|
257
291
|
}
|
|
@@ -264,15 +298,7 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
264
298
|
if (opt?.DefElem?.defname === 'schemas' && opt.DefElem.arg?.List?.items) {
|
|
265
299
|
for (const item of opt.DefElem.arg.List.items) {
|
|
266
300
|
if (item?.String?.sval) {
|
|
267
|
-
|
|
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
|
-
}
|
|
301
|
+
rewriteSchemaField(item.String, 'sval');
|
|
276
302
|
}
|
|
277
303
|
}
|
|
278
304
|
}
|
|
@@ -285,15 +311,16 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
285
311
|
DropStmt: (path) => {
|
|
286
312
|
const node = path.node;
|
|
287
313
|
if (node.removeType !== 'OBJECT_SCHEMA' && Array.isArray(node.objects)) {
|
|
314
|
+
const ns = namespaceOfObjectType(node.removeType);
|
|
288
315
|
for (const obj of node.objects) {
|
|
289
316
|
if (obj?.List?.items) {
|
|
290
|
-
transformNameList(obj.List.items,
|
|
317
|
+
transformNameList(obj.List.items, router, result, ns);
|
|
291
318
|
}
|
|
292
319
|
else if (obj?.ObjectWithArgs?.objname) {
|
|
293
|
-
transformNameList(obj.ObjectWithArgs.objname,
|
|
320
|
+
transformNameList(obj.ObjectWithArgs.objname, router, result, ns);
|
|
294
321
|
}
|
|
295
322
|
else if (obj?.TypeName?.names) {
|
|
296
|
-
transformNameList(obj.TypeName.names,
|
|
323
|
+
transformNameList(obj.TypeName.names, router, result, ns);
|
|
297
324
|
}
|
|
298
325
|
}
|
|
299
326
|
return;
|
|
@@ -304,28 +331,12 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
304
331
|
if (obj?.List?.items) {
|
|
305
332
|
for (const item of obj.List.items) {
|
|
306
333
|
if (item?.String?.sval) {
|
|
307
|
-
|
|
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
|
-
}
|
|
334
|
+
rewriteSchemaField(item.String, 'sval');
|
|
316
335
|
}
|
|
317
336
|
}
|
|
318
337
|
}
|
|
319
338
|
else if (obj?.String?.sval) {
|
|
320
|
-
|
|
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
|
-
}
|
|
339
|
+
rewriteSchemaField(obj.String, 'sval');
|
|
329
340
|
}
|
|
330
341
|
}
|
|
331
342
|
}
|
|
@@ -378,7 +389,7 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
378
389
|
ColumnDef: (path) => {
|
|
379
390
|
const node = path.node;
|
|
380
391
|
if (node.typeName?.names) {
|
|
381
|
-
transformNameList(node.typeName.names, schemaMapping, result);
|
|
392
|
+
transformNameList(node.typeName.names, schemaMapping, result, 'type');
|
|
382
393
|
}
|
|
383
394
|
},
|
|
384
395
|
// Transform ViewStmt (CREATE VIEW schema.viewname AS ...)
|
|
@@ -451,24 +462,24 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
451
462
|
TypeCast: (path) => {
|
|
452
463
|
const node = path.node;
|
|
453
464
|
if (node.typeName?.names) {
|
|
454
|
-
transformNameList(node.typeName.names, schemaMapping, result);
|
|
465
|
+
transformNameList(node.typeName.names, schemaMapping, result, 'type');
|
|
455
466
|
}
|
|
456
467
|
},
|
|
457
468
|
// Transform CreateFunctionStmt (CREATE FUNCTION schema.funcname)
|
|
458
469
|
// Also handles RETURNS [SETOF] schema.type via returnType.names
|
|
459
470
|
CreateFunctionStmt: (path) => {
|
|
460
471
|
const node = path.node;
|
|
461
|
-
transformNameList(node.funcname, schemaMapping, result);
|
|
472
|
+
transformNameList(node.funcname, schemaMapping, result, 'function');
|
|
462
473
|
// Transform the return type (e.g., RETURNS SETOF schema.tablename)
|
|
463
474
|
if (node.returnType?.names) {
|
|
464
|
-
transformNameList(node.returnType.names, schemaMapping, result);
|
|
475
|
+
transformNameList(node.returnType.names, schemaMapping, result, 'type');
|
|
465
476
|
}
|
|
466
477
|
// Transform parameter types (the walker does NOT auto-recurse into
|
|
467
478
|
// FunctionParameter.argType TypeName nodes)
|
|
468
479
|
if (Array.isArray(node.parameters)) {
|
|
469
480
|
for (const param of node.parameters) {
|
|
470
481
|
if (param?.FunctionParameter?.argType?.names) {
|
|
471
|
-
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result);
|
|
482
|
+
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result, 'type');
|
|
472
483
|
}
|
|
473
484
|
}
|
|
474
485
|
}
|
|
@@ -481,8 +492,16 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
481
492
|
for (const opt of node.options) {
|
|
482
493
|
if (opt?.DefElem?.defname === 'as' && opt.DefElem.arg?.List?.items) {
|
|
483
494
|
for (const item of opt.DefElem.arg.List.items) {
|
|
484
|
-
if (typeof item?.String?.sval
|
|
485
|
-
|
|
495
|
+
if (typeof item?.String?.sval !== 'string')
|
|
496
|
+
continue;
|
|
497
|
+
// With object routes, references inside a LANGUAGE sql body must
|
|
498
|
+
// be rewritten AST-precisely; whole-schema routes keep the
|
|
499
|
+
// cheaper (and quoting-preserving) string pass.
|
|
500
|
+
if (useAstBodies) {
|
|
501
|
+
item.String.sval = transformSqlBodyString(item.String.sval, router, result);
|
|
502
|
+
}
|
|
503
|
+
else if (item.String.sval.includes('.')) {
|
|
504
|
+
item.String.sval = transformSchemaRefsInString(item.String.sval, router, result);
|
|
486
505
|
}
|
|
487
506
|
}
|
|
488
507
|
}
|
|
@@ -494,7 +513,7 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
494
513
|
// so we must explicitly transform the RangeVar here.
|
|
495
514
|
CreateTrigStmt: (path) => {
|
|
496
515
|
const node = path.node;
|
|
497
|
-
transformNameList(node.funcname, schemaMapping, result);
|
|
516
|
+
transformNameList(node.funcname, schemaMapping, result, 'function');
|
|
498
517
|
transformRelation(node.relation, schemaMapping, result);
|
|
499
518
|
},
|
|
500
519
|
// Transform CommentStmt (COMMENT ON TABLE/COLUMN/FUNCTION schema.obj,
|
|
@@ -540,29 +559,29 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
540
559
|
// Transform CreateDomainStmt (CREATE DOMAIN schema.domname)
|
|
541
560
|
CreateDomainStmt: (path) => {
|
|
542
561
|
const node = path.node;
|
|
543
|
-
transformNameList(node.domainname, schemaMapping, result);
|
|
562
|
+
transformNameList(node.domainname, schemaMapping, result, 'type');
|
|
544
563
|
},
|
|
545
564
|
// Transform CreateEnumStmt (CREATE TYPE schema.enumname AS ENUM)
|
|
546
565
|
CreateEnumStmt: (path) => {
|
|
547
566
|
const node = path.node;
|
|
548
|
-
transformNameList(node.typeName, schemaMapping, result);
|
|
567
|
+
transformNameList(node.typeName, schemaMapping, result, 'type');
|
|
549
568
|
},
|
|
550
569
|
// Transform AlterEnumStmt (ALTER TYPE schema.enumname ADD VALUE)
|
|
551
570
|
AlterEnumStmt: (path) => {
|
|
552
571
|
const node = path.node;
|
|
553
|
-
transformNameList(node.typeName, schemaMapping, result);
|
|
572
|
+
transformNameList(node.typeName, schemaMapping, result, 'type');
|
|
554
573
|
},
|
|
555
574
|
// Transform AlterDomainStmt (ALTER DOMAIN schema.domname)
|
|
556
575
|
AlterDomainStmt: (path) => {
|
|
557
576
|
const node = path.node;
|
|
558
|
-
transformNameList(node.typeName, schemaMapping, result);
|
|
577
|
+
transformNameList(node.typeName, schemaMapping, result, 'type');
|
|
559
578
|
},
|
|
560
579
|
// Transform AlterTypeStmt (ALTER TYPE schema.typename)
|
|
561
580
|
// Note: composite type alters use RangeVar (handled by RangeVar visitor),
|
|
562
581
|
// but some ALTER TYPE statements use typeName as a name list
|
|
563
582
|
AlterTypeStmt: (path) => {
|
|
564
583
|
const node = path.node;
|
|
565
|
-
transformNameList(node.typeName, schemaMapping, result);
|
|
584
|
+
transformNameList(node.typeName, schemaMapping, result, 'type');
|
|
566
585
|
},
|
|
567
586
|
// Transform ObjectWithArgs (used in ALTER FUNCTION, DROP FUNCTION with args, etc.)
|
|
568
587
|
ObjectWithArgs: (path) => {
|
|
@@ -610,7 +629,7 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
610
629
|
AlterFunctionStmt: (path) => {
|
|
611
630
|
const node = path.node;
|
|
612
631
|
if (node.func?.objname) {
|
|
613
|
-
transformNameList(node.func.objname, schemaMapping, result);
|
|
632
|
+
transformNameList(node.func.objname, schemaMapping, result, 'function');
|
|
614
633
|
}
|
|
615
634
|
},
|
|
616
635
|
// Transform AlterOwnerStmt (ALTER ... OWNER TO role)
|
|
@@ -631,28 +650,28 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
631
650
|
CreateCastStmt: (path) => {
|
|
632
651
|
const node = path.node;
|
|
633
652
|
if (node.sourcetype?.names) {
|
|
634
|
-
transformNameList(node.sourcetype.names, schemaMapping, result);
|
|
653
|
+
transformNameList(node.sourcetype.names, schemaMapping, result, 'type');
|
|
635
654
|
}
|
|
636
655
|
if (node.targettype?.names) {
|
|
637
|
-
transformNameList(node.targettype.names, schemaMapping, result);
|
|
656
|
+
transformNameList(node.targettype.names, schemaMapping, result, 'type');
|
|
638
657
|
}
|
|
639
658
|
if (node.func?.objname) {
|
|
640
|
-
transformNameList(node.func.objname, schemaMapping, result);
|
|
659
|
+
transformNameList(node.func.objname, schemaMapping, result, 'function');
|
|
641
660
|
}
|
|
642
661
|
if (Array.isArray(node.func?.objargs)) {
|
|
643
662
|
for (const arg of node.func.objargs) {
|
|
644
663
|
if (arg?.TypeName?.names) {
|
|
645
|
-
transformNameList(arg.TypeName.names, schemaMapping, result);
|
|
664
|
+
transformNameList(arg.TypeName.names, schemaMapping, result, 'type');
|
|
646
665
|
}
|
|
647
666
|
else if (arg?.names) {
|
|
648
|
-
transformNameList(arg.names, schemaMapping, result);
|
|
667
|
+
transformNameList(arg.names, schemaMapping, result, 'type');
|
|
649
668
|
}
|
|
650
669
|
}
|
|
651
670
|
}
|
|
652
671
|
if (Array.isArray(node.func?.objfuncargs)) {
|
|
653
672
|
for (const param of node.func.objfuncargs) {
|
|
654
673
|
if (param?.FunctionParameter?.argType?.names) {
|
|
655
|
-
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result);
|
|
674
|
+
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result, 'type');
|
|
656
675
|
}
|
|
657
676
|
}
|
|
658
677
|
}
|
|
@@ -660,7 +679,7 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
660
679
|
// Transform CreateEventTrigStmt (CREATE EVENT TRIGGER ... EXECUTE FUNCTION schema.fn())
|
|
661
680
|
CreateEventTrigStmt: (path) => {
|
|
662
681
|
const node = path.node;
|
|
663
|
-
transformNameList(node.funcname, schemaMapping, result);
|
|
682
|
+
transformNameList(node.funcname, schemaMapping, result, 'function');
|
|
664
683
|
},
|
|
665
684
|
// Transform IndexElem opclass (CREATE INDEX ... (col schema.opclass))
|
|
666
685
|
IndexElem: (path) => {
|
|
@@ -701,10 +720,14 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
701
720
|
* indicating that the AST visitor is missing a handler for that node type.
|
|
702
721
|
*/
|
|
703
722
|
function validateNoUntransformedSchemas(content, schemaMapping) {
|
|
704
|
-
|
|
723
|
+
// Only schemas with a schema-level default are guaranteed to move entirely;
|
|
724
|
+
// partially (object-only) routed schemas may legitimately keep some
|
|
725
|
+
// references, so they are excluded from the strict leftover check.
|
|
726
|
+
const moved = schemaMapping instanceof router_1.SchemaRouter ? schemaMapping.fullyMovedSchemas() : schemaMapping;
|
|
727
|
+
if (moved.size === 0) {
|
|
705
728
|
return;
|
|
706
729
|
}
|
|
707
|
-
for (const [oldSchema, newSchema] of
|
|
730
|
+
for (const [oldSchema, newSchema] of moved) {
|
|
708
731
|
const escapedSchema = escapeRegexp(oldSchema);
|
|
709
732
|
// Pattern 1: quoted or unquoted schema name followed by dot (schema-qualified)
|
|
710
733
|
const dotPattern = new RegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)`, 'g');
|
|
@@ -739,11 +762,12 @@ function validateNoUntransformedSchemas(content, schemaMapping) {
|
|
|
739
762
|
* Create a PL/pgSQL visitor that transforms schema names in PL/pgSQL-specific nodes.
|
|
740
763
|
*/
|
|
741
764
|
function createPlpgsqlVisitor(schemaMapping, result) {
|
|
765
|
+
const schemaMap = schemaLevelMap(schemaMapping);
|
|
742
766
|
return {
|
|
743
767
|
PLpgSQL_type: (path) => {
|
|
744
768
|
const node = path.node;
|
|
745
769
|
if (node.typname) {
|
|
746
|
-
for (const [oldSchema, newSchema] of
|
|
770
|
+
for (const [oldSchema, newSchema] of schemaMap.entries()) {
|
|
747
771
|
if (node.typname.startsWith(oldSchema + '.')) {
|
|
748
772
|
const typeName = node.typname.substring(oldSchema.length + 1);
|
|
749
773
|
result.schemasFound.add(oldSchema);
|
|
@@ -757,7 +781,7 @@ function createPlpgsqlVisitor(schemaMapping, result) {
|
|
|
757
781
|
PLpgSQL_var: (path) => {
|
|
758
782
|
const node = path.node;
|
|
759
783
|
if (node.refname) {
|
|
760
|
-
for (const [oldSchema, newSchema] of
|
|
784
|
+
for (const [oldSchema, newSchema] of schemaMap.entries()) {
|
|
761
785
|
if (node.refname.startsWith(oldSchema + '.')) {
|
|
762
786
|
const rest = node.refname.substring(oldSchema.length + 1);
|
|
763
787
|
result.schemasFound.add(oldSchema);
|
|
@@ -774,6 +798,7 @@ function createPlpgsqlVisitor(schemaMapping, result) {
|
|
|
774
798
|
* Transform a PLpgSQL_type typname using proper AST parsing.
|
|
775
799
|
*/
|
|
776
800
|
function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
801
|
+
const schemaMap = schemaLevelMap(schemaMapping);
|
|
777
802
|
let suffix = '';
|
|
778
803
|
let baseTypname = typname;
|
|
779
804
|
const rowtypeMatch = typname.match(/(%rowtype|%type)$/i);
|
|
@@ -782,7 +807,7 @@ function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
|
782
807
|
baseTypname = typname.substring(0, typname.length - suffix.length);
|
|
783
808
|
}
|
|
784
809
|
let needsTransform = false;
|
|
785
|
-
for (const oldSchema of
|
|
810
|
+
for (const oldSchema of schemaMap.keys()) {
|
|
786
811
|
if (baseTypname.startsWith(oldSchema + '.') || baseTypname.startsWith('"' + oldSchema + '".')) {
|
|
787
812
|
needsTransform = true;
|
|
788
813
|
break;
|
|
@@ -801,7 +826,7 @@ function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
|
801
826
|
TypeName: (path) => {
|
|
802
827
|
const typeNode = path.node;
|
|
803
828
|
if (typeNode.names && Array.isArray(typeNode.names)) {
|
|
804
|
-
transformNameList(typeNode.names, schemaMapping, result);
|
|
829
|
+
transformNameList(typeNode.names, schemaMapping, result, 'type');
|
|
805
830
|
}
|
|
806
831
|
}
|
|
807
832
|
};
|
|
@@ -823,7 +848,7 @@ function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
|
823
848
|
* Uses @pgsql/quotes QuoteUtils for proper identifier quoting.
|
|
824
849
|
*/
|
|
825
850
|
function transformPlpgsqlTypeString(typname, schemaMapping, result) {
|
|
826
|
-
for (const [oldSchema, newSchema] of schemaMapping.entries()) {
|
|
851
|
+
for (const [oldSchema, newSchema] of schemaLevelMap(schemaMapping).entries()) {
|
|
827
852
|
// Unquoted schema: old_schema.rest
|
|
828
853
|
if (typname.startsWith(oldSchema + '.')) {
|
|
829
854
|
const rest = typname.substring(oldSchema.length + 1);
|
|
@@ -894,7 +919,7 @@ function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
|
894
919
|
// We cannot use walkSql here because the raw typeNameNode is not
|
|
895
920
|
// wrapped in the expected AST envelope that the traverse walker needs.
|
|
896
921
|
if (plType.typname.typeNameNode?.names) {
|
|
897
|
-
transformNameList(plType.typname.typeNameNode.names, schemaMapping, result);
|
|
922
|
+
transformNameList(plType.typname.typeNameNode.names, schemaMapping, result, 'type');
|
|
898
923
|
}
|
|
899
924
|
// The deparser can fall back to the 'original' string to render
|
|
900
925
|
// DECLARE types, so update it as well. Rewrite only the schema
|
|
@@ -913,6 +938,35 @@ function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
|
913
938
|
walkPlpgsqlForSchemas(value, schemaMapping, result);
|
|
914
939
|
}
|
|
915
940
|
}
|
|
941
|
+
/**
|
|
942
|
+
* Rewrite a `LANGUAGE sql` function-body string using the full AST visitor so
|
|
943
|
+
* object-level routes reach references inside the body (the body is an opaque
|
|
944
|
+
* String node the outer walker never parses). Parses each statement, walks it
|
|
945
|
+
* with the router-aware SQL visitor, and deparses. Falls back to the
|
|
946
|
+
* schema-level string pass for anything that does not parse standalone (e.g.
|
|
947
|
+
* PL/pgSQL blocks or C symbol names).
|
|
948
|
+
*/
|
|
949
|
+
function transformSqlBodyString(body, router, result) {
|
|
950
|
+
try {
|
|
951
|
+
const parseResult = (0, plpgsql_parser_1.parseSql)(body);
|
|
952
|
+
const stmts = parseResult?.stmts ?? [];
|
|
953
|
+
if (stmts.length === 0) {
|
|
954
|
+
return body.includes('.') ? transformSchemaRefsInString(body, router, result) : body;
|
|
955
|
+
}
|
|
956
|
+
const visitor = createSqlVisitor(router, result);
|
|
957
|
+
const pieces = [];
|
|
958
|
+
for (const stmt of stmts) {
|
|
959
|
+
if (!stmt?.stmt)
|
|
960
|
+
continue;
|
|
961
|
+
(0, traverse_1.walk)(stmt.stmt, visitor);
|
|
962
|
+
pieces.push(plpgsql_parser_1.Deparser.deparse(stmt.stmt));
|
|
963
|
+
}
|
|
964
|
+
return pieces.join(';\n');
|
|
965
|
+
}
|
|
966
|
+
catch {
|
|
967
|
+
return body.includes('.') ? transformSchemaRefsInString(body, router, result) : body;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
916
970
|
/**
|
|
917
971
|
* Escape a string for use in a regular expression
|
|
918
972
|
*/
|
|
@@ -1061,11 +1115,13 @@ function transformSql(content, schemaMapping, options, result) {
|
|
|
1061
1115
|
if (schemaMapping.size === 0) {
|
|
1062
1116
|
return { content, result: r };
|
|
1063
1117
|
}
|
|
1118
|
+
// String-level passes operate on whole schemas; give them the flat view.
|
|
1119
|
+
const passMap = schemaLevelMap(schemaMapping);
|
|
1064
1120
|
let newContent = content;
|
|
1065
1121
|
// Run pre-passes (app-specific string-level transforms)
|
|
1066
1122
|
if (opts.prePasses) {
|
|
1067
1123
|
for (const pass of opts.prePasses) {
|
|
1068
|
-
newContent = pass(newContent,
|
|
1124
|
+
newContent = pass(newContent, passMap, r);
|
|
1069
1125
|
}
|
|
1070
1126
|
}
|
|
1071
1127
|
// Main pass: AST transformation with header handling
|
|
@@ -1073,7 +1129,7 @@ function transformSql(content, schemaMapping, options, result) {
|
|
|
1073
1129
|
// Run post-passes (app-specific string-level transforms)
|
|
1074
1130
|
if (opts.postPasses) {
|
|
1075
1131
|
for (const pass of opts.postPasses) {
|
|
1076
|
-
newContent = pass(newContent,
|
|
1132
|
+
newContent = pass(newContent, passMap, r);
|
|
1077
1133
|
}
|
|
1078
1134
|
}
|
|
1079
1135
|
return { content: newContent, result: r };
|
|
@@ -1130,7 +1186,8 @@ function transformSqlContentAst(content, schemaMapping, result, options) {
|
|
|
1130
1186
|
const { header, body } = extractPgpmHeader(content);
|
|
1131
1187
|
let transformedHeader = header;
|
|
1132
1188
|
if (header.length > 0) {
|
|
1133
|
-
|
|
1189
|
+
// Header/path rewrites (-- Deploy: schemas/<schema>/...) are whole-schema.
|
|
1190
|
+
transformedHeader = transformComments(header, schemaLevelMap(schemaMapping), result);
|
|
1134
1191
|
}
|
|
1135
1192
|
let transformedBody = body;
|
|
1136
1193
|
if (options?.qualifyUnqualified && transformedBody.trim().length > 0) {
|