Skip to content

Treat a reserved word after AS as an alias - #972

Open
cpruijsen wants to merge 1 commit into
sql-formatter-org:masterfrom
cpruijsen:fix/issue-801
Open

cpruijsen wants to merge 1 commit into
sql-formatter-org:masterfrom
cpruijsen:fix/issue-801

Conversation

@cpruijsen

@cpruijsen cpruijsen commented Sep 14, 2026

Copy link
Copy Markdown

Cut down to the AS-only rule, per the discussion below. The previous version tracked statement
position to also fix the bare alias form; that machinery is gone.

A reserved word used as an alias breaks the formatter. In SELECT id AS set FROM tbl the set
token is classified as the start of a SET clause, so a clause appears where the parser does not
expect one.

Directly after AS, a reserved word can only be an alias name. keywordAliasAfterAs converts it to
IDENTIFIER, and joins the existing disambiguateTokens pipeline alongside the five neighbour-local
passes, which are unchanged.

Only the token types that cannot legitimately follow AS are converted. RESERVED_CLAUSE is
excluded apart from SET, so CREATE TABLE t AS SELECT ..., ... AS VALUES (...) and
PREPARE foo AS UPDATE ... keep working. Data types are excluded for the same reason.

Not covered: the bare alias form, as in LEFT JOIN pg_settings set ON set.name = $9. A bare alias
needs to know where it sits in the statement, which is the positional tracking this version drops.
Anyone hitting that case can add AS.

Tests: the AS alias in the shared suite, the two PostgreSQL forms from the issue, and a regression
check that SET still starts a clause after an alias, as in UPDATE tbl AS set SET x = 1.

Refs #801

@nene

nene commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request.

I have to take some time to digest it, as it's quite a mouthful.

I totally understand why this change might be quite a large one. Well.. I personally had put that one aside as an unfixable one with the current architecture.

An initial glance at the code tells me that it contains lots of code to tackle various specific cases. That in turn leads me to the initial conclusion that this whole thing is likely very fragile. That is, it's not some sort of generic fix for this whole problem, but rather it's a set of patches for the problem in various scenarios that occurred to the author, with lots of other scenarios to be patched up in the future.

In general I'm reluctant to take aboard such a large piece of code which in its essence is one big hack. Don't take this personally, I very much believe that it might not really be possible to fix it in any non-hack way. I'm OK with small hacks to correct some shortcomings in this fundamentally flawed SQL Formatter library. But I'd like to avoid big hacks that can become a burden to maintain. Instead I like directing people into using prettier-plugin-sql-cst, which is built on top of a proper SQL parser, or to use conditional comments to disable the formatter for parts of code it can't handle.

Anyway, these are just my very initial thoughts. I'm planning to have a more proper look at this, and then give my full verdict.

@cpruijsen

Copy link
Copy Markdown
Author

Yeah, that's a fair read. It is a pile of positional special cases, and each new
dialect shape probably adds one.

One thing on the size: an AS-only rule would be a fraction of this, but it only
covers the first example in #801. The second one,
LEFT JOIN pg_settings set ON set.name = $9, has no AS, and a bare alias needs
to know where it is in the statement. That's where the context tracking came from.

If you want it smaller I'll cut it to AS only and leave the bare-alias form
unfixed, around 30 lines. And if neither version belongs here, just say so and
I'll close it. No need to read the whole thing first.

@nene

nene commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Hey, that sounds like a great plan. I think fixing the AS-aliases would solve a major chunk of the problem. Plus it will give an easy way for people to tweak their code to make it passable for the formatter.

Some dialects allow reserved words as aliases, as in
`SELECT id AS set FROM tbl`. The word is tokenized as a RESERVED_* token
and the parser then reads it as the start of a clause, so the formatter
breaks the line and reindents.

Directly after AS a reserved word can only be an alias name, so convert
it to IDENTIFIER there. Only the token types that cannot legitimately
follow AS are converted, leaving `CREATE TABLE t AS SELECT ...` and
`PREPARE foo AS UPDATE ...` working.

The bare alias form, as in `FROM pg_settings set`, needs statement
position tracking and is not covered here.

Refs sql-formatter-org#801
@cpruijsen cpruijsen changed the title Keep SET/VALUES clauses after reserved-word aliases Treat a reserved word after AS as an alias Sep 18, 2026
@cpruijsen

Copy link
Copy Markdown
Author

Cut down, pushed as bcd3a42. The PR is now 82 added lines across 3 files, down from 275, and the
title and description are rewritten to match.

The positional tracking is gone entirely. What is left is keywordAliasAfterAs, a neighbour-local
pass that sits in the existing disambiguateTokens pipeline next to the five already there:

const keywordAliasAfterAs = (token: Token, i: number, tokens: Token[]): Token => {
  if (canBeAliasAfterAs(token)) {
    const prevToken = prevNonCommentToken(tokens, i);
    if (prevToken && isAsKeyword(prevToken)) {
      return { ...token, type: TokenType.IDENTIFIER, text: token.raw };
    }
  }
  return token;
};

No paren stack, no clause tracking, no new helpers. The rest of the 45 source lines is
canBeAliasAfterAs, which is a list of the token types that cannot legitimately follow AS, plus
the doc comments. RESERVED_CLAUSE is excluded apart from SET, so CREATE TABLE t AS SELECT ...,
... AS VALUES (...) and PREPARE foo AS UPDATE ... are untouched.

The bare alias form is not covered, as agreed. LEFT JOIN pg_settings set ON set.name = $9 still
formats badly, and the description says so rather than leaving people to find out. Adding AS is
the workaround.

Full suite is green, 5866 passing, plus eslint and prettier. New tests are the AS alias in the
shared suite, the two PostgreSQL forms from the issue, and a check that SET still starts a clause
after an alias, as in UPDATE tbl AS set SET x = 1.

Happy to drop the canBeAliasAfterAs list to just SET if you would rather the first version of
this be as small as possible.

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.

2 participants