@pgsql/transform 18.3.0 → 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/README.md +0 -8
- package/esm/index.js +1 -2
- package/esm/router.js +121 -0
- package/esm/transform.js +178 -121
- package/index.d.ts +3 -5
- package/index.js +3 -7
- 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/categorize.d.ts +0 -52
- package/categorize.js +0 -56
- package/esm/categorize.js +0 -51
- package/esm/fixture-closure.js +0 -197
- package/fixture-closure.d.ts +0 -71
- package/fixture-closure.js +0 -200
package/esm/transform.js
CHANGED
|
@@ -28,6 +28,15 @@ import { walk as walkSql } from '@pgsql/traverse';
|
|
|
28
28
|
import { Deparser, parseSql, transformSync, walk as walkPlpgsql } from 'plpgsql-parser';
|
|
29
29
|
import { qualifyUnqualified } from './qualify';
|
|
30
30
|
import { captureTransformAsts, validateRoundTrip } from './round-trip';
|
|
31
|
+
import { SchemaRouter } from './router';
|
|
32
|
+
/** Coerce any accepted mapping form into a {@link SchemaRouter}. */
|
|
33
|
+
function asRouter(mapping) {
|
|
34
|
+
return SchemaRouter.from(mapping);
|
|
35
|
+
}
|
|
36
|
+
/** Flat schema-level view for string-level passes that operate per source schema. */
|
|
37
|
+
function schemaLevelMap(mapping) {
|
|
38
|
+
return mapping instanceof SchemaRouter ? mapping.schemaLevelMap() : mapping;
|
|
39
|
+
}
|
|
31
40
|
/**
|
|
32
41
|
* Create a fresh result object
|
|
33
42
|
*/
|
|
@@ -57,21 +66,26 @@ export function shouldTransformSchema(schemaName, schemaMapping) {
|
|
|
57
66
|
/**
|
|
58
67
|
* Transform schema names in a String node array (used for funcname, names, etc.)
|
|
59
68
|
* These arrays contain String nodes like { String: { sval: 'schema_name' } }
|
|
69
|
+
*
|
|
70
|
+
* `ns` names the namespace of the referenced object so object-level routes can
|
|
71
|
+
* apply; the object's own name is the last element of the list. When `ns` is
|
|
72
|
+
* `unknown` (the default) only the schema-level default applies — identical to
|
|
73
|
+
* the historic whole-schema behaviour.
|
|
60
74
|
*/
|
|
61
|
-
export function transformNameList(names, schemaMapping, result) {
|
|
75
|
+
export function transformNameList(names, schemaMapping, result, ns = 'unknown') {
|
|
62
76
|
if (!names || names.length < 2)
|
|
63
77
|
return;
|
|
64
|
-
|
|
78
|
+
const router = asRouter(schemaMapping);
|
|
79
|
+
// For schema-qualified names, the first element is the schema.
|
|
65
80
|
const first = names[0];
|
|
66
81
|
if (first?.String?.sval) {
|
|
67
82
|
const schemaName = first.String.sval;
|
|
68
|
-
|
|
83
|
+
const objName = names[names.length - 1]?.String?.sval;
|
|
84
|
+
const newName = router.resolve(schemaName, objName, ns);
|
|
85
|
+
if (newName && newName !== schemaName) {
|
|
69
86
|
result.schemasFound.add(schemaName);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
first.String.sval = newName;
|
|
73
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
74
|
-
}
|
|
87
|
+
first.String.sval = newName;
|
|
88
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
}
|
|
@@ -84,13 +98,13 @@ export function transformSchemaNameField(container, field, schemaMapping, result
|
|
|
84
98
|
const schemaName = container?.[field];
|
|
85
99
|
if (typeof schemaName !== 'string')
|
|
86
100
|
return;
|
|
87
|
-
|
|
101
|
+
// A bare schema name is an operation on the schema itself: only the
|
|
102
|
+
// schema-level default applies.
|
|
103
|
+
const newName = asRouter(schemaMapping).resolve(schemaName, undefined, 'schema');
|
|
104
|
+
if (newName && newName !== schemaName) {
|
|
88
105
|
result.schemasFound.add(schemaName);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
container[field] = newName;
|
|
92
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
93
|
-
}
|
|
106
|
+
container[field] = newName;
|
|
107
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
94
108
|
}
|
|
95
109
|
}
|
|
96
110
|
/**
|
|
@@ -98,14 +112,16 @@ export function transformSchemaNameField(container, field, schemaMapping, result
|
|
|
98
112
|
* Many statement nodes embed a relation that the walker does NOT auto-recurse into.
|
|
99
113
|
*/
|
|
100
114
|
export function transformRelation(relation, schemaMapping, result) {
|
|
101
|
-
if (relation?.schemaname
|
|
102
|
-
|
|
115
|
+
if (!relation?.schemaname)
|
|
116
|
+
return;
|
|
117
|
+
const oldName = relation.schemaname;
|
|
118
|
+
// A RangeVar names a relation (table/view/sequence/matview); route by the
|
|
119
|
+
// relation name so object-level routes can send it to its own schema.
|
|
120
|
+
const newName = asRouter(schemaMapping).resolve(oldName, relation.relname, 'relation');
|
|
121
|
+
if (newName && newName !== oldName) {
|
|
103
122
|
result.schemasFound.add(oldName);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
relation.schemaname = newName;
|
|
107
|
-
result.schemasTransformed.set(oldName, newName);
|
|
108
|
-
}
|
|
123
|
+
relation.schemaname = newName;
|
|
124
|
+
result.schemasTransformed.set(oldName, newName);
|
|
109
125
|
}
|
|
110
126
|
}
|
|
111
127
|
/**
|
|
@@ -116,7 +132,9 @@ export function transformRelation(relation, schemaMapping, result) {
|
|
|
116
132
|
*/
|
|
117
133
|
export function transformSchemaRefsInString(str, schemaMapping, result) {
|
|
118
134
|
let out = str;
|
|
119
|
-
|
|
135
|
+
// References embedded in opaque strings carry no object identity, so only
|
|
136
|
+
// whole-schema (schema-level default) routes can be applied here.
|
|
137
|
+
for (const [oldSchema, newSchema] of schemaLevelMap(schemaMapping)) {
|
|
120
138
|
const pattern = new RegExp(`(?<![\\w-])("?)${escapeRegexp(oldSchema)}\\1(?=\\.)`, 'g');
|
|
121
139
|
const before = out;
|
|
122
140
|
out = out.replace(pattern, `$1${newSchema}$1`);
|
|
@@ -136,30 +154,59 @@ export function transformSchemaRefsInString(str, schemaMapping, result) {
|
|
|
136
154
|
* that require explicit handlers.
|
|
137
155
|
*/
|
|
138
156
|
export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
157
|
+
const router = asRouter(schemaMapping);
|
|
139
158
|
const assumeSchemasExist = visitorOptions?.assumeSchemasExist;
|
|
159
|
+
const useAstBodies = router.hasObjectRoutes();
|
|
160
|
+
// Rewrite a bare schema-name string field in place (schema-level default).
|
|
161
|
+
const rewriteSchemaField = (container, key) => {
|
|
162
|
+
const schemaName = container?.[key];
|
|
163
|
+
if (typeof schemaName !== 'string')
|
|
164
|
+
return;
|
|
165
|
+
const newName = router.resolve(schemaName, undefined, 'schema');
|
|
166
|
+
if (newName && newName !== schemaName) {
|
|
167
|
+
result.schemasFound.add(schemaName);
|
|
168
|
+
container[key] = newName;
|
|
169
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
// Namespace of the object a DROP/ALTER targets, from its removeType, so
|
|
173
|
+
// object routes apply to DROP TABLE/FUNCTION/TYPE (e.g. revert scripts).
|
|
174
|
+
const namespaceOfObjectType = (objType) => {
|
|
175
|
+
switch (objType) {
|
|
176
|
+
case 'OBJECT_TABLE':
|
|
177
|
+
case 'OBJECT_VIEW':
|
|
178
|
+
case 'OBJECT_SEQUENCE':
|
|
179
|
+
case 'OBJECT_MATVIEW':
|
|
180
|
+
case 'OBJECT_FOREIGN_TABLE':
|
|
181
|
+
case 'OBJECT_INDEX':
|
|
182
|
+
return 'relation';
|
|
183
|
+
case 'OBJECT_FUNCTION':
|
|
184
|
+
case 'OBJECT_PROCEDURE':
|
|
185
|
+
case 'OBJECT_AGGREGATE':
|
|
186
|
+
case 'OBJECT_ROUTINE':
|
|
187
|
+
return 'function';
|
|
188
|
+
case 'OBJECT_TYPE':
|
|
189
|
+
case 'OBJECT_DOMAIN':
|
|
190
|
+
return 'type';
|
|
191
|
+
default:
|
|
192
|
+
return 'unknown';
|
|
193
|
+
}
|
|
194
|
+
};
|
|
140
195
|
return {
|
|
141
196
|
// Transform RangeVar nodes (table references)
|
|
142
197
|
RangeVar: (path) => {
|
|
143
|
-
|
|
144
|
-
if (node.schemaname && shouldTransformSchema(node.schemaname, schemaMapping)) {
|
|
145
|
-
const oldName = node.schemaname;
|
|
146
|
-
result.schemasFound.add(oldName);
|
|
147
|
-
const newName = schemaMapping.get(oldName);
|
|
148
|
-
if (newName) {
|
|
149
|
-
node.schemaname = newName;
|
|
150
|
-
result.schemasTransformed.set(oldName, newName);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
198
|
+
transformRelation(path.node, router, result);
|
|
153
199
|
},
|
|
154
200
|
// Transform CreateSchemaStmt nodes
|
|
155
201
|
CreateSchemaStmt: (path) => {
|
|
156
202
|
const node = path.node;
|
|
157
|
-
if (node.schemaname
|
|
203
|
+
if (node.schemaname) {
|
|
158
204
|
const oldName = node.schemaname;
|
|
159
|
-
|
|
160
|
-
const newName =
|
|
161
|
-
if (newName) {
|
|
205
|
+
// The schema object itself: schema-level default only.
|
|
206
|
+
const newName = router.resolve(oldName, undefined, 'schema');
|
|
207
|
+
if (newName && newName !== oldName) {
|
|
162
208
|
node.schemaname = newName;
|
|
209
|
+
result.schemasFound.add(oldName);
|
|
163
210
|
result.schemasTransformed.set(oldName, newName);
|
|
164
211
|
}
|
|
165
212
|
}
|
|
@@ -170,7 +217,7 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
170
217
|
// Transform FuncCall nodes (function calls with schema-qualified names)
|
|
171
218
|
FuncCall: (path) => {
|
|
172
219
|
const node = path.node;
|
|
173
|
-
transformNameList(node.funcname,
|
|
220
|
+
transformNameList(node.funcname, router, result, 'function');
|
|
174
221
|
},
|
|
175
222
|
// Transform CallStmt (CALL schema.procedure(...)).
|
|
176
223
|
// Note: The walker does NOT auto-recurse into CallStmt.funccall,
|
|
@@ -178,18 +225,21 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
178
225
|
CallStmt: (path) => {
|
|
179
226
|
const node = path.node;
|
|
180
227
|
if (node.funccall?.funcname) {
|
|
181
|
-
transformNameList(node.funccall.funcname,
|
|
228
|
+
transformNameList(node.funccall.funcname, router, result, 'function');
|
|
182
229
|
}
|
|
183
230
|
},
|
|
184
231
|
// Transform TypeName nodes (type references with schema-qualified names)
|
|
185
232
|
TypeName: (path) => {
|
|
186
233
|
const node = path.node;
|
|
187
|
-
transformNameList(node.names,
|
|
234
|
+
transformNameList(node.names, router, result, 'type');
|
|
188
235
|
},
|
|
189
|
-
// Transform ColumnRef nodes (column references with schema-qualified names)
|
|
236
|
+
// Transform ColumnRef nodes (column references with schema-qualified names).
|
|
237
|
+
// A qualified column is schema.table.column — the routed object is the
|
|
238
|
+
// table, which we cannot disambiguate from the schema here, so only the
|
|
239
|
+
// schema-level default applies.
|
|
190
240
|
ColumnRef: (path) => {
|
|
191
241
|
const node = path.node;
|
|
192
|
-
transformNameList(node.fields,
|
|
242
|
+
transformNameList(node.fields, router, result);
|
|
193
243
|
},
|
|
194
244
|
// Transform GrantStmt objects (GRANT ON SCHEMA schema;
|
|
195
245
|
// GRANT ... ON ALL TABLES/FUNCTIONS/SEQUENCES IN SCHEMA schema)
|
|
@@ -211,26 +261,10 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
211
261
|
for (const arg of node.args) {
|
|
212
262
|
// search_path args can be String nodes or A_Const with sval
|
|
213
263
|
if (arg?.String?.sval) {
|
|
214
|
-
|
|
215
|
-
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
216
|
-
result.schemasFound.add(schemaName);
|
|
217
|
-
const newName = schemaMapping.get(schemaName);
|
|
218
|
-
if (newName) {
|
|
219
|
-
arg.String.sval = newName;
|
|
220
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
264
|
+
rewriteSchemaField(arg.String, 'sval');
|
|
223
265
|
}
|
|
224
266
|
else if (arg?.A_Const?.sval?.sval) {
|
|
225
|
-
|
|
226
|
-
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
227
|
-
result.schemasFound.add(schemaName);
|
|
228
|
-
const newName = schemaMapping.get(schemaName);
|
|
229
|
-
if (newName) {
|
|
230
|
-
arg.A_Const.sval.sval = newName;
|
|
231
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
267
|
+
rewriteSchemaField(arg.A_Const.sval, 'sval');
|
|
234
268
|
}
|
|
235
269
|
}
|
|
236
270
|
}
|
|
@@ -243,15 +277,7 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
243
277
|
if (opt?.DefElem?.defname === 'schemas' && opt.DefElem.arg?.List?.items) {
|
|
244
278
|
for (const item of opt.DefElem.arg.List.items) {
|
|
245
279
|
if (item?.String?.sval) {
|
|
246
|
-
|
|
247
|
-
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
248
|
-
result.schemasFound.add(schemaName);
|
|
249
|
-
const newName = schemaMapping.get(schemaName);
|
|
250
|
-
if (newName) {
|
|
251
|
-
item.String.sval = newName;
|
|
252
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
280
|
+
rewriteSchemaField(item.String, 'sval');
|
|
255
281
|
}
|
|
256
282
|
}
|
|
257
283
|
}
|
|
@@ -264,15 +290,16 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
264
290
|
DropStmt: (path) => {
|
|
265
291
|
const node = path.node;
|
|
266
292
|
if (node.removeType !== 'OBJECT_SCHEMA' && Array.isArray(node.objects)) {
|
|
293
|
+
const ns = namespaceOfObjectType(node.removeType);
|
|
267
294
|
for (const obj of node.objects) {
|
|
268
295
|
if (obj?.List?.items) {
|
|
269
|
-
transformNameList(obj.List.items,
|
|
296
|
+
transformNameList(obj.List.items, router, result, ns);
|
|
270
297
|
}
|
|
271
298
|
else if (obj?.ObjectWithArgs?.objname) {
|
|
272
|
-
transformNameList(obj.ObjectWithArgs.objname,
|
|
299
|
+
transformNameList(obj.ObjectWithArgs.objname, router, result, ns);
|
|
273
300
|
}
|
|
274
301
|
else if (obj?.TypeName?.names) {
|
|
275
|
-
transformNameList(obj.TypeName.names,
|
|
302
|
+
transformNameList(obj.TypeName.names, router, result, ns);
|
|
276
303
|
}
|
|
277
304
|
}
|
|
278
305
|
return;
|
|
@@ -283,28 +310,12 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
283
310
|
if (obj?.List?.items) {
|
|
284
311
|
for (const item of obj.List.items) {
|
|
285
312
|
if (item?.String?.sval) {
|
|
286
|
-
|
|
287
|
-
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
288
|
-
result.schemasFound.add(schemaName);
|
|
289
|
-
const newName = schemaMapping.get(schemaName);
|
|
290
|
-
if (newName) {
|
|
291
|
-
item.String.sval = newName;
|
|
292
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
313
|
+
rewriteSchemaField(item.String, 'sval');
|
|
295
314
|
}
|
|
296
315
|
}
|
|
297
316
|
}
|
|
298
317
|
else if (obj?.String?.sval) {
|
|
299
|
-
|
|
300
|
-
if (shouldTransformSchema(schemaName, schemaMapping)) {
|
|
301
|
-
result.schemasFound.add(schemaName);
|
|
302
|
-
const newName = schemaMapping.get(schemaName);
|
|
303
|
-
if (newName) {
|
|
304
|
-
obj.String.sval = newName;
|
|
305
|
-
result.schemasTransformed.set(schemaName, newName);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
318
|
+
rewriteSchemaField(obj.String, 'sval');
|
|
308
319
|
}
|
|
309
320
|
}
|
|
310
321
|
}
|
|
@@ -357,7 +368,7 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
357
368
|
ColumnDef: (path) => {
|
|
358
369
|
const node = path.node;
|
|
359
370
|
if (node.typeName?.names) {
|
|
360
|
-
transformNameList(node.typeName.names, schemaMapping, result);
|
|
371
|
+
transformNameList(node.typeName.names, schemaMapping, result, 'type');
|
|
361
372
|
}
|
|
362
373
|
},
|
|
363
374
|
// Transform ViewStmt (CREATE VIEW schema.viewname AS ...)
|
|
@@ -430,24 +441,24 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
430
441
|
TypeCast: (path) => {
|
|
431
442
|
const node = path.node;
|
|
432
443
|
if (node.typeName?.names) {
|
|
433
|
-
transformNameList(node.typeName.names, schemaMapping, result);
|
|
444
|
+
transformNameList(node.typeName.names, schemaMapping, result, 'type');
|
|
434
445
|
}
|
|
435
446
|
},
|
|
436
447
|
// Transform CreateFunctionStmt (CREATE FUNCTION schema.funcname)
|
|
437
448
|
// Also handles RETURNS [SETOF] schema.type via returnType.names
|
|
438
449
|
CreateFunctionStmt: (path) => {
|
|
439
450
|
const node = path.node;
|
|
440
|
-
transformNameList(node.funcname, schemaMapping, result);
|
|
451
|
+
transformNameList(node.funcname, schemaMapping, result, 'function');
|
|
441
452
|
// Transform the return type (e.g., RETURNS SETOF schema.tablename)
|
|
442
453
|
if (node.returnType?.names) {
|
|
443
|
-
transformNameList(node.returnType.names, schemaMapping, result);
|
|
454
|
+
transformNameList(node.returnType.names, schemaMapping, result, 'type');
|
|
444
455
|
}
|
|
445
456
|
// Transform parameter types (the walker does NOT auto-recurse into
|
|
446
457
|
// FunctionParameter.argType TypeName nodes)
|
|
447
458
|
if (Array.isArray(node.parameters)) {
|
|
448
459
|
for (const param of node.parameters) {
|
|
449
460
|
if (param?.FunctionParameter?.argType?.names) {
|
|
450
|
-
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result);
|
|
461
|
+
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result, 'type');
|
|
451
462
|
}
|
|
452
463
|
}
|
|
453
464
|
}
|
|
@@ -460,8 +471,16 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
460
471
|
for (const opt of node.options) {
|
|
461
472
|
if (opt?.DefElem?.defname === 'as' && opt.DefElem.arg?.List?.items) {
|
|
462
473
|
for (const item of opt.DefElem.arg.List.items) {
|
|
463
|
-
if (typeof item?.String?.sval
|
|
464
|
-
|
|
474
|
+
if (typeof item?.String?.sval !== 'string')
|
|
475
|
+
continue;
|
|
476
|
+
// With object routes, references inside a LANGUAGE sql body must
|
|
477
|
+
// be rewritten AST-precisely; whole-schema routes keep the
|
|
478
|
+
// cheaper (and quoting-preserving) string pass.
|
|
479
|
+
if (useAstBodies) {
|
|
480
|
+
item.String.sval = transformSqlBodyString(item.String.sval, router, result);
|
|
481
|
+
}
|
|
482
|
+
else if (item.String.sval.includes('.')) {
|
|
483
|
+
item.String.sval = transformSchemaRefsInString(item.String.sval, router, result);
|
|
465
484
|
}
|
|
466
485
|
}
|
|
467
486
|
}
|
|
@@ -473,7 +492,7 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
473
492
|
// so we must explicitly transform the RangeVar here.
|
|
474
493
|
CreateTrigStmt: (path) => {
|
|
475
494
|
const node = path.node;
|
|
476
|
-
transformNameList(node.funcname, schemaMapping, result);
|
|
495
|
+
transformNameList(node.funcname, schemaMapping, result, 'function');
|
|
477
496
|
transformRelation(node.relation, schemaMapping, result);
|
|
478
497
|
},
|
|
479
498
|
// Transform CommentStmt (COMMENT ON TABLE/COLUMN/FUNCTION schema.obj,
|
|
@@ -519,29 +538,29 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
519
538
|
// Transform CreateDomainStmt (CREATE DOMAIN schema.domname)
|
|
520
539
|
CreateDomainStmt: (path) => {
|
|
521
540
|
const node = path.node;
|
|
522
|
-
transformNameList(node.domainname, schemaMapping, result);
|
|
541
|
+
transformNameList(node.domainname, schemaMapping, result, 'type');
|
|
523
542
|
},
|
|
524
543
|
// Transform CreateEnumStmt (CREATE TYPE schema.enumname AS ENUM)
|
|
525
544
|
CreateEnumStmt: (path) => {
|
|
526
545
|
const node = path.node;
|
|
527
|
-
transformNameList(node.typeName, schemaMapping, result);
|
|
546
|
+
transformNameList(node.typeName, schemaMapping, result, 'type');
|
|
528
547
|
},
|
|
529
548
|
// Transform AlterEnumStmt (ALTER TYPE schema.enumname ADD VALUE)
|
|
530
549
|
AlterEnumStmt: (path) => {
|
|
531
550
|
const node = path.node;
|
|
532
|
-
transformNameList(node.typeName, schemaMapping, result);
|
|
551
|
+
transformNameList(node.typeName, schemaMapping, result, 'type');
|
|
533
552
|
},
|
|
534
553
|
// Transform AlterDomainStmt (ALTER DOMAIN schema.domname)
|
|
535
554
|
AlterDomainStmt: (path) => {
|
|
536
555
|
const node = path.node;
|
|
537
|
-
transformNameList(node.typeName, schemaMapping, result);
|
|
556
|
+
transformNameList(node.typeName, schemaMapping, result, 'type');
|
|
538
557
|
},
|
|
539
558
|
// Transform AlterTypeStmt (ALTER TYPE schema.typename)
|
|
540
559
|
// Note: composite type alters use RangeVar (handled by RangeVar visitor),
|
|
541
560
|
// but some ALTER TYPE statements use typeName as a name list
|
|
542
561
|
AlterTypeStmt: (path) => {
|
|
543
562
|
const node = path.node;
|
|
544
|
-
transformNameList(node.typeName, schemaMapping, result);
|
|
563
|
+
transformNameList(node.typeName, schemaMapping, result, 'type');
|
|
545
564
|
},
|
|
546
565
|
// Transform ObjectWithArgs (used in ALTER FUNCTION, DROP FUNCTION with args, etc.)
|
|
547
566
|
ObjectWithArgs: (path) => {
|
|
@@ -589,7 +608,7 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
589
608
|
AlterFunctionStmt: (path) => {
|
|
590
609
|
const node = path.node;
|
|
591
610
|
if (node.func?.objname) {
|
|
592
|
-
transformNameList(node.func.objname, schemaMapping, result);
|
|
611
|
+
transformNameList(node.func.objname, schemaMapping, result, 'function');
|
|
593
612
|
}
|
|
594
613
|
},
|
|
595
614
|
// Transform AlterOwnerStmt (ALTER ... OWNER TO role)
|
|
@@ -610,28 +629,28 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
610
629
|
CreateCastStmt: (path) => {
|
|
611
630
|
const node = path.node;
|
|
612
631
|
if (node.sourcetype?.names) {
|
|
613
|
-
transformNameList(node.sourcetype.names, schemaMapping, result);
|
|
632
|
+
transformNameList(node.sourcetype.names, schemaMapping, result, 'type');
|
|
614
633
|
}
|
|
615
634
|
if (node.targettype?.names) {
|
|
616
|
-
transformNameList(node.targettype.names, schemaMapping, result);
|
|
635
|
+
transformNameList(node.targettype.names, schemaMapping, result, 'type');
|
|
617
636
|
}
|
|
618
637
|
if (node.func?.objname) {
|
|
619
|
-
transformNameList(node.func.objname, schemaMapping, result);
|
|
638
|
+
transformNameList(node.func.objname, schemaMapping, result, 'function');
|
|
620
639
|
}
|
|
621
640
|
if (Array.isArray(node.func?.objargs)) {
|
|
622
641
|
for (const arg of node.func.objargs) {
|
|
623
642
|
if (arg?.TypeName?.names) {
|
|
624
|
-
transformNameList(arg.TypeName.names, schemaMapping, result);
|
|
643
|
+
transformNameList(arg.TypeName.names, schemaMapping, result, 'type');
|
|
625
644
|
}
|
|
626
645
|
else if (arg?.names) {
|
|
627
|
-
transformNameList(arg.names, schemaMapping, result);
|
|
646
|
+
transformNameList(arg.names, schemaMapping, result, 'type');
|
|
628
647
|
}
|
|
629
648
|
}
|
|
630
649
|
}
|
|
631
650
|
if (Array.isArray(node.func?.objfuncargs)) {
|
|
632
651
|
for (const param of node.func.objfuncargs) {
|
|
633
652
|
if (param?.FunctionParameter?.argType?.names) {
|
|
634
|
-
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result);
|
|
653
|
+
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result, 'type');
|
|
635
654
|
}
|
|
636
655
|
}
|
|
637
656
|
}
|
|
@@ -639,7 +658,7 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
639
658
|
// Transform CreateEventTrigStmt (CREATE EVENT TRIGGER ... EXECUTE FUNCTION schema.fn())
|
|
640
659
|
CreateEventTrigStmt: (path) => {
|
|
641
660
|
const node = path.node;
|
|
642
|
-
transformNameList(node.funcname, schemaMapping, result);
|
|
661
|
+
transformNameList(node.funcname, schemaMapping, result, 'function');
|
|
643
662
|
},
|
|
644
663
|
// Transform IndexElem opclass (CREATE INDEX ... (col schema.opclass))
|
|
645
664
|
IndexElem: (path) => {
|
|
@@ -680,10 +699,14 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
680
699
|
* indicating that the AST visitor is missing a handler for that node type.
|
|
681
700
|
*/
|
|
682
701
|
export function validateNoUntransformedSchemas(content, schemaMapping) {
|
|
683
|
-
|
|
702
|
+
// Only schemas with a schema-level default are guaranteed to move entirely;
|
|
703
|
+
// partially (object-only) routed schemas may legitimately keep some
|
|
704
|
+
// references, so they are excluded from the strict leftover check.
|
|
705
|
+
const moved = schemaMapping instanceof SchemaRouter ? schemaMapping.fullyMovedSchemas() : schemaMapping;
|
|
706
|
+
if (moved.size === 0) {
|
|
684
707
|
return;
|
|
685
708
|
}
|
|
686
|
-
for (const [oldSchema, newSchema] of
|
|
709
|
+
for (const [oldSchema, newSchema] of moved) {
|
|
687
710
|
const escapedSchema = escapeRegexp(oldSchema);
|
|
688
711
|
// Pattern 1: quoted or unquoted schema name followed by dot (schema-qualified)
|
|
689
712
|
const dotPattern = new RegExp(`(?:"${escapedSchema}"|\\b${escapedSchema})(?=\\.)`, 'g');
|
|
@@ -718,11 +741,12 @@ export function validateNoUntransformedSchemas(content, schemaMapping) {
|
|
|
718
741
|
* Create a PL/pgSQL visitor that transforms schema names in PL/pgSQL-specific nodes.
|
|
719
742
|
*/
|
|
720
743
|
export function createPlpgsqlVisitor(schemaMapping, result) {
|
|
744
|
+
const schemaMap = schemaLevelMap(schemaMapping);
|
|
721
745
|
return {
|
|
722
746
|
PLpgSQL_type: (path) => {
|
|
723
747
|
const node = path.node;
|
|
724
748
|
if (node.typname) {
|
|
725
|
-
for (const [oldSchema, newSchema] of
|
|
749
|
+
for (const [oldSchema, newSchema] of schemaMap.entries()) {
|
|
726
750
|
if (node.typname.startsWith(oldSchema + '.')) {
|
|
727
751
|
const typeName = node.typname.substring(oldSchema.length + 1);
|
|
728
752
|
result.schemasFound.add(oldSchema);
|
|
@@ -736,7 +760,7 @@ export function createPlpgsqlVisitor(schemaMapping, result) {
|
|
|
736
760
|
PLpgSQL_var: (path) => {
|
|
737
761
|
const node = path.node;
|
|
738
762
|
if (node.refname) {
|
|
739
|
-
for (const [oldSchema, newSchema] of
|
|
763
|
+
for (const [oldSchema, newSchema] of schemaMap.entries()) {
|
|
740
764
|
if (node.refname.startsWith(oldSchema + '.')) {
|
|
741
765
|
const rest = node.refname.substring(oldSchema.length + 1);
|
|
742
766
|
result.schemasFound.add(oldSchema);
|
|
@@ -753,6 +777,7 @@ export function createPlpgsqlVisitor(schemaMapping, result) {
|
|
|
753
777
|
* Transform a PLpgSQL_type typname using proper AST parsing.
|
|
754
778
|
*/
|
|
755
779
|
export function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
780
|
+
const schemaMap = schemaLevelMap(schemaMapping);
|
|
756
781
|
let suffix = '';
|
|
757
782
|
let baseTypname = typname;
|
|
758
783
|
const rowtypeMatch = typname.match(/(%rowtype|%type)$/i);
|
|
@@ -761,7 +786,7 @@ export function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
|
761
786
|
baseTypname = typname.substring(0, typname.length - suffix.length);
|
|
762
787
|
}
|
|
763
788
|
let needsTransform = false;
|
|
764
|
-
for (const oldSchema of
|
|
789
|
+
for (const oldSchema of schemaMap.keys()) {
|
|
765
790
|
if (baseTypname.startsWith(oldSchema + '.') || baseTypname.startsWith('"' + oldSchema + '".')) {
|
|
766
791
|
needsTransform = true;
|
|
767
792
|
break;
|
|
@@ -780,7 +805,7 @@ export function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
|
780
805
|
TypeName: (path) => {
|
|
781
806
|
const typeNode = path.node;
|
|
782
807
|
if (typeNode.names && Array.isArray(typeNode.names)) {
|
|
783
|
-
transformNameList(typeNode.names, schemaMapping, result);
|
|
808
|
+
transformNameList(typeNode.names, schemaMapping, result, 'type');
|
|
784
809
|
}
|
|
785
810
|
}
|
|
786
811
|
};
|
|
@@ -802,7 +827,7 @@ export function transformPlpgsqlTypeAst(typname, schemaMapping, result) {
|
|
|
802
827
|
* Uses @pgsql/quotes QuoteUtils for proper identifier quoting.
|
|
803
828
|
*/
|
|
804
829
|
export function transformPlpgsqlTypeString(typname, schemaMapping, result) {
|
|
805
|
-
for (const [oldSchema, newSchema] of schemaMapping.entries()) {
|
|
830
|
+
for (const [oldSchema, newSchema] of schemaLevelMap(schemaMapping).entries()) {
|
|
806
831
|
// Unquoted schema: old_schema.rest
|
|
807
832
|
if (typname.startsWith(oldSchema + '.')) {
|
|
808
833
|
const rest = typname.substring(oldSchema.length + 1);
|
|
@@ -873,7 +898,7 @@ export function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
|
873
898
|
// We cannot use walkSql here because the raw typeNameNode is not
|
|
874
899
|
// wrapped in the expected AST envelope that the traverse walker needs.
|
|
875
900
|
if (plType.typname.typeNameNode?.names) {
|
|
876
|
-
transformNameList(plType.typname.typeNameNode.names, schemaMapping, result);
|
|
901
|
+
transformNameList(plType.typname.typeNameNode.names, schemaMapping, result, 'type');
|
|
877
902
|
}
|
|
878
903
|
// The deparser can fall back to the 'original' string to render
|
|
879
904
|
// DECLARE types, so update it as well. Rewrite only the schema
|
|
@@ -892,6 +917,35 @@ export function walkPlpgsqlForSchemas(node, schemaMapping, result) {
|
|
|
892
917
|
walkPlpgsqlForSchemas(value, schemaMapping, result);
|
|
893
918
|
}
|
|
894
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Rewrite a `LANGUAGE sql` function-body string using the full AST visitor so
|
|
922
|
+
* object-level routes reach references inside the body (the body is an opaque
|
|
923
|
+
* String node the outer walker never parses). Parses each statement, walks it
|
|
924
|
+
* with the router-aware SQL visitor, and deparses. Falls back to the
|
|
925
|
+
* schema-level string pass for anything that does not parse standalone (e.g.
|
|
926
|
+
* PL/pgSQL blocks or C symbol names).
|
|
927
|
+
*/
|
|
928
|
+
function transformSqlBodyString(body, router, result) {
|
|
929
|
+
try {
|
|
930
|
+
const parseResult = parseSql(body);
|
|
931
|
+
const stmts = parseResult?.stmts ?? [];
|
|
932
|
+
if (stmts.length === 0) {
|
|
933
|
+
return body.includes('.') ? transformSchemaRefsInString(body, router, result) : body;
|
|
934
|
+
}
|
|
935
|
+
const visitor = createSqlVisitor(router, result);
|
|
936
|
+
const pieces = [];
|
|
937
|
+
for (const stmt of stmts) {
|
|
938
|
+
if (!stmt?.stmt)
|
|
939
|
+
continue;
|
|
940
|
+
walkSql(stmt.stmt, visitor);
|
|
941
|
+
pieces.push(Deparser.deparse(stmt.stmt));
|
|
942
|
+
}
|
|
943
|
+
return pieces.join(';\n');
|
|
944
|
+
}
|
|
945
|
+
catch {
|
|
946
|
+
return body.includes('.') ? transformSchemaRefsInString(body, router, result) : body;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
895
949
|
/**
|
|
896
950
|
* Escape a string for use in a regular expression
|
|
897
951
|
*/
|
|
@@ -1040,11 +1094,13 @@ export function transformSql(content, schemaMapping, options, result) {
|
|
|
1040
1094
|
if (schemaMapping.size === 0) {
|
|
1041
1095
|
return { content, result: r };
|
|
1042
1096
|
}
|
|
1097
|
+
// String-level passes operate on whole schemas; give them the flat view.
|
|
1098
|
+
const passMap = schemaLevelMap(schemaMapping);
|
|
1043
1099
|
let newContent = content;
|
|
1044
1100
|
// Run pre-passes (app-specific string-level transforms)
|
|
1045
1101
|
if (opts.prePasses) {
|
|
1046
1102
|
for (const pass of opts.prePasses) {
|
|
1047
|
-
newContent = pass(newContent,
|
|
1103
|
+
newContent = pass(newContent, passMap, r);
|
|
1048
1104
|
}
|
|
1049
1105
|
}
|
|
1050
1106
|
// Main pass: AST transformation with header handling
|
|
@@ -1052,7 +1108,7 @@ export function transformSql(content, schemaMapping, options, result) {
|
|
|
1052
1108
|
// Run post-passes (app-specific string-level transforms)
|
|
1053
1109
|
if (opts.postPasses) {
|
|
1054
1110
|
for (const pass of opts.postPasses) {
|
|
1055
|
-
newContent = pass(newContent,
|
|
1111
|
+
newContent = pass(newContent, passMap, r);
|
|
1056
1112
|
}
|
|
1057
1113
|
}
|
|
1058
1114
|
return { content: newContent, result: r };
|
|
@@ -1109,7 +1165,8 @@ function transformSqlContentAst(content, schemaMapping, result, options) {
|
|
|
1109
1165
|
const { header, body } = extractPgpmHeader(content);
|
|
1110
1166
|
let transformedHeader = header;
|
|
1111
1167
|
if (header.length > 0) {
|
|
1112
|
-
|
|
1168
|
+
// Header/path rewrites (-- Deploy: schemas/<schema>/...) are whole-schema.
|
|
1169
|
+
transformedHeader = transformComments(header, schemaLevelMap(schemaMapping), result);
|
|
1113
1170
|
}
|
|
1114
1171
|
let transformedBody = body;
|
|
1115
1172
|
if (options?.qualifyUnqualified && transformedBody.trim().length > 0) {
|
package/index.d.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
export type { CategoryProfile, ChangeCategory, } from './categorize';
|
|
2
|
-
export { buildCategoryOf, categorizeChange, TIER_PROFILE, } from './categorize';
|
|
3
1
|
export type { QualifiedName, StatementFacts, StatementKind, } from './facts';
|
|
4
2
|
export { classifyStatements } from './facts';
|
|
5
|
-
export type { ClosureChange, ClosureInputChange, ClosureReason, FixtureClosure, ResolveFixtureClosureOptions, } from './fixture-closure';
|
|
6
|
-
export { resolveFixtureClosure } from './fixture-closure';
|
|
7
3
|
export type { ObjectInventory, QualifyResult, QualifyRoutes, QualifyTargetSelector, QualifyUnqualifiedOptions, } from './qualify';
|
|
8
4
|
export { collectCreatedObjects, createQualifyVisitor, mergeInventories, qualifyUnqualified, } from './qualify';
|
|
5
|
+
export type { ObjectNamespace, RouteNamespace, RouteSpec, SchemaRoute, } from './router';
|
|
6
|
+
export { SchemaRouter } from './router';
|
|
9
7
|
export type { CapturedAsts } from './round-trip';
|
|
10
8
|
export { captureAstsFromSql, captureTransformAsts, firstDifference, normalizeParseTree, validateRoundTrip, } from './round-trip';
|
|
11
9
|
export type { NormalizeTreeOptions } from './round-trip-core';
|
|
12
10
|
export { CLEAN_TREE_VOLATILE_KEYS, cleanTree, normalizeTree, trimDefElemBody, } from './round-trip-core';
|
|
13
|
-
export type { SchemaTransformPass, SchemaTransformResult, TransformSqlOptions, } from './transform';
|
|
11
|
+
export type { SchemaMappingInput, SchemaTransformPass, SchemaTransformResult, TransformSqlOptions, } from './transform';
|
|
14
12
|
export { createPlpgsqlVisitor, createSqlVisitor, escapeRegexp, extractPgpmHeader, shouldTransformSchema, transformComments, transformJsonStringValues, transformNameList, transformPlpgsqlTypeAst, transformPlpgsqlTypeString, transformRelation, transformSchemaName, transformSql, transformSqlStatement, transformVerifyCalls, validateNoUntransformedSchemas, walkPlpgsqlForSchemas, } from './transform';
|
package/index.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.walkPlpgsqlForSchemas = exports.validateNoUntransformedSchemas = exports.transformVerifyCalls = exports.transformSqlStatement = exports.transformSql = exports.transformSchemaName = exports.transformRelation = exports.transformPlpgsqlTypeString = exports.transformPlpgsqlTypeAst = exports.transformNameList = exports.transformJsonStringValues = exports.transformComments = exports.shouldTransformSchema = exports.extractPgpmHeader = exports.escapeRegexp = exports.createSqlVisitor = exports.createPlpgsqlVisitor = exports.trimDefElemBody = exports.normalizeTree = exports.cleanTree = exports.CLEAN_TREE_VOLATILE_KEYS = exports.validateRoundTrip = exports.normalizeParseTree = exports.firstDifference = exports.captureTransformAsts = exports.captureAstsFromSql = exports.
|
|
4
|
-
var categorize_1 = require("./categorize");
|
|
5
|
-
Object.defineProperty(exports, "buildCategoryOf", { enumerable: true, get: function () { return categorize_1.buildCategoryOf; } });
|
|
6
|
-
Object.defineProperty(exports, "categorizeChange", { enumerable: true, get: function () { return categorize_1.categorizeChange; } });
|
|
7
|
-
Object.defineProperty(exports, "TIER_PROFILE", { enumerable: true, get: function () { return categorize_1.TIER_PROFILE; } });
|
|
3
|
+
exports.walkPlpgsqlForSchemas = exports.validateNoUntransformedSchemas = exports.transformVerifyCalls = exports.transformSqlStatement = exports.transformSql = exports.transformSchemaName = exports.transformRelation = exports.transformPlpgsqlTypeString = exports.transformPlpgsqlTypeAst = exports.transformNameList = exports.transformJsonStringValues = exports.transformComments = exports.shouldTransformSchema = exports.extractPgpmHeader = exports.escapeRegexp = exports.createSqlVisitor = exports.createPlpgsqlVisitor = exports.trimDefElemBody = exports.normalizeTree = exports.cleanTree = exports.CLEAN_TREE_VOLATILE_KEYS = exports.validateRoundTrip = exports.normalizeParseTree = exports.firstDifference = exports.captureTransformAsts = exports.captureAstsFromSql = exports.SchemaRouter = exports.qualifyUnqualified = exports.mergeInventories = exports.createQualifyVisitor = exports.collectCreatedObjects = exports.classifyStatements = void 0;
|
|
8
4
|
var facts_1 = require("./facts");
|
|
9
5
|
Object.defineProperty(exports, "classifyStatements", { enumerable: true, get: function () { return facts_1.classifyStatements; } });
|
|
10
|
-
var fixture_closure_1 = require("./fixture-closure");
|
|
11
|
-
Object.defineProperty(exports, "resolveFixtureClosure", { enumerable: true, get: function () { return fixture_closure_1.resolveFixtureClosure; } });
|
|
12
6
|
var qualify_1 = require("./qualify");
|
|
13
7
|
Object.defineProperty(exports, "collectCreatedObjects", { enumerable: true, get: function () { return qualify_1.collectCreatedObjects; } });
|
|
14
8
|
Object.defineProperty(exports, "createQualifyVisitor", { enumerable: true, get: function () { return qualify_1.createQualifyVisitor; } });
|
|
15
9
|
Object.defineProperty(exports, "mergeInventories", { enumerable: true, get: function () { return qualify_1.mergeInventories; } });
|
|
16
10
|
Object.defineProperty(exports, "qualifyUnqualified", { enumerable: true, get: function () { return qualify_1.qualifyUnqualified; } });
|
|
11
|
+
var router_1 = require("./router");
|
|
12
|
+
Object.defineProperty(exports, "SchemaRouter", { enumerable: true, get: function () { return router_1.SchemaRouter; } });
|
|
17
13
|
var round_trip_1 = require("./round-trip");
|
|
18
14
|
Object.defineProperty(exports, "captureAstsFromSql", { enumerable: true, get: function () { return round_trip_1.captureAstsFromSql; } });
|
|
19
15
|
Object.defineProperty(exports, "captureTransformAsts", { enumerable: true, get: function () { return round_trip_1.captureTransformAsts; } });
|