@pgsql/transform 18.1.0 → 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.
- package/README.md +29 -73
- package/categorize.d.ts +52 -0
- package/categorize.js +56 -0
- package/esm/categorize.js +51 -0
- package/esm/facts.js +289 -0
- package/esm/fixture-closure.js +197 -0
- package/esm/index.js +7 -22
- package/esm/qualify.js +297 -0
- package/esm/round-trip-core.js +126 -0
- package/esm/round-trip.js +116 -0
- package/esm/transform.js +1169 -0
- package/facts.d.ts +69 -0
- package/facts.js +292 -0
- package/fixture-closure.d.ts +71 -0
- package/fixture-closure.js +200 -0
- package/index.d.ts +14 -28
- package/index.js +43 -76
- package/package.json +22 -14
- package/qualify.d.ts +125 -0
- package/qualify.js +303 -0
- package/round-trip-core.d.ts +60 -0
- package/round-trip-core.js +133 -0
- package/round-trip.d.ts +47 -0
- package/round-trip.js +123 -0
- package/transform.d.ts +259 -0
- package/transform.js +1190 -0
- package/13/enums.d.ts +0 -62
- package/13/enums.js +0 -2
- package/13/types.d.ts +0 -2103
- package/13/types.js +0 -2
- package/14/enums.d.ts +0 -62
- package/14/enums.js +0 -2
- package/14/types.d.ts +0 -2164
- package/14/types.js +0 -2
- package/15/enums.d.ts +0 -64
- package/15/enums.js +0 -2
- package/15/types.d.ts +0 -2222
- package/15/types.js +0 -2
- package/16/enums.d.ts +0 -69
- package/16/enums.js +0 -2
- package/16/types.d.ts +0 -2328
- package/16/types.js +0 -2
- package/17/enums.d.ts +0 -76
- package/17/enums.js +0 -2
- package/17/types.d.ts +0 -2488
- package/17/types.js +0 -2
- package/18/enums.d.ts +0 -78
- package/18/enums.js +0 -2
- package/18/types.d.ts +0 -2538
- package/18/types.js +0 -2
- package/esm/13/enums.js +0 -1
- package/esm/13/types.js +0 -1
- package/esm/14/enums.js +0 -1
- package/esm/14/types.js +0 -1
- package/esm/15/enums.js +0 -1
- package/esm/15/types.js +0 -1
- package/esm/16/enums.js +0 -1
- package/esm/16/types.js +0 -1
- package/esm/17/enums.js +0 -1
- package/esm/17/types.js +0 -1
- package/esm/18/enums.js +0 -1
- package/esm/18/types.js +0 -1
- package/esm/multi-version-transformer.js +0 -80
- package/esm/transformers/context.js +0 -1
- package/esm/transformers/v13-to-v14.js +0 -2767
- package/esm/transformers/v14-to-v15.js +0 -1218
- package/esm/transformers/v15-to-v16.js +0 -2935
- package/esm/transformers/v16-to-v17.js +0 -1526
- package/esm/transformers/v17-to-v18.js +0 -204
- package/esm/transformers-direct/index.js +0 -13
- package/esm/transformers-direct/v13-to-v17/index.js +0 -74
- package/esm/transformers-direct/v13-to-v18/index.js +0 -84
- package/esm/transformers-direct/v14-to-v17/index.js +0 -63
- package/esm/transformers-direct/v14-to-v18/index.js +0 -73
- package/esm/transformers-direct/v15-to-v17/index.js +0 -53
- package/esm/transformers-direct/v15-to-v18/index.js +0 -62
- package/esm/transformers-direct/v16-to-v17/index.js +0 -40
- package/esm/transformers-direct/v16-to-v18/index.js +0 -51
- package/esm/transformers-direct/v17-to-v18/index.js +0 -39
- package/multi-version-transformer.d.ts +0 -14
- package/multi-version-transformer.js +0 -84
- package/transformers/context.d.ts +0 -4
- package/transformers/context.js +0 -2
- package/transformers/v13-to-v14.d.ts +0 -255
- package/transformers/v13-to-v14.js +0 -2771
- package/transformers/v14-to-v15.d.ts +0 -615
- package/transformers/v14-to-v15.js +0 -1222
- package/transformers/v15-to-v16.d.ts +0 -632
- package/transformers/v15-to-v16.js +0 -2939
- package/transformers/v16-to-v17.d.ts +0 -640
- package/transformers/v16-to-v17.js +0 -1530
- package/transformers/v17-to-v18.d.ts +0 -54
- package/transformers/v17-to-v18.js +0 -208
- package/transformers-direct/index.d.ts +0 -13
- package/transformers-direct/index.js +0 -25
- package/transformers-direct/v13-to-v17/index.d.ts +0 -27
- package/transformers-direct/v13-to-v17/index.js +0 -78
- package/transformers-direct/v13-to-v18/index.d.ts +0 -28
- package/transformers-direct/v13-to-v18/index.js +0 -88
- package/transformers-direct/v14-to-v17/index.d.ts +0 -26
- package/transformers-direct/v14-to-v17/index.js +0 -67
- package/transformers-direct/v14-to-v18/index.d.ts +0 -27
- package/transformers-direct/v14-to-v18/index.js +0 -77
- package/transformers-direct/v15-to-v17/index.d.ts +0 -25
- package/transformers-direct/v15-to-v17/index.js +0 -57
- package/transformers-direct/v15-to-v18/index.d.ts +0 -26
- package/transformers-direct/v15-to-v18/index.js +0 -66
- package/transformers-direct/v16-to-v17/index.d.ts +0 -24
- package/transformers-direct/v16-to-v17/index.js +0 -44
- package/transformers-direct/v16-to-v18/index.d.ts +0 -25
- package/transformers-direct/v16-to-v18/index.js +0 -55
- package/transformers-direct/v17-to-v18/index.d.ts +0 -24
- 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.
|
|
37
|
-
|
|
38
|
-
exports.
|
|
39
|
-
|
|
40
|
-
exports.
|
|
41
|
-
|
|
42
|
-
exports.
|
|
43
|
-
|
|
44
|
-
exports.
|
|
45
|
-
|
|
46
|
-
exports.
|
|
47
|
-
|
|
48
|
-
exports.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Object.defineProperty(exports, "
|
|
53
|
-
|
|
54
|
-
Object.defineProperty(exports, "
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Object.defineProperty(exports, "
|
|
59
|
-
|
|
60
|
-
Object.defineProperty(exports, "
|
|
61
|
-
var
|
|
62
|
-
Object.defineProperty(exports, "
|
|
63
|
-
|
|
64
|
-
Object.defineProperty(exports, "
|
|
65
|
-
|
|
66
|
-
Object.defineProperty(exports, "
|
|
67
|
-
|
|
68
|
-
Object.defineProperty(exports, "
|
|
69
|
-
|
|
70
|
-
Object.defineProperty(exports, "
|
|
71
|
-
|
|
72
|
-
Object.defineProperty(exports, "
|
|
73
|
-
|
|
74
|
-
Object.defineProperty(exports, "
|
|
75
|
-
|
|
76
|
-
Object.defineProperty(exports, "
|
|
77
|
-
|
|
78
|
-
Object.defineProperty(exports, "
|
|
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.
|
|
3
|
+
"version": "18.3.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
|
-
"description": "
|
|
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
|
|
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
|
-
"
|
|
33
|
-
"
|
|
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
|
-
"
|
|
37
|
-
"@pgsql/types": "^18.0.0",
|
|
38
|
-
"libpg-query": "18.1.1",
|
|
39
|
-
"makage": "^0.1.8",
|
|
40
|
-
"pg-proto-parser": "1.31.0",
|
|
41
|
-
"pgsql-deparser": "18.1.0"
|
|
40
|
+
"makage": "^0.1.8"
|
|
42
41
|
},
|
|
43
|
-
"keywords": [
|
|
44
|
-
|
|
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
|
+
}
|