SET with a list of comma separated assignments (#1757)

This commit is contained in:
Mohamed Abdeen 2025-03-12 22:02:39 +02:00 committed by GitHub
parent 3392623b00
commit 85f855150f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 564 additions and 375 deletions

View file

@ -2394,6 +2394,168 @@ pub enum CreatePolicyCommand {
Delete,
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum Set {
/// SQL Standard-style
/// SET a = 1;
SingleAssignment {
local: bool,
hivevar: bool,
variable: ObjectName,
values: Vec<Expr>,
},
/// Snowflake-style
/// SET (a, b, ..) = (1, 2, ..);
ParenthesizedAssignments {
variables: Vec<ObjectName>,
values: Vec<Expr>,
},
/// MySQL-style
/// SET a = 1, b = 2, ..;
MultipleAssignments { assignments: Vec<SetAssignment> },
/// MS-SQL session
///
/// See <https://learn.microsoft.com/en-us/sql/t-sql/statements/set-statements-transact-sql>
SetSessionParam(SetSessionParamKind),
/// ```sql
/// SET [ SESSION | LOCAL ] ROLE role_name
/// ```
///
/// Sets session state. Examples: [ANSI][1], [Postgresql][2], [MySQL][3], and [Oracle][4]
///
/// [1]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#set-role-statement
/// [2]: https://www.postgresql.org/docs/14/sql-set-role.html
/// [3]: https://dev.mysql.com/doc/refman/8.0/en/set-role.html
/// [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,
/// Role name. If NONE is specified, then the current role name is removed.
role_name: Option<Ident>,
},
/// ```sql
/// SET TIME ZONE <value>
/// ```
///
/// Note: this is a PostgreSQL-specific statements
/// `SET TIME ZONE <value>` is an alias for `SET timezone TO <value>` in PostgreSQL
/// However, we allow it for all dialects.
SetTimeZone { local: bool, value: Expr },
/// ```sql
/// SET NAMES 'charset_name' [COLLATE 'collation_name']
/// ```
SetNames {
charset_name: Ident,
collation_name: Option<String>,
},
/// ```sql
/// SET NAMES DEFAULT
/// ```
///
/// Note: this is a MySQL-specific statement.
SetNamesDefault {},
/// ```sql
/// SET TRANSACTION ...
/// ```
SetTransaction {
modes: Vec<TransactionMode>,
snapshot: Option<Value>,
session: bool,
},
}
impl Display for Set {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::ParenthesizedAssignments { variables, values } => write!(
f,
"SET ({}) = ({})",
display_comma_separated(variables),
display_comma_separated(values)
),
Self::MultipleAssignments { assignments } => {
write!(f, "SET {}", display_comma_separated(assignments))
}
Self::SetRole {
context_modifier,
role_name,
} => {
let role_name = role_name.clone().unwrap_or_else(|| Ident::new("NONE"));
write!(f, "SET{context_modifier} ROLE {role_name}")
}
Self::SetSessionParam(kind) => write!(f, "SET {kind}"),
Self::SetTransaction {
modes,
snapshot,
session,
} => {
if *session {
write!(f, "SET SESSION CHARACTERISTICS AS TRANSACTION")?;
} else {
write!(f, "SET TRANSACTION")?;
}
if !modes.is_empty() {
write!(f, " {}", display_comma_separated(modes))?;
}
if let Some(snapshot_id) = snapshot {
write!(f, " SNAPSHOT {snapshot_id}")?;
}
Ok(())
}
Self::SetTimeZone { local, value } => {
f.write_str("SET ")?;
if *local {
f.write_str("LOCAL ")?;
}
write!(f, "TIME ZONE {value}")
}
Self::SetNames {
charset_name,
collation_name,
} => {
write!(f, "SET NAMES {}", charset_name)?;
if let Some(collation) = collation_name {
f.write_str(" COLLATE ")?;
f.write_str(collation)?;
};
Ok(())
}
Self::SetNamesDefault {} => {
f.write_str("SET NAMES DEFAULT")?;
Ok(())
}
Set::SingleAssignment {
local,
hivevar,
variable,
values,
} => {
write!(
f,
"SET {}{}{} = {}",
if *local { "LOCAL " } else { "" },
if *hivevar { "HIVEVAR:" } else { "" },
variable,
display_comma_separated(values)
)
}
}
}
}
/// Convert a `Set` into a `Statement`.
/// Convenience function, instead of writing `Statement::Set(Set::Set...{...})`
impl From<Set> for Statement {
fn from(set: Set) -> Self {
Statement::Set(set)
}
}
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@ -2419,6 +2581,7 @@ pub enum Statement {
compute_statistics: bool,
has_table_keyword: bool,
},
Set(Set),
/// ```sql
/// TRUNCATE
/// ```
@ -2846,7 +3009,10 @@ pub enum Statement {
/// DROP CONNECTOR
/// ```
/// See [Hive](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362034#LanguageManualDDL-DropConnector)
DropConnector { if_exists: bool, name: Ident },
DropConnector {
if_exists: bool,
name: Ident,
},
/// ```sql
/// DECLARE
/// ```
@ -2854,7 +3020,9 @@ pub enum Statement {
///
/// Note: this is a PostgreSQL-specific statement,
/// but may also compatible with other SQL.
Declare { stmts: Vec<Declare> },
Declare {
stmts: Vec<Declare>,
},
/// ```sql
/// CREATE EXTENSION [ IF NOT EXISTS ] extension_name
/// [ WITH ] [ SCHEMA schema_name ]
@ -2916,67 +3084,23 @@ pub enum Statement {
///
/// Note: this is a PostgreSQL-specific statement,
/// but may also compatible with other SQL.
Discard { object_type: DiscardObject },
/// ```sql
/// SET [ SESSION | LOCAL ] ROLE role_name
/// ```
///
/// Sets session state. Examples: [ANSI][1], [Postgresql][2], [MySQL][3], and [Oracle][4]
///
/// [1]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#set-role-statement
/// [2]: https://www.postgresql.org/docs/14/sql-set-role.html
/// [3]: https://dev.mysql.com/doc/refman/8.0/en/set-role.html
/// [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,
/// Role name. If NONE is specified, then the current role name is removed.
role_name: Option<Ident>,
Discard {
object_type: DiscardObject,
},
/// ```sql
/// SET <variable> = expression;
/// SET (variable[, ...]) = (expression[, ...]);
/// ```
///
/// Note: this is not a standard SQL statement, but it is supported by at
/// least MySQL and PostgreSQL. Not all MySQL-specific syntactic forms are
/// supported yet.
SetVariable {
local: bool,
hivevar: bool,
variables: OneOrManyWithParens<ObjectName>,
value: Vec<Expr>,
},
/// ```sql
/// SET TIME ZONE <value>
/// ```
///
/// Note: this is a PostgreSQL-specific statements
/// `SET TIME ZONE <value>` is an alias for `SET timezone TO <value>` in PostgreSQL
SetTimeZone { local: bool, value: Expr },
/// ```sql
/// SET NAMES 'charset_name' [COLLATE 'collation_name']
/// ```
SetNames {
charset_name: Ident,
collation_name: Option<String>,
},
/// ```sql
/// SET NAMES DEFAULT
/// ```
///
/// Note: this is a MySQL-specific statement.
SetNamesDefault {},
/// `SHOW FUNCTIONS`
///
/// Note: this is a Presto-specific statement.
ShowFunctions { filter: Option<ShowStatementFilter> },
ShowFunctions {
filter: Option<ShowStatementFilter>,
},
/// ```sql
/// SHOW <variable>
/// ```
///
/// Note: this is a PostgreSQL-specific statement.
ShowVariable { variable: Vec<Ident> },
ShowVariable {
variable: Vec<Ident>,
},
/// ```sql
/// SHOW [GLOBAL | SESSION] STATUS [LIKE 'pattern' | WHERE expr]
/// ```
@ -3060,7 +3184,9 @@ pub enum Statement {
/// ```
///
/// Note: this is a MySQL-specific statement.
ShowCollation { filter: Option<ShowStatementFilter> },
ShowCollation {
filter: Option<ShowStatementFilter>,
},
/// ```sql
/// `USE ...`
/// ```
@ -3103,14 +3229,6 @@ pub enum Statement {
has_end_keyword: bool,
},
/// ```sql
/// SET TRANSACTION ...
/// ```
SetTransaction {
modes: Vec<TransactionMode>,
snapshot: Option<Value>,
session: bool,
},
/// ```sql
/// COMMENT ON ...
/// ```
///
@ -3329,7 +3447,10 @@ pub enum Statement {
/// ```
///
/// Note: this is a PostgreSQL-specific statement.
Deallocate { name: Ident, prepare: bool },
Deallocate {
name: Ident,
prepare: bool,
},
/// ```sql
/// An `EXECUTE` statement
/// ```
@ -3415,11 +3536,15 @@ pub enum Statement {
/// SAVEPOINT
/// ```
/// Define a new savepoint within the current transaction
Savepoint { name: Ident },
Savepoint {
name: Ident,
},
/// ```sql
/// RELEASE [ SAVEPOINT ] savepoint_name
/// ```
ReleaseSavepoint { name: Ident },
ReleaseSavepoint {
name: Ident,
},
/// A `MERGE` statement.
///
/// ```sql
@ -3499,7 +3624,9 @@ pub enum Statement {
/// LOCK TABLES <table_name> [READ [LOCAL] | [LOW_PRIORITY] WRITE]
/// ```
/// Note: this is a MySQL-specific statement. See <https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html>
LockTables { tables: Vec<LockTable> },
LockTables {
tables: Vec<LockTable>,
},
/// ```sql
/// UNLOCK TABLES
/// ```
@ -3533,14 +3660,18 @@ pub enum Statement {
/// listen for a notification channel
///
/// See Postgres <https://www.postgresql.org/docs/current/sql-listen.html>
LISTEN { channel: Ident },
LISTEN {
channel: Ident,
},
/// ```sql
/// UNLISTEN
/// ```
/// stop listening for a notification
///
/// See Postgres <https://www.postgresql.org/docs/current/sql-unlisten.html>
UNLISTEN { channel: Ident },
UNLISTEN {
channel: Ident,
},
/// ```sql
/// NOTIFY channel [ , payload ]
/// ```
@ -3580,10 +3711,6 @@ pub enum Statement {
/// Snowflake `REMOVE`
/// See: <https://docs.snowflake.com/en/sql-reference/sql/remove>
Remove(FileStagingCommand),
/// MS-SQL session
///
/// See <https://learn.microsoft.com/en-us/sql/t-sql/statements/set-statements-transact-sql>
SetSessionParam(SetSessionParamKind),
/// RaiseError (MSSQL)
/// RAISERROR ( { msg_id | msg_str | @local_variable }
/// { , severity , state }
@ -4644,59 +4771,7 @@ impl fmt::Display for Statement {
write!(f, "DISCARD {object_type}")?;
Ok(())
}
Self::SetRole {
context_modifier,
role_name,
} => {
let role_name = role_name.clone().unwrap_or_else(|| Ident::new("NONE"));
write!(f, "SET{context_modifier} ROLE {role_name}")
}
Statement::SetVariable {
local,
variables,
hivevar,
value,
} => {
f.write_str("SET ")?;
if *local {
f.write_str("LOCAL ")?;
}
let parenthesized = matches!(variables, OneOrManyWithParens::Many(_));
write!(
f,
"{hivevar}{name} = {l_paren}{value}{r_paren}",
hivevar = if *hivevar { "HIVEVAR:" } else { "" },
name = variables,
l_paren = parenthesized.then_some("(").unwrap_or_default(),
value = display_comma_separated(value),
r_paren = parenthesized.then_some(")").unwrap_or_default(),
)
}
Statement::SetTimeZone { local, value } => {
f.write_str("SET ")?;
if *local {
f.write_str("LOCAL ")?;
}
write!(f, "TIME ZONE {value}")
}
Statement::SetNames {
charset_name,
collation_name,
} => {
write!(f, "SET NAMES {}", charset_name)?;
if let Some(collation) = collation_name {
f.write_str(" COLLATE ")?;
f.write_str(collation)?;
};
Ok(())
}
Statement::SetNamesDefault {} => {
f.write_str("SET NAMES DEFAULT")?;
Ok(())
}
Self::Set(set) => write!(f, "{set}"),
Statement::ShowVariable { variable } => {
write!(f, "SHOW")?;
if !variable.is_empty() {
@ -4885,24 +4960,6 @@ impl fmt::Display for Statement {
}
Ok(())
}
Statement::SetTransaction {
modes,
snapshot,
session,
} => {
if *session {
write!(f, "SET SESSION CHARACTERISTICS AS TRANSACTION")?;
} else {
write!(f, "SET TRANSACTION")?;
}
if !modes.is_empty() {
write!(f, " {}", display_comma_separated(modes))?;
}
if let Some(snapshot_id) = snapshot {
write!(f, " SNAPSHOT {snapshot_id}")?;
}
Ok(())
}
Statement::Commit {
chain,
end: end_syntax,
@ -5333,7 +5390,6 @@ impl fmt::Display for Statement {
Statement::List(command) => write!(f, "LIST {command}"),
Statement::Remove(command) => write!(f, "REMOVE {command}"),
Statement::SetSessionParam(kind) => write!(f, "SET {kind}"),
}
}
}
@ -5397,6 +5453,21 @@ impl fmt::Display for SequenceOptions {
}
}
/// Assignment for a `SET` statement (name [=|TO] value)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct SetAssignment {
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)
}
}
/// Target of a `TRUNCATE TABLE` command
///
/// Note this is its own struct because `visit_relation` requires an `ObjectName` (not a `Vec<ObjectName>`)

View file

@ -230,11 +230,7 @@ impl Spanned for Values {
/// - [Statement::Fetch]
/// - [Statement::Flush]
/// - [Statement::Discard]
/// - [Statement::SetRole]
/// - [Statement::SetVariable]
/// - [Statement::SetTimeZone]
/// - [Statement::SetNames]
/// - [Statement::SetNamesDefault]
/// - [Statement::Set]
/// - [Statement::ShowFunctions]
/// - [Statement::ShowVariable]
/// - [Statement::ShowStatus]
@ -244,7 +240,6 @@ impl Spanned for Values {
/// - [Statement::ShowTables]
/// - [Statement::ShowCollation]
/// - [Statement::StartTransaction]
/// - [Statement::SetTransaction]
/// - [Statement::Comment]
/// - [Statement::Commit]
/// - [Statement::Rollback]
@ -445,11 +440,7 @@ impl Spanned for Statement {
Statement::Fetch { .. } => Span::empty(),
Statement::Flush { .. } => Span::empty(),
Statement::Discard { .. } => Span::empty(),
Statement::SetRole { .. } => Span::empty(),
Statement::SetVariable { .. } => Span::empty(),
Statement::SetTimeZone { .. } => Span::empty(),
Statement::SetNames { .. } => Span::empty(),
Statement::SetNamesDefault {} => Span::empty(),
Statement::Set(_) => Span::empty(),
Statement::ShowFunctions { .. } => Span::empty(),
Statement::ShowVariable { .. } => Span::empty(),
Statement::ShowStatus { .. } => Span::empty(),
@ -460,7 +451,6 @@ impl Spanned for Statement {
Statement::ShowCollation { .. } => Span::empty(),
Statement::Use(u) => u.span(),
Statement::StartTransaction { .. } => Span::empty(),
Statement::SetTransaction { .. } => Span::empty(),
Statement::Comment { .. } => Span::empty(),
Statement::Commit { .. } => Span::empty(),
Statement::Rollback { .. } => Span::empty(),
@ -509,7 +499,6 @@ impl Spanned for Statement {
Statement::RenameTable { .. } => Span::empty(),
Statement::RaisError { .. } => Span::empty(),
Statement::List(..) | Statement::Remove(..) => Span::empty(),
Statement::SetSessionParam { .. } => Span::empty(),
}
}
}

View file

@ -399,6 +399,16 @@ pub trait Dialect: Debug + Any {
false
}
/// Returns true if the dialect supports multiple `SET` statements
/// in a single statement.
///
/// ```sql
/// SET variable = expression [, variable = expression];
/// ```
fn supports_comma_separated_set_assignments(&self) -> bool {
false
}
/// Returns true if the dialect supports an `EXCEPT` clause following a
/// wildcard in a select list.
///

View file

@ -82,6 +82,7 @@ impl Dialect for MsSqlDialect {
fn supports_start_transaction_modifier(&self) -> bool {
true
}
fn supports_end_transaction_modifier(&self) -> bool {
true
}

View file

@ -141,6 +141,10 @@ impl Dialect for MySqlDialect {
fn supports_set_names(&self) -> bool {
true
}
fn supports_comma_separated_set_assignments(&self) -> bool {
true
}
}
/// `LOCK TABLES`

View file

@ -173,6 +173,7 @@ define_keywords!(
CHANNEL,
CHAR,
CHARACTER,
CHARACTERISTICS,
CHARACTERS,
CHARACTER_LENGTH,
CHARSET,
@ -557,6 +558,7 @@ define_keywords!(
MULTISET,
MUTATION,
NAME,
NAMES,
NANOSECOND,
NANOSECONDS,
NATIONAL,

View file

@ -4314,7 +4314,8 @@ impl<'a> Parser<'a> {
}
/// Run a parser method `f`, reverting back to the current position if unsuccessful.
/// Returns `None` if `f` returns an error
/// Returns `ParserError::RecursionLimitExceeded` if `f` returns a `RecursionLimitExceeded`.
/// Returns `Ok(None)` if `f` returns any other error.
pub fn maybe_parse<T, F>(&mut self, f: F) -> Result<Option<T>, ParserError>
where
F: FnMut(&mut Parser) -> Result<T, ParserError>,
@ -10978,47 +10979,108 @@ impl<'a> Parser<'a> {
} else {
Some(self.parse_identifier()?)
};
Ok(Statement::SetRole {
Ok(Statement::Set(Set::SetRole {
context_modifier,
role_name,
})
}))
}
pub fn parse_set(&mut self) -> Result<Statement, ParserError> {
let modifier =
self.parse_one_of_keywords(&[Keyword::SESSION, Keyword::LOCAL, Keyword::HIVEVAR]);
if let Some(Keyword::HIVEVAR) = modifier {
self.expect_token(&Token::Colon)?;
} else if let Some(set_role_stmt) =
self.maybe_parse(|parser| parser.parse_set_role(modifier))?
{
return Ok(set_role_stmt);
fn parse_set_values(
&mut self,
parenthesized_assignment: bool,
) -> Result<Vec<Expr>, ParserError> {
let mut values = vec![];
if parenthesized_assignment {
self.expect_token(&Token::LParen)?;
}
let variables = if self.parse_keywords(&[Keyword::TIME, Keyword::ZONE]) {
OneOrManyWithParens::One(ObjectName::from(vec!["TIMEZONE".into()]))
} else if self.dialect.supports_parenthesized_set_variables()
loop {
let value = if let Some(expr) = self.try_parse_expr_sub_query()? {
expr
} else if let Ok(expr) = self.parse_expr() {
expr
} else {
self.expected("variable value", self.peek_token())?
};
values.push(value);
if self.consume_token(&Token::Comma) {
continue;
}
if parenthesized_assignment {
self.expect_token(&Token::RParen)?;
}
return Ok(values);
}
}
fn parse_set_assignment(
&mut self,
) -> Result<(OneOrManyWithParens<ObjectName>, Expr), ParserError> {
let variables = if self.dialect.supports_parenthesized_set_variables()
&& self.consume_token(&Token::LParen)
{
let variables = OneOrManyWithParens::Many(
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)?;
variables
vars
} else {
OneOrManyWithParens::One(self.parse_object_name(false)?)
};
let names = matches!(&variables, OneOrManyWithParens::One(variable) if variable.to_string().eq_ignore_ascii_case("NAMES"));
if !(self.consume_token(&Token::Eq) || self.parse_keyword(Keyword::TO)) {
return self.expected("assignment operator", self.peek_token());
}
if names && self.dialect.supports_set_names() {
if self.parse_keyword(Keyword::DEFAULT) {
return Ok(Statement::SetNamesDefault {});
let values = self.parse_expr()?;
Ok((variables, values))
}
fn parse_set(&mut self) -> Result<Statement, ParserError> {
let modifier =
self.parse_one_of_keywords(&[Keyword::SESSION, Keyword::LOCAL, Keyword::HIVEVAR]);
if let Some(Keyword::HIVEVAR) = modifier {
self.expect_token(&Token::Colon)?;
}
if let Some(set_role_stmt) = self.maybe_parse(|parser| parser.parse_set_role(modifier))? {
return Ok(set_role_stmt);
}
// Handle special cases first
if self.parse_keywords(&[Keyword::TIME, Keyword::ZONE])
|| self.parse_keyword(Keyword::TIMEZONE)
{
if self.consume_token(&Token::Eq) || self.parse_keyword(Keyword::TO) {
return Ok(Set::SingleAssignment {
local: modifier == Some(Keyword::LOCAL),
hivevar: modifier == Some(Keyword::HIVEVAR),
variable: ObjectName::from(vec!["TIMEZONE".into()]),
values: self.parse_set_values(false)?,
}
.into());
} else {
// A shorthand alias for SET TIME ZONE that doesn't require
// 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),
value: self.parse_expr()?,
}
.into());
}
} else if self.dialect.supports_set_names() && self.parse_keyword(Keyword::NAMES) {
if self.parse_keyword(Keyword::DEFAULT) {
return Ok(Set::SetNamesDefault {}.into());
}
let charset_name = self.parse_identifier()?;
let collation_name = if self.parse_one_of_keywords(&[Keyword::COLLATE]).is_some() {
Some(self.parse_literal_string()?)
@ -11026,86 +11088,117 @@ impl<'a> Parser<'a> {
None
};
return Ok(Statement::SetNames {
return Ok(Set::SetNames {
charset_name,
collation_name,
});
}
let parenthesized_assignment = matches!(&variables, OneOrManyWithParens::Many(_));
if self.consume_token(&Token::Eq) || self.parse_keyword(Keyword::TO) {
if parenthesized_assignment {
self.expect_token(&Token::LParen)?;
}
let mut values = vec![];
loop {
let value = if let Some(expr) = self.try_parse_expr_sub_query()? {
expr
} else if let Ok(expr) = self.parse_expr() {
expr
} else {
self.expected("variable value", self.peek_token())?
};
values.push(value);
if self.consume_token(&Token::Comma) {
continue;
}
if parenthesized_assignment {
self.expect_token(&Token::RParen)?;
}
return Ok(Statement::SetVariable {
local: modifier == Some(Keyword::LOCAL),
hivevar: Some(Keyword::HIVEVAR) == modifier,
variables,
value: values,
});
}
}
let OneOrManyWithParens::One(variable) = variables else {
return self.expected("set variable", self.peek_token());
};
if variable.to_string().eq_ignore_ascii_case("TIMEZONE") {
// for some db (e.g. postgresql), SET TIME ZONE <value> is an alias for SET TIMEZONE [TO|=] <value>
match self.parse_expr() {
Ok(expr) => Ok(Statement::SetTimeZone {
local: modifier == Some(Keyword::LOCAL),
value: expr,
}),
_ => self.expected("timezone value", self.peek_token())?,
}
} else if variable.to_string() == "CHARACTERISTICS" {
.into());
} else if self.parse_keyword(Keyword::CHARACTERISTICS) {
self.expect_keywords(&[Keyword::AS, Keyword::TRANSACTION])?;
Ok(Statement::SetTransaction {
return Ok(Set::SetTransaction {
modes: self.parse_transaction_modes()?,
snapshot: None,
session: true,
})
} else if variable.to_string() == "TRANSACTION" && modifier.is_none() {
}
.into());
} else if self.parse_keyword(Keyword::TRANSACTION) {
if self.parse_keyword(Keyword::SNAPSHOT) {
let snapshot_id = self.parse_value()?.value;
return Ok(Statement::SetTransaction {
return Ok(Set::SetTransaction {
modes: vec![],
snapshot: Some(snapshot_id),
session: false,
});
}
.into());
}
Ok(Statement::SetTransaction {
return Ok(Set::SetTransaction {
modes: self.parse_transaction_modes()?,
snapshot: None,
session: false,
})
} else if self.dialect.supports_set_stmt_without_operator() {
self.prev_token();
self.parse_set_session_params()
} else {
self.expected("equals sign or TO", self.peek_token())
}
.into());
}
if self.dialect.supports_comma_separated_set_assignments() {
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()),
}?;
Ok(Set::SingleAssignment {
local: modifier == Some(Keyword::LOCAL),
hivevar: modifier == Some(Keyword::HIVEVAR),
variable,
values,
}
.into())
};
}
}
let variables = 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
} else {
OneOrManyWithParens::One(self.parse_object_name(false)?)
};
if self.consume_token(&Token::Eq) || self.parse_keyword(Keyword::TO) {
let stmt = match variables {
OneOrManyWithParens::One(var) => Set::SingleAssignment {
local: modifier == Some(Keyword::LOCAL),
hivevar: modifier == Some(Keyword::HIVEVAR),
variable: var,
values: self.parse_set_values(false)?,
},
OneOrManyWithParens::Many(vars) => Set::ParenthesizedAssignments {
variables: vars,
values: self.parse_set_values(true)?,
},
};
return Ok(stmt.into());
}
if self.dialect.supports_set_stmt_without_operator() {
self.prev_token();
return self.parse_set_session_params();
};
self.expected("equals sign or TO", self.peek_token())
}
pub fn parse_set_session_params(&mut self) -> Result<Statement, ParserError> {
@ -11123,15 +11216,20 @@ impl<'a> Parser<'a> {
_ => return self.expected("IO, PROFILE, TIME or XML", self.peek_token()),
};
let value = self.parse_session_param_value()?;
Ok(Statement::SetSessionParam(SetSessionParamKind::Statistics(
SetSessionParamStatistics { topic, value },
)))
Ok(
Set::SetSessionParam(SetSessionParamKind::Statistics(SetSessionParamStatistics {
topic,
value,
}))
.into(),
)
} else if self.parse_keyword(Keyword::IDENTITY_INSERT) {
let obj = self.parse_object_name(false)?;
let value = self.parse_session_param_value()?;
Ok(Statement::SetSessionParam(
SetSessionParamKind::IdentityInsert(SetSessionParamIdentityInsert { obj, value }),
Ok(Set::SetSessionParam(SetSessionParamKind::IdentityInsert(
SetSessionParamIdentityInsert { obj, value },
))
.into())
} else if self.parse_keyword(Keyword::OFFSETS) {
let keywords = self.parse_comma_separated(|parser| {
let next_token = parser.next_token();
@ -11141,9 +11239,13 @@ impl<'a> Parser<'a> {
}
})?;
let value = self.parse_session_param_value()?;
Ok(Statement::SetSessionParam(SetSessionParamKind::Offsets(
SetSessionParamOffsets { keywords, value },
)))
Ok(
Set::SetSessionParam(SetSessionParamKind::Offsets(SetSessionParamOffsets {
keywords,
value,
}))
.into(),
)
} else {
let names = self.parse_comma_separated(|parser| {
let next_token = parser.next_token();
@ -11153,9 +11255,13 @@ impl<'a> Parser<'a> {
}
})?;
let value = self.parse_expr()?.to_string();
Ok(Statement::SetSessionParam(SetSessionParamKind::Generic(
SetSessionParamGeneric { names, value },
)))
Ok(
Set::SetSessionParam(SetSessionParamKind::Generic(SetSessionParamGeneric {
names,
value,
}))
.into(),
)
}
}

View file

@ -8555,11 +8555,11 @@ fn parse_set_transaction() {
// TRANSACTION, so no need to duplicate the tests here. We just do a quick
// sanity check.
match verified_stmt("SET TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE") {
Statement::SetTransaction {
Statement::Set(Set::SetTransaction {
modes,
session,
snapshot,
} => {
}) => {
assert_eq!(
modes,
vec![
@ -8578,20 +8578,17 @@ fn parse_set_transaction() {
#[test]
fn parse_set_variable() {
match verified_stmt("SET SOMETHING = '1'") {
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local,
hivevar,
variables,
value,
} => {
variable,
values,
}) => {
assert!(!local);
assert!(!hivevar);
assert_eq!(variable, ObjectName::from(vec!["SOMETHING".into()]));
assert_eq!(
variables,
OneOrManyWithParens::One(ObjectName::from(vec!["SOMETHING".into()]))
);
assert_eq!(
value,
values,
vec![Expr::Value(
(Value::SingleQuotedString("1".into())).with_empty_span()
)]
@ -8603,24 +8600,17 @@ fn parse_set_variable() {
let multi_variable_dialects = all_dialects_where(|d| d.supports_parenthesized_set_variables());
let sql = r#"SET (a, b, c) = (1, 2, 3)"#;
match multi_variable_dialects.verified_stmt(sql) {
Statement::SetVariable {
local,
hivevar,
variables,
value,
} => {
assert!(!local);
assert!(!hivevar);
Statement::Set(Set::ParenthesizedAssignments { variables, values }) => {
assert_eq!(
variables,
OneOrManyWithParens::Many(vec![
vec![
ObjectName::from(vec!["a".into()]),
ObjectName::from(vec!["b".into()]),
ObjectName::from(vec!["c".into()]),
])
]
);
assert_eq!(
value,
values,
vec![
Expr::value(number("1")),
Expr::value(number("2")),
@ -8680,20 +8670,17 @@ fn parse_set_variable() {
#[test]
fn parse_set_role_as_variable() {
match verified_stmt("SET role = 'foobar'") {
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local,
hivevar,
variables,
value,
} => {
variable,
values,
}) => {
assert!(!local);
assert!(!hivevar);
assert_eq!(variable, ObjectName::from(vec!["role".into()]));
assert_eq!(
variables,
OneOrManyWithParens::One(ObjectName::from(vec!["role".into()]))
);
assert_eq!(
value,
values,
vec![Expr::Value(
(Value::SingleQuotedString("foobar".into())).with_empty_span()
)]
@ -8730,20 +8717,17 @@ fn parse_double_colon_cast_at_timezone() {
#[test]
fn parse_set_time_zone() {
match verified_stmt("SET TIMEZONE = 'UTC'") {
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local,
hivevar,
variables: variable,
value,
} => {
variable,
values,
}) => {
assert!(!local);
assert!(!hivevar);
assert_eq!(variable, ObjectName::from(vec!["TIMEZONE".into()]));
assert_eq!(
variable,
OneOrManyWithParens::One(ObjectName::from(vec!["TIMEZONE".into()]))
);
assert_eq!(
value,
values,
vec![Expr::Value(
(Value::SingleQuotedString("UTC".into())).with_empty_span()
)]
@ -8755,20 +8739,6 @@ fn parse_set_time_zone() {
one_statement_parses_to("SET TIME ZONE TO 'UTC'", "SET TIMEZONE = 'UTC'");
}
#[test]
fn parse_set_time_zone_alias() {
match verified_stmt("SET TIME ZONE 'UTC'") {
Statement::SetTimeZone { local, value } => {
assert!(!local);
assert_eq!(
value,
Expr::Value((Value::SingleQuotedString("UTC".into())).with_empty_span())
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_commit() {
match verified_stmt("COMMIT") {
@ -14681,3 +14651,44 @@ fn parse_set_names() {
dialects.verified_stmt("SET NAMES 'utf8'");
dialects.verified_stmt("SET NAMES UTF8 COLLATE bogus");
}
#[test]
fn parse_multiple_set_statements() -> Result<(), ParserError> {
let dialects = all_dialects_where(|d| d.supports_comma_separated_set_assignments());
let stmt = dialects.verified_stmt("SET @a = 1, b = 2");
match stmt {
Statement::Set(Set::MultipleAssignments { assignments }) => {
assert_eq!(
assignments,
vec![
SetAssignment {
name: ObjectName::from(vec!["@a".into()]),
value: Expr::value(number("1"))
},
SetAssignment {
name: ObjectName::from(vec!["b".into()]),
value: Expr::value(number("2"))
}
]
);
}
_ => panic!("Expected SetVariable with 2 variables and 2 values"),
};
Ok(())
}
#[test]
fn parse_set_time_zone_alias() {
match all_dialects().verified_stmt("SET TIME ZONE 'UTC'") {
Statement::Set(Set::SetTimeZone { local, value }) => {
assert!(!local);
assert_eq!(
value,
Expr::Value((Value::SingleQuotedString("UTC".into())).with_empty_span())
);
}
_ => unreachable!(),
}
}

View file

@ -22,9 +22,8 @@
use sqlparser::ast::{
ClusteredBy, CommentDef, CreateFunction, CreateFunctionBody, CreateFunctionUsing, CreateTable,
Expr, Function, FunctionArgumentList, FunctionArguments, Ident, ObjectName,
OneOrManyWithParens, OrderByExpr, OrderByOptions, SelectItem, Statement, TableFactor,
UnaryOperator, Use, Value,
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;
@ -92,7 +91,7 @@ fn parse_msck() {
}
#[test]
fn parse_set() {
fn parse_set_hivevar() {
let set = "SET HIVEVAR:name = a, b, c_d";
hive().verified_stmt(set);
}
@ -369,20 +368,20 @@ fn from_cte() {
fn set_statement_with_minus() {
assert_eq!(
hive().verified_stmt("SET hive.tez.java.opts = -Xmx4g"),
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: false,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![
variable: ObjectName::from(vec![
Ident::new("hive"),
Ident::new("tez"),
Ident::new("java"),
Ident::new("opts")
])),
value: vec![Expr::UnaryOp {
]),
values: vec![Expr::UnaryOp {
op: UnaryOperator::Minus,
expr: Box::new(Expr::Identifier(Ident::new("Xmx4g")))
}],
}
})
);
assert_eq!(

View file

@ -1254,14 +1254,14 @@ fn parse_mssql_declare() {
for_query: None
}]
},
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: false,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("@bar")])),
value: vec![Expr::Value(
variable: ObjectName::from(vec![Ident::new("@bar")]),
values: vec![Expr::Value(
(Value::Number("2".parse().unwrap(), false)).with_empty_span()
)],
},
}),
Statement::Query(Box::new(Query {
with: None,
limit: None,

View file

@ -617,12 +617,12 @@ fn parse_set_variables() {
mysql_and_generic().verified_stmt("SET sql_mode = CONCAT(@@sql_mode, ',STRICT_TRANS_TABLES')");
assert_eq!(
mysql_and_generic().verified_stmt("SET LOCAL autocommit = 1"),
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: true,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec!["autocommit".into()])),
value: vec![Expr::value(number("1"))],
}
variable: ObjectName::from(vec!["autocommit".into()]),
values: vec![Expr::value(number("1"))],
})
);
}
@ -2705,19 +2705,19 @@ fn parse_set_names() {
let stmt = mysql_and_generic().verified_stmt("SET NAMES utf8mb4");
assert_eq!(
stmt,
Statement::SetNames {
Statement::Set(Set::SetNames {
charset_name: "utf8mb4".into(),
collation_name: None,
}
})
);
let stmt = mysql_and_generic().verified_stmt("SET NAMES utf8mb4 COLLATE bogus");
assert_eq!(
stmt,
Statement::SetNames {
Statement::Set(Set::SetNames {
charset_name: "utf8mb4".into(),
collation_name: Some("bogus".to_string()),
}
})
);
let stmt = mysql_and_generic()
@ -2725,14 +2725,14 @@ fn parse_set_names() {
.unwrap();
assert_eq!(
stmt,
vec![Statement::SetNames {
vec![Statement::Set(Set::SetNames {
charset_name: "utf8mb4".into(),
collation_name: Some("bogus".to_string()),
}]
})]
);
let stmt = mysql_and_generic().verified_stmt("SET NAMES DEFAULT");
assert_eq!(stmt, Statement::SetNamesDefault {});
assert_eq!(stmt, Statement::Set(Set::SetNamesDefault {}));
}
#[test]

View file

@ -1432,81 +1432,77 @@ fn parse_set() {
let stmt = pg_and_generic().verified_stmt("SET a = b");
assert_eq!(
stmt,
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: false,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])),
value: vec![Expr::Identifier(Ident {
variable: ObjectName::from(vec![Ident::new("a")]),
values: vec![Expr::Identifier(Ident {
value: "b".into(),
quote_style: None,
span: Span::empty(),
})],
}
})
);
let stmt = pg_and_generic().verified_stmt("SET a = 'b'");
assert_eq!(
stmt,
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: false,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])),
value: vec![Expr::Value(
variable: ObjectName::from(vec![Ident::new("a")]),
values: vec![Expr::Value(
(Value::SingleQuotedString("b".into())).with_empty_span()
)],
}
})
);
let stmt = pg_and_generic().verified_stmt("SET a = 0");
assert_eq!(
stmt,
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: false,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])),
value: vec![Expr::value(number("0"))],
}
variable: ObjectName::from(vec![Ident::new("a")]),
values: vec![Expr::value(number("0"))],
})
);
let stmt = pg_and_generic().verified_stmt("SET a = DEFAULT");
assert_eq!(
stmt,
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: false,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])),
value: vec![Expr::Identifier(Ident::new("DEFAULT"))],
}
variable: ObjectName::from(vec![Ident::new("a")]),
values: vec![Expr::Identifier(Ident::new("DEFAULT"))],
})
);
let stmt = pg_and_generic().verified_stmt("SET LOCAL a = b");
assert_eq!(
stmt,
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: true,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![Ident::new("a")])),
value: vec![Expr::Identifier("b".into())],
}
variable: ObjectName::from(vec![Ident::new("a")]),
values: vec![Expr::Identifier("b".into())],
})
);
let stmt = pg_and_generic().verified_stmt("SET a.b.c = b");
assert_eq!(
stmt,
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: false,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![
Ident::new("a"),
Ident::new("b"),
Ident::new("c")
])),
value: vec![Expr::Identifier(Ident {
variable: ObjectName::from(vec![Ident::new("a"), Ident::new("b"), Ident::new("c")]),
values: vec![Expr::Identifier(Ident {
value: "b".into(),
quote_style: None,
span: Span::empty(),
})],
}
})
);
let stmt = pg_and_generic().one_statement_parses_to(
@ -1515,18 +1511,18 @@ fn parse_set() {
);
assert_eq!(
stmt,
Statement::SetVariable {
Statement::Set(Set::SingleAssignment {
local: false,
hivevar: false,
variables: OneOrManyWithParens::One(ObjectName::from(vec![
variable: ObjectName::from(vec![
Ident::new("hive"),
Ident::new("tez"),
Ident::new("auto"),
Ident::new("reducer"),
Ident::new("parallelism")
])),
value: vec![Expr::Value((Value::Boolean(false)).with_empty_span())],
}
]),
values: vec![Expr::Value((Value::Boolean(false)).with_empty_span())],
})
);
pg_and_generic().one_statement_parses_to("SET a TO b", "SET a = b");
@ -1560,10 +1556,10 @@ fn parse_set_role() {
let stmt = pg_and_generic().verified_stmt(query);
assert_eq!(
stmt,
Statement::SetRole {
Statement::Set(Set::SetRole {
context_modifier: ContextModifier::Session,
role_name: None,
}
})
);
assert_eq!(query, stmt.to_string());
@ -1571,14 +1567,14 @@ fn parse_set_role() {
let stmt = pg_and_generic().verified_stmt(query);
assert_eq!(
stmt,
Statement::SetRole {
Statement::Set(Set::SetRole {
context_modifier: ContextModifier::Local,
role_name: Some(Ident {
value: "rolename".to_string(),
quote_style: Some('\"'),
span: Span::empty(),
}),
}
})
);
assert_eq!(query, stmt.to_string());
@ -1586,14 +1582,14 @@ fn parse_set_role() {
let stmt = pg_and_generic().verified_stmt(query);
assert_eq!(
stmt,
Statement::SetRole {
Statement::Set(Set::SetRole {
context_modifier: ContextModifier::None,
role_name: Some(Ident {
value: "rolename".to_string(),
quote_style: Some('\''),
span: Span::empty(),
}),
}
})
);
assert_eq!(query, stmt.to_string());
}
@ -2982,16 +2978,16 @@ fn test_transaction_statement() {
let statement = pg().verified_stmt("SET TRANSACTION SNAPSHOT '000003A1-1'");
assert_eq!(
statement,
Statement::SetTransaction {
Statement::Set(Set::SetTransaction {
modes: vec![],
snapshot: Some(Value::SingleQuotedString(String::from("000003A1-1"))),
session: false
}
})
);
let statement = pg().verified_stmt("SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE");
assert_eq!(
statement,
Statement::SetTransaction {
Statement::Set(Set::SetTransaction {
modes: vec![
TransactionMode::AccessMode(TransactionAccessMode::ReadOnly),
TransactionMode::AccessMode(TransactionAccessMode::ReadWrite),
@ -2999,7 +2995,7 @@ fn test_transaction_statement() {
],
snapshot: None,
session: true
}
})
);
}