@pgsql/transform 18.7.0 → 18.9.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/transform.js CHANGED
@@ -68,6 +68,36 @@ function createResult() {
68
68
  errors: [],
69
69
  };
70
70
  }
71
+ /**
72
+ * Routing claims: each routable site (a RangeVar, a qualified name list, a
73
+ * bare schema-name field) is resolved through the router at most once per
74
+ * transform result.
75
+ *
76
+ * The walker visits parents before children, so a statement-level handler
77
+ * with namespace context (e.g. `AlterFunctionStmt` routing its
78
+ * `ObjectWithArgs` as a `function`) claims the site before a generic child
79
+ * visitor (`ObjectWithArgs`, namespace `unknown`) reaches it — first (most
80
+ * contextual) visitor wins. Without claims a site is routed twice, which
81
+ * corrupts cyclic mappings (a→b, b→a swaps back) and applies the less precise
82
+ * namespace second.
83
+ */
84
+ const routingClaims = new WeakMap();
85
+ function claimSite(result, site, field = '*') {
86
+ let byNode = routingClaims.get(result);
87
+ if (!byNode) {
88
+ byNode = new WeakMap();
89
+ routingClaims.set(result, byNode);
90
+ }
91
+ let fields = byNode.get(site);
92
+ if (!fields) {
93
+ fields = new Set();
94
+ byNode.set(site, fields);
95
+ }
96
+ if (fields.has(field))
97
+ return false;
98
+ fields.add(field);
99
+ return true;
100
+ }
71
101
  /**
72
102
  * Transform a schema name if it exists in the mapping
73
103
  */
@@ -96,6 +126,8 @@ function shouldTransformSchema(schemaName, schemaMapping) {
96
126
  function transformNameList(names, schemaMapping, result, ns = 'unknown') {
97
127
  if (!names || names.length < 2)
98
128
  return;
129
+ if (!claimSite(result, names))
130
+ return;
99
131
  const router = asRouter(schemaMapping);
100
132
  // For schema-qualified names, the first element is the schema.
101
133
  const first = names[0];
@@ -131,6 +163,8 @@ function transformSchemaNameField(container, field, schemaMapping, result) {
131
163
  const schemaName = container?.[field];
132
164
  if (typeof schemaName !== 'string')
133
165
  return;
166
+ if (!claimSite(result, container, field))
167
+ return;
134
168
  // A bare schema name is an operation on the schema itself: only the
135
169
  // schema-level default applies.
136
170
  const newName = asRouter(schemaMapping).resolve(schemaName, undefined, 'schema');
@@ -142,11 +176,16 @@ function transformSchemaNameField(container, field, schemaMapping, result) {
142
176
  }
143
177
  /**
144
178
  * Transform a RangeVar-like relation object (has schemaname/relname fields).
145
- * Many statement nodes embed a relation that the walker does NOT auto-recurse into.
179
+ * The walker auto-recurses into embedded relations (concrete `RangeVar`
180
+ * fields are tag-synthesized), so the generic `RangeVar` visitor covers
181
+ * every occurrence; statement handlers call this directly only when they
182
+ * carry extra context, and the claim guard keeps each relation routed once.
146
183
  */
147
184
  function transformRelation(relation, schemaMapping, result) {
148
185
  if (!relation?.schemaname)
149
186
  return;
187
+ if (!claimSite(result, relation))
188
+ return;
150
189
  const oldName = relation.schemaname;
151
190
  // A RangeVar names a relation (table/view/sequence/matview); route by the
152
191
  // relation name so object-level routes can send it to its own schema.
@@ -206,6 +245,8 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
206
245
  const schemaName = container?.[key];
207
246
  if (typeof schemaName !== 'string')
208
247
  return;
248
+ if (!claimSite(result, container, key))
249
+ return;
209
250
  const newName = router.resolve(schemaName, undefined, 'schema');
210
251
  if (newName && newName !== schemaName) {
211
252
  result.schemasFound.add(schemaName);
@@ -263,15 +304,6 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
263
304
  const node = path.node;
264
305
  transformNameList(node.funcname, router, result, 'function');
265
306
  },
266
- // Transform CallStmt (CALL schema.procedure(...)).
267
- // Note: The walker does NOT auto-recurse into CallStmt.funccall,
268
- // so the FuncCall visitor above never sees it.
269
- CallStmt: (path) => {
270
- const node = path.node;
271
- if (node.funccall?.funcname) {
272
- transformNameList(node.funccall.funcname, router, result, 'function');
273
- }
274
- },
275
307
  // Transform TypeName nodes (type references with schema-qualified names)
276
308
  TypeName: (path) => {
277
309
  const node = path.node;
@@ -329,8 +361,9 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
329
361
  }
330
362
  },
331
363
  // Transform DropStmt (DROP SCHEMA name; DROP TABLE/INDEX/POLICY/TRIGGER/
332
- // TYPE/FUNCTION schema.obj). The walker does NOT auto-recurse into
333
- // DropStmt.objects.
364
+ // TYPE/FUNCTION schema.obj) with the namespace derived from removeType,
365
+ // so object routes apply; claims stop the generic List/ObjectWithArgs
366
+ // visitors from re-routing the same names with namespace `unknown`.
334
367
  DropStmt: (path) => {
335
368
  const node = path.node;
336
369
  if (node.removeType !== 'OBJECT_SCHEMA' && Array.isArray(node.objects)) {
@@ -364,79 +397,10 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
364
397
  }
365
398
  }
366
399
  },
367
- // Transform InsertStmt (INSERT INTO schema.table)
368
- // Note: The walker does NOT auto-recurse into InsertStmt.relation.
369
- InsertStmt: (path) => {
370
- transformRelation(path.node.relation, schemaMapping, result);
371
- },
372
- // Transform CreateStmt (CREATE TABLE schema.table)
373
- // Note: The walker does NOT auto-recurse into CreateStmt.relation.
374
- CreateStmt: (path) => {
375
- transformRelation(path.node.relation, schemaMapping, result);
376
- },
377
- // Transform UpdateStmt (UPDATE schema.table SET ...)
378
- // Note: The walker does NOT auto-recurse into UpdateStmt.relation.
379
- UpdateStmt: (path) => {
380
- transformRelation(path.node.relation, schemaMapping, result);
381
- },
382
- // Transform DeleteStmt (DELETE FROM schema.table)
383
- // Note: The walker does NOT auto-recurse into DeleteStmt.relation.
384
- DeleteStmt: (path) => {
385
- transformRelation(path.node.relation, schemaMapping, result);
386
- },
387
- // Transform MergeStmt (MERGE INTO schema.table USING ...)
388
- // Note: The walker does NOT auto-recurse into MergeStmt.relation.
389
- MergeStmt: (path) => {
390
- transformRelation(path.node.relation, schemaMapping, result);
391
- },
392
- // Transform IndexStmt (CREATE INDEX ... ON schema.table)
393
- // Note: The walker does NOT auto-recurse into IndexStmt.relation.
394
- IndexStmt: (path) => {
395
- transformRelation(path.node.relation, schemaMapping, result);
396
- },
397
- // Transform Constraint (REFERENCES schema.table for foreign keys)
398
- // Note: The walker does NOT auto-recurse into Constraint.pktable.
399
- Constraint: (path) => {
400
- const node = path.node;
401
- if (node.contype === 'CONSTR_FOREIGN' && node.pktable) {
402
- transformRelation(node.pktable, schemaMapping, result);
403
- }
404
- },
405
- // Transform CompositeTypeStmt (CREATE TYPE schema.name AS (...))
406
- // Note: The walker does NOT auto-recurse into CompositeTypeStmt.typevar.
407
- CompositeTypeStmt: (path) => {
408
- transformRelation(path.node.typevar, schemaMapping, result);
409
- },
410
- // Transform ColumnDef (column definitions with schema-qualified types)
411
- // Note: The walker does NOT auto-recurse into ColumnDef.typeName.
412
- ColumnDef: (path) => {
413
- const node = path.node;
414
- if (node.typeName?.names) {
415
- transformNameList(node.typeName.names, schemaMapping, result, 'type');
416
- }
417
- },
418
- // Transform ViewStmt (CREATE VIEW schema.viewname AS ...)
419
- // Note: The walker does NOT auto-recurse into ViewStmt.view.
420
- ViewStmt: (path) => {
421
- transformRelation(path.node.view, schemaMapping, result);
422
- },
423
- // Transform AlterTableStmt (ALTER TABLE schema.table ...)
424
- // Note: The walker does NOT auto-recurse into AlterTableStmt.relation.
425
- AlterTableStmt: (path) => {
426
- transformRelation(path.node.relation, schemaMapping, result);
427
- },
428
- // Transform CreateSeqStmt (CREATE SEQUENCE schema.seqname)
429
- // Note: The walker does NOT auto-recurse into CreateSeqStmt.sequence.
430
- CreateSeqStmt: (path) => {
431
- transformRelation(path.node.sequence, schemaMapping, result);
432
- },
433
- // Transform AlterSeqStmt (ALTER SEQUENCE schema.seqname [OWNED BY schema.table.col])
434
- // Note: The walker does NOT auto-recurse into AlterSeqStmt.sequence.
435
- // Also handles OWNED BY clause where the schema name is in DefElem.arg.List.items.
400
+ // Transform AlterSeqStmt OWNED BY (schema name in DefElem.arg.List.items;
401
+ // a bare name list no generic visitor covers)
436
402
  AlterSeqStmt: (path) => {
437
403
  const node = path.node;
438
- transformRelation(node.sequence, schemaMapping, result);
439
- // Handle OWNED BY option: schema name is in the name list
440
404
  if (node.options) {
441
405
  for (const opt of node.options) {
442
406
  if (opt?.DefElem?.defname === 'owned_by' && opt.DefElem?.arg?.List?.items) {
@@ -445,67 +409,12 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
445
409
  }
446
410
  }
447
411
  },
448
- // Transform CreatePolicyStmt (CREATE POLICY ... ON schema.table)
449
- // Note: The walker does NOT auto-recurse into CreatePolicyStmt.table.
450
- CreatePolicyStmt: (path) => {
451
- transformRelation(path.node.table, schemaMapping, result);
452
- },
453
- // Transform RuleStmt (CREATE RULE ... ON schema.table)
454
- // Note: The walker does NOT auto-recurse into RuleStmt.relation.
455
- RuleStmt: (path) => {
456
- transformRelation(path.node.relation, schemaMapping, result);
457
- },
458
- // Transform CopyStmt (COPY schema.table ...)
459
- // Note: The walker does NOT auto-recurse into CopyStmt.relation.
460
- CopyStmt: (path) => {
461
- transformRelation(path.node.relation, schemaMapping, result);
462
- },
463
- // Transform ClusterStmt (CLUSTER schema.table USING index)
464
- // Note: The walker does NOT auto-recurse into ClusterStmt.relation.
465
- ClusterStmt: (path) => {
466
- transformRelation(path.node.relation, schemaMapping, result);
467
- },
468
- // Transform VacuumRelation (VACUUM/ANALYZE schema.table)
469
- // Note: The walker visits VacuumRelation but does NOT recurse into its relation.
470
- VacuumRelation: (path) => {
471
- transformRelation(path.node.relation, schemaMapping, result);
472
- },
473
- // Transform RefreshMatViewStmt (REFRESH MATERIALIZED VIEW schema.view)
474
- // Note: The walker does NOT auto-recurse into RefreshMatViewStmt.relation.
475
- RefreshMatViewStmt: (path) => {
476
- transformRelation(path.node.relation, schemaMapping, result);
477
- },
478
- // Transform CreateTableAsStmt (CREATE MATERIALIZED VIEW schema.viewname AS ...)
479
- // Note: The walker does NOT auto-recurse into CreateTableAsStmt.into.rel.
480
- CreateTableAsStmt: (path) => {
481
- transformRelation(path.node.into?.rel, schemaMapping, result);
482
- },
483
- // Transform TypeCast nodes (type casts like NULL::schema.type)
484
- // Note: The walker visits TypeCast but does NOT recurse into the TypeName node.
485
- TypeCast: (path) => {
486
- const node = path.node;
487
- if (node.typeName?.names) {
488
- transformNameList(node.typeName.names, schemaMapping, result, 'type');
489
- }
490
- },
491
- // Transform CreateFunctionStmt (CREATE FUNCTION schema.funcname)
492
- // Also handles RETURNS [SETOF] schema.type via returnType.names
412
+ // Transform CreateFunctionStmt (CREATE FUNCTION schema.funcname).
413
+ // funcname is a bare name list (no generic visitor); return and parameter
414
+ // types are TypeName nodes the walker reaches on its own.
493
415
  CreateFunctionStmt: (path) => {
494
416
  const node = path.node;
495
417
  transformNameList(node.funcname, schemaMapping, result, 'function');
496
- // Transform the return type (e.g., RETURNS SETOF schema.tablename)
497
- if (node.returnType?.names) {
498
- transformNameList(node.returnType.names, schemaMapping, result, 'type');
499
- }
500
- // Transform parameter types (the walker does NOT auto-recurse into
501
- // FunctionParameter.argType TypeName nodes)
502
- if (Array.isArray(node.parameters)) {
503
- for (const param of node.parameters) {
504
- if (param?.FunctionParameter?.argType?.names) {
505
- transformNameList(param.FunctionParameter.argType.names, schemaMapping, result, 'type');
506
- }
507
- }
508
- }
509
418
  // Transform schema-qualified references inside the function body
510
419
  // (AS $$...$$ is a String under the 'as' DefElem, which the walker
511
420
  // does not parse as SQL). LANGUAGE plpgsql bodies are additionally
@@ -532,12 +441,11 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
532
441
  }
533
442
  },
534
443
  // Transform CreateTrigStmt (CREATE TRIGGER ... EXECUTE PROCEDURE schema.func)
535
- // Note: The walker does NOT auto-recurse into CreateTrigStmt.relation,
536
- // so we must explicitly transform the RangeVar here.
444
+ // funcname is a bare name list; the ON relation is a RangeVar the walker
445
+ // reaches on its own.
537
446
  CreateTrigStmt: (path) => {
538
447
  const node = path.node;
539
448
  transformNameList(node.funcname, schemaMapping, result, 'function');
540
- transformRelation(node.relation, schemaMapping, result);
541
449
  },
542
450
  // Transform CommentStmt (COMMENT ON TABLE/COLUMN/FUNCTION schema.obj,
543
451
  // COMMENT ON SCHEMA schema)
@@ -548,13 +456,14 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
548
456
  // For functions, object is { ObjectWithArgs: { objname: [...] } }
549
457
  // For schemas, object is a bare { String: { sval } }
550
458
  if (node.object) {
459
+ const ns = namespaceOfObjectType(node.objtype);
551
460
  // Handle List-wrapped objects (COMMENT ON TABLE schema.tbl, COMMENT ON COLUMN schema.tbl.col)
552
461
  if (node.object?.List?.items) {
553
- transformNameList(node.object.List.items, schemaMapping, result);
462
+ transformNameList(node.object.List.items, schemaMapping, result, ns);
554
463
  }
555
464
  // Handle ObjectWithArgs-style objects (COMMENT ON FUNCTION schema.func(args))
556
465
  else if (node.object?.ObjectWithArgs?.objname) {
557
- transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
466
+ transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result, ns);
558
467
  }
559
468
  // Handle bare schema names (COMMENT ON SCHEMA schema)
560
469
  else if (node.object?.String && node.objtype === 'OBJECT_SCHEMA') {
@@ -611,41 +520,41 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
611
520
  const node = path.node;
612
521
  transformNameList(node.objname, schemaMapping, result);
613
522
  },
614
- // Transform AlterObjectSchemaStmt (ALTER ... SET SCHEMA newschema)
615
- // The walker does NOT auto-recurse into AlterObjectSchemaStmt.relation
616
- // or .object, so we must transform relation, object names, and newschema.
523
+ // Transform AlterObjectSchemaStmt (ALTER ... SET SCHEMA newschema):
524
+ // object name lists and the destination schema. The relation form is a
525
+ // RangeVar the walker reaches on its own.
617
526
  AlterObjectSchemaStmt: (path) => {
618
527
  const node = path.node;
619
- transformRelation(node.relation, schemaMapping, result);
528
+ const ns = namespaceOfObjectType(node.objectType);
620
529
  // Non-relation objects (ALTER TYPE/FUNCTION ... SET SCHEMA)
621
530
  if (node.object?.List?.items) {
622
- transformNameList(node.object.List.items, schemaMapping, result);
531
+ transformNameList(node.object.List.items, schemaMapping, result, ns);
623
532
  }
624
533
  else if (node.object?.ObjectWithArgs?.objname) {
625
- transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
534
+ transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result, ns);
626
535
  }
627
536
  else if (node.object?.TypeName?.names) {
628
- transformNameList(node.object.TypeName.names, schemaMapping, result);
537
+ transformNameList(node.object.TypeName.names, schemaMapping, result, ns);
629
538
  }
630
539
  // Transform the newschema (destination schema)
631
540
  transformSchemaNameField(node, 'newschema', schemaMapping, result);
632
541
  },
633
542
  // Transform RenameStmt (ALTER SCHEMA old RENAME TO new;
634
- // ALTER TABLE schema.tbl RENAME ...). The walker does NOT auto-recurse
635
- // into RenameStmt.relation or .object.
543
+ // ALTER TABLE schema.tbl RENAME ...). The relation form is a RangeVar
544
+ // the walker reaches on its own.
636
545
  RenameStmt: (path) => {
637
546
  const node = path.node;
638
- transformRelation(node.relation, schemaMapping, result);
639
547
  if (node.renameType === 'OBJECT_SCHEMA') {
640
548
  // subname is the old schema name; newname is the target
641
549
  transformSchemaNameField(node, 'subname', schemaMapping, result);
642
550
  transformSchemaNameField(node, 'newname', schemaMapping, result);
643
551
  }
552
+ const ns = namespaceOfObjectType(node.renameType);
644
553
  if (node.object?.List?.items) {
645
- transformNameList(node.object.List.items, schemaMapping, result);
554
+ transformNameList(node.object.List.items, schemaMapping, result, ns);
646
555
  }
647
556
  else if (node.object?.ObjectWithArgs?.objname) {
648
- transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
557
+ transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result, ns);
649
558
  }
650
559
  },
651
560
  // Transform AlterFunctionStmt (ALTER FUNCTION schema.fn(...) SET ...)
@@ -658,46 +567,26 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
658
567
  // Transform AlterOwnerStmt (ALTER ... OWNER TO role)
659
568
  AlterOwnerStmt: (path) => {
660
569
  const node = path.node;
661
- transformRelation(node.relation, schemaMapping, result);
570
+ const ns = namespaceOfObjectType(node.objectType);
662
571
  if (node.object?.List?.items) {
663
- transformNameList(node.object.List.items, schemaMapping, result);
572
+ transformNameList(node.object.List.items, schemaMapping, result, ns);
664
573
  }
665
574
  else if (node.object?.ObjectWithArgs?.objname) {
666
- transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result);
575
+ transformNameList(node.object.ObjectWithArgs.objname, schemaMapping, result, ns);
667
576
  }
668
577
  else if (node.object?.String && node.objectType === 'OBJECT_SCHEMA') {
669
578
  transformSchemaNameField(node.object.String, 'sval', schemaMapping, result);
670
579
  }
671
580
  },
672
- // Transform CreateCastStmt (CREATE CAST (src AS tgt) WITH FUNCTION fn)
581
+ // Transform CreateCastStmt (CREATE CAST (src AS tgt) WITH FUNCTION fn).
582
+ // The function name gets its `function` namespace here; the cast's
583
+ // source/target/argument types are TypeName nodes the walker reaches on
584
+ // its own.
673
585
  CreateCastStmt: (path) => {
674
586
  const node = path.node;
675
- if (node.sourcetype?.names) {
676
- transformNameList(node.sourcetype.names, schemaMapping, result, 'type');
677
- }
678
- if (node.targettype?.names) {
679
- transformNameList(node.targettype.names, schemaMapping, result, 'type');
680
- }
681
587
  if (node.func?.objname) {
682
588
  transformNameList(node.func.objname, schemaMapping, result, 'function');
683
589
  }
684
- if (Array.isArray(node.func?.objargs)) {
685
- for (const arg of node.func.objargs) {
686
- if (arg?.TypeName?.names) {
687
- transformNameList(arg.TypeName.names, schemaMapping, result, 'type');
688
- }
689
- else if (arg?.names) {
690
- transformNameList(arg.names, schemaMapping, result, 'type');
691
- }
692
- }
693
- }
694
- if (Array.isArray(node.func?.objfuncargs)) {
695
- for (const param of node.func.objfuncargs) {
696
- if (param?.FunctionParameter?.argType?.names) {
697
- transformNameList(param.FunctionParameter.argType.names, schemaMapping, result, 'type');
698
- }
699
- }
700
- }
701
590
  },
702
591
  // Transform CreateEventTrigStmt (CREATE EVENT TRIGGER ... EXECUTE FUNCTION schema.fn())
703
592
  CreateEventTrigStmt: (path) => {