diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a94a83e3..736e72b7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,22 +1,81 @@ -name: Rust +name: sea-schema on: push: - branches: [ master ] + branches: + - master pull_request: - branches: [ master ] + branches: + - master env: CARGO_TERM_COLOR: always jobs: - build: - + test: + name: Unit Test runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: Swatinem/rust-cache@v1 + + - uses: actions-rs/cargo@v1 + with: + command: build + args: > + --all + + - uses: actions-rs/cargo@v1 + with: + command: test + postgres: + name: Postgres + runs-on: ubuntu-20.04 + strategy: + matrix: + version: [13.3, 12.7, 11.12, 10.17, 9.6.22] + project: [live] + services: + postgres: + image: postgres:${{ matrix.version }} + env: + POSTGRES_HOST: 127.0.0.1 + POSTGRES_USER: sea + POSTGRES_PASSWORD: sea + ports: + - "5432:5432" + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - - uses: actions/checkout@v2 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - uses: Swatinem/rust-cache@v1 + + - uses: actions-rs/cargo@v1 + with: + command: build + args: > + --manifest-path tests/${{ matrix.project }}/postgres/Cargo.toml + + - uses: actions-rs/cargo@v1 + with: + command: test + args: > + --manifest-path tests/${{ matrix.project }}/postgres/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index b8a6acc2..4fdaa21d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,11 @@ [workspace] members = [ ".", - "tests/discovery", - "tests/writer", + "tests/discovery/mysql", + "tests/discovery/postgres", + "tests/writer/mysql", + "tests/writer/postgres", + "tests/live/postgres", ] [package] @@ -28,7 +31,7 @@ path = "src/lib.rs" [dependencies] futures = { version = "0.3", optional = true } sea-schema-derive = { version = "0.1.0", path = "sea-schema-derive" } -sea-query = { version = "^0.12" } +sea-query = { version = "^0.12", git = "https://github.com/SeaQL/sea-query.git" } serde = { version = "^1", features = ["derive"], optional = true } sqlx = { version = "^0", optional = true } diff --git a/src/lib.rs b/src/lib.rs index c77ff641..7269f14f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ pub mod mysql; #[cfg_attr(docsrs, doc(cfg(feature = "postgres")))] pub mod postgres; +pub use sea_query; + pub(crate) mod parser; pub(crate) mod sqlx_types; pub(crate) mod util; diff --git a/src/postgres/def/column.rs b/src/postgres/def/column.rs index ee4dc1aa..bf14c59f 100644 --- a/src/postgres/def/column.rs +++ b/src/postgres/def/column.rs @@ -1,8 +1,7 @@ #[cfg(feature = "with-serde")] use serde::{Deserialize, Serialize}; -use super::constraints; -use super::Type; +use super::{NotNull, Type}; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] @@ -16,7 +15,7 @@ pub struct ColumnInfo { pub default: Option, /// The generation expression for this column, if it is a generated colum pub generated: Option, - pub not_null: Option, + pub not_null: Option, // TODO: // /// A constraint that ensures the value of a column is unique among all other rows in the table // pub unique: Option>, @@ -36,7 +35,7 @@ pub type ColumnType = Type; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] -pub struct ColumnExpression(String); +pub struct ColumnExpression(pub String); impl ColumnExpression { pub fn from_option_string(maybe_string: Option) -> Option { diff --git a/src/postgres/def/constraints.rs b/src/postgres/def/constraints.rs index 1b9424de..c1403128 100644 --- a/src/postgres/def/constraints.rs +++ b/src/postgres/def/constraints.rs @@ -1,6 +1,8 @@ #[cfg(feature = "with-serde")] use serde::{Deserialize, Serialize}; +use crate as sea_schema; + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] /// An enum consisting of all constraints @@ -17,6 +19,7 @@ pub enum Constraint { #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] /// A constraint which states that a value must satisfy the following Boolean expression pub struct Check { + pub name: String, /// The Boolean expression that must be satisfied pub expr: String, /// If marked with NO INHERIT, the constraint will not propogate to child tables @@ -27,6 +30,7 @@ pub struct Check { #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] /// The constraint that a value must not be null pub struct NotNull; + impl NotNull { pub fn from_bool(boolean: bool) -> Option { if boolean { @@ -40,21 +44,45 @@ impl NotNull { #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] /// That each set of values for these columns must be unique across the whole table -pub struct Unique(pub Vec); +pub struct Unique { + pub name: String, + pub columns: Vec, +} #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] /// A constraint stating that the given columns act as a unique identifier for rows in the table. /// This implies that the columns are not null and are unique together -pub struct PrimaryKey(pub Vec); +pub struct PrimaryKey { + pub name: String, + pub columns: Vec, +} #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] /// A constraint that column references the values appearing in the row of another table pub struct References { + pub name: String, pub columns: Vec, pub table: String, pub foreign_columns: Vec, + pub on_update: Option, + pub on_delete: Option, +} + +#[derive(Clone, Debug, PartialEq, sea_schema_derive::Name)] +#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +pub enum ForeignKeyAction { + #[name = "CASCADE"] + Cascade, + #[name = "SET NULL"] + SetNull, + #[name = "SET DEFAULT"] + SetDefault, + #[name = "RESTRICT"] + Restrict, + #[name = "NO ACTION"] + NoAction, } #[derive(Clone, Debug, PartialEq)] @@ -63,6 +91,7 @@ pub struct References { /// expressions using the specified operators, at least one of these operator comparisons returns /// false or null pub struct Exclusion { + pub name: String, pub using: String, pub columns: Vec, pub operation: String, diff --git a/src/postgres/def/schema.rs b/src/postgres/def/schema.rs index e5e158ad..7deb063a 100644 --- a/src/postgres/def/schema.rs +++ b/src/postgres/def/schema.rs @@ -17,10 +17,13 @@ pub struct TableDef { pub columns: Vec, pub check_constraints: Vec, - pub unique_keys: Vec, - pub references: Vec, - - pub of_type: Option, + pub not_null_constraints: Vec, + pub unique_constraints: Vec, + pub primary_key_constraints: Vec, + pub reference_constraints: Vec, + pub exclusion_constraints: Vec, + // FIXME: Duplication? TableInfo also have of_type + // pub of_type: Option, // TODO: // pub inherets: Vec, } diff --git a/src/postgres/def/types.rs b/src/postgres/def/types.rs index f68f4578..ae5a0c04 100644 --- a/src/postgres/def/types.rs +++ b/src/postgres/def/types.rs @@ -33,9 +33,9 @@ pub enum Type { // Character types /// Variable-length character array with limit - Varchar, + Varchar(StringAttr), /// Fixed-length character array; blank padded - Char, + Char(StringAttr), /// Variable, unlimited length character array Text, @@ -44,13 +44,15 @@ pub enum Type { // Date/Time types /// Date and time - Timestamp, + Timestamp(TimeAttr), + TimestampWithTimeZone(TimeAttr), /// Date without time of day Date, /// Time without date - Time, + Time(TimeAttr), + TimeWithTimeZone(TimeAttr), /// Time interval - Interval, + Interval(IntervalAttr), /// One byte boolean value Boolean, @@ -86,7 +88,7 @@ pub enum Type { MacAddr8, /// Fixed length bit string - Bit, + Bit(BitAttr), // Text search types /// A sorted list of distincp lexemes which are words that have been normalized to merge different @@ -152,8 +154,48 @@ impl Type { "smallserial" | "serial2" => Type::SmallSerial, "serial" | "serial4" => Type::Serial, "bigserial" | "serial8" => Type::BigSerial, + "money" => Type::Money, + "character varying" | "varchar" => Type::Varchar(StringAttr::default()), + "character" | "char" => Type::Char(StringAttr::default()), + "text" => Type::Text, + "bytea" => Type::Bytea, + "timestamp" | "timestamp without time zone" => Type::Timestamp(TimeAttr::default()), + "timestamp with time zone" => Type::TimestampWithTimeZone(TimeAttr::default()), + "date" => Type::Date, + "time" | "time without time zone" => Type::Time(TimeAttr::default()), + "time with time zone" => Type::TimeWithTimeZone(TimeAttr::default()), + "interval" => Type::Interval(IntervalAttr::default()), + "boolean" => Type::Boolean, + // "" => Type::Enum, + "point" => Type::Point, + "line" => Type::Line, + "lseg" => Type::Lseg, + "box" => Type::Box, + "path" => Type::Path, + "polygon" => Type::Polygon, + "circle" => Type::Circle, + "cidr" => Type::Cidr, + "inet" => Type::Inet, + "macaddr" => Type::MacAddr, + "macaddr8" => Type::MacAddr8, + "bit" => Type::Bit(BitAttr::default()), + "tsvector" => Type::TsVector, + "tsquery" => Type::TsQuery, + "uuid" => Type::Uuid, + "xml" => Type::Xml, + "json" => Type::Json, + "array" => Type::Array, + // "" => Type::Composite, + "int4range" => Type::Int4Range, + "int8range" => Type::Int8Range, + "numrange" => Type::NumRange, + "tsrange" => Type::TsRange, + "tstzrange" => Type::TsTzRange, + "daterange" => Type::DateRange, + // "" => Type::Domain, + "pg_lsn" => Type::PgLsn, - _ => Type::Unknown(format!("{} is unknown or unimplemented", name)), + _ => Type::Unknown(name.to_owned()), } } } @@ -170,8 +212,55 @@ pub struct ArbitraryPrecisionNumericAttr { pub scale: Option, } +#[derive(Clone, Debug, PartialEq, Default)] +#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +pub struct StringAttr { + pub length: Option, +} + +#[derive(Clone, Debug, PartialEq, Default)] +#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +pub struct TimeAttr { + pub precision: Option, +} + +#[derive(Clone, Debug, PartialEq, Default)] +#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +pub struct IntervalAttr { + pub field: Option, + pub precision: Option, +} + +#[derive(Clone, Debug, PartialEq, Default)] +#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +pub struct BitAttr { + pub length: Option, +} + impl Type { pub fn has_numeric_attr(&self) -> bool { matches!(self, Type::Numeric(_) | Type::Decimal(_)) } + + pub fn has_string_attr(&self) -> bool { + matches!(self, Type::Varchar(_) | Type::Char(_)) + } + + pub fn has_time_attr(&self) -> bool { + matches!( + self, + Type::Timestamp(_) + | Type::TimestampWithTimeZone(_) + | Type::Time(_) + | Type::TimeWithTimeZone(_) + ) + } + + pub fn has_interval_attr(&self) -> bool { + matches!(self, Type::Interval(_)) + } + + pub fn has_bit_attr(&self) -> bool { + matches!(self, Type::Bit(_)) + } } diff --git a/src/postgres/discovery/executor/mock.rs b/src/postgres/discovery/executor/mock.rs new file mode 100644 index 00000000..62cdec85 --- /dev/null +++ b/src/postgres/discovery/executor/mock.rs @@ -0,0 +1,28 @@ +use crate::sqlx_types::{postgres::PgRow, PgPool}; +use sea_query::{PostgresQueryBuilder, SelectStatement}; + +use crate::debug_print; + +pub struct Executor { + pool: PgPool, +} + +pub trait IntoExecutor { + fn into_executor(self) -> Executor; +} + +impl IntoExecutor for PgPool { + fn into_executor(self) -> Executor { + Executor { pool: self } + } +} + +impl Executor { + pub async fn fetch_all(&self, select: SelectStatement) -> Vec { + let (sql, values) = select.build(PostgresQueryBuilder); + debug_print!("{}, {:?}", sql, values); + debug_print!(); + + panic!("This is a mock Executor"); + } +} diff --git a/src/postgres/discovery/executor/mod.rs b/src/postgres/discovery/executor/mod.rs new file mode 100644 index 00000000..86811f01 --- /dev/null +++ b/src/postgres/discovery/executor/mod.rs @@ -0,0 +1,9 @@ +#[cfg(feature = "sqlx-postgres")] +mod real; +#[cfg(feature = "sqlx-postgres")] +pub use real::*; + +#[cfg(not(feature = "sqlx-postgres"))] +mod mock; +#[cfg(not(feature = "sqlx-postgres"))] +pub use mock::*; diff --git a/src/postgres/discovery/executor/real.rs b/src/postgres/discovery/executor/real.rs new file mode 100644 index 00000000..203293ff --- /dev/null +++ b/src/postgres/discovery/executor/real.rs @@ -0,0 +1,35 @@ +use sea_query::{PostgresQueryBuilder, SelectStatement}; +use sqlx::{postgres::PgRow, PgPool}; + +sea_query::sea_query_driver_postgres!(); +use sea_query_driver_postgres::bind_query; + +use crate::debug_print; + +pub struct Executor { + pool: PgPool, +} + +pub trait IntoExecutor { + fn into_executor(self) -> Executor; +} + +impl IntoExecutor for PgPool { + fn into_executor(self) -> Executor { + Executor { pool: self } + } +} + +impl Executor { + pub async fn fetch_all(&self, select: SelectStatement) -> Vec { + let (sql, values) = select.build(PostgresQueryBuilder); + debug_print!("{}, {:?}", sql, values); + debug_print!(); + + let query = bind_query(sqlx::query(&sql), &values); + query + .fetch_all(&mut self.pool.acquire().await.unwrap()) + .await + .unwrap() + } +} diff --git a/src/postgres/discovery/mod.rs b/src/postgres/discovery/mod.rs new file mode 100644 index 00000000..ea673e33 --- /dev/null +++ b/src/postgres/discovery/mod.rs @@ -0,0 +1,184 @@ +//! To query & parse MySQL's INFORMATION_SCHEMA and construct a [`Schema`] + +use crate::debug_print; +use crate::postgres::def::*; +use crate::postgres::parser::parse_table_constraint_query_results; +use crate::postgres::query::{ + ColumnQueryResult, SchemaQueryBuilder, TableConstraintsQueryResult, TableQueryResult, +}; +use futures::future; +use sea_query::{Alias, Iden, IntoIden}; +use std::rc::Rc; + +mod executor; +pub use executor::*; + +pub struct SchemaDiscovery { + pub query: SchemaQueryBuilder, + pub executor: Executor, + pub schema: Rc, +} + +impl SchemaDiscovery { + pub fn new(executor: E, schema: &str) -> Self + where + E: IntoExecutor, + { + Self { + query: SchemaQueryBuilder::default(), + executor: executor.into_executor(), + schema: Alias::new(schema).into_iden(), + } + } + + pub async fn discover(mut self) -> Schema { + let tables = self.discover_tables().await; + let tables = future::join_all( + tables + .into_iter() + .map(|t| (&self, t)) + .map(Self::discover_table_static), + ) + .await; + + Schema { + schema: self.schema.to_string(), + tables, + } + } + + pub async fn discover_tables(&mut self) -> Vec { + let rows = self + .executor + .fetch_all(self.query.query_tables(self.schema.clone())) + .await; + + let tables: Vec = rows + .iter() + .map(|row| { + let result: TableQueryResult = row.into(); + debug_print!("{:?}", result); + let table = result.parse(); + debug_print!("{:?}", table); + table + }) + .collect(); + + debug_print!(); + tables + } + + async fn discover_table_static(params: (&Self, TableInfo)) -> TableDef { + let this = params.0; + let info = params.1; + Self::discover_table(this, info).await + } + + pub async fn discover_table(&self, info: TableInfo) -> TableDef { + let table = Rc::new(Alias::new(info.name.as_str())); + let columns = self + .discover_columns(self.schema.clone(), table.clone()) + .await; + let constraints = self + .discover_constraints(self.schema.clone(), table.clone()) + .await; + let ( + check_constraints, + not_null_constraints, + unique_constraints, + primary_key_constraints, + reference_constraints, + exclusion_constraints, + ) = constraints.into_iter().fold( + ( + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + Vec::new(), + ), + |mut acc, constraint| { + match constraint { + Constraint::Check(check) => acc.0.push(check), + Constraint::NotNull(not_null) => acc.1.push(not_null), + Constraint::Unique(unique) => acc.2.push(unique), + Constraint::PrimaryKey(primary_key) => acc.3.push(primary_key), + Constraint::References(references) => acc.4.push(references), + Constraint::Exclusion(exclusion) => acc.5.push(exclusion), + } + acc + }, + ); + + TableDef { + info, + columns, + check_constraints, + not_null_constraints, + unique_constraints, + primary_key_constraints, + reference_constraints, + exclusion_constraints, + } + } + + pub async fn discover_columns( + &self, + schema: Rc, + table: Rc, + ) -> Vec { + let rows = self + .executor + .fetch_all(self.query.query_columns(schema.clone(), table.clone())) + .await; + + let columns = rows + .iter() + .map(|row| { + let result: ColumnQueryResult = row.into(); + debug_print!("{:?}", result); + let column = result.parse(); + debug_print!("{:?}", column); + return column; + }) + .collect::>(); + + debug_print!(); + columns + } + + pub async fn discover_constraints( + &self, + schema: Rc, + table: Rc, + ) -> Vec { + let rows = self + .executor + .fetch_all( + self.query + .query_table_constriants(schema.clone(), table.clone()), + ) + .await; + + let results: Vec = rows + .iter() + .map(|row| { + let result = row.into(); + debug_print!("{:?}", result); + result + }) + .collect(); + debug_print!(); + + let constraints = parse_table_constraint_query_results(Box::new(results.into_iter())) + .map(|index| { + debug_print!("{:?}", index); + index + }) + .collect::>(); + debug_print!(); + + constraints + } +} diff --git a/src/postgres/mod.rs b/src/postgres/mod.rs index 620011e5..c38c9e64 100644 --- a/src/postgres/mod.rs +++ b/src/postgres/mod.rs @@ -1,11 +1,19 @@ #[cfg(feature = "def")] -#[cfg_attr(docsr, doc(cfg(feature = "def")))] +#[cfg_attr(docsrs, doc(cfg(feature = "def")))] pub mod def; +#[cfg(feature = "discovery")] +#[cfg_attr(docsrs, doc(cfg(feature = "discovery")))] +pub mod discovery; + #[cfg(feature = "parser")] #[cfg_attr(docsrs, doc(cfg(feature = "parser")))] pub mod parser; #[cfg(feature = "query")] -#[cfg_attr(docsr, doc(cfg(feature = "query")))] +#[cfg_attr(docsrs, doc(cfg(feature = "query")))] pub mod query; + +#[cfg(feature = "writer")] +#[cfg_attr(docsrs, doc(cfg(feature = "writer")))] +pub mod writer; diff --git a/src/postgres/parser/column.rs b/src/postgres/parser/column.rs index f987237e..791f2fe2 100644 --- a/src/postgres/parser/column.rs +++ b/src/postgres/parser/column.rs @@ -8,7 +8,7 @@ use crate::{ Name, }; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; impl ColumnQueryResult { pub fn parse(self) -> ColumnInfo { @@ -22,33 +22,41 @@ pub fn parse_column_query_result(result: ColumnQueryResult) -> ColumnInfo { col_type: parse_column_type(&result), default: ColumnExpression::from_option_string(result.column_default), generated: ColumnExpression::from_option_string(result.column_generated), - not_null: NotNull::from_bool(yes_or_no_to_bool(&result.is_nullable)), + not_null: NotNull::from_bool(!yes_or_no_to_bool(&result.is_nullable)), } } pub fn parse_column_type(result: &ColumnQueryResult) -> ColumnType { let mut parser_type = Parser::new(&result.column_type); - if parser_type.curr().is_none() { + let mut ctype = if parser_type.curr().is_none() { return Type::Unknown(String::default()); - } - - let ctype = if let Some(word) = parser_type.next_if_unquoted_any() { - Type::from_str(word.as_str()) } else { - Type::from_str("") + Type::from_str(result.column_type.as_str()) }; if ctype.has_numeric_attr() { - parse_numeric_attributes( + ctype = parse_numeric_attributes( result.numeric_precision, result.numeric_precision_radix, result.numeric_scale, ctype, - ) - } else { - ctype + ); + } + if ctype.has_string_attr() { + ctype = parse_string_attributes(result.character_maximum_length, ctype); + } + if ctype.has_time_attr() { + ctype = parse_time_attributes(result.datetime_precision, ctype); + } + if ctype.has_interval_attr() { + ctype = parse_interval_attributes(&result.interval_type, result.interval_precision, ctype); } + if ctype.has_bit_attr() { + ctype = parse_bit_attributes(result.character_maximum_length, ctype); + } + + ctype } pub fn parse_numeric_attributes( @@ -89,3 +97,85 @@ pub fn parse_numeric_attributes( ctype } + +pub fn parse_string_attributes( + character_maximum_length: Option, + mut ctype: ColumnType, +) -> ColumnType { + match ctype { + Type::Varchar(ref mut attr) | Type::Char(ref mut attr) => { + attr.length = match character_maximum_length { + None => None, + Some(num) => match u16::try_from(num) { + Ok(num) => Some(num), + Err(e) => None, + }, + }; + } + _ => panic!("parse_string_attributes(_) received a type that does not have StringAttr"), + }; + + ctype +} + +pub fn parse_time_attributes(datetime_precision: Option, mut ctype: ColumnType) -> ColumnType { + match ctype { + Type::Timestamp(ref mut attr) + | Type::TimestampWithTimeZone(ref mut attr) + | Type::Time(ref mut attr) + | Type::TimeWithTimeZone(ref mut attr) => { + attr.precision = match datetime_precision { + None => None, + Some(num) => match u16::try_from(num) { + Ok(num) => Some(num), + Err(e) => None, + }, + }; + } + _ => panic!("parse_time_attributes(_) received a type that does not have TimeAttr"), + }; + + ctype +} + +pub fn parse_interval_attributes( + interval_type: &Option, + interval_precision: Option, + mut ctype: ColumnType, +) -> ColumnType { + match ctype { + Type::Interval(ref mut attr) => { + attr.field = interval_type.clone(); + attr.precision = match interval_precision { + None => None, + Some(num) => match u16::try_from(num) { + Ok(num) => Some(num), + Err(e) => None, + }, + }; + } + _ => panic!("parse_interval_attributes(_) received a type that does not have IntervalAttr"), + }; + + ctype +} + +pub fn parse_bit_attributes( + character_maximum_length: Option, + mut ctype: ColumnType, +) -> ColumnType { + match ctype { + Type::Bit(ref mut attr) => { + attr.length = match character_maximum_length { + None => None, + Some(num) => match u16::try_from(num) { + Ok(num) => Some(num), + Err(e) => None, + }, + }; + } + _ => panic!("parse_bit_attributes(_) received a type that does not have BitAttr"), + }; + + ctype +} diff --git a/src/postgres/parser/mod.rs b/src/postgres/parser/mod.rs index fcd32b93..c3583df6 100644 --- a/src/postgres/parser/mod.rs +++ b/src/postgres/parser/mod.rs @@ -4,6 +4,7 @@ mod table_constraints; pub use column::*; pub use table::*; +pub use table_constraints::*; fn yes_or_no_to_bool(string: &str) -> bool { matches!(string.to_uppercase().as_str(), "YES") diff --git a/src/postgres/parser/table_constraints.rs b/src/postgres/parser/table_constraints.rs index 1997fadc..d4fe2f11 100644 --- a/src/postgres/parser/table_constraints.rs +++ b/src/postgres/parser/table_constraints.rs @@ -31,10 +31,11 @@ impl Iterator for TableConstraintsQueryResultParser { return None; }; - let constriant_name = result.constraint_name; + let constraint_name = result.constraint_name; match result.constraint_type.as_str() { "CHECK" => { Some(Constraint::Check(Check { + name: constraint_name, expr: result.check_clause.unwrap().to_string(), // TODO: How to find? no_inherit: false, @@ -48,14 +49,21 @@ impl Iterator for TableConstraintsQueryResultParser { columns.push(result.column_name.unwrap()); let table = result.referential_key_table_name.unwrap(); foreign_columns.push(result.referential_key_column_name.unwrap()); + let on_update = + ForeignKeyAction::from_str(&result.update_rule.clone().unwrap_or_default()); + let on_delete = + ForeignKeyAction::from_str(&result.delete_rule.clone().unwrap_or_default()); while let Some(result) = self.results.next() { - if result.constraint_name != constriant_name { + if result.constraint_name != constraint_name { self.curr = Some(result); return Some(Constraint::References(References { + name: constraint_name, columns, table, foreign_columns, + on_update, + on_delete, })); } @@ -64,9 +72,12 @@ impl Iterator for TableConstraintsQueryResultParser { } Some(Constraint::References(References { + name: constraint_name, columns, table, foreign_columns, + on_update, + on_delete, })) } @@ -76,15 +87,21 @@ impl Iterator for TableConstraintsQueryResultParser { columns.push(result.column_name.unwrap()); while let Some(result) = self.results.next() { - if result.constraint_name != constriant_name { + if result.constraint_name != constraint_name { self.curr = Some(result); - return Some(Constraint::PrimaryKey(PrimaryKey(columns))); + return Some(Constraint::PrimaryKey(PrimaryKey { + name: constraint_name, + columns, + })); } columns.push(result.column_name.unwrap()); } - Some(Constraint::PrimaryKey(PrimaryKey(columns))) + Some(Constraint::PrimaryKey(PrimaryKey { + name: constraint_name, + columns, + })) } "UNIQUE" => { @@ -93,16 +110,23 @@ impl Iterator for TableConstraintsQueryResultParser { columns.push(result.column_name.unwrap()); while let Some(result) = self.results.next() { - if result.constraint_name != constriant_name { + if result.constraint_name != constraint_name { self.curr = Some(result); - return Some(Constraint::PrimaryKey(PrimaryKey(columns))); + return Some(Constraint::Unique(Unique { + name: constraint_name, + columns, + })); } columns.push(result.column_name.unwrap()); } - Some(Constraint::PrimaryKey(PrimaryKey(columns))) + Some(Constraint::Unique(Unique { + name: constraint_name, + columns, + })) } + _ => { // FIXME: Invalid input error handling None diff --git a/src/postgres/query/column.rs b/src/postgres/query/column.rs index 38f24f89..3bba4f5a 100644 --- a/src/postgres/query/column.rs +++ b/src/postgres/query/column.rs @@ -14,13 +14,14 @@ pub enum ColumnsField { ColumnDefault, IsNullable, DataType, - CharacterMaximumlength, + CharacterMaximumLength, CharacterOctetLength, NumericPrecision, NumericPrecisionRadix, NumericScale, DatetimePrecision, IntervalType, + IntervalPrecision, CollationCatalog, CollationSchema, CollationName, @@ -39,7 +40,7 @@ pub enum ColumnsField { IdentityMinimum, IdentityCycle, IsGenerated, - GeneratedExpression, + GenerationExpression, IsUpdatable, } @@ -55,6 +56,14 @@ pub struct ColumnQueryResult { pub numeric_precision: Option, pub numeric_precision_radix: Option, pub numeric_scale: Option, + + pub character_maximum_length: Option, + pub character_octet_length: Option, + + pub datetime_precision: Option, + + pub interval_type: Option, + pub interval_precision: Option, } impl SchemaQueryBuilder { @@ -64,11 +73,16 @@ impl SchemaQueryBuilder { ColumnsField::ColumnName, ColumnsField::DataType, ColumnsField::ColumnDefault, - ColumnsField::GeneratedExpression, + ColumnsField::GenerationExpression, ColumnsField::IsNullable, ColumnsField::NumericPrecision, ColumnsField::NumericPrecisionRadix, ColumnsField::NumericScale, + ColumnsField::CharacterMaximumLength, + ColumnsField::CharacterOctetLength, + ColumnsField::DatetimePrecision, + ColumnsField::IntervalType, + ColumnsField::IntervalPrecision, ]) .from((InformationSchema::Schema, InformationSchema::Columns)) .and_where(Expr::col(ColumnsField::TableSchema).eq(schema.to_string())) @@ -77,7 +91,7 @@ impl SchemaQueryBuilder { } } -#[cfg(feature = "sqlx-postres")] +#[cfg(feature = "sqlx-postgres")] impl From<&PgRow> for ColumnQueryResult { fn from(row: &PgRow) -> Self { Self { @@ -89,11 +103,16 @@ impl From<&PgRow> for ColumnQueryResult { numeric_precision: row.get(5), numeric_precision_radix: row.get(6), numeric_scale: row.get(7), + character_maximum_length: row.get(8), + character_octet_length: row.get(9), + datetime_precision: row.get(10), + interval_type: row.get(11), + interval_precision: row.get(12), } } } -#[cfg(not(feature = "sqlx-postres"))] +#[cfg(not(feature = "sqlx-postgres"))] impl From<&PgRow> for ColumnQueryResult { fn from(row: &PgRow) -> Self { Self::default() diff --git a/src/postgres/query/constraints/mod.rs b/src/postgres/query/constraints/mod.rs index d5f00733..9531258d 100644 --- a/src/postgres/query/constraints/mod.rs +++ b/src/postgres/query/constraints/mod.rs @@ -9,7 +9,10 @@ pub use referential_constraints::*; pub use table_constraints::*; use super::{InformationSchema, SchemaQueryBuilder}; -use crate::sqlx_types::{postgres::PgRow, Row}; +use crate::{ + postgres::def::Constraint, + sqlx_types::{postgres::PgRow, Row}, +}; use sea_query::{Alias, Expr, Iden, JoinType, Order, Query, SelectStatement}; use std::rc::Rc; @@ -45,7 +48,11 @@ pub struct TableConstraintsQueryResult { } impl SchemaQueryBuilder { - pub fn query_table_constriants(schema: Rc, table: Rc) -> SelectStatement { + pub fn query_table_constriants( + &self, + schema: Rc, + table: Rc, + ) -> SelectStatement { type Schema = InformationSchema; type Tcf = TableConstraintsField; type Cf = CheckConstraintsFields; @@ -58,6 +65,8 @@ impl SchemaQueryBuilder { .columns(vec![ (Schema::TableConstraints, Tcf::ConstraintSchema), (Schema::TableConstraints, Tcf::ConstraintName), + (Schema::TableConstraints, Tcf::TableSchema), + (Schema::TableConstraints, Tcf::TableName), (Schema::TableConstraints, Tcf::ConstraintType), (Schema::TableConstraints, Tcf::IsDeferrable), (Schema::TableConstraints, Tcf::InitiallyDeferred), @@ -130,7 +139,7 @@ impl SchemaQueryBuilder { } } -#[cfg(feature = "sqlx-postres")] +#[cfg(feature = "sqlx-postgres")] impl From<&PgRow> for TableConstraintsQueryResult { fn from(row: &PgRow) -> Self { Self { @@ -160,7 +169,7 @@ impl From<&PgRow> for TableConstraintsQueryResult { } } -#[cfg(not(feature = "sqlx-postres"))] +#[cfg(not(feature = "sqlx-postgres"))] impl From<&PgRow> for TableConstraintsQueryResult { fn from(_row: &PgRow) -> Self { Self::default() diff --git a/src/postgres/query/table.rs b/src/postgres/query/table.rs index ea3ac4e3..087f9a58 100644 --- a/src/postgres/query/table.rs +++ b/src/postgres/query/table.rs @@ -51,7 +51,7 @@ impl SchemaQueryBuilder { } } -#[cfg(feature = "sqlx-postres")] +#[cfg(feature = "sqlx-postgres")] impl From<&PgRow> for TableQueryResult { fn from(row: &PgRow) -> Self { Self { @@ -62,7 +62,7 @@ impl From<&PgRow> for TableQueryResult { } } -#[cfg(not(feature = "sqlx-postres"))] +#[cfg(not(feature = "sqlx-postgres"))] impl From<&PgRow> for TableQueryResult { fn from(row: &PgRow) -> Self { Self::default() diff --git a/src/postgres/writer/column.rs b/src/postgres/writer/column.rs new file mode 100644 index 00000000..41143d65 --- /dev/null +++ b/src/postgres/writer/column.rs @@ -0,0 +1,219 @@ +use crate::postgres::def::{ArbitraryPrecisionNumericAttr, ColumnInfo, Type}; +use core::num; +use sea_query::{escape_string, Alias, ColumnDef, Iden}; +use std::{default, fmt::Write}; + +impl ColumnInfo { + pub fn write(&self) -> ColumnDef { + let mut self_copy = self.clone(); + let mut col_def = ColumnDef::new(Alias::new(self.name.as_str())); + let mut extras: Vec = Vec::new(); + if let Some(default) = self.default.as_ref() { + if default.0.starts_with("nextval") { + match self.col_type { + Type::SmallInt => { + self_copy.col_type = Type::SmallSerial; + } + Type::Integer => { + self_copy.col_type = Type::Serial; + } + Type::BigInt => { + self_copy.col_type = Type::BigSerial; + } + _ => {} + } + } else { + let mut string = "".to_owned(); + write!(&mut string, "DEFAULT {}", default.0).unwrap(); + extras.push(string); + } + } + col_def = self_copy.write_col_type(col_def); + if self.not_null.is_some() { + col_def = col_def.not_null(); + } + if !extras.is_empty() { + col_def = col_def.extra(extras.join(" ")); + } + col_def + } + + pub fn write_col_type(&self, mut col_def: ColumnDef) -> ColumnDef { + match &self.col_type { + Type::SmallInt => { + col_def = col_def.small_integer(); + } + Type::Integer => { + col_def = col_def.integer(); + } + Type::BigInt => { + col_def = col_def.big_integer(); + } + Type::Decimal(num_attr) | Type::Numeric(num_attr) => { + if num_attr.precision.is_none() & num_attr.scale.is_none() { + col_def = col_def.decimal(); + } else { + col_def = col_def.decimal_len( + num_attr.precision.unwrap_or(0) as u32, + num_attr.scale.unwrap_or(0) as u32, + ); + } + } + Type::Real => { + col_def = col_def.float(); + } + Type::DoublePrecision => { + col_def = col_def.double(); + } + Type::SmallSerial => { + col_def = col_def.small_integer().auto_increment(); + } + Type::Serial => { + col_def = col_def.integer().auto_increment(); + } + Type::BigSerial => { + col_def = col_def.big_integer().auto_increment(); + } + Type::Money => { + col_def = col_def.money(); + } + Type::Varchar(string_attr) => { + col_def = match string_attr.length { + Some(length) => col_def.string_len(length.into()), + None => col_def.string(), + }; + } + Type::Char(string_attr) => { + col_def = match string_attr.length { + Some(length) => col_def.char_len(length.into()), + None => col_def.char(), + }; + } + Type::Text => { + col_def = col_def.text(); + } + Type::Bytea => { + col_def = col_def.binary(); + } + Type::Timestamp(time_attr) => { + col_def = match time_attr.precision { + Some(precision) => col_def.timestamp_len(precision.into()), + None => col_def.timestamp(), + }; + } + Type::TimestampWithTimeZone(time_attr) => { + col_def = match time_attr.precision { + Some(precision) => col_def.timestamp_len(precision.into()), + None => col_def.timestamp(), + }; + } + Type::Date => { + col_def = col_def.date(); + } + Type::Time(time_attr) => { + col_def = match time_attr.precision { + Some(precision) => col_def.time_len(precision.into()), + None => col_def.time(), + }; + } + Type::TimeWithTimeZone(time_attr) => { + col_def = match time_attr.precision { + Some(precision) => col_def.time_len(precision.into()), + None => col_def.time(), + }; + } + Type::Interval(time_attr) => {} + Type::Boolean => { + col_def = col_def.boolean(); + } + Type::Point => { + col_def = col_def.custom(Alias::new("point")); + } + Type::Line => { + col_def = col_def.custom(Alias::new("line")); + } + Type::Lseg => { + col_def = col_def.custom(Alias::new("lseg")); + } + Type::Box => { + col_def = col_def.custom(Alias::new("box")); + } + Type::Path => { + col_def = col_def.custom(Alias::new("path")); + } + Type::Polygon => { + col_def = col_def.custom(Alias::new("ploygon")); + } + Type::Circle => { + col_def = col_def.custom(Alias::new("circle")); + } + Type::Cidr => { + col_def = col_def.custom(Alias::new("cidr")); + } + Type::Inet => { + col_def = col_def.custom(Alias::new("inet")); + } + Type::MacAddr => { + col_def = col_def.custom(Alias::new("macaddr")); + } + Type::MacAddr8 => { + col_def = col_def.custom(Alias::new("macaddr8")); + } + Type::Bit(bit_attr) => { + let mut str = String::new(); + write!(str, "bit"); + if bit_attr.length.is_some() { + write!(str, "("); + if let Some(length) = bit_attr.length { + write!(str, "{}", length); + } + write!(str, ")"); + } + col_def = col_def.custom(Alias::new(&str)); + } + Type::TsVector => { + col_def = col_def.custom(Alias::new("tsvector")); + } + Type::TsQuery => { + col_def = col_def.custom(Alias::new("tsquery")); + } + Type::Uuid => { + col_def = col_def.custom(Alias::new("uuid")); + } + Type::Xml => { + col_def = col_def.custom(Alias::new("xml")); + } + Type::Json => { + col_def = col_def.custom(Alias::new("json")); + } + Type::Array => { + col_def = col_def.custom(Alias::new("array")); + } + Type::Int4Range => { + col_def = col_def.custom(Alias::new("int4range")); + } + Type::Int8Range => { + col_def = col_def.custom(Alias::new("int8range")); + } + Type::NumRange => { + col_def = col_def.custom(Alias::new("numrange")); + } + Type::TsRange => { + col_def = col_def.custom(Alias::new("tsrange")); + } + Type::TsTzRange => { + col_def = col_def.custom(Alias::new("tstzrange")); + } + Type::DateRange => { + col_def = col_def.custom(Alias::new("daterange")); + } + Type::PgLsn => { + col_def = col_def.custom(Alias::new("pg_lsn")); + } + Type::Unknown(s) => { + col_def = col_def.custom(Alias::new(s)); + } + }; + col_def + } +} diff --git a/src/postgres/writer/constraints.rs b/src/postgres/writer/constraints.rs new file mode 100644 index 00000000..44e8666e --- /dev/null +++ b/src/postgres/writer/constraints.rs @@ -0,0 +1,54 @@ +use crate::postgres::def::{ForeignKeyAction, PrimaryKey, References, Unique}; +use sea_query::{Alias, ForeignKey, ForeignKeyCreateStatement, Index, IndexCreateStatement}; + +impl PrimaryKey { + pub fn write(&self) -> IndexCreateStatement { + let mut idx = Index::create().primary().name(&self.name); + for col in self.columns.iter() { + idx = idx.col(Alias::new(col)); + } + idx + } +} + +impl Unique { + pub fn write(&self) -> IndexCreateStatement { + let mut idx = Index::create().unique().name(&self.name); + for col in self.columns.iter() { + idx = idx.col(Alias::new(col)); + } + idx + } +} + +impl References { + pub fn write(&self) -> ForeignKeyCreateStatement { + let mut key = ForeignKey::create().name(&self.name); + key = key.to_tbl(Alias::new(&self.table)); + for column in self.columns.iter() { + key = key.from_col(Alias::new(&column)); + } + for ref_col in self.foreign_columns.iter() { + key = key.to_col(Alias::new(&ref_col)); + } + if let Some(on_update) = &self.on_update { + key = key.on_update(match on_update { + ForeignKeyAction::Cascade => sea_query::ForeignKeyAction::Cascade, + ForeignKeyAction::SetNull => sea_query::ForeignKeyAction::SetNull, + ForeignKeyAction::SetDefault => sea_query::ForeignKeyAction::SetDefault, + ForeignKeyAction::Restrict => sea_query::ForeignKeyAction::Restrict, + ForeignKeyAction::NoAction => sea_query::ForeignKeyAction::NoAction, + }); + } + if let Some(on_delete) = &self.on_delete { + key = key.on_delete(match on_delete { + ForeignKeyAction::Cascade => sea_query::ForeignKeyAction::Cascade, + ForeignKeyAction::SetNull => sea_query::ForeignKeyAction::SetNull, + ForeignKeyAction::SetDefault => sea_query::ForeignKeyAction::SetDefault, + ForeignKeyAction::Restrict => sea_query::ForeignKeyAction::Restrict, + ForeignKeyAction::NoAction => sea_query::ForeignKeyAction::NoAction, + }); + } + key + } +} diff --git a/src/postgres/writer/mod.rs b/src/postgres/writer/mod.rs new file mode 100644 index 00000000..818d9dfc --- /dev/null +++ b/src/postgres/writer/mod.rs @@ -0,0 +1,24 @@ +mod column; +mod constraints; +mod schema; +mod table; +mod types; + +pub use column::*; +pub use constraints::*; +pub use schema::*; +pub use table::*; +pub use types::*; + +use super::def::Schema; +use sea_query::SchemaStatement; + +impl Schema { + pub fn write(&self) -> Vec { + let mut statements = Vec::new(); + for table in self.tables.iter() { + statements.push(SchemaStatement::TableStatement(table.write())); + } + statements + } +} diff --git a/src/postgres/writer/schema.rs b/src/postgres/writer/schema.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/postgres/writer/schema.rs @@ -0,0 +1 @@ + diff --git a/src/postgres/writer/table.rs b/src/postgres/writer/table.rs new file mode 100644 index 00000000..b143cba6 --- /dev/null +++ b/src/postgres/writer/table.rs @@ -0,0 +1,22 @@ +use crate::postgres::def::TableDef; +use sea_query::{Alias, Iden, Table, TableStatement}; + +impl TableDef { + pub fn write(&self) -> TableStatement { + let mut table = Table::create(); + table.table(Alias::new(self.info.name.as_ref())); + for col in self.columns.iter() { + table.col(col.write()); + } + for primary_key in self.primary_key_constraints.iter() { + table.primary_key(primary_key.write()); + } + for unique in self.unique_constraints.iter() { + table.index(unique.write()); + } + for reference in self.reference_constraints.iter() { + table.foreign_key(reference.write()); + } + TableStatement::Create(table) + } +} diff --git a/src/postgres/writer/types.rs b/src/postgres/writer/types.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/postgres/writer/types.rs @@ -0,0 +1 @@ + diff --git a/src/sqlx_types/mock.rs b/src/sqlx_types/mock.rs index b107605f..cbcd37d2 100644 --- a/src/sqlx_types/mock.rs +++ b/src/sqlx_types/mock.rs @@ -4,6 +4,8 @@ pub mod mysql { pub struct MySqlRow; } +pub struct PgPool; + pub mod postgres { pub struct PgRow; } diff --git a/tests/discovery/Cargo.toml b/tests/discovery/Cargo.toml deleted file mode 100644 index 49cef6b4..00000000 --- a/tests/discovery/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "sea-schema-discovery-test" -version = "0.1.0" -edition = "2018" -publish = false - -[dependencies] -async-std = { version = "1.8", features = [ "attributes" ] } -sea-schema = { path = "../../", default-features = false, features = [ "with-serde", "sqlx-mysql", "runtime-async-std-native-tls", "discovery" ] } -sea-query = { version = "^0.12" } -serde_json = { version = "^1" } -sqlx = { version = "^0" } \ No newline at end of file diff --git a/tests/discovery/mysql/Cargo.toml b/tests/discovery/mysql/Cargo.toml new file mode 100644 index 00000000..5fb9dfe2 --- /dev/null +++ b/tests/discovery/mysql/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sea-schema-discovery-test-mysql" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +async-std = { version = "1.8", features = [ "attributes" ] } +sea-schema = { path = "../../../", default-features = false, features = [ "with-serde", "sqlx-mysql", "runtime-async-std-native-tls", "discovery" ] } +serde_json = { version = "^1" } +sqlx = { version = "^0" } \ No newline at end of file diff --git a/tests/discovery/Readme.md b/tests/discovery/mysql/Readme.md similarity index 100% rename from tests/discovery/Readme.md rename to tests/discovery/mysql/Readme.md diff --git a/tests/discovery/schema.rs b/tests/discovery/mysql/schema.rs similarity index 100% rename from tests/discovery/schema.rs rename to tests/discovery/mysql/schema.rs diff --git a/tests/discovery/src/main.rs b/tests/discovery/mysql/src/main.rs similarity index 100% rename from tests/discovery/src/main.rs rename to tests/discovery/mysql/src/main.rs diff --git a/tests/discovery/postgres/Cargo.toml b/tests/discovery/postgres/Cargo.toml new file mode 100644 index 00000000..e730a51a --- /dev/null +++ b/tests/discovery/postgres/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sea-schema-discovery-test-postgres" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +async-std = { version = "1.8", features = [ "attributes" ] } +sea-schema = { path = "../../../", default-features = false, features = [ "with-serde", "sqlx-postgres", "runtime-async-std-native-tls", "discovery" ] } +serde_json = { version = "^1" } +sqlx = { version = "^0" } \ No newline at end of file diff --git a/tests/discovery/postgres/Readme.md b/tests/discovery/postgres/Readme.md new file mode 100644 index 00000000..f75aafcf --- /dev/null +++ b/tests/discovery/postgres/Readme.md @@ -0,0 +1,5 @@ +# Run + +```sh +cargo run > schema.rs +``` \ No newline at end of file diff --git a/tests/discovery/postgres/schema.rs b/tests/discovery/postgres/schema.rs new file mode 100644 index 00000000..a422fac8 --- /dev/null +++ b/tests/discovery/postgres/schema.rs @@ -0,0 +1,2925 @@ +Schema { + schema: "public", + tables: [ + TableDef { + info: TableInfo { + name: "film", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "film_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('film_film_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "title", + col_type: Char( + StringAttr { + length: Some( + 255, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "description", + col_type: Text, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "release_year", + col_type: Integer, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "language_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "original_language_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "rental_duration", + col_type: SmallInt, + default: Some( + ColumnExpression( + "3", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_rate", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 4, + ), + scale: Some( + 2, + ), + }, + ), + default: Some( + ColumnExpression( + "4.99", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "length", + col_type: SmallInt, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "replacement_cost", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 5, + ), + scale: Some( + 2, + ), + }, + ), + default: Some( + ColumnExpression( + "19.99", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rating", + col_type: Unknown( + "USER", + ), + default: Some( + ColumnExpression( + "'G'::mpaa_rating", + ), + ), + generated: None, + not_null: None, + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "special_features", + col_type: Array, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "fulltext", + col_type: TsVector, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "replacement_cost IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "fulltext IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "film_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "title IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "language_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_duration IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_rate IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "film_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "language_id", + ], + table: "language", + foreign_columns: [ + "language_id", + ], + }, + References { + columns: [ + "original_language_id", + ], + table: "language", + foreign_columns: [ + "language_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "address", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "address_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('address_address_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "address", + col_type: Char( + StringAttr { + length: Some( + 50, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "address2", + col_type: Char( + StringAttr { + length: Some( + 50, + ), + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "district", + col_type: Char( + StringAttr { + length: Some( + 20, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "city_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "postal_code", + col_type: Char( + StringAttr { + length: Some( + 10, + ), + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "phone", + col_type: Char( + StringAttr { + length: Some( + 20, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "address_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "address IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "district IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "city_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "phone IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "address_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "city_id", + ], + table: "city", + foreign_columns: [ + "city_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "category", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "category_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('category_category_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "name", + col_type: Char( + StringAttr { + length: Some( + 25, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "category_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "name IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "category_id", + ], + ), + ], + reference_constraints: [], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "city", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "city_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('city_city_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "city", + col_type: Char( + StringAttr { + length: Some( + 50, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "country_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "city_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "city IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "country_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "city_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "country_id", + ], + table: "country", + foreign_columns: [ + "country_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "country", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "country_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('country_country_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "country", + col_type: Char( + StringAttr { + length: Some( + 50, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "country_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "country IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "country_id", + ], + ), + ], + reference_constraints: [], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "customer", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "customer_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('customer_customer_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "store_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "first_name", + col_type: Char( + StringAttr { + length: Some( + 45, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_name", + col_type: Char( + StringAttr { + length: Some( + 45, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "email", + col_type: Char( + StringAttr { + length: Some( + 50, + ), + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "address_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "activebool", + col_type: Boolean, + default: Some( + ColumnExpression( + "true", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "create_date", + col_type: Date, + default: Some( + ColumnExpression( + "('now'::text)::date", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: None, + }, + ColumnInfo { + name: "active", + col_type: Integer, + default: None, + generated: None, + not_null: None, + }, + ], + check_constraints: [ + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "store_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "first_name IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_name IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "address_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "activebool IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "create_date IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "customer_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "address_id", + ], + table: "address", + foreign_columns: [ + "address_id", + ], + }, + References { + columns: [ + "store_id", + ], + table: "store", + foreign_columns: [ + "store_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "film_actor", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "actor_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "film_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "actor_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "film_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "actor_id", + "film_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "actor_id", + ], + table: "actor", + foreign_columns: [ + "actor_id", + ], + }, + References { + columns: [ + "film_id", + ], + table: "film", + foreign_columns: [ + "film_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "film_category", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "film_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "category_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "film_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "category_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "film_id", + "category_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "category_id", + ], + table: "category", + foreign_columns: [ + "category_id", + ], + }, + References { + columns: [ + "film_id", + ], + table: "film", + foreign_columns: [ + "film_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "inventory", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "inventory_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('inventory_inventory_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "film_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "store_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "inventory_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "film_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "store_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "inventory_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "film_id", + ], + table: "film", + foreign_columns: [ + "film_id", + ], + }, + References { + columns: [ + "store_id", + ], + table: "store", + foreign_columns: [ + "store_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "language", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "language_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('language_language_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "name", + col_type: Char( + StringAttr { + length: Some( + 20, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "language_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "name IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "language_id", + ], + ), + ], + reference_constraints: [], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "rental", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "rental_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('rental_rental_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "inventory_id", + col_type: Integer, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "customer_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "return_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "rental_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_date IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "inventory_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "rental_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "customer_id", + ], + table: "customer", + foreign_columns: [ + "customer_id", + ], + }, + References { + columns: [ + "inventory_id", + ], + table: "inventory", + foreign_columns: [ + "inventory_id", + ], + }, + References { + columns: [ + "staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "staff", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "staff_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('staff_staff_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "first_name", + col_type: Char( + StringAttr { + length: Some( + 45, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_name", + col_type: Char( + StringAttr { + length: Some( + 45, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "address_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "email", + col_type: Char( + StringAttr { + length: Some( + 50, + ), + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "store_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "active", + col_type: Boolean, + default: Some( + ColumnExpression( + "true", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "username", + col_type: Char( + StringAttr { + length: Some( + 16, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "password", + col_type: Char( + StringAttr { + length: Some( + 40, + ), + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "picture", + col_type: Bytea, + default: None, + generated: None, + not_null: None, + }, + ], + check_constraints: [ + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "first_name IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_name IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "address_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "store_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "active IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "username IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "staff_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "address_id", + ], + table: "address", + foreign_columns: [ + "address_id", + ], + }, + References { + columns: [ + "store_id", + ], + table: "store", + foreign_columns: [ + "store_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "store", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "store_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('store_store_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "manager_staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "address_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "store_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "manager_staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "address_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "store_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "address_id", + ], + table: "address", + foreign_columns: [ + "address_id", + ], + }, + References { + columns: [ + "manager_staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "payment", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "payment_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('payment_payment_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "customer_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_id", + col_type: Integer, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "amount", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 5, + ), + scale: Some( + 2, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "payment_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "payment_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "amount IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "payment_date IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [ + PrimaryKey( + [ + "payment_id", + ], + ), + ], + reference_constraints: [ + References { + columns: [ + "customer_id", + ], + table: "customer", + foreign_columns: [ + "customer_id", + ], + }, + References { + columns: [ + "rental_id", + ], + table: "rental", + foreign_columns: [ + "rental_id", + ], + }, + References { + columns: [ + "staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "payment_p2007_01", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "payment_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('payment_payment_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "customer_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_id", + col_type: Integer, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "amount", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 5, + ), + scale: Some( + 2, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "payment_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "payment_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "amount IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "payment_date IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "(((payment_date >= '2007-01-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-02-01 00:00:00'::timestamp without time zone)))", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [], + reference_constraints: [ + References { + columns: [ + "customer_id", + ], + table: "customer", + foreign_columns: [ + "customer_id", + ], + }, + References { + columns: [ + "rental_id", + ], + table: "rental", + foreign_columns: [ + "rental_id", + ], + }, + References { + columns: [ + "staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "payment_p2007_02", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "payment_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('payment_payment_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "customer_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_id", + col_type: Integer, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "amount", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 5, + ), + scale: Some( + 2, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "payment_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "payment_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "amount IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "payment_date IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "(((payment_date >= '2007-02-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-03-01 00:00:00'::timestamp without time zone)))", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [], + reference_constraints: [ + References { + columns: [ + "customer_id", + ], + table: "customer", + foreign_columns: [ + "customer_id", + ], + }, + References { + columns: [ + "rental_id", + ], + table: "rental", + foreign_columns: [ + "rental_id", + ], + }, + References { + columns: [ + "staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "payment_p2007_03", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "payment_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('payment_payment_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "customer_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_id", + col_type: Integer, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "amount", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 5, + ), + scale: Some( + 2, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "payment_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "payment_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "amount IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "payment_date IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "(((payment_date >= '2007-03-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-04-01 00:00:00'::timestamp without time zone)))", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [], + reference_constraints: [ + References { + columns: [ + "customer_id", + ], + table: "customer", + foreign_columns: [ + "customer_id", + ], + }, + References { + columns: [ + "rental_id", + ], + table: "rental", + foreign_columns: [ + "rental_id", + ], + }, + References { + columns: [ + "staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "payment_p2007_04", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "payment_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('payment_payment_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "customer_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_id", + col_type: Integer, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "amount", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 5, + ), + scale: Some( + 2, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "payment_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "payment_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "amount IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "payment_date IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "(((payment_date >= '2007-04-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-05-01 00:00:00'::timestamp without time zone)))", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [], + reference_constraints: [ + References { + columns: [ + "customer_id", + ], + table: "customer", + foreign_columns: [ + "customer_id", + ], + }, + References { + columns: [ + "rental_id", + ], + table: "rental", + foreign_columns: [ + "rental_id", + ], + }, + References { + columns: [ + "staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "payment_p2007_05", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "payment_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('payment_payment_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "customer_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_id", + col_type: Integer, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "amount", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 5, + ), + scale: Some( + 2, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "payment_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "payment_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "amount IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "payment_date IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "(((payment_date >= '2007-05-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-06-01 00:00:00'::timestamp without time zone)))", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [], + reference_constraints: [ + References { + columns: [ + "customer_id", + ], + table: "customer", + foreign_columns: [ + "customer_id", + ], + }, + References { + columns: [ + "rental_id", + ], + table: "rental", + foreign_columns: [ + "rental_id", + ], + }, + References { + columns: [ + "staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "payment_p2007_06", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "payment_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('payment_payment_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "customer_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "staff_id", + col_type: SmallInt, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "rental_id", + col_type: Integer, + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "amount", + col_type: Numeric( + ArbitraryPrecisionNumericAttr { + precision: Some( + 5, + ), + scale: Some( + 2, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "payment_date", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ], + check_constraints: [ + Check { + expr: "payment_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "customer_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "staff_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "rental_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "amount IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "payment_date IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "(((payment_date >= '2007-06-01 00:00:00'::timestamp without time zone) AND (payment_date < '2007-07-01 00:00:00'::timestamp without time zone)))", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [], + primary_key_constraints: [], + reference_constraints: [ + References { + columns: [ + "customer_id", + ], + table: "customer", + foreign_columns: [ + "customer_id", + ], + }, + References { + columns: [ + "rental_id", + ], + table: "rental", + foreign_columns: [ + "rental_id", + ], + }, + References { + columns: [ + "staff_id", + ], + table: "staff", + foreign_columns: [ + "staff_id", + ], + }, + ], + exclusion_constraints: [], + }, + TableDef { + info: TableInfo { + name: "actor", + of_type: None, + }, + columns: [ + ColumnInfo { + name: "actor_id", + col_type: Integer, + default: Some( + ColumnExpression( + "nextval('actor_actor_id_seq'::regclass)", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "first_name", + col_type: Char( + StringAttr { + length: Some( + 45, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_name", + col_type: Char( + StringAttr { + length: Some( + 45, + ), + }, + ), + default: None, + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "last_update", + col_type: Timestamp( + TimeAttr { + precision: Some( + 6, + ), + }, + ), + default: Some( + ColumnExpression( + "now()", + ), + ), + generated: None, + not_null: Some( + NotNull, + ), + }, + ColumnInfo { + name: "interval", + col_type: Interval( + IntervalAttr { + field: Some( + "DAY TO SECOND(2)", + ), + precision: None, + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "bit", + col_type: Bit( + BitAttr { + length: Some( + 20, + ), + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "bit_varying", + col_type: Bit( + BitAttr { + length: Some( + 10, + ), + }, + ), + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "ts_vector", + col_type: TsVector, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "ts_query", + col_type: TsQuery, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "uuid", + col_type: Uuid, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "xml", + col_type: Xml, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "json", + col_type: Json, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "array_int", + col_type: Array, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "array_int_int", + col_type: Array, + default: None, + generated: None, + not_null: None, + }, + ColumnInfo { + name: "pg_lsn", + col_type: PgLsn, + default: None, + generated: None, + not_null: None, + }, + ], + check_constraints: [ + Check { + expr: "actor_id IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "first_name IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_name IS NOT NULL", + no_inherit: false, + }, + Check { + expr: "last_update IS NOT NULL", + no_inherit: false, + }, + ], + not_null_constraints: [], + unique_constraints: [ + Unique( + [ + "actor_id", + ], + ), + ], + primary_key_constraints: [ + PrimaryKey( + [ + "actor_id", + ], + ), + ], + reference_constraints: [], + exclusion_constraints: [], + }, + ], +} diff --git a/tests/discovery/postgres/src/main.rs b/tests/discovery/postgres/src/main.rs new file mode 100644 index 00000000..2f9735de --- /dev/null +++ b/tests/discovery/postgres/src/main.rs @@ -0,0 +1,17 @@ +use sea_schema::postgres::discovery::SchemaDiscovery; +use sqlx::PgPool; + +#[async_std::main] +async fn main() { + let connection = PgPool::connect("postgres://sea:sea@localhost/sakila") + .await + .unwrap(); + + let schema_discovery = SchemaDiscovery::new(connection, "public"); + + let schema = schema_discovery.discover().await; + + // println!("{}", serde_json::to_string_pretty(&schema).unwrap()); + + println!("{:#?}", schema); +} diff --git a/tests/live/postgres/Cargo.toml b/tests/live/postgres/Cargo.toml new file mode 100644 index 00000000..7fffd599 --- /dev/null +++ b/tests/live/postgres/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sea-schema-live-test-postgres" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +async-std = { version = "1.8", features = [ "attributes" ] } +sea-schema = { path = "../../../", default-features = false, features = [ "sqlx-postgres", "runtime-async-std-native-tls", "discovery", "writer", "debug-print" ] } +serde_json = { version = "^1" } +sqlx = { version = "^0" } \ No newline at end of file diff --git a/tests/live/postgres/Readme.md b/tests/live/postgres/Readme.md new file mode 100644 index 00000000..f42d5d6c --- /dev/null +++ b/tests/live/postgres/Readme.md @@ -0,0 +1,5 @@ +# Run + +```sh +cargo run +``` \ No newline at end of file diff --git a/tests/live/postgres/src/main.rs b/tests/live/postgres/src/main.rs new file mode 100644 index 00000000..d081229a --- /dev/null +++ b/tests/live/postgres/src/main.rs @@ -0,0 +1,278 @@ +use std::collections::HashMap; + +use sea_schema::postgres::{def::TableDef, discovery::SchemaDiscovery}; +use sea_schema::sea_query::{ + Alias, ColumnDef, ForeignKey, ForeignKeyAction, Index, PostgresQueryBuilder, Table, + TableCreateStatement, +}; +use sqlx::{Executor, PgPool, Pool, Postgres}; + +#[cfg_attr(test, async_std::test)] +#[cfg_attr(not(test), async_std::main)] +async fn main() { + let mut connection = setup("postgres://sea:sea@localhost", "sea-schema").await; + let mut executor = connection.acquire().await.unwrap(); + + let tbl_create_stmts = vec![ + create_bakery_table(), + create_baker_table(), + create_customer_table(), + create_order_table(), + create_cake_table(), + create_cakes_bakers_table(), + create_lineitem_table(), + ]; + + for tbl_create_stmt in tbl_create_stmts.iter() { + let sql = tbl_create_stmt.to_string(PostgresQueryBuilder); + println!("{};", sql); + println!(); + sqlx::query(&sql).execute(&mut executor).await.unwrap(); + } + + let schema_discovery = SchemaDiscovery::new(connection, "public"); + + let schema = schema_discovery.discover().await; + + println!("{:#?}", schema); + + let map: HashMap = schema + .tables + .iter() + .map(|table| (table.info.name.clone(), table.clone())) + .collect(); + + for tbl_create_stmt in tbl_create_stmts.into_iter() { + let expected_sql = tbl_create_stmt.to_string(PostgresQueryBuilder); + let table = map.get(&tbl_create_stmt.get_table_name().unwrap()).unwrap(); + let sql = table.write().to_string(PostgresQueryBuilder); + println!("Expected SQL:"); + println!("{};", expected_sql); + println!("Generated SQL:"); + println!("{};", sql); + println!(); + assert_eq!(expected_sql, sql); + } +} + +async fn setup(base_url: &str, db_name: &str) -> Pool { + let url = format!("{}/postgres", base_url); + let mut connection = PgPool::connect(&url) + .await + .unwrap() + .acquire() + .await + .unwrap(); + + let _drop_db_result = sqlx::query(&format!("DROP DATABASE IF EXISTS \"{}\";", db_name)) + .bind(db_name) + .execute(&mut connection) + .await + .unwrap(); + + let _create_db_result = sqlx::query(&format!("CREATE DATABASE \"{}\";", db_name)) + .execute(&mut connection) + .await + .unwrap(); + + let url = format!("{}/{}", base_url, db_name); + PgPool::connect(&url).await.unwrap() +} + +fn create_bakery_table() -> TableCreateStatement { + Table::create() + .table(Alias::new("bakery")) + .col( + ColumnDef::new(Alias::new("id")) + .integer() + .not_null() + .auto_increment(), + ) + .col(ColumnDef::new(Alias::new("name")).string()) + .col(ColumnDef::new(Alias::new("profit_margin")).double()) + .primary_key( + Index::create() + .primary() + .name("bakery_pkey") + .col(Alias::new("id")), + ) + .to_owned() +} + +fn create_baker_table() -> TableCreateStatement { + Table::create() + .table(Alias::new("baker")) + .col( + ColumnDef::new(Alias::new("id")) + .integer() + .not_null() + .auto_increment(), + ) + .col(ColumnDef::new(Alias::new("name")).string()) + .col(ColumnDef::new(Alias::new("contact_details")).json()) + .col(ColumnDef::new(Alias::new("bakery_id")).integer()) + .primary_key( + Index::create() + .primary() + .name("baker_pkey") + .col(Alias::new("id")), + ) + .foreign_key( + ForeignKey::create() + .name("FK_baker_bakery") + .from(Alias::new("baker"), Alias::new("bakery_id")) + .to(Alias::new("bakery"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned() +} + +fn create_customer_table() -> TableCreateStatement { + Table::create() + .table(Alias::new("customer")) + .col( + ColumnDef::new(Alias::new("id")) + .integer() + .not_null() + .auto_increment(), + ) + .col(ColumnDef::new(Alias::new("name")).string()) + .col(ColumnDef::new(Alias::new("notes")).text()) + .primary_key( + Index::create() + .primary() + .name("customer_pkey") + .col(Alias::new("id")), + ) + .to_owned() +} + +fn create_order_table() -> TableCreateStatement { + Table::create() + .table(Alias::new("order")) + .col( + ColumnDef::new(Alias::new("id")) + .integer() + .not_null() + .auto_increment(), + ) + .col(ColumnDef::new(Alias::new("total")).decimal_len(19, 4)) + .col(ColumnDef::new(Alias::new("bakery_id")).integer().not_null()) + .col( + ColumnDef::new(Alias::new("customer_id")) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(Alias::new("placed_at")) + .timestamp_len(6) + .not_null(), + ) + .primary_key( + Index::create() + .primary() + .name("order_pkey") + .col(Alias::new("id")), + ) + .foreign_key( + ForeignKey::create() + .name("FK_order_bakery") + .from(Alias::new("order"), Alias::new("bakery_id")) + .to(Alias::new("bakery"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("FK_order_customer") + .from(Alias::new("order"), Alias::new("customer_id")) + .to(Alias::new("customer"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned() +} + +fn create_lineitem_table() -> TableCreateStatement { + Table::create() + .table(Alias::new("lineitem")) + .col( + ColumnDef::new(Alias::new("id")) + .integer() + .not_null() + .auto_increment(), + ) + .col(ColumnDef::new(Alias::new("price")).decimal_len(19, 4)) + .col(ColumnDef::new(Alias::new("quantity")).integer()) + .col(ColumnDef::new(Alias::new("order_id")).integer().not_null()) + .col(ColumnDef::new(Alias::new("cake_id")).integer().not_null()) + .primary_key( + Index::create() + .primary() + .name("lineitem_pkey") + .col(Alias::new("id")), + ) + .foreign_key( + ForeignKey::create() + .name("FK_lineitem_cake") + .from(Alias::new("lineitem"), Alias::new("cake_id")) + .to(Alias::new("cake"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("FK_lineitem_order") + .from(Alias::new("lineitem"), Alias::new("order_id")) + .to(Alias::new("order"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned() +} + +fn create_cakes_bakers_table() -> TableCreateStatement { + Table::create() + .table(Alias::new("cakes_bakers")) + .col(ColumnDef::new(Alias::new("cake_id")).integer().not_null()) + .col(ColumnDef::new(Alias::new("baker_id")).integer().not_null()) + .primary_key( + Index::create() + .name("cakes_bakers_pkey") + .col(Alias::new("cake_id")) + .col(Alias::new("baker_id")), + ) + .to_owned() +} + +fn create_cake_table() -> TableCreateStatement { + Table::create() + .table(Alias::new("cake")) + .col( + ColumnDef::new(Alias::new("id")) + .integer() + .not_null() + .auto_increment(), + ) + .col(ColumnDef::new(Alias::new("name")).string()) + .col(ColumnDef::new(Alias::new("price")).decimal_len(19, 4)) + .col(ColumnDef::new(Alias::new("bakery_id")).integer().not_null()) + .col(ColumnDef::new(Alias::new("gluten_free")).boolean()) + .col(ColumnDef::new(Alias::new("serial")).uuid()) + .primary_key( + Index::create() + .primary() + .name("cake_pkey") + .col(Alias::new("id")), + ) + .foreign_key( + ForeignKey::create() + .name("FK_cake_bakery") + .from(Alias::new("cake"), Alias::new("bakery_id")) + .to(Alias::new("bakery"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned() +} diff --git a/tests/writer/Cargo.toml b/tests/writer/Cargo.toml deleted file mode 100644 index 8fa5815e..00000000 --- a/tests/writer/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "sea-schema-writer-test" -version = "0.1.0" -edition = "2018" -publish = false - -[dependencies] -async-std = { version = "1.8", features = [ "attributes" ] } -sea-schema = { path = "../../", default-features = false, features = [ "sqlx-mysql", "runtime-async-std-native-tls", "discovery", "writer" ] } -sea-query = { version = "^0.12" } -serde_json = { version = "^1" } -sqlx = { version = "^0" } \ No newline at end of file diff --git a/tests/writer/mysql/Cargo.toml b/tests/writer/mysql/Cargo.toml new file mode 100644 index 00000000..0164c066 --- /dev/null +++ b/tests/writer/mysql/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sea-schema-writer-test-mysql" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +async-std = { version = "1.8", features = [ "attributes" ] } +sea-schema = { path = "../../../", default-features = false, features = [ "sqlx-mysql", "runtime-async-std-native-tls", "discovery", "writer" ] } +serde_json = { version = "^1" } +sqlx = { version = "^0" } \ No newline at end of file diff --git a/tests/writer/Readme.md b/tests/writer/mysql/Readme.md similarity index 100% rename from tests/writer/Readme.md rename to tests/writer/mysql/Readme.md diff --git a/tests/writer/src/main.rs b/tests/writer/mysql/src/main.rs similarity index 91% rename from tests/writer/src/main.rs rename to tests/writer/mysql/src/main.rs index 1735c729..aa90bbcf 100644 --- a/tests/writer/src/main.rs +++ b/tests/writer/mysql/src/main.rs @@ -1,5 +1,5 @@ -use sea_query::MysqlQueryBuilder; use sea_schema::mysql::discovery::SchemaDiscovery; +use sea_schema::sea_query::MysqlQueryBuilder; use sqlx::MySqlPool; #[async_std::main] diff --git a/tests/writer/postgres/Cargo.toml b/tests/writer/postgres/Cargo.toml new file mode 100644 index 00000000..a799aa5d --- /dev/null +++ b/tests/writer/postgres/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sea-schema-writer-test-postgres" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +async-std = { version = "1.8", features = [ "attributes" ] } +sea-schema = { path = "../../../", default-features = false, features = [ "sqlx-postgres", "runtime-async-std-native-tls", "discovery", "writer", "debug-print" ] } +serde_json = { version = "^1" } +sqlx = { version = "^0" } \ No newline at end of file diff --git a/tests/writer/postgres/Readme.md b/tests/writer/postgres/Readme.md new file mode 100644 index 00000000..6946a346 --- /dev/null +++ b/tests/writer/postgres/Readme.md @@ -0,0 +1,5 @@ +# Run + +```sh +cargo run > schema.sql +``` \ No newline at end of file diff --git a/tests/writer/postgres/src/main.rs b/tests/writer/postgres/src/main.rs new file mode 100644 index 00000000..d1472a34 --- /dev/null +++ b/tests/writer/postgres/src/main.rs @@ -0,0 +1,19 @@ +use sea_schema::postgres::discovery::SchemaDiscovery; +use sea_schema::sea_query::PostgresQueryBuilder; +use sqlx::{Executor, PgPool, Pool, Postgres}; + +#[async_std::main] +async fn main() { + let connection = PgPool::connect("postgres://sea:sea@localhost/sakila") + .await + .unwrap(); + + let schema_discovery = SchemaDiscovery::new(connection, "public"); + + let schema = schema_discovery.discover().await; + + for table in schema.tables.iter() { + println!("{};", table.write().to_string(PostgresQueryBuilder)); + println!(); + } +}