Skip to content

Commit bec9fd2

Browse files
authored
Merge pull request apache#1 from luabase/jichaos/secrets
Secret support + sync to origin/head
2 parents 4472789 + 014f00c commit bec9fd2

File tree

4 files changed

+404
-1
lines changed

4 files changed

+404
-1
lines changed

src/ast/mod.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,23 @@ impl fmt::Display for StructField {
347347
}
348348
}
349349

350+
/// A dictionary field within a dictionary.
351+
///
352+
/// [duckdb]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
353+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
354+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
355+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
356+
pub struct DictionaryField {
357+
pub key: Ident,
358+
pub value: Box<Expr>,
359+
}
360+
361+
impl fmt::Display for DictionaryField {
362+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363+
write!(f, "{}: {}", self.key, self.value)
364+
}
365+
}
366+
350367
/// Options for `CAST` / `TRY_CAST`
351368
/// BigQuery: <https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax>
352369
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@@ -687,6 +704,14 @@ pub enum Expr {
687704
expr: Box<Expr>,
688705
name: Ident,
689706
},
707+
/// `DuckDB` specific `Struct` literal expression [1]
708+
///
709+
/// Syntax:
710+
/// ```sql
711+
/// syntax: {'field_name': expr1[, ... ]}
712+
/// ```
713+
/// [1]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
714+
Dictionary(Vec<DictionaryField>),
690715
/// An array index expression e.g. `(ARRAY[1, 2])[1]` or `(current_schemas(FALSE))[1]`
691716
ArrayIndex {
692717
obj: Box<Expr>,
@@ -1146,6 +1171,9 @@ impl fmt::Display for Expr {
11461171
Expr::Named { expr, name } => {
11471172
write!(f, "{} AS {}", expr, name)
11481173
}
1174+
Expr::Dictionary(fields) => {
1175+
write!(f, "{{{}}}", display_comma_separated(fields))
1176+
}
11491177
Expr::ArrayIndex { obj, indexes } => {
11501178
write!(f, "{obj}")?;
11511179
for i in indexes {
@@ -1999,6 +2027,19 @@ pub enum Statement {
19992027
authorization_owner: Option<ObjectName>,
20002028
},
20012029
/// ```sql
2030+
/// CREATE SECRET
2031+
/// ```
2032+
/// See [duckdb](https://duckdb.org/docs/sql/statements/create_secret.html)
2033+
CreateSecret {
2034+
or_replace: bool,
2035+
temporary: Option<bool>,
2036+
if_not_exists: bool,
2037+
name: Option<Ident>,
2038+
storage_specifier: Option<Ident>,
2039+
secret_type: Ident,
2040+
options: Vec<SecretOption>,
2041+
},
2042+
/// ```sql
20022043
/// ALTER TABLE
20032044
/// ```
20042045
AlterTable {
@@ -2080,6 +2121,15 @@ pub enum Statement {
20802121
option: Option<ReferentialAction>,
20812122
},
20822123
/// ```sql
2124+
/// DROP SECRET
2125+
/// ```
2126+
DropSecret {
2127+
if_exists: bool,
2128+
temporary: Option<bool>,
2129+
name: Ident,
2130+
storage_specifier: Option<Ident>,
2131+
},
2132+
/// ```sql
20832133
/// DECLARE
20842134
/// ```
20852135
/// Declare Cursor Variables
@@ -3515,6 +3565,48 @@ impl fmt::Display for Statement {
35153565
}
35163566
Ok(())
35173567
}
3568+
Statement::CreateSecret {
3569+
or_replace,
3570+
temporary,
3571+
if_not_exists,
3572+
name,
3573+
storage_specifier,
3574+
secret_type,
3575+
options,
3576+
} => {
3577+
write!(
3578+
f,
3579+
"CREATE {or_replace}",
3580+
or_replace = if *or_replace { "OR REPLACE " } else { "" },
3581+
)?;
3582+
if let Some(t) = temporary {
3583+
write!(f, "{}", if *t { "TEMPORARY " } else { "PERSISTENT " })?;
3584+
}
3585+
write!(
3586+
f,
3587+
"SECRET {if_not_exists}",
3588+
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
3589+
)?;
3590+
if let Some(n) = name {
3591+
write!(f, "{n} ")?;
3592+
};
3593+
if let Some(s) = storage_specifier {
3594+
write!(f, "IN {s} ")?;
3595+
}
3596+
write!(
3597+
f,
3598+
"( TYPE {secret_type}",
3599+
)?;
3600+
if !options.is_empty() {
3601+
write!(
3602+
f,
3603+
", {o}",
3604+
o = display_comma_separated(options)
3605+
)?;
3606+
}
3607+
write!(f, " )")?;
3608+
Ok(())
3609+
}
35183610
Statement::AlterTable {
35193611
name,
35203612
if_exists,
@@ -3595,6 +3687,21 @@ impl fmt::Display for Statement {
35953687
}
35963688
Ok(())
35973689
}
3690+
Statement::DropSecret { if_exists, temporary, name, storage_specifier } => {
3691+
write!(f, "DROP ")?;
3692+
if let Some(t) = temporary {
3693+
write!(f, "{}", if *t { "TEMPORARY " } else { "PERSISTENT " })?;
3694+
}
3695+
write!(
3696+
f,
3697+
"SECRET {if_exists}{name}",
3698+
if_exists = if *if_exists { "IF EXISTS " } else { "" },
3699+
)?;
3700+
if let Some(s) = storage_specifier {
3701+
write!(f, " FROM {s}")?;
3702+
}
3703+
Ok(())
3704+
}
35983705
Statement::Discard { object_type } => {
35993706
write!(f, "DISCARD {object_type}")?;
36003707
Ok(())
@@ -5026,6 +5133,20 @@ impl fmt::Display for SqlOption {
50265133
}
50275134
}
50285135

5136+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5137+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5138+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5139+
pub struct SecretOption {
5140+
pub key: Ident,
5141+
pub value: Ident,
5142+
}
5143+
5144+
impl fmt::Display for SecretOption {
5145+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5146+
write!(f, "{} {}", self.key, self.value)
5147+
}
5148+
}
5149+
50295150
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
50305151
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
50315152
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ define_keywords!(
511511
PERCENTILE_DISC,
512512
PERCENT_RANK,
513513
PERIOD,
514+
PERSISTENT,
514515
PIVOT,
515516
PLACING,
516517
PLANS,
@@ -596,6 +597,7 @@ define_keywords!(
596597
SCROLL,
597598
SEARCH,
598599
SECOND,
600+
SECRET,
599601
SECURITY,
600602
SELECT,
601603
SEMI,

src/parser/mod.rs

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,10 @@ impl<'a> Parser<'a> {
11171117
self.prev_token();
11181118
Ok(Expr::Value(self.parse_value()?))
11191119
}
1120+
Token::LBrace if dialect_of!(self is DuckDbDialect | GenericDialect) => {
1121+
self.prev_token();
1122+
self.parse_duckdb_struct_literal()
1123+
}
11201124
_ => self.expected("an expression:", next_token),
11211125
}?;
11221126

@@ -2127,6 +2131,45 @@ impl<'a> Parser<'a> {
21272131
))
21282132
}
21292133

2134+
/// DuckDB specific: Parse a duckdb dictionary [1]
2135+
///
2136+
/// Syntax:
2137+
///
2138+
/// ```sql
2139+
/// {'field_name': expr1[, ... ]}
2140+
/// ```
2141+
///
2142+
/// [1]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
2143+
fn parse_duckdb_struct_literal(&mut self) -> Result<Expr, ParserError> {
2144+
self.expect_token(&Token::LBrace)?;
2145+
2146+
let fields = self.parse_comma_separated(Self::parse_duckdb_dictionary_field)?;
2147+
2148+
self.expect_token(&Token::RBrace)?;
2149+
2150+
Ok(Expr::Dictionary(fields))
2151+
}
2152+
2153+
/// Parse a field for a duckdb dictionary [1]
2154+
/// Syntax
2155+
/// ```sql
2156+
/// 'name': expr
2157+
/// ```
2158+
///
2159+
/// [1]: https://duckdb.org/docs/sql/data_types/struct#creating-structs
2160+
fn parse_duckdb_dictionary_field(&mut self) -> Result<DictionaryField, ParserError> {
2161+
let key = self.parse_identifier(false)?;
2162+
2163+
self.expect_token(&Token::Colon)?;
2164+
2165+
let expr = self.parse_expr()?;
2166+
2167+
Ok(DictionaryField {
2168+
key,
2169+
value: Box::new(expr),
2170+
})
2171+
}
2172+
21302173
/// For nested types that use the angle bracket syntax, this matches either
21312174
/// `>`, `>>` or nothing depending on which variant is expected (specified by the previously
21322175
/// matched `trailing_bracket` argument). It returns whether there is a trailing
@@ -3031,6 +3074,7 @@ impl<'a> Parser<'a> {
30313074
let temporary = self
30323075
.parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
30333076
.is_some();
3077+
let persistent = dialect_of!(self is DuckDbDialect) && self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
30343078
if self.parse_keyword(Keyword::TABLE) {
30353079
self.parse_create_table(or_replace, temporary, global, transient)
30363080
} else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) {
@@ -3042,6 +3086,8 @@ impl<'a> Parser<'a> {
30423086
self.parse_create_function(or_replace, temporary)
30433087
} else if self.parse_keyword(Keyword::MACRO) {
30443088
self.parse_create_macro(or_replace, temporary)
3089+
} else if self.parse_keyword(Keyword::SECRET) {
3090+
self.parse_create_secret(or_replace, temporary, persistent)
30453091
} else if or_replace {
30463092
self.expected(
30473093
"[EXTERNAL] TABLE or [MATERIALIZED] VIEW or FUNCTION after CREATE OR REPLACE",
@@ -3072,6 +3118,62 @@ impl<'a> Parser<'a> {
30723118
}
30733119
}
30743120

3121+
/// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details.
3122+
pub fn parse_create_secret(
3123+
&mut self,
3124+
or_replace: bool,
3125+
temporary: bool,
3126+
persistent: bool,
3127+
) -> Result<Statement, ParserError> {
3128+
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
3129+
3130+
let mut storage_specifier = None;
3131+
let mut name = None;
3132+
if self.peek_token() != Token::LParen {
3133+
if self.parse_keyword(Keyword::IN) {
3134+
storage_specifier = self.parse_identifier(false).ok()
3135+
} else {
3136+
name = self.parse_identifier(false).ok();
3137+
}
3138+
3139+
// Storage specifier may follow the name
3140+
if storage_specifier.is_none() && self.peek_token() != Token::LParen && self.parse_keyword(Keyword::IN) {
3141+
storage_specifier = self.parse_identifier(false).ok();
3142+
}
3143+
}
3144+
3145+
self.expect_token(&Token::LParen)?;
3146+
self.expect_keyword(Keyword::TYPE)?;
3147+
let secret_type = self.parse_identifier(false)?;
3148+
3149+
let mut options = Vec::new();
3150+
if self.consume_token(&Token::Comma) {
3151+
options.append(&mut self.parse_comma_separated(|p| {
3152+
let key = p.parse_identifier(false)?;
3153+
let value = p.parse_identifier(false)?;
3154+
Ok(SecretOption { key, value })
3155+
})?);
3156+
}
3157+
self.expect_token(&Token::RParen)?;
3158+
3159+
let temp = match (temporary, persistent) {
3160+
(true, false) => Some(true),
3161+
(false, true) => Some(false),
3162+
(false, false) => None,
3163+
_ => self.expected("TEMPORARY or PERSISTENT", self.peek_token())?,
3164+
};
3165+
3166+
Ok(Statement::CreateSecret {
3167+
or_replace,
3168+
temporary: temp,
3169+
if_not_exists,
3170+
name,
3171+
storage_specifier,
3172+
secret_type,
3173+
options,
3174+
})
3175+
}
3176+
30753177
/// Parse a CACHE TABLE statement
30763178
pub fn parse_cache_table(&mut self) -> Result<Statement, ParserError> {
30773179
let (mut table_flag, mut options, mut has_as, mut query) = (None, vec![], false, None);
@@ -3805,8 +3907,9 @@ impl<'a> Parser<'a> {
38053907

38063908
pub fn parse_drop(&mut self) -> Result<Statement, ParserError> {
38073909
// MySQL dialect supports `TEMPORARY`
3808-
let temporary = dialect_of!(self is MySqlDialect | GenericDialect)
3910+
let temporary = dialect_of!(self is MySqlDialect | GenericDialect | DuckDbDialect)
38093911
&& self.parse_keyword(Keyword::TEMPORARY);
3912+
let persistent = dialect_of!(self is DuckDbDialect) && self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
38103913

38113914
let object_type = if self.parse_keyword(Keyword::TABLE) {
38123915
ObjectType::Table
@@ -3824,6 +3927,8 @@ impl<'a> Parser<'a> {
38243927
ObjectType::Stage
38253928
} else if self.parse_keyword(Keyword::FUNCTION) {
38263929
return self.parse_drop_function();
3930+
} else if self.parse_keyword(Keyword::SECRET) {
3931+
return self.parse_drop_secret(temporary, persistent);
38273932
} else {
38283933
return self.expected(
38293934
"TABLE, VIEW, INDEX, ROLE, SCHEMA, FUNCTION, STAGE or SEQUENCE after DROP",
@@ -3896,6 +4001,25 @@ impl<'a> Parser<'a> {
38964001
Ok(DropFunctionDesc { name, args })
38974002
}
38984003

4004+
/// See [DuckDB Docs](https://duckdb.org/docs/sql/statements/create_secret.html) for more details.
4005+
fn parse_drop_secret(&mut self, temporary: bool, persistent: bool) -> Result<Statement, ParserError> {
4006+
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
4007+
let name = self.parse_identifier(false)?;
4008+
let storage_specifier = if self.parse_keyword(Keyword::FROM) {
4009+
self.parse_identifier(false).ok()
4010+
} else {
4011+
None
4012+
};
4013+
let temp = match (temporary, persistent) {
4014+
(true, false) => Some(true),
4015+
(false, true) => Some(false),
4016+
(false, false) => None,
4017+
_ => self.expected("TEMPORARY or PERSISTENT", self.peek_token())?,
4018+
};
4019+
4020+
Ok(Statement::DropSecret { if_exists, temporary: temp, name, storage_specifier})
4021+
}
4022+
38994023
/// Parse a `DECLARE` statement.
39004024
///
39014025
/// ```sql

0 commit comments

Comments
 (0)