@pgsql/transform 18.6.0 → 18.8.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/esm/facts.js +4 -1
- package/esm/router.js +69 -5
- package/esm/transform.js +106 -194
- package/facts.d.ts +15 -0
- package/facts.js +4 -1
- package/index.d.ts +1 -1
- package/package.json +4 -4
- package/router.d.ts +63 -6
- package/router.js +69 -5
- package/transform.d.ts +4 -22
- package/transform.js +106 -194
package/esm/facts.js
CHANGED
|
@@ -154,7 +154,8 @@ function classifyOne(nodeTag, node) {
|
|
|
154
154
|
bodyReferences: [],
|
|
155
155
|
securityRelevant: SECURITY_TAGS.has(nodeTag),
|
|
156
156
|
securityDefiner: false,
|
|
157
|
-
dynamicSql: false
|
|
157
|
+
dynamicSql: false,
|
|
158
|
+
span: { start: 0, len: 0 }
|
|
158
159
|
};
|
|
159
160
|
switch (nodeTag) {
|
|
160
161
|
case 'CreateSchemaStmt':
|
|
@@ -348,6 +349,8 @@ export function classifyStatements(sql) {
|
|
|
348
349
|
const nodeTag = stmtNode ? Object.keys(stmtNode)[0] : 'other';
|
|
349
350
|
const node = stmtNode?.[nodeTag] ?? {};
|
|
350
351
|
const facts = classifyOne(nodeTag, node);
|
|
352
|
+
const start = stmt?.stmt_location ?? 0;
|
|
353
|
+
facts.span = { start, len: stmt?.stmt_len ?? Math.max(0, sql.length - start) };
|
|
351
354
|
if (stmtNode) {
|
|
352
355
|
walkSql(stmtNode, createFactsVisitor(facts));
|
|
353
356
|
}
|
package/esm/router.js
CHANGED
|
@@ -20,6 +20,19 @@
|
|
|
20
20
|
* `types` — matching `pg_class` / `pg_proc` / `pg_type`), mirroring the routing
|
|
21
21
|
* model already used by {@link qualifyUnqualified}. Resolution is
|
|
22
22
|
* object-route-first, then the schema-level default, then "leave unchanged".
|
|
23
|
+
*
|
|
24
|
+
* An object route may also *rebind* — change the object's **name**, not just
|
|
25
|
+
* the schema it lives in. Routing preserves identity (the same function, a new
|
|
26
|
+
* address); rebinding repoints a reference at a *different* object, which is
|
|
27
|
+
* what lets one implementation of a contract be substituted for another:
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* { auth: { functions: { uid: { schema: null, name: 'current_user_id' } } } }
|
|
31
|
+
* // auth.uid() -> current_user_id()
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* A `null` target schema de-qualifies the reference (relying on `search_path`),
|
|
35
|
+
* matching the convention used by the extension router.
|
|
23
36
|
*/
|
|
24
37
|
const NS_BUCKET = {
|
|
25
38
|
relation: 'relations',
|
|
@@ -71,6 +84,36 @@ export class SchemaRouter {
|
|
|
71
84
|
}
|
|
72
85
|
return false;
|
|
73
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* True when any object route changes a name or de-qualifies (rather than
|
|
89
|
+
* only moving between schemas). Such rewrites cannot be expressed by the
|
|
90
|
+
* string-level passes at all, so callers use this to require the AST path.
|
|
91
|
+
*/
|
|
92
|
+
hasNameRebinds() {
|
|
93
|
+
return this.nameRebinds().length > 0;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Every object route that rebinds a name or de-qualifies, keyed by source
|
|
97
|
+
* schema and namespace. Callers use this to report or verify substitutions.
|
|
98
|
+
*/
|
|
99
|
+
nameRebinds() {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const [schema, route] of this.routes) {
|
|
102
|
+
for (const ns of ['relation', 'function', 'type']) {
|
|
103
|
+
const bucket = route[NS_BUCKET[ns]];
|
|
104
|
+
if (!bucket)
|
|
105
|
+
continue;
|
|
106
|
+
for (const [from, target] of Object.entries(bucket)) {
|
|
107
|
+
if (typeof target === 'string')
|
|
108
|
+
continue;
|
|
109
|
+
if (target.name === undefined && target.schema !== null)
|
|
110
|
+
continue;
|
|
111
|
+
out.push({ schema, ns, from, to: target });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
74
117
|
/** Every source schema this router may touch. */
|
|
75
118
|
sourceSchemas() {
|
|
76
119
|
return [...this.routes.keys()];
|
|
@@ -82,18 +125,39 @@ export class SchemaRouter {
|
|
|
82
125
|
* to any object without a specific route.
|
|
83
126
|
*/
|
|
84
127
|
resolve(sourceSchema, name, ns = 'unknown') {
|
|
128
|
+
// A `null` target de-qualifies the reference; the schema-only API cannot
|
|
129
|
+
// express that, so it reads as "unchanged" here.
|
|
130
|
+
return this.resolveObject(sourceSchema, name, ns)?.schema ?? undefined;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Resolve the full target for `(sourceSchema, name)` in namespace `ns` — both
|
|
134
|
+
* the schema the reference should live in and, when the route rebinds, the
|
|
135
|
+
* name it should be reached by. Returns `undefined` to leave it unchanged.
|
|
136
|
+
*
|
|
137
|
+
* In the result, `schema` is `null` when the reference should become
|
|
138
|
+
* unqualified and `undefined` when only the name changes; `name` is
|
|
139
|
+
* `undefined` when only the schema changes.
|
|
140
|
+
*/
|
|
141
|
+
resolveObject(sourceSchema, name, ns = 'unknown') {
|
|
85
142
|
if (!sourceSchema)
|
|
86
143
|
return undefined;
|
|
87
144
|
const route = this.routes.get(sourceSchema);
|
|
88
145
|
if (!route)
|
|
89
146
|
return undefined;
|
|
90
147
|
if (name && (ns === 'relation' || ns === 'function' || ns === 'type')) {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
const target = route[NS_BUCKET[ns]]?.[name];
|
|
149
|
+
if (target !== undefined) {
|
|
150
|
+
if (typeof target === 'string')
|
|
151
|
+
return { schema: target };
|
|
152
|
+
// An object route naming no schema inherits the schema-level default,
|
|
153
|
+
// so a pure rebind leaves placement alone.
|
|
154
|
+
return {
|
|
155
|
+
schema: target.schema !== undefined ? target.schema : route.schema,
|
|
156
|
+
name: target.name
|
|
157
|
+
};
|
|
158
|
+
}
|
|
95
159
|
}
|
|
96
|
-
return route.schema;
|
|
160
|
+
return route.schema !== undefined ? { schema: route.schema } : undefined;
|
|
97
161
|
}
|
|
98
162
|
/**
|
|
99
163
|
* Source schemas whose *every* object is guaranteed to move — i.e. those
|
package/esm/transform.js
CHANGED
|
@@ -47,6 +47,36 @@ function createResult() {
|
|
|
47
47
|
errors: [],
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Routing claims: each routable site (a RangeVar, a qualified name list, a
|
|
52
|
+
* bare schema-name field) is resolved through the router at most once per
|
|
53
|
+
* transform result.
|
|
54
|
+
*
|
|
55
|
+
* The walker visits parents before children, so a statement-level handler
|
|
56
|
+
* with namespace context (e.g. `AlterFunctionStmt` routing its
|
|
57
|
+
* `ObjectWithArgs` as a `function`) claims the site before a generic child
|
|
58
|
+
* visitor (`ObjectWithArgs`, namespace `unknown`) reaches it — first (most
|
|
59
|
+
* contextual) visitor wins. Without claims a site is routed twice, which
|
|
60
|
+
* corrupts cyclic mappings (a→b, b→a swaps back) and applies the less precise
|
|
61
|
+
* namespace second.
|
|
62
|
+
*/
|
|
63
|
+
const routingClaims = new WeakMap();
|
|
64
|
+
function claimSite(result, site, field = '*') {
|
|
65
|
+
let byNode = routingClaims.get(result);
|
|
66
|
+
if (!byNode) {
|
|
67
|
+
byNode = new WeakMap();
|
|
68
|
+
routingClaims.set(result, byNode);
|
|
69
|
+
}
|
|
70
|
+
let fields = byNode.get(site);
|
|
71
|
+
if (!fields) {
|
|
72
|
+
fields = new Set();
|
|
73
|
+
byNode.set(site, fields);
|
|
74
|
+
}
|
|
75
|
+
if (fields.has(field))
|
|
76
|
+
return false;
|
|
77
|
+
fields.add(field);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
50
80
|
/**
|
|
51
81
|
* Transform a schema name if it exists in the mapping
|
|
52
82
|
*/
|
|
@@ -75,17 +105,31 @@ export function shouldTransformSchema(schemaName, schemaMapping) {
|
|
|
75
105
|
export function transformNameList(names, schemaMapping, result, ns = 'unknown') {
|
|
76
106
|
if (!names || names.length < 2)
|
|
77
107
|
return;
|
|
108
|
+
if (!claimSite(result, names))
|
|
109
|
+
return;
|
|
78
110
|
const router = asRouter(schemaMapping);
|
|
79
111
|
// For schema-qualified names, the first element is the schema.
|
|
80
112
|
const first = names[0];
|
|
81
113
|
if (first?.String?.sval) {
|
|
82
114
|
const schemaName = first.String.sval;
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
115
|
+
const last = names[names.length - 1];
|
|
116
|
+
const objName = last?.String?.sval;
|
|
117
|
+
const target = router.resolveObject(schemaName, objName, ns);
|
|
118
|
+
if (!target)
|
|
119
|
+
return;
|
|
120
|
+
if (target.name !== undefined && last?.String?.sval) {
|
|
86
121
|
result.schemasFound.add(schemaName);
|
|
87
|
-
|
|
88
|
-
|
|
122
|
+
last.String.sval = target.name;
|
|
123
|
+
}
|
|
124
|
+
if (target.schema === null) {
|
|
125
|
+
// De-qualify: drop the schema element and rely on search_path.
|
|
126
|
+
result.schemasFound.add(schemaName);
|
|
127
|
+
names.splice(0, 1);
|
|
128
|
+
}
|
|
129
|
+
else if (target.schema && target.schema !== schemaName) {
|
|
130
|
+
result.schemasFound.add(schemaName);
|
|
131
|
+
first.String.sval = target.schema;
|
|
132
|
+
result.schemasTransformed.set(schemaName, target.schema);
|
|
89
133
|
}
|
|
90
134
|
}
|
|
91
135
|
}
|
|
@@ -98,6 +142,8 @@ export function transformSchemaNameField(container, field, schemaMapping, result
|
|
|
98
142
|
const schemaName = container?.[field];
|
|
99
143
|
if (typeof schemaName !== 'string')
|
|
100
144
|
return;
|
|
145
|
+
if (!claimSite(result, container, field))
|
|
146
|
+
return;
|
|
101
147
|
// A bare schema name is an operation on the schema itself: only the
|
|
102
148
|
// schema-level default applies.
|
|
103
149
|
const newName = asRouter(schemaMapping).resolve(schemaName, undefined, 'schema');
|
|
@@ -109,19 +155,35 @@ export function transformSchemaNameField(container, field, schemaMapping, result
|
|
|
109
155
|
}
|
|
110
156
|
/**
|
|
111
157
|
* Transform a RangeVar-like relation object (has schemaname/relname fields).
|
|
112
|
-
*
|
|
158
|
+
* The walker auto-recurses into embedded relations (concrete `RangeVar`
|
|
159
|
+
* fields are tag-synthesized), so the generic `RangeVar` visitor covers
|
|
160
|
+
* every occurrence; statement handlers call this directly only when they
|
|
161
|
+
* carry extra context, and the claim guard keeps each relation routed once.
|
|
113
162
|
*/
|
|
114
163
|
export function transformRelation(relation, schemaMapping, result) {
|
|
115
164
|
if (!relation?.schemaname)
|
|
116
165
|
return;
|
|
166
|
+
if (!claimSite(result, relation))
|
|
167
|
+
return;
|
|
117
168
|
const oldName = relation.schemaname;
|
|
118
169
|
// A RangeVar names a relation (table/view/sequence/matview); route by the
|
|
119
170
|
// relation name so object-level routes can send it to its own schema.
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
171
|
+
const target = asRouter(schemaMapping).resolveObject(oldName, relation.relname, 'relation');
|
|
172
|
+
if (!target)
|
|
173
|
+
return;
|
|
174
|
+
if (target.name !== undefined && relation.relname) {
|
|
122
175
|
result.schemasFound.add(oldName);
|
|
123
|
-
relation.
|
|
124
|
-
|
|
176
|
+
relation.relname = target.name;
|
|
177
|
+
}
|
|
178
|
+
if (target.schema === null) {
|
|
179
|
+
// De-qualify: drop the schema qualifier and rely on search_path.
|
|
180
|
+
result.schemasFound.add(oldName);
|
|
181
|
+
delete relation.schemaname;
|
|
182
|
+
}
|
|
183
|
+
else if (target.schema && target.schema !== oldName) {
|
|
184
|
+
result.schemasFound.add(oldName);
|
|
185
|
+
relation.schemaname = target.schema;
|
|
186
|
+
result.schemasTransformed.set(oldName, target.schema);
|
|
125
187
|
}
|
|
126
188
|
}
|
|
127
189
|
/**
|
|
@@ -162,6 +224,8 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
162
224
|
const schemaName = container?.[key];
|
|
163
225
|
if (typeof schemaName !== 'string')
|
|
164
226
|
return;
|
|
227
|
+
if (!claimSite(result, container, key))
|
|
228
|
+
return;
|
|
165
229
|
const newName = router.resolve(schemaName, undefined, 'schema');
|
|
166
230
|
if (newName && newName !== schemaName) {
|
|
167
231
|
result.schemasFound.add(schemaName);
|
|
@@ -219,15 +283,6 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
219
283
|
const node = path.node;
|
|
220
284
|
transformNameList(node.funcname, router, result, 'function');
|
|
221
285
|
},
|
|
222
|
-
// Transform CallStmt (CALL schema.procedure(...)).
|
|
223
|
-
// Note: The walker does NOT auto-recurse into CallStmt.funccall,
|
|
224
|
-
// so the FuncCall visitor above never sees it.
|
|
225
|
-
CallStmt: (path) => {
|
|
226
|
-
const node = path.node;
|
|
227
|
-
if (node.funccall?.funcname) {
|
|
228
|
-
transformNameList(node.funccall.funcname, router, result, 'function');
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
286
|
// Transform TypeName nodes (type references with schema-qualified names)
|
|
232
287
|
TypeName: (path) => {
|
|
233
288
|
const node = path.node;
|
|
@@ -285,8 +340,9 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
285
340
|
}
|
|
286
341
|
},
|
|
287
342
|
// Transform DropStmt (DROP SCHEMA name; DROP TABLE/INDEX/POLICY/TRIGGER/
|
|
288
|
-
// TYPE/FUNCTION schema.obj)
|
|
289
|
-
//
|
|
343
|
+
// TYPE/FUNCTION schema.obj) with the namespace derived from removeType,
|
|
344
|
+
// so object routes apply; claims stop the generic List/ObjectWithArgs
|
|
345
|
+
// visitors from re-routing the same names with namespace `unknown`.
|
|
290
346
|
DropStmt: (path) => {
|
|
291
347
|
const node = path.node;
|
|
292
348
|
if (node.removeType !== 'OBJECT_SCHEMA' && Array.isArray(node.objects)) {
|
|
@@ -320,79 +376,10 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
320
376
|
}
|
|
321
377
|
}
|
|
322
378
|
},
|
|
323
|
-
// Transform
|
|
324
|
-
//
|
|
325
|
-
InsertStmt: (path) => {
|
|
326
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
327
|
-
},
|
|
328
|
-
// Transform CreateStmt (CREATE TABLE schema.table)
|
|
329
|
-
// Note: The walker does NOT auto-recurse into CreateStmt.relation.
|
|
330
|
-
CreateStmt: (path) => {
|
|
331
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
332
|
-
},
|
|
333
|
-
// Transform UpdateStmt (UPDATE schema.table SET ...)
|
|
334
|
-
// Note: The walker does NOT auto-recurse into UpdateStmt.relation.
|
|
335
|
-
UpdateStmt: (path) => {
|
|
336
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
337
|
-
},
|
|
338
|
-
// Transform DeleteStmt (DELETE FROM schema.table)
|
|
339
|
-
// Note: The walker does NOT auto-recurse into DeleteStmt.relation.
|
|
340
|
-
DeleteStmt: (path) => {
|
|
341
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
342
|
-
},
|
|
343
|
-
// Transform MergeStmt (MERGE INTO schema.table USING ...)
|
|
344
|
-
// Note: The walker does NOT auto-recurse into MergeStmt.relation.
|
|
345
|
-
MergeStmt: (path) => {
|
|
346
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
347
|
-
},
|
|
348
|
-
// Transform IndexStmt (CREATE INDEX ... ON schema.table)
|
|
349
|
-
// Note: The walker does NOT auto-recurse into IndexStmt.relation.
|
|
350
|
-
IndexStmt: (path) => {
|
|
351
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
352
|
-
},
|
|
353
|
-
// Transform Constraint (REFERENCES schema.table for foreign keys)
|
|
354
|
-
// Note: The walker does NOT auto-recurse into Constraint.pktable.
|
|
355
|
-
Constraint: (path) => {
|
|
356
|
-
const node = path.node;
|
|
357
|
-
if (node.contype === 'CONSTR_FOREIGN' && node.pktable) {
|
|
358
|
-
transformRelation(node.pktable, schemaMapping, result);
|
|
359
|
-
}
|
|
360
|
-
},
|
|
361
|
-
// Transform CompositeTypeStmt (CREATE TYPE schema.name AS (...))
|
|
362
|
-
// Note: The walker does NOT auto-recurse into CompositeTypeStmt.typevar.
|
|
363
|
-
CompositeTypeStmt: (path) => {
|
|
364
|
-
transformRelation(path.node.typevar, schemaMapping, result);
|
|
365
|
-
},
|
|
366
|
-
// Transform ColumnDef (column definitions with schema-qualified types)
|
|
367
|
-
// Note: The walker does NOT auto-recurse into ColumnDef.typeName.
|
|
368
|
-
ColumnDef: (path) => {
|
|
369
|
-
const node = path.node;
|
|
370
|
-
if (node.typeName?.names) {
|
|
371
|
-
transformNameList(node.typeName.names, schemaMapping, result, 'type');
|
|
372
|
-
}
|
|
373
|
-
},
|
|
374
|
-
// Transform ViewStmt (CREATE VIEW schema.viewname AS ...)
|
|
375
|
-
// Note: The walker does NOT auto-recurse into ViewStmt.view.
|
|
376
|
-
ViewStmt: (path) => {
|
|
377
|
-
transformRelation(path.node.view, schemaMapping, result);
|
|
378
|
-
},
|
|
379
|
-
// Transform AlterTableStmt (ALTER TABLE schema.table ...)
|
|
380
|
-
// Note: The walker does NOT auto-recurse into AlterTableStmt.relation.
|
|
381
|
-
AlterTableStmt: (path) => {
|
|
382
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
383
|
-
},
|
|
384
|
-
// Transform CreateSeqStmt (CREATE SEQUENCE schema.seqname)
|
|
385
|
-
// Note: The walker does NOT auto-recurse into CreateSeqStmt.sequence.
|
|
386
|
-
CreateSeqStmt: (path) => {
|
|
387
|
-
transformRelation(path.node.sequence, schemaMapping, result);
|
|
388
|
-
},
|
|
389
|
-
// Transform AlterSeqStmt (ALTER SEQUENCE schema.seqname [OWNED BY schema.table.col])
|
|
390
|
-
// Note: The walker does NOT auto-recurse into AlterSeqStmt.sequence.
|
|
391
|
-
// Also handles OWNED BY clause where the schema name is in DefElem.arg.List.items.
|
|
379
|
+
// Transform AlterSeqStmt OWNED BY (schema name in DefElem.arg.List.items;
|
|
380
|
+
// a bare name list no generic visitor covers)
|
|
392
381
|
AlterSeqStmt: (path) => {
|
|
393
382
|
const node = path.node;
|
|
394
|
-
transformRelation(node.sequence, schemaMapping, result);
|
|
395
|
-
// Handle OWNED BY option: schema name is in the name list
|
|
396
383
|
if (node.options) {
|
|
397
384
|
for (const opt of node.options) {
|
|
398
385
|
if (opt?.DefElem?.defname === 'owned_by' && opt.DefElem?.arg?.List?.items) {
|
|
@@ -401,67 +388,12 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
401
388
|
}
|
|
402
389
|
}
|
|
403
390
|
},
|
|
404
|
-
// Transform
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
transformRelation(path.node.table, schemaMapping, result);
|
|
408
|
-
},
|
|
409
|
-
// Transform RuleStmt (CREATE RULE ... ON schema.table)
|
|
410
|
-
// Note: The walker does NOT auto-recurse into RuleStmt.relation.
|
|
411
|
-
RuleStmt: (path) => {
|
|
412
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
413
|
-
},
|
|
414
|
-
// Transform CopyStmt (COPY schema.table ...)
|
|
415
|
-
// Note: The walker does NOT auto-recurse into CopyStmt.relation.
|
|
416
|
-
CopyStmt: (path) => {
|
|
417
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
418
|
-
},
|
|
419
|
-
// Transform ClusterStmt (CLUSTER schema.table USING index)
|
|
420
|
-
// Note: The walker does NOT auto-recurse into ClusterStmt.relation.
|
|
421
|
-
ClusterStmt: (path) => {
|
|
422
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
423
|
-
},
|
|
424
|
-
// Transform VacuumRelation (VACUUM/ANALYZE schema.table)
|
|
425
|
-
// Note: The walker visits VacuumRelation but does NOT recurse into its relation.
|
|
426
|
-
VacuumRelation: (path) => {
|
|
427
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
428
|
-
},
|
|
429
|
-
// Transform RefreshMatViewStmt (REFRESH MATERIALIZED VIEW schema.view)
|
|
430
|
-
// Note: The walker does NOT auto-recurse into RefreshMatViewStmt.relation.
|
|
431
|
-
RefreshMatViewStmt: (path) => {
|
|
432
|
-
transformRelation(path.node.relation, schemaMapping, result);
|
|
433
|
-
},
|
|
434
|
-
// Transform CreateTableAsStmt (CREATE MATERIALIZED VIEW schema.viewname AS ...)
|
|
435
|
-
// Note: The walker does NOT auto-recurse into CreateTableAsStmt.into.rel.
|
|
436
|
-
CreateTableAsStmt: (path) => {
|
|
437
|
-
transformRelation(path.node.into?.rel, schemaMapping, result);
|
|
438
|
-
},
|
|
439
|
-
// Transform TypeCast nodes (type casts like NULL::schema.type)
|
|
440
|
-
// Note: The walker visits TypeCast but does NOT recurse into the TypeName node.
|
|
441
|
-
TypeCast: (path) => {
|
|
442
|
-
const node = path.node;
|
|
443
|
-
if (node.typeName?.names) {
|
|
444
|
-
transformNameList(node.typeName.names, schemaMapping, result, 'type');
|
|
445
|
-
}
|
|
446
|
-
},
|
|
447
|
-
// Transform CreateFunctionStmt (CREATE FUNCTION schema.funcname)
|
|
448
|
-
// Also handles RETURNS [SETOF] schema.type via returnType.names
|
|
391
|
+
// Transform CreateFunctionStmt (CREATE FUNCTION schema.funcname).
|
|
392
|
+
// funcname is a bare name list (no generic visitor); return and parameter
|
|
393
|
+
// types are TypeName nodes the walker reaches on its own.
|
|
449
394
|
CreateFunctionStmt: (path) => {
|
|
450
395
|
const node = path.node;
|
|
451
396
|
transformNameList(node.funcname, schemaMapping, result, 'function');
|
|
452
|
-
// Transform the return type (e.g., RETURNS SETOF schema.tablename)
|
|
453
|
-
if (node.returnType?.names) {
|
|
454
|
-
transformNameList(node.returnType.names, schemaMapping, result, 'type');
|
|
455
|
-
}
|
|
456
|
-
// Transform parameter types (the walker does NOT auto-recurse into
|
|
457
|
-
// FunctionParameter.argType TypeName nodes)
|
|
458
|
-
if (Array.isArray(node.parameters)) {
|
|
459
|
-
for (const param of node.parameters) {
|
|
460
|
-
if (param?.FunctionParameter?.argType?.names) {
|
|
461
|
-
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result, 'type');
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
397
|
// Transform schema-qualified references inside the function body
|
|
466
398
|
// (AS $$...$$ is a String under the 'as' DefElem, which the walker
|
|
467
399
|
// does not parse as SQL). LANGUAGE plpgsql bodies are additionally
|
|
@@ -488,12 +420,11 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
488
420
|
}
|
|
489
421
|
},
|
|
490
422
|
// Transform CreateTrigStmt (CREATE TRIGGER ... EXECUTE PROCEDURE schema.func)
|
|
491
|
-
//
|
|
492
|
-
//
|
|
423
|
+
// funcname is a bare name list; the ON relation is a RangeVar the walker
|
|
424
|
+
// reaches on its own.
|
|
493
425
|
CreateTrigStmt: (path) => {
|
|
494
426
|
const node = path.node;
|
|
495
427
|
transformNameList(node.funcname, schemaMapping, result, 'function');
|
|
496
|
-
transformRelation(node.relation, schemaMapping, result);
|
|
497
428
|
},
|
|
498
429
|
// Transform CommentStmt (COMMENT ON TABLE/COLUMN/FUNCTION schema.obj,
|
|
499
430
|
// COMMENT ON SCHEMA schema)
|
|
@@ -504,13 +435,14 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
504
435
|
// For functions, object is { ObjectWithArgs: { objname: [...] } }
|
|
505
436
|
// For schemas, object is a bare { String: { sval } }
|
|
506
437
|
if (node.object) {
|
|
438
|
+
const ns = namespaceOfObjectType(node.objtype);
|
|
507
439
|
// Handle List-wrapped objects (COMMENT ON TABLE schema.tbl, COMMENT ON COLUMN schema.tbl.col)
|
|
508
440
|
if (node.object?.List?.items) {
|
|
509
|
-
transformNameList(node.object.List.items, schemaMapping, result);
|
|
441
|
+
transformNameList(node.object.List.items, schemaMapping, result, ns);
|
|
510
442
|
}
|
|
511
443
|
// Handle ObjectWithArgs-style objects (COMMENT ON FUNCTION schema.func(args))
|
|
512
444
|
else if (node.object?.ObjectWithArgs?.objname) {
|
|
513
|
-
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
445
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result, ns);
|
|
514
446
|
}
|
|
515
447
|
// Handle bare schema names (COMMENT ON SCHEMA schema)
|
|
516
448
|
else if (node.object?.String && node.objtype === 'OBJECT_SCHEMA') {
|
|
@@ -567,41 +499,41 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
567
499
|
const node = path.node;
|
|
568
500
|
transformNameList(node.objname, schemaMapping, result);
|
|
569
501
|
},
|
|
570
|
-
// Transform AlterObjectSchemaStmt (ALTER ... SET SCHEMA newschema)
|
|
571
|
-
//
|
|
572
|
-
//
|
|
502
|
+
// Transform AlterObjectSchemaStmt (ALTER ... SET SCHEMA newschema):
|
|
503
|
+
// object name lists and the destination schema. The relation form is a
|
|
504
|
+
// RangeVar the walker reaches on its own.
|
|
573
505
|
AlterObjectSchemaStmt: (path) => {
|
|
574
506
|
const node = path.node;
|
|
575
|
-
|
|
507
|
+
const ns = namespaceOfObjectType(node.objectType);
|
|
576
508
|
// Non-relation objects (ALTER TYPE/FUNCTION ... SET SCHEMA)
|
|
577
509
|
if (node.object?.List?.items) {
|
|
578
|
-
transformNameList(node.object.List.items, schemaMapping, result);
|
|
510
|
+
transformNameList(node.object.List.items, schemaMapping, result, ns);
|
|
579
511
|
}
|
|
580
512
|
else if (node.object?.ObjectWithArgs?.objname) {
|
|
581
|
-
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
513
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result, ns);
|
|
582
514
|
}
|
|
583
515
|
else if (node.object?.TypeName?.names) {
|
|
584
|
-
transformNameList(node.object.TypeName.names, schemaMapping, result);
|
|
516
|
+
transformNameList(node.object.TypeName.names, schemaMapping, result, ns);
|
|
585
517
|
}
|
|
586
518
|
// Transform the newschema (destination schema)
|
|
587
519
|
transformSchemaNameField(node, 'newschema', schemaMapping, result);
|
|
588
520
|
},
|
|
589
521
|
// Transform RenameStmt (ALTER SCHEMA old RENAME TO new;
|
|
590
|
-
// ALTER TABLE schema.tbl RENAME ...). The
|
|
591
|
-
//
|
|
522
|
+
// ALTER TABLE schema.tbl RENAME ...). The relation form is a RangeVar
|
|
523
|
+
// the walker reaches on its own.
|
|
592
524
|
RenameStmt: (path) => {
|
|
593
525
|
const node = path.node;
|
|
594
|
-
transformRelation(node.relation, schemaMapping, result);
|
|
595
526
|
if (node.renameType === 'OBJECT_SCHEMA') {
|
|
596
527
|
// subname is the old schema name; newname is the target
|
|
597
528
|
transformSchemaNameField(node, 'subname', schemaMapping, result);
|
|
598
529
|
transformSchemaNameField(node, 'newname', schemaMapping, result);
|
|
599
530
|
}
|
|
531
|
+
const ns = namespaceOfObjectType(node.renameType);
|
|
600
532
|
if (node.object?.List?.items) {
|
|
601
|
-
transformNameList(node.object.List.items, schemaMapping, result);
|
|
533
|
+
transformNameList(node.object.List.items, schemaMapping, result, ns);
|
|
602
534
|
}
|
|
603
535
|
else if (node.object?.ObjectWithArgs?.objname) {
|
|
604
|
-
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
536
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result, ns);
|
|
605
537
|
}
|
|
606
538
|
},
|
|
607
539
|
// Transform AlterFunctionStmt (ALTER FUNCTION schema.fn(...) SET ...)
|
|
@@ -614,46 +546,26 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
614
546
|
// Transform AlterOwnerStmt (ALTER ... OWNER TO role)
|
|
615
547
|
AlterOwnerStmt: (path) => {
|
|
616
548
|
const node = path.node;
|
|
617
|
-
|
|
549
|
+
const ns = namespaceOfObjectType(node.objectType);
|
|
618
550
|
if (node.object?.List?.items) {
|
|
619
|
-
transformNameList(node.object.List.items, schemaMapping, result);
|
|
551
|
+
transformNameList(node.object.List.items, schemaMapping, result, ns);
|
|
620
552
|
}
|
|
621
553
|
else if (node.object?.ObjectWithArgs?.objname) {
|
|
622
|
-
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
|
|
554
|
+
transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result, ns);
|
|
623
555
|
}
|
|
624
556
|
else if (node.object?.String && node.objectType === 'OBJECT_SCHEMA') {
|
|
625
557
|
transformSchemaNameField(node.object.String, 'sval', schemaMapping, result);
|
|
626
558
|
}
|
|
627
559
|
},
|
|
628
|
-
// Transform CreateCastStmt (CREATE CAST (src AS tgt) WITH FUNCTION fn)
|
|
560
|
+
// Transform CreateCastStmt (CREATE CAST (src AS tgt) WITH FUNCTION fn).
|
|
561
|
+
// The function name gets its `function` namespace here; the cast's
|
|
562
|
+
// source/target/argument types are TypeName nodes the walker reaches on
|
|
563
|
+
// its own.
|
|
629
564
|
CreateCastStmt: (path) => {
|
|
630
565
|
const node = path.node;
|
|
631
|
-
if (node.sourcetype?.names) {
|
|
632
|
-
transformNameList(node.sourcetype.names, schemaMapping, result, 'type');
|
|
633
|
-
}
|
|
634
|
-
if (node.targettype?.names) {
|
|
635
|
-
transformNameList(node.targettype.names, schemaMapping, result, 'type');
|
|
636
|
-
}
|
|
637
566
|
if (node.func?.objname) {
|
|
638
567
|
transformNameList(node.func.objname, schemaMapping, result, 'function');
|
|
639
568
|
}
|
|
640
|
-
if (Array.isArray(node.func?.objargs)) {
|
|
641
|
-
for (const arg of node.func.objargs) {
|
|
642
|
-
if (arg?.TypeName?.names) {
|
|
643
|
-
transformNameList(arg.TypeName.names, schemaMapping, result, 'type');
|
|
644
|
-
}
|
|
645
|
-
else if (arg?.names) {
|
|
646
|
-
transformNameList(arg.names, schemaMapping, result, 'type');
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
if (Array.isArray(node.func?.objfuncargs)) {
|
|
651
|
-
for (const param of node.func.objfuncargs) {
|
|
652
|
-
if (param?.FunctionParameter?.argType?.names) {
|
|
653
|
-
transformNameList(param.FunctionParameter.argType.names, schemaMapping, result, 'type');
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
569
|
},
|
|
658
570
|
// Transform CreateEventTrigStmt (CREATE EVENT TRIGGER ... EXECUTE FUNCTION schema.fn())
|
|
659
571
|
CreateEventTrigStmt: (path) => {
|
package/facts.d.ts
CHANGED
|
@@ -94,6 +94,21 @@ export interface StatementFacts {
|
|
|
94
94
|
* are incomplete and slicing should treat it conservatively.
|
|
95
95
|
*/
|
|
96
96
|
dynamicSql: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The statement's source span in the classified script, as reported by the
|
|
99
|
+
* parser: `start` is the byte offset of the statement's first token, `len`
|
|
100
|
+
* runs to the end of the statement (the parser excludes the trailing `;`;
|
|
101
|
+
* for the final statement the span extends to the end of the script).
|
|
102
|
+
* `sql.slice(span.start, span.start + span.len)` is the statement's
|
|
103
|
+
* verbatim source, so consumers can carry original text alongside the
|
|
104
|
+
* facts without a second parse.
|
|
105
|
+
*/
|
|
106
|
+
span: StatementSpan;
|
|
107
|
+
}
|
|
108
|
+
/** A statement's location in the source script (byte offsets). */
|
|
109
|
+
export interface StatementSpan {
|
|
110
|
+
start: number;
|
|
111
|
+
len: number;
|
|
97
112
|
}
|
|
98
113
|
/**
|
|
99
114
|
* Classify each top-level statement in a SQL script into {@link StatementFacts}.
|
package/facts.js
CHANGED
|
@@ -157,7 +157,8 @@ function classifyOne(nodeTag, node) {
|
|
|
157
157
|
bodyReferences: [],
|
|
158
158
|
securityRelevant: SECURITY_TAGS.has(nodeTag),
|
|
159
159
|
securityDefiner: false,
|
|
160
|
-
dynamicSql: false
|
|
160
|
+
dynamicSql: false,
|
|
161
|
+
span: { start: 0, len: 0 }
|
|
161
162
|
};
|
|
162
163
|
switch (nodeTag) {
|
|
163
164
|
case 'CreateSchemaStmt':
|
|
@@ -351,6 +352,8 @@ function classifyStatements(sql) {
|
|
|
351
352
|
const nodeTag = stmtNode ? Object.keys(stmtNode)[0] : 'other';
|
|
352
353
|
const node = stmtNode?.[nodeTag] ?? {};
|
|
353
354
|
const facts = classifyOne(nodeTag, node);
|
|
355
|
+
const start = stmt?.stmt_location ?? 0;
|
|
356
|
+
facts.span = { start, len: stmt?.stmt_len ?? Math.max(0, sql.length - start) };
|
|
354
357
|
if (stmtNode) {
|
|
355
358
|
(0, traverse_1.walk)(stmtNode, createFactsVisitor(facts));
|
|
356
359
|
}
|
package/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type { RoleTransformResult } from './role-transform';
|
|
|
10
10
|
export { createRoleResult, createRoleVisitor, transformRoles, } from './role-transform';
|
|
11
11
|
export type { ObjectInventory, QualifyResult, QualifyRoutes, QualifyTargetSelector, QualifyUnqualifiedOptions, } from './qualify';
|
|
12
12
|
export { collectCreatedObjects, createQualifyVisitor, mergeInventories, qualifyUnqualified, } from './qualify';
|
|
13
|
-
export type { ObjectNamespace, RouteNamespace, RouteSpec, SchemaRoute, } from './router';
|
|
13
|
+
export type { ObjectNamespace, ObjectRoute, ObjectRouteTarget, RouteNamespace, RouteSpec, SchemaRoute, } from './router';
|
|
14
14
|
export { SchemaRouter } from './router';
|
|
15
15
|
export type { CapturedAsts } from './round-trip';
|
|
16
16
|
export { captureAstsFromSql, captureTransformAsts, firstDifference, normalizeParseTree, validateRoundTrip, } from './round-trip';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgsql/transform",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.8.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "AST-based SQL transformation, qualification, classification and closure analysis for PostgreSQL",
|
|
6
6
|
"main": "index.js",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@pgsql/quotes": "18.1.0",
|
|
36
|
-
"@pgsql/traverse": "18.
|
|
37
|
-
"plpgsql-parser": "18.2.
|
|
36
|
+
"@pgsql/traverse": "18.4.0",
|
|
37
|
+
"plpgsql-parser": "18.2.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"makage": "^0.1.8"
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"classify",
|
|
50
50
|
"plpgsql"
|
|
51
51
|
],
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "979608121bc1e7daf2bd11473b2b894266e21bc7"
|
|
53
53
|
}
|