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
5 changes: 4 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
Development Version
-------------------

Nothing yet.
Bug Fixes

* Keep parenthesized expressions in comma-separated identifier lists
while preserving separate VALUES rows (issue343).


Release 0.6.0 (Aug 13, 2026)
Expand Down
8 changes: 6 additions & 2 deletions sqlparse/engine/grouping.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,18 @@ def post(tlist, pidx, tidx, nidx):
def group_identifier_list(tlist):
m_role = T.Keyword, ('null', 'role')
sqlcls = (sql.Function, sql.Case, sql.Identifier, sql.Comparison,
sql.IdentifierList, sql.Operation)
sql.IdentifierList, sql.Operation, sql.Parenthesis)
ttypes = (T_NUMERICAL + T_STRING + T_NAME
+ (T.Keyword, T.Comment, T.Wildcard))

def match(token):
return token.match(T.Punctuation, ',')

def valid(token):
if (isinstance(token, sql.Parenthesis)
and isinstance(token.parent, sql.Values)):
# VALUES rows are separate tuples, not select-list expressions.
return False
return imt(token, i=sqlcls, m=m_role, t=ttypes)

def post(tlist, pidx, tidx, nidx):
Expand Down Expand Up @@ -468,8 +472,8 @@ def group(stmt):
group_assignment,

align_comments,
group_identifier_list,
group_values,
group_identifier_list,
]:
func(stmt)
return stmt
Expand Down
10 changes: 10 additions & 0 deletions tests/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,16 @@ def test_identifier_and_functions(self):
' nvl(1)',
'from dual'])

@pytest.mark.parametrize('expressions', [
['a', 'b', '(a-b)'],
['(a-b)', 'a', 'b'],
['a', '(a-b)', 'b'],
])
def test_parenthesized_select_list(self, expressions):
query = f"SELECT {', '.join(expressions)} FROM bla"
expected = 'SELECT ' + ',\n '.join(expressions) + '\nFROM bla'
assert sqlparse.format(query, reindent=True) == expected

def test_insert_values(self):
# issue 329
f = lambda sql: sqlparse.format(sql, reindent=True)
Expand Down
40 changes: 40 additions & 0 deletions tests/test_grouping.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,46 @@ def test_grouping_identifier_list():
assert isinstance(p.tokens[0].tokens[1], sql.IdentifierList)


@pytest.mark.parametrize('expressions', [
['a', 'b', '(a-b)'],
['(a-b)', 'a', 'b'],
['a', '(a-b)', 'b'],
['(a)', '(b)'],
['a', '((b-c))'],
['a', '(SELECT b FROM c)'],
])
def test_grouping_parenthesized_select_list(expressions):
# issue343: parentheses must not split a comma-separated select list.
query = f"SELECT {', '.join(expressions)} FROM bla"
parsed = sqlparse.parse(query)[0]
_, identifiers = parsed.token_next_by(i=sql.IdentifierList)
assert identifiers is not None
assert [str(token) for token in identifiers.get_identifiers()] == expressions
assert str(parsed) == query
assert all(token.parent is identifiers for token in identifiers.tokens)


@pytest.mark.parametrize('rows', [
['(1, 2)', '(3, 4)', '(5, 6)'],
['((1), (2))', '((3), (4))'],
['((SELECT a, (b-c) FROM bar), 1)', '(2, 3)'],
])
def test_grouping_values_rows_with_parenthesized_expressions(rows):
query = f"INSERT INTO foo VALUES {', '.join(rows)}"
parsed = sqlparse.parse(query)[0]
_, values = parsed.token_next_by(i=sql.Values)
assert values is not None
assert [str(token) for token in values.tokens
if isinstance(token, sql.Parenthesis)] == rows
assert not any(isinstance(token, sql.IdentifierList)
for token in values.tokens)
assert str(parsed) == query
for row in values.get_sublists():
_, identifiers = row.token_next_by(i=sql.IdentifierList)
assert identifiers is not None
assert len(list(identifiers.get_identifiers())) == 2


def test_grouping_identifier_list_subquery():
"""identifier lists should still work in subqueries with aliases"""
p = sqlparse.parse("select * from ("
Expand Down