Skip to content

Parse SET GLOBAL variable modifier for MySQL #1696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ObjectName>,
value: Vec<Expr>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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, ""),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it work the same to define scope as an optional field (scope: Option<SetVariableScope>)? in order to represent None by convention?

}
}
}

/// Snowflake StorageSerializationPolicy for Iceberg Tables
/// ```sql
/// [ STORAGE_SERIALIZATION_POLICY = { COMPATIBLE | OPTIMIZED } ]
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,8 @@ impl Dialect for GenericDialect {
fn supports_string_escape_constant(&self) -> bool {
true
}

fn supports_global_variable_modifier(&self) -> bool {
true
}
}
6 changes: 6 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
21 changes: 18 additions & 3 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10533,8 +10533,17 @@ impl<'a> Parser<'a> {
}

pub fn parse_set(&mut self) -> Result<Statement, ParserError> {
let modifier =
self.parse_one_of_keywords(&[Keyword::SESSION, Keyword::LOCAL, Keyword::HIVEVAR]);
let modifier_keywords = if self.dialect.supports_global_variable_modifier() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking having the lone global keyword as a dialect flag might not scale API wise, to make it dialect specific maybe it might make sense to have delegate to the dialect? like with parse_column_option as an example except with a default implementation that others like hive and mysql can override.
e.g.

let scope = self.dialect.parse_set_variable_scope(self)?;

&[
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) =
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 8 additions & 8 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions tests/sqlparser_hive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"),
Expand Down
2 changes: 1 addition & 1 deletion tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))],
Expand Down
8 changes: 7 additions & 1 deletion tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))],
Expand Down Expand Up @@ -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");
}
16 changes: 8 additions & 8 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()))],
Expand All @@ -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"))],
Expand All @@ -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"))],
Expand All @@ -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())],
Expand All @@ -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"),
Expand All @@ -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"),
Expand All @@ -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"),
Expand Down