Skip to content

Commit 6c49b82

Browse files
committed
Merge remote-tracking branch 'apache/main' into prepare_0.56.0
2 parents d0e306e + 2b5bdce commit 6c49b82

File tree

10 files changed

+133
-28
lines changed

10 files changed

+133
-28
lines changed

dev/release/generate-changelog.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ def print_pulls(repo_name, title, pulls):
2828
print()
2929
for (pull, commit) in pulls:
3030
url = "https://github.com/{}/pull/{}".format(repo_name, pull.number)
31-
print("- {} [#{}]({}) ({})".format(pull.title, pull.number, url, commit.author.login))
31+
author = f"({commit.author.login})" if commit.author else ''
32+
print("- {} [#{}]({}) {}".format(pull.title, pull.number, url, author))
3233
print()
3334

3435

@@ -161,4 +162,4 @@ def cli(args=None):
161162
generate_changelog(repo, project, args.tag1, args.tag2, args.version)
162163

163164
if __name__ == "__main__":
164-
cli()
165+
cli()

src/ast/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -930,12 +930,14 @@ pub enum Expr {
930930
Nested(Box<Expr>),
931931
/// A literal value, such as string, number, date or NULL
932932
Value(ValueWithSpan),
933+
/// Prefixed expression, e.g. introducer strings, projection prefix
933934
/// <https://dev.mysql.com/doc/refman/8.0/en/charset-introducer.html>
934-
IntroducedString {
935-
introducer: String,
935+
/// <https://docs.snowflake.com/en/sql-reference/constructs/connect-by>
936+
Prefixed {
937+
prefix: Ident,
936938
/// The value of the constant.
937939
/// Hint: you can unwrap the string value using `value.into_string()`.
938-
value: Value,
940+
value: Box<Expr>,
939941
},
940942
/// A constant of form `<data_type> 'value'`.
941943
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
@@ -1655,7 +1657,7 @@ impl fmt::Display for Expr {
16551657
Expr::Collate { expr, collation } => write!(f, "{expr} COLLATE {collation}"),
16561658
Expr::Nested(ast) => write!(f, "({ast})"),
16571659
Expr::Value(v) => write!(f, "{v}"),
1658-
Expr::IntroducedString { introducer, value } => write!(f, "{introducer} {value}"),
1660+
Expr::Prefixed { prefix, value } => write!(f, "{prefix} {value}"),
16591661
Expr::TypedString { data_type, value } => {
16601662
write!(f, "{data_type}")?;
16611663
write!(f, " {value}")
@@ -2472,10 +2474,11 @@ impl fmt::Display for DeclareAssignment {
24722474
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24732475
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
24742476
pub enum DeclareType {
2475-
/// Cursor variable type. e.g. [Snowflake] [PostgreSQL]
2477+
/// Cursor variable type. e.g. [Snowflake] [PostgreSQL] [MsSql]
24762478
///
24772479
/// [Snowflake]: https://docs.snowflake.com/en/developer-guide/snowflake-scripting/cursors#declaring-a-cursor
24782480
/// [PostgreSQL]: https://www.postgresql.org/docs/current/plpgsql-cursors.html
2481+
/// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/declare-cursor-transact-sql
24792482
Cursor,
24802483

24812484
/// Result set variable type. [Snowflake]

src/ast/spans.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1543,7 +1543,7 @@ impl Spanned for Expr {
15431543
.map(|items| union_spans(items.iter().map(|i| i.span()))),
15441544
),
15451545
),
1546-
Expr::IntroducedString { value, .. } => value.span(),
1546+
Expr::Prefixed { value, .. } => value.span(),
15471547
Expr::Case {
15481548
operand,
15491549
conditions,

src/dialect/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,12 @@ pub trait Dialect: Debug + Any {
888888
keywords::RESERVED_FOR_TABLE_FACTOR
889889
}
890890

891+
/// Returns reserved keywords that may prefix a select item expression
892+
/// e.g. `SELECT CONNECT_BY_ROOT name FROM Tbl2` (Snowflake)
893+
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
894+
&[]
895+
}
896+
891897
/// Returns true if this dialect supports the `TABLESAMPLE` option
892898
/// before the table alias option. For example:
893899
///

src/dialect/snowflake.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ use alloc::{format, vec};
4444
use super::keywords::RESERVED_FOR_IDENTIFIER;
4545
use sqlparser::ast::StorageSerializationPolicy;
4646

47+
const RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR: [Keyword; 1] = [Keyword::CONNECT_BY_ROOT];
4748
/// A [`Dialect`] for [Snowflake](https://www.snowflake.com/)
4849
#[derive(Debug, Default)]
4950
pub struct SnowflakeDialect;
@@ -346,6 +347,11 @@ impl Dialect for SnowflakeDialect {
346347
fn supports_group_by_expr(&self) -> bool {
347348
true
348349
}
350+
351+
/// See: <https://docs.snowflake.com/en/sql-reference/constructs/connect-by>
352+
fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
353+
&RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR
354+
}
349355
}
350356

351357
fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ define_keywords!(
207207
CONNECT,
208208
CONNECTION,
209209
CONNECTOR,
210+
CONNECT_BY_ROOT,
210211
CONSTRAINT,
211212
CONTAINS,
212213
CONTINUE,

src/parser/mod.rs

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,9 +1388,9 @@ impl<'a> Parser<'a> {
13881388
| Token::HexStringLiteral(_)
13891389
if w.value.starts_with('_') =>
13901390
{
1391-
Ok(Expr::IntroducedString {
1392-
introducer: w.value.clone(),
1393-
value: self.parse_introduced_string_value()?,
1391+
Ok(Expr::Prefixed {
1392+
prefix: w.clone().into_ident(w_span),
1393+
value: self.parse_introduced_string_expr()?.into(),
13941394
})
13951395
}
13961396
// string introducer https://dev.mysql.com/doc/refman/8.0/en/charset-introducer.html
@@ -1399,9 +1399,9 @@ impl<'a> Parser<'a> {
13991399
| Token::HexStringLiteral(_)
14001400
if w.value.starts_with('_') =>
14011401
{
1402-
Ok(Expr::IntroducedString {
1403-
introducer: w.value.clone(),
1404-
value: self.parse_introduced_string_value()?,
1402+
Ok(Expr::Prefixed {
1403+
prefix: w.clone().into_ident(w_span),
1404+
value: self.parse_introduced_string_expr()?.into(),
14051405
})
14061406
}
14071407
Token::Arrow if self.dialect.supports_lambda_functions() => {
@@ -6446,7 +6446,7 @@ impl<'a> Parser<'a> {
64466446
/// DECLARE
64476447
// {
64486448
// { @local_variable [AS] data_type [ = value ] }
6449-
// | { @cursor_variable_name CURSOR }
6449+
// | { @cursor_variable_name CURSOR [ FOR ] }
64506450
// } [ ,...n ]
64516451
/// ```
64526452
/// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/declare-local-variable-transact-sql?view=sql-server-ver16
@@ -6462,14 +6462,19 @@ impl<'a> Parser<'a> {
64626462
/// ```text
64636463
// {
64646464
// { @local_variable [AS] data_type [ = value ] }
6465-
// | { @cursor_variable_name CURSOR }
6465+
// | { @cursor_variable_name CURSOR [ FOR ]}
64666466
// } [ ,...n ]
64676467
/// ```
64686468
/// [MsSql]: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/declare-local-variable-transact-sql?view=sql-server-ver16
64696469
pub fn parse_mssql_declare_stmt(&mut self) -> Result<Declare, ParserError> {
64706470
let name = {
64716471
let ident = self.parse_identifier()?;
6472-
if !ident.value.starts_with('@') {
6472+
if !ident.value.starts_with('@')
6473+
&& !matches!(
6474+
self.peek_token().token,
6475+
Token::Word(w) if w.keyword == Keyword::CURSOR
6476+
)
6477+
{
64736478
Err(ParserError::TokenizerError(
64746479
"Invalid MsSql variable declaration.".to_string(),
64756480
))
@@ -6493,7 +6498,14 @@ impl<'a> Parser<'a> {
64936498
_ => (None, Some(self.parse_data_type()?)),
64946499
};
64956500

6496-
let assignment = self.parse_mssql_variable_declaration_expression()?;
6501+
let (for_query, assignment) = if self.peek_keyword(Keyword::FOR) {
6502+
self.next_token();
6503+
let query = Some(self.parse_query()?);
6504+
(query, None)
6505+
} else {
6506+
let assignment = self.parse_mssql_variable_declaration_expression()?;
6507+
(None, assignment)
6508+
};
64976509

64986510
Ok(Declare {
64996511
names: vec![name],
@@ -6504,7 +6516,7 @@ impl<'a> Parser<'a> {
65046516
sensitive: None,
65056517
scroll: None,
65066518
hold: None,
6507-
for_query: None,
6519+
for_query,
65086520
})
65096521
}
65106522

@@ -9023,13 +9035,19 @@ impl<'a> Parser<'a> {
90239035
}
90249036
}
90259037

9026-
fn parse_introduced_string_value(&mut self) -> Result<Value, ParserError> {
9038+
fn parse_introduced_string_expr(&mut self) -> Result<Expr, ParserError> {
90279039
let next_token = self.next_token();
90289040
let span = next_token.span;
90299041
match next_token.token {
9030-
Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())),
9031-
Token::DoubleQuotedString(ref s) => Ok(Value::DoubleQuotedString(s.to_string())),
9032-
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
9042+
Token::SingleQuotedString(ref s) => Ok(Expr::Value(
9043+
Value::SingleQuotedString(s.to_string()).with_span(span),
9044+
)),
9045+
Token::DoubleQuotedString(ref s) => Ok(Expr::Value(
9046+
Value::DoubleQuotedString(s.to_string()).with_span(span),
9047+
)),
9048+
Token::HexStringLiteral(ref s) => Ok(Expr::Value(
9049+
Value::HexStringLiteral(s.to_string()).with_span(span),
9050+
)),
90339051
unexpected => self.expected(
90349052
"a string value",
90359053
TokenWithSpan {
@@ -13956,6 +13974,13 @@ impl<'a> Parser<'a> {
1395613974

1395713975
/// Parse a comma-delimited list of projections after SELECT
1395813976
pub fn parse_select_item(&mut self) -> Result<SelectItem, ParserError> {
13977+
let prefix = self
13978+
.parse_one_of_keywords(
13979+
self.dialect
13980+
.get_reserved_keywords_for_select_item_operator(),
13981+
)
13982+
.map(|keyword| Ident::new(format!("{:?}", keyword)));
13983+
1395913984
match self.parse_wildcard_expr()? {
1396013985
Expr::QualifiedWildcard(prefix, token) => Ok(SelectItem::QualifiedWildcard(
1396113986
SelectItemQualifiedWildcardKind::ObjectName(prefix),
@@ -14000,8 +14025,11 @@ impl<'a> Parser<'a> {
1400014025
expr => self
1400114026
.maybe_parse_select_item_alias()
1400214027
.map(|alias| match alias {
14003-
Some(alias) => SelectItem::ExprWithAlias { expr, alias },
14004-
None => SelectItem::UnnamedExpr(expr),
14028+
Some(alias) => SelectItem::ExprWithAlias {
14029+
expr: maybe_prefixed_expr(expr, prefix),
14030+
alias,
14031+
},
14032+
None => SelectItem::UnnamedExpr(maybe_prefixed_expr(expr, prefix)),
1400514033
}),
1400614034
}
1400714035
}
@@ -15363,6 +15391,17 @@ impl<'a> Parser<'a> {
1536315391
}
1536415392
}
1536515393

15394+
fn maybe_prefixed_expr(expr: Expr, prefix: Option<Ident>) -> Expr {
15395+
if let Some(prefix) = prefix {
15396+
Expr::Prefixed {
15397+
prefix,
15398+
value: Box::new(expr),
15399+
}
15400+
} else {
15401+
expr
15402+
}
15403+
}
15404+
1536615405
impl Word {
1536715406
#[deprecated(since = "0.54.0", note = "please use `into_ident` instead")]
1536815407
pub fn to_ident(&self, span: Span) -> Ident {

tests/sqlparser_mssql.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,10 @@ fn parse_mssql_declare() {
13871387
],
13881388
ast
13891389
);
1390+
1391+
let declare_cursor_for_select =
1392+
"DECLARE vend_cursor CURSOR FOR SELECT * FROM Purchasing.Vendor";
1393+
let _ = ms().verified_stmt(declare_cursor_for_select);
13901394
}
13911395

13921396
#[test]

tests/sqlparser_mysql.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3020,9 +3020,12 @@ fn parse_hex_string_introducer() {
30203020
distinct: None,
30213021
top: None,
30223022
top_before_distinct: false,
3023-
projection: vec![SelectItem::UnnamedExpr(Expr::IntroducedString {
3024-
introducer: "_latin1".to_string(),
3025-
value: Value::HexStringLiteral("4D7953514C".to_string())
3023+
projection: vec![SelectItem::UnnamedExpr(Expr::Prefixed {
3024+
prefix: Ident::from("_latin1"),
3025+
value: Expr::Value(
3026+
Value::HexStringLiteral("4D7953514C".to_string()).with_empty_span()
3027+
)
3028+
.into(),
30263029
})],
30273030
from: vec![],
30283031
lateral_views: vec![],

tests/sqlparser_snowflake.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3983,3 +3983,45 @@ fn test_nested_join_without_parentheses() {
39833983
}],
39843984
);
39853985
}
3986+
3987+
#[test]
3988+
fn parse_connect_by_root_operator() {
3989+
let sql = "SELECT CONNECT_BY_ROOT name AS root_name FROM Tbl1";
3990+
3991+
match snowflake().verified_stmt(sql) {
3992+
Statement::Query(query) => {
3993+
assert_eq!(
3994+
query.body.as_select().unwrap().projection[0],
3995+
SelectItem::ExprWithAlias {
3996+
expr: Expr::Prefixed {
3997+
prefix: Ident::new("CONNECT_BY_ROOT"),
3998+
value: Box::new(Expr::Identifier(Ident::new("name")))
3999+
},
4000+
alias: Ident::new("root_name"),
4001+
}
4002+
);
4003+
}
4004+
_ => unreachable!(),
4005+
}
4006+
4007+
let sql = "SELECT CONNECT_BY_ROOT name FROM Tbl2";
4008+
match snowflake().verified_stmt(sql) {
4009+
Statement::Query(query) => {
4010+
assert_eq!(
4011+
query.body.as_select().unwrap().projection[0],
4012+
SelectItem::UnnamedExpr(Expr::Prefixed {
4013+
prefix: Ident::new("CONNECT_BY_ROOT"),
4014+
value: Box::new(Expr::Identifier(Ident::new("name")))
4015+
})
4016+
);
4017+
}
4018+
_ => unreachable!(),
4019+
}
4020+
4021+
let sql = "SELECT CONNECT_BY_ROOT FROM Tbl2";
4022+
let res = snowflake().parse_sql_statements(sql);
4023+
assert_eq!(
4024+
res.unwrap_err().to_string(),
4025+
"sql parser error: Expected an expression, found: FROM"
4026+
);
4027+
}

0 commit comments

Comments
 (0)