diff --git a/src/ast/mod.rs b/src/ast/mod.rs index bccc580b3..1b4696fe6 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2889,7 +2889,7 @@ pub enum Statement { /// least MySQL and PostgreSQL. Not all MySQL-specific syntactic forms are /// supported yet. SetVariable { - local: bool, + scope: SetVariableScope, hivevar: bool, variables: OneOrManyWithParens, value: Vec, @@ -4510,15 +4510,12 @@ impl fmt::Display for Statement { write!(f, "SET{context_modifier} ROLE {role_name}") } Statement::SetVariable { - local, + scope, variables, hivevar, value, } => { - f.write_str("SET ")?; - if *local { - f.write_str("LOCAL ")?; - } + write!(f, "SET{scope} ")?; let parenthesized = matches!(variables, OneOrManyWithParens::Many(_)); write!( f, @@ -8433,6 +8430,31 @@ impl fmt::Display for SessionParamValue { } } +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum SetVariableScope { + /// LOCAL (transaction or function) scope + Local, + /// SESSION scope + Session, + /// MySQL-specific `GLOBAL` scope + Global, + /// Not specified; usually implies SESSION + None, +} + +impl fmt::Display for SetVariableScope { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Local => write!(f, " LOCAL"), + Self::Session => write!(f, " SESSION"), + Self::Global => write!(f, " GLOBAL"), + Self::None => write!(f, ""), + } + } +} + /// Snowflake StorageSerializationPolicy for Iceberg Tables /// ```sql /// [ STORAGE_SERIALIZATION_POLICY = { COMPATIBLE | OPTIMIZED } ] diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 4021b5753..efff20ec9 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -143,4 +143,8 @@ impl Dialect for GenericDialect { fn supports_string_escape_constant(&self) -> bool { true } + + fn supports_global_variable_modifier(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index b648869d2..827a588b7 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -876,10 +876,16 @@ pub trait Dialect: Debug + Any { fn supports_string_escape_constant(&self) -> bool { false } + /// Returns true if the dialect supports the table hints in the `FROM` clause. fn supports_table_hints(&self) -> bool { false } + + /// Returns true if the dialect allows the `GLOBAL` variable modifier in `SET` statements. + fn supports_global_variable_modifier(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index a67fe67b0..6eeafc839 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -125,6 +125,10 @@ impl Dialect for MySqlDialect { fn supports_table_hints(&self) -> bool { true } + + fn supports_global_variable_modifier(&self) -> bool { + true + } } /// `LOCK TABLES` diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ca858c42e..a4c8bfd5d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10533,8 +10533,17 @@ impl<'a> Parser<'a> { } pub fn parse_set(&mut self) -> Result { - let modifier = - self.parse_one_of_keywords(&[Keyword::SESSION, Keyword::LOCAL, Keyword::HIVEVAR]); + let modifier_keywords = if self.dialect.supports_global_variable_modifier() { + &[ + Keyword::SESSION, + Keyword::LOCAL, + Keyword::GLOBAL, + Keyword::HIVEVAR, + ][..] + } else { + &[Keyword::SESSION, Keyword::LOCAL, Keyword::HIVEVAR][..] + }; + let modifier = self.parse_one_of_keywords(modifier_keywords); if let Some(Keyword::HIVEVAR) = modifier { self.expect_token(&Token::Colon)?; } else if let Some(set_role_stmt) = @@ -10605,8 +10614,14 @@ impl<'a> Parser<'a> { if parenthesized_assignment { self.expect_token(&Token::RParen)?; } + let scope = match modifier { + Some(Keyword::LOCAL) => SetVariableScope::Local, + Some(Keyword::GLOBAL) => SetVariableScope::Global, + Some(Keyword::SESSION) => SetVariableScope::Session, + _ => SetVariableScope::None, + }; return Ok(Statement::SetVariable { - local: modifier == Some(Keyword::LOCAL), + scope, hivevar: Some(Keyword::HIVEVAR) == modifier, variables, value: values, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 6113a3703..8279b4b45 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8148,12 +8148,12 @@ fn parse_set_transaction() { fn parse_set_variable() { match verified_stmt("SET SOMETHING = '1'") { Statement::SetVariable { - local, + scope, hivevar, variables, value, } => { - assert!(!local); + assert_eq!(scope, SetVariableScope::None); assert!(!hivevar); assert_eq!( variables, @@ -8171,12 +8171,12 @@ fn parse_set_variable() { let sql = r#"SET (a, b, c) = (1, 2, 3)"#; match multi_variable_dialects.verified_stmt(sql) { Statement::SetVariable { - local, + scope, hivevar, variables, value, } => { - assert!(!local); + assert_eq!(scope, SetVariableScope::None); assert!(!hivevar); assert_eq!( variables, @@ -8248,12 +8248,12 @@ fn parse_set_variable() { fn parse_set_role_as_variable() { match verified_stmt("SET role = 'foobar'") { Statement::SetVariable { - local, + scope, hivevar, variables, value, } => { - assert!(!local); + assert_eq!(scope, SetVariableScope::None); assert!(!hivevar); assert_eq!( variables, @@ -8295,12 +8295,12 @@ fn parse_double_colon_cast_at_timezone() { fn parse_set_time_zone() { match verified_stmt("SET TIMEZONE = 'UTC'") { Statement::SetVariable { - local, + scope, hivevar, variables: variable, value, } => { - assert!(!local); + assert_eq!(scope, SetVariableScope::None); assert!(!hivevar); assert_eq!( variable, diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 5d710b17d..e5a576e4c 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -23,8 +23,8 @@ use sqlparser::ast::{ ClusteredBy, CommentDef, CreateFunction, CreateFunctionBody, CreateFunctionUsing, CreateTable, Expr, Function, FunctionArgumentList, FunctionArguments, Ident, ObjectName, - OneOrManyWithParens, OrderByExpr, SelectItem, Statement, TableFactor, UnaryOperator, Use, - Value, + OneOrManyWithParens, OrderByExpr, SelectItem, SetVariableScope, Statement, TableFactor, + UnaryOperator, Use, Value, }; use sqlparser::dialect::{GenericDialect, HiveDialect, MsSqlDialect}; use sqlparser::parser::ParserError; @@ -366,7 +366,7 @@ fn set_statement_with_minus() { assert_eq!( hive().verified_stmt("SET hive.tez.java.opts = -Xmx4g"), Statement::SetVariable { - local: false, + scope: SetVariableScope::None, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![ Ident::new("hive"), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 9046e9e74..9c4274e08 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1210,7 +1210,7 @@ fn parse_mssql_declare() { }] }, Statement::SetVariable { - local: false, + scope: SetVariableScope::None, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("@bar")])), value: vec![Expr::Value(Value::Number("2".parse().unwrap(), false))], diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 2e6dfc72b..93d1cd23c 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::SetVariable { - local: true, + scope: SetVariableScope::Local, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec!["autocommit".into()])), value: vec![Expr::Value(number("1"))], @@ -3244,3 +3244,9 @@ fn parse_double_precision() { "CREATE TABLE foo (bar DOUBLE(11,0))", ); } + +#[test] +fn parse_set_global() { + mysql_and_generic().verified_stmt("SET GLOBAL max_connections = 1000"); + mysql_and_generic().verified_stmt("SET @@GLOBAL.max_connections = 1000"); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index ee4aa2a0a..f9ac9fe54 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1431,7 +1431,7 @@ fn parse_set() { assert_eq!( stmt, Statement::SetVariable { - local: false, + scope: SetVariableScope::None, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])), value: vec![Expr::Identifier(Ident { @@ -1446,7 +1446,7 @@ fn parse_set() { assert_eq!( stmt, Statement::SetVariable { - local: false, + scope: SetVariableScope::None, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])), value: vec![Expr::Value(Value::SingleQuotedString("b".into()))], @@ -1457,7 +1457,7 @@ fn parse_set() { assert_eq!( stmt, Statement::SetVariable { - local: false, + scope: SetVariableScope::None, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])), value: vec![Expr::Value(number("0"))], @@ -1468,7 +1468,7 @@ fn parse_set() { assert_eq!( stmt, Statement::SetVariable { - local: false, + scope: SetVariableScope::None, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])), value: vec![Expr::Identifier(Ident::new("DEFAULT"))], @@ -1479,7 +1479,7 @@ fn parse_set() { assert_eq!( stmt, Statement::SetVariable { - local: true, + scope: SetVariableScope::Local, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])), value: vec![Expr::Identifier("b".into())], @@ -1490,7 +1490,7 @@ fn parse_set() { assert_eq!( stmt, Statement::SetVariable { - local: false, + scope: SetVariableScope::None, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![ Ident::new("a"), @@ -1512,7 +1512,7 @@ fn parse_set() { assert_eq!( stmt, Statement::SetVariable { - local: false, + scope: SetVariableScope::None, hivevar: false, variables: OneOrManyWithParens::One(ObjectName::from(vec![ Ident::new("hive"), @@ -1526,7 +1526,7 @@ fn parse_set() { ); pg_and_generic().one_statement_parses_to("SET a TO b", "SET a = b"); - pg_and_generic().one_statement_parses_to("SET SESSION a = b", "SET a = b"); + pg_and_generic().verified_stmt("SET SESSION a = b"); assert_eq!( pg_and_generic().parse_sql_statements("SET"),