use crate::ast::{
display_comma_separated, display_separated, ConstraintCharacteristics,
ConstraintReferenceMatchKind, Expr, Ident, IndexColumn, IndexOption, IndexType,
KeyOrIndexDisplay, NullsDistinctOption, ObjectName, ReferentialAction,
};
use crate::tokenizer::Span;
use core::fmt;
#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, vec::Vec};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut};
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum TableConstraint {
Unique(UniqueConstraint),
PrimaryKey(PrimaryKeyConstraint),
ForeignKey(ForeignKeyConstraint),
Check(CheckConstraint),
Index(IndexConstraint),
FulltextOrSpatial(FullTextOrSpatialConstraint),
}
impl From<UniqueConstraint> for TableConstraint {
fn from(constraint: UniqueConstraint) -> Self {
TableConstraint::Unique(constraint)
}
}
impl From<PrimaryKeyConstraint> for TableConstraint {
fn from(constraint: PrimaryKeyConstraint) -> Self {
TableConstraint::PrimaryKey(constraint)
}
}
impl From<ForeignKeyConstraint> for TableConstraint {
fn from(constraint: ForeignKeyConstraint) -> Self {
TableConstraint::ForeignKey(constraint)
}
}
impl From<CheckConstraint> for TableConstraint {
fn from(constraint: CheckConstraint) -> Self {
TableConstraint::Check(constraint)
}
}
impl From<IndexConstraint> for TableConstraint {
fn from(constraint: IndexConstraint) -> Self {
TableConstraint::Index(constraint)
}
}
impl From<FullTextOrSpatialConstraint> for TableConstraint {
fn from(constraint: FullTextOrSpatialConstraint) -> Self {
TableConstraint::FulltextOrSpatial(constraint)
}
}
impl fmt::Display for TableConstraint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TableConstraint::Unique(constraint) => constraint.fmt(f),
TableConstraint::PrimaryKey(constraint) => constraint.fmt(f),
TableConstraint::ForeignKey(constraint) => constraint.fmt(f),
TableConstraint::Check(constraint) => constraint.fmt(f),
TableConstraint::Index(constraint) => constraint.fmt(f),
TableConstraint::FulltextOrSpatial(constraint) => constraint.fmt(f),
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct CheckConstraint {
pub name: Option<Ident>,
pub expr: Box<Expr>,
pub enforced: Option<bool>,
}
impl fmt::Display for CheckConstraint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::ast::ddl::display_constraint_name;
write!(
f,
"{}CHECK ({})",
display_constraint_name(&self.name),
self.expr
)?;
if let Some(b) = self.enforced {
write!(f, " {}", if b { "ENFORCED" } else { "NOT ENFORCED" })
} else {
Ok(())
}
}
}
impl crate::ast::Spanned for CheckConstraint {
fn span(&self) -> Span {
self.expr
.span()
.union_opt(&self.name.as_ref().map(|i| i.span))
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct ForeignKeyConstraint {
pub name: Option<Ident>,
pub index_name: Option<Ident>,
pub columns: Vec<Ident>,
pub foreign_table: ObjectName,
pub referred_columns: Vec<Ident>,
pub on_delete: Option<ReferentialAction>,
pub on_update: Option<ReferentialAction>,
pub match_kind: Option<ConstraintReferenceMatchKind>,
pub characteristics: Option<ConstraintCharacteristics>,
}
impl fmt::Display for ForeignKeyConstraint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::ast::ddl::{display_constraint_name, display_option_spaced};
write!(
f,
"{}FOREIGN KEY{} ({}) REFERENCES {}",
display_constraint_name(&self.name),
display_option_spaced(&self.index_name),
display_comma_separated(&self.columns),
self.foreign_table,
)?;
if !self.referred_columns.is_empty() {
write!(f, "({})", display_comma_separated(&self.referred_columns))?;
}
if let Some(match_kind) = &self.match_kind {
write!(f, " {match_kind}")?;
}
if let Some(action) = &self.on_delete {
write!(f, " ON DELETE {action}")?;
}
if let Some(action) = &self.on_update {
write!(f, " ON UPDATE {action}")?;
}
if let Some(characteristics) = &self.characteristics {
write!(f, " {characteristics}")?;
}
Ok(())
}
}
impl crate::ast::Spanned for ForeignKeyConstraint {
fn span(&self) -> Span {
fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
Span::union_iter(iter)
}
union_spans(
self.name
.iter()
.map(|i| i.span)
.chain(self.index_name.iter().map(|i| i.span))
.chain(self.columns.iter().map(|i| i.span))
.chain(core::iter::once(self.foreign_table.span()))
.chain(self.referred_columns.iter().map(|i| i.span))
.chain(self.on_delete.iter().map(|i| i.span()))
.chain(self.on_update.iter().map(|i| i.span()))
.chain(self.characteristics.iter().map(|i| i.span())),
)
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct FullTextOrSpatialConstraint {
pub fulltext: bool,
pub index_type_display: KeyOrIndexDisplay,
pub opt_index_name: Option<Ident>,
pub columns: Vec<IndexColumn>,
}
impl fmt::Display for FullTextOrSpatialConstraint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.fulltext {
write!(f, "FULLTEXT")?;
} else {
write!(f, "SPATIAL")?;
}
write!(f, "{:>}", self.index_type_display)?;
if let Some(name) = &self.opt_index_name {
write!(f, " {name}")?;
}
write!(f, " ({})", display_comma_separated(&self.columns))?;
Ok(())
}
}
impl crate::ast::Spanned for FullTextOrSpatialConstraint {
fn span(&self) -> Span {
fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
Span::union_iter(iter)
}
union_spans(
self.opt_index_name
.iter()
.map(|i| i.span)
.chain(self.columns.iter().map(|i| i.span())),
)
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct IndexConstraint {
pub display_as_key: bool,
pub name: Option<Ident>,
pub index_type: Option<IndexType>,
pub columns: Vec<IndexColumn>,
pub index_options: Vec<IndexOption>,
}
impl fmt::Display for IndexConstraint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", if self.display_as_key { "KEY" } else { "INDEX" })?;
if let Some(name) = &self.name {
write!(f, " {name}")?;
}
if let Some(index_type) = &self.index_type {
write!(f, " USING {index_type}")?;
}
write!(f, " ({})", display_comma_separated(&self.columns))?;
if !self.index_options.is_empty() {
write!(f, " {}", display_comma_separated(&self.index_options))?;
}
Ok(())
}
}
impl crate::ast::Spanned for IndexConstraint {
fn span(&self) -> Span {
fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
Span::union_iter(iter)
}
union_spans(
self.name
.iter()
.map(|i| i.span)
.chain(self.columns.iter().map(|i| i.span())),
)
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct PrimaryKeyConstraint {
pub name: Option<Ident>,
pub index_name: Option<Ident>,
pub index_type: Option<IndexType>,
pub columns: Vec<IndexColumn>,
pub index_options: Vec<IndexOption>,
pub characteristics: Option<ConstraintCharacteristics>,
}
impl fmt::Display for PrimaryKeyConstraint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced};
write!(
f,
"{}PRIMARY KEY{}{} ({})",
display_constraint_name(&self.name),
display_option_spaced(&self.index_name),
display_option(" USING ", "", &self.index_type),
display_comma_separated(&self.columns),
)?;
if !self.index_options.is_empty() {
write!(f, " {}", display_separated(&self.index_options, " "))?;
}
write!(f, "{}", display_option_spaced(&self.characteristics))?;
Ok(())
}
}
impl crate::ast::Spanned for PrimaryKeyConstraint {
fn span(&self) -> Span {
fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
Span::union_iter(iter)
}
union_spans(
self.name
.iter()
.map(|i| i.span)
.chain(self.index_name.iter().map(|i| i.span))
.chain(self.columns.iter().map(|i| i.span()))
.chain(self.characteristics.iter().map(|i| i.span())),
)
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct UniqueConstraint {
pub name: Option<Ident>,
pub index_name: Option<Ident>,
pub index_type_display: KeyOrIndexDisplay,
pub index_type: Option<IndexType>,
pub columns: Vec<IndexColumn>,
pub index_options: Vec<IndexOption>,
pub characteristics: Option<ConstraintCharacteristics>,
pub nulls_distinct: NullsDistinctOption,
}
impl fmt::Display for UniqueConstraint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::ast::ddl::{display_constraint_name, display_option, display_option_spaced};
write!(
f,
"{}UNIQUE{}{:>}{}{} ({})",
display_constraint_name(&self.name),
self.nulls_distinct,
self.index_type_display,
display_option_spaced(&self.index_name),
display_option(" USING ", "", &self.index_type),
display_comma_separated(&self.columns),
)?;
if !self.index_options.is_empty() {
write!(f, " {}", display_separated(&self.index_options, " "))?;
}
write!(f, "{}", display_option_spaced(&self.characteristics))?;
Ok(())
}
}
impl crate::ast::Spanned for UniqueConstraint {
fn span(&self) -> Span {
fn union_spans<I: Iterator<Item = Span>>(iter: I) -> Span {
Span::union_iter(iter)
}
union_spans(
self.name
.iter()
.map(|i| i.span)
.chain(self.index_name.iter().map(|i| i.span))
.chain(self.columns.iter().map(|i| i.span()))
.chain(self.characteristics.iter().map(|i| i.span())),
)
}
}