Skip to content

Commit 4fdf5e1

Browse files
authored
Fix MySQL parsing of GRANT, REVOKE, and CREATE VIEW (#1538)
1 parent 687ce2d commit 4fdf5e1

11 files changed

+589
-67
lines changed

src/ast/mod.rs

+125-13
Original file line numberDiff line numberDiff line change
@@ -2368,7 +2368,7 @@ pub enum Statement {
23682368
identity: Option<TruncateIdentityOption>,
23692369
/// Postgres-specific option
23702370
/// [ CASCADE | RESTRICT ]
2371-
cascade: Option<TruncateCascadeOption>,
2371+
cascade: Option<CascadeOption>,
23722372
/// ClickHouse-specific option
23732373
/// [ ON CLUSTER cluster_name ]
23742374
///
@@ -2509,6 +2509,8 @@ pub enum Statement {
25092509
/// if not None, has Clickhouse `TO` clause, specify the table into which to insert results
25102510
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/view#materialized-view>
25112511
to: Option<ObjectName>,
2512+
/// MySQL: Optional parameters for the view algorithm, definer, and security context
2513+
params: Option<CreateViewParams>,
25122514
},
25132515
/// ```sql
25142516
/// CREATE TABLE
@@ -3178,9 +3180,9 @@ pub enum Statement {
31783180
Revoke {
31793181
privileges: Privileges,
31803182
objects: GrantObjects,
3181-
grantees: Vec<Ident>,
3183+
grantees: Vec<Grantee>,
31823184
granted_by: Option<Ident>,
3183-
cascade: bool,
3185+
cascade: Option<CascadeOption>,
31843186
},
31853187
/// ```sql
31863188
/// DEALLOCATE [ PREPARE ] { name | ALL }
@@ -3616,8 +3618,8 @@ impl fmt::Display for Statement {
36163618
}
36173619
if let Some(cascade) = cascade {
36183620
match cascade {
3619-
TruncateCascadeOption::Cascade => write!(f, " CASCADE")?,
3620-
TruncateCascadeOption::Restrict => write!(f, " RESTRICT")?,
3621+
CascadeOption::Cascade => write!(f, " CASCADE")?,
3622+
CascadeOption::Restrict => write!(f, " RESTRICT")?,
36213623
}
36223624
}
36233625

@@ -3946,11 +3948,19 @@ impl fmt::Display for Statement {
39463948
if_not_exists,
39473949
temporary,
39483950
to,
3951+
params,
39493952
} => {
39503953
write!(
39513954
f,
3952-
"CREATE {or_replace}{materialized}{temporary}VIEW {if_not_exists}{name}{to}",
3955+
"CREATE {or_replace}",
39533956
or_replace = if *or_replace { "OR REPLACE " } else { "" },
3957+
)?;
3958+
if let Some(params) = params {
3959+
params.fmt(f)?;
3960+
}
3961+
write!(
3962+
f,
3963+
"{materialized}{temporary}VIEW {if_not_exists}{name}{to}",
39543964
materialized = if *materialized { "MATERIALIZED " } else { "" },
39553965
name = name,
39563966
temporary = if *temporary { "TEMPORARY " } else { "" },
@@ -4701,7 +4711,9 @@ impl fmt::Display for Statement {
47014711
if let Some(grantor) = granted_by {
47024712
write!(f, " GRANTED BY {grantor}")?;
47034713
}
4704-
write!(f, " {}", if *cascade { "CASCADE" } else { "RESTRICT" })?;
4714+
if let Some(cascade) = cascade {
4715+
write!(f, " {}", cascade)?;
4716+
}
47054717
Ok(())
47064718
}
47074719
Statement::Deallocate { name, prepare } => write!(
@@ -5103,16 +5115,25 @@ pub enum TruncateIdentityOption {
51035115
Continue,
51045116
}
51055117

5106-
/// PostgreSQL cascade option for TRUNCATE table
5118+
/// Cascade/restrict option for Postgres TRUNCATE table, MySQL GRANT/REVOKE, etc.
51075119
/// [ CASCADE | RESTRICT ]
51085120
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
51095121
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51105122
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5111-
pub enum TruncateCascadeOption {
5123+
pub enum CascadeOption {
51125124
Cascade,
51135125
Restrict,
51145126
}
51155127

5128+
impl Display for CascadeOption {
5129+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5130+
match self {
5131+
CascadeOption::Cascade => write!(f, "CASCADE"),
5132+
CascadeOption::Restrict => write!(f, "RESTRICT"),
5133+
}
5134+
}
5135+
}
5136+
51165137
/// Transaction started with [ TRANSACTION | WORK ]
51175138
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
51185139
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -5404,7 +5425,7 @@ impl fmt::Display for Action {
54045425
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
54055426
pub struct Grantee {
54065427
pub grantee_type: GranteesType,
5407-
pub name: Option<ObjectName>,
5428+
pub name: Option<GranteeName>,
54085429
}
54095430

54105431
impl fmt::Display for Grantee {
@@ -5437,7 +5458,7 @@ impl fmt::Display for Grantee {
54375458
GranteesType::None => (),
54385459
}
54395460
if let Some(ref name) = self.name {
5440-
write!(f, "{}", name)?;
5461+
name.fmt(f)?;
54415462
}
54425463
Ok(())
54435464
}
@@ -5458,6 +5479,28 @@ pub enum GranteesType {
54585479
None,
54595480
}
54605481

5482+
/// Users/roles designated in a GRANT/REVOKE
5483+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
5484+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5485+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
5486+
pub enum GranteeName {
5487+
/// A bare identifier
5488+
ObjectName(ObjectName),
5489+
/// A MySQL user/host pair such as 'root'@'%'
5490+
UserHost { user: Ident, host: Ident },
5491+
}
5492+
5493+
impl fmt::Display for GranteeName {
5494+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5495+
match self {
5496+
GranteeName::ObjectName(name) => name.fmt(f),
5497+
GranteeName::UserHost { user, host } => {
5498+
write!(f, "{}@{}", user, host)
5499+
}
5500+
}
5501+
}
5502+
}
5503+
54615504
/// Objects on which privileges are granted in a GRANT statement.
54625505
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
54635506
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -7460,15 +7503,84 @@ pub enum MySQLColumnPosition {
74607503
impl Display for MySQLColumnPosition {
74617504
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74627505
match self {
7463-
MySQLColumnPosition::First => Ok(write!(f, "FIRST")?),
7506+
MySQLColumnPosition::First => write!(f, "FIRST"),
74647507
MySQLColumnPosition::After(ident) => {
74657508
let column_name = &ident.value;
7466-
Ok(write!(f, "AFTER {column_name}")?)
7509+
write!(f, "AFTER {column_name}")
74677510
}
74687511
}
74697512
}
74707513
}
74717514

7515+
/// MySQL `CREATE VIEW` algorithm parameter: [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
7516+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
7517+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7518+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
7519+
pub enum CreateViewAlgorithm {
7520+
Undefined,
7521+
Merge,
7522+
TempTable,
7523+
}
7524+
7525+
impl Display for CreateViewAlgorithm {
7526+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7527+
match self {
7528+
CreateViewAlgorithm::Undefined => write!(f, "UNDEFINED"),
7529+
CreateViewAlgorithm::Merge => write!(f, "MERGE"),
7530+
CreateViewAlgorithm::TempTable => write!(f, "TEMPTABLE"),
7531+
}
7532+
}
7533+
}
7534+
/// MySQL `CREATE VIEW` security parameter: [SQL SECURITY { DEFINER | INVOKER }]
7535+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
7536+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7537+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
7538+
pub enum CreateViewSecurity {
7539+
Definer,
7540+
Invoker,
7541+
}
7542+
7543+
impl Display for CreateViewSecurity {
7544+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7545+
match self {
7546+
CreateViewSecurity::Definer => write!(f, "DEFINER"),
7547+
CreateViewSecurity::Invoker => write!(f, "INVOKER"),
7548+
}
7549+
}
7550+
}
7551+
7552+
/// [MySQL] `CREATE VIEW` additional parameters
7553+
///
7554+
/// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/create-view.html
7555+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
7556+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
7557+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
7558+
pub struct CreateViewParams {
7559+
pub algorithm: Option<CreateViewAlgorithm>,
7560+
pub definer: Option<GranteeName>,
7561+
pub security: Option<CreateViewSecurity>,
7562+
}
7563+
7564+
impl Display for CreateViewParams {
7565+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7566+
let CreateViewParams {
7567+
algorithm,
7568+
definer,
7569+
security,
7570+
} = self;
7571+
if let Some(algorithm) = algorithm {
7572+
write!(f, "ALGORITHM = {algorithm} ")?;
7573+
}
7574+
if let Some(definers) = definer {
7575+
write!(f, "DEFINER = {definers} ")?;
7576+
}
7577+
if let Some(security) = security {
7578+
write!(f, "SQL SECURITY {security} ")?;
7579+
}
7580+
Ok(())
7581+
}
7582+
}
7583+
74727584
/// Engine of DB. Some warehouse has parameters of engine, e.g. [clickhouse]
74737585
///
74747586
/// [clickhouse]: https://clickhouse.com/docs/en/engines/table-engines

src/ast/spans.rs

+1
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ impl Spanned for Statement {
373373
if_not_exists: _,
374374
temporary: _,
375375
to,
376+
params: _,
376377
} => union_spans(
377378
core::iter::once(name.span())
378379
.chain(columns.iter().map(|i| i.span()))

src/dialect/generic.rs

+4
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,8 @@ impl Dialect for GenericDialect {
135135
fn supports_nested_comments(&self) -> bool {
136136
true
137137
}
138+
139+
fn supports_user_host_grantee(&self) -> bool {
140+
true
141+
}
138142
}

src/dialect/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,11 @@ pub trait Dialect: Debug + Any {
434434
false
435435
}
436436

437+
/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
438+
fn supports_user_host_grantee(&self) -> bool {
439+
false
440+
}
441+
437442
/// Dialect-specific infix parser override
438443
///
439444
/// This method is called to parse the next infix expression.

src/dialect/mysql.rs

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ impl Dialect for MySqlDialect {
107107
fn supports_insert_set(&self) -> bool {
108108
true
109109
}
110+
111+
fn supports_user_host_grantee(&self) -> bool {
112+
true
113+
}
110114
}
111115

112116
/// `LOCK TABLES`

src/keywords.rs

+5
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ define_keywords!(
8484
AFTER,
8585
AGAINST,
8686
AGGREGATION,
87+
ALGORITHM,
8788
ALIAS,
8889
ALL,
8990
ALLOCATE,
@@ -248,6 +249,7 @@ define_keywords!(
248249
DEFERRED,
249250
DEFINE,
250251
DEFINED,
252+
DEFINER,
251253
DELAYED,
252254
DELETE,
253255
DELIMITED,
@@ -423,6 +425,7 @@ define_keywords!(
423425
INTERSECTION,
424426
INTERVAL,
425427
INTO,
428+
INVOKER,
426429
IS,
427430
ISODOW,
428431
ISOLATION,
@@ -780,6 +783,7 @@ define_keywords!(
780783
TBLPROPERTIES,
781784
TEMP,
782785
TEMPORARY,
786+
TEMPTABLE,
783787
TERMINATED,
784788
TERSE,
785789
TEXT,
@@ -828,6 +832,7 @@ define_keywords!(
828832
UNBOUNDED,
829833
UNCACHE,
830834
UNCOMMITTED,
835+
UNDEFINED,
831836
UNFREEZE,
832837
UNION,
833838
UNIQUE,

0 commit comments

Comments
 (0)