@pgsql/transform 18.15.3 → 18.16.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/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, transformVerifyCalls, 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;
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, transformVerifyCalls, 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.transformVerifyCalls = 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; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgsql/transform",
3
- "version": "18.15.3",
3
+ "version": "18.16.0",
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": "9ecd247f9f0ef696a95e1f582d7353eb6bb693d4"
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;
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;
@@ -230,6 +232,98 @@ function transformSchemaRefsInString(str, schemaMapping, result) {
230
232
  }
231
233
  return out;
232
234
  }
235
+ /**
236
+ * Cast types whose string operand is an object reference rather than opaque
237
+ * text. `'sch.tbl'::regclass` is a relation the parser resolves, and the cast
238
+ * names the namespace it resolves in — so the reference can be routed from the
239
+ * cast, semantically, instead of pattern-matched. This reaches what the string
240
+ * passes cannot: a bare `'sch'::regnamespace` has no dot to match on.
241
+ */
242
+ const IDENTITY_CAST_NAMESPACES = {
243
+ regclass: 'relation',
244
+ regcollation: 'unknown',
245
+ regconfig: 'unknown',
246
+ regdictionary: 'unknown',
247
+ regnamespace: 'schema',
248
+ regoper: 'unknown',
249
+ regoperator: 'unknown',
250
+ regproc: 'function',
251
+ regprocedure: 'function',
252
+ regtype: 'type'
253
+ };
254
+ /**
255
+ * The namespace a cast resolves its operand in, or `undefined` when the target
256
+ * type is not an object-identity type.
257
+ */
258
+ function identityCastNamespace(typeName) {
259
+ const names = typeName?.names;
260
+ if (!Array.isArray(names) || names.length === 0)
261
+ return undefined;
262
+ // Only `pg_catalog`-qualified (or unqualified) reg* types are the built-ins.
263
+ if (names.length > 1 && names[0]?.String?.sval !== 'pg_catalog')
264
+ return undefined;
265
+ const typeIdent = names[names.length - 1]?.String?.sval;
266
+ if (typeof typeIdent !== 'string')
267
+ return undefined;
268
+ return IDENTITY_CAST_NAMESPACES[typeIdent.toLowerCase()];
269
+ }
270
+ const IDENTITY_TOKEN = String.raw `(?:"(?:[^"]|"")*"|[^\s".,()[\]]+)`;
271
+ const BARE_IDENTITY_RE = new RegExp(`^\\s*(${IDENTITY_TOKEN})\\s*$`);
272
+ const QUALIFIED_IDENTITY_RE = new RegExp(`^\\s*(${IDENTITY_TOKEN})\\.([\\s\\S]+)$`);
273
+ function unquoteIdentifier(token) {
274
+ if (token.length > 1 && token.startsWith('"') && token.endsWith('"')) {
275
+ return token.slice(1, -1).replace(/""/g, '"');
276
+ }
277
+ return token;
278
+ }
279
+ /**
280
+ * Route the object reference carried in the string operand of an identity
281
+ * cast. `ns` comes from the cast's target type.
282
+ *
283
+ * Unqualified operands (`'tbl'::regclass`) name no schema — they resolve
284
+ * through `search_path` — so they are returned unchanged.
285
+ */
286
+ function transformIdentityCastLiteral(sval, ns, schemaMapping, result) {
287
+ const router = asRouter(schemaMapping);
288
+ if (ns === 'schema') {
289
+ const bare = BARE_IDENTITY_RE.exec(sval);
290
+ if (!bare)
291
+ return sval;
292
+ const schemaName = unquoteIdentifier(bare[1]);
293
+ const newName = router.resolve(schemaName, undefined, 'schema');
294
+ if (!newName || newName === schemaName)
295
+ return sval;
296
+ result.schemasFound.add(schemaName);
297
+ result.schemasTransformed.set(schemaName, newName);
298
+ return quotes_1.QuoteUtils.quoteIdentifier(newName);
299
+ }
300
+ // `regprocedure` and `regoperator` carry an argument list after the name, and
301
+ // an argument type can itself be schema-qualified
302
+ // (`fn("my-schema".row_type)`), so the list is routed as ordinary schema refs.
303
+ const argsAt = sval.indexOf('(');
304
+ const head = argsAt === -1 ? sval : sval.slice(0, argsAt);
305
+ const args = argsAt === -1 ? '' : transformSchemaRefsInString(sval.slice(argsAt), router, result);
306
+ const qualified = QUALIFIED_IDENTITY_RE.exec(head);
307
+ if (!qualified)
308
+ return `${head}${args}`;
309
+ const schemaName = unquoteIdentifier(qualified[1]);
310
+ const objName = unquoteIdentifier(qualified[2].trim());
311
+ const target = router.resolveObject(schemaName, objName, ns);
312
+ if (!target)
313
+ return `${head}${args}`;
314
+ const rebound = target.name !== undefined && target.name !== objName;
315
+ const requalified = target.schema === null || (!!target.schema && target.schema !== schemaName);
316
+ if (!rebound && !requalified)
317
+ return `${head}${args}`;
318
+ result.schemasFound.add(schemaName);
319
+ const nameToken = quotes_1.QuoteUtils.quoteIdentifier(rebound ? target.name : objName);
320
+ if (target.schema === null)
321
+ return `${nameToken}${args}`;
322
+ if (target.schema && target.schema !== schemaName) {
323
+ result.schemasTransformed.set(schemaName, target.schema);
324
+ }
325
+ return `${quotes_1.QuoteUtils.quoteIdentifier(target.schema ?? schemaName)}.${nameToken}${args}`;
326
+ }
233
327
  /**
234
328
  * Create a SQL AST visitor that transforms schema names.
235
329
  *
@@ -482,9 +576,28 @@ function createSqlVisitor(schemaMapping, result, visitorOptions) {
482
576
  A_Const: (path) => {
483
577
  const node = path.node;
484
578
  if (typeof node.sval?.sval === 'string' && node.sval.sval.includes('.')) {
579
+ if (!claimSite(result, node.sval, 'sval'))
580
+ return;
485
581
  node.sval.sval = transformSchemaRefsInString(node.sval.sval, schemaMapping, result);
486
582
  }
487
583
  },
584
+ // Object identities carried as the operand of an identity cast
585
+ // ('sch.tbl'::regclass, 'sch.fn(uuid)'::regprocedure, 'sch'::regnamespace).
586
+ // The cast names the namespace, so these route through the same object
587
+ // routes as a RangeVar rather than through a string pattern — and the bare
588
+ // schema form, invisible to every string pass, routes here.
589
+ TypeCast: (path) => {
590
+ const node = path.node;
591
+ const sval = node?.arg?.A_Const?.sval;
592
+ if (typeof sval?.sval !== 'string')
593
+ return;
594
+ const ns = identityCastNamespace(node.typeName);
595
+ if (!ns)
596
+ return;
597
+ if (!claimSite(result, sval, 'sval'))
598
+ return;
599
+ sval.sval = transformIdentityCastLiteral(sval.sval, ns, router, result);
600
+ },
488
601
  // Transform DefineStmt (CREATE TYPE schema.name, CREATE AGGREGATE schema.agg)
489
602
  DefineStmt: (path) => {
490
603
  const node = path.node;