@pgsql/transform 18.4.1 → 18.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/extension-router.js +236 -0
- package/esm/extension-transform.js +202 -0
- package/esm/facts.js +95 -1
- package/esm/index.js +4 -0
- package/esm/role-router.js +62 -0
- package/esm/role-transform.js +129 -0
- package/extension-router.d.ts +161 -0
- package/extension-router.js +240 -0
- package/extension-transform.d.ts +48 -0
- package/extension-transform.js +207 -0
- package/facts.d.ts +37 -1
- package/facts.js +94 -0
- package/index.d.ts +9 -1
- package/index.js +14 -1
- package/package.json +2 -2
- package/role-router.d.ts +42 -0
- package/role-router.js +66 -0
- package/role-transform.d.ts +50 -0
- package/role-transform.js +134 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AST transform that applies a {@link RoleRouter}.
|
|
4
|
+
*
|
|
5
|
+
* Role identifiers surface in many statement forms. Most flow through a single
|
|
6
|
+
* `RoleSpec` node (grantees, ownership `... OWNER TO`, `CREATE POLICY ... TO`,
|
|
7
|
+
* `ALTER DEFAULT PRIVILEGES [FOR ROLE ...] ... TO`, `ALTER ROLE`, `DROP ROLE`,
|
|
8
|
+
* role-membership grantees, `GRANTED BY`, `REASSIGN OWNED BY`), which the
|
|
9
|
+
* traversal reaches automatically. Four positions carry a role name outside a
|
|
10
|
+
* `RoleSpec` and are handled explicitly:
|
|
11
|
+
*
|
|
12
|
+
* - `CREATE ROLE <name>` — a bare string on `CreateRoleStmt.role`.
|
|
13
|
+
* - `GRANT <role> TO ...` — the *granted* roles are `AccessPriv.priv_name`
|
|
14
|
+
* strings on `GrantRoleStmt.granted_roles` (not `RoleSpec`s).
|
|
15
|
+
* - `SET ROLE` / `SET SESSION AUTHORIZATION` — the role is a string constant
|
|
16
|
+
* in `VariableSetStmt.args`.
|
|
17
|
+
* - `ALTER ROLE <old> RENAME TO <new>` — `RenameStmt` with string
|
|
18
|
+
* `subname`/`newname`.
|
|
19
|
+
*
|
|
20
|
+
* Reserved role specifications (`PUBLIC`, `CURRENT_USER`, `SESSION_USER`, ...)
|
|
21
|
+
* carry no `rolename` and a non-`CSTRING` `roletype`, so they are never
|
|
22
|
+
* rewritten.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.createRoleResult = createRoleResult;
|
|
26
|
+
exports.createRoleVisitor = createRoleVisitor;
|
|
27
|
+
exports.transformRoles = transformRoles;
|
|
28
|
+
const traverse_1 = require("@pgsql/traverse");
|
|
29
|
+
const plpgsql_parser_1 = require("plpgsql-parser");
|
|
30
|
+
const role_router_1 = require("./role-router");
|
|
31
|
+
function createRoleResult() {
|
|
32
|
+
return { rolesRenamed: new Map() };
|
|
33
|
+
}
|
|
34
|
+
const ROLE_SET_PARAMS = new Set(['role', 'session_authorization']);
|
|
35
|
+
/**
|
|
36
|
+
* Create a SQL AST visitor that renames role identifiers against a router.
|
|
37
|
+
* Composable with the walkers used by the core transform.
|
|
38
|
+
*/
|
|
39
|
+
function createRoleVisitor(router, result) {
|
|
40
|
+
const record = (name) => {
|
|
41
|
+
result.rolesRenamed.set(name, (result.rolesRenamed.get(name) ?? 0) + 1);
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
RoleSpec: (path) => {
|
|
45
|
+
const node = path.node;
|
|
46
|
+
// Only named roles (ROLESPEC_CSTRING) carry a rolename; PUBLIC /
|
|
47
|
+
// CURRENT_USER / SESSION_USER are encoded by roletype and left alone.
|
|
48
|
+
if (node.roletype !== 'ROLESPEC_CSTRING')
|
|
49
|
+
return;
|
|
50
|
+
const target = router.resolve(node.rolename);
|
|
51
|
+
if (target === undefined)
|
|
52
|
+
return;
|
|
53
|
+
node.rolename = target;
|
|
54
|
+
record(node.rolename);
|
|
55
|
+
},
|
|
56
|
+
CreateRoleStmt: (path) => {
|
|
57
|
+
const node = path.node;
|
|
58
|
+
const target = router.resolve(node.role);
|
|
59
|
+
if (target === undefined)
|
|
60
|
+
return;
|
|
61
|
+
node.role = target;
|
|
62
|
+
record(target);
|
|
63
|
+
},
|
|
64
|
+
GrantRoleStmt: (path) => {
|
|
65
|
+
// granted_roles are role names carried as AccessPriv.priv_name (the
|
|
66
|
+
// grantee_roles RoleSpecs are handled by the RoleSpec visitor).
|
|
67
|
+
const granted = path.node.granted_roles;
|
|
68
|
+
if (!Array.isArray(granted))
|
|
69
|
+
return;
|
|
70
|
+
for (const g of granted) {
|
|
71
|
+
const priv = g?.AccessPriv;
|
|
72
|
+
if (!priv || typeof priv.priv_name !== 'string')
|
|
73
|
+
continue;
|
|
74
|
+
const target = router.resolve(priv.priv_name);
|
|
75
|
+
if (target === undefined)
|
|
76
|
+
continue;
|
|
77
|
+
priv.priv_name = target;
|
|
78
|
+
record(target);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
VariableSetStmt: (path) => {
|
|
82
|
+
const node = path.node;
|
|
83
|
+
if (!ROLE_SET_PARAMS.has(node.name) || !Array.isArray(node.args))
|
|
84
|
+
return;
|
|
85
|
+
for (const arg of node.args) {
|
|
86
|
+
const sval = arg?.A_Const?.sval?.sval ?? arg?.String?.sval;
|
|
87
|
+
if (typeof sval !== 'string')
|
|
88
|
+
continue;
|
|
89
|
+
const target = router.resolve(sval);
|
|
90
|
+
if (target === undefined)
|
|
91
|
+
continue;
|
|
92
|
+
if (arg.A_Const?.sval)
|
|
93
|
+
arg.A_Const.sval.sval = target;
|
|
94
|
+
else if (arg.String)
|
|
95
|
+
arg.String.sval = target;
|
|
96
|
+
record(target);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
RenameStmt: (path) => {
|
|
100
|
+
const node = path.node;
|
|
101
|
+
if (node.renameType !== 'OBJECT_ROLE')
|
|
102
|
+
return;
|
|
103
|
+
const subTarget = router.resolve(node.subname);
|
|
104
|
+
if (subTarget !== undefined) {
|
|
105
|
+
node.subname = subTarget;
|
|
106
|
+
record(subTarget);
|
|
107
|
+
}
|
|
108
|
+
const newTarget = router.resolve(node.newname);
|
|
109
|
+
if (newTarget !== undefined) {
|
|
110
|
+
node.newname = newTarget;
|
|
111
|
+
record(newTarget);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Apply role renaming to a SQL string: parse -> walk -> deparse. Role names in
|
|
118
|
+
* PL/pgSQL / `LANGUAGE sql` bodies are string literals (e.g. inside dynamic
|
|
119
|
+
* `EXECUTE`) and are intentionally not rewritten — see the module docs on why
|
|
120
|
+
* arbitrary string contents are out of scope.
|
|
121
|
+
*/
|
|
122
|
+
function transformRoles(sql, router) {
|
|
123
|
+
const resolved = role_router_1.RoleRouter.from(router);
|
|
124
|
+
const result = createRoleResult();
|
|
125
|
+
const out = (0, plpgsql_parser_1.transformSync)(sql, (ctx) => {
|
|
126
|
+
const stmts = ctx.sql?.stmts ?? [];
|
|
127
|
+
const visitor = createRoleVisitor(resolved, result);
|
|
128
|
+
for (const stmt of stmts) {
|
|
129
|
+
if (stmt?.stmt)
|
|
130
|
+
(0, traverse_1.walk)(stmt.stmt, visitor);
|
|
131
|
+
}
|
|
132
|
+
}, { hydrate: true, pretty: true });
|
|
133
|
+
return { sql: out, result };
|
|
134
|
+
}
|