From 9c2c4523cd34bc5539bf702caeeaa96c4bdc22e9 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 18 Feb 2021 01:48:10 +0800 Subject: [PATCH 1/4] Postgres: Create Type --- src/backend/postgres/mod.rs | 5 +- src/backend/postgres/table.rs | 16 +++--- src/backend/postgres/types.rs | 38 +++++++++++++++ src/extension/mod.rs | 3 ++ src/extension/postgres/mod.rs | 3 ++ src/extension/postgres/types.rs | 86 +++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/table/mod.rs | 3 +- tests/postgres/table.rs | 13 +++++ 9 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 src/backend/postgres/types.rs create mode 100644 src/extension/mod.rs create mode 100644 src/extension/postgres/mod.rs create mode 100644 src/extension/postgres/types.rs diff --git a/src/backend/postgres/mod.rs b/src/backend/postgres/mod.rs index acdbc7e81..dad17832e 100644 --- a/src/backend/postgres/mod.rs +++ b/src/backend/postgres/mod.rs @@ -1,7 +1,8 @@ +pub(crate) mod foreign_key; +pub(crate) mod index; pub(crate) mod query; pub(crate) mod table; -pub(crate) mod index; -pub(crate) mod foreign_key; +pub(crate) mod types; use super::*; diff --git a/src/backend/postgres/table.rs b/src/backend/postgres/table.rs index 6231e455b..cd7c30c0e 100644 --- a/src/backend/postgres/table.rs +++ b/src/backend/postgres/table.rs @@ -121,14 +121,14 @@ impl TableBuilder for PostgresQueryBuilder { } fn prepare_column_spec(&self, column_spec: &ColumnSpec, sql: &mut SqlWriter) { - write!(sql, "{}", match column_spec { - ColumnSpec::Null => "NULL".into(), - ColumnSpec::NotNull => "NOT NULL".into(), - ColumnSpec::Default(value) => format!("DEFAULT {}", pg_value_to_string(value)), - ColumnSpec::AutoIncrement => "".into(), - ColumnSpec::UniqueKey => "UNIQUE".into(), - ColumnSpec::PrimaryKey => "PRIMARY KEY".into(), - }).unwrap() + match column_spec { + ColumnSpec::Null => write!(sql, "NULL"), + ColumnSpec::NotNull => write!(sql, "NOT NULL"), + ColumnSpec::Default(value) => write!(sql, "DEFAULT {}", pg_value_to_string(value)), + ColumnSpec::AutoIncrement => write!(sql, ""), + ColumnSpec::UniqueKey => write!(sql, "UNIQUE"), + ColumnSpec::PrimaryKey => write!(sql, "PRIMARY KEY"), + }.unwrap() } fn prepare_table_opt(&self, table_opt: &TableOpt, sql: &mut SqlWriter) { diff --git a/src/backend/postgres/types.rs b/src/backend/postgres/types.rs new file mode 100644 index 000000000..144796619 --- /dev/null +++ b/src/backend/postgres/types.rs @@ -0,0 +1,38 @@ +use super::*; +use crate::extension::postgres::types::*; + +impl TypeBuilder for PostgresQueryBuilder { + fn prepare_type_create_statement(&self, create: &TypeCreateStatement, sql: &mut SqlWriter, collector: &mut dyn FnMut(Value)) { + write!(sql, "CREATE TYPE ").unwrap(); + + if let Some(name) = &create.name { + name.prepare(sql, '"'); + } + + if let Some(as_type) = &create.as_type { + write!(sql, " AS ").unwrap(); + self.prepare_create_as_type(&as_type, sql); + } + + if !create.values.is_empty() { + write!(sql, " (").unwrap(); + + for (count, val) in create.values.iter().enumerate() { + if count > 0 { + write!(sql, ", ").unwrap(); + } + self.prepare_value(&val.to_string().into(), sql, collector); + } + + write!(sql, ")").unwrap(); + } + } +} + +impl PostgresQueryBuilder { + fn prepare_create_as_type(&self, as_type: &TypeAs, sql: &mut SqlWriter) { + write!(sql, "{}", match as_type { + TypeAs::Enum => "ENUM", + }).unwrap() + } +} \ No newline at end of file diff --git a/src/extension/mod.rs b/src/extension/mod.rs new file mode 100644 index 000000000..40b39a3a4 --- /dev/null +++ b/src/extension/mod.rs @@ -0,0 +1,3 @@ +// mod mysql; +// mod sqlite; +pub mod postgres; diff --git a/src/extension/postgres/mod.rs b/src/extension/postgres/mod.rs new file mode 100644 index 000000000..5fb9ecabb --- /dev/null +++ b/src/extension/postgres/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod types; + +pub use types::*; \ No newline at end of file diff --git a/src/extension/postgres/types.rs b/src/extension/postgres/types.rs new file mode 100644 index 000000000..41c629bf5 --- /dev/null +++ b/src/extension/postgres/types.rs @@ -0,0 +1,86 @@ +use std::rc::Rc; +use crate::{backend::QueryBuilder, prepare::*, types::*, value::*}; + +/// Helper for constructing any type statement +pub struct Type; + +#[derive(Clone, Default)] +pub struct TypeCreateStatement { + pub(crate) name: Option>, + pub(crate) as_type: Option, + pub(crate) values: Vec>, +} + +#[derive(Clone)] +pub enum TypeAs { + // Composite, + Enum, + // Range, + // Base, + // Array, +} + +pub trait TypeBuilder { + /// Translate [`TypeCreateStatement`] into database specific SQL statement. + fn prepare_type_create_statement(&self, create: &TypeCreateStatement, sql: &mut SqlWriter, collector: &mut dyn FnMut(Value)); +} + +impl Type { + /// Construct type [`TypeCreateStatement`] + pub fn create() -> TypeCreateStatement { + TypeCreateStatement::new() + } +} + +impl TypeCreateStatement { + + pub fn new() -> Self { + Self::default() + } + + /// Create enum + pub fn as_enum(&mut self, name: T) -> &mut Self + where T: Iden { + self.name = Some(Rc::new(name)); + self.as_type = Some(TypeAs::Enum); + self + } + + pub fn values(&mut self, values: Vec) -> &mut Self + where T: Iden { + self.values_dyn(values.into_iter().map(|c| Rc::new(c) as Rc).collect()) + } + + pub fn values_dyn(&mut self, values: Vec>) -> &mut Self { + self.values = values; + self + } + + pub fn build(&self, type_builder: T) -> (String, Vec) { + self.build_ref(&type_builder) + } + + pub fn build_ref(&self, type_builder: &T) -> (String, Vec) { + let mut params = Vec::new(); + let mut collector = |v| params.push(v); + let sql = self.build_collect_ref(type_builder, &mut collector); + (sql, params) + } + + pub fn build_collect(&self, type_builder: T, collector: &mut dyn FnMut(Value)) -> String { + self.build_collect_ref(&type_builder, collector) + } + + pub fn build_collect_ref(&self, type_builder: &T, collector: &mut dyn FnMut(Value)) -> String { + let mut sql = SqlWriter::new(); + type_builder.prepare_type_create_statement(self, &mut sql, collector); + sql.result() + } + + /// Build corresponding SQL statement and return SQL string + pub fn to_string(&self, type_builder: T) -> String + where T: TypeBuilder + QueryBuilder { + let (sql, values) = self.build_ref(&type_builder); + inject_parameters(&sql, values, &type_builder) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7f42cbfef..2630f71ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -545,6 +545,7 @@ pub mod backend; pub mod driver; +pub mod extension; pub mod foreign_key; pub mod index; pub mod query; @@ -558,6 +559,7 @@ pub mod value; pub use backend::*; //pub use driver::*; +//pub use extension::*; pub use foreign_key::*; pub use index::*; pub use query::*; diff --git a/src/table/mod.rs b/src/table/mod.rs index 7d347f37e..bc6102a76 100644 --- a/src/table/mod.rs +++ b/src/table/mod.rs @@ -22,8 +22,7 @@ pub use drop::*; pub use rename::*; pub use truncate::*; -/// Shorthand for constructing any table statement -#[derive(Clone)] +/// Helper for constructing any table statement pub struct Table; /// All available types of table statement diff --git a/tests/postgres/table.rs b/tests/postgres/table.rs index f6116282c..a0f664ba6 100644 --- a/tests/postgres/table.rs +++ b/tests/postgres/table.rs @@ -193,4 +193,17 @@ fn alter_5() { #[should_panic(expected = "No alter option found")] fn alter_6() { Table::alter().to_string(PostgresQueryBuilder); +} + +#[test] +fn create_6() { + use sea_query::extension::postgres::Type; + + assert_eq!( + Type::create() + .as_enum(Font::Table) + .values(vec![Font::Name, Font::Variant, Font::Language]) + .to_string(PostgresQueryBuilder), + r#"CREATE TYPE "font" AS ENUM ('name', 'variant', 'language')"# + ); } \ No newline at end of file From d061cddfe6529545a0ad6844787c3c6c2719679b Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Thu, 18 Feb 2021 02:11:16 +0800 Subject: [PATCH 2/4] Postgres: Create Type example --- src/extension/postgres/types.rs | 40 ++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/extension/postgres/types.rs b/src/extension/postgres/types.rs index 41c629bf5..4945e2b88 100644 --- a/src/extension/postgres/types.rs +++ b/src/extension/postgres/types.rs @@ -13,7 +13,7 @@ pub struct TypeCreateStatement { #[derive(Clone)] pub enum TypeAs { - // Composite, + // Composite, Enum, // Range, // Base, @@ -34,11 +34,41 @@ impl Type { impl TypeCreateStatement { - pub fn new() -> Self { - Self::default() - } + pub fn new() -> Self { + Self::default() + } - /// Create enum + /// Create enum as custom type + /// + /// ``` + /// use sea_query::{*, extension::postgres::Type}; + /// + /// enum FontFamily { + /// Type, + /// Serif, + /// Sans, + /// Monospace, + /// } + /// + /// impl Iden for FontFamily { + /// fn unquoted(&self, s: &mut dyn std::fmt::Write) { + /// write!(s, "{}", match self { + /// Self::Type => "font_family", + /// Self::Serif => "serif", + /// Self::Sans => "sans", + /// Self::Monospace => "monospace", + /// }).unwrap(); + /// } + /// } + /// + /// assert_eq!( + /// Type::create() + /// .as_enum(FontFamily::Type) + /// .values(vec![FontFamily::Serif, FontFamily::Sans, FontFamily::Monospace]) + /// .to_string(PostgresQueryBuilder), + /// r#"CREATE TYPE "font_family" AS ENUM ('serif', 'sans', 'monospace')"# + /// ); + /// ``` pub fn as_enum(&mut self, name: T) -> &mut Self where T: Iden { self.name = Some(Rc::new(name)); From 34922653628f984e154f2f1d0abb56dac4e7d609 Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 19 Feb 2021 13:59:46 +0800 Subject: [PATCH 3/4] Postgres: Drop Type --- src/backend/postgres/types.rs | 24 +++++++ src/extension/postgres/types.rs | 124 +++++++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 2 deletions(-) diff --git a/src/backend/postgres/types.rs b/src/backend/postgres/types.rs index 144796619..7cbff3407 100644 --- a/src/backend/postgres/types.rs +++ b/src/backend/postgres/types.rs @@ -27,6 +27,23 @@ impl TypeBuilder for PostgresQueryBuilder { write!(sql, ")").unwrap(); } } + + fn prepare_type_drop_statement(&self, drop: &TypeDropStatement, sql: &mut SqlWriter, _collector: &mut dyn FnMut(Value)) { + write!(sql, "DROP TYPE ").unwrap(); + + if drop.if_exists { + write!(sql, "IF EXISTS ").unwrap(); + } + + for name in drop.names.iter() { + name.prepare(sql, '"'); + } + + if let Some(option) = &drop.option { + write!(sql, " ").unwrap(); + self.prepare_drop_type_opt(&option, sql); + } + } } impl PostgresQueryBuilder { @@ -35,4 +52,11 @@ impl PostgresQueryBuilder { TypeAs::Enum => "ENUM", }).unwrap() } + + fn prepare_drop_type_opt(&self, opt: &TypeDropOpt, sql: &mut SqlWriter) { + write!(sql, "{}", match opt { + TypeDropOpt::Cascade => "CASCADE", + TypeDropOpt::Restrict => "RESTRICT", + }).unwrap() + } } \ No newline at end of file diff --git a/src/extension/postgres/types.rs b/src/extension/postgres/types.rs index 4945e2b88..6835221d5 100644 --- a/src/extension/postgres/types.rs +++ b/src/extension/postgres/types.rs @@ -20,9 +20,25 @@ pub enum TypeAs { // Array, } +#[derive(Clone, Default)] +pub struct TypeDropStatement { + pub(crate) names: Vec>, + pub(crate) option: Option, + pub(crate) if_exists: bool, +} + +#[derive(Clone)] +pub enum TypeDropOpt { + Cascade, + Restrict, +} + pub trait TypeBuilder { /// Translate [`TypeCreateStatement`] into database specific SQL statement. fn prepare_type_create_statement(&self, create: &TypeCreateStatement, sql: &mut SqlWriter, collector: &mut dyn FnMut(Value)); + + /// Translate [`TypeDropStatement`] into database specific SQL statement. + fn prepare_type_drop_statement(&self, create: &TypeDropStatement, sql: &mut SqlWriter, collector: &mut dyn FnMut(Value)); } impl Type { @@ -30,6 +46,11 @@ impl Type { pub fn create() -> TypeCreateStatement { TypeCreateStatement::new() } + + /// Construct type [`TypeDropStatement`] + pub fn drop() -> TypeDropStatement { + TypeDropStatement::new() + } } impl TypeCreateStatement { @@ -81,11 +102,13 @@ impl TypeCreateStatement { self.values_dyn(values.into_iter().map(|c| Rc::new(c) as Rc).collect()) } - pub fn values_dyn(&mut self, values: Vec>) -> &mut Self { - self.values = values; + pub fn values_dyn(&mut self, mut values: Vec>) -> &mut Self { + self.values.append(&mut values); self } + // below are boiler plates + pub fn build(&self, type_builder: T) -> (String, Vec) { self.build_ref(&type_builder) } @@ -114,3 +137,100 @@ impl TypeCreateStatement { inject_parameters(&sql, values, &type_builder) } } + +impl TypeDropStatement { + + pub fn new() -> Self { + Self::default() + } + + /// Drop a type + /// + /// ``` + /// use sea_query::{*, extension::postgres::Type}; + /// + /// struct FontFamily; + /// + /// impl Iden for FontFamily { + /// fn unquoted(&self, s: &mut dyn std::fmt::Write) { + /// write!(s, "{}", "font_family").unwrap(); + /// } + /// } + /// + /// assert_eq!( + /// Type::drop() + /// .if_exists() + /// .name(FontFamily) + /// .restrict() + /// .to_string(PostgresQueryBuilder), + /// r#"DROP TYPE IF EXISTS "font_family" RESTRICT"# + /// ); + /// ``` + pub fn name(&mut self, name: T) -> &mut Self + where T: Iden { + self.name_dyn(Rc::new(name)) + } + + pub fn name_dyn(&mut self, name: Rc) -> &mut Self { + self.names.push(name); + self + } + + pub fn names(&mut self, names: Vec) -> &mut Self + where T: Iden { + self.names_dyn(names.into_iter().map(|c| Rc::new(c) as Rc).collect()) + } + + pub fn names_dyn(&mut self, mut names: Vec>) -> &mut Self { + self.names.append(&mut names); + self + } + + /// Set `IF EXISTS` + pub fn if_exists(&mut self) -> &mut Self { + self.if_exists = true; + self + } + + /// Set `CASCADE` + pub fn cascade(&mut self) -> &mut Self { + self.option = Some(TypeDropOpt::Cascade); + self + } + + /// Set `RESTRICT` + pub fn restrict(&mut self) -> &mut Self { + self.option = Some(TypeDropOpt::Restrict); + self + } + + // below are boiler plates + + pub fn build(&self, type_builder: T) -> (String, Vec) { + self.build_ref(&type_builder) + } + + pub fn build_ref(&self, type_builder: &T) -> (String, Vec) { + let mut params = Vec::new(); + let mut collector = |v| params.push(v); + let sql = self.build_collect_ref(type_builder, &mut collector); + (sql, params) + } + + pub fn build_collect(&self, type_builder: T, collector: &mut dyn FnMut(Value)) -> String { + self.build_collect_ref(&type_builder, collector) + } + + pub fn build_collect_ref(&self, type_builder: &T, collector: &mut dyn FnMut(Value)) -> String { + let mut sql = SqlWriter::new(); + type_builder.prepare_type_drop_statement(self, &mut sql, collector); + sql.result() + } + + /// Build corresponding SQL statement and return SQL string + pub fn to_string(&self, type_builder: T) -> String + where T: TypeBuilder + QueryBuilder { + let (sql, values) = self.build_ref(&type_builder); + inject_parameters(&sql, values, &type_builder) + } +} From 4f6bcf3e151ba853d3d890334cd36a576cb3cf2a Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Fri, 19 Feb 2021 14:48:25 +0800 Subject: [PATCH 4/4] Postgres: Drop Type tests --- tests/postgres/table.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/postgres/table.rs b/tests/postgres/table.rs index a0f664ba6..b1530935c 100644 --- a/tests/postgres/table.rs +++ b/tests/postgres/table.rs @@ -206,4 +206,44 @@ fn create_6() { .to_string(PostgresQueryBuilder), r#"CREATE TYPE "font" AS ENUM ('name', 'variant', 'language')"# ); +} + +#[test] +fn drop_2() { + use sea_query::extension::postgres::Type; + + assert_eq!( + Type::drop() + .name(Font::Table) + .to_string(PostgresQueryBuilder), + r#"DROP TYPE "font""# + ); +} + +#[test] +fn drop_3() { + use sea_query::extension::postgres::Type; + + assert_eq!( + Type::drop() + .if_exists() + .name(Font::Table) + .restrict() + .to_string(PostgresQueryBuilder), + r#"DROP TYPE IF EXISTS "font" RESTRICT"# + ); +} + +#[test] +fn drop_4() { + use sea_query::extension::postgres::Type; + + assert_eq!( + Type::drop() + .if_exists() + .name(Font::Table) + .cascade() + .to_string(PostgresQueryBuilder), + r#"DROP TYPE IF EXISTS "font" CASCADE"# + ); } \ No newline at end of file