mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
Fix MySQL parsing of GRANT, REVOKE, and CREATE VIEW (#1538)
This commit is contained in:
parent
687ce2d5f4
commit
4fdf5e1b30
11 changed files with 590 additions and 68 deletions
138
src/ast/mod.rs
138
src/ast/mod.rs
|
@ -2368,7 +2368,7 @@ pub enum Statement {
|
||||||
identity: Option<TruncateIdentityOption>,
|
identity: Option<TruncateIdentityOption>,
|
||||||
/// Postgres-specific option
|
/// Postgres-specific option
|
||||||
/// [ CASCADE | RESTRICT ]
|
/// [ CASCADE | RESTRICT ]
|
||||||
cascade: Option<TruncateCascadeOption>,
|
cascade: Option<CascadeOption>,
|
||||||
/// ClickHouse-specific option
|
/// ClickHouse-specific option
|
||||||
/// [ ON CLUSTER cluster_name ]
|
/// [ ON CLUSTER cluster_name ]
|
||||||
///
|
///
|
||||||
|
@ -2509,6 +2509,8 @@ pub enum Statement {
|
||||||
/// if not None, has Clickhouse `TO` clause, specify the table into which to insert results
|
/// 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>
|
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/view#materialized-view>
|
||||||
to: Option<ObjectName>,
|
to: Option<ObjectName>,
|
||||||
|
/// MySQL: Optional parameters for the view algorithm, definer, and security context
|
||||||
|
params: Option<CreateViewParams>,
|
||||||
},
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
/// CREATE TABLE
|
/// CREATE TABLE
|
||||||
|
@ -3178,9 +3180,9 @@ pub enum Statement {
|
||||||
Revoke {
|
Revoke {
|
||||||
privileges: Privileges,
|
privileges: Privileges,
|
||||||
objects: GrantObjects,
|
objects: GrantObjects,
|
||||||
grantees: Vec<Ident>,
|
grantees: Vec<Grantee>,
|
||||||
granted_by: Option<Ident>,
|
granted_by: Option<Ident>,
|
||||||
cascade: bool,
|
cascade: Option<CascadeOption>,
|
||||||
},
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
/// DEALLOCATE [ PREPARE ] { name | ALL }
|
/// DEALLOCATE [ PREPARE ] { name | ALL }
|
||||||
|
@ -3616,8 +3618,8 @@ impl fmt::Display for Statement {
|
||||||
}
|
}
|
||||||
if let Some(cascade) = cascade {
|
if let Some(cascade) = cascade {
|
||||||
match cascade {
|
match cascade {
|
||||||
TruncateCascadeOption::Cascade => write!(f, " CASCADE")?,
|
CascadeOption::Cascade => write!(f, " CASCADE")?,
|
||||||
TruncateCascadeOption::Restrict => write!(f, " RESTRICT")?,
|
CascadeOption::Restrict => write!(f, " RESTRICT")?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3946,11 +3948,19 @@ impl fmt::Display for Statement {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params,
|
||||||
} => {
|
} => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"CREATE {or_replace}{materialized}{temporary}VIEW {if_not_exists}{name}{to}",
|
"CREATE {or_replace}",
|
||||||
or_replace = if *or_replace { "OR REPLACE " } else { "" },
|
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 { "" },
|
materialized = if *materialized { "MATERIALIZED " } else { "" },
|
||||||
name = name,
|
name = name,
|
||||||
temporary = if *temporary { "TEMPORARY " } else { "" },
|
temporary = if *temporary { "TEMPORARY " } else { "" },
|
||||||
|
@ -4701,7 +4711,9 @@ impl fmt::Display for Statement {
|
||||||
if let Some(grantor) = granted_by {
|
if let Some(grantor) = granted_by {
|
||||||
write!(f, " GRANTED BY {grantor}")?;
|
write!(f, " GRANTED BY {grantor}")?;
|
||||||
}
|
}
|
||||||
write!(f, " {}", if *cascade { "CASCADE" } else { "RESTRICT" })?;
|
if let Some(cascade) = cascade {
|
||||||
|
write!(f, " {}", cascade)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Statement::Deallocate { name, prepare } => write!(
|
Statement::Deallocate { name, prepare } => write!(
|
||||||
|
@ -5103,16 +5115,25 @@ pub enum TruncateIdentityOption {
|
||||||
Continue,
|
Continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PostgreSQL cascade option for TRUNCATE table
|
/// Cascade/restrict option for Postgres TRUNCATE table, MySQL GRANT/REVOKE, etc.
|
||||||
/// [ CASCADE | RESTRICT ]
|
/// [ CASCADE | RESTRICT ]
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, 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))]
|
||||||
pub enum TruncateCascadeOption {
|
pub enum CascadeOption {
|
||||||
Cascade,
|
Cascade,
|
||||||
Restrict,
|
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 ]
|
/// Transaction started with [ TRANSACTION | WORK ]
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
@ -5404,7 +5425,7 @@ impl fmt::Display for Action {
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
pub struct Grantee {
|
pub struct Grantee {
|
||||||
pub grantee_type: GranteesType,
|
pub grantee_type: GranteesType,
|
||||||
pub name: Option<ObjectName>,
|
pub name: Option<GranteeName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Grantee {
|
impl fmt::Display for Grantee {
|
||||||
|
@ -5437,7 +5458,7 @@ impl fmt::Display for Grantee {
|
||||||
GranteesType::None => (),
|
GranteesType::None => (),
|
||||||
}
|
}
|
||||||
if let Some(ref name) = self.name {
|
if let Some(ref name) = self.name {
|
||||||
write!(f, "{}", name)?;
|
name.fmt(f)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -5458,6 +5479,28 @@ pub enum GranteesType {
|
||||||
None,
|
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.
|
/// Objects on which privileges are granted in a GRANT statement.
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
@ -7460,15 +7503,84 @@ pub enum MySQLColumnPosition {
|
||||||
impl Display for MySQLColumnPosition {
|
impl Display for MySQLColumnPosition {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
MySQLColumnPosition::First => Ok(write!(f, "FIRST")?),
|
MySQLColumnPosition::First => write!(f, "FIRST"),
|
||||||
MySQLColumnPosition::After(ident) => {
|
MySQLColumnPosition::After(ident) => {
|
||||||
let column_name = &ident.value;
|
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]
|
/// Engine of DB. Some warehouse has parameters of engine, e.g. [clickhouse]
|
||||||
///
|
///
|
||||||
/// [clickhouse]: https://clickhouse.com/docs/en/engines/table-engines
|
/// [clickhouse]: https://clickhouse.com/docs/en/engines/table-engines
|
||||||
|
|
|
@ -373,6 +373,7 @@ impl Spanned for Statement {
|
||||||
if_not_exists: _,
|
if_not_exists: _,
|
||||||
temporary: _,
|
temporary: _,
|
||||||
to,
|
to,
|
||||||
|
params: _,
|
||||||
} => union_spans(
|
} => union_spans(
|
||||||
core::iter::once(name.span())
|
core::iter::once(name.span())
|
||||||
.chain(columns.iter().map(|i| i.span()))
|
.chain(columns.iter().map(|i| i.span()))
|
||||||
|
|
|
@ -135,4 +135,8 @@ impl Dialect for GenericDialect {
|
||||||
fn supports_nested_comments(&self) -> bool {
|
fn supports_nested_comments(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_user_host_grantee(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -434,6 +434,11 @@ pub trait Dialect: Debug + Any {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does the dialect support MySQL-style `'user'@'host'` grantee syntax?
|
||||||
|
fn supports_user_host_grantee(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Dialect-specific infix parser override
|
/// Dialect-specific infix parser override
|
||||||
///
|
///
|
||||||
/// This method is called to parse the next infix expression.
|
/// This method is called to parse the next infix expression.
|
||||||
|
|
|
@ -107,6 +107,10 @@ impl Dialect for MySqlDialect {
|
||||||
fn supports_insert_set(&self) -> bool {
|
fn supports_insert_set(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supports_user_host_grantee(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `LOCK TABLES`
|
/// `LOCK TABLES`
|
||||||
|
|
|
@ -84,6 +84,7 @@ define_keywords!(
|
||||||
AFTER,
|
AFTER,
|
||||||
AGAINST,
|
AGAINST,
|
||||||
AGGREGATION,
|
AGGREGATION,
|
||||||
|
ALGORITHM,
|
||||||
ALIAS,
|
ALIAS,
|
||||||
ALL,
|
ALL,
|
||||||
ALLOCATE,
|
ALLOCATE,
|
||||||
|
@ -248,6 +249,7 @@ define_keywords!(
|
||||||
DEFERRED,
|
DEFERRED,
|
||||||
DEFINE,
|
DEFINE,
|
||||||
DEFINED,
|
DEFINED,
|
||||||
|
DEFINER,
|
||||||
DELAYED,
|
DELAYED,
|
||||||
DELETE,
|
DELETE,
|
||||||
DELIMITED,
|
DELIMITED,
|
||||||
|
@ -423,6 +425,7 @@ define_keywords!(
|
||||||
INTERSECTION,
|
INTERSECTION,
|
||||||
INTERVAL,
|
INTERVAL,
|
||||||
INTO,
|
INTO,
|
||||||
|
INVOKER,
|
||||||
IS,
|
IS,
|
||||||
ISODOW,
|
ISODOW,
|
||||||
ISOLATION,
|
ISOLATION,
|
||||||
|
@ -780,6 +783,7 @@ define_keywords!(
|
||||||
TBLPROPERTIES,
|
TBLPROPERTIES,
|
||||||
TEMP,
|
TEMP,
|
||||||
TEMPORARY,
|
TEMPORARY,
|
||||||
|
TEMPTABLE,
|
||||||
TERMINATED,
|
TERMINATED,
|
||||||
TERSE,
|
TERSE,
|
||||||
TEXT,
|
TEXT,
|
||||||
|
@ -828,6 +832,7 @@ define_keywords!(
|
||||||
UNBOUNDED,
|
UNBOUNDED,
|
||||||
UNCACHE,
|
UNCACHE,
|
||||||
UNCOMMITTED,
|
UNCOMMITTED,
|
||||||
|
UNDEFINED,
|
||||||
UNFREEZE,
|
UNFREEZE,
|
||||||
UNION,
|
UNION,
|
||||||
UNIQUE,
|
UNIQUE,
|
||||||
|
|
|
@ -805,13 +805,7 @@ impl<'a> Parser<'a> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
cascade = if self.parse_keyword(Keyword::CASCADE) {
|
cascade = self.parse_cascade_option();
|
||||||
Some(TruncateCascadeOption::Cascade)
|
|
||||||
} else if self.parse_keyword(Keyword::RESTRICT) {
|
|
||||||
Some(TruncateCascadeOption::Restrict)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_cluster = self.parse_optional_on_cluster()?;
|
let on_cluster = self.parse_optional_on_cluster()?;
|
||||||
|
@ -827,6 +821,16 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_cascade_option(&mut self) -> Option<CascadeOption> {
|
||||||
|
if self.parse_keyword(Keyword::CASCADE) {
|
||||||
|
Some(CascadeOption::Cascade)
|
||||||
|
} else if self.parse_keyword(Keyword::RESTRICT) {
|
||||||
|
Some(CascadeOption::Restrict)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_attach_duckdb_database_options(
|
pub fn parse_attach_duckdb_database_options(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<Vec<AttachDuckDBDatabaseOption>, ParserError> {
|
) -> Result<Vec<AttachDuckDBDatabaseOption>, ParserError> {
|
||||||
|
@ -4147,11 +4151,12 @@ impl<'a> Parser<'a> {
|
||||||
.is_some();
|
.is_some();
|
||||||
let persistent = dialect_of!(self is DuckDbDialect)
|
let persistent = dialect_of!(self is DuckDbDialect)
|
||||||
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
|
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
|
||||||
|
let create_view_params = self.parse_create_view_params()?;
|
||||||
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) {
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
self.parse_create_view(or_replace, temporary)
|
self.parse_create_view(or_replace, temporary, create_view_params)
|
||||||
} else if self.parse_keyword(Keyword::POLICY) {
|
} else if self.parse_keyword(Keyword::POLICY) {
|
||||||
self.parse_create_policy()
|
self.parse_create_policy()
|
||||||
} else if self.parse_keyword(Keyword::EXTERNAL) {
|
} else if self.parse_keyword(Keyword::EXTERNAL) {
|
||||||
|
@ -5039,6 +5044,7 @@ impl<'a> Parser<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
or_replace: bool,
|
or_replace: bool,
|
||||||
temporary: bool,
|
temporary: bool,
|
||||||
|
create_view_params: Option<CreateViewParams>,
|
||||||
) -> Result<Statement, ParserError> {
|
) -> Result<Statement, ParserError> {
|
||||||
let materialized = self.parse_keyword(Keyword::MATERIALIZED);
|
let materialized = self.parse_keyword(Keyword::MATERIALIZED);
|
||||||
self.expect_keyword_is(Keyword::VIEW)?;
|
self.expect_keyword_is(Keyword::VIEW)?;
|
||||||
|
@ -5116,9 +5122,68 @@ impl<'a> Parser<'a> {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params: create_view_params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse optional parameters for the `CREATE VIEW` statement supported by [MySQL].
|
||||||
|
///
|
||||||
|
/// [MySQL]: https://dev.mysql.com/doc/refman/9.1/en/create-view.html
|
||||||
|
fn parse_create_view_params(&mut self) -> Result<Option<CreateViewParams>, ParserError> {
|
||||||
|
let algorithm = if self.parse_keyword(Keyword::ALGORITHM) {
|
||||||
|
self.expect_token(&Token::Eq)?;
|
||||||
|
Some(
|
||||||
|
match self.expect_one_of_keywords(&[
|
||||||
|
Keyword::UNDEFINED,
|
||||||
|
Keyword::MERGE,
|
||||||
|
Keyword::TEMPTABLE,
|
||||||
|
])? {
|
||||||
|
Keyword::UNDEFINED => CreateViewAlgorithm::Undefined,
|
||||||
|
Keyword::MERGE => CreateViewAlgorithm::Merge,
|
||||||
|
Keyword::TEMPTABLE => CreateViewAlgorithm::TempTable,
|
||||||
|
_ => {
|
||||||
|
self.prev_token();
|
||||||
|
let found = self.next_token();
|
||||||
|
return self
|
||||||
|
.expected("UNDEFINED or MERGE or TEMPTABLE after ALGORITHM =", found);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let definer = if self.parse_keyword(Keyword::DEFINER) {
|
||||||
|
self.expect_token(&Token::Eq)?;
|
||||||
|
Some(self.parse_grantee_name()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let security = if self.parse_keywords(&[Keyword::SQL, Keyword::SECURITY]) {
|
||||||
|
Some(
|
||||||
|
match self.expect_one_of_keywords(&[Keyword::DEFINER, Keyword::INVOKER])? {
|
||||||
|
Keyword::DEFINER => CreateViewSecurity::Definer,
|
||||||
|
Keyword::INVOKER => CreateViewSecurity::Invoker,
|
||||||
|
_ => {
|
||||||
|
self.prev_token();
|
||||||
|
let found = self.next_token();
|
||||||
|
return self.expected("DEFINER or INVOKER after SQL SECURITY", found);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if algorithm.is_some() || definer.is_some() || security.is_some() {
|
||||||
|
Ok(Some(CreateViewParams {
|
||||||
|
algorithm,
|
||||||
|
definer,
|
||||||
|
security,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_create_role(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_create_role(&mut self) -> Result<Statement, ParserError> {
|
||||||
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||||
let names = self.parse_comma_separated(|p| p.parse_object_name(false))?;
|
let names = self.parse_comma_separated(|p| p.parse_object_name(false))?;
|
||||||
|
@ -8872,13 +8937,13 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a possibly qualified, possibly quoted identifier, e.g.
|
/// Parse a possibly qualified, possibly quoted identifier, optionally allowing for wildcards,
|
||||||
/// `foo` or `myschema."table"
|
/// e.g. *, *.*, `foo`.*, or "foo"."bar"
|
||||||
///
|
fn parse_object_name_with_wildcards(
|
||||||
/// The `in_table_clause` parameter indicates whether the object name is a table in a FROM, JOIN,
|
&mut self,
|
||||||
/// or similar table clause. Currently, this is used only to support unquoted hyphenated identifiers
|
in_table_clause: bool,
|
||||||
/// in this context on BigQuery.
|
allow_wildcards: bool,
|
||||||
pub fn parse_object_name(&mut self, in_table_clause: bool) -> Result<ObjectName, ParserError> {
|
) -> Result<ObjectName, ParserError> {
|
||||||
let mut idents = vec![];
|
let mut idents = vec![];
|
||||||
|
|
||||||
if dialect_of!(self is BigQueryDialect) && in_table_clause {
|
if dialect_of!(self is BigQueryDialect) && in_table_clause {
|
||||||
|
@ -8891,6 +8956,14 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
loop {
|
loop {
|
||||||
|
let ident = if allow_wildcards && self.peek_token().token == Token::Mul {
|
||||||
|
let span = self.next_token().span;
|
||||||
|
Ident {
|
||||||
|
value: Token::Mul.to_string(),
|
||||||
|
quote_style: None,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if self.dialect.supports_object_name_double_dot_notation()
|
if self.dialect.supports_object_name_double_dot_notation()
|
||||||
&& idents.len() == 1
|
&& idents.len() == 1
|
||||||
&& self.consume_token(&Token::Period)
|
&& self.consume_token(&Token::Period)
|
||||||
|
@ -8898,12 +8971,26 @@ impl<'a> Parser<'a> {
|
||||||
// Empty string here means default schema
|
// Empty string here means default schema
|
||||||
idents.push(Ident::new(""));
|
idents.push(Ident::new(""));
|
||||||
}
|
}
|
||||||
idents.push(self.parse_identifier()?);
|
self.parse_identifier()?
|
||||||
|
};
|
||||||
|
idents.push(ident);
|
||||||
if !self.consume_token(&Token::Period) {
|
if !self.consume_token(&Token::Period) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(ObjectName(idents))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a possibly qualified, possibly quoted identifier, e.g.
|
||||||
|
/// `foo` or `myschema."table"
|
||||||
|
///
|
||||||
|
/// The `in_table_clause` parameter indicates whether the object name is a table in a FROM, JOIN,
|
||||||
|
/// or similar table clause. Currently, this is used only to support unquoted hyphenated identifiers
|
||||||
|
/// in this context on BigQuery.
|
||||||
|
pub fn parse_object_name(&mut self, in_table_clause: bool) -> Result<ObjectName, ParserError> {
|
||||||
|
let ObjectName(mut idents) =
|
||||||
|
self.parse_object_name_with_wildcards(in_table_clause, false)?;
|
||||||
|
|
||||||
// BigQuery accepts any number of quoted identifiers of a table name.
|
// BigQuery accepts any number of quoted identifiers of a table name.
|
||||||
// https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_identifiers
|
// https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_identifiers
|
||||||
|
@ -11674,14 +11761,17 @@ impl<'a> Parser<'a> {
|
||||||
name: None,
|
name: None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut name = self.parse_object_name(false)?;
|
let mut name = self.parse_grantee_name()?;
|
||||||
if self.consume_token(&Token::Colon) {
|
if self.consume_token(&Token::Colon) {
|
||||||
// Redshift supports namespace prefix for extenrnal users and groups:
|
// Redshift supports namespace prefix for extenrnal users and groups:
|
||||||
// <Namespace>:<GroupName> or <Namespace>:<UserName>
|
// <Namespace>:<GroupName> or <Namespace>:<UserName>
|
||||||
// https://docs.aws.amazon.com/redshift/latest/mgmt/redshift-iam-access-control-native-idp.html
|
// https://docs.aws.amazon.com/redshift/latest/mgmt/redshift-iam-access-control-native-idp.html
|
||||||
let ident = self.parse_identifier()?;
|
let ident = self.parse_identifier()?;
|
||||||
if let Some(n) = name.0.first() {
|
if let GranteeName::ObjectName(namespace) = name {
|
||||||
name = ObjectName(vec![Ident::new(format!("{}:{}", n.value, ident.value))]);
|
name = GranteeName::ObjectName(ObjectName(vec![Ident::new(format!(
|
||||||
|
"{}:{}",
|
||||||
|
namespace, ident
|
||||||
|
))]));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Grantee {
|
Grantee {
|
||||||
|
@ -11764,7 +11854,8 @@ impl<'a> Parser<'a> {
|
||||||
} else {
|
} else {
|
||||||
let object_type =
|
let object_type =
|
||||||
self.parse_one_of_keywords(&[Keyword::SEQUENCE, Keyword::SCHEMA, Keyword::TABLE]);
|
self.parse_one_of_keywords(&[Keyword::SEQUENCE, Keyword::SCHEMA, Keyword::TABLE]);
|
||||||
let objects = self.parse_comma_separated(|p| p.parse_object_name(false));
|
let objects =
|
||||||
|
self.parse_comma_separated(|p| p.parse_object_name_with_wildcards(false, true));
|
||||||
match object_type {
|
match object_type {
|
||||||
Some(Keyword::SCHEMA) => GrantObjects::Schemas(objects?),
|
Some(Keyword::SCHEMA) => GrantObjects::Schemas(objects?),
|
||||||
Some(Keyword::SEQUENCE) => GrantObjects::Sequences(objects?),
|
Some(Keyword::SEQUENCE) => GrantObjects::Sequences(objects?),
|
||||||
|
@ -11808,23 +11899,32 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_grantee_name(&mut self) -> Result<GranteeName, ParserError> {
|
||||||
|
let mut name = self.parse_object_name(false)?;
|
||||||
|
if self.dialect.supports_user_host_grantee()
|
||||||
|
&& name.0.len() == 1
|
||||||
|
&& self.consume_token(&Token::AtSign)
|
||||||
|
{
|
||||||
|
let user = name.0.pop().unwrap();
|
||||||
|
let host = self.parse_identifier()?;
|
||||||
|
Ok(GranteeName::UserHost { user, host })
|
||||||
|
} else {
|
||||||
|
Ok(GranteeName::ObjectName(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a REVOKE statement
|
/// Parse a REVOKE statement
|
||||||
pub fn parse_revoke(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_revoke(&mut self) -> Result<Statement, ParserError> {
|
||||||
let (privileges, objects) = self.parse_grant_revoke_privileges_objects()?;
|
let (privileges, objects) = self.parse_grant_revoke_privileges_objects()?;
|
||||||
|
|
||||||
self.expect_keyword_is(Keyword::FROM)?;
|
self.expect_keyword_is(Keyword::FROM)?;
|
||||||
let grantees = self.parse_comma_separated(|p| p.parse_identifier())?;
|
let grantees = self.parse_grantees()?;
|
||||||
|
|
||||||
let granted_by = self
|
let granted_by = self
|
||||||
.parse_keywords(&[Keyword::GRANTED, Keyword::BY])
|
.parse_keywords(&[Keyword::GRANTED, Keyword::BY])
|
||||||
.then(|| self.parse_identifier().unwrap());
|
.then(|| self.parse_identifier().unwrap());
|
||||||
|
|
||||||
let loc = self.peek_token().span.start;
|
let cascade = self.parse_cascade_option();
|
||||||
let cascade = self.parse_keyword(Keyword::CASCADE);
|
|
||||||
let restrict = self.parse_keyword(Keyword::RESTRICT);
|
|
||||||
if cascade && restrict {
|
|
||||||
return parser_err!("Cannot specify both CASCADE and RESTRICT in REVOKE", loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Statement::Revoke {
|
Ok(Statement::Revoke {
|
||||||
privileges,
|
privileges,
|
||||||
|
|
|
@ -1432,6 +1432,18 @@ impl<'a> Tokenizer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(' ') => Ok(Some(Token::AtSign)),
|
Some(' ') => Ok(Some(Token::AtSign)),
|
||||||
|
// We break on quotes here, because no dialect allows identifiers starting
|
||||||
|
// with @ and containing quotation marks (e.g. `@'foo'`) unless they are
|
||||||
|
// quoted, which is tokenized as a quoted string, not here (e.g.
|
||||||
|
// `"@'foo'"`). Further, at least two dialects parse `@` followed by a
|
||||||
|
// quoted string as two separate tokens, which this allows. For example,
|
||||||
|
// Postgres parses `@'1'` as the absolute value of '1' which is implicitly
|
||||||
|
// cast to a numeric type. And when parsing MySQL-style grantees (e.g.
|
||||||
|
// `GRANT ALL ON *.* to 'root'@'localhost'`), we also want separate tokens
|
||||||
|
// for the user, the `@`, and the host.
|
||||||
|
Some('\'') => Ok(Some(Token::AtSign)),
|
||||||
|
Some('\"') => Ok(Some(Token::AtSign)),
|
||||||
|
Some('`') => Ok(Some(Token::AtSign)),
|
||||||
Some(sch) if self.dialect.is_identifier_start('@') => {
|
Some(sch) if self.dialect.is_identifier_start('@') => {
|
||||||
self.tokenize_identifier_or_keyword([ch, *sch], chars)
|
self.tokenize_identifier_or_keyword([ch, *sch], chars)
|
||||||
}
|
}
|
||||||
|
@ -1459,7 +1471,7 @@ impl<'a> Tokenizer<'a> {
|
||||||
}
|
}
|
||||||
'$' => Ok(Some(self.tokenize_dollar_preceded_value(chars)?)),
|
'$' => Ok(Some(self.tokenize_dollar_preceded_value(chars)?)),
|
||||||
|
|
||||||
//whitespace check (including unicode chars) should be last as it covers some of the chars above
|
// whitespace check (including unicode chars) should be last as it covers some of the chars above
|
||||||
ch if ch.is_whitespace() => {
|
ch if ch.is_whitespace() => {
|
||||||
self.consume_and_return(chars, Token::Whitespace(Whitespace::Space))
|
self.consume_and_return(chars, Token::Whitespace(Whitespace::Space))
|
||||||
}
|
}
|
||||||
|
@ -3396,4 +3408,56 @@ mod tests {
|
||||||
let expected = vec![Token::SingleQuotedString("''".to_string())];
|
let expected = vec![Token::SingleQuotedString("''".to_string())];
|
||||||
compare(expected, tokens);
|
compare(expected, tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mysql_users_grantees() {
|
||||||
|
let dialect = MySqlDialect {};
|
||||||
|
|
||||||
|
let sql = "CREATE USER `root`@`%`";
|
||||||
|
let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
|
||||||
|
let expected = vec![
|
||||||
|
Token::make_keyword("CREATE"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("USER"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_word("root", Some('`')),
|
||||||
|
Token::AtSign,
|
||||||
|
Token::make_word("%", Some('`')),
|
||||||
|
];
|
||||||
|
compare(expected, tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_postgres_abs_without_space_and_string_literal() {
|
||||||
|
let dialect = MySqlDialect {};
|
||||||
|
|
||||||
|
let sql = "SELECT @'1'";
|
||||||
|
let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
|
||||||
|
let expected = vec![
|
||||||
|
Token::make_keyword("SELECT"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::AtSign,
|
||||||
|
Token::SingleQuotedString("1".to_string()),
|
||||||
|
];
|
||||||
|
compare(expected, tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_postgres_abs_without_space_and_quoted_column() {
|
||||||
|
let dialect = MySqlDialect {};
|
||||||
|
|
||||||
|
let sql = r#"SELECT @"bar" FROM foo"#;
|
||||||
|
let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap();
|
||||||
|
let expected = vec![
|
||||||
|
Token::make_keyword("SELECT"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::AtSign,
|
||||||
|
Token::DoubleQuotedString("bar".to_string()),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_keyword("FROM"),
|
||||||
|
Token::Whitespace(Whitespace::Space),
|
||||||
|
Token::make_word("foo", None),
|
||||||
|
];
|
||||||
|
compare(expected, tokens);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7192,6 +7192,7 @@ fn parse_create_view() {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("myschema.myview", name.to_string());
|
assert_eq!("myschema.myview", name.to_string());
|
||||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||||
|
@ -7204,7 +7205,8 @@ fn parse_create_view() {
|
||||||
assert!(!late_binding);
|
assert!(!late_binding);
|
||||||
assert!(!if_not_exists);
|
assert!(!if_not_exists);
|
||||||
assert!(!temporary);
|
assert!(!temporary);
|
||||||
assert!(to.is_none())
|
assert!(to.is_none());
|
||||||
|
assert!(params.is_none());
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -7252,6 +7254,7 @@ fn parse_create_view_with_columns() {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("v", name.to_string());
|
assert_eq!("v", name.to_string());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -7274,7 +7277,8 @@ fn parse_create_view_with_columns() {
|
||||||
assert!(!late_binding);
|
assert!(!late_binding);
|
||||||
assert!(!if_not_exists);
|
assert!(!if_not_exists);
|
||||||
assert!(!temporary);
|
assert!(!temporary);
|
||||||
assert!(to.is_none())
|
assert!(to.is_none());
|
||||||
|
assert!(params.is_none());
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -7297,6 +7301,7 @@ fn parse_create_view_temporary() {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("myschema.myview", name.to_string());
|
assert_eq!("myschema.myview", name.to_string());
|
||||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||||
|
@ -7309,7 +7314,8 @@ fn parse_create_view_temporary() {
|
||||||
assert!(!late_binding);
|
assert!(!late_binding);
|
||||||
assert!(!if_not_exists);
|
assert!(!if_not_exists);
|
||||||
assert!(temporary);
|
assert!(temporary);
|
||||||
assert!(to.is_none())
|
assert!(to.is_none());
|
||||||
|
assert!(params.is_none());
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -7332,6 +7338,7 @@ fn parse_create_or_replace_view() {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("v", name.to_string());
|
assert_eq!("v", name.to_string());
|
||||||
assert_eq!(columns, vec![]);
|
assert_eq!(columns, vec![]);
|
||||||
|
@ -7344,7 +7351,8 @@ fn parse_create_or_replace_view() {
|
||||||
assert!(!late_binding);
|
assert!(!late_binding);
|
||||||
assert!(!if_not_exists);
|
assert!(!if_not_exists);
|
||||||
assert!(!temporary);
|
assert!(!temporary);
|
||||||
assert!(to.is_none())
|
assert!(to.is_none());
|
||||||
|
assert!(params.is_none());
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -7371,6 +7379,7 @@ fn parse_create_or_replace_materialized_view() {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("v", name.to_string());
|
assert_eq!("v", name.to_string());
|
||||||
assert_eq!(columns, vec![]);
|
assert_eq!(columns, vec![]);
|
||||||
|
@ -7383,7 +7392,8 @@ fn parse_create_or_replace_materialized_view() {
|
||||||
assert!(!late_binding);
|
assert!(!late_binding);
|
||||||
assert!(!if_not_exists);
|
assert!(!if_not_exists);
|
||||||
assert!(!temporary);
|
assert!(!temporary);
|
||||||
assert!(to.is_none())
|
assert!(to.is_none());
|
||||||
|
assert!(params.is_none());
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -7406,6 +7416,7 @@ fn parse_create_materialized_view() {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("myschema.myview", name.to_string());
|
assert_eq!("myschema.myview", name.to_string());
|
||||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||||
|
@ -7418,7 +7429,8 @@ fn parse_create_materialized_view() {
|
||||||
assert!(!late_binding);
|
assert!(!late_binding);
|
||||||
assert!(!if_not_exists);
|
assert!(!if_not_exists);
|
||||||
assert!(!temporary);
|
assert!(!temporary);
|
||||||
assert!(to.is_none())
|
assert!(to.is_none());
|
||||||
|
assert!(params.is_none());
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -7441,6 +7453,7 @@ fn parse_create_materialized_view_with_cluster_by() {
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
temporary,
|
temporary,
|
||||||
to,
|
to,
|
||||||
|
params,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("myschema.myview", name.to_string());
|
assert_eq!("myschema.myview", name.to_string());
|
||||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||||
|
@ -7453,7 +7466,8 @@ fn parse_create_materialized_view_with_cluster_by() {
|
||||||
assert!(!late_binding);
|
assert!(!late_binding);
|
||||||
assert!(!if_not_exists);
|
assert!(!if_not_exists);
|
||||||
assert!(!temporary);
|
assert!(!temporary);
|
||||||
assert!(to.is_none())
|
assert!(to.is_none());
|
||||||
|
assert!(params.is_none());
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -8574,14 +8588,14 @@ fn parse_grant() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_revoke() {
|
fn test_revoke() {
|
||||||
let sql = "REVOKE ALL PRIVILEGES ON users, auth FROM analyst CASCADE";
|
let sql = "REVOKE ALL PRIVILEGES ON users, auth FROM analyst";
|
||||||
match verified_stmt(sql) {
|
match verified_stmt(sql) {
|
||||||
Statement::Revoke {
|
Statement::Revoke {
|
||||||
privileges,
|
privileges,
|
||||||
objects: GrantObjects::Tables(tables),
|
objects: GrantObjects::Tables(tables),
|
||||||
grantees,
|
grantees,
|
||||||
cascade,
|
|
||||||
granted_by,
|
granted_by,
|
||||||
|
cascade,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Privileges::All {
|
Privileges::All {
|
||||||
|
@ -8591,7 +8605,33 @@ fn test_revoke() {
|
||||||
);
|
);
|
||||||
assert_eq_vec(&["users", "auth"], &tables);
|
assert_eq_vec(&["users", "auth"], &tables);
|
||||||
assert_eq_vec(&["analyst"], &grantees);
|
assert_eq_vec(&["analyst"], &grantees);
|
||||||
assert!(cascade);
|
assert_eq!(cascade, None);
|
||||||
|
assert_eq!(None, granted_by);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_revoke_with_cascade() {
|
||||||
|
let sql = "REVOKE ALL PRIVILEGES ON users, auth FROM analyst CASCADE";
|
||||||
|
match all_dialects_except(|d| d.is::<MySqlDialect>()).verified_stmt(sql) {
|
||||||
|
Statement::Revoke {
|
||||||
|
privileges,
|
||||||
|
objects: GrantObjects::Tables(tables),
|
||||||
|
grantees,
|
||||||
|
granted_by,
|
||||||
|
cascade,
|
||||||
|
} => {
|
||||||
|
assert_eq!(
|
||||||
|
Privileges::All {
|
||||||
|
with_privileges_keyword: true
|
||||||
|
},
|
||||||
|
privileges
|
||||||
|
);
|
||||||
|
assert_eq_vec(&["users", "auth"], &tables);
|
||||||
|
assert_eq_vec(&["analyst"], &grantees);
|
||||||
|
assert_eq!(cascade, Some(CascadeOption::Cascade));
|
||||||
assert_eq!(None, granted_by);
|
assert_eq!(None, granted_by);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
|
|
@ -33,6 +33,14 @@ use test_utils::*;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod test_utils;
|
mod test_utils;
|
||||||
|
|
||||||
|
fn mysql() -> TestedDialects {
|
||||||
|
TestedDialects::new(vec![Box::new(MySqlDialect {})])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mysql_and_generic() -> TestedDialects {
|
||||||
|
TestedDialects::new(vec![Box::new(MySqlDialect {}), Box::new(GenericDialect {})])
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_identifiers() {
|
fn parse_identifiers() {
|
||||||
mysql().verified_stmt("SELECT $a$, àà");
|
mysql().verified_stmt("SELECT $a$, àà");
|
||||||
|
@ -2732,14 +2740,6 @@ fn parse_create_table_with_fulltext_definition_should_not_accept_constraint_name
|
||||||
mysql_and_generic().verified_stmt("CREATE TABLE tb (c1 INT, CONSTRAINT cons FULLTEXT (c1))");
|
mysql_and_generic().verified_stmt("CREATE TABLE tb (c1 INT, CONSTRAINT cons FULLTEXT (c1))");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mysql() -> TestedDialects {
|
|
||||||
TestedDialects::new(vec![Box::new(MySqlDialect {})])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mysql_and_generic() -> TestedDialects {
|
|
||||||
TestedDialects::new(vec![Box::new(MySqlDialect {}), Box::new(GenericDialect {})])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_values() {
|
fn parse_values() {
|
||||||
mysql().verified_stmt("VALUES ROW(1, true, 'a')");
|
mysql().verified_stmt("VALUES ROW(1, true, 'a')");
|
||||||
|
@ -3001,6 +3001,193 @@ fn parse_bitstring_literal() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_grant() {
|
||||||
|
let sql = "GRANT ALL ON *.* TO 'jeffrey'@'%'";
|
||||||
|
let stmt = mysql().verified_stmt(sql);
|
||||||
|
if let Statement::Grant {
|
||||||
|
privileges,
|
||||||
|
objects,
|
||||||
|
grantees,
|
||||||
|
with_grant_option,
|
||||||
|
granted_by,
|
||||||
|
} = stmt
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
privileges,
|
||||||
|
Privileges::All {
|
||||||
|
with_privileges_keyword: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
objects,
|
||||||
|
GrantObjects::Tables(vec![ObjectName(vec!["*".into(), "*".into()])])
|
||||||
|
);
|
||||||
|
assert!(!with_grant_option);
|
||||||
|
assert!(granted_by.is_none());
|
||||||
|
if let [Grantee {
|
||||||
|
grantee_type: GranteesType::None,
|
||||||
|
name: Some(GranteeName::UserHost { user, host }),
|
||||||
|
}] = grantees.as_slice()
|
||||||
|
{
|
||||||
|
assert_eq!(user.value, "jeffrey");
|
||||||
|
assert_eq!(user.quote_style, Some('\''));
|
||||||
|
assert_eq!(host.value, "%");
|
||||||
|
assert_eq!(host.quote_style, Some('\''));
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_revoke() {
|
||||||
|
let sql = "REVOKE ALL ON db1.* FROM 'jeffrey'@'%'";
|
||||||
|
let stmt = mysql_and_generic().verified_stmt(sql);
|
||||||
|
if let Statement::Revoke {
|
||||||
|
privileges,
|
||||||
|
objects,
|
||||||
|
grantees,
|
||||||
|
granted_by,
|
||||||
|
cascade,
|
||||||
|
} = stmt
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
privileges,
|
||||||
|
Privileges::All {
|
||||||
|
with_privileges_keyword: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
objects,
|
||||||
|
GrantObjects::Tables(vec![ObjectName(vec!["db1".into(), "*".into()])])
|
||||||
|
);
|
||||||
|
if let [Grantee {
|
||||||
|
grantee_type: GranteesType::None,
|
||||||
|
name: Some(GranteeName::UserHost { user, host }),
|
||||||
|
}] = grantees.as_slice()
|
||||||
|
{
|
||||||
|
assert_eq!(user.value, "jeffrey");
|
||||||
|
assert_eq!(user.quote_style, Some('\''));
|
||||||
|
assert_eq!(host.value, "%");
|
||||||
|
assert_eq!(host.quote_style, Some('\''));
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
assert!(granted_by.is_none());
|
||||||
|
assert!(cascade.is_none());
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_view_algorithm_param() {
|
||||||
|
let sql = "CREATE ALGORITHM = MERGE VIEW foo AS SELECT 1";
|
||||||
|
let stmt = mysql().verified_stmt(sql);
|
||||||
|
if let Statement::CreateView {
|
||||||
|
params:
|
||||||
|
Some(CreateViewParams {
|
||||||
|
algorithm,
|
||||||
|
definer,
|
||||||
|
security,
|
||||||
|
}),
|
||||||
|
..
|
||||||
|
} = stmt
|
||||||
|
{
|
||||||
|
assert_eq!(algorithm, Some(CreateViewAlgorithm::Merge));
|
||||||
|
assert!(definer.is_none());
|
||||||
|
assert!(security.is_none());
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
mysql().verified_stmt("CREATE ALGORITHM = UNDEFINED VIEW foo AS SELECT 1");
|
||||||
|
mysql().verified_stmt("CREATE ALGORITHM = TEMPTABLE VIEW foo AS SELECT 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_view_definer_param() {
|
||||||
|
let sql = "CREATE DEFINER = 'jeffrey'@'localhost' VIEW foo AS SELECT 1";
|
||||||
|
let stmt = mysql().verified_stmt(sql);
|
||||||
|
if let Statement::CreateView {
|
||||||
|
params:
|
||||||
|
Some(CreateViewParams {
|
||||||
|
algorithm,
|
||||||
|
definer,
|
||||||
|
security,
|
||||||
|
}),
|
||||||
|
..
|
||||||
|
} = stmt
|
||||||
|
{
|
||||||
|
assert!(algorithm.is_none());
|
||||||
|
if let Some(GranteeName::UserHost { user, host }) = definer {
|
||||||
|
assert_eq!(user.value, "jeffrey");
|
||||||
|
assert_eq!(user.quote_style, Some('\''));
|
||||||
|
assert_eq!(host.value, "localhost");
|
||||||
|
assert_eq!(host.quote_style, Some('\''));
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
assert!(security.is_none());
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_view_security_param() {
|
||||||
|
let sql = "CREATE SQL SECURITY DEFINER VIEW foo AS SELECT 1";
|
||||||
|
let stmt = mysql().verified_stmt(sql);
|
||||||
|
if let Statement::CreateView {
|
||||||
|
params:
|
||||||
|
Some(CreateViewParams {
|
||||||
|
algorithm,
|
||||||
|
definer,
|
||||||
|
security,
|
||||||
|
}),
|
||||||
|
..
|
||||||
|
} = stmt
|
||||||
|
{
|
||||||
|
assert!(algorithm.is_none());
|
||||||
|
assert!(definer.is_none());
|
||||||
|
assert_eq!(security, Some(CreateViewSecurity::Definer));
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
mysql().verified_stmt("CREATE SQL SECURITY INVOKER VIEW foo AS SELECT 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_view_multiple_params() {
|
||||||
|
let sql = "CREATE ALGORITHM = UNDEFINED DEFINER = `root`@`%` SQL SECURITY INVOKER VIEW foo AS SELECT 1";
|
||||||
|
let stmt = mysql().verified_stmt(sql);
|
||||||
|
if let Statement::CreateView {
|
||||||
|
params:
|
||||||
|
Some(CreateViewParams {
|
||||||
|
algorithm,
|
||||||
|
definer,
|
||||||
|
security,
|
||||||
|
}),
|
||||||
|
..
|
||||||
|
} = stmt
|
||||||
|
{
|
||||||
|
assert_eq!(algorithm, Some(CreateViewAlgorithm::Undefined));
|
||||||
|
if let Some(GranteeName::UserHost { user, host }) = definer {
|
||||||
|
assert_eq!(user.value, "root");
|
||||||
|
assert_eq!(user.quote_style, Some('`'));
|
||||||
|
assert_eq!(host.value, "%");
|
||||||
|
assert_eq!(host.quote_style, Some('`'));
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
assert_eq!(security, Some(CreateViewSecurity::Invoker));
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_longblob_type() {
|
fn parse_longblob_type() {
|
||||||
let sql = "CREATE TABLE foo (bar LONGBLOB)";
|
let sql = "CREATE TABLE foo (bar LONGBLOB)";
|
||||||
|
|
|
@ -4171,7 +4171,7 @@ fn parse_truncate_with_options() {
|
||||||
table: true,
|
table: true,
|
||||||
only: true,
|
only: true,
|
||||||
identity: Some(TruncateIdentityOption::Restart),
|
identity: Some(TruncateIdentityOption::Restart),
|
||||||
cascade: Some(TruncateCascadeOption::Cascade),
|
cascade: Some(CascadeOption::Cascade),
|
||||||
on_cluster: None,
|
on_cluster: None,
|
||||||
},
|
},
|
||||||
truncate
|
truncate
|
||||||
|
@ -4203,7 +4203,7 @@ fn parse_truncate_with_table_list() {
|
||||||
table: true,
|
table: true,
|
||||||
only: false,
|
only: false,
|
||||||
identity: Some(TruncateIdentityOption::Restart),
|
identity: Some(TruncateIdentityOption::Restart),
|
||||||
cascade: Some(TruncateCascadeOption::Cascade),
|
cascade: Some(CascadeOption::Cascade),
|
||||||
on_cluster: None,
|
on_cluster: None,
|
||||||
},
|
},
|
||||||
truncate
|
truncate
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue