Skip to content

Commit a9f82ca

Browse files
committed
Guard case/parenthesis alignment against malformed input
reindent_aligned=True and strip_whitespace=True could both crash on certain malformed SQL where grouping doesn't produce the shape these filters expect. For CASE without a matching END as a direct child (it can end up nested inside a sibling group instead), _process_case would grab a None token and hand it to insert_before, which blew up with ValueError: None is not in list. For a parenthesis whose contents collapse into a single nested group during parsing (e.g. "( AS )"), _stripws_parenthesis assumed at least two direct children and raised IndexError on tokens[1]. Both filters now bail out gracefully instead of assuming a well-formed tree, with regression tests for each case using the exact reproducers from the two issues.
1 parent 60cdc64 commit a9f82ca

3 files changed

Lines changed: 29 additions & 3 deletions

File tree

sqlparse/filters/aligned_indent.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ def _process_case(self, tlist):
7070
cases = tlist.get_cases(skip_ws=True)
7171
# align the end as well
7272
end_token = tlist.token_next_by(m=(T.Keyword, 'END'))[1]
73+
if end_token is None:
74+
# A malformed CASE expression can leave END nested inside a
75+
# sibling group instead of being a direct child of this token
76+
# list (get_cases and token_next_by only look at direct
77+
# children), so there's nothing valid to align it against.
78+
return
7379
cases.append((None, [end_token]))
7480

7581
condition_width = [len(' '.join(map(str, cond))) if cond else 0

sqlparse/filters/others.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,15 @@ def _stripws_identifierlist(self, tlist):
112112
return self._stripws_default(tlist)
113113

114114
def _stripws_parenthesis(self, tlist):
115-
while tlist.tokens[1].is_whitespace:
115+
# A malformed parenthesis can end up with only one or two direct
116+
# children once grouping is done (e.g. the whole inside collapses
117+
# into a single nested group), so don't assume tokens[1]/tokens[-2]
118+
# are always there.
119+
while len(tlist.tokens) > 2 and tlist.tokens[1].is_whitespace:
116120
tlist.tokens.pop(1)
117-
while tlist.tokens[-2].is_whitespace:
121+
while len(tlist.tokens) > 2 and tlist.tokens[-2].is_whitespace:
118122
tlist.tokens.pop(-2)
119-
if tlist.tokens[-2].is_group:
123+
if len(tlist.tokens) > 1 and tlist.tokens[-2].is_group:
120124
# save to remove the last whitespace
121125
while tlist.tokens[-2].tokens[-1].is_whitespace:
122126
tlist.tokens[-2].tokens.pop(-1)

tests/test_regressions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,19 @@ def limit_recursion():
516516
def test_max_recursion(limit_recursion):
517517
with pytest.raises(SQLParseError):
518518
sqlparse.parse('[' * 1000 + ']' * 1000)
519+
520+
521+
def test_stripws_parenthesis_with_no_direct_children_issue885():
522+
# Malformed input can collapse the whole parenthesis body into a single
523+
# nested group, leaving fewer than two direct children of the
524+
# Parenthesis token list. This used to raise IndexError.
525+
assert sqlparse.format('( AS )', strip_whitespace=True) == '( AS )'
526+
527+
528+
def test_aligned_indent_case_without_direct_end_issue886():
529+
# Malformed CASE expressions can end up with the END keyword nested
530+
# inside a sibling group rather than being a direct child of the Case
531+
# token list, so token_next_by can't find it and used to raise
532+
# ValueError when that None was later used as an insertion point.
533+
sql = "CASE 'a' := WHERE END SELECT GO # ->>"
534+
assert sqlparse.format(sql, reindent_aligned=True) == sql

0 commit comments

Comments
 (0)