Skip to content

Commit 033dfd6

Browse files
committed
fix: quote typname parts when hydration parse fails (PG18 unquoted typnames)
1 parent a8d5cfe commit 033dfd6

1 file changed

Lines changed: 35 additions & 4 deletions

File tree

packages/plpgsql-deparser/src/hydrate.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,27 @@ function extractTypeNameFromCast(result: ParseResult): Node | undefined {
158158
* Handles special suffixes like %rowtype and %type by stripping them before
159159
* parsing and preserving them in the result.
160160
*/
161+
/**
162+
* Quotes each dot-separated part of a type name that is not already quoted,
163+
* preserving array bounds (e.g. `my-schema.mytype[]` -> `"my-schema"."mytype"[]`).
164+
*/
165+
function quoteTypnameParts(typname: string): string {
166+
let bounds = '';
167+
let base = typname;
168+
const boundsMatch = base.match(/(\[[^\]]*\])+$/);
169+
if (boundsMatch) {
170+
bounds = boundsMatch[0];
171+
base = base.substring(0, base.length - bounds.length);
172+
}
173+
const parts = base.split('.').map(part => {
174+
if (part.startsWith('"') && part.endsWith('"')) {
175+
return part;
176+
}
177+
return `"${part.replace(/"/g, '""')}"`;
178+
});
179+
return parts.join('.') + bounds;
180+
}
181+
161182
function hydrateTypeName(
162183
typname: string,
163184
path: string,
@@ -194,10 +215,20 @@ function hydrateTypeName(
194215
try {
195216
// Parse the type name by wrapping it in a cast expression
196217
// Keep quotes intact for proper parsing of special identifiers
197-
const sql = `SELECT NULL::${parseTypname}`;
198-
const parseResult = parseSync(sql);
199-
const typeNameNode = extractTypeNameFromCast(parseResult);
200-
218+
let typeNameNode;
219+
try {
220+
const sql = `SELECT NULL::${parseTypname}`;
221+
const parseResult = parseSync(sql);
222+
typeNameNode = extractTypeNameFromCast(parseResult);
223+
} catch (parseErr) {
224+
// The parser reports typnames without quoting, so identifiers that
225+
// require quotes (e.g. schema names containing dashes) fail to parse
226+
// verbatim. Retry with each dot-separated part quoted.
227+
const quoted = quoteTypnameParts(parseTypname);
228+
const parseResult = parseSync(`SELECT NULL::${quoted}`);
229+
typeNameNode = extractTypeNameFromCast(parseResult);
230+
}
231+
201232
if (typeNameNode) {
202233
stats.typeNameExpressions++;
203234
return {

0 commit comments

Comments
 (0)