Added support for CREATE DOMAIN (#1830)

This commit is contained in:
Luca Cappelletti 2025-05-04 23:21:44 +02:00 committed by GitHub
parent a497358c3a
commit ac1c339666
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 183 additions and 6 deletions

View file

@ -2153,6 +2153,55 @@ impl fmt::Display for ClusteredBy {
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
/// ```sql
/// CREATE DOMAIN name [ AS ] data_type
/// [ COLLATE collation ]
/// [ DEFAULT expression ]
/// [ domain_constraint [ ... ] ]
///
/// where domain_constraint is:
///
/// [ CONSTRAINT constraint_name ]
/// { NOT NULL | NULL | CHECK (expression) }
/// ```
/// See [PostgreSQL](https://www.postgresql.org/docs/current/sql-createdomain.html)
pub struct CreateDomain {
/// The name of the domain to be created.
pub name: ObjectName,
/// The data type of the domain.
pub data_type: DataType,
/// The collation of the domain.
pub collation: Option<Ident>,
/// The default value of the domain.
pub default: Option<Expr>,
/// The constraints of the domain.
pub constraints: Vec<TableConstraint>,
}
impl fmt::Display for CreateDomain {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"CREATE DOMAIN {name} AS {data_type}",
name = self.name,
data_type = self.data_type
)?;
if let Some(collation) = &self.collation {
write!(f, " COLLATE {collation}")?;
}
if let Some(default) = &self.default {
write!(f, " DEFAULT {default}")?;
}
if !self.constraints.is_empty() {
write!(f, " {}", display_separated(&self.constraints, " "))?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

View file

@ -55,12 +55,12 @@ pub use self::ddl::{
AlterTableAlgorithm, AlterTableLock, 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,
ConstraintCharacteristics, CreateConnector, CreateDomain, 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, IndexColumn, Insert};
pub use self::operator::{BinaryOperator, UnaryOperator};
@ -4049,6 +4049,8 @@ pub enum Statement {
sequence_options: Vec<SequenceOptions>,
owned_by: Option<ObjectName>,
},
/// A `CREATE DOMAIN` statement.
CreateDomain(CreateDomain),
/// ```sql
/// CREATE TYPE <name>
/// ```
@ -4598,6 +4600,7 @@ impl fmt::Display for Statement {
Ok(())
}
Statement::CreateFunction(create_function) => create_function.fmt(f),
Statement::CreateDomain(create_domain) => create_domain.fmt(f),
Statement::CreateTrigger {
or_alter,
or_replace,

View file

@ -483,6 +483,7 @@ impl Spanned for Statement {
Statement::CreateSchema { .. } => Span::empty(),
Statement::CreateDatabase { .. } => Span::empty(),
Statement::CreateFunction { .. } => Span::empty(),
Statement::CreateDomain { .. } => Span::empty(),
Statement::CreateTrigger { .. } => Span::empty(),
Statement::DropTrigger { .. } => Span::empty(),
Statement::CreateProcedure { .. } => Span::empty(),

View file

@ -4625,6 +4625,8 @@ impl<'a> Parser<'a> {
self.parse_create_external_table(or_replace)
} else if self.parse_keyword(Keyword::FUNCTION) {
self.parse_create_function(or_alter, or_replace, temporary)
} else if self.parse_keyword(Keyword::DOMAIN) {
self.parse_create_domain()
} else if self.parse_keyword(Keyword::TRIGGER) {
self.parse_create_trigger(or_alter, or_replace, false)
} else if self.parse_keywords(&[Keyword::CONSTRAINT, Keyword::TRIGGER]) {
@ -5974,6 +5976,35 @@ impl<'a> Parser<'a> {
Ok(owner)
}
/// Parses a [Statement::CreateDomain] statement.
fn parse_create_domain(&mut self) -> Result<Statement, ParserError> {
let name = self.parse_object_name(false)?;
self.expect_keyword_is(Keyword::AS)?;
let data_type = self.parse_data_type()?;
let collation = if self.parse_keyword(Keyword::COLLATE) {
Some(self.parse_identifier()?)
} else {
None
};
let default = if self.parse_keyword(Keyword::DEFAULT) {
Some(self.parse_expr()?)
} else {
None
};
let mut constraints = Vec::new();
while let Some(constraint) = self.parse_optional_table_constraint()? {
constraints.push(constraint);
}
Ok(Statement::CreateDomain(CreateDomain {
name,
data_type,
collation,
default,
constraints,
}))
}
/// ```sql
/// CREATE POLICY name ON table_name [ AS { PERMISSIVE | RESTRICTIVE } ]
/// [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]

View file

@ -5153,6 +5153,99 @@ fn test_escaped_string_literal() {
}
}
#[test]
fn parse_create_domain() {
let sql1 = "CREATE DOMAIN my_domain AS INTEGER CHECK (VALUE > 0)";
let expected = Statement::CreateDomain(CreateDomain {
name: ObjectName::from(vec![Ident::new("my_domain")]),
data_type: DataType::Integer(None),
collation: None,
default: None,
constraints: vec![TableConstraint::Check {
name: None,
expr: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
op: BinaryOperator::Gt,
right: Box::new(Expr::Value(test_utils::number("0").into())),
}),
}],
});
assert_eq!(pg().verified_stmt(sql1), expected);
let sql2 = "CREATE DOMAIN my_domain AS INTEGER COLLATE \"en_US\" CHECK (VALUE > 0)";
let expected = Statement::CreateDomain(CreateDomain {
name: ObjectName::from(vec![Ident::new("my_domain")]),
data_type: DataType::Integer(None),
collation: Some(Ident::with_quote('"', "en_US")),
default: None,
constraints: vec![TableConstraint::Check {
name: None,
expr: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
op: BinaryOperator::Gt,
right: Box::new(Expr::Value(test_utils::number("0").into())),
}),
}],
});
assert_eq!(pg().verified_stmt(sql2), expected);
let sql3 = "CREATE DOMAIN my_domain AS INTEGER DEFAULT 1 CHECK (VALUE > 0)";
let expected = Statement::CreateDomain(CreateDomain {
name: ObjectName::from(vec![Ident::new("my_domain")]),
data_type: DataType::Integer(None),
collation: None,
default: Some(Expr::Value(test_utils::number("1").into())),
constraints: vec![TableConstraint::Check {
name: None,
expr: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
op: BinaryOperator::Gt,
right: Box::new(Expr::Value(test_utils::number("0").into())),
}),
}],
});
assert_eq!(pg().verified_stmt(sql3), expected);
let sql4 = "CREATE DOMAIN my_domain AS INTEGER COLLATE \"en_US\" DEFAULT 1 CHECK (VALUE > 0)";
let expected = Statement::CreateDomain(CreateDomain {
name: ObjectName::from(vec![Ident::new("my_domain")]),
data_type: DataType::Integer(None),
collation: Some(Ident::with_quote('"', "en_US")),
default: Some(Expr::Value(test_utils::number("1").into())),
constraints: vec![TableConstraint::Check {
name: None,
expr: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
op: BinaryOperator::Gt,
right: Box::new(Expr::Value(test_utils::number("0").into())),
}),
}],
});
assert_eq!(pg().verified_stmt(sql4), expected);
let sql5 = "CREATE DOMAIN my_domain AS INTEGER CONSTRAINT my_constraint CHECK (VALUE > 0)";
let expected = Statement::CreateDomain(CreateDomain {
name: ObjectName::from(vec![Ident::new("my_domain")]),
data_type: DataType::Integer(None),
collation: None,
default: None,
constraints: vec![TableConstraint::Check {
name: Some(Ident::new("my_constraint")),
expr: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("VALUE"))),
op: BinaryOperator::Gt,
right: Box::new(Expr::Value(test_utils::number("0").into())),
}),
}],
});
assert_eq!(pg().verified_stmt(sql5), expected);
}
#[test]
fn parse_create_simple_before_insert_trigger() {
let sql = "CREATE TRIGGER check_insert BEFORE INSERT ON accounts FOR EACH ROW EXECUTE FUNCTION check_account_insert";