Skip to content

Commit f72b5a5

Browse files
delsehialamb
andauthored
Support basic CREATE PROCEDURE of MSSQL (apache#900)
Co-authored-by: Andrew Lamb <[email protected]>
1 parent 75f18ec commit f72b5a5

File tree

5 files changed

+185
-4
lines changed

5 files changed

+185
-4
lines changed

src/ast/ddl.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,19 @@ impl fmt::Display for IndexType {
489489
}
490490
}
491491
}
492+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
493+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
494+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
495+
pub struct ProcedureParam {
496+
pub name: Ident,
497+
pub data_type: DataType,
498+
}
499+
500+
impl fmt::Display for ProcedureParam {
501+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
502+
write!(f, "{} {}", self.name, self.data_type)
503+
}
504+
}
492505

493506
/// SQL column definition
494507
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]

src/ast/mod.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ pub use self::data_type::{
3030
};
3131
pub use self::ddl::{
3232
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
33-
ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, ReferentialAction, TableConstraint,
34-
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
33+
ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, ProcedureParam, ReferentialAction,
34+
TableConstraint, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
3535
};
3636
pub use self::operator::{BinaryOperator, UnaryOperator};
3737
pub use self::query::{
@@ -1580,6 +1580,15 @@ pub enum Statement {
15801580
params: CreateFunctionBody,
15811581
},
15821582
/// ```sql
1583+
/// CREATE PROCEDURE
1584+
/// ```
1585+
CreateProcedure {
1586+
or_alter: bool,
1587+
name: ObjectName,
1588+
params: Option<Vec<ProcedureParam>>,
1589+
body: Vec<Statement>,
1590+
},
1591+
/// ```sql
15831592
/// CREATE MACRO
15841593
/// ```
15851594
///
@@ -2111,6 +2120,30 @@ impl fmt::Display for Statement {
21112120
write!(f, "{params}")?;
21122121
Ok(())
21132122
}
2123+
Statement::CreateProcedure {
2124+
name,
2125+
or_alter,
2126+
params,
2127+
body,
2128+
} => {
2129+
write!(
2130+
f,
2131+
"CREATE {or_alter}PROCEDURE {name}",
2132+
or_alter = if *or_alter { "OR ALTER " } else { "" },
2133+
name = name
2134+
)?;
2135+
2136+
if let Some(p) = params {
2137+
if !p.is_empty() {
2138+
write!(f, " ({})", display_comma_separated(p))?;
2139+
}
2140+
}
2141+
write!(
2142+
f,
2143+
" AS BEGIN {body} END",
2144+
body = display_separated(body, "; ")
2145+
)
2146+
}
21142147
Statement::CreateMacro {
21152148
or_replace,
21162149
temporary,

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
690690
Keyword::SET,
691691
Keyword::QUALIFY,
692692
Keyword::WINDOW,
693+
Keyword::END,
693694
];
694695

695696
/// Can't be used as a column alias, so that `SELECT <expr> alias`
@@ -719,4 +720,5 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
719720
// Reserved only as a column alias in the `SELECT` clause
720721
Keyword::FROM,
721722
Keyword::INTO,
723+
Keyword::END,
722724
];

src/parser.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,14 @@ impl<'a> Parser<'a> {
346346
expecting_statement_delimiter = false;
347347
}
348348

349-
if self.peek_token() == Token::EOF {
350-
break;
349+
match self.peek_token().token {
350+
Token::EOF => break,
351+
352+
// end of statement
353+
Token::Word(word) if word.keyword == Keyword::END => break,
354+
_ => {}
351355
}
356+
352357
if expecting_statement_delimiter {
353358
return self.expected("end of statement", self.peek_token());
354359
}
@@ -2324,6 +2329,7 @@ impl<'a> Parser<'a> {
23242329
/// Parse a SQL CREATE statement
23252330
pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
23262331
let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
2332+
let or_alter = self.parse_keywords(&[Keyword::OR, Keyword::ALTER]);
23272333
let local = self.parse_one_of_keywords(&[Keyword::LOCAL]).is_some();
23282334
let global = self.parse_one_of_keywords(&[Keyword::GLOBAL]).is_some();
23292335
let transient = self.parse_one_of_keywords(&[Keyword::TRANSIENT]).is_some();
@@ -2369,6 +2375,8 @@ impl<'a> Parser<'a> {
23692375
self.parse_create_sequence(temporary)
23702376
} else if self.parse_keyword(Keyword::TYPE) {
23712377
self.parse_create_type()
2378+
} else if self.parse_keyword(Keyword::PROCEDURE) {
2379+
self.parse_create_procedure(or_alter)
23722380
} else {
23732381
self.expected("an object type after CREATE", self.peek_token())
23742382
}
@@ -3503,6 +3511,28 @@ impl<'a> Parser<'a> {
35033511
.build())
35043512
}
35053513

3514+
pub fn parse_optional_procedure_parameters(
3515+
&mut self,
3516+
) -> Result<Option<Vec<ProcedureParam>>, ParserError> {
3517+
let mut params = vec![];
3518+
if !self.consume_token(&Token::LParen) || self.consume_token(&Token::RParen) {
3519+
return Ok(Some(params));
3520+
}
3521+
loop {
3522+
if let Token::Word(_) = self.peek_token().token {
3523+
params.push(self.parse_procedure_param()?)
3524+
}
3525+
let comma = self.consume_token(&Token::Comma);
3526+
if self.consume_token(&Token::RParen) {
3527+
// allow a trailing comma, even though it's not in standard
3528+
break;
3529+
} else if !comma {
3530+
return self.expected("',' or ')' after parameter definition", self.peek_token());
3531+
}
3532+
}
3533+
Ok(Some(params))
3534+
}
3535+
35063536
pub fn parse_columns(&mut self) -> Result<(Vec<ColumnDef>, Vec<TableConstraint>), ParserError> {
35073537
let mut columns = vec![];
35083538
let mut constraints = vec![];
@@ -3530,6 +3560,12 @@ impl<'a> Parser<'a> {
35303560
Ok((columns, constraints))
35313561
}
35323562

3563+
pub fn parse_procedure_param(&mut self) -> Result<ProcedureParam, ParserError> {
3564+
let name = self.parse_identifier()?;
3565+
let data_type = self.parse_data_type()?;
3566+
Ok(ProcedureParam { name, data_type })
3567+
}
3568+
35333569
pub fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
35343570
let name = self.parse_identifier()?;
35353571
let data_type = self.parse_data_type()?;
@@ -7082,6 +7118,21 @@ impl<'a> Parser<'a> {
70827118
Ok(NamedWindowDefinition(ident, window_spec))
70837119
}
70847120

7121+
pub fn parse_create_procedure(&mut self, or_alter: bool) -> Result<Statement, ParserError> {
7122+
let name = self.parse_object_name()?;
7123+
let params = self.parse_optional_procedure_parameters()?;
7124+
self.expect_keyword(Keyword::AS)?;
7125+
self.expect_keyword(Keyword::BEGIN)?;
7126+
let statements = self.parse_statements()?;
7127+
self.expect_keyword(Keyword::END)?;
7128+
Ok(Statement::CreateProcedure {
7129+
name,
7130+
or_alter,
7131+
params,
7132+
body: statements,
7133+
})
7134+
}
7135+
70857136
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
70867137
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
70877138
self.parse_comma_separated(Parser::parse_expr)?

tests/sqlparser_mssql.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,88 @@ fn parse_mssql_delimited_identifiers() {
5555
);
5656
}
5757

58+
#[test]
59+
fn parse_create_procedure() {
60+
let sql = "CREATE OR ALTER PROCEDURE test (@foo INT, @bar VARCHAR(256)) AS BEGIN SELECT 1 END";
61+
62+
#[cfg(feature = "bigdecimal")]
63+
let one = Value::Number(bigdecimal::BigDecimal::from(1), false);
64+
65+
#[cfg(not(feature = "bigdecimal"))]
66+
let one = Value::Number("1".to_string(), false);
67+
68+
assert_eq!(
69+
ms().verified_stmt(sql),
70+
Statement::CreateProcedure {
71+
or_alter: true,
72+
body: vec![Statement::Query(Box::new(Query {
73+
with: None,
74+
limit: None,
75+
offset: None,
76+
fetch: None,
77+
locks: vec![],
78+
order_by: vec![],
79+
body: Box::new(SetExpr::Select(Box::new(Select {
80+
distinct: None,
81+
top: None,
82+
projection: vec![SelectItem::UnnamedExpr(Expr::Value(one))],
83+
into: None,
84+
from: vec![],
85+
lateral_views: vec![],
86+
selection: None,
87+
group_by: vec![],
88+
cluster_by: vec![],
89+
distribute_by: vec![],
90+
sort_by: vec![],
91+
having: None,
92+
named_window: vec![],
93+
qualify: None
94+
})))
95+
}))],
96+
params: Some(vec![
97+
ProcedureParam {
98+
name: Ident {
99+
value: "@foo".into(),
100+
quote_style: None
101+
},
102+
data_type: DataType::Int(None)
103+
},
104+
ProcedureParam {
105+
name: Ident {
106+
value: "@bar".into(),
107+
quote_style: None
108+
},
109+
data_type: DataType::Varchar(Some(CharacterLength {
110+
length: 256,
111+
unit: None
112+
}))
113+
}
114+
]),
115+
name: ObjectName(vec![Ident {
116+
value: "test".into(),
117+
quote_style: None
118+
}])
119+
}
120+
)
121+
}
122+
123+
#[test]
124+
fn parse_mssql_create_procedure() {
125+
let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS BEGIN SELECT 1 END");
126+
let _ = ms_and_generic().verified_stmt("CREATE PROCEDURE foo AS BEGIN SELECT 1 END");
127+
let _ = ms().verified_stmt(
128+
"CREATE PROCEDURE foo AS BEGIN SELECT [myColumn] FROM [myschema].[mytable] END",
129+
);
130+
let _ = ms_and_generic().verified_stmt(
131+
"CREATE PROCEDURE foo (@CustomerName NVARCHAR(50)) AS BEGIN SELECT * FROM DEV END",
132+
);
133+
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test' END");
134+
// Test a statement with END in it
135+
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN SELECT [foo], CASE WHEN [foo] IS NULL THEN 'empty' ELSE 'notempty' END AS [foo] END");
136+
// Multiple statements
137+
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10 END");
138+
}
139+
58140
#[test]
59141
fn parse_mssql_apply_join() {
60142
let _ = ms_and_generic().verified_only_select(

0 commit comments

Comments
 (0)