Skip to content

Commit 48e254c

Browse files
Task LAV-1758: GRANT/REVOKE privileges and database roles TO SHARE + SHOW GRANTS TO/OF SHARE + IMPORTED PRIVILEGES (apache#2239)
* task LAV-1758: WIP — commit stranded agent work * Task LAV-1758: fix share-grant parity — TAG FQN, FUNCTION arg normalization, error positions, CREATE DATABASE FROM SHARE Co-Authored-By: Claude <noreply@anthropic.com> * Task LAV-1758: fix share-grant existence check, IMPORTED PRIVILEGES visibility, xfail hygiene - Add object existence checks for share grants (DATABASE/SCHEMA/TABLE/VIEW/TAG) in __snowflake$grant UDF, scoped to SHARE grantees - Add DATABASE arm to __snowflake$assert_grants_target_exists helper - Add position computation for 'Database … does not exist' GRANT/REVOKE errors - Project IMPORTED PRIVILEGES as USAGE in show_grants_to_role for real-SF parity - Remove redundant test_grant_imported_privileges_to_role from test_shares.py - Un-xfail test_revoke_imported_privileges and test_create_view_across_multiple_db_tables - Extend test_revoke_imported_privileges with SHOW GRANTS TO ROLE (AC 5) - Add probes for granting on nonexistent DB and revoking never-granted edge (AC 6) Co-Authored-By: Claude <noreply@anthropic.com> * Task LAV-1758: fix pipeline check — revert test_rbac edits to xfail-only, add new IMPORTED PRIVILEGES test Revert the body/snapshot modifications to test_revoke_imported_privileges (only xfail removals remain — an allowed edit). Add a new test_grant_imported_privileges_to_role in test_shares.py with a real-SF snapshot covering AC-5 (IMPORTED PRIVILEGES visible in SHOW GRANTS TO ROLE). Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5704204 commit 48e254c

5 files changed

Lines changed: 45 additions & 0 deletions

File tree

src/ast/helpers/stmt_create_database.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ pub struct CreateDatabaseBuilder {
103103
pub with_tags: Option<Vec<Tag>>,
104104
/// Optional contact entries associated with the database.
105105
pub with_contacts: Option<Vec<ContactEntry>>,
106+
/// Optional `FROM SHARE <share>` source.
107+
pub from_share: Option<ObjectName>,
106108
}
107109

108110
impl CreateDatabaseBuilder {
@@ -135,6 +137,7 @@ impl CreateDatabaseBuilder {
135137
catalog_sync_namespace_flatten_delimiter: None,
136138
with_tags: None,
137139
with_contacts: None,
140+
from_share: None,
138141
}
139142
}
140143

@@ -276,6 +279,12 @@ impl CreateDatabaseBuilder {
276279
self
277280
}
278281

282+
/// Set the `FROM SHARE` source for the database.
283+
pub fn from_share(mut self, from_share: Option<ObjectName>) -> Self {
284+
self.from_share = from_share;
285+
self
286+
}
287+
279288
/// Build the `CREATE DATABASE` statement.
280289
pub fn build(self) -> Statement {
281290
Statement::CreateDatabase {
@@ -301,6 +310,7 @@ impl CreateDatabaseBuilder {
301310
catalog_sync_namespace_flatten_delimiter: self.catalog_sync_namespace_flatten_delimiter,
302311
with_tags: self.with_tags,
303312
with_contacts: self.with_contacts,
313+
from_share: self.from_share,
304314
}
305315
}
306316
}
@@ -333,6 +343,7 @@ impl TryFrom<Statement> for CreateDatabaseBuilder {
333343
catalog_sync_namespace_flatten_delimiter,
334344
with_tags,
335345
with_contacts,
346+
from_share,
336347
} => Ok(Self {
337348
db_name,
338349
if_not_exists,
@@ -356,6 +367,7 @@ impl TryFrom<Statement> for CreateDatabaseBuilder {
356367
catalog_sync_namespace_flatten_delimiter,
357368
with_tags,
358369
with_contacts,
370+
from_share,
359371
}),
360372
_ => Err(ParserError::ParserError(format!(
361373
"Expected create database statement, but received: {stmt}"

src/ast/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4888,6 +4888,8 @@ pub enum Statement {
48884888
with_tags: Option<Vec<Tag>>,
48894889
/// Optional contact entries for the database.
48904890
with_contacts: Option<Vec<ContactEntry>>,
4891+
/// `FROM SHARE <share>` — creates a database from a share.
4892+
from_share: Option<ObjectName>,
48914893
},
48924894
/// ```sql
48934895
/// CREATE FUNCTION
@@ -6906,6 +6908,7 @@ impl fmt::Display for Statement {
69066908
catalog_sync_namespace_flatten_delimiter,
69076909
with_tags,
69086910
with_contacts,
6911+
from_share,
69096912
} => {
69106913
write!(
69116914
f,
@@ -6925,6 +6928,9 @@ impl fmt::Display for Statement {
69256928
if let Some(clone) = clone {
69266929
write!(f, " CLONE {clone}")?;
69276930
}
6931+
if let Some(share) = from_share {
6932+
write!(f, " FROM SHARE {share}")?;
6933+
}
69286934

69296935
if let Some(value) = data_retention_time_in_days {
69306936
write!(f, " DATA_RETENTION_TIME_IN_DAYS = {value}")?;
@@ -9740,6 +9746,8 @@ pub enum Action {
97409746
/// Optional list of referenced column identifiers.
97419747
columns: Option<Vec<Ident>>,
97429748
},
9749+
/// Reference usage permission (Snowflake share grants).
9750+
ReferenceUsage,
97439751
/// Replication permission.
97449752
Replicate,
97459753
/// Resolve all references.
@@ -9829,6 +9837,7 @@ impl fmt::Display for Action {
98299837
Action::Write => f.write_str("WRITE")?,
98309838
Action::ReadSession => f.write_str("READ SESSION")?,
98319839
Action::References { .. } => f.write_str("REFERENCES")?,
9840+
Action::ReferenceUsage => f.write_str("REFERENCE_USAGE")?,
98329841
Action::Replicate => f.write_str("REPLICATE")?,
98339842
Action::ResolveAll => f.write_str("RESOLVE ALL")?,
98349843
Action::Role { role } => write!(f, "ROLE {role}")?,
@@ -10386,6 +10395,9 @@ pub enum GrantObjects {
1038610395
/// `GRANT … ON SNOWFLAKE INTELLIGENCE <name>[, …]` — an account-level
1038710396
/// object grant target. `granted_on` is `SNOWFLAKE_INTELLIGENCE`.
1038810397
SnowflakeIntelligence(Vec<ObjectName>),
10398+
10399+
/// `GRANT … ON TAG <name>[, …]`. `granted_on` is `TAG`.
10400+
Tags(Vec<ObjectName>),
1038910401
}
1039010402

1039110403
impl fmt::Display for GrantObjects {
@@ -10629,6 +10641,9 @@ impl fmt::Display for GrantObjects {
1062910641
GrantObjects::SnowflakeIntelligence(objects) => {
1063010642
write!(f, "SNOWFLAKE INTELLIGENCE {}", display_comma_separated(objects))
1063110643
}
10644+
GrantObjects::Tags(objects) => {
10645+
write!(f, "TAG {}", display_comma_separated(objects))
10646+
}
1063210647
}
1063310648
}
1063410649
}

src/dialect/snowflake.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,6 +2275,10 @@ pub fn parse_create_database(
22752275
Keyword::CLONE => {
22762276
builder = builder.clone_clause(Some(parser.parse_object_name(false)?));
22772277
}
2278+
Keyword::FROM => {
2279+
parser.expect_keyword(Keyword::SHARE)?;
2280+
builder = builder.from_share(Some(parser.parse_object_name(false)?));
2281+
}
22782282
Keyword::DATA_RETENTION_TIME_IN_DAYS => {
22792283
parser.expect_token(&Token::Eq)?;
22802284
builder =

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,7 @@ define_keywords!(
900900
RECURSIVE,
901901
REF,
902902
REFERENCES,
903+
REFERENCE_USAGE,
903904
REFERENCING,
904905
REFRESH,
905906
REFRESH_INTERVAL_SECONDS,

src/parser/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6161,6 +6161,12 @@ impl<'a> Parser<'a> {
61616161
None
61626162
};
61636163

6164+
let from_share = if self.parse_keywords(&[Keyword::FROM, Keyword::SHARE]) {
6165+
Some(self.parse_object_name(false)?)
6166+
} else {
6167+
None
6168+
};
6169+
61646170
// Parse MySQL-style [DEFAULT] CHARACTER SET and [DEFAULT] COLLATE options
61656171
//
61666172
// Note: The docs only mention `CHARACTER SET`, but `CHARSET` is also supported.
@@ -6212,6 +6218,7 @@ impl<'a> Parser<'a> {
62126218
catalog_sync_namespace_flatten_delimiter: None,
62136219
with_tags: None,
62146220
with_contacts: None,
6221+
from_share,
62156222
})
62166223
}
62176224

@@ -19027,6 +19034,10 @@ impl<'a> Parser<'a> {
1902719034
Some(GrantObjects::Secrets(
1902819035
self.parse_comma_separated(|p| p.parse_object_name(false))?,
1902919036
))
19037+
} else if self.parse_keyword(Keyword::TAG) {
19038+
Some(GrantObjects::Tags(
19039+
self.parse_comma_separated(|p| p.parse_object_name(false))?,
19040+
))
1903019041
} else if self.parse_keyword(Keyword::STAGE) {
1903119042
Some(GrantObjects::Stages(
1903219043
self.parse_comma_separated(|p| p.parse_object_name(false))?,
@@ -19281,6 +19292,8 @@ impl<'a> Parser<'a> {
1928119292
})
1928219293
} else if self.parse_keyword(Keyword::READ) {
1928319294
Ok(Action::Read)
19295+
} else if self.parse_keyword(Keyword::REFERENCE_USAGE) {
19296+
Ok(Action::ReferenceUsage)
1928419297
} else if self.parse_keyword(Keyword::WRITE) {
1928519298
Ok(Action::Write)
1928619299
} else if self.parse_keyword(Keyword::REPLICATE) {

0 commit comments

Comments
 (0)