@pgsql/transform 18.5.0 → 18.7.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 +54 -0
- package/esm/index.js +4 -0
- package/esm/role-router.js +62 -0
- package/esm/role-transform.js +129 -0
- package/esm/router.js +69 -5
- package/esm/transform.js +32 -9
- 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 +54 -0
- package/index.d.ts +10 -2
- 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
- package/router.d.ts +63 -6
- package/router.js +69 -5
- package/transform.js +32 -9
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST transform that applies a {@link RoleRouter}.
|
|
3
|
+
*
|
|
4
|
+
* Role identifiers surface in many statement forms. Most flow through a single
|
|
5
|
+
* `RoleSpec` node (grantees, ownership `... OWNER TO`, `CREATE POLICY ... TO`,
|
|
6
|
+
* `ALTER DEFAULT PRIVILEGES [FOR ROLE ...] ... TO`, `ALTER ROLE`, `DROP ROLE`,
|
|
7
|
+
* role-membership grantees, `GRANTED BY`, `REASSIGN OWNED BY`), which the
|
|
8
|
+
* traversal reaches automatically. Four positions carry a role name outside a
|
|
9
|
+
* `RoleSpec` and are handled explicitly:
|
|
10
|
+
*
|
|
11
|
+
* - `CREATE ROLE <name>` — a bare string on `CreateRoleStmt.role`.
|
|
12
|
+
* - `GRANT <role> TO ...` — the *granted* roles are `AccessPriv.priv_name`
|
|
13
|
+
* strings on `GrantRoleStmt.granted_roles` (not `RoleSpec`s).
|
|
14
|
+
* - `SET ROLE` / `SET SESSION AUTHORIZATION` — the role is a string constant
|
|
15
|
+
* in `VariableSetStmt.args`.
|
|
16
|
+
* - `ALTER ROLE <old> RENAME TO <new>` — `RenameStmt` with string
|
|
17
|
+
* `subname`/`newname`.
|
|
18
|
+
*
|
|
19
|
+
* Reserved role specifications (`PUBLIC`, `CURRENT_USER`, `SESSION_USER`, ...)
|
|
20
|
+
* carry no `rolename` and a non-`CSTRING` `roletype`, so they are never
|
|
21
|
+
* rewritten.
|
|
22
|
+
*/
|
|
23
|
+
import { walk as walkSql } from '@pgsql/traverse';
|
|
24
|
+
import { transformSync } from 'plpgsql-parser';
|
|
25
|
+
import { RoleRouter } from './role-router';
|
|
26
|
+
export function createRoleResult() {
|
|
27
|
+
return { rolesRenamed: new Map() };
|
|
28
|
+
}
|
|
29
|
+
const ROLE_SET_PARAMS = new Set(['role', 'session_authorization']);
|
|
30
|
+
/**
|
|
31
|
+
* Create a SQL AST visitor that renames role identifiers against a router.
|
|
32
|
+
* Composable with the walkers used by the core transform.
|
|
33
|
+
*/
|
|
34
|
+
export function createRoleVisitor(router, result) {
|
|
35
|
+
const record = (name) => {
|
|
36
|
+
result.rolesRenamed.set(name, (result.rolesRenamed.get(name) ?? 0) + 1);
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
RoleSpec: (path) => {
|
|
40
|
+
const node = path.node;
|
|
41
|
+
// Only named roles (ROLESPEC_CSTRING) carry a rolename; PUBLIC /
|
|
42
|
+
// CURRENT_USER / SESSION_USER are encoded by roletype and left alone.
|
|
43
|
+
if (node.roletype !== 'ROLESPEC_CSTRING')
|
|
44
|
+
return;
|
|
45
|
+
const target = router.resolve(node.rolename);
|
|
46
|
+
if (target === undefined)
|
|
47
|
+
return;
|
|
48
|
+
node.rolename = target;
|
|
49
|
+
record(node.rolename);
|
|
50
|
+
},
|
|
51
|
+
CreateRoleStmt: (path) => {
|
|
52
|
+
const node = path.node;
|
|
53
|
+
const target = router.resolve(node.role);
|
|
54
|
+
if (target === undefined)
|
|
55
|
+
return;
|
|
56
|
+
node.role = target;
|
|
57
|
+
record(target);
|
|
58
|
+
},
|
|
59
|
+
GrantRoleStmt: (path) => {
|
|
60
|
+
// granted_roles are role names carried as AccessPriv.priv_name (the
|
|
61
|
+
// grantee_roles RoleSpecs are handled by the RoleSpec visitor).
|
|
62
|
+
const granted = path.node.granted_roles;
|
|
63
|
+
if (!Array.isArray(granted))
|
|
64
|
+
return;
|
|
65
|
+
for (const g of granted) {
|
|
66
|
+
const priv = g?.AccessPriv;
|
|
67
|
+
if (!priv || typeof priv.priv_name !== 'string')
|
|
68
|
+
continue;
|
|
69
|
+
const target = router.resolve(priv.priv_name);
|
|
70
|
+
if (target === undefined)
|
|
71
|
+
continue;
|
|
72
|
+
priv.priv_name = target;
|
|
73
|
+
record(target);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
VariableSetStmt: (path) => {
|
|
77
|
+
const node = path.node;
|
|
78
|
+
if (!ROLE_SET_PARAMS.has(node.name) || !Array.isArray(node.args))
|
|
79
|
+
return;
|
|
80
|
+
for (const arg of node.args) {
|
|
81
|
+
const sval = arg?.A_Const?.sval?.sval ?? arg?.String?.sval;
|
|
82
|
+
if (typeof sval !== 'string')
|
|
83
|
+
continue;
|
|
84
|
+
const target = router.resolve(sval);
|
|
85
|
+
if (target === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
if (arg.A_Const?.sval)
|
|
88
|
+
arg.A_Const.sval.sval = target;
|
|
89
|
+
else if (arg.String)
|
|
90
|
+
arg.String.sval = target;
|
|
91
|
+
record(target);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
RenameStmt: (path) => {
|
|
95
|
+
const node = path.node;
|
|
96
|
+
if (node.renameType !== 'OBJECT_ROLE')
|
|
97
|
+
return;
|
|
98
|
+
const subTarget = router.resolve(node.subname);
|
|
99
|
+
if (subTarget !== undefined) {
|
|
100
|
+
node.subname = subTarget;
|
|
101
|
+
record(subTarget);
|
|
102
|
+
}
|
|
103
|
+
const newTarget = router.resolve(node.newname);
|
|
104
|
+
if (newTarget !== undefined) {
|
|
105
|
+
node.newname = newTarget;
|
|
106
|
+
record(newTarget);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Apply role renaming to a SQL string: parse -> walk -> deparse. Role names in
|
|
113
|
+
* PL/pgSQL / `LANGUAGE sql` bodies are string literals (e.g. inside dynamic
|
|
114
|
+
* `EXECUTE`) and are intentionally not rewritten — see the module docs on why
|
|
115
|
+
* arbitrary string contents are out of scope.
|
|
116
|
+
*/
|
|
117
|
+
export function transformRoles(sql, router) {
|
|
118
|
+
const resolved = RoleRouter.from(router);
|
|
119
|
+
const result = createRoleResult();
|
|
120
|
+
const out = transformSync(sql, (ctx) => {
|
|
121
|
+
const stmts = ctx.sql?.stmts ?? [];
|
|
122
|
+
const visitor = createRoleVisitor(resolved, result);
|
|
123
|
+
for (const stmt of stmts) {
|
|
124
|
+
if (stmt?.stmt)
|
|
125
|
+
walkSql(stmt.stmt, visitor);
|
|
126
|
+
}
|
|
127
|
+
}, { hydrate: true, pretty: true });
|
|
128
|
+
return { sql: out, result };
|
|
129
|
+
}
|
package/esm/router.js
CHANGED
|
@@ -20,6 +20,19 @@
|
|
|
20
20
|
* `types` — matching `pg_class` / `pg_proc` / `pg_type`), mirroring the routing
|
|
21
21
|
* model already used by {@link qualifyUnqualified}. Resolution is
|
|
22
22
|
* object-route-first, then the schema-level default, then "leave unchanged".
|
|
23
|
+
*
|
|
24
|
+
* An object route may also *rebind* — change the object's **name**, not just
|
|
25
|
+
* the schema it lives in. Routing preserves identity (the same function, a new
|
|
26
|
+
* address); rebinding repoints a reference at a *different* object, which is
|
|
27
|
+
* what lets one implementation of a contract be substituted for another:
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* { auth: { functions: { uid: { schema: null, name: 'current_user_id' } } } }
|
|
31
|
+
* // auth.uid() -> current_user_id()
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* A `null` target schema de-qualifies the reference (relying on `search_path`),
|
|
35
|
+
* matching the convention used by the extension router.
|
|
23
36
|
*/
|
|
24
37
|
const NS_BUCKET = {
|
|
25
38
|
relation: 'relations',
|
|
@@ -71,6 +84,36 @@ export class SchemaRouter {
|
|
|
71
84
|
}
|
|
72
85
|
return false;
|
|
73
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* True when any object route changes a name or de-qualifies (rather than
|
|
89
|
+
* only moving between schemas). Such rewrites cannot be expressed by the
|
|
90
|
+
* string-level passes at all, so callers use this to require the AST path.
|
|
91
|
+
*/
|
|
92
|
+
hasNameRebinds() {
|
|
93
|
+
return this.nameRebinds().length > 0;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Every object route that rebinds a name or de-qualifies, keyed by source
|
|
97
|
+
* schema and namespace. Callers use this to report or verify substitutions.
|
|
98
|
+
*/
|
|
99
|
+
nameRebinds() {
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const [schema, route] of this.routes) {
|
|
102
|
+
for (const ns of ['relation', 'function', 'type']) {
|
|
103
|
+
const bucket = route[NS_BUCKET[ns]];
|
|
104
|
+
if (!bucket)
|
|
105
|
+
continue;
|
|
106
|
+
for (const [from, target] of Object.entries(bucket)) {
|
|
107
|
+
if (typeof target === 'string')
|
|
108
|
+
continue;
|
|
109
|
+
if (target.name === undefined && target.schema !== null)
|
|
110
|
+
continue;
|
|
111
|
+
out.push({ schema, ns, from, to: target });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
74
117
|
/** Every source schema this router may touch. */
|
|
75
118
|
sourceSchemas() {
|
|
76
119
|
return [...this.routes.keys()];
|
|
@@ -82,18 +125,39 @@ export class SchemaRouter {
|
|
|
82
125
|
* to any object without a specific route.
|
|
83
126
|
*/
|
|
84
127
|
resolve(sourceSchema, name, ns = 'unknown') {
|
|
128
|
+
// A `null` target de-qualifies the reference; the schema-only API cannot
|
|
129
|
+
// express that, so it reads as "unchanged" here.
|
|
130
|
+
return this.resolveObject(sourceSchema, name, ns)?.schema ?? undefined;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Resolve the full target for `(sourceSchema, name)` in namespace `ns` — both
|
|
134
|
+
* the schema the reference should live in and, when the route rebinds, the
|
|
135
|
+
* name it should be reached by. Returns `undefined` to leave it unchanged.
|
|
136
|
+
*
|
|
137
|
+
* In the result, `schema` is `null` when the reference should become
|
|
138
|
+
* unqualified and `undefined` when only the name changes; `name` is
|
|
139
|
+
* `undefined` when only the schema changes.
|
|
140
|
+
*/
|
|
141
|
+
resolveObject(sourceSchema, name, ns = 'unknown') {
|
|
85
142
|
if (!sourceSchema)
|
|
86
143
|
return undefined;
|
|
87
144
|
const route = this.routes.get(sourceSchema);
|
|
88
145
|
if (!route)
|
|
89
146
|
return undefined;
|
|
90
147
|
if (name && (ns === 'relation' || ns === 'function' || ns === 'type')) {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
const target = route[NS_BUCKET[ns]]?.[name];
|
|
149
|
+
if (target !== undefined) {
|
|
150
|
+
if (typeof target === 'string')
|
|
151
|
+
return { schema: target };
|
|
152
|
+
// An object route naming no schema inherits the schema-level default,
|
|
153
|
+
// so a pure rebind leaves placement alone.
|
|
154
|
+
return {
|
|
155
|
+
schema: target.schema !== undefined ? target.schema : route.schema,
|
|
156
|
+
name: target.name
|
|
157
|
+
};
|
|
158
|
+
}
|
|
95
159
|
}
|
|
96
|
-
return route.schema;
|
|
160
|
+
return route.schema !== undefined ? { schema: route.schema } : undefined;
|
|
97
161
|
}
|
|
98
162
|
/**
|
|
99
163
|
* Source schemas whose *every* object is guaranteed to move — i.e. those
|
package/esm/transform.js
CHANGED
|
@@ -80,12 +80,24 @@ export function transformNameList(names, schemaMapping, result, ns = 'unknown')
|
|
|
80
80
|
const first = names[0];
|
|
81
81
|
if (first?.String?.sval) {
|
|
82
82
|
const schemaName = first.String.sval;
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
83
|
+
const last = names[names.length - 1];
|
|
84
|
+
const objName = last?.String?.sval;
|
|
85
|
+
const target = router.resolveObject(schemaName, objName, ns);
|
|
86
|
+
if (!target)
|
|
87
|
+
return;
|
|
88
|
+
if (target.name !== undefined && last?.String?.sval) {
|
|
86
89
|
result.schemasFound.add(schemaName);
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
last.String.sval = target.name;
|
|
91
|
+
}
|
|
92
|
+
if (target.schema === null) {
|
|
93
|
+
// De-qualify: drop the schema element and rely on search_path.
|
|
94
|
+
result.schemasFound.add(schemaName);
|
|
95
|
+
names.splice(0, 1);
|
|
96
|
+
}
|
|
97
|
+
else if (target.schema && target.schema !== schemaName) {
|
|
98
|
+
result.schemasFound.add(schemaName);
|
|
99
|
+
first.String.sval = target.schema;
|
|
100
|
+
result.schemasTransformed.set(schemaName, target.schema);
|
|
89
101
|
}
|
|
90
102
|
}
|
|
91
103
|
}
|
|
@@ -117,11 +129,22 @@ export function transformRelation(relation, schemaMapping, result) {
|
|
|
117
129
|
const oldName = relation.schemaname;
|
|
118
130
|
// A RangeVar names a relation (table/view/sequence/matview); route by the
|
|
119
131
|
// relation name so object-level routes can send it to its own schema.
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
132
|
+
const target = asRouter(schemaMapping).resolveObject(oldName, relation.relname, 'relation');
|
|
133
|
+
if (!target)
|
|
134
|
+
return;
|
|
135
|
+
if (target.name !== undefined && relation.relname) {
|
|
136
|
+
result.schemasFound.add(oldName);
|
|
137
|
+
relation.relname = target.name;
|
|
138
|
+
}
|
|
139
|
+
if (target.schema === null) {
|
|
140
|
+
// De-qualify: drop the schema qualifier and rely on search_path.
|
|
141
|
+
result.schemasFound.add(oldName);
|
|
142
|
+
delete relation.schemaname;
|
|
143
|
+
}
|
|
144
|
+
else if (target.schema && target.schema !== oldName) {
|
|
122
145
|
result.schemasFound.add(oldName);
|
|
123
|
-
relation.schemaname =
|
|
124
|
-
result.schemasTransformed.set(oldName,
|
|
146
|
+
relation.schemaname = target.schema;
|
|
147
|
+
result.schemasTransformed.set(oldName, target.schema);
|
|
125
148
|
}
|
|
126
149
|
}
|
|
127
150
|
/**
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension routing for the core transform.
|
|
3
|
+
*
|
|
4
|
+
* A PostgreSQL extension is installed into exactly one schema, and the objects
|
|
5
|
+
* it provides (functions, types, operators) live in that schema. Two databases
|
|
6
|
+
* can install the *same* extension into *different* schemas — one leaves it in
|
|
7
|
+
* `public`, another isolates it in a dedicated schema — so portable SQL must be
|
|
8
|
+
* able to move an extension's install site **and** rewrite every reference to
|
|
9
|
+
* the symbols it provides.
|
|
10
|
+
*
|
|
11
|
+
* This is a distinct problem from {@link SchemaRouter}, which routes objects the
|
|
12
|
+
* SQL itself creates. Here the objects are owned by an extension and are not
|
|
13
|
+
* declared in the script; the only way to know that a bare `crypt(...)` call
|
|
14
|
+
* belongs to `pgcrypto` (and must move when `pgcrypto` moves) is an
|
|
15
|
+
* **inventory** of which symbols each extension provides. That inventory is
|
|
16
|
+
* also **version-aware**: a symbol can graduate into the server core and cease
|
|
17
|
+
* to be extension-owned (the canonical case is `gen_random_uuid()`, provided by
|
|
18
|
+
* `pgcrypto` before PostgreSQL 13 and part of core `pg_catalog` from 13 on — it
|
|
19
|
+
* must never be routed on 13+).
|
|
20
|
+
*
|
|
21
|
+
* An {@link ExtensionRouter} answers two questions:
|
|
22
|
+
*
|
|
23
|
+
* - **install**: where should `CREATE EXTENSION <e>` (or `ALTER EXTENSION <e>
|
|
24
|
+
* SET SCHEMA`) place the extension?
|
|
25
|
+
* - **reference**: given a reference to a symbol `(schema, name)` in namespace
|
|
26
|
+
* `ns`, what schema should it live in now — including stripping the qualifier
|
|
27
|
+
* entirely (relying on `search_path`) when the target is "unqualified".
|
|
28
|
+
*
|
|
29
|
+
* A schema of `null` denotes the **unqualified** site: a bare reference
|
|
30
|
+
* (`crypt(...)`) or installing an extension with no explicit `SCHEMA` clause.
|
|
31
|
+
* This makes the router fully bidirectional: `null -> 'extensions'` qualifies
|
|
32
|
+
* bare references, and `'extensions' -> null` strips them back to bare.
|
|
33
|
+
*/
|
|
34
|
+
/** PostgreSQL namespaces an extension symbol can occupy. */
|
|
35
|
+
export type ExtensionSymbolNamespace = 'function' | 'type' | 'operator';
|
|
36
|
+
/** A single symbol provided by an extension. */
|
|
37
|
+
export interface ExtensionSymbol {
|
|
38
|
+
/** Bare symbol name (e.g. `crypt`, `citext`, `gen_random_uuid`). */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Which namespace the symbol occupies. Defaults to `function`. */
|
|
41
|
+
namespace?: ExtensionSymbolNamespace;
|
|
42
|
+
/**
|
|
43
|
+
* Major PostgreSQL version at/after which this symbol is part of the server
|
|
44
|
+
* core (`pg_catalog`) rather than the extension. At or above this version
|
|
45
|
+
* the symbol is **never routed** — it resolves from `pg_catalog` regardless
|
|
46
|
+
* of where the extension lives. The classic case is `gen_random_uuid`
|
|
47
|
+
* (`coreSince: 13`).
|
|
48
|
+
*/
|
|
49
|
+
coreSince?: number;
|
|
50
|
+
}
|
|
51
|
+
/** The set of symbols an extension provides. */
|
|
52
|
+
export interface ExtensionDefinition {
|
|
53
|
+
/** Extension name as used in `CREATE EXTENSION <name>`. */
|
|
54
|
+
name: string;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the extension is relocatable (`ALTER EXTENSION ... SET SCHEMA`
|
|
57
|
+
* succeeds). Informational for callers deciding *how* to move an existing
|
|
58
|
+
* install; the router never emits `SET SCHEMA` itself.
|
|
59
|
+
*/
|
|
60
|
+
relocatable?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* A schema the extension is pinned to and cannot be moved out of (fixed by
|
|
63
|
+
* its control file, e.g. a geocoder that always creates its own schema).
|
|
64
|
+
* When set, the router refuses to route the extension's install or symbols.
|
|
65
|
+
*/
|
|
66
|
+
fixedSchema?: string;
|
|
67
|
+
/** Symbols the extension provides. */
|
|
68
|
+
symbols: ExtensionSymbol[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A routing rule for one extension. `to` is the destination install/reference
|
|
72
|
+
* schema (or `null` to strip qualification / install without a `SCHEMA`
|
|
73
|
+
* clause). `from` lists the schemas currently holding the extension that
|
|
74
|
+
* should be rewritten; a `null` entry additionally routes bare references.
|
|
75
|
+
* When `from` is omitted, references in **any** schema (and bare ones) are
|
|
76
|
+
* routed — useful when the source layout is unknown.
|
|
77
|
+
*/
|
|
78
|
+
export interface ExtensionRoute {
|
|
79
|
+
to: string | null;
|
|
80
|
+
from?: (string | null)[];
|
|
81
|
+
}
|
|
82
|
+
/** Routing specification keyed by extension name. */
|
|
83
|
+
export type ExtensionRouteSpec = Record<string, ExtensionRoute>;
|
|
84
|
+
/** A concrete rewrite instruction: from a schema (or bare) to a schema (or bare). */
|
|
85
|
+
export interface SymbolRewrite {
|
|
86
|
+
/** Target schema, or `null` to make the reference unqualified. */
|
|
87
|
+
to: string | null;
|
|
88
|
+
}
|
|
89
|
+
export interface ExtensionRouterOptions {
|
|
90
|
+
/**
|
|
91
|
+
* The symbol inventory. Defaults to {@link COMMON_EXTENSIONS}. Callers can
|
|
92
|
+
* pass an augmented or replacement inventory (e.g. extended from live-catalog
|
|
93
|
+
* introspection).
|
|
94
|
+
*/
|
|
95
|
+
inventory?: ExtensionDefinition[];
|
|
96
|
+
/**
|
|
97
|
+
* Target PostgreSQL major version, used to apply {@link ExtensionSymbol.coreSince}.
|
|
98
|
+
* Defaults to a high value so version-graduated symbols (e.g. `gen_random_uuid`)
|
|
99
|
+
* are treated as core and never routed unless a caller opts into an older
|
|
100
|
+
* version.
|
|
101
|
+
*/
|
|
102
|
+
serverVersion?: number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A curated, platform-agnostic inventory of common contrib/extension symbols.
|
|
106
|
+
* Intentionally minimal and conservative — only well-known, stable symbols are
|
|
107
|
+
* listed so routing never touches something it cannot prove belongs to the
|
|
108
|
+
* extension. Callers extend this via {@link ExtensionRouterOptions.inventory}.
|
|
109
|
+
*/
|
|
110
|
+
export declare const COMMON_EXTENSIONS: ExtensionDefinition[];
|
|
111
|
+
/**
|
|
112
|
+
* Resolves install-schema and symbol-reference routing for extensions, backed
|
|
113
|
+
* by a version-aware symbol inventory.
|
|
114
|
+
*/
|
|
115
|
+
export declare class ExtensionRouter {
|
|
116
|
+
private readonly routes;
|
|
117
|
+
private readonly serverVersion;
|
|
118
|
+
private readonly definitions;
|
|
119
|
+
/** `${namespace}:${name}` -> providing extension (+ version predicate). */
|
|
120
|
+
private readonly symbolIndex;
|
|
121
|
+
constructor(routes?: ExtensionRouteSpec | Map<string, ExtensionRoute>, options?: ExtensionRouterOptions);
|
|
122
|
+
/**
|
|
123
|
+
* Build a router that moves the given extensions (default: every inventoried
|
|
124
|
+
* one) to a single `targetSchema`, rewriting their references from `public`
|
|
125
|
+
* and from bare (unqualified) sites. Passing `null` as `targetSchema` strips
|
|
126
|
+
* qualification instead (the "repollute public / rely on search_path"
|
|
127
|
+
* direction).
|
|
128
|
+
*/
|
|
129
|
+
static toSchema(targetSchema: string | null, options?: ExtensionRouterOptions & {
|
|
130
|
+
extensions?: string[];
|
|
131
|
+
from?: (string | null)[];
|
|
132
|
+
}): ExtensionRouter;
|
|
133
|
+
/** Coerce a spec, map, or existing router into a router. */
|
|
134
|
+
static from(source: ExtensionRouter | ExtensionRouteSpec | Map<string, ExtensionRoute>, options?: ExtensionRouterOptions): ExtensionRouter;
|
|
135
|
+
/** True when no extension routes are configured. */
|
|
136
|
+
get size(): number;
|
|
137
|
+
/** Extension names this router may rewrite. */
|
|
138
|
+
routedExtensions(): string[];
|
|
139
|
+
/** True when any configured extension has inventoried symbols to rewrite. */
|
|
140
|
+
hasSymbolRoutes(): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Resolve the install schema for `CREATE EXTENSION <extname>` /
|
|
143
|
+
* `ALTER EXTENSION <extname> SET SCHEMA`. Returns:
|
|
144
|
+
* - a string to place/keep the extension in that schema,
|
|
145
|
+
* - `null` to install with no explicit `SCHEMA` clause (server default),
|
|
146
|
+
* - `undefined` to leave the statement unchanged (no route, or the extension
|
|
147
|
+
* is pinned to a fixed schema).
|
|
148
|
+
*/
|
|
149
|
+
resolveInstall(extname: string | undefined | null): string | null | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a rewrite for a reference to `(schema, name)` in namespace `ns`,
|
|
152
|
+
* where `schema` is `null` for a bare reference. Returns the target
|
|
153
|
+
* ({@link SymbolRewrite}) or `undefined` to leave the reference unchanged.
|
|
154
|
+
*
|
|
155
|
+
* A reference is rewritten only when the inventory proves the symbol belongs
|
|
156
|
+
* to a routed extension, the symbol has not graduated to core at the target
|
|
157
|
+
* server version, the current schema is one the route rewrites `from`, and
|
|
158
|
+
* the destination actually differs from where it already is.
|
|
159
|
+
*/
|
|
160
|
+
resolveSymbol(schema: string | null | undefined, name: string | undefined, ns: ExtensionSymbolNamespace): SymbolRewrite | undefined;
|
|
161
|
+
}
|