mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
Support CREATE/DROP SECRET
for duckdb dialect (#1208)
Co-authored-by: Jichao Sun <4977515+JichaoS@users.noreply.github.com>
This commit is contained in:
parent
8f67d1a713
commit
241da85d67
4 changed files with 486 additions and 2 deletions
169
src/ast/mod.rs
169
src/ast/mod.rs
|
@ -2040,6 +2040,19 @@ pub enum Statement {
|
||||||
authorization_owner: Option<ObjectName>,
|
authorization_owner: Option<ObjectName>,
|
||||||
},
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
|
/// CREATE SECRET
|
||||||
|
/// ```
|
||||||
|
/// See [duckdb](https://duckdb.org/docs/sql/statements/create_secret.html)
|
||||||
|
CreateSecret {
|
||||||
|
or_replace: bool,
|
||||||
|
temporary: Option<bool>,
|
||||||
|
if_not_exists: bool,
|
||||||
|
name: Option<Ident>,
|
||||||
|
storage_specifier: Option<Ident>,
|
||||||
|
secret_type: Ident,
|
||||||
|
options: Vec<SecretOption>,
|
||||||
|
},
|
||||||
|
/// ```sql
|
||||||
/// ALTER TABLE
|
/// ALTER TABLE
|
||||||
/// ```
|
/// ```
|
||||||
AlterTable {
|
AlterTable {
|
||||||
|
@ -2088,6 +2101,31 @@ pub enum Statement {
|
||||||
/// true if the syntax is 'ATTACH DATABASE', false if it's just 'ATTACH'
|
/// true if the syntax is 'ATTACH DATABASE', false if it's just 'ATTACH'
|
||||||
database: bool,
|
database: bool,
|
||||||
},
|
},
|
||||||
|
/// (DuckDB-specific)
|
||||||
|
/// ```sql
|
||||||
|
/// ATTACH 'sqlite_file.db' AS sqlite_db (READ_ONLY, TYPE SQLITE);
|
||||||
|
/// ```
|
||||||
|
/// See <https://duckdb.org/docs/sql/statements/attach.html>
|
||||||
|
AttachDuckDBDatabase {
|
||||||
|
if_not_exists: bool,
|
||||||
|
/// true if the syntax is 'ATTACH DATABASE', false if it's just 'ATTACH'
|
||||||
|
database: bool,
|
||||||
|
/// An expression that indicates the path to the database file
|
||||||
|
database_path: Ident,
|
||||||
|
database_alias: Option<Ident>,
|
||||||
|
attach_options: Vec<AttachDuckDBDatabaseOption>,
|
||||||
|
},
|
||||||
|
/// (DuckDB-specific)
|
||||||
|
/// ```sql
|
||||||
|
/// DETACH db_alias;
|
||||||
|
/// ```
|
||||||
|
/// See <https://duckdb.org/docs/sql/statements/attach.html>
|
||||||
|
DetachDuckDBDatabase {
|
||||||
|
if_exists: bool,
|
||||||
|
/// true if the syntax is 'DETACH DATABASE', false if it's just 'DETACH'
|
||||||
|
database: bool,
|
||||||
|
database_alias: Ident,
|
||||||
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
/// DROP [TABLE, VIEW, ...]
|
/// DROP [TABLE, VIEW, ...]
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -2121,6 +2159,15 @@ pub enum Statement {
|
||||||
option: Option<ReferentialAction>,
|
option: Option<ReferentialAction>,
|
||||||
},
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
|
/// DROP SECRET
|
||||||
|
/// ```
|
||||||
|
DropSecret {
|
||||||
|
if_exists: bool,
|
||||||
|
temporary: Option<bool>,
|
||||||
|
name: Ident,
|
||||||
|
storage_specifier: Option<Ident>,
|
||||||
|
},
|
||||||
|
/// ```sql
|
||||||
/// DECLARE
|
/// DECLARE
|
||||||
/// ```
|
/// ```
|
||||||
/// Declare Cursor Variables
|
/// Declare Cursor Variables
|
||||||
|
@ -2772,6 +2819,40 @@ impl fmt::Display for Statement {
|
||||||
let keyword = if *database { "DATABASE " } else { "" };
|
let keyword = if *database { "DATABASE " } else { "" };
|
||||||
write!(f, "ATTACH {keyword}{database_file_name} AS {schema_name}")
|
write!(f, "ATTACH {keyword}{database_file_name} AS {schema_name}")
|
||||||
}
|
}
|
||||||
|
Statement::AttachDuckDBDatabase {
|
||||||
|
if_not_exists,
|
||||||
|
database,
|
||||||
|
database_path,
|
||||||
|
database_alias,
|
||||||
|
attach_options,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"ATTACH{database}{if_not_exists} {database_path}",
|
||||||
|
database = if *database { " DATABASE" } else { "" },
|
||||||
|
if_not_exists = if *if_not_exists { " IF NOT EXISTS" } else { "" },
|
||||||
|
)?;
|
||||||
|
if let Some(alias) = database_alias {
|
||||||
|
write!(f, " AS {alias}")?;
|
||||||
|
}
|
||||||
|
if !attach_options.is_empty() {
|
||||||
|
write!(f, " ({})", display_comma_separated(attach_options))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Statement::DetachDuckDBDatabase {
|
||||||
|
if_exists,
|
||||||
|
database,
|
||||||
|
database_alias,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"DETACH{database}{if_exists} {database_alias}",
|
||||||
|
database = if *database { " DATABASE" } else { "" },
|
||||||
|
if_exists = if *if_exists { " IF EXISTS" } else { "" },
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Statement::Analyze {
|
Statement::Analyze {
|
||||||
table_name,
|
table_name,
|
||||||
partitions,
|
partitions,
|
||||||
|
@ -3556,6 +3637,41 @@ impl fmt::Display for Statement {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Statement::CreateSecret {
|
||||||
|
or_replace,
|
||||||
|
temporary,
|
||||||
|
if_not_exists,
|
||||||
|
name,
|
||||||
|
storage_specifier,
|
||||||
|
secret_type,
|
||||||
|
options,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"CREATE {or_replace}",
|
||||||
|
or_replace = if *or_replace { "OR REPLACE " } else { "" },
|
||||||
|
)?;
|
||||||
|
if let Some(t) = temporary {
|
||||||
|
write!(f, "{}", if *t { "TEMPORARY " } else { "PERSISTENT " })?;
|
||||||
|
}
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"SECRET {if_not_exists}",
|
||||||
|
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
|
||||||
|
)?;
|
||||||
|
if let Some(n) = name {
|
||||||
|
write!(f, "{n} ")?;
|
||||||
|
};
|
||||||
|
if let Some(s) = storage_specifier {
|
||||||
|
write!(f, "IN {s} ")?;
|
||||||
|
}
|
||||||
|
write!(f, "( TYPE {secret_type}",)?;
|
||||||
|
if !options.is_empty() {
|
||||||
|
write!(f, ", {o}", o = display_comma_separated(options))?;
|
||||||
|
}
|
||||||
|
write!(f, " )")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Statement::AlterTable {
|
Statement::AlterTable {
|
||||||
name,
|
name,
|
||||||
if_exists,
|
if_exists,
|
||||||
|
@ -3636,6 +3752,26 @@ impl fmt::Display for Statement {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Statement::DropSecret {
|
||||||
|
if_exists,
|
||||||
|
temporary,
|
||||||
|
name,
|
||||||
|
storage_specifier,
|
||||||
|
} => {
|
||||||
|
write!(f, "DROP ")?;
|
||||||
|
if let Some(t) = temporary {
|
||||||
|
write!(f, "{}", if *t { "TEMPORARY " } else { "PERSISTENT " })?;
|
||||||
|
}
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"SECRET {if_exists}{name}",
|
||||||
|
if_exists = if *if_exists { "IF EXISTS " } else { "" },
|
||||||
|
)?;
|
||||||
|
if let Some(s) = storage_specifier {
|
||||||
|
write!(f, " FROM {s}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Statement::Discard { object_type } => {
|
Statement::Discard { object_type } => {
|
||||||
write!(f, "DISCARD {object_type}")?;
|
write!(f, "DISCARD {object_type}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -5070,6 +5206,39 @@ impl fmt::Display for SqlOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct SecretOption {
|
||||||
|
pub key: Ident,
|
||||||
|
pub value: Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SecretOption {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{} {}", self.key, self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum AttachDuckDBDatabaseOption {
|
||||||
|
ReadOnly(Option<bool>),
|
||||||
|
Type(Ident),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AttachDuckDBDatabaseOption {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
AttachDuckDBDatabaseOption::ReadOnly(Some(true)) => write!(f, "READ_ONLY true"),
|
||||||
|
AttachDuckDBDatabaseOption::ReadOnly(Some(false)) => write!(f, "READ_ONLY false"),
|
||||||
|
AttachDuckDBDatabaseOption::ReadOnly(None) => write!(f, "READ_ONLY"),
|
||||||
|
AttachDuckDBDatabaseOption::Type(t) => write!(f, "TYPE {}", t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
|
|
@ -224,6 +224,7 @@ define_keywords!(
|
||||||
DEREF,
|
DEREF,
|
||||||
DESC,
|
DESC,
|
||||||
DESCRIBE,
|
DESCRIBE,
|
||||||
|
DETACH,
|
||||||
DETAIL,
|
DETAIL,
|
||||||
DETERMINISTIC,
|
DETERMINISTIC,
|
||||||
DIRECTORY,
|
DIRECTORY,
|
||||||
|
@ -514,6 +515,7 @@ define_keywords!(
|
||||||
PERCENTILE_DISC,
|
PERCENTILE_DISC,
|
||||||
PERCENT_RANK,
|
PERCENT_RANK,
|
||||||
PERIOD,
|
PERIOD,
|
||||||
|
PERSISTENT,
|
||||||
PIVOT,
|
PIVOT,
|
||||||
PLACING,
|
PLACING,
|
||||||
PLANS,
|
PLANS,
|
||||||
|
@ -543,6 +545,7 @@ define_keywords!(
|
||||||
RCFILE,
|
RCFILE,
|
||||||
READ,
|
READ,
|
||||||
READS,
|
READS,
|
||||||
|
READ_ONLY,
|
||||||
REAL,
|
REAL,
|
||||||
RECURSIVE,
|
RECURSIVE,
|
||||||
REF,
|
REF,
|
||||||
|
@ -601,6 +604,7 @@ define_keywords!(
|
||||||
SCROLL,
|
SCROLL,
|
||||||
SEARCH,
|
SEARCH,
|
||||||
SECOND,
|
SECOND,
|
||||||
|
SECRET,
|
||||||
SECURITY,
|
SECURITY,
|
||||||
SELECT,
|
SELECT,
|
||||||
SEMI,
|
SEMI,
|
||||||
|
|
|
@ -473,7 +473,16 @@ impl<'a> Parser<'a> {
|
||||||
Ok(Statement::Query(self.parse_boxed_query()?))
|
Ok(Statement::Query(self.parse_boxed_query()?))
|
||||||
}
|
}
|
||||||
Keyword::TRUNCATE => Ok(self.parse_truncate()?),
|
Keyword::TRUNCATE => Ok(self.parse_truncate()?),
|
||||||
Keyword::ATTACH => Ok(self.parse_attach_database()?),
|
Keyword::ATTACH => {
|
||||||
|
if dialect_of!(self is DuckDbDialect) {
|
||||||
|
Ok(self.parse_attach_duckdb_database()?)
|
||||||
|
} else {
|
||||||
|
Ok(self.parse_attach_database()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keyword::DETACH if dialect_of!(self is DuckDbDialect | GenericDialect) => {
|
||||||
|
Ok(self.parse_detach_duckdb_database()?)
|
||||||
|
}
|
||||||
Keyword::MSCK => Ok(self.parse_msck()?),
|
Keyword::MSCK => Ok(self.parse_msck()?),
|
||||||
Keyword::CREATE => Ok(self.parse_create()?),
|
Keyword::CREATE => Ok(self.parse_create()?),
|
||||||
Keyword::CACHE => Ok(self.parse_cache_table()?),
|
Keyword::CACHE => Ok(self.parse_cache_table()?),
|
||||||
|
@ -666,6 +675,72 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_attach_duckdb_database_options(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Vec<AttachDuckDBDatabaseOption>, ParserError> {
|
||||||
|
if !self.consume_token(&Token::LParen) {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut options = vec![];
|
||||||
|
loop {
|
||||||
|
if self.parse_keyword(Keyword::READ_ONLY) {
|
||||||
|
let boolean = if self.parse_keyword(Keyword::TRUE) {
|
||||||
|
Some(true)
|
||||||
|
} else if self.parse_keyword(Keyword::FALSE) {
|
||||||
|
Some(false)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
options.push(AttachDuckDBDatabaseOption::ReadOnly(boolean));
|
||||||
|
} else if self.parse_keyword(Keyword::TYPE) {
|
||||||
|
let ident = self.parse_identifier(false)?;
|
||||||
|
options.push(AttachDuckDBDatabaseOption::Type(ident));
|
||||||
|
} else {
|
||||||
|
return self.expected("expected one of: ), READ_ONLY, TYPE", self.peek_token());
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.consume_token(&Token::RParen) {
|
||||||
|
return Ok(options);
|
||||||
|
} else if self.consume_token(&Token::Comma) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return self.expected("expected one of: ')', ','", self.peek_token());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_attach_duckdb_database(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
let database = self.parse_keyword(Keyword::DATABASE);
|
||||||
|
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||||
|
let database_path = self.parse_identifier(false)?;
|
||||||
|
let database_alias = if self.parse_keyword(Keyword::AS) {
|
||||||
|
Some(self.parse_identifier(false)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let attach_options = self.parse_attach_duckdb_database_options()?;
|
||||||
|
Ok(Statement::AttachDuckDBDatabase {
|
||||||
|
if_not_exists,
|
||||||
|
database,
|
||||||
|
database_path,
|
||||||
|
database_alias,
|
||||||
|
attach_options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_detach_duckdb_database(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
let database = self.parse_keyword(Keyword::DATABASE);
|
||||||
|
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
|
||||||
|
let database_alias = self.parse_identifier(false)?;
|
||||||
|
Ok(Statement::DetachDuckDBDatabase {
|
||||||
|
if_exists,
|
||||||
|
database,
|
||||||
|
database_alias,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_attach_database(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_attach_database(&mut self) -> Result<Statement, ParserError> {
|
||||||
let database = self.parse_keyword(Keyword::DATABASE);
|
let database = self.parse_keyword(Keyword::DATABASE);
|
||||||
let database_file_name = self.parse_expr()?;
|
let database_file_name = self.parse_expr()?;
|
||||||
|
@ -3075,6 +3150,8 @@ impl<'a> Parser<'a> {
|
||||||
let temporary = self
|
let temporary = self
|
||||||
.parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
|
.parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
|
||||||
.is_some();
|
.is_some();
|
||||||
|
let persistent = dialect_of!(self is DuckDbDialect)
|
||||||
|
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
|
||||||
if self.parse_keyword(Keyword::TABLE) {
|
if self.parse_keyword(Keyword::TABLE) {
|
||||||
self.parse_create_table(or_replace, temporary, global, transient)
|
self.parse_create_table(or_replace, temporary, global, transient)
|
||||||
} else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) {
|
} else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) {
|
||||||
|
@ -3086,6 +3163,8 @@ impl<'a> Parser<'a> {
|
||||||
self.parse_create_function(or_replace, temporary)
|
self.parse_create_function(or_replace, temporary)
|
||||||
} else if self.parse_keyword(Keyword::MACRO) {
|
} else if self.parse_keyword(Keyword::MACRO) {
|
||||||
self.parse_create_macro(or_replace, temporary)
|
self.parse_create_macro(or_replace, temporary)
|
||||||
|
} else if self.parse_keyword(Keyword::SECRET) {
|
||||||
|
self.parse_create_secret(or_replace, temporary, persistent)
|
||||||
} else if or_replace {
|
} else if or_replace {
|
||||||
self.expected(
|
self.expected(
|
||||||
"[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION after CREATE OR REPLACE",
|
"[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION after CREATE OR REPLACE",
|
||||||
|
@ -3116,6 +3195,65 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details.
|
||||||
|
pub fn parse_create_secret(
|
||||||
|
&mut self,
|
||||||
|
or_replace: bool,
|
||||||
|
temporary: bool,
|
||||||
|
persistent: bool,
|
||||||
|
) -> Result<Statement, ParserError> {
|
||||||
|
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||||
|
|
||||||
|
let mut storage_specifier = None;
|
||||||
|
let mut name = None;
|
||||||
|
if self.peek_token() != Token::LParen {
|
||||||
|
if self.parse_keyword(Keyword::IN) {
|
||||||
|
storage_specifier = self.parse_identifier(false).ok()
|
||||||
|
} else {
|
||||||
|
name = self.parse_identifier(false).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage specifier may follow the name
|
||||||
|
if storage_specifier.is_none()
|
||||||
|
&& self.peek_token() != Token::LParen
|
||||||
|
&& self.parse_keyword(Keyword::IN)
|
||||||
|
{
|
||||||
|
storage_specifier = self.parse_identifier(false).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.expect_token(&Token::LParen)?;
|
||||||
|
self.expect_keyword(Keyword::TYPE)?;
|
||||||
|
let secret_type = self.parse_identifier(false)?;
|
||||||
|
|
||||||
|
let mut options = Vec::new();
|
||||||
|
if self.consume_token(&Token::Comma) {
|
||||||
|
options.append(&mut self.parse_comma_separated(|p| {
|
||||||
|
let key = p.parse_identifier(false)?;
|
||||||
|
let value = p.parse_identifier(false)?;
|
||||||
|
Ok(SecretOption { key, value })
|
||||||
|
})?);
|
||||||
|
}
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
|
||||||
|
let temp = match (temporary, persistent) {
|
||||||
|
(true, false) => Some(true),
|
||||||
|
(false, true) => Some(false),
|
||||||
|
(false, false) => None,
|
||||||
|
_ => self.expected("TEMPORARY or PERSISTENT", self.peek_token())?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Statement::CreateSecret {
|
||||||
|
or_replace,
|
||||||
|
temporary: temp,
|
||||||
|
if_not_exists,
|
||||||
|
name,
|
||||||
|
storage_specifier,
|
||||||
|
secret_type,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a CACHE TABLE statement
|
/// Parse a CACHE TABLE statement
|
||||||
pub fn parse_cache_table(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_cache_table(&mut self) -> Result<Statement, ParserError> {
|
||||||
let (mut table_flag, mut options, mut has_as, mut query) = (None, vec![], false, None);
|
let (mut table_flag, mut options, mut has_as, mut query) = (None, vec![], false, None);
|
||||||
|
@ -3889,8 +4027,10 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
pub fn parse_drop(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_drop(&mut self) -> Result<Statement, ParserError> {
|
||||||
// MySQL dialect supports `TEMPORARY`
|
// MySQL dialect supports `TEMPORARY`
|
||||||
let temporary = dialect_of!(self is MySqlDialect | GenericDialect)
|
let temporary = dialect_of!(self is MySqlDialect | GenericDialect | DuckDbDialect)
|
||||||
&& self.parse_keyword(Keyword::TEMPORARY);
|
&& self.parse_keyword(Keyword::TEMPORARY);
|
||||||
|
let persistent = dialect_of!(self is DuckDbDialect)
|
||||||
|
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
|
||||||
|
|
||||||
let object_type = if self.parse_keyword(Keyword::TABLE) {
|
let object_type = if self.parse_keyword(Keyword::TABLE) {
|
||||||
ObjectType::Table
|
ObjectType::Table
|
||||||
|
@ -3908,6 +4048,8 @@ impl<'a> Parser<'a> {
|
||||||
ObjectType::Stage
|
ObjectType::Stage
|
||||||
} else if self.parse_keyword(Keyword::FUNCTION) {
|
} else if self.parse_keyword(Keyword::FUNCTION) {
|
||||||
return self.parse_drop_function();
|
return self.parse_drop_function();
|
||||||
|
} else if self.parse_keyword(Keyword::SECRET) {
|
||||||
|
return self.parse_drop_secret(temporary, persistent);
|
||||||
} else {
|
} else {
|
||||||
return self.expected(
|
return self.expected(
|
||||||
"TABLE, VIEW, INDEX, ROLE, SCHEMA, FUNCTION, STAGE or SEQUENCE after DROP",
|
"TABLE, VIEW, INDEX, ROLE, SCHEMA, FUNCTION, STAGE or SEQUENCE after DROP",
|
||||||
|
@ -3980,6 +4122,34 @@ impl<'a> Parser<'a> {
|
||||||
Ok(DropFunctionDesc { name, args })
|
Ok(DropFunctionDesc { name, args })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details.
|
||||||
|
fn parse_drop_secret(
|
||||||
|
&mut self,
|
||||||
|
temporary: bool,
|
||||||
|
persistent: bool,
|
||||||
|
) -> Result<Statement, ParserError> {
|
||||||
|
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
|
||||||
|
let name = self.parse_identifier(false)?;
|
||||||
|
let storage_specifier = if self.parse_keyword(Keyword::FROM) {
|
||||||
|
self.parse_identifier(false).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let temp = match (temporary, persistent) {
|
||||||
|
(true, false) => Some(true),
|
||||||
|
(false, true) => Some(false),
|
||||||
|
(false, false) => None,
|
||||||
|
_ => self.expected("TEMPORARY or PERSISTENT", self.peek_token())?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Statement::DropSecret {
|
||||||
|
if_exists,
|
||||||
|
temporary: temp,
|
||||||
|
name,
|
||||||
|
storage_specifier,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a `DECLARE` statement.
|
/// Parse a `DECLARE` statement.
|
||||||
///
|
///
|
||||||
/// ```sql
|
/// ```sql
|
||||||
|
|
|
@ -334,6 +334,147 @@ fn test_duckdb_struct_literal() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_secret() {
|
||||||
|
let sql = r#"CREATE OR REPLACE PERSISTENT SECRET IF NOT EXISTS name IN storage ( TYPE type, key1 value1, key2 value2 )"#;
|
||||||
|
let stmt = duckdb().verified_stmt(sql);
|
||||||
|
assert_eq!(
|
||||||
|
Statement::CreateSecret {
|
||||||
|
or_replace: true,
|
||||||
|
temporary: Some(false),
|
||||||
|
if_not_exists: true,
|
||||||
|
name: Some(Ident::new("name")),
|
||||||
|
storage_specifier: Some(Ident::new("storage")),
|
||||||
|
secret_type: Ident::new("type"),
|
||||||
|
options: vec![
|
||||||
|
SecretOption {
|
||||||
|
key: Ident::new("key1"),
|
||||||
|
value: Ident::new("value1"),
|
||||||
|
},
|
||||||
|
SecretOption {
|
||||||
|
key: Ident::new("key2"),
|
||||||
|
value: Ident::new("value2"),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
stmt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_secret_simple() {
|
||||||
|
let sql = r#"CREATE SECRET ( TYPE type )"#;
|
||||||
|
let stmt = duckdb().verified_stmt(sql);
|
||||||
|
assert_eq!(
|
||||||
|
Statement::CreateSecret {
|
||||||
|
or_replace: false,
|
||||||
|
temporary: None,
|
||||||
|
if_not_exists: false,
|
||||||
|
name: None,
|
||||||
|
storage_specifier: None,
|
||||||
|
secret_type: Ident::new("type"),
|
||||||
|
options: vec![]
|
||||||
|
},
|
||||||
|
stmt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_drop_secret() {
|
||||||
|
let sql = r#"DROP PERSISTENT SECRET IF EXISTS secret FROM storage"#;
|
||||||
|
let stmt = duckdb().verified_stmt(sql);
|
||||||
|
assert_eq!(
|
||||||
|
Statement::DropSecret {
|
||||||
|
if_exists: true,
|
||||||
|
temporary: Some(false),
|
||||||
|
name: Ident::new("secret"),
|
||||||
|
storage_specifier: Some(Ident::new("storage"))
|
||||||
|
},
|
||||||
|
stmt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_drop_secret_simple() {
|
||||||
|
let sql = r#"DROP SECRET secret"#;
|
||||||
|
let stmt = duckdb().verified_stmt(sql);
|
||||||
|
assert_eq!(
|
||||||
|
Statement::DropSecret {
|
||||||
|
if_exists: false,
|
||||||
|
temporary: None,
|
||||||
|
name: Ident::new("secret"),
|
||||||
|
storage_specifier: None
|
||||||
|
},
|
||||||
|
stmt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_attach_database() {
|
||||||
|
let sql = r#"ATTACH DATABASE IF NOT EXISTS 'sqlite_file.db' AS sqlite_db (READ_ONLY false, TYPE SQLITE)"#;
|
||||||
|
let stmt = duckdb().verified_stmt(sql);
|
||||||
|
assert_eq!(
|
||||||
|
Statement::AttachDuckDBDatabase {
|
||||||
|
if_not_exists: true,
|
||||||
|
database: true,
|
||||||
|
database_path: Ident::with_quote('\'', "sqlite_file.db"),
|
||||||
|
database_alias: Some(Ident::new("sqlite_db")),
|
||||||
|
attach_options: vec![
|
||||||
|
AttachDuckDBDatabaseOption::ReadOnly(Some(false)),
|
||||||
|
AttachDuckDBDatabaseOption::Type(Ident::new("SQLITE")),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
stmt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_attach_database_simple() {
|
||||||
|
let sql = r#"ATTACH 'postgres://user.name:pass-word@some.url.com:5432/postgres'"#;
|
||||||
|
let stmt = duckdb().verified_stmt(sql);
|
||||||
|
assert_eq!(
|
||||||
|
Statement::AttachDuckDBDatabase {
|
||||||
|
if_not_exists: false,
|
||||||
|
database: false,
|
||||||
|
database_path: Ident::with_quote(
|
||||||
|
'\'',
|
||||||
|
"postgres://user.name:pass-word@some.url.com:5432/postgres"
|
||||||
|
),
|
||||||
|
database_alias: None,
|
||||||
|
attach_options: vec![]
|
||||||
|
},
|
||||||
|
stmt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detach_database() {
|
||||||
|
let sql = r#"DETACH DATABASE IF EXISTS db_name"#;
|
||||||
|
let stmt = duckdb().verified_stmt(sql);
|
||||||
|
assert_eq!(
|
||||||
|
Statement::DetachDuckDBDatabase {
|
||||||
|
if_exists: true,
|
||||||
|
database: true,
|
||||||
|
database_alias: Ident::new("db_name"),
|
||||||
|
},
|
||||||
|
stmt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_detach_database_simple() {
|
||||||
|
let sql = r#"DETACH db_name"#;
|
||||||
|
let stmt = duckdb().verified_stmt(sql);
|
||||||
|
assert_eq!(
|
||||||
|
Statement::DetachDuckDBDatabase {
|
||||||
|
if_exists: false,
|
||||||
|
database: false,
|
||||||
|
database_alias: Ident::new("db_name"),
|
||||||
|
},
|
||||||
|
stmt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_duckdb_named_argument_function_with_assignment_operator() {
|
fn test_duckdb_named_argument_function_with_assignment_operator() {
|
||||||
let sql = "SELECT FUN(a := '1', b := '2') FROM foo";
|
let sql = "SELECT FUN(a := '1', b := '2') FROM foo";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue