@pgsql/transform 18.15.3 → 18.17.1
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/index.js +1 -1
- package/esm/transform.js +111 -27
- package/index.d.ts +1 -1
- package/index.js +3 -2
- package/package.json +2 -2
- package/transform.d.ts +14 -5
- package/transform.js +113 -28
package/esm/index.js
CHANGED
|
@@ -10,4 +10,4 @@ export { createRoleResult, createRoleVisitor, transformRoles, } from './role-tra
|
|
|
10
10
|
export { captureAstsFromSql, captureTransformAsts, firstDifference, normalizeParseTree, validateRoundTrip, } from './round-trip';
|
|
11
11
|
export { CLEAN_TREE_VOLATILE_KEYS, cleanTree, normalizeTree, trimDefElemBody, } from './round-trip-core';
|
|
12
12
|
export { SchemaRouter } from './router';
|
|
13
|
-
export { createPlpgsqlVisitor, createSqlVisitor, escapeRegexp, extractPgpmHeader, shouldTransformSchema, transformComments, transformJsonStringValues, transformNameList, transformPlpgsqlTypeAst, transformPlpgsqlTypeString, transformRelation, transformSchemaName, transformSql, transformSqlStatement,
|
|
13
|
+
export { createPlpgsqlVisitor, createSqlVisitor, escapeRegexp, extractPgpmHeader, identityCastNamespace, shouldTransformSchema, transformComments, transformIdentityCastLiteral, transformJsonStringValues, transformNameList, transformPlpgsqlTypeAst, transformPlpgsqlTypeString, transformRelation, transformSchemaName, transformSql, transformSqlStatement, validateNoUntransformedSchemas, walkPlpgsqlForSchemas, } from './transform';
|
package/esm/transform.js
CHANGED
|
@@ -209,6 +209,98 @@ export function transformSchemaRefsInString(str, schemaMapping, result) {
|
|
|
209
209
|
}
|
|
210
210
|
return out;
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Cast types whose string operand is an object reference rather than opaque
|
|
214
|
+
* text. `'sch.tbl'::regclass` is a relation the parser resolves, and the cast
|
|
215
|
+
* names the namespace it resolves in — so the reference can be routed from the
|
|
216
|
+
* cast, semantically, instead of pattern-matched. This reaches what the string
|
|
217
|
+
* passes cannot: a bare `'sch'::regnamespace` has no dot to match on.
|
|
218
|
+
*/
|
|
219
|
+
const IDENTITY_CAST_NAMESPACES = {
|
|
220
|
+
regclass: 'relation',
|
|
221
|
+
regcollation: 'unknown',
|
|
222
|
+
regconfig: 'unknown',
|
|
223
|
+
regdictionary: 'unknown',
|
|
224
|
+
regnamespace: 'schema',
|
|
225
|
+
regoper: 'unknown',
|
|
226
|
+
regoperator: 'unknown',
|
|
227
|
+
regproc: 'function',
|
|
228
|
+
regprocedure: 'function',
|
|
229
|
+
regtype: 'type'
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* The namespace a cast resolves its operand in, or `undefined` when the target
|
|
233
|
+
* type is not an object-identity type.
|
|
234
|
+
*/
|
|
235
|
+
export function identityCastNamespace(typeName) {
|
|
236
|
+
const names = typeName?.names;
|
|
237
|
+
if (!Array.isArray(names) || names.length === 0)
|
|
238
|
+
return undefined;
|
|
239
|
+
// Only `pg_catalog`-qualified (or unqualified) reg* types are the built-ins.
|
|
240
|
+
if (names.length > 1 && names[0]?.String?.sval !== 'pg_catalog')
|
|
241
|
+
return undefined;
|
|
242
|
+
const typeIdent = names[names.length - 1]?.String?.sval;
|
|
243
|
+
if (typeof typeIdent !== 'string')
|
|
244
|
+
return undefined;
|
|
245
|
+
return IDENTITY_CAST_NAMESPACES[typeIdent.toLowerCase()];
|
|
246
|
+
}
|
|
247
|
+
const IDENTITY_TOKEN = String.raw `(?:"(?:[^"]|"")*"|[^\s".,()[\]]+)`;
|
|
248
|
+
const BARE_IDENTITY_RE = new RegExp(`^\\s*(${IDENTITY_TOKEN})\\s*$`);
|
|
249
|
+
const QUALIFIED_IDENTITY_RE = new RegExp(`^\\s*(${IDENTITY_TOKEN})\\.([\\s\\S]+)$`);
|
|
250
|
+
function unquoteIdentifier(token) {
|
|
251
|
+
if (token.length > 1 && token.startsWith('"') && token.endsWith('"')) {
|
|
252
|
+
return token.slice(1, -1).replace(/""/g, '"');
|
|
253
|
+
}
|
|
254
|
+
return token;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Route the object reference carried in the string operand of an identity
|
|
258
|
+
* cast. `ns` comes from the cast's target type.
|
|
259
|
+
*
|
|
260
|
+
* Unqualified operands (`'tbl'::regclass`) name no schema — they resolve
|
|
261
|
+
* through `search_path` — so they are returned unchanged.
|
|
262
|
+
*/
|
|
263
|
+
export function transformIdentityCastLiteral(sval, ns, schemaMapping, result) {
|
|
264
|
+
const router = asRouter(schemaMapping);
|
|
265
|
+
if (ns === 'schema') {
|
|
266
|
+
const bare = BARE_IDENTITY_RE.exec(sval);
|
|
267
|
+
if (!bare)
|
|
268
|
+
return sval;
|
|
269
|
+
const schemaName = unquoteIdentifier(bare[1]);
|
|
270
|
+
const newName = router.resolve(schemaName, undefined, 'schema');
|
|
271
|
+
if (!newName || newName === schemaName)
|
|
272
|
+
return sval;
|
|
273
|
+
result.schemasFound.add(schemaName);
|
|
274
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
275
|
+
return QuoteUtils.quoteIdentifier(newName);
|
|
276
|
+
}
|
|
277
|
+
// `regprocedure` and `regoperator` carry an argument list after the name, and
|
|
278
|
+
// an argument type can itself be schema-qualified
|
|
279
|
+
// (`fn("my-schema".row_type)`), so the list is routed as ordinary schema refs.
|
|
280
|
+
const argsAt = sval.indexOf('(');
|
|
281
|
+
const head = argsAt === -1 ? sval : sval.slice(0, argsAt);
|
|
282
|
+
const args = argsAt === -1 ? '' : transformSchemaRefsInString(sval.slice(argsAt), router, result);
|
|
283
|
+
const qualified = QUALIFIED_IDENTITY_RE.exec(head);
|
|
284
|
+
if (!qualified)
|
|
285
|
+
return `${head}${args}`;
|
|
286
|
+
const schemaName = unquoteIdentifier(qualified[1]);
|
|
287
|
+
const objName = unquoteIdentifier(qualified[2].trim());
|
|
288
|
+
const target = router.resolveObject(schemaName, objName, ns);
|
|
289
|
+
if (!target)
|
|
290
|
+
return `${head}${args}`;
|
|
291
|
+
const rebound = target.name !== undefined && target.name !== objName;
|
|
292
|
+
const requalified = target.schema === null || (!!target.schema && target.schema !== schemaName);
|
|
293
|
+
if (!rebound && !requalified)
|
|
294
|
+
return `${head}${args}`;
|
|
295
|
+
result.schemasFound.add(schemaName);
|
|
296
|
+
const nameToken = QuoteUtils.quoteIdentifier(rebound ? target.name : objName);
|
|
297
|
+
if (target.schema === null)
|
|
298
|
+
return `${nameToken}${args}`;
|
|
299
|
+
if (target.schema && target.schema !== schemaName) {
|
|
300
|
+
result.schemasTransformed.set(schemaName, target.schema);
|
|
301
|
+
}
|
|
302
|
+
return `${QuoteUtils.quoteIdentifier(target.schema ?? schemaName)}.${nameToken}${args}`;
|
|
303
|
+
}
|
|
212
304
|
/**
|
|
213
305
|
* Create a SQL AST visitor that transforms schema names.
|
|
214
306
|
*
|
|
@@ -461,9 +553,28 @@ export function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
461
553
|
A_Const: (path) => {
|
|
462
554
|
const node = path.node;
|
|
463
555
|
if (typeof node.sval?.sval === 'string' && node.sval.sval.includes('.')) {
|
|
556
|
+
if (!claimSite(result, node.sval, 'sval'))
|
|
557
|
+
return;
|
|
464
558
|
node.sval.sval = transformSchemaRefsInString(node.sval.sval, schemaMapping, result);
|
|
465
559
|
}
|
|
466
560
|
},
|
|
561
|
+
// Object identities carried as the operand of an identity cast
|
|
562
|
+
// ('sch.tbl'::regclass, 'sch.fn(uuid)'::regprocedure, 'sch'::regnamespace).
|
|
563
|
+
// The cast names the namespace, so these route through the same object
|
|
564
|
+
// routes as a RangeVar rather than through a string pattern — and the bare
|
|
565
|
+
// schema form, invisible to every string pass, routes here.
|
|
566
|
+
TypeCast: (path) => {
|
|
567
|
+
const node = path.node;
|
|
568
|
+
const sval = node?.arg?.A_Const?.sval;
|
|
569
|
+
if (typeof sval?.sval !== 'string')
|
|
570
|
+
return;
|
|
571
|
+
const ns = identityCastNamespace(node.typeName);
|
|
572
|
+
if (!ns)
|
|
573
|
+
return;
|
|
574
|
+
if (!claimSite(result, sval, 'sval'))
|
|
575
|
+
return;
|
|
576
|
+
sval.sval = transformIdentityCastLiteral(sval.sval, ns, router, result);
|
|
577
|
+
},
|
|
467
578
|
// Transform DefineStmt (CREATE TYPE schema.name, CREATE AGGREGATE schema.agg)
|
|
468
579
|
DefineStmt: (path) => {
|
|
469
580
|
const node = path.node;
|
|
@@ -981,33 +1092,6 @@ export function transformComments(content, schemaMapping, result) {
|
|
|
981
1092
|
return prefix + newPath;
|
|
982
1093
|
});
|
|
983
1094
|
}
|
|
984
|
-
/**
|
|
985
|
-
* Transform verify function calls that use string literals.
|
|
986
|
-
* These are inside SQL strings and not part of the main AST.
|
|
987
|
-
*/
|
|
988
|
-
export function transformVerifyCalls(content, schemaMapping, result) {
|
|
989
|
-
const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
|
|
990
|
-
let newContent = content;
|
|
991
|
-
// The pattern needs the schema name spelled out, case-insensitively.
|
|
992
|
-
let lowerContent = content.toLowerCase();
|
|
993
|
-
for (const schema of schemas) {
|
|
994
|
-
const newName = schemaMapping.get(schema);
|
|
995
|
-
if (!newName)
|
|
996
|
-
continue;
|
|
997
|
-
if (!lowerContent.includes(schema.toLowerCase()))
|
|
998
|
-
continue;
|
|
999
|
-
const escapedSchema = cachedEscapeRegexp(schema);
|
|
1000
|
-
const verifyPattern = cachedRegExp(`(verify_(?:function|table|trigger|type|domain|view|index|constraint|schema|policy|table_grant|function_grant|sequence_grant|type_grant)\\s*\\(\\s*')${escapedSchema}(\\.|'\\s*\\))`, 'gi');
|
|
1001
|
-
const before = newContent;
|
|
1002
|
-
newContent = newContent.replace(verifyPattern, `$1${newName}$2`);
|
|
1003
|
-
if (newContent !== before) {
|
|
1004
|
-
lowerContent = newContent.toLowerCase();
|
|
1005
|
-
result.schemasFound.add(schema);
|
|
1006
|
-
result.schemasTransformed.set(schema, newName);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
return newContent;
|
|
1010
|
-
}
|
|
1011
1095
|
/**
|
|
1012
1096
|
* Transform schema names inside JSON/JSONB string values.
|
|
1013
1097
|
*/
|
package/index.d.ts
CHANGED
|
@@ -23,4 +23,4 @@ export { CLEAN_TREE_VOLATILE_KEYS, cleanTree, normalizeTree, trimDefElemBody, }
|
|
|
23
23
|
export type { ObjectNamespace, ObjectRoute, ObjectRouteTarget, RouteNamespace, RouteSpec, SchemaRoute, } from './router';
|
|
24
24
|
export { SchemaRouter } from './router';
|
|
25
25
|
export type { SchemaMappingInput, SchemaTransformPass, SchemaTransformResult, TransformSqlOptions, } from './transform';
|
|
26
|
-
export { createPlpgsqlVisitor, createSqlVisitor, escapeRegexp, extractPgpmHeader, shouldTransformSchema, transformComments, transformJsonStringValues, transformNameList, transformPlpgsqlTypeAst, transformPlpgsqlTypeString, transformRelation, transformSchemaName, transformSql, transformSqlStatement,
|
|
26
|
+
export { createPlpgsqlVisitor, createSqlVisitor, escapeRegexp, extractPgpmHeader, identityCastNamespace, shouldTransformSchema, transformComments, transformIdentityCastLiteral, transformJsonStringValues, transformNameList, transformPlpgsqlTypeAst, transformPlpgsqlTypeString, transformRelation, transformSchemaName, transformSql, transformSqlStatement, validateNoUntransformedSchemas, walkPlpgsqlForSchemas, } from './transform';
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.walkPlpgsqlForSchemas = exports.validateNoUntransformedSchemas = exports.
|
|
3
|
+
exports.walkPlpgsqlForSchemas = exports.validateNoUntransformedSchemas = exports.transformSqlStatement = exports.transformSql = exports.transformSchemaName = exports.transformRelation = exports.transformPlpgsqlTypeString = exports.transformPlpgsqlTypeAst = exports.transformNameList = exports.transformJsonStringValues = exports.transformIdentityCastLiteral = exports.transformComments = exports.shouldTransformSchema = exports.identityCastNamespace = exports.extractPgpmHeader = exports.escapeRegexp = exports.createSqlVisitor = exports.createPlpgsqlVisitor = exports.SchemaRouter = exports.trimDefElemBody = exports.normalizeTree = exports.cleanTree = exports.CLEAN_TREE_VOLATILE_KEYS = exports.validateRoundTrip = exports.normalizeParseTree = exports.firstDifference = exports.captureTransformAsts = exports.captureAstsFromSql = exports.transformRoles = exports.createRoleVisitor = exports.createRoleResult = exports.RoleRouter = exports.restructureSql = exports.orderStatements = exports.qualifyUnqualified = exports.mergeInventories = exports.createQualifyVisitor = exports.collectCreatedObjects = exports.identityOf = exports.buildStatementGraph = exports.classifyStatements = exports.transformExtensions = exports.createExtensionVisitor = exports.createExtensionResult = exports.ExtensionRouter = exports.COMMON_EXTENSIONS = void 0;
|
|
4
4
|
var extension_router_1 = require("./extension-router");
|
|
5
5
|
Object.defineProperty(exports, "COMMON_EXTENSIONS", { enumerable: true, get: function () { return extension_router_1.COMMON_EXTENSIONS; } });
|
|
6
6
|
Object.defineProperty(exports, "ExtensionRouter", { enumerable: true, get: function () { return extension_router_1.ExtensionRouter; } });
|
|
@@ -46,8 +46,10 @@ Object.defineProperty(exports, "createPlpgsqlVisitor", { enumerable: true, get:
|
|
|
46
46
|
Object.defineProperty(exports, "createSqlVisitor", { enumerable: true, get: function () { return transform_1.createSqlVisitor; } });
|
|
47
47
|
Object.defineProperty(exports, "escapeRegexp", { enumerable: true, get: function () { return transform_1.escapeRegexp; } });
|
|
48
48
|
Object.defineProperty(exports, "extractPgpmHeader", { enumerable: true, get: function () { return transform_1.extractPgpmHeader; } });
|
|
49
|
+
Object.defineProperty(exports, "identityCastNamespace", { enumerable: true, get: function () { return transform_1.identityCastNamespace; } });
|
|
49
50
|
Object.defineProperty(exports, "shouldTransformSchema", { enumerable: true, get: function () { return transform_1.shouldTransformSchema; } });
|
|
50
51
|
Object.defineProperty(exports, "transformComments", { enumerable: true, get: function () { return transform_1.transformComments; } });
|
|
52
|
+
Object.defineProperty(exports, "transformIdentityCastLiteral", { enumerable: true, get: function () { return transform_1.transformIdentityCastLiteral; } });
|
|
51
53
|
Object.defineProperty(exports, "transformJsonStringValues", { enumerable: true, get: function () { return transform_1.transformJsonStringValues; } });
|
|
52
54
|
Object.defineProperty(exports, "transformNameList", { enumerable: true, get: function () { return transform_1.transformNameList; } });
|
|
53
55
|
Object.defineProperty(exports, "transformPlpgsqlTypeAst", { enumerable: true, get: function () { return transform_1.transformPlpgsqlTypeAst; } });
|
|
@@ -56,6 +58,5 @@ Object.defineProperty(exports, "transformRelation", { enumerable: true, get: fun
|
|
|
56
58
|
Object.defineProperty(exports, "transformSchemaName", { enumerable: true, get: function () { return transform_1.transformSchemaName; } });
|
|
57
59
|
Object.defineProperty(exports, "transformSql", { enumerable: true, get: function () { return transform_1.transformSql; } });
|
|
58
60
|
Object.defineProperty(exports, "transformSqlStatement", { enumerable: true, get: function () { return transform_1.transformSqlStatement; } });
|
|
59
|
-
Object.defineProperty(exports, "transformVerifyCalls", { enumerable: true, get: function () { return transform_1.transformVerifyCalls; } });
|
|
60
61
|
Object.defineProperty(exports, "validateNoUntransformedSchemas", { enumerable: true, get: function () { return transform_1.validateNoUntransformedSchemas; } });
|
|
61
62
|
Object.defineProperty(exports, "walkPlpgsqlForSchemas", { enumerable: true, get: function () { return transform_1.walkPlpgsqlForSchemas; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgsql/transform",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.17.1",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "AST-based SQL transformation, qualification, classification and closure analysis for PostgreSQL",
|
|
6
6
|
"main": "index.js",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"classify",
|
|
51
51
|
"plpgsql"
|
|
52
52
|
],
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "f0bb7e531c874467d873b478c6ee06db682cefa6"
|
|
54
54
|
}
|
package/transform.d.ts
CHANGED
|
@@ -123,6 +123,19 @@ export declare function transformRelation(relation: any, schemaMapping: SchemaMa
|
|
|
123
123
|
* followed by a dot are rewritten.
|
|
124
124
|
*/
|
|
125
125
|
export declare function transformSchemaRefsInString(str: string, schemaMapping: SchemaMappingInput, result: SchemaTransformResult): string;
|
|
126
|
+
/**
|
|
127
|
+
* The namespace a cast resolves its operand in, or `undefined` when the target
|
|
128
|
+
* type is not an object-identity type.
|
|
129
|
+
*/
|
|
130
|
+
export declare function identityCastNamespace(typeName: any): RouteNamespace | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Route the object reference carried in the string operand of an identity
|
|
133
|
+
* cast. `ns` comes from the cast's target type.
|
|
134
|
+
*
|
|
135
|
+
* Unqualified operands (`'tbl'::regclass`) name no schema — they resolve
|
|
136
|
+
* through `search_path` — so they are returned unchanged.
|
|
137
|
+
*/
|
|
138
|
+
export declare function transformIdentityCastLiteral(sval: string, ns: RouteNamespace, schemaMapping: SchemaMappingInput, result: SchemaTransformResult): string;
|
|
126
139
|
/**
|
|
127
140
|
* Create a SQL AST visitor that transforms schema names.
|
|
128
141
|
*
|
|
@@ -148,6 +161,7 @@ export declare function createSqlVisitor(schemaMapping: SchemaMappingInput, resu
|
|
|
148
161
|
CreateTrigStmt: (path: any) => void;
|
|
149
162
|
CommentStmt: (path: any) => void;
|
|
150
163
|
A_Const: (path: any) => void;
|
|
164
|
+
TypeCast: (path: any) => void;
|
|
151
165
|
DefineStmt: (path: any) => void;
|
|
152
166
|
CreateDomainStmt: (path: any) => void;
|
|
153
167
|
CreateEnumStmt: (path: any) => void;
|
|
@@ -210,11 +224,6 @@ export declare function extractPgpmHeader(content: string): {
|
|
|
210
224
|
* These are not part of the SQL AST, so we use regexp for these.
|
|
211
225
|
*/
|
|
212
226
|
export declare function transformComments(content: string, schemaMapping: Map<string, string>, result: SchemaTransformResult): string;
|
|
213
|
-
/**
|
|
214
|
-
* Transform verify function calls that use string literals.
|
|
215
|
-
* These are inside SQL strings and not part of the main AST.
|
|
216
|
-
*/
|
|
217
|
-
export declare function transformVerifyCalls(content: string, schemaMapping: Map<string, string>, result: SchemaTransformResult): string;
|
|
218
227
|
/**
|
|
219
228
|
* Transform schema names inside JSON/JSONB string values.
|
|
220
229
|
*/
|
package/transform.js
CHANGED
|
@@ -31,6 +31,8 @@ exports.transformNameList = transformNameList;
|
|
|
31
31
|
exports.transformSchemaNameField = transformSchemaNameField;
|
|
32
32
|
exports.transformRelation = transformRelation;
|
|
33
33
|
exports.transformSchemaRefsInString = transformSchemaRefsInString;
|
|
34
|
+
exports.identityCastNamespace = identityCastNamespace;
|
|
35
|
+
exports.transformIdentityCastLiteral = transformIdentityCastLiteral;
|
|
34
36
|
exports.createSqlVisitor = createSqlVisitor;
|
|
35
37
|
exports.validateNoUntransformedSchemas = validateNoUntransformedSchemas;
|
|
36
38
|
exports.createPlpgsqlVisitor = createPlpgsqlVisitor;
|
|
@@ -40,7 +42,6 @@ exports.walkPlpgsqlForSchemas = walkPlpgsqlForSchemas;
|
|
|
40
42
|
exports.escapeRegexp = escapeRegexp;
|
|
41
43
|
exports.extractPgpmHeader = extractPgpmHeader;
|
|
42
44
|
exports.transformComments = transformComments;
|
|
43
|
-
exports.transformVerifyCalls = transformVerifyCalls;
|
|
44
45
|
exports.transformJsonStringValues = transformJsonStringValues;
|
|
45
46
|
exports.transformSql = transformSql;
|
|
46
47
|
exports.transformSqlStatement = transformSqlStatement;
|
|
@@ -230,6 +231,98 @@ function transformSchemaRefsInString(str, schemaMapping, result) {
|
|
|
230
231
|
}
|
|
231
232
|
return out;
|
|
232
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Cast types whose string operand is an object reference rather than opaque
|
|
236
|
+
* text. `'sch.tbl'::regclass` is a relation the parser resolves, and the cast
|
|
237
|
+
* names the namespace it resolves in — so the reference can be routed from the
|
|
238
|
+
* cast, semantically, instead of pattern-matched. This reaches what the string
|
|
239
|
+
* passes cannot: a bare `'sch'::regnamespace` has no dot to match on.
|
|
240
|
+
*/
|
|
241
|
+
const IDENTITY_CAST_NAMESPACES = {
|
|
242
|
+
regclass: 'relation',
|
|
243
|
+
regcollation: 'unknown',
|
|
244
|
+
regconfig: 'unknown',
|
|
245
|
+
regdictionary: 'unknown',
|
|
246
|
+
regnamespace: 'schema',
|
|
247
|
+
regoper: 'unknown',
|
|
248
|
+
regoperator: 'unknown',
|
|
249
|
+
regproc: 'function',
|
|
250
|
+
regprocedure: 'function',
|
|
251
|
+
regtype: 'type'
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* The namespace a cast resolves its operand in, or `undefined` when the target
|
|
255
|
+
* type is not an object-identity type.
|
|
256
|
+
*/
|
|
257
|
+
function identityCastNamespace(typeName) {
|
|
258
|
+
const names = typeName?.names;
|
|
259
|
+
if (!Array.isArray(names) || names.length === 0)
|
|
260
|
+
return undefined;
|
|
261
|
+
// Only `pg_catalog`-qualified (or unqualified) reg* types are the built-ins.
|
|
262
|
+
if (names.length > 1 && names[0]?.String?.sval !== 'pg_catalog')
|
|
263
|
+
return undefined;
|
|
264
|
+
const typeIdent = names[names.length - 1]?.String?.sval;
|
|
265
|
+
if (typeof typeIdent !== 'string')
|
|
266
|
+
return undefined;
|
|
267
|
+
return IDENTITY_CAST_NAMESPACES[typeIdent.toLowerCase()];
|
|
268
|
+
}
|
|
269
|
+
const IDENTITY_TOKEN = String.raw `(?:"(?:[^"]|"")*"|[^\s".,()[\]]+)`;
|
|
270
|
+
const BARE_IDENTITY_RE = new RegExp(`^\\s*(${IDENTITY_TOKEN})\\s*$`);
|
|
271
|
+
const QUALIFIED_IDENTITY_RE = new RegExp(`^\\s*(${IDENTITY_TOKEN})\\.([\\s\\S]+)$`);
|
|
272
|
+
function unquoteIdentifier(token) {
|
|
273
|
+
if (token.length > 1 && token.startsWith('"') && token.endsWith('"')) {
|
|
274
|
+
return token.slice(1, -1).replace(/""/g, '"');
|
|
275
|
+
}
|
|
276
|
+
return token;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Route the object reference carried in the string operand of an identity
|
|
280
|
+
* cast. `ns` comes from the cast's target type.
|
|
281
|
+
*
|
|
282
|
+
* Unqualified operands (`'tbl'::regclass`) name no schema — they resolve
|
|
283
|
+
* through `search_path` — so they are returned unchanged.
|
|
284
|
+
*/
|
|
285
|
+
function transformIdentityCastLiteral(sval, ns, schemaMapping, result) {
|
|
286
|
+
const router = asRouter(schemaMapping);
|
|
287
|
+
if (ns === 'schema') {
|
|
288
|
+
const bare = BARE_IDENTITY_RE.exec(sval);
|
|
289
|
+
if (!bare)
|
|
290
|
+
return sval;
|
|
291
|
+
const schemaName = unquoteIdentifier(bare[1]);
|
|
292
|
+
const newName = router.resolve(schemaName, undefined, 'schema');
|
|
293
|
+
if (!newName || newName === schemaName)
|
|
294
|
+
return sval;
|
|
295
|
+
result.schemasFound.add(schemaName);
|
|
296
|
+
result.schemasTransformed.set(schemaName, newName);
|
|
297
|
+
return quotes_1.QuoteUtils.quoteIdentifier(newName);
|
|
298
|
+
}
|
|
299
|
+
// `regprocedure` and `regoperator` carry an argument list after the name, and
|
|
300
|
+
// an argument type can itself be schema-qualified
|
|
301
|
+
// (`fn("my-schema".row_type)`), so the list is routed as ordinary schema refs.
|
|
302
|
+
const argsAt = sval.indexOf('(');
|
|
303
|
+
const head = argsAt === -1 ? sval : sval.slice(0, argsAt);
|
|
304
|
+
const args = argsAt === -1 ? '' : transformSchemaRefsInString(sval.slice(argsAt), router, result);
|
|
305
|
+
const qualified = QUALIFIED_IDENTITY_RE.exec(head);
|
|
306
|
+
if (!qualified)
|
|
307
|
+
return `${head}${args}`;
|
|
308
|
+
const schemaName = unquoteIdentifier(qualified[1]);
|
|
309
|
+
const objName = unquoteIdentifier(qualified[2].trim());
|
|
310
|
+
const target = router.resolveObject(schemaName, objName, ns);
|
|
311
|
+
if (!target)
|
|
312
|
+
return `${head}${args}`;
|
|
313
|
+
const rebound = target.name !== undefined && target.name !== objName;
|
|
314
|
+
const requalified = target.schema === null || (!!target.schema && target.schema !== schemaName);
|
|
315
|
+
if (!rebound && !requalified)
|
|
316
|
+
return `${head}${args}`;
|
|
317
|
+
result.schemasFound.add(schemaName);
|
|
318
|
+
const nameToken = quotes_1.QuoteUtils.quoteIdentifier(rebound ? target.name : objName);
|
|
319
|
+
if (target.schema === null)
|
|
320
|
+
return `${nameToken}${args}`;
|
|
321
|
+
if (target.schema && target.schema !== schemaName) {
|
|
322
|
+
result.schemasTransformed.set(schemaName, target.schema);
|
|
323
|
+
}
|
|
324
|
+
return `${quotes_1.QuoteUtils.quoteIdentifier(target.schema ?? schemaName)}.${nameToken}${args}`;
|
|
325
|
+
}
|
|
233
326
|
/**
|
|
234
327
|
* Create a SQL AST visitor that transforms schema names.
|
|
235
328
|
*
|
|
@@ -482,9 +575,28 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
|
|
|
482
575
|
A_Const: (path) => {
|
|
483
576
|
const node = path.node;
|
|
484
577
|
if (typeof node.sval?.sval === 'string' && node.sval.sval.includes('.')) {
|
|
578
|
+
if (!claimSite(result, node.sval, 'sval'))
|
|
579
|
+
return;
|
|
485
580
|
node.sval.sval = transformSchemaRefsInString(node.sval.sval, schemaMapping, result);
|
|
486
581
|
}
|
|
487
582
|
},
|
|
583
|
+
// Object identities carried as the operand of an identity cast
|
|
584
|
+
// ('sch.tbl'::regclass, 'sch.fn(uuid)'::regprocedure, 'sch'::regnamespace).
|
|
585
|
+
// The cast names the namespace, so these route through the same object
|
|
586
|
+
// routes as a RangeVar rather than through a string pattern — and the bare
|
|
587
|
+
// schema form, invisible to every string pass, routes here.
|
|
588
|
+
TypeCast: (path) => {
|
|
589
|
+
const node = path.node;
|
|
590
|
+
const sval = node?.arg?.A_Const?.sval;
|
|
591
|
+
if (typeof sval?.sval !== 'string')
|
|
592
|
+
return;
|
|
593
|
+
const ns = identityCastNamespace(node.typeName);
|
|
594
|
+
if (!ns)
|
|
595
|
+
return;
|
|
596
|
+
if (!claimSite(result, sval, 'sval'))
|
|
597
|
+
return;
|
|
598
|
+
sval.sval = transformIdentityCastLiteral(sval.sval, ns, router, result);
|
|
599
|
+
},
|
|
488
600
|
// Transform DefineStmt (CREATE TYPE schema.name, CREATE AGGREGATE schema.agg)
|
|
489
601
|
DefineStmt: (path) => {
|
|
490
602
|
const node = path.node;
|
|
@@ -1002,33 +1114,6 @@ function transformComments(content, schemaMapping, result) {
|
|
|
1002
1114
|
return prefix + newPath;
|
|
1003
1115
|
});
|
|
1004
1116
|
}
|
|
1005
|
-
/**
|
|
1006
|
-
* Transform verify function calls that use string literals.
|
|
1007
|
-
* These are inside SQL strings and not part of the main AST.
|
|
1008
|
-
*/
|
|
1009
|
-
function transformVerifyCalls(content, schemaMapping, result) {
|
|
1010
|
-
const schemas = Array.from(schemaMapping.keys()).sort((a, b) => b.length - a.length);
|
|
1011
|
-
let newContent = content;
|
|
1012
|
-
// The pattern needs the schema name spelled out, case-insensitively.
|
|
1013
|
-
let lowerContent = content.toLowerCase();
|
|
1014
|
-
for (const schema of schemas) {
|
|
1015
|
-
const newName = schemaMapping.get(schema);
|
|
1016
|
-
if (!newName)
|
|
1017
|
-
continue;
|
|
1018
|
-
if (!lowerContent.includes(schema.toLowerCase()))
|
|
1019
|
-
continue;
|
|
1020
|
-
const escapedSchema = cachedEscapeRegexp(schema);
|
|
1021
|
-
const verifyPattern = cachedRegExp(`(verify_(?:function|table|trigger|type|domain|view|index|constraint|schema|policy|table_grant|function_grant|sequence_grant|type_grant)\\s*\\(\\s*')${escapedSchema}(\\.|'\\s*\\))`, 'gi');
|
|
1022
|
-
const before = newContent;
|
|
1023
|
-
newContent = newContent.replace(verifyPattern, `$1${newName}$2`);
|
|
1024
|
-
if (newContent !== before) {
|
|
1025
|
-
lowerContent = newContent.toLowerCase();
|
|
1026
|
-
result.schemasFound.add(schema);
|
|
1027
|
-
result.schemasTransformed.set(schema, newName);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
return newContent;
|
|
1031
|
-
}
|
|
1032
1117
|
/**
|
|
1033
1118
|
* Transform schema names inside JSON/JSONB string values.
|
|
1034
1119
|
*/
|