mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-07-07 17:04:59 +00:00
Add support for Postgres ALTER TYPE
(#1727)
This commit is contained in:
parent
68c41a9d5a
commit
c75a992621
6 changed files with 271 additions and 61 deletions
|
@ -640,6 +640,95 @@ impl fmt::Display for AlterIndexOperation {
|
|||
}
|
||||
}
|
||||
|
||||
/// An `ALTER TYPE` statement (`Statement::AlterType`)
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct AlterType {
|
||||
pub name: ObjectName,
|
||||
pub operation: AlterTypeOperation,
|
||||
}
|
||||
|
||||
/// An [AlterType] 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 AlterTypeOperation {
|
||||
Rename(AlterTypeRename),
|
||||
AddValue(AlterTypeAddValue),
|
||||
RenameValue(AlterTypeRenameValue),
|
||||
}
|
||||
|
||||
/// See [AlterTypeOperation::Rename]
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct AlterTypeRename {
|
||||
pub new_name: Ident,
|
||||
}
|
||||
|
||||
/// See [AlterTypeOperation::AddValue]
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct AlterTypeAddValue {
|
||||
pub if_not_exists: bool,
|
||||
pub value: Ident,
|
||||
pub position: Option<AlterTypeAddValuePosition>,
|
||||
}
|
||||
|
||||
/// See [AlterTypeAddValue]
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum AlterTypeAddValuePosition {
|
||||
Before(Ident),
|
||||
After(Ident),
|
||||
}
|
||||
|
||||
/// See [AlterTypeOperation::RenameValue]
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct AlterTypeRenameValue {
|
||||
pub from: Ident,
|
||||
pub to: Ident,
|
||||
}
|
||||
|
||||
impl fmt::Display for AlterTypeOperation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Rename(AlterTypeRename { new_name }) => {
|
||||
write!(f, "RENAME TO {new_name}")
|
||||
}
|
||||
Self::AddValue(AlterTypeAddValue {
|
||||
if_not_exists,
|
||||
value,
|
||||
position,
|
||||
}) => {
|
||||
write!(f, "ADD VALUE")?;
|
||||
if *if_not_exists {
|
||||
write!(f, " IF NOT EXISTS")?;
|
||||
}
|
||||
write!(f, " {value}")?;
|
||||
match position {
|
||||
Some(AlterTypeAddValuePosition::Before(neighbor_value)) => {
|
||||
write!(f, " BEFORE {neighbor_value}")?;
|
||||
}
|
||||
Some(AlterTypeAddValuePosition::After(neighbor_value)) => {
|
||||
write!(f, " AFTER {neighbor_value}")?;
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
Self::RenameValue(AlterTypeRenameValue { from, to }) => {
|
||||
write!(f, "RENAME VALUE {from} TO {to}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An `ALTER COLUMN` (`Statement::AlterTable`) operation
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
|
|
@ -48,13 +48,15 @@ pub use self::dcl::{
|
|||
};
|
||||
pub use self::ddl::{
|
||||
AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation,
|
||||
AlterTableOperation, ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy,
|
||||
ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateFunction, Deduplicate,
|
||||
DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode, IdentityParameters,
|
||||
IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder,
|
||||
IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition,
|
||||
ProcedureParam, ReferentialAction, TableConstraint, TagsColumnOption,
|
||||
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
|
||||
AlterTableOperation, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition,
|
||||
AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, ClusteredBy, ColumnDef,
|
||||
ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty, ConstraintCharacteristics,
|
||||
CreateConnector, CreateFunction, Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs,
|
||||
GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
|
||||
IdentityPropertyKind, IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay,
|
||||
NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, TableConstraint,
|
||||
TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
|
||||
ViewColumnDef,
|
||||
};
|
||||
pub use self::dml::{CreateIndex, CreateTable, Delete, Insert};
|
||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||
|
@ -2691,6 +2693,11 @@ pub enum Statement {
|
|||
with_options: Vec<SqlOption>,
|
||||
},
|
||||
/// ```sql
|
||||
/// ALTER TYPE
|
||||
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-altertype.html)
|
||||
/// ```
|
||||
AlterType(AlterType),
|
||||
/// ```sql
|
||||
/// ALTER ROLE
|
||||
/// ```
|
||||
AlterRole {
|
||||
|
@ -4438,6 +4445,9 @@ impl fmt::Display for Statement {
|
|||
}
|
||||
write!(f, " AS {query}")
|
||||
}
|
||||
Statement::AlterType(AlterType { name, operation }) => {
|
||||
write!(f, "ALTER TYPE {name} {operation}")
|
||||
}
|
||||
Statement::AlterRole { name, operation } => {
|
||||
write!(f, "ALTER ROLE {name} {operation}")
|
||||
}
|
||||
|
|
|
@ -215,6 +215,7 @@ impl Spanned for Values {
|
|||
/// - [Statement::CopyIntoSnowflake]
|
||||
/// - [Statement::CreateSecret]
|
||||
/// - [Statement::CreateRole]
|
||||
/// - [Statement::AlterType]
|
||||
/// - [Statement::AlterRole]
|
||||
/// - [Statement::AttachDatabase]
|
||||
/// - [Statement::AttachDuckDBDatabase]
|
||||
|
@ -427,6 +428,7 @@ impl Spanned for Statement {
|
|||
.chain(with_options.iter().map(|i| i.span())),
|
||||
),
|
||||
// These statements need to be implemented
|
||||
Statement::AlterType { .. } => Span::empty(),
|
||||
Statement::AlterRole { .. } => Span::empty(),
|
||||
Statement::AttachDatabase { .. } => Span::empty(),
|
||||
Statement::AttachDuckDBDatabase { .. } => Span::empty(),
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
// limitations under the License.
|
||||
use log::debug;
|
||||
|
||||
use crate::ast::{ObjectName, Statement, UserDefinedTypeRepresentation};
|
||||
use crate::dialect::{Dialect, Precedence};
|
||||
use crate::keywords::Keyword;
|
||||
use crate::parser::{Parser, ParserError};
|
||||
|
@ -135,15 +134,6 @@ impl Dialect for PostgreSqlDialect {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
if parser.parse_keyword(Keyword::CREATE) {
|
||||
parser.prev_token(); // unconsume the CREATE in case we don't end up parsing anything
|
||||
parse_create(parser)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_filter_during_aggregation(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -259,37 +249,3 @@ impl Dialect for PostgreSqlDialect {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_create(parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
|
||||
let name = parser.maybe_parse(|parser| -> Result<ObjectName, ParserError> {
|
||||
parser.expect_keyword_is(Keyword::CREATE)?;
|
||||
parser.expect_keyword_is(Keyword::TYPE)?;
|
||||
let name = parser.parse_object_name(false)?;
|
||||
parser.expect_keyword_is(Keyword::AS)?;
|
||||
parser.expect_keyword_is(Keyword::ENUM)?;
|
||||
Ok(name)
|
||||
});
|
||||
|
||||
match name {
|
||||
Ok(name) => name.map(|name| parse_create_type_as_enum(parser, name)),
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.postgresql.org/docs/current/sql-createtype.html
|
||||
pub fn parse_create_type_as_enum(
|
||||
parser: &mut Parser,
|
||||
name: ObjectName,
|
||||
) -> Result<Statement, ParserError> {
|
||||
if !parser.consume_token(&Token::LParen) {
|
||||
return parser.expected("'(' after CREATE TYPE AS ENUM", parser.peek_token());
|
||||
}
|
||||
|
||||
let labels = parser.parse_comma_separated0(|p| p.parse_identifier(), Token::RParen)?;
|
||||
parser.expect_token(&Token::RParen)?;
|
||||
|
||||
Ok(Statement::CreateType {
|
||||
name,
|
||||
representation: UserDefinedTypeRepresentation::Enum { labels },
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8042,6 +8042,7 @@ impl<'a> Parser<'a> {
|
|||
pub fn parse_alter(&mut self) -> Result<Statement, ParserError> {
|
||||
let object_type = self.expect_one_of_keywords(&[
|
||||
Keyword::VIEW,
|
||||
Keyword::TYPE,
|
||||
Keyword::TABLE,
|
||||
Keyword::INDEX,
|
||||
Keyword::ROLE,
|
||||
|
@ -8050,6 +8051,7 @@ impl<'a> Parser<'a> {
|
|||
])?;
|
||||
match object_type {
|
||||
Keyword::VIEW => self.parse_alter_view(),
|
||||
Keyword::TYPE => self.parse_alter_type(),
|
||||
Keyword::TABLE => {
|
||||
let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
|
||||
let only = self.parse_keyword(Keyword::ONLY); // [ ONLY ]
|
||||
|
@ -8122,6 +8124,55 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Parse a [Statement::AlterType]
|
||||
pub fn parse_alter_type(&mut self) -> Result<Statement, ParserError> {
|
||||
let name = self.parse_object_name(false)?;
|
||||
|
||||
if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
|
||||
let new_name = self.parse_identifier()?;
|
||||
Ok(Statement::AlterType(AlterType {
|
||||
name,
|
||||
operation: AlterTypeOperation::Rename(AlterTypeRename { new_name }),
|
||||
}))
|
||||
} else if self.parse_keywords(&[Keyword::ADD, Keyword::VALUE]) {
|
||||
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||
let new_enum_value = self.parse_identifier()?;
|
||||
let position = if self.parse_keyword(Keyword::BEFORE) {
|
||||
Some(AlterTypeAddValuePosition::Before(self.parse_identifier()?))
|
||||
} else if self.parse_keyword(Keyword::AFTER) {
|
||||
Some(AlterTypeAddValuePosition::After(self.parse_identifier()?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Statement::AlterType(AlterType {
|
||||
name,
|
||||
operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
|
||||
if_not_exists,
|
||||
value: new_enum_value,
|
||||
position,
|
||||
}),
|
||||
}))
|
||||
} else if self.parse_keywords(&[Keyword::RENAME, Keyword::VALUE]) {
|
||||
let existing_enum_value = self.parse_identifier()?;
|
||||
self.expect_keyword(Keyword::TO)?;
|
||||
let new_enum_value = self.parse_identifier()?;
|
||||
|
||||
Ok(Statement::AlterType(AlterType {
|
||||
name,
|
||||
operation: AlterTypeOperation::RenameValue(AlterTypeRenameValue {
|
||||
from: existing_enum_value,
|
||||
to: new_enum_value,
|
||||
}),
|
||||
}))
|
||||
} else {
|
||||
return self.expected_ref(
|
||||
"{RENAME TO | { RENAME | ADD } VALUE}",
|
||||
self.peek_token_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `CALL procedure_name(arg1, arg2, ...)`
|
||||
/// or `CALL procedure_name` statement
|
||||
pub fn parse_call(&mut self) -> Result<Statement, ParserError> {
|
||||
|
@ -14222,6 +14273,10 @@ impl<'a> Parser<'a> {
|
|||
let name = self.parse_object_name(false)?;
|
||||
self.expect_keyword_is(Keyword::AS)?;
|
||||
|
||||
if self.parse_keyword(Keyword::ENUM) {
|
||||
return self.parse_create_type_enum(name);
|
||||
}
|
||||
|
||||
let mut attributes = vec![];
|
||||
if !self.consume_token(&Token::LParen) || self.consume_token(&Token::RParen) {
|
||||
return Ok(Statement::CreateType {
|
||||
|
@ -14258,6 +14313,20 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Parse remainder of `CREATE TYPE AS ENUM` statement (see [Statement::CreateType] and [Self::parse_create_type])
|
||||
///
|
||||
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtype.html)
|
||||
pub fn parse_create_type_enum(&mut self, name: ObjectName) -> Result<Statement, ParserError> {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let labels = self.parse_comma_separated0(|p| p.parse_identifier(), Token::RParen)?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
|
||||
Ok(Statement::CreateType {
|
||||
name,
|
||||
representation: UserDefinedTypeRepresentation::Enum { labels },
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_parenthesized_identifiers(&mut self) -> Result<Vec<Ident>, ParserError> {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let partitions = self.parse_comma_separated(|p| p.parse_identifier())?;
|
||||
|
|
|
@ -5293,15 +5293,8 @@ fn arrow_cast_precedence() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_type_as_enum() {
|
||||
let statement = pg().one_statement_parses_to(
|
||||
r#"CREATE TYPE public.my_type AS ENUM (
|
||||
'label1',
|
||||
'label2',
|
||||
'label3',
|
||||
'label4'
|
||||
);"#,
|
||||
"CREATE TYPE public.my_type AS ENUM ('label1', 'label2', 'label3', 'label4')",
|
||||
);
|
||||
let sql = "CREATE TYPE public.my_type AS ENUM ('label1', 'label2', 'label3', 'label4')";
|
||||
let statement = pg_and_generic().verified_stmt(sql);
|
||||
match statement {
|
||||
Statement::CreateType {
|
||||
name,
|
||||
|
@ -5316,10 +5309,101 @@ fn parse_create_type_as_enum() {
|
|||
labels
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
_ => unreachable!("{:?} should parse to Statement::CreateType", sql),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_alter_type() {
|
||||
struct TestCase {
|
||||
sql: &'static str,
|
||||
name: &'static str,
|
||||
operation: AlterTypeOperation,
|
||||
}
|
||||
vec![
|
||||
TestCase {
|
||||
sql: "ALTER TYPE public.my_type RENAME TO my_new_type",
|
||||
name: "public.my_type",
|
||||
operation: AlterTypeOperation::Rename(AlterTypeRename {
|
||||
new_name: Ident::new("my_new_type"),
|
||||
}),
|
||||
},
|
||||
TestCase {
|
||||
sql: "ALTER TYPE public.my_type ADD VALUE IF NOT EXISTS 'label3.5' BEFORE 'label4'",
|
||||
name: "public.my_type",
|
||||
operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
|
||||
if_not_exists: true,
|
||||
value: Ident::with_quote('\'', "label3.5"),
|
||||
position: Some(AlterTypeAddValuePosition::Before(Ident::with_quote(
|
||||
'\'', "label4",
|
||||
))),
|
||||
}),
|
||||
},
|
||||
TestCase {
|
||||
sql: "ALTER TYPE public.my_type ADD VALUE 'label3.5' BEFORE 'label4'",
|
||||
name: "public.my_type",
|
||||
operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
|
||||
if_not_exists: false,
|
||||
value: Ident::with_quote('\'', "label3.5"),
|
||||
position: Some(AlterTypeAddValuePosition::Before(Ident::with_quote(
|
||||
'\'', "label4",
|
||||
))),
|
||||
}),
|
||||
},
|
||||
TestCase {
|
||||
sql: "ALTER TYPE public.my_type ADD VALUE IF NOT EXISTS 'label3.5' AFTER 'label3'",
|
||||
name: "public.my_type",
|
||||
operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
|
||||
if_not_exists: true,
|
||||
value: Ident::with_quote('\'', "label3.5"),
|
||||
position: Some(AlterTypeAddValuePosition::After(Ident::with_quote(
|
||||
'\'', "label3",
|
||||
))),
|
||||
}),
|
||||
},
|
||||
TestCase {
|
||||
sql: "ALTER TYPE public.my_type ADD VALUE 'label3.5' AFTER 'label3'",
|
||||
name: "public.my_type",
|
||||
operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
|
||||
if_not_exists: false,
|
||||
value: Ident::with_quote('\'', "label3.5"),
|
||||
position: Some(AlterTypeAddValuePosition::After(Ident::with_quote(
|
||||
'\'', "label3",
|
||||
))),
|
||||
}),
|
||||
},
|
||||
TestCase {
|
||||
sql: "ALTER TYPE public.my_type ADD VALUE IF NOT EXISTS 'label5'",
|
||||
name: "public.my_type",
|
||||
operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
|
||||
if_not_exists: true,
|
||||
value: Ident::with_quote('\'', "label5"),
|
||||
position: None,
|
||||
}),
|
||||
},
|
||||
TestCase {
|
||||
sql: "ALTER TYPE public.my_type ADD VALUE 'label5'",
|
||||
name: "public.my_type",
|
||||
operation: AlterTypeOperation::AddValue(AlterTypeAddValue {
|
||||
if_not_exists: false,
|
||||
value: Ident::with_quote('\'', "label5"),
|
||||
position: None,
|
||||
}),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.for_each(|(index, tc)| {
|
||||
let statement = pg_and_generic().verified_stmt(tc.sql);
|
||||
if let Statement::AlterType(AlterType { name, operation }) = statement {
|
||||
assert_eq!(tc.name, name.to_string(), "TestCase[{index}].name");
|
||||
assert_eq!(tc.operation, operation, "TestCase[{index}].operation");
|
||||
} else {
|
||||
unreachable!("{:?} should parse to Statement::AlterType", tc.sql);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_bitstring_literal() {
|
||||
let select = pg_and_generic().verified_only_select("SELECT B'111'");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue