diff --git a/packages/deparser/src/deparser.ts b/packages/deparser/src/deparser.ts index a5ffd22c..454d881c 100644 --- a/packages/deparser/src/deparser.ts +++ b/packages/deparser/src/deparser.ts @@ -1254,18 +1254,18 @@ export class Deparser implements DeparserVisitor { } else if (nodeAny.sval !== undefined) { if (typeof nodeAny.sval === 'object' && nodeAny.sval !== null) { if (nodeAny.sval.sval !== undefined) { - return QuoteUtils.escape(nodeAny.sval.sval); + return QuoteUtils.formatEString(nodeAny.sval.sval); } else if (nodeAny.sval.String && nodeAny.sval.String.sval !== undefined) { - return QuoteUtils.escape(nodeAny.sval.String.sval); + return QuoteUtils.formatEString(nodeAny.sval.String.sval); } else if (Object.keys(nodeAny.sval).length === 0) { return "''"; } else { - return QuoteUtils.escape(nodeAny.sval.toString()); + return QuoteUtils.formatEString(nodeAny.sval.toString()); } } else if (nodeAny.sval === null) { return 'NULL'; } else { - return QuoteUtils.escape(nodeAny.sval); + return QuoteUtils.formatEString(nodeAny.sval); } } else if (nodeAny.boolval !== undefined) { if (typeof nodeAny.boolval === 'object' && nodeAny.boolval !== null) { @@ -2014,9 +2014,11 @@ export class Deparser implements DeparserVisitor { return caseMap[defName.toLowerCase()] || defName; } + + String(node: t.String, context: DeparserContext): string { if (context.isStringLiteral || context.isEnumValue) { - return `'${node.sval || ''}'`; + return QuoteUtils.formatEString(node.sval || ''); } const value = node.sval || ''; @@ -6215,8 +6217,7 @@ export class Deparser implements DeparserVisitor { if (node.comment === null || node.comment === undefined) { output.push('NULL'); } else if (node.comment) { - const escapedComment = node.comment.replace(/'/g, "''"); - output.push(`'${escapedComment}'`); + output.push(QuoteUtils.formatEString(node.comment)); } return output.join(' '); diff --git a/packages/deparser/src/utils/quote-utils.ts b/packages/deparser/src/utils/quote-utils.ts index f55fd710..b8111db0 100644 --- a/packages/deparser/src/utils/quote-utils.ts +++ b/packages/deparser/src/utils/quote-utils.ts @@ -60,4 +60,43 @@ export class QuoteUtils { static escape(literal: string): string { return `'${literal.replace(/'/g, "''")}'`; } + + /** + * Escapes a string value for use in E-prefixed string literals + * Handles both backslashes and single quotes properly + */ + static escapeEString(value: string): string { + return value.replace(/\\/g, '\\\\').replace(/'/g, "''"); + } + + /** + * Formats a string as an E-prefixed string literal with proper escaping + * This wraps the complete E-prefix logic including detection and formatting + */ + static formatEString(value: string): string { + const needsEscape = QuoteUtils.needsEscapePrefix(value); + if (needsEscape) { + const escapedValue = QuoteUtils.escapeEString(value); + return `E'${escapedValue}'`; + } else { + return QuoteUtils.escape(value); + } + } + + /** + * Determines if a string value needs E-prefix for escaped string literals + * Detects backslash escape sequences that require E-prefix in PostgreSQL + */ + static needsEscapePrefix(value: string): boolean { + if (/^\\x[0-9a-fA-F]+$/i.test(value)) { + return false; + } + + if (/\\x[0-9a-fA-F]/.test(value) && !/\\[nrtbf\\']/.test(value)) { + return false; + } + + // Check for common backslash escape sequences that require E-prefix + return /\\(?:[nrtbf\\']|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|[0-7]{1,3})/.test(value); + } }