Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions packages/deparser/src/deparser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 || '';
Expand Down Expand Up @@ -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(' ');
Expand Down
39 changes: 39 additions & 0 deletions packages/deparser/src/utils/quote-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}