mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-19 12:19:47 +00:00
Support DECLARE
syntax for snowflake and bigquery (#1122)
This commit is contained in:
parent
f75bb4be20
commit
57113a9344
5 changed files with 741 additions and 68 deletions
269
src/ast/mod.rs
269
src/ast/mod.rs
|
@ -1422,6 +1422,211 @@ pub enum Password {
|
||||||
NullPassword,
|
NullPassword,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an expression assignment within a variable `DECLARE` statement.
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
/// ```sql
|
||||||
|
/// DECLARE variable_name := 42
|
||||||
|
/// DECLARE variable_name DEFAULT 42
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum DeclareAssignment {
|
||||||
|
/// Plain expression specified.
|
||||||
|
Expr(Box<Expr>),
|
||||||
|
|
||||||
|
/// Expression assigned via the `DEFAULT` keyword
|
||||||
|
Default(Box<Expr>),
|
||||||
|
|
||||||
|
/// Expression assigned via the `:=` syntax
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```sql
|
||||||
|
/// DECLARE variable_name := 42;
|
||||||
|
/// ```
|
||||||
|
DuckAssignment(Box<Expr>),
|
||||||
|
|
||||||
|
/// Expression via the `FOR` keyword
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```sql
|
||||||
|
/// DECLARE c1 CURSOR FOR res
|
||||||
|
/// ```
|
||||||
|
For(Box<Expr>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DeclareAssignment {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DeclareAssignment::Expr(expr) => {
|
||||||
|
write!(f, "{expr}")
|
||||||
|
}
|
||||||
|
DeclareAssignment::Default(expr) => {
|
||||||
|
write!(f, "DEFAULT {expr}")
|
||||||
|
}
|
||||||
|
DeclareAssignment::DuckAssignment(expr) => {
|
||||||
|
write!(f, ":= {expr}")
|
||||||
|
}
|
||||||
|
DeclareAssignment::For(expr) => {
|
||||||
|
write!(f, "FOR {expr}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the type of a `DECLARE` statement.
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum DeclareType {
|
||||||
|
/// Cursor variable type. e.g. [Snowflake] [Postgres]
|
||||||
|
///
|
||||||
|
/// [Snowflake]: https://docs.snowflake.com/en/developer-guide/snowflake-scripting/cursors#declaring-a-cursor
|
||||||
|
/// [Postgres]: https://www.postgresql.org/docs/current/plpgsql-cursors.html
|
||||||
|
Cursor,
|
||||||
|
|
||||||
|
/// Result set variable type. [Snowflake]
|
||||||
|
///
|
||||||
|
/// Syntax:
|
||||||
|
/// ```text
|
||||||
|
/// <resultset_name> RESULTSET [ { DEFAULT | := } ( <query> ) ] ;
|
||||||
|
/// ```
|
||||||
|
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/snowflake-scripting/declare#resultset-declaration-syntax
|
||||||
|
ResultSet,
|
||||||
|
|
||||||
|
/// Exception declaration syntax. [Snowflake]
|
||||||
|
///
|
||||||
|
/// Syntax:
|
||||||
|
/// ```text
|
||||||
|
/// <exception_name> EXCEPTION [ ( <exception_number> , '<exception_message>' ) ] ;
|
||||||
|
/// ```
|
||||||
|
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/snowflake-scripting/declare#exception-declaration-syntax
|
||||||
|
Exception,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DeclareType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DeclareType::Cursor => {
|
||||||
|
write!(f, "CURSOR")
|
||||||
|
}
|
||||||
|
DeclareType::ResultSet => {
|
||||||
|
write!(f, "RESULTSET")
|
||||||
|
}
|
||||||
|
DeclareType::Exception => {
|
||||||
|
write!(f, "EXCEPTION")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `DECLARE` statement.
|
||||||
|
/// [Postgres] [Snowflake] [BigQuery]
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
/// ```sql
|
||||||
|
/// DECLARE variable_name := 42
|
||||||
|
/// DECLARE liahona CURSOR FOR SELECT * FROM films;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Postgres]: https://www.postgresql.org/docs/current/sql-declare.html
|
||||||
|
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/snowflake-scripting/declare
|
||||||
|
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#declare
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct Declare {
|
||||||
|
/// The name(s) being declared.
|
||||||
|
/// Example: `DECLARE a, b, c DEFAULT 42;
|
||||||
|
pub names: Vec<Ident>,
|
||||||
|
/// Data-type assigned to the declared variable.
|
||||||
|
/// Example: `DECLARE x INT64 DEFAULT 42;
|
||||||
|
pub data_type: Option<DataType>,
|
||||||
|
/// Expression being assigned to the declared variable.
|
||||||
|
pub assignment: Option<DeclareAssignment>,
|
||||||
|
/// Represents the type of the declared variable.
|
||||||
|
pub declare_type: Option<DeclareType>,
|
||||||
|
/// Causes the cursor to return data in binary rather than in text format.
|
||||||
|
pub binary: Option<bool>,
|
||||||
|
/// None = Not specified
|
||||||
|
/// Some(true) = INSENSITIVE
|
||||||
|
/// Some(false) = ASENSITIVE
|
||||||
|
pub sensitive: Option<bool>,
|
||||||
|
/// None = Not specified
|
||||||
|
/// Some(true) = SCROLL
|
||||||
|
/// Some(false) = NO SCROLL
|
||||||
|
pub scroll: Option<bool>,
|
||||||
|
/// None = Not specified
|
||||||
|
/// Some(true) = WITH HOLD, specifies that the cursor can continue to be used after the transaction that created it successfully commits
|
||||||
|
/// Some(false) = WITHOUT HOLD, specifies that the cursor cannot be used outside of the transaction that created it
|
||||||
|
pub hold: Option<bool>,
|
||||||
|
/// `FOR <query>` clause in a CURSOR declaration.
|
||||||
|
pub for_query: Option<Box<Query>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Declare {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let Declare {
|
||||||
|
names,
|
||||||
|
data_type,
|
||||||
|
assignment,
|
||||||
|
declare_type,
|
||||||
|
binary,
|
||||||
|
sensitive,
|
||||||
|
scroll,
|
||||||
|
hold,
|
||||||
|
for_query,
|
||||||
|
} = self;
|
||||||
|
write!(f, "{}", display_comma_separated(names))?;
|
||||||
|
|
||||||
|
if let Some(true) = binary {
|
||||||
|
write!(f, " BINARY")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sensitive) = sensitive {
|
||||||
|
if *sensitive {
|
||||||
|
write!(f, " INSENSITIVE")?;
|
||||||
|
} else {
|
||||||
|
write!(f, " ASENSITIVE")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(scroll) = scroll {
|
||||||
|
if *scroll {
|
||||||
|
write!(f, " SCROLL")?;
|
||||||
|
} else {
|
||||||
|
write!(f, " NO SCROLL")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(declare_type) = declare_type {
|
||||||
|
write!(f, " {declare_type}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(hold) = hold {
|
||||||
|
if *hold {
|
||||||
|
write!(f, " WITH HOLD")?;
|
||||||
|
} else {
|
||||||
|
write!(f, " WITHOUT HOLD")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(query) = for_query {
|
||||||
|
write!(f, " FOR {query}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(data_type) = data_type {
|
||||||
|
write!(f, " {data_type}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(expr) = assignment {
|
||||||
|
write!(f, " {expr}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sql options of a `CREATE TABLE` statement.
|
/// Sql options of a `CREATE TABLE` statement.
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
@ -1873,25 +2078,7 @@ pub enum Statement {
|
||||||
///
|
///
|
||||||
/// Note: this is a PostgreSQL-specific statement,
|
/// Note: this is a PostgreSQL-specific statement,
|
||||||
/// but may also compatible with other SQL.
|
/// but may also compatible with other SQL.
|
||||||
Declare {
|
Declare { stmts: Vec<Declare> },
|
||||||
/// Cursor name
|
|
||||||
name: Ident,
|
|
||||||
/// Causes the cursor to return data in binary rather than in text format.
|
|
||||||
binary: bool,
|
|
||||||
/// None = Not specified
|
|
||||||
/// Some(true) = INSENSITIVE
|
|
||||||
/// Some(false) = ASENSITIVE
|
|
||||||
sensitive: Option<bool>,
|
|
||||||
/// None = Not specified
|
|
||||||
/// Some(true) = SCROLL
|
|
||||||
/// Some(false) = NO SCROLL
|
|
||||||
scroll: Option<bool>,
|
|
||||||
/// None = Not specified
|
|
||||||
/// Some(true) = WITH HOLD, specifies that the cursor can continue to be used after the transaction that created it successfully commits
|
|
||||||
/// Some(false) = WITHOUT HOLD, specifies that the cursor cannot be used outside of the transaction that created it
|
|
||||||
hold: Option<bool>,
|
|
||||||
query: Box<Query>,
|
|
||||||
},
|
|
||||||
/// ```sql
|
/// ```sql
|
||||||
/// CREATE EXTENSION [ IF NOT EXISTS ] extension_name
|
/// CREATE EXTENSION [ IF NOT EXISTS ] extension_name
|
||||||
/// [ WITH ] [ SCHEMA schema_name ]
|
/// [ WITH ] [ SCHEMA schema_name ]
|
||||||
|
@ -2447,47 +2634,9 @@ impl fmt::Display for Statement {
|
||||||
write!(f, "{statement}")
|
write!(f, "{statement}")
|
||||||
}
|
}
|
||||||
Statement::Query(s) => write!(f, "{s}"),
|
Statement::Query(s) => write!(f, "{s}"),
|
||||||
Statement::Declare {
|
Statement::Declare { stmts } => {
|
||||||
name,
|
write!(f, "DECLARE ")?;
|
||||||
binary,
|
write!(f, "{}", display_separated(stmts, "; "))
|
||||||
sensitive,
|
|
||||||
scroll,
|
|
||||||
hold,
|
|
||||||
query,
|
|
||||||
} => {
|
|
||||||
write!(f, "DECLARE {name} ")?;
|
|
||||||
|
|
||||||
if *binary {
|
|
||||||
write!(f, "BINARY ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(sensitive) = sensitive {
|
|
||||||
if *sensitive {
|
|
||||||
write!(f, "INSENSITIVE ")?;
|
|
||||||
} else {
|
|
||||||
write!(f, "ASENSITIVE ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(scroll) = scroll {
|
|
||||||
if *scroll {
|
|
||||||
write!(f, "SCROLL ")?;
|
|
||||||
} else {
|
|
||||||
write!(f, "NO SCROLL ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "CURSOR ")?;
|
|
||||||
|
|
||||||
if let Some(hold) = hold {
|
|
||||||
if *hold {
|
|
||||||
write!(f, "WITH HOLD ")?;
|
|
||||||
} else {
|
|
||||||
write!(f, "WITHOUT HOLD ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "FOR {query}")
|
|
||||||
}
|
}
|
||||||
Statement::Fetch {
|
Statement::Fetch {
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -261,6 +261,7 @@ define_keywords!(
|
||||||
EVENT,
|
EVENT,
|
||||||
EVERY,
|
EVERY,
|
||||||
EXCEPT,
|
EXCEPT,
|
||||||
|
EXCEPTION,
|
||||||
EXCLUDE,
|
EXCLUDE,
|
||||||
EXCLUSIVE,
|
EXCLUSIVE,
|
||||||
EXEC,
|
EXEC,
|
||||||
|
@ -562,6 +563,7 @@ define_keywords!(
|
||||||
RESTART,
|
RESTART,
|
||||||
RESTRICT,
|
RESTRICT,
|
||||||
RESULT,
|
RESULT,
|
||||||
|
RESULTSET,
|
||||||
RETAIN,
|
RETAIN,
|
||||||
RETURN,
|
RETURN,
|
||||||
RETURNING,
|
RETURNING,
|
||||||
|
|
|
@ -3915,14 +3915,26 @@ impl<'a> Parser<'a> {
|
||||||
Ok(DropFunctionDesc { name, args })
|
Ok(DropFunctionDesc { name, args })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a `DECLARE` statement.
|
||||||
|
///
|
||||||
/// ```sql
|
/// ```sql
|
||||||
/// DECLARE name [ BINARY ] [ ASENSITIVE | INSENSITIVE ] [ [ NO ] SCROLL ]
|
/// DECLARE name [ BINARY ] [ ASENSITIVE | INSENSITIVE ] [ [ NO ] SCROLL ]
|
||||||
/// CURSOR [ { WITH | WITHOUT } HOLD ] FOR query
|
/// CURSOR [ { WITH | WITHOUT } HOLD ] FOR query
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// The syntax can vary significantly between warehouses. See the grammar
|
||||||
|
/// on the warehouse specific function in such cases.
|
||||||
pub fn parse_declare(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_declare(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
if dialect_of!(self is BigQueryDialect) {
|
||||||
|
return self.parse_big_query_declare();
|
||||||
|
}
|
||||||
|
if dialect_of!(self is SnowflakeDialect) {
|
||||||
|
return self.parse_snowflake_declare();
|
||||||
|
}
|
||||||
|
|
||||||
let name = self.parse_identifier(false)?;
|
let name = self.parse_identifier(false)?;
|
||||||
|
|
||||||
let binary = self.parse_keyword(Keyword::BINARY);
|
let binary = Some(self.parse_keyword(Keyword::BINARY));
|
||||||
let sensitive = if self.parse_keyword(Keyword::INSENSITIVE) {
|
let sensitive = if self.parse_keyword(Keyword::INSENSITIVE) {
|
||||||
Some(true)
|
Some(true)
|
||||||
} else if self.parse_keyword(Keyword::ASENSITIVE) {
|
} else if self.parse_keyword(Keyword::ASENSITIVE) {
|
||||||
|
@ -3939,6 +3951,7 @@ impl<'a> Parser<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.expect_keyword(Keyword::CURSOR)?;
|
self.expect_keyword(Keyword::CURSOR)?;
|
||||||
|
let declare_type = Some(DeclareType::Cursor);
|
||||||
|
|
||||||
let hold = match self.parse_one_of_keywords(&[Keyword::WITH, Keyword::WITHOUT]) {
|
let hold = match self.parse_one_of_keywords(&[Keyword::WITH, Keyword::WITHOUT]) {
|
||||||
Some(keyword) => {
|
Some(keyword) => {
|
||||||
|
@ -3955,15 +3968,204 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
self.expect_keyword(Keyword::FOR)?;
|
self.expect_keyword(Keyword::FOR)?;
|
||||||
|
|
||||||
let query = self.parse_query()?;
|
let query = Some(Box::new(self.parse_query()?));
|
||||||
|
|
||||||
Ok(Statement::Declare {
|
Ok(Statement::Declare {
|
||||||
name,
|
stmts: vec![Declare {
|
||||||
binary,
|
names: vec![name],
|
||||||
sensitive,
|
data_type: None,
|
||||||
scroll,
|
assignment: None,
|
||||||
hold,
|
declare_type,
|
||||||
query: Box::new(query),
|
binary,
|
||||||
|
sensitive,
|
||||||
|
scroll,
|
||||||
|
hold,
|
||||||
|
for_query: query,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a [BigQuery] `DECLARE` statement.
|
||||||
|
///
|
||||||
|
/// Syntax:
|
||||||
|
/// ```text
|
||||||
|
/// DECLARE variable_name[, ...] [{ <variable_type> | <DEFAULT expression> }];
|
||||||
|
/// ```
|
||||||
|
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#declare
|
||||||
|
pub fn parse_big_query_declare(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
let names = self.parse_comma_separated(|parser| Parser::parse_identifier(parser, false))?;
|
||||||
|
|
||||||
|
let data_type = match self.peek_token().token {
|
||||||
|
Token::Word(w) if w.keyword == Keyword::DEFAULT => None,
|
||||||
|
_ => Some(self.parse_data_type()?),
|
||||||
|
};
|
||||||
|
|
||||||
|
let expr = if data_type.is_some() {
|
||||||
|
if self.parse_keyword(Keyword::DEFAULT) {
|
||||||
|
Some(self.parse_expr()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no variable type - default expression must be specified, per BQ docs.
|
||||||
|
// i.e `DECLARE foo;` is invalid.
|
||||||
|
self.expect_keyword(Keyword::DEFAULT)?;
|
||||||
|
Some(self.parse_expr()?)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Statement::Declare {
|
||||||
|
stmts: vec![Declare {
|
||||||
|
names,
|
||||||
|
data_type,
|
||||||
|
assignment: expr.map(|expr| DeclareAssignment::Default(Box::new(expr))),
|
||||||
|
declare_type: None,
|
||||||
|
binary: None,
|
||||||
|
sensitive: None,
|
||||||
|
scroll: None,
|
||||||
|
hold: None,
|
||||||
|
for_query: None,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a [Snowflake] `DECLARE` statement.
|
||||||
|
///
|
||||||
|
/// Syntax:
|
||||||
|
/// ```text
|
||||||
|
/// DECLARE
|
||||||
|
/// [{ <variable_declaration>
|
||||||
|
/// | <cursor_declaration>
|
||||||
|
/// | <resultset_declaration>
|
||||||
|
/// | <exception_declaration> }; ... ]
|
||||||
|
///
|
||||||
|
/// <variable_declaration>
|
||||||
|
/// <variable_name> [<type>] [ { DEFAULT | := } <expression>]
|
||||||
|
///
|
||||||
|
/// <cursor_declaration>
|
||||||
|
/// <cursor_name> CURSOR FOR <query>
|
||||||
|
///
|
||||||
|
/// <resultset_declaration>
|
||||||
|
/// <resultset_name> RESULTSET [ { DEFAULT | := } ( <query> ) ] ;
|
||||||
|
///
|
||||||
|
/// <exception_declaration>
|
||||||
|
/// <exception_name> EXCEPTION [ ( <exception_number> , '<exception_message>' ) ] ;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/snowflake-scripting/declare
|
||||||
|
pub fn parse_snowflake_declare(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
let mut stmts = vec![];
|
||||||
|
loop {
|
||||||
|
let name = self.parse_identifier(false)?;
|
||||||
|
let (declare_type, for_query, assigned_expr, data_type) =
|
||||||
|
if self.parse_keyword(Keyword::CURSOR) {
|
||||||
|
self.expect_keyword(Keyword::FOR)?;
|
||||||
|
match self.peek_token().token {
|
||||||
|
Token::Word(w) if w.keyword == Keyword::SELECT => (
|
||||||
|
Some(DeclareType::Cursor),
|
||||||
|
Some(Box::new(self.parse_query()?)),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_ => (
|
||||||
|
Some(DeclareType::Cursor),
|
||||||
|
None,
|
||||||
|
Some(DeclareAssignment::For(Box::new(self.parse_expr()?))),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else if self.parse_keyword(Keyword::RESULTSET) {
|
||||||
|
let assigned_expr = if self.peek_token().token != Token::SemiColon {
|
||||||
|
self.parse_snowflake_variable_declaration_expression()?
|
||||||
|
} else {
|
||||||
|
// Nothing more to do. The statement has no further parameters.
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
(Some(DeclareType::ResultSet), None, assigned_expr, None)
|
||||||
|
} else if self.parse_keyword(Keyword::EXCEPTION) {
|
||||||
|
let assigned_expr = if self.peek_token().token == Token::LParen {
|
||||||
|
Some(DeclareAssignment::Expr(Box::new(self.parse_expr()?)))
|
||||||
|
} else {
|
||||||
|
// Nothing more to do. The statement has no further parameters.
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
(Some(DeclareType::Exception), None, assigned_expr, None)
|
||||||
|
} else {
|
||||||
|
// Without an explicit keyword, the only valid option is variable declaration.
|
||||||
|
let (assigned_expr, data_type) = if let Some(assigned_expr) =
|
||||||
|
self.parse_snowflake_variable_declaration_expression()?
|
||||||
|
{
|
||||||
|
(Some(assigned_expr), None)
|
||||||
|
} else if let Token::Word(_) = self.peek_token().token {
|
||||||
|
let data_type = self.parse_data_type()?;
|
||||||
|
(
|
||||||
|
self.parse_snowflake_variable_declaration_expression()?,
|
||||||
|
Some(data_type),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
(None, None, assigned_expr, data_type)
|
||||||
|
};
|
||||||
|
let stmt = Declare {
|
||||||
|
names: vec![name],
|
||||||
|
data_type,
|
||||||
|
assignment: assigned_expr,
|
||||||
|
declare_type,
|
||||||
|
binary: None,
|
||||||
|
sensitive: None,
|
||||||
|
scroll: None,
|
||||||
|
hold: None,
|
||||||
|
for_query,
|
||||||
|
};
|
||||||
|
|
||||||
|
stmts.push(stmt);
|
||||||
|
if self.consume_token(&Token::SemiColon) {
|
||||||
|
match self.peek_token().token {
|
||||||
|
Token::Word(w)
|
||||||
|
if ALL_KEYWORDS
|
||||||
|
.binary_search(&w.value.to_uppercase().as_str())
|
||||||
|
.is_err() =>
|
||||||
|
{
|
||||||
|
// Not a keyword - start of a new declaration.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Put back the semi-colon, this is the end of the DECLARE statement.
|
||||||
|
self.prev_token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Statement::Declare { stmts })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the assigned expression in a variable declaration.
|
||||||
|
///
|
||||||
|
/// Syntax:
|
||||||
|
/// ```text
|
||||||
|
/// [ { DEFAULT | := } <expression>]
|
||||||
|
/// ```
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/snowflake-scripting/declare#variable-declaration-syntax>
|
||||||
|
pub fn parse_snowflake_variable_declaration_expression(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Option<DeclareAssignment>, ParserError> {
|
||||||
|
Ok(match self.peek_token().token {
|
||||||
|
Token::Word(w) if w.keyword == Keyword::DEFAULT => {
|
||||||
|
self.next_token(); // Skip `DEFAULT`
|
||||||
|
Some(DeclareAssignment::Default(Box::new(self.parse_expr()?)))
|
||||||
|
}
|
||||||
|
Token::DuckAssignment => {
|
||||||
|
self.next_token(); // Skip `:=`
|
||||||
|
Some(DeclareAssignment::DuckAssignment(Box::new(
|
||||||
|
self.parse_expr()?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1278,6 +1278,70 @@ fn test_select_wildcard_with_replace() {
|
||||||
assert_eq!(expected, select.projection[0]);
|
assert_eq!(expected, select.projection[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_big_query_declare() {
|
||||||
|
for (sql, expected_names, expected_data_type, expected_assigned_expr) in [
|
||||||
|
(
|
||||||
|
"DECLARE x INT64",
|
||||||
|
vec![Ident::new("x")],
|
||||||
|
Some(DataType::Int64),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"DECLARE x INT64 DEFAULT 42",
|
||||||
|
vec![Ident::new("x")],
|
||||||
|
Some(DataType::Int64),
|
||||||
|
Some(DeclareAssignment::Default(Box::new(Expr::Value(number(
|
||||||
|
"42",
|
||||||
|
))))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"DECLARE x, y, z INT64 DEFAULT 42",
|
||||||
|
vec![Ident::new("x"), Ident::new("y"), Ident::new("z")],
|
||||||
|
Some(DataType::Int64),
|
||||||
|
Some(DeclareAssignment::Default(Box::new(Expr::Value(number(
|
||||||
|
"42",
|
||||||
|
))))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"DECLARE x DEFAULT 42",
|
||||||
|
vec![Ident::new("x")],
|
||||||
|
None,
|
||||||
|
Some(DeclareAssignment::Default(Box::new(Expr::Value(number(
|
||||||
|
"42",
|
||||||
|
))))),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
match bigquery().verified_stmt(sql) {
|
||||||
|
Statement::Declare { mut stmts } => {
|
||||||
|
assert_eq!(1, stmts.len());
|
||||||
|
let Declare {
|
||||||
|
names,
|
||||||
|
data_type,
|
||||||
|
assignment: assigned_expr,
|
||||||
|
..
|
||||||
|
} = stmts.swap_remove(0);
|
||||||
|
assert_eq!(expected_names, names);
|
||||||
|
assert_eq!(expected_data_type, data_type);
|
||||||
|
assert_eq!(expected_assigned_expr, assigned_expr);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_sql = "DECLARE x";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected a data type name, found: EOF".to_owned()),
|
||||||
|
bigquery().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_sql = "DECLARE x 42";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected a data type name, found: 42".to_owned()),
|
||||||
|
bigquery().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn bigquery() -> TestedDialects {
|
fn bigquery() -> TestedDialects {
|
||||||
TestedDialects {
|
TestedDialects {
|
||||||
dialects: vec![Box::new(BigQueryDialect {})],
|
dialects: vec![Box::new(BigQueryDialect {})],
|
||||||
|
|
|
@ -579,6 +579,262 @@ fn test_drop_stage() {
|
||||||
.one_statement_parses_to("DROP STAGE IF EXISTS s1", "DROP STAGE IF EXISTS s1");
|
.one_statement_parses_to("DROP STAGE IF EXISTS s1", "DROP STAGE IF EXISTS s1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_snowflake_declare_cursor() {
|
||||||
|
for (sql, expected_name, expected_assigned_expr, expected_query_projections) in [
|
||||||
|
(
|
||||||
|
"DECLARE c1 CURSOR FOR SELECT id, price FROM invoices",
|
||||||
|
"c1",
|
||||||
|
None,
|
||||||
|
Some(vec!["id", "price"]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"DECLARE c1 CURSOR FOR res",
|
||||||
|
"c1",
|
||||||
|
Some(DeclareAssignment::For(
|
||||||
|
Expr::Identifier(Ident::new("res")).into(),
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::Declare { mut stmts } => {
|
||||||
|
assert_eq!(1, stmts.len());
|
||||||
|
let Declare {
|
||||||
|
names,
|
||||||
|
data_type,
|
||||||
|
declare_type,
|
||||||
|
assignment: assigned_expr,
|
||||||
|
for_query,
|
||||||
|
..
|
||||||
|
} = stmts.swap_remove(0);
|
||||||
|
assert_eq!(vec![Ident::new(expected_name)], names);
|
||||||
|
assert!(data_type.is_none());
|
||||||
|
assert_eq!(Some(DeclareType::Cursor), declare_type);
|
||||||
|
assert_eq!(expected_assigned_expr, assigned_expr);
|
||||||
|
assert_eq!(
|
||||||
|
expected_query_projections,
|
||||||
|
for_query.as_ref().map(|q| {
|
||||||
|
match q.body.as_ref() {
|
||||||
|
SetExpr::Select(q) => q
|
||||||
|
.projection
|
||||||
|
.iter()
|
||||||
|
.map(|item| match item {
|
||||||
|
SelectItem::UnnamedExpr(Expr::Identifier(ident)) => {
|
||||||
|
ident.value.as_str()
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_sql = "DECLARE c1 CURSOR SELECT id FROM invoices";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected FOR, found: SELECT".to_owned()),
|
||||||
|
snowflake().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_sql = "DECLARE c1 CURSOR res";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected FOR, found: res".to_owned()),
|
||||||
|
snowflake().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_snowflake_declare_result_set() {
|
||||||
|
for (sql, expected_name, expected_assigned_expr) in [
|
||||||
|
(
|
||||||
|
"DECLARE res RESULTSET DEFAULT 42",
|
||||||
|
"res",
|
||||||
|
Some(DeclareAssignment::Default(Expr::Value(number("42")).into())),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"DECLARE res RESULTSET := 42",
|
||||||
|
"res",
|
||||||
|
Some(DeclareAssignment::DuckAssignment(
|
||||||
|
Expr::Value(number("42")).into(),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
("DECLARE res RESULTSET", "res", None),
|
||||||
|
] {
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::Declare { mut stmts } => {
|
||||||
|
assert_eq!(1, stmts.len());
|
||||||
|
let Declare {
|
||||||
|
names,
|
||||||
|
data_type,
|
||||||
|
declare_type,
|
||||||
|
assignment: assigned_expr,
|
||||||
|
for_query,
|
||||||
|
..
|
||||||
|
} = stmts.swap_remove(0);
|
||||||
|
assert_eq!(vec![Ident::new(expected_name)], names);
|
||||||
|
assert!(data_type.is_none());
|
||||||
|
assert!(for_query.is_none());
|
||||||
|
assert_eq!(Some(DeclareType::ResultSet), declare_type);
|
||||||
|
assert_eq!(expected_assigned_expr, assigned_expr);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sql = "DECLARE res RESULTSET DEFAULT (SELECT price FROM invoices)";
|
||||||
|
assert_eq!(snowflake().verified_stmt(sql).to_string(), sql);
|
||||||
|
|
||||||
|
let error_sql = "DECLARE res RESULTSET DEFAULT";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected an expression:, found: EOF".to_owned()),
|
||||||
|
snowflake().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_sql = "DECLARE res RESULTSET :=";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected an expression:, found: EOF".to_owned()),
|
||||||
|
snowflake().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_snowflake_declare_exception() {
|
||||||
|
for (sql, expected_name, expected_assigned_expr) in [
|
||||||
|
(
|
||||||
|
"DECLARE ex EXCEPTION (42, 'ERROR')",
|
||||||
|
"ex",
|
||||||
|
Some(DeclareAssignment::Expr(
|
||||||
|
Expr::Tuple(vec![
|
||||||
|
Expr::Value(number("42")),
|
||||||
|
Expr::Value(Value::SingleQuotedString("ERROR".to_string())),
|
||||||
|
])
|
||||||
|
.into(),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
("DECLARE ex EXCEPTION", "ex", None),
|
||||||
|
] {
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::Declare { mut stmts } => {
|
||||||
|
assert_eq!(1, stmts.len());
|
||||||
|
let Declare {
|
||||||
|
names,
|
||||||
|
data_type,
|
||||||
|
declare_type,
|
||||||
|
assignment: assigned_expr,
|
||||||
|
for_query,
|
||||||
|
..
|
||||||
|
} = stmts.swap_remove(0);
|
||||||
|
assert_eq!(vec![Ident::new(expected_name)], names);
|
||||||
|
assert!(data_type.is_none());
|
||||||
|
assert!(for_query.is_none());
|
||||||
|
assert_eq!(Some(DeclareType::Exception), declare_type);
|
||||||
|
assert_eq!(expected_assigned_expr, assigned_expr);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_snowflake_declare_variable() {
|
||||||
|
for (sql, expected_name, expected_data_type, expected_assigned_expr) in [
|
||||||
|
(
|
||||||
|
"DECLARE profit TEXT DEFAULT 42",
|
||||||
|
"profit",
|
||||||
|
Some(DataType::Text),
|
||||||
|
Some(DeclareAssignment::Default(Expr::Value(number("42")).into())),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"DECLARE profit DEFAULT 42",
|
||||||
|
"profit",
|
||||||
|
None,
|
||||||
|
Some(DeclareAssignment::Default(Expr::Value(number("42")).into())),
|
||||||
|
),
|
||||||
|
("DECLARE profit TEXT", "profit", Some(DataType::Text), None),
|
||||||
|
("DECLARE profit", "profit", None, None),
|
||||||
|
] {
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::Declare { mut stmts } => {
|
||||||
|
assert_eq!(1, stmts.len());
|
||||||
|
let Declare {
|
||||||
|
names,
|
||||||
|
data_type,
|
||||||
|
declare_type,
|
||||||
|
assignment: assigned_expr,
|
||||||
|
for_query,
|
||||||
|
..
|
||||||
|
} = stmts.swap_remove(0);
|
||||||
|
assert_eq!(vec![Ident::new(expected_name)], names);
|
||||||
|
assert!(for_query.is_none());
|
||||||
|
assert_eq!(expected_data_type, data_type);
|
||||||
|
assert_eq!(None, declare_type);
|
||||||
|
assert_eq!(expected_assigned_expr, assigned_expr);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snowflake().one_statement_parses_to("DECLARE profit;", "DECLARE profit");
|
||||||
|
|
||||||
|
let error_sql = "DECLARE profit INT 2";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected end of statement, found: 2".to_owned()),
|
||||||
|
snowflake().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_sql = "DECLARE profit INT DEFAULT";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected an expression:, found: EOF".to_owned()),
|
||||||
|
snowflake().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
let error_sql = "DECLARE profit DEFAULT";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected an expression:, found: EOF".to_owned()),
|
||||||
|
snowflake().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_snowflake_declare_multi_statements() {
|
||||||
|
let sql = concat!(
|
||||||
|
"DECLARE profit DEFAULT 42; ",
|
||||||
|
"res RESULTSET DEFAULT (SELECT price FROM invoices); ",
|
||||||
|
"c1 CURSOR FOR res; ",
|
||||||
|
"ex EXCEPTION (-20003, 'ERROR: Could not create table.')"
|
||||||
|
);
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::Declare { stmts } => {
|
||||||
|
let actual = stmts
|
||||||
|
.iter()
|
||||||
|
.map(|stmt| (stmt.names[0].value.as_str(), stmt.declare_type.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
("profit", None),
|
||||||
|
("res", Some(DeclareType::ResultSet)),
|
||||||
|
("c1", Some(DeclareType::Cursor)),
|
||||||
|
("ex", Some(DeclareType::Exception)),
|
||||||
|
],
|
||||||
|
actual
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_sql = "DECLARE profit DEFAULT 42 c1 CURSOR FOR res;";
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected end of statement, found: c1".to_owned()),
|
||||||
|
snowflake().parse_sql_statements(error_sql).unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_stage() {
|
fn test_create_stage() {
|
||||||
let sql = "CREATE STAGE s1.s2";
|
let sql = "CREATE STAGE s1.s2";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue