Skip to content

Commit

Permalink
Fix MySQL parsing of GRANT, REVOKE, and CREATE VIEW (#1538)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvzink authored Jan 9, 2025
1 parent 687ce2d commit 4fdf5e1
Show file tree
Hide file tree
Showing 11 changed files with 589 additions and 67 deletions.
138 changes: 125 additions & 13 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2368,7 +2368,7 @@ pub enum Statement {
identity: Option<TruncateIdentityOption>,
/// Postgres-specific option
/// [ CASCADE | RESTRICT ]
cascade: Option<TruncateCascadeOption>,
cascade: Option<CascadeOption>,
/// ClickHouse-specific option
/// [ ON CLUSTER cluster_name ]
///
Expand Down Expand Up @@ -2509,6 +2509,8 @@ pub enum Statement {
/// if not None, has Clickhouse `TO` clause, specify the table into which to insert results
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/view#materialized-view>
to: Option<ObjectName>,
/// MySQL: Optional parameters for the view algorithm, definer, and security context
params: Option<CreateViewParams>,
},
/// ```sql
/// CREATE TABLE
Expand Down Expand Up @@ -3178,9 +3180,9 @@ pub enum Statement {
Revoke {
privileges: Privileges,
objects: GrantObjects,
grantees: Vec<Ident>,
grantees: Vec<Grantee>,
granted_by: Option<Ident>,
cascade: bool,
cascade: Option<CascadeOption>,
},
/// ```sql
/// DEALLOCATE [ PREPARE ] { name | ALL }
Expand Down Expand Up @@ -3616,8 +3618,8 @@ impl fmt::Display for Statement {
}
if let Some(cascade) = cascade {
match cascade {
TruncateCascadeOption::Cascade => write!(f, " CASCADE")?,
TruncateCascadeOption::Restrict => write!(f, " RESTRICT")?,
CascadeOption::Cascade => write!(f, " CASCADE")?,
CascadeOption::Restrict => write!(f, " RESTRICT")?,
}
}

Expand Down Expand Up @@ -3946,11 +3948,19 @@ impl fmt::Display for Statement {
if_not_exists,
temporary,
to,
params,
} => {
write!(
f,
"CREATE {or_replace}{materialized}{temporary}VIEW {if_not_exists}{name}{to}",
"CREATE {or_replace}",
or_replace = if *or_replace { "OR REPLACE " } else { "" },
)?;
if let Some(params) = params {
params.fmt(f)?;
}
write!(
f,
"{materialized}{temporary}VIEW {if_not_exists}{name}{to}",
materialized = if *materialized { "MATERIALIZED " } else { "" },
name = name,
temporary = if *temporary { "TEMPORARY " } else { "" },
Expand Down Expand Up @@ -4701,7 +4711,9 @@ impl fmt::Display for Statement {
if let Some(grantor) = granted_by {
write!(f, " GRANTED BY {grantor}")?;
}
write!(f, " {}", if *cascade { "CASCADE" } else { "RESTRICT" })?;
if let Some(cascade) = cascade {
write!(f, " {}", cascade)?;
}
Ok(())
}
Statement::Deallocate { name, prepare } => write!(
Expand Down Expand Up @@ -5103,16 +5115,25 @@ pub enum TruncateIdentityOption {
Continue,
}

/// PostgreSQL cascade option for TRUNCATE table
/// Cascade/restrict option for Postgres TRUNCATE table, MySQL GRANT/REVOKE, etc.
/// [ CASCADE | RESTRICT ]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum TruncateCascadeOption {
pub enum CascadeOption {
Cascade,
Restrict,
}

impl Display for CascadeOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CascadeOption::Cascade => write!(f, "CASCADE"),
CascadeOption::Restrict => write!(f, "RESTRICT"),
}
}
}

/// Transaction started with [ TRANSACTION | WORK ]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -5404,7 +5425,7 @@ impl fmt::Display for Action {
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Grantee {
pub grantee_type: GranteesType,
pub name: Option<ObjectName>,
pub name: Option<GranteeName>,
}

impl fmt::Display for Grantee {
Expand Down Expand Up @@ -5437,7 +5458,7 @@ impl fmt::Display for Grantee {
GranteesType::None => (),
}
if let Some(ref name) = self.name {
write!(f, "{}", name)?;
name.fmt(f)?;
}
Ok(())
}
Expand All @@ -5458,6 +5479,28 @@ pub enum GranteesType {
None,
}

/// Users/roles designated in a GRANT/REVOKE
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum GranteeName {
/// A bare identifier
ObjectName(ObjectName),
/// A MySQL user/host pair such as 'root'@'%'
UserHost { user: Ident, host: Ident },
}

impl fmt::Display for GranteeName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GranteeName::ObjectName(name) => name.fmt(f),
GranteeName::UserHost { user, host } => {
write!(f, "{}@{}", user, host)
}
}
}
}

/// Objects on which privileges are granted in a GRANT statement.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -7460,15 +7503,84 @@ pub enum MySQLColumnPosition {
impl Display for MySQLColumnPosition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MySQLColumnPosition::First => Ok(write!(f, "FIRST")?),
MySQLColumnPosition::First => write!(f, "FIRST"),
MySQLColumnPosition::After(ident) => {
let column_name = &ident.value;
Ok(write!(f, "AFTER {column_name}")?)
write!(f, "AFTER {column_name}")
}
}
}
}

/// MySQL `CREATE VIEW` algorithm parameter: [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum CreateViewAlgorithm {
Undefined,
Merge,
TempTable,
}

impl Display for CreateViewAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CreateViewAlgorithm::Undefined => write!(f, "UNDEFINED"),
CreateViewAlgorithm::Merge => write!(f, "MERGE"),
CreateViewAlgorithm::TempTable => write!(f, "TEMPTABLE"),
}
}
}
/// MySQL `CREATE VIEW` security parameter: [SQL SECURITY { DEFINER | INVOKER }]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum CreateViewSecurity {
Definer,
Invoker,
}

impl Display for CreateViewSecurity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CreateViewSecurity::Definer => write!(f, "DEFINER"),
CreateViewSecurity::Invoker => write!(f, "INVOKER"),
}
}
}

/// [MySQL] `CREATE VIEW` additional parameters
///
/// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/create-view.html
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct CreateViewParams {
pub algorithm: Option<CreateViewAlgorithm>,
pub definer: Option<GranteeName>,
pub security: Option<CreateViewSecurity>,
}

impl Display for CreateViewParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let CreateViewParams {
algorithm,
definer,
security,
} = self;
if let Some(algorithm) = algorithm {
write!(f, "ALGORITHM = {algorithm} ")?;
}
if let Some(definers) = definer {
write!(f, "DEFINER = {definers} ")?;
}
if let Some(security) = security {
write!(f, "SQL SECURITY {security} ")?;
}
Ok(())
}
}

/// Engine of DB. Some warehouse has parameters of engine, e.g. [clickhouse]
///
/// [clickhouse]: https://clickhouse.com/docs/en/engines/table-engines
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ impl Spanned for Statement {
if_not_exists: _,
temporary: _,
to,
params: _,
} => union_spans(
core::iter::once(name.span())
.chain(columns.iter().map(|i| i.span()))
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,8 @@ impl Dialect for GenericDialect {
fn supports_nested_comments(&self) -> bool {
true
}

fn supports_user_host_grantee(&self) -> bool {
true
}
}
5 changes: 5 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,11 @@ pub trait Dialect: Debug + Any {
false
}

/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
fn supports_user_host_grantee(&self) -> bool {
false
}

/// Dialect-specific infix parser override
///
/// This method is called to parse the next infix expression.
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ impl Dialect for MySqlDialect {
fn supports_insert_set(&self) -> bool {
true
}

fn supports_user_host_grantee(&self) -> bool {
true
}
}

/// `LOCK TABLES`
Expand Down
5 changes: 5 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ define_keywords!(
AFTER,
AGAINST,
AGGREGATION,
ALGORITHM,
ALIAS,
ALL,
ALLOCATE,
Expand Down Expand Up @@ -248,6 +249,7 @@ define_keywords!(
DEFERRED,
DEFINE,
DEFINED,
DEFINER,
DELAYED,
DELETE,
DELIMITED,
Expand Down Expand Up @@ -423,6 +425,7 @@ define_keywords!(
INTERSECTION,
INTERVAL,
INTO,
INVOKER,
IS,
ISODOW,
ISOLATION,
Expand Down Expand Up @@ -780,6 +783,7 @@ define_keywords!(
TBLPROPERTIES,
TEMP,
TEMPORARY,
TEMPTABLE,
TERMINATED,
TERSE,
TEXT,
Expand Down Expand Up @@ -828,6 +832,7 @@ define_keywords!(
UNBOUNDED,
UNCACHE,
UNCOMMITTED,
UNDEFINED,
UNFREEZE,
UNION,
UNIQUE,
Expand Down
Loading

0 comments on commit 4fdf5e1

Please sign in to comment.