@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 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, transformVerifyCalls, validateNoUntransformedSchemas, walkPlpgsqlForSchemas, } from './transform';
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, transformVerifyCalls, validateNoUntransformedSchemas, walkPlpgsqlForSchemas, } from './transform';
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.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.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;
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.15.3",
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": "69cdce673db035fb1ee3b529480d39fac01512ab"
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
  */