This is an automated email from the ASF dual-hosted git repository.
iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 7b50ac31 Parse Snowflake USE ROLE and USE SECONDARY ROLES (#1578)
7b50ac31 is described below
commit 7b50ac31c342258a11a744a3f83ac0e99dda3978
Author: Yoav Cohen <[email protected]>
AuthorDate: Thu Dec 5 19:17:52 2024 +0100
Parse Snowflake USE ROLE and USE SECONDARY ROLES (#1578)
---
src/ast/dcl.rs | 41 ++++++++++++++++++++++++++++++++++-------
src/ast/mod.rs | 4 +++-
src/ast/spans.rs | 17 ++++++++++++-----
src/keywords.rs | 2 ++
src/parser/mod.rs | 39 +++++++++++++++++++++++++++++++--------
tests/sqlparser_common.rs | 14 +++++++-------
tests/sqlparser_snowflake.rs | 34 ++++++++++++++++++++++++++--------
7 files changed, 115 insertions(+), 36 deletions(-)
diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs
index d47476ff..735ab0cc 100644
--- a/src/ast/dcl.rs
+++ b/src/ast/dcl.rs
@@ -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.
@@ -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 {
@@ -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)),
+ }
+ }
+}
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index f782b363..bc4dda34 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -43,7 +43,9 @@ pub use self::data_type::{
ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, EnumMember,
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,
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index a5439417..cd3bda1c 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -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,
@@ -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(),
}
diff --git a/src/keywords.rs b/src/keywords.rs
index be3910f8..bd97c3c9 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -664,6 +664,7 @@ define_keywords!(
RIGHT,
RLIKE,
ROLE,
+ ROLES,
ROLLBACK,
ROLLUP,
ROOT,
@@ -682,6 +683,7 @@ define_keywords!(
SCROLL,
SEARCH,
SECOND,
+ SECONDARY,
SECRET,
SECURITY,
SELECT,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 04a103c6..b5365b51 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -10093,23 +10093,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
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 61c742da..42616d51 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -4066,8 +4066,8 @@ fn test_alter_table_with_on_cluster() {
Statement::AlterTable {
name, on_cluster, ..
} => {
- std::assert_eq!(name.to_string(), "t");
- std::assert_eq!(on_cluster, Some(Ident::with_quote('\'',
"cluster")));
+ assert_eq!(name.to_string(), "t");
+ assert_eq!(on_cluster, Some(Ident::with_quote('\'', "cluster")));
}
_ => unreachable!(),
}
@@ -4078,15 +4078,15 @@ fn test_alter_table_with_on_cluster() {
Statement::AlterTable {
name, on_cluster, ..
} => {
- std::assert_eq!(name.to_string(), "t");
- std::assert_eq!(on_cluster, Some(Ident::new("cluster_name")));
+ assert_eq!(name.to_string(), "t");
+ assert_eq!(on_cluster, Some(Ident::new("cluster_name")));
}
_ => unreachable!(),
}
let res = all_dialects()
.parse_sql_statements("ALTER TABLE t ON CLUSTER 123 ADD CONSTRAINT bar
PRIMARY KEY (baz)");
- std::assert_eq!(
+ assert_eq!(
res.unwrap_err(),
ParserError::ParserError("Expected: identifier, found:
123".to_string())
)
@@ -11226,7 +11226,7 @@ fn test_group_by_nothing() {
let Select { group_by, .. } = all_dialects_where(|d|
d.supports_group_by_expr())
.verified_only_select("SELECT count(1) FROM t GROUP BY ()");
{
- std::assert_eq!(
+ assert_eq!(
GroupByExpr::Expressions(vec![Expr::Tuple(vec![])], vec![]),
group_by
);
@@ -11235,7 +11235,7 @@ fn test_group_by_nothing() {
let Select { group_by, .. } = all_dialects_where(|d|
d.supports_group_by_expr())
.verified_only_select("SELECT name, count(1) FROM t GROUP BY name,
()");
{
- std::assert_eq!(
+ assert_eq!(
GroupByExpr::Expressions(
vec![
Identifier(Ident::new("name".to_string())),
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs
index e31811c2..fb8a60cf 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -2649,7 +2649,7 @@ fn parse_use() {
let quote_styles = ['\'', '"', '`'];
for object_name in &valid_object_names {
// Test single identifier without quotes
- std::assert_eq!(
+ assert_eq!(
snowflake().verified_stmt(&format!("USE {}", object_name)),
Statement::Use(Use::Object(ObjectName(vec![Ident::new(
object_name.to_string()
@@ -2657,7 +2657,7 @@ fn parse_use() {
);
for "e in "e_styles {
// Test single identifier with different type of quotes
- std::assert_eq!(
+ assert_eq!(
snowflake().verified_stmt(&format!("USE {}{}{}", quote,
object_name, quote)),
Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote(
quote,
@@ -2669,7 +2669,7 @@ fn parse_use() {
for "e in "e_styles {
// Test double identifier with different type of quotes
- std::assert_eq!(
+ assert_eq!(
snowflake().verified_stmt(&format!("USE
{0}CATALOG{0}.{0}my_schema{0}", quote)),
Statement::Use(Use::Object(ObjectName(vec![
Ident::with_quote(quote, "CATALOG"),
@@ -2678,7 +2678,7 @@ fn parse_use() {
);
}
// Test double identifier without quotes
- std::assert_eq!(
+ assert_eq!(
snowflake().verified_stmt("USE mydb.my_schema"),
Statement::Use(Use::Object(ObjectName(vec![
Ident::new("mydb"),
@@ -2688,37 +2688,55 @@ fn parse_use() {
for "e in "e_styles {
// Test single and double identifier with keyword and different type
of quotes
- std::assert_eq!(
+ assert_eq!(
snowflake().verified_stmt(&format!("USE DATABASE
{0}my_database{0}", quote)),
Statement::Use(Use::Database(ObjectName(vec![Ident::with_quote(
quote,
"my_database".to_string(),
)])))
);
- std::assert_eq!(
+ assert_eq!(
snowflake().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}",
quote)),
Statement::Use(Use::Schema(ObjectName(vec![Ident::with_quote(
quote,
"my_schema".to_string(),
)])))
);
- std::assert_eq!(
+ assert_eq!(
snowflake().verified_stmt(&format!("USE SCHEMA
{0}CATALOG{0}.{0}my_schema{0}", quote)),
Statement::Use(Use::Schema(ObjectName(vec![
Ident::with_quote(quote, "CATALOG"),
Ident::with_quote(quote, "my_schema")
])))
);
+ 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(),
+ )])))
+ );
+ 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
let invalid_cases = ["USE SCHEMA", "USE DATABASE", "USE WAREHOUSE"];
for sql in &invalid_cases {
- std::assert_eq!(
+ assert_eq!(
snowflake().parse_sql_statements(sql).unwrap_err(),
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]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]