Skip to content

Group ROLE as an identifier when used as a column name (issue798) - #872

Open
adarshsm wants to merge 1 commit into
andialbrecht:masterfrom
adarshsm:fix/798-role-identifier
Open

Group ROLE as an identifier when used as a column name (issue798)#872
adarshsm wants to merge 1 commit into
andialbrecht:masterfrom
adarshsm:fix/798-role-identifier

Conversation

@adarshsm

@adarshsm adarshsm commented Aug 1, 2026

Copy link
Copy Markdown

Fixes #798.

Problem

ROLE is a non-reserved keyword, so it can be used as a column name, but it is always tokenized as a keyword and group_identifier only wraps Name/String.Symbol tokens. So in SELECT a, role, b the role member stays a bare keyword while a and b become Identifiers:

>>> il = sqlparse.parse("SELECT a, role, b FROM t")[0].tokens[2]
>>> [(type(i).__name__, str(i.ttype)) for i in il.get_identifiers()]
[('Identifier', 'None'), ('Token', 'Token.Keyword'), ('Identifier', 'None')]

Why not just drop ROLE from the keyword list

That makes the column case work and passes the whole suite, but it silently regresses DDL: with ROLE as a plain name, CREATE ROLE myrole groups as an identifier ROLE aliased to myrole (same for SET ROLE). So a lexer-level change is out.

Fix

Add group_identifier_role, which wraps ROLE in an Identifier only when it is a member of an identifier list (flanked by a comma). Outside a list — CREATE ROLE, SET ROLE, DROP ROLE r1, r2, GRANT ... TO ROLE r — there is no adjacent comma, so it keeps its keyword role. This mirrors the existing m_role = ('null', 'role') special-case already in group_identifier_list.

>>> [(type(i).__name__) for i in il.get_identifiers()]
['Identifier', 'Identifier', 'Identifier']

Tests

Two regression tests in tests/test_grouping.py: role becomes an Identifier in a list, and ROLE stays an un-wrapped keyword in CREATE/SET/DROP ROLE. Full suite passes (496), ruff check sqlparse/ clean, CHANGELOG updated.

Scope note

I scoped this to ROLE to match the existing m_role handling. A few other non-reserved keywords (TYPE, SOURCE, LANGUAGE, LOCATION) have the same latent behaviour in a column list — happy to generalize the approach in this PR or a follow-up if you'd prefer.

ROLE is a non-reserved keyword, so it can also be a column name, but the
lexer always tokenized it as a keyword and `group_identifier` only wraps
Name/String.Symbol tokens. As a result `SELECT a, role, b` left `role` as a
bare keyword while `a` and `b` became Identifiers, so `get_identifiers()`
returned it inconsistently.

Simply dropping ROLE from the keyword list is not viable: it would make
`CREATE ROLE r` parse as an identifier aliased to the role name. Instead,
add `group_identifier_role`, which wraps ROLE in an Identifier only when it
sits in an identifier list (flanked by a comma). Outside a list -- e.g.
`CREATE ROLE`, `SET ROLE`, `DROP ROLE` -- there is no adjacent comma, so it
keeps its keyword role. This mirrors the existing `('null', 'role')`
special-case already present in `group_identifier_list`.
@adarshsm
adarshsm force-pushed the fix/798-role-identifier branch from 2ecbf3f to a1900a3 Compare August 29, 2026 14:41
@adarshsm

Copy link
Copy Markdown
Author

Rebased onto current master (60cdc64) — the branch had gone conflicting. Force-pushed as a1900a3; the fix itself is unchanged.

The only conflict was in CHANGELOG, which has since been reformatted (single backticks instead of double, and prNNN by author attributions). I kept the current format and restyled my entry to match, adding the attribution:

* Group `ROLE` as an identifier when it is used as a column name in an
  identifier list (e.g. `SELECT a, role, b`), so it appears alongside the
  other columns in `get_identifiers()`, while keeping it a keyword elsewhere
  such as `CREATE ROLE` (issue798, pr872 by adarshsm).

pytest tests/ is 508 passed, 2 xfailed, 1 xpassed, and ruff check sqlparse/ is clean on current master.

No rush from my side — just keeping it mergeable. The scope note in the original description still stands: TYPE, SOURCE, LANGUAGE and LOCATION have the same latent issue, and I am happy to generalise the fix if you would prefer that shape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SELECT A, ROLE, B FROM table_name;

1 participant