@pgsql/transform 18.5.0 → 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.
@@ -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
+ }