From 2bdc7ee91736b44b9b4e8e61ea6081b5b1a99b68 Mon Sep 17 00:00:00 2001 From: Test User Date: Sat, 7 Feb 2026 17:45:08 -0600 Subject: [PATCH 1/2] Fix tokenization of escaped backslashes in SQL string literals (issue #814) The regex pattern for matching single-quoted strings did not handle escaped backslashes (\) properly. This caused strings like '\', '\' to be incorrectly tokenized as a single string with a comma instead of two separate string literals. Changes: - Add \\ to the string literal patterns in SQL_REGEX to match escaped backslashes as valid content within string literals - Add test case for escaped backslash tokenization Fixes #814 --- sqlparse/keywords.py | 4 ++-- tests/test_tokenize.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/sqlparse/keywords.py b/sqlparse/keywords.py index 874431f4..64834437 100644 --- a/sqlparse/keywords.py +++ b/sqlparse/keywords.py @@ -59,9 +59,9 @@ (r'(?![_A-ZÀ-Ü])-?(\d+(\.\d*)|\.\d+)(?![_A-ZÀ-Ü])', tokens.Number.Float), (r'(?![_A-ZÀ-Ü])-?\d+(?![_A-ZÀ-Ü])', tokens.Number.Integer), - (r"'(''|\\'|[^'])*'", tokens.String.Single), + (r"'(''|\\'|\\\\|[^'])*'", tokens.String.Single), # not a real string literal in ANSI SQL: - (r'"(""|\\"|[^"])*"', tokens.String.Symbol), + (r'"(""|\\"|\\\\|[^"])*"', tokens.String.Symbol), (r'(""|".*?[^\\]")', tokens.String.Symbol), # sqlite names can be escaped with [square brackets]. left bracket # cannot be preceded by word character or a right bracket -- diff --git a/tests/test_tokenize.py b/tests/test_tokenize.py index e368e83e..f22bf9f0 100644 --- a/tests/test_tokenize.py +++ b/tests/test_tokenize.py @@ -99,6 +99,21 @@ def test_single_quotes(): assert repr(p.tokens[0])[:len(tst)] == tst +def test_single_quotes_escaped_backslash(): + # issue 814 - Incorrect Tokenization of Escaped Backslashes + # A string containing an escaped backslash (\\) should be tokenized + # as a single string literal, not split incorrectly. + sql = r"SELECT '\\', '\\'" + tokens = list(lexer.tokenize(sql)) + # Should be: SELECT, ws, '\\', ,, ws, '\\' + assert tokens[0] == (T.Keyword.DML, 'SELECT') + assert tokens[1] == (T.Whitespace, ' ') + assert tokens[2] == (T.String.Single, "'\\\\'") + assert tokens[3] == (T.Punctuation, ',') + assert tokens[4] == (T.Whitespace, ' ') + assert tokens[5] == (T.String.Single, "'\\\\'") + + def test_tokenlist_first(): p = sqlparse.parse(' select foo')[0] first = p.token_first() From f8b260805bb987ac6def899ccdcddfa0199f7262 Mon Sep 17 00:00:00 2001 From: Rami Abdelrazzaq Date: Sun, 13 Sep 2026 07:47:02 -0500 Subject: [PATCH 2/2] Strengthen escaped backslash lexer regression coverage --- tests/test_issue_814.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/test_issue_814.py diff --git a/tests/test_issue_814.py b/tests/test_issue_814.py new file mode 100644 index 00000000..8e606311 --- /dev/null +++ b/tests/test_issue_814.py @@ -0,0 +1,28 @@ +from sqlparse import lexer +from sqlparse import tokens as T + + +def test_escaped_backslashes_single_quoted_token_stream(): + tokens = list(lexer.tokenize(r"SELECT '\\', '\\'")) + + assert tokens == [ + (T.Keyword.DML, "SELECT"), + (T.Whitespace, " "), + (T.String.Single, r"'\\'"), + (T.Punctuation, ","), + (T.Whitespace, " "), + (T.String.Single, r"'\\'"), + ] + + +def test_escaped_backslashes_double_quoted_token_stream(): + tokens = list(lexer.tokenize(r'SELECT "\\", "\\"')) + + assert tokens == [ + (T.Keyword.DML, "SELECT"), + (T.Whitespace, " "), + (T.String.Symbol, r'"\\"'), + (T.Punctuation, ","), + (T.Whitespace, " "), + (T.String.Symbol, r'"\\"'), + ]