Skip to content

Commit

Permalink
Parse Snowflake USE ROLE and USE SECONDARY ROLES
Browse files Browse the repository at this point in the history
  • Loading branch information
yoavcloud committed Dec 3, 2024
1 parent e16b246 commit dfa1183
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 21 deletions.
41 changes: 34 additions & 7 deletions src/ast/dcl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut};

use super::{Expr, Ident, Password};
use super::{display_comma_separated, Expr, Ident, Password};
use crate::ast::{display_separated, ObjectName};

/// An option in `ROLE` statement.
Expand Down Expand Up @@ -204,12 +204,14 @@ impl fmt::Display for AlterRoleOperation {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum Use {
Catalog(ObjectName), // e.g. `USE CATALOG foo.bar`
Schema(ObjectName), // e.g. `USE SCHEMA foo.bar`
Database(ObjectName), // e.g. `USE DATABASE foo.bar`
Warehouse(ObjectName), // e.g. `USE WAREHOUSE foo.bar`
Object(ObjectName), // e.g. `USE foo.bar`
Default, // e.g. `USE DEFAULT`
Catalog(ObjectName), // e.g. `USE CATALOG foo.bar`
Schema(ObjectName), // e.g. `USE SCHEMA foo.bar`
Database(ObjectName), // e.g. `USE DATABASE foo.bar`
Warehouse(ObjectName), // e.g. `USE WAREHOUSE foo.bar`
Role(ObjectName), // e.g. `USE ROLE PUBLIC`
SecondaryRoles(SecondaryRoles), // e.g. `USE SECONDARY ROLES ALL`
Object(ObjectName), // e.g. `USE foo.bar`
Default, // e.g. `USE DEFAULT`
}

impl fmt::Display for Use {
Expand All @@ -220,8 +222,33 @@ impl fmt::Display for Use {
Use::Schema(name) => write!(f, "SCHEMA {}", name),
Use::Database(name) => write!(f, "DATABASE {}", name),
Use::Warehouse(name) => write!(f, "WAREHOUSE {}", name),
Use::Role(name) => write!(f, "ROLE {}", name),
Use::SecondaryRoles(secondary_roles) => {
write!(f, "SECONDARY ROLES {}", secondary_roles)
}
Use::Object(name) => write!(f, "{}", name),
Use::Default => write!(f, "DEFAULT"),
}
}
}

/// Snowflake `SECONDARY ROLES` USE variant
/// See: <https://docs.snowflake.com/en/sql-reference/sql/use-secondary-roles>
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum SecondaryRoles {
All,
None,
List(Vec<Ident>),
}

impl fmt::Display for SecondaryRoles {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SecondaryRoles::All => write!(f, "ALL"),
SecondaryRoles::None => write!(f, "NONE"),
SecondaryRoles::List(roles) => write!(f, "{}", display_comma_separated(roles)),
}
}
}
4 changes: 3 additions & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ pub use self::data_type::{
ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo,
StructBracketKind, TimezoneInfo,
};
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use};
pub use self::dcl::{
AlterRoleOperation, ResetConfig, RoleOption, SecondaryRoles, SetConfigValue, Use,
};
pub use self::ddl::{
AlterColumnOperation, AlterIndexOperation, AlterPolicyOperation, AlterTableOperation,
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty,
Expand Down
17 changes: 12 additions & 5 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use core::iter;
use crate::tokenizer::Span;

use super::{
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, Array, Assignment,
AssignmentTarget, CloseCursor, ClusteredIndex, ColumnDef, ColumnOption, ColumnOptionDef,
ConflictTarget, ConnectBy, ConstraintCharacteristics, CopySource, CreateIndex, CreateTable,
CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem, Expr,
ExprWithAlias, Fetch, FromTable, Function, FunctionArg, FunctionArgExpr,
dcl::SecondaryRoles, AlterColumnOperation, AlterIndexOperation, AlterTableOperation, Array,
Assignment, AssignmentTarget, CloseCursor, ClusteredIndex, ColumnDef, ColumnOption,
ColumnOptionDef, ConflictTarget, ConnectBy, ConstraintCharacteristics, CopySource, CreateIndex,
CreateTable, CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem,
Expr, ExprWithAlias, Fetch, FromTable, Function, FunctionArg, FunctionArgExpr,
FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr, HavingBound,
IlikeSelectItem, Insert, Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator,
JsonPath, JsonPathElem, LateralView, MatchRecognizePattern, Measure, NamedWindowDefinition,
Expand Down Expand Up @@ -484,6 +484,13 @@ impl Spanned for Use {
Use::Schema(object_name) => object_name.span(),
Use::Database(object_name) => object_name.span(),
Use::Warehouse(object_name) => object_name.span(),
Use::Role(object_name) => object_name.span(),
Use::SecondaryRoles(secondary_roles) => {
if let SecondaryRoles::List(roles) = secondary_roles {
return union_spans(roles.iter().map(|i| i.span));
}
Span::empty()
}
Use::Object(object_name) => object_name.span(),
Use::Default => Span::empty(),
}
Expand Down
2 changes: 2 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ define_keywords!(
RIGHT,
RLIKE,
ROLE,
ROLES,
ROLLBACK,
ROLLUP,
ROOT,
Expand All @@ -679,6 +680,7 @@ define_keywords!(
SCROLL,
SEARCH,
SECOND,
SECONDARY,
SECRET,
SECURITY,
SELECT,
Expand Down
39 changes: 31 additions & 8 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10050,23 +10050,46 @@ impl<'a> Parser<'a> {
} else if dialect_of!(self is DatabricksDialect) {
self.parse_one_of_keywords(&[Keyword::CATALOG, Keyword::DATABASE, Keyword::SCHEMA])
} else if dialect_of!(self is SnowflakeDialect) {
self.parse_one_of_keywords(&[Keyword::DATABASE, Keyword::SCHEMA, Keyword::WAREHOUSE])
self.parse_one_of_keywords(&[
Keyword::DATABASE,
Keyword::SCHEMA,
Keyword::WAREHOUSE,
Keyword::ROLE,
Keyword::SECONDARY,
])
} else {
None // No specific keywords for other dialects, including GenericDialect
};

let obj_name = self.parse_object_name(false)?;
let result = match parsed_keyword {
Some(Keyword::CATALOG) => Use::Catalog(obj_name),
Some(Keyword::DATABASE) => Use::Database(obj_name),
Some(Keyword::SCHEMA) => Use::Schema(obj_name),
Some(Keyword::WAREHOUSE) => Use::Warehouse(obj_name),
_ => Use::Object(obj_name),
let result = if matches!(parsed_keyword, Some(Keyword::SECONDARY)) {
self.parse_secondary_roles()?
} else {
let obj_name = self.parse_object_name(false)?;
match parsed_keyword {
Some(Keyword::CATALOG) => Use::Catalog(obj_name),
Some(Keyword::DATABASE) => Use::Database(obj_name),
Some(Keyword::SCHEMA) => Use::Schema(obj_name),
Some(Keyword::WAREHOUSE) => Use::Warehouse(obj_name),
Some(Keyword::ROLE) => Use::Role(obj_name),
_ => Use::Object(obj_name),
}
};

Ok(Statement::Use(result))
}

fn parse_secondary_roles(&mut self) -> Result<Use, ParserError> {
self.expect_keyword(Keyword::ROLES)?;
if self.parse_keyword(Keyword::NONE) {
Ok(Use::SecondaryRoles(SecondaryRoles::None))
} else if self.parse_keyword(Keyword::ALL) {
Ok(Use::SecondaryRoles(SecondaryRoles::All))
} else {
let roles = self.parse_comma_separated(|parser| parser.parse_identifier(false))?;
Ok(Use::SecondaryRoles(SecondaryRoles::List(roles)))
}
}

pub fn parse_table_and_joins(&mut self) -> Result<TableWithJoins, ParserError> {
let relation = self.parse_table_factor()?;
// Note that for keywords to be properly handled here, they need to be
Expand Down
18 changes: 18 additions & 0 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2709,6 +2709,20 @@ fn parse_use() {
Ident::with_quote(quote, "my_schema")
])))
);
std::assert_eq!(
snowflake().verified_stmt(&format!("USE ROLE {0}my_role{0}", quote)),
Statement::Use(Use::Role(ObjectName(vec![Ident::with_quote(
quote,
"my_role".to_string(),
)])))
);
std::assert_eq!(
snowflake().verified_stmt(&format!("USE WAREHOUSE {0}my_wh{0}", quote)),
Statement::Use(Use::Warehouse(ObjectName(vec![Ident::with_quote(
quote,
"my_wh".to_string(),
)])))
);
}

// Test invalid syntax - missing identifier
Expand All @@ -2719,6 +2733,10 @@ fn parse_use() {
ParserError::ParserError("Expected: identifier, found: EOF".to_string()),
);
}

snowflake().verified_stmt("USE SECONDARY ROLES ALL");
snowflake().verified_stmt("USE SECONDARY ROLES NONE");
snowflake().verified_stmt("USE SECONDARY ROLES r1, r2, r3");
}

#[test]
Expand Down

0 comments on commit dfa1183

Please sign in to comment.