diff --git a/sqlparse/filters/reindent.py b/sqlparse/filters/reindent.py index ea41ac6b..0c501015 100644 --- a/sqlparse/filters/reindent.py +++ b/sqlparse/filters/reindent.py @@ -93,8 +93,14 @@ def _split_statements(self, tlist): tidx -= 1 # only break if it's not the first token if prev_: - tlist.insert_before(tidx, self.nl()) - tidx += 1 + # Check that there is actual non-whitespace content before + # this keyword. If all preceding tokens are whitespace + # (e.g. inter-statement blank lines that process() already + # handles), skip -- otherwise we double-count the separator. + _, before = tlist.token_prev(tidx, skip_ws=True) + if before is not None: + tlist.insert_before(tidx, self.nl()) + tidx += 1 tidx, token = tlist.token_next_by(t=ttypes, idx=tidx) def _process(self, tlist): diff --git a/tests/test_format.py b/tests/test_format.py index 0cdbcf88..8ffb4522 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -649,6 +649,37 @@ def test_insert_values(self): ' , (3, 4)', ' , (5, 6)']) + def test_multi_statement_idempotent(self): + # Reformatting should not add accumulating blank lines + # between statements. + f = lambda sql: sqlparse.format(sql, reindent=True) + sql = 'select a from b; select c from d' + once = f(sql) + twice = f(once) + assert once == twice, f'not idempotent:\n {once!r}\n {twice!r}' + + # Also with keyword_case + f2 = lambda sql: sqlparse.format( + sql, reindent=True, keyword_case='upper') + once = f2(sql) + twice = f2(once) + assert once == twice + + # And with comma_first + f3 = lambda sql: sqlparse.format( + sql, reindent=True, comma_first=True) + once = f3(sql) + twice = f3(once) + assert once == twice + + def test_union_idempotent(self): + # UNION within a single statement should also be idempotent + f = lambda sql: sqlparse.format(sql, reindent=True) + sql = 'select a from b union select c from d' + once = f(sql) + twice = f(once) + assert once == twice, f'not idempotent:\n {once!r}\n {twice!r}' + class TestOutputFormat: def test_python(self):