Skip to content

Commit f44d5c8

Browse files
gregfeliceclaude
andcommitted
Support doubled-quote escaping in Cypher string literals (issue #2222)
SQL drivers (psycopg2, JDBC, etc.) escape single quotes by doubling them ('isn''t') when substituting parameters into queries. When these substitutions land inside Cypher's $$ block, the Cypher scanner rejects them because it only recognizes backslash escaping (\'). This makes it difficult to pass strings containing apostrophes through SQL drivers. Add '' and "" as escape sequences in the Cypher scanner, following the same pattern already used for backtick-quoted identifiers (``). Flex picks the longer two-character match over the one-character closing quote, so the change is backwards-compatible -- '' was previously a syntax error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 217467a commit f44d5c8

File tree

3 files changed

+72
-0
lines changed

3 files changed

+72
-0
lines changed

regress/expected/scan.out

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,47 @@ $$) AS t(a agtype, b agtype, c agtype);
294294
" \" \" ' ' " | " ' ' \" \" " | " / / \\ \b \f \n \r \t "
295295
(1 row)
296296

297+
-- doubled-quote escape sequences for SQL driver compatibility (issue #2222)
298+
SELECT * FROM cypher('scan', $$
299+
RETURN 'isn''t'
300+
$$) AS t(a agtype);
301+
a
302+
---------
303+
"isn't"
304+
(1 row)
305+
306+
SELECT * FROM cypher('scan', $$
307+
RETURN '''hello'
308+
$$) AS t(a agtype);
309+
a
310+
----------
311+
"'hello"
312+
(1 row)
313+
314+
SELECT * FROM cypher('scan', $$
315+
RETURN 'hello'''
316+
$$) AS t(a agtype);
317+
a
318+
----------
319+
"hello'"
320+
(1 row)
321+
322+
SELECT * FROM cypher('scan', $$
323+
RETURN 'it''s a ''test'''
324+
$$) AS t(a agtype);
325+
a
326+
-----------------
327+
"it's a 'test'"
328+
(1 row)
329+
330+
SELECT * FROM cypher('scan', $$
331+
RETURN "she said ""hello"""
332+
$$) AS t(a agtype);
333+
a
334+
----------------------
335+
"she said \"hello\""
336+
(1 row)
337+
297338
-- invalid escape sequence
298339
SELECT * FROM cypher('scan', $$
299340
RETURN "\a"

regress/sql/scan.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,27 @@ SELECT * FROM cypher('scan', $$
202202
RETURN " \" \" ' \' ", ' \' \' " \" ', " / \/ \\ \b \f \n \r \t "
203203
$$) AS t(a agtype, b agtype, c agtype);
204204

205+
-- doubled-quote escape sequences for SQL driver compatibility (issue #2222)
206+
SELECT * FROM cypher('scan', $$
207+
RETURN 'isn''t'
208+
$$) AS t(a agtype);
209+
210+
SELECT * FROM cypher('scan', $$
211+
RETURN '''hello'
212+
$$) AS t(a agtype);
213+
214+
SELECT * FROM cypher('scan', $$
215+
RETURN 'hello'''
216+
$$) AS t(a agtype);
217+
218+
SELECT * FROM cypher('scan', $$
219+
RETURN 'it''s a ''test'''
220+
$$) AS t(a agtype);
221+
222+
SELECT * FROM cypher('scan', $$
223+
RETURN "she said ""hello"""
224+
$$) AS t(a agtype);
225+
205226
-- invalid escape sequence
206227
SELECT * FROM cypher('scan', $$
207228
RETURN "\a"

src/backend/parser/ag_scanner.l

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ dquote \"
195195
dqchars [^"\\]+
196196
squote '
197197
sqchars [^'\\]+
198+
esdquote {dquote}{dquote}
199+
essquote {squote}{squote}
198200
esascii \\["'/\\bfnrt]
199201
esasciifail \\[^Uu]?
200202
esunicode \\(U{hexdigit}{8}|u{hexdigit}{4})
@@ -420,6 +422,14 @@ ag_token token;
420422
strbuf_append_buf(&yyextra.literal_buf, yytext, yyleng);
421423
}
422424

425+
<dqstr>{esdquote} {
426+
strbuf_append_char(&yyextra.literal_buf, '"');
427+
}
428+
429+
<sqstr>{essquote} {
430+
strbuf_append_char(&yyextra.literal_buf, '\'');
431+
}
432+
423433
<dqstr,sqstr>{esascii} {
424434
char c;
425435

0 commit comments

Comments
 (0)