From fc8793123d90d86bb72e9ab074479fa80106274f Mon Sep 17 00:00:00 2001 From: truffle Date: Fri, 3 Jul 2026 11:09:45 +0000 Subject: [PATCH] Parser: allow TOP as an identifier when not a row-limit clause `parse_select` unconditionally consumed a leading `TOP` keyword as the MSSQL/Snowflake row-limit clause, so `TOP` could never be used as an ordinary column name, alias, or table name. `SELECT top FROM t` failed with `Expected: literal int, found: FROM`. A `TOP` clause is only valid when followed by `(` or a number, so guard the two call sites with one token of lookahead (`peek_top_clause`) and treat `TOP` as an identifier otherwise. --- src/parser/mod.rs | 18 ++++++++++++++++-- tests/sqlparser_common.rs | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ac1b5e376..80fbd0689 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15115,7 +15115,8 @@ impl<'a> Parser<'a> { let mut top_before_distinct = false; let mut top = None; - if self.dialect.supports_top_before_distinct() && self.parse_keyword(Keyword::TOP) { + if self.dialect.supports_top_before_distinct() && self.peek_top_clause() { + self.expect_keyword(Keyword::TOP)?; top = Some(self.parse_top()?); top_before_distinct = true; } @@ -15126,7 +15127,8 @@ impl<'a> Parser<'a> { self.parse_all_or_distinct()? }; - if !self.dialect.supports_top_before_distinct() && self.parse_keyword(Keyword::TOP) { + if !self.dialect.supports_top_before_distinct() && self.peek_top_clause() { + self.expect_keyword(Keyword::TOP)?; top = Some(self.parse_top()?); } @@ -19386,6 +19388,18 @@ impl<'a> Parser<'a> { Ok(InterpolateExpr { column, expr }) } + /// Returns true if the parser is positioned at a `TOP` clause, i.e. the + /// `TOP` keyword followed by `(` or a number. When `TOP` is followed by + /// anything else it is an ordinary identifier (column, alias, or table + /// name), not the row-limit clause parsed by [`Self::parse_top`]. + fn peek_top_clause(&self) -> bool { + self.peek_keyword(Keyword::TOP) + && matches!( + self.peek_nth_token_ref(1).token, + Token::LParen | Token::Number(_, _) + ) + } + /// Parse a TOP clause, MSSQL equivalent of LIMIT, /// that follows after `SELECT [DISTINCT]`. pub fn parse_top(&mut self) -> Result { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 634b9aea2..c31b13d84 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -15376,6 +15376,23 @@ fn test_select_top() { dialects.verified_stmt("SELECT TOP 3 DISTINCT a, b, c FROM tbl"); } +#[test] +fn parse_top_as_identifier() { + // `TOP` is only the row-limit clause when followed by `(` or a number. + // Otherwise it is an ordinary identifier and must remain usable as a + // column name, alias, or table name. Covers both the `TOP`-before-distinct + // dialects and the general case. + let dialects = all_dialects_where(|d| d.supports_top_before_distinct()); + dialects.verified_stmt("SELECT top FROM tbl"); + dialects.verified_stmt("SELECT top, bottom FROM tbl"); + dialects.verified_stmt("SELECT top.val FROM tbl AS top"); + + let dialects = all_dialects_where(|d| !d.supports_top_before_distinct()); + dialects.verified_stmt("SELECT top FROM tbl"); + dialects.verified_stmt("SELECT top, bottom FROM tbl"); + dialects.verified_stmt("SELECT top.val FROM tbl AS top"); +} + #[test] fn parse_bang_not() { let dialects = all_dialects_where(|d| d.supports_bang_not_operator());