Skip to content

Commit 5621f04

Browse files
committed
quotes
1 parent 7df86d4 commit 5621f04

File tree

5 files changed

+105
-14
lines changed

5 files changed

+105
-14
lines changed

__fixtures__/generated/generated.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21037,6 +21037,34 @@
2103721037
"original/alter/alter-97.sql": "ALTER SCHEMA schemaname OWNER TO newowner",
2103821038
"misc/quotes_etc-1.sql": "CREATE USER MAPPING FOR local_user SERVER foreign_server OPTIONS (\"user\" 'remote_user', password 'secret123')",
2103921039
"misc/quotes_etc-2.sql": "CREATE USER MAPPING FOR local_user SERVER foreign_server OPTIONS (\"user\" 'remote_user', password 'secret123')",
21040+
"misc/quotes_etc-3.sql": "SELECT 'Line 1\nLine 2'",
21041+
"misc/quotes_etc-4.sql": "SELECT 'Column\tValue with quote: ''' AS formatted_string",
21042+
"misc/quotes_etc-5.sql": "SELECT E'Path is C:\\\\Program Files\\\\PostgreSQL\r\nDone.'",
21043+
"misc/quotes_etc-6.sql": "SELECT 'Unicode heart: ❤' AS unicode_heart",
21044+
"misc/quotes_etc-7.sql": "SELECT 'Extended Unicode: 🚀' AS rocket_emoji",
21045+
"misc/quotes_etc-8.sql": "SELECT 'Bell sound: \u0007' AS octal_escape",
21046+
"misc/quotes_etc-9.sql": "SELECT E'This is not a bytea literal: \\\\xDEAD and a newline \n'",
21047+
"misc/quotes_etc-10.sql": "SELECT E'\\\\\\\\xDEADBEEF'::bytea",
21048+
"misc/quotes_etc-11.sql": "INSERT INTO messages (content) VALUES ('Line one.\nLine two with tab:\tEnd.')",
21049+
"misc/quotes_etc-12.sql": "INSERT INTO logs (message) VALUES ('Escaped comment info: \nAuthor said: ''yes''')",
21050+
"misc/quotes_etc-13.sql": "SELECT E'Invalid path: C:\\\\Users\\\\Me\\\\Documents'",
21051+
"misc/quotes_etc-14.sql": "SELECT 'Page break here:\fNext page'",
21052+
"misc/quotes_etc-15.sql": "INSERT INTO configs (data) VALUES (E'{\"theme\": \"dark\", \"alert\": \"bell\\\\nchime\"}')",
21053+
"misc/quotes_etc-16.sql": "INSERT INTO docs (note) VALUES ('This value includes a SQL-style comment -- tricky!\nBut it''s safe here.')",
21054+
"misc/quotes_etc-17.sql": "SELECT 'Just a plain string, nothing to escape.'",
21055+
"misc/quotes_etc-18.sql": "SELECT 'Just a plain string, nothing to escape.'",
21056+
"misc/quotes_etc-19.sql": "SELECT E'This string has \"quotes\" and \\\\slashes\\\\' AS tricky_string",
21057+
"misc/quotes_etc-20.sql": "SELECT E'String with null byte: \\\\0 after this' AS null_char",
21058+
"misc/quotes_etc-21.sql": "SELECT E'This ends in backslash: \\\\' AS trailing_backslash",
21059+
"misc/quotes_etc-22.sql": "SELECT E'Config path: C:\\\\\\\\Temp\\\\\\\\Files\\\\' AS double_slash",
21060+
"misc/quotes_etc-23.sql": "SELECT 'First line\nSecond line\nThird line' AS multiline_string",
21061+
"misc/quotes_etc-24.sql": "WITH msg AS (SELECT 'CTE with newline\nand tab\tinside' AS txt) SELECT * FROM msg",
21062+
"misc/quotes_etc-25.sql": "SELECT 'Some string' AS \"select\"",
21063+
"misc/quotes_etc-26.sql": "SELECT E'Escapes: \\\\ \b \f \n \r \t \u000b ''' AS all_escapes",
21064+
"misc/quotes_etc-27.sql": "CREATE FUNCTION escape_example() RETURNS text AS $$\nBEGIN\n RETURN E'This has a newline\\\\nand tab\\\\twith quotes: \\\\'hello\\\\'';\nEND;\n$$ LANGUAGE plpgsql",
21065+
"misc/quotes_etc-28.sql": "DO $$\nBEGIN\n RAISE NOTICE 'Line one\\nLine two';\nEND;\n$$ LANGUAGE plpgsql",
21066+
"misc/quotes_etc-29.sql": "CREATE USER MAPPING FOR local_user SERVER foreign_server OPTIONS (\"user\" 'remote_user', password 'secret123')",
21067+
"misc/quotes_etc-30.sql": "CREATE USER MAPPING FOR local_user SERVER foreign_server OPTIONS (\"user\" 'remote_user', password 'secret123')",
2104021068
"misc/launchql-ext-types-1.sql": "CREATE DOMAIN attachment AS jsonb CHECK (value ?& ARRAY['url', 'mime'] AND (value ->> 'url') ~ E'^(https?)://[^\\\\s/$.?#].[^\\\\s]*$')",
2104121069
"misc/launchql-ext-types-2.sql": "COMMENT ON DOMAIN attachment IS '@name launchqlInternalTypeAttachment'",
2104221070
"misc/launchql-ext-types-3.sql": "CREATE DOMAIN email AS citext CHECK (value ~ E'^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$')",

__fixtures__/kitchen-sink/misc/quotes_etc.sql

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ CREATE USER MAPPING FOR local_user SERVER foreign_server OPTIONS (user 'remote_u
77
SELECT E'Line 1\nLine 2';
88

99
-- Tab character and single quote
10-
SELECT E'Column\tValue with quote: \'' AS formatted_string';
10+
SELECT E'Column\tValue with quote: \'' AS formatted_string;
1111

1212
-- Escaped backslash and carriage return
1313
SELECT E'Path is C:\\Program Files\\PostgreSQL\r\nDone.';
@@ -61,3 +61,45 @@ SELECT 'Just a plain string, nothing to escape.';
6161
-- Just to make sure we're parsing string types correctly
6262
-- sval.String.str with embedded backslashes and quotes
6363
SELECT E'This string has \"quotes\" and \\slashes\\' AS tricky_string;
64+
65+
66+
SELECT E'String with null byte: \\0 after this' AS null_char;
67+
68+
-- Backslash at end of string
69+
SELECT E'This ends in backslash: \\' AS trailing_backslash;
70+
71+
-- Double escaped path
72+
SELECT E'Config path: C:\\\\Temp\\\\Files\\' AS double_slash;
73+
74+
-- Multi-line string (escaped newlines)
75+
SELECT E'First line\nSecond line\nThird line' AS multiline_string;
76+
77+
-- E-string inside CTE
78+
WITH msg AS (
79+
SELECT E'CTE with newline\nand tab\tinside' AS txt
80+
)
81+
SELECT * FROM msg;
82+
83+
-- Reserved keyword as alias (quoted identifier)
84+
SELECT E'Some string' AS "select";
85+
86+
-- All common escapes in one go
87+
SELECT E'Escapes: \\ \b \f \n \r \t \v \'' AS all_escapes;
88+
89+
-- E-string inside a dollar-quoted PL/pgSQL block
90+
CREATE FUNCTION escape_example() RETURNS text AS $$
91+
BEGIN
92+
RETURN E'This has a newline\\nand tab\\twith quotes: \\'hello\\'';
93+
END;
94+
$$ LANGUAGE plpgsql;
95+
96+
-- Dollar-quoted function without E-string — for contrast
97+
DO $$
98+
BEGIN
99+
RAISE NOTICE 'Line one\nLine two';
100+
END;
101+
$$ LANGUAGE plpgsql;
102+
103+
-- CREATE USER MAPPING with and without quoted identifiers
104+
CREATE USER MAPPING FOR local_user SERVER "foreign_server" OPTIONS (user 'remote_user', password 'secret123');
105+
CREATE USER MAPPING FOR local_user SERVER foreign_server OPTIONS (user 'remote_user', password 'secret123');

packages/deparser/__tests__/kitchen-sink/misc-launchql-ext-types.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

22
import { FixtureTestUtils } from '../../test-utils';
3-
43
const fixtures = new FixtureTestUtils();
54

65
it('misc-launchql-ext-types', async () => {

packages/deparser/__tests__/kitchen-sink/misc-quotes_etc.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@ const fixtures = new FixtureTestUtils();
55
it('misc-quotes_etc', async () => {
66
await fixtures.runFixtureTests([
77
"misc/quotes_etc-1.sql",
8-
"misc/quotes_etc-2.sql"
8+
"misc/quotes_etc-2.sql",
9+
"misc/quotes_etc-3.sql",
10+
"misc/quotes_etc-4.sql",
11+
"misc/quotes_etc-5.sql",
12+
"misc/quotes_etc-6.sql",
13+
"misc/quotes_etc-7.sql",
14+
"misc/quotes_etc-8.sql",
15+
"misc/quotes_etc-9.sql",
16+
"misc/quotes_etc-10.sql",
17+
"misc/quotes_etc-11.sql",
18+
"misc/quotes_etc-12.sql",
19+
"misc/quotes_etc-13.sql",
20+
"misc/quotes_etc-14.sql",
21+
"misc/quotes_etc-15.sql",
22+
"misc/quotes_etc-16.sql",
23+
"misc/quotes_etc-17.sql",
24+
"misc/quotes_etc-18.sql",
25+
"misc/quotes_etc-19.sql",
26+
"misc/quotes_etc-20.sql",
27+
"misc/quotes_etc-21.sql",
28+
"misc/quotes_etc-22.sql",
29+
"misc/quotes_etc-23.sql",
30+
"misc/quotes_etc-24.sql",
31+
"misc/quotes_etc-25.sql",
32+
"misc/quotes_etc-26.sql",
33+
"misc/quotes_etc-27.sql",
34+
"misc/quotes_etc-28.sql",
35+
"misc/quotes_etc-29.sql",
36+
"misc/quotes_etc-30.sql"
937
]);
1038
});

packages/deparser/src/utils/quote-utils.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,9 @@ export class QuoteUtils {
8888
* Detects backslash escape sequences that require E-prefix in PostgreSQL
8989
*/
9090
static needsEscapePrefix(value: string): boolean {
91-
if (/^\\x[0-9a-fA-F]+$/i.test(value)) {
92-
return false;
93-
}
94-
95-
if (/\\x[0-9a-fA-F]/.test(value) && !/\\[nrtbf\\']/.test(value)) {
96-
return false;
97-
}
98-
99-
// Check for common backslash escape sequences that require E-prefix
100-
return /\\(?:[nrtbf\\']|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|[0-7]{1,3})/.test(value);
91+
// Always use E'' if the string contains any backslashes,
92+
// unless it's a raw \x... bytea-style literal.
93+
return !/^\\x[0-9a-fA-F]+$/i.test(value) && value.includes('\\');
10194
}
102-
}
95+
96+
}

0 commit comments

Comments
 (0)