From 7c1316c2d055da5d782c694b39ef8eadc1ed3939 Mon Sep 17 00:00:00 2001 From: repzakske <31244169+repzakske@users.noreply.github.com> Date: Thu, 15 Oct 2026 21:07:49 +0200 Subject: [PATCH 1/2] Preserve quoted newlines following backslashes --- CHANGELOG | 3 ++- sqlparse/utils.py | 2 +- tests/test_format.py | 10 ++++++++++ tests/test_utils.py | 11 +++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 44d5938e..35d8bddc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,8 @@ Development Version ------------------- -Nothing yet. +* Preserve whitespace and line endings inside quoted values containing a + backslash followed by a newline when formatting SQL. Release 0.6.0 (Aug 13, 2026) diff --git a/sqlparse/utils.py b/sqlparse/utils.py index 58c0245a..d0126380 100644 --- a/sqlparse/utils.py +++ b/sqlparse/utils.py @@ -28,7 +28,7 @@ '(?:[^'\\]|\\.)*' # Match single quoted strings ) ) -""", re.VERBOSE) +""", re.VERBOSE | re.DOTALL) LINE_MATCH = re.compile(r'(\r\n|\r|\n)') diff --git a/tests/test_format.py b/tests/test_format.py index 93495067..c5c89a03 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -175,6 +175,16 @@ def test_notransform_of_quoted_crlf(self): == "SELECT some_column LIKE 'value\\\\\\'\r' WHERE id = 1\n") +@pytest.mark.parametrize('quote', ["'", '"']) +@pytest.mark.parametrize('newline', ['\n', '\r', '\r\n']) +@pytest.mark.parametrize('options', [{}, {'strip_whitespace': True}, + {'reindent': True}]) +def test_preserve_quoted_newlines_after_backslash(quote, newline, options): + text = 'SELECT ' + quote + 'a\\' + newline + 'b \r\nc' + quote + ';' + + assert sqlparse.format(text, **options) == text + + class TestFormatReindentAligned: @staticmethod def formatter(sql): diff --git a/tests/test_utils.py b/tests/test_utils.py index d020f3fa..9405d1f2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,3 +10,14 @@ ['`foo`', 'foo'])) def test_remove_quotes(value, expected): assert utils.remove_quotes(value) == expected + + +@pytest.mark.parametrize('quote', ["'", '"']) +@pytest.mark.parametrize('newline', ['\n', '\r', '\r\n']) +@pytest.mark.parametrize('backslashes', [1, 2]) +def test_split_unquoted_newlines_after_backslash(quote, newline, backslashes): + quoted = (quote + 'a' + '\\' * backslashes + newline + + 'b \r\nc' + quote) + text = 'SELECT\r\n' + quoted + '\rFROM t' + + assert utils.split_unquoted_newlines(text) == ['SELECT', quoted, 'FROM t'] From e2003702b452475b3a94d04817b97f165b02fe56 Mon Sep 17 00:00:00 2001 From: carlitogwapito <20962552+carlitogwapito@users.noreply.github.com> Date: Fri, 16 Oct 2026 05:47:48 -0600 Subject: [PATCH 2/2] test: preserve quoted payloads during keyword transformations Signed-off-by: Casper Verbuyst --- tests/test_format.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_format.py b/tests/test_format.py index c5c89a03..0dfb8985 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -185,6 +185,20 @@ def test_preserve_quoted_newlines_after_backslash(quote, newline, options): assert sqlparse.format(text, **options) == text +@pytest.mark.parametrize('mode', ['strip_whitespace', 'reindent']) +def test_keyword_transform_preserves_multiple_literals(mode): + left = "'select a\\\nb \r\nc'" + right = "'from d\\\ne \r\nf'" + text = f'select {left} as x,\n{right} as y;' + + actual = sqlparse.format(text, keyword_case='upper', **{mode: True}) + + assert actual.startswith('SELECT ') + assert actual.count(' AS ') == 2 + assert left in actual + assert right in actual + + class TestFormatReindentAligned: @staticmethod def formatter(sql):