diff --git a/CHANGELOG.md b/CHANGELOG.md index 622d87260..4cd9901d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ assert_eq!(std::mem::size_of::(), 32); ### Breaking Changes +* Replace `ColumnSpec::Check(Expr)` with `ColumnSpec::Check(Check)` to support named check constraints + * Removed inherent `SimpleExpr` methods that duplicate `ExprTrait`. If you encounter the following error, please add `use sea_query::ExprTrait` in scope https://github.com/SeaQL/sea-query/pull/890 ```rust error[E0599]: no method named `like` found for enum `sea_query::Expr` in the current scope @@ -138,8 +140,6 @@ impl Iden for Glyph { If you had custom implementations in your own code, some may no longer compile and may need to be deleted. -### Bug Fixes - ### Upgrades * Upgraded to Rust Edition 2024 https://github.com/SeaQL/sea-query/pull/885 diff --git a/src/backend/table_builder.rs b/src/backend/table_builder.rs index 9388455f2..c6fbf0880 100644 --- a/src/backend/table_builder.rs +++ b/src/backend/table_builder.rs @@ -194,9 +194,14 @@ pub trait TableBuilder: } /// Translate the check constraint into SQL statement - fn prepare_check_constraint(&self, check: &Expr, sql: &mut dyn SqlWriter) { + fn prepare_check_constraint(&self, check: &Check, sql: &mut dyn SqlWriter) { + if let Some(name) = &check.name { + write!(sql, "CONSTRAINT ",).unwrap(); + self.prepare_iden(name, sql); + write!(sql, " ",).unwrap(); + } write!(sql, "CHECK (").unwrap(); - QueryBuilder::prepare_simple_expr(self, check, sql); + QueryBuilder::prepare_simple_expr(self, &check.expr, sql); write!(sql, ")").unwrap(); } diff --git a/src/table/column.rs b/src/table/column.rs index 8eca28f17..cfe9b2589 100644 --- a/src/table/column.rs +++ b/src/table/column.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{expr::*, types::*}; +use crate::{expr::*, table::Check, types::*}; /// Specification of a table column #[derive(Debug, Clone)] @@ -179,7 +179,7 @@ pub enum ColumnSpec { AutoIncrement, UniqueKey, PrimaryKey, - Check(Expr), + Check(Check), Generated { expr: Expr, stored: bool }, Extra(String), Comment(String), @@ -687,6 +687,7 @@ impl ColumnDef { /// /// ``` /// use sea_query::{tests_cfg::*, *}; + /// /// assert_eq!( /// Table::create() /// .table(Glyph::Table) @@ -699,12 +700,25 @@ impl ColumnDef { /// .to_string(MysqlQueryBuilder), /// r#"CREATE TABLE `glyph` ( `id` int NOT NULL CHECK (`id` > 10) )"#, /// ); + /// + /// assert_eq!( + /// Table::create() + /// .table(Glyph::Table) + /// .col( + /// ColumnDef::new(Glyph::Id) + /// .integer() + /// .not_null() + /// .check(("positive_id", Expr::col(Glyph::Id).gt(10))) + /// ) + /// .to_string(MysqlQueryBuilder), + /// r#"CREATE TABLE `glyph` ( `id` int NOT NULL CONSTRAINT `positive_id` CHECK (`id` > 10) )"#, + /// ); /// ``` - pub fn check(&mut self, value: T) -> &mut Self + pub fn check(&mut self, check: T) -> &mut Self where - T: Into, + T: Into, { - self.spec.push(ColumnSpec::Check(value.into())); + self.spec.push(ColumnSpec::Check(check.into())); self } diff --git a/src/table/constraint.rs b/src/table/constraint.rs new file mode 100644 index 000000000..52e8ee4db --- /dev/null +++ b/src/table/constraint.rs @@ -0,0 +1,49 @@ +use crate::{DynIden, Expr}; + +#[derive(Debug, Clone)] +pub struct Check { + pub(crate) name: Option, + pub(crate) expr: Expr, +} + +impl Check { + pub fn named(name: N, expr: E) -> Self + where + N: Into, + E: Into, + { + Self { + name: Some(name.into()), + expr: expr.into(), + } + } + + pub fn unnamed(expr: E) -> Self + where + E: Into, + { + Self { + name: None, + expr: expr.into(), + } + } +} + +impl From for Check +where + E: Into, +{ + fn from(expr: E) -> Self { + Self::unnamed(expr) + } +} + +impl From<(I, E)> for Check +where + I: Into, + E: Into, +{ + fn from((name, expr): (I, E)) -> Self { + Self::named(name, expr) + } +} diff --git a/src/table/create.rs b/src/table/create.rs index 6bd50738e..53c6ebe95 100644 --- a/src/table/create.rs +++ b/src/table/create.rs @@ -1,8 +1,8 @@ use inherent::inherent; use crate::{ - ColumnDef, Expr, IntoColumnDef, SchemaStatementBuilder, backend::SchemaBuilder, foreign_key::*, - index::*, types::*, + ColumnDef, IntoColumnDef, SchemaStatementBuilder, backend::SchemaBuilder, foreign_key::*, + index::*, table::constraint::Check, types::*, }; /// Create a table @@ -88,7 +88,7 @@ pub struct TableCreateStatement { pub(crate) indexes: Vec, pub(crate) foreign_keys: Vec, pub(crate) if_not_exists: bool, - pub(crate) check: Vec, + pub(crate) check: Vec, pub(crate) comment: Option, pub(crate) extra: Option, pub(crate) temporary: bool, @@ -146,8 +146,11 @@ impl TableCreateStatement { self } - pub fn check(&mut self, value: Expr) -> &mut Self { - self.check.push(value); + pub fn check(&mut self, value: T) -> &mut Self + where + T: Into, + { + self.check.push(value.into()); self } diff --git a/src/table/mod.rs b/src/table/mod.rs index fd30eeea8..1afafc20f 100644 --- a/src/table/mod.rs +++ b/src/table/mod.rs @@ -12,6 +12,7 @@ use crate::SchemaBuilder; mod alter; mod column; +mod constraint; mod create; mod drop; mod rename; @@ -19,6 +20,7 @@ mod truncate; pub use alter::*; pub use column::*; +pub use constraint::*; pub use create::*; pub use drop::*; pub use rename::*; diff --git a/src/types.rs b/src/types.rs index 5ac4d7e33..a183ae5f0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -118,6 +118,12 @@ impl std::fmt::Display for DynIden { } } +impl From<&'static str> for DynIden { + fn from(s: &'static str) -> Self { + Self(Cow::Borrowed(s)) + } +} + pub trait IntoIden { fn into_iden(self) -> DynIden; } diff --git a/tests/mysql/table.rs b/tests/mysql/table.rs index bb37efbb9..fb7e27cb9 100644 --- a/tests/mysql/table.rs +++ b/tests/mysql/table.rs @@ -430,6 +430,24 @@ fn create_with_check_constraint() { ); } +#[test] +fn create_with_named_check_constraint() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col( + ColumnDef::new(Glyph::Id) + .integer() + .not_null() + .check(("positive_id", Expr::col(Glyph::Id).gt(10))) + ) + .check(("id_range", Expr::col(Glyph::Id).lt(20))) + .check(Expr::col(Glyph::Id).ne(15)) + .to_string(MysqlQueryBuilder), + r"CREATE TABLE `glyph` ( `id` int NOT NULL CONSTRAINT `positive_id` CHECK (`id` > 10), CONSTRAINT `id_range` CHECK (`id` < 20), CHECK (`id` <> 15) )", + ); +} + #[test] fn alter_with_check_constraint() { assert_eq!( @@ -446,3 +464,20 @@ fn alter_with_check_constraint() { r"ALTER TABLE `glyph` ADD COLUMN `aspect` int NOT NULL DEFAULT 101 CHECK (`aspect` > 100)", ); } + +#[test] +fn alter_with_named_check_constraint() { + assert_eq!( + Table::alter() + .table(Glyph::Table) + .add_column( + ColumnDef::new(Glyph::Aspect) + .integer() + .not_null() + .default(101) + .check(("positive_aspect", Expr::col(Glyph::Aspect).gt(100))) + ) + .to_string(MysqlQueryBuilder), + r#"ALTER TABLE `glyph` ADD COLUMN `aspect` int NOT NULL DEFAULT 101 CONSTRAINT `positive_aspect` CHECK (`aspect` > 100)"#, + ); +} diff --git a/tests/postgres/table.rs b/tests/postgres/table.rs index cd75d2cb7..25228f048 100644 --- a/tests/postgres/table.rs +++ b/tests/postgres/table.rs @@ -559,6 +559,24 @@ fn create_with_check_constraint() { ); } +#[test] +fn create_with_named_check_constraint() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col( + ColumnDef::new(Glyph::Id) + .integer() + .not_null() + .check(("positive_id", Expr::col(Glyph::Id).gt(10))) + ) + .check(("id_range", Expr::col(Glyph::Id).lt(20))) + .check(Expr::col(Glyph::Id).ne(15)) + .to_string(PostgresQueryBuilder), + r#"CREATE TABLE "glyph" ( "id" integer NOT NULL CONSTRAINT "positive_id" CHECK ("id" > 10), CONSTRAINT "id_range" CHECK ("id" < 20), CHECK ("id" <> 15) )"#, + ); +} + #[test] fn alter_with_check_constraint() { assert_eq!( @@ -576,6 +594,23 @@ fn alter_with_check_constraint() { ); } +#[test] +fn alter_with_named_check_constraint() { + assert_eq!( + Table::alter() + .table(Glyph::Table) + .add_column( + ColumnDef::new(Glyph::Aspect) + .integer() + .not_null() + .default(101) + .check(("positive_aspect", Expr::col(Glyph::Aspect).gt(100))) + ) + .to_string(PostgresQueryBuilder), + r#"ALTER TABLE "glyph" ADD COLUMN "aspect" integer NOT NULL DEFAULT 101 CONSTRAINT "positive_aspect" CHECK ("aspect" > 100)"#, + ); +} + #[test] fn create_16() { assert_eq!( diff --git a/tests/sqlite/table.rs b/tests/sqlite/table.rs index 659ca3123..0da2b7e0e 100644 --- a/tests/sqlite/table.rs +++ b/tests/sqlite/table.rs @@ -464,6 +464,24 @@ fn create_with_check_constraint() { ); } +#[test] +fn create_with_named_check_constraint() { + assert_eq!( + Table::create() + .table(Glyph::Table) + .col( + ColumnDef::new(Glyph::Id) + .integer() + .not_null() + .check(("positive_id", Expr::col(Glyph::Id).gt(10))) + ) + .check(("id_range", Expr::col(Glyph::Id).lt(20))) + .check(Expr::col(Glyph::Id).ne(15)) + .to_string(SqliteQueryBuilder), + r#"CREATE TABLE "glyph" ( "id" integer NOT NULL CONSTRAINT "positive_id" CHECK ("id" > 10), CONSTRAINT "id_range" CHECK ("id" < 20), CHECK ("id" <> 15) )"#, + ); +} + #[test] fn alter_with_check_constraint() { assert_eq!( @@ -480,3 +498,20 @@ fn alter_with_check_constraint() { r#"ALTER TABLE "glyph" ADD COLUMN "aspect" integer NOT NULL DEFAULT 101 CHECK ("aspect" > 100)"#, ); } + +#[test] +fn alter_with_named_check_constraint() { + assert_eq!( + Table::alter() + .table(Glyph::Table) + .add_column( + ColumnDef::new(Glyph::Aspect) + .integer() + .not_null() + .default(101) + .check(("positive_aspect", Expr::col(Glyph::Aspect).gt(100))) + ) + .to_string(SqliteQueryBuilder), + r#"ALTER TABLE "glyph" ADD COLUMN "aspect" integer NOT NULL DEFAULT 101 CONSTRAINT "positive_aspect" CHECK ("aspect" > 100)"#, + ); +}