From a49ea1908d3862f3471e3e1304c15926314eda48 Mon Sep 17 00:00:00 2001 From: "r.4ntix" Date: Thu, 17 Aug 2023 20:05:54 +0800 Subject: [PATCH] feat: add `ALTER ROLE` syntax of PostgreSQL and MS SQL Server (#942) --- src/ast/dcl.rs | 195 +++++++++++++++++++++++++++++ src/ast/mod.rs | 10 ++ src/keywords.rs | 1 + src/parser/alter.rs | 204 +++++++++++++++++++++++++++++++ src/{parser.rs => parser/mod.rs} | 11 +- tests/sqlparser_mssql.rs | 54 ++++++++ tests/sqlparser_postgres.rs | 193 +++++++++++++++++++++++++++++ 7 files changed, 666 insertions(+), 2 deletions(-) create mode 100644 src/ast/dcl.rs create mode 100644 src/parser/alter.rs rename src/{parser.rs => parser/mod.rs} (99%) diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs new file mode 100644 index 000000000..f90de34d4 --- /dev/null +++ b/src/ast/dcl.rs @@ -0,0 +1,195 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! AST types specific to GRANT/REVOKE/ROLE variants of [`Statement`](crate::ast::Statement) +//! (commonly referred to as Data Control Language, or DCL) + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "visitor")] +use sqlparser_derive::{Visit, VisitMut}; + +use super::{Expr, Ident, Password}; +use crate::ast::{display_separated, ObjectName}; + +/// An option in `ROLE` statement. +/// +/// +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum RoleOption { + BypassRLS(bool), + ConnectionLimit(Expr), + CreateDB(bool), + CreateRole(bool), + Inherit(bool), + Login(bool), + Password(Password), + Replication(bool), + SuperUser(bool), + ValidUntil(Expr), +} + +impl fmt::Display for RoleOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + RoleOption::BypassRLS(value) => { + write!(f, "{}", if *value { "BYPASSRLS" } else { "NOBYPASSRLS" }) + } + RoleOption::ConnectionLimit(expr) => { + write!(f, "CONNECTION LIMIT {expr}") + } + RoleOption::CreateDB(value) => { + write!(f, "{}", if *value { "CREATEDB" } else { "NOCREATEDB" }) + } + RoleOption::CreateRole(value) => { + write!(f, "{}", if *value { "CREATEROLE" } else { "NOCREATEROLE" }) + } + RoleOption::Inherit(value) => { + write!(f, "{}", if *value { "INHERIT" } else { "NOINHERIT" }) + } + RoleOption::Login(value) => { + write!(f, "{}", if *value { "LOGIN" } else { "NOLOGIN" }) + } + RoleOption::Password(password) => match password { + Password::Password(expr) => write!(f, "PASSWORD {expr}"), + Password::NullPassword => write!(f, "PASSWORD NULL"), + }, + RoleOption::Replication(value) => { + write!( + f, + "{}", + if *value { + "REPLICATION" + } else { + "NOREPLICATION" + } + ) + } + RoleOption::SuperUser(value) => { + write!(f, "{}", if *value { "SUPERUSER" } else { "NOSUPERUSER" }) + } + RoleOption::ValidUntil(expr) => { + write!(f, "VALID UNTIL {expr}") + } + } + } +} + +/// SET config value option: +/// * SET `configuration_parameter` { TO | = } { `value` | DEFAULT } +/// * SET `configuration_parameter` FROM CURRENT +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum SetConfigValue { + Default, + FromCurrent, + Value(Expr), +} + +/// RESET config option: +/// * RESET `configuration_parameter` +/// * RESET ALL +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum ResetConfig { + ALL, + ConfigName(ObjectName), +} + +/// An `ALTER ROLE` (`Statement::AlterRole`) operation +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum AlterRoleOperation { + /// Generic + RenameRole { + role_name: Ident, + }, + /// MS SQL Server + /// + AddMember { + member_name: Ident, + }, + DropMember { + member_name: Ident, + }, + /// PostgreSQL + /// + WithOptions { + options: Vec, + }, + Set { + config_name: ObjectName, + config_value: SetConfigValue, + in_database: Option, + }, + Reset { + config_name: ResetConfig, + in_database: Option, + }, +} + +impl fmt::Display for AlterRoleOperation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AlterRoleOperation::RenameRole { role_name } => { + write!(f, "RENAME TO {role_name}") + } + AlterRoleOperation::AddMember { member_name } => { + write!(f, "ADD MEMBER {member_name}") + } + AlterRoleOperation::DropMember { member_name } => { + write!(f, "DROP MEMBER {member_name}") + } + AlterRoleOperation::WithOptions { options } => { + write!(f, "WITH {}", display_separated(options, " ")) + } + AlterRoleOperation::Set { + config_name, + config_value, + in_database, + } => { + if let Some(database_name) = in_database { + write!(f, "IN DATABASE {} ", database_name)?; + } + + match config_value { + SetConfigValue::Default => write!(f, "SET {config_name} TO DEFAULT"), + SetConfigValue::FromCurrent => write!(f, "SET {config_name} FROM CURRENT"), + SetConfigValue::Value(expr) => write!(f, "SET {config_name} TO {expr}"), + } + } + AlterRoleOperation::Reset { + config_name, + in_database, + } => { + if let Some(database_name) = in_database { + write!(f, "IN DATABASE {} ", database_name)?; + } + + match config_name { + ResetConfig::ALL => write!(f, "RESET ALL"), + ResetConfig::ConfigName(name) => write!(f, "RESET {name}"), + } + } + } + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 41a5530a3..edab3c63a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -28,6 +28,7 @@ use sqlparser_derive::{Visit, VisitMut}; pub use self::data_type::{ CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, TimezoneInfo, }; +pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue}; pub use self::ddl::{ AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, GeneratedAs, IndexType, KeyOrIndexDisplay, ProcedureParam, ReferentialAction, @@ -52,6 +53,7 @@ use crate::ast::helpers::stmt_data_loading::{ pub use visitor::*; mod data_type; +mod dcl; mod ddl; pub mod helpers; mod operator; @@ -1398,6 +1400,11 @@ pub enum Statement { query: Box, with_options: Vec, }, + /// ALTER ROLE + AlterRole { + name: Ident, + operation: AlterRoleOperation, + }, /// DROP Drop { /// The type of the object to drop: TABLE, VIEW, etc. @@ -2585,6 +2592,9 @@ impl fmt::Display for Statement { } write!(f, " AS {query}") } + Statement::AlterRole { name, operation } => { + write!(f, "ALTER ROLE {name} {operation}") + } Statement::Drop { object_type, if_exists, diff --git a/src/keywords.rs b/src/keywords.rs index 98e039414..5f3e44022 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -508,6 +508,7 @@ define_keywords!( REPEATABLE, REPLACE, REPLICATION, + RESET, RESTRICT, RESULT, RETAIN, diff --git a/src/parser/alter.rs b/src/parser/alter.rs new file mode 100644 index 000000000..838b64899 --- /dev/null +++ b/src/parser/alter.rs @@ -0,0 +1,204 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! SQL Parser for ALTER + +#[cfg(not(feature = "std"))] +use alloc::vec; + +use super::{Parser, ParserError}; +use crate::{ + ast::{AlterRoleOperation, Expr, Password, ResetConfig, RoleOption, SetConfigValue, Statement}, + dialect::{MsSqlDialect, PostgreSqlDialect}, + keywords::Keyword, + tokenizer::Token, +}; + +impl<'a> Parser<'a> { + pub fn parse_alter_role(&mut self) -> Result { + if dialect_of!(self is PostgreSqlDialect) { + return self.parse_pg_alter_role(); + } else if dialect_of!(self is MsSqlDialect) { + return self.parse_mssql_alter_role(); + } + + Err(ParserError::ParserError( + "ALTER ROLE is only support for PostgreSqlDialect, MsSqlDialect".into(), + )) + } + + fn parse_mssql_alter_role(&mut self) -> Result { + let role_name = self.parse_identifier()?; + + let operation = if self.parse_keywords(&[Keyword::ADD, Keyword::MEMBER]) { + let member_name = self.parse_identifier()?; + AlterRoleOperation::AddMember { member_name } + } else if self.parse_keywords(&[Keyword::DROP, Keyword::MEMBER]) { + let member_name = self.parse_identifier()?; + AlterRoleOperation::DropMember { member_name } + } else if self.parse_keywords(&[Keyword::WITH, Keyword::NAME]) { + if self.consume_token(&Token::Eq) { + let role_name = self.parse_identifier()?; + AlterRoleOperation::RenameRole { role_name } + } else { + return self.expected("= after WITH NAME ", self.peek_token()); + } + } else { + return self.expected("'ADD' or 'DROP' or 'WITH NAME'", self.peek_token()); + }; + + Ok(Statement::AlterRole { + name: role_name, + operation, + }) + } + + fn parse_pg_alter_role(&mut self) -> Result { + let role_name = self.parse_identifier()?; + + // [ IN DATABASE _`database_name`_ ] + let in_database = if self.parse_keywords(&[Keyword::IN, Keyword::DATABASE]) { + self.parse_object_name().ok() + } else { + None + }; + + let operation = if self.parse_keyword(Keyword::RENAME) { + if self.parse_keyword(Keyword::TO) { + let role_name = self.parse_identifier()?; + AlterRoleOperation::RenameRole { role_name } + } else { + return self.expected("TO after RENAME", self.peek_token()); + } + // SET + } else if self.parse_keyword(Keyword::SET) { + let config_name = self.parse_object_name()?; + // FROM CURRENT + if self.parse_keywords(&[Keyword::FROM, Keyword::CURRENT]) { + AlterRoleOperation::Set { + config_name, + config_value: SetConfigValue::FromCurrent, + in_database, + } + // { TO | = } { value | DEFAULT } + } else if self.consume_token(&Token::Eq) || self.parse_keyword(Keyword::TO) { + if self.parse_keyword(Keyword::DEFAULT) { + AlterRoleOperation::Set { + config_name, + config_value: SetConfigValue::Default, + in_database, + } + } else if let Ok(expr) = self.parse_expr() { + AlterRoleOperation::Set { + config_name, + config_value: SetConfigValue::Value(expr), + in_database, + } + } else { + self.expected("config value", self.peek_token())? + } + } else { + self.expected("'TO' or '=' or 'FROM CURRENT'", self.peek_token())? + } + // RESET + } else if self.parse_keyword(Keyword::RESET) { + if self.parse_keyword(Keyword::ALL) { + AlterRoleOperation::Reset { + config_name: ResetConfig::ALL, + in_database, + } + } else { + let config_name = self.parse_object_name()?; + AlterRoleOperation::Reset { + config_name: ResetConfig::ConfigName(config_name), + in_database, + } + } + // option + } else { + // [ WITH ] + let _ = self.parse_keyword(Keyword::WITH); + // option + let mut options = vec![]; + while let Some(opt) = self.maybe_parse(|parser| parser.parse_pg_role_option()) { + options.push(opt); + } + // check option + if options.is_empty() { + return self.expected("option", self.peek_token())?; + } + + AlterRoleOperation::WithOptions { options } + }; + + Ok(Statement::AlterRole { + name: role_name, + operation, + }) + } + + fn parse_pg_role_option(&mut self) -> Result { + let option = match self.parse_one_of_keywords(&[ + Keyword::BYPASSRLS, + Keyword::NOBYPASSRLS, + Keyword::CONNECTION, + Keyword::CREATEDB, + Keyword::NOCREATEDB, + Keyword::CREATEROLE, + Keyword::NOCREATEROLE, + Keyword::INHERIT, + Keyword::NOINHERIT, + Keyword::LOGIN, + Keyword::NOLOGIN, + Keyword::PASSWORD, + Keyword::REPLICATION, + Keyword::NOREPLICATION, + Keyword::SUPERUSER, + Keyword::NOSUPERUSER, + Keyword::VALID, + ]) { + Some(Keyword::BYPASSRLS) => RoleOption::BypassRLS(true), + Some(Keyword::NOBYPASSRLS) => RoleOption::BypassRLS(false), + Some(Keyword::CONNECTION) => { + self.expect_keyword(Keyword::LIMIT)?; + RoleOption::ConnectionLimit(Expr::Value(self.parse_number_value()?)) + } + Some(Keyword::CREATEDB) => RoleOption::CreateDB(true), + Some(Keyword::NOCREATEDB) => RoleOption::CreateDB(false), + Some(Keyword::CREATEROLE) => RoleOption::CreateRole(true), + Some(Keyword::NOCREATEROLE) => RoleOption::CreateRole(false), + Some(Keyword::INHERIT) => RoleOption::Inherit(true), + Some(Keyword::NOINHERIT) => RoleOption::Inherit(false), + Some(Keyword::LOGIN) => RoleOption::Login(true), + Some(Keyword::NOLOGIN) => RoleOption::Login(false), + Some(Keyword::PASSWORD) => { + let password = if self.parse_keyword(Keyword::NULL) { + Password::NullPassword + } else { + Password::Password(Expr::Value(self.parse_value()?)) + }; + RoleOption::Password(password) + } + Some(Keyword::REPLICATION) => RoleOption::Replication(true), + Some(Keyword::NOREPLICATION) => RoleOption::Replication(false), + Some(Keyword::SUPERUSER) => RoleOption::SuperUser(true), + Some(Keyword::NOSUPERUSER) => RoleOption::SuperUser(false), + Some(Keyword::VALID) => { + self.expect_keyword(Keyword::UNTIL)?; + RoleOption::ValidUntil(Expr::Value(self.parse_value()?)) + } + _ => self.expected("option", self.peek_token())?, + }; + + Ok(option) + } +} diff --git a/src/parser.rs b/src/parser/mod.rs similarity index 99% rename from src/parser.rs rename to src/parser/mod.rs index fcac8f235..d6f45359e 100644 --- a/src/parser.rs +++ b/src/parser/mod.rs @@ -33,6 +33,8 @@ use crate::dialect::*; use crate::keywords::{self, Keyword}; use crate::tokenizer::*; +mod alter; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum ParserError { TokenizerError(String), @@ -3990,8 +3992,12 @@ impl<'a> Parser<'a> { } pub fn parse_alter(&mut self) -> Result { - let object_type = - self.expect_one_of_keywords(&[Keyword::VIEW, Keyword::TABLE, Keyword::INDEX])?; + let object_type = self.expect_one_of_keywords(&[ + Keyword::VIEW, + Keyword::TABLE, + Keyword::INDEX, + Keyword::ROLE, + ])?; match object_type { Keyword::VIEW => self.parse_alter_view(), Keyword::TABLE => { @@ -4186,6 +4192,7 @@ impl<'a> Parser<'a> { operation, }) } + Keyword::ROLE => self.parse_alter_role(), // unreachable because expect_one_of_keywords used above _ => unreachable!(), } diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index d4e093ad1..56fbd576e 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -216,6 +216,60 @@ fn parse_mssql_create_role() { } } +#[test] +fn parse_alter_role() { + let sql = "ALTER ROLE old_name WITH NAME = new_name"; + assert_eq!( + ms().parse_sql_statements(sql).unwrap(), + [Statement::AlterRole { + name: Ident { + value: "old_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::RenameRole { + role_name: Ident { + value: "new_name".into(), + quote_style: None + } + }, + }] + ); + + let sql = "ALTER ROLE role_name ADD MEMBER new_member"; + assert_eq!( + ms().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::AddMember { + member_name: Ident { + value: "new_member".into(), + quote_style: None + } + }, + } + ); + + let sql = "ALTER ROLE role_name DROP MEMBER old_member"; + assert_eq!( + ms().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::DropMember { + member_name: Ident { + value: "old_member".into(), + quote_style: None + } + }, + } + ); +} + #[test] fn parse_delimited_identifiers() { // check that quoted identifiers in any position remain quoted after serialization diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index f6bb80d7e..09196db46 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -2447,6 +2447,199 @@ fn parse_create_role() { } } +#[test] +fn parse_alter_role() { + let sql = "ALTER ROLE old_name RENAME TO new_name"; + assert_eq!( + pg().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "old_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::RenameRole { + role_name: Ident { + value: "new_name".into(), + quote_style: None + } + }, + } + ); + + let sql = "ALTER ROLE role_name WITH SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 100 PASSWORD 'abcdef' VALID UNTIL '2025-01-01'"; + assert_eq!( + pg().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::WithOptions { + options: vec![ + RoleOption::SuperUser(true), + RoleOption::CreateDB(true), + RoleOption::CreateRole(true), + RoleOption::Inherit(true), + RoleOption::Login(true), + RoleOption::Replication(true), + RoleOption::BypassRLS(true), + RoleOption::ConnectionLimit(Expr::Value(number("100"))), + RoleOption::Password({ + Password::Password(Expr::Value(Value::SingleQuotedString("abcdef".into()))) + }), + RoleOption::ValidUntil(Expr::Value(Value::SingleQuotedString( + "2025-01-01".into(), + ))) + ] + }, + } + ); + + let sql = "ALTER ROLE role_name WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS PASSWORD NULL"; + assert_eq!( + pg().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::WithOptions { + options: vec![ + RoleOption::SuperUser(false), + RoleOption::CreateDB(false), + RoleOption::CreateRole(false), + RoleOption::Inherit(false), + RoleOption::Login(false), + RoleOption::Replication(false), + RoleOption::BypassRLS(false), + RoleOption::Password(Password::NullPassword), + ] + }, + } + ); + + let sql = "ALTER ROLE role_name SET maintenance_work_mem FROM CURRENT"; + assert_eq!( + pg().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::Set { + config_name: ObjectName(vec![Ident { + value: "maintenance_work_mem".into(), + quote_style: None + }]), + config_value: SetConfigValue::FromCurrent, + in_database: None + }, + } + ); + + let sql = "ALTER ROLE role_name IN DATABASE database_name SET maintenance_work_mem = 100000"; + assert_eq!( + pg().parse_sql_statements(sql).unwrap(), + [Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::Set { + config_name: ObjectName(vec![Ident { + value: "maintenance_work_mem".into(), + quote_style: None + }]), + config_value: SetConfigValue::Value(Expr::Value(number("100000"))), + in_database: Some(ObjectName(vec![Ident { + value: "database_name".into(), + quote_style: None + }])) + }, + }] + ); + + let sql = "ALTER ROLE role_name IN DATABASE database_name SET maintenance_work_mem TO 100000"; + assert_eq!( + pg().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::Set { + config_name: ObjectName(vec![Ident { + value: "maintenance_work_mem".into(), + quote_style: None + }]), + config_value: SetConfigValue::Value(Expr::Value(number("100000"))), + in_database: Some(ObjectName(vec![Ident { + value: "database_name".into(), + quote_style: None + }])) + }, + } + ); + + let sql = "ALTER ROLE role_name IN DATABASE database_name SET maintenance_work_mem TO DEFAULT"; + assert_eq!( + pg().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::Set { + config_name: ObjectName(vec![Ident { + value: "maintenance_work_mem".into(), + quote_style: None + }]), + config_value: SetConfigValue::Default, + in_database: Some(ObjectName(vec![Ident { + value: "database_name".into(), + quote_style: None + }])) + }, + } + ); + + let sql = "ALTER ROLE role_name RESET ALL"; + assert_eq!( + pg().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::Reset { + config_name: ResetConfig::ALL, + in_database: None + }, + } + ); + + let sql = "ALTER ROLE role_name IN DATABASE database_name RESET maintenance_work_mem"; + assert_eq!( + pg().verified_stmt(sql), + Statement::AlterRole { + name: Ident { + value: "role_name".into(), + quote_style: None + }, + operation: AlterRoleOperation::Reset { + config_name: ResetConfig::ConfigName(ObjectName(vec![Ident { + value: "maintenance_work_mem".into(), + quote_style: None + }])), + in_database: Some(ObjectName(vec![Ident { + value: "database_name".into(), + quote_style: None + }])) + }, + } + ); +} + #[test] fn parse_delimited_identifiers() { // check that quoted identifiers in any position remain quoted after serialization