Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
Development Version
-------------------

Nothing yet.
Enhancements

* Honor the ``comma_first`` option when formatting with
``reindent_aligned``, so the commas line up under the keywords
(issue490).


Release 0.6.0 (Aug 13, 2026)
Expand Down
4 changes: 3 additions & 1 deletion docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ The :meth:`~sqlparse.format` function accepts the following keyword arguments.
in a programming language. Allowed values are "python" and "php".

``comma_first``
If ``True`` comma-first notation for column names is used.
If ``True`` comma-first notation for column names is used. This also
applies to ``reindent_aligned``, where the commas line up under the
keywords.


Security and Performance Considerations
Expand Down
22 changes: 20 additions & 2 deletions sqlparse/filters/aligned_indent.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ class AlignedIndentFilter:
'UNION', 'VALUES',
'SET', 'BETWEEN', 'EXCEPT')

def __init__(self, char=' ', n='\n'):
def __init__(self, char=' ', n='\n', comma_first=False):
self.n = n
self.offset = 0
self.indent = 0
self.char = char
self.comma_first = comma_first
self._max_kwd_len = len('select')

def nl(self, offset=1):
Expand Down Expand Up @@ -62,9 +63,26 @@ def _process_identifierlist(self, tlist):
# columns being selected
identifiers = list(tlist.get_identifiers())
identifiers.pop(0)
[tlist.insert_before(token, self.nl()) for token in identifiers]
for token in identifiers:
if self.comma_first:
self._break_before_comma(tlist, token)
else:
tlist.insert_before(token, self.nl())
self._process_default(tlist)

def _break_before_comma(self, tlist, token):
# comma-first puts the separator at the start of the next line, two
# columns left of where the aligned item sits, so the commas line up
# just under the keyword.
_, comma = tlist.token_prev(tlist.token_index(token))
if comma is None:
return
tlist.insert_before(comma, self.nl(offset=-1))
# keep a single space between the comma and the following item
_, ws = tlist.token_next(tlist.token_index(comma), skip_ws=False)
if ws is not None and ws.ttype is not T.Whitespace:
tlist.insert_after(comma, sql.Token(T.Whitespace, ' '))

def _process_case(self, tlist):
offset_ = len('case ') + len('when ')
cases = tlist.get_cases(skip_ws=True)
Expand Down
3 changes: 2 additions & 1 deletion sqlparse/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ def build_filter_stack(stack, options):
if options.get('reindent_aligned', False):
stack.enable_grouping()
stack.stmtprocess.append(
filters.AlignedIndentFilter(char=options['indent_char']))
filters.AlignedIndentFilter(char=options['indent_char'],
comma_first=options['comma_first']))

if options.get('right_margin'):
stack.enable_grouping()
Expand Down
21 changes: 21 additions & 0 deletions tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,27 @@ def test_window_functions(self):
'(PARTITION BY b, c ORDER BY d DESC) as row_num',
' from table'])

def test_comma_first(self):
sql = ('select j.jobtitle, count(*), max(salary) as top '
'from employee e, job j where e.orig_salary < 43000 '
'group by j.jobtitle')
f = lambda s: sqlparse.format(s, reindent_aligned=True,
comma_first=True)
assert f(sql) == '\n'.join([
'select j.jobtitle',
' , count(*)',
' , max(salary) as top',
' from employee e',
' , job j',
' where e.orig_salary < 43000',
' group by j.jobtitle'])

def test_comma_first_single_column(self):
# nothing to move to the front when there is only one column
f = lambda s: sqlparse.format(s, reindent_aligned=True,
comma_first=True)
assert f('select a from t') == 'select a\n from t'


class TestSpacesAroundOperators:
@staticmethod
Expand Down