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 3a8a3bb7 SET statements: scope modifier for multiple assignments 
(#1772)
3a8a3bb7 is described below

commit 3a8a3bb7a52c2a855e40ccbf88c3073abda1f1e7
Author: Mohamed Abdeen <[email protected]>
AuthorDate: Sat Mar 22 07:38:00 2025 +0200

    SET statements: scope modifier for multiple assignments (#1772)
---
 src/ast/mod.rs              |  28 ++++++----
 src/dialect/generic.rs      |   4 ++
 src/parser/mod.rs           | 121 +++++++++++++++++++++-----------------------
 tests/sqlparser_common.rs   |  43 ++++++++++++++--
 tests/sqlparser_hive.rs     |   9 ++--
 tests/sqlparser_mssql.rs    |   2 +-
 tests/sqlparser_mysql.rs    |   2 +-
 tests/sqlparser_postgres.rs |  20 ++++----
 8 files changed, 134 insertions(+), 95 deletions(-)

diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index b1bce3e9..3264cf03 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -2638,7 +2638,7 @@ pub enum Set {
     /// SQL Standard-style
     /// SET a = 1;
     SingleAssignment {
-        scope: ContextModifier,
+        scope: Option<ContextModifier>,
         hivevar: bool,
         variable: ObjectName,
         values: Vec<Expr>,
@@ -2668,7 +2668,7 @@ pub enum Set {
     /// [4]: 
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10004.htm
     SetRole {
         /// Non-ANSI optional identifier to inform if the role is defined 
inside the current session (`SESSION`) or transaction (`LOCAL`).
-        context_modifier: ContextModifier,
+        context_modifier: Option<ContextModifier>,
         /// Role name. If NONE is specified, then the current role name is 
removed.
         role_name: Option<Ident>,
     },
@@ -2720,7 +2720,13 @@ impl Display for Set {
                 role_name,
             } => {
                 let role_name = role_name.clone().unwrap_or_else(|| 
Ident::new("NONE"));
-                write!(f, "SET {context_modifier}ROLE {role_name}")
+                write!(
+                    f,
+                    "SET {modifier}ROLE {role_name}",
+                    modifier = context_modifier
+                        .map(|m| format!("{}", m))
+                        .unwrap_or_default()
+                )
             }
             Self::SetSessionParam(kind) => write!(f, "SET {kind}"),
             Self::SetTransaction {
@@ -2775,7 +2781,7 @@ impl Display for Set {
                 write!(
                     f,
                     "SET {}{}{} = {}",
-                    scope,
+                    scope.map(|s| format!("{}", s)).unwrap_or_default(),
                     if *hivevar { "HIVEVAR:" } else { "" },
                     variable,
                     display_comma_separated(values)
@@ -5736,13 +5742,20 @@ impl fmt::Display for SequenceOptions {
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
 pub struct SetAssignment {
+    pub scope: Option<ContextModifier>,
     pub name: ObjectName,
     pub value: Expr,
 }
 
 impl fmt::Display for SetAssignment {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{} = {}", self.name, self.value)
+        write!(
+            f,
+            "{}{} = {}",
+            self.scope.map(|s| format!("{}", s)).unwrap_or_default(),
+            self.name,
+            self.value
+        )
     }
 }
 
@@ -7969,8 +7982,6 @@ impl fmt::Display for FlushLocation {
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
 pub enum ContextModifier {
-    /// No context defined. Each dialect defines the default in this scenario.
-    None,
     /// `LOCAL` identifier, usually related to transactional states.
     Local,
     /// `SESSION` identifier
@@ -7982,9 +7993,6 @@ pub enum ContextModifier {
 impl fmt::Display for ContextModifier {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
-            Self::None => {
-                write!(f, "")
-            }
             Self::Local => {
                 write!(f, "LOCAL ")
             }
diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs
index c13d5aa6..92cfca8f 100644
--- a/src/dialect/generic.rs
+++ b/src/dialect/generic.rs
@@ -159,4 +159,8 @@ impl Dialect for GenericDialect {
     fn supports_set_names(&self) -> bool {
         true
     }
+
+    fn supports_comma_separated_set_assignments(&self) -> bool {
+        true
+    }
 }
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index a43be5c6..7d8417f3 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -1822,12 +1822,12 @@ impl<'a> Parser<'a> {
         })
     }
 
-    fn keyword_to_modifier(k: Option<Keyword>) -> ContextModifier {
+    fn keyword_to_modifier(k: Keyword) -> Option<ContextModifier> {
         match k {
-            Some(Keyword::LOCAL) => ContextModifier::Local,
-            Some(Keyword::GLOBAL) => ContextModifier::Global,
-            Some(Keyword::SESSION) => ContextModifier::Session,
-            _ => ContextModifier::None,
+            Keyword::LOCAL => Some(ContextModifier::Local),
+            Keyword::GLOBAL => Some(ContextModifier::Global),
+            Keyword::SESSION => Some(ContextModifier::Session),
+            _ => None,
         }
     }
 
@@ -11157,9 +11157,11 @@ impl<'a> Parser<'a> {
     }
 
     /// Parse a `SET ROLE` statement. Expects SET to be consumed already.
-    fn parse_set_role(&mut self, modifier: Option<Keyword>) -> 
Result<Statement, ParserError> {
+    fn parse_set_role(
+        &mut self,
+        modifier: Option<ContextModifier>,
+    ) -> Result<Statement, ParserError> {
         self.expect_keyword_is(Keyword::ROLE)?;
-        let context_modifier = Self::keyword_to_modifier(modifier);
 
         let role_name = if self.parse_keyword(Keyword::NONE) {
             None
@@ -11167,7 +11169,7 @@ impl<'a> Parser<'a> {
             Some(self.parse_identifier()?)
         };
         Ok(Statement::Set(Set::SetRole {
-            context_modifier,
+            context_modifier: modifier,
             role_name,
         }))
     }
@@ -11203,46 +11205,52 @@ impl<'a> Parser<'a> {
         }
     }
 
-    fn parse_set_assignment(
-        &mut self,
-    ) -> Result<(OneOrManyWithParens<ObjectName>, Expr), ParserError> {
-        let variables = if self.dialect.supports_parenthesized_set_variables()
+    fn parse_context_modifier(&mut self) -> Option<ContextModifier> {
+        let modifier =
+            self.parse_one_of_keywords(&[Keyword::SESSION, Keyword::LOCAL, 
Keyword::GLOBAL])?;
+
+        Self::keyword_to_modifier(modifier)
+    }
+
+    /// Parse a single SET statement assignment `var = expr`.
+    fn parse_set_assignment(&mut self) -> Result<SetAssignment, ParserError> {
+        let scope = self.parse_context_modifier();
+
+        let name = if self.dialect.supports_parenthesized_set_variables()
             && self.consume_token(&Token::LParen)
         {
-            let vars = OneOrManyWithParens::Many(
-                self.parse_comma_separated(|parser: &mut Parser<'a>| 
parser.parse_identifier())?
-                    .into_iter()
-                    .map(|ident| ObjectName::from(vec![ident]))
-                    .collect(),
-            );
-            self.expect_token(&Token::RParen)?;
-            vars
+            // Parenthesized assignments are handled in the `parse_set` 
function after
+            // trying to parse list of assignments using this function.
+            // If a dialect supports both, and we find a LParen, we early exit 
from this function.
+            self.expected("Unparenthesized assignment", self.peek_token())?
         } else {
-            OneOrManyWithParens::One(self.parse_object_name(false)?)
+            self.parse_object_name(false)?
         };
 
         if !(self.consume_token(&Token::Eq) || 
self.parse_keyword(Keyword::TO)) {
             return self.expected("assignment operator", self.peek_token());
         }
 
-        let values = self.parse_expr()?;
+        let value = self.parse_expr()?;
 
-        Ok((variables, values))
+        Ok(SetAssignment { scope, name, value })
     }
 
     fn parse_set(&mut self) -> Result<Statement, ParserError> {
-        let modifier = self.parse_one_of_keywords(&[
-            Keyword::SESSION,
-            Keyword::LOCAL,
-            Keyword::HIVEVAR,
-            Keyword::GLOBAL,
-        ]);
-
-        if let Some(Keyword::HIVEVAR) = modifier {
+        let hivevar = self.parse_keyword(Keyword::HIVEVAR);
+
+        // Modifier is either HIVEVAR: or a ContextModifier (LOCAL, SESSION, 
etc), not both
+        let scope = if !hivevar {
+            self.parse_context_modifier()
+        } else {
+            None
+        };
+
+        if hivevar {
             self.expect_token(&Token::Colon)?;
         }
 
-        if let Some(set_role_stmt) = self.maybe_parse(|parser| 
parser.parse_set_role(modifier))? {
+        if let Some(set_role_stmt) = self.maybe_parse(|parser| 
parser.parse_set_role(scope))? {
             return Ok(set_role_stmt);
         }
 
@@ -11252,8 +11260,8 @@ impl<'a> Parser<'a> {
         {
             if self.consume_token(&Token::Eq) || 
self.parse_keyword(Keyword::TO) {
                 return Ok(Set::SingleAssignment {
-                    scope: Self::keyword_to_modifier(modifier),
-                    hivevar: modifier == Some(Keyword::HIVEVAR),
+                    scope,
+                    hivevar,
                     variable: ObjectName::from(vec!["TIMEZONE".into()]),
                     values: self.parse_set_values(false)?,
                 }
@@ -11263,7 +11271,7 @@ impl<'a> Parser<'a> {
                 // the assignment operator. It's originally PostgreSQL 
specific,
                 // but we allow it for all the dialects
                 return Ok(Set::SetTimeZone {
-                    local: modifier == Some(Keyword::LOCAL),
+                    local: scope == Some(ContextModifier::Local),
                     value: self.parse_expr()?,
                 }
                 .into());
@@ -11311,41 +11319,26 @@ impl<'a> Parser<'a> {
         }
 
         if self.dialect.supports_comma_separated_set_assignments() {
+            if scope.is_some() {
+                self.prev_token();
+            }
+
             if let Some(assignments) = self
                 .maybe_parse(|parser| 
parser.parse_comma_separated(Parser::parse_set_assignment))?
             {
                 return if assignments.len() > 1 {
-                    let assignments = assignments
-                        .into_iter()
-                        .map(|(var, val)| match var {
-                            OneOrManyWithParens::One(v) => Ok(SetAssignment {
-                                name: v,
-                                value: val,
-                            }),
-                            OneOrManyWithParens::Many(_) => {
-                                self.expected("List of single identifiers", 
self.peek_token())
-                            }
-                        })
-                        .collect::<Result<_, _>>()?;
-
                     Ok(Set::MultipleAssignments { assignments }.into())
                 } else {
-                    let (vars, values): (Vec<_>, Vec<_>) = 
assignments.into_iter().unzip();
-
-                    let variable = match vars.into_iter().next() {
-                        Some(OneOrManyWithParens::One(v)) => Ok(v),
-                        Some(OneOrManyWithParens::Many(_)) => self.expected(
-                            "Single assignment or list of assignments",
-                            self.peek_token(),
-                        ),
-                        None => self.expected("At least one identifier", 
self.peek_token()),
-                    }?;
+                    let SetAssignment { scope, name, value } =
+                        assignments.into_iter().next().ok_or_else(|| {
+                            ParserError::ParserError("Expected at least one 
assignment".to_string())
+                        })?;
 
                     Ok(Set::SingleAssignment {
-                        scope: Self::keyword_to_modifier(modifier),
-                        hivevar: modifier == Some(Keyword::HIVEVAR),
-                        variable,
-                        values,
+                        scope,
+                        hivevar,
+                        variable: name,
+                        values: vec![value],
                     }
                     .into())
                 };
@@ -11370,8 +11363,8 @@ impl<'a> Parser<'a> {
         if self.consume_token(&Token::Eq) || self.parse_keyword(Keyword::TO) {
             let stmt = match variables {
                 OneOrManyWithParens::One(var) => Set::SingleAssignment {
-                    scope: Self::keyword_to_modifier(modifier),
-                    hivevar: modifier == Some(Keyword::HIVEVAR),
+                    scope,
+                    hivevar,
                     variable: var,
                     values: self.parse_set_values(false)?,
                 },
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index cf95e2ae..a137291a 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -8635,7 +8635,7 @@ fn parse_set_variable() {
             variable,
             values,
         }) => {
-            assert_eq!(scope, ContextModifier::None);
+            assert_eq!(scope, None);
             assert!(!hivevar);
             assert_eq!(variable, ObjectName::from(vec!["SOMETHING".into()]));
             assert_eq!(
@@ -8655,7 +8655,7 @@ fn parse_set_variable() {
             variable,
             values,
         }) => {
-            assert_eq!(scope, ContextModifier::Global);
+            assert_eq!(scope, Some(ContextModifier::Global));
             assert!(!hivevar);
             assert_eq!(variable, ObjectName::from(vec!["VARIABLE".into()]));
             assert_eq!(
@@ -8747,7 +8747,7 @@ fn parse_set_role_as_variable() {
             variable,
             values,
         }) => {
-            assert_eq!(scope, ContextModifier::None);
+            assert_eq!(scope, None);
             assert!(!hivevar);
             assert_eq!(variable, ObjectName::from(vec!["role".into()]));
             assert_eq!(
@@ -8794,7 +8794,7 @@ fn parse_set_time_zone() {
             variable,
             values,
         }) => {
-            assert_eq!(scope, ContextModifier::None);
+            assert_eq!(scope, None);
             assert!(!hivevar);
             assert_eq!(variable, ObjectName::from(vec!["TIMEZONE".into()]));
             assert_eq!(
@@ -14859,10 +14859,12 @@ fn parse_multiple_set_statements() -> Result<(), 
ParserError> {
                 assignments,
                 vec![
                     SetAssignment {
+                        scope: None,
                         name: ObjectName::from(vec!["@a".into()]),
                         value: Expr::value(number("1"))
                     },
                     SetAssignment {
+                        scope: None,
                         name: ObjectName::from(vec!["b".into()]),
                         value: Expr::value(number("2"))
                     }
@@ -14872,6 +14874,39 @@ fn parse_multiple_set_statements() -> Result<(), 
ParserError> {
         _ => panic!("Expected SetVariable with 2 variables and 2 values"),
     };
 
+    let stmt = dialects.verified_stmt("SET GLOBAL @a = 1, SESSION b = 2, LOCAL 
c = 3, d = 4");
+
+    match stmt {
+        Statement::Set(Set::MultipleAssignments { assignments }) => {
+            assert_eq!(
+                assignments,
+                vec![
+                    SetAssignment {
+                        scope: Some(ContextModifier::Global),
+                        name: ObjectName::from(vec!["@a".into()]),
+                        value: Expr::value(number("1"))
+                    },
+                    SetAssignment {
+                        scope: Some(ContextModifier::Session),
+                        name: ObjectName::from(vec!["b".into()]),
+                        value: Expr::value(number("2"))
+                    },
+                    SetAssignment {
+                        scope: Some(ContextModifier::Local),
+                        name: ObjectName::from(vec!["c".into()]),
+                        value: Expr::value(number("3"))
+                    },
+                    SetAssignment {
+                        scope: None,
+                        name: ObjectName::from(vec!["d".into()]),
+                        value: Expr::value(number("4"))
+                    }
+                ]
+            );
+        }
+        _ => panic!("Expected MultipleAssignments with 4 scoped variables and 
4 values"),
+    };
+
     Ok(())
 }
 
diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs
index a9549cb6..2af93db7 100644
--- a/tests/sqlparser_hive.rs
+++ b/tests/sqlparser_hive.rs
@@ -21,10 +21,9 @@
 //! is also tested (on the inputs it can handle).
 
 use sqlparser::ast::{
-    ClusteredBy, CommentDef, ContextModifier, CreateFunction, 
CreateFunctionBody,
-    CreateFunctionUsing, CreateTable, Expr, Function, FunctionArgumentList, 
FunctionArguments,
-    Ident, ObjectName, OrderByExpr, OrderByOptions, SelectItem, Set, 
Statement, TableFactor,
-    UnaryOperator, Use, Value,
+    ClusteredBy, CommentDef, CreateFunction, CreateFunctionBody, 
CreateFunctionUsing, CreateTable,
+    Expr, Function, FunctionArgumentList, FunctionArguments, Ident, 
ObjectName, OrderByExpr,
+    OrderByOptions, SelectItem, Set, Statement, TableFactor, UnaryOperator, 
Use, Value,
 };
 use sqlparser::dialect::{GenericDialect, HiveDialect, MsSqlDialect};
 use sqlparser::parser::ParserError;
@@ -370,7 +369,7 @@ fn set_statement_with_minus() {
     assert_eq!(
         hive().verified_stmt("SET hive.tez.java.opts = -Xmx4g"),
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::None,
+            scope: None,
             hivevar: false,
             variable: ObjectName::from(vec![
                 Ident::new("hive"),
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index 4ea42efc..ca59b164 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -1252,7 +1252,7 @@ fn parse_mssql_declare() {
                 }]
             },
             Statement::Set(Set::SingleAssignment {
-                scope: ContextModifier::None,
+                scope: None,
                 hivevar: false,
                 variable: ObjectName::from(vec![Ident::new("@bar")]),
                 values: vec![Expr::Value(
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index 2767a78c..d52619d5 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -618,7 +618,7 @@ fn parse_set_variables() {
     assert_eq!(
         mysql_and_generic().verified_stmt("SET LOCAL autocommit = 1"),
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::Local,
+            scope: Some(ContextModifier::Local),
             hivevar: false,
             variable: ObjectName::from(vec!["autocommit".into()]),
             values: vec![Expr::value(number("1"))],
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index cf66af74..a6d65ec7 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -1432,7 +1432,7 @@ fn parse_set() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::None,
+            scope: None,
             hivevar: false,
             variable: ObjectName::from(vec![Ident::new("a")]),
             values: vec![Expr::Identifier(Ident {
@@ -1447,7 +1447,7 @@ fn parse_set() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::None,
+            scope: None,
             hivevar: false,
             variable: ObjectName::from(vec![Ident::new("a")]),
             values: vec![Expr::Value(
@@ -1460,7 +1460,7 @@ fn parse_set() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::None,
+            scope: None,
             hivevar: false,
             variable: ObjectName::from(vec![Ident::new("a")]),
             values: vec![Expr::value(number("0"))],
@@ -1471,7 +1471,7 @@ fn parse_set() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::None,
+            scope: None,
             hivevar: false,
             variable: ObjectName::from(vec![Ident::new("a")]),
             values: vec![Expr::Identifier(Ident::new("DEFAULT"))],
@@ -1482,7 +1482,7 @@ fn parse_set() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::Local,
+            scope: Some(ContextModifier::Local),
             hivevar: false,
             variable: ObjectName::from(vec![Ident::new("a")]),
             values: vec![Expr::Identifier("b".into())],
@@ -1493,7 +1493,7 @@ fn parse_set() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::None,
+            scope: None,
             hivevar: false,
             variable: ObjectName::from(vec![Ident::new("a"), Ident::new("b"), 
Ident::new("c")]),
             values: vec![Expr::Identifier(Ident {
@@ -1511,7 +1511,7 @@ fn parse_set() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SingleAssignment {
-            scope: ContextModifier::None,
+            scope: None,
             hivevar: false,
             variable: ObjectName::from(vec![
                 Ident::new("hive"),
@@ -1555,7 +1555,7 @@ fn parse_set_role() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SetRole {
-            context_modifier: ContextModifier::Session,
+            context_modifier: Some(ContextModifier::Session),
             role_name: None,
         })
     );
@@ -1566,7 +1566,7 @@ fn parse_set_role() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SetRole {
-            context_modifier: ContextModifier::Local,
+            context_modifier: Some(ContextModifier::Local),
             role_name: Some(Ident {
                 value: "rolename".to_string(),
                 quote_style: Some('\"'),
@@ -1581,7 +1581,7 @@ fn parse_set_role() {
     assert_eq!(
         stmt,
         Statement::Set(Set::SetRole {
-            context_modifier: ContextModifier::None,
+            context_modifier: None,
             role_name: Some(Ident {
                 value: "rolename".to_string(),
                 quote_style: Some('\''),


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to