From 51894597437c480e268c67439a0208772be6f5d0 Mon Sep 17 00:00:00 2001 From: MbappeWU Date: Sat, 31 Oct 2026 18:17:22 -0700 Subject: [PATCH] fix: avoid ValueError on malformed CASE with reindent_aligned AlignedIndentFilter._process_case appended a None END token when END was missing or nested inside a subgroup, then crashed in token_index during insert_before. Skip a missing END, guard empty case arms, and tolerate tokens that are not direct children. Fixes #886 --- sqlparse/filters/aligned_indent.py | 19 ++++++++++++++++--- tests/test_format.py | 6 ++++++ tests/test_regressions.py | 9 +++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/sqlparse/filters/aligned_indent.py b/sqlparse/filters/aligned_indent.py index 34f34c0e..fb9972e7 100644 --- a/sqlparse/filters/aligned_indent.py +++ b/sqlparse/filters/aligned_indent.py @@ -68,12 +68,14 @@ def _process_identifierlist(self, tlist): def _process_case(self, tlist): offset_ = len('case ') + len('when ') cases = tlist.get_cases(skip_ws=True) - # align the end as well + # align the end as well (skip when END is missing or nested) end_token = tlist.token_next_by(m=(T.Keyword, 'END'))[1] # END is missing on malformed input, e.g. when an inner BEGIN ... END # block closed the CASE. if end_token is not None: cases.append((None, [end_token])) + if not cases: + return condition_width = [len(' '.join(map(str, cond))) if cond else 0 for cond, _ in cases] @@ -81,10 +83,21 @@ def _process_case(self, tlist): for i, (cond, value) in enumerate(cases): # cond is None when 'else or end' - stmt = cond[0] if cond else value[0] + if cond: + stmt = cond[0] + elif value: + stmt = value[0] + else: + continue + if stmt is None: + continue if i > 0: - tlist.insert_before(stmt, self.nl(offset_ - len(str(stmt)))) + try: + tlist.insert_before(stmt, self.nl(offset_ - len(str(stmt)))) + except ValueError: + # Token is not a direct child (malformed CASE) — leave as-is. + continue if cond: ws = sql.Token(T.Whitespace, self.char * ( max_cond_width - condition_width[i])) diff --git a/tests/test_format.py b/tests/test_format.py index 06c93424..55961d7d 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -346,6 +346,12 @@ def test_window_functions(self): ' from table']) + + def test_malformed_case_does_not_raise(self): + # issue #886 + sql = "CASE 'a' := WHERE END SELECT GO # ->>" + assert isinstance(self.formatter(sql), str) + class TestSpacesAroundOperators: @staticmethod def formatter(sql): diff --git a/tests/test_regressions.py b/tests/test_regressions.py index 2b48da8a..5cb1c991 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -527,3 +527,12 @@ def test_max_recursion(limit_recursion): ]) def test_format_malformed_input_does_not_crash(sql, options): assert isinstance(sqlparse.format(sql, **options), str) + + +def test_reindent_aligned_malformed_case_no_valueerror_issue886(): + # Malformed CASE where END is nested inside an assignment group used to + # raise ValueError from token_index(None) inside AlignedIndentFilter. + sql = "CASE 'a' := WHERE END SELECT GO # ->>" + out = sqlparse.format(sql, reindent_aligned=True) + assert isinstance(out, str) + assert 'CASE' in out