Support CREATE/DROP SECRET for duckdb dialect (#1208)

Co-authored-by: Jichao Sun <4977515+JichaoS@users.noreply.github.com>
This commit is contained in:
Andrew Lamb 2024-04-09 16:21:08 -04:00 committed by GitHub
parent 8f67d1a713
commit 241da85d67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 486 additions and 2 deletions

View file

@ -473,7 +473,16 @@ impl<'a> Parser<'a> {
Ok(Statement::Query(self.parse_boxed_query()?))
}
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::CREATE => Ok(self.parse_create()?),
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> {
let database = self.parse_keyword(Keyword::DATABASE);
let database_file_name = self.parse_expr()?;
@ -3075,6 +3150,8 @@ impl<'a> Parser<'a> {
let temporary = self
.parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
.is_some();
let persistent = dialect_of!(self is DuckDbDialect)
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
if self.parse_keyword(Keyword::TABLE) {
self.parse_create_table(or_replace, temporary, global, transient)
} 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)
} else if self.parse_keyword(Keyword::MACRO) {
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 {
self.expected(
"[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
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);
@ -3889,8 +4027,10 @@ impl<'a> Parser<'a> {
pub fn parse_drop(&mut self) -> Result<Statement, ParserError> {
// 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);
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) {
ObjectType::Table
@ -3908,6 +4048,8 @@ impl<'a> Parser<'a> {
ObjectType::Stage
} else if self.parse_keyword(Keyword::FUNCTION) {
return self.parse_drop_function();
} else if self.parse_keyword(Keyword::SECRET) {
return self.parse_drop_secret(temporary, persistent);
} else {
return self.expected(
"TABLE, VIEW, INDEX, ROLE, SCHEMA, FUNCTION, STAGE or SEQUENCE after DROP",
@ -3980,6 +4122,34 @@ impl<'a> Parser<'a> {
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.
///
/// ```sql