@pgsql/transform 18.1.1 → 18.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +29 -73
  2. package/categorize.d.ts +52 -0
  3. package/categorize.js +56 -0
  4. package/esm/categorize.js +51 -0
  5. package/esm/facts.js +289 -0
  6. package/esm/fixture-closure.js +197 -0
  7. package/esm/index.js +7 -22
  8. package/esm/qualify.js +297 -0
  9. package/esm/round-trip-core.js +126 -0
  10. package/esm/round-trip.js +116 -0
  11. package/esm/transform.js +1169 -0
  12. package/facts.d.ts +69 -0
  13. package/facts.js +292 -0
  14. package/fixture-closure.d.ts +71 -0
  15. package/fixture-closure.js +200 -0
  16. package/index.d.ts +14 -28
  17. package/index.js +43 -76
  18. package/package.json +22 -14
  19. package/qualify.d.ts +125 -0
  20. package/qualify.js +303 -0
  21. package/round-trip-core.d.ts +60 -0
  22. package/round-trip-core.js +133 -0
  23. package/round-trip.d.ts +47 -0
  24. package/round-trip.js +123 -0
  25. package/transform.d.ts +259 -0
  26. package/transform.js +1190 -0
  27. package/13/enums.d.ts +0 -62
  28. package/13/enums.js +0 -2
  29. package/13/types.d.ts +0 -2103
  30. package/13/types.js +0 -2
  31. package/14/enums.d.ts +0 -62
  32. package/14/enums.js +0 -2
  33. package/14/types.d.ts +0 -2164
  34. package/14/types.js +0 -2
  35. package/15/enums.d.ts +0 -64
  36. package/15/enums.js +0 -2
  37. package/15/types.d.ts +0 -2222
  38. package/15/types.js +0 -2
  39. package/16/enums.d.ts +0 -69
  40. package/16/enums.js +0 -2
  41. package/16/types.d.ts +0 -2328
  42. package/16/types.js +0 -2
  43. package/17/enums.d.ts +0 -76
  44. package/17/enums.js +0 -2
  45. package/17/types.d.ts +0 -2488
  46. package/17/types.js +0 -2
  47. package/18/enums.d.ts +0 -78
  48. package/18/enums.js +0 -2
  49. package/18/types.d.ts +0 -2538
  50. package/18/types.js +0 -2
  51. package/esm/13/enums.js +0 -1
  52. package/esm/13/types.js +0 -1
  53. package/esm/14/enums.js +0 -1
  54. package/esm/14/types.js +0 -1
  55. package/esm/15/enums.js +0 -1
  56. package/esm/15/types.js +0 -1
  57. package/esm/16/enums.js +0 -1
  58. package/esm/16/types.js +0 -1
  59. package/esm/17/enums.js +0 -1
  60. package/esm/17/types.js +0 -1
  61. package/esm/18/enums.js +0 -1
  62. package/esm/18/types.js +0 -1
  63. package/esm/multi-version-transformer.js +0 -80
  64. package/esm/transformers/context.js +0 -1
  65. package/esm/transformers/v13-to-v14.js +0 -2767
  66. package/esm/transformers/v14-to-v15.js +0 -1218
  67. package/esm/transformers/v15-to-v16.js +0 -2935
  68. package/esm/transformers/v16-to-v17.js +0 -1526
  69. package/esm/transformers/v17-to-v18.js +0 -204
  70. package/esm/transformers-direct/index.js +0 -13
  71. package/esm/transformers-direct/v13-to-v17/index.js +0 -74
  72. package/esm/transformers-direct/v13-to-v18/index.js +0 -84
  73. package/esm/transformers-direct/v14-to-v17/index.js +0 -63
  74. package/esm/transformers-direct/v14-to-v18/index.js +0 -73
  75. package/esm/transformers-direct/v15-to-v17/index.js +0 -53
  76. package/esm/transformers-direct/v15-to-v18/index.js +0 -62
  77. package/esm/transformers-direct/v16-to-v17/index.js +0 -40
  78. package/esm/transformers-direct/v16-to-v18/index.js +0 -51
  79. package/esm/transformers-direct/v17-to-v18/index.js +0 -39
  80. package/multi-version-transformer.d.ts +0 -14
  81. package/multi-version-transformer.js +0 -84
  82. package/transformers/context.d.ts +0 -4
  83. package/transformers/context.js +0 -2
  84. package/transformers/v13-to-v14.d.ts +0 -255
  85. package/transformers/v13-to-v14.js +0 -2771
  86. package/transformers/v14-to-v15.d.ts +0 -615
  87. package/transformers/v14-to-v15.js +0 -1222
  88. package/transformers/v15-to-v16.d.ts +0 -632
  89. package/transformers/v15-to-v16.js +0 -2939
  90. package/transformers/v16-to-v17.d.ts +0 -640
  91. package/transformers/v16-to-v17.js +0 -1530
  92. package/transformers/v17-to-v18.d.ts +0 -54
  93. package/transformers/v17-to-v18.js +0 -208
  94. package/transformers-direct/index.d.ts +0 -13
  95. package/transformers-direct/index.js +0 -25
  96. package/transformers-direct/v13-to-v17/index.d.ts +0 -27
  97. package/transformers-direct/v13-to-v17/index.js +0 -78
  98. package/transformers-direct/v13-to-v18/index.d.ts +0 -28
  99. package/transformers-direct/v13-to-v18/index.js +0 -88
  100. package/transformers-direct/v14-to-v17/index.d.ts +0 -26
  101. package/transformers-direct/v14-to-v17/index.js +0 -67
  102. package/transformers-direct/v14-to-v18/index.d.ts +0 -27
  103. package/transformers-direct/v14-to-v18/index.js +0 -77
  104. package/transformers-direct/v15-to-v17/index.d.ts +0 -25
  105. package/transformers-direct/v15-to-v17/index.js +0 -57
  106. package/transformers-direct/v15-to-v18/index.d.ts +0 -26
  107. package/transformers-direct/v15-to-v18/index.js +0 -66
  108. package/transformers-direct/v16-to-v17/index.d.ts +0 -24
  109. package/transformers-direct/v16-to-v17/index.js +0 -44
  110. package/transformers-direct/v16-to-v18/index.d.ts +0 -25
  111. package/transformers-direct/v16-to-v18/index.js +0 -55
  112. package/transformers-direct/v17-to-v18/index.d.ts +0 -24
  113. package/transformers-direct/v17-to-v18/index.js +0 -43
package/index.js CHANGED
@@ -1,78 +1,45 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.PG18Types = exports.PG17Types = exports.PG16Types = exports.PG15Types = exports.PG14Types = exports.PG13Types = exports.PG17ToPG18Transformer = exports.PG16ToPG18Transformer = exports.PG15ToPG18Transformer = exports.PG14ToPG18Transformer = exports.PG13ToPG18Transformer = exports.PG16ToPG17Transformer = exports.PG15ToPG17Transformer = exports.PG14ToPG17Transformer = exports.PG13ToPG17Transformer = exports.V17ToV18Transformer = exports.V16ToV17Transformer = exports.V15ToV16Transformer = exports.V14ToV15Transformer = exports.V13ToV14Transformer = exports.ASTTransformer = void 0;
37
- const PG13Types = __importStar(require("./13/types"));
38
- exports.PG13Types = PG13Types;
39
- const PG14Types = __importStar(require("./14/types"));
40
- exports.PG14Types = PG14Types;
41
- const PG15Types = __importStar(require("./15/types"));
42
- exports.PG15Types = PG15Types;
43
- const PG16Types = __importStar(require("./16/types"));
44
- exports.PG16Types = PG16Types;
45
- const PG17Types = __importStar(require("./17/types"));
46
- exports.PG17Types = PG17Types;
47
- const PG18Types = __importStar(require("./18/types"));
48
- exports.PG18Types = PG18Types;
49
- var multi_version_transformer_1 = require("./multi-version-transformer");
50
- Object.defineProperty(exports, "ASTTransformer", { enumerable: true, get: function () { return multi_version_transformer_1.ASTTransformer; } });
51
- var v13_to_v14_1 = require("./transformers/v13-to-v14");
52
- Object.defineProperty(exports, "V13ToV14Transformer", { enumerable: true, get: function () { return v13_to_v14_1.V13ToV14Transformer; } });
53
- var v14_to_v15_1 = require("./transformers/v14-to-v15");
54
- Object.defineProperty(exports, "V14ToV15Transformer", { enumerable: true, get: function () { return v14_to_v15_1.V14ToV15Transformer; } });
55
- var v15_to_v16_1 = require("./transformers/v15-to-v16");
56
- Object.defineProperty(exports, "V15ToV16Transformer", { enumerable: true, get: function () { return v15_to_v16_1.V15ToV16Transformer; } });
57
- var v16_to_v17_1 = require("./transformers/v16-to-v17");
58
- Object.defineProperty(exports, "V16ToV17Transformer", { enumerable: true, get: function () { return v16_to_v17_1.V16ToV17Transformer; } });
59
- var v17_to_v18_1 = require("./transformers/v17-to-v18");
60
- Object.defineProperty(exports, "V17ToV18Transformer", { enumerable: true, get: function () { return v17_to_v18_1.V17ToV18Transformer; } });
61
- var v13_to_v17_1 = require("./transformers-direct/v13-to-v17");
62
- Object.defineProperty(exports, "PG13ToPG17Transformer", { enumerable: true, get: function () { return v13_to_v17_1.PG13ToPG17Transformer; } });
63
- var v14_to_v17_1 = require("./transformers-direct/v14-to-v17");
64
- Object.defineProperty(exports, "PG14ToPG17Transformer", { enumerable: true, get: function () { return v14_to_v17_1.PG14ToPG17Transformer; } });
65
- var v15_to_v17_1 = require("./transformers-direct/v15-to-v17");
66
- Object.defineProperty(exports, "PG15ToPG17Transformer", { enumerable: true, get: function () { return v15_to_v17_1.PG15ToPG17Transformer; } });
67
- var v16_to_v17_2 = require("./transformers-direct/v16-to-v17");
68
- Object.defineProperty(exports, "PG16ToPG17Transformer", { enumerable: true, get: function () { return v16_to_v17_2.PG16ToPG17Transformer; } });
69
- var v13_to_v18_1 = require("./transformers-direct/v13-to-v18");
70
- Object.defineProperty(exports, "PG13ToPG18Transformer", { enumerable: true, get: function () { return v13_to_v18_1.PG13ToPG18Transformer; } });
71
- var v14_to_v18_1 = require("./transformers-direct/v14-to-v18");
72
- Object.defineProperty(exports, "PG14ToPG18Transformer", { enumerable: true, get: function () { return v14_to_v18_1.PG14ToPG18Transformer; } });
73
- var v15_to_v18_1 = require("./transformers-direct/v15-to-v18");
74
- Object.defineProperty(exports, "PG15ToPG18Transformer", { enumerable: true, get: function () { return v15_to_v18_1.PG15ToPG18Transformer; } });
75
- var v16_to_v18_1 = require("./transformers-direct/v16-to-v18");
76
- Object.defineProperty(exports, "PG16ToPG18Transformer", { enumerable: true, get: function () { return v16_to_v18_1.PG16ToPG18Transformer; } });
77
- var v17_to_v18_2 = require("./transformers-direct/v17-to-v18");
78
- Object.defineProperty(exports, "PG17ToPG18Transformer", { enumerable: true, get: function () { return v17_to_v18_2.PG17ToPG18Transformer; } });
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.qualifyUnqualified = exports.mergeInventories = exports.createQualifyVisitor = exports.collectCreatedObjects = exports.resolveFixtureClosure = exports.classifyStatements = exports.TIER_PROFILE = exports.categorizeChange = exports.buildCategoryOf = void 0;
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; } });
8
+ var facts_1 = require("./facts");
9
+ 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
+ var qualify_1 = require("./qualify");
13
+ Object.defineProperty(exports, "collectCreatedObjects", { enumerable: true, get: function () { return qualify_1.collectCreatedObjects; } });
14
+ Object.defineProperty(exports, "createQualifyVisitor", { enumerable: true, get: function () { return qualify_1.createQualifyVisitor; } });
15
+ Object.defineProperty(exports, "mergeInventories", { enumerable: true, get: function () { return qualify_1.mergeInventories; } });
16
+ Object.defineProperty(exports, "qualifyUnqualified", { enumerable: true, get: function () { return qualify_1.qualifyUnqualified; } });
17
+ var round_trip_1 = require("./round-trip");
18
+ Object.defineProperty(exports, "captureAstsFromSql", { enumerable: true, get: function () { return round_trip_1.captureAstsFromSql; } });
19
+ Object.defineProperty(exports, "captureTransformAsts", { enumerable: true, get: function () { return round_trip_1.captureTransformAsts; } });
20
+ Object.defineProperty(exports, "firstDifference", { enumerable: true, get: function () { return round_trip_1.firstDifference; } });
21
+ Object.defineProperty(exports, "normalizeParseTree", { enumerable: true, get: function () { return round_trip_1.normalizeParseTree; } });
22
+ Object.defineProperty(exports, "validateRoundTrip", { enumerable: true, get: function () { return round_trip_1.validateRoundTrip; } });
23
+ var round_trip_core_1 = require("./round-trip-core");
24
+ Object.defineProperty(exports, "CLEAN_TREE_VOLATILE_KEYS", { enumerable: true, get: function () { return round_trip_core_1.CLEAN_TREE_VOLATILE_KEYS; } });
25
+ Object.defineProperty(exports, "cleanTree", { enumerable: true, get: function () { return round_trip_core_1.cleanTree; } });
26
+ Object.defineProperty(exports, "normalizeTree", { enumerable: true, get: function () { return round_trip_core_1.normalizeTree; } });
27
+ Object.defineProperty(exports, "trimDefElemBody", { enumerable: true, get: function () { return round_trip_core_1.trimDefElemBody; } });
28
+ var transform_1 = require("./transform");
29
+ Object.defineProperty(exports, "createPlpgsqlVisitor", { enumerable: true, get: function () { return transform_1.createPlpgsqlVisitor; } });
30
+ Object.defineProperty(exports, "createSqlVisitor", { enumerable: true, get: function () { return transform_1.createSqlVisitor; } });
31
+ Object.defineProperty(exports, "escapeRegexp", { enumerable: true, get: function () { return transform_1.escapeRegexp; } });
32
+ Object.defineProperty(exports, "extractPgpmHeader", { enumerable: true, get: function () { return transform_1.extractPgpmHeader; } });
33
+ Object.defineProperty(exports, "shouldTransformSchema", { enumerable: true, get: function () { return transform_1.shouldTransformSchema; } });
34
+ Object.defineProperty(exports, "transformComments", { enumerable: true, get: function () { return transform_1.transformComments; } });
35
+ Object.defineProperty(exports, "transformJsonStringValues", { enumerable: true, get: function () { return transform_1.transformJsonStringValues; } });
36
+ Object.defineProperty(exports, "transformNameList", { enumerable: true, get: function () { return transform_1.transformNameList; } });
37
+ Object.defineProperty(exports, "transformPlpgsqlTypeAst", { enumerable: true, get: function () { return transform_1.transformPlpgsqlTypeAst; } });
38
+ Object.defineProperty(exports, "transformPlpgsqlTypeString", { enumerable: true, get: function () { return transform_1.transformPlpgsqlTypeString; } });
39
+ Object.defineProperty(exports, "transformRelation", { enumerable: true, get: function () { return transform_1.transformRelation; } });
40
+ Object.defineProperty(exports, "transformSchemaName", { enumerable: true, get: function () { return transform_1.transformSchemaName; } });
41
+ Object.defineProperty(exports, "transformSql", { enumerable: true, get: function () { return transform_1.transformSql; } });
42
+ Object.defineProperty(exports, "transformSqlStatement", { enumerable: true, get: function () { return transform_1.transformSqlStatement; } });
43
+ Object.defineProperty(exports, "transformVerifyCalls", { enumerable: true, get: function () { return transform_1.transformVerifyCalls; } });
44
+ Object.defineProperty(exports, "validateNoUntransformedSchemas", { enumerable: true, get: function () { return transform_1.validateNoUntransformedSchemas; } });
45
+ Object.defineProperty(exports, "walkPlpgsqlForSchemas", { enumerable: true, get: function () { return transform_1.walkPlpgsqlForSchemas; } });
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@pgsql/transform",
3
- "version": "18.1.1",
3
+ "version": "18.3.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
- "description": "Transform PostgreSQL AST types for pgsql-parser",
5
+ "description": "AST-based SQL transformation, qualification, classification and closure analysis for PostgreSQL",
6
6
  "main": "index.js",
7
7
  "module": "esm/index.js",
8
8
  "types": "index.d.ts",
@@ -23,23 +23,31 @@
23
23
  "copy": "makage assets",
24
24
  "clean": "makage clean dist",
25
25
  "prepublishOnly": "npm run build",
26
- "build": "npm run build:proto && npm run clean && tsc && tsc -p tsconfig.esm.json && npm run copy",
26
+ "build": "npm run clean && tsc && tsc -p tsconfig.esm.json && npm run copy",
27
27
  "build:dev": "npm run clean && tsc --declarationMap && tsc -p tsconfig.esm.json && npm run copy",
28
- "build:proto": "ts-node scripts/pg-proto-parser",
29
28
  "lint": "eslint . --fix",
30
29
  "test": "jest",
31
30
  "test:watch": "jest --watch",
32
- "kitchen-sink": "ts-node scripts/make-kitchen-sink.ts",
33
- "test:ast": "ts-node scripts/test-ast.ts"
31
+ "fixtures": "ts-node scripts/make-fixtures.ts",
32
+ "scan-corpus": "node scripts/scan-corpus.js"
33
+ },
34
+ "dependencies": {
35
+ "@pgsql/quotes": "18.1.0",
36
+ "@pgsql/traverse": "18.3.0",
37
+ "plpgsql-parser": "18.2.1"
34
38
  },
35
39
  "devDependencies": {
36
- "@pgsql/parser": "^1.2.1",
37
- "@pgsql/types": "^18.0.0",
38
- "libpg-query": "18.1.2",
39
- "makage": "^0.1.8",
40
- "pg-proto-parser": "1.31.0",
41
- "pgsql-deparser": "18.1.1"
40
+ "makage": "^0.1.8"
42
41
  },
43
- "keywords": [],
44
- "gitHead": "d57254a6bae519c2ce7c54f88dff44105124a821"
42
+ "keywords": [
43
+ "sql",
44
+ "postgres",
45
+ "postgresql",
46
+ "pg",
47
+ "ast",
48
+ "transform",
49
+ "classify",
50
+ "plpgsql"
51
+ ],
52
+ "gitHead": "b59056454e08e3b7f71019c988150af2019ceda6"
45
53
  }
package/qualify.d.ts ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Opt-in qualification of unqualified object references.
3
+ *
4
+ * Handwritten SQL is typically unqualified — objects implicitly land in (and
5
+ * resolve against) the `public` schema. The core schema transform only
6
+ * rewrites *explicitly* qualified references, so ingesting such code into
7
+ * named schemas needs a prior pass that pins every unqualified reference to a
8
+ * schema first. This module provides that pass.
9
+ *
10
+ * Safety model: only routed names are qualified — either everything in an
11
+ * {@link ObjectInventory} to a single schema, or per-object routes via
12
+ * `targets`. Builtin functions (`now()`, `count(...)`), builtin types, and
13
+ * column references are never touched because they are not routed. Names
14
+ * defined by a CTE in the same statement are excluded even when they collide
15
+ * with a routed relation.
16
+ */
17
+ /** Named objects eligible for qualification, bucketed by namespace. */
18
+ export interface ObjectInventory {
19
+ /** Tables, views, sequences — anything a RangeVar can reference. */
20
+ relations: Set<string>;
21
+ /** Function and procedure names. */
22
+ functions: Set<string>;
23
+ /** Enum/domain/range/composite type names. */
24
+ types: Set<string>;
25
+ }
26
+ /** The objects a target schema should receive, by namespace. */
27
+ export interface QualifyTargetSelector {
28
+ relations?: Iterable<string>;
29
+ functions?: Iterable<string>;
30
+ types?: Iterable<string>;
31
+ }
32
+ export interface QualifyUnqualifiedOptions {
33
+ /**
34
+ * Single-target form: schema to pin unqualified references to (e.g.
35
+ * `'public'`). Mutually exclusive with `targets`.
36
+ */
37
+ schema?: string;
38
+ /**
39
+ * Names eligible for qualification (single-target form only). Defaults to
40
+ * the objects created by the content itself ({@link collectCreatedObjects}).
41
+ * When qualifying scripts that reference objects created elsewhere (e.g.
42
+ * the scripts of a whole module), collect the inventory across all scripts
43
+ * and pass it here.
44
+ */
45
+ inventory?: ObjectInventory;
46
+ /**
47
+ * Multi-target form: route specific objects to specific schemas in one
48
+ * pass. Mutually exclusive with `schema`/`inventory`.
49
+ *
50
+ * ```ts
51
+ * qualifyUnqualified(sql, {
52
+ * targets: {
53
+ * auth: { relations: ['users'] },
54
+ * shop: { relations: ['products'], functions: ['get_products'] },
55
+ * shared: { types: ['widget'] }
56
+ * }
57
+ * });
58
+ * ```
59
+ *
60
+ * Routing a name to two schemas in the same namespace is a conflict and
61
+ * throws.
62
+ */
63
+ targets?: Record<string, QualifyTargetSelector>;
64
+ /**
65
+ * Prepend `CREATE SCHEMA IF NOT EXISTS <schema>;` for every target schema
66
+ * the content does not already create. Useful when ingesting standalone
67
+ * handwritten SQL whose target schemas may not exist.
68
+ */
69
+ injectCreateSchema?: boolean;
70
+ }
71
+ export interface QualifyResult {
72
+ /** Count of references qualified, keyed by object name. */
73
+ qualified: Map<string, number>;
74
+ /** Schema each qualified object was routed to. */
75
+ routed: Map<string, string>;
76
+ }
77
+ /** Resolved name → schema routing, by namespace. */
78
+ export interface QualifyRoutes {
79
+ relations: Map<string, string>;
80
+ functions: Map<string, string>;
81
+ types: Map<string, string>;
82
+ }
83
+ /**
84
+ * Collect the objects a SQL script creates, bucketed for qualification.
85
+ * Only unqualified (schema-less) creations are collected — an explicitly
86
+ * qualified creation already declares where it lives.
87
+ */
88
+ export declare function collectCreatedObjects(sql: string): ObjectInventory;
89
+ /** Merge inventories collected from multiple scripts. */
90
+ export declare function mergeInventories(inventories: ObjectInventory[]): ObjectInventory;
91
+ /**
92
+ * Create a SQL AST visitor that qualifies unqualified references against a
93
+ * routing table. Composable with the walkers used by the core transform.
94
+ */
95
+ export declare function createQualifyVisitor(options: {
96
+ routes: QualifyRoutes;
97
+ cteNames?: Set<string>;
98
+ }, result: QualifyResult): {
99
+ RangeVar: (path: any) => void;
100
+ FuncCall: (path: any) => void;
101
+ CallStmt: (path: any) => void;
102
+ TypeName: (path: any) => void;
103
+ CreateFunctionStmt: (path: any) => void;
104
+ CreateTrigStmt: (path: any) => void;
105
+ ObjectWithArgs: (path: any) => void;
106
+ CreateEnumStmt: (path: any) => void;
107
+ CreateRangeStmt: (path: any) => void;
108
+ CreateDomainStmt: (path: any) => void;
109
+ };
110
+ /**
111
+ * Qualify unqualified references in a SQL string.
112
+ *
113
+ * Two forms:
114
+ * - `{ schema, inventory? }` — pin every inventoried name to one schema.
115
+ * - `{ targets }` — route specific objects to specific schemas in one pass.
116
+ *
117
+ * Runs as a standalone AST pass (parse → qualify → deparse). To combine with
118
+ * a schema rename, run this first (typically with `schema: 'public'`) and
119
+ * then map via the core transform — or use the `qualifyUnqualified` option on
120
+ * `transformSql`, which does exactly that.
121
+ */
122
+ export declare function qualifyUnqualified(sql: string, options: QualifyUnqualifiedOptions): {
123
+ sql: string;
124
+ result: QualifyResult;
125
+ };
package/qualify.js ADDED
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ /**
3
+ * Opt-in qualification of unqualified object references.
4
+ *
5
+ * Handwritten SQL is typically unqualified — objects implicitly land in (and
6
+ * resolve against) the `public` schema. The core schema transform only
7
+ * rewrites *explicitly* qualified references, so ingesting such code into
8
+ * named schemas needs a prior pass that pins every unqualified reference to a
9
+ * schema first. This module provides that pass.
10
+ *
11
+ * Safety model: only routed names are qualified — either everything in an
12
+ * {@link ObjectInventory} to a single schema, or per-object routes via
13
+ * `targets`. Builtin functions (`now()`, `count(...)`), builtin types, and
14
+ * column references are never touched because they are not routed. Names
15
+ * defined by a CTE in the same statement are excluded even when they collide
16
+ * with a routed relation.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.collectCreatedObjects = collectCreatedObjects;
20
+ exports.mergeInventories = mergeInventories;
21
+ exports.createQualifyVisitor = createQualifyVisitor;
22
+ exports.qualifyUnqualified = qualifyUnqualified;
23
+ const traverse_1 = require("@pgsql/traverse");
24
+ const plpgsql_parser_1 = require("plpgsql-parser");
25
+ const facts_1 = require("./facts");
26
+ const RELATION_TAGS = new Set([
27
+ 'CreateStmt',
28
+ 'ViewStmt',
29
+ 'CreateSeqStmt',
30
+ 'CreateTableAsStmt'
31
+ ]);
32
+ const TYPE_TAGS = new Set([
33
+ 'CreateEnumStmt',
34
+ 'CreateDomainStmt',
35
+ 'CreateRangeStmt',
36
+ 'CompositeTypeStmt'
37
+ ]);
38
+ /**
39
+ * Collect the objects a SQL script creates, bucketed for qualification.
40
+ * Only unqualified (schema-less) creations are collected — an explicitly
41
+ * qualified creation already declares where it lives.
42
+ */
43
+ function collectCreatedObjects(sql) {
44
+ const inventory = {
45
+ relations: new Set(),
46
+ functions: new Set(),
47
+ types: new Set()
48
+ };
49
+ for (const facts of (0, facts_1.classifyStatements)(sql)) {
50
+ for (const created of facts.creates) {
51
+ if (created.schema)
52
+ continue;
53
+ if (RELATION_TAGS.has(facts.nodeTag)) {
54
+ inventory.relations.add(created.name);
55
+ }
56
+ else if (facts.nodeTag === 'CreateFunctionStmt') {
57
+ inventory.functions.add(created.name);
58
+ }
59
+ else if (TYPE_TAGS.has(facts.nodeTag)) {
60
+ inventory.types.add(created.name);
61
+ }
62
+ }
63
+ }
64
+ return inventory;
65
+ }
66
+ /** Merge inventories collected from multiple scripts. */
67
+ function mergeInventories(inventories) {
68
+ const merged = {
69
+ relations: new Set(),
70
+ functions: new Set(),
71
+ types: new Set()
72
+ };
73
+ for (const inv of inventories) {
74
+ for (const name of inv.relations)
75
+ merged.relations.add(name);
76
+ for (const name of inv.functions)
77
+ merged.functions.add(name);
78
+ for (const name of inv.types)
79
+ merged.types.add(name);
80
+ }
81
+ return merged;
82
+ }
83
+ /** Resolve the routing table from the option forms. */
84
+ function resolveRoutes(sql, options) {
85
+ if (options.targets && options.schema) {
86
+ throw new Error('qualifyUnqualified: pass either `schema` or `targets`, not both');
87
+ }
88
+ const routes = {
89
+ relations: new Map(),
90
+ functions: new Map(),
91
+ types: new Map()
92
+ };
93
+ const route = (bucket, namespace, name, schema) => {
94
+ const existing = bucket.get(name);
95
+ if (existing && existing !== schema) {
96
+ throw new Error(`qualifyUnqualified: conflicting targets for ${namespace} "${name}": ${existing} vs ${schema}`);
97
+ }
98
+ bucket.set(name, schema);
99
+ };
100
+ if (options.targets) {
101
+ if (options.inventory) {
102
+ throw new Error('qualifyUnqualified: `inventory` only applies to the `schema` form');
103
+ }
104
+ for (const [schema, selector] of Object.entries(options.targets)) {
105
+ for (const name of selector.relations ?? [])
106
+ route(routes.relations, 'relation', name, schema);
107
+ for (const name of selector.functions ?? [])
108
+ route(routes.functions, 'function', name, schema);
109
+ for (const name of selector.types ?? [])
110
+ route(routes.types, 'type', name, schema);
111
+ }
112
+ return routes;
113
+ }
114
+ if (!options.schema) {
115
+ throw new Error('qualifyUnqualified: one of `schema` or `targets` is required');
116
+ }
117
+ const inventory = options.inventory ?? collectCreatedObjects(sql);
118
+ for (const name of inventory.relations)
119
+ routes.relations.set(name, options.schema);
120
+ for (const name of inventory.functions)
121
+ routes.functions.set(name, options.schema);
122
+ for (const name of inventory.types)
123
+ routes.types.set(name, options.schema);
124
+ return routes;
125
+ }
126
+ function collectCteNames(stmt) {
127
+ const names = new Set();
128
+ (0, traverse_1.walk)(stmt, {
129
+ CommonTableExpr: (path) => {
130
+ if (path.node?.ctename)
131
+ names.add(path.node.ctename);
132
+ }
133
+ });
134
+ return names;
135
+ }
136
+ /**
137
+ * Qualify unqualified references inside a LANGUAGE sql function body string.
138
+ * (`AS $$...$$` is a String under the 'as' DefElem; only PL/pgSQL bodies are
139
+ * exposed as a hydrated AST, so sql-language bodies need their own parse.)
140
+ */
141
+ function qualifySqlBodyString(body, routes, result) {
142
+ const parseResult = (0, plpgsql_parser_1.parseSql)(body);
143
+ const stmts = parseResult?.stmts ?? [];
144
+ if (stmts.length === 0)
145
+ return body;
146
+ const pieces = [];
147
+ for (const stmt of stmts) {
148
+ if (!stmt?.stmt)
149
+ continue;
150
+ const visitor = createQualifyVisitor({ routes, cteNames: collectCteNames(stmt.stmt) }, result);
151
+ (0, traverse_1.walk)(stmt.stmt, visitor);
152
+ pieces.push(plpgsql_parser_1.Deparser.deparse(stmt.stmt));
153
+ }
154
+ return pieces.join(';\n');
155
+ }
156
+ function schemaStringNode(schema) {
157
+ return { String: { sval: schema } };
158
+ }
159
+ /**
160
+ * Create a SQL AST visitor that qualifies unqualified references against a
161
+ * routing table. Composable with the walkers used by the core transform.
162
+ */
163
+ function createQualifyVisitor(options, result) {
164
+ const { routes } = options;
165
+ const cteNames = options.cteNames ?? new Set();
166
+ const typeSchemaOf = (name) => routes.types.get(name) ?? routes.relations.get(name);
167
+ const record = (name, schema) => {
168
+ result.qualified.set(name, (result.qualified.get(name) ?? 0) + 1);
169
+ result.routed.set(name, schema);
170
+ };
171
+ const qualifyNameList = (names, schemaOf) => {
172
+ if (!names || names.length !== 1)
173
+ return;
174
+ const name = names[0]?.String?.sval;
175
+ if (!name)
176
+ return;
177
+ const schema = schemaOf(name);
178
+ if (schema) {
179
+ names.unshift(schemaStringNode(schema));
180
+ record(name, schema);
181
+ }
182
+ };
183
+ const functionSchemaOf = (name) => routes.functions.get(name);
184
+ return {
185
+ RangeVar: (path) => {
186
+ const node = path.node;
187
+ if (!node.schemaname && node.relname && !cteNames.has(node.relname)) {
188
+ const schema = routes.relations.get(node.relname);
189
+ if (schema) {
190
+ node.schemaname = schema;
191
+ record(node.relname, schema);
192
+ }
193
+ }
194
+ },
195
+ FuncCall: (path) => {
196
+ qualifyNameList(path.node.funcname, functionSchemaOf);
197
+ },
198
+ CallStmt: (path) => {
199
+ // The walker does not auto-recurse into CallStmt.funccall.
200
+ if (path.node.funccall) {
201
+ qualifyNameList(path.node.funccall.funcname, functionSchemaOf);
202
+ }
203
+ },
204
+ TypeName: (path) => {
205
+ qualifyNameList(path.node.names, typeSchemaOf);
206
+ },
207
+ CreateFunctionStmt: (path) => {
208
+ const node = path.node;
209
+ qualifyNameList(node.funcname, functionSchemaOf);
210
+ // LANGUAGE sql bodies are opaque strings to the walker; parse and
211
+ // qualify them separately (plpgsql bodies ride the hydrated AST walk).
212
+ const isPlpgsql = (node.options ?? []).some((opt) => opt?.DefElem?.defname === 'language' &&
213
+ opt.DefElem.arg?.String?.sval === 'plpgsql');
214
+ if (!isPlpgsql && Array.isArray(node.options)) {
215
+ for (const opt of node.options) {
216
+ if (opt?.DefElem?.defname === 'as' && opt.DefElem.arg?.List?.items) {
217
+ for (const item of opt.DefElem.arg.List.items) {
218
+ if (typeof item?.String?.sval === 'string') {
219
+ try {
220
+ item.String.sval = qualifySqlBodyString(item.String.sval, routes, result);
221
+ }
222
+ catch {
223
+ // Not parseable standalone (e.g. C symbol names) — leave as-is.
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ },
231
+ CreateTrigStmt: (path) => {
232
+ qualifyNameList(path.node.funcname, functionSchemaOf);
233
+ },
234
+ ObjectWithArgs: (path) => {
235
+ qualifyNameList(path.node.objname, functionSchemaOf);
236
+ },
237
+ CreateEnumStmt: (path) => {
238
+ qualifyNameList(path.node.typeName, name => routes.types.get(name));
239
+ },
240
+ CreateRangeStmt: (path) => {
241
+ qualifyNameList(path.node.typeName, name => routes.types.get(name));
242
+ },
243
+ CreateDomainStmt: (path) => {
244
+ qualifyNameList(path.node.domainname, name => routes.types.get(name));
245
+ }
246
+ };
247
+ }
248
+ /**
249
+ * Qualify unqualified references in a SQL string.
250
+ *
251
+ * Two forms:
252
+ * - `{ schema, inventory? }` — pin every inventoried name to one schema.
253
+ * - `{ targets }` — route specific objects to specific schemas in one pass.
254
+ *
255
+ * Runs as a standalone AST pass (parse → qualify → deparse). To combine with
256
+ * a schema rename, run this first (typically with `schema: 'public'`) and
257
+ * then map via the core transform — or use the `qualifyUnqualified` option on
258
+ * `transformSql`, which does exactly that.
259
+ */
260
+ function qualifyUnqualified(sql, options) {
261
+ const result = { qualified: new Map(), routed: new Map() };
262
+ const routes = resolveRoutes(sql, options);
263
+ let qualified = (0, plpgsql_parser_1.transformSync)(sql, (ctx) => {
264
+ const stmts = ctx.sql?.stmts ?? [];
265
+ for (const stmt of stmts) {
266
+ if (stmt?.stmt) {
267
+ const visitor = createQualifyVisitor({ routes, cteNames: collectCteNames(stmt.stmt) }, result);
268
+ (0, traverse_1.walk)(stmt.stmt, visitor);
269
+ }
270
+ }
271
+ const bodyVisitor = createQualifyVisitor({ routes }, result);
272
+ for (const fn of ctx.functions) {
273
+ if (fn.plpgsql?.hydrated) {
274
+ (0, plpgsql_parser_1.walk)(fn.plpgsql.hydrated, {}, {
275
+ walkSqlExpressions: true,
276
+ sqlVisitor: bodyVisitor
277
+ });
278
+ }
279
+ }
280
+ }, { hydrate: true, pretty: true });
281
+ if (options.injectCreateSchema) {
282
+ const targetSchemas = new Set([
283
+ ...routes.relations.values(),
284
+ ...routes.functions.values(),
285
+ ...routes.types.values()
286
+ ]);
287
+ const created = new Set();
288
+ for (const facts of (0, facts_1.classifyStatements)(qualified)) {
289
+ if (facts.nodeTag === 'CreateSchemaStmt') {
290
+ for (const createdSchema of facts.creates)
291
+ created.add(createdSchema.name);
292
+ }
293
+ }
294
+ const missing = [...targetSchemas].filter(schema => !created.has(schema)).sort();
295
+ if (missing.length > 0) {
296
+ const prelude = missing
297
+ .map(schema => `CREATE SCHEMA IF NOT EXISTS ${schema};`)
298
+ .join('\n');
299
+ qualified = `${prelude}\n\n${qualified}`;
300
+ }
301
+ }
302
+ return { sql: qualified, result };
303
+ }