What
GROUPS becomes a non-reserved keyword again, so it can be used as an identifier (column, table, alias) while the GROUPS window frame unit keeps working:
SELECT groups FROM mytable
SELECT * FROM groups
CREATE TABLE mytable (groups INT)
Why / Root cause
#2460 (commit d92f407) introduced the PostgreSQL GROUPS frame unit and declared <K_GROUPS:"GROUPS"> in the reserved keyword token section. In PostgreSQL, GROUPS is a non-reserved keyword and stays a legal identifier, so statements like the ones above parsed before #2460 and fail on master with Encountered unexpected token: "groups".
The decision in #2460
Making GROUPS reserved was a deliberate, reviewed trade-off, not an oversight. #2460 initially declared K_GROUPS inside NonReservedWord() (24cf1bd); during review the raw <S_IDENTIFIER> productions (CREATE SCHEMA, SET PATH, Oracle KEEP, interval type, COLLATE, the ColDataType lookahead) were called out, and the token was then moved to the reserved set instead of broadening those productions.
One point that was not on the table then: the sibling frame units already live in exactly the state this PR restores for GROUPS. ROWS and RANGE are non-reserved on master, and the same raw-identifier productions reject them identically:
CREATE SCHEMA rows -- fails on master
CREATE SCHEMA range -- fails on master
SELECT a COLLATE rows FROM t -- fails on master
while quoted forms (CREATE SCHEMA "groups") work everywhere. "Non-reserved, raw-identifier positions need quoting" is therefore the existing status quo for frame-unit keywords in this grammar, and this PR makes GROUPS consistent with ROWS and RANGE (all three are non-reserved in PostgreSQL) rather than introducing a new inconsistency.
If keeping GROUPS reserved is preferred, closing this PR is fine. If the raw-identifier productions should accept the frame-unit words, that would be a separate change applying to ROWS, RANGE and GROUPS alike; I can prepare it on request.
How
One symmetric relocation in JSqlParserCC.jjt: the <K_GROUPS:"GROUPS"> declaration moves from the reserved token section into the NonReservedWord() production (between GRANT and GROUP_CONCAT).
Tokens declared inside NonReservedWord() fall into the MIN_NON_RESERVED_WORD … MAX_NON_RESERVED_WORD kind range, which RelObjectName() and isAliasAhead() already accept, and which KeywordsTest covers parameterized as identifiers. No other grammar rule changes, and the explicit <K_GROUPS> match in WindowElement() is unaffected (only the token kind numbering moves).
Scope
Only the keyword classification changes. GROUPS frame units (ROWS / RANGE / GROUPS BETWEEN ... EXCLUDE ...) keep parsing exactly as before; GROUPS additionally works in every identifier position covered by the non-reserved range guards (column, table, schema, alias, function name).
Testing
KeywordsTest.testGroupsAsIdentifier reproduces the issue (column, table, explicit and implicit alias, CREATE TABLE column definition): fails on master with JSQLParserException, passes with this change. The parameterized KeywordsTest identifier cases pick GROUPS up automatically (732 -> 734 tests).
Performance
gradle jmh, JSQLParserBenchmark.parseSQLStatements on performance.sql, version=latest, 10 forks x 10 iterations (100 samples) on a 32-core host:
| build |
ms/op |
master (406a4d4) |
3.712 ± 0.020 |
| this PR (37b6068) |
3.709 ± 0.022 |
The -0.08% delta lies within the 99.9% confidence intervals (the CIs overlap almost entirely): no regression.
Verification of the original issue
All statements from #2473 fail on master and parse + round-trip with this change.
Fixes #2473