mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +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>,
|
||||
grantees: Vec<Grantee>,
|
||||
with_grant_option: bool,
|
||||
as_grantor: Option<Ident>,
|
||||
granted_by: Option<Ident>,
|
||||
},
|
||||
/// ```sql
|
||||
/// DENY privileges ON object TO grantees
|
||||
/// ```
|
||||
Deny(DenyStatement),
|
||||
/// ```sql
|
||||
/// REVOKE privileges ON objects FROM grantees
|
||||
/// ```
|
||||
Revoke {
|
||||
|
@ -5580,6 +5585,7 @@ impl fmt::Display for Statement {
|
|||
objects,
|
||||
grantees,
|
||||
with_grant_option,
|
||||
as_grantor,
|
||||
granted_by,
|
||||
} => {
|
||||
write!(f, "GRANT {privileges} ")?;
|
||||
|
@ -5590,11 +5596,15 @@ impl fmt::Display for Statement {
|
|||
if *with_grant_option {
|
||||
write!(f, " WITH GRANT OPTION")?;
|
||||
}
|
||||
if let Some(grantor) = as_grantor {
|
||||
write!(f, " AS {grantor}")?;
|
||||
}
|
||||
if let Some(grantor) = granted_by {
|
||||
write!(f, " GRANTED BY {grantor}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Statement::Deny(s) => write!(f, "{s}"),
|
||||
Statement::Revoke {
|
||||
privileges,
|
||||
objects,
|
||||
|
@ -6380,6 +6390,9 @@ pub enum Action {
|
|||
},
|
||||
Delete,
|
||||
EvolveSchema,
|
||||
Exec {
|
||||
obj_type: Option<ActionExecuteObjectType>,
|
||||
},
|
||||
Execute {
|
||||
obj_type: Option<ActionExecuteObjectType>,
|
||||
},
|
||||
|
@ -6446,6 +6459,12 @@ impl fmt::Display for Action {
|
|||
Action::DatabaseRole { role } => write!(f, "DATABASE ROLE {role}")?,
|
||||
Action::Delete => f.write_str("DELETE")?,
|
||||
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 } => {
|
||||
f.write_str("EXECUTE")?;
|
||||
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
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
|
|
@ -491,6 +491,7 @@ impl Spanned for Statement {
|
|||
Statement::CreateStage { .. } => Span::empty(),
|
||||
Statement::Assert { .. } => Span::empty(),
|
||||
Statement::Grant { .. } => Span::empty(),
|
||||
Statement::Deny { .. } => Span::empty(),
|
||||
Statement::Revoke { .. } => Span::empty(),
|
||||
Statement::Deallocate { .. } => Span::empty(),
|
||||
Statement::Execute { .. } => Span::empty(),
|
||||
|
|
|
@ -49,7 +49,7 @@ pub use self::postgresql::PostgreSqlDialect;
|
|||
pub use self::redshift::RedshiftSqlDialect;
|
||||
pub use self::snowflake::SnowflakeDialect;
|
||||
pub use self::sqlite::SQLiteDialect;
|
||||
use crate::ast::{ColumnOption, Expr, Statement};
|
||||
use crate::ast::{ColumnOption, Expr, GranteesType, Statement};
|
||||
pub use crate::keywords;
|
||||
use crate::keywords::Keyword;
|
||||
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
|
||||
/// before the table alias option. For example:
|
||||
///
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
use crate::ast::helpers::attached_token::AttachedToken;
|
||||
use crate::ast::{
|
||||
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, IfStatement, Statement,
|
||||
TriggerObject,
|
||||
BeginEndStatements, ConditionalStatementBlock, ConditionalStatements, GranteesType,
|
||||
IfStatement, Statement, TriggerObject,
|
||||
};
|
||||
use crate::dialect::Dialect;
|
||||
use crate::keywords::{self, Keyword};
|
||||
|
@ -52,6 +52,10 @@ impl Dialect for MsSqlDialect {
|
|||
|| ch == '_'
|
||||
}
|
||||
|
||||
fn identifier_quote_style(&self, _identifier: &str) -> Option<char> {
|
||||
Some('[')
|
||||
}
|
||||
|
||||
/// 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>
|
||||
fn convert_type_before_value(&self) -> bool {
|
||||
|
@ -119,6 +123,11 @@ impl Dialect for MsSqlDialect {
|
|||
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 {
|
||||
!keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
|
||||
}
|
||||
|
|
|
@ -278,6 +278,7 @@ define_keywords!(
|
|||
DELIMITER,
|
||||
DELTA,
|
||||
DENSE_RANK,
|
||||
DENY,
|
||||
DEREF,
|
||||
DESC,
|
||||
DESCRIBE,
|
||||
|
|
|
@ -583,6 +583,10 @@ impl<'a> Parser<'a> {
|
|||
Keyword::SHOW => self.parse_show(),
|
||||
Keyword::USE => self.parse_use(),
|
||||
Keyword::GRANT => self.parse_grant(),
|
||||
Keyword::DENY => {
|
||||
self.prev_token();
|
||||
self.parse_deny()
|
||||
}
|
||||
Keyword::REVOKE => self.parse_revoke(),
|
||||
Keyword::START => self.parse_start_transaction(),
|
||||
Keyword::BEGIN => self.parse_begin(),
|
||||
|
@ -13381,7 +13385,7 @@ impl<'a> Parser<'a> {
|
|||
|
||||
/// Parse a GRANT statement.
|
||||
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)?;
|
||||
let grantees = self.parse_grantees()?;
|
||||
|
@ -13389,15 +13393,24 @@ impl<'a> Parser<'a> {
|
|||
let with_grant_option =
|
||||
self.parse_keywords(&[Keyword::WITH, Keyword::GRANT, Keyword::OPTION]);
|
||||
|
||||
let granted_by = self
|
||||
.parse_keywords(&[Keyword::GRANTED, Keyword::BY])
|
||||
.then(|| self.parse_identifier().unwrap());
|
||||
let as_grantor = if self.parse_keywords(&[Keyword::AS]) {
|
||||
Some(self.parse_identifier()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let granted_by = if self.parse_keywords(&[Keyword::GRANTED, Keyword::BY]) {
|
||||
Some(self.parse_identifier()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Statement::Grant {
|
||||
privileges,
|
||||
objects,
|
||||
grantees,
|
||||
with_grant_option,
|
||||
as_grantor,
|
||||
granted_by,
|
||||
})
|
||||
}
|
||||
|
@ -13406,7 +13419,7 @@ impl<'a> Parser<'a> {
|
|||
let mut values = vec![];
|
||||
let mut grantee_type = GranteesType::None;
|
||||
loop {
|
||||
grantee_type = if self.parse_keyword(Keyword::ROLE) {
|
||||
let new_grantee_type = if self.parse_keyword(Keyword::ROLE) {
|
||||
GranteesType::Role
|
||||
} else if self.parse_keyword(Keyword::USER) {
|
||||
GranteesType::User
|
||||
|
@ -13423,9 +13436,19 @@ impl<'a> Parser<'a> {
|
|||
} else if self.parse_keyword(Keyword::APPLICATION) {
|
||||
GranteesType::Application
|
||||
} 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 {
|
||||
Grantee {
|
||||
grantee_type: grantee_type.clone(),
|
||||
|
@ -13460,7 +13483,7 @@ impl<'a> Parser<'a> {
|
|||
Ok(values)
|
||||
}
|
||||
|
||||
pub fn parse_grant_revoke_privileges_objects(
|
||||
pub fn parse_grant_deny_revoke_privileges_objects(
|
||||
&mut self,
|
||||
) -> Result<(Privileges, Option<GrantObjects>), ParserError> {
|
||||
let privileges = if self.parse_keyword(Keyword::ALL) {
|
||||
|
@ -13510,7 +13533,6 @@ impl<'a> Parser<'a> {
|
|||
let object_type = self.parse_one_of_keywords(&[
|
||||
Keyword::SEQUENCE,
|
||||
Keyword::DATABASE,
|
||||
Keyword::DATABASE,
|
||||
Keyword::SCHEMA,
|
||||
Keyword::TABLE,
|
||||
Keyword::VIEW,
|
||||
|
@ -13605,6 +13627,9 @@ impl<'a> Parser<'a> {
|
|||
Ok(Action::Create { obj_type })
|
||||
} else if self.parse_keyword(Keyword::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) {
|
||||
let obj_type = self.maybe_parse_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
|
||||
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)?;
|
||||
let grantees = self.parse_grantees()?;
|
||||
|
||||
let granted_by = self
|
||||
.parse_keywords(&[Keyword::GRANTED, Keyword::BY])
|
||||
.then(|| self.parse_identifier().unwrap());
|
||||
let granted_by = if self.parse_keywords(&[Keyword::GRANTED, Keyword::BY]) {
|
||||
Some(self.parse_identifier()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
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 OWNERSHIP ON INTEGRATION int1 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]
|
||||
|
|
|
@ -2340,3 +2340,13 @@ fn parse_print() {
|
|||
let _ = ms().verified_stmt("PRINT N'Hello, ⛄️!'");
|
||||
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,
|
||||
grantees,
|
||||
with_grant_option,
|
||||
as_grantor: _,
|
||||
granted_by,
|
||||
} = stmt
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue