Skip to content

Commit ac298a6

Browse files
committed
Add additional cursor parsing support for SQL Server
- parse `OPEN cursor_name` statements - enable `FETCH` statements to parse `FROM cursor_name`, in addition to the existing `IN` parsing
1 parent 4e392f5 commit ac298a6

File tree

4 files changed

+71
-2
lines changed

4 files changed

+71
-2
lines changed

src/ast/mod.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3032,6 +3032,14 @@ pub enum Statement {
30323032
partition: Option<Box<Expr>>,
30333033
},
30343034
/// ```sql
3035+
/// OPEN cursor_name
3036+
/// ```
3037+
/// Opens a cursor.
3038+
Open {
3039+
/// Cursor name
3040+
cursor_name: Ident,
3041+
},
3042+
/// ```sql
30353043
/// CLOSE
30363044
/// ```
30373045
/// Closes the portal underlying an open cursor.
@@ -3403,6 +3411,10 @@ pub enum Statement {
34033411
/// Cursor name
34043412
name: Ident,
34053413
direction: FetchDirection,
3414+
/// Differentiate between dialects that fetch `FROM` vs fetch `IN`
3415+
///
3416+
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/fetch-transact-sql)
3417+
from_or_in: AttachedToken,
34063418
/// Optional, It's possible to fetch rows form cursor to the table
34073419
into: Option<ObjectName>,
34083420
},
@@ -4225,11 +4237,25 @@ impl fmt::Display for Statement {
42254237
Statement::Fetch {
42264238
name,
42274239
direction,
4240+
from_or_in,
42284241
into,
42294242
} => {
42304243
write!(f, "FETCH {direction} ")?;
42314244

4232-
write!(f, "IN {name}")?;
4245+
match &from_or_in.0.token {
4246+
Token::Word(w) => match w.keyword {
4247+
Keyword::FROM => {
4248+
write!(f, "FROM {name}")?;
4249+
}
4250+
Keyword::IN => {
4251+
write!(f, "IN {name}")?;
4252+
}
4253+
_ => unreachable!(),
4254+
},
4255+
_ => {
4256+
unreachable!()
4257+
}
4258+
}
42334259

42344260
if let Some(into) = into {
42354261
write!(f, " INTO {into}")?;
@@ -4488,6 +4514,11 @@ impl fmt::Display for Statement {
44884514
Ok(())
44894515
}
44904516
Statement::Delete(delete) => write!(f, "{delete}"),
4517+
Statement::Open { cursor_name } => {
4518+
write!(f, "OPEN {cursor_name}")?;
4519+
4520+
Ok(())
4521+
}
44914522
Statement::Close { cursor } => {
44924523
write!(f, "CLOSE {cursor}")?;
44934524

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ impl Spanned for Statement {
364364
from_query: _,
365365
partition: _,
366366
} => Span::empty(),
367+
Statement::Open { cursor_name } => cursor_name.span,
367368
Statement::Close { cursor } => match cursor {
368369
CloseCursor::All => Span::empty(),
369370
CloseCursor::Specific { name } => name.span,

src/parser/mod.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,10 @@ impl<'a> Parser<'a> {
570570
Keyword::ALTER => self.parse_alter(),
571571
Keyword::CALL => self.parse_call(),
572572
Keyword::COPY => self.parse_copy(),
573+
Keyword::OPEN => {
574+
self.prev_token();
575+
self.parse_open()
576+
}
573577
Keyword::CLOSE => self.parse_close(),
574578
Keyword::SET => self.parse_set(),
575579
Keyword::SHOW => self.parse_show(),
@@ -6609,7 +6613,13 @@ impl<'a> Parser<'a> {
66096613
}
66106614
};
66116615

6612-
self.expect_one_of_keywords(&[Keyword::FROM, Keyword::IN])?;
6616+
let from_or_in_token = if self.peek_keyword(Keyword::FROM) {
6617+
self.expect_keyword(Keyword::FROM)?
6618+
} else if self.peek_keyword(Keyword::IN) {
6619+
self.expect_keyword(Keyword::IN)?
6620+
} else {
6621+
return parser_err!("Expected FROM or IN", self.peek_token().span.start);
6622+
};
66136623

66146624
let name = self.parse_identifier()?;
66156625

@@ -6622,6 +6632,7 @@ impl<'a> Parser<'a> {
66226632
Ok(Statement::Fetch {
66236633
name,
66246634
direction,
6635+
from_or_in: AttachedToken(from_or_in_token),
66256636
into,
66266637
})
66276638
}
@@ -8735,6 +8746,14 @@ impl<'a> Parser<'a> {
87358746
})
87368747
}
87378748

8749+
/// Parse [Statement::Open]
8750+
fn parse_open(&mut self) -> Result<Statement, ParserError> {
8751+
self.expect_keyword(Keyword::OPEN)?;
8752+
Ok(Statement::Open {
8753+
cursor_name: self.parse_identifier()?,
8754+
})
8755+
}
8756+
87388757
pub fn parse_close(&mut self) -> Result<Statement, ParserError> {
87398758
let cursor = if self.parse_keyword(Keyword::ALL) {
87408759
CloseCursor::All

tests/sqlparser_mssql.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,24 @@ fn parse_mssql_declare() {
13931393
let _ = ms().verified_stmt(declare_cursor_for_select);
13941394
}
13951395

1396+
#[test]
1397+
fn test_mssql_cursor() {
1398+
let full_cursor_usage = "\
1399+
DECLARE Employee_Cursor CURSOR FOR \
1400+
SELECT LastName, FirstName \
1401+
FROM AdventureWorks2022.HumanResources.vEmployee \
1402+
WHERE LastName LIKE 'B%'; \
1403+
\
1404+
OPEN Employee_Cursor; \
1405+
\
1406+
FETCH NEXT FROM Employee_Cursor; \
1407+
\
1408+
CLOSE Employee_Cursor; \
1409+
DEALLOCATE Employee_Cursor\
1410+
";
1411+
let _ = ms().statements_parse_to(full_cursor_usage, 5, "");
1412+
}
1413+
13961414
#[test]
13971415
fn test_parse_raiserror() {
13981416
let sql = r#"RAISERROR('This is a test', 16, 1)"#;

0 commit comments

Comments
 (0)