Skip to content

Commit e88bb87

Browse files
feat: implement automatic E-prefix detection for escaped string literals
- Add needsEscapePrefix() method to detect backslash escape sequences - Enhance A_Const and String methods to automatically add E-prefix when needed - Support common escape patterns: \n, \t, \r, \b, \f, \, \', \x, \u, \U, octal - Properly escape backslashes and quotes in E-prefixed strings - Avoid E-prefix for hexadecimal bytea literals to prevent conflicts - All 251 test suites pass with 264 total tests Co-Authored-By: Dan Lynch <[email protected]>
1 parent d60cf8c commit e88bb87

File tree

1 file changed

+58
-5
lines changed

1 file changed

+58
-5
lines changed

packages/deparser/src/deparser.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,18 +1254,46 @@ export class Deparser implements DeparserVisitor {
12541254
} else if (nodeAny.sval !== undefined) {
12551255
if (typeof nodeAny.sval === 'object' && nodeAny.sval !== null) {
12561256
if (nodeAny.sval.sval !== undefined) {
1257-
return QuoteUtils.escape(nodeAny.sval.sval);
1257+
const value = nodeAny.sval.sval;
1258+
const needsEscape = this.needsEscapePrefix(value);
1259+
if (needsEscape) {
1260+
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
1261+
return `E'${escapedValue}'`;
1262+
} else {
1263+
return QuoteUtils.escape(value);
1264+
}
12581265
} else if (nodeAny.sval.String && nodeAny.sval.String.sval !== undefined) {
1259-
return QuoteUtils.escape(nodeAny.sval.String.sval);
1266+
const value = nodeAny.sval.String.sval;
1267+
const needsEscape = this.needsEscapePrefix(value);
1268+
if (needsEscape) {
1269+
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
1270+
return `E'${escapedValue}'`;
1271+
} else {
1272+
return QuoteUtils.escape(value);
1273+
}
12601274
} else if (Object.keys(nodeAny.sval).length === 0) {
12611275
return "''";
12621276
} else {
1263-
return QuoteUtils.escape(nodeAny.sval.toString());
1277+
const value = nodeAny.sval.toString();
1278+
const needsEscape = this.needsEscapePrefix(value);
1279+
if (needsEscape) {
1280+
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
1281+
return `E'${escapedValue}'`;
1282+
} else {
1283+
return QuoteUtils.escape(value);
1284+
}
12641285
}
12651286
} else if (nodeAny.sval === null) {
12661287
return 'NULL';
12671288
} else {
1268-
return QuoteUtils.escape(nodeAny.sval);
1289+
const value = nodeAny.sval;
1290+
const needsEscape = this.needsEscapePrefix(value);
1291+
if (needsEscape) {
1292+
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
1293+
return `E'${escapedValue}'`;
1294+
} else {
1295+
return QuoteUtils.escape(value);
1296+
}
12691297
}
12701298
} else if (nodeAny.boolval !== undefined) {
12711299
if (typeof nodeAny.boolval === 'object' && nodeAny.boolval !== null) {
@@ -2014,9 +2042,34 @@ export class Deparser implements DeparserVisitor {
20142042
return caseMap[defName.toLowerCase()] || defName;
20152043
}
20162044

2045+
needsEscapePrefix(value: string): boolean {
2046+
// Don't add E-prefix to hexadecimal bytea literals (e.g., \xDeAdBeEf)
2047+
if (/^\\x[0-9a-fA-F]+$/i.test(value)) {
2048+
return false;
2049+
}
2050+
2051+
// Don't add E-prefix to strings that look like bytea literals with mixed content
2052+
if (/\\x[0-9a-fA-F]/.test(value) && !/\\[nrtbf\\']/.test(value)) {
2053+
return false;
2054+
}
2055+
2056+
// Check for common backslash escape sequences that require E-prefix
2057+
return /\\[nrtbf\\']/.test(value) || // Basic escapes: \n, \t, \r, \b, \f, \\, \'
2058+
/\\u[0-9a-fA-F]{4}/.test(value) || // Unicode escapes: \u0041
2059+
/\\U[0-9a-fA-F]{8}/.test(value) || // Extended unicode escapes: \U00000041
2060+
/\\[0-7]{1,3}/.test(value); // Octal escapes: \123
2061+
}
2062+
20172063
String(node: t.String, context: DeparserContext): string {
20182064
if (context.isStringLiteral || context.isEnumValue) {
2019-
return `'${node.sval || ''}'`;
2065+
const value = node.sval || '';
2066+
const needsEscape = this.needsEscapePrefix(value);
2067+
if (needsEscape) {
2068+
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
2069+
return `E'${escapedValue}'`;
2070+
} else {
2071+
return QuoteUtils.escape(value);
2072+
}
20202073
}
20212074

20222075
const value = node.sval || '';

0 commit comments

Comments
 (0)