Skip to content

Commit 92db206

Browse files
authored
Snowflake: Improve accuracy of lookahead in implicit LIMIT alias (#1941)
1 parent 4d93386 commit 92db206

File tree

2 files changed

+45
-11
lines changed

2 files changed

+45
-11
lines changed

src/dialect/snowflake.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use crate::ast::helpers::stmt_data_loading::{
2323
FileStagingCommand, StageLoadSelectItem, StageLoadSelectItemKind, StageParamsObject,
2424
};
2525
use crate::ast::{
26-
ColumnOption, ColumnPolicy, ColumnPolicyProperty, CopyIntoSnowflakeKind, Ident,
27-
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
26+
ColumnOption, ColumnPolicy, ColumnPolicyProperty, CopyIntoSnowflakeKind, DollarQuotedString,
27+
Ident, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
2828
IdentityPropertyOrder, ObjectName, ObjectNamePart, RowAccessPolicy, ShowObjects, SqlOption,
2929
Statement, TagsColumnOption, WrappedCollection,
3030
};
@@ -307,22 +307,22 @@ impl Dialect for SnowflakeDialect {
307307
// they are not followed by other tokens that may change their meaning
308308
// e.g. `SELECT * EXCEPT (col1) FROM tbl`
309309
Keyword::EXCEPT
310-
// e.g. `SELECT 1 LIMIT 5`
311-
| Keyword::LIMIT
312-
// e.g. `SELECT 1 OFFSET 5 ROWS`
313-
| Keyword::OFFSET
314310
// e.g. `INSERT INTO t SELECT 1 RETURNING *`
315311
| Keyword::RETURNING if !matches!(parser.peek_token_ref().token, Token::Comma | Token::EOF) =>
316312
{
317313
false
318314
}
319315

316+
// e.g. `SELECT 1 LIMIT 5` - not an alias
317+
// e.g. `SELECT 1 OFFSET 5 ROWS` - not an alias
318+
Keyword::LIMIT | Keyword::OFFSET if peek_for_limit_options(parser) => false,
319+
320320
// `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
321321
// which would give it a different meanings, for example:
322322
// `SELECT 1 FETCH FIRST 10 ROWS` - not an alias
323323
// `SELECT 1 FETCH 10` - not an alias
324324
Keyword::FETCH if parser.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]).is_some()
325-
|| matches!(parser.peek_token().token, Token::Number(_, _)) =>
325+
|| peek_for_limit_options(parser) =>
326326
{
327327
false
328328
}
@@ -351,20 +351,23 @@ impl Dialect for SnowflakeDialect {
351351
match kw {
352352
// The following keywords can be considered an alias as long as
353353
// they are not followed by other tokens that may change their meaning
354-
Keyword::LIMIT
355-
| Keyword::RETURNING
354+
Keyword::RETURNING
356355
| Keyword::INNER
357356
| Keyword::USING
358357
| Keyword::PIVOT
359358
| Keyword::UNPIVOT
360359
| Keyword::EXCEPT
361360
| Keyword::MATCH_RECOGNIZE
362-
| Keyword::OFFSET
363361
if !matches!(parser.peek_token_ref().token, Token::SemiColon | Token::EOF) =>
364362
{
365363
false
366364
}
367365

366+
// `LIMIT` can be considered an alias as long as it's not followed by a value. For example:
367+
// `SELECT * FROM tbl LIMIT WHERE 1=1` - alias
368+
// `SELECT * FROM tbl LIMIT 3` - not an alias
369+
Keyword::LIMIT | Keyword::OFFSET if peek_for_limit_options(parser) => false,
370+
368371
// `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
369372
// which would give it a different meanings, for example:
370373
// `SELECT * FROM tbl FETCH FIRST 10 ROWS` - not an alias
@@ -373,7 +376,7 @@ impl Dialect for SnowflakeDialect {
373376
if parser
374377
.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT])
375378
.is_some()
376-
|| matches!(parser.peek_token().token, Token::Number(_, _)) =>
379+
|| peek_for_limit_options(parser) =>
377380
{
378381
false
379382
}
@@ -387,6 +390,7 @@ impl Dialect for SnowflakeDialect {
387390
{
388391
false
389392
}
393+
390394
Keyword::GLOBAL if parser.peek_keyword(Keyword::FULL) => false,
391395

392396
// Reserved keywords by the Snowflake dialect, which seem to be less strictive
@@ -472,6 +476,18 @@ impl Dialect for SnowflakeDialect {
472476
}
473477
}
474478

479+
// Peeks ahead to identify tokens that are expected after
480+
// a LIMIT/FETCH keyword.
481+
fn peek_for_limit_options(parser: &Parser) -> bool {
482+
match &parser.peek_token_ref().token {
483+
Token::Number(_, _) | Token::Placeholder(_) => true,
484+
Token::SingleQuotedString(val) if val.is_empty() => true,
485+
Token::DollarQuotedString(DollarQuotedString { value, .. }) if value.is_empty() => true,
486+
Token::Word(w) if w.keyword == Keyword::NULL => true,
487+
_ => false,
488+
}
489+
}
490+
475491
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {
476492
let stage = parse_snowflake_stage_name(parser)?;
477493
let pattern = if parser.parse_keyword(Keyword::PATTERN) {

tests/sqlparser_snowflake.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3535,6 +3535,15 @@ fn test_sql_keywords_as_select_item_aliases() {
35353535
.parse_sql_statements(&format!("SELECT 1 {kw}"))
35363536
.is_err());
35373537
}
3538+
3539+
// LIMIT is alias
3540+
snowflake().one_statement_parses_to("SELECT 1 LIMIT", "SELECT 1 AS LIMIT");
3541+
// LIMIT is not an alias
3542+
snowflake().verified_stmt("SELECT 1 LIMIT 1");
3543+
snowflake().verified_stmt("SELECT 1 LIMIT $1");
3544+
snowflake().verified_stmt("SELECT 1 LIMIT ''");
3545+
snowflake().verified_stmt("SELECT 1 LIMIT NULL");
3546+
snowflake().verified_stmt("SELECT 1 LIMIT $$$$");
35383547
}
35393548

35403549
#[test]
@@ -3586,6 +3595,15 @@ fn test_sql_keywords_as_table_aliases() {
35863595
.parse_sql_statements(&format!("SELECT * FROM tbl {kw}"))
35873596
.is_err());
35883597
}
3598+
3599+
// LIMIT is alias
3600+
snowflake().one_statement_parses_to("SELECT * FROM tbl LIMIT", "SELECT * FROM tbl AS LIMIT");
3601+
// LIMIT is not an alias
3602+
snowflake().verified_stmt("SELECT * FROM tbl LIMIT 1");
3603+
snowflake().verified_stmt("SELECT * FROM tbl LIMIT $1");
3604+
snowflake().verified_stmt("SELECT * FROM tbl LIMIT ''");
3605+
snowflake().verified_stmt("SELECT * FROM tbl LIMIT NULL");
3606+
snowflake().verified_stmt("SELECT * FROM tbl LIMIT $$$$");
35893607
}
35903608

35913609
#[test]

0 commit comments

Comments
 (0)