mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-27 16:09:09 +00:00
Support basic CREATE PROCEDURE of MSSQL (#900)
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
75f18ecfda
commit
f72b5a5d9b
5 changed files with 185 additions and 4 deletions
|
@ -489,6 +489,19 @@ impl fmt::Display for IndexType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct ProcedureParam {
|
||||||
|
pub name: Ident,
|
||||||
|
pub data_type: DataType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ProcedureParam {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} {}", self.name, self.data_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// SQL column definition
|
/// SQL column definition
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
|
|
@ -30,8 +30,8 @@ pub use self::data_type::{
|
||||||
};
|
};
|
||||||
pub use self::ddl::{
|
pub use self::ddl::{
|
||||||
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
||||||
ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, ReferentialAction, TableConstraint,
|
ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, ProcedureParam, ReferentialAction,
|
||||||
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
|
TableConstraint, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
|
||||||
};
|
};
|
||||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||||
pub use self::query::{
|
pub use self::query::{
|
||||||
|
@ -1580,6 +1580,15 @@ pub enum Statement {
|
||||||
params: CreateFunctionBody,
|
params: CreateFunctionBody,
|
||||||
},
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
|
/// CREATE PROCEDURE
|
||||||
|
/// ```
|
||||||
|
CreateProcedure {
|
||||||
|
or_alter: bool,
|
||||||
|
name: ObjectName,
|
||||||
|
params: Option<Vec<ProcedureParam>>,
|
||||||
|
body: Vec<Statement>,
|
||||||
|
},
|
||||||
|
/// ```sql
|
||||||
/// CREATE MACRO
|
/// CREATE MACRO
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
@ -2111,6 +2120,30 @@ impl fmt::Display for Statement {
|
||||||
write!(f, "{params}")?;
|
write!(f, "{params}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Statement::CreateProcedure {
|
||||||
|
name,
|
||||||
|
or_alter,
|
||||||
|
params,
|
||||||
|
body,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"CREATE {or_alter}PROCEDURE {name}",
|
||||||
|
or_alter = if *or_alter { "OR ALTER " } else { "" },
|
||||||
|
name = name
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(p) = params {
|
||||||
|
if !p.is_empty() {
|
||||||
|
write!(f, " ({})", display_comma_separated(p))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" AS BEGIN {body} END",
|
||||||
|
body = display_separated(body, "; ")
|
||||||
|
)
|
||||||
|
}
|
||||||
Statement::CreateMacro {
|
Statement::CreateMacro {
|
||||||
or_replace,
|
or_replace,
|
||||||
temporary,
|
temporary,
|
||||||
|
|
|
@ -690,6 +690,7 @@ pub const RESERVED_FOR_TABLE_ALIAS: &[Keyword] = &[
|
||||||
Keyword::SET,
|
Keyword::SET,
|
||||||
Keyword::QUALIFY,
|
Keyword::QUALIFY,
|
||||||
Keyword::WINDOW,
|
Keyword::WINDOW,
|
||||||
|
Keyword::END,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Can't be used as a column alias, so that `SELECT <expr> alias`
|
/// Can't be used as a column alias, so that `SELECT <expr> alias`
|
||||||
|
@ -719,4 +720,5 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
|
||||||
// Reserved only as a column alias in the `SELECT` clause
|
// Reserved only as a column alias in the `SELECT` clause
|
||||||
Keyword::FROM,
|
Keyword::FROM,
|
||||||
Keyword::INTO,
|
Keyword::INTO,
|
||||||
|
Keyword::END,
|
||||||
];
|
];
|
||||||
|
|
|
@ -346,9 +346,14 @@ impl<'a> Parser<'a> {
|
||||||
expecting_statement_delimiter = false;
|
expecting_statement_delimiter = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.peek_token() == Token::EOF {
|
match self.peek_token().token {
|
||||||
break;
|
Token::EOF => break,
|
||||||
|
|
||||||
|
// end of statement
|
||||||
|
Token::Word(word) if word.keyword == Keyword::END => break,
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if expecting_statement_delimiter {
|
if expecting_statement_delimiter {
|
||||||
return self.expected("end of statement", self.peek_token());
|
return self.expected("end of statement", self.peek_token());
|
||||||
}
|
}
|
||||||
|
@ -2324,6 +2329,7 @@ impl<'a> Parser<'a> {
|
||||||
/// Parse a SQL CREATE statement
|
/// Parse a SQL CREATE statement
|
||||||
pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
|
||||||
let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
|
let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
|
||||||
|
let or_alter = self.parse_keywords(&[Keyword::OR, Keyword::ALTER]);
|
||||||
let local = self.parse_one_of_keywords(&[Keyword::LOCAL]).is_some();
|
let local = self.parse_one_of_keywords(&[Keyword::LOCAL]).is_some();
|
||||||
let global = self.parse_one_of_keywords(&[Keyword::GLOBAL]).is_some();
|
let global = self.parse_one_of_keywords(&[Keyword::GLOBAL]).is_some();
|
||||||
let transient = self.parse_one_of_keywords(&[Keyword::TRANSIENT]).is_some();
|
let transient = self.parse_one_of_keywords(&[Keyword::TRANSIENT]).is_some();
|
||||||
|
@ -2369,6 +2375,8 @@ impl<'a> Parser<'a> {
|
||||||
self.parse_create_sequence(temporary)
|
self.parse_create_sequence(temporary)
|
||||||
} else if self.parse_keyword(Keyword::TYPE) {
|
} else if self.parse_keyword(Keyword::TYPE) {
|
||||||
self.parse_create_type()
|
self.parse_create_type()
|
||||||
|
} else if self.parse_keyword(Keyword::PROCEDURE) {
|
||||||
|
self.parse_create_procedure(or_alter)
|
||||||
} else {
|
} else {
|
||||||
self.expected("an object type after CREATE", self.peek_token())
|
self.expected("an object type after CREATE", self.peek_token())
|
||||||
}
|
}
|
||||||
|
@ -3503,6 +3511,28 @@ impl<'a> Parser<'a> {
|
||||||
.build())
|
.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_optional_procedure_parameters(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Option<Vec<ProcedureParam>>, ParserError> {
|
||||||
|
let mut params = vec![];
|
||||||
|
if !self.consume_token(&Token::LParen) || self.consume_token(&Token::RParen) {
|
||||||
|
return Ok(Some(params));
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
if let Token::Word(_) = self.peek_token().token {
|
||||||
|
params.push(self.parse_procedure_param()?)
|
||||||
|
}
|
||||||
|
let comma = self.consume_token(&Token::Comma);
|
||||||
|
if self.consume_token(&Token::RParen) {
|
||||||
|
// allow a trailing comma, even though it's not in standard
|
||||||
|
break;
|
||||||
|
} else if !comma {
|
||||||
|
return self.expected("',' or ')' after parameter definition", self.peek_token());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(params))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_columns(&mut self) -> Result<(Vec<ColumnDef>, Vec<TableConstraint>), ParserError> {
|
pub fn parse_columns(&mut self) -> Result<(Vec<ColumnDef>, Vec<TableConstraint>), ParserError> {
|
||||||
let mut columns = vec![];
|
let mut columns = vec![];
|
||||||
let mut constraints = vec![];
|
let mut constraints = vec![];
|
||||||
|
@ -3530,6 +3560,12 @@ impl<'a> Parser<'a> {
|
||||||
Ok((columns, constraints))
|
Ok((columns, constraints))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_procedure_param(&mut self) -> Result<ProcedureParam, ParserError> {
|
||||||
|
let name = self.parse_identifier()?;
|
||||||
|
let data_type = self.parse_data_type()?;
|
||||||
|
Ok(ProcedureParam { name, data_type })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
|
pub fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
|
||||||
let name = self.parse_identifier()?;
|
let name = self.parse_identifier()?;
|
||||||
let data_type = self.parse_data_type()?;
|
let data_type = self.parse_data_type()?;
|
||||||
|
@ -7082,6 +7118,21 @@ impl<'a> Parser<'a> {
|
||||||
Ok(NamedWindowDefinition(ident, window_spec))
|
Ok(NamedWindowDefinition(ident, window_spec))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_create_procedure(&mut self, or_alter: bool) -> Result<Statement, ParserError> {
|
||||||
|
let name = self.parse_object_name()?;
|
||||||
|
let params = self.parse_optional_procedure_parameters()?;
|
||||||
|
self.expect_keyword(Keyword::AS)?;
|
||||||
|
self.expect_keyword(Keyword::BEGIN)?;
|
||||||
|
let statements = self.parse_statements()?;
|
||||||
|
self.expect_keyword(Keyword::END)?;
|
||||||
|
Ok(Statement::CreateProcedure {
|
||||||
|
name,
|
||||||
|
or_alter,
|
||||||
|
params,
|
||||||
|
body: statements,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
|
pub fn parse_window_spec(&mut self) -> Result<WindowSpec, ParserError> {
|
||||||
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
|
let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
|
||||||
self.parse_comma_separated(Parser::parse_expr)?
|
self.parse_comma_separated(Parser::parse_expr)?
|
||||||
|
|
|
@ -55,6 +55,88 @@ fn parse_mssql_delimited_identifiers() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_procedure() {
|
||||||
|
let sql = "CREATE OR ALTER PROCEDURE test (@foo INT, @bar VARCHAR(256)) AS BEGIN SELECT 1 END";
|
||||||
|
|
||||||
|
#[cfg(feature = "bigdecimal")]
|
||||||
|
let one = Value::Number(bigdecimal::BigDecimal::from(1), false);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "bigdecimal"))]
|
||||||
|
let one = Value::Number("1".to_string(), false);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ms().verified_stmt(sql),
|
||||||
|
Statement::CreateProcedure {
|
||||||
|
or_alter: true,
|
||||||
|
body: vec![Statement::Query(Box::new(Query {
|
||||||
|
with: None,
|
||||||
|
limit: None,
|
||||||
|
offset: None,
|
||||||
|
fetch: None,
|
||||||
|
locks: vec![],
|
||||||
|
order_by: vec![],
|
||||||
|
body: Box::new(SetExpr::Select(Box::new(Select {
|
||||||
|
distinct: None,
|
||||||
|
top: None,
|
||||||
|
projection: vec![SelectItem::UnnamedExpr(Expr::Value(one))],
|
||||||
|
into: None,
|
||||||
|
from: vec![],
|
||||||
|
lateral_views: vec![],
|
||||||
|
selection: None,
|
||||||
|
group_by: vec![],
|
||||||
|
cluster_by: vec![],
|
||||||
|
distribute_by: vec![],
|
||||||
|
sort_by: vec![],
|
||||||
|
having: None,
|
||||||
|
named_window: vec![],
|
||||||
|
qualify: None
|
||||||
|
})))
|
||||||
|
}))],
|
||||||
|
params: Some(vec![
|
||||||
|
ProcedureParam {
|
||||||
|
name: Ident {
|
||||||
|
value: "@foo".into(),
|
||||||
|
quote_style: None
|
||||||
|
},
|
||||||
|
data_type: DataType::Int(None)
|
||||||
|
},
|
||||||
|
ProcedureParam {
|
||||||
|
name: Ident {
|
||||||
|
value: "@bar".into(),
|
||||||
|
quote_style: None
|
||||||
|
},
|
||||||
|
data_type: DataType::Varchar(Some(CharacterLength {
|
||||||
|
length: 256,
|
||||||
|
unit: None
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
name: ObjectName(vec![Ident {
|
||||||
|
value: "test".into(),
|
||||||
|
quote_style: None
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_mssql_create_procedure() {
|
||||||
|
let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS BEGIN SELECT 1 END");
|
||||||
|
let _ = ms_and_generic().verified_stmt("CREATE PROCEDURE foo AS BEGIN SELECT 1 END");
|
||||||
|
let _ = ms().verified_stmt(
|
||||||
|
"CREATE PROCEDURE foo AS BEGIN SELECT [myColumn] FROM [myschema].[mytable] END",
|
||||||
|
);
|
||||||
|
let _ = ms_and_generic().verified_stmt(
|
||||||
|
"CREATE PROCEDURE foo (@CustomerName NVARCHAR(50)) AS BEGIN SELECT * FROM DEV END",
|
||||||
|
);
|
||||||
|
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test' END");
|
||||||
|
// Test a statement with END in it
|
||||||
|
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN SELECT [foo], CASE WHEN [foo] IS NULL THEN 'empty' ELSE 'notempty' END AS [foo] END");
|
||||||
|
// Multiple statements
|
||||||
|
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10 END");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_mssql_apply_join() {
|
fn parse_mssql_apply_join() {
|
||||||
let _ = ms_and_generic().verified_only_select(
|
let _ = ms_and_generic().verified_only_select(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue