mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-22 06:54:07 +00:00
Add support for DENY
statements (#1836)
This commit is contained in:
parent
178a351812
commit
74a95fdbd1
9 changed files with 185 additions and 15 deletions
|
@ -3903,9 +3903,14 @@ pub enum Statement {
|
||||||
objects: Option<GrantObjects>,
|
objects: Option<GrantObjects>,
|
||||||
grantees: Vec<Grantee>,
|
grantees: Vec<Grantee>,
|
||||||
with_grant_option: bool,
|
with_grant_option: bool,
|
||||||
|
as_grantor: Option<Ident>,
|
||||||
granted_by: Option<Ident>,
|
granted_by: Option<Ident>,
|
||||||
},
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
|
/// DENY privileges ON object TO grantees
|
||||||
|
/// ```
|
||||||
|
Deny(DenyStatement),
|
||||||
|
/// ```sql
|
||||||
/// REVOKE privileges ON objects FROM grantees
|
/// REVOKE privileges ON objects FROM grantees
|
||||||
/// ```
|
/// ```
|
||||||
Revoke {
|
Revoke {
|
||||||
|
@ -5580,6 +5585,7 @@ impl fmt::Display for Statement {
|
||||||
objects,
|
objects,
|
||||||
grantees,
|
grantees,
|
||||||
with_grant_option,
|
with_grant_option,
|
||||||
|
as_grantor,
|
||||||
granted_by,
|
granted_by,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "GRANT {privileges} ")?;
|
write!(f, "GRANT {privileges} ")?;
|
||||||
|
@ -5590,11 +5596,15 @@ impl fmt::Display for Statement {
|
||||||
if *with_grant_option {
|
if *with_grant_option {
|
||||||
write!(f, " WITH GRANT OPTION")?;
|
write!(f, " WITH GRANT OPTION")?;
|
||||||
}
|
}
|
||||||
|
if let Some(grantor) = as_grantor {
|
||||||
|
write!(f, " AS {grantor}")?;
|
||||||
|
}
|
||||||
if let Some(grantor) = granted_by {
|
if let Some(grantor) = granted_by {
|
||||||
write!(f, " GRANTED BY {grantor}")?;
|
write!(f, " GRANTED BY {grantor}")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Statement::Deny(s) => write!(f, "{s}"),
|
||||||
Statement::Revoke {
|
Statement::Revoke {
|
||||||
privileges,
|
privileges,
|
||||||
objects,
|
objects,
|
||||||
|
@ -6380,6 +6390,9 @@ pub enum Action {
|
||||||
},
|
},
|
||||||
Delete,
|
Delete,
|
||||||
EvolveSchema,
|
EvolveSchema,
|
||||||
|
Exec {
|
||||||
|
obj_type: Option<ActionExecuteObjectType>,
|
||||||
|
},
|
||||||
Execute {
|
Execute {
|
||||||
obj_type: Option<ActionExecuteObjectType>,
|
obj_type: Option<ActionExecuteObjectType>,
|
||||||
},
|
},
|
||||||
|
@ -6446,6 +6459,12 @@ impl fmt::Display for Action {
|
||||||
Action::DatabaseRole { role } => write!(f, "DATABASE ROLE {role}")?,
|
Action::DatabaseRole { role } => write!(f, "DATABASE ROLE {role}")?,
|
||||||
Action::Delete => f.write_str("DELETE")?,
|
Action::Delete => f.write_str("DELETE")?,
|
||||||
Action::EvolveSchema => f.write_str("EVOLVE SCHEMA")?,
|
Action::EvolveSchema => f.write_str("EVOLVE SCHEMA")?,
|
||||||
|
Action::Exec { obj_type } => {
|
||||||
|
f.write_str("EXEC")?;
|
||||||
|
if let Some(obj_type) = obj_type {
|
||||||
|
write!(f, " {obj_type}")?
|
||||||
|
}
|
||||||
|
}
|
||||||
Action::Execute { obj_type } => {
|
Action::Execute { obj_type } => {
|
||||||
f.write_str("EXECUTE")?;
|
f.write_str("EXECUTE")?;
|
||||||
if let Some(obj_type) = obj_type {
|
if let Some(obj_type) = obj_type {
|
||||||
|
@ -6867,6 +6886,37 @@ impl fmt::Display for GrantObjects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `DENY` statement
|
||||||
|
///
|
||||||
|
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/statements/deny-transact-sql)
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct DenyStatement {
|
||||||
|
pub privileges: Privileges,
|
||||||
|
pub objects: GrantObjects,
|
||||||
|
pub grantees: Vec<Grantee>,
|
||||||
|
pub granted_by: Option<Ident>,
|
||||||
|
pub cascade: Option<CascadeOption>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DenyStatement {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "DENY {}", self.privileges)?;
|
||||||
|
write!(f, " ON {}", self.objects)?;
|
||||||
|
if !self.grantees.is_empty() {
|
||||||
|
write!(f, " TO {}", display_comma_separated(&self.grantees))?;
|
||||||
|
}
|
||||||
|
if let Some(cascade) = &self.cascade {
|
||||||
|
write!(f, " {cascade}")?;
|
||||||
|
}
|
||||||
|
if let Some(granted_by) = &self.granted_by {
|
||||||
|
write!(f, " AS {granted_by}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// SQL assignment `foo = expr` as used in SQLUpdate
|
/// SQL assignment `foo = expr` as used in SQLUpdate
|
||||||
#[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))]
|
||||||
|
|
|
@ -491,6 +491,7 @@ impl Spanned for Statement {
|
||||||
Statement::CreateStage { .. } => Span::empty(),
|
Statement::CreateStage { .. } => Span::empty(),
|
||||||
Statement::Assert { .. } => Span::empty(),
|
Statement::Assert { .. } => Span::empty(),
|
||||||
Statement::Grant { .. } => Span::empty(),
|
Statement::Grant { .. } => Span::empty(),
|
||||||
|
Statement::Deny { .. } => Span::empty(),
|
||||||
Statement::Revoke { .. } => Span::empty(),
|
Statement::Revoke { .. } => Span::empty(),
|
||||||
Statement::Deallocate { .. } => Span::empty(),
|
Statement::Deallocate { .. } => Span::empty(),
|
||||||
Statement::Execute { .. } => Span::empty(),
|
Statement::Execute { .. } => Span::empty(),
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub use self::postgresql::PostgreSqlDialect;
|
||||||
pub use self::redshift::RedshiftSqlDialect;
|
pub use self::redshift::RedshiftSqlDialect;
|
||||||
pub use self::snowflake::SnowflakeDialect;
|
pub use self::snowflake::SnowflakeDialect;
|
||||||
pub use self::sqlite::SQLiteDialect;
|
pub use self::sqlite::SQLiteDialect;
|
||||||
use crate::ast::{ColumnOption, Expr, Statement};
|
use crate::ast::{ColumnOption, Expr, GranteesType, Statement};
|
||||||
pub use crate::keywords;
|
pub use crate::keywords;
|
||||||
use crate::keywords::Keyword;
|
use crate::keywords::Keyword;
|
||||||
use crate::parser::{Parser, ParserError};
|
use crate::parser::{Parser, ParserError};
|
||||||
|
@ -910,6 +910,11 @@ pub trait Dialect: Debug + Any {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns grantee types that should be treated as identifiers
|
||||||
|
fn get_reserved_grantees_types(&self) -> &[GranteesType] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if this dialect supports the `TABLESAMPLE` option
|
/// Returns true if this dialect supports the `TABLESAMPLE` option
|
||||||
/// before the table alias option. For example:
|
/// before the table alias option. For example:
|
||||||
///
|
///
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
use crate::ast::helpers::attached_token::AttachedToken;
|
use crate::ast::helpers::attached_token::AttachedToken;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, IfStatement, Statement,
|
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, GranteesType,
|
||||||
TriggerObject,
|
IfStatement, Statement, TriggerObject,
|
||||||
};
|
};
|
||||||
use crate::dialect::Dialect;
|
use crate::dialect::Dialect;
|
||||||
use crate::keywords::{self, Keyword};
|
use crate::keywords::{self, Keyword};
|
||||||
|
@ -52,6 +52,10 @@ impl Dialect for MsSqlDialect {
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
|
||||||
|
Some('[')
|
||||||
|
}
|
||||||
|
|
||||||
/// SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)`
|
/// SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)`
|
||||||
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16>
|
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16>
|
||||||
fn convert_type_before_value(&self) -> bool {
|
fn convert_type_before_value(&self) -> bool {
|
||||||
|
@ -119,6 +123,11 @@ impl Dialect for MsSqlDialect {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See <https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/server-level-roles>
|
||||||
|
fn get_reserved_grantees_types(&self) -> &[GranteesType] {
|
||||||
|
&[GranteesType::Public]
|
||||||
|
}
|
||||||
|
|
||||||
fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
|
fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
|
||||||
!keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
|
!keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,6 +278,7 @@ define_keywords!(
|
||||||
DELIMITER,
|
DELIMITER,
|
||||||
DELTA,
|
DELTA,
|
||||||
DENSE_RANK,
|
DENSE_RANK,
|
||||||
|
DENY,
|
||||||
DEREF,
|
DEREF,
|
||||||
DESC,
|
DESC,
|
||||||
DESCRIBE,
|
DESCRIBE,
|
||||||
|
|
|
@ -583,6 +583,10 @@ impl<'a> Parser<'a> {
|
||||||
Keyword::SHOW => self.parse_show(),
|
Keyword::SHOW => self.parse_show(),
|
||||||
Keyword::USE => self.parse_use(),
|
Keyword::USE => self.parse_use(),
|
||||||
Keyword::GRANT => self.parse_grant(),
|
Keyword::GRANT => self.parse_grant(),
|
||||||
|
Keyword::DENY => {
|
||||||
|
self.prev_token();
|
||||||
|
self.parse_deny()
|
||||||
|
}
|
||||||
Keyword::REVOKE => self.parse_revoke(),
|
Keyword::REVOKE => self.parse_revoke(),
|
||||||
Keyword::START => self.parse_start_transaction(),
|
Keyword::START => self.parse_start_transaction(),
|
||||||
Keyword::BEGIN => self.parse_begin(),
|
Keyword::BEGIN => self.parse_begin(),
|
||||||
|
@ -13381,7 +13385,7 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
/// Parse a GRANT statement.
|
/// Parse a GRANT statement.
|
||||||
pub fn parse_grant(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_grant(&mut self) -> Result<Statement, ParserError> {
|
||||||
let (privileges, objects) = self.parse_grant_revoke_privileges_objects()?;
|
let (privileges, objects) = self.parse_grant_deny_revoke_privileges_objects()?;
|
||||||
|
|
||||||
self.expect_keyword_is(Keyword::TO)?;
|
self.expect_keyword_is(Keyword::TO)?;
|
||||||
let grantees = self.parse_grantees()?;
|
let grantees = self.parse_grantees()?;
|
||||||
|
@ -13389,15 +13393,24 @@ impl<'a> Parser<'a> {
|
||||||
let with_grant_option =
|
let with_grant_option =
|
||||||
self.parse_keywords(&[Keyword::WITH, Keyword::GRANT, Keyword::OPTION]);
|
self.parse_keywords(&[Keyword::WITH, Keyword::GRANT, Keyword::OPTION]);
|
||||||
|
|
||||||
let granted_by = self
|
let as_grantor = if self.parse_keywords(&[Keyword::AS]) {
|
||||||
.parse_keywords(&[Keyword::GRANTED, Keyword::BY])
|
Some(self.parse_identifier()?)
|
||||||
.then(|| self.parse_identifier().unwrap());
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let granted_by = if self.parse_keywords(&[Keyword::GRANTED, Keyword::BY]) {
|
||||||
|
Some(self.parse_identifier()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Statement::Grant {
|
Ok(Statement::Grant {
|
||||||
privileges,
|
privileges,
|
||||||
objects,
|
objects,
|
||||||
grantees,
|
grantees,
|
||||||
with_grant_option,
|
with_grant_option,
|
||||||
|
as_grantor,
|
||||||
granted_by,
|
granted_by,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -13406,7 +13419,7 @@ impl<'a> Parser<'a> {
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
let mut grantee_type = GranteesType::None;
|
let mut grantee_type = GranteesType::None;
|
||||||
loop {
|
loop {
|
||||||
grantee_type = if self.parse_keyword(Keyword::ROLE) {
|
let new_grantee_type = if self.parse_keyword(Keyword::ROLE) {
|
||||||
GranteesType::Role
|
GranteesType::Role
|
||||||
} else if self.parse_keyword(Keyword::USER) {
|
} else if self.parse_keyword(Keyword::USER) {
|
||||||
GranteesType::User
|
GranteesType::User
|
||||||
|
@ -13423,9 +13436,19 @@ impl<'a> Parser<'a> {
|
||||||
} else if self.parse_keyword(Keyword::APPLICATION) {
|
} else if self.parse_keyword(Keyword::APPLICATION) {
|
||||||
GranteesType::Application
|
GranteesType::Application
|
||||||
} else {
|
} else {
|
||||||
grantee_type // keep from previous iteraton, if not specified
|
grantee_type.clone() // keep from previous iteraton, if not specified
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if self
|
||||||
|
.dialect
|
||||||
|
.get_reserved_grantees_types()
|
||||||
|
.contains(&new_grantee_type)
|
||||||
|
{
|
||||||
|
self.prev_token();
|
||||||
|
} else {
|
||||||
|
grantee_type = new_grantee_type;
|
||||||
|
}
|
||||||
|
|
||||||
let grantee = if grantee_type == GranteesType::Public {
|
let grantee = if grantee_type == GranteesType::Public {
|
||||||
Grantee {
|
Grantee {
|
||||||
grantee_type: grantee_type.clone(),
|
grantee_type: grantee_type.clone(),
|
||||||
|
@ -13460,7 +13483,7 @@ impl<'a> Parser<'a> {
|
||||||
Ok(values)
|
Ok(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_grant_revoke_privileges_objects(
|
pub fn parse_grant_deny_revoke_privileges_objects(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<(Privileges, Option<GrantObjects>), ParserError> {
|
) -> Result<(Privileges, Option<GrantObjects>), ParserError> {
|
||||||
let privileges = if self.parse_keyword(Keyword::ALL) {
|
let privileges = if self.parse_keyword(Keyword::ALL) {
|
||||||
|
@ -13510,7 +13533,6 @@ impl<'a> Parser<'a> {
|
||||||
let object_type = self.parse_one_of_keywords(&[
|
let object_type = self.parse_one_of_keywords(&[
|
||||||
Keyword::SEQUENCE,
|
Keyword::SEQUENCE,
|
||||||
Keyword::DATABASE,
|
Keyword::DATABASE,
|
||||||
Keyword::DATABASE,
|
|
||||||
Keyword::SCHEMA,
|
Keyword::SCHEMA,
|
||||||
Keyword::TABLE,
|
Keyword::TABLE,
|
||||||
Keyword::VIEW,
|
Keyword::VIEW,
|
||||||
|
@ -13605,6 +13627,9 @@ impl<'a> Parser<'a> {
|
||||||
Ok(Action::Create { obj_type })
|
Ok(Action::Create { obj_type })
|
||||||
} else if self.parse_keyword(Keyword::DELETE) {
|
} else if self.parse_keyword(Keyword::DELETE) {
|
||||||
Ok(Action::Delete)
|
Ok(Action::Delete)
|
||||||
|
} else if self.parse_keyword(Keyword::EXEC) {
|
||||||
|
let obj_type = self.maybe_parse_action_execute_obj_type();
|
||||||
|
Ok(Action::Exec { obj_type })
|
||||||
} else if self.parse_keyword(Keyword::EXECUTE) {
|
} else if self.parse_keyword(Keyword::EXECUTE) {
|
||||||
let obj_type = self.maybe_parse_action_execute_obj_type();
|
let obj_type = self.maybe_parse_action_execute_obj_type();
|
||||||
Ok(Action::Execute { obj_type })
|
Ok(Action::Execute { obj_type })
|
||||||
|
@ -13803,16 +13828,51 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse [`Statement::Deny`]
|
||||||
|
pub fn parse_deny(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
self.expect_keyword(Keyword::DENY)?;
|
||||||
|
|
||||||
|
let (privileges, objects) = self.parse_grant_deny_revoke_privileges_objects()?;
|
||||||
|
let objects = match objects {
|
||||||
|
Some(o) => o,
|
||||||
|
None => {
|
||||||
|
return parser_err!(
|
||||||
|
"DENY statements must specify an object",
|
||||||
|
self.peek_token().span.start
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.expect_keyword_is(Keyword::TO)?;
|
||||||
|
let grantees = self.parse_grantees()?;
|
||||||
|
let cascade = self.parse_cascade_option();
|
||||||
|
let granted_by = if self.parse_keywords(&[Keyword::AS]) {
|
||||||
|
Some(self.parse_identifier()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Statement::Deny(DenyStatement {
|
||||||
|
privileges,
|
||||||
|
objects,
|
||||||
|
grantees,
|
||||||
|
cascade,
|
||||||
|
granted_by,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
/// 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_deny_revoke_privileges_objects()?;
|
||||||
|
|
||||||
self.expect_keyword_is(Keyword::FROM)?;
|
self.expect_keyword_is(Keyword::FROM)?;
|
||||||
let grantees = self.parse_grantees()?;
|
let grantees = self.parse_grantees()?;
|
||||||
|
|
||||||
let granted_by = self
|
let granted_by = if self.parse_keywords(&[Keyword::GRANTED, Keyword::BY]) {
|
||||||
.parse_keywords(&[Keyword::GRANTED, Keyword::BY])
|
Some(self.parse_identifier()?)
|
||||||
.then(|| self.parse_identifier().unwrap());
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let cascade = self.parse_cascade_option();
|
let cascade = self.parse_cascade_option();
|
||||||
|
|
||||||
|
|
|
@ -9331,6 +9331,39 @@ fn parse_grant() {
|
||||||
verified_stmt("GRANT USAGE ON WAREHOUSE wh1 TO ROLE role1");
|
verified_stmt("GRANT USAGE ON WAREHOUSE wh1 TO ROLE role1");
|
||||||
verified_stmt("GRANT OWNERSHIP ON INTEGRATION int1 TO ROLE role1");
|
verified_stmt("GRANT OWNERSHIP ON INTEGRATION int1 TO ROLE role1");
|
||||||
verified_stmt("GRANT SELECT ON VIEW view1 TO ROLE role1");
|
verified_stmt("GRANT SELECT ON VIEW view1 TO ROLE role1");
|
||||||
|
verified_stmt("GRANT EXEC ON my_sp TO runner");
|
||||||
|
verified_stmt("GRANT UPDATE ON my_table TO updater_role AS dbo");
|
||||||
|
|
||||||
|
all_dialects_where(|d| d.identifier_quote_style("none") == Some('['))
|
||||||
|
.verified_stmt("GRANT SELECT ON [my_table] TO [public]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_deny() {
|
||||||
|
let sql = "DENY INSERT, DELETE ON users TO analyst CASCADE AS admin";
|
||||||
|
match verified_stmt(sql) {
|
||||||
|
Statement::Deny(deny) => {
|
||||||
|
assert_eq!(
|
||||||
|
Privileges::Actions(vec![Action::Insert { columns: None }, Action::Delete]),
|
||||||
|
deny.privileges
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&GrantObjects::Tables(vec![ObjectName::from(vec![Ident::new("users")])]),
|
||||||
|
&deny.objects
|
||||||
|
);
|
||||||
|
assert_eq_vec(&["analyst"], &deny.grantees);
|
||||||
|
assert_eq!(Some(CascadeOption::Cascade), deny.cascade);
|
||||||
|
assert_eq!(Some(Ident::from("admin")), deny.granted_by);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
verified_stmt("DENY SELECT, INSERT, UPDATE, DELETE ON db1.sc1 TO role1, role2");
|
||||||
|
verified_stmt("DENY ALL ON db1.sc1 TO role1");
|
||||||
|
verified_stmt("DENY EXEC ON my_sp TO runner");
|
||||||
|
|
||||||
|
all_dialects_where(|d| d.identifier_quote_style("none") == Some('['))
|
||||||
|
.verified_stmt("DENY SELECT ON [my_table] TO [public]");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2340,3 +2340,13 @@ fn parse_print() {
|
||||||
let _ = ms().verified_stmt("PRINT N'Hello, ⛄️!'");
|
let _ = ms().verified_stmt("PRINT N'Hello, ⛄️!'");
|
||||||
let _ = ms().verified_stmt("PRINT @my_variable");
|
let _ = ms().verified_stmt("PRINT @my_variable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_mssql_grant() {
|
||||||
|
ms().verified_stmt("GRANT SELECT ON my_table TO public, db_admin");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_mssql_deny() {
|
||||||
|
ms().verified_stmt("DENY SELECT ON my_table TO public, db_admin");
|
||||||
|
}
|
||||||
|
|
|
@ -3538,6 +3538,7 @@ fn parse_grant() {
|
||||||
objects,
|
objects,
|
||||||
grantees,
|
grantees,
|
||||||
with_grant_option,
|
with_grant_option,
|
||||||
|
as_grantor: _,
|
||||||
granted_by,
|
granted_by,
|
||||||
} = stmt
|
} = stmt
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue